#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Nightly Medicus automatic restore --------------------------------- Workflow: 1️⃣ Kill all Medicus processes 2️⃣ Restart Firebird service (to release all open connections) 3️⃣ Clean restore folder 4️⃣ Find and extract newest Medicus backup (.fbk or .zip) 5️⃣ Restore database using gbak (Firebird service kept running) 6️⃣ Restart Firebird again to refresh state 7️⃣ Log all actions with timestamps """ import os import psutil import shutil import zipfile import subprocess from pathlib import Path from datetime import datetime import time # ========================================= # 🔧 CONFIGURATION # ========================================= FIREBIRD_GBAK = Path(r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe") BACKUP_DIR = Path(r"G:\OnedriveOrdinace\OneDrive\MedicusBackup") RESTORE_DIR = Path(r"Z:\Medicus 3\restore") TARGET_DB = Path(r"Z:\Medicus 3\data\MEDICUS.FDB") SERVICE_NAME = "FirebirdServerCGM" USER = "SYSDBA" PASSWORD = "masterkey" LOG_FILE = RESTORE_DIR / f"nightly_restore_{datetime.now():%Y%m%d_%H%M%S}.log" # ========================================= # 🪓 1) Kill Medicus processes # ========================================= def kill_medicus(): targets = ["Medicus.exe", "MedicusServer.exe", "MedicusUpdater.exe"] with open(LOG_FILE, "w", encoding="utf-8") as log: log.write(f"==== NIGHTLY RESTORE START {datetime.now():%Y-%m-%d %H:%M:%S} ====\n") for proc in psutil.process_iter(["name", "pid"]): try: if proc.info["name"] and proc.info["name"].lower() in [t.lower() for t in targets]: log.write(f"Killing {proc.info['name']} (PID {proc.pid})\n") proc.kill() except (psutil.NoSuchProcess, psutil.AccessDenied): continue log.write("All Medicus processes terminated.\n") # ========================================= # ⚙️ 2) Firebird service control # ========================================= def service_cmd(action): cmd = f'net {action} "{SERVICE_NAME}"' result = subprocess.run(cmd, shell=True, capture_output=True, text=True) return result.returncode == 0, result.stdout + result.stderr def restart_firebird(label): ok, out = service_cmd("stop") with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"\n--- Restart Firebird ({label}) ---\n") log.write(f"Stopping {SERVICE_NAME}...\n{out}\n") time.sleep(5) ok, out = service_cmd("start") with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"Starting {SERVICE_NAME}...\n{out}\n") # ========================================= # 🧹 3) Clean restore folder # ========================================= def clean_restore_folder(): for item in RESTORE_DIR.glob("*"): try: if item.is_file(): item.unlink() elif item.is_dir(): shutil.rmtree(item) except Exception as e: with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"⚠️ Could not delete {item}: {e}\n") # ========================================= # 💾 4) Find newest backup and prepare .fbk # ========================================= def prepare_backup(): backups = list(BACKUP_DIR.glob("Medicus_*.fbk")) + list(BACKUP_DIR.glob("Medicus_*.zip")) if not backups: raise SystemExit(f"❌ No Medicus backups found in {BACKUP_DIR}") latest = max(backups, key=lambda f: f.stat().st_mtime) fbk_path = RESTORE_DIR / (latest.stem + ".fbk") with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"\nUsing backup: {latest}\n") if latest.suffix.lower() == ".fbk": shutil.copy2(latest, fbk_path) else: with zipfile.ZipFile(latest, "r") as zf: fbk_files = [n for n in zf.namelist() if n.lower().endswith(".fbk")] if not fbk_files: raise SystemExit("❌ ZIP file does not contain .fbk!") zf.extract(fbk_files[0], RESTORE_DIR) extracted = RESTORE_DIR / fbk_files[0] if extracted != fbk_path: shutil.move(extracted, fbk_path) return fbk_path # ========================================= # 🧩 5) Restore database using gbak # ========================================= def restore_database(fbk_path): cmd = f'"{FIREBIRD_GBAK}" -rep "{fbk_path}" "{TARGET_DB}" -user {USER} -pas {PASSWORD}' with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"\nRunning restore command:\n{cmd}\n") result = subprocess.run(cmd, shell=True, text=True, capture_output=True) with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(result.stdout) log.write(result.stderr) log.write(f"Return code: {result.returncode}\n") if result.returncode == 0: print("✅ Medicus database successfully restored!") else: print("❌ Restore failed! Check log for details.") # ========================================= # 🚀 MAIN WORKFLOW # ========================================= if __name__ == "__main__": kill_medicus() restart_firebird("before restore") clean_restore_folder() fbk = prepare_backup() restore_database(fbk) restart_firebird("after restore") with open(LOG_FILE, "a", encoding="utf-8") as log: log.write(f"\n==== RESTORE COMPLETED {datetime.now():%Y-%m-%d %H:%M:%S} ====\n") print(f"📝 Full log saved to {LOG_FILE}")