aboutsummaryrefslogtreecommitdiffstats
path: root/crates/daemon/src/main.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-20 14:18:36 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-20 14:18:36 +0200
commit63a0ec3901a863fb07d18f4b814a98a813644382 (patch)
treec94b7b501601b5df05cf654e9f9c9b5bc66a239e /crates/daemon/src/main.rs
parentchore: Commit (diff)
downloadatuin-63a0ec3901a863fb07d18f4b814a98a813644382.zip
chore: Commit
Diffstat (limited to '')
-rw-r--r--crates/daemon/src/main.rs170
1 files changed, 18 insertions, 152 deletions
diff --git a/crates/daemon/src/main.rs b/crates/daemon/src/main.rs
index 26a5cafd..174cf94b 100644
--- a/crates/daemon/src/main.rs
+++ b/crates/daemon/src/main.rs
@@ -1,22 +1,16 @@
-#[allow(unused_imports)]
+#![expect(unused_crate_dependencies, reason = "Didn't remove them yet")]
+
use clap::Parser;
use eyre::{Result, WrapErr, bail, eyre};
use fs4::fs_std::FileExt;
use std::fs::{self, File, OpenOptions};
-use std::io::{ErrorKind, Write};
-#[cfg(unix)]
-use std::os::unix::net::UnixStream as StdUnixStream;
+use std::io::Write;
use std::path::{Path, PathBuf};
-use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use tokio::time::sleep;
use turtle_daemon::{
- DaemonEvent,
- aclient::{
- database::ClientSqlite, history::History, record::sqlite_store::SqliteStore,
- settings::Settings,
- },
- client::{ControlClient, DaemonClientErrorKind, HistoryClient, classify_error},
+ aclient::{database::ClientSqlite, record::sqlite_store::SqliteStore, settings::Settings},
+ client::{DaemonClientErrorKind, HistoryClient, classify_error},
};
#[derive(Parser, Debug)]
@@ -24,9 +18,6 @@ use turtle_daemon::{
pub(crate) enum Cmd {
/// Start the daemon server
Start {
- #[arg(long, hide = true)]
- daemonize: bool,
-
/// Also write daemon logs to the console (useful for debugging)
#[arg(long)]
show_logs: bool,
@@ -39,21 +30,6 @@ pub(crate) enum Cmd {
Stop,
}
-impl Cmd {
- pub(crate) async fn run(
- self,
- settings: Settings,
- store: SqliteStore,
- history_db: ClientSqlite,
- ) -> Result<()> {
- match self {
- Cmd::Start { .. } => run(settings, store, history_db).await,
- Cmd::Status => status_cmd(&settings).await,
- Cmd::Stop => stop_cmd(&settings).await,
- }
- }
-}
-
#[tokio::main]
async fn main() -> Result<()> {
let settings = Settings::new().wrap_err("could not load client settings")?;
@@ -63,13 +39,14 @@ async fn main() -> Result<()> {
let db = ClientSqlite::new(db_path, settings.local_timeout).await?;
let sqlite_store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
- Cmd::parse().run(settings, sqlite_store, db).await
+ match Cmd::parse() {
+ Cmd::Start { show_logs, .. } => start_cmd(settings, store, history_db, show_logs).await,
+ Cmd::Status => status_cmd(&settings).await,
+ Cmd::Stop => stop_cmd(&settings).await,
+ }
}
-const DAEMON_VERSION: &str = env!("CARGO_PKG_VERSION");
-const DAEMON_PROTOCOL_VERSION: u32 = 1;
const STARTUP_POLL: Duration = Duration::from_millis(40);
-const LOCK_POLL: Duration = Duration::from_millis(20);
const LEGACY_DAEMON_RESTART_MESSAGE: &str = "legacy daemon detected; restart daemon manually";
struct PidfileGuard {
@@ -109,18 +86,6 @@ enum Probe {
Unreachable(eyre::Report),
}
-fn daemon_matches_expected(version: &str, protocol: u32) -> bool {
- version == DAEMON_VERSION && protocol == DAEMON_PROTOCOL_VERSION
-}
-
-fn daemon_mismatch_message(version: &str, protocol: u32) -> String {
- if protocol == DAEMON_PROTOCOL_VERSION {
- format!("daemon is out of date: expected {DAEMON_VERSION}, got {version}")
- } else {
- format!("daemon protocol mismatch: expected {DAEMON_PROTOCOL_VERSION}, got {protocol}")
- }
-}
-
fn is_legacy_daemon_error(err: &eyre::Report) -> bool {
matches!(classify_error(err), DaemonClientErrorKind::Unimplemented)
}
@@ -141,6 +106,8 @@ fn open_lock_file(path: &Path) -> Result<File> {
}
async fn wait_for_lock(path: &Path, timeout: Duration) -> Result<File> {
+ const LOCK_POLL: Duration = Duration::from_millis(20);
+
let file = open_lock_file(path)?;
let start = Instant::now();
@@ -168,32 +135,6 @@ async fn wait_for_pidfile_available(path: &Path, timeout: Duration) -> Result<()
Ok(())
}
-async fn connect_client(settings: &Settings) -> Result<HistoryClient> {
- HistoryClient::new(
- #[cfg(unix)]
- settings.daemon.socket_path.clone(),
- )
- .await
-}
-
-async fn probe(settings: &Settings) -> Probe {
- let mut client = match connect_client(settings).await {
- Ok(client) => client,
- Err(err) => return Probe::Unreachable(err),
- };
-
- match client.status().await {
- Ok(status) => {
- if daemon_matches_expected(&status.version, status.protocol) {
- Probe::Ready(client)
- } else {
- Probe::NeedsRestart(daemon_mismatch_message(&status.version, status.protocol))
- }
- }
- Err(err) => Probe::Unreachable(err),
- }
-}
-
async fn request_shutdown(settings: &Settings) {
if let Ok(mut client) = connect_client(settings).await {
drop(client.shutdown().await);
@@ -204,85 +145,6 @@ fn startup_timeout(settings: &Settings) -> Duration {
Duration::from_secs_f64(settings.local_timeout.max(0.5) + 2.0)
}
-pub(crate) async fn start_history(settings: &Settings, history: History) -> Result<String> {
- match async {
- connect_client(settings)
- .await?
- .start_history(history.clone())
- .await
- }
- .await
- {
- Ok(resp) => {
- if daemon_matches_expected(&resp.version, resp.protocol) {
- return Ok(resp.id);
- }
-
- Err(eyre!(
- "{}. Restart the daemon manually",
- daemon_mismatch_message(&resp.version, resp.protocol)
- ))
- }
- Err(err) => Err(err),
- }
-}
-
-pub(crate) async fn end_history(
- settings: &Settings,
- id: String,
- duration: u64,
- exit: i64,
-) -> Result<()> {
- match async {
- connect_client(settings)
- .await?
- .end_history(id.clone(), duration, exit)
- .await
- }
- .await
- {
- Ok(resp) => {
- if daemon_matches_expected(&resp.version, resp.protocol) {
- return Ok(());
- }
-
- Err(eyre!(
- "{}. Restart the daemon manually",
- daemon_mismatch_message(&resp.version, resp.protocol)
- ))
- }
- Err(err) => Err(err),
- }
-}
-
-/// 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 {
- Ok(mut client) => {
- if let Err(e) = client.send_event(event).await {
- tracing::debug!(?e, "failed to send event to daemon");
- }
- }
- Err(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) => Ok(client),
- Probe::NeedsRestart(reason) => {
- bail!("{reason}. Restart the daemon manually");
- }
- Probe::Unreachable(err) if is_legacy_daemon_error(&err) => {
- Err(err.wrap_err(LEGACY_DAEMON_RESTART_MESSAGE))
- }
- Probe::Unreachable(err) => Err(err),
- }
-}
-
async fn status_cmd(settings: &Settings) -> Result<()> {
match probe(settings).await {
Probe::Ready(mut client) => {
@@ -292,7 +154,6 @@ async fn status_cmd(settings: &Settings) -> Result<()> {
println!(" Version: {}", status.version);
println!(" Protocol: {}", status.protocol);
println!(" Healthy: {}", status.healthy);
- #[cfg(unix)]
println!(" Socket: {}", settings.daemon.socket_path);
}
Probe::NeedsRestart(reason) => {
@@ -331,7 +192,12 @@ async fn stop_cmd(settings: &Settings) -> Result<()> {
}
}
-async fn run(settings: Settings, store: SqliteStore, history_db: ClientSqlite) -> Result<()> {
+async fn start_cmd(
+ settings: Settings,
+ store: SqliteStore,
+ history_db: ClientSqlite,
+ show_logs: bool,
+) -> Result<()> {
let pidfile_path = PathBuf::from(&settings.daemon.pidfile_path);
let _pidfile_guard = PidfileGuard::acquire(&pidfile_path)?;