use std::sync::Arc;
use anyhow::Result;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod agent;
mod init;
mod supervisor;
#[cfg(target_os = "linux")]
mod error;
mod rpc;
mod shutdown;
#[cfg(target_os = "linux")]
mod mount;
#[cfg(target_os = "linux")]
mod config;
#[cfg(target_os = "linux")]
mod rootfs_builder;
#[cfg(target_os = "linux")]
mod sandbox;
mod dns;
mod dns_server;
mod docker_events;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Init,
Serve,
}
fn parse_mode(args: &[String]) -> Mode {
match args.get(1).map(String::as_str) {
Some("init") => Mode::Init,
_ => Mode::Serve,
}
}
#[tokio::main]
async fn main() -> Result<()> {
let is_pid1 = std::process::id() == 1;
let log_dir = format!("/arcbox/{}", arcbox_constants::paths::guest::LOG);
let console_writer: Box<dyn std::io::Write + Send> =
match std::fs::OpenOptions::new().write(true).open("/dev/hvc1") {
Ok(f) => Box::new(f),
Err(_) => Box::new(std::io::stderr()),
};
let _log_guard = if std::path::Path::new("/arcbox").exists() {
match std::fs::create_dir_all(&log_dir) {
Ok(()) => {
let file_appender = tracing_appender::rolling::never(&log_dir, "agent.log");
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "arcbox_agent=info,arcbox_vm=info".into()),
)
.with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(std::sync::Mutex::new(console_writer)),
)
.with(
tracing_subscriber::fmt::layer()
.with_target(true)
.with_writer(non_blocking),
)
.init();
Some(guard)
}
Err(e) => {
eprintln!("arcbox-agent: failed to create {log_dir}: {e}, falling back to console");
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "arcbox_agent=info,arcbox_vm=info".into()),
)
.with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(std::sync::Mutex::new(console_writer)),
)
.init();
None
}
}
} else {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "arcbox_agent=info,arcbox_vm=info".into()),
)
.with(
tracing_subscriber::fmt::layer()
.with_ansi(false)
.with_writer(std::sync::Mutex::new(console_writer)),
)
.init();
None
};
if parse_mode(&std::env::args().collect::<Vec<_>>()) == Mode::Init {
tracing::info!("Running one-shot system initialization");
init::init_system();
if let Err(e) = init::verify_critical_mounts() {
tracing::error!("system initialization incomplete: {e}");
return Err(anyhow::anyhow!("system initialization incomplete: {e}"));
}
return Ok(());
}
if is_pid1 {
tracing::info!("Running as PID 1, initializing system");
init::init_system();
let sv = std::sync::Arc::new(tokio::sync::Mutex::new(supervisor::Supervisor::new()));
supervisor::spawn_reaper(sv);
}
tracing::info!("ArcBox agent starting...");
let cancel = tokio_util::sync::CancellationToken::new();
let dns = std::sync::Arc::new(dns_server::GuestDnsServer::new(cancel.clone()));
let dns_handle = {
let dns = Arc::clone(&dns);
tokio::spawn(async move {
if let Err(e) = dns.run().await {
tracing::error!(error = %e, "guest DNS server exited with error");
}
})
};
let docker_handle = {
let dns = Arc::clone(&dns);
let cancel = cancel.clone();
tokio::spawn(async move {
docker_events::reconcile_and_watch(&dns, cancel).await;
})
};
let result = agent::run().await;
cancel.cancel();
let _ = tokio::join!(dns_handle, docker_handle);
result
}
#[cfg(test)]
mod tests {
use super::{Mode, parse_mode};
fn argv(extra: &[&str]) -> Vec<String> {
std::iter::once("arcbox-agent")
.chain(extra.iter().copied())
.map(String::from)
.collect()
}
#[test]
fn init_subcommand_selects_init_mode() {
assert_eq!(parse_mode(&argv(&["init"])), Mode::Init);
}
#[test]
fn no_subcommand_defaults_to_serve() {
assert_eq!(parse_mode(&argv(&[])), Mode::Serve);
}
#[test]
fn explicit_serve_subcommand_selects_serve() {
assert_eq!(parse_mode(&argv(&["serve"])), Mode::Serve);
}
#[test]
fn unknown_subcommand_defaults_to_serve() {
assert_eq!(parse_mode(&argv(&["wat"])), Mode::Serve);
}
}