Merge pull request 'tidy: project finished backup jobs onto host row + smoke doc tweaks' (#20) from tidy-up-last-backup-projection into main

Reviewed-on: #20
This commit is contained in:
2026-05-07 16:58:16 +00:00
3 changed files with 32 additions and 13 deletions
+8 -8
View File
@@ -38,7 +38,7 @@ but the **agent** is fetched by the install script from the server's
**install script** are fetched from `<DataDir>/install/`. Plain **install script** are fetched from `<DataDir>/install/`. Plain
`make build` doesn't touch any of those — the source-of-truth files `make build` doesn't touch any of those — the source-of-truth files
in the working tree (`deploy/install/*`, `bin/restic-manager-agent`) 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 on this dev host needs replacing if the change touches agent code or
the unit file. the unit file.
@@ -53,13 +53,13 @@ asking the operator to test.**
```sh ```sh
# 1. Restage what the install script serves (binary + unit + script). # 1. Restage what the install script serves (binary + unit + script).
cp bin/restic-manager-agent \ 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 \ cp deploy/install/install.sh \
/tmp/rm-smoke/data/install/install.sh $HOME/smoke/data/install/install.sh
cp deploy/install/install.ps1 \ 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 \ 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 # 2. Replace the running agent on this dev box and restart the
# service. Skip only when the change is server-side only AND # 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 # 3. The server runs from the working tree; restart it manually
# after a build that touches server code: # after a build that touches server code:
pkill -f restic-manager-server 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_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 \ 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 A `make smoke-deploy` target that bundles all of this would be a
+11 -5
View File
@@ -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 // a *success* — restic's idempotent init returns that when the
// repo is already initialised, which is the happy path for // repo is already initialised, which is the happy path for
// onboarding against an existing repo. // onboarding against an existing repo.
if job, err := deps.Store.GetJob(ctx, p.JobID); err == nil && job != nil && if job, err := deps.Store.GetJob(ctx, p.JobID); err == nil && job != nil {
job.Kind == string(api.JobInit) { switch job.Kind {
status, errOut := repoStatusFromInit(string(p.Status), errMsg) case string(api.JobInit):
if err := deps.Store.SetHostRepoStatus(ctx, hostID, status, errOut); err != nil { status, errOut := repoStatusFromInit(string(p.Status), errMsg)
slog.Warn("ws: set host repo status", "host_id", hostID, "err", err) 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 { if deps.JobHub != nil {
+13
View File
@@ -81,6 +81,19 @@ func (s *Store) SetHostRepoStatus(ctx context.Context, hostID, status, errMsg st
return nil 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 // DeleteHost removes a host row by id. Returns ErrNotFound if no row
// matched. Foreign-key cascades (declared on every dependent table — // matched. Foreign-key cascades (declared on every dependent table —
// schedules, jobs, snapshots, source_groups, host_credentials, etc.) // schedules, jobs, snapshots, source_groups, host_credentials, etc.)