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