From 08e9e6c7994a0ce3fe633f9eef0554bdfe71a283 Mon Sep 17 00:00:00 2001 From: fatalerrors Date: Wed, 1 Apr 2026 17:20:49 +0200 Subject: [PATCH] greatly improved upgrade system --- profile.d/updates.sh | 350 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 285 insertions(+), 65 deletions(-) diff --git a/profile.d/updates.sh b/profile.d/updates.sh index f0a99c9..26a5076 100644 --- a/profile.d/updates.sh +++ b/profile.d/updates.sh @@ -39,24 +39,27 @@ export UPDT_URL="$BASE_URL/raw/branch/master" export ARCH_URL="$BASE_URL/archive/master.tar.gz" # ------------------------------------------------------------------------------ -# Check for profile updates +# Check whether a newer profile version is available # Usage: check_updates [-q] # If -q is specified, the function will operate in quiet mode (internal use only) check_updates() { - local quiet=0 - local PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@") + local quiet=0 result=5 PARSED + local vfile="" lastver="" + + PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@") if [[ $? -ne 0 ]]; then disp E "Invalid options, use \"check_updates --help\" to display usage." - return 1 + return 2 fi eval set -- "$PARSED" while true; do case "$1" in -h|--help) - printf "check_updates: Check for new versions.\n\n" - printf "Usage: check_updates\n" + printf "check_updates: Check whether a newer profile version is available.\n\n" + printf "Usage: check_updates [-q|--quiet]\n" + printf "This command only checks availability; it does not modify the installation.\n" return 0 ;; -q|--quiet) @@ -73,28 +76,35 @@ check_updates() esac done - (( $quiet != 1 )) && disp I "Checking for updates..." - local vfile="/tmp/version" - wget "$UPDT_URL/version" -O $vfile >/dev/null 2>&1 || { - disp E "Can't download version file, impossible to proceed!" + (( quiet != 1 )) && disp I "Checking for updates..." + + vfile=$(mktemp /tmp/profile_version.XXXXXX) || { + disp E "Failed to create a temporary file." + return 4 + } + + dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || { + rm -f "$vfile" + disp E "Cannot download version file; unable to continue." return 5 } if [[ -s $vfile ]]; then - local lastver=$(cat $vfile) - if [[ $lastver != $PROFVERSION ]]; then - disp I "You have version $PROFVERSION installed. Version $lastver is available." - (( $quiet != 1 )) && disp I "You should upgrade to last version when possible." + lastver=$(<"$vfile") + if [[ "$lastver" != "$PROFVERSION" ]]; then + disp I "Installed: $PROFVERSION. Available: $lastver." + (( quiet != 1 )) && disp I "You should upgrade when possible." result=1 else - (( $quiet != 1 )) && disp I "Your version is up-to-date." + (( quiet != 1 )) && disp I "Your version is up-to-date." result=0 fi - rm -f $vfile + rm -f "$vfile" else - disp E "Impossible to read temporary file, impossible to proceed." + rm -f "$vfile" + disp E "Temporary file is unreadable; unable to continue." fi - unset lastver vfile + return $result } export -f check_updates @@ -102,23 +112,63 @@ export -f check_updates # ------------------------------------------------------------------------------ -# Apply update to profile -# Usage: profile_upgrade +# Apply the available profile upgrade +# Usage: profile_upgrade [options] profile_upgrade() { - local PARSED=$(getopt -o h --long help -n 'profile_upgrade' -- "$@") + local PARSED + local check_rc=0 dry_run=0 force_git=0 switch_to_git=0 + local archive_file="" tmpbase="" use_archive=0 branch="" + local tmpdir="" archive="" extracted_root="" + + PARSED=$(getopt -o hf:t:nFb:g --long help,file:,tmpdir:,dry-run,force,branch:,switch-to-git -n 'profile_upgrade' -- "$@") if [[ $? -ne 0 ]]; then - printf "Invalid options, use \"profile_upgrade --help\" to display usage." - return 1 + disp E "Invalid options, use \"profile_upgrade --help\" to display usage." + return 2 fi eval set -- "$PARSED" while true; do case "$1" in -h|--help) - printf "profile_upgrade: Upgrade the profile to the latest version.\n\n" - printf "Usage: profile_upgrade\n" + printf "profile_upgrade: Apply the available profile upgrade.\n\n" + printf "Usage: profile_upgrade [options]\n\n" + printf "Options:\n" + printf "\t-h, --help\t\tDisplay this help screen\n" + printf "\t-f, --file ARCHIVE\tUse a local archive file for the upgrade\n" + printf "\t-t, --tmpdir DIR\tCreate the temporary working directory under DIR\n" + printf "\t-b, --branch NAME\tUse NAME as the target Git branch\n" + printf "\t-g, --switch-to-git\tReplace current install with a fresh Git clone\n" + printf "\t-n, --dry-run\t\tDisplay what would be done without changing anything\n" + printf "\t-F, --force\t\tDiscard local changes before upgrading\n\n" + printf "If the profile is installed from Git, the upgrade uses 'git pull'.\n" + printf "Otherwise, it downloads or applies an archive and refreshes the files.\n" return 0 ;; + -f|--file) + archive_file="$2" + use_archive=1 + shift 2 + ;; + -t|--tmpdir) + tmpbase="$2" + shift 2 + ;; + -b|--branch) + branch="$2" + shift 2 + ;; + -g|--switch-to-git) + switch_to_git=1 + shift + ;; + -n|--dry-run) + dry_run=1 + shift + ;; + -F|--force) + force_git=1 + shift + ;; --) shift break @@ -130,59 +180,229 @@ profile_upgrade() esac done - if check_updates -q; then - disp "No update available." - return 0 + if (( ! use_archive && ! switch_to_git )); then + check_updates -q + check_rc=$? + if (( check_rc == 0 )); then + disp I "No update available." + return 0 + elif (( check_rc > 1 )); then + disp E "Unable to check whether an update is available." + return "$check_rc" + fi fi - if [[ -s $MYPATH/profile.sh ]]; then - disp E "Installation path detection failed, cannot upgrade automatically." + if [[ ! -s $MYPATH/profile.sh ]]; then + disp E "Install path detection failed; cannot upgrade automatically." return 1 fi - if [[ -d $MYPATH/.git ]]; then + if [[ -d $MYPATH/.git ]] && (( use_archive )) && (( ! force_git )); then + disp E "Refusing archive upgrade on a Git install without --force." + return 1 + fi + + if (( switch_to_git )); then + command -v git >/dev/null 2>&1 || { + disp E "Git is required to switch this install to a Git clone." + return 3 + } + if (( dry_run )); then + disp I "[dry-run] rm -rf \"$MYPATH\"/.git" + disp I "[dry-run] git clone "$BASE_URL" \"$MYPATH\"" + [[ -n "$branch" ]] && disp I "[dry-run] git -C \"$MYPATH\" checkout "$branch"" + return 0 + fi + + if [[ -d $MYPATH/.git ]]; then + disp W "Git repository already present; no switch is needed." + else + local backup_dir="${MYPATH}.pre-git.$$.bak" + mv "$MYPATH" "$backup_dir" || { + disp E "Failed to move current install out of the way." + return 3 + } + git clone "$BASE_URL" "$MYPATH" || { + disp E "Git clone failed; previous install kept in $backup_dir." + mv "$backup_dir" "$MYPATH" 2>/dev/null || true + return 3 + } + [[ -n "$branch" ]] && ( + cd "$MYPATH" && git checkout "$branch" + ) || true + disp I "Switched installation to Git source." + disp I "Previous install kept in $backup_dir." + return 0 + fi + fi + + if [[ -d $MYPATH/.git ]] && (( ! use_archive )); then disp I "Git installation detected, applying git pull." - pushd "$MYPATH" || { + command -v git >/dev/null 2>&1 || { + disp E "Git is required for this upgrade but is not available." + return 3 + } + pushd "$MYPATH" >/dev/null || { disp E "Failed to change directory to $MYPATH." return 3 } - git pull || { - disp E "Git pull failed, upgrade not applyed." - popd - return 2 + git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { + disp E "Install directory is not a valid Git working tree." + popd >/dev/null || return 1 + return 3 } - disp I "Successfully upgraded using git." - popd + if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then + if (( force_git )); then + disp W "Force mode: local Git changes and untracked files will be lost." + if (( dry_run )); then + disp I "[dry-run] git fetch --all --prune" + disp I "[dry-run] git reset --hard HEAD" + disp I "[dry-run] git clean -fd" + else + git fetch --all --prune || { + disp E "Git fetch failed, upgrade not applied." + popd >/dev/null || return 1 + return 4 + } + git reset --hard HEAD || { + disp E "Git reset failed, upgrade not applied." + popd >/dev/null || return 1 + return 4 + } + git clean -fd || { + disp E "Git clean failed, upgrade not applied." + popd >/dev/null || return 1 + return 4 + } + fi + else + disp W "The Git working tree contains local changes." + disp W "Consider committing or stashing them before upgrading, or use --force." + disp W "Upgrade may fail if the changes conflict with the upgrade." + fi + fi + if [[ -n "$branch" ]]; then + if (( dry_run )); then + disp I "[dry-run] git fetch origin $branch" + disp I "[dry-run] git checkout $branch" + else + git fetch origin "$branch" || { + disp E "Git fetch failed for branch $branch." + popd >/dev/null || return 1 + return 2 + } + git checkout "$branch" || { + disp E "Git checkout failed for branch $branch." + popd >/dev/null || return 1 + return 2 + } + fi + fi + + if (( dry_run )); then + if [[ -n "$branch" ]]; then + disp I "[dry-run] git pull origin $branch" + else + disp I "[dry-run] git pull" + fi + else + if [[ -n "$branch" ]]; then + git pull origin "$branch" || { + disp E "Git pull failed, upgrade not applied." + popd >/dev/null || return 1 + return 2 + } + else + git pull || { + disp E "Git pull failed, upgrade not applied." + popd >/dev/null || return 1 + return 2 + } + fi + disp I "Successfully upgraded using git." + fi + popd >/dev/null || return 1 else - disp I "No Git detected. Downloading and applying upgrade from archive..." - local tmpdir="/tmp/profile_upg.$$" - mkdir -p "$tmpdir" || { - disp E "Failed to create temporary directory." - return 4 - } + if (( use_archive )); then + [[ -r "$archive_file" ]] || { + disp E "Local archive '$archive_file' is missing or unreadable." + return 4 + } + disp I "Using local archive $archive_file." + else + disp W "No Git repo found. Git is the recommended source." + disp I "Applying upgrade from archive..." + fi - local archive="$tmpdir/profile.tar.gz" - wget -q "$ARCH_URL" -O "$archive" || { - disp E "Failed to download archive." + if [[ -n "$tmpbase" ]]; then + if (( dry_run )); then + disp I "[dry-run] mkdir -p \"$tmpbase\"" + disp I "[dry-run] mktemp -d \"$tmpbase/profile_upg.XXXXXX\"" + tmpdir="$tmpbase/profile_upg.DRYRUN" + else + mkdir -p "$tmpbase" || { + disp E "Failed to create temporary directory base $tmpbase." + return 5 + } + tmpdir=$(mktemp -d "$tmpbase/profile_upg.XXXXXX") || { + disp E "Failed to create temp working directory under $tmpbase." + return 5 + } + fi + else + if (( dry_run )); then + disp I "[dry-run] mktemp -d /tmp/profile_upg.XXXXXX" + tmpdir="/tmp/profile_upg.DRYRUN" + else + tmpdir=$(mktemp -d /tmp/profile_upg.XXXXXX) || { + disp E "Failed to create temporary directory." + return 5 + } + fi + fi + + if (( use_archive )); then + archive="$archive_file" + else + archive="$tmpdir/profile.tar.gz" + if (( dry_run )); then + disp I "[dry-run] dwl \"$ARCH_URL\" \"$archive\"" + else + dwl "$ARCH_URL" "$archive" || { + disp E "Failed to download archive." + rm -rf "$tmpdir" + return 6 + } + fi + fi + + if (( dry_run )); then + disp I "[dry-run] tar -xzf \"$archive\" -C \"$tmpdir\"" + disp I "[dry-run] cp -a /. \"$MYPATH\"/" + else + tar -xzf "$archive" -C "$tmpdir" || { + disp E "Archive extraction failed." + rm -rf "$tmpdir" + return 7 + } + + extracted_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | head -n 1) + if [[ -z "$extracted_root" ]]; then + disp E "Could not find extracted profile files." + rm -rf "$tmpdir" + return 8 + fi + + disp I "Installing new version..." + cp -a "$extracted_root"/. "$MYPATH"/ || { + disp E "Failed to copy new files into $MYPATH." + rm -rf "$tmpdir" + return 9 + } + + disp I "Upgrade complete. Please log out and log in again." rm -rf "$tmpdir" - return 5 - } - - tar -xzf "$archive" -C "$tmpdir" || { - disp E "Archive extraction failed." - rm -rf "$tmpdir" - return 6 - } - - disp I "Installing new version..." - cp -r "$tmpdir"/profile/* "$MYPATH"/ || { - disp E "Failed to copy new files to $MYPATH." - rm -rf "$tmpdir" - return 7 - } - - disp I "Upgrade complete. You should now logout and login again." - rm -rf "$tmpdir" + fi fi } export -f profile_upgrade