aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-13 15:42:59 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-13 15:42:59 +0200
commit717a68c9b135b13219002166480f0c06bcb37d7c (patch)
treea3bf474cf81f978558b960096477c12a7d37b6d4
parentfix(sqlite): Ensure that database migration runs sequentially (diff)
downloadatuin-717a68c9b135b13219002166480f0c06bcb37d7c.zip
chore(daemon): Remove the `autostart` feature
A service manager should deal with that.
-rw-r--r--crates/turtle/src/atuin_client/settings.rs7
-rw-r--r--crates/turtle/src/command/client/daemon.rs183
-rw-r--r--crates/turtle/src/command/client/history.rs11
-rw-r--r--crates/turtle/src/command/client/search/engines/daemon.rs30
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),
};