Development - wizardofhoms/risks GitHub Wiki
In order to ease development and keep the (rather large) codebase structured, the bashly CLI framework is used.
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
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 inhush_format_command.sh
) -
lib/
- Contains the code used by CLI commands insrc/
, 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 inhush_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 theMakefile
), and some hidden commands. -
hush_commands.yml
is an example of YAML file in which commands are declared, here thehush
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, azsh
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 therisks
configuration file), and perform other pre-run checks that are not handled by the rest of our 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 namedrisks_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.
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 thelib/
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 ).
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
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.
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
}
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.
The Bashly documentation is very well made:
- The website documentation can be followed as a progressive tutorial.
- Various examples for each functionality can be found here.