From a442f26e39223c62b14273944ede06b00b3b1fdd Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 8 Jun 2026 09:20:34 -0400 Subject: [PATCH] feat(server): store runtime socket in per-user runtime directory Moves the Crush socket out of the shared temp folder and into the per-user runtime directory on Linux and macOS, falling back to the temp folder when needed. Guards against socket paths that are too long to bind. Co-Authored-By: Charm Crush --- internal/server/server.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/internal/server/server.go b/internal/server/server.go index 7c51dab2adf7d8c715743c214d3489900c1636e3..5a154059719b8381a338817873425e5d91b94d9c 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -7,7 +7,9 @@ import ( "net" "net/http" "net/url" + "os" "os/user" + "path/filepath" "runtime" "strings" @@ -17,6 +19,23 @@ import ( httpswagger "github.com/swaggo/http-swagger/v2" ) +// maxUnixSocketPathLen is the maximum length of a Unix domain socket +// path. The macOS sun_path field is 104 bytes; Linux allows 108. We +// use 104 so the resulting path is portable across both platforms. +const maxUnixSocketPathLen = 104 + +// socketDir returns the directory used for the Crush Unix socket. +// It prefers $XDG_RUNTIME_DIR when set (systemd's per-user runtime +// directory on Linux), and otherwise falls back to [os.TempDir], +// which resolves to the per-user private $TMPDIR on macOS and to +// /tmp on Linux. +func socketDir() string { + if dir := os.Getenv("XDG_RUNTIME_DIR"); dir != "" { + return dir + } + return os.TempDir() +} + // ErrServerClosed is returned when the server is closed. var ErrServerClosed = http.ErrServerClosed @@ -44,6 +63,14 @@ func ParseHostURL(host string) (*url.URL, error) { } // DefaultHost returns the default server host. +// +// On Windows the address is a named pipe under \\.\pipe\. On Unix +// platforms the socket lives in the per-user runtime directory +// returned by [socketDir] and is named crush-.sock, falling +// back to crush.sock when the current uid cannot be determined. If +// the composed path would exceed [maxUnixSocketPathLen] bytes (the +// macOS sun_path limit), we fall back to /tmp/crush-.sock so +// the socket remains bindable. func DefaultHost() string { sock := "crush.sock" usr, err := user.Current() @@ -53,7 +80,11 @@ func DefaultHost() string { if runtime.GOOS == "windows" { return fmt.Sprintf("npipe:////./pipe/%s", sock) } - return fmt.Sprintf("unix:///tmp/%s", sock) + path := filepath.Join(socketDir(), sock) + if len(path) > maxUnixSocketPathLen { + path = filepath.Join("/tmp", sock) + } + return "unix://" + path } // Server represents a Crush server bound to a specific address.