Preflight every managed user target before mutation, atomically replace only owner-matched byte-identical regular files, and fail closed on customized or unsafe paths.
- commit
- 4c8b625e1b3499bb176c00488fb14e7ba5b60d87
- parent
- 635bb9474e46219ad0d4c40ca18b7f8990b85dd4
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 4 | + |
| M | scripts/ | 91 | ++++++++++++++++++++--- |
| A | scripts/ | 121 | ++++++++++++++++++++++++++++++ |
@@ -501,6 +501,10 @@ stages and validates the entire release, then changes `current` last. It records
501501 the former release as `previous`; `sigil-spark-host-rollback` revalidates every502502 target file before swapping `current`/`previous`. Neither path creates identity503503 or `host.toml`, changes `/etc`, restarts PipeWire, or starts/enables the service.504+Before changing any package-managed user asset, the installer preflights all of505+them. It can adopt an operator-owned regular file only when it is byte-identical506+to the new release asset; any local modification or unsafe target rejects the507+whole asset migration without changing an earlier destination.504508 505509 For a temporary dirty and unsigned development package only, replace the506510 Minisign option with `--allow-dirty --allow-unsigned`. The manifest records that@@ -43,7 +43,7 @@ done
4343 [[ "$(uname -s)" == Linux ]] || die "this package must be installed on Linux"4444 [[ "$(uname -m)" == x86_64 ]] || die "expected x86_64, got $(uname -m)"4545 [[ "$(id -u)" -ne 0 ]] || die "run as the dedicated gaming user, not root"46-for command in diff flock ldd sha256sum systemctl systemd-analyze timeout; do46+for command in cmp diff flock ldd sha256sum systemctl systemd-analyze timeout; do4747 command -v "$command" >/dev/null 2>&1 || die "$command is required"4848 done4949 @@ -111,6 +111,7 @@ next_current="$install_root/.current-package-$release_id-$$"
111111 next_previous="$install_root/.previous-package-$release_id-$$"112112 lock_path="$install_root/.install.lock"113113 verify_unit="$install_root/.verify-package-$release_id-$$.service"114+next_managed_links=()114115 115116 install -d -m 0755 "$HOME/.local" "$HOME/.local/libexec" "$install_root" "$releases_root"116117 exec 9>"$lock_path"@@ -125,6 +126,9 @@ cleanup() {
125126 fi126127 [[ ! -L "$next_current" ]] || rm -f -- "$next_current"127128 [[ ! -L "$next_previous" ]] || rm -f -- "$next_previous"129+ for next_managed_link in "${next_managed_links[@]}"; do130+ [[ ! -L "$next_managed_link" ]] || rm -f -- "$next_managed_link"131+ done128132 [[ ! -f "$verify_unit" ]] || rm -f -- "$verify_unit"129133 exit "$status"130134 }@@ -204,25 +208,90 @@ local_bin="$HOME/.local/bin"
204208 asset_dir="$HOME/.local/share/sigil-spark/package-assets"205209 install -d -m 0700 "$config_root" "$pipewire_dir" "$systemd_dir" "$local_bin" "$asset_dir"206210 207-ensure_managed_link() {211+# BEGIN managed asset helpers (exercised directly by bazzite-package-assets.sh)212+preflight_managed_link() {208213 local link="$1"209214 local target="$2"210- if [[ -e "$link" || -L "$link" ]]; then211- [[ -L "$link" && "$(readlink -- "$link")" == "$target" ]] \212- || die "refusing to replace unmanaged path: $link"213- return215+ local release_asset="$3"216+217+ if [[ -L "$link" ]]; then218+ [[ "$(readlink "$link")" == "$target" ]] \219+ || die "refusing to replace unmanaged symlink: $link"220+ managed_link_action=keep221+ elif [[ -e "$link" ]]; then222+ [[ -f "$link" && -O "$link" ]] \223+ || die "refusing to replace unsafe or unowned path: $link"224+ cmp -s "$link" "$release_asset" \225+ || die "refusing to replace modified path: $link"226+ managed_link_action=replace227+ else228+ managed_link_action=create214229 fi215- ln -s "$target" "$link"216230 }217231 218-ensure_managed_link "$pipewire_dir/50-sigil-spark-audio.conf" \232+install_managed_links() {233+ local -a actions=()234+ local -a prepared_links=()235+ local index link next_link236+237+ [[ "${#managed_links[@]}" -eq 4 \238+ && "${#managed_links[@]}" -eq "${#managed_targets[@]}" \239+ && "${#managed_links[@]}" -eq "${#managed_release_assets[@]}" ]] \240+ || die "internal managed asset list mismatch"241+242+ # Classify every destination before changing any of them. This prevents a243+ # late unmanaged path from leaving an earlier package asset half-migrated.244+ for index in "${!managed_links[@]}"; do245+ preflight_managed_link \246+ "${managed_links[$index]}" \247+ "${managed_targets[$index]}" \248+ "${managed_release_assets[$index]}"249+ actions+=("$managed_link_action")250+ done251+252+ next_managed_links=()253+ for index in "${!managed_links[@]}"; do254+ [[ "${actions[$index]}" == keep ]] && continue255+ link="${managed_links[$index]}"256+ next_link="${link}.sigil-package-$release_id-$$-$index"257+ [[ ! -e "$next_link" && ! -L "$next_link" ]] \258+ || die "temporary managed link path already exists: $next_link"259+ prepared_links[index]="$next_link"260+ next_managed_links+=("$next_link")261+ done262+263+ for index in "${!managed_links[@]}"; do264+ [[ "${actions[$index]}" == keep ]] && continue265+ next_link="${prepared_links[$index]}"266+ ln -s "${managed_targets[$index]}" "$next_link"267+ if mv --help 2>&1 | grep -q -- '--no-target-directory'; then268+ mv -Tf -- "$next_link" "${managed_links[$index]}"269+ else270+ mv -f "$next_link" "${managed_links[$index]}"271+ fi272+ done273+}274+# END managed asset helpers275+276+managed_links=(277+ "$pipewire_dir/50-sigil-spark-audio.conf"278+ "$systemd_dir/sigil-host.service"279+ "$local_bin/sigil-spark-host-rollback"280+ "$asset_dir/70-sigil-remote-input.rules"281+)282+managed_targets=(219283 "$install_root/current/assets/50-sigil-spark-audio.conf"220-ensure_managed_link "$systemd_dir/sigil-host.service" \221284 "$install_root/current/assets/sigil-host.service"222-ensure_managed_link "$local_bin/sigil-spark-host-rollback" \223285 "$install_root/current/tools/rollback-bazzite-release.sh"224-ensure_managed_link "$asset_dir/70-sigil-remote-input.rules" \225286 "$install_root/current/assets/70-sigil-remote-input.rules"287+)288+managed_release_assets=(289+ "$release_path/assets/50-sigil-spark-audio.conf"290+ "$release_path/assets/sigil-host.service"291+ "$release_path/tools/rollback-bazzite-release.sh"292+ "$release_path/assets/70-sigil-remote-input.rules"293+)294+install_managed_links226295 227296 ln -s "releases/$release_id" "$next_current"228297 if [[ -n "$current_release_id" && "$current_release_id" != "$release_id" ]]; then@@ -0,0 +1,121 @@
1+2+3+set -euo pipefail4+5+script_dir="$(CDPATH='' cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"6+repo_dir="$(CDPATH='' cd -- "$script_dir/../.." && pwd -P)"7+installer="$repo_dir/scripts/install-bazzite-package.sh"8+temp_parent="${TMPDIR:-/tmp}"9+temp_parent="${temp_parent%/}"10+temp_root="$(mktemp -d "$temp_parent/sigil-package-assets.XXXXXX")"11+helper_script="$temp_root/managed-assets.sh"12+13+cleanup() {14+ local status=$?15+ trap - EXIT INT TERM HUP16+ case "$temp_root" in17+ "$temp_parent"/sigil-package-assets.??????) rm -rf -- "$temp_root" ;;18+ esac19+ exit "$status"20+}21+trap cleanup EXIT INT TERM HUP22+23+sed -n \24+ '/^# BEGIN managed asset helpers /,/^# END managed asset helpers$/p' \25+ "$installer" | sed '1d;$d' >"$helper_script"26+grep -Fq 'install_managed_links()' "$helper_script"27+28+# shellcheck source=/dev/null29+source "$helper_script"30+31+die() {32+ printf 'test installer rejected: %s\n' "$*" >&233+ exit 134+}35+36+# Referenced by the sourced production helper block.37+# shellcheck disable=SC203438+release_id=0123456789abcdef39+# shellcheck disable=SC203440+next_managed_links=()41+42+make_assets() {43+ local root="$1"44+ local index45+ install -d -m 0700 "$root/assets" "$root/links"46+ for index in 0 1 2 3; do47+ printf 'release asset %s\n' "$index" >"$root/assets/$index"48+ done49+}50+51+set_managed_arrays() {52+ local root="$1"53+ managed_links=(54+ "$root/links/0"55+ "$root/links/1"56+ "$root/links/2"57+ "$root/links/3"58+ )59+ managed_targets=(60+ "$root/future/0"61+ "$root/future/1"62+ "$root/future/2"63+ "$root/future/3"64+ )65+ managed_release_assets=(66+ "$root/assets/0"67+ "$root/assets/1"68+ "$root/assets/2"69+ "$root/assets/3"70+ )71+}72+73+success_root="$temp_root/success"74+make_assets "$success_root"75+set_managed_arrays "$success_root"76+ln -s "${managed_targets[0]}" "${managed_links[0]}"77+cp "${managed_release_assets[2]}" "${managed_links[2]}"78+cp "${managed_release_assets[3]}" "${managed_links[3]}"79+install_managed_links80+for index in 0 1 2 3; do81+ [[ -L "${managed_links[$index]}" ]]82+ [[ "$(readlink "${managed_links[$index]}")" == "${managed_targets[$index]}" ]]83+done84+85+mismatch_root="$temp_root/mismatch"86+make_assets "$mismatch_root"87+set_managed_arrays "$mismatch_root"88+cp "${managed_release_assets[1]}" "${managed_links[1]}"89+ln -s "${managed_targets[2]}" "${managed_links[2]}"90+printf 'locally modified\n' >"${managed_links[3]}"91+if ( install_managed_links ); then92+ printf 'FAIL: mismatched regular asset unexpectedly succeeded\n' >&293+ exit 194+fi95+[[ ! -e "${managed_links[0]}" && ! -L "${managed_links[0]}" ]]96+[[ -f "${managed_links[1]}" && ! -L "${managed_links[1]}" ]]97+cmp -s "${managed_release_assets[1]}" "${managed_links[1]}"98+[[ -L "${managed_links[2]}" ]]99+[[ "$(readlink "${managed_links[2]}")" == "${managed_targets[2]}" ]]100+[[ -f "${managed_links[3]}" && ! -L "${managed_links[3]}" ]]101+grep -Fq 'locally modified' "${managed_links[3]}"102+103+unsafe_root="$temp_root/unsafe"104+make_assets "$unsafe_root"105+set_managed_arrays "$unsafe_root"106+cp "${managed_release_assets[1]}" "${managed_links[1]}"107+ln -s "${managed_targets[2]}" "${managed_links[2]}"108+mkfifo "${managed_links[3]}"109+if ( install_managed_links ); then110+ printf 'FAIL: unsafe managed asset type unexpectedly succeeded\n' >&2111+ exit 1112+fi113+[[ ! -e "${managed_links[0]}" && ! -L "${managed_links[0]}" ]]114+[[ -f "${managed_links[1]}" && ! -L "${managed_links[1]}" ]]115+[[ -L "${managed_links[2]}" ]]116+[[ -p "${managed_links[3]}" ]]117+118+# Intentional literal verifies the ownership gate in the extracted production helper.119+# shellcheck disable=SC2016120+grep -Fq '[[ -f "$link" && -O "$link" ]]' "$helper_script"121+printf 'bazzite_package_asset_tests=ok\n'