Leon4gr45's picture
Deploy complete BrainAPI source ea2acb63d782
89e57b7 verified
Raw History Blame Contribute Delete
3.23 kB
"""Cross-process dirty generation and public, credential-free backup status."""
import fcntl
import json
import os
from contextlib import contextmanager
from pathlib import Path
def enabled():
return os.getenv("BRAIN_BACKUP_ENABLED", "false").lower() == "true"
def state_root():
return Path(os.getenv("BRAIN_BACKUP_STATE_DIR", "/tmp/brainapi-backup-state"))
@contextmanager
def locked(path):
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a+") as stream:
if os.fstat(stream.fileno()).st_uid == os.geteuid():
os.fchmod(stream.fileno(), 0o660)
fcntl.flock(stream, fcntl.LOCK_EX)
stream.seek(0)
yield stream
def generation():
with locked(state_root() / "dirty") as stream:
return int(stream.read() or "0")
def mark_dirty():
if not enabled():
return
# Called after COMMIT: do not turn a committed mutation into a false HTTP 500.
try:
with locked(state_root() / "dirty") as stream:
value = int(stream.read() or "0") + 1
stream.seek(0)
stream.truncate()
stream.write(str(value))
stream.flush()
os.fsync(stream.fileno())
except OSError:
import logging
logging.getLogger(__name__).error("BACKUP_DIRTY_MARK_FAILED; periodic reconciliation remains active")
def write_status(**fields):
path = state_root() / "status.json"
path.parent.mkdir(parents=True, exist_ok=True)
temp = path.with_suffix(f".{os.getpid()}.tmp")
try:
prior = json.loads(path.read_text())
except (OSError, ValueError):
prior = {}
startup_id = os.getenv("BRAIN_BACKUP_STARTUP_ID")
if prior.get("startup_id") != startup_id:
prior = {}
prior.update(fields, startup_id=startup_id)
if prior.get("state") == "ready":
prior.pop("error", None)
temp.write_text(json.dumps(prior, sort_keys=True))
temp.replace(path)
def public_status():
if not enabled():
return {"enabled": False}
try:
value = json.loads((state_root() / "status.json").read_text())
from datetime import datetime, timezone
stamp = value.get("last_success")
age = max(0., (datetime.now(timezone.utc) - datetime.fromisoformat(stamp)).total_seconds()) if stamp else None
local, remote = value.get("commit"), value.get("remote_checkpoint")
dirty = generation() != value.get("checkpoint_generation", 0)
return {"enabled": True, **value, "dirty": dirty, "backup_dirty": dirty,
"restored_checkpoint": value.get("restored_checkpoint"),
"remote_checkpoint": remote, "checkpoint_lag": (0 if local == remote else 1) if remote else None,
"backup_last_success": stamp, "backup_age_seconds": age}
except (OSError, ValueError):
return {"enabled": True, "state": "not_ready"}
def require_startup_ready():
if not enabled():
return
status = public_status()
if not os.getenv("BRAIN_BACKUP_STARTUP_ID") or status.get("startup_id") != os.getenv("BRAIN_BACKUP_STARTUP_ID") or status.get("state") not in {"ready", "degraded"}:
raise RuntimeError("BACKUP_STARTUP_RESTORE_REQUIRED")