mod context;
mod dns_service;
mod kubernetes_proxy;
mod recovery;
mod self_setup;
mod services;
mod shutdown;
mod startup;
use anyhow::Result;
use arcbox_api::SetupPhase;
use arcbox_logging::LogConfig;
use clap::Parser;
use macos_resolver::FileResolver;
use tracing::info;
#[derive(Debug, Parser)]
#[command(name = "arcbox-daemon")]
#[command(author, version, about, long_about = None)]
pub struct DaemonArgs {
#[arg(long)]
pub socket: Option<std::path::PathBuf>,
#[arg(long)]
pub grpc_socket: Option<std::path::PathBuf>,
#[arg(long)]
pub data_dir: Option<std::path::PathBuf>,
#[arg(long)]
pub kernel: Option<std::path::PathBuf>,
#[arg(long)]
pub docker_integration: bool,
#[arg(long)]
pub foreground: bool,
#[arg(long)]
pub guest_docker_vsock_port: Option<u32>,
}
fn main() -> Result<()> {
let args = DaemonArgs::parse();
let _sentry_guard = sentry::init(sentry::ClientOptions {
dsn: std::env::var("ARCBOX_DAEMON_SENTRY_DSN")
.or_else(|_| std::env::var("SENTRY_DSN"))
.ok()
.and_then(|s| s.parse().ok()),
release: Some(env!("CARGO_PKG_VERSION").into()),
environment: std::env::var("ARCBOX_DAEMON_SENTRY_ENVIRONMENT")
.or_else(|_| std::env::var("SENTRY_ENVIRONMENT"))
.ok()
.map(Into::into),
traces_sample_rate: 0.2,
sample_rate: 1.0,
attach_stacktrace: true,
..Default::default()
});
let data_dir = startup::resolve_data_dir(args.data_dir.as_ref());
let log_guard = arcbox_logging::init_with_sentry(LogConfig {
log_dir: data_dir.join(arcbox_constants::paths::host::LOG),
file_name: arcbox_constants::paths::host::DAEMON_LOG.to_string(),
default_filter: "arcbox=info,arcbox_daemon=info".to_string(),
foreground: args.foreground,
..LogConfig::default()
});
let result = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to build tokio runtime")
.block_on(run(args));
if let Err(ref e) = result {
tracing::error!("Daemon exited with error: {e:?}");
}
log_guard.flush();
result
}
async fn run(args: DaemonArgs) -> Result<()> {
info!("Starting ArcBox daemon...");
let early = startup::init_early(args).await?;
let ctx = startup::acquire_lock(early).await?;
let shared_runtime = ctx.shared_runtime.clone();
let grpc = services::start_grpc(&ctx, shared_runtime).await?;
startup::wait_for_resources(&ctx).await?;
let runtime = startup::init_runtime(&ctx).await?;
let handles = services::start_services(&ctx, &runtime, grpc).await?;
recovery::run(&ctx, &runtime).await;
check_resolver_installed(&ctx.dns_domain);
ctx.setup_state.set_phase(SetupPhase::Ready, "Daemon ready");
println!("ArcBox daemon started");
println!(" Docker API: {}", ctx.layout.docker_socket.display());
println!(" gRPC API: {}", ctx.layout.grpc_socket.display());
println!(" DNS: 127.0.0.1:{}", ctx.dns_port);
println!(" Data: {}", ctx.layout.data_dir.display());
println!();
println!("Use 'arcbox docker enable' to configure Docker CLI integration.");
println!("Press Ctrl+C to stop.");
shutdown::run(ctx, handles).await
}
fn check_resolver_installed(domain: &str) {
let resolver = FileResolver::new("arcbox");
if !resolver.is_registered(domain) {
println!("Hint: Run 'sudo arcbox dns install' to enable *.{domain} DNS resolution.");
}
}