diff --git a/check b/check deleted file mode 100755 index f4d0387..0000000 --- a/check +++ /dev/null @@ -1,76 +0,0 @@ -#! /usr/bin/env sh - -# state - -## user-configured settings -TORI_ROOT="$HOME/.config/tori" - -## global constants -OS="$(uname -s)" - -## global state -base_files= -bkp_files= -user_packages= -system_packages= - -# behavior - -## utility functions - -log() { - local level="$1" # unimplemented - local message="$2" - - if [ -n "$DEBUG" ] && [ $level = debug ]; then - printf "$(date "+%H:%M:%N") $message\n" - fi -} - -## configuration processing functions - -scan_directory() { - local target="$1" - local files= - local escaped_config_root="$(echo $TORI_ROOT | sed 's/\//\\\//g')" - - if [ -d "$target" ]; then - scan="$(find "$target" -type f)" - for line in $scan; do - line="$(echo $line | sed "s/$escaped_config_root\///")" - files="$line\n$files" - done - fi - - echo "$files" -} - -scan_packages() { - local package_manager - local args__get_manually_installed - local system_packages_eval - - if [ $OS = "FreeBSD" ]; then - package_manager="pkg" - args__get_manually_installed='query -e "%a = 0" "%n"' - fi - - system_packages=$(eval "$package_manager $args__get_manually_installed") - user_packages="$(cat $TORI_ROOT/packages | sort | uniq)" - - if [ "$system_packages" = "$user_packages" ]; then - log debug "packages match" - else - log debug "packages mismatch" - log debug "system: $system_packages" - log debug "user: $user_packages" - fi -} - -base_files="$(scan_directory "$TORI_ROOT/base")" -bkp_files="$(scan_directory "$TORI_ROOT/bkp")" - -log debug "collected base files:\n$base_files" -log debug "collected bkp files:\n$bkp_files" - -scan_packages diff --git a/docs/check.md b/docs/check.md index 793d96f..23e5f7d 100644 --- a/docs/check.md +++ b/docs/check.md @@ -1,7 +1,20 @@ -The `check` executable traverses the configuration directory to assemble a file list containing full paths for both the `base` and `bkp` directories. +The `check` option performs two tasks through the configuration processing functions available at `src/configuration.sh` -This is currently accomplished by resorting to `find`. While this allows for cleaner code, it relies on an external program with an interface that may be unpredictable. +1. Traverse the configuration directory to assemble a file list containing full paths for both the `base` and `bkp` directories +2. Compare the installed packages with the `packages` file at the root of the configuration directory -A better option might be using a POSIX-compliant wildcard such as `.[!.]* ..?* *` to match the files directly (e.g., in a for loop). +The first task is currently accomplished by resorting to `find`. While this allows for cleaner code, it relies on a utility with variable behavior across operating systems. Given the simplicity of the query, a better option might be using a POSIX-compliant wildcard such as `.[!.]* ..?* *` to match the files directly (e.g., in a for loop). Another option that may provide both readability and portability is repeating the match, once for hidden file and once for non-hidden files. -Another option that may provide both readability and portability is repeating the match, once for hidden file and once for non-hidden files. +The second task is accomplished by resorting to the package management functions available at `src/package.sh`. The `package_manager` function abstracts the actualy package manager provided by the underlying system and provides an OS-independent way to query the current manually installed packages. + +Through the parsed `packages` configuration file at the root of the configuration directory (`~/.config/tori/packages` by default), both package lists are sorted and deduplicated before they can be filtered by each other using `grep` inverted matching. + +This allows us to obtain both differences and display them to the user. If no resolution strategy has been configured, several options are displayed: + +1. Install/uninstall all +2. Enter packages to install/uninstall +3. Add all to/remove all from configuration +4. Enter packages to add to/remove from configuration +5. Decide in editor + +Not all of these options are implemented and some require significant more effort than others. The last option, in particular, requires defining and parsing a syntax that allows users to interface with the possible options. diff --git a/docs/portability.md b/docs/portability.md index a469d45..6eec863 100644 --- a/docs/portability.md +++ b/docs/portability.md @@ -8,10 +8,16 @@ Below is a list of assumptions made about what your system supports: - shell - `local` keyword + - `read` keyword with `read -r -p ` syntax + - `printf` + - `echo` -- executables +- utilities - `find` + - `grep` - `sed` + - `xargs` + - `uname` - `date` with nanoseconds as `%N` - While nanoseconds support in `date` is not in the POSIX 2017 standard, it is used only when `$DEBUG` is set in the environment and is available on the currently supported systems (FreeBSD, Void Linux) and on the next operating system with planned support (Debian). - `env` at `/usr/bin/env` diff --git a/docs/user-data.md b/docs/user-data.md index 1c14b24..7076bb1 100644 --- a/docs/user-data.md +++ b/docs/user-data.md @@ -1,8 +1,8 @@ -User data is initially configured to $HOME/.config/tori. At this stage, this allows concentrating all necessary data in a single place, however it is not ideal to have non-configuration file there. +User data is initially configured to $HOME/.config/tori. At this stage, this allows concentrating all necessary data in a single place, however it is not ideal to have non-configuration files, such as backups, all in the same location. When making these configurable, it would be interesting to move the default backup location to $HOME/.local. -The user data is organized in two main directories: `base` and `bkp`. +User data is organized in two main directories: `base` and `bkp`. - `base`: contains the files that will be matched against the current system's files - `bkp`: contains the original files prior to any intervention by `tori`. This directory is further split into two directories: diff --git a/docs/user-packages.md b/docs/user-packages.md index bc43eca..4ed7af5 100644 --- a/docs/user-packages.md +++ b/docs/user-packages.md @@ -6,3 +6,5 @@ In order to take advantage of all the features offered by `tori` for package man The `packages` file is located at the root of the configuration directory and contains a list of package names, one per line. Blank lines and lines beginning with `#` are ignored. When processing the package list, `tori` will compare the list of installed packages to the list in the configuration and ask the user for what action to take in order to conciliate them, unless a default action has been specified. + +For information on how the application determines differences between the configuration package list and the actually installed packages, see [`check`](check). diff --git a/docs/utilities.md b/docs/utilities.md new file mode 100644 index 0000000..3f5d11c --- /dev/null +++ b/docs/utilities.md @@ -0,0 +1,24 @@ +The utilities functions available at `src/utilities.sh` provide functionality that is either very simple in purpose or general to the whole application. + +The two utility functions presently implemented are: + +1. `log ` +2. `prepare_directories` + +## `log` + +This utilitiy takes a log level as its first argument and a message as its second argument. The log message must be wrapped in double quotes, otherwise only the first world will be considered part of the message and the rest will be discarded. + +The current log levels are: +- `debug`: Displays only when `DEBUG` is set in the environment. The value `DEBUG` is set to does not matter. To disable the log messages, unset `DEBUG`, for example, with `export DEBUG=` or 'unset DEBUG` +- `user`: Always displays, with `[tori]` at the very left followed by a second-precision timestamp, a colon and the message +- `fatal`: Always displays, exactly as `user` + +For now, all log messages are printed to `STDERR` so as not to shadow function return values. + +## `prepare_directories` + +`prepare_directories` runs at the very start of execution in order to verify that critical directories exist. These directories are: + +- `TMP_DIR`, default `/tmp/tori`: created if not found +- `CONFIG_ROOT`, default `~/.config/tori`: application exits with an error and exit code 1 if not found diff --git a/src/configuration.sh b/src/configuration.sh new file mode 100644 index 0000000..f28132a --- /dev/null +++ b/src/configuration.sh @@ -0,0 +1,34 @@ +# configuration processing functions + +scan_directory() { + local target="$1" + local files= + local escaped_config_root="$(echo $CONFIG_ROOT | sed 's/\//\\\//g')" + + if [ -d "$target" ]; then + scan="$(find "$target" -type f)" + for line in $scan; do + line="$(echo $line | sed "s/$escaped_config_root\///")" + files="$line\n$files" + done + fi + + echo "$files" +} + +scan_packages() { + + system_packages="$(get_system_packages)" + user_packages="$(get_user_packages)" + + if [ "$system_packages" = "$user_packages" ]; then + log debug "packages match" + else + log debug "packages mismatch" + log debug "system: $system_packages" + log debug "user: $user_packages" + + log user "System and configuration packages differ" + resolve_packages + fi +} diff --git a/src/package.sh b/src/package.sh new file mode 100644 index 0000000..6f6151c --- /dev/null +++ b/src/package.sh @@ -0,0 +1,75 @@ +# package management functions + +get_user_packages() { + cat $CONFIG_ROOT/packages | sort | uniq +} + +package_manager() { + local command="$1" + local manager + local args__get_manually_installed + local output + + if [ $OS = "FreeBSD" ]; then + manager="pkg" + args__get_manually_installed='query -e "%a = 0" "%n"' + fi + + if [ "$command" = 'get_manually_installed' ]; then + output=$(eval $manager $args__get_manually_installed) + log debug "package.package_manager returning: $output" + printf "$output" + fi +} + +get_system_packages() { + local packages=$(package_manager get_manually_installed) + log debug "package.get_system_packages returning: $packages" + printf "$packages" +} + +resolve_packages() { + local strategy= + + echo "$system_packages" > "$TMP_DIR/system_packages" + echo "$user_packages" > "$TMP_DIR/user_packages" + + local not_on_configuration="$(grep -v -x -f "$TMP_DIR/user_packages" "$TMP_DIR/system_packages" | xargs)" + local not_installed=$(grep -v -x -f "$TMP_DIR/system_packages" "$TMP_DIR/user_packages" | xargs) + + if [ -n "$not_on_configuration" ]; then + + printf "\nInstalled packages not on configuration: $not_on_configuration\n" + echo " [1] Uninstall all" + echo " [2] Enter packages to uninstall" + echo " [3] Add all to configuration" + echo " [4] Enter packages to add to configuration" + echo " [5] Decide on editor" + echo " [6] Cancel" + + read -r -p "Choose an option [1-6]: " strategy + log debug "Input: strategy = $strategy" + + if [ "$strategy" = 1 ]; then + : # TODO + fi + fi + + if [ -n "$not_installed" ]; then + + printf "\nPackages on configuration but not installed: $not_installed\n" + echo " [1] Install all" + echo " [2] Enter packages to install" + echo " [3] Remove all from configuration" + echo " [4] Enter packages to remove from configuration" + echo " [5] Decide on editor" + echo " [6] Cancel" + + read -r -p "Choose an option [1-6]: " strategy + log debug "Input: strategy = $strategy" + + if [ $strategy -eq 1 ]; then + : # TODO + fi + fi +} diff --git a/src/tori b/src/tori new file mode 100755 index 0000000..c9b2342 --- /dev/null +++ b/src/tori @@ -0,0 +1,51 @@ +#! /usr/bin/env sh + +VERSION="0.0.4 2024-06-30" +TORI_ROOT="$HOME/tori" +CONFIG_ROOT="$HOME/.config/tori" +TMP_DIR="/tmp/tori" + +. "$TORI_ROOT/src/package.sh" +. "$TORI_ROOT/src/utilities.sh" +. "$TORI_ROOT/src/configuration.sh" + +# state + +## user input +argument="$1" +parameter="$2" + +## global constants +OS="$(uname -s)" + +## global state +base_files= +bkp_files= +user_packages= +system_packages= + +# entry point + +prepare_directories + +if [ "$argument" = check ]; then + base_files="$(scan_directory "$CONFIG_ROOT/base")" + bkp_files="$(scan_directory "$CONFIG_ROOT/bkp")" + + log debug "collected base files:\n$base_files" + log debug "collected bkp files:\n$bkp_files" + + scan_packages +elif [ "$argument" = version ] || [ "$argument" = '-v' ] || [ "$argument" = '--version' ]; then + echo "$VERSION" +elif [ "$argument" = help ] || [ "$argument" = -h ] || [ "$argument" = --help ]; then + printf "\n\ttori: configuration managent and system replication tool\n" + printf "\n\tOptions:\n\n" + printf "\tcheck\t\tcompare configuration to system state\n" + printf "\n" + printf "\tversion\t\tprint current version with release date\n" + printf "\thelp\t\tshow this help text\n" + printf "\n\tSee 'man tori' or https://brew.bsd.cafe/jutty/tori for more\n\n" +else + echo "Use 'tori help' for usage instructions" +fi diff --git a/src/utilities.sh b/src/utilities.sh new file mode 100644 index 0000000..92dc194 --- /dev/null +++ b/src/utilities.sh @@ -0,0 +1,26 @@ +# utility functions + +log() { + local level="$1" + local message="$2" + + if [ $level = fatal ]; then + printf "[tori] $(date "+%H:%M:%S"): $message\n" 1>&2 + elif [ $level = user ]; then + printf "[tori] $(date "+%H:%M:%S"): $message\n" 1>&2 + elif [ -n "$DEBUG" ] && [ $level = debug ]; then + printf "$(date "+%H:%M:%N") $message\n" 1>&2 + fi +} + +prepare_directories() { + if ! [ -d "$TMP_DIR" ]; then + mkdir "$TMP_DIR" + fi + + if ! [ -d "$CONFIG_ROOT" ]; then + log fatal "Configuration root not found at $CONFIG_ROOT" + exit 1 + fi +} +