* feat(snapshots): preserve stack dossiers with fleet snapshots
Fleet snapshots can now optionally capture each stack's Dossier notes
alongside its compose and .env files, so a recovery restores the
operational knowledge around a stack, not just its configuration.
- Opt-in global setting "snapshot_documentation" (default off), toggled
from the renamed Fleet settings section.
- Capture reads local dossiers from the database and remote dossiers over
the Distributed API proxy; only stacks with notes are recorded, and
secret values are never included.
- Captured notes are stored encrypted at rest in a new fleet_snapshots
column and surfaced in the snapshot detail view behind a badge.
- Cloud and downloaded archives gain a documentation.json (archive_version 2).
- Restore stays conservative: dossier notes are written back only when the
operator explicitly opts in, on both single-stack and restore-all paths.
- Existing snapshots and archives remain valid; behavior is unchanged when
the setting is off.
* fix(snapshots): harden dossier-notes restore against bad input and partial failures
Address review findings on the documentation-snapshots restore path:
- Parse `restoreNotes` strictly (=== true) on single-stack restore, matching
restore-all, so a stray non-boolean can never opt in to overwriting notes.
- Guard findSnapshotDossier: require an array of stacks and real dossier
content, so a malformed or all-blank entry can't clobber current notes.
- Make the dossier-notes write non-fatal relative to the file restore: a notes
failure (e.g. a remote dossier PUT) is caught, reported via `notesError`, and
no longer 500s the single restore or fails the stack in restore-all once the
files are already written.
- Surface the partial outcome in the UI: a warning toast on single restore, a
summary note on restore-all, and gate the "Documentation captured" badge and
restore-all notes control on captured stacks while rendering capture warnings.
Adds tests for strict parsing, malformed/blank blobs, remote notes restore
(success + non-fatal failure, single and bulk), and scheduled capture-on.
* fix(snapshots): drop unused binding in restore-all remote notes test
The restore-all remote notes test destructured a node id it never uses
(restore-all is driven by snapshot id alone), tripping no-unused-vars and
failing the lint step. Bind only the snapshot id.
* fix(fleet-snapshots): gate reads on admin role and encrypt content at rest
Fleet snapshots capture every node's compose.yaml and .env, so the data is
as sensitive as the live stacks. This hardens access and reliability across
the snapshot pipeline.
- Restrict snapshot reads to administrators. GET /api/fleet/snapshots and
/:id now require the admin role, matching create, restore, and delete; the
Fleet "Snapshots" tab and its panel render only for admins. Previously any
authenticated user could enumerate snapshots and read every node's .env.
- Encrypt snapshot file contents at rest with the instance key. Restore and
cloud-archive paths decrypt on read, so cloud archives stay portable and a
database copy no longer exposes stack secrets in plaintext. Rows written
before this change still read back as plaintext.
- Surface partial captures. A stack whose compose file cannot be read or
fetched, or a file over the 1 MB capture cap, is recorded as a warning and
shown on the snapshot instead of being silently dropped, so a snapshot is
never mistaken for complete. Remote .env read errors are now distinguished
from a genuinely absent .env.
Adds route-authz, capture-warning, and encryption round-trip tests; updates
the Fleet-Wide Backups feature docs.
* fix(fleet-snapshots): gate cloud snapshot reads on admin role
The cloud snapshot read routes were guarded by provider/license only, not by
role, while their write counterparts (upload, delete) already required admin
and the Cloud Backup settings surface is admin-only. Because a downloaded
archive contains plaintext compose and .env files, a non-admin could list and
download cloud snapshots and read every node's secrets, the same exposure the
local snapshot reads were just closed against.
- Require admin on GET /api/cloud-backup/snapshots, /status/:id, and
/object/:keyB64/download, matching the local snapshot reads and the
admin-only Cloud Backup settings section.
- When capturing a remote node, treat a 200 response carrying X-Env-Exists:
false as a stack with no .env (matching the local ENOENT path) instead of
storing an empty .env that restore would later write back.
Adds non-admin authorization tests for the cloud read routes and a remote
absent-.env capture test.