#!/bin/sh
# Runs BEFORE the package files are removed.
#
# This is the only moment the cleanup can happen: DSM deletes target/ on
# removal, so by the time postuninst runs the binary that knows how to take
# our rules out of the kernel is already gone.
#
# Without this, removing the package leaves the SVRGUARD_BLOCK chain and the
# INPUT jump in place — the host goes on dropping several hundred addresses
# with nothing left that can list, explain or undo them. An uninstall that
# silently keeps enforcing is worse than one that fails loudly.
#
# clear-firewall, not uninstall: the latter also tries to stop and
# deregister an OS service, and on DSM the service belongs to the package
# manager, not to us.

# DSM runs this on UPGRADE as well as on removal, and the two need
# opposite behaviour. On an upgrade the rules must stay: they live in the
# kernel and keep protecting the host across the swap, whereas clearing
# them would unblock every attacker until the new version's first sync —
# a hole that widens with every scheduled update.
#
# SYNOPKG_PKG_STATUS is how DSM tells the two apart. When it is absent
# (older DSM, or a manual invocation) the safe reading is "this really is
# a removal": leaving rules behind with no owner is the worse of the two
# mistakes, and a needless clear is repaired by the next sync.
# The three paths DSM gives us. Overridable ONLY so the acceptance harness
# can run this exact file instead of a doctored copy of it -- a check that
# runs on an edited duplicate stops being evidence about the real script.
# DSM never sets these, so the defaults are what ships.
VAR_DIR="${SVRGUARD_VAR_DIR:-/var/packages/SvrGuard/var}"
BIN="${SVRGUARD_BIN:-/var/packages/SvrGuard/target/bin/svrguard}"
VOLUMES="${SVRGUARD_VOLUMES:-/volume1 /volume2 /volume3}"
CFG="${VAR_DIR}/svrguard.ini"

UPGRADING=""
if [ "${SYNOPKG_PKG_STATUS:-}" = "UPGRADE" ]; then
    UPGRADING="yes"
    echo "SvrGuard: upgrade in progress, keeping firewall rules in place"
fi

# The data copy below runs on upgrades too, and that is not belt-and-braces.
# Measured on DSM 6 (2026-07-28): an in-place `synopkg install` of a newer
# package emptied var/ — a database holding 315 blocks came back at 4 KB
# while the 288 kernel rules stayed, leaving the host dropping addresses no
# longer recorded anywhere. Upgrades therefore need saving exactly as much
# as removals do; only the firewall handling differs between the two.
if [ -x "$BIN" ] && [ -f "$CFG" ] && [ -z "$UPGRADING" ]; then
    # Hand the licence seat back first (plan 20260806 S4). Same rule as the
    # firewall: only on a real removal. Releasing it on every upgrade would
    # take the customer's cover away each time DSM updated the package, and
    # the update would still report success.
    #
    # The hub may be unreachable from a NAS being decommissioned, so this can
    # only warn -- it mails the owner and prints where to click, and the
    # removal continues either way.
    echo "SvrGuard: releasing the licence seat"
    "$BIN" unpair -config "$CFG" -uninstall 2>&1 || \
        echo "SvrGuard: WARNING could not tell the hub — free the seat yourself in the hub console"

    echo "SvrGuard: removing firewall rules"
    # Never fail the uninstall over this. A user who clicked "remove" must
    # end up with the package removed; a rule we could not delete is worth
    # reporting, not worth blocking on.
    "$BIN" clear-firewall -config "$CFG" 2>&1 || \
        echo "SvrGuard: WARNING could not remove all firewall rules — check: iptables -S SVRGUARD_BLOCK"
fi

