aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmon <salmonik@tuta.io>2026-08-09 23:53:47 +0200
committersalmon <salmonik@tuta.io>2026-08-09 23:53:47 +0200
commitce1b4011d409997b10ca205f459cedcb6b292bd7 (patch)
treebbfd1572f0d6d3a30478bbfb613a09df0022940b
parentMake install.sh POSIX sh so it runs without bash (diff)
downloadrsplayer-ce1b4011d409997b10ca205f459cedcb6b292bd7.tar.gz
rsplayer-ce1b4011d409997b10ca205f459cedcb6b292bd7.zip
Make daemon socket/PID paths environment-independent; fix rc service HOME
-rw-r--r--README.md8
-rwxr-xr-xrsplayer.rc39
-rw-r--r--src/config.rs38
-rw-r--r--src/daemon.rs14
4 files changed, 68 insertions, 31 deletions
diff --git a/README.md b/README.md
index ef8ebaf..fbc5ffa 100644
--- a/README.md
+++ b/README.md
@@ -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.
38rsplayer_home=$(pw usershow "${rsplayer_user}" 2>/dev/null | awk -F: '{print $9}')
39if [ -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
43fi
37 44
38command="/usr/local/bin/rsp" 45command="/usr/local/bin/rsp"
39procname="${command}" 46procname="${command}"
40pidfile="${rsplayer_runtime_dir}/rsplayer.pid" 47state_dir="${rsplayer_home}/.local/state/rsplayer"
48pidfile="${state_dir}/rsplayer.pid"
49socket="${state_dir}/daemon.sock"
41 50
42start_cmd="rsplayer_start" 51start_cmd="rsplayer_start"
43stop_cmd="rsplayer_stop" 52stop_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
63rsplayer_stop() 72rsplayer_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
72run_rc_command "$1" 85run_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).
318pub 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.
332pub 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
1611fn get_pid_path(_config: &Config) -> std::path::PathBuf { 1606fn 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