ee3ee241ea
Cohesive batch from a smoke-test session against a real rest-server.
Themed bullets:
* Agent runs as root, sandboxed via systemd. CapabilityBoundingSet
drops to CAP_DAC_READ_SEARCH + restore caps; ProtectSystem=strict
with ReadWritePaths confined to /etc + /var/lib/restic-manager;
NoNewPrivileges blocks escalation. Install script no longer
creates a service user. spec.md §4.2 / §14.1 / §14.3 explain the
rationale (matches UrBackup / Veeam / Bareos defaults; trying to
back up "everything" as an unprivileged user creates silent skips
on /home, /root, /var/lib/* with no upside vs the threat model
the agent already implies).
* Init-repo end-to-end. New JobKind="init" wired through agent
runner, restic.Env.RunInit, server dispatcher, and a UI button
(red "Initialise repo" in the run-now panel). hosts.repo_initialised_at
flips on init success, on backup success, or on a non-empty
snapshots.report. The "Run now" / "Init" / "Retry" branching now
drives both the dashboard host row and the host-detail panel.
Migrations 0004 (column), 0005 (jobs.kind CHECK widened — using
the safe create-new-then-rename pattern; first version corrupted
job_logs.job_id FK), 0006 (cleans up job_logs FK on already-
affected DBs).
* rest-server creds embedded at exec time only. restic.Env gains
RepoUsername; mergeRestCreds() builds the user:pass@-prefixed URL
inside envSlice() and never assigns it back to the struct, so
nothing slog-able ever sees the cleartext form. RedactURL helper
for any future surface that needs to log a URL safely. Both
helpers tested.
* Add-host UX. Repo password is now optional — server mints a
24-byte URL-safe random one and surfaces it once, alongside an
htpasswd snippet ("echo PASS | htpasswd -B -i ... USERNAME") so
the operator pastes one command on the rest-server host and one
on the endpoint. Result page also links the install snippet at
/install/install.sh (was /install.sh — 404'd before) and pipes
to bash (not sh — script uses set -o pipefail and other
bashisms; on Debian/Ubuntu sh is dash).
* Late-subscriber race in JobHub. A fast-failing job could finish
(DB write + Broadcast) before the browser's HX-Redirect → page
load → WS-connect path completed, so the JS sat forever waiting
on a job.finished that already passed. JobHub split into
Register + Send + Run; handleJobStream now subscribes first,
re-fetches the job, and sends a synthetic job.finished if the
state is already terminal.
* HTMX error visibility. New toast partial listens to
htmx:responseError and surfaces the response body as a
bottom-right toast — every server-side validation error now
becomes visible without per-handler JS wiring. Also handles
custom rm:toast events for future server-pushed notifications
via the HX-Trigger header. Themed via existing CSS vars.
* Dashboard rows are now whole-row clickable to host detail
(CSS card-link pattern: absolute-positioned anchor + .row-action
z-index restoration so the action button stays clickable).
"View →" on a running job links to /jobs/<id> rather than
/hosts/<id> since the row click already covers the host page.
* "Run first" / "Run first backup" → "Run now" everywhere for
consistency.
* runbook (docs/e2e-smoke.md) updated — live-log streaming step
now reflects P1-26; mentions the browser-driven Run-now flow.
* _diag/dump-creds — moved out of cmd/ so go build doesn't pick
it up; .gitignore now excludes /_diag/ entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.8 KiB
SQL
48 lines
1.8 KiB
SQL
-- 0005_jobs_init_kind.sql
|
|
--
|
|
-- Add 'init' to the jobs.kind CHECK constraint so the operator can
|
|
-- dispatch a `restic init` job from the UI before the first backup.
|
|
-- SQLite can't ALTER a CHECK in place, so we rebuild the table.
|
|
--
|
|
-- Rebuild pattern note: we create jobs_new (with the wider CHECK),
|
|
-- copy data over, DROP the original jobs table, then ALTER RENAME
|
|
-- jobs_new TO jobs. This avoids the trap of renaming the original
|
|
-- first — with legacy_alter_table=OFF (the modern default), a rename
|
|
-- propagates into FK references in dependent tables (e.g.
|
|
-- job_logs.job_id), leaving them pointing at the temporary name even
|
|
-- after we drop it. Migration 0006 cleans up the orphan FK left by
|
|
-- the first version of this migration on already-affected DBs.
|
|
|
|
PRAGMA foreign_keys = OFF;
|
|
|
|
CREATE TABLE jobs_new (
|
|
id TEXT PRIMARY KEY,
|
|
host_id TEXT NOT NULL REFERENCES hosts(id) ON DELETE CASCADE,
|
|
kind TEXT NOT NULL CHECK (kind IN ('backup','init','forget','prune','check','unlock')),
|
|
status TEXT NOT NULL CHECK (status IN ('queued','running','succeeded','failed','cancelled')),
|
|
scheduled_id TEXT REFERENCES schedules(id) ON DELETE SET NULL,
|
|
actor_kind TEXT NOT NULL CHECK (actor_kind IN ('user','schedule','system')),
|
|
actor_id TEXT,
|
|
started_at TEXT,
|
|
finished_at TEXT,
|
|
exit_code INTEGER,
|
|
stats TEXT,
|
|
error TEXT,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
|
|
INSERT INTO jobs_new
|
|
SELECT id, host_id, kind, status, scheduled_id, actor_kind, actor_id,
|
|
started_at, finished_at, exit_code, stats, error, created_at
|
|
FROM jobs;
|
|
|
|
DROP TABLE jobs;
|
|
|
|
ALTER TABLE jobs_new RENAME TO jobs;
|
|
|
|
CREATE INDEX jobs_host_id ON jobs(host_id);
|
|
CREATE INDEX jobs_status ON jobs(status);
|
|
CREATE INDEX jobs_created_at ON jobs(created_at);
|
|
|
|
PRAGMA foreign_keys = ON;
|