diff options
| author | salmon <salmonik@tuta.io> | 2026-08-09 23:53:47 +0200 |
|---|---|---|
| committer | salmon <salmonik@tuta.io> | 2026-08-09 23:53:47 +0200 |
| commit | ce1b4011d409997b10ca205f459cedcb6b292bd7 (patch) | |
| tree | bbfd1572f0d6d3a30478bbfb613a09df0022940b | |
| parent | Make install.sh POSIX sh so it runs without bash (diff) | |
| download | rsplayer-ce1b4011d409997b10ca205f459cedcb6b292bd7.tar.gz rsplayer-ce1b4011d409997b10ca205f459cedcb6b292bd7.zip | |
Make daemon socket/PID paths environment-independent; fix rc service HOME
| -rw-r--r-- | README.md | 8 | ||||
| -rwxr-xr-x | rsplayer.rc | 39 | ||||
| -rw-r--r-- | src/config.rs | 38 | ||||
| -rw-r--r-- | src/daemon.rs | 14 |
4 files changed, 68 insertions, 31 deletions
| @@ -44,9 +44,11 @@ FreeBSD notes: | |||
| 44 | as rsplayer_user via daemon(8); `service rsplayer status`/`stop`/`restart` | 44 | as rsplayer_user via daemon(8); `service rsplayer status`/`stop`/`restart` |
| 45 | work as expected. You can also run `rsp start-daemon` manually from your | 45 | work as expected. You can also run `rsp start-daemon` manually from your |
| 46 | login shell — `start-daemon`/`stop-daemon`/`restart` behave the same. | 46 | login shell — `start-daemon`/`stop-daemon`/`restart` behave the same. |
| 47 | - XDG_RUNTIME_DIR is not set by default on FreeBSD, so the daemon socket and PID | 47 | - The daemon socket and PID file live in ~/.local/state/rsplayer/ (the XDG |
| 48 | file fall back to /tmp/rsplayer/. Set XDG_RUNTIME_DIR in your rc.d script or | 48 | state dir), not in XDG_RUNTIME_DIR: the path is identical whether the daemon |
| 49 | shell profile if you prefer a private runtime dir. | 49 | was started by the service or manually. This matters on FreeBSD, where |
| 50 | XDG_RUNTIME_DIR is often unset (previously the fallback was the | ||
| 51 | world-writable /tmp/rsplayer). | ||
| 50 | - Desktop notifications and MPRIS (playerctl etc.) require a running D-Bus session | 52 | - Desktop notifications and MPRIS (playerctl etc.) require a running D-Bus session |
| 51 | bus (`pkg install dbus`) and a notification daemon; without them the daemon runs | 53 | bus (`pkg install dbus`) and a notification daemon; without them the daemon runs |
| 52 | fine, those features are just disabled. | 54 | fine, those features are just disabled. |
diff --git a/rsplayer.rc b/rsplayer.rc index 33a46b9..706387b 100755 --- a/rsplayer.rc +++ b/rsplayer.rc | |||
| @@ -14,11 +14,10 @@ | |||
| 14 | # rsplayer_group="wheel" # group of that user | 14 | # rsplayer_group="wheel" # group of that user |
| 15 | # | 15 | # |
| 16 | # rsplayer_user must be a regular user: the daemon socket and PID file live | 16 | # rsplayer_user must be a regular user: the daemon socket and PID file live |
| 17 | # in rsplayer_runtime_dir (default /tmp/rsplayer, matching the daemon's | 17 | # in that user's XDG state directory (~/.local/state/rsplayer) and the `rsp` |
| 18 | # fallback when XDG_RUNTIME_DIR is unset) and the `rsp` CLI connects to that | 18 | # CLI connects to that socket, so the daemon and the CLI must run as the |
| 19 | # socket, so the daemon and the CLI must run as the same user. | 19 | # same user. This also means the socket path no longer depends on the |
| 20 | # | 20 | # environment (XDG_RUNTIME_DIR), so the service and manual runs agree. |
| 21 | # Optional: rsplayer_runtime_dir="/tmp/rsplayer" | ||
| 22 | 21 | ||
| 23 | # PROVIDE: rsplayer | 22 | # PROVIDE: rsplayer |
| 24 | # REQUIRE: LOGIN dbus | 23 | # REQUIRE: LOGIN dbus |
| @@ -33,11 +32,21 @@ load_rc_config $name | |||
| 33 | 32 | ||
| 34 | : ${rsplayer_user:="root"} | 33 | : ${rsplayer_user:="root"} |
| 35 | : ${rsplayer_group:="wheel"} | 34 | : ${rsplayer_group:="wheel"} |
| 36 | : ${rsplayer_runtime_dir:="/tmp/rsplayer"} | 35 | |
| 36 | # daemon(8) does not set HOME when dropping privileges, and rsp derives its | ||
| 37 | # config and runtime paths from it, so resolve the user's home explicitly. | ||
| 38 | rsplayer_home=$(pw usershow "${rsplayer_user}" 2>/dev/null | awk -F: '{print $9}') | ||
| 39 | if [ -z "${rsplayer_home}" ]; then | ||
| 40 | echo "Cannot determine home directory for user '${rsplayer_user}'." | ||
| 41 | echo "Set rsplayer_user in /etc/rc.conf." | ||
| 42 | exit 1 | ||
| 43 | fi | ||
| 37 | 44 | ||
| 38 | command="/usr/local/bin/rsp" | 45 | command="/usr/local/bin/rsp" |
| 39 | procname="${command}" | 46 | procname="${command}" |
| 40 | pidfile="${rsplayer_runtime_dir}/rsplayer.pid" | 47 | state_dir="${rsplayer_home}/.local/state/rsplayer" |
| 48 | pidfile="${state_dir}/rsplayer.pid" | ||
| 49 | socket="${state_dir}/daemon.sock" | ||
| 41 | 50 | ||
| 42 | start_cmd="rsplayer_start" | 51 | start_cmd="rsplayer_start" |
| 43 | stop_cmd="rsplayer_stop" | 52 | stop_cmd="rsplayer_stop" |
| @@ -54,19 +63,23 @@ rsplayer_start() | |||
| 54 | echo " connect to the daemon socket. Set rsplayer_user in rc.conf." | 63 | echo " connect to the daemon socket. Set rsplayer_user in rc.conf." |
| 55 | fi | 64 | fi |
| 56 | 65 | ||
| 57 | install -d -o "${rsplayer_user}" -g "${rsplayer_group}" -m 700 "${rsplayer_runtime_dir}" | 66 | echo "Starting rsplayer as ${rsplayer_user} (HOME=${rsplayer_home})." |
| 58 | echo "Starting rsplayer as ${rsplayer_user}." | 67 | /usr/bin/env HOME="${rsplayer_home}" \ |
| 59 | /usr/sbin/daemon -u "${rsplayer_user}" -o "${rsplayer_runtime_dir}/rsplayer.log" \ | 68 | /usr/sbin/daemon -u "${rsplayer_user}" -o "${state_dir}/rsplayer.log" \ |
| 60 | "${command}" start-daemon | 69 | "${command}" start-daemon |
| 61 | } | 70 | } |
| 62 | 71 | ||
| 63 | rsplayer_stop() | 72 | rsplayer_stop() |
| 64 | { | 73 | { |
| 65 | echo "Stopping rsplayer." | 74 | echo "Stopping rsplayer." |
| 66 | if ! su -m "${rsplayer_user}" -c "${command} stop-daemon"; then | 75 | if ! su -m "${rsplayer_user}" -c "env HOME='${rsplayer_home}' ${command} stop-daemon"; then |
| 67 | echo "rsplayer is not running; cleaning up stale files." | 76 | # stop-daemon could not reach the daemon (stale/missing PID file or |
| 68 | rm -f "${pidfile}" "${rsplayer_runtime_dir}/daemon.sock" | 77 | # socket, e.g. a daemon started under an older runtime dir): kill any |
| 78 | # leftover rsp start-daemon processes for this user. | ||
| 79 | echo "Graceful stop failed; killing leftover daemons." | ||
| 80 | pkill -u "${rsplayer_user}" -f "${command} start-daemon" 2>/dev/null || true | ||
| 69 | fi | 81 | fi |
| 82 | rm -f "${pidfile}" "${socket}" | ||
| 70 | } | 83 | } |
| 71 | 84 | ||
| 72 | run_rc_command "$1" | 85 | run_rc_command "$1" |
diff --git a/src/config.rs b/src/config.rs index 67cb366..f20fb3a 100644 --- a/src/config.rs +++ b/src/config.rs | |||
| @@ -303,9 +303,39 @@ impl Config { | |||
| 303 | } | 303 | } |
| 304 | 304 | ||
| 305 | pub fn get_socket_path() -> PathBuf { | 305 | pub fn get_socket_path() -> PathBuf { |
| 306 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | 306 | runtime_dir().join("daemon.sock") |
| 307 | .map(PathBuf::from) | ||
| 308 | .unwrap_or_else(|_| PathBuf::from("/tmp")); | ||
| 309 | runtime_dir.join("rsplayer").join("daemon.sock") | ||
| 310 | } | 307 | } |
| 311 | } | 308 | } |
| 309 | |||
| 310 | /// Directory holding the daemon socket and PID file. | ||
| 311 | /// | ||
| 312 | /// Uses the user's XDG state directory (~/.local/state/rsplayer) so the path | ||
| 313 | /// is identical no matter how the daemon was started (rc.d service, login | ||
| 314 | /// shell, ...). The previous XDG_RUNTIME_DIR-based path depended on the | ||
| 315 | /// environment and broke the service/CLI handshake on FreeBSD, where | ||
| 316 | /// XDG_RUNTIME_DIR is often unset (falling back to the world-writable | ||
| 317 | /// /tmp/rsplayer). | ||
| 318 | pub fn runtime_dir() -> PathBuf { | ||
| 319 | if let Some(base) = directories::BaseDirs::new() | ||
| 320 | && let Some(state) = base.state_dir() | ||
| 321 | { | ||
| 322 | return state.join("rsplayer"); | ||
| 323 | } | ||
| 324 | // Fallback when no home/state dir is resolvable | ||
| 325 | std::env::var("XDG_RUNTIME_DIR") | ||
| 326 | .map(PathBuf::from) | ||
| 327 | .unwrap_or_else(|_| PathBuf::from("/tmp")) | ||
| 328 | .join("rsplayer") | ||
| 329 | } | ||
| 330 | |||
| 331 | /// Create the runtime directory (private, 0700) and return its path. | ||
| 332 | pub fn ensure_runtime_dir() -> std::io::Result<PathBuf> { | ||
| 333 | let dir = runtime_dir(); | ||
| 334 | std::fs::create_dir_all(&dir)?; | ||
| 335 | #[cfg(unix)] | ||
| 336 | { | ||
| 337 | use std::os::unix::fs::PermissionsExt; | ||
| 338 | std::fs::set_permissions(&dir, std::fs::Permissions::from_mode(0o700))?; | ||
| 339 | } | ||
| 340 | Ok(dir) | ||
| 341 | } | ||
diff --git a/src/daemon.rs b/src/daemon.rs index 279620d..21f1a3b 100644 --- a/src/daemon.rs +++ b/src/daemon.rs | |||
| @@ -203,13 +203,8 @@ pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | |||
| 203 | std::fs::remove_file(&socket_path).ok(); | 203 | std::fs::remove_file(&socket_path).ok(); |
| 204 | } | 204 | } |
| 205 | 205 | ||
| 206 | // Create runtime directory | 206 | // Create runtime directory (private, 0700) |
| 207 | if let Some(parent) = socket_path.parent() { | 207 | crate::config::ensure_runtime_dir()?; |
| 208 | std::fs::create_dir_all(parent)?; | ||
| 209 | } | ||
| 210 | if let Some(parent) = pid_path.parent() { | ||
| 211 | std::fs::create_dir_all(parent)?; | ||
| 212 | } | ||
| 213 | 208 | ||
| 214 | // Write PID file | 209 | // Write PID file |
| 215 | let pid = std::process::id(); | 210 | let pid = std::process::id(); |
| @@ -1609,10 +1604,7 @@ fn find_audio_in_dir(dir: &Path) -> Option<Vec<std::path::PathBuf>> { | |||
| 1609 | 1604 | ||
| 1610 | /// Get the PID file path for the daemon | 1605 | /// Get the PID file path for the daemon |
| 1611 | fn get_pid_path(_config: &Config) -> std::path::PathBuf { | 1606 | fn get_pid_path(_config: &Config) -> std::path::PathBuf { |
| 1612 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | 1607 | crate::config::runtime_dir().join("rsplayer.pid") |
| 1613 | .map(std::path::PathBuf::from) | ||
| 1614 | .unwrap_or_else(|_| std::path::PathBuf::from("/tmp")); | ||
| 1615 | runtime_dir.join("rsplayer").join("rsplayer.pid") | ||
| 1616 | } | 1608 | } |
| 1617 | 1609 | ||
| 1618 | /// Check if a process with the given PID is running | 1610 | /// Check if a process with the given PID is running |
