From afb5820a4d55e1c41883c61d446c361d77ffaaaa Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 14 Jun 2026 18:44:04 +0200 Subject: [PATCH] Support Unix Sockets (#6721) --- agent/rpc/dial.go | 14 +++++++++++ cmd/agent/core/flags.go | 4 ++-- cmd/server/flags.go | 4 ++-- cmd/server/grpc_server.go | 16 ++++++++++++- cmd/server/server.go | 24 +++++++++++++++++-- .../10-configuration/10-server.md | 5 ++-- .../10-configuration/30-agent.md | 4 ++-- 7 files changed, 60 insertions(+), 11 deletions(-) diff --git a/agent/rpc/dial.go b/agent/rpc/dial.go index 04f8d2e13..df30ad46b 100644 --- a/agent/rpc/dial.go +++ b/agent/rpc/dial.go @@ -18,6 +18,9 @@ import ( "context" "crypto/tls" "fmt" + "os" + "path/filepath" + "strings" "time" "google.golang.org/grpc" @@ -80,6 +83,17 @@ func Dial(authCtx context.Context, cfg DialConfig) (*AgentConn, error) { Timeout: cfg.KeepaliveTimeout, }) + if strings.HasPrefix(cfg.ServerAddr, "unix://") { + addr, _ := filepath.Abs(strings.TrimPrefix(cfg.ServerAddr, "unix://")) + if _, err := os.Stat(addr); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("can not connect to unix socket, %q not exist", addr) + } + return nil, fmt.Errorf("can not get unix socket stat: %w", err) + } + cfg.ServerAddr = "unix://" + addr + } + authConn, err := grpc.NewClient(cfg.ServerAddr, transport, keepaliveOpts) if err != nil { return nil, fmt.Errorf("create auth gRPC connection: %w", err) diff --git a/cmd/agent/core/flags.go b/cmd/agent/core/flags.go index a875609b8..d880f073f 100644 --- a/cmd/agent/core/flags.go +++ b/cmd/agent/core/flags.go @@ -27,7 +27,7 @@ var flags = []cli.Flag{ &cli.StringFlag{ Sources: cli.EnvVars("WOODPECKER_SERVER"), Name: "server", - Usage: "server address", + Usage: "server grpc address, supports unix socket via unix:// prefix", Value: "localhost:9000", }, &cli.StringFlag{ @@ -44,7 +44,7 @@ var flags = []cli.Flag{ &cli.BoolFlag{ Sources: cli.EnvVars("WOODPECKER_GRPC_SECURE"), Name: "grpc-secure", - Usage: "should the connection to WOODPECKER_SERVER be made using a secure transport", + Usage: "should the connection to WOODPECKER_SERVER be made using a secure transport (tls)", }, &cli.BoolFlag{ Sources: cli.EnvVars("WOODPECKER_GRPC_VERIFY"), diff --git a/cmd/server/flags.go b/cmd/server/flags.go index 9e4e5c35c..efad75d47 100644 --- a/cmd/server/flags.go +++ b/cmd/server/flags.go @@ -71,7 +71,7 @@ var flags = append([]cli.Flag{ &cli.StringFlag{ Sources: cli.EnvVars("WOODPECKER_SERVER_ADDR"), Name: "server-addr", - Usage: "server address", + Usage: "configures the HTTP listener, supports unix socket via unix:// prefix", Value: ":8000", }, &cli.StringFlag{ @@ -108,7 +108,7 @@ var flags = append([]cli.Flag{ &cli.StringFlag{ Sources: cli.EnvVars("WOODPECKER_GRPC_ADDR"), Name: "grpc-addr", - Usage: "grpc address", + Usage: "grpc socket server opens, by default on all IPs via port 9000, use unix:// prefix for unix socket", Value: ":9000", }, &cli.StringFlag{ diff --git a/cmd/server/grpc_server.go b/cmd/server/grpc_server.go index 2430e4b79..017d27d82 100644 --- a/cmd/server/grpc_server.go +++ b/cmd/server/grpc_server.go @@ -18,6 +18,9 @@ import ( "context" "fmt" "net" + "os" + "path/filepath" + "strings" "github.com/prometheus/client_golang/prometheus" "github.com/urfave/cli/v3" @@ -28,7 +31,18 @@ import ( ) func runGrpcServer(ctx context.Context, c *cli.Command, _store store.Store) error { - lis, err := net.Listen("tcp", c.String("grpc-addr")) + network := "tcp" + addr := c.String("grpc-addr") + + if strings.HasPrefix(addr, "unix://") { + network = "unix" + addr, _ = filepath.Abs(strings.TrimPrefix(addr, "unix://")) + if _, err := os.Stat(filepath.Dir(addr)); os.IsNotExist(err) { + return fmt.Errorf("can not listen to unix socket, parent folder %q not exist", filepath.Dir(addr)) + } + } + + lis, err := net.Listen(network, addr) if err != nil { return fmt.Errorf("failed to listen on grpc-addr: %w", err) } diff --git a/cmd/server/server.go b/cmd/server/server.go index 647450c7d..bbd5588ff 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -19,9 +19,12 @@ import ( "crypto/tls" "errors" "fmt" + "net" "net/http" "net/http/httputil" "net/url" + "os" + "path/filepath" "strings" "time" @@ -246,8 +249,25 @@ func run(ctx context.Context, c *cli.Command) error { } else { // start the server without tls serviceWaitingGroup.Go(func() error { + network := "tcp" + addr := c.String("server-addr") + if strings.HasPrefix(addr, "unix://") { + network = "unix" + addr, _ = filepath.Abs(strings.TrimPrefix(addr, "unix://")) + if _, err := os.Stat(filepath.Dir(addr)); os.IsNotExist(err) { + err = fmt.Errorf("can not listen to unix socket, parent folder %q not exist", filepath.Dir(addr)) + stopServerFunc(err) + return err + } + } + lis, err := net.Listen(network, addr) + if err != nil { + err = fmt.Errorf("could not start web listener: %w", err) + stopServerFunc(err) + return err + } + httpServer := &http.Server{ - Addr: c.String("server-addr"), Handler: handler, } @@ -262,7 +282,7 @@ func run(ctx context.Context, c *cli.Command) error { }() log.Info().Msg("starting http server ...") - if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + if err := httpServer.Serve(lis); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Error().Err(err).Msg("http server failed") stopServerFunc(fmt.Errorf("http server failed: %w", err)) } diff --git a/docs/docs/30-administration/10-configuration/10-server.md b/docs/docs/30-administration/10-configuration/10-server.md index ef91052be..4cd8ce347 100644 --- a/docs/docs/30-administration/10-configuration/10-server.md +++ b/docs/docs/30-administration/10-configuration/10-server.md @@ -558,7 +558,7 @@ Examples: - Name: `WOODPECKER_SERVER_ADDR` - Default: `:8000` -Configures the HTTP listener port. +Configures the HTTP listener, supports unix socket via unix:// prefix". --- @@ -624,7 +624,8 @@ Example: `WOODPECKER_CUSTOM_JS_FILE=/usr/local/www/woodpecker.js` - Name: `WOODPECKER_GRPC_ADDR` - Default: `:9000` -Configures the gRPC listener port. +Configures the gRPC listener. Use `localhost:9000` or any IP address to bind it to a specific interface. +If you want an unix socket use `unix://` prefix, for example `unix:///run/woodpecker-grcp.sock`. --- diff --git a/docs/docs/30-administration/10-configuration/30-agent.md b/docs/docs/30-administration/10-configuration/30-agent.md index 6c4d2bdb8..b16c361e1 100644 --- a/docs/docs/30-administration/10-configuration/30-agent.md +++ b/docs/docs/30-administration/10-configuration/30-agent.md @@ -63,7 +63,7 @@ To get an _agent token_ you have to register the agent manually in the server us - Name: `WOODPECKER_SERVER` - Default: `localhost:9000` -Configures gRPC address of the server. +Configures gRPC address to the server. If you want to use an unix socket add `unix://` prefix and the path. --- @@ -215,7 +215,7 @@ After pinging for a keepalive check, the agent waits for a duration of this time - Name: `WOODPECKER_GRPC_SECURE` - Default: `false` -Configures if the connection to `WOODPECKER_SERVER` should be made using a secure transport. +Configures if the connection to `WOODPECKER_SERVER` should be made using a secure transport (tls). ---