From 68aae4715d067844c84b5aab07fd1d825e0d451e Mon Sep 17 00:00:00 2001 From: Christoph Haas Date: Sat, 19 Sep 2026 22:20:31 +0000 Subject: [PATCH] Add failure logging/email notification in backup.sh; harden /opt/backup perms --- .gitignore | 3 +++ README.backup.md | 28 +++++++++++++++++++++++++++- backup.env.sample | 3 +++ backup.sh | 24 ++++++++++++++++++++---- setup.sh | 4 ++++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 86d713d..0bab2b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # Secrets — never commit backup.env encryption-key.pem + +# Runtime artifacts +backup.log diff --git a/README.backup.md b/README.backup.md index 4798224..a16d1f7 100644 --- a/README.backup.md +++ b/README.backup.md @@ -11,13 +11,14 @@ Everything lives in `/opt/backup/`. The connection details and credentials | File | Purpose | |------|---------| -| `backup.env` | Shared config: server, datastore, namespace, token, key (sourced by both scripts) | +| `backup.env` | Shared config: server, datastore, namespace, token, key, `NOTIFY_EMAIL` (sourced by both scripts) | | `backup.env.sample` | Template — copy to `backup.env` and fill in real values | | `setup.sh` | Install the client and create the encryption key (run once as root) | | `backup.sh` | Full backup of `/` with client-side encryption | | `restore.sh` | FUSE-mount the encrypted backup to browse/restore files | | `/opt/backup/encryption-key.pem` | Client-side encryption key (store a copy off-host!) | | `/opt/backup/exclude` | Paths excluded from the backup | +| `backup.log` | Runtime log written by `backup.sh` (created on each run) | ## Setup @@ -53,6 +54,31 @@ chmod 600 backup.env 4. The client encrypts everything locally before uploading. The PBS only ever stores ciphertext and cannot read the data. +The client's output is piped through `tee` into `backup.log` (the file in the +table above). The script captures the client's exit status (via +`set -o pipefail`), so a failed `proxmox-backup-client` run is detected even +though its output went through `tee`. + +## Failure notification + +If `backup.sh` fails it exits non-zero and, when a mailbox is configured, emails +the failure. Set it in `backup.env`: + +``` +NOTIFY_EMAIL="admin@example.com" +``` + +Empty (the default) disables mail. There is no `mail`/`mailx` on the hosts, so +the script sends via Postfix directly with: + +``` +... | sendmail -t +``` + +The mail body contains the timestamp and the contents of `backup.log`. This only +covers `backup.sh` failures — a *missed* run (host down, cron removed) is not +detected, so monitor the PBS side too. + Encryption is client-side. The key file has no password (`kdf: none`), so **possession of the key file alone is enough to decrypt** — protect it. diff --git a/backup.env.sample b/backup.env.sample index f9c72f7..946e8ec 100644 --- a/backup.env.sample +++ b/backup.env.sample @@ -27,3 +27,6 @@ ENCRYPTION_KEY_FILE="/opt/backup/encryption-key.pem" # --- Local hostname (used as backup-id and archive-name prefix) --- HOSTNAME="$(hostname)" + +# --- Failure notification (email sent on backup failure; empty = disabled) --- +NOTIFY_EMAIL="admin@example.com" diff --git a/backup.sh b/backup.sh index f06865c..9f2bb9c 100755 --- a/backup.sh +++ b/backup.sh @@ -10,6 +10,7 @@ source "$SCRIPT_DIR/backup.env" # ============================================================================== BACKUP_LABEL="${HOSTNAME}-root" EXCLUDE_FILE="/opt/backup/exclude" +LOG_FILE="${SCRIPT_DIR}/backup.log" # ============================================================================== # EXECUTION @@ -27,9 +28,24 @@ while IFS= read -r line; do EXCLUDE_ARGS+=(--exclude "$line") done < "$EXCLUDE_FILE" -proxmox-backup-client backup "${BACKUP_LABEL}.pxar:/" \ +if proxmox-backup-client backup "${BACKUP_LABEL}.pxar:/" \ --keyfile "$ENCRYPTION_KEY_FILE" \ --ns "$BACKUP_NS" \ - "${EXCLUDE_ARGS[@]}" - -echo "Backup completed successfully!" + "${EXCLUDE_ARGS[@]}" 2>&1 | tee "$LOG_FILE"; then + echo "Backup completed successfully!" +else + echo "Backup FAILED — see $LOG_FILE" >&2 + if [ -n "${NOTIFY_EMAIL:-}" ]; then + { + echo "To: $NOTIFY_EMAIL" + echo "From: root@${HOSTNAME}" + echo "Subject: [pbs-backup] FAILED on ${HOSTNAME}" + echo + echo "The Proxmox backup on ${HOSTNAME} failed at $(date -R)." + echo + echo "--- backup log ---" + cat "$LOG_FILE" + } | sendmail -t + fi + exit 1 +fi diff --git a/setup.sh b/setup.sh index a9f3086..ac3f5a3 100755 --- a/setup.sh +++ b/setup.sh @@ -107,6 +107,10 @@ EOF systemctl daemon-reload systemctl enable --now pbs-backup.timer +# 8. Hardening: deny read/write/execute to "others" for everything in /opt/backup +# (including the directory itself), so config and backups are not world-readable. +chmod -R o-rwx "$BACKUP_DIR" + echo echo "Done. Fill in the credentials, then run ${BACKUP_DIR}/backup.sh (or wait for the timer):" echo " ${EDITOR:-vi} ${ENV_FILE} # server/datastore/namespace/token"