Nextcloud biztonsági mentés Hetzner Storage Boxba

Az otthoni NAS-on futó Nextcloud kényelmes és rugalmas megoldás, de nem szabad megfeledkezni a rendszeres, offsite backupról. Egy lemezhiba, ransomware vagy véletlen törlés bármikor megtörténhet.

Architektúra

Nextcloud (Docker)  
    ↓  
MariaDB dump + Nextcloud data mappa  
    ↓  
restic (titkosított, deduplikált backup)  
    ↓  
SFTP  
    ↓  
Hetzner Storage Box

Mi kerül mentésre?

Az esetemben konkrétan ezek:

/srv/docker/nextcloud/conf
/srv/docker/nextcloud/db
/mnt/data/nextcloud
+ MariaDB dump

A mentés eszköze: restic

A backup lépései

  1. Nextcloud maintenance mode bekapcsolása
  2. MariaDB dump készítése
  3. restic backup futtatása
  4. retention policy alkalmazása
  5. maintenance mode kikapcsolása
  6. success flag írása watchdog számára

A Storage Box-hoz a hozzáférés SSH kulccsal történik, nem jelszóval.

Az ~/.ssh/config példa:

Host hetzner-box
  HostName <host>.your-storagebox.de
  User <username>
  Port 23
  IdentityFile ~/.ssh/hetzner_storage

A backup script

A backup script esetemben a nextcloud-backup.sh fájl.
Többek közt feltételezi, hogy a MariaDB container environment változói (MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE) be vannak állítva:

#!/bin/bash

set -Eeuo pipefail

#####################################
# CONFIG
#####################################

ADMIN_EMAIL="user@email.tld"
HOSTNAME="$(hostname)"

APP_CONTAINER="nextcloud-compose"
DB_CONTAINER="nextcloud-compose-db"

COMPOSE_DIR="/srv/docker/nextcloud"
DATA_DIR="/mnt/data/nextcloud"

DB_DUMP="/root/nextcloud-db.sql"
LOCKFILE="/var/run/nextcloud-backup.lock"
LOGFILE="/var/log/nextcloud-backup.log"

export RESTIC_REPOSITORY="sftp:hetzner-box:/home/backup"
export RESTIC_PASSWORD_FILE="/root/.restic-pass"

#####################################
# FUNCTIONS
#####################################

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"
}

cleanup() {
    log "Disabling maintenance mode..."
    docker exec -u www-data "$APP_CONTAINER" php occ maintenance:mode --off || true
    rm -f "$LOCKFILE"
}

error_handler() {
    log "ERROR occurred. Cleaning up..."
    exit 1
}

trap cleanup EXIT
trap error_handler ERR

#####################################
# LOCK PROTECTION
#####################################

if [ -f "$LOCKFILE" ]; then
    log "Backup already running. Exiting."
    exit 1
fi

touch "$LOCKFILE"

log "===== BACKUP STARTED ====="

#####################################
# ENABLE MAINTENANCE MODE
#####################################

log "Enabling maintenance mode..."
docker exec -u www-data "$APP_CONTAINER" php occ maintenance:mode --on

#####################################
# DATABASE DUMP (container env-ből)
#####################################

log "Creating MariaDB dump..."

docker exec "$DB_CONTAINER" sh -c \
  'mariadb-dump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
  > "$DB_DUMP"

#####################################
# RESTIC BACKUP
#####################################

log "Running restic backup..."

restic backup \
    "$DATA_DIR" \
    "$COMPOSE_DIR/conf" \
    "$COMPOSE_DIR/db" \
    "$DB_DUMP" \
    --verbose >> "$LOGFILE" 2>&1

#####################################
# RETENTION POLICY
#####################################

log "Applying retention policy..."

restic forget --prune \
    --keep-daily 7 \
    --keep-weekly 4 \
    --keep-monthly 6 >> "$LOGFILE" 2>&1

log "Backup completed successfully."
touch /var/run/nextcloud-backup.success

# --- Weekly status email (Sunday = 7) ---
DAY_OF_WEEK=$(date +%u)

if [ "$DAY_OF_WEEK" -eq 7 ]; then
    STATS=$(restic stats latest --mode raw-data)
    SNAPSHOT_COUNT=$(restic snapshots | grep ID | wc -l)

    SUBJECT="Weekly Nextcloud Backup Status on $HOSTNAME"

    BODY="Weekly backup report

Hostname: $HOSTNAME
Date: $(date)

Snapshots stored: $SNAPSHOT_COUNT

Latest snapshot stats:
$STATS
"

    {
        echo "Subject: $SUBJECT"
        echo "From: $ADMIN_EMAIL"
        echo
        echo "$BODY"
    } | msmtp "$ADMIN_EMAIL"

    log "Weekly status email sent."
fi

exit 0

Napi futás 03:00-kor cronból:

0 3 * * * /srv/docker/nextcloud/nextcloud-backup.sh

A backup script siker esetén létrehozza a /var/run/nextcloud-backup.success fájlt. Ezt ellenőrzi egy watchdog. Abban az esetben ha ez a nextcloud-backup.success fájl nincs, vagy túl régi - nem volt sikeres backup, a beállított msmtp címre riasztó emailt küld. Ezen kívül küld egy heti státusz email-t.

A watchdog esetemben a nextcloud-backup-watchdog.sh így néz ki:

#!/bin/bash

ADMIN_EMAIL="user@email.tld"
HOSTNAME="$(hostname)"
SUCCESS_FILE="/var/run/nextcloud-backup.success"

if [ ! -f "$SUCCESS_FILE" ]; then
    exit 0
fi

LAST_RUN=$(stat -c %Y "$SUCCESS_FILE")

NOW=$(date +%s)

DIFF=$((NOW - LAST_RUN))

# 24 óra = 86400 másodperc
if [ "$DIFF" -gt 86400 ]; then

    SUBJECT="Nextcloud Backup NOT running on $HOSTNAME"
    BODY="No successful backup log update in the last 24 hours.

Last log modification:
$(date -d @$LAST_RUN)

Please check the system."

    {
        echo "Subject: $SUBJECT"
        echo "From: $ADMIN_EMAIL"
        echo
        echo "$BODY"
    } | msmtp "$ADMIN_EMAIL"

fi

Óránkénti futtatás cronból:

0 * * * * /srv/docker/nextcloud/nextcloud-backup-watchdog.sh

A watchdog script óránként fut le cronból, és minden egész órakor ellenőrzi, hogy az elmúlt 24 órában volt-e sikeres backup.

Visszaállítás teszt

A mentés addig nem backup, amíg nincs restore tesztelve.

restic restore latest --target /tmp/restore-test

Eredményként hibamentes visszaállítást kell kapnunk a /tmp/restore-test mappába.