use arcbox_constants::ports::DOCKER_API_VSOCK_PORT;
use figment::{
Figment,
providers::{Env, Format, Serialized, Toml},
};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
pub data_dir: PathBuf,
pub vm: VmDefaults,
pub machine: MachineDefaults,
pub network: NetworkConfig,
pub docker: DockerConfig,
pub container: ContainerRuntimeConfig,
pub logging: LoggingConfig,
pub storage: StorageConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
data_dir: default_data_dir(),
vm: VmDefaults::default(),
machine: MachineDefaults::default(),
network: NetworkConfig::default(),
docker: DockerConfig::default(),
container: ContainerRuntimeConfig::default(),
logging: LoggingConfig::default(),
storage: StorageConfig::default(),
}
}
}
impl Config {
pub fn load() -> Result<Self, Box<figment::Error>> {
Figment::new()
.merge(Serialized::defaults(Self::default()))
.merge(Toml::file(system_config_path()))
.merge(Toml::file(user_config_path()))
.merge(Env::prefixed("ARCBOX_").split("_"))
.extract()
.map_err(Box::new)
}
pub fn load_from(path: impl AsRef<std::path::Path>) -> Result<Self, Box<figment::Error>> {
Figment::new()
.merge(Serialized::defaults(Self::default()))
.merge(Toml::file(path))
.merge(Env::prefixed("ARCBOX_").split("_"))
.extract()
.map_err(Box::new)
}
#[must_use]
pub fn data_subdir(&self) -> PathBuf {
self.data_dir.join(arcbox_constants::paths::host::DATA)
}
#[must_use]
pub fn images_dir(&self) -> PathBuf {
self.data_subdir().join("images")
}
#[must_use]
pub fn containers_dir(&self) -> PathBuf {
self.data_subdir().join("containers")
}
#[must_use]
pub fn machines_dir(&self) -> PathBuf {
self.data_subdir().join("machines")
}
#[must_use]
pub fn volumes_dir(&self) -> PathBuf {
self.data_subdir().join("volumes")
}
#[must_use]
pub fn run_dir(&self) -> PathBuf {
self.data_dir.join(arcbox_constants::paths::host::RUN)
}
#[must_use]
pub fn log_dir(&self) -> PathBuf {
self.data_dir.join(arcbox_constants::paths::host::LOG)
}
#[must_use]
pub fn docker_img_path(&self) -> PathBuf {
self.data_subdir().join("docker.img")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct VmDefaults {
pub cpus: u32,
pub memory_mb: u64,
pub kernel_path: Option<PathBuf>,
}
impl VmDefaults {
#[must_use]
pub fn effective_cpus(&self) -> u32 {
if self.cpus == 0 {
arcbox_hypervisor::default_vm_cpu_count()
} else {
self.cpus
}
}
}
impl Default for VmDefaults {
fn default() -> Self {
Self {
cpus: arcbox_hypervisor::default_vm_cpu_count(),
memory_mb: arcbox_hypervisor::default_vm_memory_size() / (1024 * 1024),
kernel_path: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct MachineDefaults {
pub disk_gb: u64,
pub default_distro: String,
pub default_version: Option<String>,
pub auto_mount_home: bool,
}
impl Default for MachineDefaults {
fn default() -> Self {
Self {
disk_gb: 50,
default_distro: "ubuntu".to_string(),
default_version: None,
auto_mount_home: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct NetworkConfig {
pub subnet: String,
pub gateway: Option<String>,
pub dns: Vec<String>,
pub ipv6: bool,
pub mtu: u16,
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
subnet: "10.0.2.0/24".to_string(),
gateway: None,
dns: vec!["8.8.8.8".to_string(), "8.8.4.4".to_string()],
ipv6: false,
mtu: 1500,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct DockerConfig {
pub socket_path: PathBuf,
pub enabled: bool,
}
impl Default for DockerConfig {
fn default() -> Self {
Self {
socket_path: default_docker_socket_path(),
enabled: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ContainerRuntimeConfig {
pub guest_docker_vsock_port: u32,
pub startup_timeout_ms: u64,
}
impl Default for ContainerRuntimeConfig {
fn default() -> Self {
Self {
guest_docker_vsock_port: DOCKER_API_VSOCK_PORT,
startup_timeout_ms: 60_000,
}
}
}
fn default_docker_socket_path() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".arcbox")
.join(arcbox_constants::paths::host::RUN)
.join("docker.sock")
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct LoggingConfig {
pub level: String,
pub file: Option<PathBuf>,
pub format: String,
}
impl Default for LoggingConfig {
fn default() -> Self {
Self {
level: "info".to_string(),
file: None,
format: "text".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct StorageConfig {
pub driver: String,
pub image_backend: String,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
driver: "overlay2".to_string(),
image_backend: "oci".to_string(),
}
}
}
fn default_data_dir() -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/var/lib"))
.join(".arcbox")
}
fn user_config_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("~/.config"))
.join("arcbox")
.join("config.toml")
}
fn system_config_path() -> PathBuf {
PathBuf::from("/etc/arcbox/config.toml")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.vm.cpus, arcbox_hypervisor::default_vm_cpu_count());
let expected_mb = arcbox_hypervisor::default_vm_memory_size() / (1024 * 1024);
assert_eq!(config.vm.memory_mb, expected_mb);
assert!(config.vm.memory_mb >= 512);
assert!(config.vm.memory_mb <= 16384);
assert_eq!(config.machine.disk_gb, 50);
assert!(config.docker.enabled);
assert_eq!(
config.container.guest_docker_vsock_port,
DOCKER_API_VSOCK_PORT
);
}
#[test]
fn test_effective_cpus_zero_resolves_to_default() {
let vm = VmDefaults {
cpus: 0,
..VmDefaults::default()
};
assert_eq!(
vm.effective_cpus(),
arcbox_hypervisor::default_vm_cpu_count()
);
}
#[test]
fn test_effective_cpus_explicit_passes_through() {
let vm = VmDefaults {
cpus: 3,
..VmDefaults::default()
};
assert_eq!(vm.effective_cpus(), 3);
}
#[test]
fn test_config_paths() {
let config = Config::default();
assert!(config.images_dir().ends_with("data/images"));
assert!(config.containers_dir().ends_with("data/containers"));
assert!(config.machines_dir().ends_with("data/machines"));
assert!(config.volumes_dir().ends_with("data/volumes"));
assert!(config.run_dir().ends_with("run"));
assert!(config.log_dir().ends_with("log"));
assert!(config.docker_img_path().ends_with("data/docker.img"));
}
}