aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 17:11:20 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-03 17:11:20 +0300
commit5ed262a43ac7f264a1a7523b6d4507ba4a976604 (patch)
tree8de14a54cf67bb8ee656c3536035f68c7703a01a /src
parentf159ef69ffe59fd327a4a4f3c4a53aaea567b32a (diff)
fix: isolate debug runtime paths and block duplicate herdr instances
Diffstat (limited to 'src')
-rw-r--r--src/api/mod.rs42
-rw-r--r--src/config.rs26
-rw-r--r--src/main.rs23
-rw-r--r--src/persist.rs8
4 files changed, 66 insertions, 33 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs
index 2e7692a4..1a713323 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -71,19 +71,17 @@ pub fn socket_path() -> PathBuf {
return PathBuf::from(path);
}
- if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
- return PathBuf::from(dir).join("herdr.sock");
- }
-
- if let Ok(dir) = std::env::var("XDG_CONFIG_HOME") {
- return PathBuf::from(dir).join("herdr/herdr.sock");
- }
+ let socket_name = if crate::config::app_dir_name() == "herdr" {
+ "herdr.sock"
+ } else {
+ "herdr-dev.sock"
+ };
- if let Ok(home) = std::env::var("HOME") {
- return PathBuf::from(home).join(".config/herdr/herdr.sock");
+ if let Ok(dir) = std::env::var("XDG_RUNTIME_DIR") {
+ return PathBuf::from(dir).join(socket_name);
}
- PathBuf::from("/tmp/herdr.sock")
+ crate::config::config_dir().join("herdr.sock")
}
pub struct ServerHandle {
@@ -143,6 +141,30 @@ fn prepare_socket_path(path: &Path) -> std::io::Result<()> {
fs::create_dir_all(parent)?;
}
+ if !path.exists() {
+ return Ok(());
+ }
+
+ match UnixStream::connect(path) {
+ Ok(_) => {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::AddrInUse,
+ format!(
+ "herdr is already running (socket busy at {})",
+ path.display()
+ ),
+ ));
+ }
+ Err(err)
+ if matches!(
+ err.kind(),
+ std::io::ErrorKind::ConnectionRefused
+ | std::io::ErrorKind::NotFound
+ | std::io::ErrorKind::TimedOut
+ ) => {}
+ Err(err) => return Err(err),
+ }
+
if let Err(err) = fs::remove_file(path) {
if err.kind() != std::io::ErrorKind::NotFound {
return Err(err);
diff --git a/src/config.rs b/src/config.rs
index c901c2d6..d5417421 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -6,6 +6,24 @@ use serde::Deserialize;
pub const CONFIG_PATH_ENV_VAR: &str = "HERDR_CONFIG_PATH";
use tracing::warn;
+pub fn app_dir_name() -> &'static str {
+ if cfg!(debug_assertions) {
+ "herdr-dev"
+ } else {
+ "herdr"
+ }
+}
+
+pub fn config_dir() -> PathBuf {
+ if let Ok(dir) = std::env::var("XDG_CONFIG_HOME") {
+ PathBuf::from(dir).join(app_dir_name())
+ } else if let Ok(home) = std::env::var("HOME") {
+ PathBuf::from(home).join(format!(".config/{}", app_dir_name()))
+ } else {
+ PathBuf::from(format!("/tmp/{}", app_dir_name()))
+ }
+}
+
use crate::detect::Agent;
#[derive(Debug, Clone, Deserialize)]
@@ -705,13 +723,7 @@ pub fn config_path() -> PathBuf {
if let Ok(path) = std::env::var(CONFIG_PATH_ENV_VAR) {
return PathBuf::from(path);
}
- if let Ok(dir) = std::env::var("XDG_CONFIG_HOME") {
- PathBuf::from(dir).join("herdr/config.toml")
- } else if let Ok(home) = std::env::var("HOME") {
- PathBuf::from(home).join(".config/herdr/config.toml")
- } else {
- PathBuf::from("/tmp/herdr/config.toml")
- }
+ config_dir().join("config.toml")
}
fn upsert_top_level_bool(content: &str, key: &str, value: bool) -> String {
diff --git a/src/main.rs b/src/main.rs
index 385f6f5f..ee72e49b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,13 +43,7 @@ fn init_logging() {
use std::fs::{self, OpenOptions};
use tracing_subscriber::EnvFilter;
- let log_dir = if let Ok(dir) = std::env::var("XDG_CONFIG_HOME") {
- std::path::PathBuf::from(dir).join("herdr")
- } else if let Ok(home) = std::env::var("HOME") {
- std::path::PathBuf::from(home).join(".config/herdr")
- } else {
- std::path::PathBuf::from("/tmp/herdr")
- };
+ let log_dir = crate::config::config_dir();
let _ = fs::create_dir_all(&log_dir);
let log_path = log_dir.join("herdr.log");
@@ -218,7 +212,10 @@ fn main() -> io::Result<()> {
println!(" --help, -h Show this help");
println!();
println!("Config: {}", config::config_path().display());
- println!("Logs: ~/.config/herdr/herdr.log");
+ println!(
+ "Logs: {}",
+ config::config_dir().join("herdr.log").display()
+ );
println!("Env: HERDR_CONFIG_PATH overrides config file path");
println!("Home: https://herdr.dev");
return Ok(());
@@ -272,7 +269,15 @@ fn main() -> io::Result<()> {
let (api_tx, api_rx) = tokio::sync::mpsc::unbounded_channel();
let event_hub = api::EventHub::default();
- let _api_server = api::start_server(api_tx, event_hub.clone())?;
+ let _api_server = match api::start_server(api_tx, event_hub.clone()) {
+ Ok(server) => server,
+ Err(err) if err.kind() == io::ErrorKind::AddrInUse => {
+ eprintln!("error: herdr is already running");
+ eprintln!("socket: {}", api::socket_path().display());
+ std::process::exit(1);
+ }
+ Err(err) => return Err(err),
+ };
let no_session = std::env::args().any(|a| a == "--no-session");
let show_changelog = std::env::args().any(|a| a == "--show-changelog");
diff --git a/src/persist.rs b/src/persist.rs
index c296b0ac..fb8ab2d8 100644
--- a/src/persist.rs
+++ b/src/persist.rs
@@ -467,13 +467,7 @@ fn collect_ids_inner(node: &Node, ids: &mut Vec<PaneId>) {
// --- File I/O ---
fn session_path() -> PathBuf {
- if let Ok(dir) = std::env::var("XDG_CONFIG_HOME") {
- PathBuf::from(dir).join("herdr/session.json")
- } else if let Ok(home) = std::env::var("HOME") {
- PathBuf::from(home).join(".config/herdr/session.json")
- } else {
- PathBuf::from("/tmp/herdr/session.json")
- }
+ crate::config::config_dir().join("session.json")
}
pub fn save(snapshot: &SessionSnapshot) {