Files
dkl/src/bootstrap.rs
2025-11-10 16:46:13 +01:00

231 lines
5.6 KiB
Rust

use std::collections::BTreeMap as Map;
pub const TAKE_ALL: i16 = -1;
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Config {
pub anti_phishing_code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub keymap: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub modules: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resolv_conf: Option<String>,
#[serde(default)]
pub vpns: Map<String, String>,
pub networks: Vec<Network>,
pub auths: Vec<Auth>,
#[serde(default)]
pub ssh: SSHServer,
#[serde(default)]
pub pre_lvm_crypt: Vec<CryptDev>,
#[serde(default)]
pub lvm: Vec<LvmVG>,
#[serde(default)]
pub crypt: Vec<CryptDev>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signer_public_key: Option<String>,
pub bootstrap: Bootstrap,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Auth {
pub name: String,
#[serde(alias = "sshKey")]
#[serde(skip_serializing_if = "Option::is_none")]
pub ssh_key: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Network {
pub name: String,
pub interfaces: Vec<NetworkInterface>,
pub script: String,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct NetworkInterface {
pub var: String,
pub n: i16,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub regexps: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filter: Option<UdevFilter>,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct SSHServer {
pub listen: String,
pub user_ca: Option<String>,
}
impl Default for SSHServer {
fn default() -> Self {
Self {
listen: "[::]:22".to_string(),
user_ca: None,
}
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct LvmVG {
#[serde(alias = "vg")]
pub name: String,
pub pvs: LvmPV,
#[serde(default)]
pub defaults: LvmLVDefaults,
pub lvs: Vec<LvmLV>,
}
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
pub struct LvmLVDefaults {
#[serde(default)]
pub fs: Filesystem,
#[serde(default)]
pub raid: Raid,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Filesystem {
Ext4,
Xfs,
Btrfs,
Other(String),
}
impl Filesystem {
pub fn fstype(&self) -> &str {
use Filesystem as F;
match self {
F::Ext4 => "ext4",
F::Xfs => "xfs",
F::Btrfs => "btrfs",
F::Other(t) => t,
}
}
}
impl Default for Filesystem {
fn default() -> Self {
Filesystem::Ext4
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct LvmLV {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub fs: Option<Filesystem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub raid: Option<Raid>,
#[serde(flatten)]
pub size: LvSize,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum LvSize {
Size(String),
Extents(String),
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct LvmPV {
pub n: i16,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub regexps: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub udev: Option<UdevFilter>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct CryptDev {
pub name: String,
// hit the limit of enum representation here (flatten + enum variant case)
#[serde(skip_serializing_if = "Option::is_none")]
pub dev: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub udev: Option<UdevFilter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub optional: Option<bool>,
}
impl CryptDev {
pub fn filter(&self) -> DevFilter<'_> {
if let Some(dev) = self.dev.as_deref() {
DevFilter::Dev(dev)
} else if let Some(prefix) = self.prefix.as_deref() {
DevFilter::Prefix(prefix)
} else if let Some(udev) = self.udev.as_ref() {
DevFilter::Udev(udev)
} else {
DevFilter::None
}
}
pub fn optional(&self) -> bool {
self.optional.unwrap_or_else(|| match self.filter() {
DevFilter::None => true,
DevFilter::Dev(_) => false,
DevFilter::Prefix(_) => true,
DevFilter::Udev(_) => true,
})
}
}
#[test]
fn test_parse_crypt_dev() {
for s in [
"name: sys0\ndev: /dev/sda\n",
"name: crypt-\nprefix: /dev/sd\n",
"name: crypt-${name}\nudev: !glob [ DEVNAME, /dev/sd* ]\n",
] {
let dev: CryptDev = serde_yaml::from_str(s).unwrap();
dev.filter();
dev.optional();
}
}
pub enum DevFilter<'t> {
None,
Dev(&'t str),
Prefix(&'t str),
Udev(&'t UdevFilter),
}
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum UdevFilter {
Eq(String, String),
Glob(String, String),
And(Vec<UdevFilter>),
Or(Vec<UdevFilter>),
Not(Box<UdevFilter>),
}
#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub struct Raid {
pub mirrors: Option<u8>,
pub stripes: Option<u8>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct Bootstrap {
pub dev: String,
pub seed: Option<String>,
}