Compare commits
14 commits
915b0db08b
...
690bee973b
Author | SHA1 | Date | |
---|---|---|---|
690bee973b | |||
67de45f5ba | |||
e94e68540f | |||
de634f3749 | |||
59e040d597 | |||
4b6a956995 | |||
7b2f1494b9 | |||
005638bef4 | |||
a0fdbe0149 | |||
85151e1de6 | |||
1b9e519e05 | |||
e0e1920b0b | |||
91c4f6be4c | |||
21b5f8ff2d |
12 changed files with 217 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
|
|
|
@ -6,4 +6,5 @@ check() {
|
|||
log debug "collected bkp files:\n$bkp_files"
|
||||
|
||||
scan_packages
|
||||
merge_files "$base_files"
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ scan_directory() {
|
|||
done
|
||||
fi
|
||||
|
||||
echo "$files"
|
||||
printf "%b" "$files"
|
||||
}
|
||||
|
||||
scan_packages() {
|
||||
|
|
40
src/file/backup.sh
Normal file
40
src/file/backup.sh
Normal 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
85
src/file/file_merge.sh
Normal 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
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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=
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
13
tori
|
@ -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=
|
||||
|
|
Loading…
Reference in a new issue