package daemon import ( "errors" "fmt" "io/fs" "os" "path/filepath" "slices" "strings" "sync" "github.com/adrg/xdg" ) // EnvVars are variables which are derived based on the environment which the // process is running in. type EnvVars struct { RuntimeDirPath string StateDirPath string } func (e EnvVars) init() error { var errs []error if err := mkDir(e.RuntimeDirPath); err != nil { errs = append(errs, fmt.Errorf( "creating runtime directory %q: %w", e.RuntimeDirPath, err, )) } if err := mkDir(e.StateDirPath); err != nil { errs = append(errs, fmt.Errorf( "creating state directory %q: %w", e.StateDirPath, err, )) } return errors.Join(errs...) } func getDefaultHTTPSocketDirPath() string { path, err := firstExistingDir( "/tmp", // TODO it's possible the daemon process can't actually write to these "/run", "/var/run", "/dev/shm", ) if err != nil { panic(fmt.Sprintf("Failed to find directory for HTTP socket: %v", err)) } return path } // HTTPSocketPath returns the path to the daemon's HTTP socket which is used for // RPC and other functionality. var HTTPSocketPath = sync.OnceValue(func() string { return envOr( "ISLE_DAEMON_HTTP_SOCKET_PATH", func() string { return filepath.Join( getDefaultHTTPSocketDirPath(), "isle-daemon.sock", ) }, ) }) // GetEnvVars will return the EnvVars of the current processes, as determined by // the process's environment. var GetEnvVars = sync.OnceValue(func() (v EnvVars) { // RUNTIME_DIRECTORY/STATE_DIRECTORY are used by the systemd service in // conjunction with the RuntimeDirectory/StateDirectory directives. v.RuntimeDirPath = envOr( "RUNTIME_DIRECTORY", func() string { return filepath.Join(xdg.RuntimeDir, "isle") }, ) v.StateDirPath = envOr( "STATE_DIRECTORY", func() string { return filepath.Join(xdg.StateHome, "isle") }, ) return }) //////////////////////////////////////////////////////////////////////////////// // Jigs func envOr(name string, fallback func() string) string { if v := os.Getenv(name); v != "" { return v } return fallback() } func firstExistingDir(paths ...string) (string, error) { var errs []error for _, path := range paths { stat, err := os.Stat(path) switch { case errors.Is(err, fs.ErrExist): continue case err != nil: errs = append( errs, fmt.Errorf("checking if path %q exists: %w", path, err), ) case !stat.IsDir(): errs = append( errs, fmt.Errorf("path %q exists but is not a directory", path), ) default: return path, nil } } err := fmt.Errorf( "no directory found at any of the following paths: %s", strings.Join(paths, ", "), ) if len(errs) > 0 { err = errors.Join(slices.Insert(errs, 0, err)...) } return "", err }