detached 411b756

refactor: Improve configuration loading and blacklist management

- Enhance config loading in `init_config.go` with more robust handling of container environments
- Add support for forced blacklist reloading and improved logging
- Implement methods to dynamically add IPs and ranges to blacklist
- Improve CIDR parsing and validation in blacklist configuration
- Optimize config file creation and environment variable handling
commit
411b75655753fcfc0c62512c9fae07340f814a5d
author
bamen · · 2025-03-09 08:21 UTC
parent
922efce
signature
unsigned
2 files · +130 -24
M internal/common/init_config.go +19 -17
@@ -268,8 +268,10 @@ func (config *Config) LoadConfig() (*Config, error) {
268268 configFilePath = filepath.Join(configDir, "config.yml")
269269 }
270270
271+ configExists := true
271272 _, err = os.Stat(configFilePath)
272273 if errors.Is(err, fs.ErrNotExist) {
274+ configExists = false
273275 fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
274276
275277 // Create config dir if it doesn't exist
@@ -314,36 +316,28 @@ func (config *Config) LoadConfig() (*Config, error) {
314316 },
315317 }
316318
317- // If running in a container, skip the prompt and load from env vars
319+ // If running in a container, skip the prompt
318320 if docker.IsRunningInContainer() {
319321 config.ContainerEngine.Podman = false
320- fmt.Println("Running in container, loading configuration from environment variables...")
321- loadConfigFromEnv(config)
322-
323322 // Handle the Podman case if the GORDON_CONTAINER_PODMAN env var is set to true
324323 if os.Getenv("GORDON_CONTAINER_PODMAN") == "true" {
325324 config.ContainerEngine.Podman = true
326325 fmt.Println("Podman container engine detected via environment variable")
327326 }
328327 }
329-
330- err = config.SaveConfig()
331- if err != nil {
332- return nil, fmt.Errorf("error saving new configuration: %w", err)
333- }
334- return config, nil
335- }
336-
337- if err != nil {
328+ } else if err != nil {
338329 return nil, fmt.Errorf("error checking configuration file: %w", err)
339330 }
340331
341- err = readAndUnmarshalConfig(os.DirFS(configDir), "config.yml", config)
342- if err != nil {
343- return nil, err
332+ // If config file exists, read it
333+ if configExists {
334+ err = readAndUnmarshalConfig(os.DirFS(filepath.Dir(configFilePath)), filepath.Base(configFilePath), config)
335+ if err != nil {
336+ return nil, err
337+ }
344338 }
345339
346- // If running in a container, override with env vars
340+ // Only load environment variables once, after either creating a new config or reading an existing one
347341 if docker.IsRunningInContainer() {
348342 fmt.Println("Running in container, overriding configuration with environment variables...")
349343 loadConfigFromEnv(config)
@@ -356,6 +350,14 @@ func (config *Config) LoadConfig() (*Config, error) {
356350 config.Build.RunEnv = "prod"
357351 }
358352
353+ // If we created a new config, save it
354+ if !configExists {
355+ err = config.SaveConfig()
356+ if err != nil {
357+ return nil, fmt.Errorf("error saving new configuration: %w", err)
358+ }
359+ }
360+
359361 // Debug output to verify config was loaded correctly
360362 fmt.Printf("Loaded configuration from %s\n", configFilePath)
361363 fmt.Printf("Container engine config - Docker socket: %s, Podman socket: %s, Using Podman: %t, Network: %s\n",
@@ -268,8 +268,10 @@ func (config *Config) LoadConfig() (*Config, error) {
268 configFilePath = filepath.Join(configDir, "config.yml")268 configFilePath = filepath.Join(configDir, "config.yml")
269 }269 }
270 270
271+ configExists := true
271 _, err = os.Stat(configFilePath)272 _, err = os.Stat(configFilePath)
272 if errors.Is(err, fs.ErrNotExist) {273 if errors.Is(err, fs.ErrNotExist) {
274+ configExists = false
273 fmt.Printf("Config file not found, creating it at %s\n", configFilePath)275 fmt.Printf("Config file not found, creating it at %s\n", configFilePath)
274 276
275 // Create config dir if it doesn't exist277 // Create config dir if it doesn't exist
@@ -314,36 +316,28 @@ func (config *Config) LoadConfig() (*Config, error) {
314 },316 },
315 }317 }
316 318
317- // If running in a container, skip the prompt and load from env vars319+ // If running in a container, skip the prompt
318 if docker.IsRunningInContainer() {320 if docker.IsRunningInContainer() {
319 config.ContainerEngine.Podman = false321 config.ContainerEngine.Podman = false
320- fmt.Println("Running in container, loading configuration from environment variables...")
321- loadConfigFromEnv(config)
322-
323 // Handle the Podman case if the GORDON_CONTAINER_PODMAN env var is set to true322 // Handle the Podman case if the GORDON_CONTAINER_PODMAN env var is set to true
324 if os.Getenv("GORDON_CONTAINER_PODMAN") == "true" {323 if os.Getenv("GORDON_CONTAINER_PODMAN") == "true" {
325 config.ContainerEngine.Podman = true324 config.ContainerEngine.Podman = true
326 fmt.Println("Podman container engine detected via environment variable")325 fmt.Println("Podman container engine detected via environment variable")
327 }326 }
328 }327 }
329-328+ } else if err != nil {
330- err = config.SaveConfig()
331- if err != nil {
332- return nil, fmt.Errorf("error saving new configuration: %w", err)
333- }
334- return config, nil
335- }
336-
337- if err != nil {
338 return nil, fmt.Errorf("error checking configuration file: %w", err)329 return nil, fmt.Errorf("error checking configuration file: %w", err)
339 }330 }
340 331
341- err = readAndUnmarshalConfig(os.DirFS(configDir), "config.yml", config)332+ // If config file exists, read it
342- if err != nil {333+ if configExists {
343- return nil, err334+ err = readAndUnmarshalConfig(os.DirFS(filepath.Dir(configFilePath)), filepath.Base(configFilePath), config)
335+ if err != nil {
336+ return nil, err
337+ }
344 }338 }
345 339
346- // If running in a container, override with env vars340+ // Only load environment variables once, after either creating a new config or reading an existing one
347 if docker.IsRunningInContainer() {341 if docker.IsRunningInContainer() {
348 fmt.Println("Running in container, overriding configuration with environment variables...")342 fmt.Println("Running in container, overriding configuration with environment variables...")
349 loadConfigFromEnv(config)343 loadConfigFromEnv(config)
@@ -356,6 +350,14 @@ func (config *Config) LoadConfig() (*Config, error) {
356 config.Build.RunEnv = "prod"350 config.Build.RunEnv = "prod"
357 }351 }
358 352
353+ // If we created a new config, save it
354+ if !configExists {
355+ err = config.SaveConfig()
356+ if err != nil {
357+ return nil, fmt.Errorf("error saving new configuration: %w", err)
358+ }
359+ }
360+
359 // Debug output to verify config was loaded correctly361 // Debug output to verify config was loaded correctly
360 fmt.Printf("Loaded configuration from %s\n", configFilePath)362 fmt.Printf("Loaded configuration from %s\n", configFilePath)
361 fmt.Printf("Container engine config - Docker socket: %s, Podman socket: %s, Using Podman: %t, Network: %s\n",363 fmt.Printf("Container engine config - Docker socket: %s, Podman socket: %s, Using Podman: %t, Network: %s\n",
M internal/proxy/blacklist.go +111 -7
@@ -1,9 +1,11 @@
11 package proxy
22
33 import (
4+ "fmt"
45 "net"
56 "os"
67 "path/filepath"
8+ "strings"
79 "sync"
810 "time"
911
@@ -71,9 +73,11 @@ func (b *BlacklistConfig) Load() error {
7173 return err
7274 }
7375
74- // Check if file hasn't been modified
75- if fileInfo.ModTime() == b.LastMod {
76- return nil // File hasn't changed
76+ // Check if file hasn't been modified in the last 10 seconds
77+ // This ensures we check more frequently, but not on every single request
78+ forceReload := time.Since(b.LastMod) > 10*time.Second
79+ if fileInfo.ModTime() == b.LastMod && !forceReload {
80+ return nil // File hasn't changed and we're within the refresh window
7781 }
7882
7983 // Read the blacklist file
@@ -91,6 +95,11 @@ func (b *BlacklistConfig) Load() error {
9195 // Parse IP ranges into networks
9296 networks := make([]*net.IPNet, 0, len(tempConfig.Ranges))
9397 for _, cidr := range tempConfig.Ranges {
98+ // Add /32 to plain IPs if not already a CIDR
99+ if !strings.Contains(cidr, "/") {
100+ cidr = cidr + "/32"
101+ }
102+
94103 _, network, err := net.ParseCIDR(cidr)
95104 if err != nil {
96105 log.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err)
@@ -105,10 +114,17 @@ func (b *BlacklistConfig) Load() error {
105114 b.Networks = networks
106115 b.LastMod = fileInfo.ModTime()
107116
108- log.Info("Loaded blacklist",
109- "ips", len(b.IPs),
110- "ranges", len(b.Ranges),
111- "last_modified", b.LastMod.Format(time.RFC3339))
117+ // If the file has actually changed, log about it
118+ if fileInfo.ModTime() != b.LastMod {
119+ log.Info("Loaded blacklist",
120+ "ips", len(b.IPs),
121+ "ranges", len(b.Ranges),
122+ "last_modified", b.LastMod.Format(time.RFC3339))
123+ } else if forceReload {
124+ log.Debug("Reloaded blacklist (forced refresh)",
125+ "ips", len(b.IPs),
126+ "ranges", len(b.Ranges))
127+ }
112128
113129 return nil
114130 }
@@ -146,3 +162,91 @@ func (b *BlacklistConfig) IsBlocked(ip string) bool {
146162
147163 return false
148164 }
165+
166+// AddIP adds an IP to the blacklist immediately
167+func (b *BlacklistConfig) AddIP(ip string) error {
168+ b.mu.Lock()
169+ defer b.mu.Unlock()
170+
171+ // Read the current file
172+ data, err := os.ReadFile(b.path)
173+ if err != nil {
174+ return err
175+ }
176+
177+ // Parse the current data
178+ var config BlacklistConfig
179+ if err := yaml.Unmarshal(data, &config); err != nil {
180+ return err
181+ }
182+
183+ // Check if the IP is already in the list
184+ for _, blockedIP := range config.IPs {
185+ if blockedIP == ip {
186+ return nil // Already in the list
187+ }
188+ }
189+
190+ // Add the IP to the list
191+ config.IPs = append(config.IPs, ip)
192+
193+ // Write the updated config back to the file
194+ newData, err := yaml.Marshal(config)
195+ if err != nil {
196+ return err
197+ }
198+
199+ if err := os.WriteFile(b.path, newData, 0644); err != nil {
200+ return err
201+ }
202+
203+ // Force a reload
204+ return b.Load()
205+}
206+
207+// AddRange adds an IP range to the blacklist immediately
208+func (b *BlacklistConfig) AddRange(cidr string) error {
209+ // Make sure the CIDR is valid
210+ _, _, err := net.ParseCIDR(cidr)
211+ if err != nil {
212+ return fmt.Errorf("invalid CIDR: %w", err)
213+ }
214+
215+ b.mu.Lock()
216+ defer b.mu.Unlock()
217+
218+ // Read the current file
219+ data, err := os.ReadFile(b.path)
220+ if err != nil {
221+ return err
222+ }
223+
224+ // Parse the current data
225+ var config BlacklistConfig
226+ if err := yaml.Unmarshal(data, &config); err != nil {
227+ return err
228+ }
229+
230+ // Check if the range is already in the list
231+ for _, blockedRange := range config.Ranges {
232+ if blockedRange == cidr {
233+ return nil // Already in the list
234+ }
235+ }
236+
237+ // Add the range to the list
238+ config.Ranges = append(config.Ranges, cidr)
239+
240+ // Write the updated config back to the file
241+ newData, err := yaml.Marshal(config)
242+ if err != nil {
243+ return err
244+ }
245+
246+ if err := os.WriteFile(b.path, newData, 0644); err != nil {
247+ return err
248+ }
249+
250+ // Force a reload
251+ return b.Load()
252+}
@@ -1,9 +1,11 @@
1 package proxy1 package proxy
2 2
3 import (3 import (
4+ "fmt"
4 "net"5 "net"
5 "os"6 "os"
6 "path/filepath"7 "path/filepath"
8+ "strings"
7 "sync"9 "sync"
8 "time"10 "time"
9 11
@@ -71,9 +73,11 @@ func (b *BlacklistConfig) Load() error {
71 return err73 return err
72 }74 }
73 75
74- // Check if file hasn't been modified76+ // Check if file hasn't been modified in the last 10 seconds
75- if fileInfo.ModTime() == b.LastMod {77+ // This ensures we check more frequently, but not on every single request
76- return nil // File hasn't changed78+ forceReload := time.Since(b.LastMod) > 10*time.Second
79+ if fileInfo.ModTime() == b.LastMod && !forceReload {
80+ return nil // File hasn't changed and we're within the refresh window
77 }81 }
78 82
79 // Read the blacklist file83 // Read the blacklist file
@@ -91,6 +95,11 @@ func (b *BlacklistConfig) Load() error {
91 // Parse IP ranges into networks95 // Parse IP ranges into networks
92 networks := make([]*net.IPNet, 0, len(tempConfig.Ranges))96 networks := make([]*net.IPNet, 0, len(tempConfig.Ranges))
93 for _, cidr := range tempConfig.Ranges {97 for _, cidr := range tempConfig.Ranges {
98+ // Add /32 to plain IPs if not already a CIDR
99+ if !strings.Contains(cidr, "/") {
100+ cidr = cidr + "/32"
101+ }
102+
94 _, network, err := net.ParseCIDR(cidr)103 _, network, err := net.ParseCIDR(cidr)
95 if err != nil {104 if err != nil {
96 log.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err)105 log.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err)
@@ -105,10 +114,17 @@ func (b *BlacklistConfig) Load() error {
105 b.Networks = networks114 b.Networks = networks
106 b.LastMod = fileInfo.ModTime()115 b.LastMod = fileInfo.ModTime()
107 116
108- log.Info("Loaded blacklist",117+ // If the file has actually changed, log about it
109- "ips", len(b.IPs),118+ if fileInfo.ModTime() != b.LastMod {
110- "ranges", len(b.Ranges),119+ log.Info("Loaded blacklist",
111- "last_modified", b.LastMod.Format(time.RFC3339))120+ "ips", len(b.IPs),
121+ "ranges", len(b.Ranges),
122+ "last_modified", b.LastMod.Format(time.RFC3339))
123+ } else if forceReload {
124+ log.Debug("Reloaded blacklist (forced refresh)",
125+ "ips", len(b.IPs),
126+ "ranges", len(b.Ranges))
127+ }
112 128
113 return nil129 return nil
114 }130 }
@@ -146,3 +162,91 @@ func (b *BlacklistConfig) IsBlocked(ip string) bool {
146 162
147 return false163 return false
148 }164 }
165+
166+// AddIP adds an IP to the blacklist immediately
167+func (b *BlacklistConfig) AddIP(ip string) error {
168+ b.mu.Lock()
169+ defer b.mu.Unlock()
170+
171+ // Read the current file
172+ data, err := os.ReadFile(b.path)
173+ if err != nil {
174+ return err
175+ }
176+
177+ // Parse the current data
178+ var config BlacklistConfig
179+ if err := yaml.Unmarshal(data, &config); err != nil {
180+ return err
181+ }
182+
183+ // Check if the IP is already in the list
184+ for _, blockedIP := range config.IPs {
185+ if blockedIP == ip {
186+ return nil // Already in the list
187+ }
188+ }
189+
190+ // Add the IP to the list
191+ config.IPs = append(config.IPs, ip)
192+
193+ // Write the updated config back to the file
194+ newData, err := yaml.Marshal(config)
195+ if err != nil {
196+ return err
197+ }
198+
199+ if err := os.WriteFile(b.path, newData, 0644); err != nil {
200+ return err
201+ }
202+
203+ // Force a reload
204+ return b.Load()
205+}
206+
207+// AddRange adds an IP range to the blacklist immediately
208+func (b *BlacklistConfig) AddRange(cidr string) error {
209+ // Make sure the CIDR is valid
210+ _, _, err := net.ParseCIDR(cidr)
211+ if err != nil {
212+ return fmt.Errorf("invalid CIDR: %w", err)
213+ }
214+
215+ b.mu.Lock()
216+ defer b.mu.Unlock()
217+
218+ // Read the current file
219+ data, err := os.ReadFile(b.path)
220+ if err != nil {
221+ return err
222+ }
223+
224+ // Parse the current data
225+ var config BlacklistConfig
226+ if err := yaml.Unmarshal(data, &config); err != nil {
227+ return err
228+ }
229+
230+ // Check if the range is already in the list
231+ for _, blockedRange := range config.Ranges {
232+ if blockedRange == cidr {
233+ return nil // Already in the list
234+ }
235+ }
236+
237+ // Add the range to the list
238+ config.Ranges = append(config.Ranges, cidr)
239+
240+ // Write the updated config back to the file
241+ newData, err := yaml.Marshal(config)
242+ if err != nil {
243+ return err
244+ }
245+
246+ if err := os.WriteFile(b.path, newData, 0644); err != nil {
247+ return err
248+ }
249+
250+ // Force a reload
251+ return b.Load()
252+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close