Development - wizardofhoms/risks GitHub Wiki

In order to ease development and keep the (rather large) codebase structured, the bashly CLI framework is used.

Installing bashly

Bashly requires ruby, and the bashly gem. To install all of them in the TemplateVM:

sudo apt install ruby
gem install bashly
sudo poweroff

Alternatively, if you want to install the bashly gem in the development AppVM:

gem install --user-install bashly

Code base

Structure

The codebase is structured according to bashly conventions and usage.

  • settings.yml holds the general settings used by bashly when searching for YAML declarations, shell code, and for generating the final script.
  • Makefile is used to adjust for a few things that bashly doesn't handle, and to bundle the process into one command/workflow.
  • risks is the final generated CLI script.
  • src/ contains all the code needed by bashly to generate our CLI script.

In src/, the code is structured as the following:

  • <command_files>... - All files corresponding to CLI commands. Note that they are named after their CLI command position (eg. risks hush format command code is contained in hush_format_command.sh)
  • lib/ - Contains the code used by CLI commands in src/, split into files depending on their role.
  • lib/validations/ - Contains all functions used to validate CLI args/flags when parsing command. after their CLI command position (eg. risks hush format command code is contained in hush_format_command.sh)

Additionally (still in src/), some files have a special role:

  • bashly.yml is the root YAML declaration scanned by bashly. It comprises the list of required binaries, a list of other YAML files in which commands are declared, the version tag (generated in the Makefile), and some hidden commands.
  • hush_commands.yml is an example of YAML file in which commands are declared, here the hush subcommand and its own subcommands, along with their arguments, flags, help string, example usage, etc.
  • header.sh contains the shebang header to be included at the top of our final CLI script (here, a zsh shebang)
  • initialize.sh contains shell code that is to be executed before any command is actually run. In this script, we initialize global variables (sometimes parsing them from the risks configuration file), and perform other pre-run checks that are not handled by the rest of our code.

Additional notes on the code

  • Although the resulting script is a ZSH script (with the corresponding shebang header), all files in the src/ directory must have the .sh extension for bashly to scan and recognize them.
  • Note that although each <subject>_command.sh file does not declare a corresponding function, bashly will actually include the code in the file into a function named risks_subject_command, so if you happen to develop with checkers like shellcheck, and that warnings are raised about not being allowed to declare local variables, you can safely ignore/mute them. This also applies to any error/warning caused by you using zsh syntax.

Conventions on code structure

Since bashly offers some facilities for querying our command args and flags, we try to split the functionality like this:

  • Command files (eg. hush_format_command.sh) are charged of initializing, setting and checking the command args/flags.
  • The command file then calls on specialized functions located in src/lib/, passing the arguments the latter require. Accordingly, most of the functions in the lib/ directory do (and should) document on the parameters they require.
  • As a result, some command files are only made of a single line (a lib/ function call ).

Development workflow (adding commands)

Declaring the command interface with YAML

We first use a YAML file to declare some commands. Here is the file declaring the hush command and its subcommands, arguments and flags, located in src/:

# Hush partition commands #
name: hush 
help: 'Format or manage the hush partition mount points and read/write permissions'

commands:
  - name: mount
    help: 'Mount the hush drive' 
  - name: rw 
    help: 'Set read-write permissions on the hush partition'
  - name: ro 
    help: 'Set read-only permissions on the hush partition'
  - name: umount 
    help: 'Unmount the hush drive'

  - name: format 
    help: 'Format a drive for storing identity secrets (GPG/coffin/age keys)'
    args:
      - name: device
        required: true
        help: "Device file of the sdcard drive (must be a whole drive without a partition number, eg. /dev/sda)"
        validate: file_exists
    flags:
      - &verbose
        long: --verbose
        short: -v
        help: "Show verbose logging"
      - long: --size-percent
        short: -P
        arg: percent
        default: "90"
        required: false
        help: "Size of encrypted partition, as a percentage of the entire drive"
        conflicts: [ --size-absolute ]
      - long: --size-absolute
        short: -A
        arg: size 
        required: false
        help: "Size of encrypted partition, in absolute terms, WITH unit (eg. 100G, 20M, etc)"
        conflicts: [ --size-percent ]
        validate: partition_size
    examples:
      - "hush format --size-absolute 200M /dev/xvdi   # The encrypted part will be 200M in size"
      - "hush format --size-percent 70 /dev/xvdi      # The encrypted part takes 70% of the drive"

