From 301e1709745b3c94c2519679f447d4c958f78c17 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Sat, 19 Jul 2025 21:10:27 +0200 Subject: [PATCH] Refactor instance management and configuration handling --- server/cmd/llamactl.go | 6 +-- server/pkg/config.go | 33 ++++++++++++++-- server/pkg/handlers.go | 4 +- server/pkg/instance.go | 59 +++++++++++++++++++++++------ server/pkg/{options.go => llama.go} | 39 ------------------- server/pkg/manager.go | 37 +++++++++--------- server/pkg/validation.go | 2 +- 7 files changed, 102 insertions(+), 78 deletions(-) rename server/pkg/{options.go => llama.go} (92%) diff --git a/server/cmd/llamactl.go b/server/cmd/llamactl.go index a294681..36171e7 100644 --- a/server/cmd/llamactl.go +++ b/server/cmd/llamactl.go @@ -21,7 +21,7 @@ func main() { } // Initialize the instance manager - instanceManager := llamactl.NewInstanceManager() + instanceManager := llamactl.NewInstanceManager(config.Instances) // Create a new handler with the instance manager handler := llamactl.NewHandler(instanceManager) @@ -30,6 +30,6 @@ func main() { r := llamactl.SetupRouter(handler) // Start the server with the router - fmt.Println("Starting llamactl on port 8080...") - http.ListenAndServe(":8080", r) + fmt.Printf("Starting llamactl on port %d...\n", config.Server.Port) + http.ListenAndServe(fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port), r) } diff --git a/server/pkg/config.go b/server/pkg/config.go index 4b334d4..8c4cc3d 100644 --- a/server/pkg/config.go +++ b/server/pkg/config.go @@ -28,8 +28,8 @@ type ServerConfig struct { // InstancesConfig contains instance management configuration type InstancesConfig struct { - // Port range for instances (e.g., "8000-9000") - PortRange string `yaml:"port_range"` + // Port range for instances (e.g., 8000,9000) + PortRange [2]int `yaml:"port_range"` // Directory where instance logs will be stored LogDirectory string `yaml:"log_directory"` @@ -62,7 +62,7 @@ func LoadConfig(configPath string) (Config, error) { Port: 8080, }, Instances: InstancesConfig{ - PortRange: "8000-9000", + PortRange: [2]int{8000, 9000}, LogDirectory: "/tmp/llamactl", MaxInstances: 10, LlamaExecutable: "llama-server", @@ -121,7 +121,9 @@ func loadEnvVars(cfg *Config) { // Instance config if portRange := os.Getenv("LLAMACTL_INSTANCE_PORT_RANGE"); portRange != "" { - cfg.Instances.PortRange = portRange + if ports := parsePortRange(portRange); ports != [2]int{0, 0} { + cfg.Instances.PortRange = ports + } } if logDir := os.Getenv("LLAMACTL_LOG_DIR"); logDir != "" { cfg.Instances.LogDirectory = logDir @@ -168,6 +170,29 @@ func parseDelaySeconds(s string) (time.Duration, error) { return time.Duration(seconds * float64(time.Second)), nil } +// parsePortRange parses port range from string formats like "8000-9000" or "8000,9000" +func parsePortRange(s string) [2]int { + var parts []string + + // Try both separators + if strings.Contains(s, "-") { + parts = strings.Split(s, "-") + } else if strings.Contains(s, ",") { + parts = strings.Split(s, ",") + } + + // Parse the two parts + if len(parts) == 2 { + start, err1 := strconv.Atoi(strings.TrimSpace(parts[0])) + end, err2 := strconv.Atoi(strings.TrimSpace(parts[1])) + if err1 == nil && err2 == nil { + return [2]int{start, end} + } + } + + return [2]int{0, 0} // Invalid format +} + // getDefaultConfigLocations returns platform-specific config file locations func getDefaultConfigLocations() []string { var locations []string diff --git a/server/pkg/handlers.go b/server/pkg/handlers.go index fcb8727..58f7ca9 100644 --- a/server/pkg/handlers.go +++ b/server/pkg/handlers.go @@ -127,7 +127,7 @@ func (h *Handler) CreateInstance() http.HandlerFunc { return } - var options InstanceOptions + var options CreateInstanceRequest if err := json.NewDecoder(r.Body).Decode(&options); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return @@ -199,7 +199,7 @@ func (h *Handler) UpdateInstance() http.HandlerFunc { return } - var options InstanceOptions + var options CreateInstanceRequest if err := json.NewDecoder(r.Body).Decode(&options); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return diff --git a/server/pkg/instance.go b/server/pkg/instance.go index 4e70b52..01af664 100644 --- a/server/pkg/instance.go +++ b/server/pkg/instance.go @@ -18,9 +18,43 @@ import ( "time" ) +// Duration is a custom type that wraps time.Duration for better JSON/Swagger support +// @description Duration in seconds +type Duration time.Duration + +// MarshalJSON implements json.Marshaler for Duration +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Duration(d).Seconds()) +} + +// UnmarshalJSON implements json.Unmarshaler for Duration +func (d *Duration) UnmarshalJSON(data []byte) error { + var seconds float64 + if err := json.Unmarshal(data, &seconds); err != nil { + return err + } + *d = Duration(time.Duration(seconds * float64(time.Second))) + return nil +} + +// ToDuration converts Duration to time.Duration +func (d Duration) ToDuration() time.Duration { + return time.Duration(d) +} + +type CreateInstanceRequest struct { + // Auto restart + AutoRestart bool `json:"auto_restart,omitempty"` + MaxRestarts int `json:"max_restarts,omitempty"` + RestartDelay Duration `json:"restart_delay,omitempty"` // Duration in seconds + + LlamaServerOptions `json:",inline"` +} + +// Instance represents a running instance of the llama server type Instance struct { - Name string `json:"name"` - options *InstanceOptions `json:"-"` // Now unexported - access via GetOptions/SetOptions + Name string `json:"name"` + options *CreateInstanceRequest `json:"-"` // Now unexported - access via GetOptions/SetOptions // Status Running bool `json:"running"` @@ -40,12 +74,15 @@ type Instance struct { proxy *httputil.ReverseProxy `json:"-"` // Reverse proxy for this instance } -func NewInstance(name string, options *InstanceOptions) *Instance { +// NewInstance creates a new instance with the given name, log path, and options +func NewInstance(name string, logPath string, options *CreateInstanceRequest) *Instance { return &Instance{ Name: name, options: options, Running: false, + + logPath: logPath, } } @@ -80,13 +117,13 @@ func (i *Instance) closeLogFiles() { } } -func (i *Instance) GetOptions() *InstanceOptions { +func (i *Instance) GetOptions() *CreateInstanceRequest { i.mu.Lock() defer i.mu.Unlock() return i.options } -func (i *Instance) SetOptions(options *InstanceOptions) { +func (i *Instance) SetOptions(options *CreateInstanceRequest) { i.mu.Lock() defer i.mu.Unlock() if options == nil { @@ -317,9 +354,9 @@ func (i *Instance) MarshalJSON() ([]byte, error) { // Create a temporary struct with exported fields for JSON marshalling temp := struct { - Name string `json:"name"` - Options *InstanceOptions `json:"options,omitempty"` - Running bool `json:"running"` + Name string `json:"name"` + Options *CreateInstanceRequest `json:"options,omitempty"` + Running bool `json:"running"` }{ Name: i.Name, Options: i.options, @@ -333,9 +370,9 @@ func (i *Instance) MarshalJSON() ([]byte, error) { func (i *Instance) UnmarshalJSON(data []byte) error { // Create a temporary struct for unmarshalling temp := struct { - Name string `json:"name"` - Options *InstanceOptions `json:"options,omitempty"` - Running bool `json:"running"` + Name string `json:"name"` + Options *CreateInstanceRequest `json:"options,omitempty"` + Running bool `json:"running"` }{} if err := json.Unmarshal(data, &temp); err != nil { diff --git a/server/pkg/options.go b/server/pkg/llama.go similarity index 92% rename from server/pkg/options.go rename to server/pkg/llama.go index d3113b0..8d373d9 100644 --- a/server/pkg/options.go +++ b/server/pkg/llama.go @@ -5,42 +5,8 @@ import ( "reflect" "strconv" "strings" - "time" ) -// Duration is a custom type that wraps time.Duration for better JSON/Swagger support -// @description Duration in seconds -type Duration time.Duration - -// MarshalJSON implements json.Marshaler for Duration -func (d Duration) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Duration(d).Seconds()) -} - -// UnmarshalJSON implements json.Unmarshaler for Duration -func (d *Duration) UnmarshalJSON(data []byte) error { - var seconds float64 - if err := json.Unmarshal(data, &seconds); err != nil { - return err - } - *d = Duration(time.Duration(seconds * float64(time.Second))) - return nil -} - -// ToDuration converts Duration to time.Duration -func (d Duration) ToDuration() time.Duration { - return time.Duration(d) -} - -type InstanceOptions struct { - // Auto restart - AutoRestart bool `json:"auto_restart,omitempty"` - MaxRestarts int `json:"max_restarts,omitempty"` - RestartDelay Duration `json:"restart_delay,omitempty" example:"5"` // Duration in seconds - - LlamaServerOptions `json:",inline"` -} - type LlamaServerOptions struct { // Common params VerbosePrompt bool `json:"verbose_prompt,omitempty"` @@ -396,8 +362,3 @@ func (o *LlamaServerOptions) BuildCommandArgs() []string { return args } - -// BuildCommandArgs converts InstanceOptions to command line arguments by delegating to LlamaServerOptions -func (o *InstanceOptions) BuildCommandArgs() []string { - return o.LlamaServerOptions.BuildCommandArgs() -} diff --git a/server/pkg/manager.go b/server/pkg/manager.go index 139de70..086d1b6 100644 --- a/server/pkg/manager.go +++ b/server/pkg/manager.go @@ -7,9 +7,9 @@ import ( // InstanceManager defines the interface for managing instances of the llama server. type InstanceManager interface { ListInstances() ([]*Instance, error) - CreateInstance(name string, options *InstanceOptions) (*Instance, error) + CreateInstance(name string, options *CreateInstanceRequest) (*Instance, error) GetInstance(name string) (*Instance, error) - UpdateInstance(name string, options *InstanceOptions) (*Instance, error) + UpdateInstance(name string, options *CreateInstanceRequest) (*Instance, error) DeleteInstance(name string) error StartInstance(name string) (*Instance, error) StopInstance(name string) (*Instance, error) @@ -18,17 +18,17 @@ type InstanceManager interface { } type instanceManager struct { - instances map[string]*Instance - portRange [][2]int // Range of ports to use for instances - ports map[int]bool + instances map[string]*Instance + ports map[int]bool + instancesConfig InstancesConfig } // NewInstanceManager creates a new instance of InstanceManager. -func NewInstanceManager() InstanceManager { +func NewInstanceManager(instancesConfig InstancesConfig) InstanceManager { return &instanceManager{ - instances: make(map[string]*Instance), - portRange: [][2]int{{8000, 9000}}, - ports: make(map[int]bool), + instances: make(map[string]*Instance), + ports: make(map[int]bool), + instancesConfig: instancesConfig, } } @@ -43,7 +43,7 @@ func (im *instanceManager) ListInstances() ([]*Instance, error) { // CreateInstance creates a new instance with the given options and returns it. // The instance is initially in a "stopped" state. -func (im *instanceManager) CreateInstance(name string, options *InstanceOptions) (*Instance, error) { +func (im *instanceManager) CreateInstance(name string, options *CreateInstanceRequest) (*Instance, error) { if options == nil { return nil, fmt.Errorf("instance options cannot be nil") } @@ -72,7 +72,7 @@ func (im *instanceManager) CreateInstance(name string, options *InstanceOptions) options.Port = port } - instance := NewInstance(name, options) + instance := NewInstance(name, im.instancesConfig.LogDirectory, options) im.instances[instance.Name] = instance return instance, nil @@ -88,7 +88,7 @@ func (im *instanceManager) GetInstance(name string) (*Instance, error) { } // UpdateInstance updates the options of an existing instance and returns it. -func (im *instanceManager) UpdateInstance(name string, options *InstanceOptions) (*Instance, error) { +func (im *instanceManager) UpdateInstance(name string, options *CreateInstanceRequest) (*Instance, error) { instance, exists := im.instances[name] if !exists { return nil, fmt.Errorf("instance with name %s not found", name) @@ -178,13 +178,14 @@ func (im *instanceManager) GetInstanceLogs(name string) (string, error) { } func (im *instanceManager) getNextAvailablePort() (int, error) { - for _, portRange := range im.portRange { - for port := portRange[0]; port <= portRange[1]; port++ { - if !im.ports[port] { - im.ports[port] = true - return port, nil - } + portRange := im.instancesConfig.PortRange + + for port := portRange[0]; port <= portRange[1]; port++ { + if !im.ports[port] { + im.ports[port] = true + return port, nil } } + return 0, fmt.Errorf("no available ports in the specified range") } diff --git a/server/pkg/validation.go b/server/pkg/validation.go index 3d0bc23..087f12f 100644 --- a/server/pkg/validation.go +++ b/server/pkg/validation.go @@ -32,7 +32,7 @@ func validateStringForInjection(value string) error { } // ValidateInstanceOptions performs minimal security validation -func ValidateInstanceOptions(options *InstanceOptions) error { +func ValidateInstanceOptions(options *CreateInstanceRequest) error { if options == nil { return ValidationError(fmt.Errorf("options cannot be nil")) }