| 1 |
package server |
| 2 |
|
| 3 |
import ( |
| 4 |
"fmt" |
| 5 |
"os" |
| 6 |
|
| 7 |
"gopkg.in/yaml.v3" |
| 8 |
) |
| 9 |
|
| 10 |
/* Server configuration loaded from YAML. |
| 11 |
* Supports TLS, Basic Auth via htpasswd, and per-repo access control. */ |
| 12 |
|
| 13 |
// Config is the server configuration |
| 14 |
type Config struct { |
| 15 |
Server ServerConfig `yaml:"server"` |
| 16 |
Auth AuthConfig `yaml:"auth"` |
| 17 |
Storage StorageConfig `yaml:"storage"` |
| 18 |
Logging LoggingConfig `yaml:"logging"` |
| 19 |
Repos []RepoConfig `yaml:"repositories"` |
| 20 |
} |
| 21 |
|
| 22 |
// ServerConfig contains HTTP server settings |
| 23 |
type ServerConfig struct { |
| 24 |
Host string `yaml:"host"` |
| 25 |
Port int `yaml:"port"` |
| 26 |
BaseURL string `yaml:"base_url"` |
| 27 |
TLS TLSConfig `yaml:"tls"` |
| 28 |
} |
| 29 |
|
| 30 |
// TLSConfig contains TLS settings |
| 31 |
type TLSConfig struct { |
| 32 |
Enabled bool `yaml:"enabled"` |
| 33 |
Cert string `yaml:"cert"` |
| 34 |
Key string `yaml:"key"` |
| 35 |
} |
| 36 |
|
| 37 |
// AuthConfig contains authentication settings |
| 38 |
type AuthConfig struct { |
| 39 |
Enabled bool `yaml:"enabled"` |
| 40 |
HtpasswdFile string `yaml:"htpasswd_file"` |
| 41 |
Realm string `yaml:"realm"` |
| 42 |
} |
| 43 |
|
| 44 |
// StorageConfig contains storage settings |
| 45 |
type StorageConfig struct { |
| 46 |
Root string `yaml:"root"` |
| 47 |
MaxRepoSize string `yaml:"max_repo_size"` |
| 48 |
MaxFileSize string `yaml:"max_file_size"` |
| 49 |
} |
| 50 |
|
| 51 |
// LoggingConfig contains logging settings |
| 52 |
type LoggingConfig struct { |
| 53 |
Level string `yaml:"level"` // debug, info, warn, error |
| 54 |
Format string `yaml:"format"` // json, text |
| 55 |
File string `yaml:"file"` // log file path (empty for stdout) |
| 56 |
} |
| 57 |
|
| 58 |
// RepoConfig is per-repository configuration |
| 59 |
type RepoConfig struct { |
| 60 |
Name string `yaml:"name"` |
| 61 |
Path string `yaml:"path"` // relative to storage.root |
| 62 |
Description string `yaml:"description"` // optional description |
| 63 |
Public bool `yaml:"public"` // allow anonymous read |
| 64 |
AllowedUsers []string `yaml:"allowed_users"` // full access |
| 65 |
ReadOnlyUsers []string `yaml:"read_only_users"` // read-only access |
| 66 |
} |
| 67 |
|
| 68 |
// LoadConfig loads configuration from YAML file |
| 69 |
func LoadConfig(path string) (*Config, error) { |
| 70 |
data, err := os.ReadFile(path) |
| 71 |
if err != nil { |
| 72 |
return nil, fmt.Errorf("read config: %w", err) |
| 73 |
} |
| 74 |
|
| 75 |
cfg := &Config{} |
| 76 |
if err := yaml.Unmarshal(data, cfg); err != nil { |
| 77 |
return nil, fmt.Errorf("parse config: %w", err) |
| 78 |
} |
| 79 |
|
| 80 |
/* apply defaults */ |
| 81 |
if cfg.Server.Host == "" { |
| 82 |
cfg.Server.Host = "0.0.0.0" |
| 83 |
} |
| 84 |
if cfg.Server.Port == 0 { |
| 85 |
cfg.Server.Port = 8080 |
| 86 |
} |
| 87 |
if cfg.Auth.Realm == "" { |
| 88 |
cfg.Auth.Realm = "Larc Repository" |
| 89 |
} |
| 90 |
if cfg.Logging.Level == "" { |
| 91 |
cfg.Logging.Level = "info" |
| 92 |
} |
| 93 |
if cfg.Logging.Format == "" { |
| 94 |
cfg.Logging.Format = "json" |
| 95 |
} |
| 96 |
|
| 97 |
return cfg, nil |
| 98 |
} |
| 99 |
|
| 100 |
// DefaultConfig returns default configuration |
| 101 |
func DefaultConfig() *Config { |
| 102 |
return &Config{ |
| 103 |
Server: ServerConfig{ |
| 104 |
Host: "0.0.0.0", |
| 105 |
Port: 8080, |
| 106 |
}, |
| 107 |
Auth: AuthConfig{ |
| 108 |
Enabled: false, |
| 109 |
Realm: "Larc Repository", |
| 110 |
}, |
| 111 |
Storage: StorageConfig{ |
| 112 |
Root: "./repos", |
| 113 |
MaxRepoSize: "10GB", |
| 114 |
MaxFileSize: "100MB", |
| 115 |
}, |
| 116 |
Logging: LoggingConfig{ |
| 117 |
Level: "info", |
| 118 |
Format: "json", |
| 119 |
}, |
| 120 |
} |
| 121 |
} |
| 122 |
|
| 123 |
// Validate validates the configuration |
| 124 |
func (c *Config) Validate() error { |
| 125 |
if c.Auth.Enabled && c.Auth.HtpasswdFile == "" { |
| 126 |
return fmt.Errorf("auth enabled but htpasswd_file not set") |
| 127 |
} |
| 128 |
|
| 129 |
if c.Server.TLS.Enabled { |
| 130 |
if c.Server.TLS.Cert == "" || c.Server.TLS.Key == "" { |
| 131 |
return fmt.Errorf("TLS enabled but cert or key not set") |
| 132 |
} |
| 133 |
} |
| 134 |
|
| 135 |
return nil |
| 136 |
} |
| 137 |
|
| 138 |
// GetRepoConfig returns config for a specific repository |
| 139 |
func (c *Config) GetRepoConfig(name string) *RepoConfig { |
| 140 |
for i := range c.Repos { |
| 141 |
if c.Repos[i].Name == name { |
| 142 |
return &c.Repos[i] |
| 143 |
} |
| 144 |
} |
| 145 |
return nil |
| 146 |
} |
| 147 |
|
| 148 |
// IsUserAllowed checks if user has access to repository |
| 149 |
func (r *RepoConfig) IsUserAllowed(username string, writeAccess bool) bool { |
| 150 |
/* public repos allow anonymous read */ |
| 151 |
if r.Public && !writeAccess && username == "" { |
| 152 |
return true |
| 153 |
} |
| 154 |
|
| 155 |
/* check allowed users (full access) */ |
| 156 |
for _, u := range r.AllowedUsers { |
| 157 |
if u == username { |
| 158 |
return true |
| 159 |
} |
| 160 |
} |
| 161 |
|
| 162 |
/* check read-only users */ |
| 163 |
if !writeAccess { |
| 164 |
for _, u := range r.ReadOnlyUsers { |
| 165 |
if u == username { |
| 166 |
return true |
| 167 |
} |
| 168 |
} |
| 169 |
} |
| 170 |
|
| 171 |
return false |
| 172 |
} |
| 173 |
|