1use std::sync::Arc;
8
9use ijima_core::{IjimaError, Result, Store};
10
11use crate::IjimaAuth;
12use crate::api;
13use crate::key_store;
14
15#[derive(Debug, Clone)]
17pub struct DaemonConfig {
18 pub host: String,
20 pub port: u16,
22}
23
24pub fn init_tracing() {
27 use tracing_subscriber::EnvFilter;
28 let _ = tracing_subscriber::fmt()
29 .with_env_filter(
30 EnvFilter::try_from_env("IJIMA_LOG").unwrap_or_else(|_| EnvFilter::new("ijima=info")),
31 )
32 .try_init();
33}
34
35impl Default for DaemonConfig {
36 fn default() -> Self {
37 Self {
38 host: std::env::var("IJIMA_HOST").unwrap_or_else(|_| "127.0.0.1".into()),
39 port: std::env::var("IJIMA_PORT")
40 .ok()
41 .and_then(|p| p.parse().ok())
42 .unwrap_or(7373),
43 }
44 }
45}
46
47pub async fn serve(config: &DaemonConfig) -> Result<()> {
55 init_tracing();
56 let key_path = key_store::default_key_path()?;
57 let seed = key_store::load_or_create(&key_path)?;
58 let auth = Arc::new(IjimaAuth::from_embedded_policy_with_seed(seed)?);
59
60 #[cfg(feature = "embeddings-candle")]
61 let embedder: Option<Arc<dyn ijima_core::Embedder>> = {
62 let e: Arc<dyn ijima_core::Embedder> =
63 Arc::new(crate::embeddings_candle::CandleEmbedder::from_env()?);
64 tracing::info!(model = %e.model_id(), "embedder loaded");
65 Some(e)
66 };
67 #[cfg(not(feature = "embeddings-candle"))]
68 let embedder: Option<Arc<dyn ijima_core::Embedder>> = None;
69
70 let data_dir = std::env::var("IJIMA_DIR")
73 .map(std::path::PathBuf::from)
74 .or_else(|_| {
75 std::env::var_os("HOME")
76 .map(|h| std::path::PathBuf::from(h).join(".ijima"))
77 .ok_or_else(|| {
78 ijima_core::IjimaError::invalid_input(
79 "cannot resolve data dir: set IJIMA_DIR or HOME",
80 )
81 })
82 })?;
83 let db_path = data_dir.join("ijima.db");
84
85 #[cfg(feature = "embeddings-candle")]
86 let store_inner = Arc::new(
87 crate::SurrealStore::open_persistent_with(&db_path, embedder.clone().unwrap()).await?,
88 );
89 #[cfg(not(feature = "embeddings-candle"))]
90 let store_inner = Arc::new(crate::SurrealStore::open_persistent(&db_path).await?);
91 let store: Arc<dyn Store> = store_inner.clone();
92 let kg: Arc<dyn ijima_core::KnowledgeGraph> = store_inner;
93
94 #[cfg(feature = "rate-limit")]
98 let rate_limiter: Option<crate::rate_limit::RateLimitState> =
99 if std::env::var_os("IJIMA_RATE_DISABLE").is_some() {
100 None
101 } else {
102 let base = std::env::var("IJIMA_RATE_BASE")
103 .ok()
104 .and_then(|v| v.parse().ok())
105 .unwrap_or(10.0);
106 let mult = std::env::var("IJIMA_RATE_MULTIPLIER")
107 .ok()
108 .and_then(|v| v.parse().ok())
109 .unwrap_or(1.0);
110 tracing::info!(
111 base_tokens_per_second = base,
112 multiplier = mult,
113 "rate limiting enabled (Schubert intersection-number capacity)"
114 );
115 Some(crate::rate_limit::make_rate_limiter(base, mult))
116 };
117
118 let app = api::app(
119 auth,
120 store,
121 kg,
122 embedder,
123 std::sync::Arc::new(crate::redaction::Redactor::new()),
124 #[cfg(feature = "rate-limit")]
125 rate_limiter,
126 );
127
128 let addr = format!("{}:{}", config.host, config.port);
129
130 #[cfg(feature = "tls")]
133 if let (Some(cert_path), Some(key_path)) = (
134 std::env::var_os("IJIMA_TLS_CERT"),
135 std::env::var_os("IJIMA_TLS_KEY"),
136 ) {
137 let tls_config =
138 axum_server::tls_rustls::RustlsConfig::from_pem_file(&cert_path, &key_path)
139 .await
140 .map_err(|e| IjimaError::Store {
141 detail: format!("tls config: {e}"),
142 })?;
143 let listener = axum_server::bind_rustls(
144 addr.parse().map_err(|e| IjimaError::Store {
145 detail: format!("parse {addr}: {e}"),
146 })?,
147 tls_config,
148 );
149 eprintln!("ijima: listening on https://{addr}");
150 listener
151 .serve(app.into_make_service())
152 .await
153 .map_err(|e| IjimaError::Store {
154 detail: format!("serve: {e}"),
155 })?;
156 return Ok(());
157 }
158
159 let listener = tokio::net::TcpListener::bind(&addr)
160 .await
161 .map_err(|e| IjimaError::Store {
162 detail: format!("bind {addr}: {e}"),
163 })?;
164 tracing::info!(addr = %addr, "ijima listening");
165 axum::serve(listener, app)
166 .await
167 .map_err(|e| IjimaError::Store {
168 detail: format!("serve: {e}"),
169 })?;
170 Ok(())
171}