diff --git a/backend/go.mod b/backend/go.mod index fd535ed1..06f12696 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -10,6 +10,7 @@ require ( github.com/aws/smithy-go v1.25.1 github.com/caarlos0/env/v11 v11.4.1 github.com/cenkalti/backoff/v5 v5.0.3 + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec github.com/disintegration/imaging v1.6.2 github.com/dunglas/go-urlpattern v0.0.0-20241020164140-716dfa1c80b1 diff --git a/backend/go.sum b/backend/go.sum index 39cbd15f..917097ed 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -65,6 +65,8 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/backend/internal/bootstrap/router_bootstrap.go b/backend/internal/bootstrap/router_bootstrap.go index f8e40b0d..96a8eabb 100644 --- a/backend/internal/bootstrap/router_bootstrap.go +++ b/backend/internal/bootstrap/router_bootstrap.go @@ -15,6 +15,7 @@ import ( "sync/atomic" "time" + "github.com/coreos/go-systemd/activation" "github.com/fsnotify/fsnotify" sloggin "github.com/gin-contrib/slog" "github.com/gin-gonic/gin" @@ -163,24 +164,25 @@ func initServer(r *gin.Engine) (*serverConfig, error) { return nil, err } - network, addr := listenerNetworkAndAddr() - listener, err := net.Listen(network, addr) //nolint:noctx - if err != nil { - return nil, fmt.Errorf("failed to create %s listener: %w", network, err) + var socketFn func() (*socket, error) + if common.EnvConfig.SystemdSocket { + socketFn = systemdSocket + } else if common.EnvConfig.UnixSocket != "" { + socketFn = unixSocket + } else { + socketFn = tcpSocket } - if err := setUnixSocketMode(network, addr); err != nil { - listener.Close() + socket, err := socketFn() + if err != nil { return nil, err } - return &serverConfig{ - addr: addr, - certProvider: certProvider, - listener: listener, - server: newHTTPServer(r, protocols), - tlsConfig: tlsConfig, - }, nil + addr := socket.addr + listener := socket.listener + server := newHTTPServer(r, protocols) + + return &serverConfig{addr, certProvider, listener, server, tlsConfig}, nil } func initServerProtocols() (*http.Protocols, *tls.Config, *tlsCertProvider, error) { @@ -208,6 +210,64 @@ func initServerProtocols() (*http.Protocols, *tls.Config, *tlsCertProvider, erro return protocols, tlsConfig, certProvider, nil } +type socket struct { + addr string + listener net.Listener +} + +func systemdSocket() (*socket, error) { + listeners, err := activation.Listeners() + if err != nil { + return nil, fmt.Errorf("failed to receive socket from systemd: %w", err) + } + + if len(listeners) == 0 { + return nil, errors.New("did not receive any sockets from systemd") + } + + if len(listeners) > 1 { + return nil, errors.New("received too many sockets from systemd") + } + + return &socket{"(systemd)", listeners[0]}, nil +} + +func unixSocket() (*socket, error) { + addr := common.EnvConfig.UnixSocket + os.Remove(addr) // remove dangling the socket file to avoid file-exist error + + listener, err := net.Listen("unix", addr) //nolint:noctx + if err != nil { + return nil, fmt.Errorf("failed to create UNIX socket: %w", err) + } + + if common.EnvConfig.UnixSocketMode != "" { + mode, err := strconv.ParseUint(common.EnvConfig.UnixSocketMode, 8, 32) + if err != nil { + listener.Close() + return nil, fmt.Errorf("failed to parse UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err) + } + + if err := os.Chmod(addr, os.FileMode(mode)); err != nil { + listener.Close() + return nil, fmt.Errorf("failed to set UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err) + } + } + + return &socket{addr, listener}, nil +} + +func tcpSocket() (*socket, error) { + addr := net.JoinHostPort(common.EnvConfig.Host, common.EnvConfig.Port) + + listener, err := net.Listen("tcp", addr) //nolint:noctx + if err != nil { + return nil, fmt.Errorf("failed to create TCP socket: %w", err) + } + + return &socket{addr, listener}, nil +} + func newHTTPServer(r *gin.Engine, protocols *http.Protocols) *http.Server { return &http.Server{ MaxHeaderBytes: 1 << 20, @@ -227,33 +287,6 @@ func newHTTPServer(r *gin.Engine, protocols *http.Protocols) *http.Server { } } -func listenerNetworkAndAddr() (string, string) { - if common.EnvConfig.UnixSocket == "" { - return "tcp", net.JoinHostPort(common.EnvConfig.Host, common.EnvConfig.Port) - } - - addr := common.EnvConfig.UnixSocket - os.Remove(addr) // remove dangling the socket file to avoid file-exist error - return "unix", addr -} - -func setUnixSocketMode(network, addr string) error { - if network != "unix" || common.EnvConfig.UnixSocketMode == "" { - return nil - } - - mode, err := strconv.ParseUint(common.EnvConfig.UnixSocketMode, 8, 32) - if err != nil { - return fmt.Errorf("failed to parse UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err) - } - - if err := os.Chmod(addr, os.FileMode(mode)); err != nil { - return fmt.Errorf("failed to set UNIX socket mode '%s': %w", common.EnvConfig.UnixSocketMode, err) - } - - return nil -} - func runServer(ctx context.Context, config *serverConfig) error { slog.Info("Server listening", slog.String("addr", config.addr), slog.Bool("tls", config.tlsConfig != nil)) diff --git a/backend/internal/common/env_config.go b/backend/internal/common/env_config.go index 82a421f4..fa4f3f0c 100644 --- a/backend/internal/common/env_config.go +++ b/backend/internal/common/env_config.go @@ -68,6 +68,7 @@ type EnvConfigSchema struct { Host string `env:"HOST" options:"toLower"` UnixSocket string `env:"UNIX_SOCKET"` UnixSocketMode string `env:"UNIX_SOCKET_MODE"` + SystemdSocket bool `env:"SYSTEMD_SOCKET"` LocalIPv6Ranges string `env:"LOCAL_IPV6_RANGES"` TLSCertFile string `env:"TLS_CERT" options:"file"` @@ -153,6 +154,9 @@ func ValidateEnvConfig(config *EnvConfigSchema) error { if err := validateFileBackend(config); err != nil { return err } + if config.SystemdSocket && config.UnixSocket != "" { + return errors.New("SYSTEMD_SOCKET and UNIX_SOCKET are mutually exclusive") + } if err := validateLocalIPv6Ranges(config.LocalIPv6Ranges); err != nil { return err }