Ops-dashboard/app/settings/backups/_components/backups-panel.tsx
Madhura68 ab87c0fada feat(server-backup): restic dual-repo backup (NAS + B2) with dashboard UI
Adds a server-wide backup capability beyond the existing ops_dashboard
pg_dump flow:

- Daily systemd timer (03:30) runs pg_dumpall + Forgejo dump, then restic
  to a local NAS repo and an offsite Backblaze B2 repo with Object Lock.
  Phase-based script with single-instance flock, structured statusfile,
  systemd hardening, and live-datadir excludes (Postgres / Forgejo) so
  the dumps stay authoritative.
- Ops-agent gets nine new read-only/trigger commands (snapshots, stats,
  status, logs, plus two triggers) backed by sudoers-whitelisted wrapper
  scripts that source /etc/restic-backup.env so the agent never sees the
  restic password or B2 keys.
- Two new flows (server_backup_full, server_backup_restore_test) drive
  the dashboard's "Backup now" and "Restore test" buttons.
- /settings/backups gains a Server backup section with overall + per-phase
  status, NAS / B2 snapshot tables, restore-size / raw-data / dedup-ratio
  stats, and the last restore-test result. The existing pg_dump section
  is preserved unchanged.
- Runbook docs/runbooks/server-backup.md follows the tailscale-setup
  pattern (plan + addendum) and covers B2 Object Lock + scoped keys,
  Forgejo subplan with isolated restore-test stack, the off-server
  maintenance flow for B2 prune, and the integrity-check schedule.

Code-only change — installation on scrum4me-srv follows the runbook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 13:03:00 +02:00

53 lines
1.2 KiB
TypeScript

'use client'
import type { BackupFile } from '../page'
import type {
BackupStatusEnvelope,
ResticSnapshot,
ResticStats,
} from '../_lib/types'
import DatabaseBackupsSection from './database-backups-section'
import ServerBackupSection from './server-backup-section'
type Props = {
backups: BackupFile[]
listError: string | null
envelope: BackupStatusEnvelope
nasSnapshots: ResticSnapshot[]
b2Snapshots: ResticSnapshot[]
nasStats: ResticStats | null
b2Stats: ResticStats | null
serverBackupErrors: {
status?: string
nasSnapshots?: string
b2Snapshots?: string
nasStats?: string
b2Stats?: string
}
}
export default function BackupsPanel({
backups,
listError,
envelope,
nasSnapshots,
b2Snapshots,
nasStats,
b2Stats,
serverBackupErrors,
}: Props) {
return (
<div className="space-y-12">
<DatabaseBackupsSection backups={backups} listError={listError} />
<div className="h-px bg-border" />
<ServerBackupSection
envelope={envelope}
nasSnapshots={nasSnapshots}
b2Snapshots={b2Snapshots}
nasStats={nasStats}
b2Stats={b2Stats}
errors={serverBackupErrors}
/>
</div>
)
}