notebookvb

This commit is contained in:
Vladimir Buzalka
2026-06-14 12:07:35 +02:00
parent 9133fe9497
commit 2bdac59676
16 changed files with 1484 additions and 29 deletions
+95
View File
@@ -0,0 +1,95 @@
# MedicusFirebird — Firebird 2.5 zrcadlo Medicus DB na toweru
Kontejnerizované zrcadlo ostré Medicus databáze (Firebird) na serveru **tower** (Unraid, 192.168.1.76).
Nahrazuje dosavadní restore na Windows VM **reporter** — tu lze po ověření na Firebird části vypnout.
## Proč
Všechny ostatní DB (MySQL, PostgreSQL, MongoDB, Redis) běží na toweru jako Docker.
Firebird sem logicky patří taky: jeden host, jeden režim záloh/monitoringu, žádná Windows VM navíc.
## Tok dat
```
Ordinace: gbak -> zip -> rsync na tower (~02:15)
Tower: /mnt/user/OrdinaceSynology/MedicusBackup/MEDICUS_RRMMDD_HHMM.zip (zalohy se HROMADI)
restore_medicus.sh (denne):
1) nejnovejsi MEDICUS_*.zip podle nazvu; pokud == last_restored.txt -> skip
2) pocka az velikost prestane rust (probiha-li jeste rsync) + overi unzip -t
3) unzip .fbk -> gbak -r do medicus_new.fdb -> stop+swap+start kontejneru
4) zapise marker (last_restored.txt)
5) GFS retence zaloh (prune_backups.sh)
Kontejner: firebird-medicus -> serve tower:3050 /firebird/data/medicus.fdb
```
Zdrojová DB: **Firebird 2.5.7**, ODS 11.2, dialect 3, page size 8192.
Image: `jacobalberty/firebird:2.5-ss` = **Firebird 2.5.9** (restore 2.5.7 → 2.5.9 v rámci řady OK).
## Soubory
| Soubor | Popis |
|--------|-------|
| `firebird_create.sh` | Jednorázové vytvoření / znovuvytvoření kontejneru |
| `restore_medicus.sh` | Denní rutina: obnova z nejnovější zálohy + retence (cron) |
| `prune_backups.sh` | GFS retence záloh (volá se z restore; lze i samostatně) |
| `verify_firebird.sh` | Kontrola: verze enginu, ODS, počet pacientů |
| `last_restored.txt` | Marker poslední úspěšně restorované zálohy (vzniká za běhu) |
| `restore.log` | Log denních běhů |
## Umístění na toweru
- Skripty: `/mnt/user/Scripts/MedicusFirebird/`
- Data kontejneru: `/mnt/user/appdata/firebird-medicus/fb` (→ `/firebird`, soubor `data/medicus.fdb`)
- Rozbalovací prostor pro `.fbk`: `/mnt/user/appdata/firebird-medicus/work` (→ `/work`)
## Kontejner
```
docker run -d --name firebird-medicus --restart unless-stopped \
-p 3050:3050 -e ISC_PASSWORD=masterkey -e TZ=Europe/Prague \
-v /mnt/user/appdata/firebird-medicus/fb:/firebird \
-v /mnt/user/appdata/firebird-medicus/work:/work \
jacobalberty/firebird:2.5-ss
```
Pozn.: `gbak`/`isql` jsou v `/usr/local/firebird/bin/` (nejsou v PATH → volat plnou cestou).
Hesla jsou v `security2.fdb` (nastaveno přes `ISC_PASSWORD`), ne v `medicus.fdb` — restore dat heslo nemění.
## Robustnost restoru
Zálohy se v adresáři **hromadí** a nejnovější se může právě **přenášet přes rsync**, proto:
- výběr nejnovější podle **názvu** (`RRMMDD_HHMM` → lexikálně = chronologicky)
- **stav** v `last_restored.txt` → když není nic novějšího, nic se nedělá
- **čeká na dokončení přenosu** (velikost se ustálí) a ověří integritu `unzip -t` — nikdy nezpracuje nekompletní soubor
- marker se zapíše **až po úspěšném** restoru; zámek (`flock`) proti souběhu
## Retence záloh (GFS, sekvenční, počítaná)
`prune_backups.sh` drží v adresáři záloh schéma:
1. **30 posledních dní** — nech všechny denní
2. **pak 8 týdnů** — z každého ISO-týdne 1× (nejnovější = konec týdne)
3. **pak 12 měsíců** — z každého měsíce 1× (nejnovější)
4. starší → smazat
Datum se čte **z názvu** (ne mtime). Neparsovatelné názvy se nikdy nemažou.
Bezpečnostní přepínač `DRY_RUN=1` (jen výpis) / `DRY_RUN=0` (maže). V denní rutině řízeno `RETENTION_DRYRUN`
v `restore_medicus.sh` (ostré = 0).
## Připojení klientů (fdb / DSN)
```
192.168.1.76:/firebird/data/medicus.fdb SYSDBA / masterkey, charset win1250
# nebo: tower:/firebird/data/medicus.fdb
```
V `Knihovny/medicus_db.py` je odpovídající záznam v `dsn_map` (klíč `TOWER`).
Cutover skriptů/MCP z reporteru (2.5.7) na tower (2.5.9) = otevřené rozhodnutí.
## Cron (na toweru)
Záloha přistává ~02:15; denní rutina poté. Plánovat přes **User Scripts plugin**
(vzor: `PostgreSQLRestoreFromBackup`), spouštět:
```
/mnt/user/Scripts/MedicusFirebird/restore_medicus.sh # napr. 06:30 denne
```
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# Vytvori (nebo znovuvytvori) Firebird 2.5 kontejner = zrcadlo Medicus DB na toweru.
# Spousti se jednorazove pri zakladani / zmene konfigurace.
set -euo pipefail
NAME=firebird-medicus
IMAGE=jacobalberty/firebird:2.5-ss
APPDATA=/mnt/user/appdata/firebird-medicus
FBDIR="$APPDATA/fb" # -> /firebird (data, system, security2.fdb)
WORKDIR="$APPDATA/work" # -> /work (sem se rozbaluje .fbk pred restorem)
PASS=masterkey
mkdir -p "$FBDIR" "$WORKDIR"
# odstran stary kontejner, pokud existuje (data v appdata zustanou)
docker rm -f "$NAME" 2>/dev/null || true
docker run -d \
--name "$NAME" \
--restart unless-stopped \
-p 3050:3050 \
-e ISC_PASSWORD="$PASS" \
-e TZ=Europe/Prague \
-v "$FBDIR":/firebird \
-v "$WORKDIR":/work \
"$IMAGE"
echo "Kontejner $NAME vytvoren. Cekam na start serveru..."
sleep 10
docker ps --filter "name=$NAME" --format "{{.Names}} {{.Status}} {{.Ports}}"
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# GFS retence PLNYCH zaloh Medicus (kazda zaloha je kompletni -> mazani ostatnich je bezpecne).
#
# SEKVENCNI, POCITANE tiery (jdou ZA sebou, neprekryvaji se), od nejnovejsiho zpet:
# 1) DENNI : poslednich 30 dni -> nech VSECHNY
# 2) TYDENNI : pak presne 8 tydnu dozadu -> z kazdeho ISO-tydne 1x (nejnovejsi = konec tydne)
# 3) MESICNI : pak presne 12 mesicu dozadu -> z kazdeho mesice 1x (nejnovejsi)
# 4) starsi : smazat
# Reference "ted" = datum NEJNOVEJSI zalohy. Datum se cte Z NAZVU MEDICUS_RRMMDD_HHMM.zip.
#
# BEZPECNOST: DRY_RUN=1 (default) jen vypisuje, NIC nemaze. DRY_RUN=0 skutecne maze.
# Neznamy/neparsovatelny nazev se NIKDY nemaze.
set -euo pipefail
BACKUP_DIR="${BACKUP_DIR:-/mnt/user/OrdinaceSynology/MedicusBackup}"
DAILY_DAYS="${DAILY_DAYS:-30}"
WEEKLY_WEEKS="${WEEKLY_WEEKS:-8}"
MONTHLY_MONTHS="${MONTHLY_MONTHS:-12}"
DRY_RUN="${DRY_RUN:-1}"
date_from_name() { local d="${1#MEDICUS_}"; d="${d:0:6}"; echo "20${d:0:2}-${d:2:2}-${d:4:2}"; }
mapfile -t FILES < <(cd "$BACKUP_DIR" && ls -1 MEDICUS_*.zip 2>/dev/null | sort -r) # nejnovejsi prvni
[ "${#FILES[@]}" -eq 0 ] && { echo "Zadne zalohy v $BACKUP_DIR"; exit 0; }
REF=$(date_from_name "${FILES[0]}")
date -d "$REF" >/dev/null 2>&1 || { echo "CHYBA: nelze precist datum z ${FILES[0]}"; exit 1; }
D_CUT=$(date -d "$REF -${DAILY_DAYS} days" +%F)
echo "REF=$REF denni>=$D_CUT, pak ${WEEKLY_WEEKS}x tydenni, pak ${MONTHLY_MONTHS}x mesicni (starsi smazat)"
declare -A KEEP seen_week seen_month
dn=0; w=0; m=0
for f in "${FILES[@]}"; do
dt=$(date_from_name "$f")
if ! date -d "$dt" >/dev/null 2>&1; then KEEP[$f]="?"; continue; fi # neparsovatelne -> ponechat
if [[ ! "$dt" < "$D_CUT" ]]; then KEEP[$f]="d"; dn=$((dn+1)); continue; fi # 1) denni (30 dni)
if [ "$w" -lt "$WEEKLY_WEEKS" ]; then # 2) tydenni (8x)
wk=$(date -d "$dt" +%G-%V)
[ -z "${seen_week[$wk]:-}" ] && { seen_week[$wk]=1; w=$((w+1)); KEEP[$f]="w"; }
continue
fi
if [ "$m" -lt "$MONTHLY_MONTHS" ]; then # 3) mesicni (12x)
mo=$(date -d "$dt" +%Y-%m)
[ -z "${seen_month[$mo]:-}" ] && { seen_month[$mo]=1; m=$((m+1)); KEEP[$f]="m"; }
continue
fi
done
mode="DRY-RUN (nic se nemaze)"; [ "$DRY_RUN" = "0" ] && mode="OSTRY (maze!)"
echo "=== GFS retence $mode | $BACKUP_DIR ==="
echo "schema: ${DAILY_DAYS}d / ${WEEKLY_WEEKS}t / ${MONTHLY_MONTHS}m | celkem: ${#FILES[@]} | ponechano: ${#KEEP[@]} (denni=$dn tydenni=$w mesicni=$m)"
del=0
for f in "${FILES[@]}"; do
if [ -n "${KEEP[$f]:-}" ]; then
printf ' KEEP [%s] %s\n' "${KEEP[$f]}" "$f"
else
printf ' DEL %s\n' "$f"; del=$((del+1))
[ "$DRY_RUN" = "0" ] && rm -f -- "$BACKUP_DIR/$f"
fi
done
echo "=== ke smazani: $del ==="
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
# Denni obnova zrcadla Medicus DB z nejnovejsi gbak zalohy do Firebird kontejneru
# + GFS retence zaloh.
#
# Zalohy se v adresari HROMADI a nejnovejsi se muze prave PRENASET pres rsync. Proto:
# - vybira nejnovejsi MEDICUS_*.zip podle nazvu (RRMMDD_HHMM -> lexikalne = chronologicky)
# - pamatuje si posledni uspesne restorovanou (last_restored.txt) -> neni-li nic novejsiho, konci
# - ceka, az velikost prestane rust (probiha-li rsync), a overi integritu (unzip -t),
# teprve pak restoruje -> nikdy nezpracuje nekompletni prenos
# - marker se zapise az PO uspesnem restoru
# - na konci spusti GFS retenci zaloh (prune_backups.sh)
set -euo pipefail
NAME=firebird-medicus
APPDATA=/mnt/user/appdata/firebird-medicus
DATA="$APPDATA/fb/data"
WORK="$APPDATA/work"
BACKUP_DIR=/mnt/user/OrdinaceSynology/MedicusBackup
GBAK=/usr/local/firebird/bin/gbak
PASS=masterkey
BASE_DIR=/mnt/user/Scripts/MedicusFirebird
STATE="$BASE_DIR/last_restored.txt"
LOG="$BASE_DIR/restore.log"
# Retence: ostra (maze dle GFS 30d/8t/12m). Pro testovaci beh prepnout na 1 (jen vypis).
RETENTION_DRYRUN="${RETENTION_DRYRUN:-0}"
exec >>"$LOG" 2>&1
exec 9>"$BASE_DIR/.restore.lock"
flock -n 9 || { echo "$(date '+%F %T') jiny restore uz bezi -> koncim."; exit 0; }
echo "===== $(date '+%F %T') restore start ====="
mkdir -p "$WORK" "$DATA"
# --- 1) nejnovejsi zaloha podle nazvu ---
ZIP=$(ls -1 "$BACKUP_DIR"/MEDICUS_*.zip 2>/dev/null | sort | tail -1 || true)
if [ -z "${ZIP:-}" ]; then echo "CHYBA: zadna zaloha v $BACKUP_DIR"; exit 1; fi
ZIP_BASE=$(basename "$ZIP")
LAST=$(cat "$STATE" 2>/dev/null || echo "")
echo "Nejnovejsi: $ZIP_BASE | posledni restorovana: ${LAST:-<zadna>}"
# --- 2) uz restorovana? ---
if [ "$ZIP_BASE" = "$LAST" ]; then
echo "Nic noveho -> restore preskocen."
else
# --- 3) pockej na dokonceni prenosu (velikost se ustali) ---
prev=-1; stable=0
for i in $(seq 1 80); do # max ~20 min cekani na rsync
cur=$(stat -c %s "$ZIP" 2>/dev/null || echo 0)
if [ "$cur" = "$prev" ] && [ "$cur" -gt 0 ]; then stable=1; break; fi
echo " ...$ZIP_BASE = $cur B, cekam na ustaleni"
prev=$cur; sleep 15
done
[ "$stable" = "1" ] || { echo "CHYBA: $ZIP_BASE se stale meni -> koncim (priste)."; exit 1; }
# --- 4) integrita (nekompletni/poskozeny zip neprojde) ---
echo "unzip -t ..."
unzip -tqq "$ZIP" || { echo "CHYBA: $ZIP_BASE neprosel unzip -t -> koncim."; exit 1; }
# --- 5) rozbaleni .fbk ---
rm -f "$WORK"/*.fbk
unzip -o "$ZIP" -d "$WORK" >/dev/null
FBK=$(ls -1t "$WORK"/*.fbk | head -1)
FBK_BASE=$(basename "$FBK")
echo "FBK: $FBK_BASE ($(du -h "$FBK" | cut -f1))"
# --- 6) restore pres bezici server do noveho souboru ---
docker start "$NAME" >/dev/null 2>&1 || true
sleep 8
echo "gbak restore -> medicus_new.fdb ..."
docker exec "$NAME" rm -f /firebird/data/medicus_new.fdb
docker exec "$NAME" "$GBAK" -r -p 8192 -user SYSDBA -password "$PASS" \
"/work/$FBK_BASE" "localhost:/firebird/data/medicus_new.fdb"
# --- 7) atomicky swap + restart ---
echo "swap + restart ..."
docker stop "$NAME" >/dev/null
mv -f "$DATA/medicus_new.fdb" "$DATA/medicus.fdb"
docker start "$NAME" >/dev/null
sleep 8
rm -f "$WORK"/*.fbk
# --- 8) marker az po uspechu ---
echo "$ZIP_BASE" > "$STATE"
echo "restore OK: $ZIP_BASE"
fi
# --- 9) GFS retence zaloh ---
echo "--- retence zaloh (DRY_RUN=$RETENTION_DRYRUN) ---"
DRY_RUN="$RETENTION_DRYRUN" "$BASE_DIR/prune_backups.sh" || echo "VAROVANI: prune_backups.sh selhal"
echo "===== $(date '+%F %T') hotovo ====="
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# Rychla kontrola obnoveneho zrcadla: verze enginu, ODS, pocet pacientu.
set -euo pipefail
NAME=firebird-medicus
PASS=masterkey
ISQL=/usr/local/firebird/bin/isql
DB=localhost:/firebird/data/medicus.fdb
docker exec -i "$NAME" "$ISQL" -user SYSDBA -password "$PASS" "$DB" <<'SQL'
SELECT rdb$get_context('SYSTEM','ENGINE_VERSION') AS ENGINE FROM rdb$database;
SELECT MON$ODS_MAJOR AS ODS_MAJOR, MON$ODS_MINOR AS ODS_MINOR, MON$PAGE_SIZE AS PAGE_SIZE FROM MON$DATABASE;
SELECT COUNT(*) AS PACIENTU FROM KAR;
QUIT;
SQL