diff --git a/.env.example b/.env.example index 081d0db..97e509a 100644 --- a/.env.example +++ b/.env.example @@ -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" diff --git a/backup/Dockerfile b/backup/Dockerfile index 8e1d651..104ac6e 100644 --- a/backup/Dockerfile +++ b/backup/Dockerfile @@ -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 diff --git a/backup/backup.sh b/backup/backup.sh index 2e596bd..cc415f7 100644 --- a/backup/backup.sh +++ b/backup/backup.sh @@ -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 diff --git a/backup/entrypoint.sh b/backup/entrypoint.sh index b063181..238abae 100644 --- a/backup/entrypoint.sh +++ b/backup/entrypoint.sh @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 44d47c3..cc447eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: