This commit is contained in:
Executable
+179
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
root=$(cd -- "$(dirname -- "$0")/.." && pwd -P)
|
||||
bash -n "$root/uninstall.sh"
|
||||
|
||||
tmp=$(cd "$(mktemp -d)" && pwd -P)
|
||||
cleanup() { rm -rf -- "$tmp" 2>/dev/null || true; }
|
||||
trap cleanup EXIT
|
||||
|
||||
make_fixture() {
|
||||
local fixture=$1
|
||||
mkdir -p "$fixture/opt/tallynote/releases/1.1.1/dist" \
|
||||
"$fixture/opt/tallynote/.update-work" \
|
||||
"$fixture/var/lib/tallynote/files" \
|
||||
"$fixture/var/lib/tallynote/staging" \
|
||||
"$fixture/var/lib/tallynote/exports" \
|
||||
"$fixture/var/lib/tallynote-backups" \
|
||||
"$fixture/etc/tallynote" \
|
||||
"$fixture/etc/systemd/system" \
|
||||
"$fixture/usr/local/sbin" \
|
||||
"$fixture/usr/local/libexec"
|
||||
printf '%s\n' 'release' > "$fixture/opt/tallynote/releases/1.1.1/dist/index.js"
|
||||
ln -s "$fixture/opt/tallynote/releases/1.1.1" "$fixture/opt/tallynote/current"
|
||||
printf '%s\n' \
|
||||
"TALLYNOTE_INSTALL_PREFIX=$fixture/opt/tallynote" \
|
||||
"TALLYNOTE_DATA_DIR=$fixture/var/lib/tallynote" \
|
||||
"TALLYNOTE_UPDATE_PUBLIC_KEY_FILE=$fixture/etc/tallynote/update-signing-key.pub" \
|
||||
> "$fixture/etc/tallynote/tallynote.env"
|
||||
chmod 600 "$fixture/etc/tallynote/tallynote.env"
|
||||
printf '%s\n' 'fake public key' > "$fixture/etc/tallynote/update-signing-key.pub"
|
||||
for unit in tallynote.service tallynote-update.service tallynote-update.path; do
|
||||
printf '%s\n' "Description=TallyNote $unit" "WorkingDirectory=$fixture/opt/tallynote/current" "PathExists=$fixture/var/lib/tallynote/update-request.json" > "$fixture/etc/systemd/system/$unit"
|
||||
done
|
||||
printf '%s\n' '#!/usr/bin/env bash' 'PREFIX=${TALLYNOTE_INSTALL_PREFIX:-/opt/tallynote}' 'echo TallyNote' > "$fixture/usr/local/sbin/tallynote-update"
|
||||
printf '%s\n' '#!/usr/bin/env bash' 'PREFIX=${TALLYNOTE_INSTALL_PREFIX:-/opt/tallynote}' 'echo TallyNote' > "$fixture/usr/local/libexec/tallynote-update-runner"
|
||||
cp "$root/uninstall.sh" "$fixture/usr/local/sbin/tallynote-uninstall"
|
||||
chmod 755 "$fixture/usr/local/sbin/tallynote-update" "$fixture/usr/local/libexec/tallynote-update-runner" "$fixture/usr/local/sbin/tallynote-uninstall"
|
||||
printf '%s\n' 'sqlite' > "$fixture/var/lib/tallynote/tallynote.db"
|
||||
printf '%s\n' 'backup' > "$fixture/var/lib/tallynote-backups/backup.db"
|
||||
}
|
||||
|
||||
make_systemctl() {
|
||||
local fixture=$1
|
||||
cat > "$fixture/systemctl" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -u
|
||||
printf '%s\n' "$*" >> "$TALLYNOTE_TEST_SYSTEMCTL_LOG"
|
||||
case "${1:-}" in
|
||||
is-active) exit 0 ;;
|
||||
stop|disable|daemon-reload) exit 0 ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
EOF
|
||||
chmod 755 "$fixture/systemctl"
|
||||
}
|
||||
|
||||
run_uninstall() {
|
||||
local fixture=$1
|
||||
TALLYNOTE_UNINSTALL_TEST_MODE=true \
|
||||
TALLYNOTE_UNINSTALL_ROOT="$fixture" \
|
||||
TALLYNOTE_SYSTEMCTL_BIN="$fixture/systemctl" \
|
||||
TALLYNOTE_TEST_SYSTEMCTL_LOG="$fixture/systemctl.log" \
|
||||
bash "$root/uninstall.sh" "${@:2}"
|
||||
}
|
||||
|
||||
fixture="$tmp/normal"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
run_uninstall "$fixture"
|
||||
[[ -d "$fixture/var/lib/tallynote" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
[[ -d "$fixture/var/lib/tallynote-backups" && -f "$fixture/var/lib/tallynote-backups/backup.db" ]]
|
||||
[[ ! -e "$fixture/opt/tallynote" || -z "$(find "$fixture/opt/tallynote" -mindepth 1 -print -quit 2>/dev/null)" ]]
|
||||
[[ ! -e "$fixture/etc/systemd/system/tallynote.service" ]]
|
||||
[[ ! -e "$fixture/usr/local/sbin/tallynote-update" ]]
|
||||
grep -n '^is-active.*tallynote-update.path' "$fixture/systemctl.log" >/dev/null
|
||||
grep -n '^stop tallynote-update.path' "$fixture/systemctl.log" >/dev/null
|
||||
path_stop=$(grep -n '^stop tallynote-update.path' "$fixture/systemctl.log" | head -n1 | cut -d: -f1)
|
||||
update_stop=$(grep -n '^stop tallynote-update.service' "$fixture/systemctl.log" | head -n1 | cut -d: -f1)
|
||||
main_stop=$(grep -n '^stop tallynote.service' "$fixture/systemctl.log" | head -n1 | cut -d: -f1)
|
||||
(( path_stop < update_stop && update_stop < main_stop ))
|
||||
|
||||
# Re-running after the first uninstall is harmless and does not touch data.
|
||||
run_uninstall "$fixture"
|
||||
[[ -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
|
||||
# Purge requires the explicit acknowledgement flag and must fail before any
|
||||
# application files are removed.
|
||||
fixture="$tmp/purge"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
if run_uninstall "$fixture" --purge-data >/dev/null 2>&1; then
|
||||
echo 'expected --purge-data without --yes to fail' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -e "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
run_uninstall "$fixture" --purge-data --yes --purge-config
|
||||
[[ ! -e "$fixture/var/lib/tallynote" && ! -e "$fixture/var/lib/tallynote-backups" ]]
|
||||
[[ ! -e "$fixture/etc/tallynote" ]]
|
||||
|
||||
# A custom data path must not overlap the release prefix; otherwise removing
|
||||
# releases could destroy data that the default uninstall promises to keep.
|
||||
fixture="$tmp/overlap"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
printf '%s\n' \
|
||||
"TALLYNOTE_INSTALL_PREFIX=$fixture/opt/tallynote" \
|
||||
"TALLYNOTE_DATA_DIR=$fixture/opt/tallynote/releases/data" \
|
||||
> "$fixture/etc/tallynote/tallynote.env"
|
||||
mkdir -p "$fixture/opt/tallynote/releases/data"
|
||||
printf '%s\n' protected > "$fixture/opt/tallynote/releases/data/keep.db"
|
||||
if run_uninstall "$fixture" >/dev/null 2>&1; then
|
||||
echo 'expected overlapping data path to fail' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -f "$fixture/opt/tallynote/releases/data/keep.db" ]]
|
||||
|
||||
# Trailing-slash aliases are rejected before the lexical overlap guard can be
|
||||
# bypassed.
|
||||
fixture="$tmp/trailing"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
printf '%s\n' \
|
||||
"TALLYNOTE_INSTALL_PREFIX=$fixture/opt/tallynote/" \
|
||||
"TALLYNOTE_DATA_DIR=$fixture/var/lib/tallynote" \
|
||||
> "$fixture/etc/tallynote/tallynote.env"
|
||||
if run_uninstall "$fixture" >/dev/null 2>&1; then
|
||||
echo 'expected trailing slash path to fail' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -e "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
|
||||
# Dot-component aliases are rejected as well; textual paths must be canonical
|
||||
# before the managed-directory containment checks run.
|
||||
fixture="$tmp/dot"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
printf '%s\n' \
|
||||
"TALLYNOTE_INSTALL_PREFIX=$fixture/opt/tallynote/." \
|
||||
"TALLYNOTE_DATA_DIR=$fixture/var/lib/tallynote" \
|
||||
> "$fixture/etc/tallynote/tallynote.env"
|
||||
if run_uninstall "$fixture" >/dev/null 2>&1; then
|
||||
echo 'expected dot path component to fail' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -e "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
|
||||
# Pending update state blocks destructive work until an operator overrides it.
|
||||
fixture="$tmp/pending"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
printf '%s\n' pending > "$fixture/opt/tallynote/.update-state"
|
||||
if run_uninstall "$fixture" >/dev/null 2>&1; then
|
||||
echo 'expected pending update state to block uninstall' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -e "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
|
||||
# A current link escaping the release tree is rejected without deleting data.
|
||||
fixture="$tmp/link"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
rm -f "$fixture/opt/tallynote/current"
|
||||
ln -s "$fixture/outside" "$fixture/opt/tallynote/current"
|
||||
if run_uninstall "$fixture" >/dev/null 2>&1; then
|
||||
echo 'expected unsafe current symlink to fail' >&2
|
||||
exit 1
|
||||
fi
|
||||
[[ -L "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
|
||||
# dry-run must not call systemctl or remove files.
|
||||
fixture="$tmp/dry-run"
|
||||
make_fixture "$fixture"
|
||||
make_systemctl "$fixture"
|
||||
run_uninstall "$fixture" --dry-run >/dev/null
|
||||
[[ -e "$fixture/opt/tallynote/current" && -f "$fixture/var/lib/tallynote/tallynote.db" ]]
|
||||
[[ ! -e "$fixture/systemctl.log" ]]
|
||||
|
||||
printf '%s\n' 'uninstaller shell tests passed'
|
||||
Reference in New Issue
Block a user