201 lines
8.1 KiB
Bash
Executable File
201 lines
8.1 KiB
Bash
Executable File
#!/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
|
|
|
|
# The exported variable, not a literal. This was hardcoded while load_config.sh already exported
|
|
# CONF_RAM_CACHE_DIR, so the watchdog would have gone on watching an empty directory the moment
|
|
# that path moved — and reported the cache healthy because nothing was ever missing from a
|
|
# location nothing writes to.
|
|
RAM_CACHE="$CONF_RAM_CACHE_DIR"
|
|
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
|