f0dfa689fe
Three small follow-ups from review:
1. Restore target is now operator-editable. Default value is the
literal '\$HOME/rm-restore/<job-id>/' (agent expands \$HOME at
run time using os.UserHomeDir(); also handles \${HOME} and ~/
prefixes). Operator can replace with any absolute path.
- ui_restore.go validates the input is either absolute or starts
with one of the recognised prefixes; other env-var refs (\$PATH
etc.) are deliberately rejected so operator paths can't pick up
arbitrary agent env values.
- host_restore.html replaces the read-only mono-text display with
a real <input>; help text spells out that \$HOME resolves
agent-side and <job-id> is substituted on dispatch.
- install.sh + the systemd unit prep /root/rm-restore so the
default works under the sandbox: ReadWritePaths gains a soft
'-/root/rm-restore' entry (the '-' makes the bind-mount soft-fail
if missing, but install.sh pre-creates it root-owned 0700).
2. --no-ownership flag now gated on restic version. The flag was
added in restic 0.17 and 0.16 rejects it. Previously dropped it
wholesale — that meant new-dir restores silently preserved
ownership against design intent on 0.17+. Now the agent threads
its detected restic version (sysinfo already collects it) through
runner.Config -> restic.Env, and RunRestore appends --no-ownership
only when AtLeastVersion(0, 17) returns true. 0.16 hosts still
restore with original uid/gid; help text in the wizard explicitly
notes this. The previous 'Original ownership is preserved' copy
was wrong for new-dir mode and is corrected.
3. golangci-lint misspell locale switched US -> UK and the codebase
swept (73 corrections, mostly behaviour/serialise/recognise/honour).
Wire-format ErrorCode 'unauthorized' -> 'unauthorised' is a tiny
contract change but the agent doesn't parse those codes today and
no external API consumers exist yet. Tests passed before + after.
Tests:
- internal/restic/version_test.go covers Env.AtLeastVersion across
edge cases (empty, exact match, patch above, minor below, non-
numeric) and expandHome on \$HOME / \${HOME} / ~/, plus
pass-through for absolute paths and refusal of other env vars.
- ui_restore_test updated: TargetDir now starts '\$HOME/rm-restore/'
with the job_id substituted into the placeholder.
Live verified on the smoke env: default target restored to
/root/rm-restore/<job-id>/ as the agent's expanded \$HOME (2 files,
14 bytes); custom override '/tmp/custom-restore/<job-id>/' restored
into the agent's PrivateTmp namespace (1 file, 6 bytes); both jobs
'succeeded', exit 0.
81 lines
2.2 KiB
Go
81 lines
2.2 KiB
Go
// Package sysinfo collects host metadata at agent startup: OS, arch,
|
|
// hostname, restic version. The agent sends this in `hello` so the
|
|
// server's Host row stays current.
|
|
package sysinfo
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.dcglab.co.uk/steve/restic-manager/internal/api"
|
|
)
|
|
|
|
// Snapshot is the bundle of metadata reported in `hello`.
|
|
type Snapshot struct {
|
|
Hostname string
|
|
OS api.HostOS
|
|
Arch api.HostArch
|
|
ResticVersion string
|
|
ProtocolVersion int
|
|
BootTime time.Time
|
|
}
|
|
|
|
// Collect probes the running host. resticPath, if non-empty,
|
|
// overrides PATH lookup.
|
|
func Collect(ctx context.Context, resticPath string) (Snapshot, error) {
|
|
hn, err := os.Hostname()
|
|
if err != nil {
|
|
return Snapshot{}, fmt.Errorf("sysinfo: hostname: %w", err)
|
|
}
|
|
|
|
osTag := api.HostOS(runtime.GOOS)
|
|
archTag := api.HostArch(runtime.GOARCH)
|
|
|
|
resticVer, _ := detectResticVersion(ctx, resticPath) // empty on failure is fine
|
|
return Snapshot{
|
|
Hostname: hn,
|
|
OS: osTag,
|
|
Arch: archTag,
|
|
ResticVersion: resticVer,
|
|
ProtocolVersion: api.CurrentProtocolVersion,
|
|
}, nil
|
|
}
|
|
|
|
// detectResticVersion runs `restic version` and parses the first line.
|
|
// Output looks like:
|
|
//
|
|
// restic 0.17.1 compiled with go1.22.5 on linux/amd64
|
|
//
|
|
// Returns the version token (e.g. "0.17.1") or "" if restic isn't
|
|
// found. We never block startup on a missing restic — the operator
|
|
// might not have installed it yet, and the agent should still be
|
|
// able to connect and report.
|
|
func detectResticVersion(ctx context.Context, override string) (string, error) {
|
|
bin := override
|
|
if bin == "" {
|
|
var err error
|
|
bin, err = exec.LookPath("restic")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
versionCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
out, err := exec.CommandContext(versionCtx, bin, "version").Output()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
first := strings.SplitN(strings.TrimSpace(string(out)), "\n", 2)[0]
|
|
parts := strings.Fields(first)
|
|
if len(parts) >= 2 && parts[0] == "restic" {
|
|
return parts[1], nil
|
|
}
|
|
return "", fmt.Errorf("sysinfo: unrecognised restic version output: %q", first)
|
|
}
|