diff --git a/cmd/hecate/main.go b/cmd/hecate/main.go index b53d411..70ebabd 100644 --- a/cmd/hecate/main.go +++ b/cmd/hecate/main.go @@ -51,6 +51,11 @@ func main() { logger.Printf("status failed: %v", err) 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": usage() default: @@ -203,6 +208,43 @@ func runStatus(logger *log.Logger, args []string) error { 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) { targets := make([]service.Target, 0, len(cfg.UPS.Targets)+1) switch cfg.UPS.Provider { @@ -282,12 +324,14 @@ Commands: shutdown Perform graceful cluster shutdown daemon Monitor UPS and auto-trigger shutdown status Print current hecate status and estimates + intent Read or manually set intent state Examples: hecate startup --config /etc/hecate/hecate.yaml --execute --force-flux-branch main hecate shutdown --config /etc/hecate/hecate.yaml --execute --reason "manual-maintenance" hecate daemon --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 `) }