#!/usr/bin/env bash # ------------------------------------------------------------------------------ # Copyright (c) 2013-2026 Geoffray Levasseur # Protected by the BSD3 license. Please read bellow for details. # # * Redistribution and use in source and binary forms, # * with or without modification, are permitted provided # * that the following conditions are met: # * # * Redistributions of source code must retain the above # * copyright notice, this list of conditions and the # * following disclaimer. # * # * Redistributions in binary form must reproduce the above # * copyright notice, this list of conditions and the following # * disclaimer in the documentation and/or other materials # * provided with the distribution. # * # * Neither the name of the copyright holder nor the names # * of any other contributors may be used to endorse or # * promote products derived from this software without # * specific prior written permission. # * # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # * OF SUCH DAMAGE. # ------------------------------------------------------------------------------ export BASE_URL="https://git.geoffray-levasseur.org/fatalerrors/profile" export UPDT_URL="$BASE_URL/raw/branch/master" export ARCH_URL="$BASE_URL/archive/master.tar.gz" # ------------------------------------------------------------------------------ # 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 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 2 fi eval set -- "$PARSED" while true; do case "$1" in -h|--help) 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) quiet=1 shift ;; --) shift break ;; *) break ;; esac done (( 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 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." result=0 fi rm -f "$vfile" else rm -f "$vfile" disp E "Temporary file is unreadable; unable to continue." fi return $result } export -f check_updates # ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------ # Apply the available profile upgrade # Usage: profile_upgrade [options] 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 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: 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 ;; *) disp E "Invalid options, use \"profile_upgrade --help\" to display usage." return 1 ;; esac done 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 "Install path detection failed; cannot upgrade automatically." return 1 fi 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." 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 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 } 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 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 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" fi fi } export -f profile_upgrade # ------------------------------------------------------------------------------ # EOF