pub mod artifact;
pub mod auth;
pub mod builtins;
pub mod cache;
pub mod canonical;
pub mod catalog;
pub mod client;
pub mod definitions;
pub mod export;
pub mod firstrun;
pub mod fsnotify;
pub mod history;
pub mod http;
pub mod ipc;
pub mod jobs;
pub mod lock;
pub mod log;
pub mod metrics;
pub mod ops;
pub mod paths;
pub mod pidlock;
pub mod pubsub;
pub mod schedule;
pub mod server;
pub mod shard;
pub mod snapshot;
pub mod source_resolver;
pub mod state;
pub mod ticker;
pub mod zask;
pub mod zask_builtin;
pub mod zcomplete_builtin;
pub mod zd_dispatch;
pub mod zhistory_builtin;
pub mod zjob_builtin;
pub mod zsource_builtin;
pub mod zsync;
pub mod zsync_builtin;
pub use ipc::{Event, Frame, Hello, ProtocolVersion, Welcome, PROTOCOL_VERSION};
pub use paths::CachePaths;
pub type Result<T> = std::result::Result<T, DaemonError>;
#[derive(thiserror::Error, Debug)]
pub enum DaemonError {
#[error("io: {0}")]
Io(#[from] std::io::Error),
#[error("json: {0}")]
Json(#[from] serde_json::Error),
#[error("nix: {0}")]
Nix(#[from] nix::Error),
#[error("rusqlite: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("singleton: another daemon is running (pid {0})")]
AlreadyRunning(i32),
#[error("protocol: client v{client} incompatible with daemon v{daemon}")]
ProtocolMismatch { client: u32, daemon: u32 },
#[error("protocol: malformed handshake")]
BadHandshake,
#[error("protocol: frame too large ({size} > {max})")]
FrameTooLarge { size: usize, max: usize },
#[error("daemon: shutting down")]
Shutdown,
#[error("op: unknown opcode {0:?}")]
UnknownOp(String),
#[error("op: bad args for {op}: {reason}")]
BadArgs { op: String, reason: String },
#[error("client: not connected to daemon")]
NotConnected,
#[error("client: timed out after {0:?}")]
Timeout(std::time::Duration),
#[error("{0}")]
Other(String),
}
impl DaemonError {
pub fn other<S: Into<String>>(msg: S) -> Self {
Self::Other(msg.into())
}
}
pub fn run() -> Result<()> {
let paths = paths::CachePaths::resolve()?;
paths.ensure_dirs()?;
if let Err(e) = paths.ensure_default_configs() {
eprintln!("zshrs-daemon: failed to seed default configs: {e}");
}
let _log_guard = log::init(&paths)?;
tracing::info!(version = env!("CARGO_PKG_VERSION"), "zshrs-daemon starting");
let pid = std::process::id();
let cwd = std::env::current_dir()
.map(|p| p.display().to_string())
.unwrap_or_else(|_| "?".to_string());
let zshrs_home = std::env::var("ZSHRS_HOME").unwrap_or_default();
tracing::trace!(
pid,
cwd,
zshrs_home = if zshrs_home.is_empty() { "<unset>" } else { &zshrs_home },
root = %paths.root.display(),
socket = %paths.socket.display(),
pid_file = %paths.pid_file.display(),
log = %paths.log.display(),
daemon_toml = %paths.daemon_config_path().display(),
shell_toml = %paths.shell_config_path().display(),
catalog_db = %paths.catalog_db.display(),
history_db = %paths.history_db.display(),
cache_db = %paths.cache_db.display(),
images_dir = %paths.images.display(),
artifacts_dir = %paths.artifacts_dir.display(),
snapshots_dir = %paths.snapshots_dir.display(),
replay_dir = %paths.replay_dir.display(),
"daemon: resolved environment"
);
let _pid_lock = pidlock::acquire(&paths)?;
tracing::trace!(pid_file = %paths.pid_file.display(), pid, "daemon: pidlock acquired");
let rt = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("zshrs-daemon")
.build()?;
let result = rt.block_on(server::serve(paths.clone()));
tracing::info!("zshrs-daemon exiting");
result
}