Gradual Automation With Do Nothing Scripts
Oct 2025 - Alex Alejandre

For some time upfront, automation saves us time in the future, but the break-even point’s rarely clear. Some prfer “runbook script” Do-nothing scripts help us hack the worthiness curve and focus on the low hanging fruit: Print the next instruction, execute manuallyor automate when friction repeats. Or reversed: automate some steps while “shelling out to a person” to do the others manually.

This piecemeal approach helps stay productive and work around blockers too. Instead of being stumped on something for a day, we can leave it unimplemented while still delivering value elsewhere in the process!

This workflow fits an entire business well too. Every business, even thousands of years ago, has processes. Today, managers and business analysts document and optimize existing processes or create new roles and tasks from scratch. Quick gains can be had from creating do-nothing scripts documenting the entire business, which get automated according to need and ability. Going further, logging our scripts gives us metrics on what to automate first. Updating the script overtime is also easier to propagate than teaching people up to date commands/processes.

For a minimal example, backing up our password store and required gpg keys requires entering (copypasting) in the right order, reentering a password multiple times etc. which is tedious and error-prone. Roughly, we have these instructions:

To Backup

First, pick an ID for encryption:

gpg --list-secret-keys

Second, export all public and private keys:

gpg --export-secret-keys --armor > private-keys.asc
gpg --export --armor > public-keys.asc
gpg --export-ownertrust > trust-db.txt

Third, copy and encrypt password store:

tar -cz ~/.password-store/ | gpg --encrypt --recipient YOUR_KEY_ID > pass-backup.tar.gz.gpg

N.b. -c makes a new archive and -z compresses the archive with gzip. While the password store’s already encrypted, this protects metadata like filenames and directory structure.

To Restore

First, restore keys:

gpg --import private-keys.asc
gpg --import public-keys.asc
gpg --import-ownertrust trust-db.txt

Second, restore passord store:

tar -xzf password-store-backup.tar.gz -C ~/

Third, clean up

Let’s Automate

With our instructions collected, we can put each step into its own self-documenting function. Here’s the backup written in Janet:

(defn pick-encryption-key []
	(print "gpg --list-secret-keys"))

(defn export-keys []
	(print `gpg --export-secret-keys --armor > private-keys.asc
		gpg --export --armor > public-keys.asc
		gpg --export-ownertrust > trust-db.txt`))

(defn copy-and-encrypt-password-store []
	(print `tar -cz ~/.password-store/
        | gpg --encrypt --recipient YOUR_KEY_ID
        > pass-backup.tar.gz.gpg`))
	
(defn execute-sequence
	"Execute array of funcs in order, pausing after each."
	[functions &opt pause-msg]
	(default pause-msg "Press enter to continue...")
	(each function functions
		(function)
		(print pause-msg)
		(getline)))

(execute-sequence [pick-encryption-key
                   export-keys
                   copy-and-encrypt-password-store])

While Janet has a lovely shell DSL called janet-sh, as a novice Janet programmer, I was blocked by uncertainty around how password requests are handled (with system popups, keyring requests etc. on my system). Typically, I would avoid the task until a foggy later (which might not come) where I understand the solution or go down a rabbithole trying to understand it before starting the project. As is, the program outputs commands to copypaste and run, but with the overall architecture out of the way, it’s easy to extend:

(import sh)

(defn pick-encryption-key []
	(sh/$ "gpg" "--list-secret-keys"))

Might as well experiment how Janet and Linux Mint’s keyring work:

(sh/$ sudo echo "ok")
(sh/$ sudo echo "ok")

Great, I only needed to enter it once! That clears the way for the final solution (summarized with these expressions):

(with [out (file/open "private-keys.asc" :w)]
    (sh/$ "gpg" "--export-secret-keys" "--armor" > ,out))
        
(with [out (file/open "pass-backup.tar.gz.gpg" :w)]
    (sh/$ tar -czf - ,(string (os/getenv "HOME") "/.password-store")
        | gpg --encrypt --recipient ,key-id > ,out))

A few notes, in janet-sh:

  • > requires a file object (and can’t create one by itself)
  • ~ does not resolve
  • , is needed to escape implicit quotes
  • you can give quoted or unquoted arguments

Altogether:

(import sh) 

(def backup-location "key-backup.tar")

(defn pick-encryption-key []
	(sh/$ "gpg" "--list-secret-keys"))

(defn export-keys []
  (with [out (file/open "private-keys.asc" :w)]
        (sh/$ "gpg" "--export-secret-keys" "--armor" > ,out))
  (with [out (file/open "public-keys.asc" :w)]
        (sh/$ "gpg" "--export" "--armor" > ,out))
  (with [out (file/open "trust-db.txt" :w)]
        (sh/$ "gpg" "--export-ownertrust" > ,out)))

(defn copy-and-encrypt-password-store
  []
  (print "paste your key id")
  (def key-id (string/trim (getline)))
  (with [out (file/open "pass-backup.tar.gz.gpg" :w)]
        (sh/$ tar -czf - ,(string (os/getenv "HOME") "/.password-store") | gpg --encrypt --recipient ,key-id > ,out)))

(defn zip []
  (def files ["private-keys.asc" "public-keys.asc" "trust-db.txt" "pass-backup.tar.gz.gpg"])
  (sh/$ tar -cvf ,backup-location ;files)
  (sh/$ rm ;files)
  (print "Saved " backup-location " in your cwd:" (os/cwd)))

(defn main [_ & args]
  (map |($) [export-keys
             pick-encryption-key
             copy-and-encrypt-password-store
             zip]))

This will only ask for a password and ask you to copypaste a key ID. The full code for backing up and restoring is here.