1package config
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/spf13/viper"
8)
9
10type Config struct {
11 Server ServerConfig `mapstructure:"server"`
12 RegistryAuth RegistryAuthConfig `mapstructure:"registry_auth"`
13 Routes map[string]string `mapstructure:"routes"`
14}
15
16type ServerConfig struct {
17 Port int `mapstructure:"port"`
18 RegistryPort int `mapstructure:"registry_port"`
19 RegistryDomain string `mapstructure:"registry_domain"`
20 Runtime string `mapstructure:"runtime"`
21 SocketPath string `mapstructure:"socket_path"`
22 SSLEmail string `mapstructure:"ssl_email"`
23 DataDir string `mapstructure:"data_dir"`
24}
25
26type RegistryAuthConfig struct {
27 Enabled bool `mapstructure:"enabled"`
28 Username string `mapstructure:"username"`
29 Password string `mapstructure:"password"`
30}
31
32type Route struct {
33 Domain string
34 Image string
35 HTTPS bool
36}
37
38func Load() (*Config, error) {
39 var cfg Config
40
41 // Set defaults
42 viper.SetDefault("server.port", 8080)
43 viper.SetDefault("server.registry_port", 5000)
44 viper.SetDefault("server.runtime", "auto")
45 viper.SetDefault("server.socket_path", "")
46 viper.SetDefault("server.data_dir", "./data")
47 viper.SetDefault("registry_auth.enabled", true)
48
49 // Handle the routes manually since Viper struggles with domain names
50 cfg.Routes = make(map[string]string)
51
52 // Get server config first
53 if err := viper.UnmarshalKey("server", &cfg.Server); err != nil {
54 return nil, fmt.Errorf("unable to decode server config: %v", err)
55 }
56
57 // Get registry auth config
58 if err := viper.UnmarshalKey("registry_auth", &cfg.RegistryAuth); err != nil {
59 return nil, fmt.Errorf("unable to decode registry auth config: %v", err)
60 }
61
62 // Get routes manually from the raw config
63 routesRaw := viper.Get("routes")
64 if routesRaw != nil {
65 if routes, ok := routesRaw.(map[string]interface{}); ok {
66 for domain, image := range routes {
67 if imageStr, ok := image.(string); ok {
68 cfg.Routes[domain] = imageStr
69 }
70 }
71 }
72 }
73
74 // Validate required fields
75 if cfg.Server.SSLEmail == "" {
76 return nil, fmt.Errorf("server.ssl_email is required")
77 }
78
79 validRuntimes := []string{"auto", "docker", "podman", "podman-rootless"}
80 isValid := false
81 for _, valid := range validRuntimes {
82 if cfg.Server.Runtime == valid {
83 isValid = true
84 break
85 }
86 }
87 if !isValid {
88 return nil, fmt.Errorf("server.runtime must be one of: %s", strings.Join(validRuntimes, ", "))
89 }
90
91 // Validate registry auth config
92 if cfg.RegistryAuth.Enabled {
93 if cfg.RegistryAuth.Username == "" || cfg.RegistryAuth.Password == "" {
94 return nil, fmt.Errorf("Registry auth enabled but username/password not provided")
95 }
96 }
97
98 // Validate registry domain if provided
99 if cfg.Server.RegistryDomain != "" {
100 // Basic domain validation - should not contain protocol or paths
101 if strings.Contains(cfg.Server.RegistryDomain, "://") || strings.Contains(cfg.Server.RegistryDomain, "/") {
102 return nil, fmt.Errorf("registry_domain should be just the domain name (e.g. 'registry.example.com')")
103 }
104 }
105
106 return &cfg, nil
107}
108
109func (c *Config) GetRoutes() []Route {
110 var routes []Route
111
112 for domain, image := range c.Routes {
113 route := Route{
114 Image: image,
115 HTTPS: true, // Default to HTTPS
116 }
117
118 // Check if domain has http:// prefix
119 if strings.HasPrefix(domain, "http://") {
120 route.Domain = strings.TrimPrefix(domain, "http://")
121 route.HTTPS = false
122 } else {
123 route.Domain = domain
124 }
125
126 routes = append(routes, route)
127 }
128
129 return routes
130}