Skip to content

feat: restore from full backup snapshot#1323

Merged
perber merged 5 commits into
mainfrom
feat/full-backup-restore
Jul 21, 2026
Merged

feat: restore from full backup snapshot#1323
perber merged 5 commits into
mainfrom
feat/full-backup-restore

Conversation

@perber

@perber perber commented Jul 21, 2026

Copy link
Copy Markdown
Owner

No description provided.

perber added 3 commits July 20, 2026 20:05
Adds a live, zero-downtime restore path for Full Backup snapshots (validate,
gate writes, swap files, hot-swap AuthService's user store, invalidate
sessions, reload branding, resync), an offline `restore-snapshot` CLI
subcommand for disaster recovery, and a self-restart fallback for the rare
case a failed restore can't be cleanly rolled back. Also fixes a pre-existing
gap where branding.json (site name/logo/favicon selection) was never
included in snapshot ZIPs.

See docs/adr/0009-restore-hot-swap-and-write-gate.md for the design
rationale.
- Fix data loss: RollbackAll skipped items whose move-in rename failed
  after move-aside already succeeded, permanently losing that item while
  reporting a clean rollback. Track movedAside separately from swapped.
- Reject TriggerRestore while a previous restore left the instance in
  NeedsIntervention state, instead of silently clearing the flag and
  compounding a half-swapped filesystem.
- Fix a race in AuthService.Close() (unguarded read vs. the new mutex) and
  track the restore goroutine so shutdown waits for an in-flight restore
  before closing AuthService out from under it.
- Fix multi-step AuthService methods (TOTP setup/confirm/disable, recovery
  code consumption) that could straddle two different user stores if a
  restore's swap landed mid-request; they now capture the store once per
  request instead of re-reading it at each call.
- Replace the write-gate's substring path exemption (vulnerable to
  --base-path collisions disabling the gate entirely, and to false
  positives on similarly-named routes) with proper path-segment matching;
  also exempt login/refresh/logout, which never touch swapped files.
- Use a LocalizedError for the write-gate's 503 response, log the
  self-restart failure at its swallow point, and move a hardcoded restore.ts
  error string into the i18n namespace, per repo conventions.
- Add an explicit startup warning for live restore on Windows, where
  renaming users.db while AuthService may still hold it open is unverified.
Also drops the dangling #api-keys-experimental anchor link left over from
the API Keys section removal, and fixes the cross-references into the
removed sections.
docs/adr and docs/dev are kept as local-only internal notes, not pushed to
the shared repo yet — these three (added in the restore feature commit)
were pushed by mistake, breaking that convention. Files remain on disk
locally, just untracked again.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds end-to-end “restore from full backup snapshot” support: a backend restore manager that validates/stages/swaps snapshot contents (with rollback + self-restart fallback), global write-gating during the swap, and a frontend UI flow to trigger/poll restore and show progress.

