diff --git a/cmd/homelab-deploy/main.go b/cmd/homelab-deploy/main.go index 22a3617..573b7a2 100644 --- a/cmd/homelab-deploy/main.go +++ b/cmd/homelab-deploy/main.go @@ -16,7 +16,7 @@ import ( "github.com/urfave/cli/v3" ) -const version = "0.1.6" +const version = "0.1.7" func main() { app := &cli.Command{ @@ -27,6 +27,7 @@ func main() { listenerCommand(), mcpCommand(), deployCommand(), + listHostsCommand(), }, } @@ -270,3 +271,88 @@ func deployCommand() *cli.Command { }, } } + +func listHostsCommand() *cli.Command { + return &cli.Command{ + Name: "list-hosts", + Usage: "List available deployment targets", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "nats-url", + Usage: "NATS server URL", + Sources: cli.EnvVars("HOMELAB_DEPLOY_NATS_URL"), + Required: true, + }, + &cli.StringFlag{ + Name: "nkey-file", + Usage: "Path to NKey seed file for NATS authentication", + Sources: cli.EnvVars("HOMELAB_DEPLOY_NKEY_FILE"), + Required: true, + }, + &cli.StringFlag{ + Name: "tier", + Usage: "Filter by tier (test or prod)", + Sources: cli.EnvVars("HOMELAB_DEPLOY_TIER"), + }, + &cli.StringFlag{ + Name: "discover-subject", + Usage: "NATS subject for host discovery", + Sources: cli.EnvVars("HOMELAB_DEPLOY_DISCOVER_SUBJECT"), + Value: "deploy.discover", + }, + &cli.IntFlag{ + Name: "timeout", + Usage: "Timeout in seconds for discovery", + Sources: cli.EnvVars("HOMELAB_DEPLOY_DISCOVER_TIMEOUT"), + Value: 5, + }, + }, + Action: func(ctx context.Context, c *cli.Command) error { + tierFilter := c.String("tier") + if tierFilter != "" && tierFilter != "test" && tierFilter != "prod" { + return fmt.Errorf("tier must be 'test' or 'prod', got %q", tierFilter) + } + + // Handle shutdown signals + ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) + defer cancel() + + responses, err := deploycli.Discover( + ctx, + c.String("nats-url"), + c.String("nkey-file"), + c.String("discover-subject"), + time.Duration(c.Int("timeout"))*time.Second, + ) + if err != nil { + return fmt.Errorf("discovery failed: %w", err) + } + + if len(responses) == 0 { + fmt.Println("No hosts responded to discovery request") + return nil + } + + fmt.Println("Available deployment targets:") + fmt.Println() + + for _, resp := range responses { + if tierFilter != "" && resp.Tier != tierFilter { + continue + } + + role := resp.Role + if role == "" { + role = "(none)" + } + + fmt.Printf("- %s (tier=%s, role=%s)\n", resp.Hostname, resp.Tier, role) + for _, subj := range resp.DeploySubjects { + fmt.Printf(" %s\n", subj) + } + } + + return nil + }, + } +}