Made the create command require a port number

This commit is contained in:
2026-06-05 23:38:42 -05:00
parent 53b9f30d50
commit 9f4e27869b
3 changed files with 95 additions and 10 deletions

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"strconv"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@@ -41,6 +42,7 @@ func StartDaemon(cfg *AppConfig, configPath string) error {
mux.HandleFunc("/instances/stop", ds.handleStop) mux.HandleFunc("/instances/stop", ds.handleStop)
mux.HandleFunc("/instances/command", ds.handleCommand) mux.HandleFunc("/instances/command", ds.handleCommand)
mux.HandleFunc("/instances/list", ds.handleList) mux.HandleFunc("/instances/list", ds.handleList)
mux.HandleFunc("/instances/logs", ds.handleGetLogs)
corsWrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { corsWrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*") 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") name := r.URL.Query().Get("name")
version := r.URL.Query().Get("version") version := r.URL.Query().Get("version")
if name == "" || version == "" { port := r.URL.Query().Get("port")
http.Error(w, "Missing name or version parameters", http.StatusBadRequest) 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 return
} }
@@ -156,11 +165,11 @@ func (ds *DaemonServer) handleCreate(w http.ResponseWriter, r *http.Request) {
options := VsServerConfigOptions{ options := VsServerConfigOptions{
Version: version, Version: version,
ServerName: name, ServerName: name,
Port: 42424, Port: converted_port,
MaxClients: 10, MaxClients: 10,
} }
err := DownloadAndExtractServer(version, ds.cfg.Storage.InstallDir) err = DownloadAndExtractServer(version, ds.cfg.Storage.InstallDir)
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("Installation failed: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("Installation failed: %v", err), http.StatusInternalServerError)
return return
@@ -303,3 +312,33 @@ func (ds *DaemonServer) handleList(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(responseList) 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)
}
}

View File

@@ -54,12 +54,13 @@ func main() {
} }
case "create": case "create":
if len(args) < 3 { if len(args) < 4 {
log.Fatalf("Usage: vssm create <instance_name> <version>") log.Fatalf("Usage: vssm create <instance_name> <version> <port>")
} }
name := args[1] name := args[1]
version := args[2] 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": case "start":
if len(args) < 2 { if len(args) < 2 {

View File

@@ -15,12 +15,20 @@ type ProcessManager struct {
sync.RWMutex sync.RWMutex
ActiveInstances map[string]*exec.Cmd ActiveInstances map[string]*exec.Cmd
StdinPipes map[string]io.WriteCloser StdinPipes map[string]io.WriteCloser
LogBuffers map[string]*InstanceLogBuffer
}
type InstanceLogBuffer struct {
sync.RWMutex
Lines []string
MaxLines int
} }
func NewProcessManager() *ProcessManager { func NewProcessManager() *ProcessManager {
return &ProcessManager{ return &ProcessManager{
ActiveInstances: make(map[string]*exec.Cmd), ActiveInstances: make(map[string]*exec.Cmd),
StdinPipes: make(map[string]io.WriteCloser), 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.ActiveInstances[name] = cmd
pm.StdinPipes[name] = stdinPipe pm.StdinPipes[name] = stdinPipe
pm.LogBuffers[name] = NewInstanceLogBuffer(200)
go pm.streamLogs(name, stdoutPipe) go pm.streamLogs(name, stdoutPipe)
go pm.watchProcessExit(name, cmd) go pm.watchProcessExit(name, cmd)
@@ -79,9 +88,17 @@ func (pm *ProcessManager) streamLogs(name string, stdout io.ReadCloser) {
scanner := bufio.NewScanner(stdout) scanner := bufio.NewScanner(stdout)
for scanner.Scan() { for scanner.Scan() {
//stream straight to the manager console terminal, line := scanner.Text()
// but later dispatch payloads to our web UI or a log file
fmt.Printf("[%s]: %s\n", name, 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 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
}