package cmd import ( "fmt" "time" "github.com/spf13/cobra" "github.com/steveyegge/gastown/internal/daemon" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/tmux" "github.com/steveyegge/gastown/internal/workspace" ) var downCmd = &cobra.Command{ Use: "down", GroupID: GroupServices, Short: "Stop all Gas Town services", Long: `Stop all Gas Town long-lived services. This gracefully shuts down all infrastructure agents: • Witnesses - Per-rig polecat managers • Mayor - Global work coordinator • Deacon - Health orchestrator • Daemon - Go background process Polecats are NOT stopped by this command - use 'gt swarm stop' or kill individual polecats with 'gt polecat kill'. This is useful for: • Taking a break (stop token consumption) • Clean shutdown before system maintenance • Resetting the town to a clean state`, RunE: runDown, } var ( downQuiet bool downForce bool downAll bool ) func init() { downCmd.Flags().BoolVarP(&downQuiet, "quiet", "q", false, "Only show errors") downCmd.Flags().BoolVarP(&downForce, "force", "f", false, "Force kill without graceful shutdown") downCmd.Flags().BoolVarP(&downAll, "all", "a", false, "Also kill the tmux server") rootCmd.AddCommand(downCmd) } func runDown(cmd *cobra.Command, args []string) error { townRoot, err := workspace.FindFromCwdOrError() if err != nil { return fmt.Errorf("not in a Gas Town workspace: %w", err) } t := tmux.NewTmux() allOK := true // Stop in reverse order of startup // 1. Stop witnesses first rigs := discoverRigs(townRoot) for _, rigName := range rigs { sessionName := fmt.Sprintf("gt-%s-witness", rigName) if err := stopSession(t, sessionName); err != nil { printDownStatus(fmt.Sprintf("Witness (%s)", rigName), false, err.Error()) allOK = false } else { printDownStatus(fmt.Sprintf("Witness (%s)", rigName), true, "stopped") } } // 2. Stop Mayor if err := stopSession(t, MayorSessionName); err != nil { printDownStatus("Mayor", false, err.Error()) allOK = false } else { printDownStatus("Mayor", true, "stopped") } // 3. Stop Deacon if err := stopSession(t, DeaconSessionName); err != nil { printDownStatus("Deacon", false, err.Error()) allOK = false } else { printDownStatus("Deacon", true, "stopped") } // 4. Stop Daemon last running, _, _ := daemon.IsRunning(townRoot) if running { if err := daemon.StopDaemon(townRoot); err != nil { printDownStatus("Daemon", false, err.Error()) allOK = false } else { printDownStatus("Daemon", true, "stopped") } } else { printDownStatus("Daemon", true, "not running") } // 5. Kill tmux server if --all if downAll { if err := t.KillServer(); err != nil { printDownStatus("Tmux server", false, err.Error()) allOK = false } else { printDownStatus("Tmux server", true, "killed") } } fmt.Println() if allOK { fmt.Printf("%s All services stopped\n", style.Bold.Render("✓")) } else { fmt.Printf("%s Some services failed to stop\n", style.Bold.Render("✗")) return fmt.Errorf("not all services stopped") } return nil } func printDownStatus(name string, ok bool, detail string) { if downQuiet && ok { return } if ok { fmt.Printf("%s %s: %s\n", style.SuccessPrefix, name, style.Dim.Render(detail)) } else { fmt.Printf("%s %s: %s\n", style.ErrorPrefix, name, detail) } } // stopSession gracefully stops a tmux session. func stopSession(t *tmux.Tmux, sessionName string) error { running, err := t.HasSession(sessionName) if err != nil { return err } if !running { return nil // Already stopped } // Try graceful shutdown first (Ctrl-C) if !downForce { _ = t.SendKeysRaw(sessionName, "C-c") time.Sleep(100 * time.Millisecond) } // Kill the session return t.KillSession(sessionName) }