SysOps: deploy-all — 2026-06-09 10:41 UTC
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Daily configuration backup to Gitea — executed after CEO approval."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from app.db import execute, fetch_all, fetch_one
|
||||
|
||||
BACKUP_ROOT = Path(os.getenv("BACKUP_ROOT", "/data/backup-root"))
|
||||
GITEA_REMOTE = os.getenv(
|
||||
"GITEA_BACKUP_REMOTE",
|
||||
"http://sysops:Foodlinkk%23SysOps2026@gitea:3001/aissa/foodlinkk-command-center.git",
|
||||
)
|
||||
GITEA_GIT_USER = os.getenv("GITEA_SYSOPS_USER", "sysops")
|
||||
GITEA_GIT_EMAIL = os.getenv("GITEA_SYSOPS_EMAIL", "sysops@foodlinkk.local")
|
||||
GITEA_GIT_NAME = os.getenv("GITEA_SYSOPS_NAME", "sysops")
|
||||
GITEA_BRANCH = os.getenv("GITEA_BACKUP_BRANCH", "main")
|
||||
|
||||
BACKUP_PATHS = [
|
||||
"docker-compose.yml",
|
||||
".env.example",
|
||||
"migrations",
|
||||
"monitoring",
|
||||
"cockpit/requirements.txt",
|
||||
"tools-api/requirements.txt",
|
||||
"deploy-all.sh",
|
||||
"README.md",
|
||||
]
|
||||
|
||||
|
||||
def _iso_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def _run(cmd: list[str], cwd: Path | None = None, timeout: int = 120) -> dict[str, Any]:
|
||||
try:
|
||||
proc = subprocess.run( # noqa: S603
|
||||
cmd,
|
||||
cwd=str(cwd) if cwd else None,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout,
|
||||
check=False,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
return {"ok": False, "error": "timeout", "cmd": cmd}
|
||||
except FileNotFoundError as exc:
|
||||
return {"ok": False, "error": str(exc), "cmd": cmd}
|
||||
return {
|
||||
"ok": proc.returncode == 0,
|
||||
"code": proc.returncode,
|
||||
"stdout": (proc.stdout or "").strip(),
|
||||
"stderr": (proc.stderr or "").strip(),
|
||||
"cmd": cmd,
|
||||
}
|
||||
|
||||
|
||||
def _table_exists(name: str) -> bool:
|
||||
row = fetch_one(
|
||||
"""
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = %s
|
||||
) AS ok
|
||||
""",
|
||||
(name,),
|
||||
)
|
||||
return bool(row and row.get("ok"))
|
||||
|
||||
|
||||
def _record_backup(
|
||||
status: str,
|
||||
message: str,
|
||||
files_count: int = 0,
|
||||
commit_ref: str | None = None,
|
||||
approval_request_id: int | None = None,
|
||||
payload: dict | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
if not _table_exists("config_backups"):
|
||||
return None
|
||||
row = fetch_one(
|
||||
"""
|
||||
INSERT INTO config_backups (approval_request_id, commit_ref, files_count, status, message, payload)
|
||||
VALUES (%s, %s, %s, %s, %s, %s::jsonb)
|
||||
RETURNING id, created_at
|
||||
""",
|
||||
(
|
||||
approval_request_id,
|
||||
commit_ref,
|
||||
files_count,
|
||||
status,
|
||||
message,
|
||||
json.dumps(payload or {}),
|
||||
),
|
||||
)
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def list_backups(limit: int = 20) -> list[dict[str, Any]]:
|
||||
if not _table_exists("config_backups"):
|
||||
return []
|
||||
rows = fetch_all(
|
||||
"SELECT * FROM config_backups ORDER BY created_at DESC LIMIT %s",
|
||||
(max(1, min(limit, 100)),),
|
||||
)
|
||||
out = []
|
||||
for r in rows:
|
||||
item = dict(r)
|
||||
if hasattr(item.get("created_at"), "isoformat"):
|
||||
item["created_at"] = item["created_at"].isoformat()
|
||||
out.append(item)
|
||||
return out
|
||||
|
||||
|
||||
def prepare_backup_manifest() -> dict[str, Any]:
|
||||
root = BACKUP_ROOT
|
||||
files: list[str] = []
|
||||
if root.exists():
|
||||
for rel in BACKUP_PATHS:
|
||||
p = root / rel
|
||||
if p.is_file():
|
||||
files.append(rel)
|
||||
elif p.is_dir():
|
||||
for fp in p.rglob("*"):
|
||||
if fp.is_file() and not any(x in str(fp) for x in [".git", "__pycache__", ".pyc"]):
|
||||
files.append(str(fp.relative_to(root)))
|
||||
return {
|
||||
"root": str(root),
|
||||
"root_exists": root.exists(),
|
||||
"files": sorted(files)[:500],
|
||||
"files_count": len(files),
|
||||
"paths_configured": BACKUP_PATHS,
|
||||
"generated_at": _iso_now(),
|
||||
}
|
||||
|
||||
|
||||
def run_gitea_backup(approval_request_id: int | None = None) -> dict[str, Any]:
|
||||
from app.connectors.gitea_repo_sync import run_gitea_repo_sync
|
||||
|
||||
return run_gitea_repo_sync(reason="approved_backup", approval_request_id=approval_request_id)
|
||||
Reference in New Issue
Block a user