Compare commits

...

14 commits

12 changed files with 217 additions and 24 deletions

View file

@ -1,3 +1,7 @@
0.6.0 2024-09-03: File management with the tree strategy
File backups
Extract authorization command to top level
ask and tildify utility functions
0.5.0 2024-07-18: "Decide in editor" package conflict resolution strategy
Drop pipefail shell option for dash compatibility
Add numerical debug levels to the log utility function

View file

@ -2,7 +2,7 @@
tori is a tool to track your personal systems' configurations and replicate them.
If you'd like a more detailed description of what it is, its purpose, origins and goals, see the [announcement blog post](https://blog.jutty.dev/posts/introducing-tori.html).
If you'd like a more detailed description of what it is, its purpose, origins and goals, see the [announcement blog post](https://blog.jutty.dev/posts/introducing-tori/).
Refer to the [project website](https://tori.jutty.dev) for updates and access to [documentation](https://tori.jutty.dev/docs).

View file

@ -6,4 +6,5 @@ check() {
log debug "collected bkp files:\n$bkp_files"
scan_packages
merge_files "$base_files"
}

View file

@ -15,7 +15,7 @@ scan_directory() {
done
fi
echo "$files"
printf "%b" "$files"
}
scan_packages() {

40
src/file/backup.sh Normal file
View file

@ -0,0 +1,40 @@
# takes a list of newline-separated absolute paths
# backs each path up, creating canonical or ephemeral copies as needed
backup_paths() {
local paths="$1"
local canonical_path=
local ephemeral_path=
for path in $paths; do
canonical_path="$BACKUP_ROOT/canonical$path"
timestamp="$(date +'%Y-%m-%dT%H-%M-%S')"
ephemeral_path="$BACKUP_ROOT/ephemeral${path}_$timestamp"
if [ -f "$canonical_path" ]; then
log debug "[backup] Creating ephemeral copy for $path"
mkdir -p "$(dirname "$ephemeral_path")"
if [ -f "$ephemeral_path" ]; then
log debug "[backup] Overwriting ephemeral copy for $path"
if [ -r "$path" ]; then
cp -f "$path" "$ephemeral_path"
else
$AUTHORIZE_COMMAND cp -f "$path" "$ephemeral_path"
fi
else
if [ -r "$path" ]; then
cp "$path" "$ephemeral_path"
else
$AUTHORIZE_COMMAND cp "$path" "$ephemeral_path"
fi
fi
else
log debug "[backup] Creating canonical copy for $path"
mkdir -p "$(dirname "$canonical_path")"
if [ -r "$path" ]; then
cp "$path" "$canonical_path"
else
$AUTHORIZE_COMMAND cp "$path" "$canonical_path"
fi
fi
done
}

85
src/file/file_merge.sh Normal file
View file

@ -0,0 +1,85 @@
merge_files() {
local base_files="$1"
local strategy="${2:-tree}"
if [ "$strategy" == tree ]; then
log info "[merge_files] Merging with $strategy strategy"
if ! file_scan_tree "$base_files"; then
if ! file_merge_tree "$base_files"; then
merge_files "$base_files"
fi
fi
fi
}
file_scan_tree() {
local base_files="$1"
for file in $base_files; do
local absolute_path="$(echo "$file" | sed 's/^base//')"
local config_path="$CONFIG_ROOT/$file"
if ! diff "$absolute_path" "$config_path" > /dev/null 2>&1; then
return 1
fi
done
return 0
}
file_merge_tree() {
local base_files="$1"
local overwrite_choice=
for file in $base_files; do
log debug "[merge_tree] Processing $file"
local absolute_path="$(echo "$file" | sed 's/^base//')"
log debug "[merge_tree] Absolute path: $absolute_path"
local config_path="$CONFIG_ROOT/$file"
log debug "[merge_tree] Config path: $config_path"
if diff "$absolute_path" "$config_path" > /dev/null 2>&1; then
log debug "[merge_tree] Files match"
else
log debug "[merge_tree] Files differ"
local prompt_verb="Differs"
local prompt_options="Overwrite system,Overwrite configuration,Show difference"
if ! [ -f "$absolute_path" ]; then
local prompt_verb="In configuration only"
local prompt_options="Copy to system"
fi
overwrite_choice="$(ask "$prompt_verb: $(tildify "$absolute_path")" "$prompt_options")"
log debug "[merge_tree] Overwrite choice: $overwrite_choice"
if [ "$overwrite_choice" -eq 0 ]; then
return 0
elif [ "$overwrite_choice" -eq 1 ]; then
backup_paths "$absolute_path"
if [ -r "$config_path" ] && [ -w "$absolute_path" ]; then
cp -vi "$config_path" "$absolute_path"
else
$AUTHORIZE_COMMAND cp -vi "$config_path" "$absolute_path"
fi
return 1
elif [ "$overwrite_choice" -eq 2 ]; then
backup_paths "$config_path"
if [ -r "$absolute_path" ] && [ -w "$config_path" ]; then
cp -vi "$absolute_path" "$config_path"
else
$AUTHORIZE_COMMAND cp -vi "$absolute_path" "$config_path"
fi
return 1
elif [ "$overwrite_choice" -eq 3 ]; then
echo "< $(tildify "$absolute_path") | $(echo "$config_path" | sed "s*$CONFIG_ROOT/**") >"
if [ -r "$absolute_path" ] && [ -r "$config_path" ]; then
diff "$absolute_path" "$config_path"
else
$AUTHORIZE_COMMAND diff "$absolute_path" "$config_path"
fi
return 1
else
log user 'Invalid choice'
return 1
fi
fi
done
}

View file

@ -9,3 +9,6 @@
. "$TORI_ROOT/src/package/validate_input_packages.sh"
. "$TORI_ROOT/src/package/package_conflict_input_parser.sh"
. "$TORI_ROOT/src/package/update_package_cache.sh"
. "$TORI_ROOT/src/file/file_merge.sh"
. "$TORI_ROOT/src/file/backup.sh"

View file

@ -1,8 +1,8 @@
package_conflict_input_parser() {
local packages="$1"
local conflict_type="$2"
local input="$TMP_DIR/package_conflict_input"
local input_choices="$TMP_DIR/package_conflict_input_choices"
local input="$TMP_ROOT/package_conflict_input"
local input_choices="$TMP_ROOT/package_conflict_input_choices"
local choices=
local packages_to_install=
local packages_to_uninstall=

View file

@ -3,18 +3,18 @@ resolve_packages() {
local input_packages=
# shellcheck disable=SC2154
( echo "$system_packages" > "$TMP_DIR/system_packages"
echo "$user_packages" > "$TMP_DIR/user_packages" )
( echo "$system_packages" > "$TMP_ROOT/system_packages"
echo "$user_packages" > "$TMP_ROOT/user_packages" )
local packages_not_on_configuration="$(grep -v -x -f \
"$TMP_DIR/user_packages" "$TMP_DIR/system_packages" | xargs)"
"$TMP_ROOT/user_packages" "$TMP_ROOT/system_packages" | xargs)"
if [ -n "$packages_not_on_configuration" ]; then
not_on_configuration_dialog "$packages_not_on_configuration"
fi
local packages_not_installed=$(grep -v -x -f \
"$TMP_DIR/system_packages" "$TMP_DIR/user_packages" | xargs)
"$TMP_ROOT/system_packages" "$TMP_ROOT/user_packages" | xargs)
if [ -n "$packages_not_installed" ]; then
not_installed_dialog "$packages_not_installed"

View file

@ -2,7 +2,6 @@ package_manager() {
local command="$1"
local manager
local authorizer="sudo"
local args__install
local args__uninstall
local args__get_manually_installed
@ -25,11 +24,11 @@ package_manager() {
if [ "$command" = 'get_manually_installed' ]; then
eval $manager "$args__get_manually_installed"
elif [ "$command" = 'install' ]; then
$authorizer $manager $args__install $args__user_args
$AUTHORIZE_COMMAND $manager $args__install $args__user_args
elif [ "$command" = 'uninstall' ]; then
$authorizer $manager $args__uninstall $args__user_args
$AUTHORIZE_COMMAND $manager $args__uninstall $args__user_args
elif [ "$command" = 'update' ]; then
$authorizer $manager $args__update
$AUTHORIZE_COMMAND $manager $args__update
elif [ "$command" = 'get_available' ]; then
eval $manager "$args__get_available"
else

View file

@ -5,11 +5,11 @@ log() {
local message="$2"
print_user_message() {
echo "[tori] $(date "+%H:%M:%S"): $1" 1>&2
printf "%b\n" "[tori] $(date "+%H:%M:%S"): $1" 1>&2
}
print_debug_message() {
echo "$(date "+%H:%M:%N") $1" 1>&2
printf "%b\n" "$(date "+%H:%M:%N") $1" 1>&2
}
if [ -z "$DEBUG" ]; then
@ -43,6 +43,56 @@ log() {
fi
}
confirm() {
local question="$1"
local answer=
read -rp "$question [y/N] " answer
if [ "$answer" == y ] || [ "$answer" == Y ]; then
return 0;
else
return 1;
fi
}
ask() {
local question="$1"
local options="$2"
local answer=
local options_count=0
local dialog_options=
local IFS=,
for option in $options; do
_=$((options_count+=1))
dialog_options="$dialog_options\n [$options_count] $option"
done;
IFS=
dialog_options="$dialog_options\n [0] Exit"
printf "%s" "$question" >&2
printf "%b" "$dialog_options" >&2
printf "\n%s" "Choose an option number: " >&2
read -r read_answer
answer="$(echo "$read_answer" | xargs)"
if [ -z "$answer" ]; then
log info "[ask] Invalid choice"
echo -1
return 1
elif [ "$answer" -ge 0 ] 2> /dev/null && [ "$answer" -le $options_count ]; then
echo "$answer"
else
log info "[ask] Invalid choice"
echo -1
return 1
fi
}
tildify() {
echo "$1" | sed "s*$HOME*~*"
}
set_opts() {
local target="$1"
local sign=
@ -74,12 +124,22 @@ set_opts() {
}
prepare_directories() {
if ! [ -d "$TMP_DIR" ]; then
mkdir "$TMP_DIR"
if ! [ -d "$TMP_ROOT" ]; then
mkdir "$TMP_ROOT"
fi
if ! [ -d "$CACHE_DIR" ]; then
mkdir -p "$CACHE_DIR"
if ! [ -d "$CACHE_ROOT" ]; then
mkdir -p "$CACHE_ROOT"
fi
if ! [ -d "$BACKUP_ROOT" ]; then
mkdir -p "$BACKUP_ROOT"
if ! [ -d "$BACKUP_ROOT/canonical" ]; then
mkdir "$BACKUP_ROOT/canonical"
fi
if ! [ -d "$BACKUP_ROOT/ephemeral" ]; then
mkdir "$BACKUP_ROOT/ephemeral"
fi
fi
if ! [ -d "$CONFIG_ROOT" ]; then

13
tori
View file

@ -2,11 +2,12 @@
main() {
# paths
VERSION="0.5.0 2024-07-18"
VERSION="0.6.0 2024-09-03"
TORI_ROOT="$HOME/.local/share/tori"
CONFIG_ROOT="$HOME/.config/tori"
TMP_DIR="/tmp/tori"
CACHE_DIR="$HOME/.cache/tori"
BACKUP_ROOT="$HOME/.local/state/tori/backup"
TMP_ROOT="/tmp/tori"
CACHE_ROOT="$HOME/.cache/tori"
# os-independent state
@ -18,14 +19,14 @@ main() {
parameter="$2"
# import source
check_core_paths
. "$TORI_ROOT/src/index.sh"
set_opts on
## os-dependent state
set_opts on
OS="$(get_operating_system)"
PACKAGE_CACHE="$CACHE_DIR/${OS}_packages.cache"
PACKAGE_CACHE="$CACHE_ROOT/${OS}_packages.cache"
AUTHORIZE_COMMAND="sudo"
base_files=
bkp_files=