Implement package list comparison, major refactor

This commit is contained in:
Juno Takano 2024-06-30 01:14:49 -03:00
parent 51e6e87eaa
commit 202a0a8b0a
10 changed files with 238 additions and 83 deletions

76
check
View file

@ -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

View file

@ -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.

View file

@ -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 <prompt> <variable_with_user_input>` 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`

View file

@ -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:

View file

@ -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).

24
docs/utilities.md Normal file
View file

@ -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 <level> <message>`
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

34
src/configuration.sh Normal file
View file

@ -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
}

75
src/package.sh Normal file
View file

@ -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
}

51
src/tori Executable file
View file

@ -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

26
src/utilities.sh Normal file
View file

@ -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
}