use anyhow::{Result, bail};
use clap::Parser;
use std::io::IsTerminal as _;
use std::process::ExitCode;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod commands;
mod connect;
mod error;
use commands::{Cli, Commands};
fn main() -> ExitCode {
let _sentry_guard = sentry::init(sentry::ClientOptions {
dsn: std::env::var("ARCBOX_CLI_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_CLI_SENTRY_ENVIRONMENT")
.or_else(|_| std::env::var("SENTRY_ENVIRONMENT"))
.ok()
.map(Into::into),
sample_rate: 1.0,
attach_stacktrace: true,
..Default::default()
});
let cli = Cli::parse();
let debug = cli.debug;
match run(cli) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{}", crate::error::render(&error, debug));
ExitCode::FAILURE
}
}
}
fn run(cli: Cli) -> Result<()> {
cli.validate_output_format()?;
if let Some(ref socket) = cli.socket {
unsafe {
std::env::set_var("ARCBOX_SOCKET", socket.as_os_str());
}
if let Some(data_dir) = arcbox_cli::runtime_selection::data_dir_from_docker_socket(socket) {
let development_instance =
arcbox_cli::runtime_selection::is_development_instance(&data_dir);
let selected_profile = cli.profile.or_else(|| {
std::env::var(arcbox_constants::env::PROFILE)
.ok()
.and_then(|value| value.parse().ok())
});
if development_instance
&& selected_profile.is_some_and(|profile| {
profile != arcbox_constants::paths::ArcboxProfile::Development
})
{
bail!(
"--socket selects an isolated development instance, but the selected profile is production"
);
}
if let Some(configured) = std::env::var_os(arcbox_constants::env::DATA_DIR) {
if std::path::Path::new(&configured) != data_dir {
bail!(
"--socket selects {}, but {} selects {}",
data_dir.display(),
arcbox_constants::env::DATA_DIR,
std::path::Path::new(&configured).display()
);
}
} else {
unsafe {
std::env::set_var(arcbox_constants::env::DATA_DIR, &data_dir);
}
}
if development_instance && selected_profile.is_none() {
unsafe {
std::env::set_var(
arcbox_constants::env::PROFILE,
arcbox_constants::paths::ArcboxProfile::Development.as_str(),
);
}
}
}
}
if let Some(profile) = cli.profile {
unsafe {
std::env::set_var(arcbox_constants::env::PROFILE, profile.as_str());
}
}
if std::env::var_os(arcbox_constants::env::DOCKER_CONTEXT).is_none() {
let profile = arcbox_constants::paths::ArcboxProfile::from_env_or_default();
let data_dir = arcbox_constants::paths::HostLayout::from_env_or_default().data_dir;
if let Some(context) =
arcbox_cli::runtime_selection::docker_context_name_for(profile, &data_dir)
{
unsafe {
std::env::set_var(arcbox_constants::env::DOCKER_CONTEXT, context);
}
}
}
let filter = if cli.debug {
"arcbox=debug,arcbox_cli=debug"
} else {
"arcbox=info"
};
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| filter.into()),
)
.with(
tracing_subscriber::fmt::layer()
.with_target(false)
.with_ansi(arcbox_cli::terminal::ansi_enabled(
std::io::stderr().is_terminal(),
)),
)
.init();
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to build tokio runtime");
let result = runtime.block_on(async {
match cli.command {
Commands::Machine(cmd) => commands::machine::execute(cmd).await,
#[cfg(target_os = "macos")]
Commands::Macos(cmd) => commands::macos::execute(cmd).await,
Commands::Migrate(cmd) => commands::migrate::execute(cmd).await,
Commands::Sandbox(cmd) => commands::sandbox::execute(cmd).await,
Commands::Claude(args) => {
commands::agent::execute(&commands::agent::CLAUDE, args).await
}
Commands::Docker(cmd) => commands::docker::execute(cmd, cli.format).await,
Commands::Kubernetes(cmd) => commands::kubernetes::execute(cmd).await,
Commands::System(cmd) => commands::system::execute(cmd).await,
Commands::Boot(cmd) => commands::boot::execute(cmd, cli.format).await,
Commands::Disk(cmd) => commands::disk::execute(cmd, cli.format).await,
#[cfg(target_os = "macos")]
Commands::Dns(cmd) => commands::dns::execute(cmd, cli.format).await,
Commands::Daemon(args) => commands::daemon::execute(args).await,
Commands::Logs(args) => commands::logs::execute(args).await,
Commands::Setup(cmd) => commands::setup::execute(cmd, cli.format).await,
Commands::Doctor => commands::doctor::execute(cli.format).await,
Commands::Top(args) => commands::top::execute(args, cli.format).await,
#[cfg(target_os = "macos")]
Commands::Install(args) => commands::install::execute(args).await,
#[cfg(target_os = "macos")]
Commands::Uninstall(args) => commands::uninstall::execute(args).await,
#[cfg(target_os = "macos")]
Commands::Internal(cmd) => commands::internal::execute(cmd).await,
Commands::Info => execute_info().await,
Commands::Version => commands::version::execute(cli.format).await,
}
});
runtime.shutdown_background();
result
}
async fn execute_info() -> Result<()> {
println!("ArcBox Version: {}", env!("CARGO_PKG_VERSION"));
println!("OS: {}", std::env::consts::OS);
println!("Arch: {}", std::env::consts::ARCH);
println!(
"CPUs: {}",
std::thread::available_parallelism().map_or(1, |n| n.get())
);
match commands::machine::machine_count().await {
Ok(machine_count) => println!("Machines: {}", machine_count),
Err(_) => println!("Machines: (daemon not running)"),
}
Ok(())
}