mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
Refactor instance management and configuration handling
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user