Add backup script for external Firebird databases
Nightly backup of all MEDICUS_FILES_*.fdb from U:\externi to U:\externabackup via gbak + zip with email notification. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
172
Backup/BackupExterniDB.py
Normal file
172
Backup/BackupExterniDB.py
Normal file
@@ -0,0 +1,172 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
SRC_DIR = Path(r"U:\externi")
|
||||
BACKUP_DIR = Path(r"U:\externabackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Find all FDB files (case-insensitive)
|
||||
fdb_files = sorted(SRC_DIR.glob("MEDICUS_FILES_*.fdb"))
|
||||
fdb_upper = sorted(SRC_DIR.glob("MEDICUS_FILES_*.FDB"))
|
||||
fdb_all = sorted(
|
||||
set(fdb_files + fdb_upper),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"{name}_{ts}.zip"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
|
||||
result = {
|
||||
"file": fdb.name,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
try:
|
||||
# 1) GBAK
|
||||
print(f"GBAK: {fdb.name} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
cmd = [
|
||||
GBAK, "-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
db_conn, str(fbk),
|
||||
"-v",
|
||||
]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd, stdout=f, stderr=subprocess.STDOUT, check=True,
|
||||
)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s)")
|
||||
|
||||
# 2) ZIP
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = result["fbk_size"]
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zipf, "w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(
|
||||
f"\r ZIP {name}: {pct:6.2f}%",
|
||||
end="", flush=True,
|
||||
)
|
||||
print()
|
||||
|
||||
result["t_zip"] = time.time() - t1
|
||||
result["zip_size"] = zipf.stat().st_size
|
||||
|
||||
# 3) DELETE FBK + LOG
|
||||
fbk.unlink()
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
backed_up.append(result)
|
||||
|
||||
except Exception:
|
||||
result["error"] = traceback.format_exc()
|
||||
errors.append(result)
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# Build report
|
||||
report = []
|
||||
report.append(f"Backup externi DB - {now.strftime('%d.%m.%Y %H:%M')}")
|
||||
report.append(f"Celkem souboru: {len(fdb_all)}")
|
||||
report.append(f"Zalohovano: {len(backed_up)}")
|
||||
report.append(f"Chyby: {len(errors)}")
|
||||
report.append("")
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Backed up ---")
|
||||
total_zip = 0
|
||||
for r in backed_up:
|
||||
total_zip += r["zip_size"]
|
||||
report.append(
|
||||
f" {r['file']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB -> "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"(gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
report.append(f" Total ZIP: {total_zip / 1024 / 1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- ERRORS ---")
|
||||
for r in errors:
|
||||
report.append(f" {r['file']}: {r['error']}")
|
||||
report.append("")
|
||||
|
||||
# Send email
|
||||
has_errors = len(errors) > 0
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} "
|
||||
f"MEDICUS externi DB - "
|
||||
f"backup {len(backed_up)}/{len(fdb_all)}"
|
||||
f"{f', {len(errors)} errors' if has_errors else ''}"
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user