Backup: switch NAS transport to SMB/CIFS, fix crond/OOM/live-tar defects

Wire the backup service against the Synology share over SMB/CIFS (the NAS
authenticates with a user/password; NFS is IP-allowlist only). Also fixes
three defects found bringing the service up live:

- entrypoint crash-loop: dcron's crond fails "setpgid: Operation not
  permitted" in this runtime -> use busybox crond; make repo-init idempotent
  (check via `restic cat config`, tolerate init-on-existing) so a transient
  CIFS/lock hiccup can't kill PID 1.
- OOM: pg_dump of a ~276MB DB + tar + restic exceeded the 256M cap -> 1G.
- live tar abort: GNU tar exits 1 when TDLib files change mid-read (worker is
  live); per design this is best-effort, so tolerate exit 1, fatal only >=2.

Kuma push is now optional (empty URL disables alerting) since it's deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-24 11:36:19 +02:00
co-authored by Claude Opus 4.8
parent ceae4f384b
commit 2e7e6cca9b
5 changed files with 30 additions and 16 deletions
+5 -3
View File
@@ -37,9 +37,11 @@ WORKER_MAX_ZIP_SIZE_MB=4096
MULTIPART_TIMEOUT_HOURS=0
LOG_LEVEL="info"
# Backup (NAS via NFS + restic)
# Backup (NAS via SMB/CIFS + restic)
NAS_HOST="" # Synology NAS IP or hostname reachable from this host
NAS_EXPORT_PATH="" # NFS export path, e.g. /volume1/dragonsstash-backups
NAS_SHARE="" # SMB share name, e.g. dragonsstash_backups
NAS_USERNAME="" # SMB user with read/write on the share
NAS_PASSWORD="" # SMB user password (avoid commas — they delimit cifs mount opts)
RESTIC_PASSWORD="" # generate with: openssl rand -base64 32
KUMA_PUSH_URL="" # Uptime Kuma Push monitor URL (create the monitor first)
KUMA_PUSH_URL="" # optional: Uptime Kuma Push monitor URL; leave empty to disable alerting
TZ="Etc/UTC"
+3 -1
View File
@@ -1,6 +1,8 @@
FROM alpine:3.20
RUN apk add --no-cache restic postgresql16-client curl tzdata dcron tar bash
# Note: use busybox's built-in crond (Alpine base), NOT the dcron package —
# dcron's crond fails with "setpgid: Operation not permitted" in this runtime.
RUN apk add --no-cache restic postgresql16-client curl tzdata tar bash
COPY backup/backup.sh /backup.sh
COPY backup/entrypoint.sh /entrypoint.sh
+11 -4
View File
@@ -2,6 +2,7 @@
set -euo pipefail
report_failure() {
[ -n "${KUMA_PUSH_URL:-}" ] || return 0
curl -fsS "$KUMA_PUSH_URL" --get \
--data-urlencode "status=down" \
--data-urlencode "msg=$BASH_COMMAND failed" || true
@@ -15,11 +16,17 @@ trap 'rm -f "$DUMP_FILE" "$TAR_FILE"' EXIT
pg_dump -h dragonsstash-db -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc -f "$DUMP_FILE"
tar czf "$TAR_FILE" -C /data tdlib-worker tdlib-bot
# TDLib volumes are tarred live (best-effort, per design). A file changing
# mid-read makes GNU tar exit 1 (warning) — that is expected here and must not
# abort the backup. Only a genuine error (exit >= 2) is fatal.
tar --warning=no-file-changed -czf "$TAR_FILE" -C /data tdlib-worker tdlib-bot \
|| { rc=$?; [ "$rc" -le 1 ] || exit "$rc"; }
restic backup "$DUMP_FILE" "$TAR_FILE"
restic forget --keep-daily 14 --prune
curl -fsS "$KUMA_PUSH_URL" --get \
--data-urlencode "status=up" \
--data-urlencode "msg=OK"
if [ -n "${KUMA_PUSH_URL:-}" ]; then
curl -fsS "$KUMA_PUSH_URL" --get \
--data-urlencode "status=up" \
--data-urlencode "msg=OK"
fi
+6 -3
View File
@@ -1,8 +1,11 @@
#!/bin/bash
set -euo pipefail
set -uo pipefail
if ! restic snapshots >/dev/null 2>&1; then
restic init
# Ensure the repo exists, but never crash-loop on it: a transient error reading
# the repo (CIFS hiccup, stale lock) must not kill PID 1. `restic init` failing
# because the repo already exists is expected and harmless here.
if ! restic cat config >/dev/null 2>&1; then
restic init || echo "restic init skipped (repo already exists or temporarily unreachable)"
fi
exec crond -f -l 2
+5 -5
View File
@@ -109,7 +109,7 @@ services:
- POSTGRES_DB=${POSTGRES_DB:-dragonsstash}
- RESTIC_REPOSITORY=/backups/restic-repo
- RESTIC_PASSWORD=${RESTIC_PASSWORD:?Set RESTIC_PASSWORD in .env}
- KUMA_PUSH_URL=${KUMA_PUSH_URL:?Set KUMA_PUSH_URL in .env}
- KUMA_PUSH_URL=${KUMA_PUSH_URL:-}
- TZ=${TZ:-Etc/UTC}
volumes:
- tdlib_state:/data/tdlib-worker:ro
@@ -122,7 +122,7 @@ services:
deploy:
resources:
limits:
memory: 256M
memory: 1G
networks:
- backend
@@ -158,9 +158,9 @@ volumes:
manual_uploads:
nas_backups:
driver_opts:
type: nfs
o: "addr=${NAS_HOST},rw,nfsvers=4,soft,timeo=100"
device: ":${NAS_EXPORT_PATH}"
type: cifs
o: "username=${NAS_USERNAME},password=${NAS_PASSWORD},vers=3.0,uid=0,gid=0,file_mode=0660,dir_mode=0770"
device: "//${NAS_HOST}/${NAS_SHARE}"
networks:
frontend: