Bring script headers onto the template and close safeguard gaps

Headers claimed protections the code never had, and several destructive paths had no
guard against a collapsed config value.
This commit is contained in:
Gmer4Lfe
2026-08-01 20:37:59 -04:00
parent cdce877601
commit e8b114094a
78 changed files with 3301 additions and 277 deletions
+107 -6
View File
@@ -21,6 +21,25 @@
# 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
# ==============================================================================================
#
@@ -38,11 +57,70 @@
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# require_partnership — exits early if PARTNERSHIP_ENABLED=false
# FALLBACK_ENABLED gate — exits if fallback is disabled
# CONF_SYNC_ENABLED gate — exits if conf sync is disabled
# REMOTE_ID presence check — exits if partner identity is unset
# --dry-run mode — shows what would happen without touching the backup
# 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
#
# ==============================================================================================
@@ -50,6 +128,17 @@ 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
@@ -58,7 +147,19 @@ require_partnership
[[ -z "${REMOTE_ID:-}" ]] && exit 0
RAM_CACHE="/tmp/.cache/vv/d"
SAVE_DIR="$PERSISTENT_CONF_CACHE"
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
+57
View File
@@ -73,6 +73,63 @@
# attention.
#
# ==============================================================================================
# OPERATIONAL SAFEGUARDS
# ==============================================================================================
#
# Root Enforcement
# Truncating container-owned log files requires root.
#
# Lock Acquisition
# acquire_lock() prevents concurrent runs. Two instances would race on the
# strike state file and the growth baseline, double-counting strikes and
# potentially truncating a file one cycle early.
#
# Host Detection
# detect_hosts() aliases HOST*_WATCHDOG_APPDATA_SIZES to the correct host's
# suppress ceilings.
#
# WATCHDOG_CHECK_APPDATA Toggle
# Exits cleanly before any scanning when the master toggle is off.
#
# Path Existence Guard
# Every entry in WATCHDOG_APPDATA_PATHS is skipped unless it is a non-empty
# string naming a real directory. An unconfigured array cannot cause a scan
# from an unintended location.
#
# Truncate-Never-Delete
# Action is always truncate -s 0, never rm. The container keeps its open file
# handle and space is reclaimed immediately, so a still-running service does
# not lose its log destination mid-write.
#
# Filename Restriction
# Only *.log and *.log.* files are ever truncation candidates. Databases,
# caches, game saves and every other growing file are alert-only — detected
# and reported, never modified.
#
# Truncation Opt-In
# WATCHDOG_APPDATA_TRUNCATE_LOGS defaults to false. Without it explicitly
# enabled the action cycle escalates to a critical notification and holds
# strikes rather than touching any file.
#
# Strike Threshold
# Nothing acts on first detection. WATCHDOG_APPDATA_STRIKE_LIMIT consecutive
# cycles are required, separating a legitimate library scan or save burst
# from a genuine runaway. Strikes auto-clear when the condition resolves.
#
# Suppress Ceiling
# Containers listed in WATCHDOG_APPDATA_SIZES are exempt from growth alerts
# while under their configured ceiling — prevents known-large stable data
# from generating recurring false alarms.
#
# Dry Run Support
# --dry-run reports every truncation that would occur and performs none.
#
# Atomic Baseline Update
# The growth baseline is written to a temp file and moved into place, so an
# interrupted run cannot leave a half-written baseline that would read as
# false growth on the next cycle.
#
# ==============================================================================================
# CONFIGURATION
# ==============================================================================================
#