#[cfg(unix)]
use std::path::PathBuf;
#[cfg(unix)]
use tokio::net::UnixListener;
mod protocol;
mod session;
mod handlers;
mod sanitize;
mod subprocess;
mod keystore;
mod webauthn;
mod audit;
mod remote;
mod localhost;
mod encrypted_inventory;
#[cfg(windows)]
mod winpipe;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();
audit::init_audit_logger(None);
audit::log_simple(audit::EventType::DaemonStart, audit::EventResult::Success);
let session = session::new_shared_session();
handlers::init_session(session.clone());
if keystore::is_dev_mode() {
tracing::warn!("SCRT4_DEV_MODE=1 — bootstrapping dev session with fixed key");
tracing::warn!("DO NOT use this distribution for real secrets");
let master_key = keystore::DEV_MASTER_KEY_B64.to_string();
let secrets = if keystore::secrets_path().exists() {
match keystore::decrypt_secrets(&master_key) {
Ok(s) => s,
Err(e) => {
tracing::error!("Dev vault decrypt failed (likely from a different SCRT4_DEV_MODE state): {}", e);
tracing::error!("Refusing to overwrite. Remove ~/.scrt4-dev manually if you want a fresh start.");
return Err(format!("dev vault decrypt failed: {}", e).into());
}
}
} else {
if let Err(e) = keystore::save_encrypted_env(&std::collections::HashMap::new(), &master_key) {
tracing::error!("Failed to initialize empty dev vault: {}", e);
return Err(format!("dev vault init failed: {}", e).into());
}
std::collections::HashMap::new()
};
let token: Vec<u8> = (0..32).map(|_| rand::random::<u8>()).collect();
let mut sess = session.write().await;
if let Err(e) = sess.store(token, secrets, 0) {
tracing::error!("Failed to bootstrap dev session: {}", e);
return Err(format!("dev session bootstrap failed: {}", e).into());
}
sess.set_master_key(master_key);
tracing::info!("Dev session bootstrapped successfully");
}
let remote_config = remote::load_config();
if remote_config.enabled {
let remote_listener = remote::RemoteListener::new(remote_config);
tokio::spawn(async move {
if let Err(e) = remote_listener.start().await {
tracing::error!("Remote listener error: {}", e);
}
});
}
let shutdown = async {
tokio::signal::ctrl_c().await.ok();
audit::log_simple(audit::EventType::DaemonStop, audit::EventResult::Success);
};
#[cfg(unix)]
{
let socket_path = get_socket_path();
let _ = std::fs::remove_file(&socket_path);
if let Some(parent) = socket_path.parent() {
std::fs::create_dir_all(parent)?;
}
let listener = UnixListener::bind(&socket_path)?;
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&socket_path, std::fs::Permissions::from_mode(0o600))?;
}
tracing::info!("Daemon listening on {:?}", socket_path);
tokio::select! {
_ = async {
loop {
match listener.accept().await {
Ok((stream, _)) => {
audit::log_simple(audit::EventType::ClientConnect, audit::EventResult::Success);
tokio::spawn(handlers::handle_connection(stream));
}
Err(e) => {
tracing::error!("Accept error: {}", e);
}
}
}
} => {}
_ = shutdown => {
tracing::info!("Shutting down daemon");
}
}
}
#[cfg(windows)]
{
tokio::select! {
res = winpipe::serve() => {
if let Err(e) = res {
tracing::error!("Pipe listener error: {}", e);
}
}
_ = shutdown => {
tracing::info!("Shutting down daemon");
}
}
}
Ok(())
}
#[cfg(unix)]
fn get_socket_path() -> PathBuf {
if let Ok(runtime_dir) = std::env::var("XDG_RUNTIME_DIR") {
PathBuf::from(runtime_dir).join("scrt4.sock")
} else {
let uid = unsafe { libc::getuid() };
PathBuf::from(format!("/tmp/scrt4-{}.sock", uid))
}
}