diff options
-rw-r--r-- | Cargo.lock | 26 | ||||
-rw-r--r-- | zone_nspawn/Cargo.toml | 3 | ||||
-rw-r--r-- | zone_nspawn/src/config.rs | 40 | ||||
-rw-r--r-- | zone_nspawn/src/container.rs | 113 | ||||
-rw-r--r-- | zone_nspawn/src/error.rs | 12 | ||||
-rw-r--r-- | zone_nspawn/src/lib.rs | 36 | ||||
-rw-r--r-- | zone_nspawn/src/network.rs | 163 | ||||
-rw-r--r-- | zone_nspawn/src/nspawn.rs | 29 | ||||
-rw-r--r-- | zoned/src/api.rs | 9 | ||||
-rw-r--r-- | zoned/src/config.rs | 4 | ||||
-rw-r--r-- | zoned/src/main.rs | 7 |
11 files changed, 412 insertions, 30 deletions
@@ -1249,6 +1249,12 @@ dependencies = [ ] [[package]] +name = "result" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194d8e591e405d1eecf28819740abed6d719d1a2db87fc0bcdedee9a26d55560" + +[[package]] name = "rocket" version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1498,6 +1504,17 @@ dependencies = [ ] [[package]] +name = "serde_ini" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb236687e2bb073a7521c021949be944641e671b8505a94069ca37b656c81139" +dependencies = [ + "result", + "serde", + "void", +] + +[[package]] name = "serde_json" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2067,6 +2084,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2238,7 +2261,10 @@ dependencies = [ name = "zone_nspawn" version = "0.1.0" dependencies = [ + "derive_builder", + "figment", "serde", + "serde_ini", "serde_json", "thiserror", ] diff --git a/zone_nspawn/Cargo.toml b/zone_nspawn/Cargo.toml index 3aa2d05..18c8cad 100644 --- a/zone_nspawn/Cargo.toml +++ b/zone_nspawn/Cargo.toml @@ -8,3 +8,6 @@ edition = "2021" thiserror = "1.0.30" serde = "1.0.136" serde_json = "1.0.78" +derive_builder = "0.10.2" +serde_ini = "0.2.0" +figment = "0.10.6" diff --git a/zone_nspawn/src/config.rs b/zone_nspawn/src/config.rs new file mode 100644 index 0000000..3c13ff5 --- /dev/null +++ b/zone_nspawn/src/config.rs @@ -0,0 +1,40 @@ +use figment::{ + error::Result, + providers::{Format, Serialized, Toml}, + value::{Dict, Map}, + Figment, Metadata, Profile, Provider, +}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Config { + pub network_configs_path: Option<PathBuf>, +} + +impl Default for Config { + fn default() -> Self { + Config { + network_configs_path: None, + } + } +} + +impl Config { + pub fn from<T: Provider>(provider: T) -> Result<Config> { + Figment::from(provider).extract() + } + + pub fn figment() -> Figment { + Figment::from(Config::default()).merge(Toml::file("NSpawn.toml").nested()) + } +} + +impl Provider for Config { + fn metadata(&self) -> Metadata { + Metadata::named("ZFS Config") + } + + fn data(&self) -> Result<Map<Profile, Dict>> { + Serialized::defaults(Config::default()).data() + } +} diff --git a/zone_nspawn/src/container.rs b/zone_nspawn/src/container.rs new file mode 100644 index 0000000..5a1dc71 --- /dev/null +++ b/zone_nspawn/src/container.rs @@ -0,0 +1,113 @@ +use crate::Config; +use crate::NetworkConfig; +use crate::{Error, Result}; +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; +use serde_ini::de::from_bufread; +use std::fs::File; +use std::io::BufReader; +use std::{collections::HashMap, ffi::OsString, fs, path::PathBuf, process::Command}; + +#[derive(Builder, Debug, Serialize, Deserialize, Clone)] +#[builder(build_fn(private, name = "build_container"), derive(Debug))] +pub struct Container { + pub machine: OsString, + + #[builder(setter(skip))] + pub class: OsString, + + #[builder(setter(skip))] + pub service: OsString, + + #[builder(setter(skip))] + pub os: OsString, + + #[builder(setter(skip))] + pub version: OsString, + + #[builder(setter(skip))] + pub addresses: OsString, + + #[builder(setter(each = "add_network_config"))] + #[serde(skip_deserializing)] + pub network_configs: HashMap<PathBuf, NetworkConfig>, + + #[builder(setter(each = "add_option"))] + #[serde(skip_deserializing)] + pub options: Vec<String>, +} + +impl Container { + fn create(&self) -> Result<Container> { + // TODO + // exec systemd-nspawn --settings=trusted --quiet --console=interactive + // --link-journal=no --resolv-conf=off --timezone=off --capability=all + // --boot --directory=${SRVRPATH} --private-users=false --bind-ro=/sys/module + // --bind-ro=/lib/modules --network-zone=${USERNAME} + // --network-veth-extra=vn-${SRVRNAME}:host9 + Command::new("systemd-nspawn") + .args(&self.options) + .status()? + .success() + .then(|| self) + .ok_or_else(|| Error::NSpawn(format!("Failed to create container: {:?}", self)))? + .network_configuration() + .map(|_| self.to_owned()) + } + + fn network_configuration(&self) -> Result<()> { + for (filebuf, network_config) in self.network_configs.iter() { + fs::write(filebuf, serde_ini::ser::to_vec(network_config)?)?; + } + + Ok(()) + } +} + +impl ContainerBuilder { + pub fn build(&self) -> Result<Container> { + self.build_container()?.create() + } + + pub fn build_with_config(&mut self, config: &Config) -> Result<Container> { + self.add_network_configs_from_config(config)? + .build_container()? + .create() + } + + fn add_network_configs_from_config(&mut self, config: &Config) -> Result<&mut Self> { + let dir = match &config.network_configs_path { + Some(it) => it, + None => { + return Err(Error::FileError(format!( + "Failed to create container: {:?}", + self + ))) + } + }; + + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + if let Ok(entry) = entry { + let filename = entry.file_name(); + let file = File::open(entry.path())?; + let file_contents = + match from_bufread( + BufReader::new(file), + ) { + Ok(it) => it, + Err(_) => { + return Err(Error::FileError(format!( + "Failed to create container: {:?}", + self + ))) + } + }; + self.add_network_config((PathBuf::from(filename), file_contents)); + } + } + } + + Ok(self) + } +} diff --git a/zone_nspawn/src/error.rs b/zone_nspawn/src/error.rs index e5af696..c1d0601 100644 --- a/zone_nspawn/src/error.rs +++ b/zone_nspawn/src/error.rs @@ -16,4 +16,16 @@ pub enum Error { #[error("Failed to parse Command output: {0:?}")] Utf8(#[from] std::string::FromUtf8Error), + + #[error("File Error: {0:?}")] + FileError(String), + + #[error("Config Error: {0:?}")] + Config(#[from] figment::Error), + + #[error("Builder Error: {0:?}")] + Builder(#[from] crate::container::ContainerBuilderError), + + #[error("Serialize Error: {0:?}")] + Serialize(#[from] serde_ini::ser::Error), } diff --git a/zone_nspawn/src/lib.rs b/zone_nspawn/src/lib.rs index cf218d4..0b5d256 100644 --- a/zone_nspawn/src/lib.rs +++ b/zone_nspawn/src/lib.rs @@ -1,29 +1,15 @@ -use serde::{Deserialize, Serialize}; -use std::{ffi::OsString, process::Command}; +pub use crate::config::Config; +pub use crate::container::Container; +pub use crate::container::ContainerBuilder; +pub use crate::network::NetworkConfig; +pub use crate::nspawn::NSpawn; +pub use crate::error::{Error, Result}; -pub use error::{Error, Result}; - -pub mod error; - -#[derive(Serialize, Deserialize, Clone)] -pub struct Container { - pub machine: OsString, - pub class: OsString, - pub service: OsString, - pub os: OsString, - pub version: OsString, - pub addresses: OsString, -} - -/// Uses Command to call to machinectl list -o json -pub fn get_containers() -> Result<Vec<Container>> { - Command::new("machinectl") - .arg("list") - .args(["-o", "json"]) - .output() - .map(|o| serde_json::from_slice(o.stdout.as_slice()))? - .map_err(Error::from) -} +mod config; +mod container; +mod error; +mod network; +mod nspawn; #[cfg(test)] mod tests { diff --git a/zone_nspawn/src/network.rs b/zone_nspawn/src/network.rs new file mode 100644 index 0000000..a98b416 --- /dev/null +++ b/zone_nspawn/src/network.rs @@ -0,0 +1,163 @@ +use derive_builder::Builder; +use serde::{Deserialize, Serialize}; +use std::ffi::OsString; + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct NetworkConfig { + pub net_match: Option<Match>, + pub link: Option<Link>, + pub network: Option<Network>, + pub address: Option<Address>, + pub route: Option<Route>, + pub dhcpv4: Option<DHCPv4>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +#[serde(rename = "Match")] +pub struct Match { + pub name: Option<OsString>, + #[serde(rename = "MACAddress")] + pub mac: Option<OsString>, + pub host: Option<OsString>, + #[serde(rename = "Virtualization")] + pub virt: Option<bool>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct Link { + #[serde(rename = "MACAddress")] + pub mac: Option<OsString>, + #[serde(rename = "MTUBytes")] + pub mtubytes: Option<u64>, + pub muticast: Option<bool>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct Network { + #[serde(rename = "DHCP")] + pub dhcp: Option<OsString>, + #[serde(rename = "DHCPServer")] + pub dhcpserver: Option<bool>, + #[serde(rename = "MulticastDNS")] + pub multicast_dns: Option<OsString>, + #[serde(rename = "DNSSEC")] + pub dnssec: Option<OsString>, + #[serde(rename = "DNS")] + pub dns: Option<OsString>, + pub domains: Option<OsString>, + #[serde(rename = "IPForward")] + pub ipforward: Option<OsString>, + #[serde(rename = "IPMasquerade")] + pub ipmasquerade: Option<OsString>, + #[serde(rename = "IPv6PrivacyExtensions")] + pub ipv6_privacy_extensions: Option<OsString>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct Address { + pub address: Option<OsString>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct Route { + pub gateway: Option<OsString>, + pub destination: Option<OsString>, +} + +#[derive(Debug, Builder, Clone, Deserialize, Serialize)] +#[builder(setter(strip_option), default)] +#[serde(rename_all = "PascalCase")] +pub struct DHCPv4 { + #[serde(rename = "UseDNS")] + pub use_dns: Option<bool>, + pub anonymize: Option<bool>, + #[serde(rename = "UseDomains")] + pub use_domains: Option<OsString>, +} + +impl Default for NetworkConfig { + fn default() -> Self { + NetworkConfig { + net_match: Some(Match::default()), + link: Some(Link::default()), + network: Some(Network::default()), + address: Some(Address::default()), + route: Some(Route::default()), + dhcpv4: Some(DHCPv4::default()), + } + } +} + +impl Default for Match { + fn default() -> Self { + Match { + name: None, + mac: None, + host: None, + virt: None, + } + } +} + +impl Default for Link { + fn default() -> Self { + Link { + mac: None, + mtubytes: None, + muticast: None, + } + } +} + +impl Default for Network { + fn default() -> Self { + Network { + dhcp: Some(OsString::from("false")), + dhcpserver: Some(false), + multicast_dns: Some(OsString::from("false")), + dnssec: Some(OsString::from("false")), + dns: None, + domains: None, + ipforward: Some(OsString::from("false")), + ipmasquerade: Some(OsString::from("no")), + ipv6_privacy_extensions: Some(OsString::from("false")), + } + } +} + +impl Default for Address { + fn default() -> Self { + Address { address: None } + } +} + +impl Default for Route { + fn default() -> Self { + Route { + gateway: None, + destination: None, + } + } +} + +impl Default for DHCPv4 { + fn default() -> Self { + DHCPv4 { + use_dns: Some(true), + anonymize: Some(false), + use_domains: Some(OsString::from("false")), + } + } +} diff --git a/zone_nspawn/src/nspawn.rs b/zone_nspawn/src/nspawn.rs new file mode 100644 index 0000000..bf58303 --- /dev/null +++ b/zone_nspawn/src/nspawn.rs @@ -0,0 +1,29 @@ +use crate::{Config, Container, Error, Result}; +use figment::{Figment, Provider}; +use std::process::Command; +#[derive(Default, Debug)] +pub struct NSpawn { + pub config: Config, +} + +impl NSpawn { + pub fn new() -> Result<Self> { + Self::custom(Config::figment()) + } + + pub fn custom<T: Provider>(provider: T) -> Result<Self> { + Config::from(Figment::from(provider)) + .map_err(Error::from) + .map(|config| Self { config }) + } + + /// Uses Command to call to machinectl list -o json + pub fn get_containers() -> Result<Vec<Container>> { + Command::new("machinectl") + .arg("list") + .args(["-o", "json"]) + .output() + .map(|o| serde_json::from_slice(o.stdout.as_slice()))? + .map_err(Error::from) + } +} diff --git a/zoned/src/api.rs b/zoned/src/api.rs index 64c466b..3bf5923 100644 --- a/zoned/src/api.rs +++ b/zoned/src/api.rs @@ -25,10 +25,11 @@ pub fn test_endpoint(zfs: &State<zone_zfs::ZFS>) -> Json<String> { #[openapi(tag = "Container")] #[get("/container/list?<container..>")] pub fn container_list(container: Container) -> Result<Json<Vec<Container>>> { - zone_nspawn::get_containers() - .map(|v| container.filter_from(v)) - .map(Json::from) - .map_err(Error::from) + todo!("add nspawn to state"); + // zone_nspawn::get_containers() + // .map(|v| container.filter_from(v)) + // .map(Json::from) + // .map_err(Error::from) } /// Create container diff --git a/zoned/src/config.rs b/zoned/src/config.rs index aa16360..abbe94f 100644 --- a/zoned/src/config.rs +++ b/zoned/src/config.rs @@ -5,4 +5,6 @@ pub struct Config { pub(crate) rocket_config: rocket::Config, pub(crate) zfs_config: zone_zfs::Config, -} + + pub(crate) network_configs: Vec<zone_nspawn::NetworkConfig>, +}
\ No newline at end of file diff --git a/zoned/src/main.rs b/zoned/src/main.rs index 14743b3..c009762 100644 --- a/zoned/src/main.rs +++ b/zoned/src/main.rs @@ -21,6 +21,13 @@ async fn main() { } }; + // create empty vec + // create network_config from toml + // create network_config from toml + // ... + + // give to manage + match zoned::build_rocket(&config).manage(zfs).launch().await { Ok(()) => println!("Rocket shut down gracefully."), Err(err) => eprintln!("Rocket had an error: {}", err), |