150 lines
5.4 KiB
Python
150 lines
5.4 KiB
Python
#!/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}")
|