152 lines
3.7 KiB
Go
152 lines
3.7 KiB
Go
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",
|
|
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)
|
|
}
|