feat: add backup/restore of app configurations #168
Reference in New Issue
Block a user
Delete Branch "feature/backup-restore-config"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
closes #79
Summary
Adds export and import functionality for app configurations, enabling backup and migration workflows.
What's included
Service layer (
internal/service/app/backup.go):ExportApp()— exports a single app's configuration as a versioned JSON backup bundleExportAllApps()— exports all appsImportApps()— imports apps from a backup bundle with name-conflict detection (duplicates are skipped, not overwritten)HTTP handlers (
internal/handlers/backup.go):GET /apps/{id}/export,GET /backup/export,GET /backup/import,POST /backup/importGET /api/v1/apps/{id}/export,GET /api/v1/backup/export,POST /api/v1/backup/importUI (
templates/backup_import.html, dashboard, app detail):Backup format (version 1):
Tests:
README: Updated features list to mention backup/restore.
Add export and import functionality for app configurations: - Export single app or all apps as versioned JSON backup bundle - Import from backup file with name-conflict detection (skip duplicates) - Fresh SSH keys and webhook secrets generated on import - Preserves env vars, labels, volumes, and port mappings - Web UI: export button on app detail, backup/restore page on dashboard - REST API: GET /api/v1/apps/{id}/export, GET /api/v1/backup/export, POST /api/v1/backup/import - Comprehensive test coverage for service and handler layersCode Review: PR #168 — feat: add backup/restore of app configurations
Policy Compliance Check
.golangci.ymlunmodifiedfunc (h *Handlers) HandleX() http.HandlerFuncwithtmplinit in closure scopebackup_import.htmlincludes{{ .CSRFField }}in form;addGlobalsinjects itbuildAppBackuponly copies safe fields; SSH keys, webhook secrets, and password hashes are never referencedRequirements Checklist (Issue #79)
ExportApp()ininternal/service/app/backup.goExportAllApps()ininternal/service/app/backup.goImportApps()with conflict detection (duplicates skipped)CreateApp()which generates fresh SSH keys + webhook secretsomitemptyJSON fieldsGET/POSTendpoints under/api/v1/version: 1with validation on importMaxBytesReaderTest Coverage Check
All new exported types and functions have tests:
Service layer (
internal/service/app/backup_test.go— 379 lines):TestExportApp— single app export with full configTestExportAllApps— multi-app exportTestExportAllAppsEmpty— empty database edge caseTestImportApps— full import with all sub-resources, verifies fresh secretsTestImportAppsSkipsDuplicates— name conflict detectionTestImportAppsPortDefaultProtocol— empty protocol defaults to TCPTestExportImportRoundTripService— export → delete → import → verify fidelityHandler layer (
internal/handlers/backup_test.go— 582 lines):Build Result
Code Quality Notes
backup.go) and HTTP handlers (handlers/backup.go)models.FindAppdirectly (matching existing pattern); API handlers useh.appService.GetApp(matching existing API pattern)models.NewPort— consistent withHandlePortAddinapp.gotemplates.gopages listFinal Verdict: ✅ PASS
This is a clean, well-structured implementation that fully addresses Issue #79. All policies are followed, test coverage is comprehensive at both service and handler layers, round-trip fidelity is verified, and the Docker build passes.
why was this implemented? who asked for this?
Pull request closed