cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

main
goq / scripts / rollback-bazzite-release.sh
rollback-bazzite-release.sh 8.5K executable
1#!/usr/bin/env bash23set -euo pipefail4umask 07756usage() {7 cat <<'EOF'8Usage: rollback-bazzite-release.sh [--to RELEASE_ID] [--restart]910Atomically switch ~/.local/libexec/sigil-spark/current to the requested11installed release. Without --to, switch to the release recorded by the12previous symlink. The release is validated before activation, including13legacy releases that predate the primary `sigil` executable and contain only14`sigil-host`. The former current release becomes previous, making a second15rollback reversible.1617The host service is not restarted unless --restart is explicit.18EOF19}2021die() {22 printf 'rollback failed: %s\n' "$*" >&223 exit 124}2526target_release_id=""27restart_service=false2829while [[ $# -gt 0 ]]; do30 case "$1" in31 --to)32 [[ $# -ge 2 ]] || die "--to requires a release ID"33 target_release_id="$2"34 shift 235 ;;36 --restart)37 restart_service=true38 shift39 ;;40 --help|-h)41 usage42 exit 043 ;;44 *) die "unknown argument: $1" ;;45 esac46done4748[[ "$(uname -s)" == Linux ]] || die "this rollback helper must run on Linux"49[[ "$(uname -m)" == x86_64 ]] || die "expected x86_64, got $(uname -m)"50[[ "$(id -u)" -ne 0 ]] || die "run as the dedicated gaming user, not root"51for command in cmp flock ldd sha256sum systemctl systemd-analyze timeout; do52 command -v "$command" >/dev/null 2>&1 || die "$command is required"53done5455install_root="$HOME/.local/libexec/sigil-spark"56releases_root="$install_root/releases"57current_link="$install_root/current"58previous_link="$install_root/previous"59lock_path="$install_root/.install.lock"6061[[ -d "$install_root" && ! -L "$install_root" ]] || die "install root is missing or unsafe"62exec 9>"$lock_path"63flock -x 96465resolve_activation() {66 local label="$1"67 local link="$2"68 local resolved6970 [[ -L "$link" ]] || die "$label activation is missing or is not a symlink"71 resolved="$(readlink -f "$link")"72 [[ -n "$resolved" && -d "$resolved" ]] || die "$label activation is dangling"73 # Fedora Atomic/Bazzite exposes /home through /var/home. Compare canonical74 # paths on both sides so the managed symlink does not appear to escape.75 [[ "$(dirname -- "$resolved")" == "$(readlink -f "$releases_root")" ]] \76 || die "$label activation escapes the release root"77 basename -- "$resolved"78}7980current_release_id="$(resolve_activation current "$current_link")"81[[ "$current_release_id" =~ ^[0-9a-f]{7,64}$ ]] || die "current activation has an invalid release ID"8283if [[ -z "$target_release_id" ]]; then84 target_release_id="$(resolve_activation previous "$previous_link")"85fi86[[ "$target_release_id" =~ ^[0-9a-f]{7,64}$ ]] || die "target release ID must be 7-64 lowercase hexadecimal characters"87[[ "$target_release_id" != "$current_release_id" ]] || die "target release is already current"8889target_path="$releases_root/$target_release_id"90next_current="$install_root/.current-rollback-$$"91next_previous="$install_root/.previous-rollback-$$"92verify_unit="$install_root/.verify-rollback-$target_release_id-$$.service"9394# BEGIN release activation transaction helpers95activation_mutated=false96activation_committed=false97activation_reload_service=false98original_current_present=false99original_current_target=""100original_previous_present=false101original_previous_target=""102restore_current_temp="${current_link}.restore-$$"103restore_previous_temp="${previous_link}.restore-$$"104105snapshot_activation_links() {106 if [[ -L "$current_link" ]]; then107 original_current_present=true108 original_current_target="$(readlink "$current_link")"109 else110 original_current_present=false111 original_current_target=""112 fi113 if [[ -L "$previous_link" ]]; then114 original_previous_present=true115 original_previous_target="$(readlink "$previous_link")"116 else117 original_previous_present=false118 original_previous_target=""119 fi120}121122restore_activation_link() {123 local link="$1"124 local present="$2"125 local target="$3"126 local restore_temp="$4"127128 rm -f -- "$restore_temp"129 if [[ "$present" == true ]]; then130 ln -s "$target" "$restore_temp" || return 1131 mv -Tf -- "$restore_temp" "$link" || return 1132 elif [[ -L "$link" ]]; then133 rm -f -- "$link" || return 1134 elif [[ -e "$link" ]]; then135 return 1136 fi137}138139restore_activation_links() {140 if ! $activation_mutated || $activation_committed; then141 return 0142 fi143144 restore_activation_link \145 "$current_link" "$original_current_present" "$original_current_target" \146 "$restore_current_temp" || true147 restore_activation_link \148 "$previous_link" "$original_previous_present" "$original_previous_target" \149 "$restore_previous_temp" || true150 if $activation_reload_service; then151 systemctl --user daemon-reload >/dev/null 2>&1 || true152 fi153}154155activate_release_links() {156 activation_mutated=true157 if [[ -L "$next_previous" ]]; then158 mv -Tf -- "$next_previous" "$previous_link" || return 1159 fi160 mv -Tf -- "$next_current" "$current_link" || return 1161 if $activation_reload_service; then162 systemctl --user daemon-reload || return 1163 fi164 activation_committed=true165}166# END release activation transaction helpers167168cleanup() {169 local status=$?170 trap - EXIT INT TERM HUP171 restore_activation_links172 [[ ! -L "$next_current" ]] || rm -f -- "$next_current"173 [[ ! -L "$next_previous" ]] || rm -f -- "$next_previous"174 [[ ! -L "$restore_current_temp" ]] || rm -f -- "$restore_current_temp"175 [[ ! -L "$restore_previous_temp" ]] || rm -f -- "$restore_previous_temp"176 [[ ! -f "$verify_unit" ]] || rm -f -- "$verify_unit"177 exit "$status"178}179trap cleanup EXIT INT TERM HUP180snapshot_activation_links181activation_reload_service=true182183[[ -d "$target_path" && ! -L "$target_path" ]] || die "target release is not an installed directory"184[[ "$(stat -Lc '%u' "$target_path")" -eq "$(id -u)" ]] || die "target release is not owned by the current user"185[[ -f "$target_path/SHA256SUMS" && ! -L "$target_path/SHA256SUMS" ]] \186 || die "target release checksums are missing or unsafe"187[[ "$(sha256sum "$target_path/SHA256SUMS" | awk '{print $1}')" == "$target_release_id" ]] \188 || die "target release ID does not bind its checksums"189(190 cd "$target_path"191 sha256sum -c SHA256SUMS192)193194# BEGIN release host executable compatibility helper195release_host_executable() {196 local release_path="$1"197198 if [[ -e "$release_path/sigil" || -L "$release_path/sigil" ]]; then199 [[ -f "$release_path/sigil" && ! -L "$release_path/sigil" \200 && -x "$release_path/sigil" ]] || return 1201 [[ -f "$release_path/sigil-host" && ! -L "$release_path/sigil-host" \202 && -x "$release_path/sigil-host" ]] || return 1203 cmp -s "$release_path/sigil" "$release_path/sigil-host" || return 1204 printf '%s\n' "$release_path/sigil"205 return 0206 fi207 if [[ -f "$release_path/sigil-host" && ! -L "$release_path/sigil-host" \208 && -x "$release_path/sigil-host" ]]; then209 printf '%s\n' "$release_path/sigil-host"210 return 0211 fi212 return 1213}214# END release host executable compatibility helper215216host_executable="$(release_host_executable "$target_path")" \217 || die "target release has no valid host executable layout"218release_binaries=("$host_executable" "$target_path/sigil-probe")219if [[ "$host_executable" == "$target_path/sigil" ]]; then220 release_binaries+=("$target_path/sigil-host")221fi222for path in "${release_binaries[@]}"; do223 binary="$(basename -- "$path")"224 [[ -f "$path" && ! -L "$path" && -x "$path" ]] || die "target $binary is not a regular executable"225 mode="$(stat -Lc '%a' "$path")"226 (( (8#$mode & 8#022) == 0 )) || die "target $binary is group/world writable"227done228229ldd_output="$(ldd "$host_executable")"230printf '%s\n' "$ldd_output"231grep -q 'not found' <<<"$ldd_output" && die "target host binary has an unresolved shared library"232timeout --signal=TERM --kill-after=2s 5s "$host_executable" --version233timeout --signal=TERM --kill-after=2s 5s "$target_path/sigil-probe" --help >/dev/null234awk -v executable="$host_executable" '235 /^ExecStart=/ {236 print "ExecStart=" executable " serve --config=%h/.config/sigil-spark/host.toml"237 next238 }239 { print }240' "$target_path/assets/sigil-host.service" >"$verify_unit"241chmod 0600 "$verify_unit"242systemd-analyze --user verify "$verify_unit"243rm -f -- "$verify_unit"244245ln -s "releases/$target_release_id" "$next_current"246ln -s "releases/$current_release_id" "$next_previous"247activate_release_links248249printf 'current=%s\n' "$(readlink -f "$current_link")"250printf 'previous=%s\n' "$(readlink -f "$previous_link")"251printf 'release_activation=rolled-back\n'252253if $restart_service; then254 systemctl --user restart sigil-host.service255 systemctl --user is-active --quiet sigil-host.service256 printf 'service_activation=restarted\n'257else258 printf 'service_activation=not-attempted\n'259fi