#!/usr/bin/env bash set -Eeuo pipefail ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT fail() { printf 'Purge fixture failed: %s\n' "$1" >&2 exit 1 } sed '$d' "$ROOT/deploy/purge.sh" > "$WORK/purge-functions.sh" # shellcheck disable=SC1090,SC1091 source "$WORK/purge-functions.sh" install() { local -a arguments=() while [ "$#" -gt 0 ]; do case "$1" in -o|-g) shift 2 ;; *) arguments+=("$1"); shift ;; esac done command install "${arguments[@]}" } APP_ROOT="$WORK/opt/kaidi" BAOTA_ROOT="$WORK/www/kaidi" CONFIG_ROOT="$WORK/etc/kaidi" STATE_ROOT="$WORK/var/lib/kaidi" UPDATE_STATE_ROOT="$WORK/var/lib/kaidi-update" LOG_ROOT="$WORK/var/log/kaidi" FIRST_LOGIN_FILE="$WORK/root/kaidi-first-login.txt" # shellcheck disable=SC2034 # Referenced by the sourced purge helper. BACKUP_ROOT="$WORK/recovery" BACKUP_ARCHIVE= mkdir -p \ "$APP_ROOT" \ "$BAOTA_ROOT" \ "$CONFIG_ROOT" \ "$STATE_ROOT/files" \ "$UPDATE_STATE_ROOT/backups" \ "$UPDATE_STATE_ROOT/failed" \ "$UPDATE_STATE_ROOT/transactions" \ "$LOG_ROOT" \ "$(dirname "$FIRST_LOGIN_FILE")" \ "$WORK/external-mysql" \ "$WORK/reverse-proxy" printf 'DB_PASSWORD="secret"\n' > "$CONFIG_ROOT/kaidi.env" printf 'document\n' > "$STATE_ROOT/files/document.txt" printf 'dump\n' > "$UPDATE_STATE_ROOT/backups/mysql.sql" printf 'setup code\n' > "$FIRST_LOGIN_FILE" printf 'database sentinel\n' > "$WORK/external-mysql/keep" printf 'proxy sentinel\n' > "$WORK/reverse-proxy/keep" create_recovery_backup [ -s "$BACKUP_ARCHIVE" ] || fail 'root-only recovery archive was not created' [ "$(stat -c '%a' "$BACKUP_ARCHIVE" 2>/dev/null || stat -f '%Lp' "$BACKUP_ARCHIVE")" = 600 ] \ || fail 'recovery archive mode is not 0600' archive_list=$(tar -tzf "$BACKUP_ARCHIVE") grep -Fq "${CONFIG_ROOT#/}/kaidi.env" <<< "$archive_list" \ || fail 'configuration was omitted from the recovery archive' grep -Fq "${STATE_ROOT#/}/files/document.txt" <<< "$archive_list" \ || fail 'file storage was omitted from the recovery archive' grep -Fq "${UPDATE_STATE_ROOT#/}/backups/mysql.sql" <<< "$archive_list" \ || fail 'database backup was omitted from the recovery archive' remove_managed_paths for path in "$APP_ROOT" "$BAOTA_ROOT" "$CONFIG_ROOT" "$STATE_ROOT" "$UPDATE_STATE_ROOT" "$LOG_ROOT"; do [ ! -e "$path" ] || fail "managed path was not removed: $path" done [ ! -e "$FIRST_LOGIN_FILE" ] || fail 'first-login file was not removed' [ -f "$WORK/external-mysql/keep" ] || fail 'external database state was removed' [ -f "$WORK/reverse-proxy/keep" ] || fail 'reverse-proxy state was removed' [ -s "$BACKUP_ARCHIVE" ] || fail 'recovery archive was removed with the installation' [ "$(tail -n 1 "$ROOT/deploy/purge.sh")" = 'main "$@"' ] \ || fail 'purge actions can execute before a complete curl stream is parsed' # shellcheck disable=SC2016 # Match the literal guard in the purge source. grep -Fq 'KAIDI_PURGE_CONFIRM=$REQUIRED_CONFIRMATION' "$ROOT/deploy/purge.sh" \ || fail 'destructive removal no longer requires explicit confirmation' grep -Fq 'External MySQL data and reverse-proxy configuration will not be modified' "$ROOT/deploy/purge.sh" \ || fail 'purge boundary is no longer explicit' # shellcheck disable=SC2016 # Match literal service-account cleanup calls. grep -Fq 'terminate_uid_processes "$service_uid"' "$ROOT/deploy/purge.sh" \ || fail 'dedicated service-account processes are not terminated before account removal' # shellcheck disable=SC2016 # Match literal forced account removal. grep -Fq 'userdel --force "$SERVICE_USER"' "$ROOT/deploy/purge.sh" \ || fail 'dedicated service-account removal is not resilient to a restarting panel process' grep -Fq '/etc/systemd/system/kaidi-update.service.d' "$ROOT/deploy/purge.sh" \ || fail 'updater systemd drop-ins are not removed' printf 'Local purge and recovery fixture passed\n'