package main import ( "bytes" "encoding/json" "fmt" "io" "log" "net/http" "net/url" "os" "path/filepath" "text/tabwriter" ) func main() { home, err := os.UserHomeDir() if err != nil { log.Fatalf("Could not locate user home dir: %v", err) } configPath := filepath.Join(home, ".config", "vs-manager", "config.json") cfg, err := LoadOrCreateConfig(configPath) if err != nil { log.Fatalf("Initialization failed: %v", err) } if len(os.Args) < 2 { printUsage() return } subCommand := os.Args[1] switch subCommand { case "daemon": fmt.Println("Initializing VS server manager background supervisor...") if err := StartDaemon(cfg); err != nil { log.Fatalf("Daemon runtime fatal error: %v", err) } case "create": if len(os.Args) < 4 { log.Fatalf("Usage: go run . create ") } name := os.Args[2] version := os.Args[3] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/create?name=%s&version=%s", url.QueryEscape(name), url.QueryEscape(version)), nil) case "start": if len(os.Args) < 3 { log.Fatalf("Usage: go run . start ") } name := os.Args[2] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/start?name=%s", url.QueryEscape(name)), nil) case "stop": if len(os.Args) < 3 { log.Fatalf("Usage: go run . stop ") } name := os.Args[2] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/stop?name=%s", url.QueryEscape(name)), nil) case "cmd": if len(os.Args) < 4 { log.Fatalf("Usage: go run . cmd \"\"") } name := os.Args[2] serverCmd := os.Args[3] payload := CommandPayload{Command: serverCmd} body, _ := json.Marshal(payload) sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/command?name=%s", url.QueryEscape(name)), bytes.NewBuffer(body)) case "list", "status": fetchAndPrintStatus(cfg) default: printUsage() } } func sendIPCRequest(cfg *AppConfig, method string, path string, body io.Reader) { targetUrl := fmt.Sprintf("http://%s%s", cfg.Daemon.ListenAddress, path) req, err := http.NewRequest(method, targetUrl, body) if err != nil { log.Fatalf("Failed to construct IPC frame: %v", err) } if body != nil { req.Header.Set("Content-Type", "application/json") } resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatalf("IPC connection failed. Is the vs-manager daemon running? Error: %v", err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode != http.StatusOK { log.Fatalf("Error from daemon: %s", string(respBody)) } fmt.Println(string(respBody)) } func fetchAndPrintStatus(cfg *AppConfig) { targetUrl := fmt.Sprintf("http://%s/instances/list", cfg.Daemon.ListenAddress) resp, err := http.Get(targetUrl) if err != nil { log.Fatalf("IPC connection failed. Is the vs-manager daemon running? Error: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { respBody, _ := io.ReadAll(resp.Body) log.Fatalf("Error from daemon: %s", string(respBody)) } var instances []InstanceStatusResponse if err := json.NewDecoder(resp.Body).Decode(&instances); err != nil { log.Fatalf("Failed to decode daemon status matrix: %v", err) } if len(instances) == 0 { fmt.Println("No instances configured yet. Use 'create' to provision one.") return } w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) fmt.Fprintln(w, "INSTANCE NAME\tVERSION\tPORT\tSTATUS") fmt.Fprintln(w, "-------------\t-------\t----\t------") for _, inst := range instances { fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", inst.Name, inst.Version, inst.Port, inst.Status) } w.Flush() } func printUsage() { fmt.Println("Vintage Story Server Manager") fmt.Println("\nUsage:") fmt.Println(" go run . daemon Starts the background process supervisor") fmt.Println(" go run . create Provisions baseline configuration and stores instance profile") fmt.Println(" go run . start Launches an existing server instance using stored profile") fmt.Println(" go run . stop Gracefully shuts down a server instance") fmt.Println(" go run . cmd \"\" Dispatches a terminal console command down the pipe") }