1pub mod artifact;
15pub mod auth;
17pub mod builtins;
19pub mod cache;
21pub mod canonical;
23pub mod catalog;
25pub mod client;
27pub mod definitions;
29pub mod export;
31pub mod firstrun;
33pub mod fsnotify;
35pub mod history;
37pub mod http;
39pub mod ipc;
41pub mod jobs;
43pub mod lock;
45pub mod log;
47pub mod metrics;
49pub mod ops;
51pub mod paths;
53pub mod pidlock;
55pub mod pubsub;
57pub mod schedule;
59pub mod server;
61pub mod shard;
63pub mod snapshot;
65pub mod source_resolver;
67pub mod state;
69pub mod ticker;
71pub mod zask;
73pub mod zask_builtin;
75pub mod zcomplete_builtin;
77pub mod zd_dispatch;
79pub mod zhistory_builtin;
81pub mod zjob_builtin;
83pub mod zsource_builtin;
85pub mod zsync;
87pub mod zsync_builtin;
89
90pub use ipc::{Event, Frame, Hello, ProtocolVersion, Welcome, PROTOCOL_VERSION};
99pub use paths::CachePaths;
100
101pub type Result<T> = std::result::Result<T, DaemonError>;
103#[derive(thiserror::Error, Debug)]
105pub enum DaemonError {
106 #[error("io: {0}")]
108 Io(#[from] std::io::Error),
109 #[error("json: {0}")]
111 Json(#[from] serde_json::Error),
112 #[error("nix: {0}")]
114 Nix(#[from] nix::Error),
115 #[error("rusqlite: {0}")]
117 Sqlite(#[from] rusqlite::Error),
118 #[error("singleton: another daemon is running (pid {0})")]
120 AlreadyRunning(i32),
121
122 #[error("protocol: client v{client} incompatible with daemon v{daemon}")]
123 ProtocolMismatch { client: u32, daemon: u32 },
124 #[error("protocol: malformed handshake")]
126 BadHandshake,
127
128 #[error("protocol: frame too large ({size} > {max})")]
129 FrameTooLarge { size: usize, max: usize },
130 #[error("daemon: shutting down")]
132 Shutdown,
133 #[error("op: unknown opcode {0:?}")]
135 UnknownOp(String),
136
137 #[error("op: bad args for {op}: {reason}")]
138 BadArgs { op: String, reason: String },
139 #[error("client: not connected to daemon")]
141 NotConnected,
142 #[error("client: timed out after {0:?}")]
144 Timeout(std::time::Duration),
145 #[error("{0}")]
147 Other(String),
148}
149
150impl DaemonError {
151 pub fn other<S: Into<String>>(msg: S) -> Self {
153 Self::Other(msg.into())
154 }
155}
156
157pub fn run() -> Result<()> {
162 let paths = paths::CachePaths::resolve()?;
163 paths.ensure_dirs()?;
164
165 if let Err(e) = paths.ensure_default_configs() {
169 eprintln!("zshrs-daemon: failed to seed default configs: {e}");
170 }
171
172 let _log_guard = log::init(&paths)?;
174
175 tracing::info!(version = env!("CARGO_PKG_VERSION"), "zshrs-daemon starting");
176
177 let pid = std::process::id();
181 let cwd = std::env::current_dir()
182 .map(|p| p.display().to_string())
183 .unwrap_or_else(|_| "?".to_string());
184 let zshrs_home = std::env::var("ZSHRS_HOME").unwrap_or_default();
185 tracing::trace!(
186 pid,
187 cwd,
188 zshrs_home = if zshrs_home.is_empty() { "<unset>" } else { &zshrs_home },
189 root = %paths.root.display(),
190 socket = %paths.socket.display(),
191 pid_file = %paths.pid_file.display(),
192 log = %paths.log.display(),
193 daemon_toml = %paths.daemon_config_path().display(),
194 shell_toml = %paths.shell_config_path().display(),
195 catalog_db = %paths.catalog_db.display(),
196 history_db = %paths.history_db.display(),
197 cache_db = %paths.cache_db.display(),
198 images_dir = %paths.images.display(),
199 artifacts_dir = %paths.artifacts_dir.display(),
200 snapshots_dir = %paths.snapshots_dir.display(),
201 replay_dir = %paths.replay_dir.display(),
202 "daemon: resolved environment"
203 );
204
205 let _pid_lock = pidlock::acquire(&paths)?;
207 tracing::trace!(pid_file = %paths.pid_file.display(), pid, "daemon: pidlock acquired");
208
209 let rt = tokio::runtime::Builder::new_multi_thread()
211 .enable_all()
212 .thread_name("zshrs-daemon")
213 .build()?;
214
215 let result = rt.block_on(server::serve(paths.clone()));
216
217 tracing::info!("zshrs-daemon exiting");
218
219 result
220}