hecate: add intent subcommand for safe state recovery

This commit is contained in:
Brad Stein 2026-04-04 19:00:39 -03:00
parent 7b1f69ab3c
commit 6717cd1f3e

View File

@ -51,6 +51,11 @@ func main() {
logger.Printf("status failed: %v", err) logger.Printf("status failed: %v", err)
os.Exit(1) os.Exit(1)
} }
case "intent":
if err := runIntent(logger, os.Args[2:]); err != nil {
logger.Printf("intent failed: %v", err)
os.Exit(1)
}
case "help", "-h", "--help": case "help", "-h", "--help":
usage() usage()
default: default:
@ -203,6 +208,43 @@ func runStatus(logger *log.Logger, args []string) error {
return nil return nil
} }
func runIntent(logger *log.Logger, args []string) error {
fs := flag.NewFlagSet("intent", flag.ExitOnError)
configPath := fs.String("config", "/etc/hecate/hecate.yaml", "Path to config file")
setState := fs.String("set", "", "Set intent state (normal|startup_in_progress|shutting_down|shutdown_complete)")
reason := fs.String("reason", "manual-intent", "Intent reason (used with --set)")
source := fs.String("source", "manual", "Intent source (used with --set)")
execute := fs.Bool("execute", false, "Actually write intent state (default read-only)")
_ = fs.Parse(args)
cfg, err := config.Load(*configPath)
if err != nil {
return err
}
if strings.TrimSpace(*setState) == "" {
in, readErr := state.ReadIntent(cfg.State.IntentPath)
if readErr != nil {
return readErr
}
if in.State == "" {
logger.Printf("intent=none")
return nil
}
logger.Printf("intent=%s reason=%q source=%s updated_at=%s",
in.State, in.Reason, in.Source, in.UpdatedAt.Format(time.RFC3339))
return nil
}
if !*execute {
return fmt.Errorf("refusing to write intent without --execute")
}
stateValue := strings.TrimSpace(*setState)
if err := state.MustWriteIntent(cfg.State.IntentPath, stateValue, *reason, *source); err != nil {
return err
}
logger.Printf("intent updated: state=%s reason=%q source=%s", stateValue, *reason, *source)
return nil
}
func buildUPSTargets(cfg config.Config) ([]service.Target, error) { func buildUPSTargets(cfg config.Config) ([]service.Target, error) {
targets := make([]service.Target, 0, len(cfg.UPS.Targets)+1) targets := make([]service.Target, 0, len(cfg.UPS.Targets)+1)
switch cfg.UPS.Provider { switch cfg.UPS.Provider {
@ -282,12 +324,14 @@ Commands:
shutdown Perform graceful cluster shutdown shutdown Perform graceful cluster shutdown
daemon Monitor UPS and auto-trigger shutdown daemon Monitor UPS and auto-trigger shutdown
status Print current hecate status and estimates status Print current hecate status and estimates
intent Read or manually set intent state
Examples: Examples:
hecate startup --config /etc/hecate/hecate.yaml --execute --force-flux-branch main hecate startup --config /etc/hecate/hecate.yaml --execute --force-flux-branch main
hecate shutdown --config /etc/hecate/hecate.yaml --execute --reason "manual-maintenance" hecate shutdown --config /etc/hecate/hecate.yaml --execute --reason "manual-maintenance"
hecate daemon --config /etc/hecate/hecate.yaml hecate daemon --config /etc/hecate/hecate.yaml
hecate status --config /etc/hecate/hecate.yaml hecate status --config /etc/hecate/hecate.yaml
hecate intent --config /etc/hecate/hecate.yaml --set normal --reason "manual-clear" --execute
`) `)
} }