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}")
|