Files
felhom-controller/controller/cmd/controller/opactions_wiring.go
T
admin 5b3b541602
gates / gates (push) Successful in 31s
clear_hold (D64 = B) + the box command through the running controller (R-1343), R-1340, R-1341 — tests red first; commit only
- clear_hold: a fifth verb in the closed operator-action list; lifts a hold of any reason in the RUNNING
  controller (memory + settings.json together), forgets the update's held flag, drops a waiting put-back
  from the app-stop marker (copy kept). Refused on a bad name or no hold. No new event type.
- --clear-restore-hold asks the running controller over <data dir>/ctl/hold.sock (unix, 0600, peer uid
  checked, no TCP); edits settings.json only when no controller answers, and says so (in-guest restart).
- R-1340: the operator's HELD STOPPED mail is built from the hold record (restore kind, mixed cause,
  failed put-back step, database replay or not, kept copy) — never the option-C database story by default.
- R-1341: the OOM WARN line once per new kill (container, start time, kill count); event unchanged.
2026-10-29 12:49:46 +01:00

69 lines
2.7 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"log"
"sync/atomic"
"time"
"gitea.dooplex.hu/admin/felhom-controller/internal/backup"
"gitea.dooplex.hu/admin/felhom-controller/internal/report"
"gitea.dooplex.hu/admin/felhom-controller/internal/scheduler"
)
// opActionReportFire holds the out-of-cycle report trigger once main has built it (func()). The
// executor is built earlier than the trigger, so it reads it through this holder.
var opActionReportFire atomic.Value
// operatorActionHandlers wires the five operator actions (R-314/R-279/R-177, `09` §3 decision 185)
// to the running process. ONE wiring site, called from main and from the wiring test, so the test
// exercises the doors production uses (the seam-built-but-never-wired trap).
//
// offsite_backup_now makes the SAME four checks as the household's „back up now"
// (offboxRunHandler) and then runs the same call, so an operator press can never do what the
// household's own press cannot.
//
// clear_hold (D64 = B) goes through clearHoldNow with hold — the same door the box command's socket uses
// (holdclear.go), so the hub's button and the box command can never differ. hold.clear nil = not available.
func operatorActionHandlers(backupMgr *backup.Manager, sched *scheduler.Scheduler, hold holdClearDeps, resultReady func(), logger *log.Logger) report.OperatorActionHandlers {
h := report.OperatorActionHandlers{ResultReady: resultReady}
if sched != nil {
h.RunJob = sched.RunNow
}
if hold.clear != nil {
h.ClearHold = func(stack string, actionID int64) (string, error) {
return clearHoldNow(hold, stack, fmt.Sprintf("the operator via the hub, action #%d", actionID))
}
}
if backupMgr == nil {
return h
}
h.OffsiteBackupNow = func() (string, func(ctx context.Context) error) {
switch {
case !backupMgr.OffboxConfigured():
return "no off-site target is set on this box", nil
case !backupMgr.OffboxRunnable():
return "the off-site key step (escrow) is not done yet — the run is refused until it is", nil
case backupMgr.OffboxOrphaned():
return "the off-site store is orphaned (written with an older key) — a run cannot write to it", nil
case backupMgr.IsRunning():
return "a backup run is already in flight", nil
}
return "", func(ctx context.Context) error {
if logger != nil {
logger.Printf("[INFO] [opaction] off-site backup started at the operator's request")
}
err := backupMgr.RunOffboxBackupWithProgress(ctx)
if errors.Is(err, backup.ErrOffboxRunInFlight) {
return fmt.Errorf("%w: %v", report.ErrActionRefused, err)
}
return err
}
}
h.AbandonStop = backupMgr.StopAbandon
h.AbandonExtend = func(days int) (time.Time, error) { return backupMgr.ExtendAbandon(days) }
return h
}