Files
sxmo-utils/scripts/core/sxmo_migrate.sh
2023-05-14 00:19:17 -07:00

347 lines
10 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2022 Sxmo Contributors
# include common definitions
# shellcheck source=scripts/core/sxmo_common.sh
. sxmo_common.sh
. /etc/profile.d/sxmo_init.sh
_sxmo_load_environments
smartdiff() {
if command -v colordiff > /dev/null; then
colordiff "$@"
else
diff "$@"
fi
}
fetchversion() {
head -n5 "$1" | grep -m1 "configversion: " | sed 's|.*configversion: \(.*\)|\1|'
}
resolvedifference() {
userfile="$1"
defaultfile="$2"
(
printf "\e[31mThe file \e[32m%s\e[31m differs\e[0m\n" "$userfile"
smartdiff -ud "$defaultfile" "$userfile"
) | more
printf "\e[31mMigration options for \e[32m%s\e[31m:\e[0m\n" "$userfile"
printf "1 - Use [d]efault. Apply the Sxmo default, discarding all your own changes.\n"
printf "2 - Open [e]ditor and merge the changes yourself; take care to set the same configversion.\n"
printf "3 - Use your [u]ser version as-is; you verified it's compatible. (Auto-updates configversion only).\n"
printf "4 - [i]gnore, do not resolve and don't change anything, ask again next time. (default)\n"
printf "\e[33mHow do you want to resolve this? Choose one of the options above [1234deui]\e[0m "
read -r reply < /dev/tty
abort=0
case "$reply" in
[1dD]*)
#use default
case "$userfile" in
*hooks*)
#just remove the user hook, will use default automatically
rm "$userfile"
abort=1 #no need for any further cleanup
;;
*)
cp "$defaultfile" "$userfile" || abort=1
;;
esac
;;
[2eE]*)
#open editor with both files and the diff
export DIFFTOOL="${DIFFTOOL:-vimdiff}"
if [ -n "$DIFFTOOL" ] && command -v "$DIFFTOOL" >/dev/null; then # ex vimdiff
set -- "$DIFFTOOL" "$defaultfile" "$userfile"
else
diff -u "$defaultfile" "$userfile" > "${XDG_RUNTIME_DIR}/migrate.diff"
# shellcheck disable=SC2086
set -- $EDITOR "$userfile" "$defaultfile" "${XDG_RUNTIME_DIR}/migrate.diff"
fi
if ! "$@"; then
#user may bail editor, in which case we ignore everything
abort=1
fi
if [ -z "$DIFFTOOL" ]; then
rm "${XDG_RUNTIME_DIR}/migrate.diff"
fi
;;
[3uU]*)
#update configversion automatically
refversion="$(fetchversion "$reffile")"
userversion="$(fetchversion "$userfile")"
if [ -n "$userversion" ]; then
sed -i "s/configversion: $userversion/configversion: $refversion/" "$userfile" || abort=1
elif [ -n "$refversion" ]; then
refline="$(head -n5 "$reffile" | grep -m1 "configversion: ")"
# fall back in case the userfile doesn't contain a configversion at all yet
sed -i "2i$refline" "$userfile" || abort=1
fi
;;
*)
abort=1
;;
esac
if [ "$abort" -eq 0 ]; then
#finish the migration, removing .needs-migration and moving to right place
case "$userfile" in
*needs-migration)
mv -f "$userfile" "${userfile%.needs-migration}"
;;
esac
fi
printf "\n"
}
checkconfigversion() {
userfile="$1"
reffile="$2"
if [ ! -e "$userfile" ] || [ ! -e "$reffile" ]; then
#if the userfile doesn't exist then we revert to default anyway so it's considered up to date
return 0
fi
refversion="$(fetchversion "$reffile")"
if [ -z "$refversion" ]; then
#no ref version found, check file diff instead
if diff "$reffile" "$userfile" > /dev/null; then
return 0
else
return 1
fi
fi
userversion="$(fetchversion "$userfile")"
if [ -z "$userversion" ]; then
#no user version found, check file contents instead
tmpreffile="${XDG_RUNTIME_DIR}/versioncheck"
grep -v "configversion: " "$reffile" > "$tmpreffile"
if diff "$tmpreffile" "$userfile" > /dev/null; then
rm "$tmpreffile"
return 0
else
rm "$tmpreffile"
return 1
fi
fi
[ "$refversion" = "$userversion" ]
}
defaultconfig() {
defaultfile="$1"
userfile="$2"
filemode="$3"
if [ -e "$userfile.needs-migration" ] && { [ "$MODE" = "interactive" ] || [ "$MODE" = "all" ]; }; then
resolvedifference "$userfile.needs-migration" "$defaultfile"
chmod "$filemode" "$userfile" 2> /dev/null
elif [ ! -r "$userfile" ]; then
mkdir -p "$(dirname "$userfile")"
sxmo_log "Installing default configuration $userfile..."
cp "$defaultfile" "$userfile"
chmod "$filemode" "$userfile"
elif [ "$MODE" = "reset" ]; then
if [ ! -e "$userfile.needs-migration" ]; then
mv "$userfile" "$userfile.needs-migration"
else
sxmo_log "$userfile was already flagged for needing migration; not overwriting the older one"
fi
cp "$defaultfile" "$userfile"
chmod "$filemode" "$userfile"
elif ! checkconfigversion "$userfile" "$defaultfile" || [ "$MODE" = "all" ]; then
case "$MODE" in
"interactive"|"all")
resolvedifference "$userfile" "$defaultfile"
;;
"sync")
sxmo_log "$userfile is out of date, disabling and marked as needing migration..."
[ ! -e "$userfile.needs-migration" ] && cp "$userfile" "$userfile.needs-migration" #never overwrite older .needs-migration files, they take precendence
chmod "$filemode" "$userfile.needs-migration"
cp "$defaultfile" "$userfile"
chmod "$filemode" "$userfile"
;;
esac
fi
}
checkhooks() {
if ! [ -e "$XDG_CONFIG_HOME/sxmo/hooks/" ]; then
return
fi
for hook in \
"$XDG_CONFIG_HOME/sxmo/hooks/"* \
${SXMO_DEVICE_NAME:+"$XDG_CONFIG_HOME/sxmo/hooks/$SXMO_DEVICE_NAME/"*}; do
{ [ -e "$hook" ] && [ -f "$hook" ];} || continue #sanity check because shell enters loop even when there are no files in dir (null glob)
[ -h "$hook" ] && continue # shallow symlink
if printf %s "$hook" | grep -q "/$SXMO_DEVICE_NAME/"; then
# We also compare the device user hook to the system
# default version
DEFAULT_PATH="$(xdg_data_path sxmo/default_hooks/"$SXMO_DEVICE_NAME"/):$(xdg_data_path sxmo/default_hooks/)"
else
# We dont want to compare a default user hook to the device
# system version
DEFAULT_PATH="$(xdg_data_path sxmo/default_hooks/)"
fi
if [ "$MODE" = "reset" ]; then
if [ ! -e "$hook.needs-migration" ]; then
mv "$hook" "$hook.needs-migration" #move the hook away
else
sxmo_log "$hook was already flagged for needing migration; not overwriting the older one"
rm "$hook"
fi
continue
fi
case "$hook" in
*.needs-migration)
defaulthook="$(PATH="$DEFAULT_PATH" command -v "$(basename "$hook" ".needs-migration")")"
[ "$MODE" = sync ] && continue # ignore this already synced hook
;;
*.backup)
#skip
continue
;;
*)
#if there is already one marked as needing migration, use that one instead and skip this one
[ -e "$hook.needs-migration" ] && continue
defaulthook="$(PATH="$DEFAULT_PATH" command -v "$(basename "$hook")")"
;;
esac
if [ -f "$defaulthook" ]; then
if diff "$hook" "$defaulthook" > /dev/null && [ "$MODE" != "sync" ]; then
printf "\e[33mHook %s is identical to the default, so you don't need a custom hook, remove it? [y/N]\e[0m" "$hook"
read -r reply < /dev/tty
if [ "y" = "$reply" ]; then
rm "$hook"
fi
elif ! checkconfigversion "$hook" "$defaulthook" || [ "$MODE" = "all" ]; then
case "$MODE" in
"interactive"|"all")
resolvedifference "$hook" "$defaulthook"
;;
"sync")
sxmo_log "$hook is out of date, disabling and marked as needing migration..."
#never overwrite older .needs-migration files, they take precendence
if [ ! -e "$hook.needs-migration" ]; then
mv "$hook" "$hook.needs-migration"
else
rm "$hook"
fi
;;
esac
fi
elif [ "$MODE" != "sync" ]; then
(
smartdiff -ud "/dev/null" "$hook"
printf "\e[31mThe hook \e[32m%s\e[31m does not exist (anymore), remove it? [y/N] \e[0m\n" "$hook"
) | more
read -r reply < /dev/tty
if [ "y" = "$reply" ]; then
rm "$hook"
fi
printf "\n"
fi
done
}
common() {
defaultconfig "$(xdg_data_path sxmo/appcfg/profile_template)" "$XDG_CONFIG_HOME/sxmo/profile" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/fontconfig.conf)" "$XDG_CONFIG_HOME/fontconfig/conf.d/50-sxmo.conf" 644
}
sway() {
defaultconfig "$(xdg_data_path sxmo/appcfg/sway_template)" "$XDG_CONFIG_HOME/sxmo/sway" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/foot.ini)" "$XDG_CONFIG_HOME/foot/foot.ini" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/mako.conf)" "$XDG_CONFIG_HOME/mako/config" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/bonsai_tree.json)" "$XDG_CONFIG_HOME/sxmo/bonsai_tree.json" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/wob.ini)" "$XDG_CONFIG_HOME/wob/wob.ini" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/conky.conf)" "$XDG_CONFIG_HOME/sxmo/conky.conf" 644
}
xorg() {
defaultconfig "$(xdg_data_path sxmo/appcfg/Xresources)" "$HOME/.Xresources" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/xinit_template)" "$XDG_CONFIG_HOME/sxmo/xinit" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/dunst.conf)" "$XDG_CONFIG_HOME/dunst/dunstrc" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/bonsai_tree.json)" "$XDG_CONFIG_HOME/sxmo/bonsai_tree.json" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/xob_styles.cfg)" "$XDG_CONFIG_HOME/xob/styles.cfg" 644
defaultconfig "$(xdg_data_path sxmo/appcfg/conky.conf)" "$XDG_CONFIG_HOME/sxmo/conky.conf" 644
}
#set default mode
[ -z "$*" ] && set -- interactive
# Don't allow running with sudo, or as root
if [ -n "$SUDO_USER" ]; then
echo "$0 can't be run with sudo, it must be run as your user" >&2
exit 127
fi
if [ "$USER" = "root" ]; then
echo "$0 can't be run as root, it must be run as your user" >&2
exit 127
fi
# Execute idempotent migrations
find "$(xdg_data_path sxmo/migrations)" -type f | sort -n | tr '\n' '\0' | xargs -0 sh
if [ -z "$*" ]; then
set -- sync interactive
fi
#modes may be chained
for MODE in "$@"; do
case "$MODE" in
"interactive"|"all")
common
sway
xorg
checkhooks
;;
"sync"|"reset")
case "$SXMO_WM" in
sway)
common
sway
;;
dwm)
common
xorg
;;
*)
common
sway
xorg
;;
esac
checkhooks
;;
"state")
NEED_MIGRATION="$(find "$XDG_CONFIG_HOME/" -name "*.needs-migration")"
if [ -n "$NEED_MIGRATION" ]; then
sxmo_log "The following configuration files need migration: $NEED_MIGRATION"
exit "$(echo "$NEED_MIGRATION" | wc -l)" #exit code represents number of files needing migration
else
sxmo_log "All configuration files are up to date"
fi
;;
*)
sxmo_log "Invalid mode: $MODE"
exit 2
;;
esac
done