// 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) }