# Copy the data out before DSM destroys it.
#
# DSM removes the WHOLE package directory on uninstall, var/ included —
# confirmed on DSM 6. That takes the block history, the tuned rules and the
# hub enrolment key with it. Losing the enrolment key is the expensive one:
# to the hub the reinstalled host looks brand new and consumes another seat.
#
# Removing a package in DSM is one click and is often done to reinstall or
# move a version, so the data is copied somewhere that survives, and the
# path is printed. Deleting it stays the operator's decision.
[ -f "$CFG" ] || exit 0

# A live SQLite database is up to three files, and only the trio taken at
# one instant is a valid snapshot. This used to be `cp -a var/. backup/`,
# which is wrong in a way that took six months to surface and ten minutes
# to destroy a database (2026-09-10, ds414):
#
#   * it copies INTO the old backup instead of replacing it, and
#   * a clean shutdown checkpoints and DELETES -wal/-shm, so there is
#     nothing left in var/ to overwrite the PREVIOUS upgrade's -wal.
#
# The restore then put a fresh .db next to a six-hour-old -wal, SQLite
# replayed a log belonging to a different image, and every page it touched
# came out doubly referenced. So: ask the binary for one consistent file
# (VACUUM INTO), and never write into the live backup directory.
for VOL in $VOLUMES; do
    [ -d "$VOL" ] || continue
    BACKUP="${VOL}/SvrGuard-backup"
    NEW="${BACKUP}.new"
    rm -rf "$NEW" 2>/dev/null
    mkdir -p "$NEW" 2>/dev/null || continue
    chmod 700 "$NEW" 2>/dev/null

    FAIL=""
    # Everything except the database trio. The .db comes from db-backup
    # below; -wal/-shm must never travel on their own.
    for f in "$VAR_DIR"/*; do
        [ -e "$f" ] || continue
        case "${f##*/}" in
            svrguard.db|svrguard.db-wal|svrguard.db-shm) continue ;;
        esac
        cp -a "$f" "$NEW/" 2>/dev/null || FAIL=1
    done

    if [ -x "$BIN" ] && "$BIN" db-backup -config "$CFG" -out "$NEW/svrguard.db" >/dev/null 2>&1; then
        :
    else
        # Fall back to copying the files. The trio travels together here:
        # if the package is being killed rather than stopped, the -wal
        # holds transactions the .db does not, and dropping it loses the
        # newest blocks. This copy is NOT guaranteed consistent -- postinst
        # checks it after restoring, which is where that gets caught.
        echo "SvrGuard: could not make a consistent copy; falling back to a file copy"
        cp -a "${VAR_DIR}/svrguard.db" "$NEW/" 2>/dev/null || FAIL=1
        for EXTRA in "${VAR_DIR}/svrguard.db-wal" "${VAR_DIR}/svrguard.db-shm"; do
            [ -f "$EXTRA" ] && { cp -a "$EXTRA" "$NEW/" 2>/dev/null || FAIL=1; }
        done
        : > "$NEW/RAW_COPY"
    fi

    if [ -n "$FAIL" ]; then
        # ⭐ Leave the previous backup exactly as it was. The old code
        # emptied the backup directory first, so a failure here -- and the
        # most likely failure is a database too broken to copy -- destroyed
        # the last good copy seconds before DSM deleted var/.
        rm -rf "$NEW"
        echo "SvrGuard: WARNING could not copy data to $BACKUP; the previous backup is untouched"
        break
    fi

    rm -rf "${BACKUP}.prev"
    [ -d "$BACKUP" ] && mv "$BACKUP" "${BACKUP}.prev"
    mv "$NEW" "$BACKUP"
    rm -rf "${BACKUP}.prev"
    chmod 700 "$BACKUP" 2>/dev/null
    echo "SvrGuard: configuration and database copied to $BACKUP"
    if [ -n "$UPGRADING" ]; then
        echo "SvrGuard: it will be restored automatically after the upgrade"
    else
        echo "SvrGuard: installing again restores it automatically"
        echo "SvrGuard: (delete $BACKUP yourself once you no longer want it)"
    fi
    break
done

exit 0
