mod assets;
mod cleanup;
mod lock;
pub use assets::find_bundle_contents;
pub use lock::DaemonLock;
use std::path::PathBuf;
use std::sync::Arc;
use anyhow::{Context, Result};
use arcbox_api::{SetupPhase, SetupState};
use arcbox_constants::paths::HostLayout;
use arcbox_core::{Config, Runtime};
use macos_resolver::to_env_prefix;
use tokio_util::sync::CancellationToken;
use tracing::{info, warn};
use crate::DaemonArgs;
use crate::context::{DaemonContext, EarlyContext, VmArgs};
const DNS_PREFIX: &str = "arcbox";
const DEFAULT_DNS_DOMAIN: &str = "arcbox.local";
pub async fn init_early(args: DaemonArgs) -> Result<EarlyContext> {
let setup_state = Arc::new(SetupState::new());
let mut layout = HostLayout::resolve(args.data_dir.as_deref());
if let Some(socket) = args.socket {
layout.docker_socket = socket;
}
if let Some(grpc) = args.grpc_socket {
layout.grpc_socket = grpc;
}
std::fs::create_dir_all(&layout.data_dir).context("Failed to create data directory")?;
std::fs::create_dir_all(&layout.run_dir).context("Failed to create run directory")?;
std::fs::create_dir_all(&layout.log_dir).context("Failed to create log directory")?;
std::fs::create_dir_all(&layout.data_subdir)
.context("Failed to create persistent data directory")?;
let dns_domain = dns_domain();
let dns_port = dns_port();
Ok(EarlyContext {
layout,
shared_runtime: Arc::new(std::sync::OnceLock::new()),
setup_state,
shutdown: CancellationToken::new(),
dns_domain,
dns_port,
docker_integration: args.docker_integration,
vm_args: VmArgs {
guest_docker_vsock_port: args.guest_docker_vsock_port,
kernel: args.kernel,
},
})
}
pub async fn acquire_lock(early: EarlyContext) -> Result<DaemonContext> {
let lock_file = early.layout.lock_file.clone();
let lock = tokio::task::spawn_blocking(move || DaemonLock::acquire(&lock_file))
.await
.context("lock task panicked")?
.context("failed to acquire daemon lock")?;
Ok(DaemonContext {
layout: early.layout,
daemon_lock: lock,
shared_runtime: early.shared_runtime,
setup_state: early.setup_state,
shutdown: early.shutdown,
dns_domain: early.dns_domain,
dns_port: early.dns_port,
docker_integration: early.docker_integration,
vm_args: early.vm_args,
})
}
#[cfg(target_os = "macos")]
pub async fn wait_for_resources(ctx: &DaemonContext) -> Result<()> {
let candidates = ["docker.img", "docker-rosetta.img"];
let docker_imgs: Vec<std::path::PathBuf> = candidates
.iter()
.map(|name| ctx.layout.data_subdir.join(name))
.filter(|path| path.exists())
.collect();
if docker_imgs.is_empty() {
return Ok(());
}
ctx.setup_state
.set_phase(SetupPhase::CleaningUp, "Waiting for resource release…");
tokio::task::spawn_blocking(move || {
for path in docker_imgs {
cleanup::wait_for_docker_img_holders(&path);
}
})
.await
.context("resource wait task panicked")?;
ctx.setup_state.set_phase(
SetupPhase::Initializing,
"Finished waiting for resource release (see logs for details)",
);
Ok(())
}
#[cfg(not(target_os = "macos"))]
pub async fn wait_for_resources(_ctx: &DaemonContext) -> Result<()> {
Ok(())
}
pub async fn init_runtime(ctx: &DaemonContext) -> Result<Arc<Runtime>> {
let mut config = Config::load().unwrap_or_else(|err| {
warn!(error = %err, "Failed to load config file; falling back to defaults");
Config::default()
});
config.data_dir = ctx.layout.data_dir.clone();
if let Some(port) = ctx.vm_args.guest_docker_vsock_port {
config.container.guest_docker_vsock_port = port;
}
let selected_guest_docker_port = config.container.guest_docker_vsock_port;
if let Some(ref kernel) = ctx.vm_args.kernel {
config.vm.kernel_path = Some(kernel.clone());
}
let data_dir = ctx.layout.data_dir.clone();
tokio::task::spawn_blocking(move || {
assets::seed_from_bundle(&data_dir);
assets::ensure_agent_binary(&data_dir)
})
.await
.context("seed task panicked")?
.context("seed failed")?;
assets::ensure_boot_assets(&ctx.layout.data_dir, &ctx.setup_state).await?;
ctx.setup_state
.set_phase(SetupPhase::AssetsReady, "Boot assets ready");
let runtime = Arc::new(Runtime::new(config).context("Failed to create runtime")?);
runtime
.init()
.await
.context("Failed to initialize runtime")?;
info!(
data_dir = %ctx.layout.data_dir.display(),
guest_docker_vsock_port = selected_guest_docker_port,
"Runtime initialized"
);
if ctx.dns_domain != DEFAULT_DNS_DOMAIN {
runtime.network_manager().set_dns_domain(&ctx.dns_domain);
}
ctx.shared_runtime
.set(Arc::clone(&runtime))
.map_err(|_| anyhow::anyhow!("init_runtime called twice"))?;
Ok(runtime)
}
pub fn resolve_data_dir(data_dir: Option<&PathBuf>) -> PathBuf {
HostLayout::resolve(data_dir.map(PathBuf::as_path)).data_dir
}
fn dns_port() -> u16 {
let key = format!("{}_DNS_PORT", to_env_prefix(DNS_PREFIX));
std::env::var(key)
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(5553)
}
fn dns_domain() -> String {
let key = format!("{}_DNS_DOMAIN", to_env_prefix(DNS_PREFIX));
std::env::var(key).unwrap_or_else(|_| DEFAULT_DNS_DOMAIN.to_string())
}