mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 17:14:28 +00:00
Refactor instance management and configuration handling
This commit is contained in:
@@ -21,7 +21,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the instance manager
|
// Initialize the instance manager
|
||||||
instanceManager := llamactl.NewInstanceManager()
|
instanceManager := llamactl.NewInstanceManager(config.Instances)
|
||||||
|
|
||||||
// Create a new handler with the instance manager
|
// Create a new handler with the instance manager
|
||||||
handler := llamactl.NewHandler(instanceManager)
|
handler := llamactl.NewHandler(instanceManager)
|
||||||
@@ -30,6 +30,6 @@ func main() {
|
|||||||
r := llamactl.SetupRouter(handler)
|
r := llamactl.SetupRouter(handler)
|
||||||
|
|
||||||
// Start the server with the router
|
// Start the server with the router
|
||||||
fmt.Println("Starting llamactl on port 8080...")
|
fmt.Printf("Starting llamactl on port %d...\n", config.Server.Port)
|
||||||
http.ListenAndServe(":8080", r)
|
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
|
// InstancesConfig contains instance management configuration
|
||||||
type InstancesConfig struct {
|
type InstancesConfig struct {
|
||||||
// Port range for instances (e.g., "8000-9000")
|
// Port range for instances (e.g., 8000,9000)
|
||||||
PortRange string `yaml:"port_range"`
|
PortRange [2]int `yaml:"port_range"`
|
||||||
|
|
||||||
// Directory where instance logs will be stored
|
// Directory where instance logs will be stored
|
||||||
LogDirectory string `yaml:"log_directory"`
|
LogDirectory string `yaml:"log_directory"`
|
||||||
@@ -62,7 +62,7 @@ func LoadConfig(configPath string) (Config, error) {
|
|||||||
Port: 8080,
|
Port: 8080,
|
||||||
},
|
},
|
||||||
Instances: InstancesConfig{
|
Instances: InstancesConfig{
|
||||||
PortRange: "8000-9000",
|
PortRange: [2]int{8000, 9000},
|
||||||
LogDirectory: "/tmp/llamactl",
|
LogDirectory: "/tmp/llamactl",
|
||||||
MaxInstances: 10,
|
MaxInstances: 10,
|
||||||
LlamaExecutable: "llama-server",
|
LlamaExecutable: "llama-server",
|
||||||
@@ -121,7 +121,9 @@ func loadEnvVars(cfg *Config) {
|
|||||||
|
|
||||||
// Instance config
|
// Instance config
|
||||||
if portRange := os.Getenv("LLAMACTL_INSTANCE_PORT_RANGE"); portRange != "" {
|
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 != "" {
|
if logDir := os.Getenv("LLAMACTL_LOG_DIR"); logDir != "" {
|
||||||
cfg.Instances.LogDirectory = logDir
|
cfg.Instances.LogDirectory = logDir
|
||||||
@@ -168,6 +170,29 @@ func parseDelaySeconds(s string) (time.Duration, error) {
|
|||||||
return time.Duration(seconds * float64(time.Second)), nil
|
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
|
// getDefaultConfigLocations returns platform-specific config file locations
|
||||||
func getDefaultConfigLocations() []string {
|
func getDefaultConfigLocations() []string {
|
||||||
var locations []string
|
var locations []string
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ func (h *Handler) CreateInstance() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var options InstanceOptions
|
var options CreateInstanceRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -199,7 +199,7 @@ func (h *Handler) UpdateInstance() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var options InstanceOptions
|
var options CreateInstanceRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -18,9 +18,43 @@ import (
|
|||||||
"time"
|
"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 {
|
type Instance struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
options *InstanceOptions `json:"-"` // Now unexported - access via GetOptions/SetOptions
|
options *CreateInstanceRequest `json:"-"` // Now unexported - access via GetOptions/SetOptions
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
Running bool `json:"running"`
|
Running bool `json:"running"`
|
||||||
@@ -40,12 +74,15 @@ type Instance struct {
|
|||||||
proxy *httputil.ReverseProxy `json:"-"` // Reverse proxy for this instance
|
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{
|
return &Instance{
|
||||||
Name: name,
|
Name: name,
|
||||||
options: options,
|
options: options,
|
||||||
|
|
||||||
Running: false,
|
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()
|
i.mu.Lock()
|
||||||
defer i.mu.Unlock()
|
defer i.mu.Unlock()
|
||||||
return i.options
|
return i.options
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) SetOptions(options *InstanceOptions) {
|
func (i *Instance) SetOptions(options *CreateInstanceRequest) {
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
defer i.mu.Unlock()
|
defer i.mu.Unlock()
|
||||||
if options == nil {
|
if options == nil {
|
||||||
@@ -317,9 +354,9 @@ func (i *Instance) MarshalJSON() ([]byte, error) {
|
|||||||
|
|
||||||
// Create a temporary struct with exported fields for JSON marshalling
|
// Create a temporary struct with exported fields for JSON marshalling
|
||||||
temp := struct {
|
temp := struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Options *InstanceOptions `json:"options,omitempty"`
|
Options *CreateInstanceRequest `json:"options,omitempty"`
|
||||||
Running bool `json:"running"`
|
Running bool `json:"running"`
|
||||||
}{
|
}{
|
||||||
Name: i.Name,
|
Name: i.Name,
|
||||||
Options: i.options,
|
Options: i.options,
|
||||||
@@ -333,9 +370,9 @@ func (i *Instance) MarshalJSON() ([]byte, error) {
|
|||||||
func (i *Instance) UnmarshalJSON(data []byte) error {
|
func (i *Instance) UnmarshalJSON(data []byte) error {
|
||||||
// Create a temporary struct for unmarshalling
|
// Create a temporary struct for unmarshalling
|
||||||
temp := struct {
|
temp := struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Options *InstanceOptions `json:"options,omitempty"`
|
Options *CreateInstanceRequest `json:"options,omitempty"`
|
||||||
Running bool `json:"running"`
|
Running bool `json:"running"`
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &temp); err != nil {
|
if err := json.Unmarshal(data, &temp); err != nil {
|
||||||
|
|||||||
@@ -5,42 +5,8 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"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 {
|
type LlamaServerOptions struct {
|
||||||
// Common params
|
// Common params
|
||||||
VerbosePrompt bool `json:"verbose_prompt,omitempty"`
|
VerbosePrompt bool `json:"verbose_prompt,omitempty"`
|
||||||
@@ -396,8 +362,3 @@ func (o *LlamaServerOptions) BuildCommandArgs() []string {
|
|||||||
|
|
||||||
return args
|
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.
|
// InstanceManager defines the interface for managing instances of the llama server.
|
||||||
type InstanceManager interface {
|
type InstanceManager interface {
|
||||||
ListInstances() ([]*Instance, error)
|
ListInstances() ([]*Instance, error)
|
||||||
CreateInstance(name string, options *InstanceOptions) (*Instance, error)
|
CreateInstance(name string, options *CreateInstanceRequest) (*Instance, error)
|
||||||
GetInstance(name string) (*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
|
DeleteInstance(name string) error
|
||||||
StartInstance(name string) (*Instance, error)
|
StartInstance(name string) (*Instance, error)
|
||||||
StopInstance(name string) (*Instance, error)
|
StopInstance(name string) (*Instance, error)
|
||||||
@@ -18,17 +18,17 @@ type InstanceManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type instanceManager struct {
|
type instanceManager struct {
|
||||||
instances map[string]*Instance
|
instances map[string]*Instance
|
||||||
portRange [][2]int // Range of ports to use for instances
|
ports map[int]bool
|
||||||
ports map[int]bool
|
instancesConfig InstancesConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInstanceManager creates a new instance of InstanceManager.
|
// NewInstanceManager creates a new instance of InstanceManager.
|
||||||
func NewInstanceManager() InstanceManager {
|
func NewInstanceManager(instancesConfig InstancesConfig) InstanceManager {
|
||||||
return &instanceManager{
|
return &instanceManager{
|
||||||
instances: make(map[string]*Instance),
|
instances: make(map[string]*Instance),
|
||||||
portRange: [][2]int{{8000, 9000}},
|
ports: make(map[int]bool),
|
||||||
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.
|
// CreateInstance creates a new instance with the given options and returns it.
|
||||||
// The instance is initially in a "stopped" state.
|
// 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 {
|
if options == nil {
|
||||||
return nil, fmt.Errorf("instance options cannot be 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
|
options.Port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
instance := NewInstance(name, options)
|
instance := NewInstance(name, im.instancesConfig.LogDirectory, options)
|
||||||
im.instances[instance.Name] = instance
|
im.instances[instance.Name] = instance
|
||||||
|
|
||||||
return instance, nil
|
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.
|
// 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]
|
instance, exists := im.instances[name]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("instance with name %s not found", name)
|
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) {
|
func (im *instanceManager) getNextAvailablePort() (int, error) {
|
||||||
for _, portRange := range im.portRange {
|
portRange := im.instancesConfig.PortRange
|
||||||
for port := portRange[0]; port <= portRange[1]; port++ {
|
|
||||||
if !im.ports[port] {
|
for port := portRange[0]; port <= portRange[1]; port++ {
|
||||||
im.ports[port] = true
|
if !im.ports[port] {
|
||||||
return port, nil
|
im.ports[port] = true
|
||||||
}
|
return port, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, fmt.Errorf("no available ports in the specified range")
|
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
|
// ValidateInstanceOptions performs minimal security validation
|
||||||
func ValidateInstanceOptions(options *InstanceOptions) error {
|
func ValidateInstanceOptions(options *CreateInstanceRequest) error {
|
||||||
if options == nil {
|
if options == nil {
|
||||||
return ValidationError(fmt.Errorf("options cannot be nil"))
|
return ValidationError(fmt.Errorf("options cannot be nil"))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user