on main help sign in
refactor(server): derive per-host cache dir from parsed host URL
dd1be0eChristian Rocha
internal/cmd/root.go+18−10
@@ -426,7 +426,7 @@ func ensureServer(cmd *cobra.Command, hostURL *url.URL) error {
426// duration of "spawn + readiness probe" and released before the caller
426// duration of "spawn + readiness probe" and released before the caller
427// resumes its normal lifetime.
427// resumes its normal lifetime.
428func spawnAndWaitReady(cmd *cobra.Command, hostURL *url.URL) error {
428func spawnAndWaitReady(cmd *cobra.Command, hostURL *url.URL) error {
429− chDir, err := perHostServerDir()
429+ chDir, err := perHostServerDir(hostURL)
430 if err != nil {
430 if err != nil {
431 return err
431 return err
432 }
432 }
@@ -435,7 +435,7 @@ func spawnAndWaitReady(cmd *cobra.Command, hostURL *url.URL) error {
435 // If the lock itself is unavailable, fall back to the
435 // If the lock itself is unavailable, fall back to the
436 // unsynchronized path rather than blocking the user.
436 // unsynchronized path rather than blocking the user.
437 slog.Warn("Failed to acquire spawn lock, proceeding without single-flight", "error", err)
437 slog.Warn("Failed to acquire spawn lock, proceeding without single-flight", "error", err)
438− if err := startDetachedServer(cmd); err != nil {
438+ if err := startDetachedServer(cmd, hostURL); err != nil {
439 return err
439 return err
440 }
440 }
441 return waitForServerReady(cmd.Context(), hostURL)
441 return waitForServerReady(cmd.Context(), hostURL)
@@ -452,7 +452,7 @@ func spawnAndWaitReady(cmd *cobra.Command, hostURL *url.URL) error {
452 return nil
452 return nil
453 }
453 }
454
454
455− if err := startDetachedServer(cmd); err != nil {
455+ if err := startDetachedServer(cmd, hostURL); err != nil {
456 return err
456 return err
457 }
457 }
458 return waitForServerReady(cmd.Context(), hostURL)
458 return waitForServerReady(cmd.Context(), hostURL)
@@ -469,17 +469,25 @@ func quickHealthProbe(ctx context.Context, hostURL *url.URL) error {
469}
469}
470
470
471// perHostServerDir returns (and creates) the cache directory used for
471// perHostServerDir returns (and creates) the cache directory used for
472−// per-host server state (logs, start.lock, etc.). It mirrors the path
472+// per-host server state (logs, start.lock, etc.). The path is derived
473−// computed in startDetachedServer so both code paths stay in sync.
473+// from the parsed host URL rather than the global flag so the same key
474+// is computed regardless of where the host came from.
474−func perHostServerDir() (string, error) {
475+func perHostServerDir(hostURL *url.URL) (string, error) {
475− safeClientHost := safeNameRegexp.ReplaceAllString(clientHost, "_")
476− chDir := filepath.Join(config.GlobalCacheDir(), "server-"+safeClientHost)
476+ chDir := filepath.Join(config.GlobalCacheDir(), "server-"+safeHostName(hostURL))
477 if err := os.MkdirAll(chDir, 0o700); err != nil {
477 if err := os.MkdirAll(chDir, 0o700); err != nil {
478 return "", fmt.Errorf("failed to create server working directory: %v", err)
478 return "", fmt.Errorf("failed to create server working directory: %v", err)
479 }
479 }
480 return chDir, nil
480 return chDir, nil
481}
481}
482
482
483+// safeHostName returns a filesystem-safe identifier for hostURL,
484+// suitable for use as a directory name. It mirrors the input shape of
485+// the --host flag so client and server compute the same key.
486+func safeHostName(hostURL *url.URL) string {
487+ return safeNameRegexp.ReplaceAllString(
488+ hostURL.Scheme+"://"+hostURL.Host+hostURL.Path, "_")
489+}
490+
483// serverReadyTimeout returns the total budget for the readiness probe.
491// serverReadyTimeout returns the total budget for the readiness probe.
484// Overridable via CRUSH_SERVER_READY_TIMEOUT (parsed as a Go duration).
492// Overridable via CRUSH_SERVER_READY_TIMEOUT (parsed as a Go duration).
485func serverReadyTimeout() time.Duration {
493func serverReadyTimeout() time.Duration {
@@ -631,13 +639,13 @@ func restartIfStale(cmd *cobra.Command, hostURL *url.URL) (restarted bool, err e
631
639
632var safeNameRegexp = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
640var safeNameRegexp = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
633
641
634−func startDetachedServer(cmd *cobra.Command) error {
642+func startDetachedServer(cmd *cobra.Command, hostURL *url.URL) error {
635 exe, err := os.Executable()
643 exe, err := os.Executable()
636 if err != nil {
644 if err != nil {
637 return fmt.Errorf("failed to get executable path: %v", err)
645 return fmt.Errorf("failed to get executable path: %v", err)
638 }
646 }
639
647
640− chDir, err := perHostServerDir()
648+ chDir, err := perHostServerDir(hostURL)
641 if err != nil {
649 if err != nil {
642 return err
650 return err
643 }
651 }
internal/cmd/server.go+6−6
@@ -42,7 +42,12 @@ var serverCmd = &cobra.Command{
42 return fmt.Errorf("failed to load configuration: %v", err)
42 return fmt.Errorf("failed to load configuration: %v", err)
43 }
43 }
44
44
45+ hostURL, err := server.ParseHostURL(serverHost)
46+ if err != nil {
47+ return fmt.Errorf("invalid server host: %v", err)
48+ }
49+
45− logFile := filepath.Join(config.GlobalCacheDir(), "server-"+safeNameRegexp.ReplaceAllString(serverHost, "_"), "crush.log")
50+ logFile := filepath.Join(config.GlobalCacheDir(), "server-"+safeHostName(hostURL), "crush.log")
46
51
47 if term.IsTerminal(os.Stderr.Fd()) {
52 if term.IsTerminal(os.Stderr.Fd()) {
48 crushlog.Setup(logFile, debug, os.Stderr)
53 crushlog.Setup(logFile, debug, os.Stderr)
@@ -50,11 +55,6 @@ var serverCmd = &cobra.Command{
50 crushlog.Setup(logFile, debug)
55 crushlog.Setup(logFile, debug)
51 }
56 }
52
57
53− hostURL, err := server.ParseHostURL(serverHost)
54− if err != nil {
55− return fmt.Errorf("invalid server host: %v", err)
56− }
57−
58 srv := server.NewServer(cfg, hostURL.Scheme, hostURL.Host)
58 srv := server.NewServer(cfg, hostURL.Scheme, hostURL.Host)
59 srv.SetLogger(slog.Default())
59 srv.SetLogger(slog.Default())
60 slog.Info("Starting Crush server...", "addr", serverHost)
60 slog.Info("Starting Crush server...", "addr", serverHost)