homelab/services/games-console/cli/cmd/servers/servers.go
Dan V deb6c38d7b chore: commit homelab setup — deployment, services, orchestration, skill
- Add .gitignore: exclude compiled binaries, build artifacts, and Helm
  values files containing real secrets (authentik, prometheus)
- Add all Kubernetes deployment manifests (deployment/)
- Add services source code: ha-sync, device-inventory, games-console,
  paperclip, parts-inventory
- Add Ansible orchestration: playbooks, roles, inventory, cloud-init
- Add hardware specs, execution plans, scripts, HOMELAB.md
- Add skills/homelab/SKILL.md + skills/install.sh to preserve Copilot skill
- Remove previously-tracked inventory-cli binary from git index

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-09 08:10:32 +02:00

266 lines
6.7 KiB
Go

package servers
import (
"fmt"
"os"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/vandachevici/gconsole/api"
)
// NewServersCmd returns the 'servers' command with all subcommands.
func NewServersCmd(getAPIURL func(string) string) *cobra.Command {
cmd := &cobra.Command{
Use: "servers",
Short: "Manage game servers",
}
var apiURLFlag string
cmd.PersistentFlags().StringVar(&apiURLFlag, "api-url", "", "API URL override")
client := func() *api.Client {
return api.NewClient(getAPIURL(apiURLFlag))
}
cmd.AddCommand(newListCmd(client))
cmd.AddCommand(newGetCmd(client))
cmd.AddCommand(newLogsCmd(client))
cmd.AddCommand(newCreateCmd(client))
cmd.AddCommand(newEditCmd(client))
cmd.AddCommand(newDeleteCmd(client))
cmd.AddCommand(newStartCmd(client))
cmd.AddCommand(newStopCmd(client))
cmd.AddCommand(newRestartCmd(client))
return cmd
}
// --- list ---
func newListCmd(client func() *api.Client) *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all game servers",
RunE: func(cmd *cobra.Command, args []string) error {
servers, err := client().ListServers()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "NAME\tTYPE\tSTATUS\tREADY\tNODEPORT\tIMAGE")
for _, s := range servers {
fmt.Fprintf(w, "%s\t%s\t%s\t%d/%d\t%d\t%s\n",
s.Name, s.Type, s.Status, s.Ready, s.Replicas, s.NodePort, s.Image)
}
return w.Flush()
},
}
}
// --- get ---
func newGetCmd(client func() *api.Client) *cobra.Command {
return &cobra.Command{
Use: "get <name>",
Short: "Get details of a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
s, err := client().GetServer(args[0])
if err != nil {
return err
}
fmt.Printf("Name: %s\n", s.Name)
fmt.Printf("Type: %s\n", s.Type)
fmt.Printf("Status: %s\n", s.Status)
fmt.Printf("Ready: %d/%d\n", s.Ready, s.Replicas)
fmt.Printf("Image: %s\n", s.Image)
fmt.Printf("Port: %d\n", s.Port)
fmt.Printf("NodePort: %d\n", s.NodePort)
fmt.Printf("Created: %s\n", s.CreatedAt)
if len(s.EnvVars) > 0 {
fmt.Println("Env Vars:")
for k, v := range s.EnvVars {
fmt.Printf(" %s=%s\n", k, v)
}
}
return nil
},
}
}
// --- logs ---
func newLogsCmd(client func() *api.Client) *cobra.Command {
var lines int
cmd := &cobra.Command{
Use: "logs <name>",
Short: "Get logs from a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
logs, err := client().GetLogs(args[0], lines)
if err != nil {
return err
}
fmt.Print(logs)
return nil
},
}
cmd.Flags().IntVar(&lines, "lines", 100, "Number of log lines to retrieve")
return cmd
}
// --- create ---
func newCreateCmd(client func() *api.Client) *cobra.Command {
var (
image string
port int32
nodePort int32
gameType string
envVars []string
)
cmd := &cobra.Command{
Use: "create <name>",
Short: "Create a new game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
req := api.CreateServerRequest{
Name: args[0],
Type: gameType,
Image: image,
Port: port,
NodePort: nodePort,
EnvVars: parseEnvVars(envVars),
}
s, err := client().CreateServer(req)
if err != nil {
return err
}
fmt.Printf("Created server %q (type: %s, image: %s)\n", s.Name, s.Type, s.Image)
return nil
},
}
cmd.Flags().StringVar(&image, "image", "", "Container image (required)")
cmd.Flags().Int32Var(&port, "port", 25565, "Game server port")
cmd.Flags().Int32Var(&nodePort, "node-port", 0, "Kubernetes NodePort (0 = auto-assign)")
cmd.Flags().StringVar(&gameType, "type", "unknown", "Server type (minecraft, factorio, openttd, ...)")
cmd.Flags().StringArrayVar(&envVars, "env", nil, "Environment variables (KEY=VALUE), repeatable")
cmd.MarkFlagRequired("image")
return cmd
}
// --- edit ---
func newEditCmd(client func() *api.Client) *cobra.Command {
var (
image string
envVars []string
)
cmd := &cobra.Command{
Use: "edit <name>",
Short: "Edit a game server's image or environment variables",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
req := api.UpdateServerRequest{
Image: image,
EnvVars: parseEnvVars(envVars),
}
s, err := client().UpdateServer(args[0], req)
if err != nil {
return err
}
fmt.Printf("Updated server %q\n", s.Name)
return nil
},
}
cmd.Flags().StringVar(&image, "image", "", "New container image")
cmd.Flags().StringArrayVar(&envVars, "env", nil, "Environment variables (KEY=VALUE), repeatable")
return cmd
}
// --- delete ---
func newDeleteCmd(client func() *api.Client) *cobra.Command {
var confirm bool
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if !confirm {
return fmt.Errorf("pass --confirm to delete server %q", args[0])
}
if err := client().DeleteServer(args[0]); err != nil {
return err
}
fmt.Printf("Deleted server %q\n", args[0])
return nil
},
}
cmd.Flags().BoolVar(&confirm, "confirm", false, "Confirm deletion")
return cmd
}
// --- start / stop / restart ---
func newStartCmd(client func() *api.Client) *cobra.Command {
return &cobra.Command{
Use: "start <name>",
Short: "Start a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := client().Start(args[0]); err != nil {
return err
}
fmt.Printf("Started %q\n", args[0])
return nil
},
}
}
func newStopCmd(client func() *api.Client) *cobra.Command {
return &cobra.Command{
Use: "stop <name>",
Short: "Stop a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := client().Stop(args[0]); err != nil {
return err
}
fmt.Printf("Stopped %q\n", args[0])
return nil
},
}
}
func newRestartCmd(client func() *api.Client) *cobra.Command {
return &cobra.Command{
Use: "restart <name>",
Short: "Restart a game server",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if err := client().Restart(args[0]); err != nil {
return err
}
fmt.Printf("Restarting %q\n", args[0])
return nil
},
}
}
// parseEnvVars converts ["KEY=VAL", ...] to map[string]string.
func parseEnvVars(pairs []string) map[string]string {
if len(pairs) == 0 {
return nil
}
m := make(map[string]string, len(pairs))
for _, p := range pairs {
parts := strings.SplitN(p, "=", 2)
if len(parts) == 2 {
m[parts[0]] = parts[1]
}
}
return m
}