Files
gastown/internal/cmd/down.go
gastown/polecats/rictus 637e959c48 Add Boot shutdown to gt down command (gt-xyzb5)
Boot is spawned by the daemon as the Deacons watchdog. During shutdown
Boot was never explicitly stopped. Now gt down stops Boot between Mayor
and Deacon (step 3) to ensure clean shutdown.

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 22:12:10 -08:00

173 lines
4.5 KiB
Go

package cmd
import (
"fmt"
"time"
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/boot"
"github.com/steveyegge/gastown/internal/daemon"
"github.com/steveyegge/gastown/internal/events"
"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
• Boot - Deacon's watchdog
• 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 Boot (Deacon's watchdog)
if err := stopSession(t, boot.SessionName); err != nil {
printDownStatus("Boot", false, err.Error())
allOK = false
} else {
printDownStatus("Boot", true, "stopped")
}
// 4. Stop Deacon
if err := stopSession(t, DeaconSessionName); err != nil {
printDownStatus("Deacon", false, err.Error())
allOK = false
} else {
printDownStatus("Deacon", true, "stopped")
}
// 5. 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")
}
// 6. 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("✓"))
// Log halt event with stopped services
stoppedServices := []string{"daemon", "deacon", "boot", "mayor"}
for _, rigName := range rigs {
stoppedServices = append(stoppedServices, fmt.Sprintf("%s/witness", rigName))
}
if downAll {
stoppedServices = append(stoppedServices, "tmux-server")
}
_ = events.LogFeed(events.TypeHalt, "gt", events.HaltPayload(stoppedServices))
} 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, best-effort interrupt)
if !downForce {
_ = t.SendKeysRaw(sessionName, "C-c")
time.Sleep(100 * time.Millisecond)
}
// Kill the session
return t.KillSession(sessionName)
}