aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/atuin_daemon/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/turtle/src/atuin_daemon/mod.rs')
-rw-r--r--crates/turtle/src/atuin_daemon/mod.rs128
1 files changed, 128 insertions, 0 deletions
diff --git a/crates/turtle/src/atuin_daemon/mod.rs b/crates/turtle/src/atuin_daemon/mod.rs
new file mode 100644
index 00000000..b05eb95c
--- /dev/null
+++ b/crates/turtle/src/atuin_daemon/mod.rs
@@ -0,0 +1,128 @@
+use crate::atuin_client::database::Sqlite as HistoryDatabase;
+use crate::atuin_client::record::sqlite_store::SqliteStore;
+use crate::atuin_client::settings::{Settings, watcher::global_settings_watcher};
+use eyre::Result;
+
+pub mod client;
+pub mod components;
+pub mod control;
+pub mod daemon;
+pub mod events;
+pub mod history;
+pub mod search;
+pub mod semantic;
+pub mod server;
+
+// Re-export core daemon types for convenience
+pub use daemon::{Component, Daemon, DaemonBuilder, DaemonHandle};
+pub use events::DaemonEvent;
+
+// Re-export components
+pub use components::{HistoryComponent, SearchComponent, SemanticComponent, SyncComponent};
+
+// Re-export client helpers
+pub use client::{ControlClient, SemanticClient, emit_event, emit_event_with_settings};
+
+/// 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 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()
+ .await?;
+
+ // 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();
+ });
+
+ // Start the gRPC server in the background
+ server::run_grpc_server(
+ settings,
+ history_service,
+ search_service,
+ semantic_service,
+ control_service.into_server(),
+ handle,
+ )
+ .await?;
+
+ // Run the daemon event loop
+ daemon.run_event_loop().await?;
+
+ // Stop all components on shutdown
+ 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() => {},
+ }
+}