aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock26
-rw-r--r--zone_nspawn/Cargo.toml3
-rw-r--r--zone_nspawn/src/config.rs40
-rw-r--r--zone_nspawn/src/container.rs113
-rw-r--r--zone_nspawn/src/error.rs12
-rw-r--r--zone_nspawn/src/lib.rs36
-rw-r--r--zone_nspawn/src/network.rs163
-rw-r--r--zone_nspawn/src/nspawn.rs29
-rw-r--r--zoned/src/api.rs9
-rw-r--r--zoned/src/config.rs4
-rw-r--r--zoned/src/main.rs7
11 files changed, 412 insertions, 30 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 358921b..57dcdff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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),