#!/bin/sh
# Runs after files are unpacked, on both first install and upgrade.
#
# The guiding rule: an upgrade must never touch the operator's config or
# database. A fresh ini would silently reset thresholds and drop the hub
# enrolment key, which on the hub side looks like a brand new host and
# consumes another seat.

# Overridable ONLY so the acceptance harness can run this exact file rather
# than a doctored copy; DSM never sets them and the defaults are what ships.
VAR_DIR="${SVRGUARD_VAR_DIR:-/var/packages/SvrGuard/var}"
PKG_DIR="${SVRGUARD_PKG_DIR:-/var/packages/SvrGuard/target}"
VOLUMES="${SVRGUARD_VOLUMES:-/volume1 /volume2 /volume3}"
CFG="${VAR_DIR}/svrguard.ini"

mkdir -p "$VAR_DIR"
chmod 700 "$VAR_DIR"

# Restore what a previous uninstall copied out.
#
# DSM deletes the whole package directory on removal AND on upgrade (both
# measured on DSM 6), so preuninst saves var/ to a volume. Without this,
# every reinstall — and every version bump — starts from an empty database
# and, worse, a new hub enrolment, which the hub reads as a brand new host
# and charges another seat for. This restore is what makes upgrades
# non-destructive; it is not a rare-path safety net.
#
# Only when there is nothing here already: an existing install's data must
# never be overwritten by an older backup.
#
# ⛔ -wal/-shm are NOT restored. preuninst writes one consistent .db
# (VACUUM INTO), so there is no log to replay; restoring a stale one next
# to a fresh database is what corrupted ds414 on 2026-09-10 -- SQLite
# replays a log belonging to a different image and every page it touches
# comes out doubly referenced. The single exception is a RAW_COPY backup:
# that one is three files taken together and only means anything as a set.
#
# A blacklist, not a whitelist: what must not travel is those two
# suffixes, and everything else in var/ (the rollback packages above all)
# has to survive an upgrade. A whitelist would silently drop each new kind
# of file somebody adds later.
if [ ! -f "$CFG" ]; then
    for VOL in $VOLUMES; do
        BACKUP="${VOL}/SvrGuard-backup"
        if [ -f "${BACKUP}/svrguard.ini" ]; then
            echo "SvrGuard: restoring configuration and database from $BACKUP"
            RESTORED=1
            for f in "$BACKUP"/*; do
                [ -e "$f" ] || continue
                case "${f##*/}" in
                    svrguard.db-wal|svrguard.db-shm)
                        [ -f "${BACKUP}/RAW_COPY" ] || continue ;;
                    RAW_COPY)
                        # A property of the backup, not of var/. Copying it
                        # in would make the next backup inherit a marker
                        # that is no longer true.
                        continue ;;
                esac
                cp -a "$f" "$VAR_DIR/" 2>/dev/null || RESTORED=""
            done
            [ -n "$RESTORED" ] && \
                echo "SvrGuard: restored; $BACKUP can be deleted once you have verified the console"
            break
        fi
    done
fi

# Check what was just restored, before anything starts using it.
#
# The fallback path in preuninst copies three files one at a time, which is
# only consistent if nothing was writing -- and "nothing was writing" is
# exactly what cannot be assumed on the path that needed a fallback. This
# is where that gets caught, while the damaged file can still be set aside
# with its name intact.
if [ -f "$CFG" ] && [ -f "${VAR_DIR}/svrguard.db" ] && [ -x "${PKG_DIR}/bin/svrguard" ]; then
    if ! "${PKG_DIR}/bin/svrguard" db-check -config "$CFG" >/dev/null 2>&1; then
        KEEP="${VAR_DIR}/svrguard.db.corrupt-$(date +%Y%m%d-%H%M%S)"
        mv "${VAR_DIR}/svrguard.db" "$KEEP" 2>/dev/null
        rm -f "${VAR_DIR}/svrguard.db-wal" "${VAR_DIR}/svrguard.db-shm"
        echo "SvrGuard: WARNING the restored database is damaged; kept as $KEEP"
        echo "SvrGuard: the service will rebuild what it can on start-up and say so in the console"
    fi
fi

if [ ! -f "$CFG" ]; then
    # Defaults chosen for this platform, not copied from the generic sample:
    #   - backend=none: DSM kernels ship neither nftables nor the ip_set
    #     modules, so no kernel blocking layer is available. Detection,
    #     notification and hub reporting all still work. Saying so here is
    #     better than letting the operator discover it from a failed sync.
    #   - auth_log_paths: this is where DSM logs SSH and FTP failures.
    #   - console on 8092 so DSM's "open" button reaches it.
    cat > "$CFG" <<'EOF'
[database]
path = /var/packages/SvrGuard/var/svrguard.db

