server: maintenance ticker drives forget/prune/check on cadence
Wires a 60s server-side ticker to the pure-logic maintenance.Decide
introduced in the previous commit. Decisions flow through a new
DispatchMaintenance method on *Server, which:
- skips offline hosts (no pending_runs queueing — maintenance is
not a backup, missed fires shouldn't pile up)
- silently skips prune when admin creds aren't bound
- pushes admin creds before prune, then dispatches with
RequiresAdminCreds=true (same as operator-driven prune)
- persists job rows with actor_kind="system"
Reshapes the forget wire payload from a single RetentionPolicy to a
ForgetGroups list (one tag + per-group keep-* per source group). The
agent walks the groups and runs `restic forget --tag <name> --keep-*`
once per group. Dead-code removed: CommandRunPayload.RetentionPolicy,
the old forget JSON-decode in cmd/agent, and the single-policy form of
restic.RunForget.
This commit is contained in:
+37
-19
@@ -151,8 +151,7 @@ func (e Env) RunBackup(ctx context.Context, paths, excludes, tags []string, hand
|
||||
}
|
||||
|
||||
// ForgetPolicy mirrors restic forget's --keep-* flags. All optional;
|
||||
// nil/zero means "don't pass that flag." Caller passes whatever the
|
||||
// schedule's RetentionPolicy carries.
|
||||
// nil/zero means "don't pass that flag."
|
||||
type ForgetPolicy struct {
|
||||
KeepLast *int
|
||||
KeepHourly *int
|
||||
@@ -181,30 +180,49 @@ func (p ForgetPolicy) args() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Empty reports whether no retention dimensions are set. restic
|
||||
// forget refuses to run without at least one keep-* flag (it would
|
||||
// delete every snapshot), so the agent rejects empty policies before
|
||||
// invoking restic.
|
||||
// Empty reports whether no retention dimensions are set.
|
||||
func (p ForgetPolicy) Empty() bool {
|
||||
return p.KeepLast == nil && p.KeepHourly == nil &&
|
||||
p.KeepDaily == nil && p.KeepWeekly == nil &&
|
||||
p.KeepMonthly == nil && p.KeepYearly == nil
|
||||
}
|
||||
|
||||
// RunForget executes `restic forget --keep-* … --json` against the
|
||||
// configured repo. Does NOT pass --prune — pruning lives behind a
|
||||
// separate, admin-only credential (see spec §4.3 / P2-06). Restic
|
||||
// just rewrites the snapshot index; the actual data deletion waits
|
||||
// for the next prune. Returns nil on a clean exit.
|
||||
func (e Env) RunForget(ctx context.Context, policy ForgetPolicy, handle LineHandler) error {
|
||||
if policy.Empty() {
|
||||
return fmt.Errorf("restic forget: refusing to run with empty retention policy (would delete every snapshot)")
|
||||
// ForgetGroup is one (tag, retention-policy) pair fed to RunForget.
|
||||
// The wrapper invokes `restic forget --tag <Tag> --keep-* …` per
|
||||
// group so retention can be targeted at a single source-group's
|
||||
// snapshots without disturbing snapshots tagged for other groups.
|
||||
type ForgetGroup struct {
|
||||
Tag string
|
||||
Policy ForgetPolicy
|
||||
}
|
||||
|
||||
// RunForget executes one `restic forget --tag <Tag> --keep-* …`
|
||||
// invocation per group. Does NOT pass --prune — pruning lives behind
|
||||
// a separate admin-only credential (see spec §4.3 / P2-06). Restic
|
||||
// rewrites the snapshot index; the actual data deletion waits for
|
||||
// the next prune. Empty groups slice is rejected (would be a no-op);
|
||||
// any group with an empty policy is rejected (restic forget without
|
||||
// any keep-* would delete every snapshot in the tagged set).
|
||||
// Returns the first error encountered, or nil when every group runs
|
||||
// to a clean exit.
|
||||
func (e Env) RunForget(ctx context.Context, groups []ForgetGroup, handle LineHandler) error {
|
||||
if len(groups) == 0 {
|
||||
return fmt.Errorf("restic forget: refusing to run with no groups (would be a no-op)")
|
||||
}
|
||||
args := append([]string{"forget", "--json"}, policy.args()...)
|
||||
cmd := exec.CommandContext(ctx, e.Bin, args...)
|
||||
cmd.Env = e.envSlice()
|
||||
cmd.Dir = e.WorkDir
|
||||
return runWithPump(cmd, handle)
|
||||
for _, g := range groups {
|
||||
if g.Policy.Empty() {
|
||||
return fmt.Errorf("restic forget: group %q has empty retention policy (would delete every snapshot)", g.Tag)
|
||||
}
|
||||
args := []string{"forget", "--json", "--tag", g.Tag}
|
||||
args = append(args, g.Policy.args()...)
|
||||
cmd := exec.CommandContext(ctx, e.Bin, args...)
|
||||
cmd.Env = e.envSlice()
|
||||
cmd.Dir = e.WorkDir
|
||||
if err := runWithPump(cmd, handle); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunInit executes `restic init` against the configured repo. Returns
|
||||
|
||||
Reference in New Issue
Block a user