Make backup script role-aware and add manual-backup docs

Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/a9c69b4d-1c8d-4ade-b444-33043e52fc63

Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-05 04:43:04 +00:00
committed by GitHub
parent a1d83e731a
commit 64744d1d93
2 changed files with 166 additions and 10 deletions

View File

@@ -18,6 +18,8 @@ BACKUP_LOG="/var/log/sovran-hub-backup.log"
BACKUP_STATUS="/var/log/sovran-hub-backup.status"
MEDIA_ROOT="/run/media"
MIN_FREE_GB=10
HUB_CONFIG_JSON="/var/lib/sovran-hub/config.json"
ROLE_STATE_NIX="/etc/nixos/role-state.nix"
# ── Internal drive labels/paths to NEVER use as backup targets ───
INTERNAL_LABELS=("BTCEcoandBackup" "sovran_systemsos")
@@ -125,6 +127,40 @@ for d in flatten(data.get('blockdevices', [])):
echo "$target"
}
# ── Detect the configured system role ───────────────────────────
#
# Priority:
# 1. Hub config JSON (/var/lib/sovran-hub/config.json) — "role" key
# 2. role-state.nix (/etc/nixos/role-state.nix) — grep for true flag
# 3. Default: server_plus_desktop
detect_role() {
local role="server_plus_desktop"
# 1. Try the Hub config JSON
if [[ -f "$HUB_CONFIG_JSON" ]] && command -v python3 &>/dev/null; then
local r
r=$(python3 -c \
"import json,sys; d=json.load(open(sys.argv[1])); print(d.get('role',''))" \
"$HUB_CONFIG_JSON" 2>/dev/null || true)
if [[ -n "$r" ]]; then
echo "$r"
return
fi
fi
# 2. Fall back to parsing role-state.nix
if [[ -f "$ROLE_STATE_NIX" ]]; then
if grep -q 'roles\.desktop = lib\.mkDefault true' "$ROLE_STATE_NIX" 2>/dev/null; then
role="desktop"
elif grep -q 'roles\.node = lib\.mkDefault true' "$ROLE_STATE_NIX" 2>/dev/null; then
role="node"
fi
fi
echo "$role"
}
# ── Initialise log file ──────────────────────────────────────────
: > "$BACKUP_LOG"
@@ -133,6 +169,17 @@ set_status "RUNNING"
log "=== Sovran_SystemsOS External Hub Backup ==="
log "Starting backup process…"
# ── Detect system role ───────────────────────────────────────────
ROLE="$(detect_role)"
case "$ROLE" in
desktop) ROLE_LABEL="Desktop Only" ;;
node) ROLE_LABEL="Node (Bitcoin-only)" ;;
server_plus_desktop) ROLE_LABEL="Server + Desktop" ;;
*) ROLE_LABEL="$ROLE" ;;
esac
log "Detected role: $ROLE_LABEL"
# ── Detect target drive ──────────────────────────────────────────
if [[ -n "${BACKUP_TARGET:-}" ]]; then
@@ -190,16 +237,29 @@ log ""
log "── Stage 2/4: Secrets ───────────────────────────────────────"
mkdir -p "$BACKUP_DIR/secrets"
for SRC in /etc/nix-bitcoin-secrets /var/lib/domains; do
if [[ "$ROLE" == "desktop" ]]; then
log "Skipping /etc/nix-bitcoin-secrets — not applicable for Desktop Only role."
# /var/lib/domains is still backed up if present (hub state)
for SRC in /var/lib/domains; do
if [[ -e "$SRC" ]]; then
rsync -a --info=progress2 "$SRC" "$BACKUP_DIR/secrets/" 2>&1 | tee -a "$BACKUP_LOG" || \
log "WARNING: Could not copy $SRC — continuing."
else
log " (not found: $SRC — skipping)"
fi
done
done
else
for SRC in /etc/nix-bitcoin-secrets /var/lib/domains; do
if [[ -e "$SRC" ]]; then
rsync -a --info=progress2 "$SRC" "$BACKUP_DIR/secrets/" 2>&1 | tee -a "$BACKUP_LOG" || \
log "WARNING: Could not copy $SRC — continuing."
else
log " (not found: $SRC — skipping)"
fi
done
fi
# Hub state files from /var/lib/secrets/
# Hub state files from /var/lib/secrets/ (backed up for all roles)
if [[ -d /var/lib/secrets ]]; then
mkdir -p "$BACKUP_DIR/secrets/hub-state"
rsync -a --info=progress2 /var/lib/secrets/ "$BACKUP_DIR/secrets/hub-state/" 2>&1 | tee -a "$BACKUP_LOG" || \
@@ -230,7 +290,9 @@ fi
log ""
log "── Stage 4/4: Wallet and node data (/var/lib/lnd) ──────────"
if [[ -d /var/lib/lnd ]]; then
if [[ "$ROLE" == "desktop" ]]; then
log "Skipping Stage 4 (LND wallet data) — not applicable for Desktop Only role."
elif [[ -d /var/lib/lnd ]]; then
rsync -a --info=progress2 \
--exclude='logs/' \
/var/lib/lnd/ "$BACKUP_DIR/lnd/" 2>&1 | tee -a "$BACKUP_LOG" || \
@@ -248,6 +310,7 @@ log "Generating BACKUP_MANIFEST.txt …"
echo "Sovran_SystemsOS Backup Manifest"
echo "Generated: $(date)"
echo "Hostname: $(hostname)"
echo "Role: $ROLE_LABEL"
echo "Target: $TARGET"
echo ""
echo "Contents:"

