7f11cfb36c
Makes PBS DR storage visible like the restic pool box (v0.64.0), differentiated. Scoping
correction: restic = subaccounts on the shared Hetzner Storage Box (Hetzner API); PBS DR =
the felhom-offsite PBS datastore on the ep0 endpoint VM (NO Hetzner API). Option A
(Viktor-ruled): a read-only `usage` op on the felhom-tenantsync ep0 forced command (twin of
fingerprint), polled by a new hub checker on the 15-min throttle. READ-ONLY throughout.
Phase-0 (gate PASSED): on ep0 (PBS 4.2.3), df -B1 --output=size,used,avail <datastore path>
yields bytes (39990112256/7627939840/... ~19%), read-only, existing sudo context, no admin token.
- scripts/felhom-tenantsync.sh -> v1.2.0: read-only `usage` short-circuit (df on the datastore
path), no customer_id, no admin token, NO mutation. + a bash harness proving zero mutation.
- tenantsync.Client.Usage() + BoxUsage; unknown-op -> typed ErrUsageUnsupported (graceful).
- monitor.PBSDRBoxChecker: OffsiteBoxChecker clone over a usageReader seam; 15-min throttle,
cached PBSBoxSnapshot, escalation-only pbsdr_box_fill on the "pbsdr-box" scope (operator only,
no SaveEvent), recovery re-arm. Fill only. THREE states: ok / unavailable (ep0 <=v1.1.0,
neutral no-alert) / degraded (exec failed, keep last).
- config: Alerting.PBSDRBoxFill{Warn,Crit}Percent (80/90); built with the tenantsync client,
60s sweep, SetPBSDRBox. Hub deploy INDEPENDENT of the ep0 update (graceful degradation).
- web: /offsite splits into Restic + PBS DR hash tabs (endpoint cards under PBS DR); PBS panel;
the single dashboard tile becomes two gauges (RESTIC pct.ratio, PBS DR pct / n/a).
- runbook offsite-endpoint.md 10: v1.2.0 update steps (no sudoers/authorized_keys change).
Tests: 10 Go + the harness; 3 red-proofs (usage mutation, escalation-only, unavailable-drives-band)
confirmed red then restored. go build/vet/test + bash -n + hub confirm gate all pass.
69 lines
3.1 KiB
Bash
69 lines
3.1 KiB
Bash
#!/usr/bin/env bash
|
|
# tenantsync-usage-harness.sh (v0.65.0, R-5) — Group-A test for the felhom-tenantsync `usage` op.
|
|
# Stubs proxmox-backup-manager + df on PATH (real jq), runs the op, and asserts:
|
|
# 1. usage → {"status":"ok","total","used","avail"} + exit 0 + ZERO mutation calls.
|
|
# 2. provision (regression) → reaches the mutation path (admin-token generate) — the usage op did
|
|
# not disturb the case dispatch — and NEVER emits the usage JSON.
|
|
# Non-hollow: the stub LOGS every proxmox-backup-manager invocation; the zero-mutation assertion reads it.
|
|
set -uo pipefail
|
|
|
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
SCRIPT="$HERE/felhom-tenantsync.sh"
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
STUB="$WORK/bin"; mkdir -p "$STUB"
|
|
export MUTLOG="$WORK/mutations.log"; : > "$MUTLOG"
|
|
export CALLLOG="$WORK/calls.log"; : > "$CALLLOG"
|
|
DS_PATH="$WORK/ds"; mkdir -p "$DS_PATH"
|
|
|
|
# Stub proxmox-backup-manager: read-only ops answered; MUTATING subcommands logged to MUTLOG.
|
|
cat > "$STUB/proxmox-backup-manager" <<'PBM'
|
|
#!/usr/bin/env bash
|
|
echo "pbm $*" >> "$CALLLOG"
|
|
case "$1 $2" in
|
|
"cert info") echo "Fingerprint (sha256): aa:bb:cc:dd" ;;
|
|
"datastore list") printf '[{"name":"scratch","path":"/srv/pbs-scratch"},{"name":"felhom-offsite","path":"%s"}]\n' "$DS_PATH" ;;
|
|
"user generate-token") echo "MUTATION: $*" >> "$MUTLOG"; echo '{"value":"FAKE-ADMIN-TOKEN"}' ;;
|
|
"user delete-token") echo "MUTATION: $*" >> "$MUTLOG" ;;
|
|
"acl update") echo "MUTATION: $*" >> "$MUTLOG" ;;
|
|
"namespace "*) echo "MUTATION: $*" >> "$MUTLOG" ;;
|
|
*) echo "MUTATION: $*" >> "$MUTLOG" ;; # any other subcommand is mutating here
|
|
esac
|
|
PBM
|
|
chmod +x "$STUB/proxmox-backup-manager"
|
|
|
|
# Stub proxmox-backup-client (used only by the mutating ops; log if invoked).
|
|
cat > "$STUB/proxmox-backup-client" <<'PBC'
|
|
#!/usr/bin/env bash
|
|
echo "MUTATION: pbc $*" >> "$MUTLOG"
|
|
PBC
|
|
chmod +x "$STUB/proxmox-backup-client"
|
|
|
|
# Stub df: fixed bytes (total 41943040, used 8388608, avail 33554432 ⇒ 20% full).
|
|
cat > "$STUB/df" <<'DF'
|
|
#!/usr/bin/env bash
|
|
echo "1B-blocks Used Avail"
|
|
echo "41943040 8388608 33554432"
|
|
DF
|
|
chmod +x "$STUB/df"
|
|
|
|
export DS_PATH CALLLOG MUTLOG
|
|
RC=0
|
|
fail() { echo "FAIL: $*"; RC=1; }
|
|
|
|
# --- Test 1: usage op ---
|
|
OUT=$(echo '{"op":"usage"}' | PATH="$STUB:$PATH" bash "$SCRIPT" 2>/dev/null); EC=$?
|
|
[ "$EC" -eq 0 ] || fail "usage exit $EC (want 0)"
|
|
EXP='{"status":"ok","total":41943040,"used":8388608,"avail":33554432}'
|
|
[ "$OUT" = "$EXP" ] || fail "usage stdout = $OUT (want $EXP)"
|
|
if [ -s "$MUTLOG" ]; then fail "usage ran mutations: $(cat "$MUTLOG")"; else echo "PASS: usage emits totals, exit 0, ZERO mutations"; fi
|
|
|
|
# --- Test 2: provision regression (dispatch intact; usage op did not break the case) ---
|
|
: > "$MUTLOG"
|
|
POUT=$(echo '{"op":"provision","customer_id":"harness"}' | PATH="$STUB:$PATH" bash "$SCRIPT" 2>/dev/null || true)
|
|
grep -q "generate-token" "$MUTLOG" || fail "provision did not reach the mutation path (case dispatch broken by the usage op?)"
|
|
case "$POUT" in *'"total"'*) fail "provision leaked the usage shape: $POUT" ;; esac
|
|
[ "$RC" -eq 0 ] && echo "PASS: provision still dispatches to the mutation path (regression)"
|
|
|
|
exit $RC
|