#!/bin/bash
# ==============================================================================================
# ============================= Conf Cache Watchdog ============================================
# ==============================================================================================
#
# PURPOSE
# ─────────────────────────────────────────────────────────────────────────────
# Maintains the persistent partner conf backup at $PERSISTENT_CONF_CACHE.
# Runs every 15 minutes via SYSTEM_WATCHDOG_SCRIPTS.
#
# When remote is OFFLINE:
#   Writes/refreshes partner confs from RAM cache → persistent location.
#   Means if this host reboots while remote is still down, conf_cache_restore.sh
#   can load the partner vars into RAM so fallback.sh has what it needs.
#
# When remote is ONLINE:
#   Removes the persistent backup if present — not needed, conf_sync.sh will
#   pull fresh on next boot.
#
# Silent when remote is online and no backup exists (normal state).
# No-op when FALLBACK_ENABLED=false or CONF_SYNC_ENABLED=false.
#
# ==============================================================================================
# OPERATIONAL MODEL
# ==============================================================================================
#
# One decision, driven entirely by remote reachability:
#
#   1. Gates
#        → PARTNERSHIP_ENABLED, FALLBACK_ENABLED, CONF_SYNC_ENABLED, REMOTE_ID
#        → any gate closed means exit 0, no work, no output
#
#   2. ping_remote
#        REACHABLE   → remove $PERSISTENT_CONF_CACHE if it exists, exit
#        UNREACHABLE → refresh the backup from the RAM cache
#
#   3. Refresh (remote offline only)
#        → copy every host*.conf from the RAM cache except this host's own
#        → own conf is excluded: it is already on disk, the backup exists
#          solely to survive a reboot without the partner's vars
#
# ==============================================================================================
# DESIGN PRINCIPLES
# ==============================================================================================
#
# Online = No Backup Needed
#   When the remote is reachable, conf_sync.sh will pull fresh on the next boot.
#   The persistent backup is removed — a stale backup is worse than no backup
#   because it can mask a connectivity problem that conf_sync.sh would catch.
#
# Offline = Stay Ready
#   While the remote is down, the RAM cache is the best available copy of partner
#   vars. Refreshing the persistent backup every 15 minutes ensures it reflects
#   the last-known-good state, not an old copy from days earlier.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
#   Writes to and removes $PERSISTENT_CONF_CACHE, which lives under the plugin
#   directory and is not user-writable.
#
# Lock Acquisition
#   acquire_lock() prevents concurrent execution. Without it a slow run can still
#   be copying confs into the backup while the next run, seeing the remote back
#   online, rm -rf's the directory out from under it.
#
# Cache Path Sanity Guard
#   PERSISTENT_CONF_CACHE is validated as an absolute path at least three levels
#   deep before any rm -rf. It is built from ${SCRIPTS_DIR} — if that is ever
#   unset the path collapses toward the filesystem root, and this script would
#   otherwise recursively delete whatever it collapsed to.
#
# Partnership Gate
#   require_partnership() exits early if PARTNERSHIP_ENABLED=false.
#
# FALLBACK_ENABLED / CONF_SYNC_ENABLED Gates
#   Exits cleanly if either is disabled — the backup only has meaning when
#   fallback can actually consume it.
#
# REMOTE_ID Presence Check
#   Exits if partner identity is unset. Without a partner there is nothing to
#   back up and the own-conf exclusion below could not be applied correctly.
#
# Own-Conf Exclusion
#   This host's own conf is never written into the partner backup. Restoring it
#   later would overwrite live local config with a stale copy.
#
# Dry Run Support
#   --dry-run reports every removal and copy without touching the backup.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#
# master.conf
#
#   PERSISTENT_CONF_CACHE
#       Destination for the partner conf backup. Must survive a reboot, so it
#       lives under ${SCRIPTS_DIR}, not in /tmp.
#
#   FALLBACK_ENABLED
#       Master fallback toggle. Backup is pointless when fallback cannot run.
#
#   CONF_SYNC_ENABLED
#       Conf sync toggle. When off, no RAM cache is being maintained to back up.
#
#   PARTNERSHIP_ENABLED
#       Checked via require_partnership().
#
# ==============================================================================================
# RUNTIME MODES
# ==============================================================================================
#
# conf_cache_watchdog.sh
#     Refresh or remove the persistent partner conf backup based on remote state
#
# conf_cache_watchdog.sh --dry-run
#     Report what would be written or removed without changing the backup
#
# conf_cache_watchdog.sh --log
#     Verbose per-file output
#
# ==============================================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../../load_config.sh"

parse_args "$@"

# ==============================================================================================
# ━━━ Setup ━━━
# ==============================================================================================
if [[ "$EUID" -ne 0 ]]; then
    error "Must be run as root"
    exit 1
fi

acquire_lock

detect_hosts
require_partnership

[[ "${FALLBACK_ENABLED:-false}" != "true" ]] && exit 0
[[ "${CONF_SYNC_ENABLED:-true}" != "true" ]] && exit 0
[[ -z "${REMOTE_ID:-}"         ]]            && exit 0

RAM_CACHE="/tmp/.cache/vv/d"
SAVE_DIR="${PERSISTENT_CONF_CACHE:-}"

# SAVE_DIR is rm -rf'd below and is built from ${SCRIPTS_DIR}. If that is ever unset the
# path collapses toward / — require an absolute path at least three levels deep so a
# collapsed or empty value can never name a system directory.
_slashes="${SAVE_DIR//[^\/]/}"
if [[ -z "$SAVE_DIR" || "$SAVE_DIR" != /* || "${#_slashes}" -lt 3 ]]; then
    error "PERSISTENT_CONF_CACHE is unset or unsafe ('${SAVE_DIR:-unset}') — refusing to manage conf backup"
    notify "conf_cache_watchdog aborted on $(hostname) — PERSISTENT_CONF_CACHE is '${SAVE_DIR:-unset}'" \
        "Conf Cache Watchdog" "warning"
    exit 1
fi
unset _slashes

if ping_remote; then
    if [[ -d "$SAVE_DIR" ]]; then
        [[ "$DRY_RUN" == true ]] && warn "DRY RUN — would remove $SAVE_DIR (remote back online)" && exit 0
        rm -rf "$SAVE_DIR"
        log "Remote online — persistent conf backup removed"
    fi
    exit 0
fi

# Remote offline — write/refresh backup from RAM cache
if [[ ! -d "$RAM_CACHE" ]]; then
    log "Remote offline but RAM cache not populated — nothing to back up"
    exit 0
fi

saved=0
for conf in "$RAM_CACHE"/host*.conf; do
    [[ -f "$conf" ]] || continue
    base="$(basename "$conf")"
    [[ "${base,,}" == "${MY_ID,,}.conf" ]] && continue

    if [[ "$DRY_RUN" == true ]]; then
        warn "DRY RUN — would write $base → $SAVE_DIR/"
        (( saved++ ))
        continue
    fi

    mkdir -p "$SAVE_DIR"
    cp "$conf" "$SAVE_DIR/$base" && (( saved++ ))
done

[[ "$saved" -gt 0 ]] && log "Remote offline — partner conf backup refreshed ($saved file(s))"
exit 0
