add configs and apply::files
This commit is contained in:
45
src/apply.rs
Normal file
45
src/apply.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use eyre::Result;
|
||||
use log::info;
|
||||
use std::path::Path;
|
||||
use tokio::fs;
|
||||
|
||||
pub async fn files(files: &[crate::File], root: &str) -> Result<()> {
|
||||
for file in files {
|
||||
let path = chroot(root, &file.path);
|
||||
let path = Path::new(&path);
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
use crate::FileKind as K;
|
||||
match &file.kind {
|
||||
K::Content(content) => fs::write(path, content.as_bytes()).await?,
|
||||
K::Dir(true) => fs::create_dir(path).await?,
|
||||
K::Dir(false) => {} // shouldn't happen, but semantic is to ignore
|
||||
K::Symlink(tgt) => fs::symlink(tgt, path).await?,
|
||||
}
|
||||
|
||||
match file.kind {
|
||||
K::Symlink(_) => {}
|
||||
_ => set_perms(path, file.mode).await?,
|
||||
}
|
||||
|
||||
info!("created {}", file.path);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_perms(path: impl AsRef<Path>, mode: Option<u32>) -> std::io::Result<()> {
|
||||
if let Some(mode) = mode.filter(|m| *m != 0) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = std::fs::Permissions::from_mode(mode);
|
||||
fs::set_permissions(path, mode).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn chroot(root: &str, path: &str) -> String {
|
||||
format!("{root}/{}", path.trim_start_matches(|c| c == '/'))
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use eyre::{format_err, Result};
|
||||
use eyre::{Result, format_err};
|
||||
use futures_util::StreamExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
|
192
src/bootstrap.rs
Normal file
192
src/bootstrap.rs
Normal file
@ -0,0 +1,192 @@
|
||||
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,
|
||||
pub regexps: Vec<String>,
|
||||
}
|
||||
|
||||
#[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,
|
||||
pub regexps: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CryptDev {
|
||||
pub name: String,
|
||||
#[serde(flatten)]
|
||||
pub filter: DevFilter,
|
||||
pub optional: Option<bool>,
|
||||
}
|
||||
impl CryptDev {
|
||||
pub fn optional(&self) -> bool {
|
||||
self.optional.unwrap_or_else(|| self.filter.is_prefix())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DevFilter {
|
||||
Dev(String),
|
||||
Prefix(String),
|
||||
}
|
||||
impl DevFilter {
|
||||
pub fn is_dev(&self) -> bool {
|
||||
match self {
|
||||
Self::Dev(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub fn is_prefix(&self) -> bool {
|
||||
match self {
|
||||
Self::Prefix(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
64
src/lib.rs
64
src/lib.rs
@ -1 +1,65 @@
|
||||
pub mod apply;
|
||||
pub mod bootstrap;
|
||||
pub mod dls;
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Config {
|
||||
pub layers: Vec<String>,
|
||||
pub root_user: RootUser,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub mounts: Vec<Mount>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub files: Vec<File>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub groups: Vec<Group>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct RootUser {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub password_hash: Option<String>,
|
||||
pub authorized_keys: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Mount {
|
||||
pub r#type: Option<String>,
|
||||
pub dev: String,
|
||||
pub path: String,
|
||||
pub options: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Group {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uid: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct File {
|
||||
pub path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<u32>,
|
||||
#[serde(flatten)]
|
||||
pub kind: FileKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FileKind {
|
||||
Content(String),
|
||||
Symlink(String),
|
||||
Dir(bool),
|
||||
}
|
||||
|
Reference in New Issue
Block a user