1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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(crate) mod client;
pub(crate) mod components;
pub(crate) mod control;
pub(crate) mod daemon;
pub(crate) mod events;
pub(crate) mod history;
pub(crate) mod search;
pub(crate) mod semantic;
pub(crate) mod server;
// 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()
.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() => {},
}
}
|