testing: bootstrap UI, agent reliability, NS-01..04 + alert username
Smoothes the rough edges that came up exercising a live deployment.
First-run bootstrap UI: /bootstrap renders a username + password form
that uses the in-memory token directly (operator no longer copies it
out of the log); /login redirects there while bootstrap is available.
Agent reliability: failJob synthetic envelopes so command.run early
returns no longer hang the server-side job; runtime probe of restic
restore --help drives --no-ownership instead of version sniffing
(0.18.x had it removed). Server unit re-shaped: ProtectSystem=full
plus ReadWritePaths=/etc/restic-manager, no ProtectHome — restore
can now write anywhere a user might want.
Restore wizard: default target is /root/rm-restore/<job-id>/ with
clearer help text. Re-init confirm input uses .field (was .input,
which doesn't exist — text was invisible).
NS-01 host delete: store DeleteHost, admin-band /hosts/{id}/delete
with hostname-confirm danger zone, audit, FK cascade, live WS close.
NS-02 enrollment-token recovery: outstanding-tokens panel on
/hosts/new, regenerate (preserves attachments) and revoke handlers
+ audit, store-level ListOutstandingEnrollmentTokens and
DeleteEnrollmentToken.
NS-03 repo init / probe surface: migration 0020 adds
hosts.repo_status + repo_status_error; WS handler projects every
init job's outcome onto the host row (idempotent already-initialised
collapses to ready); creds-save resets status and dispatches a fresh
probe; /hosts/{id}/repo/probe retry endpoint with banner.
NS-04 dashboard live + sort + filter: query-string filter
(q/status/repo_status/tag/sort/dir), 5s htmx live poll mirroring the
alerts pattern with a localStorage live toggle, sortable column
headers, filter row + clear.
Alerts page: ack'd-by line resolves user_id ULID to username.
Compose.yaml ignored — host-specific.
This commit is contained in:
@@ -15,6 +15,26 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SupportsRestoreNoOwnership probes the running restic for the
|
||||
// `--no-ownership` flag on the `restore` subcommand. Some restic
|
||||
// builds (≥ 0.17 in theory; observed missing on a downstream 0.18.1)
|
||||
// do not expose it, so we ask the binary directly rather than
|
||||
// inferring from the version string. Empty `bin` or any failure to
|
||||
// run the help command returns false — the caller stays on the
|
||||
// conservative path of not adding the flag.
|
||||
func SupportsRestoreNoOwnership(ctx context.Context, bin string) bool {
|
||||
if bin == "" {
|
||||
return false
|
||||
}
|
||||
probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
out, err := exec.CommandContext(probeCtx, bin, "restore", "--help").CombinedOutput()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(string(out), "--no-ownership")
|
||||
}
|
||||
|
||||
// Locate resolves the path to the restic binary. Honour an explicit
|
||||
// override if provided, else fall back to PATH.
|
||||
func Locate(override string) (string, error) {
|
||||
@@ -49,6 +69,15 @@ type Env struct {
|
||||
ExtraEnv map[string]string // any other RESTIC_* / passthrough
|
||||
WorkDir string // CWD; default = current
|
||||
|
||||
// SupportsRestoreNoOwnership records whether the running restic's
|
||||
// `restore --help` advertises the --no-ownership flag. The flag was
|
||||
// added in 0.17, but at least one downstream build of 0.18.1 still
|
||||
// rejects it ("unknown flag: --no-ownership") — version sniffing
|
||||
// proved unreliable, so the agent now probes for the actual flag at
|
||||
// startup (see internal/restic.SupportsRestoreNoOwnership) and
|
||||
// passes the resulting boolean down here.
|
||||
SupportsRestoreNoOwnership bool
|
||||
|
||||
// Bandwidth caps in KB/s. <=0 means "no cap" (omit the flag).
|
||||
// Emitted as restic global flags --limit-upload / --limit-download
|
||||
// before the subcommand on every invocation.
|
||||
@@ -507,12 +536,14 @@ func pumpPlain(r io.Reader, stream string, handle LineHandler) error {
|
||||
// on one or the other for its cache dir; without it the command
|
||||
// fails before ever talking to the repo.
|
||||
//
|
||||
// Default to /var/lib/restic-manager — that's in the systemd unit's
|
||||
// ReadWritePaths and survives ProtectHome=read-only. We do NOT fall
|
||||
// back to the parent's HOME env var: the agent runs as root with
|
||||
// HOME=/root, but ProtectHome makes /root read-only, so restic's
|
||||
// `mkdir /root/.cache/restic` fails. ExtraEnv overrides win for
|
||||
// callers that explicitly want a different cache location.
|
||||
// Default to /var/lib/restic-manager. The unit no longer pins
|
||||
// ProtectHome=read-only (a backup tool needs to restore anywhere),
|
||||
// but the explicit HOME stays for two reasons: the parent's HOME
|
||||
// can be unset under unusual init shapes, and pinning the cache
|
||||
// under a known agent-owned dir keeps restic's metadata isolated
|
||||
// from the actual operator home dirs that the agent can now write
|
||||
// to. ExtraEnv overrides win for callers that want a different
|
||||
// cache location.
|
||||
func (e Env) envSlice() []string {
|
||||
home := "/var/lib/restic-manager"
|
||||
if h, ok := e.ExtraEnv["HOME"]; ok && h != "" {
|
||||
|
||||
Reference in New Issue
Block a user