diff --git a/CLAUDE.md b/CLAUDE.md index be175fb..690645d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ but the **agent** is fetched by the install script from the server's **install script** are fetched from `/install/`. Plain `make build` doesn't touch any of those — the source-of-truth files in the working tree (`deploy/install/*`, `bin/restic-manager-agent`) -must be copied into `/tmp/rm-smoke/data/...` *and* the running agent +must be copied into `$HOME/smoke/data/...` *and* the running agent on this dev host needs replacing if the change touches agent code or the unit file. @@ -53,13 +53,13 @@ asking the operator to test.** ```sh # 1. Restage what the install script serves (binary + unit + script). cp bin/restic-manager-agent \ - /tmp/rm-smoke/data/agent-binaries/restic-manager-agent-linux-amd64 + $HOME/smoke/data/agent-binaries/restic-manager-agent-linux-amd64 cp deploy/install/install.sh \ - /tmp/rm-smoke/data/install/install.sh + $HOME/smoke/data/install/install.sh cp deploy/install/install.ps1 \ - /tmp/rm-smoke/data/install/install.ps1 + $HOME/smoke/data/install/install.ps1 cp deploy/install/restic-manager-agent.service \ - /tmp/rm-smoke/data/install/restic-manager-agent.service + $HOME/smoke/data/install/restic-manager-agent.service # 2. Replace the running agent on this dev box and restart the # service. Skip only when the change is server-side only AND @@ -74,11 +74,11 @@ sudo -n systemctl restart restic-manager-agent # 3. The server runs from the working tree; restart it manually # after a build that touches server code: pkill -f restic-manager-server -RM_LISTEN=:8080 RM_DATA_DIR=/tmp/rm-smoke/data \ +RM_LISTEN=:8080 RM_DATA_DIR=$HOME/smoke/data \ RM_BASE_URL=http://127.0.0.1:8080 \ -RM_SECRET_KEY_FILE=/tmp/rm-smoke/data/secret.key \ +RM_SECRET_KEY_FILE=$HOME/smoke/data/secret.key \ RM_COOKIE_SECURE=false \ -./bin/restic-manager-server >> /tmp/rm-smoke/server.log 2>&1 & +./bin/restic-manager-server >> $HOME/smoke/server.log 2>&1 & ``` A `make smoke-deploy` target that bundles all of this would be a diff --git a/internal/server/ws/handler.go b/internal/server/ws/handler.go index 312f568..5a0473c 100644 --- a/internal/server/ws/handler.go +++ b/internal/server/ws/handler.go @@ -227,11 +227,17 @@ func dispatchAgentMessage(ctx context.Context, c *Conn, hostID string, env api.E // a *success* — restic's idempotent init returns that when the // repo is already initialised, which is the happy path for // onboarding against an existing repo. - if job, err := deps.Store.GetJob(ctx, p.JobID); err == nil && job != nil && - job.Kind == string(api.JobInit) { - status, errOut := repoStatusFromInit(string(p.Status), errMsg) - if err := deps.Store.SetHostRepoStatus(ctx, hostID, status, errOut); err != nil { - slog.Warn("ws: set host repo status", "host_id", hostID, "err", err) + if job, err := deps.Store.GetJob(ctx, p.JobID); err == nil && job != nil { + switch job.Kind { + case string(api.JobInit): + status, errOut := repoStatusFromInit(string(p.Status), errMsg) + if err := deps.Store.SetHostRepoStatus(ctx, hostID, status, errOut); err != nil { + slog.Warn("ws: set host repo status", "host_id", hostID, "err", err) + } + case string(api.JobBackup): + if err := deps.Store.SetHostLastBackup(ctx, hostID, string(p.Status), p.FinishedAt); err != nil { + slog.Warn("ws: set host last backup", "host_id", hostID, "err", err) + } } } if deps.JobHub != nil { diff --git a/internal/store/hosts.go b/internal/store/hosts.go index 3d11209..18d5c7d 100644 --- a/internal/store/hosts.go +++ b/internal/store/hosts.go @@ -81,6 +81,19 @@ func (s *Store) SetHostRepoStatus(ctx context.Context, hostID, status, errMsg st return nil } +// SetHostLastBackup projects a finished backup job onto the host row +// so the dashboard can show last-run state without trawling the jobs +// table. Called from the WS handler on job.finished where kind=backup. +func (s *Store) SetHostLastBackup(ctx context.Context, hostID, status string, when time.Time) error { + _, err := s.db.ExecContext(ctx, + `UPDATE hosts SET last_backup_at = ?, last_backup_status = ? WHERE id = ?`, + when.UTC().Format(time.RFC3339Nano), status, hostID) + if err != nil { + return fmt.Errorf("store: set host last backup: %w", err) + } + return nil +} + // DeleteHost removes a host row by id. Returns ErrNotFound if no row // matched. Foreign-key cascades (declared on every dependent table — // schedules, jobs, snapshots, source_groups, host_credentials, etc.)