aboutsummaryrefslogtreecommitdiffstats
path: root/crates/daemon/src/lib.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 21:43:23 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 21:43:23 +0200
commit3223d93cb3c77ab02aa0a35f2a8314e447cee9a4 (patch)
tree3971a1f37f5fe3cadb747c5229a0d54e868e45e3 /crates/daemon/src/lib.rs
parentfix(client/sync): Pass through precise error on `SyncError::WrongKey` (diff)
downloadatuin-3223d93cb3c77ab02aa0a35f2a8314e447cee9a4.zip
chore: Separate daemon, client, server, and lib into crates
Diffstat (limited to 'crates/daemon/src/lib.rs')
-rw-r--r--crates/daemon/src/lib.rs124
1 files changed, 124 insertions, 0 deletions
diff --git a/crates/daemon/src/lib.rs b/crates/daemon/src/lib.rs
new file mode 100644
index 00000000..5f0f489e
--- /dev/null
+++ b/crates/daemon/src/lib.rs
@@ -0,0 +1,124 @@
+use crate::atuin_client::database::ClientSqlite as HistoryDatabase;
+use crate::atuin_client::record::sqlite_store::SqliteStore;
+use crate::atuin_client::settings::{Settings, watcher::global_settings_watcher};
+use eyre::Result;
+
+pub(crate) mod client;
+pub(crate) mod components;
+pub(crate) mod control;
+pub(crate) mod daemon;
+pub(crate) mod events;
+pub(crate) mod search;
+pub(crate) mod server;
+
+pub(crate) mod generated;
+
+// Re-export core daemon types for convenience
+pub(crate) use daemon::Daemon;
+pub(crate) use events::DaemonEvent;
+
+// Re-export components
+pub(crate) use components::{HistoryComponent, SearchComponent, SemanticComponent, SyncComponent};
+
+// Re-export client helpers
+pub(crate) use client::SemanticClient;
+
+/// Boot the daemon using the new component-based architecture.
+///
+/// This creates a daemon with the standard components (history, search, sync),
+/// starts the gRPC server with their services, and runs the event loop.
+pub(crate) async fn boot(
+ settings: Settings,
+ store: SqliteStore,
+ history_db: HistoryDatabase,
+) -> Result<()> {
+ // Create the components
+ let history_component = HistoryComponent::new();
+ let search_component = SearchComponent::new();
+ let semantic_component = SemanticComponent::new();
+ let sync_component = SyncComponent::new();
+
+ // Get the gRPC services before moving components into the daemon
+ // (The services share state with the components via Arc)
+ let history_service = history_component.grpc_service();
+ let search_service = search_component.grpc_service();
+ let semantic_service = semantic_component.grpc_service();
+
+ // Build the daemon
+ let mut daemon = Daemon::builder(settings.clone())
+ .store(store)
+ .history_db(history_db)
+ .component(history_component)
+ .component(search_component)
+ .component(semantic_component)
+ .component(sync_component)
+ .build()?;
+
+ // Get a handle for the control service and gRPC server shutdown
+ let handle = daemon.handle();
+
+ // Create the control service
+ let control_service = control::ControlService::new(handle.clone());
+
+ // Start all components first (so gRPC services can work)
+ daemon.start_components().await?;
+
+ // Spawn config file watcher to reload settings on changes
+ if let Ok(watcher) = global_settings_watcher() {
+ let mut settings_rx = watcher.subscribe();
+ let watcher_handle = handle.clone();
+ tokio::spawn(async move {
+ tracing::info!("config file watcher started");
+ while settings_rx.changed().await.is_ok() {
+ // Use the already-loaded settings from the watcher
+ // (avoids parsing the config file twice)
+ let new_settings = (*settings_rx.borrow()).clone();
+ watcher_handle.apply_settings((*new_settings).clone()).await;
+ }
+ tracing::debug!("config file watcher stopped");
+ });
+ } else {
+ tracing::warn!(
+ "failed to start config file watcher; settings changes will require daemon restart"
+ );
+ }
+
+ // Spawn signal handler to emit ShutdownRequested on Ctrl+C/SIGTERM
+ let signal_handle = handle.clone();
+ tokio::spawn(async move {
+ shutdown_signal().await;
+ tracing::info!("received shutdown signal");
+ signal_handle.shutdown();
+ });
+
+ server::run_grpc_server(
+ &settings,
+ history_service,
+ search_service,
+ semantic_service,
+ control_service.into_server(),
+ handle,
+ )?;
+
+ daemon.run_event_loop().await?;
+
+ // After the event loop exited, we shut-down the components.
+ daemon.stop_components().await;
+
+ tracing::info!("daemon shut down complete");
+ Ok(())
+}
+
+/// Wait for a shutdown signal (Ctrl+C or SIGTERM).
+#[cfg(unix)]
+async fn shutdown_signal() {
+ let mut term = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
+ .expect("failed to register sigterm handler");
+ let mut int = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
+ .expect("failed to register sigint handler");
+
+ tokio::select! {
+ _ = term.recv() => {},
+ _ = int.recv() => {},
+ }
+}