package main import ( "bytes" "encoding/json" "flag" "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) } defaultConfigPath := filepath.Join(home, ".config", "vssm", "config.json") configFlag := flag.String("config", defaultConfigPath, "Explicit path targeting a custom vssm config.json profile") flag.Usage = printUsage flag.Parse() args := flag.Args() if len(args) < 1 { printUsage() return } absConfigPath, err := filepath.Abs(*configFlag) if err != nil { log.Fatalf("Failed to resolve absolute configuration path target: %v", err) } cfg, err := LoadOrCreateConfig(absConfigPath) if err != nil { log.Fatalf("Initialization failed: %v", err) } subCommand := args[0] switch subCommand { case "daemon": fmt.Printf("Initializing VS server manager background supervisor [Config: %s]...\n", absConfigPath) if err := StartDaemon(cfg, absConfigPath); err != nil { log.Fatalf("Daemon runtime fatal error: %v", err) } case "create": if len(args) < 3 { log.Fatalf("Usage: vssm create ") } name := args[1] version := args[2] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/create?name=%s&version=%s", url.QueryEscape(name), url.QueryEscape(version)), nil) case "start": if len(args) < 2 { log.Fatalf("Usage: vssm start ") } name := args[1] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/start?name=%s", url.QueryEscape(name)), nil) case "stop": if len(args) < 2 { log.Fatalf("Usage: vssm stop ") } name := args[1] sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/stop?name=%s", url.QueryEscape(name)), nil) case "cmd": if len(args) < 3 { log.Fatalf("Usage: vssm cmd \"\"") } name := args[1] serverCmd := args[2] 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) case "show-config": fmt.Printf("%v", cfg.Storage.AppDataDir) 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 && resp.StatusCode != http.StatusCreated { 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("VintageStory Server Manager") fmt.Println("\nGlobal Options:") fmt.Println(" --config Explicitly target a non-default config structure file location") fmt.Println("\nUsage Commands:") fmt.Println(" vssm daemon Starts the background process supervisor") fmt.Println(" vssm create Provisions baseline configuration and stores instance profile") fmt.Println(" vssm start Launches an existing server instance using stored profile") fmt.Println(" vssm stop Gracefully shuts down a server instance") fmt.Println(" vssm cmd \"\" Dispatches a terminal console command down the pipe") fmt.Println(" vssm list Displays the operational matrix of all instances") }