Wire management-platform's backup/restore routes to the k8s scripts

api_backup_run and restore_start were still hardcoded to the Docker-era
backup-myapps.sh/restore-myapps.sh, which target Docker volumes that no
longer exist for the 5 apps now running in k3s (n8n, odoo, mautic,
nextcloud, frappe/erpnext) — every backup/restore triggered from the UI
has been silently hollow for these apps since the migration.

- api_backup_run: /root/backup-myapps.sh -> /root/CloudOps/backup/backup-k8s-apps.sh
- restore_start: switched from the old image-relative path (which resolved
  to /app/restore-myapps.sh, baked into the Docker image from platform/)
  to an absolute /root/CloudOps/backup/restore-k8s-apps.sh path — the new
  backup/ folder is a sibling of platform/, not part of the Docker build
  context, so it's only reachable via the pod's existing /root hostPath
  mount, the same way api_backup_run already reaches its script.

--apps flag shape and the job-log streaming contract are unchanged, so no
frontend/template changes needed for the core flow. modules/backups.py's
three hardcoded myapps-backup-* references (get_local_backups, get_vm_backups
x2, delete_backup's filename regex) now also recognize the new
myapps-k8s-backup-* prefix, so both backup lineages stay visible/manageable
in the UI.

Depends on the previous commit (scoped ServiceAccount + k3s kubectl access
for the pod) to actually function when triggered from the UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
root
2026-08-20 11:30:14 +02:00
parent f3f08c3ef6
commit a6fb9e5e18
2 changed files with 29 additions and 14 deletions

View File

@@ -56,8 +56,12 @@ def _ssh_main(remote_cmd, timeout=30):
# ────────────────────────────────────────────────────────────────
def get_local_backups():
# myapps-backup-* is the legacy Docker-era lineage; myapps-k8s-backup-*
# is the k8s-native one (backup-k8s-apps.sh). Listed together, sorted
# by mtime, so history from both stays visible in the UI.
stdout, _ = _ssh_main(
"ls -t /root/backups/myapps-backup-*.tar.gz 2>/dev/null | head -20"
"ls -t /root/backups/myapps-backup-*.tar.gz "
"/root/backups/myapps-k8s-backup-*.tar.gz 2>/dev/null | head -20"
)
files = []
if stdout:
@@ -77,7 +81,8 @@ def get_vm_backups():
f"-o StrictHostKeyChecking=no -o ConnectTimeout=10 "
f"-o BatchMode=yes "
f"{VM_USER}@{VM_HOST} "
f"'ls -t /backups/cloudproject/myapps-backup-*.tar.gz 2>/dev/null | head -20'"
f"'ls -t /backups/cloudproject/myapps-backup-*.tar.gz "
f"/backups/cloudproject/myapps-k8s-backup-*.tar.gz 2>/dev/null | head -20'"
)
stdout, _ = _run(cmd, timeout=25)
if stdout:
@@ -90,7 +95,10 @@ def get_vm_backups():
else:
backup_dir = '/backups/cloudproject'
if os.path.exists(backup_dir):
files = glob.glob(f'{backup_dir}/myapps-backup-*.tar.gz')
files = (
glob.glob(f'{backup_dir}/myapps-backup-*.tar.gz')
+ glob.glob(f'{backup_dir}/myapps-k8s-backup-*.tar.gz')
)
files.sort(key=os.path.getmtime, reverse=True)
vm_backups = [os.path.basename(f) for f in files[:20]]
return vm_backups
@@ -408,7 +416,11 @@ def _sha256_file(path):
# ────────────────────────────────────────────────────────────────
def delete_backup(backup_file, source='local'):
if not re.match(r'^myapps-backup-\d{8}_\d{6}\.tar\.gz$', backup_file):
# Accepts both the legacy Docker-era lineage (myapps-backup-*) and the
# k8s-native one (myapps-k8s-backup-*, from backup-k8s-apps.sh). Still a
# tight anchor match — this guards against path traversal in a filename
# that ends up interpolated into a shell/filesystem path below.
if not re.match(r'^myapps-(k8s-)?backup-\d{8}_\d{6}\.tar\.gz$', backup_file):
return False, f'Invalid backup filename: {backup_file}'
if source == 'local':