#!/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