notebookvb
This commit is contained in:
@@ -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\")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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`.
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 \
|
||||||
|
|||||||
Reference in New Issue
Block a user