Skip to main content

taarr

Reference


Table of Contents


## Why

I built my own Lua-based configuration management software. A little more than thousand commits in, I realized the oneshot nature of shell scripts more convenient.

CFEngine, Puppet, Chef and others did not offer advantages over a combination of rerun/bashing + drist. Cons mostly outweight the pros. Slow and complicated are the common complaints. If you’re just templating shell commands then plain shell scripts should suffice.

According to the Lindy effect; shell scripts, openssh and tar will outlive these mentioned CM software.

Huge shell scripts can be unwieldy. taarr will help you manage your scripts.


## Multicall modes

You can use a symlink to activate the modes.

Example for sudo mode:

ln -s rr rrs

### Console

Verbose mode.

When called as rrv or a console is detected it runs in verbose mode.

$ rrv make:yyy
29 Jun 24 11:55 +0800 rr 2.2.0 “Craving Detonator”
29 Jun 24 11:55 +0800 Running make:yyy via local…
29 Jun 24 11:55 +0800 Running yyy…
 local │ -rw-r--r-- 1 root root 2439 Apr  6 10:39 /etc/passwd
 local │ stdout last
29 Jun 24 11:55 +0800 Failure running script!
 local ┌─ stderr
 local │
 local │ stderr
 local │ next
 local │ last
 local │
 local └─
 local ┌─ debug
 local │
 local │ exit status 255
 local │
 local └─
29 Jun 24 11:55 +0800 Total run time: 1s. Something went wrong.

In this mode it also logs to the file LOG in the current working directory.

NOTE: logs STDOUT and STDERR as a debug entry when no errors are detected

TIP: Example command line to extract STDOUT from the log file

grep 3EB340F6 LOG | grep stdout | cut -f28 -d\" | base64 -d

### Silent

When called as rr and a console is not detected it only shows errors as structured JSON.

{"level":"error","stdout":"ss\n","stderr":"ee\n","time":"2021-07-20T20:16:04+08:00","message":"Output"}
{"level":"error","elapsed":"1.798478ms","time":"2021-07-20T20:16:04+08:00","message":"Something went wrong."}

### Dump

When called as rrd, dumps the generated script. This is mainly for debugging and allows running scripts generated from a managed namespace without the rr executable.

### Sudo

When called as rrs, asks for a sudo password for the remote user. Similar to the ansible options --become -K. Also turns on console mode since a TTY is expected anyway.

### Log

When called as rrl, reads the JSON file LOG then shows a log of executions for audit trail.


## Audit trail

A log is generated for each script invocation. The JSON file LOG contains an audit trail of all script invocations.

The environment variable LOG is logged as the log field. Without the variable the arguments are used for the field. If no variable or arguments passed then it is logged as UNDEFINED.

This can be useful for audit trails or for changelogs. Before running a script you should set the LOG environment variable.

LOG='Upgrade a thing for bugfix` rr program:upgrade

## Readmes

### Top-level

When invoked without any arguments, rr prints the README in the current working directory.

### namespace/script

Any case insensitive file named readme* in the namespace and script directories can be shown by invoking rr in the following ways:

Prints namespace/readme*

rr namespace
rr namespace/

Prints namespace/script/readme*

rr namespace/script
rr namespace/script/

## Environment variables

Any environment variable prefixed with rr__ are passed to the script.

For example, the rr__USERNAME=test environment variable is passed to the script as USERNAME=test

$ cat tmp/env/script
echo $USERNAME

$ env rr__USERNAME=test rr tmp:env
Thu, 22 Jul 2021 18:13:32 +0800 rr 0.10.0 "Kilowatt Triceps"
Thu, 22 Jul 2021 18:13:32 +0800 Running tmp:env via local...
Thu, 22 Jul 2021 18:13:32 +0800 Running env...
Thu, 22 Jul 2021 18:13:32 +0800 Done. Output:
 local ┌─ stdout
 local │
 local │ test
 local │ 
 local └─
Thu, 22 Jul 2021 18:13:32 +0800 Total run time: 101.648007ms. All OK.

## Idempotence

The default behavior of scripts is to indicate two results: no errors(ok) and failure(failed). To emulate idempotence your script can print the following string to STDERR in it’s own line and exit 0:

__REPAIRED__

When writing your script you have to check if an operation is needed to apply a change or not. Such a precondition can be written like this:

if test -d /some_dir
then
  exit 0
else
  {
    mkdir /some_dir
  } >/dev/null 2>&1 || exit 1
  >&2 printf "__REPAIRED__\\n"
  exit 0
fi

In the JSON log and report mode the result value will say repaired.

No changes, script exited –> ok
Script succesfully executed changes –> repaired
Repair needed but unable to apply changes –> failed

To borrow terminology from *CFEngine*. There are no changes when the **desired state** is already the expected outcome.

Any state –> Desired state
Desired state –> Desired state


## Hierarchy

TOPLEVEL                            <--- Directory of namespaces or a project
├── .files                          <--- synced to any host
├── .files-avocado                  <--- synced to the avocado host
├── .lib                            <--- files sourced by all scripts
├── VARS                            <--- sourced by all scripts, ideal for frequently changed variables
├── LOG                             <--- Log generated (JSON); can be printed with `rrl`
├── HOSTS                           <--- custom SSH config
└── namespace
    ├── .files                      <--- synced to any host when namespace:* is called
    ├── .files-avocado              <--- synced to the avocado host when namespace:* is called
    ├── .lib                        <--- sourced along with namespace:* scripts
    ├── readme                      <--- Documentation for namespace
    └── script directory
        ├── .files                  <--- synced to any host when namespace:script is called
        ├── .files-avocado          <--- synced to the avocado host when namespace:script is called
        ├── .lib                    <--- sourced along with namespace:script scripts
        ├── readme                  <--- Documentation for script
        ├── script                  <--- the main shell script
        ├── script.pre              <--- run before the main script
        ├── script.post             <--- run after the main script
        └── shell                   <--- shell interpreter for the script

## VARS

The VARS file is sourced by all scripts in all namespaces. It is ideal for variables that frequently change.


## Shell

The shell used to run the script can be set within the file namespace/script/shell. Example:

$ cat namespace/script/shell
/usr/bin/fish

## Notes

Tested on Linux and macOS.

Remote host only requires OpenSSH server and tar(1).

Scripts should reference $@ if it wants to use arguments passed.


## Decision record

  • Environment variables are not logged. They may contain secrets.