diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-13 15:42:59 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2026-06-13 15:42:59 +0200 |
| commit | 717a68c9b135b13219002166480f0c06bcb37d7c (patch) | |
| tree | a3bf474cf81f978558b960096477c12a7d37b6d4 | |
| parent | fix(sqlite): Ensure that database migration runs sequentially (diff) | |
| download | atuin-717a68c9b135b13219002166480f0c06bcb37d7c.zip | |
chore(daemon): Remove the `autostart` feature
A service manager should deal with that.
| -rw-r--r-- | crates/turtle/src/atuin_client/settings.rs | 7 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/daemon.rs | 183 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/history.rs | 11 | ||||
| -rw-r--r-- | crates/turtle/src/command/client/search/engines/daemon.rs | 30 |
4 files changed, 24 insertions, 207 deletions
diff --git a/crates/turtle/src/atuin_client/settings.rs b/crates/turtle/src/atuin_client/settings.rs index c02221eb..b32a04e0 100644 --- a/crates/turtle/src/atuin_client/settings.rs +++ b/crates/turtle/src/atuin_client/settings.rs @@ -388,9 +388,6 @@ pub(crate) struct Preview { #[derive(Clone, Debug, Deserialize, Serialize)] pub(crate) struct Daemon { - /// Automatically start and manage a local daemon when needed. - pub(crate) autostart: bool, - /// The daemon will handle sync on an interval. How often to sync, in seconds. pub(crate) sync_frequency: u64, @@ -504,7 +501,6 @@ impl Default for Preview { impl Default for Daemon { fn default() -> Self { Self { - autostart: false, sync_frequency: 300, socket_path: String::new(), pidfile_path: String::new(), @@ -1051,7 +1047,6 @@ impl Settings { .set_default("command_chaining", false)? .set_default("store_failed", true)? .set_default("daemon.sync_frequency", 300)? - .set_default("daemon.autostart", false)? .set_default("daemon.socket_path", socket_path.to_str())? .set_default("daemon.pidfile_path", pidfile_path.to_str())? .set_default("daemon.systemd_socket", false)? @@ -1447,7 +1442,6 @@ mod tests { let meta_db_path: String = config.get("meta.db_path")?; let daemon_socket_path: String = config.get("daemon.socket_path")?; let daemon_pidfile_path: String = config.get("daemon.pidfile_path")?; - let daemon_autostart: bool = config.get("daemon.autostart")?; assert_eq!(db_path, custom_dir.join("history.db").to_str().unwrap()); assert_eq!(key_path, custom_dir.join("key").to_str().unwrap()); @@ -1472,7 +1466,6 @@ mod tests { daemon_pidfile_path, custom_dir.join("atuin-daemon.pid").to_str().unwrap() ); - assert!(!daemon_autostart); Ok(()) } diff --git a/crates/turtle/src/command/client/daemon.rs b/crates/turtle/src/command/client/daemon.rs index 4960dacd..30b478ec 100644 --- a/crates/turtle/src/command/client/daemon.rs +++ b/crates/turtle/src/command/client/daemon.rs @@ -146,21 +146,6 @@ fn is_legacy_daemon_error(err: &eyre::Report) -> bool { matches!(classify_error(err), DaemonClientErrorKind::Unimplemented) } -fn should_retry_after_error(err: &eyre::Report) -> bool { - matches!( - classify_error(err), - DaemonClientErrorKind::Connect - | DaemonClientErrorKind::Unavailable - | DaemonClientErrorKind::Unimplemented - ) -} - -fn daemon_startup_lock_path(pidfile_path: &Path) -> PathBuf { - let mut os = pidfile_path.as_os_str().to_os_string(); - os.push(".startup.lock"); - PathBuf::from(os) -} - fn open_lock_file(path: &Path) -> Result<File> { if let Some(parent) = path.parent() { fs::create_dir_all(parent) @@ -317,78 +302,6 @@ async fn wait_until_ready(settings: &Settings, timeout: Duration) -> Result<Hist } } -fn ensure_autostart_supported(settings: &Settings) -> Result<()> { - #[cfg(unix)] - if settings.daemon.systemd_socket { - bail!( - "daemon autostart is incompatible with `daemon.systemd_socket = true`; use systemd to manage the daemon" - ); - } - - Ok(()) -} - -/// Ensure the daemon is running, starting it if necessary. -/// -/// If the daemon is already running and up-to-date, this is a no-op. -/// If it is not running or needs a restart, this will spawn a new daemon -/// process and wait for it to become ready. -/// -/// Returns an error if the daemon could not be started. -pub(crate) async fn ensure_daemon_running(settings: &Settings) -> Result<()> { - ensure_autostart_supported(settings)?; - - let timeout = startup_timeout(settings); - let pidfile_path = PathBuf::from(&settings.daemon.pidfile_path); - let startup_lock_path = daemon_startup_lock_path(&pidfile_path); - let startup_lock = wait_for_lock(&startup_lock_path, timeout).await?; - - match probe(settings).await { - Probe::Ready(_) => { - drop(startup_lock); - return Ok(()); - } - Probe::NeedsRestart(_) => { - request_shutdown(settings).await; - } - Probe::Unreachable(err) => { - if is_legacy_daemon_error(&err) { - return Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE)); - } - } - } - - // This prevents rapid-fire hook invocations from racing daemon restart. - wait_for_pidfile_available(&pidfile_path, timeout).await?; - - #[cfg(unix)] - remove_stale_socket_if_present(settings)?; - - spawn_daemon_process()?; - drop(wait_until_ready(settings, timeout).await?); - - drop(startup_lock); - Ok(()) -} - -async fn restart_daemon(settings: &Settings) -> Result<HistoryClient> { - ensure_daemon_running(settings).await?; - connect_client(settings).await -} - -fn ensure_reply_compatible(settings: &Settings, version: &str, protocol: u32) -> Result<()> { - if daemon_matches_expected(version, protocol) { - return Ok(()); - } - - let message = daemon_mismatch_message(version, protocol); - if settings.daemon.autostart { - bail!("{message}"); - } - - bail!("{message}. Enable `daemon.autostart = true` or restart the daemon manually"); -} - pub(crate) async fn start_history(settings: &Settings, history: History) -> Result<String> { match async { connect_client(settings) @@ -403,24 +316,13 @@ pub(crate) async fn start_history(settings: &Settings, history: History) -> Resu return Ok(resp.id); } - if !settings.daemon.autostart { - return Err(eyre!( - "{}. Enable `daemon.autostart = true` or restart the daemon manually", - daemon_mismatch_message(&resp.version, resp.protocol) - )); - } + Err(eyre!( + "{}. Restart the daemon manually", + daemon_mismatch_message(&resp.version, resp.protocol) + )) } - Err(err) if !settings.daemon.autostart => return Err(err), - Err(err) if !should_retry_after_error(&err) => return Err(err), - Err(_) => {} + Err(err) => Err(err), } - - let resp = restart_daemon(settings) - .await? - .start_history(history) - .await?; - ensure_reply_compatible(settings, &resp.version, resp.protocol)?; - Ok(resp.id) } pub(crate) async fn end_history( @@ -442,36 +344,16 @@ pub(crate) async fn end_history( return Ok(()); } - if !settings.daemon.autostart { - return Err(eyre!( - "{}. Enable `daemon.autostart = true` or restart the daemon manually", - daemon_mismatch_message(&resp.version, resp.protocol) - )); - } - - // End succeeded on the running daemon, so avoid replaying it. - // We only restart to make subsequent hook calls target the expected version. - drop(restart_daemon(settings).await); - return Ok(()); + Err(eyre!( + "{}. Restart the daemon manually", + daemon_mismatch_message(&resp.version, resp.protocol) + )) } - Err(err) if !settings.daemon.autostart => return Err(err), - Err(err) if !should_retry_after_error(&err) => return Err(err), - Err(_) => {} + Err(err) => Err(err), } - - let resp = restart_daemon(settings) - .await? - .end_history(id, duration, exit) - .await?; - ensure_reply_compatible(settings, &resp.version, resp.protocol)?; - Ok(()) } -/// Emit a daemon event, auto-starting the daemon if it is not running. -/// -/// If the daemon is not reachable and `daemon.autostart` is enabled, this -/// will start the daemon and retry the event. If the daemon cannot be -/// started or the retry fails, a warning is printed to stderr. +/// Emit a daemon event. pub(crate) async fn emit_event(settings: &Settings, event: DaemonEvent) { // Try to connect and send match ControlClient::from_settings(settings).await { @@ -479,48 +361,24 @@ pub(crate) async fn emit_event(settings: &Settings, event: DaemonEvent) { if let Err(e) = client.send_event(event).await { tracing::debug!(?e, "failed to send event to daemon"); } - return; - } - Err(e) if !settings.daemon.autostart || !should_retry_after_error(&e) => { - tracing::debug!(?e, "daemon not available, skipping event emission"); - return; - } - Err(_) => {} - } - - // Auto-start the daemon and retry - if let Err(e) = ensure_daemon_running(settings).await { - eprintln!("Could not start daemon: {e}"); - return; - } - - match ControlClient::from_settings(settings).await { - Ok(mut client) => { - if let Err(e) = client.send_event(event).await { - eprintln!("Daemon started but failed to send event: {e}"); - } } Err(e) => { - eprintln!("Daemon started but failed to connect: {e}"); + tracing::debug!(?e, "daemon not available, skipping event emission"); } } } pub(crate) async fn tail_client(settings: &Settings) -> Result<HistoryClient> { match probe(settings).await { - Probe::Ready(client) => return Ok(client), - Probe::NeedsRestart(reason) if !settings.daemon.autostart => { - bail!("{reason}. Enable `daemon.autostart = true` or restart the daemon manually"); + Probe::Ready(client) => Ok(client), + Probe::NeedsRestart(reason) => { + bail!("{reason}. Restart the daemon manually"); } Probe::Unreachable(err) if is_legacy_daemon_error(&err) => { - return Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE)); + Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE)) } - Probe::Unreachable(err) if !settings.daemon.autostart => return Err(err), - Probe::Unreachable(err) if !should_retry_after_error(&err) => return Err(err), - Probe::NeedsRestart(_) | Probe::Unreachable(_) => {} + Probe::Unreachable(err) => Err(err), } - - restart_daemon(settings).await } async fn status_cmd(settings: &Settings) -> Result<()> { @@ -720,11 +578,4 @@ mod tests { let msg = daemon_mismatch_message(DAEMON_VERSION, 999); assert!(msg.contains("protocol mismatch"), "got: {msg}"); } - - #[test] - fn test_startup_lock_path() { - let pidfile = Path::new("/tmp/atuin-daemon.pid"); - let lock = daemon_startup_lock_path(pidfile); - assert_eq!(lock, PathBuf::from("/tmp/atuin-daemon.pid.startup.lock")); - } } diff --git a/crates/turtle/src/command/client/history.rs b/crates/turtle/src/command/client/history.rs index fcc622d7..fde73449 100644 --- a/crates/turtle/src/command/client/history.rs +++ b/crates/turtle/src/command/client/history.rs @@ -5,12 +5,14 @@ use std::{ time::Duration, }; -use crate::atuin_common::utils::{self, Escapable as _}; +use crate::{ + atuin_common::utils::{self, Escapable as _}, + command::client::daemon, +}; use clap::Subcommand; use eyre::{Context, Result, bail}; use runtime_format::{FormatKey, FormatKeyError, ParseSegment, ParsedFmt}; -use super::daemon as daemon_cmd; use colored::Colorize; use serde::Serialize; @@ -30,7 +32,6 @@ use crate::atuin_client::{ use log::debug; use time::{OffsetDateTime, macros::format_description}; -use super::daemon; use super::search::format_duration_into; #[derive(Subcommand, Debug)] @@ -855,7 +856,7 @@ impl Cmd { history_store.incremental_build(db, &[id]).await?; } - daemon_cmd::emit_event(settings, crate::atuin_daemon::DaemonEvent::HistoryPruned).await; + daemon::emit_event(settings, crate::atuin_daemon::DaemonEvent::HistoryPruned).await; } Ok(()) } @@ -910,7 +911,7 @@ impl Cmd { history_store.incremental_build(db, &[id]).await?; } - daemon_cmd::emit_event( + daemon::emit_event( settings, crate::atuin_daemon::DaemonEvent::HistoryDeleted { ids }, ) diff --git a/crates/turtle/src/command/client/search/engines/daemon.rs b/crates/turtle/src/command/client/search/engines/daemon.rs index cb0fdf7d..ee92ebaf 100644 --- a/crates/turtle/src/command/client/search/engines/daemon.rs +++ b/crates/turtle/src/command/client/search/engines/daemon.rs @@ -3,7 +3,7 @@ use crate::atuin_client::{ history::History, settings::{SearchMode, Settings}, }; -use crate::atuin_daemon::client::{DaemonClientErrorKind, SearchClient, classify_error}; +use crate::atuin_daemon::client::SearchClient; use async_trait::async_trait; use atuin_nucleo_matcher::{ Config, Matcher, Utf32Str, @@ -14,12 +14,10 @@ use tracing::{Level, debug, instrument, span}; use uuid::Uuid; use super::{SearchEngine, SearchState}; -use crate::command::client::daemon; pub(crate) struct Search { client: Option<SearchClient>, query_id: u64, - settings: Settings, #[cfg(unix)] socket_path: String, } @@ -29,7 +27,6 @@ impl Search { Self { client: None, query_id: 0, - settings: settings.clone(), #[cfg(unix)] socket_path: settings.daemon.socket_path.clone(), } @@ -51,15 +48,6 @@ impl Search { Ok(()) } - fn should_retry(err: &eyre::Report) -> bool { - matches!( - classify_error(err), - DaemonClientErrorKind::Connect - | DaemonClientErrorKind::Unavailable - | DaemonClientErrorKind::Unimplemented - ) - } - fn next_query_id(&mut self) -> u64 { self.query_id += 1; self.query_id @@ -142,22 +130,6 @@ impl SearchEngine for Search { let mut stream = match first_attempt { Ok(stream) => stream, - Err(err) if self.settings.daemon.autostart && Self::should_retry(&err) => { - debug!("daemon not available, attempting auto-start"); - self.client = None; - - daemon::ensure_daemon_running(&self.settings).await?; - - let client = self.get_client().await?; - client - .search( - query.clone(), - query_id, - state.filter_mode, - Some(state.context.clone()), - ) - .await? - } Err(err) => return Err(err), }; |
