95 lines
4.4 KiB
Bash
95 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
INSTALLER="$ROOT_DIR/install.sh"
|
|
|
|
fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; }
|
|
|
|
[ -r "$INSTALLER" ] || fail "install.sh is missing"
|
|
sh -n "$INSTALLER" || fail "install.sh must be valid POSIX shell"
|
|
|
|
grep -Fq 'mkdir "$LOCK_DIR"' "$INSTALLER" || fail "installer lock must use atomic mkdir"
|
|
grep -Fq 'Removing stale installer lock' "$INSTALLER" || fail "stale installer lock must be recoverable"
|
|
|
|
grep -Fq 'trap cleanup EXIT' "$INSTALLER" || fail "cleanup must run on EXIT"
|
|
grep -Fq 'trap on_int INT' "$INSTALLER" || fail "INT handler missing"
|
|
grep -Fq 'on_int() { exit 130; }' "$INSTALLER" || fail "INT must return 130"
|
|
grep -Fq 'on_term() { exit 143; }' "$INSTALLER" || fail "TERM must return 143"
|
|
if grep -Fq 'trap cleanup EXIT HUP INT TERM' "$INSTALLER"; then
|
|
fail "cleanup-only signal trap can resume installation after a signal"
|
|
fi
|
|
|
|
awk '
|
|
/^main\(\)/ { in_main=1 }
|
|
in_main && /sync_time/ && !sync { sync=NR }
|
|
in_main && /ensure_bootstrap_ucode_runtime/ && !bootstrap { bootstrap=NR }
|
|
END { exit !(sync > 0 && bootstrap > sync) }
|
|
' "$INSTALLER" || fail "sync_time must happen before bootstrap package/network operations"
|
|
|
|
grep -Fq 'apk --wait "$APK_LOCK_WAIT_SECONDS"' "$INSTALLER" || fail "apk --wait support is required"
|
|
grep -Fq 'diagnose_apk_lock' "$INSTALLER" || fail "APK lock diagnosis is required"
|
|
if grep -Fq 'sanitize_apk_world' "$INSTALLER"; then
|
|
fail "installer must not mutate /etc/apk/world as generic APK cleanup"
|
|
fi
|
|
|
|
grep -Fq 'TACHYON_DOWNLOAD_BYTES=' "$INSTALLER" || fail "release download size accounting missing"
|
|
grep -Fq 'free_kb /tmp' "$INSTALLER" || fail "/tmp free-space check missing"
|
|
grep -Fq 'MIN_TMP_HEADROOM_KB' "$INSTALLER" || fail "/tmp safety headroom missing"
|
|
|
|
grep -Fq 'pkg_install_local_bundle "$@"' "$INSTALLER" || fail "bundle transaction helper is not used"
|
|
grep -Fq 'apk-transaction' "$INSTALLER" || fail "APK transaction tag missing"
|
|
grep -Fq 'opkg-transaction' "$INSTALLER" || fail "opkg transaction tag missing"
|
|
|
|
awk '
|
|
/^main\(\)/ { in_main=1 }
|
|
in_main && /download_release/ && !download { download=NR }
|
|
in_main && /snapshot_state/ && !snapshot { snapshot=NR }
|
|
in_main && /prepare_transaction/ && !prepare { prepare=NR }
|
|
in_main && /install_core_transaction/ && !install { install=NR }
|
|
in_main && /healthcheck/ && !health { health=NR }
|
|
in_main && /commit_transaction/ && !commit { commit=NR }
|
|
END { exit !(download > 0 && snapshot > download && prepare > snapshot && install > prepare && health > install && commit > health) }
|
|
' "$INSTALLER" || fail "transaction phase ordering is unsafe"
|
|
|
|
grep -Fq 'restore_snapshot' "$INSTALLER" || fail "failed transaction must restore recovery snapshot"
|
|
grep -Fq 'TX_COMMITTED' "$INSTALLER" || fail "transaction commit guard missing"
|
|
|
|
grep -Fq '/fd/1000' "$INSTALLER" || fail "procd fd1000 lock-holder cleanup missing"
|
|
grep -Fq 'is_protected_pid' "$INSTALLER" || fail "installer must protect its own process ancestry"
|
|
grep -Fq '99-tachyon-wan|flock 1000|/etc/init.d/tachyon' "$INSTALLER" || fail "legacy init-lock waiter cleanup missing"
|
|
|
|
for pattern in 'tachyon_' 'luci-app-tachyon_' 'luci-i18n-tachyon-ru_' 'sha256sums.txt'; do
|
|
grep -Fq "$pattern" "$INSTALLER" || fail "release asset contract missing: $pattern"
|
|
done
|
|
grep -Fq 'Checksum mismatch' "$INSTALLER" || fail "checksum failure path missing"
|
|
|
|
if grep -qi 'jsdelivr' "$INSTALLER"; then
|
|
fail "GitHub release assets must not use the jsDelivr Git-tree endpoint"
|
|
fi
|
|
|
|
grep -Fq '/usr/bin/tachyon component_action sing_box "$_action"' "$INSTALLER" || fail "sing-box must be delegated to component_action"
|
|
grep -Fq 'require("core.common")' "$INSTALLER" || fail "runtime healthcheck must require core.common"
|
|
|
|
export TACHYON_INSTALLER_TEST=1
|
|
# shellcheck source=/dev/null
|
|
. "$INSTALLER"
|
|
|
|
TEST_TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TEST_TMP"' EXIT
|
|
LOCK_DIR="$TEST_TMP/lock"
|
|
acquire_lock || fail "first atomic lock acquisition failed"
|
|
[ -f "$LOCK_DIR/pid" ] || fail "lock PID was not recorded"
|
|
release_lock
|
|
[ ! -e "$LOCK_DIR" ] || fail "lock was not released"
|
|
|
|
start="$(date +%s)"
|
|
if run_with_deadline 1 sh -c 'sleep 5'; then
|
|
fail "deadline watchdog accepted an over-deadline command"
|
|
fi
|
|
elapsed="$(( $(date +%s) - start ))"
|
|
[ "$elapsed" -lt 4 ] || fail "deadline watchdog did not stop the command promptly"
|
|
run_with_deadline 3 sh -c 'exit 0' || fail "deadline helper changed a successful exit status"
|
|
|
|
printf 'PASS: ultimate installer transaction contract\n'
|