Then, we link this file by declaring it into the root bashly.yml file:

name: risks 
help: Identity creation and management tool
version: 0.1.0

commands:
  # Drives/partitions
- import: src/hush_commands.yml
- import: src/backup_commands.yml
  # Identity/stores management commands
- import: src/create_commands.yml
- import: src/open_command.yml
...

We then generate the corresponding sh command files:

bashly generate

Implementing the command

We now have the following files in our directory:

src/
    lib/                            # Library code, called by commands
    bashly.yml                      # Root CLI YAML declaration
    hush_commands.yml               # Hush commands interface declaration
    hush_format_command.sh
    hush_mount_command.sh
    hush_ro_command.sh
    hush_rw_command.sh
    hush_umount_command.sh
risks                               # The risks CLI generated with all .sh files

For example, the implementation of the hush mount command looks like:

if ! is_named_partition_mapper_present "${SDCARD_ENC_PART_MAPPER}" ; then
    _failure "Device mapper /dev/${SDCARD_ENC_PART_MAPPER} not found.\n\
        Be sure you have attached your hush partition.       "
fi

if is_hush_mounted ; then
    _message "Sdcard already mounted"
    play_sound
    return 0
fi

if ! is_luks_mapper_present "${SDCARD_ENC_PART_MAPPER}" ; then
    # decrypts the "hush partition": it will ask for passphrase
    if ! sudo cryptsetup open --type luks "${SDCARD_ENC_PART}" "${SDCARD_ENC_PART_MAPPER}" ; then
        _failure "The hush partition ${SDCARD_ENC_PART} can not be decrypted"
    fi
fi

# creates the "hush partition" mount point if it doesn't exist
if [ ! -d "${HUSH_DIR}" ]; then
    mkdir -p "${HUSH_DIR}" &> /dev/null
fi

# mounts the "hush partition" in read-only mode by default
if ! sudo mount -o ro "/dev/mapper/${SDCARD_ENC_PART_MAPPER}" "${HUSH_DIR}" ; then
    _failure "/dev/mapper/${SDCARD_ENC_PART_MAPPER} can not be mounted on ${HUSH_DIR}"
fi

play_sound "plugged"

echo
_success "SDCARD has been mounted read-only. To give write permissions, use:"
_success "risks hush rw"
echo

Note that most functions called in this file are stored in the src/lib directory.

Generating the final CLI

Then, we generate the CLI again with the command:

bashly generate

If you look into the risks binary, you will see that the code above is now encapsulated into the following function:

risks_hush_mount_command() {
    # Code goes here
}

Useful library code

There are a few files worth of interest for developers:

  • src/initialize.sh contains all the code that is evaluated before any command code is ran. In it are defined the global variables (GPGPASS, IDENTITY, and many more). Some functions are also ran in this file, to ensure a configuration file exists, load values from it, check hush udev rules are correctly linked, etc.
  • lib/logging.sh contains all the code for logging output to the console, with _message, _verbose, _warning, and _failure (which will exit the program when being called)
  • lib/run.sh contains a _run function taking an arbitrary number of arguments to be evaluated, conveniently splitting and storing stdout and stderr output, and returning the command's exit code. This function is very useful so as to mute most of the messages that you would not want to be printed when another function/command is calling it. This function goes in hand with _catch, which checks the exit code of the last command, and will trigger _failure if non-nil, also printing the stored stderr buffer.
  • src/lib/validations/ is a directory containing functions used as argument/flag validation functions. See this page to learn the naming and behavior conventions for these functions.

Additional resources

The Bashly documentation is very well made:

⚠️ **GitHub.com Fallback** ⚠️