Add scheduled and manual backups
Backups are .tar.gz archives of a consistent database snapshot (VACUUM INTO) plus the images folder. They run on a cron schedule, daily at 3:00 by default, and the newest N scheduled ones are kept (7 by default). Manual backups are never pruned. Settings → Backups offers schedule presets or a custom cron expression, retention, "Back up now", and download/delete for each backup. A small built-in parser handles standard 5-field cron syntax with names, ranges, steps and @daily-style shorthands. Backups go to CANON_BACKUP_DIR (default data/backups). Compose mounts a separate canon-backups volume at /backups, overridable with CANON_BACKUP_PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
72b49d709a
commit
f3185ba7bd
18 files changed
+940
-10
No files matched your search
@@ -19,6 +19,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/cdnutter/canon/internal/auth"
|
||||
"github.com/cdnutter/canon/internal/backup"
|
||||
"github.com/cdnutter/canon/internal/config"
|
||||
"github.com/cdnutter/canon/internal/imgcache"
|
||||
"github.com/cdnutter/canon/internal/providers"
|
||||
@@ -35,6 +36,7 @@ type Server struct {
|
||||
cfg config.Config
|
||||
store *store.Store
|
||||
images *imgcache.Cache
|
||||
backups *backup.Manager
|
||||
providers *providers.Registry
|
||||
log *slog.Logger
|
||||
|
||||
@@ -48,11 +50,12 @@ type Server struct {
|
||||
previewClient *http.Client
|
||||
}
|
||||
|
||||
func New(cfg config.Config, st *store.Store, images *imgcache.Cache, log *slog.Logger, version string) (*Server, error) {
|
||||
func New(cfg config.Config, st *store.Store, images *imgcache.Cache, backups *backup.Manager, log *slog.Logger, version string) (*Server, error) {
|
||||
s := &Server{
|
||||
cfg: cfg,
|
||||
store: st,
|
||||
images: images,
|
||||
backups: backups,
|
||||
log: log,
|
||||
assetVer: version,
|
||||
loginLimit: auth.NewLimiter(10, 15*time.Minute),
|
||||
@@ -125,6 +128,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("GET /admin/lists/{id}", s.requireUser(s.handleAdminList))
|
||||
mux.HandleFunc("GET /admin/settings", s.requireUser(s.handleAdminSettings))
|
||||
mux.HandleFunc("GET /admin/preview", s.requireUser(s.handleAdminPreview))
|
||||
mux.HandleFunc("GET /admin/backups/{name}", s.requireUser(s.handleBackupDownload))
|
||||
|
||||
// Admin JSON API
|
||||
api := func(pattern string, h http.HandlerFunc) { mux.HandleFunc(pattern, s.requireAPI(h)) }
|
||||
@@ -147,6 +151,10 @@ func (s *Server) Handler() http.Handler {
|
||||
api("DELETE /api/settings/avatar", s.apiDeleteAvatar)
|
||||
api("POST /api/integrations/test", s.apiTestIntegration)
|
||||
api("PUT /api/account", s.apiUpdateAccount)
|
||||
api("GET /api/backups", s.apiBackups)
|
||||
api("POST /api/backups", s.apiCreateBackup)
|
||||
api("PUT /api/backups/schedule", s.apiBackupSchedule)
|
||||
api("DELETE /api/backups/{name}", s.apiDeleteBackup)
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { s.notFound(w, r) })
|
||||
|
||||
|
||||
Reference in new issue
Block a user