on main help sign in
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 <crush@charm.land>

commita442f26e39223c62b14273944ede06b00b3b1fdd authorChristian Rocha <christian@rocha.is> dateMon 8 Jun 2026, 09:20 (4 months ago) parent1839102
1 file changed+32−1
internal/server/server.go+32−1
internal/server/server.go+32−1
@@ -7,7 +7,9 @@ import (
77 "net"
88 "net/http"
99 "net/url"
10+ "os"
1011 "os/user"
12+ "path/filepath"
1113 "runtime"
1214 "strings"
1315
@@ -17,6 +19,23 @@ import (
1719 httpswagger "github.com/swaggo/http-swagger/v2"
1820)
1921
22+// maxUnixSocketPathLen is the maximum length of a Unix domain socket
23+// path. The macOS sun_path field is 104 bytes; Linux allows 108. We
24+// use 104 so the resulting path is portable across both platforms.
25+const maxUnixSocketPathLen = 104
26+
27+// socketDir returns the directory used for the Crush Unix socket.
28+// It prefers $XDG_RUNTIME_DIR when set (systemd's per-user runtime
29+// directory on Linux), and otherwise falls back to [os.TempDir],
30+// which resolves to the per-user private $TMPDIR on macOS and to
31+// /tmp on Linux.
32+func socketDir() string {
33+ if dir := os.Getenv("XDG_RUNTIME_DIR"); dir != "" {
34+ return dir
35+ }
36+ return os.TempDir()
37+}
38+
2039// ErrServerClosed is returned when the server is closed.
2140var ErrServerClosed = http.ErrServerClosed
2241
@@ -44,6 +63,14 @@ func ParseHostURL(host string) (*url.URL, error) {
4463}
4564
4665// DefaultHost returns the default server host.
66+//
67+// On Windows the address is a named pipe under \\.\pipe\. On Unix
68+// platforms the socket lives in the per-user runtime directory
69+// returned by [socketDir] and is named crush-<uid>.sock, falling
70+// back to crush.sock when the current uid cannot be determined. If
71+// the composed path would exceed [maxUnixSocketPathLen] bytes (the
72+// macOS sun_path limit), we fall back to /tmp/crush-<uid>.sock so
73+// the socket remains bindable.
4774func DefaultHost() string {
4875 sock := "crush.sock"
4976 usr, err := user.Current()
@@ -53,7 +80,11 @@ func DefaultHost() string {
5380 if runtime.GOOS == "windows" {
5481 return fmt.Sprintf("npipe:////./pipe/%s", sock)
5582 }
56− return fmt.Sprintf("unix:///tmp/%s", sock)
83+ path := filepath.Join(socketDir(), sock)
84+ if len(path) > maxUnixSocketPathLen {
85+ path = filepath.Join("/tmp", sock)
86+ }
87+ return "unix://" + path
5788}
5889
5990// Server represents a Crush server bound to a specific address.