diff --git a/cmd/server/main.go b/cmd/server/main.go index b0bc1ab..4cd0d19 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -23,11 +23,16 @@ func main() { fmt.Println("Using default configuration.") } - // Create the log directory if it doesn't exist - err = os.MkdirAll(config.Instances.LogDirectory, 0755) - if err != nil { + // Create the data directory if it doesn't exist + if config.Data.AutoCreate { + if err := os.MkdirAll(config.Data.Directory, 0755); err != nil { + fmt.Printf("Error creating data directory: %v\n", err) + fmt.Println("Persisting data will not be possible.") + } + } + if err := os.MkdirAll(config.Instances.LogDirectory, 0755); err != nil { fmt.Printf("Error creating log directory: %v\n", err) - return + fmt.Println("Persisting instance logs will not be possible.") } // Initialize the instance manager diff --git a/pkg/config.go b/pkg/config.go index d5b4571..4e94927 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -15,6 +15,7 @@ type Config struct { Server ServerConfig `yaml:"server"` Instances InstancesConfig `yaml:"instances"` Auth AuthConfig `yaml:"auth"` + Data DataConfig `yaml:"data"` } // ServerConfig contains HTTP server configuration @@ -32,6 +33,15 @@ type ServerConfig struct { EnableSwagger bool `yaml:"enable_swagger"` } +// DataConfig contains data storage configuration +type DataConfig struct { + // Directory where all llamactl data will be stored (instances.json, logs, etc.) + Directory string `yaml:"directory"` + + // Automatically create the data directory if it doesn't exist + AutoCreate bool `yaml:"auto_create"` +} + // InstancesConfig contains instance management configuration type InstancesConfig struct { // Port range for instances (e.g., 8000,9000) @@ -85,9 +95,13 @@ func LoadConfig(configPath string) (Config, error) { AllowedOrigins: []string{"*"}, // Default to allow all origins EnableSwagger: false, }, + Data: DataConfig{ + Directory: getDefaultDataDirectory(), + AutoCreate: true, + }, Instances: InstancesConfig{ PortRange: [2]int{8000, 9000}, - LogDirectory: "/tmp/llamactl", + LogDirectory: filepath.Join(getDefaultDataDirectory(), "logs"), MaxInstances: -1, // -1 means unlimited LlamaExecutable: "llama-server", DefaultAutoRestart: true, @@ -157,6 +171,16 @@ func loadEnvVars(cfg *Config) { } } + // Data config + if dataDir := os.Getenv("LLAMACTL_DATA_DIRECTORY"); dataDir != "" { + cfg.Data.Directory = dataDir + } + if autoCreate := os.Getenv("LLAMACTL_AUTO_CREATE_DATA_DIR"); autoCreate != "" { + if b, err := strconv.ParseBool(autoCreate); err == nil { + cfg.Data.AutoCreate = b + } + } + // Instance config if portRange := os.Getenv("LLAMACTL_INSTANCE_PORT_RANGE"); portRange != "" { if ports := ParsePortRange(portRange); ports != [2]int{0, 0} { @@ -231,6 +255,45 @@ func ParsePortRange(s string) [2]int { return [2]int{0, 0} // Invalid format } +// getDefaultDataDirectory returns platform-specific default data directory +func getDefaultDataDirectory() string { + switch runtime.GOOS { + case "windows": + // Try PROGRAMDATA first (system-wide), fallback to LOCALAPPDATA (user) + if programData := os.Getenv("PROGRAMDATA"); programData != "" { + return filepath.Join(programData, "llamactl") + } + if localAppData := os.Getenv("LOCALAPPDATA"); localAppData != "" { + return filepath.Join(localAppData, "llamactl") + } + return "C:\\ProgramData\\llamactl" // Final fallback + + case "darwin": + // For macOS, use user's Application Support directory + // System-wide would be /usr/local/var/llamactl but requires sudo + homeDir, _ := os.UserHomeDir() + if homeDir != "" { + return filepath.Join(homeDir, "Library", "Application Support", "llamactl") + } + return "/usr/local/var/llamactl" // Fallback + + default: + // Linux and other Unix-like systems + // Try system directory first, fallback to user directory + if os.Geteuid() == 0 { // Running as root + return "/var/lib/llamactl" + } + // For non-root users, use XDG data home + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return filepath.Join(xdgDataHome, "llamactl") + } + if homeDir, _ := os.UserHomeDir(); homeDir != "" { + return filepath.Join(homeDir, ".local", "share", "llamactl") + } + return "/var/lib/llamactl" // Final fallback + } +} + // getDefaultConfigLocations returns platform-specific config file locations func getDefaultConfigLocations() []string { var locations []string diff --git a/pkg/config_test.go b/pkg/config_test.go index a6c7b5b..c5de26e 100644 --- a/pkg/config_test.go +++ b/pkg/config_test.go @@ -22,6 +22,12 @@ func TestLoadConfig_Defaults(t *testing.T) { if cfg.Server.Port != 8080 { t.Errorf("Expected default port to be 8080, got %d", cfg.Server.Port) } + if cfg.Data.Directory != "/var/lib/llamactl" { + t.Errorf("Expected default data directory '/var/lib/llamactl', got %q", cfg.Data.Directory) + } + if !cfg.Data.AutoCreate { + t.Error("Expected default data auto-create to be true") + } if cfg.Instances.PortRange != [2]int{8000, 9000} { t.Errorf("Expected default port range [8000, 9000], got %v", cfg.Instances.PortRange) }