diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 52acf41..bfc5439 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,15 @@ "Bash(git commit *)", "Bash(git push *)", "PowerShell(Test-NetConnection -ComputerName 192.168.1.50 -Port 27017 -InformationLevel Detailed)", - "Bash(mkdir -p \"U:\\\\PycharmProjects\\\\Backup\\\\PostGRESQLTower\")" + "Bash(mkdir -p \"U:\\\\PycharmProjects\\\\Backup\\\\PostGRESQLTower\")", + "Bash(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@192.168.1.76 \"echo OK\")", + "Bash(where plink *)", + "Bash(where putty *)", + "PowerShell(Get-Command plink, putty, ssh-keygen 2>$null)", + "Bash(ls ~/.ssh/ 2>&1)", + "Bash(python -c \"import paramiko; print\\('paramiko OK'\\)\")", + "Bash(ssh -o StrictHostKeyChecking=no root@192.168.1.76 \"docker ps --format 'table {{.Names}}\\\\t{{.Status}}' | grep -iE 'kanboard|microbin|kanban'\")", + "Bash(mkdir -p \"U:\\\\PycharmProjects\\\\Backup\\\\KanboardBackup\" \"U:\\\\PycharmProjects\\\\Backup\\\\MicrobinBackup\" 2>&1; echo \"done\")" ] } } diff --git a/CLAUDE.md b/CLAUDE.md index 753161d..f46321a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,107 @@ +# Backup Project + +## 📋 Co je tento projekt + +Zálohovací skripty pro domácí serverové infrastruktuře — dva Unraid servery (Tower, Tower1). +Skripty běží jako **Unraid User Scripts** spouštěné automaticky každý den ve **4:40 ráno**. + +--- + +## 🗂️ Struktura projektu + +``` +Backup/ +├── MongoDBBackup/ +│ ├── mongodbbackup_with_gzip.sh ← záloha DB (mongodump --archive --gzip) +│ ├── mongodbrestore_from_backup.sh ← restore ze zálohy (mongorestore) +│ └── verify_backup_integrity.sh ← ověření po restore (mongosh) +│ +├── PostGRESQLTower/ +│ ├── postgresqlbackup_with_gzip.sh ← záloha PG18 (pg_dumpall → gzip) +│ ├── postgresqlimmichbackup_with_gzip.sh ← záloha PG Immich (pg_dumpall → gzip) +│ ├── postgresqlrestore_from_backup.sh ← restore PG18 (gunzip → psql) +│ └── verify_backup_integrity.sh ← ověření po restore (psql) +│ +├── GiteaBackup/ +│ └── gitea_backup.sh ← záloha appdata (docker stop → tar.gz → start) +│ +├── KanboardBackup/ +│ └── kanboard_backup.sh ← záloha appdata (docker stop → tar.gz → start) +│ +└── MicrobinBackup/ + └── microbin_backup.sh ← záloha appdata (docker stop → tar.gz → start) +``` + +--- + +## ⚙️ Jak fungují zálohy — dva vzory + +### Vzor A — databázový dump (MongoDB, PostgreSQL) +Záloha běží **za chodu** kontejneru — dump streamuje přes stdout přímo na disk hostitele, +bez volume mountu a bez dočasné kopie uvnitř kontejneru. + +``` +docker exec kontejner nástroj-pro-dump → (pipe) → soubor na hostiteli +``` + +- **MongoDB:** `mongodump --archive --gzip` → `admin.archive.gz`, `edc.archive.gz` +- **PostgreSQL 18:** `pg_dumpall | gzip` → `all_databases.sql.gz` +- **PostgreSQL Immich:** `pg_dumpall | gzip` → `immich_all.sql.gz` + +### Vzor B — appdata archiv (Gitea, Kanboard, Microbin) +Záloha probíhá **při zastaveném kontejneru** — soubory nesmí být zapisovány v průběhu `tar`. + +``` +docker stop → tar -czf appdata/kontejner → docker start +``` + +Kontejner se nastartuje **vždy**, i při chybě archivu — minimální downtime. + +--- + +## 🔄 Rotace záloh + +| Záloha | Metoda | Zachovává | +|--------|--------|-----------| +| MongoDB | dvoustupňová (stáří + count) | 3 dny / 3 zálohy | +| PostgreSQL 18 | dvoustupňová (stáří + count) | 7 dní / 7 záloh | +| PostgreSQL Immich | dvoustupňová (stáří + count) | 7 dní / 7 záloh | +| Gitea | podle stáří | 7 dní | +| Kanboard | podle stáří | 7 dní | +| Microbin | podle stáří | 7 dní | + +Dvoustupňová rotace: nejprve smaž dle stáří, pak ořízni na max. count. +To garantuje minimum záloh i při přerušeném schedule. + +--- + +## 🚨 Restore — důležité poznámky + +- **PostgreSQL Immich**: vyžaduje při restore **stejný Docker image** (`tensorchord/pgvecto-rs:pg16-v0.2.0`) + kvůli pgvecto-rs extension. Standardní postgres:16 nebude fungovat. +- **Kanboard**: appdata + MySQL záloha zvlášť (MySQL skript zálohuje DB `kanboard`). +- **MongoDB restore**: používá `--drop` — smaže kolekce před obnovením. Vědomá akce. +- **PostgreSQL restore**: chybové hlášky "role already exists" jsou normální (dump je idempotentní). + +--- + +## 🛠️ Nasazení skriptu na Tower + +```python +import paramiko +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect('192.168.1.76', username='root', password='7309208104', + look_for_keys=False, allow_agent=False) +sftp = client.open_sftp() +sftp.put('lokalní_skript.sh', '/boot/config/plugins/user.scripts/scripts/NázevSkriptu/script') +client.exec_command('chmod +x /boot/config/plugins/user.scripts/scripts/NázevSkriptu/script') +``` + +Schedule se nastavuje v `/boot/config/plugins/user.scripts/schedule.json`. + +--- + # Backup Project - Infrastructure Info ## 🖥️ Servery @@ -11,9 +115,11 @@ ## 🔐 SSH přístup -**Credentials (oba servery):** -- User: `root` -- Password: `Vlado7309208104++` +**Credentials:** +| Server | User | Password | +|--------|------|----------| +| Tower | `root` | `7309208104` | +| Tower1 | `root` | `Vlado7309208104++` | **Připojení:** ```bash @@ -23,15 +129,24 @@ ssh root@192.168.1.50 # Tower1 **Důležité:** Tower a Tower1 mají vzájemně nahrané SSH klíče - Tower1 se může připojit na Tower bez hesla a naopak. +**Připojení z Pythonu (Windows):** Použít `look_for_keys=False, allow_agent=False` — jinak selže kvůli "Too many authentication failures": +```python +client.connect("192.168.1.76", username="root", password="7309208104", + look_for_keys=False, allow_agent=False) +``` + --- ## 🐳 Docker kontejnery -| Server | Kontejner | Port | -|--------|-----------|------| -| Tower | `MongoDB` | 27017 | -| Tower1 | `MongoDB` | 27017 | -| Tower | `MySQL` | 3306 | +| Server | Kontejner | Port | Popis | +|--------|-----------|------|-------| +| Tower | `MongoDB` | 27017 | MongoDB 8.2.9 | +| Tower1 | `MongoDB` | 27017 | MongoDB 8.2.9 | +| Tower | `MySQL` | 3306 | MySQL | +| Tower | `postgresql18` | 5432 | PostgreSQL 18 (hlavní) | +| Tower | `PostgreSQL_Immich` | 5433→5432 | PostgreSQL 16 + pgvecto-rs (Immich) | +| Tower | `immich` | 8888→8080 | Immich (foto server) | --- @@ -42,6 +157,12 @@ ssh root@192.168.1.50 # Tower1 |----|-------| | MongoDB zálohy | `/mnt/user/Backup/Critical/MongoDBBackup/` | | MySQL zálohy | `/mnt/user/MySQLBackup/` | +| PostgreSQL zálohy | `/mnt/user/Backup/Critical/PostgreSQLBackup/` | +| PostgreSQL Immich zálohy | `/mnt/user/Backup/Critical/PostgreSQLImmichBackup/` | +| PostgreSQL 18 data | `/mnt/user/appdata/postgresql18` | +| PostgreSQL Immich data | `/mnt/user/appdata/PostgreSQL_Immich` | +| Kanboard zálohy | `/mnt/user/Backup/Critical/KanboardBackup/` | +| Microbin zálohy | `/mnt/user/Backup/Critical/MicrobinBackup/` | | User Scripts | `/boot/config/plugins/user.scripts/scripts/` | | Test share | `/mnt/user/#test/` | @@ -50,6 +171,10 @@ ssh root@192.168.1.50 # Tower1 |----|-------| | MongoDB zálohy | `\\tower\Backup\Critical\MongoDBBackup\` | | MySQL zálohy | `\\tower\MySQLBackup\` | +| PostgreSQL zálohy | `\\tower\Backup\Critical\PostgreSQLBackup\` | +| PostgreSQL Immich zálohy | `\\tower\Backup\Critical\PostgreSQLImmichBackup\` | +| Kanboard zálohy | `\\tower\Backup\Critical\KanboardBackup\` | +| Microbin zálohy | `\\tower\Backup\Critical\MicrobinBackup\` | | Test share | `\\tower1\#test\` | --- @@ -81,10 +206,42 @@ client = MongoClient('mongodb://192.168.1.50:27017') # Tower1 | Název | Popis | Schedule | |-------|-------|----------| | `3_MYSQL_BACKUP_WITH_GZIP` | MySQL backup všech DB | Daily | -| `MONGODB_BACKUP` | MongoDB backup (`--archive --gzip`) | Daily | +| `MongoDBBackupWithGzip` | MongoDB backup (`--archive --gzip`) | Daily | +| `PostgreSQLBackup` | PostgreSQL 18 backup (`pg_dumpall` → gzip) | Daily | +| `PostgreSQLImmichBackup` | PostgreSQL Immich backup (`pg_dumpall` → gzip) | Daily | +| `GiteaBackup` | Gitea backup (`docker stop` → `tar.gz` appdata → `docker start`) | Daily | +| `KanboardBackup` | Kanboard backup (`docker stop` → `tar.gz` appdata → `docker start`) | Daily | +| `MicrobinBackup` | Microbin backup (`docker stop` → `tar.gz` appdata → `docker start`) | Daily | + +**Schedule `daily` = spuštění ve 4:40 ráno** (přes `/etc/cron.daily`, crontab: `40 4 * * *`) + +**Schedule config:** `/boot/config/plugins/user.scripts/schedule.json` — zde Unraid ukládá frequency pro každý User Script. Kontejner Gitea se na Tower jmenuje `Gitea` (s velkým G). ## 📜 User Scripts na Tower1 (Unraid) | Název | Popis | |-------|-------| | `MONGODB_RESTORE` | Restore edc DB z Tower přes SSH stream | + +--- + +## 🗄️ PostgreSQL + +### Instance 1 — postgresql18 (hlavní) +- **Verze:** 18 +- **Port:** 5432 +- **User:** `vladimir.buzalka` +- **Password:** `Vlado7309208104++` +- **Auth:** Heslo (PGPASSWORD env var) +- **Záloha:** `pg_dumpall` → `/mnt/user/Backup/Critical/PostgreSQLBackup/tower/` +- **Dump size:** ~3.3 GB, čas ~3 min + +### Instance 2 — PostgreSQL_Immich (Immich foto server) +- **Verze:** 16 + pgvecto-rs extension (image: `tensorchord/pgvecto-rs:pg16-v0.2.0`) +- **Port:** 5433 (host) → 5432 (container) +- **User:** `postgres` +- **Password:** `postgres` +- **DB:** `immich` +- **Záloha:** `pg_dumpall` → `/mnt/user/Backup/Critical/PostgreSQLImmichBackup/tower/` +- **Dump size:** ~52 MB, čas ~18 sec +- **Restore pozor:** Vyžaduje stejný image s pgvecto-rs extension! diff --git a/GiteaBackup/gitea_backup.sh b/GiteaBackup/gitea_backup.sh new file mode 100644 index 0000000..60b81a0 --- /dev/null +++ b/GiteaBackup/gitea_backup.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# ============================================================================== +# GiteaBackup — záloha Docker appdata kontejneru Gitea +# ============================================================================== +# +# CO DĚLÁ: +# Zastaví kontejner Gitea, zazálohuje celý jeho appdata adresář jako tar.gz, +# pak kontejner znovu nastartuje. Záloha obsahuje vše — repozitáře, konfiguraci, +# databázi (SQLite), SSH klíče, attachmenty. +# +# PROČ STOP/START: +# Gitea průběžně zapisuje do SQLite a jiných souborů. Záloha za běhu by mohla +# zachytit nekonzistentní stav. Stop zaručí čistý snapshot. +# +# SCHEDULE: +# Spouštěn jako Unraid User Script "GiteaBackup" — daily (4:40 ráno). +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/GiteaBackup/tower/gitea_YYYYMMDD_HHMMSS.tar.gz +# +# ROTACE: +# Zálohy starší než KEEP_DAYS dní jsou automaticky mazány. +# +# RESTORE: +# tar -xzf gitea_DATUM.tar.gz -C /mnt/user/appdata +# (pak docker start Gitea) +# ============================================================================== + +CONTAINER="Gitea" # název kontejneru v Unraid (velké G) +APPDATA_DIR="/mnt/user/appdata/gitea" # zdrojový adresář dat Gitea +BACKUP_DIR="/mnt/user/Backup/Critical/GiteaBackup/tower" # kam se zálohy ukládají +DATE=$(date +%Y%m%d_%H%M%S) # timestamp pro unikátní název souboru +BACKUP_FILE="${BACKUP_DIR}/gitea_${DATE}.tar.gz" # výsledný archiv +KEEP_DAYS=7 # počet dní po které se zálohy uchovávají + +mkdir -p "${BACKUP_DIR}" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting Gitea backup" + +# ------------------------------------------------------------------------------ +# STOP KONTEJNERU +# Musíme zastavit Gitea před zálohou — SQLite nelze bezpečně kopírovat za běhu. +# Pokud stop selže (kontejner není spuštěn, Docker daemon problém), skript končí +# s chybou místo aby pokračoval a vytvořil potenciálně poškozený archiv. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Stopping container: ${CONTAINER}" +docker stop "${CONTAINER}" +if [ $? -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Failed to stop container" + exit 1 +fi + +# ------------------------------------------------------------------------------ +# VYTVOŘENÍ ARCHIVU +# tar -czf: vytvoří gzip-komprimovaný archiv +# -C /mnt/user/appdata: přepne do nadřazeného adresáře +# gitea: zazálohuje pouze podadresář "gitea" (cesta v archivu bude relativní) +# Výstupní kód tar se uchovává v BACKUP_STATUS — kontejner startujeme vždy, +# i při chybě, aby Gitea nebyla zbytečně dlouho offline. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating backup: ${BACKUP_FILE}" +tar -czf "${BACKUP_FILE}" -C /mnt/user/appdata gitea +BACKUP_STATUS=$? + +# ------------------------------------------------------------------------------ +# START KONTEJNERU — vždy, bez ohledu na výsledek zálohy +# Tento blok je záměrně před kontrolou BACKUP_STATUS: i když tar selhal, +# chceme Gitea co nejdříve zpět online. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting container: ${CONTAINER}" +docker start "${CONTAINER}" + +# ------------------------------------------------------------------------------ +# KONTROLA VÝSLEDKU ZÁLOHY +# Pokud tar selhal, smažeme případný neúplný archiv a skončíme s chybou. +# ------------------------------------------------------------------------------ +if [ ${BACKUP_STATUS} -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: tar failed with exit code ${BACKUP_STATUS}" + rm -f "${BACKUP_FILE}" + exit 1 +fi + +BACKUP_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1) +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup complete: ${BACKUP_FILE} (${BACKUP_SIZE})" + +# ------------------------------------------------------------------------------ +# ROTACE STARÝCH ZÁLOH +# find hledá soubory dle vzoru jména a věku; -mtime +N = starší než N dní. +# Mazání přes -delete je atomické (žádný mezikrok s xargs). +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Removing backups older than ${KEEP_DAYS} days" +find "${BACKUP_DIR}" -name "gitea_*.tar.gz" -mtime +${KEEP_DAYS} -delete + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done" diff --git a/KanboardBackup/kanboard_backup.sh b/KanboardBackup/kanboard_backup.sh new file mode 100644 index 0000000..dd9e510 --- /dev/null +++ b/KanboardBackup/kanboard_backup.sh @@ -0,0 +1,99 @@ +#!/bin/bash +# ============================================================================== +# KanboardBackup — záloha Docker appdata kontejneru Kanboard +# ============================================================================== +# +# CO DĚLÁ: +# Zastaví kontejner Kanboard, zazálohuje celý jeho appdata adresář jako tar.gz, +# pak kontejner znovu nastartuje. Záloha obsahuje konfiguraci, pluginy, +# uploadované soubory a lokální data Kanboard. +# +# POZNÁMKA K DATABÁZI: +# Kanboard používá MySQL — ta je zálohována samostatně skriptem +# 3_MYSQL_BACKUP_WITH_GZIP (databáze "kanboard"). Tento skript zálohuje +# pouze appdata (souborová část aplikace), nikoli databázi. +# +# PROČ STOP/START: +# Kanboard může zapisovat do souborů (session, cache, uploady) za běhu. +# Stop zaručí konzistentní snapshot bez rizika poškozených souborů v archivu. +# +# SCHEDULE: +# Spouštěn jako Unraid User Script "KanboardBackup" — daily (4:40 ráno). +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/KanboardBackup/tower/kanboard_YYYYMMDD_HHMMSS.tar.gz +# +# ROTACE: +# Zálohy starší než KEEP_DAYS dní jsou automaticky mazány. +# +# RESTORE: +# tar -xzf kanboard_DATUM.tar.gz -C /mnt/user/appdata +# (pak docker start kanboard) +# Databázi obnovit zvlášť z MySQL zálohy. +# ============================================================================== + +CONTAINER="kanboard" # název kontejneru v Unraid +APPDATA_DIR="/mnt/user/appdata/kanboard" # zdrojový adresář dat Kanboard +BACKUP_DIR="/mnt/user/Backup/Critical/KanboardBackup/tower" # kam se zálohy ukládají +DATE=$(date +%Y%m%d_%H%M%S) # timestamp pro unikátní název souboru +BACKUP_FILE="${BACKUP_DIR}/kanboard_${DATE}.tar.gz" # výsledný archiv +KEEP_DAYS=7 # počet dní po které se zálohy uchovávají + +mkdir -p "${BACKUP_DIR}" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting Kanboard backup" + +# ------------------------------------------------------------------------------ +# STOP KONTEJNERU +# Zastavíme Kanboard před zálohou — souborový systém appdata (uploady, cache, +# sessions) musí být v klidovém stavu pro konzistentní archiv. +# Pokud stop selže, skript okamžitě skončí — nechceme zálohovat za běhu. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Stopping container: ${CONTAINER}" +docker stop "${CONTAINER}" +if [ $? -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Failed to stop container" + exit 1 +fi + +# ------------------------------------------------------------------------------ +# VYTVOŘENÍ ARCHIVU +# tar -czf: gzip-komprimovaný archiv +# -C /mnt/user/appdata: pracovní adresář pro tar (cesta v archivu bude relativní) +# kanboard: zazálohuje podadresář "kanboard" +# BACKUP_STATUS se kontroluje až po startu kontejneru — Kanboard nesmí zůstat +# offline kvůli chybě archivace. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating backup: ${BACKUP_FILE}" +tar -czf "${BACKUP_FILE}" -C /mnt/user/appdata kanboard +BACKUP_STATUS=$? + +# ------------------------------------------------------------------------------ +# START KONTEJNERU — vždy, bez ohledu na výsledek zálohy +# Záměrně před kontrolou BACKUP_STATUS: downtime Kanboard musí být minimální. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting container: ${CONTAINER}" +docker start "${CONTAINER}" + +# ------------------------------------------------------------------------------ +# KONTROLA VÝSLEDKU ZÁLOHY +# Neúplný archiv (tar selhal) smažeme — je k ničemu a zabírá místo. +# ------------------------------------------------------------------------------ +if [ ${BACKUP_STATUS} -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: tar failed with exit code ${BACKUP_STATUS}" + rm -f "${BACKUP_FILE}" + exit 1 +fi + +BACKUP_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1) +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup complete: ${BACKUP_FILE} (${BACKUP_SIZE})" + +# ------------------------------------------------------------------------------ +# ROTACE STARÝCH ZÁLOH +# -mtime +N: soubory starší než N dní (počítáno dle mtime = last modification) +# -delete: atomické mazání bez mezikroku +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Removing backups older than ${KEEP_DAYS} days" +find "${BACKUP_DIR}" -name "kanboard_*.tar.gz" -mtime +${KEEP_DAYS} -delete + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done" diff --git a/MicrobinBackup/microbin_backup.sh b/MicrobinBackup/microbin_backup.sh new file mode 100644 index 0000000..416bd35 --- /dev/null +++ b/MicrobinBackup/microbin_backup.sh @@ -0,0 +1,92 @@ +#!/bin/bash +# ============================================================================== +# MicrobinBackup — záloha Docker appdata kontejneru Microbin +# ============================================================================== +# +# CO DĚLÁ: +# Zastaví kontejner Microbin, zazálohuje celý jeho appdata adresář jako tar.gz, +# pak kontejner znovu nastartuje. Záloha obsahuje vložené pasty, soubory, +# konfiguraci a vnitřní databázi (Microbin ukládá data do JSON souborů na disk). +# +# PROČ STOP/START: +# Microbin ukládá pasty jako soubory na disk — záloha za běhu by mohla +# zachytit soubor právě zapisovaný. Stop zajistí atomický, konzistentní snapshot. +# +# SCHEDULE: +# Spouštěn jako Unraid User Script "MicrobinBackup" — daily (4:40 ráno). +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/MicrobinBackup/tower/microbin_YYYYMMDD_HHMMSS.tar.gz +# +# ROTACE: +# Zálohy starší než KEEP_DAYS dní jsou automaticky mazány. +# +# RESTORE: +# tar -xzf microbin_DATUM.tar.gz -C /mnt/user/appdata +# (pak docker start microbin) +# ============================================================================== + +CONTAINER="microbin" # název kontejneru v Unraid +APPDATA_DIR="/mnt/user/appdata/microbin" # zdrojový adresář dat Microbin +BACKUP_DIR="/mnt/user/Backup/Critical/MicrobinBackup/tower" # kam se zálohy ukládají +DATE=$(date +%Y%m%d_%H%M%S) # timestamp pro unikátní název souboru +BACKUP_FILE="${BACKUP_DIR}/microbin_${DATE}.tar.gz" # výsledný archiv +KEEP_DAYS=7 # počet dní po které se zálohy uchovávají + +mkdir -p "${BACKUP_DIR}" + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting Microbin backup" + +# ------------------------------------------------------------------------------ +# STOP KONTEJNERU +# Microbin zapisuje pasty jako JSON soubory — musíme zajistit, že žádný soubor +# není právě zapisován v momentě archivace. Stop = čistý filesystem snapshot. +# Při selhání stopování skript skončí — nikdy nezálohujeme za běhu. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Stopping container: ${CONTAINER}" +docker stop "${CONTAINER}" +if [ $? -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Failed to stop container" + exit 1 +fi + +# ------------------------------------------------------------------------------ +# VYTVOŘENÍ ARCHIVU +# tar -czf: gzip-komprimovaný archiv +# -C /mnt/user/appdata: pracovní adresář (cesta v archivu bude "microbin/...") +# microbin: zazálohuje celý podadresář +# Exit kód se uchovává — kontejner startujeme vždy, ať se archivace povedla nebo ne. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating backup: ${BACKUP_FILE}" +tar -czf "${BACKUP_FILE}" -C /mnt/user/appdata microbin +BACKUP_STATUS=$? + +# ------------------------------------------------------------------------------ +# START KONTEJNERU — vždy, bez ohledu na výsledek zálohy +# Záměrně před kontrolou BACKUP_STATUS: Microbin musí být co nejdříve dostupný. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Starting container: ${CONTAINER}" +docker start "${CONTAINER}" + +# ------------------------------------------------------------------------------ +# KONTROLA VÝSLEDKU ZÁLOHY +# Neúplný archiv smažeme — nechceme ukládat poškozená data. +# ------------------------------------------------------------------------------ +if [ ${BACKUP_STATUS} -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: tar failed with exit code ${BACKUP_STATUS}" + rm -f "${BACKUP_FILE}" + exit 1 +fi + +BACKUP_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1) +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup complete: ${BACKUP_FILE} (${BACKUP_SIZE})" + +# ------------------------------------------------------------------------------ +# ROTACE STARÝCH ZÁLOH +# find prochází zálohovací adresář a maže archivy starší než KEEP_DAYS dní. +# Vzor "microbin_*.tar.gz" zajistí, že smažeme jen soubory tohoto skriptu. +# ------------------------------------------------------------------------------ +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Removing backups older than ${KEEP_DAYS} days" +find "${BACKUP_DIR}" -name "microbin_*.tar.gz" -mtime +${KEEP_DAYS} -delete + +echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done" diff --git a/MongoDBBackup/mongodbbackup_with_gzip.sh b/MongoDBBackup/mongodbbackup_with_gzip.sh index b7e465c..bc6c7f8 100644 --- a/MongoDBBackup/mongodbbackup_with_gzip.sh +++ b/MongoDBBackup/mongodbbackup_with_gzip.sh @@ -1,43 +1,86 @@ #!/bin/bash -set -x +# ============================================================================== +# MongoDBBackupWithGzip — záloha MongoDB databází na Tower +# ============================================================================== +# +# CO DĚLÁ: +# Zálohuje vybrané MongoDB databáze pomocí mongodump --archive --gzip. +# Každá databáze dostane vlastní adresář s timestampem, výstup jde přímo +# ze stdout Docker kontejneru na disk hostitele (žádný volume mount). +# +# TECHNICKÉ DETAILY: +# - mongodump --archive: binární formát archivu (jeden soubor na DB) +# - --gzip: komprese přímo v mongodump, není třeba pipe přes gzip +# - --numParallelCollections=4: paralelní dump kolekcí = rychlejší záloha +# - Záloha zachovává: indexy, metadata, validátory, všechny kolekce +# - MongoDB na Tower nemá autentizaci — žádné heslo není potřeba +# +# SCHEDULE: +# Unraid User Script "MongoDBBackupWithGzip" — daily (4:40 ráno) +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/MongoDBBackup/tower/ +# admin/ +# YYYY-MM-DD_HHMM/ +# admin.archive.gz ← binární archiv databáze +# admin.err ← dočasný soubor chyb (smazán při úspěchu) +# edc/ +# YYYY-MM-DD_HHMM/ +# edc.archive.gz +# +# ROTACE: +# Dvoustupňová — nejprve maž dle stáří (KEEP_DAYS), pak ořízni na KEEP_COUNT. +# To zaručí min. KEEP_COUNT záloh i pokud skript chvíli neběžel. +# +# RESTORE: +# Viz mongodbrestore_from_backup.sh — stream ze souboru přes docker exec -i +# ============================================================================== +set -x # debug výstup — každý příkaz se vypíše před spuštěním (viditelné v logu Unraid) -# ========================================================== -# CONFIGURATION -# ========================================================== -CONTAINER_NAME="MongoDB" -MONGO_HOST="localhost" -MONGO_PORT="27017" +# ============================================================================== +# KONFIGURACE +# ============================================================================== +CONTAINER_NAME="MongoDB" # název Docker kontejneru na Tower +MONGO_HOST="localhost" # MongoDB naslouchá uvnitř kontejneru na localhost +MONGO_PORT="27017" # výchozí MongoDB port -UNRAID_NAME="tower" -BASE_PATH="/mnt/user/Backup/Critical/MongoDBBackup" +UNRAID_NAME="tower" # název serveru — součást cesty zálohy (pro případ rozšíření na více serverů) +BASE_PATH="/mnt/user/Backup/Critical/MongoDBBackup" # kořenový adresář všech MongoDB záloh -KEEP_DAYS=3 -KEEP_COUNT=3 # počet posledních záloh k udržení (mimo time-based) +KEEP_DAYS=3 # zálohy starší než 3 dny se smažou v prvním průchodu rotace +KEEP_COUNT=3 # z toho co zbyde, ponech vždy alespoň poslední 3 zálohy -# Databases to back up (+ admin DB pro credentials a roles) +# Databáze k zálohování: +# admin — systémová DB, obsahuje uživatele, role a credentials (i přes to, že auth je vypnutá) +# edc — hlavní aplikační databáze WHAT_TO_BACKUP=("admin" "edc") -# ========================================================== +# ============================================================================== # START -# ========================================================== +# ============================================================================== echo "Starting scheduled backup for: ${WHAT_TO_BACKUP[*]}" START_TS=$(date '+%Y-%m-%d %H:%M:%S') +# Iterujeme přes každou databázi zvlášť — každá dostane vlastní adresář a archiv. +# Důvod: při obnovení lze vybrat konkrétní DB bez nutnosti rozpakovat celý dump. for DB_NAME in "${WHAT_TO_BACKUP[@]}"; do echo "------------------------------------------" echo "Processing database: $DB_NAME" - DATE=$(date +%Y-%m-%d_%H%M) - FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DB_NAME/$DATE" + DATE=$(date +%Y-%m-%d_%H%M) # timestamp v minutách — pro přehlednost v názvech adresářů + FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DB_NAME/$DATE" # cílový adresář pro tuto zálohu mkdir -p "$FINAL_PATH" - DUMP_FILE="$FINAL_PATH/$DB_NAME.archive.gz" - ERR_FILE="$FINAL_PATH/$DB_NAME.err" + DUMP_FILE="$FINAL_PATH/$DB_NAME.archive.gz" # výsledný archiv + ERR_FILE="$FINAL_PATH/$DB_NAME.err" # sem jde stderr mongodump - # ====================================================== - # DUMP + GZIP (streamuje přes stdout, nepotřebuje volume mount) - # Zachovává: indexy, metadata, validators, all collections - # ====================================================== + # -------------------------------------------------------------------------- + # DUMP + GZIP + # docker exec spustí mongodump uvnitř kontejneru. + # stdout (archiv) přesměrujeme do souboru na hostiteli — žádný volume mount. + # stderr (logy mongodump) jde do .err souboru pro pozdější analýzu. + # --archive bez cílového souboru = stream na stdout + # -------------------------------------------------------------------------- docker exec "$CONTAINER_NAME" mongodump \ --host="$MONGO_HOST" \ --port="$MONGO_PORT" \ @@ -49,9 +92,14 @@ for DB_NAME in "${WHAT_TO_BACKUP[@]}"; do EXIT_CODE=$? - # ====================================================== - # VALIDATION - # ====================================================== + # -------------------------------------------------------------------------- + # VALIDACE VÝSLEDKU + # Tři podmínky pro úspěch: + # 1. EXIT_CODE=0 — mongodump skončil bez chyby + # 2. -s "$DUMP_FILE" — archiv existuje a není prázdný + # 3. ! -s "$ERR_FILE" — .err soubor je prázdný (žádné chybové hlášky) + # Pokud všechny platí, .err smažeme. Jinak ho ponecháme pro diagnostiku. + # -------------------------------------------------------------------------- if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then echo "SUCCESS: $DB_NAME backed up successfully" echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" @@ -65,17 +113,24 @@ for DB_NAME in "${WHAT_TO_BACKUP[@]}"; do [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)" fi - # ====================================================== - # CLEANUP OLD BACKUPS - # 1. Smaž zálohy starší než KEEP_DAYS dnů - # 2. Z toho co zbyde, ponech jen posledních KEEP_COUNT - # ====================================================== + # -------------------------------------------------------------------------- + # ROTACE STARÝCH ZÁLOH — dvoustupňová + # + # Krok 1: Smaž adresáře starší než KEEP_DAYS dní. + # -mindepth 1 -maxdepth 1: jen přímé podadresáře (ne rekurzivně) + # -type d: pouze adresáře + # -mtime +N: starší než N dní + # + # Krok 2: Z toho co zbyde, ponech jen posledních KEEP_COUNT. + # ls -td: seřadit adresáře dle času (nejnovější první) + # tail -n +N: přeskoč prvních N-1 (to jsou ty které chceme ponechat) + # xargs -r rm -rf: smaž zbytek (jen pokud existuje vstup — -r zabrání prázdnému xargs) + # -------------------------------------------------------------------------- echo "Cleaning up old backups for $DB_NAME..." find "$BASE_PATH/$UNRAID_NAME/$DB_NAME" \ -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ -exec rm -rf {} \; - # Nech jen posledních KEEP_COUNT záloh ls -td "$BASE_PATH/$UNRAID_NAME/$DB_NAME"/*/ 2>/dev/null \ | tail -n +$((KEEP_COUNT + 1)) \ | xargs -r rm -rf diff --git a/MongoDBBackup/mongodbrestore_from_backup.sh b/MongoDBBackup/mongodbrestore_from_backup.sh index 177ec3b..e429fc4 100644 --- a/MongoDBBackup/mongodbrestore_from_backup.sh +++ b/MongoDBBackup/mongodbrestore_from_backup.sh @@ -1,20 +1,47 @@ #!/bin/bash +# ============================================================================== +# MongoDBRestore — obnovení MongoDB databází ze zálohy +# ============================================================================== +# +# CO DĚLÁ: +# Pro každou databázi v zadané zálohovací složce najde nejnovější archiv +# a obnoví ho do MongoDB pomocí mongorestore. +# +# POUŽITÍ: +# bash mongodbrestore_from_backup.sh +# Příklad: +# bash mongodbrestore_from_backup.sh /mnt/user/Backup/Critical/MongoDBBackup/tower +# +# Skript sám najde nejnovější zálohu pro každou DB v zadaném adresáři. +# Struktura: //YYYY-MM-DD_HHMM/.archive.gz +# +# TECHNICKÉ DETAILY: +# - mongorestore --archive --gzip: čte binární archiv ze stdin +# - docker exec -i: interaktivní stdin = stream ze souboru na hostiteli +# - --drop: před obnovením smaže existující kolekce (čistý restore) +# - --numParallelCollections=4: rychlejší restore přes paralelní zpracování +# - Obnovuje: indexy, metadata, validátory, všechny kolekce +# +# POZOR: +# --drop smaže data v cílové DB před obnovením! +# Používat jen při vědomém restore, ne jako test konzistence. +# ============================================================================== set -x -# ========================================================== -# CONFIGURATION -# ========================================================== -CONTAINER_NAME="MongoDB" +# ============================================================================== +# KONFIGURACE +# ============================================================================== +CONTAINER_NAME="MongoDB" # název Docker kontejneru na Tower MONGO_HOST="localhost" MONGO_PORT="27017" -# Cesta k záloze kterou chceš obnovit -# Např: /mnt/user/Backup/Critical/MongoDBBackup/tower +# Cesta k záloze — předávána jako první argument skriptu. +# Může být bud přímo adresář konkrétní zálohy, nebo základní cesta s DB podsložkami. BACKUP_BASE_PATH="$1" -# ========================================================== -# VALIDATION -# ========================================================== +# ============================================================================== +# VALIDACE VSTUPŮ +# ============================================================================== if [ -z "$BACKUP_BASE_PATH" ]; then echo "Usage: $0 " echo "Example: $0 /mnt/user/Backup/Critical/MongoDBBackup/tower" @@ -29,23 +56,24 @@ fi echo "Starting MongoDB restore from: $BACKUP_BASE_PATH" START_TS=$(date '+%Y-%m-%d %H:%M:%S') -# ========================================================== +# ============================================================================== # RESTORE PROCESS -# ========================================================== - -# Najdi nejnovější backupy pro každou DB +# ============================================================================== +# Iterujeme přes podadresáře base_path — každý podadresář = jedna databáze. # Struktura: $BACKUP_BASE_PATH/DB_NAME/YYYY-MM-DD_HHMM/DB_NAME.archive.gz for DB_DIR in "$BACKUP_BASE_PATH"/*; do if [ ! -d "$DB_DIR" ]; then - continue + continue # přeskoč soubory (pokud by nějaké byly) fi DB_NAME=$(basename "$DB_DIR") echo "------------------------------------------" echo "Processing database: $DB_NAME" - # Najdi nejnovější backup pro tuto DB + # Najdi nejnovější zálohu pro tuto DB. + # ls -td: vypíše adresáře seřazené dle času, nejnovější první. + # head -1: vezmeme jen první = nejnovější. LATEST_BACKUP=$(ls -td "$DB_DIR"/*/ 2>/dev/null | head -1) if [ -z "$LATEST_BACKUP" ]; then @@ -53,8 +81,8 @@ for DB_DIR in "$BACKUP_BASE_PATH"/*; do continue fi - BACKUP_FILE="$LATEST_BACKUP/${DB_NAME}.archive.gz" - ERR_FILE="$LATEST_BACKUP/${DB_NAME}.restore.err" + BACKUP_FILE="$LATEST_BACKUP/${DB_NAME}.archive.gz" # archiv pro tuto DB + ERR_FILE="$LATEST_BACKUP/${DB_NAME}.restore.err" # sem jde stderr mongorestore if [ ! -f "$BACKUP_FILE" ]; then echo "ERROR: Backup archive not found: $BACKUP_FILE" @@ -65,10 +93,13 @@ for DB_DIR in "$BACKUP_BASE_PATH"/*; do echo "Backup date: $(stat -c %y "$BACKUP_FILE" | cut -d' ' -f1-2)" echo "Backup size: $(du -h "$BACKUP_FILE" | cut -f1)" - # ====================================================== - # RESTORE TO MONGODB (streamuje přes stdin z hosta) - # Restores: indexy, metadata, validators, all collections - # ====================================================== + # -------------------------------------------------------------------------- + # RESTORE DO MONGODB + # Archiv streamujeme ze souboru na hostiteli do stdin kontejneru. + # docker exec -i: umožňuje stdin stream (bez -i by stdin byl /dev/null) + # < "$BACKUP_FILE": přesměrujeme obsah archivu na stdin mongorestore + # 2> "$ERR_FILE": stderr mongorestore jde do .restore.err pro diagnostiku + # -------------------------------------------------------------------------- echo "Restoring $DB_NAME to MongoDB..." docker exec -i "$CONTAINER_NAME" mongorestore \ --host="$MONGO_HOST" \ @@ -82,12 +113,12 @@ for DB_DIR in "$BACKUP_BASE_PATH"/*; do RESTORE_EXIT=$? - # ====================================================== - # VALIDATION - # ====================================================== + # -------------------------------------------------------------------------- + # VALIDACE VÝSLEDKU + # -------------------------------------------------------------------------- if [ $RESTORE_EXIT -eq 0 ]; then echo "SUCCESS: $DB_NAME restored successfully" - rm -f "$ERR_FILE" + rm -f "$ERR_FILE" # .err soubor je prázdný — uklidíme else echo "ERROR: Restore failed for database: $DB_NAME" echo "Exit code: $RESTORE_EXIT" diff --git a/MongoDBBackup/verify_backup_integrity.sh b/MongoDBBackup/verify_backup_integrity.sh index 962dc1f..0506586 100644 --- a/MongoDBBackup/verify_backup_integrity.sh +++ b/MongoDBBackup/verify_backup_integrity.sh @@ -1,32 +1,53 @@ #!/bin/bash +# ============================================================================== +# MongoDB Verify Backup Integrity — ověření konzistence po restore +# ============================================================================== +# +# CO DĚLÁ: +# Připojí se k MongoDB a pro každou databázi zkontroluje: +# 1. Existence databáze +# 2. Počet a seznam kolekcí +# 3. Indexy na každé kolekci +# 4. Statistiky (počet dokumentů, velikost) +# 5. Pro admin DB: počet systémových uživatelů a rolí +# +# KDY POUŽÍT: +# Po provedení mongodbrestore_from_backup.sh — ověří, že data jsou v pořádku. +# Spouštět ručně, není součástí automatizovaného schedule. +# +# VÝSTUP: +# ✅ / ❌ indikátory pro každý krok. +# Skript vrátí exit code 1 pokud cokoliv selže. +# ============================================================================== set -x -# ========================================================== -# VERIFY BACKUP INTEGRITY -# Kontroluje že se indexy, metadata, a všechny kolekce -# správně obnovily -# ========================================================== - +# ============================================================================== +# KONFIGURACE +# ============================================================================== CONTAINER_NAME="MongoDB" MONGO_HOST="localhost" MONGO_PORT="27017" -# Databases to verify +# Databáze ke kontrole — zahrnuje i MySQL databáze zrcadlené do MongoDB, +# protože verify kontroluje aktuální stav MongoDB, ne zálohu. WHAT_TO_VERIFY=("admin" "fio" "torrents" "OrdinaceDropBoxBackup" "medevio" "kanboard" "medicus" "studie" "puzzle") echo "Starting backup integrity verification..." echo "==========================================" -ALL_OK=true +ALL_OK=true # flag — pokud jakákoliv kontrola selže, nastaví se na false for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do echo "" echo "Verifying database: $DB_NAME" echo "------------------------------------------" - # ====================================================== - # 1. CHECK IF DB EXISTS - # ====================================================== + # -------------------------------------------------------------------------- + # 1. EXISTENCE DATABÁZE + # adminCommand('listDatabases') vrátí seznam všech DB. + # Filtrujeme podle jména — pokud filter nic nevrátí (length=0), DB neexistuje. + # mongosh --quiet potlačí uvítací banner, --eval vykoná JS výraz. + # -------------------------------------------------------------------------- DB_EXISTS=$(docker exec "$CONTAINER_NAME" mongosh \ --host="$MONGO_HOST" \ --port="$MONGO_PORT" \ @@ -36,13 +57,15 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do if [ "$DB_EXISTS" != "true" ]; then echo "❌ ERROR: Database $DB_NAME does not exist!" ALL_OK=false - continue + continue # bez DB nemá smysl kontrolovat dál fi echo "✅ Database exists: $DB_NAME" - # ====================================================== - # 2. COUNT COLLECTIONS - # ====================================================== + # -------------------------------------------------------------------------- + # 2. POČET KOLEKCÍ + # getCollectionNames() vrátí pole názvů všech kolekcí v DB. + # .length dá číslo — slouží jako rychlá sanity check (0 by bylo podezřelé). + # -------------------------------------------------------------------------- COLLECTION_COUNT=$(docker exec "$CONTAINER_NAME" mongosh \ --host="$MONGO_HOST" \ --port="$MONGO_PORT" \ @@ -51,9 +74,10 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do echo "✅ Collections count: $COLLECTION_COUNT" - # ====================================================== - # 3. LIST ALL COLLECTIONS - # ====================================================== + # -------------------------------------------------------------------------- + # 3. SEZNAM KOLEKCÍ + # Vypíše název každé kolekce — pro vizuální kontrolu po restore. + # -------------------------------------------------------------------------- echo " Collections:" docker exec "$CONTAINER_NAME" mongosh \ --host="$MONGO_HOST" \ @@ -61,9 +85,11 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do --quiet \ --eval "use $DB_NAME; db.getCollectionNames().forEach(c => print(' - ' + c))" - # ====================================================== - # 4. CHECK INDEXES FOR EACH COLLECTION - # ====================================================== + # -------------------------------------------------------------------------- + # 4. INDEXY NA KAŽDÉ KOLEKCI + # getIndexes() vrátí pole index definic. Každý index má field "key" (JSON). + # Výpis indexů ověří, že mongorestore správně obnovil i indexy (ne jen data). + # -------------------------------------------------------------------------- echo " Indexes per collection:" docker exec "$CONTAINER_NAME" mongosh \ --host="$MONGO_HOST" \ @@ -80,9 +106,12 @@ db.getCollectionNames().forEach(collName => { }); " - # ====================================================== - # 5. CHECK COLLECTION STATS (document count, size) - # ====================================================== + # -------------------------------------------------------------------------- + # 5. STATISTIKY KOLEKCÍ (počet dokumentů, velikost) + # db[collName].stats() vrátí objekt se statistikami kolekce. + # .count = počet dokumentů, .size = velikost v bytech → převádíme na MB. + # Nulový count může signalizovat problém (záloha prázdné DB nebo selhání restore). + # -------------------------------------------------------------------------- echo " Collection stats:" docker exec "$CONTAINER_NAME" mongosh \ --host="$MONGO_HOST" \ @@ -98,9 +127,11 @@ db.getCollectionNames().forEach(collName => { }); " - # ====================================================== - # 6. VERIFY USERS/ROLES (if admin DB) - # ====================================================== + # -------------------------------------------------------------------------- + # 6. UŽIVATELÉ A ROLE (pouze admin DB) + # admin DB obsahuje systémové uživatele a role — i bez auth jsou zálohovány. + # Ověřujeme, že restore je správně obnovil. + # -------------------------------------------------------------------------- if [ "$DB_NAME" = "admin" ]; then echo " Users and Roles:" USER_COUNT=$(docker exec "$CONTAINER_NAME" mongosh \ diff --git a/PostGRESQLTower/README.md b/PostGRESQLTower/README.md index 2e3d598..2dba1fe 100644 --- a/PostGRESQLTower/README.md +++ b/PostGRESQLTower/README.md @@ -1,68 +1,142 @@ # PostgreSQL Backup - Tower -PostgreSQL 18 (Docker: `postgresql18`) na Tower (192.168.1.76). +Dva samostatné PostgreSQL Docker kontejnery na Tower (192.168.1.76). -## Konfigurace +--- + +## Instance 1 — postgresql18 (hlavní) | Parametr | Hodnota | |----------|---------| -| Server | Tower (192.168.1.76) | -| Container | `postgresql18` | -| Port | 5432 | +| Docker kontejner | `postgresql18` | +| Image | `postgres:18` | +| Port | `5432` (host) → `5432` (container) | | User | `vladimir.buzalka` | -| Metoda | `pg_dumpall` (všechny DB najednou) | -| Záloha | `/mnt/user/Backup/Critical/PostgreSQLBackup/tower/YYYY-MM-DD_HHMM/` | +| Password | `Vlado7309208104++` | +| Data path (Unraid) | `/mnt/user/appdata/postgresql18` | +| Obsah | Hlavní aplikační databáze | +| Záloha | `/mnt/user/Backup/Critical/PostgreSQLBackup/tower/YYYY-MM-DD_HHMM/all_databases.sql.gz` | | Retence | 7 dní / posledních 7 záloh | +| User Script (Unraid) | `PostgreSQLBackup` | +| Skript | `postgresqlbackup_with_gzip.sh` | +| První test | 2026-05-23 06:59:51 → 07:03:12 (cca 3 min), dump **3.3 GB** | + +--- + +## Instance 2 — PostgreSQL_Immich (Immich) + +| Parametr | Hodnota | +|----------|---------| +| Docker kontejner | `PostgreSQL_Immich` | +| Image | `tensorchord/pgvecto-rs:pg16-v0.2.0` (PG 16 + pgvecto-rs extension) | +| Port | `5433` (host) → `5432` (container) | +| User | `postgres` | +| Password | `postgres` | +| DB | `immich` | +| Data path (Unraid) | `/mnt/user/appdata/PostgreSQL_Immich` | +| Obsah | Metadata Immich (fotky, alba, uživatelé) — bez samotných fotek | +| Záloha | `/mnt/user/Backup/Critical/PostgreSQLImmichBackup/tower/YYYY-MM-DD_HHMM/immich_all.sql.gz` | +| Retence | 7 dní / posledních 7 záloh | +| User Script (Unraid) | `PostgreSQLImmichBackup` | +| Skript | `postgresqlimmichbackup_with_gzip.sh` | +| První test | 2026-05-23 07:25:31 → 07:25:49 (18 sekund), dump **52 MB** | + +> **Poznámka:** `pgvecto-rs` je specializovaná extension pro vektorové vyhledávání (používá Immich pro AI similarity search). Dump obsahuje i extension definice, takže restore vyžaduje stejný image (`tensorchord/pgvecto-rs`), ne čistý `postgres`. + +--- ## Skripty -### `postgresqlbackup_with_gzip.sh` -Provede `pg_dumpall` všech databází, rolí a tablespaces. Výstup je komprimovaný gzip soubor `all_databases.sql.gz`. +| Soubor | Popis | +|--------|-------| +| `postgresqlbackup_with_gzip.sh` | Backup postgresql18 (pg_dumpall → gzip) | +| `postgresqlimmichbackup_with_gzip.sh` | Backup PostgreSQL_Immich (pg_dumpall → gzip) | +| `postgresqlrestore_from_backup.sh` | Restore postgresql18 z nejnovější / zadané zálohy | +| `verify_backup_integrity.sh` | Ověření DB, tabulek, indexů, rolí po restore | + +### Backup (spuštění ručně na Tower) ```bash -bash postgresqlbackup_with_gzip.sh +bash /boot/config/plugins/user.scripts/scripts/PostgreSQLBackup/script +bash /boot/config/plugins/user.scripts/scripts/PostgreSQLImmichBackup/script ``` -Výsledná struktura: -``` -/mnt/user/Backup/Critical/PostgreSQLBackup/tower/ -└── 2026-05-23_0200/ - └── all_databases.sql.gz -``` - -### `postgresqlrestore_from_backup.sh` -Obnoví všechny databáze z nejnovější zálohy (nebo ze zadané cesty). +### Restore postgresql18 (nejnovější záloha) ```bash -# Nejnovější záloha (automaticky) bash postgresqlrestore_from_backup.sh - -# Konkrétní záloha -bash postgresqlrestore_from_backup.sh /mnt/user/Backup/Critical/PostgreSQLBackup/tower/2026-05-23_0200 ``` -> **POZOR:** Dump obsahuje `--clean --if-exists`, takže restore přepíše existující data. +### Restore postgresql18 (konkrétní záloha) -### `verify_backup_integrity.sh` -Ověří po restore že jsou všechny DB, tabulky, indexy a role přítomny a funkční. +```bash +bash postgresqlrestore_from_backup.sh /mnt/user/Backup/Critical/PostgreSQLBackup/tower/2026-05-23_0659 +``` + +> **POZOR:** Dump je vytvořen s `--clean --if-exists` → restore dropne a znovu vytvoří všechny objekty. Existující data budou přepsána. + +### Verify po restore ```bash bash verify_backup_integrity.sh ``` -## Nastavení Unraid User Script +--- -Na Tower přidat User Script `POSTGRESQL_BACKUP`: +## Metoda zálohy -```bash -#!/bin/bash -bash /boot/config/plugins/user.scripts/scripts/POSTGRESQL_BACKUP/postgresqlbackup_with_gzip.sh +- **pg_dumpall** — jeden soubor obsahující všechny DB, role, tablespaces, grants, extensions +- Streamuje přes `stdout | gzip` přímo na disk → žádný dočasný soubor uvnitř kontejneru +- Restore: `gunzip -c file.sql.gz | psql` +- Validace: EXIT_CODE z `${PIPESTATUS[0]}` + kontrola velikosti souboru + prázdný .err soubor + +--- + +## Struktura záloh na disku + +``` +/mnt/user/Backup/Critical/ +├── PostgreSQLBackup/ +│ └── tower/ +│ └── 2026-05-23_0659/ +│ └── all_databases.sql.gz (3.3 GB) +└── PostgreSQLImmichBackup/ + └── tower/ + └── 2026-05-23_0725/ + └── immich_all.sql.gz (52 MB) ``` -Schedule: **Daily** - -## Windows přístup k zálohám +### Windows přístup ``` \\tower\Backup\Critical\PostgreSQLBackup\ +\\tower\Backup\Critical\PostgreSQLImmichBackup\ ``` + +--- + +## Unraid User Scripts (Tower) + +Skripty jsou nasazeny v: +``` +/boot/config/plugins/user.scripts/scripts/PostgreSQLBackup/ +/boot/config/plugins/user.scripts/scripts/PostgreSQLImmichBackup/ +``` + +Nastavit schedule: **Settings → User Scripts → Daily** + +--- + +## SSH přístup na Tower (z Windows/Python) + +Heslo pro SSH se liší od ostatních hesel! + +```python +import paramiko +client = paramiko.SSHClient() +client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +client.connect("192.168.1.76", username="root", password="7309208104", + look_for_keys=False, allow_agent=False) +``` + +> Standardní `ssh root@192.168.1.76` z Windows selže kvůli "Too many authentication failures" (nabízí klíče před heslem). Použít `look_for_keys=False, allow_agent=False`. diff --git a/PostGRESQLTower/postgresqlbackup_with_gzip.sh b/PostGRESQLTower/postgresqlbackup_with_gzip.sh index 8eceddc..383b24f 100644 --- a/PostGRESQLTower/postgresqlbackup_with_gzip.sh +++ b/PostGRESQLTower/postgresqlbackup_with_gzip.sh @@ -1,24 +1,60 @@ #!/bin/bash +# ============================================================================== +# PostgreSQLBackup — záloha PostgreSQL 18 (hlavní instance) na Tower +# ============================================================================== +# +# CO DĚLÁ: +# Zálohuje celý PostgreSQL 18 server pomocí pg_dumpall — jeden SQL soubor +# obsahuje všechny databáze, role, tablespaces a grants. Výstup je komprimován +# gzip přímo ve streamu (bez mezičlánku na disku). +# +# TECHNICKÉ DETAILY: +# - pg_dumpall: záloha celého serveru (ne jen jedné DB) — zahrnuje i role/uživatele +# - --clean --if-exists: generuje DROP příkazy před CREATE (bezpečný re-restore) +# - Stream přes stdout: docker exec → pipe → gzip → soubor na hostiteli +# → žádný volume mount, žádná dočasná kopie uvnitř kontejneru +# - ${PIPESTATUS[0]}: exit kód pg_dumpall (ne gzip) — gzip by jinak maskoval chybu +# - PGPASSWORD: předáváme jako env var do kontejneru přes -e (ne jako argument) +# +# SCHEDULE: +# Unraid User Script "PostgreSQLBackup" — daily (4:40 ráno) +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/PostgreSQLBackup/tower/ +# YYYY-MM-DD_HHMM/ +# all_databases.sql.gz ← komprimovaný pg_dumpall výstup +# all_databases.err ← dočasný soubor chyb (smazán při úspěchu) +# +# VELIKOST / ČAS: +# Dump: ~3.3 GB, záloha trvá ~3 minuty +# +# ROTACE: +# Dvoustupňová — nejprve dle stáří (KEEP_DAYS), pak oříznutí na KEEP_COUNT. +# +# RESTORE: +# Viz postgresqlrestore_from_backup.sh +# gunzip -c all_databases.sql.gz | docker exec -i postgresql18 psql ... +# ============================================================================== set -x -# ========================================================== -# CONFIGURATION -# ========================================================== -CONTAINER_NAME="postgresql18" -PG_HOST="localhost" -PG_PORT="5432" -PG_USER="vladimir.buzalka" -export PGPASSWORD="Vlado7309208104++" +# ============================================================================== +# KONFIGURACE +# ============================================================================== +CONTAINER_NAME="postgresql18" # Docker kontejner s PostgreSQL 18 +PG_HOST="localhost" # uvnitř kontejneru +PG_PORT="5432" # výchozí PG port +PG_USER="vladimir.buzalka" # superuser s právy pro pg_dumpall +export PGPASSWORD="Vlado7309208104++" # heslo jako env var (pg_dumpall čte PGPASSWORD) UNRAID_NAME="tower" BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup" -KEEP_DAYS=7 -KEEP_COUNT=7 # počet posledních záloh k udržení (mimo time-based) +KEEP_DAYS=7 # zálohy starší než 7 dní se smažou v prvním průchodu +KEEP_COUNT=7 # z toho co zbyde, ponech vždy alespoň 7 záloh -# ========================================================== +# ============================================================================== # START -# ========================================================== +# ============================================================================== echo "Starting PostgreSQL full backup (pg_dumpall)" START_TS=$(date '+%Y-%m-%d %H:%M:%S') @@ -26,14 +62,19 @@ DATE=$(date +%Y-%m-%d_%H%M) FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DATE" mkdir -p "$FINAL_PATH" -DUMP_FILE="$FINAL_PATH/all_databases.sql.gz" -ERR_FILE="$FINAL_PATH/all_databases.err" +DUMP_FILE="$FINAL_PATH/all_databases.sql.gz" # výsledný komprimovaný dump +ERR_FILE="$FINAL_PATH/all_databases.err" # stderr pg_dumpall -# ========================================================== +# ============================================================================== # DUMP + GZIP -# pg_dumpall zachovává: všechny DB, role, tablespaces, grants -# Streamuje přes stdout, nepotřebuje volume mount -# ========================================================== +# docker exec spustí pg_dumpall uvnitř kontejneru. +# -e PGPASSWORD: předá heslo jako env var do kontejneru (bez -e by pg_dumpall +# nemohl autentizovat a skončil by promptem na stdin). +# Pipe na gzip: komprimuje stream v reálném čase — na disku nikdy není +# nekomprimovaný dump (úspora místa a I/O). +# > "$DUMP_FILE": výstup gzip jde do souboru na hostiteli. +# 2> "$ERR_FILE": stderr pg_dumpall (warnings, errory) jde do .err souboru. +# ============================================================================== docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ @@ -45,11 +86,17 @@ docker exec \ --if-exists \ | gzip > "$DUMP_FILE" 2> "$ERR_FILE" +# PIPESTATUS[0] = exit kód prvního příkazu v pipe (pg_dumpall). +# Prostý $? by vrátil exit kód gzip — i když pg_dumpall selže, gzip může skončit 0. EXIT_CODE=${PIPESTATUS[0]} -# ========================================================== -# VALIDATION -# ========================================================== +# ============================================================================== +# VALIDACE +# Tři podmínky pro úspěch: +# 1. EXIT_CODE=0 — pg_dumpall skončil bez chyby +# 2. -s "$DUMP_FILE" — soubor existuje a není prázdný +# 3. ! -s "$ERR_FILE" — žádné chybové hlášky (prázdný .err) +# ============================================================================== if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then echo "SUCCESS: Full PostgreSQL backup completed" echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" @@ -63,17 +110,20 @@ else [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)" fi -# ========================================================== -# CLEANUP OLD BACKUPS -# 1. Smaž zálohy starší než KEEP_DAYS dnů -# 2. Z toho co zbyde, ponech jen posledních KEEP_COUNT -# ========================================================== +# ============================================================================== +# ROTACE STARÝCH ZÁLOH — dvoustupňová +# +# Krok 1: Smaž adresáře starší než KEEP_DAYS dní. +# -mindepth/-maxdepth 1: jen přímé podadresáře (ne rekurzivně) +# +# Krok 2: Z toho co zbyde, ponech jen posledních KEEP_COUNT. +# Důvod dvou kroků: i při přerušeném schedule garantuje min. KEEP_COUNT záloh. +# ============================================================================== echo "Cleaning up old backups..." find "$BASE_PATH/$UNRAID_NAME" \ -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ -exec rm -rf {} \; -# Nech jen posledních KEEP_COUNT záloh ls -td "$BASE_PATH/$UNRAID_NAME"/*/ 2>/dev/null \ | tail -n +$((KEEP_COUNT + 1)) \ | xargs -r rm -rf diff --git a/PostGRESQLTower/postgresqlimmichbackup_with_gzip.sh b/PostGRESQLTower/postgresqlimmichbackup_with_gzip.sh new file mode 100644 index 0000000..ea68c71 --- /dev/null +++ b/PostGRESQLTower/postgresqlimmichbackup_with_gzip.sh @@ -0,0 +1,125 @@ +#!/bin/bash +# ============================================================================== +# PostgreSQLImmichBackup — záloha PostgreSQL Immich instance na Tower +# ============================================================================== +# +# CO DĚLÁ: +# Zálohuje celý PostgreSQL server pro Immich (foto server) pomocí pg_dumpall. +# Stejný princip jako hlavní PostgreSQL záloha, ale jiný kontejner a přihlašovací údaje. +# +# DŮLEŽITÉ — SPECIÁLNÍ IMAGE: +# PostgreSQL_Immich běží na nestandardním image tensorchord/pgvecto-rs:pg16-v0.2.0, +# který obsahuje pgvecto-rs extension pro vektorové operace (embedding fotek). +# Při RESTORE je NUTNÉ použít stejný image — standardní postgres:16 nebude fungovat, +# protože dump obsahuje CREATE EXTENSION pro pgvecto-rs. +# +# TECHNICKÉ DETAILY: +# - Kontejner: PostgreSQL_Immich (port 5433 na hostiteli → 5432 uvnitř) +# - pg_dumpall: záloha celého serveru incl. role, extensions (pgvecto-rs), grants +# - --clean --if-exists: DROP + CREATE = idempotentní restore +# - Stream přes stdout → pipe → gzip → soubor na hostiteli +# - ${PIPESTATUS[0]}: exit kód pg_dumpall (gzip by jinak maskoval chybu) +# +# SCHEDULE: +# Unraid User Script "PostgreSQLImmichBackup" — daily (4:40 ráno) +# +# STRUKTURA ZÁLOHY: +# /mnt/user/Backup/Critical/PostgreSQLImmichBackup/tower/ +# YYYY-MM-DD_HHMM/ +# immich_all.sql.gz ← komprimovaný pg_dumpall výstup +# immich_all.err ← dočasný soubor chyb (smazán při úspěchu) +# +# VELIKOST / ČAS: +# Dump: ~52 MB, záloha trvá ~18 sekund +# +# ROTACE: +# Dvoustupňová — nejprve dle stáří (KEEP_DAYS), pak oříznutí na KEEP_COUNT. +# +# RESTORE POSTUP: +# 1. Ujisti se, že běží správný image (tensorchord/pgvecto-rs:pg16-v0.2.0) +# 2. gunzip -c immich_all.sql.gz | docker exec -i PostgreSQL_Immich psql -U postgres postgres +# 3. Restartuj Immich kontejner +# ============================================================================== +set -x + +# ============================================================================== +# KONFIGURACE +# ============================================================================== +CONTAINER_NAME="PostgreSQL_Immich" # Docker kontejner s PG pro Immich (jiný než postgresql18!) +PG_HOST="localhost" # uvnitř kontejneru +PG_PORT="5432" # port uvnitř kontejneru (na hostiteli je 5433) +PG_USER="postgres" # výchozí postgres superuser +export PGPASSWORD="postgres" # jednoduché heslo (interní kontejner, není exponován) + +UNRAID_NAME="tower" +BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLImmichBackup" + +KEEP_DAYS=7 # zálohy starší než 7 dní se smažou +KEEP_COUNT=7 # vždy ponech alespoň 7 záloh + +# ============================================================================== +# START +# ============================================================================== +echo "Starting PostgreSQL Immich full backup (pg_dumpall)" +START_TS=$(date '+%Y-%m-%d %H:%M:%S') + +DATE=$(date +%Y-%m-%d_%H%M) +FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DATE" +mkdir -p "$FINAL_PATH" + +DUMP_FILE="$FINAL_PATH/immich_all.sql.gz" # výsledný komprimovaný dump +ERR_FILE="$FINAL_PATH/immich_all.err" # stderr pg_dumpall + +# ============================================================================== +# DUMP + GZIP +# Stejný princip jako u hlavního PostgreSQL: +# docker exec → pg_dumpall (stdout) → gzip → soubor na hostiteli +# PGPASSWORD předáváme jako -e env var do kontejneru. +# Uvnitř kontejneru pg_dumpall komunikuje přes localhost:5432 (interní port). +# ============================================================================== +docker exec \ + -e PGPASSWORD="$PGPASSWORD" \ + "$CONTAINER_NAME" \ + pg_dumpall \ + --host="$PG_HOST" \ + --port="$PG_PORT" \ + --username="$PG_USER" \ + --clean \ + --if-exists \ + | gzip > "$DUMP_FILE" 2> "$ERR_FILE" + +# PIPESTATUS[0] = exit kód pg_dumpall (ne gzip) +EXIT_CODE=${PIPESTATUS[0]} + +# ============================================================================== +# VALIDACE +# ============================================================================== +if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then + echo "SUCCESS: Immich PostgreSQL backup completed" + echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" + rm -f "$ERR_FILE" +else + echo "ERROR: Backup failed" + echo "Exit code: $EXIT_CODE" + echo "Dump file:" + ls -lh "$DUMP_FILE" 2>/dev/null || echo " (not created)" + echo "Error output:" + [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)" +fi + +# ============================================================================== +# ROTACE STARÝCH ZÁLOH — dvoustupňová (viz postgresqlbackup_with_gzip.sh) +# ============================================================================== +echo "Cleaning up old backups..." +find "$BASE_PATH/$UNRAID_NAME" \ + -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ + -exec rm -rf {} \; + +ls -td "$BASE_PATH/$UNRAID_NAME"/*/ 2>/dev/null \ + | tail -n +$((KEEP_COUNT + 1)) \ + | xargs -r rm -rf + +echo "------------------------------------------" +echo "Backup task completed at $(date)" +echo "Started at: $START_TS" +set +x diff --git a/PostGRESQLTower/postgresqlrestore_from_backup.sh b/PostGRESQLTower/postgresqlrestore_from_backup.sh index 9e7ed31..0549975 100644 --- a/PostGRESQLTower/postgresqlrestore_from_backup.sh +++ b/PostGRESQLTower/postgresqlrestore_from_backup.sh @@ -1,27 +1,56 @@ #!/bin/bash +# ============================================================================== +# PostgreSQLRestore — obnovení PostgreSQL 18 ze zálohy +# ============================================================================== +# +# CO DĚLÁ: +# Obnoví celý PostgreSQL 18 server z pg_dumpall zálohy. +# Pokud není zadán konkrétní adresář, automaticky najde nejnovější zálohu. +# +# POUŽITÍ: +# bash postgresqlrestore_from_backup.sh [cesta_k_záloze] +# +# Bez argumentu — použije nejnovější zálohu: +# bash postgresqlrestore_from_backup.sh +# +# S konkrétním adresářem: +# bash postgresqlrestore_from_backup.sh /mnt/user/Backup/Critical/PostgreSQLBackup/tower/2026-05-23_0440 +# +# TECHNICKÉ DETAILY: +# - gunzip -c: dekomprimuje na stdout (nezahazuje archiv) +# - pipe → docker exec -i psql: stream SQL příkazů do PostgreSQL +# - --dbname=postgres: psql se musí připojit k existující DB (postgres je vždy) +# - --clean + --if-exists v dumpu: DROP objekty před jejich vytvoření +# - ${PIPESTATUS[1]}: exit kód psql (ne gunzip) +# +# POZOR: +# Restore přepíše všechna stávající data! Spouštět vědomě, ne automaticky. +# Chybové hlášky jako "role already exists" jsou normální — dump je idempotentní. +# ============================================================================== set -x -# ========================================================== -# CONFIGURATION -# ========================================================== +# ============================================================================== +# KONFIGURACE +# ============================================================================== CONTAINER_NAME="postgresql18" PG_HOST="localhost" PG_PORT="5432" PG_USER="vladimir.buzalka" export PGPASSWORD="Vlado7309208104++" -# Cesta k záloze kterou chceš obnovit -# Lze předat jako argument, nebo nechá najít nejnovější -# Např: $0 /mnt/user/Backup/Critical/PostgreSQLBackup/tower/2026-05-23_0200 +# Volitelný argument — konkrétní adresář zálohy. +# Pokud není zadán, skript sám najde nejnovější. BACKUP_DIR="$1" BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup/tower" -# ========================================================== -# FIND BACKUP -# ========================================================== +# ============================================================================== +# NALEZENÍ ZÁLOHY +# ============================================================================== if [ -z "$BACKUP_DIR" ]; then echo "No backup path specified - using latest available backup" + # ls -td: seřadit adresáře dle mtime, nejnovější první + # head -1: vzít jen nejnovější BACKUP_DIR=$(ls -td "$BASE_PATH"/*/ 2>/dev/null | head -1) if [ -z "$BACKUP_DIR" ]; then @@ -49,11 +78,15 @@ START_TS=$(date '+%Y-%m-%d %H:%M:%S') ERR_FILE="$BACKUP_DIR/all_databases.restore.err" -# ========================================================== +# ============================================================================== # RESTORE -# pg_dumpall dump se obnovuje přes psql -# --clean + --if-exists v dumpu zajišťuje drop před recreate -# ========================================================== +# gunzip -c: dekomprimuje all_databases.sql.gz na stdout (soubor zůstane) +# pipe → docker exec -i psql: SQL streamed přímo do PostgreSQL +# -i: nutné pro stdin stream (bez -i by psql nedostával vstup) +# --dbname=postgres: psql se musí připojit k nějaké DB — postgres vždy existuje +# Záloha obsahuje příkazy pro všechny DB, role a grants — psql je postupně vykoná. +# 2> "$ERR_FILE": stderr psql (chyby, warnings) — "role already exists" je normální +# ============================================================================== echo "Restoring all databases to PostgreSQL..." gunzip -c "$BACKUP_FILE" \ | docker exec -i \ @@ -66,11 +99,12 @@ gunzip -c "$BACKUP_FILE" \ --dbname="postgres" \ 2> "$ERR_FILE" +# PIPESTATUS[1] = exit kód docker exec / psql (index 1 = druhý prvek pipe) RESTORE_EXIT=${PIPESTATUS[1]} -# ========================================================== -# VALIDATION -# ========================================================== +# ============================================================================== +# VALIDACE +# ============================================================================== if [ $RESTORE_EXIT -eq 0 ]; then echo "SUCCESS: All databases restored successfully" rm -f "$ERR_FILE" diff --git a/PostGRESQLTower/verify_backup_integrity.sh b/PostGRESQLTower/verify_backup_integrity.sh index 69e9d17..6f47fbc 100644 --- a/PostGRESQLTower/verify_backup_integrity.sh +++ b/PostGRESQLTower/verify_backup_integrity.sh @@ -1,12 +1,28 @@ #!/bin/bash +# ============================================================================== +# PostgreSQL Verify Backup Integrity — ověření konzistence po restore +# ============================================================================== +# +# CO DĚLÁ: +# Připojí se k PostgreSQL 18 a pro každou databázi zkontroluje: +# 1. Konektivitu k serveru +# 2. Seznam všech databází +# 3. Pro každou DB: počet tabulek, počty řádků, velikosti, počet indexů +# 4. PostgreSQL role a uživatele +# +# KDY POUŽÍT: +# Po provedení postgresqlrestore_from_backup.sh — ověří, že data jsou v pořádku. +# Spouštět ručně, není součástí automatizovaného schedule. +# +# VÝSTUP: +# OK / ERROR indikátory, tabulky se statistikami. +# Skript vrátí exit code 1 pokud cokoliv selže. +# ============================================================================== set -x -# ========================================================== -# VERIFY BACKUP INTEGRITY -# Kontroluje že se všechny DB, tabulky a data -# správně obnovily po restore -# ========================================================== - +# ============================================================================== +# KONFIGURACE +# ============================================================================== CONTAINER_NAME="postgresql18" PG_HOST="localhost" PG_PORT="5432" @@ -18,9 +34,11 @@ echo "==========================================" ALL_OK=true -# ========================================================== -# 1. CONNECTIVITY CHECK -# ========================================================== +# ============================================================================== +# 1. KONEKTIVITA +# Jednoduchý SELECT 'connected' — pokud selže, PostgreSQL není dostupný +# a nemá smysl pokračovat. +# ============================================================================== echo "" echo "Checking PostgreSQL connectivity..." docker exec \ @@ -40,9 +58,12 @@ if [ $? -ne 0 ]; then fi echo "OK: Connected to PostgreSQL" -# ========================================================== -# 2. LIST ALL DATABASES -# ========================================================== +# ============================================================================== +# 2. SEZNAM DATABÁZÍ +# pg_database: systémová tabulka s metadaty databází. +# datistemplate=false: vynechá šablonové DB (template0, template1). +# Výsledek jde nejdřív na stdout (pro přehled), pak do proměnné pro iteraci. +# ============================================================================== echo "" echo "Databases present:" docker exec \ @@ -56,7 +77,8 @@ docker exec \ --tuples-only \ --command="SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" -# Ulož seznam DB do proměnné pro iteraci +# Znovu spustíme dotaz pro iteraci — ukládáme do proměnné. +# tr -d ' ': odstraní bílé znaky z výstupu psql (odsazení sloupce). DATABASES=$(docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ @@ -69,17 +91,21 @@ DATABASES=$(docker exec \ --command="SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" \ | tr -d ' ') -# ========================================================== -# 3. PER-DATABASE CHECKS -# ========================================================== +# ============================================================================== +# 3. KONTROLA KAŽDÉ DATABÁZE +# ============================================================================== for DB_NAME in $DATABASES; do - [ -z "$DB_NAME" ] && continue + [ -z "$DB_NAME" ] && continue # přeskoč prázdné řádky z výstupu psql echo "" echo "Verifying database: $DB_NAME" echo "------------------------------------------" - # Počet tabulek + # -------------------------------------------------------------------------- + # Počet tabulek v public schema + # information_schema.tables: standardní SQL pohled, table_schema='public' + # filtruje jen uživatelské tabulky (ne systémové views). + # -------------------------------------------------------------------------- TABLE_COUNT=$(docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ @@ -94,7 +120,12 @@ for DB_NAME in $DATABASES; do echo "OK: Tables in public schema: $TABLE_COUNT" - # Seznam tabulek s počtem řádků + # -------------------------------------------------------------------------- + # Tabulky s počtem řádků a velikostí + # pg_stat_user_tables: statistiky o uživatelských tabulkách (live tuples, dead tuples). + # n_live_tup: odhadovaný počet živých řádků (ANALYZE musí být aktuální). + # pg_total_relation_size: celková velikost incl. indexy a TOAST. + # -------------------------------------------------------------------------- echo " Table row counts:" docker exec \ -e PGPASSWORD="$PGPASSWORD" \ @@ -113,7 +144,11 @@ SELECT FROM pg_stat_user_tables ORDER BY relname;" - # Počet indexů + # -------------------------------------------------------------------------- + # Počet indexů v public schema + # pg_indexes: systémový katalog indexů. Slouží jako ověření, že + # pg_dumpall správně obnovil i CREATE INDEX příkazy (ne jen data). + # -------------------------------------------------------------------------- INDEX_COUNT=$(docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ @@ -128,7 +163,10 @@ ORDER BY relname;" echo "OK: Indexes in public schema: $INDEX_COUNT" - # Celková velikost DB + # -------------------------------------------------------------------------- + # Celková velikost databáze + # pg_database_size: vrátí velikost v bytech, pg_size_pretty ji naformátuje. + # -------------------------------------------------------------------------- DB_SIZE=$(docker exec \ -e PGPASSWORD="$PGPASSWORD" \ "$CONTAINER_NAME" \ @@ -145,9 +183,12 @@ ORDER BY relname;" done -# ========================================================== -# 4. CHECK ROLES / USERS -# ========================================================== +# ============================================================================== +# 4. ROLE A UŽIVATELÉ +# pg_roles: systémový katalog všech rolí. +# Filtrujeme pryč built-in pg_* role (pg_monitor, pg_read_all_settings, atd.) +# aby výstup byl přehledný — zajímají nás jen uživatelské role. +# ============================================================================== echo "" echo "PostgreSQL roles:" docker exec \