- 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>
266 lines
6.7 KiB
Go
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
|
|
}
|