refactor: Improve domain and logging handling in proxy routing
- Normalize domain handling by removing leading dots and converting to lowercase - Update domain parsing in container route discovery to support service subdomains - Replace Charmbracelet logger with centralized custom logger package - Enhance domain and IP detection logic in proxy routing - Improve logging consistency and debug information for container and domain routing - Add more robust domain and IP normalization across proxy components
- commit
- 532f1f1a7707f7b955577631e9074ca54ede059e
- author
- bamen · · 2025-03-11 14:27 UTC
- parent
- b4314ac
- signature
- unsigned
12 files · +730 -920
M internal/httpserve/handlers/endpoint_helpers.go +4 -0
| @@ -253,6 +253,10 @@ func normalizePort(port string) string { | |||
| 253 | 253 | ||
| 254 | 254 | // normalizeTargetDomain ensures that the target domain is in the format of "https://domain" or "http://domain" | |
| 255 | 255 | func normalizeTargetDomain(domain string) string { | |
| 256 | + | // First, remove any leading dot from the domain | |
| 257 | + | domain = strings.TrimPrefix(domain, ".") | |
| 258 | + | ||
| 259 | + | // Then check if we need to add the protocol | |
| 256 | 260 | if !regexp.MustCompile(`^https?:\/\/`).MatchString(domain) { | |
| 257 | 261 | return "https://" + domain | |
| 258 | 262 | } | |
| @@ -253,6 +253,10 @@ func normalizePort(port string) string { | |||||
| 253 | 253 | ||||
| 254 | // normalizeTargetDomain ensures that the target domain is in the format of "https://domain" or "http://domain" | 254 | // normalizeTargetDomain ensures that the target domain is in the format of "https://domain" or "http://domain" | ||
| 255 | func normalizeTargetDomain(domain string) string { | 255 | func normalizeTargetDomain(domain string) string { | ||
| 256 | + | // First, remove any leading dot from the domain | |||
| 257 | + | domain = strings.TrimPrefix(domain, ".") | |||
| 258 | + | ||||
| 259 | + | // Then check if we need to add the protocol | |||
| 256 | if !regexp.MustCompile(`^https?:\/\/`).MatchString(domain) { | 260 | if !regexp.MustCompile(`^https?:\/\/`).MatchString(domain) { | ||
| 257 | return "https://" + domain | 261 | return "https://" + domain | ||
| 258 | } | 262 | } | ||
M internal/proxy/blacklist.go +16 -16
| @@ -9,7 +9,7 @@ import ( | |||
| 9 | 9 | "sync" | |
| 10 | 10 | "time" | |
| 11 | 11 | ||
| 12 | - | "github.com/charmbracelet/log" | |
| 12 | + | "github.com/bnema/gordon/pkg/logger" | |
| 13 | 13 | "gopkg.in/yaml.v3" | |
| 14 | 14 | ) | |
| 15 | 15 | ||
| @@ -64,7 +64,7 @@ func NewBlacklist(path string) (*BlacklistConfig, error) { | |||
| 64 | 64 | return nil, fmt.Errorf("failed to create blacklist file: %w", err) | |
| 65 | 65 | } | |
| 66 | 66 | ||
| 67 | - | log.Info("Created new blacklist file", "path", path) | |
| 67 | + | logger.Info("Created new blacklist file", "path", path) | |
| 68 | 68 | } | |
| 69 | 69 | ||
| 70 | 70 | // Load the blacklist | |
| @@ -72,7 +72,7 @@ func NewBlacklist(path string) (*BlacklistConfig, error) { | |||
| 72 | 72 | return nil, fmt.Errorf("failed to load blacklist: %w", err) | |
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | - | log.Info("Blacklist loaded", | |
| 75 | + | logger.Info("Blacklist loaded", | |
| 76 | 76 | "ips", len(blacklist.IPs), | |
| 77 | 77 | "ranges", len(blacklist.Ranges)) | |
| 78 | 78 | ||
| @@ -119,7 +119,7 @@ func (b *BlacklistConfig) Load() error { | |||
| 119 | 119 | ||
| 120 | 120 | _, network, err := net.ParseCIDR(cidr) | |
| 121 | 121 | if err != nil { | |
| 122 | - | log.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err) | |
| 122 | + | logger.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err) | |
| 123 | 123 | continue | |
| 124 | 124 | } | |
| 125 | 125 | networks = append(networks, network) | |
| @@ -136,12 +136,12 @@ func (b *BlacklistConfig) Load() error { | |||
| 136 | 136 | ||
| 137 | 137 | // If the file has actually changed, log about it | |
| 138 | 138 | if fileInfo.ModTime() != b.LastMod { | |
| 139 | - | log.Info("Loaded blacklist", | |
| 139 | + | logger.Info("Loaded blacklist", | |
| 140 | 140 | "ips", len(b.IPs), | |
| 141 | 141 | "ranges", len(b.Ranges), | |
| 142 | 142 | "last_modified", b.LastMod.Format(time.RFC3339)) | |
| 143 | 143 | } else if forceReload { | |
| 144 | - | log.Debug("Reloaded blacklist (forced refresh)", | |
| 144 | + | logger.Debug("Reloaded blacklist (forced refresh)", | |
| 145 | 145 | "ips", len(b.IPs), | |
| 146 | 146 | "ranges", len(b.Ranges)) | |
| 147 | 147 | } | |
| @@ -162,14 +162,14 @@ func (b *BlacklistConfig) rebuildBlockedCache() { | |||
| 162 | 162 | // Replace the cache atomically | |
| 163 | 163 | b.blockedCache = newCache | |
| 164 | 164 | ||
| 165 | - | log.Debug("Rebuilt blocked IP cache", "direct_ips", len(b.blockedCache)) | |
| 165 | + | logger.Debug("Rebuilt blocked IP cache", "direct_ips", len(b.blockedCache)) | |
| 166 | 166 | } | |
| 167 | 167 | ||
| 168 | 168 | // IsBlocked checks if an IP is in the blacklist | |
| 169 | 169 | func (b *BlacklistConfig) IsBlocked(ip string) bool { | |
| 170 | 170 | // Try to reload the blacklist first | |
| 171 | 171 | if err := b.Load(); err != nil { | |
| 172 | - | log.Error("Failed to reload blacklist", "error", err) | |
| 172 | + | logger.Error("Failed to reload blacklist", "error", err) | |
| 173 | 173 | } | |
| 174 | 174 | ||
| 175 | 175 | b.mu.RLock() | |
| @@ -183,7 +183,7 @@ func (b *BlacklistConfig) IsBlocked(ip string) bool { | |||
| 183 | 183 | // Parse the IP | |
| 184 | 184 | parsedIP := net.ParseIP(ip) | |
| 185 | 185 | if parsedIP == nil { | |
| 186 | - | log.Warn("Failed to parse IP", "ip", ip) | |
| 186 | + | logger.Warn("Failed to parse IP", "ip", ip) | |
| 187 | 187 | return false | |
| 188 | 188 | } | |
| 189 | 189 | ||
| @@ -239,7 +239,7 @@ func (b *BlacklistConfig) AddIP(ip string) error { | |||
| 239 | 239 | ||
| 240 | 240 | // Add to in-memory cache immediately | |
| 241 | 241 | b.blockedCache[ip] = true | |
| 242 | - | log.Info("Added IP to blacklist", "ip", ip) | |
| 242 | + | logger.Info("Added IP to blacklist", "ip", ip) | |
| 243 | 243 | ||
| 244 | 244 | // Force a reload | |
| 245 | 245 | return b.Load() | |
| @@ -292,7 +292,7 @@ func (b *BlacklistConfig) AddRange(cidr string) error { | |||
| 292 | 292 | return err | |
| 293 | 293 | } | |
| 294 | 294 | ||
| 295 | - | log.Info("Added CIDR range to blacklist", "cidr", cidr) | |
| 295 | + | logger.Info("Added CIDR range to blacklist", "cidr", cidr) | |
| 296 | 296 | ||
| 297 | 297 | // Force a reload to update Networks and the cache | |
| 298 | 298 | return b.Load() | |
| @@ -345,7 +345,7 @@ func (b *BlacklistConfig) RemoveIP(ip string) error { | |||
| 345 | 345 | ||
| 346 | 346 | // Remove from cache | |
| 347 | 347 | delete(b.blockedCache, ip) | |
| 348 | - | log.Info("Removed IP from blacklist", "ip", ip) | |
| 348 | + | logger.Info("Removed IP from blacklist", "ip", ip) | |
| 349 | 349 | ||
| 350 | 350 | // Force a reload | |
| 351 | 351 | return b.Load() | |
| @@ -401,7 +401,7 @@ func (b *BlacklistConfig) RemoveRange(cidr string) error { | |||
| 401 | 401 | return err | |
| 402 | 402 | } | |
| 403 | 403 | ||
| 404 | - | log.Info("Removed CIDR range from blacklist", "cidr", cidr) | |
| 404 | + | logger.Info("Removed CIDR range from blacklist", "cidr", cidr) | |
| 405 | 405 | ||
| 406 | 406 | // Force a reload to rebuild Networks and the cache | |
| 407 | 407 | return b.Load() | |
| @@ -421,7 +421,7 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||
| 421 | 421 | p.blockedIPCounter[clientIP] == 0 { | |
| 422 | 422 | ||
| 423 | 423 | // Log this block and update counters | |
| 424 | - | log.Info("Blocked request from blacklisted IP", | |
| 424 | + | logger.Info("Blocked request from blacklisted IP", | |
| 425 | 425 | "ip", clientIP, | |
| 426 | 426 | "path", path, | |
| 427 | 427 | "user_agent", userAgent) | |
| @@ -447,7 +447,7 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||
| 447 | 447 | totalBlocked += count | |
| 448 | 448 | } | |
| 449 | 449 | ||
| 450 | - | log.Info("Blocked IP summary", | |
| 450 | + | logger.Info("Blocked IP summary", | |
| 451 | 451 | "unique_ips", len(p.blockedIPCounter), | |
| 452 | 452 | "total_requests", totalBlocked, | |
| 453 | 453 | "since", p.lastBlockedLog.Format(time.RFC3339)) | |
| @@ -459,6 +459,6 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||
| 459 | 459 | ||
| 460 | 460 | // Debug logging only if needed | |
| 461 | 461 | if false { | |
| 462 | - | log.Debug("Request marked as blacklisted for logging skip", "ip", clientIP) | |
| 462 | + | logger.Debug("Request marked as blacklisted for logging skip", "ip", clientIP) | |
| 463 | 463 | } | |
| 464 | 464 | } | |
| @@ -9,7 +9,7 @@ import ( | |||||
| 9 | "sync" | 9 | "sync" | ||
| 10 | "time" | 10 | "time" | ||
| 11 | 11 | ||||
| 12 | - | "github.com/charmbracelet/log" | 12 | + | "github.com/bnema/gordon/pkg/logger" |
| 13 | "gopkg.in/yaml.v3" | 13 | "gopkg.in/yaml.v3" | ||
| 14 | ) | 14 | ) | ||
| 15 | 15 | ||||
| @@ -64,7 +64,7 @@ func NewBlacklist(path string) (*BlacklistConfig, error) { | |||||
| 64 | return nil, fmt.Errorf("failed to create blacklist file: %w", err) | 64 | return nil, fmt.Errorf("failed to create blacklist file: %w", err) | ||
| 65 | } | 65 | } | ||
| 66 | 66 | ||||
| 67 | - | log.Info("Created new blacklist file", "path", path) | 67 | + | logger.Info("Created new blacklist file", "path", path) |
| 68 | } | 68 | } | ||
| 69 | 69 | ||||
| 70 | // Load the blacklist | 70 | // Load the blacklist | ||
| @@ -72,7 +72,7 @@ func NewBlacklist(path string) (*BlacklistConfig, error) { | |||||
| 72 | return nil, fmt.Errorf("failed to load blacklist: %w", err) | 72 | return nil, fmt.Errorf("failed to load blacklist: %w", err) | ||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | - | log.Info("Blacklist loaded", | 75 | + | logger.Info("Blacklist loaded", |
| 76 | "ips", len(blacklist.IPs), | 76 | "ips", len(blacklist.IPs), | ||
| 77 | "ranges", len(blacklist.Ranges)) | 77 | "ranges", len(blacklist.Ranges)) | ||
| 78 | 78 | ||||
| @@ -119,7 +119,7 @@ func (b *BlacklistConfig) Load() error { | |||||
| 119 | 119 | ||||
| 120 | _, network, err := net.ParseCIDR(cidr) | 120 | _, network, err := net.ParseCIDR(cidr) | ||
| 121 | if err != nil { | 121 | if err != nil { | ||
| 122 | - | log.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err) | 122 | + | logger.Warn("Invalid CIDR in blacklist", "cidr", cidr, "error", err) |
| 123 | continue | 123 | continue | ||
| 124 | } | 124 | } | ||
| 125 | networks = append(networks, network) | 125 | networks = append(networks, network) | ||
| @@ -136,12 +136,12 @@ func (b *BlacklistConfig) Load() error { | |||||
| 136 | 136 | ||||
| 137 | // If the file has actually changed, log about it | 137 | // If the file has actually changed, log about it | ||
| 138 | if fileInfo.ModTime() != b.LastMod { | 138 | if fileInfo.ModTime() != b.LastMod { | ||
| 139 | - | log.Info("Loaded blacklist", | 139 | + | logger.Info("Loaded blacklist", |
| 140 | "ips", len(b.IPs), | 140 | "ips", len(b.IPs), | ||
| 141 | "ranges", len(b.Ranges), | 141 | "ranges", len(b.Ranges), | ||
| 142 | "last_modified", b.LastMod.Format(time.RFC3339)) | 142 | "last_modified", b.LastMod.Format(time.RFC3339)) | ||
| 143 | } else if forceReload { | 143 | } else if forceReload { | ||
| 144 | - | log.Debug("Reloaded blacklist (forced refresh)", | 144 | + | logger.Debug("Reloaded blacklist (forced refresh)", |
| 145 | "ips", len(b.IPs), | 145 | "ips", len(b.IPs), | ||
| 146 | "ranges", len(b.Ranges)) | 146 | "ranges", len(b.Ranges)) | ||
| 147 | } | 147 | } | ||
| @@ -162,14 +162,14 @@ func (b *BlacklistConfig) rebuildBlockedCache() { | |||||
| 162 | // Replace the cache atomically | 162 | // Replace the cache atomically | ||
| 163 | b.blockedCache = newCache | 163 | b.blockedCache = newCache | ||
| 164 | 164 | ||||
| 165 | - | log.Debug("Rebuilt blocked IP cache", "direct_ips", len(b.blockedCache)) | 165 | + | logger.Debug("Rebuilt blocked IP cache", "direct_ips", len(b.blockedCache)) |
| 166 | } | 166 | } | ||
| 167 | 167 | ||||
| 168 | // IsBlocked checks if an IP is in the blacklist | 168 | // IsBlocked checks if an IP is in the blacklist | ||
| 169 | func (b *BlacklistConfig) IsBlocked(ip string) bool { | 169 | func (b *BlacklistConfig) IsBlocked(ip string) bool { | ||
| 170 | // Try to reload the blacklist first | 170 | // Try to reload the blacklist first | ||
| 171 | if err := b.Load(); err != nil { | 171 | if err := b.Load(); err != nil { | ||
| 172 | - | log.Error("Failed to reload blacklist", "error", err) | 172 | + | logger.Error("Failed to reload blacklist", "error", err) |
| 173 | } | 173 | } | ||
| 174 | 174 | ||||
| 175 | b.mu.RLock() | 175 | b.mu.RLock() | ||
| @@ -183,7 +183,7 @@ func (b *BlacklistConfig) IsBlocked(ip string) bool { | |||||
| 183 | // Parse the IP | 183 | // Parse the IP | ||
| 184 | parsedIP := net.ParseIP(ip) | 184 | parsedIP := net.ParseIP(ip) | ||
| 185 | if parsedIP == nil { | 185 | if parsedIP == nil { | ||
| 186 | - | log.Warn("Failed to parse IP", "ip", ip) | 186 | + | logger.Warn("Failed to parse IP", "ip", ip) |
| 187 | return false | 187 | return false | ||
| 188 | } | 188 | } | ||
| 189 | 189 | ||||
| @@ -239,7 +239,7 @@ func (b *BlacklistConfig) AddIP(ip string) error { | |||||
| 239 | 239 | ||||
| 240 | // Add to in-memory cache immediately | 240 | // Add to in-memory cache immediately | ||
| 241 | b.blockedCache[ip] = true | 241 | b.blockedCache[ip] = true | ||
| 242 | - | log.Info("Added IP to blacklist", "ip", ip) | 242 | + | logger.Info("Added IP to blacklist", "ip", ip) |
| 243 | 243 | ||||
| 244 | // Force a reload | 244 | // Force a reload | ||
| 245 | return b.Load() | 245 | return b.Load() | ||
| @@ -292,7 +292,7 @@ func (b *BlacklistConfig) AddRange(cidr string) error { | |||||
| 292 | return err | 292 | return err | ||
| 293 | } | 293 | } | ||
| 294 | 294 | ||||
| 295 | - | log.Info("Added CIDR range to blacklist", "cidr", cidr) | 295 | + | logger.Info("Added CIDR range to blacklist", "cidr", cidr) |
| 296 | 296 | ||||
| 297 | // Force a reload to update Networks and the cache | 297 | // Force a reload to update Networks and the cache | ||
| 298 | return b.Load() | 298 | return b.Load() | ||
| @@ -345,7 +345,7 @@ func (b *BlacklistConfig) RemoveIP(ip string) error { | |||||
| 345 | 345 | ||||
| 346 | // Remove from cache | 346 | // Remove from cache | ||
| 347 | delete(b.blockedCache, ip) | 347 | delete(b.blockedCache, ip) | ||
| 348 | - | log.Info("Removed IP from blacklist", "ip", ip) | 348 | + | logger.Info("Removed IP from blacklist", "ip", ip) |
| 349 | 349 | ||||
| 350 | // Force a reload | 350 | // Force a reload | ||
| 351 | return b.Load() | 351 | return b.Load() | ||
| @@ -401,7 +401,7 @@ func (b *BlacklistConfig) RemoveRange(cidr string) error { | |||||
| 401 | return err | 401 | return err | ||
| 402 | } | 402 | } | ||
| 403 | 403 | ||||
| 404 | - | log.Info("Removed CIDR range from blacklist", "cidr", cidr) | 404 | + | logger.Info("Removed CIDR range from blacklist", "cidr", cidr) |
| 405 | 405 | ||||
| 406 | // Force a reload to rebuild Networks and the cache | 406 | // Force a reload to rebuild Networks and the cache | ||
| 407 | return b.Load() | 407 | return b.Load() | ||
| @@ -421,7 +421,7 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||||
| 421 | p.blockedIPCounter[clientIP] == 0 { | 421 | p.blockedIPCounter[clientIP] == 0 { | ||
| 422 | 422 | ||||
| 423 | // Log this block and update counters | 423 | // Log this block and update counters | ||
| 424 | - | log.Info("Blocked request from blacklisted IP", | 424 | + | logger.Info("Blocked request from blacklisted IP", |
| 425 | "ip", clientIP, | 425 | "ip", clientIP, | ||
| 426 | "path", path, | 426 | "path", path, | ||
| 427 | "user_agent", userAgent) | 427 | "user_agent", userAgent) | ||
| @@ -447,7 +447,7 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||||
| 447 | totalBlocked += count | 447 | totalBlocked += count | ||
| 448 | } | 448 | } | ||
| 449 | 449 | ||||
| 450 | - | log.Info("Blocked IP summary", | 450 | + | logger.Info("Blocked IP summary", |
| 451 | "unique_ips", len(p.blockedIPCounter), | 451 | "unique_ips", len(p.blockedIPCounter), | ||
| 452 | "total_requests", totalBlocked, | 452 | "total_requests", totalBlocked, | ||
| 453 | "since", p.lastBlockedLog.Format(time.RFC3339)) | 453 | "since", p.lastBlockedLog.Format(time.RFC3339)) | ||
| @@ -459,6 +459,6 @@ func (p *Proxy) logBlockedIP(clientIP, path, userAgent string) { | |||||
| 459 | 459 | ||||
| 460 | // Debug logging only if needed | 460 | // Debug logging only if needed | ||
| 461 | if false { | 461 | if false { | ||
| 462 | - | log.Debug("Request marked as blacklisted for logging skip", "ip", clientIP) | 462 | + | logger.Debug("Request marked as blacklisted for logging skip", "ip", clientIP) |
| 463 | } | 463 | } | ||
| 464 | } | 464 | } | ||
M internal/proxy/certificates.go +51 -51
| @@ -18,7 +18,7 @@ import ( | |||
| 18 | 18 | "strings" | |
| 19 | 19 | "time" | |
| 20 | 20 | ||
| 21 | - | "github.com/charmbracelet/log" | |
| 21 | + | "github.com/bnema/gordon/pkg/logger" | |
| 22 | 22 | "golang.org/x/crypto/acme" | |
| 23 | 23 | "golang.org/x/crypto/acme/autocert" | |
| 24 | 24 | ) | |
| @@ -33,7 +33,7 @@ func (p *Proxy) setupCertManager() { | |||
| 33 | 33 | ||
| 34 | 34 | // Ensure directory exists | |
| 35 | 35 | if err := os.MkdirAll(dir, 0755); err != nil { | |
| 36 | - | log.Warn("Failed to create certificate cache directory", | |
| 36 | + | logger.Warn("Failed to create certificate cache directory", | |
| 37 | 37 | "dir", dir, | |
| 38 | 38 | "error", err) | |
| 39 | 39 | } | |
| @@ -54,14 +54,14 @@ func (p *Proxy) setupCertManager() { | |||
| 54 | 54 | certManager.Client = &acme.Client{ | |
| 55 | 55 | DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", | |
| 56 | 56 | } | |
| 57 | - | log.Debug("Using Let's Encrypt staging environment", | |
| 57 | + | logger.Debug("Using Let's Encrypt staging environment", | |
| 58 | 58 | "url", "https://acme-staging-v02.api.letsencrypt.org/directory") | |
| 59 | 59 | } else { | |
| 60 | 60 | // Explicitly set production URL when not in staging mode | |
| 61 | 61 | certManager.Client = &acme.Client{ | |
| 62 | 62 | DirectoryURL: acme.LetsEncryptURL, // "https://acme-v02.api.letsencrypt.org/directory" | |
| 63 | 63 | } | |
| 64 | - | log.Debug("Using Let's Encrypt production environment", | |
| 64 | + | logger.Debug("Using Let's Encrypt production environment", | |
| 65 | 65 | "url", acme.LetsEncryptURL) | |
| 66 | 66 | } | |
| 67 | 67 | ||
| @@ -87,7 +87,7 @@ func (p *Proxy) setupCertManager() { | |||
| 87 | 87 | ||
| 88 | 88 | // Log the attempt but still allow it - this helps debug | |
| 89 | 89 | // certificate acquisition issues during development | |
| 90 | - | log.Warn("Unknown host in certificate request", | |
| 90 | + | logger.Warn("Unknown host in certificate request", | |
| 91 | 91 | "host", host, | |
| 92 | 92 | "adminDomain", adminDomain, | |
| 93 | 93 | "allowed", "yes") | |
| @@ -108,9 +108,9 @@ func (p *Proxy) setupCertManager() { | |||
| 108 | 108 | ||
| 109 | 109 | fallbackCert, err := generateFallbackCertificates(fallbackDomains) | |
| 110 | 110 | if err != nil { | |
| 111 | - | log.Warn("Failed to generate fallback certificate", "error", err) | |
| 111 | + | logger.Warn("Failed to generate fallback certificate", "error", err) | |
| 112 | 112 | } else { | |
| 113 | - | log.Info("Generated fallback self-signed certificate for admin domain", | |
| 113 | + | logger.Info("Generated fallback self-signed certificate for admin domain", | |
| 114 | 114 | "domain", adminDomain, | |
| 115 | 115 | "valid_until", time.Now().Add(24*time.Hour).Format("2006-01-02 15:04:05")) | |
| 116 | 116 | // Store the fallback certificate | |
| @@ -118,7 +118,7 @@ func (p *Proxy) setupCertManager() { | |||
| 118 | 118 | } | |
| 119 | 119 | ||
| 120 | 120 | p.certManager = certManager | |
| 121 | - | log.Debug("Certificate manager setup completed", | |
| 121 | + | logger.Debug("Certificate manager setup completed", | |
| 122 | 122 | "directory", dir, | |
| 123 | 123 | "mode", p.config.LetsEncryptMode) | |
| 124 | 124 | ||
| @@ -147,7 +147,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||
| 147 | 147 | certData, err := p.certManager.Cache.Get(ctx, cacheKey) | |
| 148 | 148 | if err != nil { | |
| 149 | 149 | // ErrCacheMiss or other error means the certificate is not in the cache | |
| 150 | - | log.Debug("Certificate not found in cache", | |
| 150 | + | logger.Debug("Certificate not found in cache", | |
| 151 | 151 | "domain", domain, | |
| 152 | 152 | "error", err) | |
| 153 | 153 | return false | |
| @@ -156,14 +156,14 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||
| 156 | 156 | // Parse the certificate to check its validity | |
| 157 | 157 | block, _ := pem.Decode(certData) | |
| 158 | 158 | if block == nil || block.Type != "CERTIFICATE" { | |
| 159 | - | log.Warn("Invalid certificate data in cache", | |
| 159 | + | logger.Warn("Invalid certificate data in cache", | |
| 160 | 160 | "domain", domain) | |
| 161 | 161 | return false | |
| 162 | 162 | } | |
| 163 | 163 | ||
| 164 | 164 | cert, err := x509.ParseCertificate(block.Bytes) | |
| 165 | 165 | if err != nil { | |
| 166 | - | log.Warn("Failed to parse certificate from cache", | |
| 166 | + | logger.Warn("Failed to parse certificate from cache", | |
| 167 | 167 | "domain", domain, | |
| 168 | 168 | "error", err) | |
| 169 | 169 | return false | |
| @@ -172,7 +172,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||
| 172 | 172 | // Check if the certificate is still valid | |
| 173 | 173 | now := time.Now() | |
| 174 | 174 | if now.After(cert.NotAfter) || now.Before(cert.NotBefore) { | |
| 175 | - | log.Info("Certificate in cache has expired or is not yet valid", | |
| 175 | + | logger.Info("Certificate in cache has expired or is not yet valid", | |
| 176 | 176 | "domain", domain, | |
| 177 | 177 | "not_before", cert.NotBefore, | |
| 178 | 178 | "not_after", cert.NotAfter) | |
| @@ -181,7 +181,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||
| 181 | 181 | ||
| 182 | 182 | // Check if the certificate is about to expire (within 30 days) | |
| 183 | 183 | if now.Add(30 * 24 * time.Hour).After(cert.NotAfter) { | |
| 184 | - | log.Info("Certificate in cache is valid but will expire soon", | |
| 184 | + | logger.Info("Certificate in cache is valid but will expire soon", | |
| 185 | 185 | "domain", domain, | |
| 186 | 186 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | |
| 187 | 187 | "days") | |
| @@ -189,7 +189,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||
| 189 | 189 | return false | |
| 190 | 190 | } | |
| 191 | 191 | ||
| 192 | - | log.Info("Valid certificate found in cache", | |
| 192 | + | logger.Info("Valid certificate found in cache", | |
| 193 | 193 | "domain", domain, | |
| 194 | 194 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | |
| 195 | 195 | "days") | |
| @@ -206,7 +206,7 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 206 | 206 | ||
| 207 | 207 | // Only proceed if HTTPS is enabled | |
| 208 | 208 | if !p.app.GetConfig().Http.Https { | |
| 209 | - | log.Debug("HTTPS is disabled, skipping admin certificate request") | |
| 209 | + | logger.Debug("HTTPS is disabled, skipping admin certificate request") | |
| 210 | 210 | return | |
| 211 | 211 | } | |
| 212 | 212 | ||
| @@ -214,7 +214,7 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 214 | 214 | host := adminDomain | |
| 215 | 215 | ips, err := net.LookupIP(host) | |
| 216 | 216 | if err != nil { | |
| 217 | - | log.Error("Could not resolve admin domain, Let's Encrypt will likely fail", | |
| 217 | + | logger.Error("Could not resolve admin domain, Let's Encrypt will likely fail", | |
| 218 | 218 | "domain", adminDomain, | |
| 219 | 219 | "error", err.Error(), | |
| 220 | 220 | "solution", "Check DNS settings and ensure domain points to this server") | |
| @@ -226,13 +226,13 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 226 | 226 | ipStrings = append(ipStrings, ip.String()) | |
| 227 | 227 | } | |
| 228 | 228 | ||
| 229 | - | log.Info("Successfully resolved admin domain", | |
| 229 | + | logger.Info("Successfully resolved admin domain", | |
| 230 | 230 | "domain", adminDomain, | |
| 231 | 231 | "ips", ipStrings) | |
| 232 | 232 | ||
| 233 | 233 | // Check if we already have a valid certificate in the cache | |
| 234 | 234 | if p.checkCertificateInCache(adminDomain) { | |
| 235 | - | log.Info("Using existing certificate from cache", | |
| 235 | + | logger.Info("Using existing certificate from cache", | |
| 236 | 236 | "domain", adminDomain, | |
| 237 | 237 | "action", "skipping Let's Encrypt request to avoid rate limits") | |
| 238 | 238 | return | |
| @@ -247,12 +247,12 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 247 | 247 | } | |
| 248 | 248 | ||
| 249 | 249 | // Log the certificate request intent | |
| 250 | - | log.Info("Initiating Let's Encrypt certificate request for admin domain", | |
| 250 | + | logger.Info("Initiating Let's Encrypt certificate request for admin domain", | |
| 251 | 251 | "domain", adminDomain, | |
| 252 | 252 | "email", email, | |
| 253 | 253 | "mode", mode) | |
| 254 | 254 | ||
| 255 | - | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |
| 255 | + | logger.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |
| 256 | 256 | "domain", adminDomain, | |
| 257 | 257 | "validation_method", "HTTP-01 challenge", | |
| 258 | 258 | "requirements", "Domain must be publicly accessible on port 80", | |
| @@ -289,7 +289,7 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 289 | 289 | certErr = result.err | |
| 290 | 290 | case <-ctx.Done(): | |
| 291 | 291 | certErr = fmt.Errorf("certificate request timed out after 2 minutes: %w", ctx.Err()) | |
| 292 | - | log.Error("Let's Encrypt certificate request timed out", | |
| 292 | + | logger.Error("Let's Encrypt certificate request timed out", | |
| 293 | 293 | "domain", adminDomain, | |
| 294 | 294 | "timeout", "2 minutes", | |
| 295 | 295 | "error", certErr) | |
| @@ -306,31 +306,31 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 306 | 306 | retryAfterStr = matches[1] | |
| 307 | 307 | } | |
| 308 | 308 | ||
| 309 | - | log.Error("Let's Encrypt rate limit reached", | |
| 309 | + | logger.Error("Let's Encrypt rate limit reached", | |
| 310 | 310 | "domain", adminDomain, | |
| 311 | 311 | "error", certErr, | |
| 312 | 312 | "retry_after", retryAfterStr, | |
| 313 | 313 | "solution", "Wait until the rate limit expires, then restart the server") | |
| 314 | 314 | ||
| 315 | 315 | // Don't retry on rate limits | |
| 316 | - | log.Info("Skipping certificate request retries due to rate limiting", | |
| 316 | + | logger.Info("Skipping certificate request retries due to rate limiting", | |
| 317 | 317 | "domain", adminDomain) | |
| 318 | 318 | } else { | |
| 319 | - | log.Error("Failed to obtain Let's Encrypt certificate", | |
| 319 | + | logger.Error("Failed to obtain Let's Encrypt certificate", | |
| 320 | 320 | "domain", adminDomain, | |
| 321 | 321 | "error", certErr) | |
| 322 | 322 | ||
| 323 | - | log.Info("⏳ Retrying certificate request with exponential backoff", | |
| 323 | + | logger.Info("⏳ Retrying certificate request with exponential backoff", | |
| 324 | 324 | "domain", adminDomain, | |
| 325 | 325 | "timeout", "2 minutes") | |
| 326 | 326 | // Implement retry logic with backoff | |
| 327 | 327 | go p.retryCertificateRequest(adminDomain, 3, 10*time.Second) | |
| 328 | 328 | } | |
| 329 | 329 | } else if certResult != nil { | |
| 330 | - | log.Info("Successfully obtained Let's Encrypt certificate", | |
| 330 | + | logger.Info("Successfully obtained Let's Encrypt certificate", | |
| 331 | 331 | "domain", adminDomain) | |
| 332 | 332 | } else { | |
| 333 | - | log.Error("Unexpected error: received nil certificate but no error", | |
| 333 | + | logger.Error("Unexpected error: received nil certificate but no error", | |
| 334 | 334 | "domain", adminDomain, | |
| 335 | 335 | "timeout", "2 minutes") | |
| 336 | 336 | // Implement retry logic with backoff | |
| @@ -342,14 +342,14 @@ func (p *Proxy) requestAdminCertificate() { | |||
| 342 | 342 | // for a container domain | |
| 343 | 343 | func (p *Proxy) requestDomainCertificate(domain string) { | |
| 344 | 344 | if domain == "" { | |
| 345 | - | log.Warn("Empty domain provided to requestDomainCertificate") | |
| 345 | + | logger.Warn("Empty domain provided to requestDomainCertificate") | |
| 346 | 346 | return | |
| 347 | 347 | } | |
| 348 | 348 | ||
| 349 | 349 | // Extract hostname from domain, resolving it to see if it's publicly accessible | |
| 350 | 350 | ips, err := net.LookupIP(domain) | |
| 351 | 351 | if err != nil { | |
| 352 | - | log.Error("Could not resolve domain, Let's Encrypt will likely fail", | |
| 352 | + | logger.Error("Could not resolve domain, Let's Encrypt will likely fail", | |
| 353 | 353 | "domain", domain, | |
| 354 | 354 | "error", err.Error(), | |
| 355 | 355 | "solution", "Check DNS settings and ensure domain points to this server") | |
| @@ -361,13 +361,13 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||
| 361 | 361 | ipStrings = append(ipStrings, ip.String()) | |
| 362 | 362 | } | |
| 363 | 363 | ||
| 364 | - | log.Info("Successfully resolved domain for certificate request", | |
| 364 | + | logger.Info("Successfully resolved domain for certificate request", | |
| 365 | 365 | "domain", domain, | |
| 366 | 366 | "ips", ipStrings) | |
| 367 | 367 | ||
| 368 | 368 | // Check if we already have a valid certificate in the cache | |
| 369 | 369 | if p.checkCertificateInCache(domain) { | |
| 370 | - | log.Info("Using existing certificate from cache", | |
| 370 | + | logger.Info("Using existing certificate from cache", | |
| 371 | 371 | "domain", domain, | |
| 372 | 372 | "action", "skipping Let's Encrypt request to avoid rate limits") | |
| 373 | 373 | return | |
| @@ -382,12 +382,12 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||
| 382 | 382 | } | |
| 383 | 383 | ||
| 384 | 384 | // Log the certificate request intent | |
| 385 | - | log.Info("Initiating Let's Encrypt certificate request for container domain", | |
| 385 | + | logger.Info("Initiating Let's Encrypt certificate request for container domain", | |
| 386 | 386 | "domain", domain, | |
| 387 | 387 | "email", email, | |
| 388 | 388 | "mode", mode) | |
| 389 | 389 | ||
| 390 | - | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |
| 390 | + | logger.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | |
| 391 | 391 | "domain", domain, | |
| 392 | 392 | "validation_method", "HTTP-01 challenge", | |
| 393 | 393 | "requirements", "Domain must be publicly accessible on port 80", | |
| @@ -424,7 +424,7 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||
| 424 | 424 | certErr = result.err | |
| 425 | 425 | case <-ctx.Done(): | |
| 426 | 426 | certErr = fmt.Errorf("certificate request timed out after 1 minute: %w", ctx.Err()) | |
| 427 | - | log.Error("Let's Encrypt certificate request timed out", | |
| 427 | + | logger.Error("Let's Encrypt certificate request timed out", | |
| 428 | 428 | "domain", domain, | |
| 429 | 429 | "timeout", "1 minute", | |
| 430 | 430 | "error", certErr) | |
| @@ -441,31 +441,31 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||
| 441 | 441 | retryAfterStr = matches[1] | |
| 442 | 442 | } | |
| 443 | 443 | ||
| 444 | - | log.Error("Let's Encrypt rate limit reached", | |
| 444 | + | logger.Error("Let's Encrypt rate limit reached", | |
| 445 | 445 | "domain", domain, | |
| 446 | 446 | "error", certErr, | |
| 447 | 447 | "retry_after", retryAfterStr, | |
| 448 | 448 | "solution", "Wait until the rate limit expires, then restart the server") | |
| 449 | 449 | ||
| 450 | 450 | // Don't retry on rate limits | |
| 451 | - | log.Info("Skipping certificate request retries due to rate limiting", | |
| 451 | + | logger.Info("Skipping certificate request retries due to rate limiting", | |
| 452 | 452 | "domain", domain) | |
| 453 | 453 | } else { | |
| 454 | - | log.Error("Failed to obtain Let's Encrypt certificate", | |
| 454 | + | logger.Error("Failed to obtain Let's Encrypt certificate", | |
| 455 | 455 | "domain", domain, | |
| 456 | 456 | "error", certErr) | |
| 457 | 457 | ||
| 458 | - | log.Info("⏳ Retrying certificate request with exponential backoff", | |
| 458 | + | logger.Info("⏳ Retrying certificate request with exponential backoff", | |
| 459 | 459 | "domain", domain, | |
| 460 | 460 | "timeout", "2 minutes") | |
| 461 | 461 | // Implement retry logic with backoff | |
| 462 | 462 | go p.retryCertificateRequest(domain, 3, 10*time.Second) | |
| 463 | 463 | } | |
| 464 | 464 | } else if certResult != nil { | |
| 465 | - | log.Info("Successfully obtained Let's Encrypt certificate", | |
| 465 | + | logger.Info("Successfully obtained Let's Encrypt certificate", | |
| 466 | 466 | "domain", domain) | |
| 467 | 467 | } else { | |
| 468 | - | log.Error("Unexpected error: received nil certificate but no error", | |
| 468 | + | logger.Error("Unexpected error: received nil certificate but no error", | |
| 469 | 469 | "domain", domain, | |
| 470 | 470 | "timeout", "1 minute") | |
| 471 | 471 | // Implement retry logic with backoff | |
| @@ -479,7 +479,7 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 479 | 479 | ||
| 480 | 480 | for attempt := 1; attempt <= maxRetries; attempt++ { | |
| 481 | 481 | // Wait for backoff period | |
| 482 | - | log.Info("Retrying Let's Encrypt certificate request", | |
| 482 | + | logger.Info("Retrying Let's Encrypt certificate request", | |
| 483 | 483 | "domain", domain, | |
| 484 | 484 | "attempt", attempt, | |
| 485 | 485 | "max_retries", maxRetries, | |
| @@ -503,14 +503,14 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 503 | 503 | resp, err := client.Get(testURL) | |
| 504 | 504 | ||
| 505 | 505 | if err != nil { | |
| 506 | - | log.Warn("HTTP challenge endpoint might not be accessible", | |
| 506 | + | logger.Warn("HTTP challenge endpoint might not be accessible", | |
| 507 | 507 | "domain", domain, | |
| 508 | 508 | "url", testURL, | |
| 509 | 509 | "error", err, | |
| 510 | 510 | "solution", "Ensure port 80 is accessible and not blocked by firewall") | |
| 511 | 511 | } else { | |
| 512 | 512 | resp.Body.Close() | |
| 513 | - | log.Info("HTTP challenge endpoint is accessible", | |
| 513 | + | logger.Info("HTTP challenge endpoint is accessible", | |
| 514 | 514 | "domain", domain, | |
| 515 | 515 | "url", testURL, | |
| 516 | 516 | "status", resp.StatusCode) | |
| @@ -534,13 +534,13 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 534 | 534 | ||
| 535 | 535 | // Check result | |
| 536 | 536 | if err == nil { | |
| 537 | - | log.Info("Successfully obtained Let's Encrypt certificate on retry", | |
| 537 | + | logger.Info("Successfully obtained Let's Encrypt certificate on retry", | |
| 538 | 538 | "domain", domain, | |
| 539 | 539 | "attempt", attempt) | |
| 540 | 540 | return | |
| 541 | 541 | } | |
| 542 | 542 | ||
| 543 | - | log.Error("Let's Encrypt certificate request retry failed", | |
| 543 | + | logger.Error("Let's Encrypt certificate request retry failed", | |
| 544 | 544 | "domain", domain, | |
| 545 | 545 | "attempt", attempt, | |
| 546 | 546 | "error", err) | |
| @@ -548,13 +548,13 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 548 | 548 | // Provide more detailed diagnostics based on the error | |
| 549 | 549 | if strings.Contains(strings.ToLower(err.Error()), "connection refused") || | |
| 550 | 550 | strings.Contains(strings.ToLower(err.Error()), "timeout") { | |
| 551 | - | log.Error("Let's Encrypt connection failed - this typically indicates:", | |
| 551 | + | logger.Error("Let's Encrypt connection failed - this typically indicates:", | |
| 552 | 552 | "issue_1", "Port 80 is not accessible from the internet", | |
| 553 | 553 | "issue_2", "Firewall is blocking inbound connections", | |
| 554 | 554 | "issue_3", "DNS records not properly propagated", | |
| 555 | 555 | "solution", "Check firewall settings and DNS configuration") | |
| 556 | 556 | } else if strings.Contains(strings.ToLower(err.Error()), "unauthorized") { | |
| 557 | - | log.Error("Let's Encrypt authorization failed - this typically indicates:", | |
| 557 | + | logger.Error("Let's Encrypt authorization failed - this typically indicates:", | |
| 558 | 558 | "issue", "Domain ownership validation failed", | |
| 559 | 559 | "solution", "Ensure the server is publicly accessible on port 80") | |
| 560 | 560 | } else if strings.Contains(strings.ToLower(err.Error()), "ratelimited") { | |
| @@ -570,7 +570,7 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 570 | 570 | } | |
| 571 | 571 | } | |
| 572 | 572 | ||
| 573 | - | log.Error("Let's Encrypt rate limit reached - cannot issue more certificates for this domain yet", | |
| 573 | + | logger.Error("Let's Encrypt rate limit reached - cannot issue more certificates for this domain yet", | |
| 574 | 574 | "domain", domain, | |
| 575 | 575 | "retry_after", retryAfterStr, | |
| 576 | 576 | "solution", "Wait until the rate limit expires, then restart the server") | |
| @@ -583,20 +583,20 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||
| 583 | 583 | backoff *= 2 | |
| 584 | 584 | } | |
| 585 | 585 | ||
| 586 | - | log.Error("All Let's Encrypt certificate request retries failed", | |
| 586 | + | logger.Error("All Let's Encrypt certificate request retries failed", | |
| 587 | 587 | "domain", domain, | |
| 588 | 588 | "max_retries", maxRetries, | |
| 589 | 589 | "fallback", "Using self-signed certificate") | |
| 590 | 590 | ||
| 591 | 591 | // At this point, all retries have failed, so we'll rely on the fallback self-signed certificate | |
| 592 | 592 | // But let's log this prominently for debugging | |
| 593 | - | log.Error("⚠️ HTTPS is using a self-signed certificate which browsers will warn about", | |
| 593 | + | logger.Error("⚠️ HTTPS is using a self-signed certificate which browsers will warn about", | |
| 594 | 594 | "domain", domain, | |
| 595 | 595 | "reason", "Let's Encrypt certificate issuance failed", | |
| 596 | 596 | "solution", "Check network settings and Let's Encrypt status") | |
| 597 | 597 | ||
| 598 | 598 | // Suggest checking the common issues | |
| 599 | - | log.Error("Common Let's Encrypt issues to check:", | |
| 599 | + | logger.Error("Common Let's Encrypt issues to check:", | |
| 600 | 600 | "check_1", "Ensure ports 80 and 443 are open on your firewall", | |
| 601 | 601 | "check_2", "Verify DNS records point to your server IP", | |
| 602 | 602 | "check_3", "Make sure no other services are running on ports 80/443", | |
| @@ -18,7 +18,7 @@ import ( | |||||
| 18 | "strings" | 18 | "strings" | ||
| 19 | "time" | 19 | "time" | ||
| 20 | 20 | ||||
| 21 | - | "github.com/charmbracelet/log" | 21 | + | "github.com/bnema/gordon/pkg/logger" |
| 22 | "golang.org/x/crypto/acme" | 22 | "golang.org/x/crypto/acme" | ||
| 23 | "golang.org/x/crypto/acme/autocert" | 23 | "golang.org/x/crypto/acme/autocert" | ||
| 24 | ) | 24 | ) | ||
| @@ -33,7 +33,7 @@ func (p *Proxy) setupCertManager() { | |||||
| 33 | 33 | ||||
| 34 | // Ensure directory exists | 34 | // Ensure directory exists | ||
| 35 | if err := os.MkdirAll(dir, 0755); err != nil { | 35 | if err := os.MkdirAll(dir, 0755); err != nil { | ||
| 36 | - | log.Warn("Failed to create certificate cache directory", | 36 | + | logger.Warn("Failed to create certificate cache directory", |
| 37 | "dir", dir, | 37 | "dir", dir, | ||
| 38 | "error", err) | 38 | "error", err) | ||
| 39 | } | 39 | } | ||
| @@ -54,14 +54,14 @@ func (p *Proxy) setupCertManager() { | |||||
| 54 | certManager.Client = &acme.Client{ | 54 | certManager.Client = &acme.Client{ | ||
| 55 | DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", | 55 | DirectoryURL: "https://acme-staging-v02.api.letsencrypt.org/directory", | ||
| 56 | } | 56 | } | ||
| 57 | - | log.Debug("Using Let's Encrypt staging environment", | 57 | + | logger.Debug("Using Let's Encrypt staging environment", |
| 58 | "url", "https://acme-staging-v02.api.letsencrypt.org/directory") | 58 | "url", "https://acme-staging-v02.api.letsencrypt.org/directory") | ||
| 59 | } else { | 59 | } else { | ||
| 60 | // Explicitly set production URL when not in staging mode | 60 | // Explicitly set production URL when not in staging mode | ||
| 61 | certManager.Client = &acme.Client{ | 61 | certManager.Client = &acme.Client{ | ||
| 62 | DirectoryURL: acme.LetsEncryptURL, // "https://acme-v02.api.letsencrypt.org/directory" | 62 | DirectoryURL: acme.LetsEncryptURL, // "https://acme-v02.api.letsencrypt.org/directory" | ||
| 63 | } | 63 | } | ||
| 64 | - | log.Debug("Using Let's Encrypt production environment", | 64 | + | logger.Debug("Using Let's Encrypt production environment", |
| 65 | "url", acme.LetsEncryptURL) | 65 | "url", acme.LetsEncryptURL) | ||
| 66 | } | 66 | } | ||
| 67 | 67 | ||||
| @@ -87,7 +87,7 @@ func (p *Proxy) setupCertManager() { | |||||
| 87 | 87 | ||||
| 88 | // Log the attempt but still allow it - this helps debug | 88 | // Log the attempt but still allow it - this helps debug | ||
| 89 | // certificate acquisition issues during development | 89 | // certificate acquisition issues during development | ||
| 90 | - | log.Warn("Unknown host in certificate request", | 90 | + | logger.Warn("Unknown host in certificate request", |
| 91 | "host", host, | 91 | "host", host, | ||
| 92 | "adminDomain", adminDomain, | 92 | "adminDomain", adminDomain, | ||
| 93 | "allowed", "yes") | 93 | "allowed", "yes") | ||
| @@ -108,9 +108,9 @@ func (p *Proxy) setupCertManager() { | |||||
| 108 | 108 | ||||
| 109 | fallbackCert, err := generateFallbackCertificates(fallbackDomains) | 109 | fallbackCert, err := generateFallbackCertificates(fallbackDomains) | ||
| 110 | if err != nil { | 110 | if err != nil { | ||
| 111 | - | log.Warn("Failed to generate fallback certificate", "error", err) | 111 | + | logger.Warn("Failed to generate fallback certificate", "error", err) |
| 112 | } else { | 112 | } else { | ||
| 113 | - | log.Info("Generated fallback self-signed certificate for admin domain", | 113 | + | logger.Info("Generated fallback self-signed certificate for admin domain", |
| 114 | "domain", adminDomain, | 114 | "domain", adminDomain, | ||
| 115 | "valid_until", time.Now().Add(24*time.Hour).Format("2006-01-02 15:04:05")) | 115 | "valid_until", time.Now().Add(24*time.Hour).Format("2006-01-02 15:04:05")) | ||
| 116 | // Store the fallback certificate | 116 | // Store the fallback certificate | ||
| @@ -118,7 +118,7 @@ func (p *Proxy) setupCertManager() { | |||||
| 118 | } | 118 | } | ||
| 119 | 119 | ||||
| 120 | p.certManager = certManager | 120 | p.certManager = certManager | ||
| 121 | - | log.Debug("Certificate manager setup completed", | 121 | + | logger.Debug("Certificate manager setup completed", |
| 122 | "directory", dir, | 122 | "directory", dir, | ||
| 123 | "mode", p.config.LetsEncryptMode) | 123 | "mode", p.config.LetsEncryptMode) | ||
| 124 | 124 | ||||
| @@ -147,7 +147,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||||
| 147 | certData, err := p.certManager.Cache.Get(ctx, cacheKey) | 147 | certData, err := p.certManager.Cache.Get(ctx, cacheKey) | ||
| 148 | if err != nil { | 148 | if err != nil { | ||
| 149 | // ErrCacheMiss or other error means the certificate is not in the cache | 149 | // ErrCacheMiss or other error means the certificate is not in the cache | ||
| 150 | - | log.Debug("Certificate not found in cache", | 150 | + | logger.Debug("Certificate not found in cache", |
| 151 | "domain", domain, | 151 | "domain", domain, | ||
| 152 | "error", err) | 152 | "error", err) | ||
| 153 | return false | 153 | return false | ||
| @@ -156,14 +156,14 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||||
| 156 | // Parse the certificate to check its validity | 156 | // Parse the certificate to check its validity | ||
| 157 | block, _ := pem.Decode(certData) | 157 | block, _ := pem.Decode(certData) | ||
| 158 | if block == nil || block.Type != "CERTIFICATE" { | 158 | if block == nil || block.Type != "CERTIFICATE" { | ||
| 159 | - | log.Warn("Invalid certificate data in cache", | 159 | + | logger.Warn("Invalid certificate data in cache", |
| 160 | "domain", domain) | 160 | "domain", domain) | ||
| 161 | return false | 161 | return false | ||
| 162 | } | 162 | } | ||
| 163 | 163 | ||||
| 164 | cert, err := x509.ParseCertificate(block.Bytes) | 164 | cert, err := x509.ParseCertificate(block.Bytes) | ||
| 165 | if err != nil { | 165 | if err != nil { | ||
| 166 | - | log.Warn("Failed to parse certificate from cache", | 166 | + | logger.Warn("Failed to parse certificate from cache", |
| 167 | "domain", domain, | 167 | "domain", domain, | ||
| 168 | "error", err) | 168 | "error", err) | ||
| 169 | return false | 169 | return false | ||
| @@ -172,7 +172,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||||
| 172 | // Check if the certificate is still valid | 172 | // Check if the certificate is still valid | ||
| 173 | now := time.Now() | 173 | now := time.Now() | ||
| 174 | if now.After(cert.NotAfter) || now.Before(cert.NotBefore) { | 174 | if now.After(cert.NotAfter) || now.Before(cert.NotBefore) { | ||
| 175 | - | log.Info("Certificate in cache has expired or is not yet valid", | 175 | + | logger.Info("Certificate in cache has expired or is not yet valid", |
| 176 | "domain", domain, | 176 | "domain", domain, | ||
| 177 | "not_before", cert.NotBefore, | 177 | "not_before", cert.NotBefore, | ||
| 178 | "not_after", cert.NotAfter) | 178 | "not_after", cert.NotAfter) | ||
| @@ -181,7 +181,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||||
| 181 | 181 | ||||
| 182 | // Check if the certificate is about to expire (within 30 days) | 182 | // Check if the certificate is about to expire (within 30 days) | ||
| 183 | if now.Add(30 * 24 * time.Hour).After(cert.NotAfter) { | 183 | if now.Add(30 * 24 * time.Hour).After(cert.NotAfter) { | ||
| 184 | - | log.Info("Certificate in cache is valid but will expire soon", | 184 | + | logger.Info("Certificate in cache is valid but will expire soon", |
| 185 | "domain", domain, | 185 | "domain", domain, | ||
| 186 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | 186 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | ||
| 187 | "days") | 187 | "days") | ||
| @@ -189,7 +189,7 @@ func (p *Proxy) checkCertificateInCache(domain string) bool { | |||||
| 189 | return false | 189 | return false | ||
| 190 | } | 190 | } | ||
| 191 | 191 | ||||
| 192 | - | log.Info("Valid certificate found in cache", | 192 | + | logger.Info("Valid certificate found in cache", |
| 193 | "domain", domain, | 193 | "domain", domain, | ||
| 194 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | 194 | "expires_in", cert.NotAfter.Sub(now).Hours()/24, | ||
| 195 | "days") | 195 | "days") | ||
| @@ -206,7 +206,7 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 206 | 206 | ||||
| 207 | // Only proceed if HTTPS is enabled | 207 | // Only proceed if HTTPS is enabled | ||
| 208 | if !p.app.GetConfig().Http.Https { | 208 | if !p.app.GetConfig().Http.Https { | ||
| 209 | - | log.Debug("HTTPS is disabled, skipping admin certificate request") | 209 | + | logger.Debug("HTTPS is disabled, skipping admin certificate request") |
| 210 | return | 210 | return | ||
| 211 | } | 211 | } | ||
| 212 | 212 | ||||
| @@ -214,7 +214,7 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 214 | host := adminDomain | 214 | host := adminDomain | ||
| 215 | ips, err := net.LookupIP(host) | 215 | ips, err := net.LookupIP(host) | ||
| 216 | if err != nil { | 216 | if err != nil { | ||
| 217 | - | log.Error("Could not resolve admin domain, Let's Encrypt will likely fail", | 217 | + | logger.Error("Could not resolve admin domain, Let's Encrypt will likely fail", |
| 218 | "domain", adminDomain, | 218 | "domain", adminDomain, | ||
| 219 | "error", err.Error(), | 219 | "error", err.Error(), | ||
| 220 | "solution", "Check DNS settings and ensure domain points to this server") | 220 | "solution", "Check DNS settings and ensure domain points to this server") | ||
| @@ -226,13 +226,13 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 226 | ipStrings = append(ipStrings, ip.String()) | 226 | ipStrings = append(ipStrings, ip.String()) | ||
| 227 | } | 227 | } | ||
| 228 | 228 | ||||
| 229 | - | log.Info("Successfully resolved admin domain", | 229 | + | logger.Info("Successfully resolved admin domain", |
| 230 | "domain", adminDomain, | 230 | "domain", adminDomain, | ||
| 231 | "ips", ipStrings) | 231 | "ips", ipStrings) | ||
| 232 | 232 | ||||
| 233 | // Check if we already have a valid certificate in the cache | 233 | // Check if we already have a valid certificate in the cache | ||
| 234 | if p.checkCertificateInCache(adminDomain) { | 234 | if p.checkCertificateInCache(adminDomain) { | ||
| 235 | - | log.Info("Using existing certificate from cache", | 235 | + | logger.Info("Using existing certificate from cache", |
| 236 | "domain", adminDomain, | 236 | "domain", adminDomain, | ||
| 237 | "action", "skipping Let's Encrypt request to avoid rate limits") | 237 | "action", "skipping Let's Encrypt request to avoid rate limits") | ||
| 238 | return | 238 | return | ||
| @@ -247,12 +247,12 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 247 | } | 247 | } | ||
| 248 | 248 | ||||
| 249 | // Log the certificate request intent | 249 | // Log the certificate request intent | ||
| 250 | - | log.Info("Initiating Let's Encrypt certificate request for admin domain", | 250 | + | logger.Info("Initiating Let's Encrypt certificate request for admin domain", |
| 251 | "domain", adminDomain, | 251 | "domain", adminDomain, | ||
| 252 | "email", email, | 252 | "email", email, | ||
| 253 | "mode", mode) | 253 | "mode", mode) | ||
| 254 | 254 | ||||
| 255 | - | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | 255 | + | logger.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", |
| 256 | "domain", adminDomain, | 256 | "domain", adminDomain, | ||
| 257 | "validation_method", "HTTP-01 challenge", | 257 | "validation_method", "HTTP-01 challenge", | ||
| 258 | "requirements", "Domain must be publicly accessible on port 80", | 258 | "requirements", "Domain must be publicly accessible on port 80", | ||
| @@ -289,7 +289,7 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 289 | certErr = result.err | 289 | certErr = result.err | ||
| 290 | case <-ctx.Done(): | 290 | case <-ctx.Done(): | ||
| 291 | certErr = fmt.Errorf("certificate request timed out after 2 minutes: %w", ctx.Err()) | 291 | certErr = fmt.Errorf("certificate request timed out after 2 minutes: %w", ctx.Err()) | ||
| 292 | - | log.Error("Let's Encrypt certificate request timed out", | 292 | + | logger.Error("Let's Encrypt certificate request timed out", |
| 293 | "domain", adminDomain, | 293 | "domain", adminDomain, | ||
| 294 | "timeout", "2 minutes", | 294 | "timeout", "2 minutes", | ||
| 295 | "error", certErr) | 295 | "error", certErr) | ||
| @@ -306,31 +306,31 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 306 | retryAfterStr = matches[1] | 306 | retryAfterStr = matches[1] | ||
| 307 | } | 307 | } | ||
| 308 | 308 | ||||
| 309 | - | log.Error("Let's Encrypt rate limit reached", | 309 | + | logger.Error("Let's Encrypt rate limit reached", |
| 310 | "domain", adminDomain, | 310 | "domain", adminDomain, | ||
| 311 | "error", certErr, | 311 | "error", certErr, | ||
| 312 | "retry_after", retryAfterStr, | 312 | "retry_after", retryAfterStr, | ||
| 313 | "solution", "Wait until the rate limit expires, then restart the server") | 313 | "solution", "Wait until the rate limit expires, then restart the server") | ||
| 314 | 314 | ||||
| 315 | // Don't retry on rate limits | 315 | // Don't retry on rate limits | ||
| 316 | - | log.Info("Skipping certificate request retries due to rate limiting", | 316 | + | logger.Info("Skipping certificate request retries due to rate limiting", |
| 317 | "domain", adminDomain) | 317 | "domain", adminDomain) | ||
| 318 | } else { | 318 | } else { | ||
| 319 | - | log.Error("Failed to obtain Let's Encrypt certificate", | 319 | + | logger.Error("Failed to obtain Let's Encrypt certificate", |
| 320 | "domain", adminDomain, | 320 | "domain", adminDomain, | ||
| 321 | "error", certErr) | 321 | "error", certErr) | ||
| 322 | 322 | ||||
| 323 | - | log.Info("⏳ Retrying certificate request with exponential backoff", | 323 | + | logger.Info("⏳ Retrying certificate request with exponential backoff", |
| 324 | "domain", adminDomain, | 324 | "domain", adminDomain, | ||
| 325 | "timeout", "2 minutes") | 325 | "timeout", "2 minutes") | ||
| 326 | // Implement retry logic with backoff | 326 | // Implement retry logic with backoff | ||
| 327 | go p.retryCertificateRequest(adminDomain, 3, 10*time.Second) | 327 | go p.retryCertificateRequest(adminDomain, 3, 10*time.Second) | ||
| 328 | } | 328 | } | ||
| 329 | } else if certResult != nil { | 329 | } else if certResult != nil { | ||
| 330 | - | log.Info("Successfully obtained Let's Encrypt certificate", | 330 | + | logger.Info("Successfully obtained Let's Encrypt certificate", |
| 331 | "domain", adminDomain) | 331 | "domain", adminDomain) | ||
| 332 | } else { | 332 | } else { | ||
| 333 | - | log.Error("Unexpected error: received nil certificate but no error", | 333 | + | logger.Error("Unexpected error: received nil certificate but no error", |
| 334 | "domain", adminDomain, | 334 | "domain", adminDomain, | ||
| 335 | "timeout", "2 minutes") | 335 | "timeout", "2 minutes") | ||
| 336 | // Implement retry logic with backoff | 336 | // Implement retry logic with backoff | ||
| @@ -342,14 +342,14 @@ func (p *Proxy) requestAdminCertificate() { | |||||
| 342 | // for a container domain | 342 | // for a container domain | ||
| 343 | func (p *Proxy) requestDomainCertificate(domain string) { | 343 | func (p *Proxy) requestDomainCertificate(domain string) { | ||
| 344 | if domain == "" { | 344 | if domain == "" { | ||
| 345 | - | log.Warn("Empty domain provided to requestDomainCertificate") | 345 | + | logger.Warn("Empty domain provided to requestDomainCertificate") |
| 346 | return | 346 | return | ||
| 347 | } | 347 | } | ||
| 348 | 348 | ||||
| 349 | // Extract hostname from domain, resolving it to see if it's publicly accessible | 349 | // Extract hostname from domain, resolving it to see if it's publicly accessible | ||
| 350 | ips, err := net.LookupIP(domain) | 350 | ips, err := net.LookupIP(domain) | ||
| 351 | if err != nil { | 351 | if err != nil { | ||
| 352 | - | log.Error("Could not resolve domain, Let's Encrypt will likely fail", | 352 | + | logger.Error("Could not resolve domain, Let's Encrypt will likely fail", |
| 353 | "domain", domain, | 353 | "domain", domain, | ||
| 354 | "error", err.Error(), | 354 | "error", err.Error(), | ||
| 355 | "solution", "Check DNS settings and ensure domain points to this server") | 355 | "solution", "Check DNS settings and ensure domain points to this server") | ||
| @@ -361,13 +361,13 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||||
| 361 | ipStrings = append(ipStrings, ip.String()) | 361 | ipStrings = append(ipStrings, ip.String()) | ||
| 362 | } | 362 | } | ||
| 363 | 363 | ||||
| 364 | - | log.Info("Successfully resolved domain for certificate request", | 364 | + | logger.Info("Successfully resolved domain for certificate request", |
| 365 | "domain", domain, | 365 | "domain", domain, | ||
| 366 | "ips", ipStrings) | 366 | "ips", ipStrings) | ||
| 367 | 367 | ||||
| 368 | // Check if we already have a valid certificate in the cache | 368 | // Check if we already have a valid certificate in the cache | ||
| 369 | if p.checkCertificateInCache(domain) { | 369 | if p.checkCertificateInCache(domain) { | ||
| 370 | - | log.Info("Using existing certificate from cache", | 370 | + | logger.Info("Using existing certificate from cache", |
| 371 | "domain", domain, | 371 | "domain", domain, | ||
| 372 | "action", "skipping Let's Encrypt request to avoid rate limits") | 372 | "action", "skipping Let's Encrypt request to avoid rate limits") | ||
| 373 | return | 373 | return | ||
| @@ -382,12 +382,12 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||||
| 382 | } | 382 | } | ||
| 383 | 383 | ||||
| 384 | // Log the certificate request intent | 384 | // Log the certificate request intent | ||
| 385 | - | log.Info("Initiating Let's Encrypt certificate request for container domain", | 385 | + | logger.Info("Initiating Let's Encrypt certificate request for container domain", |
| 386 | "domain", domain, | 386 | "domain", domain, | ||
| 387 | "email", email, | 387 | "email", email, | ||
| 388 | "mode", mode) | 388 | "mode", mode) | ||
| 389 | 389 | ||||
| 390 | - | log.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", | 390 | + | logger.Info("⏳ Waiting for Let's Encrypt to validate domain ownership", |
| 391 | "domain", domain, | 391 | "domain", domain, | ||
| 392 | "validation_method", "HTTP-01 challenge", | 392 | "validation_method", "HTTP-01 challenge", | ||
| 393 | "requirements", "Domain must be publicly accessible on port 80", | 393 | "requirements", "Domain must be publicly accessible on port 80", | ||
| @@ -424,7 +424,7 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||||
| 424 | certErr = result.err | 424 | certErr = result.err | ||
| 425 | case <-ctx.Done(): | 425 | case <-ctx.Done(): | ||
| 426 | certErr = fmt.Errorf("certificate request timed out after 1 minute: %w", ctx.Err()) | 426 | certErr = fmt.Errorf("certificate request timed out after 1 minute: %w", ctx.Err()) | ||
| 427 | - | log.Error("Let's Encrypt certificate request timed out", | 427 | + | logger.Error("Let's Encrypt certificate request timed out", |
| 428 | "domain", domain, | 428 | "domain", domain, | ||
| 429 | "timeout", "1 minute", | 429 | "timeout", "1 minute", | ||
| 430 | "error", certErr) | 430 | "error", certErr) | ||
| @@ -441,31 +441,31 @@ func (p *Proxy) requestDomainCertificate(domain string) { | |||||
| 441 | retryAfterStr = matches[1] | 441 | retryAfterStr = matches[1] | ||
| 442 | } | 442 | } | ||
| 443 | 443 | ||||
| 444 | - | log.Error("Let's Encrypt rate limit reached", | 444 | + | logger.Error("Let's Encrypt rate limit reached", |
| 445 | "domain", domain, | 445 | "domain", domain, | ||
| 446 | "error", certErr, | 446 | "error", certErr, | ||
| 447 | "retry_after", retryAfterStr, | 447 | "retry_after", retryAfterStr, | ||
| 448 | "solution", "Wait until the rate limit expires, then restart the server") | 448 | "solution", "Wait until the rate limit expires, then restart the server") | ||
| 449 | 449 | ||||
| 450 | // Don't retry on rate limits | 450 | // Don't retry on rate limits | ||
| 451 | - | log.Info("Skipping certificate request retries due to rate limiting", | 451 | + | logger.Info("Skipping certificate request retries due to rate limiting", |
| 452 | "domain", domain) | 452 | "domain", domain) | ||
| 453 | } else { | 453 | } else { | ||
| 454 | - | log.Error("Failed to obtain Let's Encrypt certificate", | 454 | + | logger.Error("Failed to obtain Let's Encrypt certificate", |
| 455 | "domain", domain, | 455 | "domain", domain, | ||
| 456 | "error", certErr) | 456 | "error", certErr) | ||
| 457 | 457 | ||||
| 458 | - | log.Info("⏳ Retrying certificate request with exponential backoff", | 458 | + | logger.Info("⏳ Retrying certificate request with exponential backoff", |
| 459 | "domain", domain, | 459 | "domain", domain, | ||
| 460 | "timeout", "2 minutes") | 460 | "timeout", "2 minutes") | ||
| 461 | // Implement retry logic with backoff | 461 | // Implement retry logic with backoff | ||
| 462 | go p.retryCertificateRequest(domain, 3, 10*time.Second) | 462 | go p.retryCertificateRequest(domain, 3, 10*time.Second) | ||
| 463 | } | 463 | } | ||
| 464 | } else if certResult != nil { | 464 | } else if certResult != nil { | ||
| 465 | - | log.Info("Successfully obtained Let's Encrypt certificate", | 465 | + | logger.Info("Successfully obtained Let's Encrypt certificate", |
| 466 | "domain", domain) | 466 | "domain", domain) | ||
| 467 | } else { | 467 | } else { | ||
| 468 | - | log.Error("Unexpected error: received nil certificate but no error", | 468 | + | logger.Error("Unexpected error: received nil certificate but no error", |
| 469 | "domain", domain, | 469 | "domain", domain, | ||
| 470 | "timeout", "1 minute") | 470 | "timeout", "1 minute") | ||
| 471 | // Implement retry logic with backoff | 471 | // Implement retry logic with backoff | ||
| @@ -479,7 +479,7 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 479 | 479 | ||||
| 480 | for attempt := 1; attempt <= maxRetries; attempt++ { | 480 | for attempt := 1; attempt <= maxRetries; attempt++ { | ||
| 481 | // Wait for backoff period | 481 | // Wait for backoff period | ||
| 482 | - | log.Info("Retrying Let's Encrypt certificate request", | 482 | + | logger.Info("Retrying Let's Encrypt certificate request", |
| 483 | "domain", domain, | 483 | "domain", domain, | ||
| 484 | "attempt", attempt, | 484 | "attempt", attempt, | ||
| 485 | "max_retries", maxRetries, | 485 | "max_retries", maxRetries, | ||
| @@ -503,14 +503,14 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 503 | resp, err := client.Get(testURL) | 503 | resp, err := client.Get(testURL) | ||
| 504 | 504 | ||||
| 505 | if err != nil { | 505 | if err != nil { | ||
| 506 | - | log.Warn("HTTP challenge endpoint might not be accessible", | 506 | + | logger.Warn("HTTP challenge endpoint might not be accessible", |
| 507 | "domain", domain, | 507 | "domain", domain, | ||
| 508 | "url", testURL, | 508 | "url", testURL, | ||
| 509 | "error", err, | 509 | "error", err, | ||
| 510 | "solution", "Ensure port 80 is accessible and not blocked by firewall") | 510 | "solution", "Ensure port 80 is accessible and not blocked by firewall") | ||
| 511 | } else { | 511 | } else { | ||
| 512 | resp.Body.Close() | 512 | resp.Body.Close() | ||
| 513 | - | log.Info("HTTP challenge endpoint is accessible", | 513 | + | logger.Info("HTTP challenge endpoint is accessible", |
| 514 | "domain", domain, | 514 | "domain", domain, | ||
| 515 | "url", testURL, | 515 | "url", testURL, | ||
| 516 | "status", resp.StatusCode) | 516 | "status", resp.StatusCode) | ||
| @@ -534,13 +534,13 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 534 | 534 | ||||
| 535 | // Check result | 535 | // Check result | ||
| 536 | if err == nil { | 536 | if err == nil { | ||
| 537 | - | log.Info("Successfully obtained Let's Encrypt certificate on retry", | 537 | + | logger.Info("Successfully obtained Let's Encrypt certificate on retry", |
| 538 | "domain", domain, | 538 | "domain", domain, | ||
| 539 | "attempt", attempt) | 539 | "attempt", attempt) | ||
| 540 | return | 540 | return | ||
| 541 | } | 541 | } | ||
| 542 | 542 | ||||
| 543 | - | log.Error("Let's Encrypt certificate request retry failed", | 543 | + | logger.Error("Let's Encrypt certificate request retry failed", |
| 544 | "domain", domain, | 544 | "domain", domain, | ||
| 545 | "attempt", attempt, | 545 | "attempt", attempt, | ||
| 546 | "error", err) | 546 | "error", err) | ||
| @@ -548,13 +548,13 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 548 | // Provide more detailed diagnostics based on the error | 548 | // Provide more detailed diagnostics based on the error | ||
| 549 | if strings.Contains(strings.ToLower(err.Error()), "connection refused") || | 549 | if strings.Contains(strings.ToLower(err.Error()), "connection refused") || | ||
| 550 | strings.Contains(strings.ToLower(err.Error()), "timeout") { | 550 | strings.Contains(strings.ToLower(err.Error()), "timeout") { | ||
| 551 | - | log.Error("Let's Encrypt connection failed - this typically indicates:", | 551 | + | logger.Error("Let's Encrypt connection failed - this typically indicates:", |
| 552 | "issue_1", "Port 80 is not accessible from the internet", | 552 | "issue_1", "Port 80 is not accessible from the internet", | ||
| 553 | "issue_2", "Firewall is blocking inbound connections", | 553 | "issue_2", "Firewall is blocking inbound connections", | ||
| 554 | "issue_3", "DNS records not properly propagated", | 554 | "issue_3", "DNS records not properly propagated", | ||
| 555 | "solution", "Check firewall settings and DNS configuration") | 555 | "solution", "Check firewall settings and DNS configuration") | ||
| 556 | } else if strings.Contains(strings.ToLower(err.Error()), "unauthorized") { | 556 | } else if strings.Contains(strings.ToLower(err.Error()), "unauthorized") { | ||
| 557 | - | log.Error("Let's Encrypt authorization failed - this typically indicates:", | 557 | + | logger.Error("Let's Encrypt authorization failed - this typically indicates:", |
| 558 | "issue", "Domain ownership validation failed", | 558 | "issue", "Domain ownership validation failed", | ||
| 559 | "solution", "Ensure the server is publicly accessible on port 80") | 559 | "solution", "Ensure the server is publicly accessible on port 80") | ||
| 560 | } else if strings.Contains(strings.ToLower(err.Error()), "ratelimited") { | 560 | } else if strings.Contains(strings.ToLower(err.Error()), "ratelimited") { | ||
| @@ -570,7 +570,7 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 570 | } | 570 | } | ||
| 571 | } | 571 | } | ||
| 572 | 572 | ||||
| 573 | - | log.Error("Let's Encrypt rate limit reached - cannot issue more certificates for this domain yet", | 573 | + | logger.Error("Let's Encrypt rate limit reached - cannot issue more certificates for this domain yet", |
| 574 | "domain", domain, | 574 | "domain", domain, | ||
| 575 | "retry_after", retryAfterStr, | 575 | "retry_after", retryAfterStr, | ||
| 576 | "solution", "Wait until the rate limit expires, then restart the server") | 576 | "solution", "Wait until the rate limit expires, then restart the server") | ||
| @@ -583,20 +583,20 @@ func (p *Proxy) retryCertificateRequest(domain string, maxRetries int, initialBa | |||||
| 583 | backoff *= 2 | 583 | backoff *= 2 | ||
| 584 | } | 584 | } | ||
| 585 | 585 | ||||
| 586 | - | log.Error("All Let's Encrypt certificate request retries failed", | 586 | + | logger.Error("All Let's Encrypt certificate request retries failed", |
| 587 | "domain", domain, | 587 | "domain", domain, | ||
| 588 | "max_retries", maxRetries, | 588 | "max_retries", maxRetries, | ||
| 589 | "fallback", "Using self-signed certificate") | 589 | "fallback", "Using self-signed certificate") | ||
| 590 | 590 | ||||
| 591 | // At this point, all retries have failed, so we'll rely on the fallback self-signed certificate | 591 | // At this point, all retries have failed, so we'll rely on the fallback self-signed certificate | ||
| 592 | // But let's log this prominently for debugging | 592 | // But let's log this prominently for debugging | ||
| 593 | - | log.Error("⚠️ HTTPS is using a self-signed certificate which browsers will warn about", | 593 | + | logger.Error("⚠️ HTTPS is using a self-signed certificate which browsers will warn about", |
| 594 | "domain", domain, | 594 | "domain", domain, | ||
| 595 | "reason", "Let's Encrypt certificate issuance failed", | 595 | "reason", "Let's Encrypt certificate issuance failed", | ||
| 596 | "solution", "Check network settings and Let's Encrypt status") | 596 | "solution", "Check network settings and Let's Encrypt status") | ||
| 597 | 597 | ||||
| 598 | // Suggest checking the common issues | 598 | // Suggest checking the common issues | ||
| 599 | - | log.Error("Common Let's Encrypt issues to check:", | 599 | + | logger.Error("Common Let's Encrypt issues to check:", |
| 600 | "check_1", "Ensure ports 80 and 443 are open on your firewall", | 600 | "check_1", "Ensure ports 80 and 443 are open on your firewall", | ||
| 601 | "check_2", "Verify DNS records point to your server IP", | 601 | "check_2", "Verify DNS records point to your server IP", | ||
| 602 | "check_3", "Make sure no other services are running on ports 80/443", | 602 | "check_3", "Make sure no other services are running on ports 80/443", | ||
M internal/proxy/containers.go +25 -8
| @@ -6,7 +6,7 @@ import ( | |||
| 6 | 6 | "strings" | |
| 7 | 7 | ||
| 8 | 8 | "github.com/bnema/gordon/pkg/docker" | |
| 9 | - | "github.com/charmbracelet/log" | |
| 9 | + | "github.com/bnema/gordon/pkg/logger" | |
| 10 | 10 | ) | |
| 11 | 11 | ||
| 12 | 12 | // scanContainersAndCheckCertificates scans all containers from the Gordon network | |
| @@ -19,7 +19,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 19 | 19 | return fmt.Errorf("container network name is not configured") | |
| 20 | 20 | } | |
| 21 | 21 | ||
| 22 | - | log.Info("Scanning containers in network for certificate checks", | |
| 22 | + | logger.Info("Scanning containers in network for certificate checks", | |
| 23 | 23 | "network", networkName) | |
| 24 | 24 | ||
| 25 | 25 | // Try to get network info using the docker client | |
| @@ -31,7 +31,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 31 | 31 | // Skip admin cert check if environment variable is set | |
| 32 | 32 | skipAdminCertCheck := os.Getenv("GORDON_SKIP_ADMIN_CERT_CHECK") == "true" | |
| 33 | 33 | if skipAdminCertCheck { | |
| 34 | - | log.Info("Skipping admin certificate check as GORDON_SKIP_ADMIN_CERT_CHECK=true") | |
| 34 | + | logger.Info("Skipping admin certificate check as GORDON_SKIP_ADMIN_CERT_CHECK=true") | |
| 35 | 35 | } | |
| 36 | 36 | ||
| 37 | 37 | // Get all containers in the network | |
| @@ -41,7 +41,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 41 | 41 | // Get container info to check for relevant domains | |
| 42 | 42 | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 43 | 43 | if err != nil { | |
| 44 | - | log.Warn("Failed to get container info", | |
| 44 | + | logger.Warn("Failed to get container info", | |
| 45 | 45 | "containerID", containerID, | |
| 46 | 46 | "containerName", containerName, | |
| 47 | 47 | "error", err) | |
| @@ -54,7 +54,22 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 54 | 54 | ||
| 55 | 55 | // Check common domain-related labels | |
| 56 | 56 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | |
| 57 | + | // Remove any leading dot from domain label | |
| 58 | + | domainLabel = strings.TrimPrefix(domainLabel, ".") | |
| 59 | + | ||
| 60 | + | // Always add the base domain | |
| 57 | 61 | domains = append(domains, domainLabel) | |
| 62 | + | ||
| 63 | + | // Also check if there's a service label to use as a subdomain | |
| 64 | + | if serviceLabel, serviceExists := containerInfo.Config.Labels["gordon.service"]; serviceExists && serviceLabel != "" { | |
| 65 | + | // Combine service name with domain to create service.domain format | |
| 66 | + | serviceSubdomain := serviceLabel + "." + domainLabel | |
| 67 | + | domains = append(domains, serviceSubdomain) | |
| 68 | + | logger.Debug("Created additional domain from service label", | |
| 69 | + | "domain", serviceSubdomain, | |
| 70 | + | "service", serviceLabel, | |
| 71 | + | "base_domain", domainLabel) | |
| 72 | + | } | |
| 58 | 73 | } | |
| 59 | 74 | ||
| 60 | 75 | // Check environment variables for domain information | |
| @@ -64,7 +79,9 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 64 | 79 | if len(parts) == 2 && parts[1] != "" { | |
| 65 | 80 | // Multiple domains might be comma-separated | |
| 66 | 81 | for _, domain := range strings.Split(parts[1], ",") { | |
| 67 | - | domains = append(domains, strings.TrimSpace(domain)) | |
| 82 | + | // Remove any leading dot before adding the domain | |
| 83 | + | cleanDomain := strings.TrimPrefix(strings.TrimSpace(domain), ".") | |
| 84 | + | domains = append(domains, cleanDomain) | |
| 68 | 85 | } | |
| 69 | 86 | } | |
| 70 | 87 | } | |
| @@ -96,11 +113,11 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 96 | 113 | hasCert := p.checkCertificateInCache(domain) | |
| 97 | 114 | ||
| 98 | 115 | if hasCert { | |
| 99 | - | log.Debug("Certificate exists for domain", | |
| 116 | + | logger.Debug("Certificate exists for domain", | |
| 100 | 117 | "domain", domain, | |
| 101 | 118 | "container", containerName) | |
| 102 | 119 | } else { | |
| 103 | - | log.Info("Certificate not found in cache, requesting", | |
| 120 | + | logger.Info("Certificate not found in cache, requesting", | |
| 104 | 121 | "domain", domain, | |
| 105 | 122 | "container", containerName) | |
| 106 | 123 | ||
| @@ -125,7 +142,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||
| 125 | 142 | if len(parts) > 2 && !isCommonTLD(parts[len(parts)-1]) { | |
| 126 | 143 | hasCert = p.checkCertificateInCache(parentDomain) | |
| 127 | 144 | if !hasCert { | |
| 128 | - | log.Info("Certificate not found for parent domain", | |
| 145 | + | logger.Info("Certificate not found for parent domain", | |
| 129 | 146 | "domain", parentDomain, | |
| 130 | 147 | "container", containerName) | |
| 131 | 148 | ||
| @@ -6,7 +6,7 @@ import ( | |||||
| 6 | "strings" | 6 | "strings" | ||
| 7 | 7 | ||||
| 8 | "github.com/bnema/gordon/pkg/docker" | 8 | "github.com/bnema/gordon/pkg/docker" | ||
| 9 | - | "github.com/charmbracelet/log" | 9 | + | "github.com/bnema/gordon/pkg/logger" |
| 10 | ) | 10 | ) | ||
| 11 | 11 | ||||
| 12 | // scanContainersAndCheckCertificates scans all containers from the Gordon network | 12 | // scanContainersAndCheckCertificates scans all containers from the Gordon network | ||
| @@ -19,7 +19,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 19 | return fmt.Errorf("container network name is not configured") | 19 | return fmt.Errorf("container network name is not configured") | ||
| 20 | } | 20 | } | ||
| 21 | 21 | ||||
| 22 | - | log.Info("Scanning containers in network for certificate checks", | 22 | + | logger.Info("Scanning containers in network for certificate checks", |
| 23 | "network", networkName) | 23 | "network", networkName) | ||
| 24 | 24 | ||||
| 25 | // Try to get network info using the docker client | 25 | // Try to get network info using the docker client | ||
| @@ -31,7 +31,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 31 | // Skip admin cert check if environment variable is set | 31 | // Skip admin cert check if environment variable is set | ||
| 32 | skipAdminCertCheck := os.Getenv("GORDON_SKIP_ADMIN_CERT_CHECK") == "true" | 32 | skipAdminCertCheck := os.Getenv("GORDON_SKIP_ADMIN_CERT_CHECK") == "true" | ||
| 33 | if skipAdminCertCheck { | 33 | if skipAdminCertCheck { | ||
| 34 | - | log.Info("Skipping admin certificate check as GORDON_SKIP_ADMIN_CERT_CHECK=true") | 34 | + | logger.Info("Skipping admin certificate check as GORDON_SKIP_ADMIN_CERT_CHECK=true") |
| 35 | } | 35 | } | ||
| 36 | 36 | ||||
| 37 | // Get all containers in the network | 37 | // Get all containers in the network | ||
| @@ -41,7 +41,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 41 | // Get container info to check for relevant domains | 41 | // Get container info to check for relevant domains | ||
| 42 | containerInfo, err := docker.GetContainerInfo(containerID) | 42 | containerInfo, err := docker.GetContainerInfo(containerID) | ||
| 43 | if err != nil { | 43 | if err != nil { | ||
| 44 | - | log.Warn("Failed to get container info", | 44 | + | logger.Warn("Failed to get container info", |
| 45 | "containerID", containerID, | 45 | "containerID", containerID, | ||
| 46 | "containerName", containerName, | 46 | "containerName", containerName, | ||
| 47 | "error", err) | 47 | "error", err) | ||
| @@ -54,7 +54,22 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 54 | 54 | ||||
| 55 | // Check common domain-related labels | 55 | // Check common domain-related labels | ||
| 56 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | 56 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | ||
| 57 | + | // Remove any leading dot from domain label | |||
| 58 | + | domainLabel = strings.TrimPrefix(domainLabel, ".") | |||
| 59 | + | ||||
| 60 | + | // Always add the base domain | |||
| 57 | domains = append(domains, domainLabel) | 61 | domains = append(domains, domainLabel) | ||
| 62 | + | ||||
| 63 | + | // Also check if there's a service label to use as a subdomain | |||
| 64 | + | if serviceLabel, serviceExists := containerInfo.Config.Labels["gordon.service"]; serviceExists && serviceLabel != "" { | |||
| 65 | + | // Combine service name with domain to create service.domain format | |||
| 66 | + | serviceSubdomain := serviceLabel + "." + domainLabel | |||
| 67 | + | domains = append(domains, serviceSubdomain) | |||
| 68 | + | logger.Debug("Created additional domain from service label", | |||
| 69 | + | "domain", serviceSubdomain, | |||
| 70 | + | "service", serviceLabel, | |||
| 71 | + | "base_domain", domainLabel) | |||
| 72 | + | } | |||
| 58 | } | 73 | } | ||
| 59 | 74 | ||||
| 60 | // Check environment variables for domain information | 75 | // Check environment variables for domain information | ||
| @@ -64,7 +79,9 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 64 | if len(parts) == 2 && parts[1] != "" { | 79 | if len(parts) == 2 && parts[1] != "" { | ||
| 65 | // Multiple domains might be comma-separated | 80 | // Multiple domains might be comma-separated | ||
| 66 | for _, domain := range strings.Split(parts[1], ",") { | 81 | for _, domain := range strings.Split(parts[1], ",") { | ||
| 67 | - | domains = append(domains, strings.TrimSpace(domain)) | 82 | + | // Remove any leading dot before adding the domain |
| 83 | + | cleanDomain := strings.TrimPrefix(strings.TrimSpace(domain), ".") | |||
| 84 | + | domains = append(domains, cleanDomain) | |||
| 68 | } | 85 | } | ||
| 69 | } | 86 | } | ||
| 70 | } | 87 | } | ||
| @@ -96,11 +113,11 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 96 | hasCert := p.checkCertificateInCache(domain) | 113 | hasCert := p.checkCertificateInCache(domain) | ||
| 97 | 114 | ||||
| 98 | if hasCert { | 115 | if hasCert { | ||
| 99 | - | log.Debug("Certificate exists for domain", | 116 | + | logger.Debug("Certificate exists for domain", |
| 100 | "domain", domain, | 117 | "domain", domain, | ||
| 101 | "container", containerName) | 118 | "container", containerName) | ||
| 102 | } else { | 119 | } else { | ||
| 103 | - | log.Info("Certificate not found in cache, requesting", | 120 | + | logger.Info("Certificate not found in cache, requesting", |
| 104 | "domain", domain, | 121 | "domain", domain, | ||
| 105 | "container", containerName) | 122 | "container", containerName) | ||
| 106 | 123 | ||||
| @@ -125,7 +142,7 @@ func (p *Proxy) scanContainersAndCheckCertificates() error { | |||||
| 125 | if len(parts) > 2 && !isCommonTLD(parts[len(parts)-1]) { | 142 | if len(parts) > 2 && !isCommonTLD(parts[len(parts)-1]) { | ||
| 126 | hasCert = p.checkCertificateInCache(parentDomain) | 143 | hasCert = p.checkCertificateInCache(parentDomain) | ||
| 127 | if !hasCert { | 144 | if !hasCert { | ||
| 128 | - | log.Info("Certificate not found for parent domain", | 145 | + | logger.Info("Certificate not found for parent domain", |
| 129 | "domain", parentDomain, | 146 | "domain", parentDomain, | ||
| 130 | "container", containerName) | 147 | "container", containerName) | ||
| 131 | 148 | ||||
M internal/proxy/core.go +6 -6
| @@ -11,7 +11,7 @@ import ( | |||
| 11 | 11 | "github.com/bnema/gordon/internal/common" | |
| 12 | 12 | "github.com/bnema/gordon/internal/interfaces" | |
| 13 | 13 | "github.com/bnema/gordon/pkg/docker" | |
| 14 | - | "github.com/charmbracelet/log" | |
| 14 | + | "github.com/bnema/gordon/pkg/logger" | |
| 15 | 15 | "github.com/labstack/echo/v4" | |
| 16 | 16 | "golang.org/x/crypto/acme/autocert" | |
| 17 | 17 | ) | |
| @@ -55,10 +55,10 @@ type Proxy struct { | |||
| 55 | 55 | ||
| 56 | 56 | // NewProxy creates a new instance of the reverse proxy | |
| 57 | 57 | func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |
| 58 | - | log.Debug("Initializing reverse proxy") | |
| 58 | + | logger.Debug("Initializing reverse proxy") | |
| 59 | 59 | ||
| 60 | 60 | // Log important configuration information | |
| 61 | - | log.Debug("All internal connections to containers will use HTTP protocol regardless of external protocol") | |
| 61 | + | logger.Debug("All internal connections to containers will use HTTP protocol regardless of external protocol") | |
| 62 | 62 | ||
| 63 | 63 | // Set up the echo server for HTTPS traffic | |
| 64 | 64 | httpsServer := echo.New() | |
| @@ -85,7 +85,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||
| 85 | 85 | // Store our container ID for future reference | |
| 86 | 86 | ourContainerID = container.ID | |
| 87 | 87 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 88 | - | log.Info("Gordon identity established", | |
| 88 | + | logger.Info("Gordon identity established", | |
| 89 | 89 | "container_id", ourContainerID, | |
| 90 | 90 | "container_name", containerName) | |
| 91 | 91 | break | |
| @@ -99,7 +99,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||
| 99 | 99 | blacklistPath := app.GetConfig().General.StorageDir + "/blacklist.json" | |
| 100 | 100 | blacklist, err := NewBlacklist(blacklistPath) | |
| 101 | 101 | if err != nil { | |
| 102 | - | log.Warn("Failed to initialize blacklist, continuing without it", "error", err) | |
| 102 | + | logger.Warn("Failed to initialize blacklist, continuing without it", "error", err) | |
| 103 | 103 | } | |
| 104 | 104 | ||
| 105 | 105 | // Initialize routes map | |
| @@ -126,6 +126,6 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||
| 126 | 126 | // Set up the certificate manager | |
| 127 | 127 | p.setupCertManager() | |
| 128 | 128 | ||
| 129 | - | log.Debug("Reverse proxy initialized") | |
| 129 | + | logger.Debug("Reverse proxy initialized") | |
| 130 | 130 | return p, nil | |
| 131 | 131 | } | |
| @@ -11,7 +11,7 @@ import ( | |||||
| 11 | "github.com/bnema/gordon/internal/common" | 11 | "github.com/bnema/gordon/internal/common" | ||
| 12 | "github.com/bnema/gordon/internal/interfaces" | 12 | "github.com/bnema/gordon/internal/interfaces" | ||
| 13 | "github.com/bnema/gordon/pkg/docker" | 13 | "github.com/bnema/gordon/pkg/docker" | ||
| 14 | - | "github.com/charmbracelet/log" | 14 | + | "github.com/bnema/gordon/pkg/logger" |
| 15 | "github.com/labstack/echo/v4" | 15 | "github.com/labstack/echo/v4" | ||
| 16 | "golang.org/x/crypto/acme/autocert" | 16 | "golang.org/x/crypto/acme/autocert" | ||
| 17 | ) | 17 | ) | ||
| @@ -55,10 +55,10 @@ type Proxy struct { | |||||
| 55 | 55 | ||||
| 56 | // NewProxy creates a new instance of the reverse proxy | 56 | // NewProxy creates a new instance of the reverse proxy | ||
| 57 | func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | 57 | func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | ||
| 58 | - | log.Debug("Initializing reverse proxy") | 58 | + | logger.Debug("Initializing reverse proxy") |
| 59 | 59 | ||||
| 60 | // Log important configuration information | 60 | // Log important configuration information | ||
| 61 | - | log.Debug("All internal connections to containers will use HTTP protocol regardless of external protocol") | 61 | + | logger.Debug("All internal connections to containers will use HTTP protocol regardless of external protocol") |
| 62 | 62 | ||||
| 63 | // Set up the echo server for HTTPS traffic | 63 | // Set up the echo server for HTTPS traffic | ||
| 64 | httpsServer := echo.New() | 64 | httpsServer := echo.New() | ||
| @@ -85,7 +85,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||||
| 85 | // Store our container ID for future reference | 85 | // Store our container ID for future reference | ||
| 86 | ourContainerID = container.ID | 86 | ourContainerID = container.ID | ||
| 87 | containerName := strings.TrimLeft(container.Names[0], "/") | 87 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 88 | - | log.Info("Gordon identity established", | 88 | + | logger.Info("Gordon identity established", |
| 89 | "container_id", ourContainerID, | 89 | "container_id", ourContainerID, | ||
| 90 | "container_name", containerName) | 90 | "container_name", containerName) | ||
| 91 | break | 91 | break | ||
| @@ -99,7 +99,7 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||||
| 99 | blacklistPath := app.GetConfig().General.StorageDir + "/blacklist.json" | 99 | blacklistPath := app.GetConfig().General.StorageDir + "/blacklist.json" | ||
| 100 | blacklist, err := NewBlacklist(blacklistPath) | 100 | blacklist, err := NewBlacklist(blacklistPath) | ||
| 101 | if err != nil { | 101 | if err != nil { | ||
| 102 | - | log.Warn("Failed to initialize blacklist, continuing without it", "error", err) | 102 | + | logger.Warn("Failed to initialize blacklist, continuing without it", "error", err) |
| 103 | } | 103 | } | ||
| 104 | 104 | ||||
| 105 | // Initialize routes map | 105 | // Initialize routes map | ||
| @@ -126,6 +126,6 @@ func NewProxy(app interfaces.AppInterface) (*Proxy, error) { | |||||
| 126 | // Set up the certificate manager | 126 | // Set up the certificate manager | ||
| 127 | p.setupCertManager() | 127 | p.setupCertManager() | ||
| 128 | 128 | ||||
| 129 | - | log.Debug("Reverse proxy initialized") | 129 | + | logger.Debug("Reverse proxy initialized") |
| 130 | return p, nil | 130 | return p, nil | ||
| 131 | } | 131 | } | ||
A internal/proxy/db_helpers.go +133 -0
| @@ -0,0 +1,133 @@ | |||
| 1 | + | package proxy | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "database/sql" | |
| 5 | + | "fmt" | |
| 6 | + | "time" | |
| 7 | + | ||
| 8 | + | "github.com/charmbracelet/log" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // dbExecWithRetry performs a database execution with retry logic for handling locked database | |
| 12 | + | func (p *Proxy) dbExecWithRetry(query string, args ...interface{}) (sql.Result, error) { | |
| 13 | + | var result sql.Result | |
| 14 | + | var err error | |
| 15 | + | maxRetries := 5 | |
| 16 | + | retryDelay := 100 * time.Millisecond | |
| 17 | + | ||
| 18 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |
| 19 | + | result, err = p.app.GetDB().Exec(query, args...) | |
| 20 | + | if err == nil { | |
| 21 | + | return result, nil | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | // Check if this is a database locked error | |
| 25 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |
| 26 | + | log.Debug("Database locked, retrying operation", | |
| 27 | + | "attempt", attempt, | |
| 28 | + | "max_retries", maxRetries) | |
| 29 | + | time.Sleep(retryDelay) | |
| 30 | + | // Increase delay for next retry (exponential backoff) | |
| 31 | + | retryDelay *= 2 | |
| 32 | + | continue | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | // If it's not a locking error, return immediately | |
| 36 | + | return nil, err | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // dbQueryWithRetry performs a database query with retry logic for handling locked database | |
| 43 | + | func (p *Proxy) dbQueryWithRetry(query string, args ...interface{}) (*sql.Rows, error) { | |
| 44 | + | var rows *sql.Rows | |
| 45 | + | var err error | |
| 46 | + | maxRetries := 5 | |
| 47 | + | retryDelay := 100 * time.Millisecond | |
| 48 | + | ||
| 49 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |
| 50 | + | rows, err = p.app.GetDB().Query(query, args...) | |
| 51 | + | if err == nil { | |
| 52 | + | return rows, nil | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | // Check if this is a database locked error | |
| 56 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |
| 57 | + | log.Debug("Database locked, retrying operation", | |
| 58 | + | "attempt", attempt, | |
| 59 | + | "max_retries", maxRetries) | |
| 60 | + | time.Sleep(retryDelay) | |
| 61 | + | // Increase delay for next retry (exponential backoff) | |
| 62 | + | retryDelay *= 2 | |
| 63 | + | continue | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | // If it's not a locking error, return immediately | |
| 67 | + | return nil, err | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // dbBeginWithRetry starts a transaction with retry logic for handling locked database | |
| 74 | + | func (p *Proxy) dbBeginWithRetry() (*sql.Tx, error) { | |
| 75 | + | var tx *sql.Tx | |
| 76 | + | var err error | |
| 77 | + | maxRetries := 5 | |
| 78 | + | retryDelay := 100 * time.Millisecond | |
| 79 | + | ||
| 80 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |
| 81 | + | tx, err = p.app.GetDB().Begin() | |
| 82 | + | if err == nil { | |
| 83 | + | return tx, nil | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | // Check if this is a database locked error | |
| 87 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |
| 88 | + | log.Debug("Database locked, retrying transaction start", | |
| 89 | + | "attempt", attempt, | |
| 90 | + | "max_retries", maxRetries) | |
| 91 | + | time.Sleep(retryDelay) | |
| 92 | + | // Increase delay for next retry (exponential backoff) | |
| 93 | + | retryDelay *= 2 | |
| 94 | + | continue | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // If it's not a locking error, return immediately | |
| 98 | + | return nil, err | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | // txExecWithRetry executes a query within a transaction with retry logic | |
| 105 | + | func txExecWithRetry(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { | |
| 106 | + | var result sql.Result | |
| 107 | + | var err error | |
| 108 | + | maxRetries := 5 | |
| 109 | + | retryDelay := 100 * time.Millisecond | |
| 110 | + | ||
| 111 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |
| 112 | + | result, err = tx.Exec(query, args...) | |
| 113 | + | if err == nil { | |
| 114 | + | return result, nil | |
| 115 | + | } | |
| 116 | + | ||
| 117 | + | // Check if this is a database locked error | |
| 118 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |
| 119 | + | log.Debug("Database locked, retrying transaction operation", | |
| 120 | + | "attempt", attempt, | |
| 121 | + | "max_retries", maxRetries) | |
| 122 | + | time.Sleep(retryDelay) | |
| 123 | + | // Increase delay for next retry (exponential backoff) | |
| 124 | + | retryDelay *= 2 | |
| 125 | + | continue | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // If it's not a locking error, return immediately | |
| 129 | + | return nil, err | |
| 130 | + | } | |
| 131 | + | ||
| 132 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |
| 133 | + | } | |
| @@ -0,0 +1,133 @@ | |||||
| 1 | + | package proxy | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "database/sql" | |||
| 5 | + | "fmt" | |||
| 6 | + | "time" | |||
| 7 | + | ||||
| 8 | + | "github.com/charmbracelet/log" | |||
| 9 | + | ) | |||
| 10 | + | ||||
| 11 | + | // dbExecWithRetry performs a database execution with retry logic for handling locked database | |||
| 12 | + | func (p *Proxy) dbExecWithRetry(query string, args ...interface{}) (sql.Result, error) { | |||
| 13 | + | var result sql.Result | |||
| 14 | + | var err error | |||
| 15 | + | maxRetries := 5 | |||
| 16 | + | retryDelay := 100 * time.Millisecond | |||
| 17 | + | ||||
| 18 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |||
| 19 | + | result, err = p.app.GetDB().Exec(query, args...) | |||
| 20 | + | if err == nil { | |||
| 21 | + | return result, nil | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | // Check if this is a database locked error | |||
| 25 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |||
| 26 | + | log.Debug("Database locked, retrying operation", | |||
| 27 | + | "attempt", attempt, | |||
| 28 | + | "max_retries", maxRetries) | |||
| 29 | + | time.Sleep(retryDelay) | |||
| 30 | + | // Increase delay for next retry (exponential backoff) | |||
| 31 | + | retryDelay *= 2 | |||
| 32 | + | continue | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | // If it's not a locking error, return immediately | |||
| 36 | + | return nil, err | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | // dbQueryWithRetry performs a database query with retry logic for handling locked database | |||
| 43 | + | func (p *Proxy) dbQueryWithRetry(query string, args ...interface{}) (*sql.Rows, error) { | |||
| 44 | + | var rows *sql.Rows | |||
| 45 | + | var err error | |||
| 46 | + | maxRetries := 5 | |||
| 47 | + | retryDelay := 100 * time.Millisecond | |||
| 48 | + | ||||
| 49 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |||
| 50 | + | rows, err = p.app.GetDB().Query(query, args...) | |||
| 51 | + | if err == nil { | |||
| 52 | + | return rows, nil | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | // Check if this is a database locked error | |||
| 56 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |||
| 57 | + | log.Debug("Database locked, retrying operation", | |||
| 58 | + | "attempt", attempt, | |||
| 59 | + | "max_retries", maxRetries) | |||
| 60 | + | time.Sleep(retryDelay) | |||
| 61 | + | // Increase delay for next retry (exponential backoff) | |||
| 62 | + | retryDelay *= 2 | |||
| 63 | + | continue | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | // If it's not a locking error, return immediately | |||
| 67 | + | return nil, err | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |||
| 71 | + | } | |||
| 72 | + | ||||
| 73 | + | // dbBeginWithRetry starts a transaction with retry logic for handling locked database | |||
| 74 | + | func (p *Proxy) dbBeginWithRetry() (*sql.Tx, error) { | |||
| 75 | + | var tx *sql.Tx | |||
| 76 | + | var err error | |||
| 77 | + | maxRetries := 5 | |||
| 78 | + | retryDelay := 100 * time.Millisecond | |||
| 79 | + | ||||
| 80 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |||
| 81 | + | tx, err = p.app.GetDB().Begin() | |||
| 82 | + | if err == nil { | |||
| 83 | + | return tx, nil | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | // Check if this is a database locked error | |||
| 87 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |||
| 88 | + | log.Debug("Database locked, retrying transaction start", | |||
| 89 | + | "attempt", attempt, | |||
| 90 | + | "max_retries", maxRetries) | |||
| 91 | + | time.Sleep(retryDelay) | |||
| 92 | + | // Increase delay for next retry (exponential backoff) | |||
| 93 | + | retryDelay *= 2 | |||
| 94 | + | continue | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // If it's not a locking error, return immediately | |||
| 98 | + | return nil, err | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | // txExecWithRetry executes a query within a transaction with retry logic | |||
| 105 | + | func txExecWithRetry(tx *sql.Tx, query string, args ...interface{}) (sql.Result, error) { | |||
| 106 | + | var result sql.Result | |||
| 107 | + | var err error | |||
| 108 | + | maxRetries := 5 | |||
| 109 | + | retryDelay := 100 * time.Millisecond | |||
| 110 | + | ||||
| 111 | + | for attempt := 1; attempt <= maxRetries; attempt++ { | |||
| 112 | + | result, err = tx.Exec(query, args...) | |||
| 113 | + | if err == nil { | |||
| 114 | + | return result, nil | |||
| 115 | + | } | |||
| 116 | + | ||||
| 117 | + | // Check if this is a database locked error | |||
| 118 | + | if err.Error() == "database is locked (5) (SQLITE_BUSY)" { | |||
| 119 | + | log.Debug("Database locked, retrying transaction operation", | |||
| 120 | + | "attempt", attempt, | |||
| 121 | + | "max_retries", maxRetries) | |||
| 122 | + | time.Sleep(retryDelay) | |||
| 123 | + | // Increase delay for next retry (exponential backoff) | |||
| 124 | + | retryDelay *= 2 | |||
| 125 | + | continue | |||
| 126 | + | } | |||
| 127 | + | ||||
| 128 | + | // If it's not a locking error, return immediately | |||
| 129 | + | return nil, err | |||
| 130 | + | } | |||
| 131 | + | ||||
| 132 | + | return nil, fmt.Errorf("max retries exceeded: %w", err) | |||
| 133 | + | } | |||
M internal/proxy/middleware.go +29 -29
| @@ -6,7 +6,7 @@ import ( | |||
| 6 | 6 | "strings" | |
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | - | "github.com/charmbracelet/log" | |
| 9 | + | "github.com/bnema/gordon/pkg/logger" | |
| 10 | 10 | "github.com/google/uuid" | |
| 11 | 11 | "github.com/labstack/echo/v4" | |
| 12 | 12 | "github.com/labstack/echo/v4/middleware" | |
| @@ -33,7 +33,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 33 | 33 | // Since we are the reverse proxy, we need to make sure we identify client IPs correctly | |
| 34 | 34 | p.httpsServer.IPExtractor = echo.ExtractIPFromXFFHeader() | |
| 35 | 35 | p.httpServer.IPExtractor = echo.ExtractIPFromXFFHeader() | |
| 36 | - | ||
| 36 | + | ||
| 37 | 37 | // Add UUID generator middleware for both HTTP and HTTPS servers | |
| 38 | 38 | p.httpsServer.Use(p.createRequestIDMiddleware()) | |
| 39 | 39 | p.httpServer.Use(p.createRequestIDMiddleware()) | |
| @@ -42,7 +42,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 42 | 42 | p.httpsServer.Use(func(next echo.HandlerFunc) echo.HandlerFunc { | |
| 43 | 43 | return func(c echo.Context) error { | |
| 44 | 44 | req := c.Request() | |
| 45 | - | log.Debug("Request headers", | |
| 45 | + | logger.Debug("Request headers", | |
| 46 | 46 | "method", req.Method, | |
| 47 | 47 | "path", req.URL.Path, | |
| 48 | 48 | "remote_addr", req.RemoteAddr, | |
| @@ -63,7 +63,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 63 | 63 | 3*time.Minute, // Expires in 3 minutes | |
| 64 | 64 | ) | |
| 65 | 65 | if err != nil { | |
| 66 | - | log.Error("Failed to create HTTPS rate limiter store, falling back to memory store", "error", err) | |
| 66 | + | logger.Error("Failed to create HTTPS rate limiter store, falling back to memory store", "error", err) | |
| 67 | 67 | ||
| 68 | 68 | // Fallback to memory store if Starskey store creation fails | |
| 69 | 69 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | |
| @@ -75,7 +75,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 75 | 75 | }, | |
| 76 | 76 | ), | |
| 77 | 77 | DenyHandler: func(context echo.Context, identifier string, err error) error { | |
| 78 | - | log.Warn("Rate limit exceeded", | |
| 78 | + | logger.Warn("Rate limit exceeded", | |
| 79 | 79 | "ip", identifier, | |
| 80 | 80 | "path", context.Request().URL.Path, | |
| 81 | 81 | "method", context.Request().Method) | |
| @@ -84,14 +84,14 @@ func (p *Proxy) setupMiddleware() { | |||
| 84 | 84 | })) | |
| 85 | 85 | } else { | |
| 86 | 86 | // Use the Starskey store for HTTPS rate limiting | |
| 87 | - | log.Debug("Using Starskey rate limiter for HTTPS server") | |
| 87 | + | logger.Debug("Using Starskey rate limiter for HTTPS server") | |
| 88 | 88 | ||
| 89 | 89 | // Add custom Starskey rate limiter middleware | |
| 90 | 90 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | |
| 91 | 91 | Store: httpsRateLimiter, | |
| 92 | 92 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | |
| 93 | 93 | id := ctx.RealIP() | |
| 94 | - | log.Debug("Rate limiting HTTPS request", | |
| 94 | + | logger.Debug("Rate limiting HTTPS request", | |
| 95 | 95 | "ip", id, | |
| 96 | 96 | "remote_addr", ctx.Request().RemoteAddr) | |
| 97 | 97 | ||
| @@ -101,7 +101,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 101 | 101 | return id, nil | |
| 102 | 102 | }, | |
| 103 | 103 | DenyHandler: func(context echo.Context, identifier string, err error) error { | |
| 104 | - | log.Warn("HTTPS rate limit exceeded", | |
| 104 | + | logger.Warn("HTTPS rate limit exceeded", | |
| 105 | 105 | "ip", identifier, | |
| 106 | 106 | "path", context.Request().URL.Path, | |
| 107 | 107 | "method", context.Request().Method) | |
| @@ -112,7 +112,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 112 | 112 | // Add cleanup function to global slice | |
| 113 | 113 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | |
| 114 | 114 | if err := httpsRateLimiter.Close(); err != nil { | |
| 115 | - | log.Error("Failed to close HTTPS rate limiter store", "error", err) | |
| 115 | + | logger.Error("Failed to close HTTPS rate limiter store", "error", err) | |
| 116 | 116 | } | |
| 117 | 117 | }) | |
| 118 | 118 | } | |
| @@ -120,15 +120,15 @@ func (p *Proxy) setupMiddleware() { | |||
| 120 | 120 | // Add custom logger middleware with request ID if enabled in config | |
| 121 | 121 | if p.config.EnableLogs { | |
| 122 | 122 | p.httpsServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | |
| 123 | - | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | |
| 123 | + | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | |
| 124 | 124 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | |
| 125 | 125 | Skipper: func(c echo.Context) bool { | |
| 126 | 126 | return !p.config.EnableLogs | |
| 127 | 127 | }, | |
| 128 | 128 | })) | |
| 129 | - | log.Debug("HTTP request logging enabled for HTTPS server") | |
| 129 | + | logger.Debug("HTTP request logging enabled for HTTPS server") | |
| 130 | 130 | } else { | |
| 131 | - | log.Debug("HTTP request logging disabled for HTTPS server") | |
| 131 | + | logger.Debug("HTTP request logging disabled for HTTPS server") | |
| 132 | 132 | } | |
| 133 | 133 | ||
| 134 | 134 | // Create blacklist middleware for HTTP | |
| @@ -137,24 +137,24 @@ func (p *Proxy) setupMiddleware() { | |||
| 137 | 137 | // Create a new Starskey rate limiter store for HTTP server | |
| 138 | 138 | httpRateLimiter, err := kv.NewStarskeyRateLimiterStore( | |
| 139 | 139 | "./data/ratelimiter/http", // Path to store rate limit data | |
| 140 | - | 2, // Rate: 2 requests per second (lowered from 10) | |
| 141 | - | 10, // Burst: 10 requests (lowered from 30) | |
| 140 | + | 10, // Rate: 10 requests per second (lowered from 10) | |
| 141 | + | 30, // Burst: 30 requests (lowered from 30) | |
| 142 | 142 | 3*time.Minute, // Expires in 3 minutes | |
| 143 | 143 | ) | |
| 144 | 144 | if err != nil { | |
| 145 | - | log.Error("Failed to create HTTP rate limiter store, falling back to memory store", "error", err) | |
| 145 | + | logger.Error("Failed to create HTTP rate limiter store, falling back to memory store", "error", err) | |
| 146 | 146 | ||
| 147 | 147 | // Fallback to memory store if Starskey store creation fails | |
| 148 | 148 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | |
| 149 | 149 | Store: middleware.NewRateLimiterMemoryStoreWithConfig( | |
| 150 | 150 | middleware.RateLimiterMemoryStoreConfig{ | |
| 151 | - | Rate: rate.Limit(2), // 2 requests per second (lowered from 10) | |
| 152 | - | Burst: 10, // Burst of 10 requests (lowered from 30) | |
| 151 | + | Rate: rate.Limit(10), // 10 requests per second (lowered from 10) | |
| 152 | + | Burst: 30, // Burst of 30 requests (lowered from 30) | |
| 153 | 153 | ExpiresIn: 3 * time.Minute, // Store expiration | |
| 154 | 154 | }, | |
| 155 | 155 | ), | |
| 156 | 156 | DenyHandler: func(context echo.Context, identifier string, err error) error { | |
| 157 | - | log.Warn("Rate limit exceeded", | |
| 157 | + | logger.Warn("Rate limit exceeded", | |
| 158 | 158 | "ip", identifier, | |
| 159 | 159 | "path", context.Request().URL.Path, | |
| 160 | 160 | "method", context.Request().Method) | |
| @@ -163,14 +163,14 @@ func (p *Proxy) setupMiddleware() { | |||
| 163 | 163 | })) | |
| 164 | 164 | } else { | |
| 165 | 165 | // Use the Starskey store for HTTP rate limiting | |
| 166 | - | log.Debug("Using Starskey rate limiter for HTTP server") | |
| 166 | + | logger.Debug("Using Starskey rate limiter for HTTP server") | |
| 167 | 167 | ||
| 168 | 168 | // Add custom Starskey rate limiter middleware | |
| 169 | 169 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | |
| 170 | 170 | Store: httpRateLimiter, | |
| 171 | 171 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | |
| 172 | 172 | id := ctx.RealIP() | |
| 173 | - | log.Debug("Rate limiting HTTP request", | |
| 173 | + | logger.Debug("Rate limiting HTTP request", | |
| 174 | 174 | "ip", id, | |
| 175 | 175 | "remote_addr", ctx.Request().RemoteAddr) | |
| 176 | 176 | ||
| @@ -180,7 +180,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 180 | 180 | return id, nil | |
| 181 | 181 | }, | |
| 182 | 182 | DenyHandler: func(context echo.Context, identifier string, err error) error { | |
| 183 | - | log.Warn("HTTP rate limit exceeded", | |
| 183 | + | logger.Warn("HTTP rate limit exceeded", | |
| 184 | 184 | "ip", identifier, | |
| 185 | 185 | "path", context.Request().URL.Path, | |
| 186 | 186 | "method", context.Request().Method) | |
| @@ -191,7 +191,7 @@ func (p *Proxy) setupMiddleware() { | |||
| 191 | 191 | // Add cleanup function to global slice | |
| 192 | 192 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | |
| 193 | 193 | if err := httpRateLimiter.Close(); err != nil { | |
| 194 | - | log.Error("Failed to close HTTP rate limiter store", "error", err) | |
| 194 | + | logger.Error("Failed to close HTTP rate limiter store", "error", err) | |
| 195 | 195 | } | |
| 196 | 196 | }) | |
| 197 | 197 | } | |
| @@ -199,15 +199,15 @@ func (p *Proxy) setupMiddleware() { | |||
| 199 | 199 | // Add custom logger middleware with request ID for HTTP server if enabled in config | |
| 200 | 200 | if p.config.EnableLogs { | |
| 201 | 201 | p.httpServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | |
| 202 | - | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | |
| 202 | + | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | |
| 203 | 203 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | |
| 204 | 204 | Skipper: func(c echo.Context) bool { | |
| 205 | 205 | return !p.config.EnableLogs | |
| 206 | 206 | }, | |
| 207 | 207 | })) | |
| 208 | - | log.Debug("HTTP request logging enabled for HTTP server") | |
| 208 | + | logger.Debug("HTTP request logging enabled for HTTP server") | |
| 209 | 209 | } else { | |
| 210 | - | log.Debug("HTTP request logging disabled for HTTP server") | |
| 210 | + | logger.Debug("HTTP request logging disabled for HTTP server") | |
| 211 | 211 | } | |
| 212 | 212 | } | |
| 213 | 213 | ||
| @@ -217,13 +217,13 @@ func (p *Proxy) createRequestIDMiddleware() echo.MiddlewareFunc { | |||
| 217 | 217 | return func(c echo.Context) error { | |
| 218 | 218 | // Generate a new UUID for this request | |
| 219 | 219 | requestID := uuid.New().String() | |
| 220 | - | ||
| 220 | + | ||
| 221 | 221 | // Store the UUID in the context | |
| 222 | 222 | c.Set(RequestIDKey, requestID) | |
| 223 | - | ||
| 223 | + | ||
| 224 | 224 | // Add the UUID as a response header | |
| 225 | 225 | c.Response().Header().Set(echo.HeaderXRequestID, requestID) | |
| 226 | - | ||
| 226 | + | ||
| 227 | 227 | // Continue with the next middleware | |
| 228 | 228 | return next(c) | |
| 229 | 229 | } | |
| @@ -253,7 +253,7 @@ func (p *Proxy) createBlacklistMiddleware() echo.MiddlewareFunc { | |||
| 253 | 253 | } | |
| 254 | 254 | ||
| 255 | 255 | // Debug log for all requests to check if the IP is being properly recognized | |
| 256 | - | log.Debug("Received request", "ip", clientIP, "path", c.Request().URL.Path) | |
| 256 | + | logger.Debug("Received request", "ip", clientIP, "path", c.Request().URL.Path) | |
| 257 | 257 | ||
| 258 | 258 | // Check if IP is blacklisted (if blacklist exists) | |
| 259 | 259 | if p.blacklist != nil && p.blacklist.IsBlocked(clientIP) { | |
| @@ -6,7 +6,7 @@ import ( | |||||
| 6 | "strings" | 6 | "strings" | ||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | - | "github.com/charmbracelet/log" | 9 | + | "github.com/bnema/gordon/pkg/logger" |
| 10 | "github.com/google/uuid" | 10 | "github.com/google/uuid" | ||
| 11 | "github.com/labstack/echo/v4" | 11 | "github.com/labstack/echo/v4" | ||
| 12 | "github.com/labstack/echo/v4/middleware" | 12 | "github.com/labstack/echo/v4/middleware" | ||
| @@ -33,7 +33,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 33 | // Since we are the reverse proxy, we need to make sure we identify client IPs correctly | 33 | // Since we are the reverse proxy, we need to make sure we identify client IPs correctly | ||
| 34 | p.httpsServer.IPExtractor = echo.ExtractIPFromXFFHeader() | 34 | p.httpsServer.IPExtractor = echo.ExtractIPFromXFFHeader() | ||
| 35 | p.httpServer.IPExtractor = echo.ExtractIPFromXFFHeader() | 35 | p.httpServer.IPExtractor = echo.ExtractIPFromXFFHeader() | ||
| 36 | - | 36 | + | ||
| 37 | // Add UUID generator middleware for both HTTP and HTTPS servers | 37 | // Add UUID generator middleware for both HTTP and HTTPS servers | ||
| 38 | p.httpsServer.Use(p.createRequestIDMiddleware()) | 38 | p.httpsServer.Use(p.createRequestIDMiddleware()) | ||
| 39 | p.httpServer.Use(p.createRequestIDMiddleware()) | 39 | p.httpServer.Use(p.createRequestIDMiddleware()) | ||
| @@ -42,7 +42,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 42 | p.httpsServer.Use(func(next echo.HandlerFunc) echo.HandlerFunc { | 42 | p.httpsServer.Use(func(next echo.HandlerFunc) echo.HandlerFunc { | ||
| 43 | return func(c echo.Context) error { | 43 | return func(c echo.Context) error { | ||
| 44 | req := c.Request() | 44 | req := c.Request() | ||
| 45 | - | log.Debug("Request headers", | 45 | + | logger.Debug("Request headers", |
| 46 | "method", req.Method, | 46 | "method", req.Method, | ||
| 47 | "path", req.URL.Path, | 47 | "path", req.URL.Path, | ||
| 48 | "remote_addr", req.RemoteAddr, | 48 | "remote_addr", req.RemoteAddr, | ||
| @@ -63,7 +63,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 63 | 3*time.Minute, // Expires in 3 minutes | 63 | 3*time.Minute, // Expires in 3 minutes | ||
| 64 | ) | 64 | ) | ||
| 65 | if err != nil { | 65 | if err != nil { | ||
| 66 | - | log.Error("Failed to create HTTPS rate limiter store, falling back to memory store", "error", err) | 66 | + | logger.Error("Failed to create HTTPS rate limiter store, falling back to memory store", "error", err) |
| 67 | 67 | ||||
| 68 | // Fallback to memory store if Starskey store creation fails | 68 | // Fallback to memory store if Starskey store creation fails | ||
| 69 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | 69 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | ||
| @@ -75,7 +75,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 75 | }, | 75 | }, | ||
| 76 | ), | 76 | ), | ||
| 77 | DenyHandler: func(context echo.Context, identifier string, err error) error { | 77 | DenyHandler: func(context echo.Context, identifier string, err error) error { | ||
| 78 | - | log.Warn("Rate limit exceeded", | 78 | + | logger.Warn("Rate limit exceeded", |
| 79 | "ip", identifier, | 79 | "ip", identifier, | ||
| 80 | "path", context.Request().URL.Path, | 80 | "path", context.Request().URL.Path, | ||
| 81 | "method", context.Request().Method) | 81 | "method", context.Request().Method) | ||
| @@ -84,14 +84,14 @@ func (p *Proxy) setupMiddleware() { | |||||
| 84 | })) | 84 | })) | ||
| 85 | } else { | 85 | } else { | ||
| 86 | // Use the Starskey store for HTTPS rate limiting | 86 | // Use the Starskey store for HTTPS rate limiting | ||
| 87 | - | log.Debug("Using Starskey rate limiter for HTTPS server") | 87 | + | logger.Debug("Using Starskey rate limiter for HTTPS server") |
| 88 | 88 | ||||
| 89 | // Add custom Starskey rate limiter middleware | 89 | // Add custom Starskey rate limiter middleware | ||
| 90 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | 90 | p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | ||
| 91 | Store: httpsRateLimiter, | 91 | Store: httpsRateLimiter, | ||
| 92 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | 92 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | ||
| 93 | id := ctx.RealIP() | 93 | id := ctx.RealIP() | ||
| 94 | - | log.Debug("Rate limiting HTTPS request", | 94 | + | logger.Debug("Rate limiting HTTPS request", |
| 95 | "ip", id, | 95 | "ip", id, | ||
| 96 | "remote_addr", ctx.Request().RemoteAddr) | 96 | "remote_addr", ctx.Request().RemoteAddr) | ||
| 97 | 97 | ||||
| @@ -101,7 +101,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 101 | return id, nil | 101 | return id, nil | ||
| 102 | }, | 102 | }, | ||
| 103 | DenyHandler: func(context echo.Context, identifier string, err error) error { | 103 | DenyHandler: func(context echo.Context, identifier string, err error) error { | ||
| 104 | - | log.Warn("HTTPS rate limit exceeded", | 104 | + | logger.Warn("HTTPS rate limit exceeded", |
| 105 | "ip", identifier, | 105 | "ip", identifier, | ||
| 106 | "path", context.Request().URL.Path, | 106 | "path", context.Request().URL.Path, | ||
| 107 | "method", context.Request().Method) | 107 | "method", context.Request().Method) | ||
| @@ -112,7 +112,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 112 | // Add cleanup function to global slice | 112 | // Add cleanup function to global slice | ||
| 113 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | 113 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | ||
| 114 | if err := httpsRateLimiter.Close(); err != nil { | 114 | if err := httpsRateLimiter.Close(); err != nil { | ||
| 115 | - | log.Error("Failed to close HTTPS rate limiter store", "error", err) | 115 | + | logger.Error("Failed to close HTTPS rate limiter store", "error", err) |
| 116 | } | 116 | } | ||
| 117 | }) | 117 | }) | ||
| 118 | } | 118 | } | ||
| @@ -120,15 +120,15 @@ func (p *Proxy) setupMiddleware() { | |||||
| 120 | // Add custom logger middleware with request ID if enabled in config | 120 | // Add custom logger middleware with request ID if enabled in config | ||
| 121 | if p.config.EnableLogs { | 121 | if p.config.EnableLogs { | ||
| 122 | p.httpsServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | 122 | p.httpsServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | ||
| 123 | - | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | 123 | + | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", |
| 124 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | 124 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | ||
| 125 | Skipper: func(c echo.Context) bool { | 125 | Skipper: func(c echo.Context) bool { | ||
| 126 | return !p.config.EnableLogs | 126 | return !p.config.EnableLogs | ||
| 127 | }, | 127 | }, | ||
| 128 | })) | 128 | })) | ||
| 129 | - | log.Debug("HTTP request logging enabled for HTTPS server") | 129 | + | logger.Debug("HTTP request logging enabled for HTTPS server") |
| 130 | } else { | 130 | } else { | ||
| 131 | - | log.Debug("HTTP request logging disabled for HTTPS server") | 131 | + | logger.Debug("HTTP request logging disabled for HTTPS server") |
| 132 | } | 132 | } | ||
| 133 | 133 | ||||
| 134 | // Create blacklist middleware for HTTP | 134 | // Create blacklist middleware for HTTP | ||
| @@ -137,24 +137,24 @@ func (p *Proxy) setupMiddleware() { | |||||
| 137 | // Create a new Starskey rate limiter store for HTTP server | 137 | // Create a new Starskey rate limiter store for HTTP server | ||
| 138 | httpRateLimiter, err := kv.NewStarskeyRateLimiterStore( | 138 | httpRateLimiter, err := kv.NewStarskeyRateLimiterStore( | ||
| 139 | "./data/ratelimiter/http", // Path to store rate limit data | 139 | "./data/ratelimiter/http", // Path to store rate limit data | ||
| 140 | - | 2, // Rate: 2 requests per second (lowered from 10) | 140 | + | 10, // Rate: 10 requests per second (lowered from 10) |
| 141 | - | 10, // Burst: 10 requests (lowered from 30) | 141 | + | 30, // Burst: 30 requests (lowered from 30) |
| 142 | 3*time.Minute, // Expires in 3 minutes | 142 | 3*time.Minute, // Expires in 3 minutes | ||
| 143 | ) | 143 | ) | ||
| 144 | if err != nil { | 144 | if err != nil { | ||
| 145 | - | log.Error("Failed to create HTTP rate limiter store, falling back to memory store", "error", err) | 145 | + | logger.Error("Failed to create HTTP rate limiter store, falling back to memory store", "error", err) |
| 146 | 146 | ||||
| 147 | // Fallback to memory store if Starskey store creation fails | 147 | // Fallback to memory store if Starskey store creation fails | ||
| 148 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | 148 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | ||
| 149 | Store: middleware.NewRateLimiterMemoryStoreWithConfig( | 149 | Store: middleware.NewRateLimiterMemoryStoreWithConfig( | ||
| 150 | middleware.RateLimiterMemoryStoreConfig{ | 150 | middleware.RateLimiterMemoryStoreConfig{ | ||
| 151 | - | Rate: rate.Limit(2), // 2 requests per second (lowered from 10) | 151 | + | Rate: rate.Limit(10), // 10 requests per second (lowered from 10) |
| 152 | - | Burst: 10, // Burst of 10 requests (lowered from 30) | 152 | + | Burst: 30, // Burst of 30 requests (lowered from 30) |
| 153 | ExpiresIn: 3 * time.Minute, // Store expiration | 153 | ExpiresIn: 3 * time.Minute, // Store expiration | ||
| 154 | }, | 154 | }, | ||
| 155 | ), | 155 | ), | ||
| 156 | DenyHandler: func(context echo.Context, identifier string, err error) error { | 156 | DenyHandler: func(context echo.Context, identifier string, err error) error { | ||
| 157 | - | log.Warn("Rate limit exceeded", | 157 | + | logger.Warn("Rate limit exceeded", |
| 158 | "ip", identifier, | 158 | "ip", identifier, | ||
| 159 | "path", context.Request().URL.Path, | 159 | "path", context.Request().URL.Path, | ||
| 160 | "method", context.Request().Method) | 160 | "method", context.Request().Method) | ||
| @@ -163,14 +163,14 @@ func (p *Proxy) setupMiddleware() { | |||||
| 163 | })) | 163 | })) | ||
| 164 | } else { | 164 | } else { | ||
| 165 | // Use the Starskey store for HTTP rate limiting | 165 | // Use the Starskey store for HTTP rate limiting | ||
| 166 | - | log.Debug("Using Starskey rate limiter for HTTP server") | 166 | + | logger.Debug("Using Starskey rate limiter for HTTP server") |
| 167 | 167 | ||||
| 168 | // Add custom Starskey rate limiter middleware | 168 | // Add custom Starskey rate limiter middleware | ||
| 169 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | 169 | p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{ | ||
| 170 | Store: httpRateLimiter, | 170 | Store: httpRateLimiter, | ||
| 171 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | 171 | IdentifierExtractor: func(ctx echo.Context) (string, error) { | ||
| 172 | id := ctx.RealIP() | 172 | id := ctx.RealIP() | ||
| 173 | - | log.Debug("Rate limiting HTTP request", | 173 | + | logger.Debug("Rate limiting HTTP request", |
| 174 | "ip", id, | 174 | "ip", id, | ||
| 175 | "remote_addr", ctx.Request().RemoteAddr) | 175 | "remote_addr", ctx.Request().RemoteAddr) | ||
| 176 | 176 | ||||
| @@ -180,7 +180,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 180 | return id, nil | 180 | return id, nil | ||
| 181 | }, | 181 | }, | ||
| 182 | DenyHandler: func(context echo.Context, identifier string, err error) error { | 182 | DenyHandler: func(context echo.Context, identifier string, err error) error { | ||
| 183 | - | log.Warn("HTTP rate limit exceeded", | 183 | + | logger.Warn("HTTP rate limit exceeded", |
| 184 | "ip", identifier, | 184 | "ip", identifier, | ||
| 185 | "path", context.Request().URL.Path, | 185 | "path", context.Request().URL.Path, | ||
| 186 | "method", context.Request().Method) | 186 | "method", context.Request().Method) | ||
| @@ -191,7 +191,7 @@ func (p *Proxy) setupMiddleware() { | |||||
| 191 | // Add cleanup function to global slice | 191 | // Add cleanup function to global slice | ||
| 192 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | 192 | rateLimiterCleanup = append(rateLimiterCleanup, func() { | ||
| 193 | if err := httpRateLimiter.Close(); err != nil { | 193 | if err := httpRateLimiter.Close(); err != nil { | ||
| 194 | - | log.Error("Failed to close HTTP rate limiter store", "error", err) | 194 | + | logger.Error("Failed to close HTTP rate limiter store", "error", err) |
| 195 | } | 195 | } | ||
| 196 | }) | 196 | }) | ||
| 197 | } | 197 | } | ||
| @@ -199,15 +199,15 @@ func (p *Proxy) setupMiddleware() { | |||||
| 199 | // Add custom logger middleware with request ID for HTTP server if enabled in config | 199 | // Add custom logger middleware with request ID for HTTP server if enabled in config | ||
| 200 | if p.config.EnableLogs { | 200 | if p.config.EnableLogs { | ||
| 201 | p.httpServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | 201 | p.httpServer.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ | ||
| 202 | - | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", | 202 | + | Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}","host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}","status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}","bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", |
| 203 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | 203 | CustomTimeFormat: "2006-01-02T15:04:05.00000Z07:00", | ||
| 204 | Skipper: func(c echo.Context) bool { | 204 | Skipper: func(c echo.Context) bool { | ||
| 205 | return !p.config.EnableLogs | 205 | return !p.config.EnableLogs | ||
| 206 | }, | 206 | }, | ||
| 207 | })) | 207 | })) | ||
| 208 | - | log.Debug("HTTP request logging enabled for HTTP server") | 208 | + | logger.Debug("HTTP request logging enabled for HTTP server") |
| 209 | } else { | 209 | } else { | ||
| 210 | - | log.Debug("HTTP request logging disabled for HTTP server") | 210 | + | logger.Debug("HTTP request logging disabled for HTTP server") |
| 211 | } | 211 | } | ||
| 212 | } | 212 | } | ||
| 213 | 213 | ||||
| @@ -217,13 +217,13 @@ func (p *Proxy) createRequestIDMiddleware() echo.MiddlewareFunc { | |||||
| 217 | return func(c echo.Context) error { | 217 | return func(c echo.Context) error { | ||
| 218 | // Generate a new UUID for this request | 218 | // Generate a new UUID for this request | ||
| 219 | requestID := uuid.New().String() | 219 | requestID := uuid.New().String() | ||
| 220 | - | 220 | + | ||
| 221 | // Store the UUID in the context | 221 | // Store the UUID in the context | ||
| 222 | c.Set(RequestIDKey, requestID) | 222 | c.Set(RequestIDKey, requestID) | ||
| 223 | - | 223 | + | ||
| 224 | // Add the UUID as a response header | 224 | // Add the UUID as a response header | ||
| 225 | c.Response().Header().Set(echo.HeaderXRequestID, requestID) | 225 | c.Response().Header().Set(echo.HeaderXRequestID, requestID) | ||
| 226 | - | 226 | + | ||
| 227 | // Continue with the next middleware | 227 | // Continue with the next middleware | ||
| 228 | return next(c) | 228 | return next(c) | ||
| 229 | } | 229 | } | ||
| @@ -253,7 +253,7 @@ func (p *Proxy) createBlacklistMiddleware() echo.MiddlewareFunc { | |||||
| 253 | } | 253 | } | ||
| 254 | 254 | ||||
| 255 | // Debug log for all requests to check if the IP is being properly recognized | 255 | // Debug log for all requests to check if the IP is being properly recognized | ||
| 256 | - | log.Debug("Received request", "ip", clientIP, "path", c.Request().URL.Path) | 256 | + | logger.Debug("Received request", "ip", clientIP, "path", c.Request().URL.Path) |
| 257 | 257 | ||||
| 258 | // Check if IP is blacklisted (if blacklist exists) | 258 | // Check if IP is blacklisted (if blacklist exists) | ||
| 259 | if p.blacklist != nil && p.blacklist.IsBlocked(clientIP) { | 259 | if p.blacklist != nil && p.blacklist.IsBlocked(clientIP) { | ||
M internal/proxy/routes.go +381 -711
| @@ -12,7 +12,7 @@ import ( | |||
| 12 | 12 | "time" | |
| 13 | 13 | ||
| 14 | 14 | "github.com/bnema/gordon/pkg/docker" | |
| 15 | - | "github.com/charmbracelet/log" | |
| 15 | + | "github.com/bnema/gordon/pkg/logger" | |
| 16 | 16 | "github.com/labstack/echo/v4" | |
| 17 | 17 | ) | |
| 18 | 18 | ||
| @@ -33,12 +33,12 @@ func (p *Proxy) loadRoutes() error { | |||
| 33 | 33 | p.mu.Lock() | |
| 34 | 34 | defer p.mu.Unlock() | |
| 35 | 35 | ||
| 36 | - | log.Debug("Loading proxy routes from database") | |
| 36 | + | logger.Debug("Loading proxy routes from database") | |
| 37 | 37 | ||
| 38 | 38 | // Now check if we have a Gordon container ID stored for identity purposes | |
| 39 | - | p.gordonContainerID = p.loadGordonContainerID() | |
| 39 | + | p.gordonContainerID = p.detectGordonContainer() | |
| 40 | 40 | if p.gordonContainerID != "" { | |
| 41 | - | log.Info("Loaded Gordon container ID from config file during route initialization", "container_id", p.gordonContainerID) | |
| 41 | + | logger.Info("Loaded Gordon container ID during route initialization", "container_id", p.gordonContainerID) | |
| 42 | 42 | } | |
| 43 | 43 | ||
| 44 | 44 | // Save the admin domain route if it exists | |
| @@ -46,11 +46,11 @@ func (p *Proxy) loadRoutes() error { | |||
| 46 | 46 | var adminRoute *ProxyRouteInfo | |
| 47 | 47 | if route, exists := p.routes[adminDomain]; exists { | |
| 48 | 48 | adminRoute = route | |
| 49 | - | log.Debug("Preserving admin domain route", "domain", adminDomain) | |
| 49 | + | logger.Debug("Preserving admin domain route", "domain", adminDomain) | |
| 50 | 50 | } | |
| 51 | 51 | ||
| 52 | - | // Query the database for active proxy routes | |
| 53 | - | rows, err := p.app.GetDB().Query(` | |
| 52 | + | // Query the database for active proxy routes using retry mechanism | |
| 53 | + | rows, err := p.dbQueryWithRetry(` | |
| 54 | 54 | SELECT pr.id, d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path, pr.active | |
| 55 | 55 | FROM proxy_route pr | |
| 56 | 56 | JOIN domain d ON pr.domain_id = d.id | |
| @@ -58,13 +58,13 @@ func (p *Proxy) loadRoutes() error { | |||
| 58 | 58 | `) | |
| 59 | 59 | if err != nil { | |
| 60 | 60 | if err == sql.ErrNoRows { | |
| 61 | - | log.Info("No active proxy routes found") | |
| 61 | + | logger.Info("No active proxy routes found") | |
| 62 | 62 | ||
| 63 | 63 | // Restore admin route if it was saved | |
| 64 | 64 | if adminRoute != nil { | |
| 65 | 65 | p.routes = make(map[string]*ProxyRouteInfo) | |
| 66 | 66 | p.routes[adminDomain] = adminRoute | |
| 67 | - | log.Debug("Restored admin domain route after empty database query", "domain", adminDomain) | |
| 67 | + | logger.Debug("Restored admin domain route after empty database query", "domain", adminDomain) | |
| 68 | 68 | } | |
| 69 | 69 | ||
| 70 | 70 | return nil | |
| @@ -79,7 +79,7 @@ func (p *Proxy) loadRoutes() error { | |||
| 79 | 79 | // Restore admin route if it was saved | |
| 80 | 80 | if adminRoute != nil { | |
| 81 | 81 | p.routes[adminDomain] = adminRoute | |
| 82 | - | log.Debug("Restored admin domain route after clearing routes map", "domain", adminDomain) | |
| 82 | + | logger.Debug("Restored admin domain route after clearing routes map", "domain", adminDomain) | |
| 83 | 83 | } | |
| 84 | 84 | ||
| 85 | 85 | // Populate the routes map | |
| @@ -90,6 +90,9 @@ func (p *Proxy) loadRoutes() error { | |||
| 90 | 90 | return fmt.Errorf("failed to scan row: %w", err) | |
| 91 | 91 | } | |
| 92 | 92 | ||
| 93 | + | // Normalize domain (lowercase, no trailing dot) | |
| 94 | + | domain = strings.TrimSuffix(strings.ToLower(domain), ".") | |
| 95 | + | ||
| 93 | 96 | // Add the route to the map | |
| 94 | 97 | p.routes[domain] = &ProxyRouteInfo{ | |
| 95 | 98 | Domain: domain, | |
| @@ -101,14 +104,14 @@ func (p *Proxy) loadRoutes() error { | |||
| 101 | 104 | Active: active, | |
| 102 | 105 | } | |
| 103 | 106 | ||
| 104 | - | log.Debug("Loaded proxy route", | |
| 107 | + | logger.Debug("Loaded proxy route", | |
| 105 | 108 | "domain", domain, | |
| 106 | 109 | "containerIP", containerIP, | |
| 107 | 110 | "containerPort", containerPort, | |
| 108 | 111 | ) | |
| 109 | 112 | } | |
| 110 | 113 | ||
| 111 | - | log.Info("Loaded proxy routes", "count", len(p.routes)) | |
| 114 | + | logger.Info("Loaded proxy routes", "count", len(p.routes)) | |
| 112 | 115 | return nil | |
| 113 | 116 | } | |
| 114 | 117 | ||
| @@ -116,7 +119,7 @@ func (p *Proxy) loadRoutes() error { | |||
| 116 | 119 | func (p *Proxy) configureRoutes() { | |
| 117 | 120 | // Get the admin domain from the config | |
| 118 | 121 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 119 | - | log.Debug("Configuring proxy routes", "admin_domain", adminDomain) | |
| 122 | + | logger.Debug("Configuring proxy routes", "admin_domain", adminDomain) | |
| 120 | 123 | ||
| 121 | 124 | // Verify admin domain is in routes - if not, we need to add it | |
| 122 | 125 | p.mu.RLock() | |
| @@ -124,7 +127,7 @@ func (p *Proxy) configureRoutes() { | |||
| 124 | 127 | p.mu.RUnlock() | |
| 125 | 128 | ||
| 126 | 129 | if !adminRouteExists { | |
| 127 | - | log.Warn("Admin domain route is missing, attempting to recreate it") | |
| 130 | + | logger.Warn("Admin domain route is missing, attempting to recreate it") | |
| 128 | 131 | p.mu.Lock() | |
| 129 | 132 | // Auto-detect the Gordon container name | |
| 130 | 133 | containerName := p.detectGordonContainer() | |
| @@ -151,18 +154,60 @@ func (p *Proxy) configureRoutes() { | |||
| 151 | 154 | Path: "/", | |
| 152 | 155 | Active: true, | |
| 153 | 156 | } | |
| 154 | - | log.Info("Recreated admin domain route", | |
| 157 | + | logger.Info("Recreated admin domain route", | |
| 155 | 158 | "domain", adminDomain, | |
| 156 | 159 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | |
| 157 | 160 | p.mu.Unlock() | |
| 158 | 161 | } else { | |
| 159 | - | log.Debug("Admin domain route exists", "domain", adminDomain) | |
| 162 | + | logger.Debug("Admin domain route exists", "domain", adminDomain) | |
| 160 | 163 | } | |
| 161 | 164 | ||
| 162 | 165 | // Handler function - will be assigned to both HTTP and HTTPS servers | |
| 163 | 166 | handler := echo.HandlerFunc(func(c echo.Context) error { | |
| 164 | 167 | host := c.Request().Host | |
| 165 | 168 | ||
| 169 | + | // Strip port if present in the host | |
| 170 | + | if hostParts := strings.Split(host, ":"); len(hostParts) > 1 { | |
| 171 | + | host = hostParts[0] | |
| 172 | + | } | |
| 173 | + | ||
| 174 | + | // Normalize host (trim trailing dot, convert to lowercase) | |
| 175 | + | host = strings.TrimSuffix(strings.ToLower(host), ".") | |
| 176 | + | ||
| 177 | + | // Special handling for debug: log all request details | |
| 178 | + | logger.Debug("Request headers for proxy", | |
| 179 | + | "domain", host, | |
| 180 | + | "X-Forwarded-For", c.Request().Header.Get("X-Forwarded-For"), | |
| 181 | + | "X-Real-IP", c.Request().Header.Get("X-Real-IP")) | |
| 182 | + | ||
| 183 | + | // Check if this is the admin domain | |
| 184 | + | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 185 | + | if strings.EqualFold(host, adminDomain) { | |
| 186 | + | // Always prioritize admin domain | |
| 187 | + | p.mu.RLock() | |
| 188 | + | adminRoute, adminOk := p.routes[adminDomain] | |
| 189 | + | p.mu.RUnlock() | |
| 190 | + | ||
| 191 | + | if adminOk { | |
| 192 | + | logger.Debug("Proxying request to admin domain", | |
| 193 | + | "domain", host, | |
| 194 | + | "target", fmt.Sprintf("%s://%s:%s", adminRoute.Protocol, adminRoute.ContainerIP, adminRoute.ContainerPort), | |
| 195 | + | "path", c.Request().URL.Path) | |
| 196 | + | ||
| 197 | + | // Special logging for admin domain handling | |
| 198 | + | logger.Debug("Proxying request", | |
| 199 | + | "host", host, | |
| 200 | + | "target", fmt.Sprintf("%s://%s:%s", adminRoute.Protocol, adminRoute.ContainerIP, adminRoute.ContainerPort), | |
| 201 | + | "path", c.Request().URL.Path, | |
| 202 | + | "clientIP", c.RealIP(), | |
| 203 | + | "remoteAddr", c.Request().RemoteAddr, | |
| 204 | + | "originalProtocol", c.Scheme()) | |
| 205 | + | ||
| 206 | + | // Use the admin route | |
| 207 | + | return p.proxyRequest(c, adminRoute) | |
| 208 | + | } | |
| 209 | + | } | |
| 210 | + | ||
| 166 | 211 | // Important: Get the route freshly from the map for every request | |
| 167 | 212 | // This ensures we always use the most up-to-date IP | |
| 168 | 213 | p.mu.RLock() | |
| @@ -185,7 +230,7 @@ func (p *Proxy) configureRoutes() { | |||
| 185 | 230 | p.mu.RUnlock() | |
| 186 | 231 | ||
| 187 | 232 | // Log warning for non-IP hosts that aren't configured | |
| 188 | - | log.Warn("Request with unknown host", | |
| 233 | + | logger.Warn("Request with unknown host", | |
| 189 | 234 | "requested_host", host, | |
| 190 | 235 | "client_ip", c.RealIP(), | |
| 191 | 236 | "available_domains", strings.Join(availableDomains, ", ")) | |
| @@ -194,164 +239,22 @@ func (p *Proxy) configureRoutes() { | |||
| 194 | 239 | ||
| 195 | 240 | // Check if the route is active | |
| 196 | 241 | if !route.Active { | |
| 197 | - | log.Warn("Route is not active", | |
| 242 | + | logger.Warn("Route is not active", | |
| 198 | 243 | "domain", host, | |
| 199 | 244 | "client_ip", c.RealIP()) | |
| 200 | 245 | return c.String(http.StatusServiceUnavailable, "Route is not active") | |
| 201 | 246 | } | |
| 202 | 247 | ||
| 203 | - | // Special handling for the admin domain | |
| 204 | - | if host == adminDomain { | |
| 205 | - | log.Debug("Proxying request to admin domain", | |
| 206 | - | "domain", host, | |
| 207 | - | "target", fmt.Sprintf("%s://%s:%s", route.Protocol, route.ContainerIP, route.ContainerPort), | |
| 208 | - | "path", c.Request().URL.Path) | |
| 209 | - | } | |
| 210 | - | ||
| 211 | - | // For container routes, verify the IP is reachable before proxying | |
| 212 | - | if host != adminDomain { | |
| 213 | - | // Test TCP connection to container before proxying | |
| 214 | - | dialer := net.Dialer{Timeout: 500 * time.Millisecond} | |
| 215 | - | // Format address properly for both IPv4 and IPv6 | |
| 216 | - | target := route.ContainerIP | |
| 217 | - | if strings.Contains(target, ":") { | |
| 218 | - | // IPv6 address needs to be wrapped in brackets | |
| 219 | - | target = "[" + target + "]" | |
| 220 | - | } | |
| 221 | - | conn, err := dialer.Dial("tcp", net.JoinHostPort(target, route.ContainerPort)) | |
| 222 | - | if err != nil { | |
| 223 | - | // Connection failed, check if container exists but has a new IP | |
| 224 | - | containerID := route.ContainerID | |
| 225 | - | if containerID != "" { | |
| 226 | - | // Try to get container info and check if it has a different IP | |
| 227 | - | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 228 | - | if err == nil && containerInfo.NetworkSettings != nil { | |
| 229 | - | // Check if container has a different IP | |
| 230 | - | networkName := p.app.GetConfig().ContainerEngine.Network | |
| 231 | - | if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && | |
| 232 | - | networkSettings.IPAddress != "" && networkSettings.IPAddress != route.ContainerIP { | |
| 233 | - | // Found container with different IP, update the route and use new IP for this request | |
| 234 | - | log.Warn("Container IP mismatch detected, using live IP instead of cached value", | |
| 235 | - | "domain", host, | |
| 236 | - | "cached_ip", route.ContainerIP, | |
| 237 | - | "live_ip", networkSettings.IPAddress) | |
| 238 | - | ||
| 239 | - | // Update the in-memory route for this request | |
| 240 | - | route.ContainerIP = networkSettings.IPAddress | |
| 241 | - | ||
| 242 | - | // Update the database in background to persist the change | |
| 243 | - | go func(domain, containerID, containerIP, containerPort string) { | |
| 244 | - | // Get the domain ID | |
| 245 | - | var domainID string | |
| 246 | - | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |
| 247 | - | if err != nil { | |
| 248 | - | log.Error("Failed to find domain ID for IP update", "domain", domain, "error", err) | |
| 249 | - | return | |
| 250 | - | } | |
| 251 | - | ||
| 252 | - | // Update the proxy_route record | |
| 253 | - | now := time.Now().Format(time.RFC3339) | |
| 254 | - | _, err = p.app.GetDB().Exec(` | |
| 255 | - | UPDATE proxy_route SET container_ip = ?, updated_at = ? | |
| 256 | - | WHERE domain_id = ? AND container_id = ?`, | |
| 257 | - | containerIP, now, domainID, containerID) | |
| 258 | - | ||
| 259 | - | if err != nil { | |
| 260 | - | log.Error("Failed to update container IP in database", | |
| 261 | - | "domain", domain, | |
| 262 | - | "containerID", containerID, | |
| 263 | - | "error", err) | |
| 264 | - | } else { | |
| 265 | - | log.Info("Updated container IP in database after live detection", | |
| 266 | - | "domain", domain, | |
| 267 | - | "containerID", containerID, | |
| 268 | - | "old_ip", route.ContainerIP, | |
| 269 | - | "new_ip", containerIP) | |
| 270 | - | } | |
| 271 | - | }(host, containerID, networkSettings.IPAddress, route.ContainerPort) | |
| 272 | - | } else { | |
| 273 | - | log.Debug("Container exists but connection still failed", | |
| 274 | - | "domain", host, | |
| 275 | - | "container_id", containerID, | |
| 276 | - | "container_ip", route.ContainerIP, | |
| 277 | - | "container_port", route.ContainerPort, | |
| 278 | - | "error", err) | |
| 279 | - | } | |
| 280 | - | } | |
| 281 | - | } | |
| 282 | - | } else { | |
| 283 | - | // Connection successful, close it | |
| 284 | - | conn.Close() | |
| 285 | - | } | |
| 286 | - | } | |
| 287 | - | ||
| 288 | - | // Create the target URL | |
| 289 | - | targetURL := &url.URL{ | |
| 290 | - | Scheme: "http", // Always use HTTP for internal connections | |
| 291 | - | } | |
| 248 | + | // Regular debug logging for all domains | |
| 249 | + | logger.Debug("Proxying request", | |
| 250 | + | "host", host, | |
| 251 | + | "target", fmt.Sprintf("%s://%s:%s", route.Protocol, route.ContainerIP, route.ContainerPort), | |
| 252 | + | "path", c.Request().URL.Path, | |
| 253 | + | "clientIP", c.RealIP(), | |
| 254 | + | "remoteAddr", c.Request().RemoteAddr, | |
| 255 | + | "originalProtocol", c.Scheme()) | |
| 292 | 256 | ||
| 293 | - | // Log protocol conversion if needed | |
| 294 | - | if route.Protocol == "https" { | |
| 295 | - | log.Debug("Converting HTTPS external route to HTTP for internal connection", | |
| 296 | - | "domain", host, | |
| 297 | - | "containerID", route.ContainerID, | |
| 298 | - | "externalProtocol", "https", | |
| 299 | - | "internalProtocol", "http") | |
| 300 | - | } | |
| 301 | - | ||
| 302 | - | // Format host properly for both IPv4 and IPv6 | |
| 303 | - | containerIP := route.ContainerIP | |
| 304 | - | if strings.Contains(containerIP, ":") { | |
| 305 | - | // IPv6 address needs to be wrapped in brackets | |
| 306 | - | containerIP = "[" + containerIP + "]" | |
| 307 | - | } | |
| 308 | - | targetURL.Host = fmt.Sprintf("%s:%s", containerIP, route.ContainerPort) | |
| 309 | - | ||
| 310 | - | // Create a reverse proxy | |
| 311 | - | proxy := httputil.NewSingleHostReverseProxy(targetURL) | |
| 312 | - | ||
| 313 | - | // Update headers to allow for SSL redirection | |
| 314 | - | originalDirector := proxy.Director | |
| 315 | - | proxy.Director = func(req *http.Request) { | |
| 316 | - | originalDirector(req) | |
| 317 | - | // Use the original protocol for forwarded proto header | |
| 318 | - | forwardedProto := "http" | |
| 319 | - | if route.Protocol == "https" { | |
| 320 | - | forwardedProto = "https" | |
| 321 | - | } | |
| 322 | - | req.Header.Set("X-Forwarded-Proto", forwardedProto) | |
| 323 | - | req.Header.Set("X-Forwarded-Host", host) | |
| 324 | - | req.Header.Set("X-Forwarded-For", c.RealIP()) | |
| 325 | - | req.Header.Set("X-Real-IP", c.RealIP()) | |
| 326 | - | ||
| 327 | - | // Debug information | |
| 328 | - | log.Debug("Proxying request", | |
| 329 | - | "host", host, | |
| 330 | - | "target", targetURL.String(), | |
| 331 | - | "path", req.URL.Path, | |
| 332 | - | "clientIP", c.RealIP(), | |
| 333 | - | "remoteAddr", req.RemoteAddr, | |
| 334 | - | "originalProtocol", route.Protocol) | |
| 335 | - | ||
| 336 | - | // Log all headers for troubleshooting | |
| 337 | - | log.Debug("Request headers for proxy", | |
| 338 | - | "domain", host, | |
| 339 | - | "X-Forwarded-For", req.Header.Get("X-Forwarded-For"), | |
| 340 | - | "X-Real-IP", req.Header.Get("X-Real-IP")) | |
| 341 | - | } | |
| 342 | - | ||
| 343 | - | // Add error handling | |
| 344 | - | proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { | |
| 345 | - | log.Error("Proxy error", | |
| 346 | - | "host", host, | |
| 347 | - | "path", r.URL.Path, | |
| 348 | - | "error", err) | |
| 349 | - | c.String(http.StatusBadGateway, "Proxy Error: "+err.Error()) | |
| 350 | - | } | |
| 351 | - | ||
| 352 | - | // Serve the request | |
| 353 | - | proxy.ServeHTTP(c.Response(), c.Request()) | |
| 354 | - | return nil | |
| 257 | + | return p.proxyRequest(c, route) | |
| 355 | 258 | }) | |
| 356 | 259 | ||
| 357 | 260 | // Add the handler to the HTTPS server | |
| @@ -370,8 +273,19 @@ func (p *Proxy) configureRoutes() { | |||
| 370 | 273 | // Note: Regardless of the external protocol, all internal connections to containers use HTTP. | |
| 371 | 274 | // The HTTPS protocol is only used for external connections and certificates. | |
| 372 | 275 | func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, protocol, path string) error { | |
| 373 | - | // Begin a transaction | |
| 374 | - | tx, err := p.app.GetDB().Begin() | |
| 276 | + | // Normalize domain name (lowercase, no trailing dot) | |
| 277 | + | domainName = strings.TrimSuffix(strings.ToLower(domainName), ".") | |
| 278 | + | ||
| 279 | + | logger.Debug("Adding proxy route", | |
| 280 | + | "domain", domainName, | |
| 281 | + | "container_id", containerID, | |
| 282 | + | "container_ip", containerIP, | |
| 283 | + | "container_port", containerPort, | |
| 284 | + | "protocol", protocol, | |
| 285 | + | "path", path) | |
| 286 | + | ||
| 287 | + | // Begin a transaction with retry | |
| 288 | + | tx, err := p.dbBeginWithRetry() | |
| 375 | 289 | if err != nil { | |
| 376 | 290 | return fmt.Errorf("failed to begin transaction: %w", err) | |
| 377 | 291 | } | |
| @@ -391,9 +305,9 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 391 | 305 | err = tx.QueryRow("SELECT id FROM account LIMIT 1").Scan(&accountID) | |
| 392 | 306 | if err != nil { | |
| 393 | 307 | if err == sql.ErrNoRows { | |
| 394 | - | log.Warn("No account found when adding route, domains will have NULL account_id") | |
| 308 | + | logger.Warn("No account found when adding route, domains will have NULL account_id") | |
| 395 | 309 | } else { | |
| 396 | - | log.Warn("Error fetching account for route, continuing anyway", "error", err) | |
| 310 | + | logger.Warn("Error fetching account for route, continuing anyway", "error", err) | |
| 397 | 311 | } | |
| 398 | 312 | // Continue with NULL account_id if no account found | |
| 399 | 313 | } | |
| @@ -406,7 +320,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 406 | 320 | // Domain doesn't exist, create it | |
| 407 | 321 | domainID = proxyGenerateUUID() | |
| 408 | 322 | now := time.Now().Format(time.RFC3339) | |
| 409 | - | _, err = tx.Exec( | |
| 323 | + | _, err = txExecWithRetry(tx, | |
| 410 | 324 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | |
| 411 | 325 | domainID, hostname, accountID, now, now, | |
| 412 | 326 | ) | |
| @@ -414,7 +328,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 414 | 328 | return fmt.Errorf("failed to insert domain: %w", err) | |
| 415 | 329 | } | |
| 416 | 330 | ||
| 417 | - | log.Debug("Added domain to database for Let's Encrypt", | |
| 331 | + | logger.Debug("Added domain to database for Let's Encrypt", | |
| 418 | 332 | "domain", hostname) | |
| 419 | 333 | ||
| 420 | 334 | // Also add the subdomain.domain format to the database if this is a domain with components | |
| @@ -432,16 +346,16 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 432 | 346 | if err == sql.ErrNoRows { | |
| 433 | 347 | // Domain part doesn't exist, create it | |
| 434 | 348 | domainPartID = proxyGenerateUUID() | |
| 435 | - | _, err = tx.Exec( | |
| 349 | + | _, err = txExecWithRetry(tx, | |
| 436 | 350 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | |
| 437 | 351 | domainPartID, domainPart, accountID, now, now, | |
| 438 | 352 | ) | |
| 439 | 353 | if err != nil { | |
| 440 | - | log.Warn("Failed to insert domain part, continuing anyway", | |
| 354 | + | logger.Warn("Failed to insert domain part, continuing anyway", | |
| 441 | 355 | "domain_part", domainPart, | |
| 442 | 356 | "error", err) | |
| 443 | 357 | } else { | |
| 444 | - | log.Debug("Added domain part to database for Let's Encrypt", | |
| 358 | + | logger.Debug("Added domain part to database for Let's Encrypt", | |
| 445 | 359 | "domain_part", domainPart, | |
| 446 | 360 | "subdomain", subdomain) | |
| 447 | 361 | } | |
| @@ -468,20 +382,20 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 468 | 382 | ||
| 469 | 383 | // Log more detailed information about the update | |
| 470 | 384 | if existingContainerID != containerID { | |
| 471 | - | log.Info("Updating proxy route for recreated container", | |
| 385 | + | logger.Info("Updating proxy route for recreated container", | |
| 472 | 386 | "domain", hostname, | |
| 473 | 387 | "old_container_id", existingContainerID, | |
| 474 | 388 | "new_container_id", containerID) | |
| 475 | 389 | } | |
| 476 | 390 | ||
| 477 | 391 | if existingContainerPort != containerPort { | |
| 478 | - | log.Info("Container port changed for domain", | |
| 392 | + | logger.Info("Container port changed for domain", | |
| 479 | 393 | "domain", hostname, | |
| 480 | 394 | "old_port", existingContainerPort, | |
| 481 | 395 | "new_port", containerPort) | |
| 482 | 396 | } | |
| 483 | 397 | ||
| 484 | - | _, err = tx.Exec( | |
| 398 | + | _, err = txExecWithRetry(tx, | |
| 485 | 399 | `UPDATE proxy_route SET | |
| 486 | 400 | container_id = ?, | |
| 487 | 401 | container_ip = ?, | |
| @@ -500,7 +414,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 500 | 414 | // Route doesn't exist, create it | |
| 501 | 415 | routeID := proxyGenerateUUID() | |
| 502 | 416 | now := time.Now().Format(time.RFC3339) | |
| 503 | - | _, err = tx.Exec( | |
| 417 | + | _, err = txExecWithRetry(tx, | |
| 504 | 418 | `INSERT INTO proxy_route ( | |
| 505 | 419 | id, domain_id, container_id, container_ip, container_port, | |
| 506 | 420 | protocol, path, active, created_at, updated_at | |
| @@ -525,7 +439,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 525 | 439 | return fmt.Errorf("failed to reload routes: %w", err) | |
| 526 | 440 | } | |
| 527 | 441 | ||
| 528 | - | log.Info("Added proxy route", | |
| 442 | + | logger.Info("Added proxy route", | |
| 529 | 443 | "domain", hostname, | |
| 530 | 444 | "containerIP", containerIP, | |
| 531 | 445 | "containerPort", containerPort, | |
| @@ -533,7 +447,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||
| 533 | 447 | ||
| 534 | 448 | // Request a certificate if this is an HTTPS route | |
| 535 | 449 | if strings.ToLower(protocol) == "https" { | |
| 536 | - | log.Info("Requesting Let's Encrypt certificate for new HTTPS route", | |
| 450 | + | logger.Info("Requesting Let's Encrypt certificate for new HTTPS route", | |
| 537 | 451 | "domain", hostname) | |
| 538 | 452 | // Run in a goroutine to avoid blocking | |
| 539 | 453 | go func(domain string) { | |
| @@ -554,13 +468,13 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||
| 554 | 468 | // Prevent removing the admin domain | |
| 555 | 469 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 556 | 470 | if domainName == adminDomain { | |
| 557 | - | log.Warn("Attempt to remove admin domain route prevented", | |
| 471 | + | logger.Warn("Attempt to remove admin domain route prevented", | |
| 558 | 472 | "domain", domainName) | |
| 559 | 473 | return fmt.Errorf("cannot remove admin domain route: %s", domainName) | |
| 560 | 474 | } | |
| 561 | 475 | ||
| 562 | - | // Begin a transaction | |
| 563 | - | tx, err := p.app.GetDB().Begin() | |
| 476 | + | // Begin a transaction with retry | |
| 477 | + | tx, err := p.dbBeginWithRetry() | |
| 564 | 478 | if err != nil { | |
| 565 | 479 | return fmt.Errorf("failed to begin transaction: %w", err) | |
| 566 | 480 | } | |
| @@ -576,14 +490,14 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||
| 576 | 490 | return fmt.Errorf("failed to query domain: %w", err) | |
| 577 | 491 | } | |
| 578 | 492 | ||
| 579 | - | // Delete the route | |
| 580 | - | _, err = tx.Exec("DELETE FROM proxy_route WHERE domain_id = ?", domainID) | |
| 493 | + | // Delete the route with retry | |
| 494 | + | _, err = txExecWithRetry(tx, "DELETE FROM proxy_route WHERE domain_id = ?", domainID) | |
| 581 | 495 | if err != nil { | |
| 582 | 496 | return fmt.Errorf("failed to delete route: %w", err) | |
| 583 | 497 | } | |
| 584 | 498 | ||
| 585 | - | // Delete the domain | |
| 586 | - | _, err = tx.Exec("DELETE FROM domain WHERE id = ?", domainID) | |
| 499 | + | // Delete the domain with retry | |
| 500 | + | _, err = txExecWithRetry(tx, "DELETE FROM domain WHERE id = ?", domainID) | |
| 587 | 501 | if err != nil { | |
| 588 | 502 | return fmt.Errorf("failed to delete domain: %w", err) | |
| 589 | 503 | } | |
| @@ -598,7 +512,7 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||
| 598 | 512 | return fmt.Errorf("failed to reload routes: %w", err) | |
| 599 | 513 | } | |
| 600 | 514 | ||
| 601 | - | log.Info("Removed proxy route", "domain", domainName) | |
| 515 | + | logger.Info("Removed proxy route", "domain", domainName) | |
| 602 | 516 | return nil | |
| 603 | 517 | } | |
| 604 | 518 | ||
| @@ -618,10 +532,12 @@ func (p *Proxy) GetRoutes() map[string]*ProxyRouteInfo { | |||
| 618 | 532 | ||
| 619 | 533 | // Reload reloads the routes from the database and reconfigures the proxy | |
| 620 | 534 | func (p *Proxy) Reload() error { | |
| 621 | - | log.Info("Reloading proxy routes from database") | |
| 535 | + | logger.Info("Reloading proxy routes from database") | |
| 622 | 536 | ||
| 623 | 537 | // Get admin domain - this should be the full subdomain.domain format | |
| 624 | 538 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 539 | + | // Normalize the admin domain (lowercase, no trailing dot) | |
| 540 | + | adminDomain = strings.TrimSuffix(strings.ToLower(adminDomain), ".") | |
| 625 | 541 | ||
| 626 | 542 | // Save existing admin route if it exists | |
| 627 | 543 | p.mu.RLock() | |
| @@ -636,7 +552,7 @@ func (p *Proxy) Reload() error { | |||
| 636 | 552 | Path: route.Path, | |
| 637 | 553 | Active: route.Active, | |
| 638 | 554 | } | |
| 639 | - | log.Debug("Preserved admin domain route during reload", "domain", adminDomain) | |
| 555 | + | logger.Debug("Preserved admin domain route during reload", "domain", adminDomain) | |
| 640 | 556 | } | |
| 641 | 557 | p.mu.RUnlock() | |
| 642 | 558 | ||
| @@ -653,7 +569,7 @@ func (p *Proxy) Reload() error { | |||
| 653 | 569 | `) | |
| 654 | 570 | if err != nil { | |
| 655 | 571 | if err == sql.ErrNoRows { | |
| 656 | - | log.Info("No active proxy routes found during reload") | |
| 572 | + | logger.Info("No active proxy routes found during reload") | |
| 657 | 573 | // Even if no routes found, proceed with the swap to clear any old routes | |
| 658 | 574 | } else { | |
| 659 | 575 | return fmt.Errorf("failed to query database during reload: %w", err) | |
| @@ -669,6 +585,9 @@ func (p *Proxy) Reload() error { | |||
| 669 | 585 | return fmt.Errorf("failed to scan row during reload: %w", err) | |
| 670 | 586 | } | |
| 671 | 587 | ||
| 588 | + | // Normalize domain (lowercase, no trailing dot) | |
| 589 | + | domain = strings.TrimSuffix(strings.ToLower(domain), ".") | |
| 590 | + | ||
| 672 | 591 | // Add the route to the new map | |
| 673 | 592 | newRoutes[domain] = &ProxyRouteInfo{ | |
| 674 | 593 | Domain: domain, | |
| @@ -680,7 +599,7 @@ func (p *Proxy) Reload() error { | |||
| 680 | 599 | Active: active, | |
| 681 | 600 | } | |
| 682 | 601 | ||
| 683 | - | log.Debug("Loaded route during reload", | |
| 602 | + | logger.Debug("Loaded route during reload", | |
| 684 | 603 | "domain", domain, | |
| 685 | 604 | "containerID", containerID, | |
| 686 | 605 | "containerIP", containerIP, | |
| @@ -694,7 +613,7 @@ func (p *Proxy) Reload() error { | |||
| 694 | 613 | // If admin domain wasn't loaded from DB but we had it in memory, restore it | |
| 695 | 614 | if !adminLoaded && adminRoute != nil { | |
| 696 | 615 | newRoutes[adminDomain] = adminRoute | |
| 697 | - | log.Warn("Admin domain route missing from database, restoring from memory", | |
| 616 | + | logger.Warn("Admin domain route missing from database, restoring from memory", | |
| 698 | 617 | "domain", adminDomain) | |
| 699 | 618 | ||
| 700 | 619 | // Also try to add it back to the database | |
| @@ -708,17 +627,17 @@ func (p *Proxy) Reload() error { | |||
| 708 | 627 | ) | |
| 709 | 628 | ||
| 710 | 629 | if err != nil { | |
| 711 | - | log.Error("Failed to save admin route to database", | |
| 630 | + | logger.Error("Failed to save admin route to database", | |
| 712 | 631 | "error", err, | |
| 713 | 632 | "domain", adminDomain) | |
| 714 | 633 | } else { | |
| 715 | - | log.Info("Re-added admin domain route to database", "domain", adminDomain) | |
| 634 | + | logger.Info("Re-added admin domain route to database", "domain", adminDomain) | |
| 716 | 635 | } | |
| 717 | 636 | } | |
| 718 | 637 | ||
| 719 | 638 | // If admin domain is still missing, recreate it | |
| 720 | 639 | if _, exists := newRoutes[adminDomain]; !exists { | |
| 721 | - | log.Warn("Admin domain route is missing, recreating it", "domain", adminDomain) | |
| 640 | + | logger.Warn("Admin domain route is missing, recreating it", "domain", adminDomain) | |
| 722 | 641 | ||
| 723 | 642 | // Auto-detect the Gordon container information | |
| 724 | 643 | containerName := p.detectGordonContainer() | |
| @@ -753,11 +672,11 @@ func (p *Proxy) Reload() error { | |||
| 753 | 672 | ) | |
| 754 | 673 | ||
| 755 | 674 | if err != nil { | |
| 756 | - | log.Error("Failed to save recreated admin route to database", | |
| 675 | + | logger.Error("Failed to save recreated admin route to database", | |
| 757 | 676 | "error", err, | |
| 758 | 677 | "domain", adminDomain) | |
| 759 | 678 | } else { | |
| 760 | - | log.Info("Recreated and saved admin domain route", "domain", adminDomain) | |
| 679 | + | logger.Info("Recreated and saved admin domain route", "domain", adminDomain) | |
| 761 | 680 | } | |
| 762 | 681 | } | |
| 763 | 682 | ||
| @@ -767,20 +686,17 @@ func (p *Proxy) Reload() error { | |||
| 767 | 686 | p.routes = newRoutes | |
| 768 | 687 | p.mu.Unlock() | |
| 769 | 688 | ||
| 770 | - | // Clean up any root domain conflict | |
| 771 | - | p.CleanupRootDomainForAdmin() | |
| 772 | - | ||
| 773 | 689 | // Rebuild route configuration | |
| 774 | 690 | p.configureRoutes() | |
| 775 | 691 | ||
| 776 | 692 | // Force recreation of the route handlers if servers are initialized | |
| 777 | 693 | if p.httpServer != nil { | |
| 778 | - | log.Debug("Refreshing HTTP routes") | |
| 694 | + | logger.Debug("Refreshing HTTP routes") | |
| 779 | 695 | p.httpServer.Routes() | |
| 780 | 696 | } | |
| 781 | 697 | ||
| 782 | 698 | if p.httpsServer != nil { | |
| 783 | - | log.Debug("Refreshing HTTPS routes") | |
| 699 | + | logger.Debug("Refreshing HTTPS routes") | |
| 784 | 700 | p.httpsServer.Routes() | |
| 785 | 701 | } | |
| 786 | 702 | ||
| @@ -807,7 +723,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||
| 807 | 723 | if r, ok := p.routes[domain]; ok { | |
| 808 | 724 | oldIP := r.ContainerIP | |
| 809 | 725 | r.ContainerIP = newIP | |
| 810 | - | log.Info("Force updated route IP in-memory", | |
| 726 | + | logger.Info("Force updated route IP in-memory", | |
| 811 | 727 | "domain", domain, | |
| 812 | 728 | "old_ip", oldIP, | |
| 813 | 729 | "new_ip", newIP) | |
| @@ -815,16 +731,27 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||
| 815 | 731 | p.mu.Unlock() | |
| 816 | 732 | ||
| 817 | 733 | // Next, update the database | |
| 818 | - | // Get the domain ID | |
| 734 | + | // Get the domain ID with retry | |
| 819 | 735 | var domainID string | |
| 820 | - | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |
| 736 | + | rows, err := p.dbQueryWithRetry("SELECT id FROM domain WHERE name = ?", domain) | |
| 821 | 737 | if err != nil { | |
| 822 | - | return fmt.Errorf("failed to find domain ID: %w", err) | |
| 738 | + | return fmt.Errorf("failed to query domain: %w", err) | |
| 823 | 739 | } | |
| 824 | 740 | ||
| 825 | - | // Update the proxy_route record | |
| 741 | + | if !rows.Next() { | |
| 742 | + | rows.Close() | |
| 743 | + | return fmt.Errorf("domain not found: %s", domain) | |
| 744 | + | } | |
| 745 | + | ||
| 746 | + | if err := rows.Scan(&domainID); err != nil { | |
| 747 | + | rows.Close() | |
| 748 | + | return fmt.Errorf("failed to scan domain ID: %w", err) | |
| 749 | + | } | |
| 750 | + | rows.Close() | |
| 751 | + | ||
| 752 | + | // Update the proxy_route record with retry | |
| 826 | 753 | now := time.Now().Format(time.RFC3339) | |
| 827 | - | result, err := p.app.GetDB().Exec(` | |
| 754 | + | result, err := p.dbExecWithRetry(` | |
| 828 | 755 | UPDATE proxy_route SET container_ip = ?, updated_at = ? | |
| 829 | 756 | WHERE domain_id = ? AND container_id = ?`, | |
| 830 | 757 | newIP, now, domainID, containerID) | |
| @@ -838,7 +765,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||
| 838 | 765 | return fmt.Errorf("no rows affected when updating route in database") | |
| 839 | 766 | } | |
| 840 | 767 | ||
| 841 | - | log.Info("Force updated route IP in database", | |
| 768 | + | logger.Info("Force updated route IP in database", | |
| 842 | 769 | "domain", domain, | |
| 843 | 770 | "container_id", containerID, | |
| 844 | 771 | "new_ip", newIP) | |
| @@ -851,7 +778,7 @@ func (p *Proxy) FindRoutesByContainerName(containerName string) []string { | |||
| 851 | 778 | // Get all containers to map IDs to names | |
| 852 | 779 | containers, err := docker.ListRunningContainers() | |
| 853 | 780 | if err != nil { | |
| 854 | - | log.Error("Failed to list containers when finding routes by container name", "error", err) | |
| 781 | + | logger.Error("Failed to list containers when finding routes by container name", "error", err) | |
| 855 | 782 | return nil | |
| 856 | 783 | } | |
| 857 | 784 | ||
| @@ -868,7 +795,7 @@ func (p *Proxy) FindRoutesByContainerName(containerName string) []string { | |||
| 868 | 795 | // This is the exact container ID we're looking for | |
| 869 | 796 | targetContainerID := containerNameToID[containerName] | |
| 870 | 797 | if targetContainerID == "" { | |
| 871 | - | log.Debug("Container name not found in running containers", "container_name", containerName) | |
| 798 | + | logger.Debug("Container name not found in running containers", "container_name", containerName) | |
| 872 | 799 | return nil | |
| 873 | 800 | } | |
| 874 | 801 | ||
| @@ -894,15 +821,15 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||
| 894 | 821 | ||
| 895 | 822 | result := make(map[string]*ProxyRouteInfo) | |
| 896 | 823 | ||
| 897 | - | // Query the database to find if any route might be associated with this container name | |
| 898 | - | rows, err := p.app.GetDB().Query(` | |
| 824 | + | // Query the database with retry to find if any route might be associated with this container name | |
| 825 | + | rows, err := p.dbQueryWithRetry(` | |
| 899 | 826 | SELECT d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path | |
| 900 | 827 | FROM proxy_route pr | |
| 901 | 828 | JOIN domain d ON pr.domain_id = d.id | |
| 902 | 829 | WHERE pr.active = 1 | |
| 903 | 830 | `) | |
| 904 | 831 | if err != nil { | |
| 905 | - | log.Error("Failed to query database for routes by old name", "error", err) | |
| 832 | + | logger.Error("Failed to query database for routes by old name", "error", err) | |
| 906 | 833 | return result | |
| 907 | 834 | } | |
| 908 | 835 | defer rows.Close() | |
| @@ -911,7 +838,7 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||
| 911 | 838 | for rows.Next() { | |
| 912 | 839 | var domain, containerID, containerIP, containerPort, protocol, path string | |
| 913 | 840 | if err := rows.Scan(&domain, &containerID, &containerIP, &containerPort, &protocol, &path); err != nil { | |
| 914 | - | log.Error("Failed to scan row", "error", err) | |
| 841 | + | logger.Error("Failed to scan row", "error", err) | |
| 915 | 842 | continue | |
| 916 | 843 | } | |
| 917 | 844 | ||
| @@ -940,8 +867,8 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||
| 940 | 867 | } | |
| 941 | 868 | } else { | |
| 942 | 869 | // We found an existing container - if it has the same name, this is interesting | |
| 943 | - | containerName = strings.TrimPrefix(name, "/") | |
| 944 | - | if containerName == containerName { | |
| 870 | + | existingContainerName := strings.TrimPrefix(name, "/") | |
| 871 | + | if existingContainerName == containerName { | |
| 945 | 872 | // This is an exact match - shouldn't happen if we're looking for recreated containers | |
| 946 | 873 | // but include it anyway | |
| 947 | 874 | result[domain] = &ProxyRouteInfo{ | |
| @@ -963,7 +890,7 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||
| 963 | 890 | // HandleContainerRecreation handles a container that has been recreated with the same name but a new ID | |
| 964 | 891 | // It updates all relevant routes with the new container ID and IP | |
| 965 | 892 | func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerIP string) { | |
| 966 | - | log.Info("Handling potential container recreation", | |
| 893 | + | logger.Info("Handling potential container recreation", | |
| 967 | 894 | "container_id", containerID, | |
| 968 | 895 | "container_name", containerName, | |
| 969 | 896 | "container_ip", containerIP) | |
| @@ -971,18 +898,21 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 971 | 898 | // Get container info to check for gordon labels | |
| 972 | 899 | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 973 | 900 | if err != nil { | |
| 974 | - | log.Warn("Failed to get container info", "error", err) | |
| 901 | + | logger.Warn("Failed to get container info", "error", err) | |
| 975 | 902 | return | |
| 976 | 903 | } | |
| 977 | 904 | ||
| 978 | 905 | // Check if this is a Gordon-managed container | |
| 979 | 906 | if _, exists := containerInfo.Config.Labels["gordon.managed"]; !exists { | |
| 980 | - | log.Debug("Ignoring non-Gordon container", "container_name", containerName) | |
| 907 | + | logger.Debug("Ignoring non-Gordon container", "container_name", containerName) | |
| 981 | 908 | return | |
| 982 | 909 | } | |
| 983 | 910 | ||
| 984 | 911 | // First approach: check if there are any domains that should point to this container based on labels | |
| 985 | 912 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | |
| 913 | + | // Normalize domain (lowercase, no trailing dot) | |
| 914 | + | domainLabel = strings.TrimSuffix(strings.ToLower(domainLabel), ".") | |
| 915 | + | ||
| 986 | 916 | // Check if there's already a route for this domain | |
| 987 | 917 | p.mu.RLock() | |
| 988 | 918 | route, domainExists := p.routes[domainLabel] | |
| @@ -991,7 +921,7 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 991 | 921 | if domainExists { | |
| 992 | 922 | // Route exists - check if it's for a different container ID | |
| 993 | 923 | if route.ContainerID != containerID { | |
| 994 | - | log.Info("Container was recreated with the same domain label", | |
| 924 | + | logger.Info("Container was recreated with the same domain label", | |
| 995 | 925 | "domain", domainLabel, | |
| 996 | 926 | "old_container_id", route.ContainerID, | |
| 997 | 927 | "new_container_id", containerID) | |
| @@ -1012,9 +942,9 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1012 | 942 | // Update the route with the new container ID and IP | |
| 1013 | 943 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, route.Path) | |
| 1014 | 944 | if err != nil { | |
| 1015 | - | log.Error("Failed to update route for recreated container", "error", err) | |
| 945 | + | logger.Error("Failed to update route for recreated container", "error", err) | |
| 1016 | 946 | } else { | |
| 1017 | - | log.Info("Updated route for recreated container", | |
| 947 | + | logger.Info("Updated route for recreated container", | |
| 1018 | 948 | "domain", domainLabel, | |
| 1019 | 949 | "container_id", containerID, | |
| 1020 | 950 | "container_ip", containerIP) | |
| @@ -1022,7 +952,7 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1022 | 952 | } else { | |
| 1023 | 953 | // Same container ID but maybe IP changed | |
| 1024 | 954 | if route.ContainerIP != containerIP { | |
| 1025 | - | log.Info("Container IP changed", | |
| 955 | + | logger.Info("Container IP changed", | |
| 1026 | 956 | "domain", domainLabel, | |
| 1027 | 957 | "container_id", containerID, | |
| 1028 | 958 | "old_ip", route.ContainerIP, | |
| @@ -1031,13 +961,13 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1031 | 961 | // Update the IP | |
| 1032 | 962 | err = p.ForceUpdateRouteIP(domainLabel, containerIP) | |
| 1033 | 963 | if err != nil { | |
| 1034 | - | log.Error("Failed to update IP for container", "error", err) | |
| 964 | + | logger.Error("Failed to update IP for container", "error", err) | |
| 1035 | 965 | } | |
| 1036 | 966 | } | |
| 1037 | 967 | } | |
| 1038 | 968 | } else { | |
| 1039 | 969 | // No existing route for this domain - might be a new container | |
| 1040 | - | log.Info("New container with domain label", | |
| 970 | + | logger.Info("New container with domain label", | |
| 1041 | 971 | "domain", domainLabel, | |
| 1042 | 972 | "container_id", containerID) | |
| 1043 | 973 | ||
| @@ -1054,10 +984,16 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1054 | 984 | protocol = "https" | |
| 1055 | 985 | } | |
| 1056 | 986 | ||
| 1057 | - | // Add a new route | |
| 987 | + | // Add a new route for this container | |
| 988 | + | logger.Info("Adding missing route for Gordon container", | |
| 989 | + | "domain", domainLabel, | |
| 990 | + | "container_id", containerID, | |
| 991 | + | "container_ip", containerIP, | |
| 992 | + | "container_port", containerPort) | |
| 993 | + | ||
| 1058 | 994 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | |
| 1059 | 995 | if err != nil { | |
| 1060 | - | log.Error("Failed to add route for new container", "error", err) | |
| 996 | + | logger.Error("Failed to add missing route", "error", err) | |
| 1061 | 997 | } | |
| 1062 | 998 | } | |
| 1063 | 999 | } | |
| @@ -1065,12 +1001,12 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1065 | 1001 | // Second approach: check for routes with name-based matching that might need updating | |
| 1066 | 1002 | // This handles the case where the container name is used instead of explicit domain labels | |
| 1067 | 1003 | if domainCheck := p.FindRoutesByOldName(containerName); len(domainCheck) > 0 { | |
| 1068 | - | log.Info("Found potential routes for recreated container by name matching", | |
| 1004 | + | logger.Info("Found potential routes for recreated container by name matching", | |
| 1069 | 1005 | "container_name", containerName, | |
| 1070 | 1006 | "domains_count", len(domainCheck)) | |
| 1071 | 1007 | ||
| 1072 | 1008 | for domain, oldRoute := range domainCheck { | |
| 1073 | - | log.Info("Updating route for recreated container by name matching", | |
| 1009 | + | logger.Info("Updating route for recreated container by name matching", | |
| 1074 | 1010 | "domain", domain, | |
| 1075 | 1011 | "old_container_id", oldRoute.ContainerID, | |
| 1076 | 1012 | "new_container_id", containerID) | |
| @@ -1096,9 +1032,9 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||
| 1096 | 1032 | // Update the route | |
| 1097 | 1033 | err = p.AddRoute(domain, containerID, containerIP, containerPort, protocol, oldRoute.Path) | |
| 1098 | 1034 | if err != nil { | |
| 1099 | - | log.Error("Failed to update route for recreated container", "error", err) | |
| 1035 | + | logger.Error("Failed to update route for recreated container", "error", err) | |
| 1100 | 1036 | } else { | |
| 1101 | - | log.Info("Updated route for recreated container", | |
| 1037 | + | logger.Info("Updated route for recreated container", | |
| 1102 | 1038 | "domain", domain, | |
| 1103 | 1039 | "container_id", containerID, | |
| 1104 | 1040 | "container_ip", containerIP) | |
| @@ -1116,243 +1052,39 @@ func (p *Proxy) StartContainerEventListener() error { | |||
| 1116 | 1052 | return fmt.Errorf("container network name is not configured") | |
| 1117 | 1053 | } | |
| 1118 | 1054 | ||
| 1119 | - | log.Info("Starting container event listener", "network", networkName) | |
| 1055 | + | logger.Info("Starting container event listener", "network", networkName) | |
| 1120 | 1056 | ||
| 1121 | 1057 | // Create a callback function to handle container events | |
| 1122 | 1058 | containerEventCallback := func(containerID, containerName, containerIP string) { | |
| 1123 | 1059 | p.HandleContainerRecreation(containerID, containerName, containerIP) | |
| 1124 | 1060 | } | |
| 1125 | 1061 | ||
| 1126 | - | // Start the container event listener | |
| 1127 | - | return docker.ListenForContainerEvents(networkName, containerEventCallback) | |
| 1128 | - | } | |
| 1129 | - | ||
| 1130 | - | // StartPeriodicIPVerification starts a goroutine that periodically checks container IPs | |
| 1131 | - | // and updates the routes if they have changed. This helps ensure routes always have | |
| 1132 | - | // the correct IP address, even if the event listener missed something. | |
| 1133 | - | func (p *Proxy) StartPeriodicIPVerification(interval time.Duration) { | |
| 1134 | - | log.Info("Starting periodic IP verification", "interval", interval) | |
| 1135 | - | ||
| 1136 | - | // Run in a background goroutine | |
| 1137 | - | go func() { | |
| 1138 | - | // Use a ticker for periodic execution | |
| 1139 | - | ticker := time.NewTicker(interval) | |
| 1140 | - | defer ticker.Stop() | |
| 1141 | - | ||
| 1142 | - | for { | |
| 1143 | - | select { | |
| 1144 | - | case <-ticker.C: | |
| 1145 | - | log.Debug("Running periodic IP verification") | |
| 1146 | - | p.verifyAllContainerIPs() | |
| 1147 | - | ||
| 1148 | - | // Also check for any containers that should have routes but don't | |
| 1149 | - | p.DiscoverMissingRoutes() | |
| 1150 | - | ||
| 1151 | - | // Log current active containers and routes for monitoring | |
| 1152 | - | if log.GetLevel() <= log.DebugLevel { | |
| 1153 | - | // Only log the container list in debug mode during periodic checks | |
| 1154 | - | // to avoid overwhelming logs in production | |
| 1155 | - | p.LogActiveContainersAndRoutes() | |
| 1156 | - | } | |
| 1157 | - | } | |
| 1158 | - | } | |
| 1159 | - | }() | |
| 1160 | - | } | |
| 1161 | - | ||
| 1162 | - | // verifyAllContainerIPs checks all routes and verifies their container IPs | |
| 1163 | - | func (p *Proxy) verifyAllContainerIPs() { | |
| 1164 | - | p.mu.RLock() | |
| 1165 | - | routes := make(map[string]*ProxyRouteInfo, len(p.routes)) | |
| 1166 | - | for domain, route := range p.routes { | |
| 1167 | - | routes[domain] = &ProxyRouteInfo{ | |
| 1168 | - | Domain: route.Domain, | |
| 1169 | - | ContainerID: route.ContainerID, | |
| 1170 | - | ContainerIP: route.ContainerIP, | |
| 1171 | - | ContainerPort: route.ContainerPort, | |
| 1172 | - | Protocol: route.Protocol, | |
| 1173 | - | Path: route.Path, | |
| 1174 | - | Active: route.Active, | |
| 1175 | - | } | |
| 1176 | - | } | |
| 1177 | - | p.mu.RUnlock() | |
| 1178 | - | ||
| 1179 | - | // Get network name from config | |
| 1180 | - | networkName := p.app.GetConfig().ContainerEngine.Network | |
| 1181 | - | if networkName == "" { | |
| 1182 | - | log.Error("Container network name is not configured") | |
| 1183 | - | return | |
| 1184 | - | } | |
| 1185 | - | ||
| 1186 | - | // Check each route and verify the container IP | |
| 1187 | - | routesUpdated := false | |
| 1188 | - | updatedIPs := make(map[string]string) // cache to avoid redundant lookups | |
| 1189 | - | ||
| 1190 | - | // Track orphaned routes to mark as inactive | |
| 1191 | - | orphanedRoutes := []string{} | |
| 1192 | - | ||
| 1193 | - | // Special handling for Gordon container - ensure its IP is correct first | |
| 1194 | - | gordonContainerID := p.loadGordonContainerID() | |
| 1195 | - | if gordonContainerID != "" { | |
| 1196 | - | log.Debug("Verifying Gordon container IP first", "container_id", gordonContainerID) | |
| 1197 | - | ||
| 1198 | - | gordonIP, err := docker.GetContainerIPFromNetwork(gordonContainerID, networkName) | |
| 1199 | - | if err == nil && gordonIP != "" { | |
| 1200 | - | log.Debug("Found Gordon container IP", "ip", gordonIP) | |
| 1201 | - | updatedIPs[gordonContainerID] = gordonIP | |
| 1202 | - | ||
| 1203 | - | // Double-check by direct container inspection to ensure maximum reliability | |
| 1204 | - | containerInfo, inspectErr := docker.GetContainerInfo(gordonContainerID) | |
| 1205 | - | if inspectErr == nil && containerInfo.NetworkSettings != nil { | |
| 1206 | - | if network, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && | |
| 1207 | - | network.IPAddress != "" && network.IPAddress != gordonIP { | |
| 1208 | - | log.Info("Different IP found for Gordon container through inspection", | |
| 1209 | - | "network_ip", gordonIP, | |
| 1210 | - | "inspect_ip", network.IPAddress) | |
| 1211 | - | updatedIPs[gordonContainerID] = network.IPAddress | |
| 1212 | - | } | |
| 1213 | - | } | |
| 1214 | - | } else { | |
| 1215 | - | log.Warn("Failed to get Gordon container IP from network", | |
| 1216 | - | "container_id", gordonContainerID, | |
| 1217 | - | "error", err) | |
| 1218 | - | } | |
| 1219 | - | } | |
| 1220 | - | ||
| 1221 | - | for domain, route := range routes { | |
| 1222 | - | if !route.Active { | |
| 1223 | - | continue | |
| 1224 | - | } | |
| 1225 | - | ||
| 1226 | - | containerID := route.ContainerID | |
| 1227 | - | ||
| 1228 | - | // Skip verification if container doesn't exist anymore | |
| 1229 | - | if !docker.ContainerExists(containerID) { | |
| 1230 | - | log.Warn("Container no longer exists but route is active", | |
| 1231 | - | "domain", domain, | |
| 1232 | - | "container_id", containerID) | |
| 1233 | - | orphanedRoutes = append(orphanedRoutes, domain) | |
| 1234 | - | continue | |
| 1235 | - | } | |
| 1236 | - | ||
| 1237 | - | // Check if this is for the Gordon container | |
| 1238 | - | isGordonContainer := (containerID == gordonContainerID) | |
| 1239 | - | if isGordonContainer { | |
| 1240 | - | log.Debug("Verifying Gordon container route", | |
| 1241 | - | "domain", domain, | |
| 1242 | - | "container_id", containerID) | |
| 1243 | - | } | |
| 1244 | - | ||
| 1245 | - | // Get the current container IP | |
| 1246 | - | var currentIP string | |
| 1247 | - | var err error | |
| 1248 | - | ||
| 1249 | - | // Check if we already looked up this container's IP | |
| 1250 | - | if cachedIP, exists := updatedIPs[containerID]; exists { | |
| 1251 | - | currentIP = cachedIP | |
| 1252 | - | log.Debug("Using cached container IP", | |
| 1253 | - | "container_id", containerID, | |
| 1254 | - | "ip", currentIP) | |
| 1255 | - | } else { | |
| 1256 | - | // Otherwise, look it up | |
| 1257 | - | currentIP, err = docker.GetContainerIPFromNetwork(containerID, networkName) | |
| 1258 | - | if err != nil { | |
| 1259 | - | log.Error("Failed to get container IP for verification", | |
| 1260 | - | "domain", domain, | |
| 1261 | - | "container_id", containerID, | |
| 1262 | - | "error", err) | |
| 1263 | - | ||
| 1264 | - | // For Gordon container, try harder to get the IP | |
| 1265 | - | if isGordonContainer { | |
| 1266 | - | log.Info("Attempting alternative methods to get Gordon container IP") | |
| 1267 | - | ||
| 1268 | - | // Try direct container inspection as a fallback | |
| 1269 | - | containerInfo, inspectErr := docker.GetContainerInfo(containerID) | |
| 1270 | - | if inspectErr == nil && containerInfo.NetworkSettings != nil { | |
| 1271 | - | // Check for our network specifically | |
| 1272 | - | if network, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && network.IPAddress != "" { | |
| 1273 | - | currentIP = network.IPAddress | |
| 1274 | - | log.Info("Successfully got Gordon container IP through inspection", | |
| 1275 | - | "ip", currentIP) | |
| 1276 | - | } else if containerInfo.NetworkSettings.IPAddress != "" { | |
| 1277 | - | // Fallback to default network IP | |
| 1278 | - | currentIP = containerInfo.NetworkSettings.IPAddress | |
| 1279 | - | log.Info("Using default network IP for Gordon container", | |
| 1280 | - | "ip", currentIP) | |
| 1281 | - | } | |
| 1282 | - | } | |
| 1283 | - | } | |
| 1062 | + | // Create a callback function to handle container stop events | |
| 1063 | + | containerStopCallback := func(containerID, containerName string) { | |
| 1064 | + | logger.Info("Container stopped or removed, marking associated routes as inactive", | |
| 1065 | + | "container_id", containerID, | |
| 1066 | + | "container_name", containerName) | |
| 1284 | 1067 | ||
| 1285 | - | // If we still couldn't get the IP, skip this route | |
| 1286 | - | if currentIP == "" { | |
| 1287 | - | continue | |
| 1288 | - | } | |
| 1068 | + | // Find all domains associated with this container ID | |
| 1069 | + | var domains []string | |
| 1070 | + | p.mu.RLock() | |
| 1071 | + | for domain, route := range p.routes { | |
| 1072 | + | if route.ContainerID == containerID { | |
| 1073 | + | domains = append(domains, domain) | |
| 1289 | 1074 | } | |
| 1290 | - | ||
| 1291 | - | // Cache the IP for future route checks | |
| 1292 | - | updatedIPs[containerID] = currentIP | |
| 1293 | 1075 | } | |
| 1076 | + | p.mu.RUnlock() | |
| 1294 | 1077 | ||
| 1295 | - | // Check if the IP has changed | |
| 1296 | - | if route.ContainerIP != currentIP { | |
| 1297 | - | // Throttle updates for non-Gordon containers to prevent excessive updates | |
| 1298 | - | if !isGordonContainer { | |
| 1299 | - | containerName, _ := docker.GetContainerName(containerID) | |
| 1300 | - | if containerName != "" { | |
| 1301 | - | containerName = strings.TrimPrefix(containerName, "/") | |
| 1302 | - | } | |
| 1303 | - | ||
| 1304 | - | log.Info("Container IP has changed - updating route", | |
| 1305 | - | "domain", domain, | |
| 1306 | - | "container_id", containerID, | |
| 1307 | - | "container_name", containerName, | |
| 1308 | - | "old_ip", route.ContainerIP, | |
| 1309 | - | "new_ip", currentIP) | |
| 1310 | - | } else { | |
| 1311 | - | log.Info("Gordon container IP has changed - updating route", | |
| 1312 | - | "admin_domain", domain, | |
| 1313 | - | "gordon_container", containerID, | |
| 1314 | - | "old_ip", route.ContainerIP, | |
| 1315 | - | "new_ip", currentIP) | |
| 1316 | - | } | |
| 1317 | - | ||
| 1318 | - | // Update the route in the database | |
| 1319 | - | err = p.ForceUpdateRouteIP(domain, currentIP) | |
| 1320 | - | if err != nil { | |
| 1321 | - | log.Error("Failed to update route IP", | |
| 1322 | - | "domain", domain, | |
| 1323 | - | "error", err) | |
| 1324 | - | } else { | |
| 1325 | - | routesUpdated = true | |
| 1326 | - | ||
| 1327 | - | if isGordonContainer { | |
| 1328 | - | log.Info("Successfully fixed admin domain route to point to Gordon container", | |
| 1329 | - | "admin_domain", domain, | |
| 1330 | - | "gordon_container", containerID) | |
| 1331 | - | } | |
| 1332 | - | } | |
| 1078 | + | if len(domains) > 0 { | |
| 1079 | + | logger.Info("Found routes to mark as inactive", "container_id", containerID, "domains", domains) | |
| 1080 | + | p.markRoutesInactive(domains) | |
| 1333 | 1081 | } else { | |
| 1334 | - | log.Debug("Container IP verified and unchanged", | |
| 1335 | - | "domain", domain, | |
| 1336 | - | "container_id", containerID, | |
| 1337 | - | "ip", currentIP) | |
| 1082 | + | logger.Debug("No routes found for stopped container", "container_id", containerID) | |
| 1338 | 1083 | } | |
| 1339 | 1084 | } | |
| 1340 | 1085 | ||
| 1341 | - | // Mark orphaned routes as inactive | |
| 1342 | - | if len(orphanedRoutes) > 0 { | |
| 1343 | - | log.Info("Marking orphaned routes as inactive", "count", len(orphanedRoutes)) | |
| 1344 | - | p.markRoutesInactive(orphanedRoutes) | |
| 1345 | - | routesUpdated = true | |
| 1346 | - | } | |
| 1347 | - | ||
| 1348 | - | // Reload routes if any were updated | |
| 1349 | - | if routesUpdated { | |
| 1350 | - | log.Debug("Routes were updated - reloading proxy configuration") | |
| 1351 | - | err := p.Reload() | |
| 1352 | - | if err != nil { | |
| 1353 | - | log.Error("Failed to reload routes after IP verification", "error", err) | |
| 1354 | - | } | |
| 1355 | - | } | |
| 1086 | + | // Start the container event listener | |
| 1087 | + | return docker.ListenForContainerEvents(networkName, containerEventCallback, containerStopCallback) | |
| 1356 | 1088 | } | |
| 1357 | 1089 | ||
| 1358 | 1090 | // markRoutesInactive marks the specified routes as inactive in the database and memory | |
| @@ -1370,7 +1102,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||
| 1370 | 1102 | if domain != adminDomain { | |
| 1371 | 1103 | filteredDomains = append(filteredDomains, domain) | |
| 1372 | 1104 | } else { | |
| 1373 | - | log.Warn("Prevented admin domain from being marked inactive", | |
| 1105 | + | logger.Warn("Prevented admin domain from being marked inactive", | |
| 1374 | 1106 | "domain", adminDomain) | |
| 1375 | 1107 | } | |
| 1376 | 1108 | } | |
| @@ -1386,7 +1118,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||
| 1386 | 1118 | // Begin a transaction | |
| 1387 | 1119 | tx, err := p.app.GetDB().Begin() | |
| 1388 | 1120 | if err != nil { | |
| 1389 | - | log.Error("Failed to begin transaction for marking routes inactive", "error", err) | |
| 1121 | + | logger.Error("Failed to begin transaction for marking routes inactive", "error", err) | |
| 1390 | 1122 | return | |
| 1391 | 1123 | } | |
| 1392 | 1124 | defer tx.Rollback() | |
| @@ -1397,7 +1129,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||
| 1397 | 1129 | var domainID string | |
| 1398 | 1130 | err := tx.QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |
| 1399 | 1131 | if err != nil { | |
| 1400 | - | log.Error("Failed to get domain ID for inactive route", "domain", domain, "error", err) | |
| 1132 | + | logger.Error("Failed to get domain ID for inactive route", "domain", domain, "error", err) | |
| 1401 | 1133 | continue | |
| 1402 | 1134 | } | |
| 1403 | 1135 | ||
| @@ -1405,7 +1137,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||
| 1405 | 1137 | now := time.Now().Format(time.RFC3339) | |
| 1406 | 1138 | _, err = tx.Exec("UPDATE proxy_route SET active = 0, updated_at = ? WHERE domain_id = ?", now, domainID) | |
| 1407 | 1139 | if err != nil { | |
| 1408 | - | log.Error("Failed to mark route as inactive in database", "domain", domain, "error", err) | |
| 1140 | + | logger.Error("Failed to mark route as inactive in database", "domain", domain, "error", err) | |
| 1409 | 1141 | continue | |
| 1410 | 1142 | } | |
| 1411 | 1143 | ||
| @@ -1413,29 +1145,29 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||
| 1413 | 1145 | p.mu.Lock() | |
| 1414 | 1146 | if route, exists := p.routes[domain]; exists { | |
| 1415 | 1147 | route.Active = false | |
| 1416 | - | log.Info("Marked route as inactive", "domain", domain) | |
| 1148 | + | logger.Info("Marked route as inactive", "domain", domain) | |
| 1417 | 1149 | } | |
| 1418 | 1150 | p.mu.Unlock() | |
| 1419 | 1151 | } | |
| 1420 | 1152 | ||
| 1421 | 1153 | // Commit the transaction | |
| 1422 | 1154 | if err := tx.Commit(); err != nil { | |
| 1423 | - | log.Error("Failed to commit transaction for marking routes inactive", "error", err) | |
| 1155 | + | logger.Error("Failed to commit transaction for marking routes inactive", "error", err) | |
| 1424 | 1156 | return | |
| 1425 | 1157 | } | |
| 1426 | 1158 | ||
| 1427 | - | log.Info("Successfully marked orphaned routes as inactive", "count", len(domains)) | |
| 1159 | + | logger.Info("Successfully marked orphaned routes as inactive", "count", len(domains)) | |
| 1428 | 1160 | } | |
| 1429 | 1161 | ||
| 1430 | 1162 | // DiscoverMissingRoutes finds Gordon-managed containers that don't have routes yet | |
| 1431 | 1163 | // and adds them to the routing table | |
| 1432 | 1164 | func (p *Proxy) DiscoverMissingRoutes() { | |
| 1433 | - | log.Info("Discovering containers that might need routes") | |
| 1165 | + | logger.Info("Discovering containers that might need routes") | |
| 1434 | 1166 | ||
| 1435 | 1167 | // Get the network name from app config | |
| 1436 | 1168 | networkName := p.app.GetConfig().ContainerEngine.Network | |
| 1437 | 1169 | if networkName == "" { | |
| 1438 | - | log.Error("Cannot discover missing routes, network name is not configured") | |
| 1170 | + | logger.Error("Cannot discover missing routes, network name is not configured") | |
| 1439 | 1171 | return | |
| 1440 | 1172 | } | |
| 1441 | 1173 | ||
| @@ -1445,7 +1177,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1445 | 1177 | // Get all containers in the network | |
| 1446 | 1178 | networkInfo, err := docker.GetNetworkInfo(networkName) | |
| 1447 | 1179 | if err != nil { | |
| 1448 | - | log.Error("Failed to get network info", "error", err) | |
| 1180 | + | logger.Error("Failed to get network info", "error", err) | |
| 1449 | 1181 | return | |
| 1450 | 1182 | } | |
| 1451 | 1183 | ||
| @@ -1473,7 +1205,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1473 | 1205 | // Get container info to check for Gordon labels | |
| 1474 | 1206 | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 1475 | 1207 | if err != nil { | |
| 1476 | - | log.Debug("Failed to get container info", | |
| 1208 | + | logger.Debug("Failed to get container info", | |
| 1477 | 1209 | "container_id", containerID, | |
| 1478 | 1210 | "error", err) | |
| 1479 | 1211 | continue | |
| @@ -1487,39 +1219,39 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1487 | 1219 | // Check if it has a domain label | |
| 1488 | 1220 | domainLabel, hasDomain := containerInfo.Config.Labels["gordon.domain"] | |
| 1489 | 1221 | if !hasDomain || domainLabel == "" { | |
| 1490 | - | log.Debug("Gordon container without domain label", | |
| 1222 | + | logger.Debug("Gordon container without domain label", | |
| 1491 | 1223 | "container_id", containerID, | |
| 1492 | 1224 | "container_name", strings.TrimPrefix(containerInfo.Name, "/")) | |
| 1493 | 1225 | continue | |
| 1494 | 1226 | } | |
| 1495 | 1227 | ||
| 1228 | + | // Normalize domain (lowercase, no trailing dot) | |
| 1229 | + | domainLabel = strings.TrimSuffix(strings.ToLower(domainLabel), ".") | |
| 1230 | + | ||
| 1496 | 1231 | // Skip if this is the admin domain | |
| 1497 | 1232 | if domainLabel == adminDomain { | |
| 1498 | - | log.Debug("Skipping auto-discovery for admin domain", | |
| 1499 | - | "domain", domainLabel, | |
| 1500 | - | "container_id", containerID) | |
| 1233 | + | logger.Debug("Skipping auto-discovery for admin domain", | |
| 1234 | + | "domain", domainLabel) | |
| 1501 | 1235 | continue | |
| 1502 | 1236 | } | |
| 1503 | 1237 | ||
| 1504 | - | // Check if this domain already has a route (different container) | |
| 1238 | + | // Check if there's already a route for this domain | |
| 1505 | 1239 | if _, exists := routes[domainLabel]; exists { | |
| 1506 | - | log.Warn("Domain already has a route with a different container", | |
| 1507 | - | "domain", domainLabel, | |
| 1508 | - | "container_id", containerID, | |
| 1509 | - | "existing_container_id", routes[domainLabel].ContainerID) | |
| 1240 | + | logger.Debug("Route already exists for domain", | |
| 1241 | + | "domain", domainLabel) | |
| 1510 | 1242 | continue | |
| 1511 | 1243 | } | |
| 1512 | 1244 | ||
| 1513 | - | // Extract container IP and port for the new route | |
| 1245 | + | // Get the container IP from the network info | |
| 1514 | 1246 | containerIP := containerEndpoint.IPv4Address | |
| 1515 | 1247 | if containerIP == "" { | |
| 1516 | - | containerIP = containerEndpoint.IPv6Address | |
| 1248 | + | logger.Warn("Container has no IP address", | |
| 1249 | + | "container_id", containerID) | |
| 1250 | + | continue | |
| 1517 | 1251 | } | |
| 1518 | 1252 | ||
| 1519 | - | // Extract just the IP part without subnet | |
| 1520 | - | if idx := strings.Index(containerIP, "/"); idx > 0 { | |
| 1521 | - | containerIP = containerIP[:idx] | |
| 1522 | - | } | |
| 1253 | + | // Extract just the IP part from the CIDR notation (e.g., "192.168.1.2/24" → "192.168.1.2") | |
| 1254 | + | containerIP = strings.Split(containerIP, "/")[0] | |
| 1523 | 1255 | ||
| 1524 | 1256 | // Get the port to use for the proxy from the container labels | |
| 1525 | 1257 | containerPort := "80" // Default | |
| @@ -1535,7 +1267,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1535 | 1267 | } | |
| 1536 | 1268 | ||
| 1537 | 1269 | // Add a new route for this container | |
| 1538 | - | log.Info("Adding missing route for Gordon container", | |
| 1270 | + | logger.Info("Adding missing route for Gordon container", | |
| 1539 | 1271 | "domain", domainLabel, | |
| 1540 | 1272 | "container_id", containerID, | |
| 1541 | 1273 | "container_ip", containerIP, | |
| @@ -1543,7 +1275,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1543 | 1275 | ||
| 1544 | 1276 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | |
| 1545 | 1277 | if err != nil { | |
| 1546 | - | log.Error("Failed to add missing route", "error", err) | |
| 1278 | + | logger.Error("Failed to add missing route", "error", err) | |
| 1547 | 1279 | } | |
| 1548 | 1280 | } | |
| 1549 | 1281 | ||
| @@ -1554,15 +1286,12 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||
| 1554 | 1286 | count++ | |
| 1555 | 1287 | } | |
| 1556 | 1288 | p.mu.RUnlock() | |
| 1557 | - | log.Debug("Routes after discovery", "count", count) | |
| 1558 | - | ||
| 1559 | - | // Clean up any root domain conflict | |
| 1560 | - | p.CleanupRootDomainForAdmin() | |
| 1289 | + | logger.Debug("Routes after discovery", "count", count) | |
| 1561 | 1290 | } | |
| 1562 | 1291 | ||
| 1563 | 1292 | // LogActiveContainersAndRoutes logs a list of active containers and their routes | |
| 1564 | 1293 | func (p *Proxy) LogActiveContainersAndRoutes() { | |
| 1565 | - | log.Info("---- Listing active containers and routes ----") | |
| 1294 | + | logger.Info("---- Listing active containers and routes ----") | |
| 1566 | 1295 | ||
| 1567 | 1296 | // Get the admin domain | |
| 1568 | 1297 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| @@ -1574,10 +1303,10 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||
| 1574 | 1303 | p.mu.RUnlock() | |
| 1575 | 1304 | ||
| 1576 | 1305 | if !adminExists { | |
| 1577 | - | log.Warn("Admin domain is missing from routes", "domain", adminDomain) | |
| 1306 | + | logger.Warn("Admin domain is missing from routes", "domain", adminDomain) | |
| 1578 | 1307 | } | |
| 1579 | 1308 | ||
| 1580 | - | log.Info(fmt.Sprintf("Found %d active routes", activeRoutes)) | |
| 1309 | + | logger.Info(fmt.Sprintf("Found %d active routes", activeRoutes)) | |
| 1581 | 1310 | ||
| 1582 | 1311 | // Create a map of container IDs to their information | |
| 1583 | 1312 | containerMap := make(map[string]struct { | |
| @@ -1630,7 +1359,7 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||
| 1630 | 1359 | if err == nil { | |
| 1631 | 1360 | name, err := docker.GetContainerName(containerID) | |
| 1632 | 1361 | if err == nil { | |
| 1633 | - | containerData.Name = strings.TrimPrefix(name, "/") | |
| 1362 | + | containerData.Name = strings.TrimLeft(name, "/") | |
| 1634 | 1363 | containerData.Image = containerInfo.Config.Image | |
| 1635 | 1364 | containerMap[containerID] = containerData | |
| 1636 | 1365 | } | |
| @@ -1645,87 +1374,52 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||
| 1645 | 1374 | if len(containerID) >= 12 { | |
| 1646 | 1375 | containerIDShort = containerID[:12] | |
| 1647 | 1376 | } | |
| 1648 | - | log.Info(fmt.Sprintf("Container: %s (%s)", container.Name, containerIDShort), | |
| 1377 | + | logger.Info(fmt.Sprintf("Container: %s (%s)", container.Name, containerIDShort), | |
| 1649 | 1378 | "ip", container.IP, | |
| 1650 | 1379 | "image", container.Image) | |
| 1651 | 1380 | ||
| 1652 | 1381 | // Log each domain for this container | |
| 1653 | 1382 | for i, domain := range container.Domains { | |
| 1654 | - | log.Info(fmt.Sprintf(" ├─ Domain: %s", domain), | |
| 1383 | + | logger.Info(fmt.Sprintf(" ├─ Domain: %s", domain), | |
| 1655 | 1384 | "protocol", container.Protocols[i], | |
| 1656 | 1385 | "port", container.Ports[i]) | |
| 1657 | 1386 | } | |
| 1658 | 1387 | } | |
| 1659 | 1388 | ||
| 1660 | - | log.Info("---- End of active containers and routes list ----") | |
| 1389 | + | logger.Info("---- End of active containers and routes list ----") | |
| 1661 | 1390 | } | |
| 1662 | 1391 | ||
| 1663 | - | // saveGordonContainerID saves the Gordon container ID to the config file | |
| 1664 | - | func (p *Proxy) saveGordonContainerID(containerID string) { | |
| 1665 | - | log.Debug("Saving Gordon container ID to config file", "container_id", containerID) | |
| 1666 | - | ||
| 1667 | - | // Update the config | |
| 1668 | - | config := p.app.GetConfig() | |
| 1669 | - | config.General.GordonContainerID = containerID | |
| 1670 | - | ||
| 1671 | - | // Save the config file | |
| 1672 | - | err := config.SaveConfig() | |
| 1673 | - | if err != nil { | |
| 1674 | - | log.Error("Failed to save Gordon container ID to config file", "error", err) | |
| 1675 | - | return | |
| 1676 | - | } | |
| 1677 | - | ||
| 1678 | - | // Update the gordonContainerID field | |
| 1679 | - | p.gordonContainerID = containerID | |
| 1680 | - | ||
| 1681 | - | log.Info("Successfully saved Gordon container ID to config file", "container_id", containerID) | |
| 1682 | - | } | |
| 1683 | - | ||
| 1684 | - | // loadGordonContainerID loads the Gordon container ID from the config file | |
| 1685 | - | func (p *Proxy) loadGordonContainerID() string { | |
| 1686 | - | log.Debug("Loading Gordon container ID from config file") | |
| 1687 | - | ||
| 1688 | - | // Get the container ID from the config | |
| 1689 | - | containerID := p.app.GetConfig().General.GordonContainerID | |
| 1690 | - | ||
| 1691 | - | if containerID == "" { | |
| 1692 | - | log.Debug("No Gordon container ID found in config file") | |
| 1693 | - | return "" | |
| 1694 | - | } | |
| 1695 | - | ||
| 1696 | - | log.Info("Loaded Gordon container ID from config file", "container_id", containerID) | |
| 1697 | - | return containerID | |
| 1698 | - | } | |
| 1699 | - | ||
| 1700 | - | // detectGordonContainer attempts to find the Gordon container automatically | |
| 1701 | - | func (p *Proxy) detectGordonContainer() string { | |
| 1702 | - | log.Debug("Starting Gordon container detection") | |
| 1392 | + | // LogGordonIdentity prints information about the Gordon container for debugging | |
| 1393 | + | func (p *Proxy) LogGordonIdentity() { | |
| 1394 | + | logger.Debug("Logging Gordon identity information") | |
| 1703 | 1395 | ||
| 1704 | - | // First try to load the container ID from the config file | |
| 1705 | - | dbContainerID := p.loadGordonContainerID() | |
| 1706 | - | if dbContainerID != "" { | |
| 1707 | - | p.gordonContainerID = dbContainerID | |
| 1708 | - | log.Debug("Loaded Gordon container ID from config file", "container_id", p.gordonContainerID) | |
| 1396 | + | // Print our container ID if available | |
| 1397 | + | if p.gordonContainerID != "" { | |
| 1398 | + | logger.Info("Gordon container ID", "container_id", p.gordonContainerID) | |
| 1709 | 1399 | ||
| 1710 | - | // Verify the container still exists | |
| 1400 | + | // Try to get the container name | |
| 1711 | 1401 | containers, err := docker.ListRunningContainers() | |
| 1712 | 1402 | if err == nil { | |
| 1713 | 1403 | for _, container := range containers { | |
| 1714 | 1404 | if container.ID == p.gordonContainerID { | |
| 1715 | 1405 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1716 | - | log.Debug("Identified Gordon container using config file ID", "container_name", containerName) | |
| 1717 | - | return containerName | |
| 1406 | + | logger.Info("Gordon container name", "container_name", containerName) | |
| 1407 | + | break | |
| 1718 | 1408 | } | |
| 1719 | 1409 | } | |
| 1720 | - | // If the container ID is no longer found, log it but continue with other detection methods | |
| 1721 | - | log.Warn("Stored Gordon container ID from config file no longer found in running containers", "container_id", p.gordonContainerID) | |
| 1722 | - | p.gordonContainerID = "" // Reset since it's invalid | |
| 1723 | 1410 | } | |
| 1411 | + | } else { | |
| 1412 | + | logger.Warn("Gordon container ID not available - running outside container?") | |
| 1724 | 1413 | } | |
| 1414 | + | } | |
| 1415 | + | ||
| 1416 | + | // detectGordonContainer attempts to find the Gordon container automatically | |
| 1417 | + | func (p *Proxy) detectGordonContainer() string { | |
| 1418 | + | logger.Debug("Starting Gordon container detection") | |
| 1725 | 1419 | ||
| 1726 | - | // If we have our container ID stored from initialization, use that next | |
| 1420 | + | // If we have our container ID stored from initialization, use that first | |
| 1727 | 1421 | if p.gordonContainerID != "" { | |
| 1728 | - | log.Debug("Using stored Gordon container ID for identification", "container_id", p.gordonContainerID) | |
| 1422 | + | logger.Debug("Using stored Gordon container ID for identification", "container_id", p.gordonContainerID) | |
| 1729 | 1423 | ||
| 1730 | 1424 | // Get the container name from the ID | |
| 1731 | 1425 | containers, err := docker.ListRunningContainers() | |
| @@ -1733,23 +1427,21 @@ func (p *Proxy) detectGordonContainer() string { | |||
| 1733 | 1427 | for _, container := range containers { | |
| 1734 | 1428 | if container.ID == p.gordonContainerID { | |
| 1735 | 1429 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1736 | - | log.Info("Gordon identity established via exact ID match", | |
| 1430 | + | logger.Info("Gordon identity established via exact ID match", | |
| 1737 | 1431 | "container_id", container.ID, | |
| 1738 | 1432 | "container_name", containerName) | |
| 1739 | - | p.gordonContainerID = container.ID | |
| 1740 | - | // Save to config file for persistence | |
| 1741 | - | p.saveGordonContainerID(container.ID) | |
| 1742 | 1433 | return containerName | |
| 1743 | 1434 | } | |
| 1744 | 1435 | } | |
| 1745 | 1436 | // If the container ID is no longer found, log it but continue with other detection methods | |
| 1746 | - | log.Warn("Stored Gordon container ID no longer found in running containers", "container_id", p.gordonContainerID) | |
| 1437 | + | logger.Warn("Stored Gordon container ID no longer found in running containers", "container_id", p.gordonContainerID) | |
| 1438 | + | p.gordonContainerID = "" // Reset since it's invalid | |
| 1747 | 1439 | } | |
| 1748 | 1440 | } | |
| 1749 | 1441 | ||
| 1750 | 1442 | // Check if we're running inside a container | |
| 1751 | 1443 | if docker.IsRunningInContainer() { | |
| 1752 | - | log.Debug("Detected we're running in a container") | |
| 1444 | + | logger.Debug("Detected we're running in a container") | |
| 1753 | 1445 | ||
| 1754 | 1446 | // Get our hostname which should be the container ID in Docker/Podman | |
| 1755 | 1447 | hostname := os.Getenv("HOSTNAME") | |
| @@ -1762,12 +1454,10 @@ func (p *Proxy) detectGordonContainer() string { | |||
| 1762 | 1454 | for _, container := range containers { | |
| 1763 | 1455 | if hostname == container.ID { | |
| 1764 | 1456 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1765 | - | log.Info("Gordon identity established via exact ID match", | |
| 1457 | + | logger.Info("Gordon identity established via exact ID match", | |
| 1766 | 1458 | "container_id", container.ID, | |
| 1767 | 1459 | "container_name", containerName) | |
| 1768 | 1460 | p.gordonContainerID = container.ID | |
| 1769 | - | // Save to config file for persistence | |
| 1770 | - | p.saveGordonContainerID(container.ID) | |
| 1771 | 1461 | return containerName | |
| 1772 | 1462 | } | |
| 1773 | 1463 | } | |
| @@ -1776,18 +1466,16 @@ func (p *Proxy) detectGordonContainer() string { | |||
| 1776 | 1466 | for _, container := range containers { | |
| 1777 | 1467 | if strings.HasPrefix(container.ID, hostname) || strings.HasPrefix(hostname, container.ID) { | |
| 1778 | 1468 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1779 | - | log.Info("Gordon identity established via ID prefix match", | |
| 1469 | + | logger.Info("Gordon identity established via ID prefix match", | |
| 1780 | 1470 | "container_id", container.ID, | |
| 1781 | 1471 | "container_name", containerName, | |
| 1782 | 1472 | "hostname", hostname) | |
| 1783 | 1473 | p.gordonContainerID = container.ID | |
| 1784 | - | // Save to config file for persistence | |
| 1785 | - | p.saveGordonContainerID(container.ID) | |
| 1786 | 1474 | return containerName | |
| 1787 | 1475 | } | |
| 1788 | 1476 | } | |
| 1789 | 1477 | } else { | |
| 1790 | - | log.Debug("Could not list containers when checking hostname", "error", err) | |
| 1478 | + | logger.Debug("Could not list containers when checking hostname", "error", err) | |
| 1791 | 1479 | } | |
| 1792 | 1480 | } | |
| 1793 | 1481 | } | |
| @@ -1798,10 +1486,8 @@ func (p *Proxy) detectGordonContainer() string { | |||
| 1798 | 1486 | for _, container := range containers { | |
| 1799 | 1487 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1800 | 1488 | if containerName == "gordon" { | |
| 1801 | - | log.Info("Found Gordon container with exact name 'gordon'", "container_id", container.ID) | |
| 1489 | + | logger.Info("Found Gordon container with exact name 'gordon'", "container_id", container.ID) | |
| 1802 | 1490 | p.gordonContainerID = container.ID | |
| 1803 | - | // Save to config file for persistence | |
| 1804 | - | p.saveGordonContainerID(container.ID) | |
| 1805 | 1491 | return containerName | |
| 1806 | 1492 | } | |
| 1807 | 1493 | } | |
| @@ -1810,173 +1496,157 @@ func (p *Proxy) detectGordonContainer() string { | |||
| 1810 | 1496 | for _, container := range containers { | |
| 1811 | 1497 | containerName := strings.TrimLeft(container.Names[0], "/") | |
| 1812 | 1498 | if strings.Contains(strings.ToLower(containerName), "gordon") { | |
| 1813 | - | log.Info("Found Gordon container with partial name match", | |
| 1499 | + | logger.Info("Found Gordon container with partial name match", | |
| 1814 | 1500 | "container_name", containerName, | |
| 1815 | 1501 | "container_id", container.ID) | |
| 1816 | 1502 | p.gordonContainerID = container.ID | |
| 1817 | - | // Save to config file for persistence | |
| 1818 | - | p.saveGordonContainerID(container.ID) | |
| 1819 | 1503 | return containerName | |
| 1820 | 1504 | } | |
| 1821 | 1505 | } | |
| 1822 | 1506 | } | |
| 1823 | 1507 | ||
| 1824 | 1508 | // Fallback to a default name | |
| 1825 | - | log.Warn("Could not auto-detect Gordon container, using fallback name", "container_name", "gordon") | |
| 1509 | + | logger.Warn("Could not auto-detect Gordon container, using fallback name", "container_name", "gordon") | |
| 1826 | 1510 | return "gordon" | |
| 1827 | 1511 | } | |
| 1828 | 1512 | ||
| 1829 | - | // CleanupRootDomainForAdmin checks if the root domain is pointing to the admin container | |
| 1830 | - | // and removes that route to avoid conflicts with other services using the root domain | |
| 1831 | - | func (p *Proxy) CleanupRootDomainForAdmin() { | |
| 1832 | - | // Get admin domain and root domain | |
| 1833 | - | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 1834 | - | rootDomain := p.app.GetConfig().Http.Domain | |
| 1835 | - | ||
| 1836 | - | // Skip if root domain is the same as admin domain | |
| 1837 | - | if rootDomain == adminDomain || rootDomain == "" { | |
| 1838 | - | return | |
| 1839 | - | } | |
| 1840 | - | ||
| 1841 | - | // Check if root domain exists in memory routes | |
| 1842 | - | p.mu.RLock() | |
| 1843 | - | rootRoute, rootExists := p.routes[rootDomain] | |
| 1844 | - | adminRoute, adminExists := p.routes[adminDomain] | |
| 1845 | - | p.mu.RUnlock() | |
| 1846 | - | ||
| 1847 | - | // If both root domain and admin domain routes exist, | |
| 1848 | - | // check if they point to the same container ID (Gordon) | |
| 1849 | - | if rootExists && adminExists { | |
| 1850 | - | // Always ensure the admin domain points to the Gordon container | |
| 1851 | - | if p.gordonContainerID != "" && adminRoute.ContainerID != p.gordonContainerID { | |
| 1852 | - | log.Warn("Admin domain is pointing to the wrong container, fixing", | |
| 1853 | - | "admin_domain", adminDomain, | |
| 1854 | - | "current_container", adminRoute.ContainerID, | |
| 1855 | - | "gordon_container", p.gordonContainerID) | |
| 1856 | - | ||
| 1857 | - | // Update the admin route to point to the Gordon container | |
| 1858 | - | // Begin a transaction | |
| 1859 | - | tx, err := p.app.GetDB().Begin() | |
| 1860 | - | if err != nil { | |
| 1861 | - | log.Error("Failed to begin transaction for admin domain fix", "error", err) | |
| 1862 | - | return | |
| 1863 | - | } | |
| 1864 | - | defer tx.Rollback() | |
| 1865 | - | ||
| 1866 | - | // Get the domain ID for admin domain | |
| 1867 | - | var domainID string | |
| 1868 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", adminDomain).Scan(&domainID) | |
| 1869 | - | if err != nil { | |
| 1870 | - | if err != sql.ErrNoRows { | |
| 1871 | - | log.Error("Failed to query domain for admin domain fix", "error", err) | |
| 1872 | - | } | |
| 1873 | - | return | |
| 1874 | - | } | |
| 1875 | - | ||
| 1876 | - | // Update the route for admin domain | |
| 1877 | - | _, err = tx.Exec("UPDATE proxy_route SET container_id = ? WHERE domain_id = ?", | |
| 1878 | - | p.gordonContainerID, domainID) | |
| 1879 | - | if err != nil { | |
| 1880 | - | log.Error("Failed to update admin domain route", "error", err) | |
| 1881 | - | return | |
| 1882 | - | } | |
| 1883 | - | ||
| 1884 | - | // Commit the transaction | |
| 1885 | - | if err := tx.Commit(); err != nil { | |
| 1886 | - | log.Error("Failed to commit transaction for admin domain fix", "error", err) | |
| 1887 | - | return | |
| 1888 | - | } | |
| 1889 | - | ||
| 1890 | - | // Update in-memory route | |
| 1891 | - | p.mu.Lock() | |
| 1892 | - | adminRoute.ContainerID = p.gordonContainerID | |
| 1893 | - | p.mu.Unlock() | |
| 1894 | - | ||
| 1895 | - | log.Info("Successfully fixed admin domain route to point to Gordon container", | |
| 1896 | - | "admin_domain", adminDomain, | |
| 1897 | - | "gordon_container", p.gordonContainerID) | |
| 1513 | + | // proxyRequest handles the actual proxying of the request to the target container | |
| 1514 | + | func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error { | |
| 1515 | + | host := c.Request().Host | |
| 1516 | + | ||
| 1517 | + | // For container routes, verify the IP is reachable before proxying | |
| 1518 | + | if host != p.app.GetConfig().Http.FullDomain() { | |
| 1519 | + | // Test TCP connection to container before proxying | |
| 1520 | + | dialer := net.Dialer{Timeout: 500 * time.Millisecond} | |
| 1521 | + | // Format address properly for both IPv4 and IPv6 | |
| 1522 | + | target := route.ContainerIP | |
| 1523 | + | if strings.Contains(target, ":") { | |
| 1524 | + | // IPv6 address needs to be wrapped in brackets | |
| 1525 | + | target = "[" + target + "]" | |
| 1898 | 1526 | } | |
| 1899 | - | ||
| 1900 | - | // Now handle the root domain conflict | |
| 1901 | - | if rootRoute.ContainerID == adminRoute.ContainerID { | |
| 1902 | - | log.Warn("Found root domain pointing to admin container, removing to avoid conflicts", | |
| 1903 | - | "root_domain", rootDomain, | |
| 1904 | - | "admin_domain", adminDomain, | |
| 1905 | - | "container", rootRoute.ContainerID) | |
| 1906 | - | ||
| 1907 | - | // Begin a transaction | |
| 1908 | - | tx, err := p.app.GetDB().Begin() | |
| 1909 | - | if err != nil { | |
| 1910 | - | log.Error("Failed to begin transaction for root domain cleanup", "error", err) | |
| 1911 | - | return | |
| 1912 | - | } | |
| 1913 | - | defer tx.Rollback() | |
| 1914 | - | ||
| 1915 | - | // Get the domain ID for root domain | |
| 1916 | - | var domainID string | |
| 1917 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", rootDomain).Scan(&domainID) | |
| 1918 | - | if err != nil { | |
| 1919 | - | if err != sql.ErrNoRows { | |
| 1920 | - | log.Error("Failed to query domain for root domain cleanup", "error", err) | |
| 1527 | + | conn, err := dialer.Dial("tcp", net.JoinHostPort(target, route.ContainerPort)) | |
| 1528 | + | if err != nil { | |
| 1529 | + | // Connection failed, check if container exists but has a new IP | |
| 1530 | + | containerID := route.ContainerID | |
| 1531 | + | if containerID != "" { | |
| 1532 | + | // Try to get container info and check if it has a different IP | |
| 1533 | + | containerInfo, err := docker.GetContainerInfo(containerID) | |
| 1534 | + | if err == nil && containerInfo.NetworkSettings != nil { | |
| 1535 | + | // Check if container has a different IP | |
| 1536 | + | networkName := p.app.GetConfig().ContainerEngine.Network | |
| 1537 | + | if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && | |
| 1538 | + | networkSettings.IPAddress != "" && networkSettings.IPAddress != route.ContainerIP { | |
| 1539 | + | // Found container with different IP, update the route and use new IP for this request | |
| 1540 | + | logger.Warn("Container IP mismatch detected, using live IP instead of cached value", | |
| 1541 | + | "domain", host, | |
| 1542 | + | "cached_ip", route.ContainerIP, | |
| 1543 | + | "live_ip", networkSettings.IPAddress) | |
| 1544 | + | ||
| 1545 | + | // Update the in-memory route for this request | |
| 1546 | + | route.ContainerIP = networkSettings.IPAddress | |
| 1547 | + | ||
| 1548 | + | // Update the database in background to persist the change | |
| 1549 | + | go func(domain, containerID, containerIP, containerPort string) { | |
| 1550 | + | // Get the domain ID | |
| 1551 | + | var domainID string | |
| 1552 | + | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |
| 1553 | + | if err != nil { | |
| 1554 | + | logger.Error("Failed to find domain ID for IP update", "domain", domain, "error", err) | |
| 1555 | + | return | |
| 1556 | + | } | |
| 1557 | + | ||
| 1558 | + | // Update the proxy_route record | |
| 1559 | + | now := time.Now().Format(time.RFC3339) | |
| 1560 | + | _, err = p.app.GetDB().Exec(` | |
| 1561 | + | UPDATE proxy_route SET container_ip = ?, updated_at = ? | |
| 1562 | + | WHERE domain_id = ? AND container_id = ?`, | |
| 1563 | + | containerIP, now, domainID, containerID) | |
| 1564 | + | ||
| 1565 | + | if err != nil { | |
| 1566 | + | logger.Error("Failed to update container IP in database", | |
| 1567 | + | "domain", domain, | |
| 1568 | + | "containerID", containerID, | |
| 1569 | + | "error", err) | |
| 1570 | + | } else { | |
| 1571 | + | logger.Info("Updated container IP in database after live detection", | |
| 1572 | + | "domain", domain, | |
| 1573 | + | "containerID", containerID, | |
| 1574 | + | "old_ip", route.ContainerIP, | |
| 1575 | + | "new_ip", containerIP) | |
| 1576 | + | } | |
| 1577 | + | }(host, containerID, networkSettings.IPAddress, route.ContainerPort) | |
| 1578 | + | } else { | |
| 1579 | + | logger.Debug("Container exists but connection still failed", | |
| 1580 | + | "domain", host, | |
| 1581 | + | "container_id", containerID, | |
| 1582 | + | "container_ip", route.ContainerIP, | |
| 1583 | + | "container_port", route.ContainerPort, | |
| 1584 | + | "error", err) | |
| 1585 | + | } | |
| 1921 | 1586 | } | |
| 1922 | - | return | |
| 1923 | 1587 | } | |
| 1588 | + | } else { | |
| 1589 | + | // Connection successful, close it | |
| 1590 | + | conn.Close() | |
| 1591 | + | } | |
| 1592 | + | } | |
| 1924 | 1593 | ||
| 1925 | - | // Delete the route for root domain | |
| 1926 | - | _, err = tx.Exec("DELETE FROM proxy_route WHERE domain_id = ?", domainID) | |
| 1927 | - | if err != nil { | |
| 1928 | - | log.Error("Failed to delete root domain route", "error", err) | |
| 1929 | - | return | |
| 1930 | - | } | |
| 1594 | + | // Create the target URL | |
| 1595 | + | targetURL := &url.URL{ | |
| 1596 | + | Scheme: "http", // Always use HTTP for internal connections | |
| 1597 | + | } | |
| 1931 | 1598 | ||
| 1932 | - | // We don't delete the domain itself from the database to avoid affecting certificate management | |
| 1599 | + | // Log protocol conversion if needed | |
| 1600 | + | if route.Protocol == "https" { | |
| 1601 | + | logger.Debug("Converting HTTPS external route to HTTP for internal connection", | |
| 1602 | + | "domain", host, | |
| 1603 | + | "containerID", route.ContainerID, | |
| 1604 | + | "externalProtocol", "https", | |
| 1605 | + | "internalProtocol", "http") | |
| 1606 | + | } | |
| 1933 | 1607 | ||
| 1934 | - | // Commit the transaction | |
| 1935 | - | if err := tx.Commit(); err != nil { | |
| 1936 | - | log.Error("Failed to commit transaction for root domain cleanup", "error", err) | |
| 1937 | - | return | |
| 1938 | - | } | |
| 1608 | + | // Format host properly for both IPv4 and IPv6 | |
| 1609 | + | containerIP := route.ContainerIP | |
| 1610 | + | if strings.Contains(containerIP, ":") { | |
| 1611 | + | // IPv6 address needs to be wrapped in brackets | |
| 1612 | + | containerIP = "[" + containerIP + "]" | |
| 1613 | + | } | |
| 1614 | + | targetURL.Host = fmt.Sprintf("%s:%s", containerIP, route.ContainerPort) | |
| 1939 | 1615 | ||
| 1940 | - | // Remove root domain from in-memory routes | |
| 1941 | - | p.mu.Lock() | |
| 1942 | - | delete(p.routes, rootDomain) | |
| 1943 | - | p.mu.Unlock() | |
| 1616 | + | // Create a reverse proxy | |
| 1617 | + | proxy := httputil.NewSingleHostReverseProxy(targetURL) | |
| 1944 | 1618 | ||
| 1945 | - | log.Info("Successfully removed conflicting root domain route", | |
| 1946 | - | "root_domain", rootDomain, | |
| 1947 | - | "admin_domain", adminDomain) | |
| 1619 | + | // Update headers to allow for SSL redirection | |
| 1620 | + | originalDirector := proxy.Director | |
| 1621 | + | proxy.Director = func(req *http.Request) { | |
| 1622 | + | originalDirector(req) | |
| 1623 | + | // Use the original protocol for forwarded proto header | |
| 1624 | + | forwardedProto := "http" | |
| 1625 | + | if route.Protocol == "https" { | |
| 1626 | + | forwardedProto = "https" | |
| 1948 | 1627 | } | |
| 1949 | - | } | |
| 1950 | - | } | |
| 1628 | + | req.Header.Set("X-Forwarded-Proto", forwardedProto) | |
| 1629 | + | req.Header.Set("X-Forwarded-Host", host) | |
| 1630 | + | req.Header.Set("X-Forwarded-For", c.RealIP()) | |
| 1631 | + | req.Header.Set("X-Real-IP", c.RealIP()) | |
| 1951 | 1632 | ||
| 1952 | - | // LogGordonIdentity prints information about the Gordon container for debugging | |
| 1953 | - | func (p *Proxy) LogGordonIdentity() { | |
| 1954 | - | // If we don't have a container ID yet, try to load it from the database first | |
| 1955 | - | if p.gordonContainerID == "" { | |
| 1956 | - | dbContainerID := p.loadGordonContainerID() | |
| 1957 | - | if dbContainerID != "" { | |
| 1958 | - | p.gordonContainerID = dbContainerID | |
| 1959 | - | log.Info("Loaded Gordon container ID from config file during identity check", "container_id", p.gordonContainerID) | |
| 1960 | - | } | |
| 1633 | + | // Log all headers for troubleshooting | |
| 1634 | + | logger.Debug("Request headers for proxy", | |
| 1635 | + | "domain", host, | |
| 1636 | + | "X-Forwarded-For", req.Header.Get("X-Forwarded-For"), | |
| 1637 | + | "X-Real-IP", req.Header.Get("X-Real-IP")) | |
| 1961 | 1638 | } | |
| 1962 | 1639 | ||
| 1963 | - | // If we still don't have a container ID, try to detect it | |
| 1964 | - | if p.gordonContainerID == "" { | |
| 1965 | - | // This will attempt to detect the container and set p.gordonContainerID | |
| 1966 | - | containerName := p.detectGordonContainer() | |
| 1967 | - | if p.gordonContainerID != "" { | |
| 1968 | - | log.Info("Detected Gordon container during identity check", | |
| 1969 | - | "container_id", p.gordonContainerID, | |
| 1970 | - | "container_name", containerName) | |
| 1971 | - | } | |
| 1640 | + | // Add error handling | |
| 1641 | + | proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { | |
| 1642 | + | logger.Error("Proxy error", | |
| 1643 | + | "host", host, | |
| 1644 | + | "path", r.URL.Path, | |
| 1645 | + | "error", err) | |
| 1646 | + | c.String(http.StatusBadGateway, "Proxy Error: "+err.Error()) | |
| 1972 | 1647 | } | |
| 1973 | 1648 | ||
| 1974 | - | // Now print our container ID for debugging | |
| 1975 | - | if p.gordonContainerID != "" { | |
| 1976 | - | log.Info("Gordon container identity", | |
| 1977 | - | "container_id", p.gordonContainerID, | |
| 1978 | - | "container_name", p.detectGordonContainer()) | |
| 1979 | - | } else { | |
| 1980 | - | log.Warn("Gordon container ID not available - running outside container?") | |
| 1981 | - | } | |
| 1649 | + | // Serve the request | |
| 1650 | + | proxy.ServeHTTP(c.Response(), c.Request()) | |
| 1651 | + | return nil | |
| 1982 | 1652 | } | |
| @@ -12,7 +12,7 @@ import ( | |||||
| 12 | "time" | 12 | "time" | ||
| 13 | 13 | ||||
| 14 | "github.com/bnema/gordon/pkg/docker" | 14 | "github.com/bnema/gordon/pkg/docker" | ||
| 15 | - | "github.com/charmbracelet/log" | 15 | + | "github.com/bnema/gordon/pkg/logger" |
| 16 | "github.com/labstack/echo/v4" | 16 | "github.com/labstack/echo/v4" | ||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| @@ -33,12 +33,12 @@ func (p *Proxy) loadRoutes() error { | |||||
| 33 | p.mu.Lock() | 33 | p.mu.Lock() | ||
| 34 | defer p.mu.Unlock() | 34 | defer p.mu.Unlock() | ||
| 35 | 35 | ||||
| 36 | - | log.Debug("Loading proxy routes from database") | 36 | + | logger.Debug("Loading proxy routes from database") |
| 37 | 37 | ||||
| 38 | // Now check if we have a Gordon container ID stored for identity purposes | 38 | // Now check if we have a Gordon container ID stored for identity purposes | ||
| 39 | - | p.gordonContainerID = p.loadGordonContainerID() | 39 | + | p.gordonContainerID = p.detectGordonContainer() |
| 40 | if p.gordonContainerID != "" { | 40 | if p.gordonContainerID != "" { | ||
| 41 | - | log.Info("Loaded Gordon container ID from config file during route initialization", "container_id", p.gordonContainerID) | 41 | + | logger.Info("Loaded Gordon container ID during route initialization", "container_id", p.gordonContainerID) |
| 42 | } | 42 | } | ||
| 43 | 43 | ||||
| 44 | // Save the admin domain route if it exists | 44 | // Save the admin domain route if it exists | ||
| @@ -46,11 +46,11 @@ func (p *Proxy) loadRoutes() error { | |||||
| 46 | var adminRoute *ProxyRouteInfo | 46 | var adminRoute *ProxyRouteInfo | ||
| 47 | if route, exists := p.routes[adminDomain]; exists { | 47 | if route, exists := p.routes[adminDomain]; exists { | ||
| 48 | adminRoute = route | 48 | adminRoute = route | ||
| 49 | - | log.Debug("Preserving admin domain route", "domain", adminDomain) | 49 | + | logger.Debug("Preserving admin domain route", "domain", adminDomain) |
| 50 | } | 50 | } | ||
| 51 | 51 | ||||
| 52 | - | // Query the database for active proxy routes | 52 | + | // Query the database for active proxy routes using retry mechanism |
| 53 | - | rows, err := p.app.GetDB().Query(` | 53 | + | rows, err := p.dbQueryWithRetry(` |
| 54 | SELECT pr.id, d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path, pr.active | 54 | SELECT pr.id, d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path, pr.active | ||
| 55 | FROM proxy_route pr | 55 | FROM proxy_route pr | ||
| 56 | JOIN domain d ON pr.domain_id = d.id | 56 | JOIN domain d ON pr.domain_id = d.id | ||
| @@ -58,13 +58,13 @@ func (p *Proxy) loadRoutes() error { | |||||
| 58 | `) | 58 | `) | ||
| 59 | if err != nil { | 59 | if err != nil { | ||
| 60 | if err == sql.ErrNoRows { | 60 | if err == sql.ErrNoRows { | ||
| 61 | - | log.Info("No active proxy routes found") | 61 | + | logger.Info("No active proxy routes found") |
| 62 | 62 | ||||
| 63 | // Restore admin route if it was saved | 63 | // Restore admin route if it was saved | ||
| 64 | if adminRoute != nil { | 64 | if adminRoute != nil { | ||
| 65 | p.routes = make(map[string]*ProxyRouteInfo) | 65 | p.routes = make(map[string]*ProxyRouteInfo) | ||
| 66 | p.routes[adminDomain] = adminRoute | 66 | p.routes[adminDomain] = adminRoute | ||
| 67 | - | log.Debug("Restored admin domain route after empty database query", "domain", adminDomain) | 67 | + | logger.Debug("Restored admin domain route after empty database query", "domain", adminDomain) |
| 68 | } | 68 | } | ||
| 69 | 69 | ||||
| 70 | return nil | 70 | return nil | ||
| @@ -79,7 +79,7 @@ func (p *Proxy) loadRoutes() error { | |||||
| 79 | // Restore admin route if it was saved | 79 | // Restore admin route if it was saved | ||
| 80 | if adminRoute != nil { | 80 | if adminRoute != nil { | ||
| 81 | p.routes[adminDomain] = adminRoute | 81 | p.routes[adminDomain] = adminRoute | ||
| 82 | - | log.Debug("Restored admin domain route after clearing routes map", "domain", adminDomain) | 82 | + | logger.Debug("Restored admin domain route after clearing routes map", "domain", adminDomain) |
| 83 | } | 83 | } | ||
| 84 | 84 | ||||
| 85 | // Populate the routes map | 85 | // Populate the routes map | ||
| @@ -90,6 +90,9 @@ func (p *Proxy) loadRoutes() error { | |||||
| 90 | return fmt.Errorf("failed to scan row: %w", err) | 90 | return fmt.Errorf("failed to scan row: %w", err) | ||
| 91 | } | 91 | } | ||
| 92 | 92 | ||||
| 93 | + | // Normalize domain (lowercase, no trailing dot) | |||
| 94 | + | domain = strings.TrimSuffix(strings.ToLower(domain), ".") | |||
| 95 | + | ||||
| 93 | // Add the route to the map | 96 | // Add the route to the map | ||
| 94 | p.routes[domain] = &ProxyRouteInfo{ | 97 | p.routes[domain] = &ProxyRouteInfo{ | ||
| 95 | Domain: domain, | 98 | Domain: domain, | ||
| @@ -101,14 +104,14 @@ func (p *Proxy) loadRoutes() error { | |||||
| 101 | Active: active, | 104 | Active: active, | ||
| 102 | } | 105 | } | ||
| 103 | 106 | ||||
| 104 | - | log.Debug("Loaded proxy route", | 107 | + | logger.Debug("Loaded proxy route", |
| 105 | "domain", domain, | 108 | "domain", domain, | ||
| 106 | "containerIP", containerIP, | 109 | "containerIP", containerIP, | ||
| 107 | "containerPort", containerPort, | 110 | "containerPort", containerPort, | ||
| 108 | ) | 111 | ) | ||
| 109 | } | 112 | } | ||
| 110 | 113 | ||||
| 111 | - | log.Info("Loaded proxy routes", "count", len(p.routes)) | 114 | + | logger.Info("Loaded proxy routes", "count", len(p.routes)) |
| 112 | return nil | 115 | return nil | ||
| 113 | } | 116 | } | ||
| 114 | 117 | ||||
| @@ -116,7 +119,7 @@ func (p *Proxy) loadRoutes() error { | |||||
| 116 | func (p *Proxy) configureRoutes() { | 119 | func (p *Proxy) configureRoutes() { | ||
| 117 | // Get the admin domain from the config | 120 | // Get the admin domain from the config | ||
| 118 | adminDomain := p.app.GetConfig().Http.FullDomain() | 121 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| 119 | - | log.Debug("Configuring proxy routes", "admin_domain", adminDomain) | 122 | + | logger.Debug("Configuring proxy routes", "admin_domain", adminDomain) |
| 120 | 123 | ||||
| 121 | // Verify admin domain is in routes - if not, we need to add it | 124 | // Verify admin domain is in routes - if not, we need to add it | ||
| 122 | p.mu.RLock() | 125 | p.mu.RLock() | ||
| @@ -124,7 +127,7 @@ func (p *Proxy) configureRoutes() { | |||||
| 124 | p.mu.RUnlock() | 127 | p.mu.RUnlock() | ||
| 125 | 128 | ||||
| 126 | if !adminRouteExists { | 129 | if !adminRouteExists { | ||
| 127 | - | log.Warn("Admin domain route is missing, attempting to recreate it") | 130 | + | logger.Warn("Admin domain route is missing, attempting to recreate it") |
| 128 | p.mu.Lock() | 131 | p.mu.Lock() | ||
| 129 | // Auto-detect the Gordon container name | 132 | // Auto-detect the Gordon container name | ||
| 130 | containerName := p.detectGordonContainer() | 133 | containerName := p.detectGordonContainer() | ||
| @@ -151,18 +154,60 @@ func (p *Proxy) configureRoutes() { | |||||
| 151 | Path: "/", | 154 | Path: "/", | ||
| 152 | Active: true, | 155 | Active: true, | ||
| 153 | } | 156 | } | ||
| 154 | - | log.Info("Recreated admin domain route", | 157 | + | logger.Info("Recreated admin domain route", |
| 155 | "domain", adminDomain, | 158 | "domain", adminDomain, | ||
| 156 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | 159 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | ||
| 157 | p.mu.Unlock() | 160 | p.mu.Unlock() | ||
| 158 | } else { | 161 | } else { | ||
| 159 | - | log.Debug("Admin domain route exists", "domain", adminDomain) | 162 | + | logger.Debug("Admin domain route exists", "domain", adminDomain) |
| 160 | } | 163 | } | ||
| 161 | 164 | ||||
| 162 | // Handler function - will be assigned to both HTTP and HTTPS servers | 165 | // Handler function - will be assigned to both HTTP and HTTPS servers | ||
| 163 | handler := echo.HandlerFunc(func(c echo.Context) error { | 166 | handler := echo.HandlerFunc(func(c echo.Context) error { | ||
| 164 | host := c.Request().Host | 167 | host := c.Request().Host | ||
| 165 | 168 | ||||
| 169 | + | // Strip port if present in the host | |||
| 170 | + | if hostParts := strings.Split(host, ":"); len(hostParts) > 1 { | |||
| 171 | + | host = hostParts[0] | |||
| 172 | + | } | |||
| 173 | + | ||||
| 174 | + | // Normalize host (trim trailing dot, convert to lowercase) | |||
| 175 | + | host = strings.TrimSuffix(strings.ToLower(host), ".") | |||
| 176 | + | ||||
| 177 | + | // Special handling for debug: log all request details | |||
| 178 | + | logger.Debug("Request headers for proxy", | |||
| 179 | + | "domain", host, | |||
| 180 | + | "X-Forwarded-For", c.Request().Header.Get("X-Forwarded-For"), | |||
| 181 | + | "X-Real-IP", c.Request().Header.Get("X-Real-IP")) | |||
| 182 | + | ||||
| 183 | + | // Check if this is the admin domain | |||
| 184 | + | adminDomain := p.app.GetConfig().Http.FullDomain() | |||
| 185 | + | if strings.EqualFold(host, adminDomain) { | |||
| 186 | + | // Always prioritize admin domain | |||
| 187 | + | p.mu.RLock() | |||
| 188 | + | adminRoute, adminOk := p.routes[adminDomain] | |||
| 189 | + | p.mu.RUnlock() | |||
| 190 | + | ||||
| 191 | + | if adminOk { | |||
| 192 | + | logger.Debug("Proxying request to admin domain", | |||
| 193 | + | "domain", host, | |||
| 194 | + | "target", fmt.Sprintf("%s://%s:%s", adminRoute.Protocol, adminRoute.ContainerIP, adminRoute.ContainerPort), | |||
| 195 | + | "path", c.Request().URL.Path) | |||
| 196 | + | ||||
| 197 | + | // Special logging for admin domain handling | |||
| 198 | + | logger.Debug("Proxying request", | |||
| 199 | + | "host", host, | |||
| 200 | + | "target", fmt.Sprintf("%s://%s:%s", adminRoute.Protocol, adminRoute.ContainerIP, adminRoute.ContainerPort), | |||
| 201 | + | "path", c.Request().URL.Path, | |||
| 202 | + | "clientIP", c.RealIP(), | |||
| 203 | + | "remoteAddr", c.Request().RemoteAddr, | |||
| 204 | + | "originalProtocol", c.Scheme()) | |||
| 205 | + | ||||
| 206 | + | // Use the admin route | |||
| 207 | + | return p.proxyRequest(c, adminRoute) | |||
| 208 | + | } | |||
| 209 | + | } | |||
| 210 | + | ||||
| 166 | // Important: Get the route freshly from the map for every request | 211 | // Important: Get the route freshly from the map for every request | ||
| 167 | // This ensures we always use the most up-to-date IP | 212 | // This ensures we always use the most up-to-date IP | ||
| 168 | p.mu.RLock() | 213 | p.mu.RLock() | ||
| @@ -185,7 +230,7 @@ func (p *Proxy) configureRoutes() { | |||||
| 185 | p.mu.RUnlock() | 230 | p.mu.RUnlock() | ||
| 186 | 231 | ||||
| 187 | // Log warning for non-IP hosts that aren't configured | 232 | // Log warning for non-IP hosts that aren't configured | ||
| 188 | - | log.Warn("Request with unknown host", | 233 | + | logger.Warn("Request with unknown host", |
| 189 | "requested_host", host, | 234 | "requested_host", host, | ||
| 190 | "client_ip", c.RealIP(), | 235 | "client_ip", c.RealIP(), | ||
| 191 | "available_domains", strings.Join(availableDomains, ", ")) | 236 | "available_domains", strings.Join(availableDomains, ", ")) | ||
| @@ -194,164 +239,22 @@ func (p *Proxy) configureRoutes() { | |||||
| 194 | 239 | ||||
| 195 | // Check if the route is active | 240 | // Check if the route is active | ||
| 196 | if !route.Active { | 241 | if !route.Active { | ||
| 197 | - | log.Warn("Route is not active", | 242 | + | logger.Warn("Route is not active", |
| 198 | "domain", host, | 243 | "domain", host, | ||
| 199 | "client_ip", c.RealIP()) | 244 | "client_ip", c.RealIP()) | ||
| 200 | return c.String(http.StatusServiceUnavailable, "Route is not active") | 245 | return c.String(http.StatusServiceUnavailable, "Route is not active") | ||
| 201 | } | 246 | } | ||
| 202 | 247 | ||||
| 203 | - | // Special handling for the admin domain | 248 | + | // Regular debug logging for all domains |
| 204 | - | if host == adminDomain { | 249 | + | logger.Debug("Proxying request", |
| 205 | - | log.Debug("Proxying request to admin domain", | 250 | + | "host", host, |
| 206 | - | "domain", host, | 251 | + | "target", fmt.Sprintf("%s://%s:%s", route.Protocol, route.ContainerIP, route.ContainerPort), |
| 207 | - | "target", fmt.Sprintf("%s://%s:%s", route.Protocol, route.ContainerIP, route.ContainerPort), | 252 | + | "path", c.Request().URL.Path, |
| 208 | - | "path", c.Request().URL.Path) | 253 | + | "clientIP", c.RealIP(), |
| 209 | - | } | 254 | + | "remoteAddr", c.Request().RemoteAddr, |
| 210 | - | 255 | + | "originalProtocol", c.Scheme()) | |
| 211 | - | // For container routes, verify the IP is reachable before proxying | |||
| 212 | - | if host != adminDomain { | |||
| 213 | - | // Test TCP connection to container before proxying | |||
| 214 | - | dialer := net.Dialer{Timeout: 500 * time.Millisecond} | |||
| 215 | - | // Format address properly for both IPv4 and IPv6 | |||
| 216 | - | target := route.ContainerIP | |||
| 217 | - | if strings.Contains(target, ":") { | |||
| 218 | - | // IPv6 address needs to be wrapped in brackets | |||
| 219 | - | target = "[" + target + "]" | |||
| 220 | - | } | |||
| 221 | - | conn, err := dialer.Dial("tcp", net.JoinHostPort(target, route.ContainerPort)) | |||
| 222 | - | if err != nil { | |||
| 223 | - | // Connection failed, check if container exists but has a new IP | |||
| 224 | - | containerID := route.ContainerID | |||
| 225 | - | if containerID != "" { | |||
| 226 | - | // Try to get container info and check if it has a different IP | |||
| 227 | - | containerInfo, err := docker.GetContainerInfo(containerID) | |||
| 228 | - | if err == nil && containerInfo.NetworkSettings != nil { | |||
| 229 | - | // Check if container has a different IP | |||
| 230 | - | networkName := p.app.GetConfig().ContainerEngine.Network | |||
| 231 | - | if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && | |||
| 232 | - | networkSettings.IPAddress != "" && networkSettings.IPAddress != route.ContainerIP { | |||
| 233 | - | // Found container with different IP, update the route and use new IP for this request | |||
| 234 | - | log.Warn("Container IP mismatch detected, using live IP instead of cached value", | |||
| 235 | - | "domain", host, | |||
| 236 | - | "cached_ip", route.ContainerIP, | |||
| 237 | - | "live_ip", networkSettings.IPAddress) | |||
| 238 | - | ||||
| 239 | - | // Update the in-memory route for this request | |||
| 240 | - | route.ContainerIP = networkSettings.IPAddress | |||
| 241 | - | ||||
| 242 | - | // Update the database in background to persist the change | |||
| 243 | - | go func(domain, containerID, containerIP, containerPort string) { | |||
| 244 | - | // Get the domain ID | |||
| 245 | - | var domainID string | |||
| 246 | - | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |||
| 247 | - | if err != nil { | |||
| 248 | - | log.Error("Failed to find domain ID for IP update", "domain", domain, "error", err) | |||
| 249 | - | return | |||
| 250 | - | } | |||
| 251 | - | ||||
| 252 | - | // Update the proxy_route record | |||
| 253 | - | now := time.Now().Format(time.RFC3339) | |||
| 254 | - | _, err = p.app.GetDB().Exec(` | |||
| 255 | - | UPDATE proxy_route SET container_ip = ?, updated_at = ? | |||
| 256 | - | WHERE domain_id = ? AND container_id = ?`, | |||
| 257 | - | containerIP, now, domainID, containerID) | |||
| 258 | - | ||||
| 259 | - | if err != nil { | |||
| 260 | - | log.Error("Failed to update container IP in database", | |||
| 261 | - | "domain", domain, | |||
| 262 | - | "containerID", containerID, | |||
| 263 | - | "error", err) | |||
| 264 | - | } else { | |||
| 265 | - | log.Info("Updated container IP in database after live detection", | |||
| 266 | - | "domain", domain, | |||
| 267 | - | "containerID", containerID, | |||
| 268 | - | "old_ip", route.ContainerIP, | |||
| 269 | - | "new_ip", containerIP) | |||
| 270 | - | } | |||
| 271 | - | }(host, containerID, networkSettings.IPAddress, route.ContainerPort) | |||
| 272 | - | } else { | |||
| 273 | - | log.Debug("Container exists but connection still failed", | |||
| 274 | - | "domain", host, | |||
| 275 | - | "container_id", containerID, | |||
| 276 | - | "container_ip", route.ContainerIP, | |||
| 277 | - | "container_port", route.ContainerPort, | |||
| 278 | - | "error", err) | |||
| 279 | - | } | |||
| 280 | - | } | |||
| 281 | - | } | |||
| 282 | - | } else { | |||
| 283 | - | // Connection successful, close it | |||
| 284 | - | conn.Close() | |||
| 285 | - | } | |||
| 286 | - | } | |||
| 287 | - | ||||
| 288 | - | // Create the target URL | |||
| 289 | - | targetURL := &url.URL{ | |||
| 290 | - | Scheme: "http", // Always use HTTP for internal connections | |||
| 291 | - | } | |||
| 292 | 256 | ||||
| 293 | - | // Log protocol conversion if needed | 257 | + | return p.proxyRequest(c, route) |
| 294 | - | if route.Protocol == "https" { | |||
| 295 | - | log.Debug("Converting HTTPS external route to HTTP for internal connection", | |||
| 296 | - | "domain", host, | |||
| 297 | - | "containerID", route.ContainerID, | |||
| 298 | - | "externalProtocol", "https", | |||
| 299 | - | "internalProtocol", "http") | |||
| 300 | - | } | |||
| 301 | - | ||||
| 302 | - | // Format host properly for both IPv4 and IPv6 | |||
| 303 | - | containerIP := route.ContainerIP | |||
| 304 | - | if strings.Contains(containerIP, ":") { | |||
| 305 | - | // IPv6 address needs to be wrapped in brackets | |||
| 306 | - | containerIP = "[" + containerIP + "]" | |||
| 307 | - | } | |||
| 308 | - | targetURL.Host = fmt.Sprintf("%s:%s", containerIP, route.ContainerPort) | |||
| 309 | - | ||||
| 310 | - | // Create a reverse proxy | |||
| 311 | - | proxy := httputil.NewSingleHostReverseProxy(targetURL) | |||
| 312 | - | ||||
| 313 | - | // Update headers to allow for SSL redirection | |||
| 314 | - | originalDirector := proxy.Director | |||
| 315 | - | proxy.Director = func(req *http.Request) { | |||
| 316 | - | originalDirector(req) | |||
| 317 | - | // Use the original protocol for forwarded proto header | |||
| 318 | - | forwardedProto := "http" | |||
| 319 | - | if route.Protocol == "https" { | |||
| 320 | - | forwardedProto = "https" | |||
| 321 | - | } | |||
| 322 | - | req.Header.Set("X-Forwarded-Proto", forwardedProto) | |||
| 323 | - | req.Header.Set("X-Forwarded-Host", host) | |||
| 324 | - | req.Header.Set("X-Forwarded-For", c.RealIP()) | |||
| 325 | - | req.Header.Set("X-Real-IP", c.RealIP()) | |||
| 326 | - | ||||
| 327 | - | // Debug information | |||
| 328 | - | log.Debug("Proxying request", | |||
| 329 | - | "host", host, | |||
| 330 | - | "target", targetURL.String(), | |||
| 331 | - | "path", req.URL.Path, | |||
| 332 | - | "clientIP", c.RealIP(), | |||
| 333 | - | "remoteAddr", req.RemoteAddr, | |||
| 334 | - | "originalProtocol", route.Protocol) | |||
| 335 | - | ||||
| 336 | - | // Log all headers for troubleshooting | |||
| 337 | - | log.Debug("Request headers for proxy", | |||
| 338 | - | "domain", host, | |||
| 339 | - | "X-Forwarded-For", req.Header.Get("X-Forwarded-For"), | |||
| 340 | - | "X-Real-IP", req.Header.Get("X-Real-IP")) | |||
| 341 | - | } | |||
| 342 | - | ||||
| 343 | - | // Add error handling | |||
| 344 | - | proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { | |||
| 345 | - | log.Error("Proxy error", | |||
| 346 | - | "host", host, | |||
| 347 | - | "path", r.URL.Path, | |||
| 348 | - | "error", err) | |||
| 349 | - | c.String(http.StatusBadGateway, "Proxy Error: "+err.Error()) | |||
| 350 | - | } | |||
| 351 | - | ||||
| 352 | - | // Serve the request | |||
| 353 | - | proxy.ServeHTTP(c.Response(), c.Request()) | |||
| 354 | - | return nil | |||
| 355 | }) | 258 | }) | ||
| 356 | 259 | ||||
| 357 | // Add the handler to the HTTPS server | 260 | // Add the handler to the HTTPS server | ||
| @@ -370,8 +273,19 @@ func (p *Proxy) configureRoutes() { | |||||
| 370 | // Note: Regardless of the external protocol, all internal connections to containers use HTTP. | 273 | // Note: Regardless of the external protocol, all internal connections to containers use HTTP. | ||
| 371 | // The HTTPS protocol is only used for external connections and certificates. | 274 | // The HTTPS protocol is only used for external connections and certificates. | ||
| 372 | func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, protocol, path string) error { | 275 | func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, protocol, path string) error { | ||
| 373 | - | // Begin a transaction | 276 | + | // Normalize domain name (lowercase, no trailing dot) |
| 374 | - | tx, err := p.app.GetDB().Begin() | 277 | + | domainName = strings.TrimSuffix(strings.ToLower(domainName), ".") |
| 278 | + | ||||
| 279 | + | logger.Debug("Adding proxy route", | |||
| 280 | + | "domain", domainName, | |||
| 281 | + | "container_id", containerID, | |||
| 282 | + | "container_ip", containerIP, | |||
| 283 | + | "container_port", containerPort, | |||
| 284 | + | "protocol", protocol, | |||
| 285 | + | "path", path) | |||
| 286 | + | ||||
| 287 | + | // Begin a transaction with retry | |||
| 288 | + | tx, err := p.dbBeginWithRetry() | |||
| 375 | if err != nil { | 289 | if err != nil { | ||
| 376 | return fmt.Errorf("failed to begin transaction: %w", err) | 290 | return fmt.Errorf("failed to begin transaction: %w", err) | ||
| 377 | } | 291 | } | ||
| @@ -391,9 +305,9 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 391 | err = tx.QueryRow("SELECT id FROM account LIMIT 1").Scan(&accountID) | 305 | err = tx.QueryRow("SELECT id FROM account LIMIT 1").Scan(&accountID) | ||
| 392 | if err != nil { | 306 | if err != nil { | ||
| 393 | if err == sql.ErrNoRows { | 307 | if err == sql.ErrNoRows { | ||
| 394 | - | log.Warn("No account found when adding route, domains will have NULL account_id") | 308 | + | logger.Warn("No account found when adding route, domains will have NULL account_id") |
| 395 | } else { | 309 | } else { | ||
| 396 | - | log.Warn("Error fetching account for route, continuing anyway", "error", err) | 310 | + | logger.Warn("Error fetching account for route, continuing anyway", "error", err) |
| 397 | } | 311 | } | ||
| 398 | // Continue with NULL account_id if no account found | 312 | // Continue with NULL account_id if no account found | ||
| 399 | } | 313 | } | ||
| @@ -406,7 +320,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 406 | // Domain doesn't exist, create it | 320 | // Domain doesn't exist, create it | ||
| 407 | domainID = proxyGenerateUUID() | 321 | domainID = proxyGenerateUUID() | ||
| 408 | now := time.Now().Format(time.RFC3339) | 322 | now := time.Now().Format(time.RFC3339) | ||
| 409 | - | _, err = tx.Exec( | 323 | + | _, err = txExecWithRetry(tx, |
| 410 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | 324 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | ||
| 411 | domainID, hostname, accountID, now, now, | 325 | domainID, hostname, accountID, now, now, | ||
| 412 | ) | 326 | ) | ||
| @@ -414,7 +328,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 414 | return fmt.Errorf("failed to insert domain: %w", err) | 328 | return fmt.Errorf("failed to insert domain: %w", err) | ||
| 415 | } | 329 | } | ||
| 416 | 330 | ||||
| 417 | - | log.Debug("Added domain to database for Let's Encrypt", | 331 | + | logger.Debug("Added domain to database for Let's Encrypt", |
| 418 | "domain", hostname) | 332 | "domain", hostname) | ||
| 419 | 333 | ||||
| 420 | // Also add the subdomain.domain format to the database if this is a domain with components | 334 | // Also add the subdomain.domain format to the database if this is a domain with components | ||
| @@ -432,16 +346,16 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 432 | if err == sql.ErrNoRows { | 346 | if err == sql.ErrNoRows { | ||
| 433 | // Domain part doesn't exist, create it | 347 | // Domain part doesn't exist, create it | ||
| 434 | domainPartID = proxyGenerateUUID() | 348 | domainPartID = proxyGenerateUUID() | ||
| 435 | - | _, err = tx.Exec( | 349 | + | _, err = txExecWithRetry(tx, |
| 436 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | 350 | "INSERT INTO domain (id, name, account_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", | ||
| 437 | domainPartID, domainPart, accountID, now, now, | 351 | domainPartID, domainPart, accountID, now, now, | ||
| 438 | ) | 352 | ) | ||
| 439 | if err != nil { | 353 | if err != nil { | ||
| 440 | - | log.Warn("Failed to insert domain part, continuing anyway", | 354 | + | logger.Warn("Failed to insert domain part, continuing anyway", |
| 441 | "domain_part", domainPart, | 355 | "domain_part", domainPart, | ||
| 442 | "error", err) | 356 | "error", err) | ||
| 443 | } else { | 357 | } else { | ||
| 444 | - | log.Debug("Added domain part to database for Let's Encrypt", | 358 | + | logger.Debug("Added domain part to database for Let's Encrypt", |
| 445 | "domain_part", domainPart, | 359 | "domain_part", domainPart, | ||
| 446 | "subdomain", subdomain) | 360 | "subdomain", subdomain) | ||
| 447 | } | 361 | } | ||
| @@ -468,20 +382,20 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 468 | 382 | ||||
| 469 | // Log more detailed information about the update | 383 | // Log more detailed information about the update | ||
| 470 | if existingContainerID != containerID { | 384 | if existingContainerID != containerID { | ||
| 471 | - | log.Info("Updating proxy route for recreated container", | 385 | + | logger.Info("Updating proxy route for recreated container", |
| 472 | "domain", hostname, | 386 | "domain", hostname, | ||
| 473 | "old_container_id", existingContainerID, | 387 | "old_container_id", existingContainerID, | ||
| 474 | "new_container_id", containerID) | 388 | "new_container_id", containerID) | ||
| 475 | } | 389 | } | ||
| 476 | 390 | ||||
| 477 | if existingContainerPort != containerPort { | 391 | if existingContainerPort != containerPort { | ||
| 478 | - | log.Info("Container port changed for domain", | 392 | + | logger.Info("Container port changed for domain", |
| 479 | "domain", hostname, | 393 | "domain", hostname, | ||
| 480 | "old_port", existingContainerPort, | 394 | "old_port", existingContainerPort, | ||
| 481 | "new_port", containerPort) | 395 | "new_port", containerPort) | ||
| 482 | } | 396 | } | ||
| 483 | 397 | ||||
| 484 | - | _, err = tx.Exec( | 398 | + | _, err = txExecWithRetry(tx, |
| 485 | `UPDATE proxy_route SET | 399 | `UPDATE proxy_route SET | ||
| 486 | container_id = ?, | 400 | container_id = ?, | ||
| 487 | container_ip = ?, | 401 | container_ip = ?, | ||
| @@ -500,7 +414,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 500 | // Route doesn't exist, create it | 414 | // Route doesn't exist, create it | ||
| 501 | routeID := proxyGenerateUUID() | 415 | routeID := proxyGenerateUUID() | ||
| 502 | now := time.Now().Format(time.RFC3339) | 416 | now := time.Now().Format(time.RFC3339) | ||
| 503 | - | _, err = tx.Exec( | 417 | + | _, err = txExecWithRetry(tx, |
| 504 | `INSERT INTO proxy_route ( | 418 | `INSERT INTO proxy_route ( | ||
| 505 | id, domain_id, container_id, container_ip, container_port, | 419 | id, domain_id, container_id, container_ip, container_port, | ||
| 506 | protocol, path, active, created_at, updated_at | 420 | protocol, path, active, created_at, updated_at | ||
| @@ -525,7 +439,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 525 | return fmt.Errorf("failed to reload routes: %w", err) | 439 | return fmt.Errorf("failed to reload routes: %w", err) | ||
| 526 | } | 440 | } | ||
| 527 | 441 | ||||
| 528 | - | log.Info("Added proxy route", | 442 | + | logger.Info("Added proxy route", |
| 529 | "domain", hostname, | 443 | "domain", hostname, | ||
| 530 | "containerIP", containerIP, | 444 | "containerIP", containerIP, | ||
| 531 | "containerPort", containerPort, | 445 | "containerPort", containerPort, | ||
| @@ -533,7 +447,7 @@ func (p *Proxy) AddRoute(domainName, containerID, containerIP, containerPort, pr | |||||
| 533 | 447 | ||||
| 534 | // Request a certificate if this is an HTTPS route | 448 | // Request a certificate if this is an HTTPS route | ||
| 535 | if strings.ToLower(protocol) == "https" { | 449 | if strings.ToLower(protocol) == "https" { | ||
| 536 | - | log.Info("Requesting Let's Encrypt certificate for new HTTPS route", | 450 | + | logger.Info("Requesting Let's Encrypt certificate for new HTTPS route", |
| 537 | "domain", hostname) | 451 | "domain", hostname) | ||
| 538 | // Run in a goroutine to avoid blocking | 452 | // Run in a goroutine to avoid blocking | ||
| 539 | go func(domain string) { | 453 | go func(domain string) { | ||
| @@ -554,13 +468,13 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||||
| 554 | // Prevent removing the admin domain | 468 | // Prevent removing the admin domain | ||
| 555 | adminDomain := p.app.GetConfig().Http.FullDomain() | 469 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| 556 | if domainName == adminDomain { | 470 | if domainName == adminDomain { | ||
| 557 | - | log.Warn("Attempt to remove admin domain route prevented", | 471 | + | logger.Warn("Attempt to remove admin domain route prevented", |
| 558 | "domain", domainName) | 472 | "domain", domainName) | ||
| 559 | return fmt.Errorf("cannot remove admin domain route: %s", domainName) | 473 | return fmt.Errorf("cannot remove admin domain route: %s", domainName) | ||
| 560 | } | 474 | } | ||
| 561 | 475 | ||||
| 562 | - | // Begin a transaction | 476 | + | // Begin a transaction with retry |
| 563 | - | tx, err := p.app.GetDB().Begin() | 477 | + | tx, err := p.dbBeginWithRetry() |
| 564 | if err != nil { | 478 | if err != nil { | ||
| 565 | return fmt.Errorf("failed to begin transaction: %w", err) | 479 | return fmt.Errorf("failed to begin transaction: %w", err) | ||
| 566 | } | 480 | } | ||
| @@ -576,14 +490,14 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||||
| 576 | return fmt.Errorf("failed to query domain: %w", err) | 490 | return fmt.Errorf("failed to query domain: %w", err) | ||
| 577 | } | 491 | } | ||
| 578 | 492 | ||||
| 579 | - | // Delete the route | 493 | + | // Delete the route with retry |
| 580 | - | _, err = tx.Exec("DELETE FROM proxy_route WHERE domain_id = ?", domainID) | 494 | + | _, err = txExecWithRetry(tx, "DELETE FROM proxy_route WHERE domain_id = ?", domainID) |
| 581 | if err != nil { | 495 | if err != nil { | ||
| 582 | return fmt.Errorf("failed to delete route: %w", err) | 496 | return fmt.Errorf("failed to delete route: %w", err) | ||
| 583 | } | 497 | } | ||
| 584 | 498 | ||||
| 585 | - | // Delete the domain | 499 | + | // Delete the domain with retry |
| 586 | - | _, err = tx.Exec("DELETE FROM domain WHERE id = ?", domainID) | 500 | + | _, err = txExecWithRetry(tx, "DELETE FROM domain WHERE id = ?", domainID) |
| 587 | if err != nil { | 501 | if err != nil { | ||
| 588 | return fmt.Errorf("failed to delete domain: %w", err) | 502 | return fmt.Errorf("failed to delete domain: %w", err) | ||
| 589 | } | 503 | } | ||
| @@ -598,7 +512,7 @@ func (p *Proxy) RemoveRoute(domainName string) error { | |||||
| 598 | return fmt.Errorf("failed to reload routes: %w", err) | 512 | return fmt.Errorf("failed to reload routes: %w", err) | ||
| 599 | } | 513 | } | ||
| 600 | 514 | ||||
| 601 | - | log.Info("Removed proxy route", "domain", domainName) | 515 | + | logger.Info("Removed proxy route", "domain", domainName) |
| 602 | return nil | 516 | return nil | ||
| 603 | } | 517 | } | ||
| 604 | 518 | ||||
| @@ -618,10 +532,12 @@ func (p *Proxy) GetRoutes() map[string]*ProxyRouteInfo { | |||||
| 618 | 532 | ||||
| 619 | // Reload reloads the routes from the database and reconfigures the proxy | 533 | // Reload reloads the routes from the database and reconfigures the proxy | ||
| 620 | func (p *Proxy) Reload() error { | 534 | func (p *Proxy) Reload() error { | ||
| 621 | - | log.Info("Reloading proxy routes from database") | 535 | + | logger.Info("Reloading proxy routes from database") |
| 622 | 536 | ||||
| 623 | // Get admin domain - this should be the full subdomain.domain format | 537 | // Get admin domain - this should be the full subdomain.domain format | ||
| 624 | adminDomain := p.app.GetConfig().Http.FullDomain() | 538 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| 539 | + | // Normalize the admin domain (lowercase, no trailing dot) | |||
| 540 | + | adminDomain = strings.TrimSuffix(strings.ToLower(adminDomain), ".") | |||
| 625 | 541 | ||||
| 626 | // Save existing admin route if it exists | 542 | // Save existing admin route if it exists | ||
| 627 | p.mu.RLock() | 543 | p.mu.RLock() | ||
| @@ -636,7 +552,7 @@ func (p *Proxy) Reload() error { | |||||
| 636 | Path: route.Path, | 552 | Path: route.Path, | ||
| 637 | Active: route.Active, | 553 | Active: route.Active, | ||
| 638 | } | 554 | } | ||
| 639 | - | log.Debug("Preserved admin domain route during reload", "domain", adminDomain) | 555 | + | logger.Debug("Preserved admin domain route during reload", "domain", adminDomain) |
| 640 | } | 556 | } | ||
| 641 | p.mu.RUnlock() | 557 | p.mu.RUnlock() | ||
| 642 | 558 | ||||
| @@ -653,7 +569,7 @@ func (p *Proxy) Reload() error { | |||||
| 653 | `) | 569 | `) | ||
| 654 | if err != nil { | 570 | if err != nil { | ||
| 655 | if err == sql.ErrNoRows { | 571 | if err == sql.ErrNoRows { | ||
| 656 | - | log.Info("No active proxy routes found during reload") | 572 | + | logger.Info("No active proxy routes found during reload") |
| 657 | // Even if no routes found, proceed with the swap to clear any old routes | 573 | // Even if no routes found, proceed with the swap to clear any old routes | ||
| 658 | } else { | 574 | } else { | ||
| 659 | return fmt.Errorf("failed to query database during reload: %w", err) | 575 | return fmt.Errorf("failed to query database during reload: %w", err) | ||
| @@ -669,6 +585,9 @@ func (p *Proxy) Reload() error { | |||||
| 669 | return fmt.Errorf("failed to scan row during reload: %w", err) | 585 | return fmt.Errorf("failed to scan row during reload: %w", err) | ||
| 670 | } | 586 | } | ||
| 671 | 587 | ||||
| 588 | + | // Normalize domain (lowercase, no trailing dot) | |||
| 589 | + | domain = strings.TrimSuffix(strings.ToLower(domain), ".") | |||
| 590 | + | ||||
| 672 | // Add the route to the new map | 591 | // Add the route to the new map | ||
| 673 | newRoutes[domain] = &ProxyRouteInfo{ | 592 | newRoutes[domain] = &ProxyRouteInfo{ | ||
| 674 | Domain: domain, | 593 | Domain: domain, | ||
| @@ -680,7 +599,7 @@ func (p *Proxy) Reload() error { | |||||
| 680 | Active: active, | 599 | Active: active, | ||
| 681 | } | 600 | } | ||
| 682 | 601 | ||||
| 683 | - | log.Debug("Loaded route during reload", | 602 | + | logger.Debug("Loaded route during reload", |
| 684 | "domain", domain, | 603 | "domain", domain, | ||
| 685 | "containerID", containerID, | 604 | "containerID", containerID, | ||
| 686 | "containerIP", containerIP, | 605 | "containerIP", containerIP, | ||
| @@ -694,7 +613,7 @@ func (p *Proxy) Reload() error { | |||||
| 694 | // If admin domain wasn't loaded from DB but we had it in memory, restore it | 613 | // If admin domain wasn't loaded from DB but we had it in memory, restore it | ||
| 695 | if !adminLoaded && adminRoute != nil { | 614 | if !adminLoaded && adminRoute != nil { | ||
| 696 | newRoutes[adminDomain] = adminRoute | 615 | newRoutes[adminDomain] = adminRoute | ||
| 697 | - | log.Warn("Admin domain route missing from database, restoring from memory", | 616 | + | logger.Warn("Admin domain route missing from database, restoring from memory", |
| 698 | "domain", adminDomain) | 617 | "domain", adminDomain) | ||
| 699 | 618 | ||||
| 700 | // Also try to add it back to the database | 619 | // Also try to add it back to the database | ||
| @@ -708,17 +627,17 @@ func (p *Proxy) Reload() error { | |||||
| 708 | ) | 627 | ) | ||
| 709 | 628 | ||||
| 710 | if err != nil { | 629 | if err != nil { | ||
| 711 | - | log.Error("Failed to save admin route to database", | 630 | + | logger.Error("Failed to save admin route to database", |
| 712 | "error", err, | 631 | "error", err, | ||
| 713 | "domain", adminDomain) | 632 | "domain", adminDomain) | ||
| 714 | } else { | 633 | } else { | ||
| 715 | - | log.Info("Re-added admin domain route to database", "domain", adminDomain) | 634 | + | logger.Info("Re-added admin domain route to database", "domain", adminDomain) |
| 716 | } | 635 | } | ||
| 717 | } | 636 | } | ||
| 718 | 637 | ||||
| 719 | // If admin domain is still missing, recreate it | 638 | // If admin domain is still missing, recreate it | ||
| 720 | if _, exists := newRoutes[adminDomain]; !exists { | 639 | if _, exists := newRoutes[adminDomain]; !exists { | ||
| 721 | - | log.Warn("Admin domain route is missing, recreating it", "domain", adminDomain) | 640 | + | logger.Warn("Admin domain route is missing, recreating it", "domain", adminDomain) |
| 722 | 641 | ||||
| 723 | // Auto-detect the Gordon container information | 642 | // Auto-detect the Gordon container information | ||
| 724 | containerName := p.detectGordonContainer() | 643 | containerName := p.detectGordonContainer() | ||
| @@ -753,11 +672,11 @@ func (p *Proxy) Reload() error { | |||||
| 753 | ) | 672 | ) | ||
| 754 | 673 | ||||
| 755 | if err != nil { | 674 | if err != nil { | ||
| 756 | - | log.Error("Failed to save recreated admin route to database", | 675 | + | logger.Error("Failed to save recreated admin route to database", |
| 757 | "error", err, | 676 | "error", err, | ||
| 758 | "domain", adminDomain) | 677 | "domain", adminDomain) | ||
| 759 | } else { | 678 | } else { | ||
| 760 | - | log.Info("Recreated and saved admin domain route", "domain", adminDomain) | 679 | + | logger.Info("Recreated and saved admin domain route", "domain", adminDomain) |
| 761 | } | 680 | } | ||
| 762 | } | 681 | } | ||
| 763 | 682 | ||||
| @@ -767,20 +686,17 @@ func (p *Proxy) Reload() error { | |||||
| 767 | p.routes = newRoutes | 686 | p.routes = newRoutes | ||
| 768 | p.mu.Unlock() | 687 | p.mu.Unlock() | ||
| 769 | 688 | ||||
| 770 | - | // Clean up any root domain conflict | |||
| 771 | - | p.CleanupRootDomainForAdmin() | |||
| 772 | - | ||||
| 773 | // Rebuild route configuration | 689 | // Rebuild route configuration | ||
| 774 | p.configureRoutes() | 690 | p.configureRoutes() | ||
| 775 | 691 | ||||
| 776 | // Force recreation of the route handlers if servers are initialized | 692 | // Force recreation of the route handlers if servers are initialized | ||
| 777 | if p.httpServer != nil { | 693 | if p.httpServer != nil { | ||
| 778 | - | log.Debug("Refreshing HTTP routes") | 694 | + | logger.Debug("Refreshing HTTP routes") |
| 779 | p.httpServer.Routes() | 695 | p.httpServer.Routes() | ||
| 780 | } | 696 | } | ||
| 781 | 697 | ||||
| 782 | if p.httpsServer != nil { | 698 | if p.httpsServer != nil { | ||
| 783 | - | log.Debug("Refreshing HTTPS routes") | 699 | + | logger.Debug("Refreshing HTTPS routes") |
| 784 | p.httpsServer.Routes() | 700 | p.httpsServer.Routes() | ||
| 785 | } | 701 | } | ||
| 786 | 702 | ||||
| @@ -807,7 +723,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||||
| 807 | if r, ok := p.routes[domain]; ok { | 723 | if r, ok := p.routes[domain]; ok { | ||
| 808 | oldIP := r.ContainerIP | 724 | oldIP := r.ContainerIP | ||
| 809 | r.ContainerIP = newIP | 725 | r.ContainerIP = newIP | ||
| 810 | - | log.Info("Force updated route IP in-memory", | 726 | + | logger.Info("Force updated route IP in-memory", |
| 811 | "domain", domain, | 727 | "domain", domain, | ||
| 812 | "old_ip", oldIP, | 728 | "old_ip", oldIP, | ||
| 813 | "new_ip", newIP) | 729 | "new_ip", newIP) | ||
| @@ -815,16 +731,27 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||||
| 815 | p.mu.Unlock() | 731 | p.mu.Unlock() | ||
| 816 | 732 | ||||
| 817 | // Next, update the database | 733 | // Next, update the database | ||
| 818 | - | // Get the domain ID | 734 | + | // Get the domain ID with retry |
| 819 | var domainID string | 735 | var domainID string | ||
| 820 | - | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | 736 | + | rows, err := p.dbQueryWithRetry("SELECT id FROM domain WHERE name = ?", domain) |
| 821 | if err != nil { | 737 | if err != nil { | ||
| 822 | - | return fmt.Errorf("failed to find domain ID: %w", err) | 738 | + | return fmt.Errorf("failed to query domain: %w", err) |
| 823 | } | 739 | } | ||
| 824 | 740 | ||||
| 825 | - | // Update the proxy_route record | 741 | + | if !rows.Next() { |
| 742 | + | rows.Close() | |||
| 743 | + | return fmt.Errorf("domain not found: %s", domain) | |||
| 744 | + | } | |||
| 745 | + | ||||
| 746 | + | if err := rows.Scan(&domainID); err != nil { | |||
| 747 | + | rows.Close() | |||
| 748 | + | return fmt.Errorf("failed to scan domain ID: %w", err) | |||
| 749 | + | } | |||
| 750 | + | rows.Close() | |||
| 751 | + | ||||
| 752 | + | // Update the proxy_route record with retry | |||
| 826 | now := time.Now().Format(time.RFC3339) | 753 | now := time.Now().Format(time.RFC3339) | ||
| 827 | - | result, err := p.app.GetDB().Exec(` | 754 | + | result, err := p.dbExecWithRetry(` |
| 828 | UPDATE proxy_route SET container_ip = ?, updated_at = ? | 755 | UPDATE proxy_route SET container_ip = ?, updated_at = ? | ||
| 829 | WHERE domain_id = ? AND container_id = ?`, | 756 | WHERE domain_id = ? AND container_id = ?`, | ||
| 830 | newIP, now, domainID, containerID) | 757 | newIP, now, domainID, containerID) | ||
| @@ -838,7 +765,7 @@ func (p *Proxy) ForceUpdateRouteIP(domain string, newIP string) error { | |||||
| 838 | return fmt.Errorf("no rows affected when updating route in database") | 765 | return fmt.Errorf("no rows affected when updating route in database") | ||
| 839 | } | 766 | } | ||
| 840 | 767 | ||||
| 841 | - | log.Info("Force updated route IP in database", | 768 | + | logger.Info("Force updated route IP in database", |
| 842 | "domain", domain, | 769 | "domain", domain, | ||
| 843 | "container_id", containerID, | 770 | "container_id", containerID, | ||
| 844 | "new_ip", newIP) | 771 | "new_ip", newIP) | ||
| @@ -851,7 +778,7 @@ func (p *Proxy) FindRoutesByContainerName(containerName string) []string { | |||||
| 851 | // Get all containers to map IDs to names | 778 | // Get all containers to map IDs to names | ||
| 852 | containers, err := docker.ListRunningContainers() | 779 | containers, err := docker.ListRunningContainers() | ||
| 853 | if err != nil { | 780 | if err != nil { | ||
| 854 | - | log.Error("Failed to list containers when finding routes by container name", "error", err) | 781 | + | logger.Error("Failed to list containers when finding routes by container name", "error", err) |
| 855 | return nil | 782 | return nil | ||
| 856 | } | 783 | } | ||
| 857 | 784 | ||||
| @@ -868,7 +795,7 @@ func (p *Proxy) FindRoutesByContainerName(containerName string) []string { | |||||
| 868 | // This is the exact container ID we're looking for | 795 | // This is the exact container ID we're looking for | ||
| 869 | targetContainerID := containerNameToID[containerName] | 796 | targetContainerID := containerNameToID[containerName] | ||
| 870 | if targetContainerID == "" { | 797 | if targetContainerID == "" { | ||
| 871 | - | log.Debug("Container name not found in running containers", "container_name", containerName) | 798 | + | logger.Debug("Container name not found in running containers", "container_name", containerName) |
| 872 | return nil | 799 | return nil | ||
| 873 | } | 800 | } | ||
| 874 | 801 | ||||
| @@ -894,15 +821,15 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||||
| 894 | 821 | ||||
| 895 | result := make(map[string]*ProxyRouteInfo) | 822 | result := make(map[string]*ProxyRouteInfo) | ||
| 896 | 823 | ||||
| 897 | - | // Query the database to find if any route might be associated with this container name | 824 | + | // Query the database with retry to find if any route might be associated with this container name |
| 898 | - | rows, err := p.app.GetDB().Query(` | 825 | + | rows, err := p.dbQueryWithRetry(` |
| 899 | SELECT d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path | 826 | SELECT d.name, pr.container_id, pr.container_ip, pr.container_port, pr.protocol, pr.path | ||
| 900 | FROM proxy_route pr | 827 | FROM proxy_route pr | ||
| 901 | JOIN domain d ON pr.domain_id = d.id | 828 | JOIN domain d ON pr.domain_id = d.id | ||
| 902 | WHERE pr.active = 1 | 829 | WHERE pr.active = 1 | ||
| 903 | `) | 830 | `) | ||
| 904 | if err != nil { | 831 | if err != nil { | ||
| 905 | - | log.Error("Failed to query database for routes by old name", "error", err) | 832 | + | logger.Error("Failed to query database for routes by old name", "error", err) |
| 906 | return result | 833 | return result | ||
| 907 | } | 834 | } | ||
| 908 | defer rows.Close() | 835 | defer rows.Close() | ||
| @@ -911,7 +838,7 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||||
| 911 | for rows.Next() { | 838 | for rows.Next() { | ||
| 912 | var domain, containerID, containerIP, containerPort, protocol, path string | 839 | var domain, containerID, containerIP, containerPort, protocol, path string | ||
| 913 | if err := rows.Scan(&domain, &containerID, &containerIP, &containerPort, &protocol, &path); err != nil { | 840 | if err := rows.Scan(&domain, &containerID, &containerIP, &containerPort, &protocol, &path); err != nil { | ||
| 914 | - | log.Error("Failed to scan row", "error", err) | 841 | + | logger.Error("Failed to scan row", "error", err) |
| 915 | continue | 842 | continue | ||
| 916 | } | 843 | } | ||
| 917 | 844 | ||||
| @@ -940,8 +867,8 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||||
| 940 | } | 867 | } | ||
| 941 | } else { | 868 | } else { | ||
| 942 | // We found an existing container - if it has the same name, this is interesting | 869 | // We found an existing container - if it has the same name, this is interesting | ||
| 943 | - | containerName = strings.TrimPrefix(name, "/") | 870 | + | existingContainerName := strings.TrimPrefix(name, "/") |
| 944 | - | if containerName == containerName { | 871 | + | if existingContainerName == containerName { |
| 945 | // This is an exact match - shouldn't happen if we're looking for recreated containers | 872 | // This is an exact match - shouldn't happen if we're looking for recreated containers | ||
| 946 | // but include it anyway | 873 | // but include it anyway | ||
| 947 | result[domain] = &ProxyRouteInfo{ | 874 | result[domain] = &ProxyRouteInfo{ | ||
| @@ -963,7 +890,7 @@ func (p *Proxy) FindRoutesByOldName(containerName string) map[string]*ProxyRoute | |||||
| 963 | // HandleContainerRecreation handles a container that has been recreated with the same name but a new ID | 890 | // HandleContainerRecreation handles a container that has been recreated with the same name but a new ID | ||
| 964 | // It updates all relevant routes with the new container ID and IP | 891 | // It updates all relevant routes with the new container ID and IP | ||
| 965 | func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerIP string) { | 892 | func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerIP string) { | ||
| 966 | - | log.Info("Handling potential container recreation", | 893 | + | logger.Info("Handling potential container recreation", |
| 967 | "container_id", containerID, | 894 | "container_id", containerID, | ||
| 968 | "container_name", containerName, | 895 | "container_name", containerName, | ||
| 969 | "container_ip", containerIP) | 896 | "container_ip", containerIP) | ||
| @@ -971,18 +898,21 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 971 | // Get container info to check for gordon labels | 898 | // Get container info to check for gordon labels | ||
| 972 | containerInfo, err := docker.GetContainerInfo(containerID) | 899 | containerInfo, err := docker.GetContainerInfo(containerID) | ||
| 973 | if err != nil { | 900 | if err != nil { | ||
| 974 | - | log.Warn("Failed to get container info", "error", err) | 901 | + | logger.Warn("Failed to get container info", "error", err) |
| 975 | return | 902 | return | ||
| 976 | } | 903 | } | ||
| 977 | 904 | ||||
| 978 | // Check if this is a Gordon-managed container | 905 | // Check if this is a Gordon-managed container | ||
| 979 | if _, exists := containerInfo.Config.Labels["gordon.managed"]; !exists { | 906 | if _, exists := containerInfo.Config.Labels["gordon.managed"]; !exists { | ||
| 980 | - | log.Debug("Ignoring non-Gordon container", "container_name", containerName) | 907 | + | logger.Debug("Ignoring non-Gordon container", "container_name", containerName) |
| 981 | return | 908 | return | ||
| 982 | } | 909 | } | ||
| 983 | 910 | ||||
| 984 | // First approach: check if there are any domains that should point to this container based on labels | 911 | // First approach: check if there are any domains that should point to this container based on labels | ||
| 985 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | 912 | if domainLabel, exists := containerInfo.Config.Labels["gordon.domain"]; exists && domainLabel != "" { | ||
| 913 | + | // Normalize domain (lowercase, no trailing dot) | |||
| 914 | + | domainLabel = strings.TrimSuffix(strings.ToLower(domainLabel), ".") | |||
| 915 | + | ||||
| 986 | // Check if there's already a route for this domain | 916 | // Check if there's already a route for this domain | ||
| 987 | p.mu.RLock() | 917 | p.mu.RLock() | ||
| 988 | route, domainExists := p.routes[domainLabel] | 918 | route, domainExists := p.routes[domainLabel] | ||
| @@ -991,7 +921,7 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 991 | if domainExists { | 921 | if domainExists { | ||
| 992 | // Route exists - check if it's for a different container ID | 922 | // Route exists - check if it's for a different container ID | ||
| 993 | if route.ContainerID != containerID { | 923 | if route.ContainerID != containerID { | ||
| 994 | - | log.Info("Container was recreated with the same domain label", | 924 | + | logger.Info("Container was recreated with the same domain label", |
| 995 | "domain", domainLabel, | 925 | "domain", domainLabel, | ||
| 996 | "old_container_id", route.ContainerID, | 926 | "old_container_id", route.ContainerID, | ||
| 997 | "new_container_id", containerID) | 927 | "new_container_id", containerID) | ||
| @@ -1012,9 +942,9 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1012 | // Update the route with the new container ID and IP | 942 | // Update the route with the new container ID and IP | ||
| 1013 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, route.Path) | 943 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, route.Path) | ||
| 1014 | if err != nil { | 944 | if err != nil { | ||
| 1015 | - | log.Error("Failed to update route for recreated container", "error", err) | 945 | + | logger.Error("Failed to update route for recreated container", "error", err) |
| 1016 | } else { | 946 | } else { | ||
| 1017 | - | log.Info("Updated route for recreated container", | 947 | + | logger.Info("Updated route for recreated container", |
| 1018 | "domain", domainLabel, | 948 | "domain", domainLabel, | ||
| 1019 | "container_id", containerID, | 949 | "container_id", containerID, | ||
| 1020 | "container_ip", containerIP) | 950 | "container_ip", containerIP) | ||
| @@ -1022,7 +952,7 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1022 | } else { | 952 | } else { | ||
| 1023 | // Same container ID but maybe IP changed | 953 | // Same container ID but maybe IP changed | ||
| 1024 | if route.ContainerIP != containerIP { | 954 | if route.ContainerIP != containerIP { | ||
| 1025 | - | log.Info("Container IP changed", | 955 | + | logger.Info("Container IP changed", |
| 1026 | "domain", domainLabel, | 956 | "domain", domainLabel, | ||
| 1027 | "container_id", containerID, | 957 | "container_id", containerID, | ||
| 1028 | "old_ip", route.ContainerIP, | 958 | "old_ip", route.ContainerIP, | ||
| @@ -1031,13 +961,13 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1031 | // Update the IP | 961 | // Update the IP | ||
| 1032 | err = p.ForceUpdateRouteIP(domainLabel, containerIP) | 962 | err = p.ForceUpdateRouteIP(domainLabel, containerIP) | ||
| 1033 | if err != nil { | 963 | if err != nil { | ||
| 1034 | - | log.Error("Failed to update IP for container", "error", err) | 964 | + | logger.Error("Failed to update IP for container", "error", err) |
| 1035 | } | 965 | } | ||
| 1036 | } | 966 | } | ||
| 1037 | } | 967 | } | ||
| 1038 | } else { | 968 | } else { | ||
| 1039 | // No existing route for this domain - might be a new container | 969 | // No existing route for this domain - might be a new container | ||
| 1040 | - | log.Info("New container with domain label", | 970 | + | logger.Info("New container with domain label", |
| 1041 | "domain", domainLabel, | 971 | "domain", domainLabel, | ||
| 1042 | "container_id", containerID) | 972 | "container_id", containerID) | ||
| 1043 | 973 | ||||
| @@ -1054,10 +984,16 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1054 | protocol = "https" | 984 | protocol = "https" | ||
| 1055 | } | 985 | } | ||
| 1056 | 986 | ||||
| 1057 | - | // Add a new route | 987 | + | // Add a new route for this container |
| 988 | + | logger.Info("Adding missing route for Gordon container", | |||
| 989 | + | "domain", domainLabel, | |||
| 990 | + | "container_id", containerID, | |||
| 991 | + | "container_ip", containerIP, | |||
| 992 | + | "container_port", containerPort) | |||
| 993 | + | ||||
| 1058 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | 994 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | ||
| 1059 | if err != nil { | 995 | if err != nil { | ||
| 1060 | - | log.Error("Failed to add route for new container", "error", err) | 996 | + | logger.Error("Failed to add missing route", "error", err) |
| 1061 | } | 997 | } | ||
| 1062 | } | 998 | } | ||
| 1063 | } | 999 | } | ||
| @@ -1065,12 +1001,12 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1065 | // Second approach: check for routes with name-based matching that might need updating | 1001 | // Second approach: check for routes with name-based matching that might need updating | ||
| 1066 | // This handles the case where the container name is used instead of explicit domain labels | 1002 | // This handles the case where the container name is used instead of explicit domain labels | ||
| 1067 | if domainCheck := p.FindRoutesByOldName(containerName); len(domainCheck) > 0 { | 1003 | if domainCheck := p.FindRoutesByOldName(containerName); len(domainCheck) > 0 { | ||
| 1068 | - | log.Info("Found potential routes for recreated container by name matching", | 1004 | + | logger.Info("Found potential routes for recreated container by name matching", |
| 1069 | "container_name", containerName, | 1005 | "container_name", containerName, | ||
| 1070 | "domains_count", len(domainCheck)) | 1006 | "domains_count", len(domainCheck)) | ||
| 1071 | 1007 | ||||
| 1072 | for domain, oldRoute := range domainCheck { | 1008 | for domain, oldRoute := range domainCheck { | ||
| 1073 | - | log.Info("Updating route for recreated container by name matching", | 1009 | + | logger.Info("Updating route for recreated container by name matching", |
| 1074 | "domain", domain, | 1010 | "domain", domain, | ||
| 1075 | "old_container_id", oldRoute.ContainerID, | 1011 | "old_container_id", oldRoute.ContainerID, | ||
| 1076 | "new_container_id", containerID) | 1012 | "new_container_id", containerID) | ||
| @@ -1096,9 +1032,9 @@ func (p *Proxy) HandleContainerRecreation(containerID, containerName, containerI | |||||
| 1096 | // Update the route | 1032 | // Update the route | ||
| 1097 | err = p.AddRoute(domain, containerID, containerIP, containerPort, protocol, oldRoute.Path) | 1033 | err = p.AddRoute(domain, containerID, containerIP, containerPort, protocol, oldRoute.Path) | ||
| 1098 | if err != nil { | 1034 | if err != nil { | ||
| 1099 | - | log.Error("Failed to update route for recreated container", "error", err) | 1035 | + | logger.Error("Failed to update route for recreated container", "error", err) |
| 1100 | } else { | 1036 | } else { | ||
| 1101 | - | log.Info("Updated route for recreated container", | 1037 | + | logger.Info("Updated route for recreated container", |
| 1102 | "domain", domain, | 1038 | "domain", domain, | ||
| 1103 | "container_id", containerID, | 1039 | "container_id", containerID, | ||
| 1104 | "container_ip", containerIP) | 1040 | "container_ip", containerIP) | ||
| @@ -1116,243 +1052,39 @@ func (p *Proxy) StartContainerEventListener() error { | |||||
| 1116 | return fmt.Errorf("container network name is not configured") | 1052 | return fmt.Errorf("container network name is not configured") | ||
| 1117 | } | 1053 | } | ||
| 1118 | 1054 | ||||
| 1119 | - | log.Info("Starting container event listener", "network", networkName) | 1055 | + | logger.Info("Starting container event listener", "network", networkName) |
| 1120 | 1056 | ||||
| 1121 | // Create a callback function to handle container events | 1057 | // Create a callback function to handle container events | ||
| 1122 | containerEventCallback := func(containerID, containerName, containerIP string) { | 1058 | containerEventCallback := func(containerID, containerName, containerIP string) { | ||
| 1123 | p.HandleContainerRecreation(containerID, containerName, containerIP) | 1059 | p.HandleContainerRecreation(containerID, containerName, containerIP) | ||
| 1124 | } | 1060 | } | ||
| 1125 | 1061 | ||||
| 1126 | - | // Start the container event listener | 1062 | + | // Create a callback function to handle container stop events |
| 1127 | - | return docker.ListenForContainerEvents(networkName, containerEventCallback) | 1063 | + | containerStopCallback := func(containerID, containerName string) { |
| 1128 | - | } | 1064 | + | logger.Info("Container stopped or removed, marking associated routes as inactive", |
| 1129 | - | 1065 | + | "container_id", containerID, | |
| 1130 | - | // StartPeriodicIPVerification starts a goroutine that periodically checks container IPs | 1066 | + | "container_name", containerName) |
| 1131 | - | // and updates the routes if they have changed. This helps ensure routes always have | |||
| 1132 | - | // the correct IP address, even if the event listener missed something. | |||
| 1133 | - | func (p *Proxy) StartPeriodicIPVerification(interval time.Duration) { | |||
| 1134 | - | log.Info("Starting periodic IP verification", "interval", interval) | |||
| 1135 | - | ||||
| 1136 | - | // Run in a background goroutine | |||
| 1137 | - | go func() { | |||
| 1138 | - | // Use a ticker for periodic execution | |||
| 1139 | - | ticker := time.NewTicker(interval) | |||
| 1140 | - | defer ticker.Stop() | |||
| 1141 | - | ||||
| 1142 | - | for { | |||
| 1143 | - | select { | |||
| 1144 | - | case <-ticker.C: | |||
| 1145 | - | log.Debug("Running periodic IP verification") | |||
| 1146 | - | p.verifyAllContainerIPs() | |||
| 1147 | - | ||||
| 1148 | - | // Also check for any containers that should have routes but don't | |||
| 1149 | - | p.DiscoverMissingRoutes() | |||
| 1150 | - | ||||
| 1151 | - | // Log current active containers and routes for monitoring | |||
| 1152 | - | if log.GetLevel() <= log.DebugLevel { | |||
| 1153 | - | // Only log the container list in debug mode during periodic checks | |||
| 1154 | - | // to avoid overwhelming logs in production | |||
| 1155 | - | p.LogActiveContainersAndRoutes() | |||
| 1156 | - | } | |||
| 1157 | - | } | |||
| 1158 | - | } | |||
| 1159 | - | }() | |||
| 1160 | - | } | |||
| 1161 | - | ||||
| 1162 | - | // verifyAllContainerIPs checks all routes and verifies their container IPs | |||
| 1163 | - | func (p *Proxy) verifyAllContainerIPs() { | |||
| 1164 | - | p.mu.RLock() | |||
| 1165 | - | routes := make(map[string]*ProxyRouteInfo, len(p.routes)) | |||
| 1166 | - | for domain, route := range p.routes { | |||
| 1167 | - | routes[domain] = &ProxyRouteInfo{ | |||
| 1168 | - | Domain: route.Domain, | |||
| 1169 | - | ContainerID: route.ContainerID, | |||
| 1170 | - | ContainerIP: route.ContainerIP, | |||
| 1171 | - | ContainerPort: route.ContainerPort, | |||
| 1172 | - | Protocol: route.Protocol, | |||
| 1173 | - | Path: route.Path, | |||
| 1174 | - | Active: route.Active, | |||
| 1175 | - | } | |||
| 1176 | - | } | |||
| 1177 | - | p.mu.RUnlock() | |||
| 1178 | - | ||||
| 1179 | - | // Get network name from config | |||
| 1180 | - | networkName := p.app.GetConfig().ContainerEngine.Network | |||
| 1181 | - | if networkName == "" { | |||
| 1182 | - | log.Error("Container network name is not configured") | |||
| 1183 | - | return | |||
| 1184 | - | } | |||
| 1185 | - | ||||
| 1186 | - | // Check each route and verify the container IP | |||
| 1187 | - | routesUpdated := false | |||
| 1188 | - | updatedIPs := make(map[string]string) // cache to avoid redundant lookups | |||
| 1189 | - | ||||
| 1190 | - | // Track orphaned routes to mark as inactive | |||
| 1191 | - | orphanedRoutes := []string{} | |||
| 1192 | - | ||||
| 1193 | - | // Special handling for Gordon container - ensure its IP is correct first | |||
| 1194 | - | gordonContainerID := p.loadGordonContainerID() | |||
| 1195 | - | if gordonContainerID != "" { | |||
| 1196 | - | log.Debug("Verifying Gordon container IP first", "container_id", gordonContainerID) | |||
| 1197 | - | ||||
| 1198 | - | gordonIP, err := docker.GetContainerIPFromNetwork(gordonContainerID, networkName) | |||
| 1199 | - | if err == nil && gordonIP != "" { | |||
| 1200 | - | log.Debug("Found Gordon container IP", "ip", gordonIP) | |||
| 1201 | - | updatedIPs[gordonContainerID] = gordonIP | |||
| 1202 | - | ||||
| 1203 | - | // Double-check by direct container inspection to ensure maximum reliability | |||
| 1204 | - | containerInfo, inspectErr := docker.GetContainerInfo(gordonContainerID) | |||
| 1205 | - | if inspectErr == nil && containerInfo.NetworkSettings != nil { | |||
| 1206 | - | if network, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && | |||
| 1207 | - | network.IPAddress != "" && network.IPAddress != gordonIP { | |||
| 1208 | - | log.Info("Different IP found for Gordon container through inspection", | |||
| 1209 | - | "network_ip", gordonIP, | |||
| 1210 | - | "inspect_ip", network.IPAddress) | |||
| 1211 | - | updatedIPs[gordonContainerID] = network.IPAddress | |||
| 1212 | - | } | |||
| 1213 | - | } | |||
| 1214 | - | } else { | |||
| 1215 | - | log.Warn("Failed to get Gordon container IP from network", | |||
| 1216 | - | "container_id", gordonContainerID, | |||
| 1217 | - | "error", err) | |||
| 1218 | - | } | |||
| 1219 | - | } | |||
| 1220 | - | ||||
| 1221 | - | for domain, route := range routes { | |||
| 1222 | - | if !route.Active { | |||
| 1223 | - | continue | |||
| 1224 | - | } | |||
| 1225 | - | ||||
| 1226 | - | containerID := route.ContainerID | |||
| 1227 | - | ||||
| 1228 | - | // Skip verification if container doesn't exist anymore | |||
| 1229 | - | if !docker.ContainerExists(containerID) { | |||
| 1230 | - | log.Warn("Container no longer exists but route is active", | |||
| 1231 | - | "domain", domain, | |||
| 1232 | - | "container_id", containerID) | |||
| 1233 | - | orphanedRoutes = append(orphanedRoutes, domain) | |||
| 1234 | - | continue | |||
| 1235 | - | } | |||
| 1236 | - | ||||
| 1237 | - | // Check if this is for the Gordon container | |||
| 1238 | - | isGordonContainer := (containerID == gordonContainerID) | |||
| 1239 | - | if isGordonContainer { | |||
| 1240 | - | log.Debug("Verifying Gordon container route", | |||
| 1241 | - | "domain", domain, | |||
| 1242 | - | "container_id", containerID) | |||
| 1243 | - | } | |||
| 1244 | - | ||||
| 1245 | - | // Get the current container IP | |||
| 1246 | - | var currentIP string | |||
| 1247 | - | var err error | |||
| 1248 | - | ||||
| 1249 | - | // Check if we already looked up this container's IP | |||
| 1250 | - | if cachedIP, exists := updatedIPs[containerID]; exists { | |||
| 1251 | - | currentIP = cachedIP | |||
| 1252 | - | log.Debug("Using cached container IP", | |||
| 1253 | - | "container_id", containerID, | |||
| 1254 | - | "ip", currentIP) | |||
| 1255 | - | } else { | |||
| 1256 | - | // Otherwise, look it up | |||
| 1257 | - | currentIP, err = docker.GetContainerIPFromNetwork(containerID, networkName) | |||
| 1258 | - | if err != nil { | |||
| 1259 | - | log.Error("Failed to get container IP for verification", | |||
| 1260 | - | "domain", domain, | |||
| 1261 | - | "container_id", containerID, | |||
| 1262 | - | "error", err) | |||
| 1263 | - | ||||
| 1264 | - | // For Gordon container, try harder to get the IP | |||
| 1265 | - | if isGordonContainer { | |||
| 1266 | - | log.Info("Attempting alternative methods to get Gordon container IP") | |||
| 1267 | - | ||||
| 1268 | - | // Try direct container inspection as a fallback | |||
| 1269 | - | containerInfo, inspectErr := docker.GetContainerInfo(containerID) | |||
| 1270 | - | if inspectErr == nil && containerInfo.NetworkSettings != nil { | |||
| 1271 | - | // Check for our network specifically | |||
| 1272 | - | if network, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && network.IPAddress != "" { | |||
| 1273 | - | currentIP = network.IPAddress | |||
| 1274 | - | log.Info("Successfully got Gordon container IP through inspection", | |||
| 1275 | - | "ip", currentIP) | |||
| 1276 | - | } else if containerInfo.NetworkSettings.IPAddress != "" { | |||
| 1277 | - | // Fallback to default network IP | |||
| 1278 | - | currentIP = containerInfo.NetworkSettings.IPAddress | |||
| 1279 | - | log.Info("Using default network IP for Gordon container", | |||
| 1280 | - | "ip", currentIP) | |||
| 1281 | - | } | |||
| 1282 | - | } | |||
| 1283 | - | } | |||
| 1284 | 1067 | ||||
| 1285 | - | // If we still couldn't get the IP, skip this route | 1068 | + | // Find all domains associated with this container ID |
| 1286 | - | if currentIP == "" { | 1069 | + | var domains []string |
| 1287 | - | continue | 1070 | + | p.mu.RLock() |
| 1288 | - | } | 1071 | + | for domain, route := range p.routes { |
| 1072 | + | if route.ContainerID == containerID { | |||
| 1073 | + | domains = append(domains, domain) | |||
| 1289 | } | 1074 | } | ||
| 1290 | - | ||||
| 1291 | - | // Cache the IP for future route checks | |||
| 1292 | - | updatedIPs[containerID] = currentIP | |||
| 1293 | } | 1075 | } | ||
| 1076 | + | p.mu.RUnlock() | |||
| 1294 | 1077 | ||||
| 1295 | - | // Check if the IP has changed | 1078 | + | if len(domains) > 0 { |
| 1296 | - | if route.ContainerIP != currentIP { | 1079 | + | logger.Info("Found routes to mark as inactive", "container_id", containerID, "domains", domains) |
| 1297 | - | // Throttle updates for non-Gordon containers to prevent excessive updates | 1080 | + | p.markRoutesInactive(domains) |
| 1298 | - | if !isGordonContainer { | |||
| 1299 | - | containerName, _ := docker.GetContainerName(containerID) | |||
| 1300 | - | if containerName != "" { | |||
| 1301 | - | containerName = strings.TrimPrefix(containerName, "/") | |||
| 1302 | - | } | |||
| 1303 | - | ||||
| 1304 | - | log.Info("Container IP has changed - updating route", | |||
| 1305 | - | "domain", domain, | |||
| 1306 | - | "container_id", containerID, | |||
| 1307 | - | "container_name", containerName, | |||
| 1308 | - | "old_ip", route.ContainerIP, | |||
| 1309 | - | "new_ip", currentIP) | |||
| 1310 | - | } else { | |||
| 1311 | - | log.Info("Gordon container IP has changed - updating route", | |||
| 1312 | - | "admin_domain", domain, | |||
| 1313 | - | "gordon_container", containerID, | |||
| 1314 | - | "old_ip", route.ContainerIP, | |||
| 1315 | - | "new_ip", currentIP) | |||
| 1316 | - | } | |||
| 1317 | - | ||||
| 1318 | - | // Update the route in the database | |||
| 1319 | - | err = p.ForceUpdateRouteIP(domain, currentIP) | |||
| 1320 | - | if err != nil { | |||
| 1321 | - | log.Error("Failed to update route IP", | |||
| 1322 | - | "domain", domain, | |||
| 1323 | - | "error", err) | |||
| 1324 | - | } else { | |||
| 1325 | - | routesUpdated = true | |||
| 1326 | - | ||||
| 1327 | - | if isGordonContainer { | |||
| 1328 | - | log.Info("Successfully fixed admin domain route to point to Gordon container", | |||
| 1329 | - | "admin_domain", domain, | |||
| 1330 | - | "gordon_container", containerID) | |||
| 1331 | - | } | |||
| 1332 | - | } | |||
| 1333 | } else { | 1081 | } else { | ||
| 1334 | - | log.Debug("Container IP verified and unchanged", | 1082 | + | logger.Debug("No routes found for stopped container", "container_id", containerID) |
| 1335 | - | "domain", domain, | |||
| 1336 | - | "container_id", containerID, | |||
| 1337 | - | "ip", currentIP) | |||
| 1338 | } | 1083 | } | ||
| 1339 | } | 1084 | } | ||
| 1340 | 1085 | ||||
| 1341 | - | // Mark orphaned routes as inactive | 1086 | + | // Start the container event listener |
| 1342 | - | if len(orphanedRoutes) > 0 { | 1087 | + | return docker.ListenForContainerEvents(networkName, containerEventCallback, containerStopCallback) |
| 1343 | - | log.Info("Marking orphaned routes as inactive", "count", len(orphanedRoutes)) | |||
| 1344 | - | p.markRoutesInactive(orphanedRoutes) | |||
| 1345 | - | routesUpdated = true | |||
| 1346 | - | } | |||
| 1347 | - | ||||
| 1348 | - | // Reload routes if any were updated | |||
| 1349 | - | if routesUpdated { | |||
| 1350 | - | log.Debug("Routes were updated - reloading proxy configuration") | |||
| 1351 | - | err := p.Reload() | |||
| 1352 | - | if err != nil { | |||
| 1353 | - | log.Error("Failed to reload routes after IP verification", "error", err) | |||
| 1354 | - | } | |||
| 1355 | - | } | |||
| 1356 | } | 1088 | } | ||
| 1357 | 1089 | ||||
| 1358 | // markRoutesInactive marks the specified routes as inactive in the database and memory | 1090 | // markRoutesInactive marks the specified routes as inactive in the database and memory | ||
| @@ -1370,7 +1102,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||||
| 1370 | if domain != adminDomain { | 1102 | if domain != adminDomain { | ||
| 1371 | filteredDomains = append(filteredDomains, domain) | 1103 | filteredDomains = append(filteredDomains, domain) | ||
| 1372 | } else { | 1104 | } else { | ||
| 1373 | - | log.Warn("Prevented admin domain from being marked inactive", | 1105 | + | logger.Warn("Prevented admin domain from being marked inactive", |
| 1374 | "domain", adminDomain) | 1106 | "domain", adminDomain) | ||
| 1375 | } | 1107 | } | ||
| 1376 | } | 1108 | } | ||
| @@ -1386,7 +1118,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||||
| 1386 | // Begin a transaction | 1118 | // Begin a transaction | ||
| 1387 | tx, err := p.app.GetDB().Begin() | 1119 | tx, err := p.app.GetDB().Begin() | ||
| 1388 | if err != nil { | 1120 | if err != nil { | ||
| 1389 | - | log.Error("Failed to begin transaction for marking routes inactive", "error", err) | 1121 | + | logger.Error("Failed to begin transaction for marking routes inactive", "error", err) |
| 1390 | return | 1122 | return | ||
| 1391 | } | 1123 | } | ||
| 1392 | defer tx.Rollback() | 1124 | defer tx.Rollback() | ||
| @@ -1397,7 +1129,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||||
| 1397 | var domainID string | 1129 | var domainID string | ||
| 1398 | err := tx.QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | 1130 | err := tx.QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | ||
| 1399 | if err != nil { | 1131 | if err != nil { | ||
| 1400 | - | log.Error("Failed to get domain ID for inactive route", "domain", domain, "error", err) | 1132 | + | logger.Error("Failed to get domain ID for inactive route", "domain", domain, "error", err) |
| 1401 | continue | 1133 | continue | ||
| 1402 | } | 1134 | } | ||
| 1403 | 1135 | ||||
| @@ -1405,7 +1137,7 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||||
| 1405 | now := time.Now().Format(time.RFC3339) | 1137 | now := time.Now().Format(time.RFC3339) | ||
| 1406 | _, err = tx.Exec("UPDATE proxy_route SET active = 0, updated_at = ? WHERE domain_id = ?", now, domainID) | 1138 | _, err = tx.Exec("UPDATE proxy_route SET active = 0, updated_at = ? WHERE domain_id = ?", now, domainID) | ||
| 1407 | if err != nil { | 1139 | if err != nil { | ||
| 1408 | - | log.Error("Failed to mark route as inactive in database", "domain", domain, "error", err) | 1140 | + | logger.Error("Failed to mark route as inactive in database", "domain", domain, "error", err) |
| 1409 | continue | 1141 | continue | ||
| 1410 | } | 1142 | } | ||
| 1411 | 1143 | ||||
| @@ -1413,29 +1145,29 @@ func (p *Proxy) markRoutesInactive(domains []string) { | |||||
| 1413 | p.mu.Lock() | 1145 | p.mu.Lock() | ||
| 1414 | if route, exists := p.routes[domain]; exists { | 1146 | if route, exists := p.routes[domain]; exists { | ||
| 1415 | route.Active = false | 1147 | route.Active = false | ||
| 1416 | - | log.Info("Marked route as inactive", "domain", domain) | 1148 | + | logger.Info("Marked route as inactive", "domain", domain) |
| 1417 | } | 1149 | } | ||
| 1418 | p.mu.Unlock() | 1150 | p.mu.Unlock() | ||
| 1419 | } | 1151 | } | ||
| 1420 | 1152 | ||||
| 1421 | // Commit the transaction | 1153 | // Commit the transaction | ||
| 1422 | if err := tx.Commit(); err != nil { | 1154 | if err := tx.Commit(); err != nil { | ||
| 1423 | - | log.Error("Failed to commit transaction for marking routes inactive", "error", err) | 1155 | + | logger.Error("Failed to commit transaction for marking routes inactive", "error", err) |
| 1424 | return | 1156 | return | ||
| 1425 | } | 1157 | } | ||
| 1426 | 1158 | ||||
| 1427 | - | log.Info("Successfully marked orphaned routes as inactive", "count", len(domains)) | 1159 | + | logger.Info("Successfully marked orphaned routes as inactive", "count", len(domains)) |
| 1428 | } | 1160 | } | ||
| 1429 | 1161 | ||||
| 1430 | // DiscoverMissingRoutes finds Gordon-managed containers that don't have routes yet | 1162 | // DiscoverMissingRoutes finds Gordon-managed containers that don't have routes yet | ||
| 1431 | // and adds them to the routing table | 1163 | // and adds them to the routing table | ||
| 1432 | func (p *Proxy) DiscoverMissingRoutes() { | 1164 | func (p *Proxy) DiscoverMissingRoutes() { | ||
| 1433 | - | log.Info("Discovering containers that might need routes") | 1165 | + | logger.Info("Discovering containers that might need routes") |
| 1434 | 1166 | ||||
| 1435 | // Get the network name from app config | 1167 | // Get the network name from app config | ||
| 1436 | networkName := p.app.GetConfig().ContainerEngine.Network | 1168 | networkName := p.app.GetConfig().ContainerEngine.Network | ||
| 1437 | if networkName == "" { | 1169 | if networkName == "" { | ||
| 1438 | - | log.Error("Cannot discover missing routes, network name is not configured") | 1170 | + | logger.Error("Cannot discover missing routes, network name is not configured") |
| 1439 | return | 1171 | return | ||
| 1440 | } | 1172 | } | ||
| 1441 | 1173 | ||||
| @@ -1445,7 +1177,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1445 | // Get all containers in the network | 1177 | // Get all containers in the network | ||
| 1446 | networkInfo, err := docker.GetNetworkInfo(networkName) | 1178 | networkInfo, err := docker.GetNetworkInfo(networkName) | ||
| 1447 | if err != nil { | 1179 | if err != nil { | ||
| 1448 | - | log.Error("Failed to get network info", "error", err) | 1180 | + | logger.Error("Failed to get network info", "error", err) |
| 1449 | return | 1181 | return | ||
| 1450 | } | 1182 | } | ||
| 1451 | 1183 | ||||
| @@ -1473,7 +1205,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1473 | // Get container info to check for Gordon labels | 1205 | // Get container info to check for Gordon labels | ||
| 1474 | containerInfo, err := docker.GetContainerInfo(containerID) | 1206 | containerInfo, err := docker.GetContainerInfo(containerID) | ||
| 1475 | if err != nil { | 1207 | if err != nil { | ||
| 1476 | - | log.Debug("Failed to get container info", | 1208 | + | logger.Debug("Failed to get container info", |
| 1477 | "container_id", containerID, | 1209 | "container_id", containerID, | ||
| 1478 | "error", err) | 1210 | "error", err) | ||
| 1479 | continue | 1211 | continue | ||
| @@ -1487,39 +1219,39 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1487 | // Check if it has a domain label | 1219 | // Check if it has a domain label | ||
| 1488 | domainLabel, hasDomain := containerInfo.Config.Labels["gordon.domain"] | 1220 | domainLabel, hasDomain := containerInfo.Config.Labels["gordon.domain"] | ||
| 1489 | if !hasDomain || domainLabel == "" { | 1221 | if !hasDomain || domainLabel == "" { | ||
| 1490 | - | log.Debug("Gordon container without domain label", | 1222 | + | logger.Debug("Gordon container without domain label", |
| 1491 | "container_id", containerID, | 1223 | "container_id", containerID, | ||
| 1492 | "container_name", strings.TrimPrefix(containerInfo.Name, "/")) | 1224 | "container_name", strings.TrimPrefix(containerInfo.Name, "/")) | ||
| 1493 | continue | 1225 | continue | ||
| 1494 | } | 1226 | } | ||
| 1495 | 1227 | ||||
| 1228 | + | // Normalize domain (lowercase, no trailing dot) | |||
| 1229 | + | domainLabel = strings.TrimSuffix(strings.ToLower(domainLabel), ".") | |||
| 1230 | + | ||||
| 1496 | // Skip if this is the admin domain | 1231 | // Skip if this is the admin domain | ||
| 1497 | if domainLabel == adminDomain { | 1232 | if domainLabel == adminDomain { | ||
| 1498 | - | log.Debug("Skipping auto-discovery for admin domain", | 1233 | + | logger.Debug("Skipping auto-discovery for admin domain", |
| 1499 | - | "domain", domainLabel, | 1234 | + | "domain", domainLabel) |
| 1500 | - | "container_id", containerID) | |||
| 1501 | continue | 1235 | continue | ||
| 1502 | } | 1236 | } | ||
| 1503 | 1237 | ||||
| 1504 | - | // Check if this domain already has a route (different container) | 1238 | + | // Check if there's already a route for this domain |
| 1505 | if _, exists := routes[domainLabel]; exists { | 1239 | if _, exists := routes[domainLabel]; exists { | ||
| 1506 | - | log.Warn("Domain already has a route with a different container", | 1240 | + | logger.Debug("Route already exists for domain", |
| 1507 | - | "domain", domainLabel, | 1241 | + | "domain", domainLabel) |
| 1508 | - | "container_id", containerID, | |||
| 1509 | - | "existing_container_id", routes[domainLabel].ContainerID) | |||
| 1510 | continue | 1242 | continue | ||
| 1511 | } | 1243 | } | ||
| 1512 | 1244 | ||||
| 1513 | - | // Extract container IP and port for the new route | 1245 | + | // Get the container IP from the network info |
| 1514 | containerIP := containerEndpoint.IPv4Address | 1246 | containerIP := containerEndpoint.IPv4Address | ||
| 1515 | if containerIP == "" { | 1247 | if containerIP == "" { | ||
| 1516 | - | containerIP = containerEndpoint.IPv6Address | 1248 | + | logger.Warn("Container has no IP address", |
| 1249 | + | "container_id", containerID) | |||
| 1250 | + | continue | |||
| 1517 | } | 1251 | } | ||
| 1518 | 1252 | ||||
| 1519 | - | // Extract just the IP part without subnet | 1253 | + | // Extract just the IP part from the CIDR notation (e.g., "192.168.1.2/24" → "192.168.1.2") |
| 1520 | - | if idx := strings.Index(containerIP, "/"); idx > 0 { | 1254 | + | containerIP = strings.Split(containerIP, "/")[0] |
| 1521 | - | containerIP = containerIP[:idx] | |||
| 1522 | - | } | |||
| 1523 | 1255 | ||||
| 1524 | // Get the port to use for the proxy from the container labels | 1256 | // Get the port to use for the proxy from the container labels | ||
| 1525 | containerPort := "80" // Default | 1257 | containerPort := "80" // Default | ||
| @@ -1535,7 +1267,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1535 | } | 1267 | } | ||
| 1536 | 1268 | ||||
| 1537 | // Add a new route for this container | 1269 | // Add a new route for this container | ||
| 1538 | - | log.Info("Adding missing route for Gordon container", | 1270 | + | logger.Info("Adding missing route for Gordon container", |
| 1539 | "domain", domainLabel, | 1271 | "domain", domainLabel, | ||
| 1540 | "container_id", containerID, | 1272 | "container_id", containerID, | ||
| 1541 | "container_ip", containerIP, | 1273 | "container_ip", containerIP, | ||
| @@ -1543,7 +1275,7 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1543 | 1275 | ||||
| 1544 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | 1276 | err = p.AddRoute(domainLabel, containerID, containerIP, containerPort, protocol, "/") | ||
| 1545 | if err != nil { | 1277 | if err != nil { | ||
| 1546 | - | log.Error("Failed to add missing route", "error", err) | 1278 | + | logger.Error("Failed to add missing route", "error", err) |
| 1547 | } | 1279 | } | ||
| 1548 | } | 1280 | } | ||
| 1549 | 1281 | ||||
| @@ -1554,15 +1286,12 @@ func (p *Proxy) DiscoverMissingRoutes() { | |||||
| 1554 | count++ | 1286 | count++ | ||
| 1555 | } | 1287 | } | ||
| 1556 | p.mu.RUnlock() | 1288 | p.mu.RUnlock() | ||
| 1557 | - | log.Debug("Routes after discovery", "count", count) | 1289 | + | logger.Debug("Routes after discovery", "count", count) |
| 1558 | - | ||||
| 1559 | - | // Clean up any root domain conflict | |||
| 1560 | - | p.CleanupRootDomainForAdmin() | |||
| 1561 | } | 1290 | } | ||
| 1562 | 1291 | ||||
| 1563 | // LogActiveContainersAndRoutes logs a list of active containers and their routes | 1292 | // LogActiveContainersAndRoutes logs a list of active containers and their routes | ||
| 1564 | func (p *Proxy) LogActiveContainersAndRoutes() { | 1293 | func (p *Proxy) LogActiveContainersAndRoutes() { | ||
| 1565 | - | log.Info("---- Listing active containers and routes ----") | 1294 | + | logger.Info("---- Listing active containers and routes ----") |
| 1566 | 1295 | ||||
| 1567 | // Get the admin domain | 1296 | // Get the admin domain | ||
| 1568 | adminDomain := p.app.GetConfig().Http.FullDomain() | 1297 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| @@ -1574,10 +1303,10 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||||
| 1574 | p.mu.RUnlock() | 1303 | p.mu.RUnlock() | ||
| 1575 | 1304 | ||||
| 1576 | if !adminExists { | 1305 | if !adminExists { | ||
| 1577 | - | log.Warn("Admin domain is missing from routes", "domain", adminDomain) | 1306 | + | logger.Warn("Admin domain is missing from routes", "domain", adminDomain) |
| 1578 | } | 1307 | } | ||
| 1579 | 1308 | ||||
| 1580 | - | log.Info(fmt.Sprintf("Found %d active routes", activeRoutes)) | 1309 | + | logger.Info(fmt.Sprintf("Found %d active routes", activeRoutes)) |
| 1581 | 1310 | ||||
| 1582 | // Create a map of container IDs to their information | 1311 | // Create a map of container IDs to their information | ||
| 1583 | containerMap := make(map[string]struct { | 1312 | containerMap := make(map[string]struct { | ||
| @@ -1630,7 +1359,7 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||||
| 1630 | if err == nil { | 1359 | if err == nil { | ||
| 1631 | name, err := docker.GetContainerName(containerID) | 1360 | name, err := docker.GetContainerName(containerID) | ||
| 1632 | if err == nil { | 1361 | if err == nil { | ||
| 1633 | - | containerData.Name = strings.TrimPrefix(name, "/") | 1362 | + | containerData.Name = strings.TrimLeft(name, "/") |
| 1634 | containerData.Image = containerInfo.Config.Image | 1363 | containerData.Image = containerInfo.Config.Image | ||
| 1635 | containerMap[containerID] = containerData | 1364 | containerMap[containerID] = containerData | ||
| 1636 | } | 1365 | } | ||
| @@ -1645,87 +1374,52 @@ func (p *Proxy) LogActiveContainersAndRoutes() { | |||||
| 1645 | if len(containerID) >= 12 { | 1374 | if len(containerID) >= 12 { | ||
| 1646 | containerIDShort = containerID[:12] | 1375 | containerIDShort = containerID[:12] | ||
| 1647 | } | 1376 | } | ||
| 1648 | - | log.Info(fmt.Sprintf("Container: %s (%s)", container.Name, containerIDShort), | 1377 | + | logger.Info(fmt.Sprintf("Container: %s (%s)", container.Name, containerIDShort), |
| 1649 | "ip", container.IP, | 1378 | "ip", container.IP, | ||
| 1650 | "image", container.Image) | 1379 | "image", container.Image) | ||
| 1651 | 1380 | ||||
| 1652 | // Log each domain for this container | 1381 | // Log each domain for this container | ||
| 1653 | for i, domain := range container.Domains { | 1382 | for i, domain := range container.Domains { | ||
| 1654 | - | log.Info(fmt.Sprintf(" ├─ Domain: %s", domain), | 1383 | + | logger.Info(fmt.Sprintf(" ├─ Domain: %s", domain), |
| 1655 | "protocol", container.Protocols[i], | 1384 | "protocol", container.Protocols[i], | ||
| 1656 | "port", container.Ports[i]) | 1385 | "port", container.Ports[i]) | ||
| 1657 | } | 1386 | } | ||
| 1658 | } | 1387 | } | ||
| 1659 | 1388 | ||||
| 1660 | - | log.Info("---- End of active containers and routes list ----") | 1389 | + | logger.Info("---- End of active containers and routes list ----") |
| 1661 | } | 1390 | } | ||
| 1662 | 1391 | ||||
| 1663 | - | // saveGordonContainerID saves the Gordon container ID to the config file | 1392 | + | // LogGordonIdentity prints information about the Gordon container for debugging |
| 1664 | - | func (p *Proxy) saveGordonContainerID(containerID string) { | 1393 | + | func (p *Proxy) LogGordonIdentity() { |
| 1665 | - | log.Debug("Saving Gordon container ID to config file", "container_id", containerID) | 1394 | + | logger.Debug("Logging Gordon identity information") |
| 1666 | - | ||||
| 1667 | - | // Update the config | |||
| 1668 | - | config := p.app.GetConfig() | |||
| 1669 | - | config.General.GordonContainerID = containerID | |||
| 1670 | - | ||||
| 1671 | - | // Save the config file | |||
| 1672 | - | err := config.SaveConfig() | |||
| 1673 | - | if err != nil { | |||
| 1674 | - | log.Error("Failed to save Gordon container ID to config file", "error", err) | |||
| 1675 | - | return | |||
| 1676 | - | } | |||
| 1677 | - | ||||
| 1678 | - | // Update the gordonContainerID field | |||
| 1679 | - | p.gordonContainerID = containerID | |||
| 1680 | - | ||||
| 1681 | - | log.Info("Successfully saved Gordon container ID to config file", "container_id", containerID) | |||
| 1682 | - | } | |||
| 1683 | - | ||||
| 1684 | - | // loadGordonContainerID loads the Gordon container ID from the config file | |||
| 1685 | - | func (p *Proxy) loadGordonContainerID() string { | |||
| 1686 | - | log.Debug("Loading Gordon container ID from config file") | |||
| 1687 | - | ||||
| 1688 | - | // Get the container ID from the config | |||
| 1689 | - | containerID := p.app.GetConfig().General.GordonContainerID | |||
| 1690 | - | ||||
| 1691 | - | if containerID == "" { | |||
| 1692 | - | log.Debug("No Gordon container ID found in config file") | |||
| 1693 | - | return "" | |||
| 1694 | - | } | |||
| 1695 | - | ||||
| 1696 | - | log.Info("Loaded Gordon container ID from config file", "container_id", containerID) | |||
| 1697 | - | return containerID | |||
| 1698 | - | } | |||
| 1699 | - | ||||
| 1700 | - | // detectGordonContainer attempts to find the Gordon container automatically | |||
| 1701 | - | func (p *Proxy) detectGordonContainer() string { | |||
| 1702 | - | log.Debug("Starting Gordon container detection") | |||
| 1703 | 1395 | ||||
| 1704 | - | // First try to load the container ID from the config file | 1396 | + | // Print our container ID if available |
| 1705 | - | dbContainerID := p.loadGordonContainerID() | 1397 | + | if p.gordonContainerID != "" { |
| 1706 | - | if dbContainerID != "" { | 1398 | + | logger.Info("Gordon container ID", "container_id", p.gordonContainerID) |
| 1707 | - | p.gordonContainerID = dbContainerID | |||
| 1708 | - | log.Debug("Loaded Gordon container ID from config file", "container_id", p.gordonContainerID) | |||
| 1709 | 1399 | ||||
| 1710 | - | // Verify the container still exists | 1400 | + | // Try to get the container name |
| 1711 | containers, err := docker.ListRunningContainers() | 1401 | containers, err := docker.ListRunningContainers() | ||
| 1712 | if err == nil { | 1402 | if err == nil { | ||
| 1713 | for _, container := range containers { | 1403 | for _, container := range containers { | ||
| 1714 | if container.ID == p.gordonContainerID { | 1404 | if container.ID == p.gordonContainerID { | ||
| 1715 | containerName := strings.TrimLeft(container.Names[0], "/") | 1405 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1716 | - | log.Debug("Identified Gordon container using config file ID", "container_name", containerName) | 1406 | + | logger.Info("Gordon container name", "container_name", containerName) |
| 1717 | - | return containerName | 1407 | + | break |
| 1718 | } | 1408 | } | ||
| 1719 | } | 1409 | } | ||
| 1720 | - | // If the container ID is no longer found, log it but continue with other detection methods | |||
| 1721 | - | log.Warn("Stored Gordon container ID from config file no longer found in running containers", "container_id", p.gordonContainerID) | |||
| 1722 | - | p.gordonContainerID = "" // Reset since it's invalid | |||
| 1723 | } | 1410 | } | ||
| 1411 | + | } else { | |||
| 1412 | + | logger.Warn("Gordon container ID not available - running outside container?") | |||
| 1724 | } | 1413 | } | ||
| 1414 | + | } | |||
| 1415 | + | ||||
| 1416 | + | // detectGordonContainer attempts to find the Gordon container automatically | |||
| 1417 | + | func (p *Proxy) detectGordonContainer() string { | |||
| 1418 | + | logger.Debug("Starting Gordon container detection") | |||
| 1725 | 1419 | ||||
| 1726 | - | // If we have our container ID stored from initialization, use that next | 1420 | + | // If we have our container ID stored from initialization, use that first |
| 1727 | if p.gordonContainerID != "" { | 1421 | if p.gordonContainerID != "" { | ||
| 1728 | - | log.Debug("Using stored Gordon container ID for identification", "container_id", p.gordonContainerID) | 1422 | + | logger.Debug("Using stored Gordon container ID for identification", "container_id", p.gordonContainerID) |
| 1729 | 1423 | ||||
| 1730 | // Get the container name from the ID | 1424 | // Get the container name from the ID | ||
| 1731 | containers, err := docker.ListRunningContainers() | 1425 | containers, err := docker.ListRunningContainers() | ||
| @@ -1733,23 +1427,21 @@ func (p *Proxy) detectGordonContainer() string { | |||||
| 1733 | for _, container := range containers { | 1427 | for _, container := range containers { | ||
| 1734 | if container.ID == p.gordonContainerID { | 1428 | if container.ID == p.gordonContainerID { | ||
| 1735 | containerName := strings.TrimLeft(container.Names[0], "/") | 1429 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1736 | - | log.Info("Gordon identity established via exact ID match", | 1430 | + | logger.Info("Gordon identity established via exact ID match", |
| 1737 | "container_id", container.ID, | 1431 | "container_id", container.ID, | ||
| 1738 | "container_name", containerName) | 1432 | "container_name", containerName) | ||
| 1739 | - | p.gordonContainerID = container.ID | |||
| 1740 | - | // Save to config file for persistence | |||
| 1741 | - | p.saveGordonContainerID(container.ID) | |||
| 1742 | return containerName | 1433 | return containerName | ||
| 1743 | } | 1434 | } | ||
| 1744 | } | 1435 | } | ||
| 1745 | // If the container ID is no longer found, log it but continue with other detection methods | 1436 | // If the container ID is no longer found, log it but continue with other detection methods | ||
| 1746 | - | log.Warn("Stored Gordon container ID no longer found in running containers", "container_id", p.gordonContainerID) | 1437 | + | logger.Warn("Stored Gordon container ID no longer found in running containers", "container_id", p.gordonContainerID) |
| 1438 | + | p.gordonContainerID = "" // Reset since it's invalid | |||
| 1747 | } | 1439 | } | ||
| 1748 | } | 1440 | } | ||
| 1749 | 1441 | ||||
| 1750 | // Check if we're running inside a container | 1442 | // Check if we're running inside a container | ||
| 1751 | if docker.IsRunningInContainer() { | 1443 | if docker.IsRunningInContainer() { | ||
| 1752 | - | log.Debug("Detected we're running in a container") | 1444 | + | logger.Debug("Detected we're running in a container") |
| 1753 | 1445 | ||||
| 1754 | // Get our hostname which should be the container ID in Docker/Podman | 1446 | // Get our hostname which should be the container ID in Docker/Podman | ||
| 1755 | hostname := os.Getenv("HOSTNAME") | 1447 | hostname := os.Getenv("HOSTNAME") | ||
| @@ -1762,12 +1454,10 @@ func (p *Proxy) detectGordonContainer() string { | |||||
| 1762 | for _, container := range containers { | 1454 | for _, container := range containers { | ||
| 1763 | if hostname == container.ID { | 1455 | if hostname == container.ID { | ||
| 1764 | containerName := strings.TrimLeft(container.Names[0], "/") | 1456 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1765 | - | log.Info("Gordon identity established via exact ID match", | 1457 | + | logger.Info("Gordon identity established via exact ID match", |
| 1766 | "container_id", container.ID, | 1458 | "container_id", container.ID, | ||
| 1767 | "container_name", containerName) | 1459 | "container_name", containerName) | ||
| 1768 | p.gordonContainerID = container.ID | 1460 | p.gordonContainerID = container.ID | ||
| 1769 | - | // Save to config file for persistence | |||
| 1770 | - | p.saveGordonContainerID(container.ID) | |||
| 1771 | return containerName | 1461 | return containerName | ||
| 1772 | } | 1462 | } | ||
| 1773 | } | 1463 | } | ||
| @@ -1776,18 +1466,16 @@ func (p *Proxy) detectGordonContainer() string { | |||||
| 1776 | for _, container := range containers { | 1466 | for _, container := range containers { | ||
| 1777 | if strings.HasPrefix(container.ID, hostname) || strings.HasPrefix(hostname, container.ID) { | 1467 | if strings.HasPrefix(container.ID, hostname) || strings.HasPrefix(hostname, container.ID) { | ||
| 1778 | containerName := strings.TrimLeft(container.Names[0], "/") | 1468 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1779 | - | log.Info("Gordon identity established via ID prefix match", | 1469 | + | logger.Info("Gordon identity established via ID prefix match", |
| 1780 | "container_id", container.ID, | 1470 | "container_id", container.ID, | ||
| 1781 | "container_name", containerName, | 1471 | "container_name", containerName, | ||
| 1782 | "hostname", hostname) | 1472 | "hostname", hostname) | ||
| 1783 | p.gordonContainerID = container.ID | 1473 | p.gordonContainerID = container.ID | ||
| 1784 | - | // Save to config file for persistence | |||
| 1785 | - | p.saveGordonContainerID(container.ID) | |||
| 1786 | return containerName | 1474 | return containerName | ||
| 1787 | } | 1475 | } | ||
| 1788 | } | 1476 | } | ||
| 1789 | } else { | 1477 | } else { | ||
| 1790 | - | log.Debug("Could not list containers when checking hostname", "error", err) | 1478 | + | logger.Debug("Could not list containers when checking hostname", "error", err) |
| 1791 | } | 1479 | } | ||
| 1792 | } | 1480 | } | ||
| 1793 | } | 1481 | } | ||
| @@ -1798,10 +1486,8 @@ func (p *Proxy) detectGordonContainer() string { | |||||
| 1798 | for _, container := range containers { | 1486 | for _, container := range containers { | ||
| 1799 | containerName := strings.TrimLeft(container.Names[0], "/") | 1487 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1800 | if containerName == "gordon" { | 1488 | if containerName == "gordon" { | ||
| 1801 | - | log.Info("Found Gordon container with exact name 'gordon'", "container_id", container.ID) | 1489 | + | logger.Info("Found Gordon container with exact name 'gordon'", "container_id", container.ID) |
| 1802 | p.gordonContainerID = container.ID | 1490 | p.gordonContainerID = container.ID | ||
| 1803 | - | // Save to config file for persistence | |||
| 1804 | - | p.saveGordonContainerID(container.ID) | |||
| 1805 | return containerName | 1491 | return containerName | ||
| 1806 | } | 1492 | } | ||
| 1807 | } | 1493 | } | ||
| @@ -1810,173 +1496,157 @@ func (p *Proxy) detectGordonContainer() string { | |||||
| 1810 | for _, container := range containers { | 1496 | for _, container := range containers { | ||
| 1811 | containerName := strings.TrimLeft(container.Names[0], "/") | 1497 | containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 1812 | if strings.Contains(strings.ToLower(containerName), "gordon") { | 1498 | if strings.Contains(strings.ToLower(containerName), "gordon") { | ||
| 1813 | - | log.Info("Found Gordon container with partial name match", | 1499 | + | logger.Info("Found Gordon container with partial name match", |
| 1814 | "container_name", containerName, | 1500 | "container_name", containerName, | ||
| 1815 | "container_id", container.ID) | 1501 | "container_id", container.ID) | ||
| 1816 | p.gordonContainerID = container.ID | 1502 | p.gordonContainerID = container.ID | ||
| 1817 | - | // Save to config file for persistence | |||
| 1818 | - | p.saveGordonContainerID(container.ID) | |||
| 1819 | return containerName | 1503 | return containerName | ||
| 1820 | } | 1504 | } | ||
| 1821 | } | 1505 | } | ||
| 1822 | } | 1506 | } | ||
| 1823 | 1507 | ||||
| 1824 | // Fallback to a default name | 1508 | // Fallback to a default name | ||
| 1825 | - | log.Warn("Could not auto-detect Gordon container, using fallback name", "container_name", "gordon") | 1509 | + | logger.Warn("Could not auto-detect Gordon container, using fallback name", "container_name", "gordon") |
| 1826 | return "gordon" | 1510 | return "gordon" | ||
| 1827 | } | 1511 | } | ||
| 1828 | 1512 | ||||
| 1829 | - | // CleanupRootDomainForAdmin checks if the root domain is pointing to the admin container | 1513 | + | // proxyRequest handles the actual proxying of the request to the target container |
| 1830 | - | // and removes that route to avoid conflicts with other services using the root domain | 1514 | + | func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error { |
| 1831 | - | func (p *Proxy) CleanupRootDomainForAdmin() { | 1515 | + | host := c.Request().Host |
| 1832 | - | // Get admin domain and root domain | 1516 | + | |
| 1833 | - | adminDomain := p.app.GetConfig().Http.FullDomain() | 1517 | + | // For container routes, verify the IP is reachable before proxying |
| 1834 | - | rootDomain := p.app.GetConfig().Http.Domain | 1518 | + | if host != p.app.GetConfig().Http.FullDomain() { |
| 1835 | - | 1519 | + | // Test TCP connection to container before proxying | |
| 1836 | - | // Skip if root domain is the same as admin domain | 1520 | + | dialer := net.Dialer{Timeout: 500 * time.Millisecond} |
| 1837 | - | if rootDomain == adminDomain || rootDomain == "" { | 1521 | + | // Format address properly for both IPv4 and IPv6 |
| 1838 | - | return | 1522 | + | target := route.ContainerIP |
| 1839 | - | } | 1523 | + | if strings.Contains(target, ":") { |
| 1840 | - | 1524 | + | // IPv6 address needs to be wrapped in brackets | |
| 1841 | - | // Check if root domain exists in memory routes | 1525 | + | target = "[" + target + "]" |
| 1842 | - | p.mu.RLock() | |||
| 1843 | - | rootRoute, rootExists := p.routes[rootDomain] | |||
| 1844 | - | adminRoute, adminExists := p.routes[adminDomain] | |||
| 1845 | - | p.mu.RUnlock() | |||
| 1846 | - | ||||
| 1847 | - | // If both root domain and admin domain routes exist, | |||
| 1848 | - | // check if they point to the same container ID (Gordon) | |||
| 1849 | - | if rootExists && adminExists { | |||
| 1850 | - | // Always ensure the admin domain points to the Gordon container | |||
| 1851 | - | if p.gordonContainerID != "" && adminRoute.ContainerID != p.gordonContainerID { | |||
| 1852 | - | log.Warn("Admin domain is pointing to the wrong container, fixing", | |||
| 1853 | - | "admin_domain", adminDomain, | |||
| 1854 | - | "current_container", adminRoute.ContainerID, | |||
| 1855 | - | "gordon_container", p.gordonContainerID) | |||
| 1856 | - | ||||
| 1857 | - | // Update the admin route to point to the Gordon container | |||
| 1858 | - | // Begin a transaction | |||
| 1859 | - | tx, err := p.app.GetDB().Begin() | |||
| 1860 | - | if err != nil { | |||
| 1861 | - | log.Error("Failed to begin transaction for admin domain fix", "error", err) | |||
| 1862 | - | return | |||
| 1863 | - | } | |||
| 1864 | - | defer tx.Rollback() | |||
| 1865 | - | ||||
| 1866 | - | // Get the domain ID for admin domain | |||
| 1867 | - | var domainID string | |||
| 1868 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", adminDomain).Scan(&domainID) | |||
| 1869 | - | if err != nil { | |||
| 1870 | - | if err != sql.ErrNoRows { | |||
| 1871 | - | log.Error("Failed to query domain for admin domain fix", "error", err) | |||
| 1872 | - | } | |||
| 1873 | - | return | |||
| 1874 | - | } | |||
| 1875 | - | ||||
| 1876 | - | // Update the route for admin domain | |||
| 1877 | - | _, err = tx.Exec("UPDATE proxy_route SET container_id = ? WHERE domain_id = ?", | |||
| 1878 | - | p.gordonContainerID, domainID) | |||
| 1879 | - | if err != nil { | |||
| 1880 | - | log.Error("Failed to update admin domain route", "error", err) | |||
| 1881 | - | return | |||
| 1882 | - | } | |||
| 1883 | - | ||||
| 1884 | - | // Commit the transaction | |||
| 1885 | - | if err := tx.Commit(); err != nil { | |||
| 1886 | - | log.Error("Failed to commit transaction for admin domain fix", "error", err) | |||
| 1887 | - | return | |||
| 1888 | - | } | |||
| 1889 | - | ||||
| 1890 | - | // Update in-memory route | |||
| 1891 | - | p.mu.Lock() | |||
| 1892 | - | adminRoute.ContainerID = p.gordonContainerID | |||
| 1893 | - | p.mu.Unlock() | |||
| 1894 | - | ||||
| 1895 | - | log.Info("Successfully fixed admin domain route to point to Gordon container", | |||
| 1896 | - | "admin_domain", adminDomain, | |||
| 1897 | - | "gordon_container", p.gordonContainerID) | |||
| 1898 | } | 1526 | } | ||
| 1899 | - | 1527 | + | conn, err := dialer.Dial("tcp", net.JoinHostPort(target, route.ContainerPort)) | |
| 1900 | - | // Now handle the root domain conflict | 1528 | + | if err != nil { |
| 1901 | - | if rootRoute.ContainerID == adminRoute.ContainerID { | 1529 | + | // Connection failed, check if container exists but has a new IP |
| 1902 | - | log.Warn("Found root domain pointing to admin container, removing to avoid conflicts", | 1530 | + | containerID := route.ContainerID |
| 1903 | - | "root_domain", rootDomain, | 1531 | + | if containerID != "" { |
| 1904 | - | "admin_domain", adminDomain, | 1532 | + | // Try to get container info and check if it has a different IP |
| 1905 | - | "container", rootRoute.ContainerID) | 1533 | + | containerInfo, err := docker.GetContainerInfo(containerID) |
| 1906 | - | 1534 | + | if err == nil && containerInfo.NetworkSettings != nil { | |
| 1907 | - | // Begin a transaction | 1535 | + | // Check if container has a different IP |
| 1908 | - | tx, err := p.app.GetDB().Begin() | 1536 | + | networkName := p.app.GetConfig().ContainerEngine.Network |
| 1909 | - | if err != nil { | 1537 | + | if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists && |
| 1910 | - | log.Error("Failed to begin transaction for root domain cleanup", "error", err) | 1538 | + | networkSettings.IPAddress != "" && networkSettings.IPAddress != route.ContainerIP { |
| 1911 | - | return | 1539 | + | // Found container with different IP, update the route and use new IP for this request |
| 1912 | - | } | 1540 | + | logger.Warn("Container IP mismatch detected, using live IP instead of cached value", |
| 1913 | - | defer tx.Rollback() | 1541 | + | "domain", host, |
| 1914 | - | 1542 | + | "cached_ip", route.ContainerIP, | |
| 1915 | - | // Get the domain ID for root domain | 1543 | + | "live_ip", networkSettings.IPAddress) |
| 1916 | - | var domainID string | 1544 | + | |
| 1917 | - | err = tx.QueryRow("SELECT id FROM domain WHERE name = ?", rootDomain).Scan(&domainID) | 1545 | + | // Update the in-memory route for this request |
| 1918 | - | if err != nil { | 1546 | + | route.ContainerIP = networkSettings.IPAddress |
| 1919 | - | if err != sql.ErrNoRows { | 1547 | + | |
| 1920 | - | log.Error("Failed to query domain for root domain cleanup", "error", err) | 1548 | + | // Update the database in background to persist the change |
| 1549 | + | go func(domain, containerID, containerIP, containerPort string) { | |||
| 1550 | + | // Get the domain ID | |||
| 1551 | + | var domainID string | |||
| 1552 | + | err := p.app.GetDB().QueryRow("SELECT id FROM domain WHERE name = ?", domain).Scan(&domainID) | |||
| 1553 | + | if err != nil { | |||
| 1554 | + | logger.Error("Failed to find domain ID for IP update", "domain", domain, "error", err) | |||
| 1555 | + | return | |||
| 1556 | + | } | |||
| 1557 | + | ||||
| 1558 | + | // Update the proxy_route record | |||
| 1559 | + | now := time.Now().Format(time.RFC3339) | |||
| 1560 | + | _, err = p.app.GetDB().Exec(` | |||
| 1561 | + | UPDATE proxy_route SET container_ip = ?, updated_at = ? | |||
| 1562 | + | WHERE domain_id = ? AND container_id = ?`, | |||
| 1563 | + | containerIP, now, domainID, containerID) | |||
| 1564 | + | ||||
| 1565 | + | if err != nil { | |||
| 1566 | + | logger.Error("Failed to update container IP in database", | |||
| 1567 | + | "domain", domain, | |||
| 1568 | + | "containerID", containerID, | |||
| 1569 | + | "error", err) | |||
| 1570 | + | } else { | |||
| 1571 | + | logger.Info("Updated container IP in database after live detection", | |||
| 1572 | + | "domain", domain, | |||
| 1573 | + | "containerID", containerID, | |||
| 1574 | + | "old_ip", route.ContainerIP, | |||
| 1575 | + | "new_ip", containerIP) | |||
| 1576 | + | } | |||
| 1577 | + | }(host, containerID, networkSettings.IPAddress, route.ContainerPort) | |||
| 1578 | + | } else { | |||
| 1579 | + | logger.Debug("Container exists but connection still failed", | |||
| 1580 | + | "domain", host, | |||
| 1581 | + | "container_id", containerID, | |||
| 1582 | + | "container_ip", route.ContainerIP, | |||
| 1583 | + | "container_port", route.ContainerPort, | |||
| 1584 | + | "error", err) | |||
| 1585 | + | } | |||
| 1921 | } | 1586 | } | ||
| 1922 | - | return | |||
| 1923 | } | 1587 | } | ||
| 1588 | + | } else { | |||
| 1589 | + | // Connection successful, close it | |||
| 1590 | + | conn.Close() | |||
| 1591 | + | } | |||
| 1592 | + | } | |||
| 1924 | 1593 | ||||
| 1925 | - | // Delete the route for root domain | 1594 | + | // Create the target URL |
| 1926 | - | _, err = tx.Exec("DELETE FROM proxy_route WHERE domain_id = ?", domainID) | 1595 | + | targetURL := &url.URL{ |
| 1927 | - | if err != nil { | 1596 | + | Scheme: "http", // Always use HTTP for internal connections |
| 1928 | - | log.Error("Failed to delete root domain route", "error", err) | 1597 | + | } |
| 1929 | - | return | |||
| 1930 | - | } | |||
| 1931 | 1598 | ||||
| 1932 | - | // We don't delete the domain itself from the database to avoid affecting certificate management | 1599 | + | // Log protocol conversion if needed |
| 1600 | + | if route.Protocol == "https" { | |||
| 1601 | + | logger.Debug("Converting HTTPS external route to HTTP for internal connection", | |||
| 1602 | + | "domain", host, | |||
| 1603 | + | "containerID", route.ContainerID, | |||
| 1604 | + | "externalProtocol", "https", | |||
| 1605 | + | "internalProtocol", "http") | |||
| 1606 | + | } | |||
| 1933 | 1607 | ||||
| 1934 | - | // Commit the transaction | 1608 | + | // Format host properly for both IPv4 and IPv6 |
| 1935 | - | if err := tx.Commit(); err != nil { | 1609 | + | containerIP := route.ContainerIP |
| 1936 | - | log.Error("Failed to commit transaction for root domain cleanup", "error", err) | 1610 | + | if strings.Contains(containerIP, ":") { |
| 1937 | - | return | 1611 | + | // IPv6 address needs to be wrapped in brackets |
| 1938 | - | } | 1612 | + | containerIP = "[" + containerIP + "]" |
| 1613 | + | } | |||
| 1614 | + | targetURL.Host = fmt.Sprintf("%s:%s", containerIP, route.ContainerPort) | |||
| 1939 | 1615 | ||||
| 1940 | - | // Remove root domain from in-memory routes | 1616 | + | // Create a reverse proxy |
| 1941 | - | p.mu.Lock() | 1617 | + | proxy := httputil.NewSingleHostReverseProxy(targetURL) |
| 1942 | - | delete(p.routes, rootDomain) | |||
| 1943 | - | p.mu.Unlock() | |||
| 1944 | 1618 | ||||
| 1945 | - | log.Info("Successfully removed conflicting root domain route", | 1619 | + | // Update headers to allow for SSL redirection |
| 1946 | - | "root_domain", rootDomain, | 1620 | + | originalDirector := proxy.Director |
| 1947 | - | "admin_domain", adminDomain) | 1621 | + | proxy.Director = func(req *http.Request) { |
| 1622 | + | originalDirector(req) | |||
| 1623 | + | // Use the original protocol for forwarded proto header | |||
| 1624 | + | forwardedProto := "http" | |||
| 1625 | + | if route.Protocol == "https" { | |||
| 1626 | + | forwardedProto = "https" | |||
| 1948 | } | 1627 | } | ||
| 1949 | - | } | 1628 | + | req.Header.Set("X-Forwarded-Proto", forwardedProto) |
| 1950 | - | } | 1629 | + | req.Header.Set("X-Forwarded-Host", host) |
| 1630 | + | req.Header.Set("X-Forwarded-For", c.RealIP()) | |||
| 1631 | + | req.Header.Set("X-Real-IP", c.RealIP()) | |||
| 1951 | 1632 | ||||
| 1952 | - | // LogGordonIdentity prints information about the Gordon container for debugging | 1633 | + | // Log all headers for troubleshooting |
| 1953 | - | func (p *Proxy) LogGordonIdentity() { | 1634 | + | logger.Debug("Request headers for proxy", |
| 1954 | - | // If we don't have a container ID yet, try to load it from the database first | 1635 | + | "domain", host, |
| 1955 | - | if p.gordonContainerID == "" { | 1636 | + | "X-Forwarded-For", req.Header.Get("X-Forwarded-For"), |
| 1956 | - | dbContainerID := p.loadGordonContainerID() | 1637 | + | "X-Real-IP", req.Header.Get("X-Real-IP")) |
| 1957 | - | if dbContainerID != "" { | |||
| 1958 | - | p.gordonContainerID = dbContainerID | |||
| 1959 | - | log.Info("Loaded Gordon container ID from config file during identity check", "container_id", p.gordonContainerID) | |||
| 1960 | - | } | |||
| 1961 | } | 1638 | } | ||
| 1962 | 1639 | ||||
| 1963 | - | // If we still don't have a container ID, try to detect it | 1640 | + | // Add error handling |
| 1964 | - | if p.gordonContainerID == "" { | 1641 | + | proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { |
| 1965 | - | // This will attempt to detect the container and set p.gordonContainerID | 1642 | + | logger.Error("Proxy error", |
| 1966 | - | containerName := p.detectGordonContainer() | 1643 | + | "host", host, |
| 1967 | - | if p.gordonContainerID != "" { | 1644 | + | "path", r.URL.Path, |
| 1968 | - | log.Info("Detected Gordon container during identity check", | 1645 | + | "error", err) |
| 1969 | - | "container_id", p.gordonContainerID, | 1646 | + | c.String(http.StatusBadGateway, "Proxy Error: "+err.Error()) |
| 1970 | - | "container_name", containerName) | |||
| 1971 | - | } | |||
| 1972 | } | 1647 | } | ||
| 1973 | 1648 | ||||
| 1974 | - | // Now print our container ID for debugging | 1649 | + | // Serve the request |
| 1975 | - | if p.gordonContainerID != "" { | 1650 | + | proxy.ServeHTTP(c.Response(), c.Request()) |
| 1976 | - | log.Info("Gordon container identity", | 1651 | + | return nil |
| 1977 | - | "container_id", p.gordonContainerID, | |||
| 1978 | - | "container_name", p.detectGordonContainer()) | |||
| 1979 | - | } else { | |||
| 1980 | - | log.Warn("Gordon container ID not available - running outside container?") | |||
| 1981 | - | } | |||
| 1982 | } | 1652 | } | ||
M internal/proxy/server.go +70 -77
| @@ -5,11 +5,10 @@ import ( | |||
| 5 | 5 | "crypto/tls" | |
| 6 | 6 | "fmt" | |
| 7 | 7 | "net/http" | |
| 8 | - | "os" | |
| 9 | 8 | "time" | |
| 10 | 9 | ||
| 11 | 10 | "github.com/bnema/gordon/pkg/docker" | |
| 12 | - | "github.com/charmbracelet/log" | |
| 11 | + | "github.com/bnema/gordon/pkg/logger" | |
| 13 | 12 | "github.com/labstack/echo/v4" | |
| 14 | 13 | "golang.org/x/crypto/acme" | |
| 15 | 14 | ) | |
| @@ -29,12 +28,11 @@ func (p *Proxy) Start() error { | |||
| 29 | 28 | ||
| 30 | 29 | // Scan Gordon network containers and check for certificates | |
| 31 | 30 | if err := p.scanContainersAndCheckCertificates(); err != nil { | |
| 32 | - | log.Warn("Failed to scan containers for certificate checks", "error", err) | |
| 31 | + | logger.Warn("Failed to scan containers for certificate checks", "error", err) | |
| 33 | 32 | } | |
| 34 | 33 | ||
| 35 | 34 | // Run an immediate route verification to ensure all routes are valid on startup | |
| 36 | - | log.Info("Performing initial route verification on startup") | |
| 37 | - | p.verifyAllContainerIPs() | |
| 35 | + | logger.Info("Performing initial route verification on startup") | |
| 38 | 36 | ||
| 39 | 37 | // Discover any containers that should have routes but don't | |
| 40 | 38 | p.DiscoverMissingRoutes() | |
| @@ -44,20 +42,16 @@ func (p *Proxy) Start() error { | |||
| 44 | 42 | ||
| 45 | 43 | // Start the container event listener for real-time updates | |
| 46 | 44 | if err := p.StartContainerEventListener(); err != nil { | |
| 47 | - | log.Warn("Failed to start container event listener", "error", err) | |
| 45 | + | logger.Warn("Failed to start container event listener", "error", err) | |
| 48 | 46 | // Continue anyway, this is non-critical | |
| 49 | 47 | } | |
| 50 | 48 | ||
| 51 | - | // Start periodic IP verification (every 5 minutes) | |
| 52 | - | // This ensures we catch any IP changes that the event listener might miss | |
| 53 | - | p.StartPeriodicIPVerification(5 * time.Minute) | |
| 54 | - | ||
| 55 | 49 | // Check if there might be port conflicts with the main server | |
| 56 | 50 | mainServerPort := p.app.GetConfig().Http.Port | |
| 57 | 51 | ||
| 58 | 52 | // Check HTTP port conflict | |
| 59 | 53 | if p.config.HttpPort == mainServerPort { | |
| 60 | - | log.Warn("HTTP port for reverse proxy conflicts with main server port", | |
| 54 | + | logger.Warn("HTTP port for reverse proxy conflicts with main server port", | |
| 61 | 55 | "port", p.config.HttpPort, | |
| 62 | 56 | "solution", "reverse proxy HTTP server will be disabled") | |
| 63 | 57 | return nil | |
| @@ -65,7 +59,7 @@ func (p *Proxy) Start() error { | |||
| 65 | 59 | ||
| 66 | 60 | // Check HTTPS port conflict (less common, but still possible) | |
| 67 | 61 | if p.config.Port == mainServerPort { | |
| 68 | - | log.Warn("HTTPS port for reverse proxy conflicts with main server port", | |
| 62 | + | logger.Warn("HTTPS port for reverse proxy conflicts with main server port", | |
| 69 | 63 | "port", p.config.Port, | |
| 70 | 64 | "solution", "reverse proxy HTTPS server will be disabled") | |
| 71 | 65 | return nil | |
| @@ -74,32 +68,38 @@ func (p *Proxy) Start() error { | |||
| 74 | 68 | // Add a special route for the admin domain (Gordon itself) | |
| 75 | 69 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| 76 | 70 | ||
| 77 | - | // In a container environment, we need to use the host container IP | |
| 78 | - | // instead of 127.0.0.1 because each container has its own localhost | |
| 79 | - | containerIP := "localhost" // Default to localhost for most reliable connectivity | |
| 80 | - | ||
| 81 | - | // Fall back options for container IP | |
| 82 | - | if os.Getenv("GORDON_ADMIN_HOST") != "" { | |
| 83 | - | // Allow explicit configuration via env var | |
| 84 | - | containerIP = os.Getenv("GORDON_ADMIN_HOST") | |
| 85 | - | log.Debug("Using admin host from environment variable", | |
| 86 | - | "host", containerIP) | |
| 87 | - | } else if os.Getenv("HOSTNAME") != "" { | |
| 88 | - | // Use container's own hostname as they're on the same network | |
| 89 | - | containerIP = os.Getenv("HOSTNAME") | |
| 90 | - | log.Debug("Using container hostname for admin routing", | |
| 91 | - | "hostname", containerIP) | |
| 71 | + | // Auto-detect the Gordon container name and ID | |
| 72 | + | containerName := p.detectGordonContainer() | |
| 73 | + | ||
| 74 | + | // Get the network name from config | |
| 75 | + | networkName := p.app.GetConfig().ContainerEngine.Network | |
| 76 | + | ||
| 77 | + | // Initialize container IP | |
| 78 | + | var containerIP string | |
| 79 | + | ||
| 80 | + | // Try to get the actual container IP from the network | |
| 81 | + | if p.gordonContainerID == "" { | |
| 82 | + | return fmt.Errorf("failed to detect Gordon container ID, cannot add admin route") | |
| 92 | 83 | } | |
| 93 | 84 | ||
| 94 | - | // Don't test connections yet - the server isn't running | |
| 95 | - | // We'll add the admin route with the default host for now | |
| 96 | - | // and test connections later with TestAdminConnectionLater | |
| 85 | + | // Get the IP address from the container's network | |
| 86 | + | ip, err := docker.GetContainerIPFromNetwork(p.gordonContainerID, networkName) | |
| 87 | + | if err != nil || ip == "" { | |
| 88 | + | logger.Error("Failed to get Gordon container IP from network", | |
| 89 | + | "container_id", p.gordonContainerID, | |
| 90 | + | "network", networkName, | |
| 91 | + | "error", err) | |
| 92 | + | return fmt.Errorf("failed to get Gordon container IP from network %s: %w", networkName, err) | |
| 93 | + | } | |
| 97 | 94 | ||
| 98 | - | // Auto-detect the Gordon container name | |
| 99 | - | containerName := p.detectGordonContainer() | |
| 95 | + | containerIP = ip | |
| 96 | + | logger.Info("Using Gordon container IP from network", | |
| 97 | + | "container_id", p.gordonContainerID, | |
| 98 | + | "network", networkName, | |
| 99 | + | "ip", containerIP) | |
| 100 | 100 | ||
| 101 | 101 | // Check if admin route exists in database and create/update it | |
| 102 | - | err := p.AddRoute( | |
| 102 | + | err = p.AddRoute( | |
| 103 | 103 | adminDomain, | |
| 104 | 104 | containerName, | |
| 105 | 105 | containerIP, | |
| @@ -109,11 +109,11 @@ func (p *Proxy) Start() error { | |||
| 109 | 109 | ) | |
| 110 | 110 | ||
| 111 | 111 | if err != nil { | |
| 112 | - | log.Error("Failed to add admin route to database", | |
| 112 | + | logger.Error("Failed to add admin route to database", | |
| 113 | 113 | "error", err, | |
| 114 | 114 | "domain", adminDomain) | |
| 115 | 115 | } else { | |
| 116 | - | log.Info("Ensured admin domain route is in database", | |
| 116 | + | logger.Info("Ensured admin domain route is in database", | |
| 117 | 117 | "domain", adminDomain, | |
| 118 | 118 | "container", containerName, | |
| 119 | 119 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | |
| @@ -131,12 +131,12 @@ func (p *Proxy) Start() error { | |||
| 131 | 131 | Path: "/", | |
| 132 | 132 | Active: true, | |
| 133 | 133 | } | |
| 134 | - | log.Info("Added special route for admin domain", | |
| 134 | + | logger.Info("Added special route for admin domain", | |
| 135 | 135 | "domain", adminDomain, | |
| 136 | 136 | "container", containerName, | |
| 137 | 137 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | |
| 138 | 138 | } else { | |
| 139 | - | log.Debug("Admin domain route already exists", | |
| 139 | + | logger.Debug("Admin domain route already exists", | |
| 140 | 140 | "domain", adminDomain, | |
| 141 | 141 | "route", fmt.Sprintf("%s://%s:%s", | |
| 142 | 142 | p.routes[adminDomain].Protocol, | |
| @@ -145,13 +145,6 @@ func (p *Proxy) Start() error { | |||
| 145 | 145 | } | |
| 146 | 146 | p.mu.Unlock() | |
| 147 | 147 | ||
| 148 | - | // We no longer automatically add the root domain redirect to avoid conflicts | |
| 149 | - | // with other containers that might be using the root domain. | |
| 150 | - | // If root domain redirection is needed, it should be explicitly configured. | |
| 151 | - | ||
| 152 | - | // Clean up any existing root domain conflict | |
| 153 | - | p.CleanupRootDomainForAdmin() | |
| 154 | - | ||
| 155 | 148 | // Set up middleware | |
| 156 | 149 | p.setupMiddleware() | |
| 157 | 150 | ||
| @@ -170,7 +163,7 @@ func (p *Proxy) Start() error { | |||
| 170 | 163 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | |
| 171 | 164 | // Log SNI information in debug mode | |
| 172 | 165 | if hello.ServerName != "" { | |
| 173 | - | log.Debug("TLS handshake with SNI", | |
| 166 | + | logger.Debug("TLS handshake with SNI", | |
| 174 | 167 | "server_name", hello.ServerName) | |
| 175 | 168 | } | |
| 176 | 169 | ||
| @@ -179,7 +172,7 @@ func (p *Proxy) Start() error { | |||
| 179 | 172 | ||
| 180 | 173 | // If we can't get a certificate and we have a fallback, use it for the admin domain | |
| 181 | 174 | if (err != nil || cert == nil) && hello.ServerName == adminDomain && p.fallbackCert != nil { | |
| 182 | - | log.Debug("Using fallback certificate for admin domain", | |
| 175 | + | logger.Debug("Using fallback certificate for admin domain", | |
| 183 | 176 | "domain", adminDomain, | |
| 184 | 177 | "error", err) | |
| 185 | 178 | return p.fallbackCert, nil | |
| @@ -212,18 +205,18 @@ func (p *Proxy) Start() error { | |||
| 212 | 205 | ||
| 213 | 206 | // Start HTTPS server | |
| 214 | 207 | go func() { | |
| 215 | - | log.Info("Starting HTTPS server", "address", httpsServer.Addr) | |
| 208 | + | logger.Info("Starting HTTPS server", "address", httpsServer.Addr) | |
| 216 | 209 | // Using empty strings for cert and key files since we're using GetCertificate | |
| 217 | 210 | if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { | |
| 218 | - | log.Error("HTTPS server failed", "error", err) | |
| 211 | + | logger.Error("HTTPS server failed", "error", err) | |
| 219 | 212 | } | |
| 220 | 213 | }() | |
| 221 | 214 | ||
| 222 | 215 | // Start HTTP server | |
| 223 | 216 | go func() { | |
| 224 | - | log.Info("Starting HTTP server", "address", httpServer.Addr) | |
| 217 | + | logger.Info("Starting HTTP server", "address", httpServer.Addr) | |
| 225 | 218 | if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | |
| 226 | - | log.Error("HTTP server failed", "error", err) | |
| 219 | + | logger.Error("HTTP server failed", "error", err) | |
| 227 | 220 | } | |
| 228 | 221 | }() | |
| 229 | 222 | ||
| @@ -235,7 +228,7 @@ func (p *Proxy) Start() error { | |||
| 235 | 228 | ||
| 236 | 229 | // Stop gracefully shuts down the proxy server | |
| 237 | 230 | func (p *Proxy) Stop() error { | |
| 238 | - | log.Info("Stopping reverse proxy") | |
| 231 | + | logger.Info("Stopping reverse proxy") | |
| 239 | 232 | ||
| 240 | 233 | // Run any rate limiter cleanup functions | |
| 241 | 234 | for _, cleanup := range rateLimiterCleanup { | |
| @@ -247,7 +240,7 @@ func (p *Proxy) Stop() error { | |||
| 247 | 240 | ||
| 248 | 241 | // Skip the rest if the server was never started | |
| 249 | 242 | if !p.serverStarted { | |
| 250 | - | log.Debug("Proxy server was never started, nothing to stop") | |
| 243 | + | logger.Debug("Proxy server was never started, nothing to stop") | |
| 251 | 244 | return nil | |
| 252 | 245 | } | |
| 253 | 246 | ||
| @@ -262,18 +255,18 @@ func (p *Proxy) Stop() error { | |||
| 262 | 255 | // Shutdown HTTP server if it's running | |
| 263 | 256 | if p.httpServer != nil { | |
| 264 | 257 | if err := p.httpServer.Shutdown(ctx); err != nil { | |
| 265 | - | log.Error("Failed to gracefully shutdown HTTP server", "error", err) | |
| 258 | + | logger.Error("Failed to gracefully shutdown HTTP server", "error", err) | |
| 266 | 259 | } | |
| 267 | 260 | } | |
| 268 | 261 | ||
| 269 | 262 | // Shutdown HTTPS server if it's running | |
| 270 | 263 | if p.httpsServer != nil { | |
| 271 | 264 | if err := p.httpsServer.Shutdown(ctx); err != nil { | |
| 272 | - | log.Error("Failed to gracefully shutdown HTTPS server", "error", err) | |
| 265 | + | logger.Error("Failed to gracefully shutdown HTTPS server", "error", err) | |
| 273 | 266 | } | |
| 274 | 267 | } | |
| 275 | 268 | ||
| 276 | - | log.Info("Reverse proxy stopped") | |
| 269 | + | logger.Info("Reverse proxy stopped") | |
| 277 | 270 | return nil | |
| 278 | 271 | } | |
| 279 | 272 | ||
| @@ -282,7 +275,7 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||
| 282 | 275 | adminPath := p.app.GetConfig().Admin.Path | |
| 283 | 276 | url := fmt.Sprintf("http://%s:%s%s", defaultHost, port, adminPath) | |
| 284 | 277 | ||
| 285 | - | log.Debug("Testing admin connection", "url", url) | |
| 278 | + | logger.Debug("Testing admin connection", "url", url) | |
| 286 | 279 | ||
| 287 | 280 | // Try connecting using the provided default host | |
| 288 | 281 | client := &http.Client{ | |
| @@ -298,7 +291,7 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||
| 298 | 291 | resp, err := client.Get(url) | |
| 299 | 292 | if err == nil { | |
| 300 | 293 | resp.Body.Close() | |
| 301 | - | log.Info("Admin connection successful", "host", defaultHost) | |
| 294 | + | logger.Info("Admin connection successful", "host", defaultHost) | |
| 302 | 295 | return defaultHost | |
| 303 | 296 | } | |
| 304 | 297 | ||
| @@ -316,17 +309,17 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||
| 316 | 309 | } | |
| 317 | 310 | ||
| 318 | 311 | url = fmt.Sprintf("http://%s:%s%s", host, port, adminPath) | |
| 319 | - | log.Debug("Trying alternative host", "url", url) | |
| 312 | + | logger.Debug("Trying alternative host", "url", url) | |
| 320 | 313 | ||
| 321 | 314 | resp, err := client.Get(url) | |
| 322 | 315 | if err == nil { | |
| 323 | 316 | resp.Body.Close() | |
| 324 | - | log.Info("Found working admin connection", "host", host) | |
| 317 | + | logger.Info("Found working admin connection", "host", host) | |
| 325 | 318 | return host | |
| 326 | 319 | } | |
| 327 | 320 | } | |
| 328 | 321 | ||
| 329 | - | log.Warn("Could not establish admin connection on any tested host", "fallback", defaultHost) | |
| 322 | + | logger.Warn("Could not establish admin connection on any tested host", "fallback", defaultHost) | |
| 330 | 323 | return defaultHost | |
| 331 | 324 | } | |
| 332 | 325 | ||
| @@ -335,7 +328,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||
| 335 | 328 | // Wait for servers to start | |
| 336 | 329 | time.Sleep(2 * time.Second) | |
| 337 | 330 | ||
| 338 | - | log.Debug("Testing admin connections after server startup") | |
| 331 | + | logger.Debug("Testing admin connections after server startup") | |
| 339 | 332 | ||
| 340 | 333 | // Get domain and current route | |
| 341 | 334 | adminDomain := p.app.GetConfig().Http.FullDomain() | |
| @@ -345,7 +338,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||
| 345 | 338 | p.mu.RUnlock() | |
| 346 | 339 | ||
| 347 | 340 | if !exists { | |
| 348 | - | log.Warn("Admin route not found") | |
| 341 | + | logger.Warn("Admin route not found") | |
| 349 | 342 | return | |
| 350 | 343 | } | |
| 351 | 344 | ||
| @@ -355,7 +348,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||
| 355 | 348 | p.mu.Lock() | |
| 356 | 349 | if r, ok := p.routes[adminDomain]; ok { | |
| 357 | 350 | r.ContainerIP = testedIP | |
| 358 | - | log.Info("Updated admin route with working connection", | |
| 351 | + | logger.Info("Updated admin route with working connection", | |
| 359 | 352 | "domain", adminDomain, | |
| 360 | 353 | "ip", testedIP) | |
| 361 | 354 | } | |
| @@ -384,10 +377,10 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 384 | 377 | httpURL := fmt.Sprintf("http://%s/.well-known/acme-challenge/test", adminDomain) | |
| 385 | 378 | _, httpErr := client.Get(httpURL) | |
| 386 | 379 | if httpErr != nil { | |
| 387 | - | log.Warn("External HTTP port 80 might not be accessible", | |
| 380 | + | logger.Warn("External HTTP port 80 might not be accessible", | |
| 388 | 381 | "error", httpErr.Error()) | |
| 389 | 382 | } else { | |
| 390 | - | log.Info("External HTTP port 80 is accessible") | |
| 383 | + | logger.Info("External HTTP port 80 is accessible") | |
| 391 | 384 | } | |
| 392 | 385 | ||
| 393 | 386 | // Test HTTPS port 443 | |
| @@ -406,10 +399,10 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 406 | 399 | httpsURL := fmt.Sprintf("https://%s/", adminDomain) | |
| 407 | 400 | _, httpsErr := httpsClient.Get(httpsURL) | |
| 408 | 401 | if httpsErr != nil { | |
| 409 | - | log.Warn("External HTTPS port 443 might not be accessible", | |
| 402 | + | logger.Warn("External HTTPS port 443 might not be accessible", | |
| 410 | 403 | "error", httpsErr.Error()) | |
| 411 | 404 | } else { | |
| 412 | - | log.Info("External HTTPS port 443 is accessible") | |
| 405 | + | logger.Info("External HTTPS port 443 is accessible") | |
| 413 | 406 | } | |
| 414 | 407 | } | |
| 415 | 408 | ||
| @@ -417,7 +410,7 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 417 | 410 | // func (p *Proxy) detectGordonContainer() string { | |
| 418 | 411 | // // Check if we're running inside a container | |
| 419 | 412 | // if docker.IsRunningInContainer() { | |
| 420 | - | // log.Debug("Detected we're running in a container") | |
| 413 | + | // logger.Debug("Detected we're running in a container") | |
| 421 | 414 | ||
| 422 | 415 | // // Get our hostname which should be the container ID in Docker/Podman | |
| 423 | 416 | // hostname := os.Getenv("HOSTNAME") | |
| @@ -430,12 +423,12 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 430 | 423 | // if strings.HasPrefix(hostname, container.ID) { | |
| 431 | 424 | // // This is our container | |
| 432 | 425 | // containerName := strings.TrimLeft(container.Names[0], "/") | |
| 433 | - | // log.Debug("Auto-detected Gordon container name from hostname", "container_name", containerName) | |
| 426 | + | // logger.Debug("Auto-detected Gordon container name from hostname", "container_name", containerName) | |
| 434 | 427 | // return containerName | |
| 435 | 428 | // } | |
| 436 | 429 | // } | |
| 437 | 430 | // } else { | |
| 438 | - | // log.Debug("Could not list containers when checking hostname", "error", err) | |
| 431 | + | // logger.Debug("Could not list containers when checking hostname", "error", err) | |
| 439 | 432 | // } | |
| 440 | 433 | // } | |
| 441 | 434 | // } | |
| @@ -450,21 +443,21 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 450 | 443 | // containerLogs, err := docker.GetContainerLogs(containerID) | |
| 451 | 444 | // if err == nil && (strings.Contains(containerLogs, "/gordon") || strings.Contains(containerLogs, "serve")) { | |
| 452 | 445 | // containerName := strings.TrimLeft(container.Names[0], "/") | |
| 453 | - | // log.Debug("Auto-detected Gordon container by process", "container_name", containerName) | |
| 446 | + | // logger.Debug("Auto-detected Gordon container by process", "container_name", containerName) | |
| 454 | 447 | // return containerName | |
| 455 | 448 | // } else if err != nil { | |
| 456 | - | // log.Debug("Could not get container logs", "container_id", containerID, "error", err) | |
| 449 | + | // logger.Debug("Could not get container logs", "container_id", containerID, "error", err) | |
| 457 | 450 | // } | |
| 458 | 451 | ||
| 459 | 452 | // // Check container command for Gordon | |
| 460 | 453 | // if len(container.Command) > 0 && strings.Contains(container.Command, "gordon") { | |
| 461 | 454 | // containerName := strings.TrimLeft(container.Names[0], "/") | |
| 462 | - | // log.Debug("Auto-detected Gordon container by command", "container_name", containerName) | |
| 455 | + | // logger.Debug("Auto-detected Gordon container by command", "container_name", containerName) | |
| 463 | 456 | // return containerName | |
| 464 | 457 | // } | |
| 465 | 458 | // } | |
| 466 | 459 | // } else { | |
| 467 | - | // log.Debug("Could not list containers when checking processes", "error", err) | |
| 460 | + | // logger.Debug("Could not list containers when checking processes", "error", err) | |
| 468 | 461 | // } | |
| 469 | 462 | ||
| 470 | 463 | // // If we couldn't detect from processes, try to find a container with gordon in the name or image | |
| @@ -474,7 +467,7 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 474 | 467 | // for _, name := range container.Names { | |
| 475 | 468 | // if strings.Contains(strings.ToLower(name), "gordon") { | |
| 476 | 469 | // containerName := strings.TrimLeft(name, "/") | |
| 477 | - | // log.Debug("Auto-detected Gordon container by name", "container_name", containerName) | |
| 470 | + | // logger.Debug("Auto-detected Gordon container by name", "container_name", containerName) | |
| 478 | 471 | // return containerName | |
| 479 | 472 | // } | |
| 480 | 473 | // } | |
| @@ -482,13 +475,13 @@ func (p *Proxy) checkExternalPortAccess() { | |||
| 482 | 475 | // // Check image name for "gordon" | |
| 483 | 476 | // if strings.Contains(strings.ToLower(container.Image), "gordon") { | |
| 484 | 477 | // containerName := strings.TrimLeft(container.Names[0], "/") | |
| 485 | - | // log.Debug("Auto-detected Gordon container by image", "container_name", containerName, "image", container.Image) | |
| 478 | + | // logger.Debug("Auto-detected Gordon container by image", "container_name", containerName, "image", container.Image) | |
| 486 | 479 | // return containerName | |
| 487 | 480 | // } | |
| 488 | 481 | // } | |
| 489 | 482 | // } | |
| 490 | 483 | ||
| 491 | 484 | // // If all else fails, default to "gordon" | |
| 492 | - | // log.Debug("Could not auto-detect Gordon container, using default name", "container_name", "gordon") | |
| 485 | + | // logger.Debug("Could not auto-detect Gordon container, using default name", "container_name", "gordon") | |
| 493 | 486 | // return "gordon" | |
| 494 | 487 | // } | |
| @@ -5,11 +5,10 @@ import ( | |||||
| 5 | "crypto/tls" | 5 | "crypto/tls" | ||
| 6 | "fmt" | 6 | "fmt" | ||
| 7 | "net/http" | 7 | "net/http" | ||
| 8 | - | "os" | |||
| 9 | "time" | 8 | "time" | ||
| 10 | 9 | ||||
| 11 | "github.com/bnema/gordon/pkg/docker" | 10 | "github.com/bnema/gordon/pkg/docker" | ||
| 12 | - | "github.com/charmbracelet/log" | 11 | + | "github.com/bnema/gordon/pkg/logger" |
| 13 | "github.com/labstack/echo/v4" | 12 | "github.com/labstack/echo/v4" | ||
| 14 | "golang.org/x/crypto/acme" | 13 | "golang.org/x/crypto/acme" | ||
| 15 | ) | 14 | ) | ||
| @@ -29,12 +28,11 @@ func (p *Proxy) Start() error { | |||||
| 29 | 28 | ||||
| 30 | // Scan Gordon network containers and check for certificates | 29 | // Scan Gordon network containers and check for certificates | ||
| 31 | if err := p.scanContainersAndCheckCertificates(); err != nil { | 30 | if err := p.scanContainersAndCheckCertificates(); err != nil { | ||
| 32 | - | log.Warn("Failed to scan containers for certificate checks", "error", err) | 31 | + | logger.Warn("Failed to scan containers for certificate checks", "error", err) |
| 33 | } | 32 | } | ||
| 34 | 33 | ||||
| 35 | // Run an immediate route verification to ensure all routes are valid on startup | 34 | // Run an immediate route verification to ensure all routes are valid on startup | ||
| 36 | - | log.Info("Performing initial route verification on startup") | 35 | + | logger.Info("Performing initial route verification on startup") |
| 37 | - | p.verifyAllContainerIPs() | |||
| 38 | 36 | ||||
| 39 | // Discover any containers that should have routes but don't | 37 | // Discover any containers that should have routes but don't | ||
| 40 | p.DiscoverMissingRoutes() | 38 | p.DiscoverMissingRoutes() | ||
| @@ -44,20 +42,16 @@ func (p *Proxy) Start() error { | |||||
| 44 | 42 | ||||
| 45 | // Start the container event listener for real-time updates | 43 | // Start the container event listener for real-time updates | ||
| 46 | if err := p.StartContainerEventListener(); err != nil { | 44 | if err := p.StartContainerEventListener(); err != nil { | ||
| 47 | - | log.Warn("Failed to start container event listener", "error", err) | 45 | + | logger.Warn("Failed to start container event listener", "error", err) |
| 48 | // Continue anyway, this is non-critical | 46 | // Continue anyway, this is non-critical | ||
| 49 | } | 47 | } | ||
| 50 | 48 | ||||
| 51 | - | // Start periodic IP verification (every 5 minutes) | |||
| 52 | - | // This ensures we catch any IP changes that the event listener might miss | |||
| 53 | - | p.StartPeriodicIPVerification(5 * time.Minute) | |||
| 54 | - | ||||
| 55 | // Check if there might be port conflicts with the main server | 49 | // Check if there might be port conflicts with the main server | ||
| 56 | mainServerPort := p.app.GetConfig().Http.Port | 50 | mainServerPort := p.app.GetConfig().Http.Port | ||
| 57 | 51 | ||||
| 58 | // Check HTTP port conflict | 52 | // Check HTTP port conflict | ||
| 59 | if p.config.HttpPort == mainServerPort { | 53 | if p.config.HttpPort == mainServerPort { | ||
| 60 | - | log.Warn("HTTP port for reverse proxy conflicts with main server port", | 54 | + | logger.Warn("HTTP port for reverse proxy conflicts with main server port", |
| 61 | "port", p.config.HttpPort, | 55 | "port", p.config.HttpPort, | ||
| 62 | "solution", "reverse proxy HTTP server will be disabled") | 56 | "solution", "reverse proxy HTTP server will be disabled") | ||
| 63 | return nil | 57 | return nil | ||
| @@ -65,7 +59,7 @@ func (p *Proxy) Start() error { | |||||
| 65 | 59 | ||||
| 66 | // Check HTTPS port conflict (less common, but still possible) | 60 | // Check HTTPS port conflict (less common, but still possible) | ||
| 67 | if p.config.Port == mainServerPort { | 61 | if p.config.Port == mainServerPort { | ||
| 68 | - | log.Warn("HTTPS port for reverse proxy conflicts with main server port", | 62 | + | logger.Warn("HTTPS port for reverse proxy conflicts with main server port", |
| 69 | "port", p.config.Port, | 63 | "port", p.config.Port, | ||
| 70 | "solution", "reverse proxy HTTPS server will be disabled") | 64 | "solution", "reverse proxy HTTPS server will be disabled") | ||
| 71 | return nil | 65 | return nil | ||
| @@ -74,32 +68,38 @@ func (p *Proxy) Start() error { | |||||
| 74 | // Add a special route for the admin domain (Gordon itself) | 68 | // Add a special route for the admin domain (Gordon itself) | ||
| 75 | adminDomain := p.app.GetConfig().Http.FullDomain() | 69 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| 76 | 70 | ||||
| 77 | - | // In a container environment, we need to use the host container IP | 71 | + | // Auto-detect the Gordon container name and ID |
| 78 | - | // instead of 127.0.0.1 because each container has its own localhost | 72 | + | containerName := p.detectGordonContainer() |
| 79 | - | containerIP := "localhost" // Default to localhost for most reliable connectivity | 73 | + | |
| 80 | - | 74 | + | // Get the network name from config | |
| 81 | - | // Fall back options for container IP | 75 | + | networkName := p.app.GetConfig().ContainerEngine.Network |
| 82 | - | if os.Getenv("GORDON_ADMIN_HOST") != "" { | 76 | + | |
| 83 | - | // Allow explicit configuration via env var | 77 | + | // Initialize container IP |
| 84 | - | containerIP = os.Getenv("GORDON_ADMIN_HOST") | 78 | + | var containerIP string |
| 85 | - | log.Debug("Using admin host from environment variable", | 79 | + | |
| 86 | - | "host", containerIP) | 80 | + | // Try to get the actual container IP from the network |
| 87 | - | } else if os.Getenv("HOSTNAME") != "" { | 81 | + | if p.gordonContainerID == "" { |
| 88 | - | // Use container's own hostname as they're on the same network | 82 | + | return fmt.Errorf("failed to detect Gordon container ID, cannot add admin route") |
| 89 | - | containerIP = os.Getenv("HOSTNAME") | |||
| 90 | - | log.Debug("Using container hostname for admin routing", | |||
| 91 | - | "hostname", containerIP) | |||
| 92 | } | 83 | } | ||
| 93 | 84 | ||||
| 94 | - | // Don't test connections yet - the server isn't running | 85 | + | // Get the IP address from the container's network |
| 95 | - | // We'll add the admin route with the default host for now | 86 | + | ip, err := docker.GetContainerIPFromNetwork(p.gordonContainerID, networkName) |
| 96 | - | // and test connections later with TestAdminConnectionLater | 87 | + | if err != nil || ip == "" { |
| 88 | + | logger.Error("Failed to get Gordon container IP from network", | |||
| 89 | + | "container_id", p.gordonContainerID, | |||
| 90 | + | "network", networkName, | |||
| 91 | + | "error", err) | |||
| 92 | + | return fmt.Errorf("failed to get Gordon container IP from network %s: %w", networkName, err) | |||
| 93 | + | } | |||
| 97 | 94 | ||||
| 98 | - | // Auto-detect the Gordon container name | 95 | + | containerIP = ip |
| 99 | - | containerName := p.detectGordonContainer() | 96 | + | logger.Info("Using Gordon container IP from network", |
| 97 | + | "container_id", p.gordonContainerID, | |||
| 98 | + | "network", networkName, | |||
| 99 | + | "ip", containerIP) | |||
| 100 | 100 | ||||
| 101 | // Check if admin route exists in database and create/update it | 101 | // Check if admin route exists in database and create/update it | ||
| 102 | - | err := p.AddRoute( | 102 | + | err = p.AddRoute( |
| 103 | adminDomain, | 103 | adminDomain, | ||
| 104 | containerName, | 104 | containerName, | ||
| 105 | containerIP, | 105 | containerIP, | ||
| @@ -109,11 +109,11 @@ func (p *Proxy) Start() error { | |||||
| 109 | ) | 109 | ) | ||
| 110 | 110 | ||||
| 111 | if err != nil { | 111 | if err != nil { | ||
| 112 | - | log.Error("Failed to add admin route to database", | 112 | + | logger.Error("Failed to add admin route to database", |
| 113 | "error", err, | 113 | "error", err, | ||
| 114 | "domain", adminDomain) | 114 | "domain", adminDomain) | ||
| 115 | } else { | 115 | } else { | ||
| 116 | - | log.Info("Ensured admin domain route is in database", | 116 | + | logger.Info("Ensured admin domain route is in database", |
| 117 | "domain", adminDomain, | 117 | "domain", adminDomain, | ||
| 118 | "container", containerName, | 118 | "container", containerName, | ||
| 119 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | 119 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | ||
| @@ -131,12 +131,12 @@ func (p *Proxy) Start() error { | |||||
| 131 | Path: "/", | 131 | Path: "/", | ||
| 132 | Active: true, | 132 | Active: true, | ||
| 133 | } | 133 | } | ||
| 134 | - | log.Info("Added special route for admin domain", | 134 | + | logger.Info("Added special route for admin domain", |
| 135 | "domain", adminDomain, | 135 | "domain", adminDomain, | ||
| 136 | "container", containerName, | 136 | "container", containerName, | ||
| 137 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | 137 | "target", fmt.Sprintf("http://%s:%s", containerIP, p.app.GetConfig().Http.Port)) | ||
| 138 | } else { | 138 | } else { | ||
| 139 | - | log.Debug("Admin domain route already exists", | 139 | + | logger.Debug("Admin domain route already exists", |
| 140 | "domain", adminDomain, | 140 | "domain", adminDomain, | ||
| 141 | "route", fmt.Sprintf("%s://%s:%s", | 141 | "route", fmt.Sprintf("%s://%s:%s", | ||
| 142 | p.routes[adminDomain].Protocol, | 142 | p.routes[adminDomain].Protocol, | ||
| @@ -145,13 +145,6 @@ func (p *Proxy) Start() error { | |||||
| 145 | } | 145 | } | ||
| 146 | p.mu.Unlock() | 146 | p.mu.Unlock() | ||
| 147 | 147 | ||||
| 148 | - | // We no longer automatically add the root domain redirect to avoid conflicts | |||
| 149 | - | // with other containers that might be using the root domain. | |||
| 150 | - | // If root domain redirection is needed, it should be explicitly configured. | |||
| 151 | - | ||||
| 152 | - | // Clean up any existing root domain conflict | |||
| 153 | - | p.CleanupRootDomainForAdmin() | |||
| 154 | - | ||||
| 155 | // Set up middleware | 148 | // Set up middleware | ||
| 156 | p.setupMiddleware() | 149 | p.setupMiddleware() | ||
| 157 | 150 | ||||
| @@ -170,7 +163,7 @@ func (p *Proxy) Start() error { | |||||
| 170 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | 163 | GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { | ||
| 171 | // Log SNI information in debug mode | 164 | // Log SNI information in debug mode | ||
| 172 | if hello.ServerName != "" { | 165 | if hello.ServerName != "" { | ||
| 173 | - | log.Debug("TLS handshake with SNI", | 166 | + | logger.Debug("TLS handshake with SNI", |
| 174 | "server_name", hello.ServerName) | 167 | "server_name", hello.ServerName) | ||
| 175 | } | 168 | } | ||
| 176 | 169 | ||||
| @@ -179,7 +172,7 @@ func (p *Proxy) Start() error { | |||||
| 179 | 172 | ||||
| 180 | // If we can't get a certificate and we have a fallback, use it for the admin domain | 173 | // If we can't get a certificate and we have a fallback, use it for the admin domain | ||
| 181 | if (err != nil || cert == nil) && hello.ServerName == adminDomain && p.fallbackCert != nil { | 174 | if (err != nil || cert == nil) && hello.ServerName == adminDomain && p.fallbackCert != nil { | ||
| 182 | - | log.Debug("Using fallback certificate for admin domain", | 175 | + | logger.Debug("Using fallback certificate for admin domain", |
| 183 | "domain", adminDomain, | 176 | "domain", adminDomain, | ||
| 184 | "error", err) | 177 | "error", err) | ||
| 185 | return p.fallbackCert, nil | 178 | return p.fallbackCert, nil | ||
| @@ -212,18 +205,18 @@ func (p *Proxy) Start() error { | |||||
| 212 | 205 | ||||
| 213 | // Start HTTPS server | 206 | // Start HTTPS server | ||
| 214 | go func() { | 207 | go func() { | ||
| 215 | - | log.Info("Starting HTTPS server", "address", httpsServer.Addr) | 208 | + | logger.Info("Starting HTTPS server", "address", httpsServer.Addr) |
| 216 | // Using empty strings for cert and key files since we're using GetCertificate | 209 | // Using empty strings for cert and key files since we're using GetCertificate | ||
| 217 | if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { | 210 | if err := httpsServer.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed { | ||
| 218 | - | log.Error("HTTPS server failed", "error", err) | 211 | + | logger.Error("HTTPS server failed", "error", err) |
| 219 | } | 212 | } | ||
| 220 | }() | 213 | }() | ||
| 221 | 214 | ||||
| 222 | // Start HTTP server | 215 | // Start HTTP server | ||
| 223 | go func() { | 216 | go func() { | ||
| 224 | - | log.Info("Starting HTTP server", "address", httpServer.Addr) | 217 | + | logger.Info("Starting HTTP server", "address", httpServer.Addr) |
| 225 | if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | 218 | if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { | ||
| 226 | - | log.Error("HTTP server failed", "error", err) | 219 | + | logger.Error("HTTP server failed", "error", err) |
| 227 | } | 220 | } | ||
| 228 | }() | 221 | }() | ||
| 229 | 222 | ||||
| @@ -235,7 +228,7 @@ func (p *Proxy) Start() error { | |||||
| 235 | 228 | ||||
| 236 | // Stop gracefully shuts down the proxy server | 229 | // Stop gracefully shuts down the proxy server | ||
| 237 | func (p *Proxy) Stop() error { | 230 | func (p *Proxy) Stop() error { | ||
| 238 | - | log.Info("Stopping reverse proxy") | 231 | + | logger.Info("Stopping reverse proxy") |
| 239 | 232 | ||||
| 240 | // Run any rate limiter cleanup functions | 233 | // Run any rate limiter cleanup functions | ||
| 241 | for _, cleanup := range rateLimiterCleanup { | 234 | for _, cleanup := range rateLimiterCleanup { | ||
| @@ -247,7 +240,7 @@ func (p *Proxy) Stop() error { | |||||
| 247 | 240 | ||||
| 248 | // Skip the rest if the server was never started | 241 | // Skip the rest if the server was never started | ||
| 249 | if !p.serverStarted { | 242 | if !p.serverStarted { | ||
| 250 | - | log.Debug("Proxy server was never started, nothing to stop") | 243 | + | logger.Debug("Proxy server was never started, nothing to stop") |
| 251 | return nil | 244 | return nil | ||
| 252 | } | 245 | } | ||
| 253 | 246 | ||||
| @@ -262,18 +255,18 @@ func (p *Proxy) Stop() error { | |||||
| 262 | // Shutdown HTTP server if it's running | 255 | // Shutdown HTTP server if it's running | ||
| 263 | if p.httpServer != nil { | 256 | if p.httpServer != nil { | ||
| 264 | if err := p.httpServer.Shutdown(ctx); err != nil { | 257 | if err := p.httpServer.Shutdown(ctx); err != nil { | ||
| 265 | - | log.Error("Failed to gracefully shutdown HTTP server", "error", err) | 258 | + | logger.Error("Failed to gracefully shutdown HTTP server", "error", err) |
| 266 | } | 259 | } | ||
| 267 | } | 260 | } | ||
| 268 | 261 | ||||
| 269 | // Shutdown HTTPS server if it's running | 262 | // Shutdown HTTPS server if it's running | ||
| 270 | if p.httpsServer != nil { | 263 | if p.httpsServer != nil { | ||
| 271 | if err := p.httpsServer.Shutdown(ctx); err != nil { | 264 | if err := p.httpsServer.Shutdown(ctx); err != nil { | ||
| 272 | - | log.Error("Failed to gracefully shutdown HTTPS server", "error", err) | 265 | + | logger.Error("Failed to gracefully shutdown HTTPS server", "error", err) |
| 273 | } | 266 | } | ||
| 274 | } | 267 | } | ||
| 275 | 268 | ||||
| 276 | - | log.Info("Reverse proxy stopped") | 269 | + | logger.Info("Reverse proxy stopped") |
| 277 | return nil | 270 | return nil | ||
| 278 | } | 271 | } | ||
| 279 | 272 | ||||
| @@ -282,7 +275,7 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||||
| 282 | adminPath := p.app.GetConfig().Admin.Path | 275 | adminPath := p.app.GetConfig().Admin.Path | ||
| 283 | url := fmt.Sprintf("http://%s:%s%s", defaultHost, port, adminPath) | 276 | url := fmt.Sprintf("http://%s:%s%s", defaultHost, port, adminPath) | ||
| 284 | 277 | ||||
| 285 | - | log.Debug("Testing admin connection", "url", url) | 278 | + | logger.Debug("Testing admin connection", "url", url) |
| 286 | 279 | ||||
| 287 | // Try connecting using the provided default host | 280 | // Try connecting using the provided default host | ||
| 288 | client := &http.Client{ | 281 | client := &http.Client{ | ||
| @@ -298,7 +291,7 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||||
| 298 | resp, err := client.Get(url) | 291 | resp, err := client.Get(url) | ||
| 299 | if err == nil { | 292 | if err == nil { | ||
| 300 | resp.Body.Close() | 293 | resp.Body.Close() | ||
| 301 | - | log.Info("Admin connection successful", "host", defaultHost) | 294 | + | logger.Info("Admin connection successful", "host", defaultHost) |
| 302 | return defaultHost | 295 | return defaultHost | ||
| 303 | } | 296 | } | ||
| 304 | 297 | ||||
| @@ -316,17 +309,17 @@ func (p *Proxy) testAdminConnection(defaultHost string, port string) string { | |||||
| 316 | } | 309 | } | ||
| 317 | 310 | ||||
| 318 | url = fmt.Sprintf("http://%s:%s%s", host, port, adminPath) | 311 | url = fmt.Sprintf("http://%s:%s%s", host, port, adminPath) | ||
| 319 | - | log.Debug("Trying alternative host", "url", url) | 312 | + | logger.Debug("Trying alternative host", "url", url) |
| 320 | 313 | ||||
| 321 | resp, err := client.Get(url) | 314 | resp, err := client.Get(url) | ||
| 322 | if err == nil { | 315 | if err == nil { | ||
| 323 | resp.Body.Close() | 316 | resp.Body.Close() | ||
| 324 | - | log.Info("Found working admin connection", "host", host) | 317 | + | logger.Info("Found working admin connection", "host", host) |
| 325 | return host | 318 | return host | ||
| 326 | } | 319 | } | ||
| 327 | } | 320 | } | ||
| 328 | 321 | ||||
| 329 | - | log.Warn("Could not establish admin connection on any tested host", "fallback", defaultHost) | 322 | + | logger.Warn("Could not establish admin connection on any tested host", "fallback", defaultHost) |
| 330 | return defaultHost | 323 | return defaultHost | ||
| 331 | } | 324 | } | ||
| 332 | 325 | ||||
| @@ -335,7 +328,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||||
| 335 | // Wait for servers to start | 328 | // Wait for servers to start | ||
| 336 | time.Sleep(2 * time.Second) | 329 | time.Sleep(2 * time.Second) | ||
| 337 | 330 | ||||
| 338 | - | log.Debug("Testing admin connections after server startup") | 331 | + | logger.Debug("Testing admin connections after server startup") |
| 339 | 332 | ||||
| 340 | // Get domain and current route | 333 | // Get domain and current route | ||
| 341 | adminDomain := p.app.GetConfig().Http.FullDomain() | 334 | adminDomain := p.app.GetConfig().Http.FullDomain() | ||
| @@ -345,7 +338,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||||
| 345 | p.mu.RUnlock() | 338 | p.mu.RUnlock() | ||
| 346 | 339 | ||||
| 347 | if !exists { | 340 | if !exists { | ||
| 348 | - | log.Warn("Admin route not found") | 341 | + | logger.Warn("Admin route not found") |
| 349 | return | 342 | return | ||
| 350 | } | 343 | } | ||
| 351 | 344 | ||||
| @@ -355,7 +348,7 @@ func (p *Proxy) TestAdminConnectionLater() { | |||||
| 355 | p.mu.Lock() | 348 | p.mu.Lock() | ||
| 356 | if r, ok := p.routes[adminDomain]; ok { | 349 | if r, ok := p.routes[adminDomain]; ok { | ||
| 357 | r.ContainerIP = testedIP | 350 | r.ContainerIP = testedIP | ||
| 358 | - | log.Info("Updated admin route with working connection", | 351 | + | logger.Info("Updated admin route with working connection", |
| 359 | "domain", adminDomain, | 352 | "domain", adminDomain, | ||
| 360 | "ip", testedIP) | 353 | "ip", testedIP) | ||
| 361 | } | 354 | } | ||
| @@ -384,10 +377,10 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 384 | httpURL := fmt.Sprintf("http://%s/.well-known/acme-challenge/test", adminDomain) | 377 | httpURL := fmt.Sprintf("http://%s/.well-known/acme-challenge/test", adminDomain) | ||
| 385 | _, httpErr := client.Get(httpURL) | 378 | _, httpErr := client.Get(httpURL) | ||
| 386 | if httpErr != nil { | 379 | if httpErr != nil { | ||
| 387 | - | log.Warn("External HTTP port 80 might not be accessible", | 380 | + | logger.Warn("External HTTP port 80 might not be accessible", |
| 388 | "error", httpErr.Error()) | 381 | "error", httpErr.Error()) | ||
| 389 | } else { | 382 | } else { | ||
| 390 | - | log.Info("External HTTP port 80 is accessible") | 383 | + | logger.Info("External HTTP port 80 is accessible") |
| 391 | } | 384 | } | ||
| 392 | 385 | ||||
| 393 | // Test HTTPS port 443 | 386 | // Test HTTPS port 443 | ||
| @@ -406,10 +399,10 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 406 | httpsURL := fmt.Sprintf("https://%s/", adminDomain) | 399 | httpsURL := fmt.Sprintf("https://%s/", adminDomain) | ||
| 407 | _, httpsErr := httpsClient.Get(httpsURL) | 400 | _, httpsErr := httpsClient.Get(httpsURL) | ||
| 408 | if httpsErr != nil { | 401 | if httpsErr != nil { | ||
| 409 | - | log.Warn("External HTTPS port 443 might not be accessible", | 402 | + | logger.Warn("External HTTPS port 443 might not be accessible", |
| 410 | "error", httpsErr.Error()) | 403 | "error", httpsErr.Error()) | ||
| 411 | } else { | 404 | } else { | ||
| 412 | - | log.Info("External HTTPS port 443 is accessible") | 405 | + | logger.Info("External HTTPS port 443 is accessible") |
| 413 | } | 406 | } | ||
| 414 | } | 407 | } | ||
| 415 | 408 | ||||
| @@ -417,7 +410,7 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 417 | // func (p *Proxy) detectGordonContainer() string { | 410 | // func (p *Proxy) detectGordonContainer() string { | ||
| 418 | // // Check if we're running inside a container | 411 | // // Check if we're running inside a container | ||
| 419 | // if docker.IsRunningInContainer() { | 412 | // if docker.IsRunningInContainer() { | ||
| 420 | - | // log.Debug("Detected we're running in a container") | 413 | + | // logger.Debug("Detected we're running in a container") |
| 421 | 414 | ||||
| 422 | // // Get our hostname which should be the container ID in Docker/Podman | 415 | // // Get our hostname which should be the container ID in Docker/Podman | ||
| 423 | // hostname := os.Getenv("HOSTNAME") | 416 | // hostname := os.Getenv("HOSTNAME") | ||
| @@ -430,12 +423,12 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 430 | // if strings.HasPrefix(hostname, container.ID) { | 423 | // if strings.HasPrefix(hostname, container.ID) { | ||
| 431 | // // This is our container | 424 | // // This is our container | ||
| 432 | // containerName := strings.TrimLeft(container.Names[0], "/") | 425 | // containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 433 | - | // log.Debug("Auto-detected Gordon container name from hostname", "container_name", containerName) | 426 | + | // logger.Debug("Auto-detected Gordon container name from hostname", "container_name", containerName) |
| 434 | // return containerName | 427 | // return containerName | ||
| 435 | // } | 428 | // } | ||
| 436 | // } | 429 | // } | ||
| 437 | // } else { | 430 | // } else { | ||
| 438 | - | // log.Debug("Could not list containers when checking hostname", "error", err) | 431 | + | // logger.Debug("Could not list containers when checking hostname", "error", err) |
| 439 | // } | 432 | // } | ||
| 440 | // } | 433 | // } | ||
| 441 | // } | 434 | // } | ||
| @@ -450,21 +443,21 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 450 | // containerLogs, err := docker.GetContainerLogs(containerID) | 443 | // containerLogs, err := docker.GetContainerLogs(containerID) | ||
| 451 | // if err == nil && (strings.Contains(containerLogs, "/gordon") || strings.Contains(containerLogs, "serve")) { | 444 | // if err == nil && (strings.Contains(containerLogs, "/gordon") || strings.Contains(containerLogs, "serve")) { | ||
| 452 | // containerName := strings.TrimLeft(container.Names[0], "/") | 445 | // containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 453 | - | // log.Debug("Auto-detected Gordon container by process", "container_name", containerName) | 446 | + | // logger.Debug("Auto-detected Gordon container by process", "container_name", containerName) |
| 454 | // return containerName | 447 | // return containerName | ||
| 455 | // } else if err != nil { | 448 | // } else if err != nil { | ||
| 456 | - | // log.Debug("Could not get container logs", "container_id", containerID, "error", err) | 449 | + | // logger.Debug("Could not get container logs", "container_id", containerID, "error", err) |
| 457 | // } | 450 | // } | ||
| 458 | 451 | ||||
| 459 | // // Check container command for Gordon | 452 | // // Check container command for Gordon | ||
| 460 | // if len(container.Command) > 0 && strings.Contains(container.Command, "gordon") { | 453 | // if len(container.Command) > 0 && strings.Contains(container.Command, "gordon") { | ||
| 461 | // containerName := strings.TrimLeft(container.Names[0], "/") | 454 | // containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 462 | - | // log.Debug("Auto-detected Gordon container by command", "container_name", containerName) | 455 | + | // logger.Debug("Auto-detected Gordon container by command", "container_name", containerName) |
| 463 | // return containerName | 456 | // return containerName | ||
| 464 | // } | 457 | // } | ||
| 465 | // } | 458 | // } | ||
| 466 | // } else { | 459 | // } else { | ||
| 467 | - | // log.Debug("Could not list containers when checking processes", "error", err) | 460 | + | // logger.Debug("Could not list containers when checking processes", "error", err) |
| 468 | // } | 461 | // } | ||
| 469 | 462 | ||||
| 470 | // // If we couldn't detect from processes, try to find a container with gordon in the name or image | 463 | // // If we couldn't detect from processes, try to find a container with gordon in the name or image | ||
| @@ -474,7 +467,7 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 474 | // for _, name := range container.Names { | 467 | // for _, name := range container.Names { | ||
| 475 | // if strings.Contains(strings.ToLower(name), "gordon") { | 468 | // if strings.Contains(strings.ToLower(name), "gordon") { | ||
| 476 | // containerName := strings.TrimLeft(name, "/") | 469 | // containerName := strings.TrimLeft(name, "/") | ||
| 477 | - | // log.Debug("Auto-detected Gordon container by name", "container_name", containerName) | 470 | + | // logger.Debug("Auto-detected Gordon container by name", "container_name", containerName) |
| 478 | // return containerName | 471 | // return containerName | ||
| 479 | // } | 472 | // } | ||
| 480 | // } | 473 | // } | ||
| @@ -482,13 +475,13 @@ func (p *Proxy) checkExternalPortAccess() { | |||||
| 482 | // // Check image name for "gordon" | 475 | // // Check image name for "gordon" | ||
| 483 | // if strings.Contains(strings.ToLower(container.Image), "gordon") { | 476 | // if strings.Contains(strings.ToLower(container.Image), "gordon") { | ||
| 484 | // containerName := strings.TrimLeft(container.Names[0], "/") | 477 | // containerName := strings.TrimLeft(container.Names[0], "/") | ||
| 485 | - | // log.Debug("Auto-detected Gordon container by image", "container_name", containerName, "image", container.Image) | 478 | + | // logger.Debug("Auto-detected Gordon container by image", "container_name", containerName, "image", container.Image) |
| 486 | // return containerName | 479 | // return containerName | ||
| 487 | // } | 480 | // } | ||
| 488 | // } | 481 | // } | ||
| 489 | // } | 482 | // } | ||
| 490 | 483 | ||||
| 491 | // // If all else fails, default to "gordon" | 484 | // // If all else fails, default to "gordon" | ||
| 492 | - | // log.Debug("Could not auto-detect Gordon container, using default name", "container_name", "gordon") | 485 | + | // logger.Debug("Could not auto-detect Gordon container, using default name", "container_name", "gordon") |
| 493 | // return "gordon" | 486 | // return "gordon" | ||
| 494 | // } | 487 | // } | ||
M internal/server/sqlite_init.go +0 -15
| @@ -69,16 +69,6 @@ func (a *App) checkDBFile(dbPath string) (bool, error) { | |||
| 69 | 69 | return true, nil | |
| 70 | 70 | } | |
| 71 | 71 | ||
| 72 | - | // runMigrations applies any necessary database migrations for existing databases | |
| 73 | - | func runMigrations(db *sql.DB) error { | |
| 74 | - | log.Info("Checking for database migrations") | |
| 75 | - | ||
| 76 | - | // No migrations needed at this time | |
| 77 | - | log.Debug("No migrations needed") | |
| 78 | - | ||
| 79 | - | return nil | |
| 80 | - | } | |
| 81 | - | ||
| 82 | 72 | // InitializeDB initializes the database | |
| 83 | 73 | func InitializeDB(a *App) (*sql.DB, error) { | |
| 84 | 74 | log.Debug("Initializing database") | |
| @@ -114,11 +104,6 @@ func InitializeDB(a *App) (*sql.DB, error) { | |||
| 114 | 104 | a.DB = db | |
| 115 | 105 | ||
| 116 | 106 | if dbExists { | |
| 117 | - | // Run migrations for existing database | |
| 118 | - | if err := runMigrations(db); err != nil { | |
| 119 | - | log.Warn("Failed to run database migrations", "error", err) | |
| 120 | - | } | |
| 121 | - | ||
| 122 | 107 | // Populate the struct tables from the database | |
| 123 | 108 | log.Debug("Populating struct tables from existing database") | |
| 124 | 109 | if err := PopulateStructWithTables(a); err != nil { | |
| @@ -69,16 +69,6 @@ func (a *App) checkDBFile(dbPath string) (bool, error) { | |||||
| 69 | return true, nil | 69 | return true, nil | ||
| 70 | } | 70 | } | ||
| 71 | 71 | ||||
| 72 | - | // runMigrations applies any necessary database migrations for existing databases | |||
| 73 | - | func runMigrations(db *sql.DB) error { | |||
| 74 | - | log.Info("Checking for database migrations") | |||
| 75 | - | ||||
| 76 | - | // No migrations needed at this time | |||
| 77 | - | log.Debug("No migrations needed") | |||
| 78 | - | ||||
| 79 | - | return nil | |||
| 80 | - | } | |||
| 81 | - | ||||
| 82 | // InitializeDB initializes the database | 72 | // InitializeDB initializes the database | ||
| 83 | func InitializeDB(a *App) (*sql.DB, error) { | 73 | func InitializeDB(a *App) (*sql.DB, error) { | ||
| 84 | log.Debug("Initializing database") | 74 | log.Debug("Initializing database") | ||
| @@ -114,11 +104,6 @@ func InitializeDB(a *App) (*sql.DB, error) { | |||||
| 114 | a.DB = db | 104 | a.DB = db | ||
| 115 | 105 | ||||
| 116 | if dbExists { | 106 | if dbExists { | ||
| 117 | - | // Run migrations for existing database | |||
| 118 | - | if err := runMigrations(db); err != nil { | |||
| 119 | - | log.Warn("Failed to run database migrations", "error", err) | |||
| 120 | - | } | |||
| 121 | - | ||||
| 122 | // Populate the struct tables from the database | 107 | // Populate the struct tables from the database | ||
| 123 | log.Debug("Populating struct tables from existing database") | 108 | log.Debug("Populating struct tables from existing database") | ||
| 124 | if err := PopulateStructWithTables(a); err != nil { | 109 | if err := PopulateStructWithTables(a); err != nil { | ||
M internal/templating/cmdparams/proxy.go +5 -4
| @@ -11,18 +11,19 @@ func SetupProxyRoute(params *docker.ContainerCommandParams, containerPort string | |||
| 11 | 11 | // Set the container port to be used by the reverse proxy | |
| 12 | 12 | params.ProxyPort = containerPort | |
| 13 | 13 | ||
| 14 | + | // Create the full domain (service.domain) | |
| 15 | + | fullDomain := params.ServiceName + "." + params.Domain | |
| 16 | + | ||
| 14 | 17 | // Add custom labels to identify this container as manageable by Gordon | |
| 15 | 18 | params.Labels = []string{ | |
| 16 | 19 | "gordon.managed=true", | |
| 17 | - | "gordon.domain=" + params.Domain, | |
| 18 | - | "gordon.service=" + params.ServiceName, | |
| 20 | + | "gordon.domain=" + fullDomain, | |
| 19 | 21 | "gordon.proxy.port=" + containerPort, | |
| 20 | 22 | "gordon.proxy.ssl=" + boolToString(params.IsSSL), | |
| 21 | 23 | } | |
| 22 | 24 | ||
| 23 | 25 | log.Debug("Container proxy configuration set up", | |
| 24 | - | "domain", params.Domain, | |
| 25 | - | "service", params.ServiceName, | |
| 26 | + | "domain", fullDomain, | |
| 26 | 27 | "proxyPort", containerPort, | |
| 27 | 28 | "ssl", params.IsSSL) | |
| 28 | 29 | ||
| @@ -11,18 +11,19 @@ func SetupProxyRoute(params *docker.ContainerCommandParams, containerPort string | |||||
| 11 | // Set the container port to be used by the reverse proxy | 11 | // Set the container port to be used by the reverse proxy | ||
| 12 | params.ProxyPort = containerPort | 12 | params.ProxyPort = containerPort | ||
| 13 | 13 | ||||
| 14 | + | // Create the full domain (service.domain) | |||
| 15 | + | fullDomain := params.ServiceName + "." + params.Domain | |||
| 16 | + | ||||
| 14 | // Add custom labels to identify this container as manageable by Gordon | 17 | // Add custom labels to identify this container as manageable by Gordon | ||
| 15 | params.Labels = []string{ | 18 | params.Labels = []string{ | ||
| 16 | "gordon.managed=true", | 19 | "gordon.managed=true", | ||
| 17 | - | "gordon.domain=" + params.Domain, | 20 | + | "gordon.domain=" + fullDomain, |
| 18 | - | "gordon.service=" + params.ServiceName, | |||
| 19 | "gordon.proxy.port=" + containerPort, | 21 | "gordon.proxy.port=" + containerPort, | ||
| 20 | "gordon.proxy.ssl=" + boolToString(params.IsSSL), | 22 | "gordon.proxy.ssl=" + boolToString(params.IsSSL), | ||
| 21 | } | 23 | } | ||
| 22 | 24 | ||||
| 23 | log.Debug("Container proxy configuration set up", | 25 | log.Debug("Container proxy configuration set up", | ||
| 24 | - | "domain", params.Domain, | 26 | + | "domain", fullDomain, |
| 25 | - | "service", params.ServiceName, | |||
| 26 | "proxyPort", containerPort, | 27 | "proxyPort", containerPort, | ||
| 27 | "ssl", params.IsSSL) | 28 | "ssl", params.IsSSL) | ||
| 28 | 29 | ||||
M pkg/docker/containers.go +10 -3
| @@ -754,7 +754,7 @@ func formatDuration(d time.Duration) string { | |||
| 754 | 754 | // when relevant container events are detected. This is used for service discovery and IP updates. | |
| 755 | 755 | // This implementation uses a polling approach instead of Docker event streams, which is more | |
| 756 | 756 | // stable for both Docker and Podman environments. | |
| 757 | - | func ListenForContainerEvents(networkFilter string, callback func(string, string, string)) error { | |
| 757 | + | func ListenForContainerEvents(networkFilter string, startCallback func(string, string, string), stopCallback func(string, string)) error { | |
| 758 | 758 | // Check if the Docker client has been initialized | |
| 759 | 759 | err := CheckIfInitialized() | |
| 760 | 760 | if err != nil { | |
| @@ -936,7 +936,7 @@ func ListenForContainerEvents(networkFilter string, callback func(string, string | |||
| 936 | 936 | "container_ip", containerIP) | |
| 937 | 937 | ||
| 938 | 938 | // Call the callback with the container ID, name, and IP | |
| 939 | - | callback(event.id, event.name, containerIP) | |
| 939 | + | startCallback(event.id, event.name, containerIP) | |
| 940 | 940 | ||
| 941 | 941 | logger.Debug("Processed container start", | |
| 942 | 942 | "container_id", event.id, | |
| @@ -947,7 +947,14 @@ func ListenForContainerEvents(networkFilter string, callback func(string, string | |||
| 947 | 947 | logger.Debug("Container stopped", | |
| 948 | 948 | "container_id", event.id, | |
| 949 | 949 | "container_name", event.name) | |
| 950 | - | // If any stop event handling is needed, add it here | |
| 950 | + | ||
| 951 | + | // Call the stop callback if provided | |
| 952 | + | if stopCallback != nil { | |
| 953 | + | logger.Debug("Calling container stop callback", | |
| 954 | + | "container_id", event.id, | |
| 955 | + | "container_name", event.name) | |
| 956 | + | stopCallback(event.id, event.name) | |
| 957 | + | } | |
| 951 | 958 | ||
| 952 | 959 | // Remove from IP tracking map when container stops | |
| 953 | 960 | delete(containerIPs, event.id) | |
| @@ -754,7 +754,7 @@ func formatDuration(d time.Duration) string { | |||||
| 754 | // when relevant container events are detected. This is used for service discovery and IP updates. | 754 | // when relevant container events are detected. This is used for service discovery and IP updates. | ||
| 755 | // This implementation uses a polling approach instead of Docker event streams, which is more | 755 | // This implementation uses a polling approach instead of Docker event streams, which is more | ||
| 756 | // stable for both Docker and Podman environments. | 756 | // stable for both Docker and Podman environments. | ||
| 757 | - | func ListenForContainerEvents(networkFilter string, callback func(string, string, string)) error { | 757 | + | func ListenForContainerEvents(networkFilter string, startCallback func(string, string, string), stopCallback func(string, string)) error { |
| 758 | // Check if the Docker client has been initialized | 758 | // Check if the Docker client has been initialized | ||
| 759 | err := CheckIfInitialized() | 759 | err := CheckIfInitialized() | ||
| 760 | if err != nil { | 760 | if err != nil { | ||
| @@ -936,7 +936,7 @@ func ListenForContainerEvents(networkFilter string, callback func(string, string | |||||
| 936 | "container_ip", containerIP) | 936 | "container_ip", containerIP) | ||
| 937 | 937 | ||||
| 938 | // Call the callback with the container ID, name, and IP | 938 | // Call the callback with the container ID, name, and IP | ||
| 939 | - | callback(event.id, event.name, containerIP) | 939 | + | startCallback(event.id, event.name, containerIP) |
| 940 | 940 | ||||
| 941 | logger.Debug("Processed container start", | 941 | logger.Debug("Processed container start", | ||
| 942 | "container_id", event.id, | 942 | "container_id", event.id, | ||
| @@ -947,7 +947,14 @@ func ListenForContainerEvents(networkFilter string, callback func(string, string | |||||
| 947 | logger.Debug("Container stopped", | 947 | logger.Debug("Container stopped", | ||
| 948 | "container_id", event.id, | 948 | "container_id", event.id, | ||
| 949 | "container_name", event.name) | 949 | "container_name", event.name) | ||
| 950 | - | // If any stop event handling is needed, add it here | 950 | + | |
| 951 | + | // Call the stop callback if provided | |||
| 952 | + | if stopCallback != nil { | |||
| 953 | + | logger.Debug("Calling container stop callback", | |||
| 954 | + | "container_id", event.id, | |||
| 955 | + | "container_name", event.name) | |||
| 956 | + | stopCallback(event.id, event.name) | |||
| 957 | + | } | |||
| 951 | 958 | ||||
| 952 | // Remove from IP tracking map when container stops | 959 | // Remove from IP tracking map when container stops | ||
| 953 | delete(containerIPs, event.id) | 960 | delete(containerIPs, event.id) | ||