93
docs/manual-backup.md Normal file
View File

@@ -0,0 +1,93 @@
# Sovran Hub — Manual Backup
The manual backup service copies critical system data from your Sovran Pro to an external USB drive, providing a third copy of your data (your Sovran Pro already maintains an automatic internal backup on its second drive).
Backups are written to:
```
<USB drive>/Sovran_SystemsOS_Backup/<timestamp>/
```
where `<timestamp>` is formatted as `YYYYMMDD_HHMMSS`.
---
## Backup Stages
The script always attempts all four stages, but skips stages that are irrelevant to the system's configured role (see [Per-Role Breakdown](#per-role-breakdown) below).
| Stage | Directory | Contents |
|-------|-----------|----------|
| **1/4 — NixOS config** | `/etc/nixos/` | Full NixOS system configuration: `role-state.nix`, `custom.nix`, flake files, and any other config managed by the Hub |
| **2/4 — Secrets** | `/etc/nix-bitcoin-secrets`, `/var/lib/domains`, `/var/lib/secrets` | Bitcoin/LND secrets, domain configurations for all web services, and Hub state files |
| **3/4 — Home directory** | `/home/` | All user home directories (`.cache/` and Trash are excluded) |
| **4/4 — LND wallet data** | `/var/lib/lnd/` | Lightning Network node wallet and channel data (log files excluded) |
---
## Per-Role Breakdown
The script detects the system role at runtime by reading `/var/lib/sovran-hub/config.json` (falling back to `/etc/nixos/role-state.nix`) and adjusts its behaviour accordingly.
### Server + Desktop (default)
All services are enabled: Bitcoin, Matrix Synapse, Vaultwarden, WordPress, Nextcloud.
| Stage | Status | Notes |
|-------|--------|-------|
| Stage 1 — NixOS config | ✅ Backed up | Full server configuration |
| Stage 2 — Secrets | ✅ Backed up | Bitcoin secrets, domain configs, and Hub state |
| Stage 3 — Home directory | ✅ Backed up | Desktop user data |
| Stage 4 — LND wallet | ✅ Backed up | Lightning wallet and channel data |
This produces the largest backup. All four stages generate meaningful data.
### Desktop Only
All server services are disabled (`bitcoin = false`, `synapse = false`, `vaultwarden = false`, `wordpress = false`, `nextcloud = false`). Only GNOME desktop is active.
| Stage | Status | Notes |
|-------|--------|-------|
| Stage 1 — NixOS config | ✅ Backed up | Simpler config (no server services) |
| Stage 2 — Secrets | ⚠️ Partial | `/etc/nix-bitcoin-secrets` is **skipped** (not applicable for Desktop Only role). `/var/lib/domains` and `/var/lib/secrets` (Hub state) are still backed up if present |
| Stage 3 — Home directory | ✅ Backed up | **The most important data for this role** |
| Stage 4 — LND wallet | ⏭️ Skipped | Explicitly skipped — not applicable for Desktop Only role |
This produces the smallest and fastest backup. Stages 1 and 3 are the primary sources of meaningful data.
### Node (Bitcoin-only)
Only the Bitcoin ecosystem is active: `bitcoind`, `electrs`, `lnd`, `rtl`, `btcpay`, `mempool`, and `bip110`. All other server services are disabled.
| Stage | Status | Notes |
|-------|--------|-------|
| Stage 1 — NixOS config | ✅ Backed up | Node-specific configuration |
| Stage 2 — Secrets | ✅ Backed up | Bitcoin secrets and Hub state. `/var/lib/domains` may be minimal (BTCPay runs but is not exposed via Caddy) |
| Stage 3 — Home directory | ✅ Backed up | User data |
| Stage 4 — LND wallet | ✅ Backed up | **Critical** — Lightning wallet and channel data |
All four stages run, matching Server + Desktop behaviour. The `/var/lib/domains` directory may be sparsely populated since non-Bitcoin web services are not configured.
---
## Backup Manifest
After all stages complete, the script writes a `BACKUP_MANIFEST.txt` file inside the timestamped backup directory. This file records the date, hostname, detected role, target drive, and a directory listing of everything that was backed up.
---
## Running the Backup
The backup is triggered from the Sovran Hub web UI. You can also run it directly:
```bash
# Auto-detect the first external USB drive
sudo bash /path/to/sovran-hub-backup.sh
# Specify a target drive explicitly
sudo BACKUP_TARGET=/run/media/<user>/<drive> bash /path/to/sovran-hub-backup.sh
```
The script requires at least **10 GB** of free space on the target drive and will refuse to write to internal system drives.
Logs are written to `/var/log/sovran-hub-backup.log` and the current status (`RUNNING`, `SUCCESS`, or `FAILED`) is tracked in `/var/log/sovran-hub-backup.status`.