Pri väčšom množstve Docker služieb a Linux serverov som potreboval vytvoriť jednotný spôsob zálohovania na lokálny Synology NAS.

    Cieľom bolo vytvoriť riešenie, ktoré:

    • automaticky zálohuje Linux server,
    • používa šifrované Restic repository,
    • ukladá dáta na Synology NAS cez NFS,
    • vytvára konzistentné dumpy databáz,
    • používa deduplikáciu,
    • automaticky maže staré snapshoty,
    • spúšťa sa cez systemd timer,
    • po dokončení odošle e-mail BACKUP OK alebo BACKUP FAILED.

    Použitá retention politika:

    7 denných snapshotov
    4 týždenné snapshoty
    6 mesačných snapshotov

    Čo budeme potrebovať?

    Linux server
    Synology NAS
    NFS
    Restic
    systemd
    msmtp
    SMTP server

    V tomto návode budeme používať ukážkové hodnoty:

    Linux server:  192.0.2.10
    Synology NAS:  192.0.2.20
    
    NAS share:
    /volume1/NetBackup
    
    Lokálny mount:
    /mnt/nas-backup
    
    Restic repository:
    /mnt/nas-backup/BACKUP/server01/restic

    Reálne IP adresy a názvy si samozrejme nahraďte vlastnými.

    Príprava Synology NAS

    Na Synology vytvoríme zdieľaný priečinok, napríklad:

    NetBackup

    Výsledná cesta môže byť:

    /volume1/NetBackup

    Pre daný Linux server následne povolíme NFS prístup.

    Odporúčam povoliť prístup iba konkrétnej IP adrese alebo subnetu, z ktorého sa bude backup vykonávať.

    Pripojenie NAS cez NFS

    Na Linux serveri vytvoríme mount point:

    sudo mkdir -p /mnt/nas-backup

    Nainštalujeme NFS klienta:

    sudo apt update
    sudo apt install nfs-common

    Do:

    sudo nano /etc/fstab

    Pridáme:

    192.0.2.20:/volume1/NetBackup /mnt/nas-backup nfs4 rw,vers=4.1,_netdev,nofail,x-systemd.automount 0 0

    Aplikujeme:

    sudo systemctl daemon-reload

    Mount môžeme aktivovať napríklad:

    ls /mnt/nas-backup

    Kontrola:

    mountpoint /mnt/nas-backup

    Správny výsledok:

    /mnt/nas-backup is a mountpoint

    Inštalácia Restic

    Na Ubuntu:

    sudo apt update
    sudo apt install restic

    Verziu skontrolujeme:

    restic version

    Vytvorenie Restic hesla

    Najprv vytvoríme adresár:

    sudo mkdir -p /root/.config/restic
    sudo chmod 700 /root/.config/restic

    Vygenerujeme silné náhodné heslo:

    openssl rand -base64 48

    Výsledné heslo uložíme:

    sudo nano /root/.config/restic/server01.pass

    Nastavíme práva:

    sudo chmod 600 /root/.config/restic/server01.pass

    Toto heslo je kritické. Bez neho nie je možné Restic repository obnoviť.

    Je preto potrebné uložiť jeho kópiu aj mimo samotného servera.

    Vytvorenie Restic repository

    Na NAS vytvoríme adresár:

    sudo mkdir -p /mnt/nas-backup/BACKUP/server01/restic

    Nastavíme heslo:

    export RESTIC_PASSWORD_FILE=/root/.config/restic/server01.pass

    A inicializujeme repository:

    restic -r /mnt/nas-backup/BACKUP/server01/restic init

    Prvý manuálny backup

    Napríklad:

    restic -r /mnt/nas-backup/BACKUP/server01/restic backup \
        /etc \
        /home \
        --tag server01

    Po úspešnom backupe Restic vytvorí nový snapshot.

    Snapshoty zobrazíme:

    restic -r /mnt/nas-backup/BACKUP/server01/restic snapshots

    Ako Restic funguje

    Restic nevytvára pri každom spustení kompletnú fyzickú kópiu všetkých dát.

    Používa:

    • snapshoty
    • deduplikáciu
    • kompresiu
    • šifrovanie

    Ak sa teda medzi dvoma backupmi väčšina súborov nezmenila, Restic znovu neuloží identické bloky dát.

    Nový snapshot preto môže reprezentovať napríklad niekoľko GB dát, ale fyzicky do repository pribudne iba niekoľko MB.

    Príklad:

    processed 22000 files, 4.6 GiB
    Added to repository: 25 MiB

    Backup databáz

    Databázový adresár by sme nemali jednoducho kopírovať počas behu databázového servera.

    Pre MariaDB vytvoríme pred Restic snapshotom konzistentný SQL dump.

    Príklad:

    docker exec aplikacia-db sh -lc '
    DUMP="$(command -v mariadb-dump || command -v mysqldump)"
    
    "$DUMP" \
        -u"$MYSQL_USER" \
        -p"$MYSQL_PASSWORD" \
        --single-transaction \
        --quick \
        "$MYSQL_DATABASE"
    ' | gzip -1 > /var/backups/aplikacia.sql.gz

    Integritu gzip súboru môžeme skontrolovať:

    gzip -t /var/backups/aplikacia.sql.gz

    Ak príkaz nič nevypíše a skončí exit code 0, archív je v poriadku.

    Automatický backup skript

    Vytvoríme:

    sudo nano /usr/local/sbin/backup-server01.sh

    Príklad kompletného skriptu:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail
    umask 077
    
    REPO="/mnt/nas-backup/BACKUP/server01/restic"
    PASSWORD_FILE="/root/.config/restic/server01.pass"
    TAG="server01"
    
    NAS="/mnt/nas-backup"
    STAGE="/var/backups/server01"
    
    LOCK_FILE="/run/lock/backup-server01.lock"
    
    export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"
    
    log() {
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    }
    
    # Zabránenie súbežnému spusteniu
    exec 9>"$LOCK_FILE"
    
    if ! flock -n 9; then
        log "ERROR: Backup už beží."
        exit 1
    fi
    
    # Kontrola Restic hesla
    if [[ ! -r "$PASSWORD_FILE" ]]; then
        log "ERROR: Restic password file neexistuje."
        exit 1
    fi
    
    # Aktivácia systemd automountu
    ls "$NAS" >/dev/null 2>&1 || true
    
    # Kontrola NAS
    if ! mountpoint -q "$NAS"; then
        log "ERROR: NAS nie je pripojený."
        exit 1
    fi
    
    # Kontrola repository
    if [[ ! -d "$REPO" ]]; then
        log "ERROR: Restic repository neexistuje."
        exit 1
    fi
    
    mkdir -p "$STAGE"
    chmod 700 "$STAGE"
    
    log "=== Restic backup ==="
    
    restic -r "$REPO" backup \
        /etc \
        /home \
        "$STAGE" \
        --exclude='/home/*/.cache/**' \
        --tag "$TAG"
    
    log "=== Retention ==="
    
    restic -r "$REPO" forget \
        --keep-daily 7 \
        --keep-weekly 4 \
        --keep-monthly 6 \
        --group-by host,tags \
        --tag "$TAG" \
        --prune
    
    log "============================================"
    log "Backup úspešne dokončený."
    log "============================================"

    Nastavíme práva:

    sudo chmod 750 /usr/local/sbin/backup-server01.sh

    Syntax skontrolujeme:

    sudo bash -n /usr/local/sbin/backup-server01.sh

    Retention politika

    V našom skripte používame:

    restic -r "$REPO" forget \
        --keep-daily 7 \
        --keep-weekly 4 \
        --keep-monthly 6 \
        --group-by host,tags \
        --tag "$TAG" \
        --prune

    To znamená:

    7 posledných denných backupov
    4 týždenné backupy
    6 mesačných backupov

    Parameter:

    --prune

    následne odstráni dátové bloky, ktoré už nepoužíva žiadny zachovaný snapshot.

    Systemd service

    Vytvoríme:

    sudo nano /etc/systemd/system/backup-server01.service

    Obsah:

    [Unit]
    Description=Restic backup SERVER01
    Wants=network-online.target
    After=network-online.target
    
    [Service]
    Type=oneshot
    ExecStart=/usr/local/sbin/backup-server01.sh
    
    User=root
    Group=root

    Systemd timer

    Vytvoríme:

    sudo nano /etc/systemd/system/backup-server01.timer

    Napríklad backup každý deň približne o 02:30:

    [Unit]
    Description=Daily Restic backup SERVER01
    
    [Timer]
    OnCalendar=*-*-* 02:30:00
    RandomizedDelaySec=300
    Persistent=true
    
    [Install]
    WantedBy=timers.target

    Načítame konfiguráciu:

    sudo systemctl daemon-reload

    Timer aktivujeme:

    sudo systemctl enable --now backup-server01.timer

    Kontrola:

    systemctl list-timers --all | grep backup-server01

    Manuálny test

    Service môžeme spustiť ručne:

    sudo systemctl start backup-server01.service

    Stav:

    sudo systemctl status backup-server01.service

    Log:

    sudo journalctl -u backup-server01.service -n 100 --no-pager

    E-mailové notifikácie

    Na odosielanie informácie o stave backupu môžeme použiť msmtp.

    Inštalácia:

    sudo apt install msmtp msmtp-mta

    Konfigurácia:

    sudo nano /etc/msmtprc

    Príklad:

    defaults
    auth on
    tls on
    tls_starttls on
    tls_trust_file /etc/ssl/certs/ca-certificates.crt
    
    account backup
    host mail.example.com
    port 587
    from backup@example.com
    user backup@example.com
    passwordeval "cat /root/.config/msmtp/backup.pass"
    
    account default : backup

    Nastavíme:

    sudo chmod 600 /etc/msmtprc

    SMTP heslo:

    sudo nano /root/.config/msmtp/backup.pass

    Práva:

    sudo chmod 600 /root/.config/msmtp/backup.pass

    Test:

    printf 'Subject: Backup test\n\nSMTP funguje.\n' \
    | msmtp admin@example.com

    Backup OK / FAILED e-mail

    Do backup skriptu môžeme doplniť:

    MAIL_TO="admin@example.com"
    MAIL_FROM="backup@example.com"
    SERVER_NAME="SERVER01"
    
    send_backup_mail() {
        local SUBJECT="$1"
        local BODY="$2"
    
        {
            printf 'From: %s\n' "$MAIL_FROM"
            printf 'To: %s\n' "$MAIL_TO"
            printf 'Subject: %s\n' "$SUBJECT"
            printf 'Date: %s\n' "$(date -R)"
            printf 'MIME-Version: 1.0\n'
            printf 'Content-Type: text/plain; charset=UTF-8\n'
            printf '\n%s\n' "$BODY"
        } | msmtp "$MAIL_TO"
    }

    A následne EXIT trap:

    backup_exit_notification() {
        local RC=$?
    
        trap - EXIT
        set +e
    
        local HOST
        local NOW
    
        HOST="$(hostname -f 2>/dev/null || hostname)"
        NOW="$(date '+%Y-%m-%d %H:%M:%S %Z')"
    
        if [[ "$RC" -eq 0 ]]; then
    
            send_backup_mail \
                "[BACKUP OK] ${SERVER_NAME}" \
                "Backup bol úspešne dokončený.
    
    Server: ${SERVER_NAME}
    Hostname: ${HOST}
    Čas: ${NOW}
    Status: SUCCESS"
    
        else
    
            send_backup_mail \
                "[BACKUP FAILED] ${SERVER_NAME}" \
                "Backup servera zlyhal.
    
    Server: ${SERVER_NAME}
    Hostname: ${HOST}
    Čas: ${NOW}
    Exit code: ${RC}"
    
        fi
    
        exit "$RC"
    }
    
    trap backup_exit_notification EXIT

    Tak dostaneme po každom spustení napríklad:

    [BACKUP OK] SERVER01

    alebo:

    [BACKUP FAILED] SERVER01

    Kontrola posledného snapshotu

    sudo env \
    RESTIC_PASSWORD_FILE=/root/.config/restic/server01.pass \
    restic \
    -r /mnt/nas-backup/BACKUP/server01/restic \
    snapshots \
    --latest 1 \
    --tag server01

    Kontrola obsahu snapshotu

    Najprv získame ID snapshotu:

    restic -r "$REPO" snapshots

    Napríklad:

    ID        Time                 Host
    ef739d4d  2026-08-27 02:30     server01

    Obsah:

    restic -r "$REPO" ls ef739d4d

    Vyhľadanie konkrétneho backupu:

    restic -r "$REPO" ls ef739d4d \
    | grep database.sql.gz

    Obnova jednotlivého súboru

    Napríklad:

    mkdir -p /tmp/restic-restore
    restic -r "$REPO" restore latest \
        --target /tmp/restic-restore \
        --include /etc/fstab

    Výsledok bude napríklad:

    /tmp/restic-restore/etc/fstab

    Obnova celého snapshotu

    restic -r "$REPO" restore ef739d4d \
        --target /tmp/server-restore

    Pred kopírovaním obnovených dát späť do produkčného systému je vhodné ich najprv skontrolovať.

    Kontrola integrity Restic repository

    Priebežne môžeme spustiť:

    restic -r "$REPO" check

    Dôkladnejšia kontrola dát:

    restic -r "$REPO" check --read-data

    Pri veľkom repository môže táto kontrola trvať výrazne dlhšie.

    Príklad zálohovania Docker aplikácie

    Pri Docker aplikácii je vhodné zálohovať:

    docker-compose.yml
    .env
    databázový dump
    persistentné volumes
    aplikačné kľúče
    SSL certifikáty

    Príklad:

    restic -r "$REPO" backup \
        /var/backups/aplikacia \
        /home/user/aplikacia \
        /var/lib/docker/volumes/aplikacia_data/_data \
        --tag server01

    Ak /home/user/aplikacia/.env obsahuje heslá, nie je problém ho ukladať do Restic repository, pretože Restic dáta šifruje.

    Samotné Restic heslo však musí byť uložené samostatne mimo repository.

    Praktická štruktúra backupu

    Výsledok môže vyzerať napríklad:

    Synology NAS
    │
    └── NetBackup
        │
        └── BACKUP
            │
            ├── server01
            │   └── restic
            │
            ├── server02
            │   └── restic
            │
            └── server03
                └── restic

    Každý server má:

    vlastné Restic repository
    vlastné Restic heslo
    vlastný tag
    vlastný systemd timer
    vlastný backup skript

    Bezpečnostné odporúčania

    Restic repository je šifrované, ale stále odporúčam:

    NFS povoliť iba vybraným serverom
    Restic heslo uložiť mimo servera
    .env súbory chmod 600
    backup skripty spúšťať ako root iba ak je to potrebné
    DB heslá nevkladať priamo do verejných skriptov
    pravidelne kontrolovať backup logy
    testovať obnovu dát

    Najdôležitejšie pravidlo je:

    Backup, ktorý sme nikdy neskúsili obnoviť, ešte nemáme reálne overený.

    Záver

    Výsledkom je plne automatizovaný backup systém:

    Linux server
          ↓
    databázové dumpy
          ↓
    Restic
          ↓
    šifrovanie + deduplikácia
          ↓
    NFS
          ↓
    Synology NAS
          ↓
    retention + prune
          ↓
    e-mail OK / FAILED

    Takto je možné relatívne jednoducho zálohovať viac Linux serverov a Docker aplikácií do jedného NAS zariadenia, pričom každý server zostáva oddelený vlastným repository a vlastným šifrovacím heslom.

    Reálny príklad – FreeIPA + BookStack + Jellyfin

    Nasledujúci príklad vychádza z reálneho nasadenia, kde jeden Linux server prevádzkuje viac služieb naraz.

    Pred vytvorením Restic snapshotu skript:

    FreeIPA
    → vytvorí natívny ipa-backup
    → skontroluje stav FreeIPA služieb

    BookStack
    → vytvorí konzistentný MariaDB dump
    → skontroluje integritu gzip
    → porovná počet tabuliek
    → nájde aktuálny /config Docker volume

    Jellyfin
    → zálohuje aplikačné dáta
    → vynechá cache

    Restic
    → vytvorí šifrovaný snapshot
    → aplikuje retention
    → vykoná prune

    E-mail
    → pošle BACKUP OK alebo BACKUP FAILED

    Kompletný backup skript

    Vytvoríme:

    sudo nano /usr/local/sbin/backup-server01.sh

    Obsah:

    #!/usr/bin/env bash
    
    set -Eeuo pipefail
    umask 077
    
    
    # ============================================================
    # E-mail notification
    # ============================================================
    
    MAIL_TO="admin@example.com"
    MAIL_FROM="backup@example.com"
    SERVER_NAME="SERVER01"
    
    NOTIFY_REPO="/mnt/nas-backup/BACKUP/server01/restic"
    NOTIFY_RESTIC_PASS="/root/.config/restic/server01.pass"
    NOTIFY_TAG="server01"
    
    
    send_backup_mail() {
        local SUBJECT="$1"
        local BODY="$2"
    
        {
            printf 'From: %s\n' "$MAIL_FROM"
            printf 'To: %s\n' "$MAIL_TO"
            printf 'Subject: %s\n' "$SUBJECT"
            printf 'Date: %s\n' "$(date -R)"
            printf 'MIME-Version: 1.0\n'
            printf 'Content-Type: text/plain; charset=UTF-8\n'
            printf '\n%s\n' "$BODY"
        } | msmtp "$MAIL_TO"
    }
    
    
    backup_exit_notification() {
        local RC=$?
    
        trap - EXIT
        set +e
    
        local HOST
        local NOW
        local LATEST
    
        HOST="$(hostname -f 2>/dev/null || hostname)"
        NOW="$(date '+%Y-%m-%d %H:%M:%S %Z')"
    
        if [[ "$RC" -eq 0 ]]; then
    
            LATEST="$(
                RESTIC_PASSWORD_FILE="$NOTIFY_RESTIC_PASS" \
                restic -r "$NOTIFY_REPO" snapshots \
                    --latest 1 \
                    --tag "$NOTIFY_TAG" \
                    --compact 2>/dev/null
            )"
    
            send_backup_mail \
                "[BACKUP OK] ${SERVER_NAME}" \
                "Backup bol uspesne dokonceny.
    
    Server: ${SERVER_NAME}
    Hostname: ${HOST}
    Cas: ${NOW}
    Status: SUCCESS
    
    Posledny Restic snapshot:
    
    ${LATEST}
    
    Repository:
    ${NOTIFY_REPO}"
    
        else
    
            send_backup_mail \
                "[BACKUP FAILED] ${SERVER_NAME}" \
                "Backup servera ZLYHAL.
    
    Server: ${SERVER_NAME}
    Hostname: ${HOST}
    Cas: ${NOW}
    Exit code: ${RC}
    
    Skontroluj:
    
    systemctl status backup-server01.service
    journalctl -u backup-server01.service -n 100 --no-pager"
    
        fi
    
        exit "$RC"
    }
    
    
    trap backup_exit_notification EXIT
    
    
    # ============================================================
    # Základná konfigurácia
    # ============================================================
    
    LOCK_FILE="/run/lock/backup-server01.lock"
    
    NAS="/mnt/nas-backup"
    
    REPO="${NAS}/BACKUP/server01/restic"
    
    PASSWORD_FILE="/root/.config/restic/server01.pass"
    
    TAG="server01"
    
    
    FREEIPA_STAGE="/var/backups/server01/freeipa"
    BOOKSTACK_STAGE="/var/backups/server01/bookstack"
    
    
    FREEIPA_CONTAINER="freeipa-server"
    
    BOOKSTACK_CONTAINER="bookstack"
    BOOKSTACK_DB_CONTAINER="bookstack-db"
    
    
    log() {
        echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    }
    
    
    # ============================================================
    # Zabránenie súbežnému spusteniu
    # ============================================================
    
    exec 9>"$LOCK_FILE"
    
    if ! flock -n 9; then
        log "ERROR: Backup už beží."
        exit 1
    fi
    
    
    # ============================================================
    # Kontrola Restic hesla
    # ============================================================
    
    if [[ ! -r "$PASSWORD_FILE" ]]; then
        log "ERROR: Restic password file neexistuje alebo nie je čitateľný."
        exit 1
    fi
    
    
    # ============================================================
    # Aktivácia systemd NFS automountu
    # ============================================================
    
    ls "$NAS" >/dev/null 2>&1 || true
    
    
    # ============================================================
    # Kontrola NAS
    # ============================================================
    
    if ! mountpoint -q "$NAS"; then
        log "ERROR: NAS nie je pripojený na $NAS."
        exit 1
    fi
    
    
    # ============================================================
    # Kontrola repository
    # ============================================================
    
    if [[ ! -d "$REPO" ]]; then
        log "ERROR: Restic repository neexistuje: $REPO"
        exit 1
    fi
    
    
    # ============================================================
    # Príprava staging adresárov
    # ============================================================
    
    mkdir -p "$FREEIPA_STAGE"
    mkdir -p "$BOOKSTACK_STAGE"
    
    chmod 700 "$FREEIPA_STAGE"
    chmod 700 "$BOOKSTACK_STAGE"
    
    
    export RESTIC_PASSWORD_FILE="$PASSWORD_FILE"
    
    
    
    # ============================================================
    # 1. FreeIPA native backup
    # ============================================================
    
    log "=== FreeIPA native backup ==="
    
    
    docker exec "$FREEIPA_CONTAINER" ipa-backup
    
    
    log "Kontrola FreeIPA služieb..."
    
    docker exec "$FREEIPA_CONTAINER" ipactl status
    
    
    LATEST_IPA_BACKUP="$(
        docker exec "$FREEIPA_CONTAINER" bash -lc \
        'ls -1dt /var/lib/ipa/backup/ipa-full-* 2>/dev/null | head -n1'
    )"
    
    
    if [[ -z "$LATEST_IPA_BACKUP" ]]; then
        log "ERROR: Nenašiel sa FreeIPA backup."
        exit 1
    fi
    
    
    IPA_BACKUP_NAME="$(basename "$LATEST_IPA_BACKUP")"
    
    
    log "Kopírujem $IPA_BACKUP_NAME na hosta..."
    
    
    rm -rf "${FREEIPA_STAGE:?}/${IPA_BACKUP_NAME}"
    
    
    docker cp \
        "${FREEIPA_CONTAINER}:${LATEST_IPA_BACKUP}" \
        "${FREEIPA_STAGE}/"
    
    
    if [[ ! -f "${FREEIPA_STAGE}/${IPA_BACKUP_NAME}/ipa-full.tar" ]]; then
        log "ERROR: ipa-full.tar nebol skopírovaný."
        exit 1
    fi
    
    
    log "FreeIPA backup pripravený: ${FREEIPA_STAGE}/${IPA_BACKUP_NAME}"
    
    
    
    # ============================================================
    # 2. BookStack MariaDB backup
    # ============================================================
    
    log "=== BookStack MariaDB backup ==="
    
    
    BOOKSTACK_TMP="${BOOKSTACK_STAGE}/bookstack.sql.gz.tmp"
    
    BOOKSTACK_FINAL="${BOOKSTACK_STAGE}/bookstack.sql.gz"
    
    
    rm -f "$BOOKSTACK_TMP"
    
    
    docker exec "$BOOKSTACK_DB_CONTAINER" sh -lc '
    
    if command -v mariadb-dump >/dev/null 2>&1; then
        DUMP=mariadb-dump
    else
        DUMP=mysqldump
    fi
    
    "$DUMP" \
        -u"$MYSQL_USER" \
        -p"$MYSQL_PASSWORD" \
        --single-transaction \
        --quick \
        --skip-lock-tables \
        "$MYSQL_DATABASE"
    
    ' | gzip -1 > "$BOOKSTACK_TMP"
    
    
    # ============================================================
    # Kontrola gzip integrity
    # ============================================================
    
    gzip -t "$BOOKSTACK_TMP"
    
    
    
    # ============================================================
    # Počet tabuliek v živej DB
    # ============================================================
    
    DB_TABLES="$(
        docker exec "$BOOKSTACK_DB_CONTAINER" sh -lc '
    
        mariadb \
            -u"$MYSQL_USER" \
            -p"$MYSQL_PASSWORD" \
            "$MYSQL_DATABASE" \
            -Nse "SHOW TABLES;" | wc -l
    
        '
    )"
    
    
    # ============================================================
    # Počet tabuliek v SQL dumpe
    # ============================================================
    
    DUMP_TABLES="$(
        gzip -dc "$BOOKSTACK_TMP" |
        grep -c '^CREATE TABLE' || true
    )"
    
    
    log "BookStack DB tabuliek: $DB_TABLES"
    
    log "BookStack dump tabuliek: $DUMP_TABLES"
    
    
    
    if [[ "$DB_TABLES" -lt 1 ]]; then
        log "ERROR: BookStack databáza nemá žiadne tabuľky."
        exit 1
    fi
    
    
    if [[ "$DUMP_TABLES" -ne "$DB_TABLES" ]]; then
        log "ERROR: Počet tabuliek v dumpe nesedí s databázou."
        exit 1
    fi
    
    
    mv "$BOOKSTACK_TMP" "$BOOKSTACK_FINAL"
    
    
    log "BookStack SQL backup OK."
    
    
    
    # ============================================================
    # 3. Zistenie aktuálneho BookStack /config volume
    # ============================================================
    
    BOOKSTACK_CONFIG="$(
        docker inspect "$BOOKSTACK_CONTAINER" \
        --format '{{range .Mounts}}{{if eq .Destination "/config"}}{{.Source}}{{end}}{{end}}'
    )"
    
    
    if [[ -z "$BOOKSTACK_CONFIG" || ! -d "$BOOKSTACK_CONFIG" ]]; then
        log "ERROR: BookStack /config volume sa nepodarilo nájsť."
        exit 1
    fi
    
    
    log "BookStack config: $BOOKSTACK_CONFIG"
    
    
    
    # ============================================================
    # 4. Restic backup
    # ============================================================
    
    log "=== Restic backup ==="
    
    
    restic -r "$REPO" backup \
        /var/backups/server01 \
        /home/user/freeipa-docker \
        /home/user/bookstack \
        "$BOOKSTACK_CONFIG" \
        /home/user/jellyfin \
        /etc/fstab \
        --exclude='/home/user/jellyfin/cache/**' \
        --tag "$TAG"
    
    
    
    # ============================================================
    # 5. Retention
    # ============================================================
    
    log "=== Restic retention ==="
    
    
    restic -r "$REPO" forget \
        --keep-daily 7 \
        --keep-weekly 4 \
        --keep-monthly 6 \
        --group-by host,tags \
        --tag "$TAG" \
        --prune
    
    
    
    # ============================================================
    # 6. Cleanup lokálnych FreeIPA backupov
    #    ponecháme posledné 2
    # ============================================================
    
    log "=== Cleanup starých FreeIPA staging backupov ==="
    
    
    mapfile -t OLD_LOCAL < <(
    
        find "$FREEIPA_STAGE" \
            -mindepth 1 \
            -maxdepth 1 \
            -type d \
            -name 'ipa-full-*' \
            -printf '%T@ %p\n' |
    
        sort -nr |
    
        tail -n +3 |
    
        cut -d' ' -f2-
    
    )
    
    
    for DIR in "${OLD_LOCAL[@]}"; do
    
        log "Odstraňujem lokálny staging: $DIR"
    
        rm -rf -- "$DIR"
    
    done
    
    
    
    # ============================================================
    # Cleanup backupov priamo vo FreeIPA kontajneri
    # ponecháme posledné 2
    # ============================================================
    
    docker exec "$FREEIPA_CONTAINER" bash -lc '
    
    ls -1dt /var/lib/ipa/backup/ipa-full-* 2>/dev/null |
    tail -n +3 |
    xargs -r rm -rf --
    
    '
    
    
    
    log "============================================"
    
    log "Backup SERVER01 úspešne dokončený."
    
    log "============================================"

    Nastavíme práva:

    sudo chmod 750 /usr/local/sbin/backup-server01.sh

    Pred prvým spustením skontrolujeme syntax:

    sudo bash -n /usr/local/sbin/backup-server01.sh \
    && echo "SCRIPT OK"

    Správny výsledok:

    SCRIPT OK

    Čo tento skript reálne zálohuje

    Výsledný Restic snapshot obsahuje napríklad:

    /var/backups/server01
    │
    ├── freeipa
    │   └── ipa-full-YYYY-MM-DD-HH-MM-SS
    │       └── ipa-full.tar
    │
    └── bookstack
        └── bookstack.sql.gz
    
    
    /home/user/freeipa-docker
    
    /home/user/bookstack
    ├── docker-compose.yml
    └── .env
    
    BookStack /config Docker volume
    
    /home/user/jellyfin
    
    /etc/fstab

    Súbor:

    /home/user/bookstack/.env

    môže obsahovať napríklad:

    APP_KEY
    databázové heslá

    Jeho zálohovanie do Restic repository je v poriadku, pretože obsah repository je šifrovaný.

    Restic heslo samotné však nesmie byť uložené iba v tomto repository.

    Prečo používame SQL dump databázy

    Pri MariaDB nekopírujeme iba živý databázový adresár.

    Pred Restic snapshotom používame:

    mariadb-dump \
        --single-transaction \
        --quick \
        --skip-lock-tables

    Výsledkom je konzistentný SQL dump:

    bookstack.sql.gz

    Skript následne skontroluje:

    gzip integritu
    počet tabuliek v databáze
    počet tabuliek v dumpe

    Ak napríklad databáza obsahuje:

    41 tabuliek

    a dump takisto:

    41 tabuliek

    skript pokračuje.

    Ak čísla nesedia, celý backup skončí chybou a nevytvorí sa falošne úspešný snapshot.

    Automatické vyhľadanie Docker volume

    Namiesto zapisovania dlhého Docker volume ID priamo do skriptu:

    /var/lib/docker/volumes/xxxxxxxxxxxx/_data

    sa aktuálna cesta zisťuje automaticky:

    docker inspect bookstack \
        --format '{{range .Mounts}}{{if eq .Destination "/config"}}{{.Source}}{{end}}{{end}}'

    Takto skript nájde aktuálny /config aj v prípade, že sa názov alebo interné ID volume zmení.

    Retention

    Po úspešnom vytvorení snapshotu:

    restic forget \
        --keep-daily 7 \
        --keep-weekly 4 \
        --keep-monthly 6 \
        --prune

    zostáva:

    7 denných snapshotov
    4 týždenné snapshoty
    6 mesačných snapshotov

    Restic následne pomocou:

    --prune

    odstráni dátové bloky, ktoré už nepotrebuje žiadny existujúci snapshot.

    Cleanup FreeIPA backupov

    ipa-backup vytvára lokálne zálohy aj priamo vo FreeIPA kontajneri.

    Aby časom nezaplnili disk, skript ponecháva iba:

    2 posledné FreeIPA native backupy

    Staršie sú odstránené až po úspešnom Restic backupe.

    To znamená, že dlhodobú históriu už zabezpečuje Restic repository na NAS.

    Výsledok

    Celý proces vyzerá:

    FreeIPA
       ↓
    ipa-backup
       ↓
    FreeIPA native archive
       │
       │
    BookStack
       ↓
    MariaDB dump
       ↓
    kontrola integrity
       │
       │
    Docker /config
       │
       │
    Jellyfin
       │
       ↓
    RESTIC
       ↓
    šifrovanie
       ↓
    deduplikácia
       ↓
    Synology NAS
       ↓
    retention
       ↓
    prune
       ↓
    BACKUP OK / FAILED e-mail