[collector]
; DSM keeps web logs here; harmless when the web station is not installed
log_dir = /var/log/httpd
; DSM names them apache24-access_log, NOT access.log — an underscore, and
; no dot before "log". The project defaults (*access.log) match nothing
; here, so these are spelled out or the web logs are silently never read.
log_globs = *access_log,*access_log.1
error_log_globs = *error_log,*error_log.1
; nginx (DSM's own reverse proxy) — enable if you serve sites through it
; nginx_log_dir = /var/log/nginx
; SSH and FTP authentication failures
auth_log_paths = /var/log/auth.log

[scheduler]
interval_minutes = 5

[web]
listen_addr = 0.0.0.0:8092

[console]
lang = zh-Hant

[blocklist]
; auto probes the kernel in order — nftables, ipset, iptables — and uses
; the first that works. On DSM the first two are absent (no nf_tables, no
; ip_set modules) and it lands on iptables, which is verified working.
; This was pinned to "none" while the iptables backend did not exist yet.
;
; Set to "none" only if you want detection and notifications WITHOUT the
; firewall being touched — a monitoring-only deployment, or a host whose
; firewall another system owns.
backend = auto

[maintenance]
retention_days = 3

; ⛔ This section is what makes the package updatable at all. On DSM the
; console's update button and the daily schedule both read [update] url --
; unlike a Linux host, where the licence check reaches the supply channel and
; only the download needs this. Leave it out and the button is drawn, pressed,
; and refused, which is what every DSM install did before 2026-09-09.
;
; Clear the value to switch update checks off.
[update]
url = https://svrguard.ofuyuan.com/update/agent

; Central management (optional). The usual way to join a hub is from this
; package's own page: open it in Package Center (port 8092) and sign in with
; your SvrGuard Hub account -- nothing has to be edited here.
;
; The keys below are for the headless case only: a NAS whose console you
; cannot open in a browser. enroll_token is the per-host code minted by
; pre-registering this host on the Users page of the admin console, which
; only the vendor's operators can open -- ask your supplier for it. It is
; single-use and bound to this one host.
; [fleet]
; host_url = https://svrguard.ofuyuan.com
; agent_uid = my-nas
; enroll_token = <per-host code, ask your supplier>
; heartbeat_minutes = 1
EOF
    chmod 600 "$CFG"
else
    # Upgrade path. The console default moved from 8080 to 8092 (8080 is the
    # most contested port on any machine), and INFO's adminport moved with it
    # — that is what DSM's "Open" button and the URL on the package page use.
    # An existing install keeps its ini, so without this the button would
    # point at a port nobody is listening on.
    #
    # Only the value this script itself wrote is migrated. If the operator
    # chose their own address, it is left exactly as they set it: a package
    # upgrade rewriting a deliberate configuration change is worse than a
    # button pointing at the wrong port.
    if grep -q '^[[:space:]]*listen_addr[[:space:]]*=[[:space:]]*0\.0\.0\.0:8080[[:space:]]*$' "$CFG"; then
        sed -i 's/^\([[:space:]]*listen_addr[[:space:]]*=[[:space:]]*\)0\.0\.0\.0:8080[[:space:]]*$/\10.0.0.0:8092/' "$CFG"
        echo "SvrGuard: console port migrated 8080 -> 8092 to match this package's Open button" >&2
    fi

    # Every ini this script wrote before 2026-09-09 has no [update] section at
    # all, so those hosts could never update themselves: the button was drawn
    # and then refused with "no update source configured".
    #
    # ⭐ The section MISSING and the url EMPTY are different facts, and only
    # the first one is safe to fix here. A missing section means nobody ever
    # made a choice -- this script simply never wrote one. An empty url is a
    # documented way to switch updates off, so it is left exactly as it is.
    # That is why this tests for the section header, not for the value.
    if ! grep -q '^[[:space:]]*\[update\][[:space:]]*$' "$CFG"; then
        # Appended, not inserted: ini sections do not care about order, and
        # a section written at the end cannot land inside another one.
        cat >> "$CFG" <<'EOF'

; Added by the package upgrade: installs made before 2026-09-09 had no update
; source, so the console's update button always refused. Clear the value to
; switch update checks off.
[update]
url = https://svrguard.ofuyuan.com/update/agent
EOF
        echo "SvrGuard: added the missing [update] source; this host can now update itself" >&2
    fi
fi

chmod 755 "${PKG_DIR}/bin/svrguard" 2>/dev/null

# ---- capabilities ----
#
# Declared in config/synology/privilege, not granted here. DSM 7 applies the
# `capabilities` field of a package tool at install time, and that is the
# mechanism the platform supports: AntiVirus asks for cap_dac_read_search the
# same way for bin/synoavscan.
#
# ⛔ setcap cannot work from this script, on any DSM 7 host. /bin/setcap and
# /usr/bin/setcap are both `-rwx------ root root` (measured on ds218j and on
# MR12) and control scripts do not run with enough privilege to execute them,
# so every candidate path fails and the block that used to be here could only
# ever end in a warning. ⭐ Once conf/privilege grants the capability that
# warning is not merely useless, it is wrong -- and a warning that is always
# on is how the next real one gets ignored.
#
# DSM 6 needs none of it: there the package runs as root.
#
# ⚠️ What the agent needs, and what now provides it:
#   read root-owned auth logs  cap_dac_read_search on bin/svrguard, via conf/privilege
#   write firewall rules       bin/svrguard-fw, setuid root via conf/privilege
#
# ⚠️ Both of those are applied by DSM, so neither can be confirmed from here.
# The judgement is on the installed host: `getcap` on the agent binary, and
# the xt_set reference count in /proc/modules for the firewall half.

touch "${VAR_DIR}/svrguard.log"
chmod 600 "${VAR_DIR}/svrguard.log"

# Clear the emergency marker: a package install is exactly the repair that
# marker asks a person to perform, so leaving it behind would keep a banner
# up about a state that no longer exists.
#
# ⚠️ Here rather than in the helper that wrote it. The helper cannot know
# whether anybody acted on it -- it is gone by then -- and a warning that
# clears itself on a timer would be telling the reader the host recovered
# when nothing about the host changed.
rm -f "${VAR_DIR}/update/EMERGENCY"

# No account is created here on purpose. The console detects an empty
# users table and serves a first-run setup form instead of a login page,
# so the administrator is created in the browser after clicking "Open" in
# Package Center. Generating a password here would mean telling a NAS owner
# to read it out of a file with File Station or ssh, which for most people
# is the same as "it does not work".
#
# Setup is only accepted for a short window after the service starts, so an
# unconfigured console cannot be claimed by a passer-by; missing the window
# just means restarting the package.

exit 0