Changes:

  • Backend: introduce internal/restore restore state machine + /api/admin/restore/* routes, and gate mutating requests during live restore via a global write-gate middleware.
  • Frontend: add restore API client + Zustand restore store, wire restore actions/progress UI into the Full Backup (snapshot) settings page, and add i18n strings.
  • CLI/docs: add leafwiki restore-snapshot <zip> offline restore subcommand; add dev architecture/source-map/ADR docs and adjust README wording.

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
ui/leafwiki-ui/src/stores/restore.ts New Zustand store to trigger restore and poll restore/resync status.
ui/leafwiki-ui/src/locales/en/snapshot.json Copy update for snapshot page description.
ui/leafwiki-ui/src/locales/en/restore.json New i18n namespace for restore UI strings.
ui/leafwiki-ui/src/lib/i18n.ts Register restore namespace.
ui/leafwiki-ui/src/lib/api/restore.ts New typed client for restore endpoints.
ui/leafwiki-ui/src/features/snapshot/SnapshotSettings.tsx Add restore actions, progress banner, and self-restart intervention UI.
README.md Remove prior Full Backup section; adjust Git Backup note (now potentially inaccurate vs new feature).
internal/wiki/wiki.go Wire restore routes into Wiki composition and expose BrandingService.
internal/wiki/restore/routes.go New admin restore routes (trigger/status/self-restart) with auth+CSRF.
internal/wiki/restore/routes_test.go Route-layer tests for restore handlers.
internal/wiki/restore/errors.go Restore route error mapping + response shape.
internal/snapshot/snapshot.go Include branding.json in snapshots (optional).
internal/snapshot/snapshot_test.go Validate snapshot ZIP now contains branding config; optional-file behavior test.
internal/restore/writegate.go New write-gate primitive (no HTTP dependency) to block mutations during swap.
internal/restore/writegate_test.go Concurrency/behavior tests for WriteGate.
internal/restore/swap.go Snapshot ZIP extract/validate + staged swap/rollback/commit implementation.
internal/restore/swap_test.go Extensive swap/extract validation tests (zip slip, rollback correctness, etc.).
internal/restore/selfrestart_windows.go Windows implementation: spawn new process then exit.
internal/restore/selfrestart_unix.go Unix implementation: exec in-place.
internal/restore/offline.go Offline restore helper used by CLI subcommand.
internal/restore/offline_test.go Offline restore tests (swap + validation failure safety).
internal/restore/manager.go Restore manager orchestration (validate → gate → swap → reopen auth → invalidate sessions → reload branding → resync).
internal/restore/manager_test.go Integration-style tests for Manager happy path and failure modes.
internal/restore/job.go Background job state tracking (phase/done/error/versionWarning/needsIntervention).
internal/restore/job_test.go Job state tests.
internal/restore/helpers_test.go Shared restore test fixtures (fixture snapshots + users DB builders).
internal/restore/config.go Restore manager config wiring.
internal/http/router.go Register global write-gate middleware when enabled.
internal/http/middleware/maintenance/writegate.go Gin middleware wrapper for WriteGate with exempt paths.
internal/http/middleware/maintenance/writegate_test.go Middleware exemption + gating tests.
internal/core/auth/session_store.go Add DeleteAllSessions() to wipe sessions after restore.
internal/core/auth/auth_service.go Add ReplaceUserStore() hot-swap with RWMutex; add InvalidateAllSessions().
internal/core/auth/auth_service_replace_test.go Tests for ReplaceUserStore + concurrent access + session invalidation.
internal/branding/branding_service.go Add Reload() to refresh in-memory branding after restore swap.
internal/branding/branding_service_test.go Test Reload() behavior.
docs/dev/source-map.md New internal codebase navigation doc (contains some dead links / local paths).
docs/dev/architecture.md New architecture overview doc (contains several dead ADR links).
docs/adr/0009-restore-hot-swap-and-write-gate.md New ADR for restore/write-gate design (contains a dead link).
cmd/leafwiki/main.go Wire snapshot+restore manager, write-gate, and add restore-snapshot subcommand.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/restore/manager.go
Comment thread ui/leafwiki-ui/src/stores/restore.ts
Comment thread README.md
Comment thread ui/leafwiki-ui/src/features/snapshot/SnapshotSettings.tsx
@perber perber changed the title Feat/full backup restore feat: restore from full backup snapshot Jul 21, 2026
- pollResyncTail no longer silently reports success when the tail-end
  resync job is lost or repeated status polls fail: a new resyncConfirmed
  flag lets the UI show a distinct warning instead of a false "completed"
  or a misleading full-restore failure (the restore itself already
  succeeded by this point).
- Surface the "already running" case via a dedicated toast.info message,
  matching the existing resync_already_running convention in
  MaintenanceSettings, instead of discarding it for a generic error toast.
@perber
perber merged commit 31faf2b into main Jul 21, 2026
9 checks passed
@perber
perber deleted the feat/full-backup-restore branch July 21, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants