pub const DOCKER_DATA_MOUNT_POINT: &str = "/var/lib/docker";
pub const CONTAINERD_DATA_MOUNT_POINT: &str = "/var/lib/containerd";
pub const K3S_DATA_MOUNT_POINT: &str = "/var/lib/rancher/k3s";
pub const KUBELET_DATA_MOUNT_POINT: &str = "/var/lib/kubelet";
pub const CNI_DATA_MOUNT_POINT: &str = "/var/lib/cni";
pub const DOCKER_API_UNIX_SOCKET: &str = "/var/run/docker.sock";
pub const CONTAINERD_SOCKET: &str = "/run/containerd/containerd.sock";
pub const K3S_KUBECONFIG_PATH: &str = "/var/lib/rancher/k3s/k3s.yaml";
pub const K3S_CNI_CONF_DIR: &str = "/var/lib/rancher/k3s/agent/etc/cni/net.d";
pub const K3S_CNI_BIN_DIR: &str = "/var/lib/rancher/k3s/data/cni";
pub const ARCBOX_RUNTIME_BIN_DIR: &str = "/arcbox/runtime/bin";
pub mod privileged {
pub const HELPER_BINARY: &str = "/usr/local/libexec/arcbox-helper";
pub const HELPER_PLIST: &str = "/Library/LaunchDaemons/com.arcboxlabs.desktop.helper.plist";
pub const HELPER_SOCKET: &str = "/var/run/arcbox-helper.sock";
pub const DOCKER_SOCKET: &str = "/var/run/docker.sock";
}
pub mod labels {
pub const DAEMON: &str = "com.arcboxlabs.desktop.daemon";
pub const DEVELOPMENT_DAEMON: &str = "com.arcboxlabs.desktop.dev.daemon";
pub const HELPER: &str = "com.arcboxlabs.desktop.helper";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ArcboxProfile {
#[default]
Production,
Development,
}
impl ArcboxProfile {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Production => "production",
Self::Development => "development",
}
}
#[must_use]
pub const fn docker_context_name(self) -> &'static str {
match self {
Self::Production => "arcbox",
Self::Development => "arcbox-dev",
}
}
#[must_use]
pub const fn daemon_label(self) -> &'static str {
match self {
Self::Production => labels::DAEMON,
Self::Development => labels::DEVELOPMENT_DAEMON,
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn from_env_or_default() -> Self {
std::env::var(crate::env::PROFILE)
.ok()
.and_then(|value| value.parse().ok())
.unwrap_or_default()
}
#[cfg(feature = "std")]
#[must_use]
pub fn default_data_dir(self) -> std::path::PathBuf {
dirs::home_dir().map_or_else(
|| std::path::PathBuf::from("/var/lib/arcbox"),
|home| match self {
Self::Production => home.join(".arcbox"),
Self::Development => home.join(".arcbox-dev"),
},
)
}
}
impl core::fmt::Display for ArcboxProfile {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(self.as_str())
}
}
impl core::str::FromStr for ArcboxProfile {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value.trim().to_ascii_lowercase().as_str() {
"production" | "prod" => Ok(Self::Production),
"development" | "dev" => Ok(Self::Development),
other => Err(format!(
"unknown ArcBox profile '{other}' (expected production or development)"
)),
}
}
}
pub const DOCKER_CLI_TOOLS: &[&str] = &[
"docker",
"docker-buildx",
"docker-compose",
"docker-credential-osxkeychain",
];
pub const DOCKER_CLI_PLUGINS: &[&str] = &["docker-buildx", "docker-compose"];
#[cfg(feature = "std")]
pub fn is_arcbox_owned(target: &std::path::Path) -> bool {
target
.to_string_lossy()
.contains(".app/Contents/MacOS/xbin/")
}
pub mod host {
pub const RUN: &str = "run";
pub const LOG: &str = "log";
pub const DATA: &str = "data";
pub const DAEMON_LOG: &str = "daemon.log";
pub const AGENT_LOG: &str = "agent.log";
pub const DAEMON_LOCK: &str = "daemon.lock";
pub const DOCKER_SOCKET: &str = "docker.sock";
pub const GRPC_SOCKET: &str = "arcbox.sock";
}
#[cfg(feature = "std")]
#[derive(Debug, Clone)]
pub struct HostLayout {
pub data_dir: std::path::PathBuf,
pub run_dir: std::path::PathBuf,
pub log_dir: std::path::PathBuf,
pub data_subdir: std::path::PathBuf,
pub docker_socket: std::path::PathBuf,
pub grpc_socket: std::path::PathBuf,
pub lock_file: std::path::PathBuf,
pub daemon_log: std::path::PathBuf,
}
#[cfg(feature = "std")]
impl HostLayout {
#[must_use]
pub fn new(data_dir: std::path::PathBuf) -> Self {
let run_dir = data_dir.join(host::RUN);
let log_dir = data_dir.join(host::LOG);
let data_subdir = data_dir.join(host::DATA);
let docker_socket = run_dir.join(host::DOCKER_SOCKET);
let grpc_socket = run_dir.join(host::GRPC_SOCKET);
let lock_file = run_dir.join(host::DAEMON_LOCK);
let daemon_log = log_dir.join(host::DAEMON_LOG);
Self {
data_dir,
run_dir,
log_dir,
data_subdir,
docker_socket,
grpc_socket,
lock_file,
daemon_log,
}
}
#[must_use]
pub fn for_profile(profile: ArcboxProfile) -> Self {
Self::new(profile.default_data_dir())
}
#[must_use]
pub fn resolve(data_dir: Option<&std::path::Path>) -> Self {
Self::resolve_for_profile(ArcboxProfile::Production, data_dir)
}
#[must_use]
pub fn resolve_for_profile(profile: ArcboxProfile, data_dir: Option<&std::path::Path>) -> Self {
match data_dir {
Some(d) => Self::new(d.to_path_buf()),
None => Self::for_profile(profile),
}
}
#[must_use]
pub fn resolve_for_profile_from_env(
profile: ArcboxProfile,
data_dir: Option<&std::path::Path>,
) -> Self {
if let Some(data_dir) = data_dir {
return Self::new(data_dir.to_path_buf());
}
if let Ok(data_dir) = std::env::var(crate::env::DATA_DIR) {
if !data_dir.is_empty() {
return Self::new(std::path::PathBuf::from(data_dir));
}
}
Self::for_profile(profile)
}
#[must_use]
pub fn from_env_or_default() -> Self {
Self::resolve_for_profile_from_env(ArcboxProfile::from_env_or_default(), None)
}
}
#[cfg(feature = "std")]
#[must_use]
pub fn default_data_dir() -> std::path::PathBuf {
ArcboxProfile::Production.default_data_dir()
}
pub mod privileged_log {
pub const HELPER_LOG_DIR: &str = "/var/log/arcbox";
pub const HELPER_LOG: &str = "helper.log";
}
pub mod guest {
pub const LOG: &str = "log";
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn host_layout_new_derives_all_paths() {
let layout = HostLayout::new(PathBuf::from("/tmp/arcbox"));
assert_eq!(layout.run_dir, PathBuf::from("/tmp/arcbox/run"));
assert_eq!(layout.log_dir, PathBuf::from("/tmp/arcbox/log"));
assert_eq!(layout.data_subdir, PathBuf::from("/tmp/arcbox/data"));
assert_eq!(
layout.docker_socket,
PathBuf::from("/tmp/arcbox/run/docker.sock")
);
assert_eq!(
layout.grpc_socket,
PathBuf::from("/tmp/arcbox/run/arcbox.sock")
);
assert_eq!(
layout.lock_file,
PathBuf::from("/tmp/arcbox/run/daemon.lock")
);
assert_eq!(
layout.daemon_log,
PathBuf::from("/tmp/arcbox/log/daemon.log")
);
}
#[test]
fn host_layout_resolve_uses_explicit_dir() {
let dir = PathBuf::from("/custom/dir");
let layout = HostLayout::resolve(Some(&dir));
assert_eq!(layout.data_dir, dir);
}
#[test]
fn host_layout_resolve_uses_default_when_none() {
let layout = HostLayout::resolve(None);
assert_eq!(layout.data_dir, default_data_dir());
}
#[test]
fn host_layout_resolve_for_profile_uses_explicit_dir() {
let dir = PathBuf::from("/custom/dev");
let layout = HostLayout::resolve_for_profile_from_env(
ArcboxProfile::Development,
Some(dir.as_path()),
);
assert_eq!(layout.data_dir, dir);
}
#[test]
fn development_profile_uses_dev_data_dir_and_context() {
let layout = HostLayout::for_profile(ArcboxProfile::Development);
assert!(layout.data_dir.ends_with(".arcbox-dev"));
assert_eq!(
ArcboxProfile::Development.docker_context_name(),
"arcbox-dev"
);
assert_eq!(
ArcboxProfile::Development.daemon_label(),
labels::DEVELOPMENT_DAEMON
);
}
#[test]
fn parses_profile_names_and_aliases() {
assert_eq!(
"production".parse::<ArcboxProfile>().unwrap(),
ArcboxProfile::Production
);
assert_eq!(
"dev".parse::<ArcboxProfile>().unwrap(),
ArcboxProfile::Development
);
}
#[test]
fn default_data_dir_returns_home_based_path() {
if dirs::home_dir().is_some() {
let dir = default_data_dir();
assert!(dir.ends_with(".arcbox"));
}
}
}