notebookvb

This commit is contained in:
Administrator
2026-05-23 09:38:53 +02:00
parent 13065aab94
commit 916ab42bcc
13 changed files with 1085 additions and 194 deletions
+9 -1
View File
@@ -10,7 +10,15 @@
"Bash(git commit *)", "Bash(git commit *)",
"Bash(git push *)", "Bash(git push *)",
"PowerShell(Test-NetConnection -ComputerName 192.168.1.50 -Port 27017 -InformationLevel Detailed)", "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\")"
] ]
} }
} }
+166 -9
View File
@@ -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 # Backup Project - Infrastructure Info
## 🖥️ Servery ## 🖥️ Servery
@@ -11,9 +115,11 @@
## 🔐 SSH přístup ## 🔐 SSH přístup
**Credentials (oba servery):** **Credentials:**
- User: `root` | Server | User | Password |
- Password: `Vlado7309208104++` |--------|------|----------|
| Tower | `root` | `7309208104` |
| Tower1 | `root` | `Vlado7309208104++` |
**Připojení:** **Připojení:**
```bash ```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. **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 ## 🐳 Docker kontejnery
| Server | Kontejner | Port | | Server | Kontejner | Port | Popis |
|--------|-----------|------| |--------|-----------|------|-------|
| Tower | `MongoDB` | 27017 | | Tower | `MongoDB` | 27017 | MongoDB 8.2.9 |
| Tower1 | `MongoDB` | 27017 | | Tower1 | `MongoDB` | 27017 | MongoDB 8.2.9 |
| Tower | `MySQL` | 3306 | | 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/` | | MongoDB zálohy | `/mnt/user/Backup/Critical/MongoDBBackup/` |
| MySQL zálohy | `/mnt/user/MySQLBackup/` | | 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/` | | User Scripts | `/boot/config/plugins/user.scripts/scripts/` |
| Test share | `/mnt/user/#test/` | | Test share | `/mnt/user/#test/` |
@@ -50,6 +171,10 @@ ssh root@192.168.1.50 # Tower1
|----|-------| |----|-------|
| MongoDB zálohy | `\\tower\Backup\Critical\MongoDBBackup\` | | MongoDB zálohy | `\\tower\Backup\Critical\MongoDBBackup\` |
| MySQL zálohy | `\\tower\MySQLBackup\` | | 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\` | | Test share | `\\tower1\#test\` |
--- ---
@@ -81,10 +206,42 @@ client = MongoClient('mongodb://192.168.1.50:27017') # Tower1
| Název | Popis | Schedule | | Název | Popis | Schedule |
|-------|-------|----------| |-------|-------|----------|
| `3_MYSQL_BACKUP_WITH_GZIP` | MySQL backup všech DB | Daily | | `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) ## 📜 User Scripts na Tower1 (Unraid)
| Název | Popis | | Název | Popis |
|-------|-------| |-------|-------|
| `MONGODB_RESTORE` | Restore edc DB z Tower přes SSH stream | | `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!
+94
View File
@@ -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"
+99
View File
@@ -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"
+92
View File
@@ -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"
+86 -31
View File
@@ -1,43 +1,86 @@
#!/bin/bash #!/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 # KONFIGURACE
# ========================================================== # ==============================================================================
CONTAINER_NAME="MongoDB" CONTAINER_NAME="MongoDB" # název Docker kontejneru na Tower
MONGO_HOST="localhost" MONGO_HOST="localhost" # MongoDB naslouchá uvnitř kontejneru na localhost
MONGO_PORT="27017" MONGO_PORT="27017" # výchozí MongoDB port
UNRAID_NAME="tower" 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" BASE_PATH="/mnt/user/Backup/Critical/MongoDBBackup" # kořenový adresář všech MongoDB záloh
KEEP_DAYS=3 KEEP_DAYS=3 # zálohy starší než 3 dny se smažou v prvním průchodu rotace
KEEP_COUNT=3 # počet posledních záloh k udržení (mimo time-based) 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") WHAT_TO_BACKUP=("admin" "edc")
# ========================================================== # ==============================================================================
# START # START
# ========================================================== # ==============================================================================
echo "Starting scheduled backup for: ${WHAT_TO_BACKUP[*]}" echo "Starting scheduled backup for: ${WHAT_TO_BACKUP[*]}"
START_TS=$(date '+%Y-%m-%d %H:%M:%S') 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 for DB_NAME in "${WHAT_TO_BACKUP[@]}"; do
echo "------------------------------------------" echo "------------------------------------------"
echo "Processing database: $DB_NAME" echo "Processing database: $DB_NAME"
DATE=$(date +%Y-%m-%d_%H%M) 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" FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DB_NAME/$DATE" # cílový adresář pro tuto zálohu
mkdir -p "$FINAL_PATH" mkdir -p "$FINAL_PATH"
DUMP_FILE="$FINAL_PATH/$DB_NAME.archive.gz" DUMP_FILE="$FINAL_PATH/$DB_NAME.archive.gz" # výsledný archiv
ERR_FILE="$FINAL_PATH/$DB_NAME.err" ERR_FILE="$FINAL_PATH/$DB_NAME.err" # sem jde stderr mongodump
# ====================================================== # --------------------------------------------------------------------------
# DUMP + GZIP (streamuje přes stdout, nepotřebuje volume mount) # DUMP + GZIP
# Zachovává: indexy, metadata, validators, all collections # 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 \ docker exec "$CONTAINER_NAME" mongodump \
--host="$MONGO_HOST" \ --host="$MONGO_HOST" \
--port="$MONGO_PORT" \ --port="$MONGO_PORT" \
@@ -49,9 +92,14 @@ for DB_NAME in "${WHAT_TO_BACKUP[@]}"; do
EXIT_CODE=$? 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 if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then
echo "SUCCESS: $DB_NAME backed up successfully" echo "SUCCESS: $DB_NAME backed up successfully"
echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" 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)" [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)"
fi fi
# ====================================================== # --------------------------------------------------------------------------
# CLEANUP OLD BACKUPS # ROTACE STARÝCH ZÁLOH — dvoustupňová
# 1. Smaž zálohy starší než KEEP_DAYS dnů #
# 2. Z toho co zbyde, ponech jen posledních KEEP_COUNT # 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..." echo "Cleaning up old backups for $DB_NAME..."
find "$BASE_PATH/$UNRAID_NAME/$DB_NAME" \ find "$BASE_PATH/$UNRAID_NAME/$DB_NAME" \
-mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \
-exec rm -rf {} \; -exec rm -rf {} \;
# Nech jen posledních KEEP_COUNT záloh
ls -td "$BASE_PATH/$UNRAID_NAME/$DB_NAME"/*/ 2>/dev/null \ ls -td "$BASE_PATH/$UNRAID_NAME/$DB_NAME"/*/ 2>/dev/null \
| tail -n +$((KEEP_COUNT + 1)) \ | tail -n +$((KEEP_COUNT + 1)) \
| xargs -r rm -rf | xargs -r rm -rf
+56 -25
View File
@@ -1,20 +1,47 @@
#!/bin/bash #!/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 <cesta_k_záloze>
# 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: <base_path>/<DB_NAME>/YYYY-MM-DD_HHMM/<DB_NAME>.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 set -x
# ========================================================== # ==============================================================================
# CONFIGURATION # KONFIGURACE
# ========================================================== # ==============================================================================
CONTAINER_NAME="MongoDB" CONTAINER_NAME="MongoDB" # název Docker kontejneru na Tower
MONGO_HOST="localhost" MONGO_HOST="localhost"
MONGO_PORT="27017" MONGO_PORT="27017"
# Cesta k záloze kterou chceš obnovit # Cesta k záloze — předávána jako první argument skriptu.
# Např: /mnt/user/Backup/Critical/MongoDBBackup/tower # Může být bud přímo adresář konkrétní zálohy, nebo základní cesta s DB podsložkami.
BACKUP_BASE_PATH="$1" BACKUP_BASE_PATH="$1"
# ========================================================== # ==============================================================================
# VALIDATION # VALIDACE VSTUPŮ
# ========================================================== # ==============================================================================
if [ -z "$BACKUP_BASE_PATH" ]; then if [ -z "$BACKUP_BASE_PATH" ]; then
echo "Usage: $0 <backup_base_path>" echo "Usage: $0 <backup_base_path>"
echo "Example: $0 /mnt/user/Backup/Critical/MongoDBBackup/tower" echo "Example: $0 /mnt/user/Backup/Critical/MongoDBBackup/tower"
@@ -29,23 +56,24 @@ fi
echo "Starting MongoDB restore from: $BACKUP_BASE_PATH" echo "Starting MongoDB restore from: $BACKUP_BASE_PATH"
START_TS=$(date '+%Y-%m-%d %H:%M:%S') START_TS=$(date '+%Y-%m-%d %H:%M:%S')
# ========================================================== # ==============================================================================
# RESTORE PROCESS # RESTORE PROCESS
# ========================================================== # ==============================================================================
# Iterujeme přes podadresáře base_path — každý podadresář = jedna databáze.
# Najdi nejnovější backupy pro každou DB
# Struktura: $BACKUP_BASE_PATH/DB_NAME/YYYY-MM-DD_HHMM/DB_NAME.archive.gz # Struktura: $BACKUP_BASE_PATH/DB_NAME/YYYY-MM-DD_HHMM/DB_NAME.archive.gz
for DB_DIR in "$BACKUP_BASE_PATH"/*; do for DB_DIR in "$BACKUP_BASE_PATH"/*; do
if [ ! -d "$DB_DIR" ]; then if [ ! -d "$DB_DIR" ]; then
continue continue # přeskoč soubory (pokud by nějaké byly)
fi fi
DB_NAME=$(basename "$DB_DIR") DB_NAME=$(basename "$DB_DIR")
echo "------------------------------------------" echo "------------------------------------------"
echo "Processing database: $DB_NAME" 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) LATEST_BACKUP=$(ls -td "$DB_DIR"/*/ 2>/dev/null | head -1)
if [ -z "$LATEST_BACKUP" ]; then if [ -z "$LATEST_BACKUP" ]; then
@@ -53,8 +81,8 @@ for DB_DIR in "$BACKUP_BASE_PATH"/*; do
continue continue
fi fi
BACKUP_FILE="$LATEST_BACKUP/${DB_NAME}.archive.gz" BACKUP_FILE="$LATEST_BACKUP/${DB_NAME}.archive.gz" # archiv pro tuto DB
ERR_FILE="$LATEST_BACKUP/${DB_NAME}.restore.err" ERR_FILE="$LATEST_BACKUP/${DB_NAME}.restore.err" # sem jde stderr mongorestore
if [ ! -f "$BACKUP_FILE" ]; then if [ ! -f "$BACKUP_FILE" ]; then
echo "ERROR: Backup archive not found: $BACKUP_FILE" 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 date: $(stat -c %y "$BACKUP_FILE" | cut -d' ' -f1-2)"
echo "Backup size: $(du -h "$BACKUP_FILE" | cut -f1)" echo "Backup size: $(du -h "$BACKUP_FILE" | cut -f1)"
# ====================================================== # --------------------------------------------------------------------------
# RESTORE TO MONGODB (streamuje přes stdin z hosta) # RESTORE DO MONGODB
# Restores: indexy, metadata, validators, all collections # 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..." echo "Restoring $DB_NAME to MongoDB..."
docker exec -i "$CONTAINER_NAME" mongorestore \ docker exec -i "$CONTAINER_NAME" mongorestore \
--host="$MONGO_HOST" \ --host="$MONGO_HOST" \
@@ -82,12 +113,12 @@ for DB_DIR in "$BACKUP_BASE_PATH"/*; do
RESTORE_EXIT=$? RESTORE_EXIT=$?
# ====================================================== # --------------------------------------------------------------------------
# VALIDATION # VALIDACE VÝSLEDKU
# ====================================================== # --------------------------------------------------------------------------
if [ $RESTORE_EXIT -eq 0 ]; then if [ $RESTORE_EXIT -eq 0 ]; then
echo "SUCCESS: $DB_NAME restored successfully" echo "SUCCESS: $DB_NAME restored successfully"
rm -f "$ERR_FILE" rm -f "$ERR_FILE" # .err soubor je prázdný — uklidíme
else else
echo "ERROR: Restore failed for database: $DB_NAME" echo "ERROR: Restore failed for database: $DB_NAME"
echo "Exit code: $RESTORE_EXIT" echo "Exit code: $RESTORE_EXIT"
+58 -27
View File
@@ -1,32 +1,53 @@
#!/bin/bash #!/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 set -x
# ========================================================== # ==============================================================================
# VERIFY BACKUP INTEGRITY # KONFIGURACE
# Kontroluje že se indexy, metadata, a všechny kolekce # ==============================================================================
# správně obnovily
# ==========================================================
CONTAINER_NAME="MongoDB" CONTAINER_NAME="MongoDB"
MONGO_HOST="localhost" MONGO_HOST="localhost"
MONGO_PORT="27017" 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") WHAT_TO_VERIFY=("admin" "fio" "torrents" "OrdinaceDropBoxBackup" "medevio" "kanboard" "medicus" "studie" "puzzle")
echo "Starting backup integrity verification..." echo "Starting backup integrity verification..."
echo "==========================================" 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 for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do
echo "" echo ""
echo "Verifying database: $DB_NAME" echo "Verifying database: $DB_NAME"
echo "------------------------------------------" 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 \ DB_EXISTS=$(docker exec "$CONTAINER_NAME" mongosh \
--host="$MONGO_HOST" \ --host="$MONGO_HOST" \
--port="$MONGO_PORT" \ --port="$MONGO_PORT" \
@@ -36,13 +57,15 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do
if [ "$DB_EXISTS" != "true" ]; then if [ "$DB_EXISTS" != "true" ]; then
echo "❌ ERROR: Database $DB_NAME does not exist!" echo "❌ ERROR: Database $DB_NAME does not exist!"
ALL_OK=false ALL_OK=false
continue continue # bez DB nemá smysl kontrolovat dál
fi fi
echo "✅ Database exists: $DB_NAME" 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 \ COLLECTION_COUNT=$(docker exec "$CONTAINER_NAME" mongosh \
--host="$MONGO_HOST" \ --host="$MONGO_HOST" \
--port="$MONGO_PORT" \ --port="$MONGO_PORT" \
@@ -51,9 +74,10 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do
echo "✅ Collections count: $COLLECTION_COUNT" 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:" echo " Collections:"
docker exec "$CONTAINER_NAME" mongosh \ docker exec "$CONTAINER_NAME" mongosh \
--host="$MONGO_HOST" \ --host="$MONGO_HOST" \
@@ -61,9 +85,11 @@ for DB_NAME in "${WHAT_TO_VERIFY[@]}"; do
--quiet \ --quiet \
--eval "use $DB_NAME; db.getCollectionNames().forEach(c => print(' - ' + c))" --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:" echo " Indexes per collection:"
docker exec "$CONTAINER_NAME" mongosh \ docker exec "$CONTAINER_NAME" mongosh \
--host="$MONGO_HOST" \ --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:" echo " Collection stats:"
docker exec "$CONTAINER_NAME" mongosh \ docker exec "$CONTAINER_NAME" mongosh \
--host="$MONGO_HOST" \ --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 if [ "$DB_NAME" = "admin" ]; then
echo " Users and Roles:" echo " Users and Roles:"
USER_COUNT=$(docker exec "$CONTAINER_NAME" mongosh \ USER_COUNT=$(docker exec "$CONTAINER_NAME" mongosh \
+108 -34
View File
@@ -1,68 +1,142 @@
# PostgreSQL Backup - Tower # 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 | | Parametr | Hodnota |
|----------|---------| |----------|---------|
| Server | Tower (192.168.1.76) | | Docker kontejner | `postgresql18` |
| Container | `postgresql18` | | Image | `postgres:18` |
| Port | 5432 | | Port | `5432` (host) → `5432` (container) |
| User | `vladimir.buzalka` | | User | `vladimir.buzalka` |
| Metoda | `pg_dumpall` (všechny DB najednou) | | Password | `Vlado7309208104++` |
| Záloha | `/mnt/user/Backup/Critical/PostgreSQLBackup/tower/YYYY-MM-DD_HHMM/` | | 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 | | 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 ## Skripty
### `postgresqlbackup_with_gzip.sh` | Soubor | Popis |
Provede `pg_dumpall` všech databází, rolí a tablespaces. Výstup je komprimovaný gzip soubor `all_databases.sql.gz`. |--------|-------|
| `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
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: ### Restore postgresql18 (nejnovější záloha)
```
/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).
```bash ```bash
# Nejnovější záloha (automaticky)
bash postgresqlrestore_from_backup.sh 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` ```bash
Ověří po restore že jsou všechny DB, tabulky, indexy a role přítomny a funkční. 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
bash verify_backup_integrity.sh bash verify_backup_integrity.sh
``` ```
## Nastavení Unraid User Script ---
Na Tower přidat User Script `POSTGRESQL_BACKUP`: ## Metoda zálohy
```bash - **pg_dumpall** — jeden soubor obsahující všechny DB, role, tablespaces, grants, extensions
#!/bin/bash - Streamuje přes `stdout | gzip` přímo na disk → žádný dočasný soubor uvnitř kontejneru
bash /boot/config/plugins/user.scripts/scripts/POSTGRESQL_BACKUP/postgresqlbackup_with_gzip.sh - 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
## Windows přístup k zálohám
``` ```
\\tower\Backup\Critical\PostgreSQLBackup\ \\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`.
+77 -27
View File
@@ -1,24 +1,60 @@
#!/bin/bash #!/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 set -x
# ========================================================== # ==============================================================================
# CONFIGURATION # KONFIGURACE
# ========================================================== # ==============================================================================
CONTAINER_NAME="postgresql18" CONTAINER_NAME="postgresql18" # Docker kontejner s PostgreSQL 18
PG_HOST="localhost" PG_HOST="localhost" # uvnitř kontejneru
PG_PORT="5432" PG_PORT="5432" # výchozí PG port
PG_USER="vladimir.buzalka" PG_USER="vladimir.buzalka" # superuser s právy pro pg_dumpall
export PGPASSWORD="Vlado7309208104++" export PGPASSWORD="Vlado7309208104++" # heslo jako env var (pg_dumpall čte PGPASSWORD)
UNRAID_NAME="tower" UNRAID_NAME="tower"
BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup" BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup"
KEEP_DAYS=7 KEEP_DAYS=7 # zálohy starší než 7 dní se smažou v prvním průchodu
KEEP_COUNT=7 # počet posledních záloh k udržení (mimo time-based) KEEP_COUNT=7 # z toho co zbyde, ponech vždy alespoň 7 záloh
# ========================================================== # ==============================================================================
# START # START
# ========================================================== # ==============================================================================
echo "Starting PostgreSQL full backup (pg_dumpall)" echo "Starting PostgreSQL full backup (pg_dumpall)"
START_TS=$(date '+%Y-%m-%d %H:%M:%S') 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" FINAL_PATH="$BASE_PATH/$UNRAID_NAME/$DATE"
mkdir -p "$FINAL_PATH" mkdir -p "$FINAL_PATH"
DUMP_FILE="$FINAL_PATH/all_databases.sql.gz" DUMP_FILE="$FINAL_PATH/all_databases.sql.gz" # výsledný komprimovaný dump
ERR_FILE="$FINAL_PATH/all_databases.err" ERR_FILE="$FINAL_PATH/all_databases.err" # stderr pg_dumpall
# ========================================================== # ==============================================================================
# DUMP + GZIP # DUMP + GZIP
# pg_dumpall zachovává: všechny DB, role, tablespaces, grants # docker exec spustí pg_dumpall uvnitř kontejneru.
# Streamuje přes stdout, nepotřebuje volume mount # -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 \ docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
"$CONTAINER_NAME" \ "$CONTAINER_NAME" \
@@ -45,11 +86,17 @@ docker exec \
--if-exists \ --if-exists \
| gzip > "$DUMP_FILE" 2> "$ERR_FILE" | 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]} 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 if [ $EXIT_CODE -eq 0 ] && [ -s "$DUMP_FILE" ] && [ ! -s "$ERR_FILE" ]; then
echo "SUCCESS: Full PostgreSQL backup completed" echo "SUCCESS: Full PostgreSQL backup completed"
echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)" echo "Dump size: $(du -h "$DUMP_FILE" | cut -f1)"
@@ -63,17 +110,20 @@ else
[ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)" [ -s "$ERR_FILE" ] && cat "$ERR_FILE" || echo " (no stderr output)"
fi fi
# ========================================================== # ==============================================================================
# CLEANUP OLD BACKUPS # ROTACE STARÝCH ZÁLOH — dvoustupňová
# 1. Smaž zálohy starší než KEEP_DAYS dnů #
# 2. Z toho co zbyde, ponech jen posledních KEEP_COUNT # 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..." echo "Cleaning up old backups..."
find "$BASE_PATH/$UNRAID_NAME" \ find "$BASE_PATH/$UNRAID_NAME" \
-mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \ -mindepth 1 -maxdepth 1 -type d -mtime +$KEEP_DAYS \
-exec rm -rf {} \; -exec rm -rf {} \;
# Nech jen posledních KEEP_COUNT záloh
ls -td "$BASE_PATH/$UNRAID_NAME"/*/ 2>/dev/null \ ls -td "$BASE_PATH/$UNRAID_NAME"/*/ 2>/dev/null \
| tail -n +$((KEEP_COUNT + 1)) \ | tail -n +$((KEEP_COUNT + 1)) \
| xargs -r rm -rf | xargs -r rm -rf
@@ -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
@@ -1,27 +1,56 @@
#!/bin/bash #!/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 set -x
# ========================================================== # ==============================================================================
# CONFIGURATION # KONFIGURACE
# ========================================================== # ==============================================================================
CONTAINER_NAME="postgresql18" CONTAINER_NAME="postgresql18"
PG_HOST="localhost" PG_HOST="localhost"
PG_PORT="5432" PG_PORT="5432"
PG_USER="vladimir.buzalka" PG_USER="vladimir.buzalka"
export PGPASSWORD="Vlado7309208104++" export PGPASSWORD="Vlado7309208104++"
# Cesta k záloze kterou chceš obnovit # Volitelný argument — konkrétní adresář zálohy.
# Lze předat jako argument, nebo nechá najít nejnovější # Pokud není zadán, skript sám najde nejnovější.
# Např: $0 /mnt/user/Backup/Critical/PostgreSQLBackup/tower/2026-05-23_0200
BACKUP_DIR="$1" BACKUP_DIR="$1"
BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup/tower" BASE_PATH="/mnt/user/Backup/Critical/PostgreSQLBackup/tower"
# ========================================================== # ==============================================================================
# FIND BACKUP # NALEZENÍ ZÁLOHY
# ========================================================== # ==============================================================================
if [ -z "$BACKUP_DIR" ]; then if [ -z "$BACKUP_DIR" ]; then
echo "No backup path specified - using latest available backup" 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) BACKUP_DIR=$(ls -td "$BASE_PATH"/*/ 2>/dev/null | head -1)
if [ -z "$BACKUP_DIR" ]; then 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" ERR_FILE="$BACKUP_DIR/all_databases.restore.err"
# ========================================================== # ==============================================================================
# RESTORE # RESTORE
# pg_dumpall dump se obnovuje přes psql # gunzip -c: dekomprimuje all_databases.sql.gz na stdout (soubor zůstane)
# --clean + --if-exists v dumpu zajišťuje drop před recreate # 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..." echo "Restoring all databases to PostgreSQL..."
gunzip -c "$BACKUP_FILE" \ gunzip -c "$BACKUP_FILE" \
| docker exec -i \ | docker exec -i \
@@ -66,11 +99,12 @@ gunzip -c "$BACKUP_FILE" \
--dbname="postgres" \ --dbname="postgres" \
2> "$ERR_FILE" 2> "$ERR_FILE"
# PIPESTATUS[1] = exit kód docker exec / psql (index 1 = druhý prvek pipe)
RESTORE_EXIT=${PIPESTATUS[1]} RESTORE_EXIT=${PIPESTATUS[1]}
# ========================================================== # ==============================================================================
# VALIDATION # VALIDACE
# ========================================================== # ==============================================================================
if [ $RESTORE_EXIT -eq 0 ]; then if [ $RESTORE_EXIT -eq 0 ]; then
echo "SUCCESS: All databases restored successfully" echo "SUCCESS: All databases restored successfully"
rm -f "$ERR_FILE" rm -f "$ERR_FILE"
+65 -24
View File
@@ -1,12 +1,28 @@
#!/bin/bash #!/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 set -x
# ========================================================== # ==============================================================================
# VERIFY BACKUP INTEGRITY # KONFIGURACE
# Kontroluje že se všechny DB, tabulky a data # ==============================================================================
# správně obnovily po restore
# ==========================================================
CONTAINER_NAME="postgresql18" CONTAINER_NAME="postgresql18"
PG_HOST="localhost" PG_HOST="localhost"
PG_PORT="5432" PG_PORT="5432"
@@ -18,9 +34,11 @@ echo "=========================================="
ALL_OK=true ALL_OK=true
# ========================================================== # ==============================================================================
# 1. CONNECTIVITY CHECK # 1. KONEKTIVITA
# ========================================================== # Jednoduchý SELECT 'connected' — pokud selže, PostgreSQL není dostupný
# a nemá smysl pokračovat.
# ==============================================================================
echo "" echo ""
echo "Checking PostgreSQL connectivity..." echo "Checking PostgreSQL connectivity..."
docker exec \ docker exec \
@@ -40,9 +58,12 @@ if [ $? -ne 0 ]; then
fi fi
echo "OK: Connected to PostgreSQL" 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 ""
echo "Databases present:" echo "Databases present:"
docker exec \ docker exec \
@@ -56,7 +77,8 @@ docker exec \
--tuples-only \ --tuples-only \
--command="SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" --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 \ DATABASES=$(docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
"$CONTAINER_NAME" \ "$CONTAINER_NAME" \
@@ -69,17 +91,21 @@ DATABASES=$(docker exec \
--command="SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" \ --command="SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname;" \
| tr -d ' ') | tr -d ' ')
# ========================================================== # ==============================================================================
# 3. PER-DATABASE CHECKS # 3. KONTROLA KAŽDÉ DATABÁZE
# ========================================================== # ==============================================================================
for DB_NAME in $DATABASES; do 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 ""
echo "Verifying database: $DB_NAME" echo "Verifying database: $DB_NAME"
echo "------------------------------------------" 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 \ TABLE_COUNT=$(docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
"$CONTAINER_NAME" \ "$CONTAINER_NAME" \
@@ -94,7 +120,12 @@ for DB_NAME in $DATABASES; do
echo "OK: Tables in public schema: $TABLE_COUNT" 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:" echo " Table row counts:"
docker exec \ docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
@@ -113,7 +144,11 @@ SELECT
FROM pg_stat_user_tables FROM pg_stat_user_tables
ORDER BY relname;" 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 \ INDEX_COUNT=$(docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
"$CONTAINER_NAME" \ "$CONTAINER_NAME" \
@@ -128,7 +163,10 @@ ORDER BY relname;"
echo "OK: Indexes in public schema: $INDEX_COUNT" 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 \ DB_SIZE=$(docker exec \
-e PGPASSWORD="$PGPASSWORD" \ -e PGPASSWORD="$PGPASSWORD" \
"$CONTAINER_NAME" \ "$CONTAINER_NAME" \
@@ -145,9 +183,12 @@ ORDER BY relname;"
done 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 ""
echo "PostgreSQL roles:" echo "PostgreSQL roles:"
docker exec \ docker exec \