fix(app) capture server refs for graceful shutdown, fix lint issues
- Capture both return values from startServer so registrySrv/proxySrv are defined for gracefulShutdown. - Tighten self-signed cert file permissions from 0644 to 0600 (gosec G302). - Extract waitForShutdown to reduce runServers cyclomatic complexity (gocyclo). - Fix pre-existing gofmt indentation on DataDir field.
- commit
- 657aee7bf8b1c86e5235622f53f10ce9e1fc8b8e
- author
- brice · · 2026-02-07 06:10 UTC
- committer
- brice · · 2026-02-07 06:11 UTC
- parent
- c3329b4
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- tag: v2.12.0
1 file · +15 -10
M internal/app/run.go +15 -10
| @@ -78,7 +78,7 @@ type Config struct { | |||
| 78 | 78 | TLSPort int `mapstructure:"tls_port"` | |
| 79 | 79 | TLSCertFile string `mapstructure:"tls_cert_file"` | |
| 80 | 80 | TLSKeyFile string `mapstructure:"tls_key_file"` | |
| 81 | - | DataDir string `mapstructure:"data_dir"` | |
| 81 | + | DataDir string `mapstructure:"data_dir"` | |
| 82 | 82 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | |
| 83 | 83 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | |
| 84 | 84 | MaxProxyResponseSize string `mapstructure:"max_proxy_response_size"` // e.g., "1GB", "0" for no limit | |
| @@ -298,7 +298,7 @@ func generateSelfSignedTLSCert(certPath, keyPath, domainName string) error { | |||
| 298 | 298 | return err | |
| 299 | 299 | } | |
| 300 | 300 | ||
| 301 | - | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | |
| 301 | + | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) | |
| 302 | 302 | if err != nil { | |
| 303 | 303 | return err | |
| 304 | 304 | } | |
| @@ -1277,8 +1277,8 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||
| 1277 | 1277 | ||
| 1278 | 1278 | errChan := make(chan error, 3) | |
| 1279 | 1279 | ||
| 1280 | - | registryReady := startServer(fmt.Sprintf(":%d", cfg.Server.RegistryPort), registryHandler, "registry", errChan, log) | |
| 1281 | - | proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", errChan, log) | |
| 1280 | + | registrySrv, registryReady := startServer(fmt.Sprintf(":%d", cfg.Server.RegistryPort), registryHandler, "registry", errChan, log) | |
| 1281 | + | proxySrv, proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", errChan, log) | |
| 1282 | 1282 | var tlsReady <-chan struct{} | |
| 1283 | 1283 | if cfg.Server.TLSEnabled { | |
| 1284 | 1284 | if cfg.Server.TLSCertFile == "" || cfg.Server.TLSKeyFile == "" { | |
| @@ -1326,10 +1326,19 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||
| 1326 | 1326 | // Auto-start after servers are listening (registry port is now bound). | |
| 1327 | 1327 | syncAndAutoStart(ctx, svc, log) | |
| 1328 | 1328 | ||
| 1329 | + | waitForShutdown(ctx, errChan, reloadChan, deployChan, eventBus, log) | |
| 1330 | + | gracefulShutdown(registrySrv, proxySrv, containerSvc, log) | |
| 1331 | + | return nil | |
| 1332 | + | } | |
| 1333 | + | ||
| 1334 | + | // waitForShutdown blocks on the event loop, handling server errors and | |
| 1335 | + | // Unix signals (reload, deploy, shutdown) until the context is cancelled. | |
| 1336 | + | func waitForShutdown(ctx context.Context, errChan <-chan error, reloadChan, deployChan <-chan os.Signal, eventBus out.EventBus, log zerowrap.Logger) { | |
| 1329 | 1337 | for { | |
| 1330 | 1338 | select { | |
| 1331 | 1339 | case err := <-errChan: | |
| 1332 | - | return err | |
| 1340 | + | log.Error().Err(err).Msg("server error") | |
| 1341 | + | return | |
| 1333 | 1342 | case <-reloadChan: | |
| 1334 | 1343 | log.Info().Msg("reload signal received (SIGUSR1)") | |
| 1335 | 1344 | if err := eventBus.Publish(domain.EventManualReload, nil); err != nil { | |
| @@ -1348,13 +1357,9 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||
| 1348 | 1357 | } | |
| 1349 | 1358 | case <-ctx.Done(): | |
| 1350 | 1359 | log.Info().Msg("shutdown signal received") | |
| 1351 | - | goto shutdown | |
| 1360 | + | return | |
| 1352 | 1361 | } | |
| 1353 | 1362 | } | |
| 1354 | - | ||
| 1355 | - | shutdown: | |
| 1356 | - | gracefulShutdown(registrySrv, proxySrv, containerSvc, log) | |
| 1357 | - | return nil | |
| 1358 | 1363 | } | |
| 1359 | 1364 | ||
| 1360 | 1365 | // gracefulShutdown stops HTTP servers with a 30s timeout, then shuts down | |
| @@ -78,7 +78,7 @@ type Config struct { | |||||
| 78 | TLSPort int `mapstructure:"tls_port"` | 78 | TLSPort int `mapstructure:"tls_port"` | ||
| 79 | TLSCertFile string `mapstructure:"tls_cert_file"` | 79 | TLSCertFile string `mapstructure:"tls_cert_file"` | ||
| 80 | TLSKeyFile string `mapstructure:"tls_key_file"` | 80 | TLSKeyFile string `mapstructure:"tls_key_file"` | ||
| 81 | - | DataDir string `mapstructure:"data_dir"` | 81 | + | DataDir string `mapstructure:"data_dir"` |
| 82 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | 82 | MaxProxyBodySize string `mapstructure:"max_proxy_body_size"` // e.g., "512MB", "1GB" | ||
| 83 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | 83 | MaxBlobChunkSize string `mapstructure:"max_blob_chunk_size"` // e.g., "512MB", "1GB" | ||
| 84 | MaxProxyResponseSize string `mapstructure:"max_proxy_response_size"` // e.g., "1GB", "0" for no limit | 84 | MaxProxyResponseSize string `mapstructure:"max_proxy_response_size"` // e.g., "1GB", "0" for no limit | ||
| @@ -298,7 +298,7 @@ func generateSelfSignedTLSCert(certPath, keyPath, domainName string) error { | |||||
| 298 | return err | 298 | return err | ||
| 299 | } | 299 | } | ||
| 300 | 300 | ||||
| 301 | - | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) | 301 | + | certOut, err := os.OpenFile(certPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
| 302 | if err != nil { | 302 | if err != nil { | ||
| 303 | return err | 303 | return err | ||
| 304 | } | 304 | } | ||
| @@ -1277,8 +1277,8 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||||
| 1277 | 1277 | ||||
| 1278 | errChan := make(chan error, 3) | 1278 | errChan := make(chan error, 3) | ||
| 1279 | 1279 | ||||
| 1280 | - | registryReady := startServer(fmt.Sprintf(":%d", cfg.Server.RegistryPort), registryHandler, "registry", errChan, log) | 1280 | + | registrySrv, registryReady := startServer(fmt.Sprintf(":%d", cfg.Server.RegistryPort), registryHandler, "registry", errChan, log) |
| 1281 | - | proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", errChan, log) | 1281 | + | proxySrv, proxyReady := startServer(fmt.Sprintf(":%d", cfg.Server.Port), proxyHandler, "proxy", errChan, log) |
| 1282 | var tlsReady <-chan struct{} | 1282 | var tlsReady <-chan struct{} | ||
| 1283 | if cfg.Server.TLSEnabled { | 1283 | if cfg.Server.TLSEnabled { | ||
| 1284 | if cfg.Server.TLSCertFile == "" || cfg.Server.TLSKeyFile == "" { | 1284 | if cfg.Server.TLSCertFile == "" || cfg.Server.TLSKeyFile == "" { | ||
| @@ -1326,10 +1326,19 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||||
| 1326 | // Auto-start after servers are listening (registry port is now bound). | 1326 | // Auto-start after servers are listening (registry port is now bound). | ||
| 1327 | syncAndAutoStart(ctx, svc, log) | 1327 | syncAndAutoStart(ctx, svc, log) | ||
| 1328 | 1328 | ||||
| 1329 | + | waitForShutdown(ctx, errChan, reloadChan, deployChan, eventBus, log) | |||
| 1330 | + | gracefulShutdown(registrySrv, proxySrv, containerSvc, log) | |||
| 1331 | + | return nil | |||
| 1332 | + | } | |||
| 1333 | + | ||||
| 1334 | + | // waitForShutdown blocks on the event loop, handling server errors and | |||
| 1335 | + | // Unix signals (reload, deploy, shutdown) until the context is cancelled. | |||
| 1336 | + | func waitForShutdown(ctx context.Context, errChan <-chan error, reloadChan, deployChan <-chan os.Signal, eventBus out.EventBus, log zerowrap.Logger) { | |||
| 1329 | for { | 1337 | for { | ||
| 1330 | select { | 1338 | select { | ||
| 1331 | case err := <-errChan: | 1339 | case err := <-errChan: | ||
| 1332 | - | return err | 1340 | + | log.Error().Err(err).Msg("server error") |
| 1341 | + | return | |||
| 1333 | case <-reloadChan: | 1342 | case <-reloadChan: | ||
| 1334 | log.Info().Msg("reload signal received (SIGUSR1)") | 1343 | log.Info().Msg("reload signal received (SIGUSR1)") | ||
| 1335 | if err := eventBus.Publish(domain.EventManualReload, nil); err != nil { | 1344 | if err := eventBus.Publish(domain.EventManualReload, nil); err != nil { | ||
| @@ -1348,13 +1357,9 @@ func runServers(ctx context.Context, cfg Config, registryHandler, proxyHandler h | |||||
| 1348 | } | 1357 | } | ||
| 1349 | case <-ctx.Done(): | 1358 | case <-ctx.Done(): | ||
| 1350 | log.Info().Msg("shutdown signal received") | 1359 | log.Info().Msg("shutdown signal received") | ||
| 1351 | - | goto shutdown | 1360 | + | return |
| 1352 | } | 1361 | } | ||
| 1353 | } | 1362 | } | ||
| 1354 | - | ||||
| 1355 | - | shutdown: | |||
| 1356 | - | gracefulShutdown(registrySrv, proxySrv, containerSvc, log) | |||
| 1357 | - | return nil | |||
| 1358 | } | 1363 | } | ||
| 1359 | 1364 | ||||
| 1360 | // gracefulShutdown stops HTTP servers with a 30s timeout, then shuts down | 1365 | // gracefulShutdown stops HTTP servers with a 30s timeout, then shuts down | ||