Files
restic-manager/internal/restic/snapshots.go
T
steve 02250670c1 ui: snapshots SIZE/FILES tooltip when host's restic is < 0.17
Per-snapshot size + file-count come from the embedded summary block
restic added to 'snapshots --json' in 0.17 (the source comment in
internal/restic/snapshots.go incorrectly said 0.16+). Hosts running
0.16.x leave those columns blank.

- Fix the snapshots.go doc comment: '0.16+' -> '0.17+'.
- hostDetailPage carries a LegacyRestic bool computed from the host's
  reported ResticVersion via Env.AtLeastVersion(0, 17). Empty version
  also counts as legacy (conservative default).
- Template attaches title='Needs restic 0.17+ on the agent host. This
  host runs <ver>.' + cursor:help on the SIZE / FILES headers when
  the flag is true. Hosts already on 0.17+ get no tooltip and no
  extra styling.

A host upgrading restic to 0.17+ gets the columns populated on the
next backup automatically — no further code change needed.
2026-05-04 17:45:32 +01:00

61 lines
2.0 KiB
Go

package restic
import (
"context"
"encoding/json"
"errors"
"fmt"
"os/exec"
"time"
)
// Snapshot mirrors a single entry in `restic snapshots --json`. We
// decode only the fields we project to the server; restic's full
// shape has more (parent, tree, program version) that we don't need.
//
// Summary is only populated by restic 0.17+ (which embeds the backup
// summary inside each snapshot record). Older clients leave it nil
// and the agent reports zero size/file-count — the UI degrades to
// "—" and the column headers carry a tooltip explaining the version
// requirement (see web/templates/pages/host_detail.html).
type Snapshot struct {
ID string `json:"id"`
ShortID string `json:"short_id"`
Time time.Time `json:"time"`
Hostname string `json:"hostname"`
Paths []string `json:"paths"`
Tags []string `json:"tags,omitempty"`
Summary *SnapshotSummary `json:"summary,omitempty"`
}
// SnapshotSummary mirrors the embedded summary block restic 0.16+
// writes into each snapshot record. The naming follows restic's JSON.
type SnapshotSummary struct {
TotalFilesProcessed int64 `json:"total_files_processed"`
TotalBytesProcessed int64 `json:"total_bytes_processed"`
}
// ListSnapshots calls `restic snapshots --json` and returns the
// parsed list. Output is a single JSON array — small even on large
// repos (each entry is ~200 bytes), so we read it all into memory.
func (e Env) ListSnapshots(ctx context.Context) ([]Snapshot, error) {
cmd := exec.CommandContext(ctx, e.Bin, "snapshots", "--json")
cmd.Env = e.envSlice()
cmd.Dir = e.WorkDir
out, err := cmd.Output()
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
return nil, fmt.Errorf("restic snapshots: exit %d: %s",
ee.ExitCode(), string(ee.Stderr))
}
return nil, fmt.Errorf("restic snapshots: %w", err)
}
var snaps []Snapshot
if err := json.Unmarshal(out, &snaps); err != nil {
return nil, fmt.Errorf("restic snapshots: parse json: %w", err)
}
return snaps, nil
}