From 9f4e27869b47237b277ab44745970c14c0ee7ff6 Mon Sep 17 00:00:00 2001 From: chris bell Date: Fri, 5 Jun 2026 23:38:42 -0500 Subject: [PATCH] Made the create command require a port number --- daemon.go | 47 +++++++++++++++++++++++++++++++++++++++++++---- main.go | 7 ++++--- process.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 95 insertions(+), 10 deletions(-) diff --git a/daemon.go b/daemon.go index 535a300..1d72c87 100644 --- a/daemon.go +++ b/daemon.go @@ -6,6 +6,7 @@ import ( "net/http" "os" "os/signal" + "strconv" "sync" "syscall" "time" @@ -41,6 +42,7 @@ func StartDaemon(cfg *AppConfig, configPath string) error { mux.HandleFunc("/instances/stop", ds.handleStop) mux.HandleFunc("/instances/command", ds.handleCommand) mux.HandleFunc("/instances/list", ds.handleList) + mux.HandleFunc("/instances/logs", ds.handleGetLogs) corsWrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") @@ -143,8 +145,15 @@ func (ds *DaemonServer) handleCreate(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") version := r.URL.Query().Get("version") - if name == "" || version == "" { - http.Error(w, "Missing name or version parameters", http.StatusBadRequest) + port := r.URL.Query().Get("port") + if name == "" || version == "" || port == "" { + http.Error(w, "Missing name, version, or port parameters", http.StatusBadRequest) + return + } + + converted_port, err := strconv.Atoi(port) + if err != nil { + http.Error(w, "Could not convert provided port to a valid port number", http.StatusBadRequest) return } @@ -156,11 +165,11 @@ func (ds *DaemonServer) handleCreate(w http.ResponseWriter, r *http.Request) { options := VsServerConfigOptions{ Version: version, ServerName: name, - Port: 42424, + Port: converted_port, MaxClients: 10, } - err := DownloadAndExtractServer(version, ds.cfg.Storage.InstallDir) + err = DownloadAndExtractServer(version, ds.cfg.Storage.InstallDir) if err != nil { http.Error(w, fmt.Sprintf("Installation failed: %v", err), http.StatusInternalServerError) return @@ -303,3 +312,33 @@ func (ds *DaemonServer) handleList(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(responseList) } + +func (ds *DaemonServer) handleGetLogs(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + name := r.URL.Query().Get("name") + if name == "" { + http.Error(w, "Missing name parameter", http.StatusBadRequest) + return + } + + ds.procManager.RLock() + buf, exists := ds.procManager.LogBuffers[name] + ds.procManager.RUnlock() + + if !exists || buf == nil { + w.Header().Set("Content-Type", "application/json") + w.Write([]byte("[]")) + return + } + + logLines := buf.GetSnapshot() + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(logLines); err != nil { + http.Error(w, fmt.Sprintf("Failed encoding log matrix: %v", err), http.StatusInternalServerError) + } +} diff --git a/main.go b/main.go index 2a66c03..24be728 100644 --- a/main.go +++ b/main.go @@ -54,12 +54,13 @@ func main() { } case "create": - if len(args) < 3 { - log.Fatalf("Usage: vssm create ") + if len(args) < 4 { + 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) + port := args[3] + sendIPCRequest(cfg, "POST", fmt.Sprintf("/instances/create?name=%s&version=%s&port=%s", url.QueryEscape(name), url.QueryEscape(version), url.QueryEscape(port)), nil) case "start": if len(args) < 2 { diff --git a/process.go b/process.go index d2924ce..d455d21 100644 --- a/process.go +++ b/process.go @@ -15,12 +15,20 @@ type ProcessManager struct { sync.RWMutex ActiveInstances map[string]*exec.Cmd StdinPipes map[string]io.WriteCloser + LogBuffers map[string]*InstanceLogBuffer +} + +type InstanceLogBuffer struct { + sync.RWMutex + Lines []string + MaxLines int } func NewProcessManager() *ProcessManager { return &ProcessManager{ ActiveInstances: make(map[string]*exec.Cmd), StdinPipes: make(map[string]io.WriteCloser), + LogBuffers: map[string]*InstanceLogBuffer{}, } } @@ -66,6 +74,7 @@ func (pm *ProcessManager) StartInstance(name string, version string, options VsS pm.ActiveInstances[name] = cmd pm.StdinPipes[name] = stdinPipe + pm.LogBuffers[name] = NewInstanceLogBuffer(200) go pm.streamLogs(name, stdoutPipe) go pm.watchProcessExit(name, cmd) @@ -79,9 +88,17 @@ func (pm *ProcessManager) streamLogs(name string, stdout io.ReadCloser) { scanner := bufio.NewScanner(stdout) for scanner.Scan() { - //stream straight to the manager console terminal, - // but later dispatch payloads to our web UI or a log file - fmt.Printf("[%s]: %s\n", name, scanner.Text()) + line := scanner.Text() + + fmt.Printf("[%s]: %s\n", name, line) + + pm.RLock() + buf, exists := pm.LogBuffers[name] + pm.RUnlock() + + if exists { + buf.Append(line) + } } } @@ -119,3 +136,31 @@ func (pm *ProcessManager) SendCommand(name string, command string) error { return nil } + +func NewInstanceLogBuffer(maxLines int) *InstanceLogBuffer { + return &InstanceLogBuffer{ + Lines: make([]string, 0, maxLines), + MaxLines: maxLines, + } +} + +func (lb *InstanceLogBuffer) Append(line string) { + lb.Lock() + defer lb.Unlock() + + if len(lb.Lines) >= lb.MaxLines { + // Shift array out by dropping index 0 + lb.Lines = lb.Lines[1:] + } + lb.Lines = append(lb.Lines, line) +} + +func (lb *InstanceLogBuffer) GetSnapshot() []string { + lb.RLock() + defer lb.RUnlock() + + // Return a copy so the caller can safely iterate or serialize to JSON without lock conflicts + snapshot := make([]string, len(lb.Lines)) + copy(snapshot, lb.Lines) + return snapshot +}