Compare commits

..

1 Commits

Author SHA1 Message Date
Mikaël Cluseau 9c9c231397 chore: Release init version 2.5.3 2026-02-10 17:51:16 +01:00
22 changed files with 976 additions and 1049 deletions
Generated
+802 -479
View File
File diff suppressed because it is too large Load Diff
+3 -5
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "init" name = "init"
version = "2.6.10" version = "2.5.3"
edition = "2024" edition = "2024"
[profile.release] [profile.release]
@@ -25,11 +25,9 @@ shell-escape = "0.1.5"
tokio = { version = "1.38.0", features = ["rt", "net", "fs", "process", "io-std", "io-util", "sync", "macros", "signal"] } tokio = { version = "1.38.0", features = ["rt", "net", "fs", "process", "io-std", "io-util", "sync", "macros", "signal"] }
termios = "0.3.3" termios = "0.3.3"
unix_mode = "0.1.4" unix_mode = "0.1.4"
base64 = "0.22.1"
sys-info = "0.9.1" sys-info = "0.9.1"
dkl = { git = "https://novit.tech/direktil/dkl", version = "1.0.0" } dkl = { git = "https://novit.tech/direktil/dkl", version = "1.0.0" }
openssl = "0.10.73" openssl = "0.10.73"
#reqwest = { version = "0.13.1", features = ["native-tls", "system-proxy"], default-features = false } reqwest = { version = "0.13.1", features = ["native-tls"] }
reqwest = { git = "https://github.com/mcluseau/rs-reqwest", version = "0.13.1", features = ["native-tls", "system-proxy", "socks"], default-features = false }
glob = "0.3.3" glob = "0.3.3"
hex = "0.4.3"
tar = { version = "0.4.46", default-features = false }
+7 -23
View File
@@ -1,4 +1,4 @@
from rust:1.96.0-alpine as rust from rust:1.93.0-alpine as rust
run apk add --no-cache git musl-dev libudev-zero-dev openssl-dev cryptsetup-dev lvm2-dev clang-libs clang-dev run apk add --no-cache git musl-dev libudev-zero-dev openssl-dev cryptsetup-dev lvm2-dev clang-libs clang-dev
@@ -9,7 +9,8 @@ run --mount=type=cache,id=novit-rs,target=/usr/local/cargo/registry \
RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --root /dist RUSTFLAGS="-C target-feature=-crt-static" cargo install --path . --root /dist
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from alpine:3.23.4 as system from alpine:3.23.3 as initrd
run apk add zstd lz4
workdir /system workdir /system
@@ -17,7 +18,7 @@ run . /etc/os-release \
&& wget -O- https://dl-cdn.alpinelinux.org/alpine/v${VERSION_ID%.*}/releases/x86_64/alpine-minirootfs-${VERSION_ID}-x86_64.tar.gz |tar zxv && wget -O- https://dl-cdn.alpinelinux.org/alpine/v${VERSION_ID%.*}/releases/x86_64/alpine-minirootfs-${VERSION_ID}-x86_64.tar.gz |tar zxv
run apk add --no-cache --update -p . musl libgcc coreutils \ run apk add --no-cache --update -p . musl libgcc coreutils \
ethtool iproute2 lvm2 lvm2-extra lvm2-dmeventd udev cryptsetup \ iproute2 lvm2 lvm2-extra lvm2-dmeventd udev cryptsetup \
e2fsprogs lsblk openssl openssh-server wireguard-tools-wg-quick \ e2fsprogs lsblk openssl openssh-server wireguard-tools-wg-quick \
&& rm -rf usr/share/apk var/cache/apk etc/motd dev/* && rm -rf usr/share/apk var/cache/apk etc/motd dev/*
@@ -30,26 +31,9 @@ run mkdir -p bin run var/log; cd bin && for cmd in init-version init-connect boo
# check viability # check viability
run chroot . init-version run chroot . init-version
# ------------------------------------------------------------------------ run find * |cpio -H newc -oF /initrd
from alpine:3.23.4 as initrd
copy --from=system /system /system
run cd /system && find * |cpio -H newc -oF /initrd
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from debian:stable-backports as initramfs from alpine:3.23.2
run apt update && apt install -y erofs-utils copy --from=initrd /initrd /
copy --from=system /system /system
run mkfs.erofs \
-z lzma -C131072 -Efragments,ztailpacking \
-T0 --all-time --ignore-mtime \
/initramfs /system
# ------------------------------------------------------------------------
from alpine:3.23.4
copy --from=initrd /initrd /initrd
entrypoint ["base64","/initrd"] entrypoint ["base64","/initrd"]
#copy --from=initramfs /initramfs /
#entrypoint ["base64","/initramfs"]
BIN
View File
Binary file not shown.
-62
View File
@@ -1,62 +0,0 @@
#! /bin/bash
base_initrd=dist/initrd
flavor=dhcp
dir=tmp/$flavor-initrd
dist=dist/$flavor
uki=$dist/bootx64.efi
linux_v=6.18.34
set -ex
mkdir -p tmp/dl $dist
linux=tmp/dl/linux-$linux_v
modules=tmp/dl/modules-$linux_v
[ -e $linux ] || curl -o $linux https://dkl.novit.io/dist/kernels/6.18.35
[ -e $modules ] || curl -o $modules https://dkl.novit.io/dist/layers/modules/6.18.35.erofs
rm -fr $dir
mkdir $dir
cpio --quiet --extract --file $base_initrd --directory $dir
cp -rv $flavor/. $dir
cp $modules $dir/modules.fs
(cd $dir && find * |cpio --create -H newc -R 0:0) >$dir.cpio
if cpio -tF $dir.cpio 2>&1 |grep bytes.of.junk; then echo "bad cpio archive"; exit 1; fi
zstd -12 -T0 -vf $dir.cpio &&
mv $dir.cpio.zst $dist/initrd
cp $linux $dist/vmlinuz
ukify build --output $uki --os-release "Direktil DHCP" \
--linux $linux --initrd $dist/initrd
MB=$(( 2**20 ))
sz=$(( ( $(stat -c %s $uki) + MB ) / MB + 2 ))
efi=$dist/efi.img
if [ -e $efi ]; then rm $efi; fi
truncate -s ${sz}M $efi
sgdisk -n 1:2048:0 -t 1:ef00 -c 1:"EFI System" $efi
offset=$(( 2048 * 512 ))
export MTOOLSRC=$(mktemp)
trap "rm -f $MTOOLSRC" exit
echo "drive e: file=\"$efi\" offset=$offset" >$MTOOLSRC
args="-i $efi@@$offset"
mformat -F e:
mmd e:EFI e:EFI/BOOT
mcopy $uki e:EFI/BOOT/BOOTX64.EFI
+1 -1
View File
@@ -1,7 +1,7 @@
use std::process::Command; use std::process::Command;
fn main() { fn main() {
let output = Command::new("git") let output = Command::new("git")
.args(["rev-parse", "HEAD"]) .args(&["rev-parse", "HEAD"])
.output() .output()
.unwrap(); .unwrap();
let git_commit = String::from_utf8(output.stdout).unwrap(); let git_commit = String::from_utf8(output.stdout).unwrap();
-37
View File
@@ -1,37 +0,0 @@
---
anti_phishing_code: "direktil<3"
modules: /modules.fs
auths:
- name: adm1@novit
sshKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICkpbU6sf4t0f6XAv9DuW3XH5iLM0AI5rc8PT2jwea1N
- name: adm2@novit
sshKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILIomzqVAIqb7BedauhAo2VgbLqme5Jx/vjGUqZLoJqF
ssh:
listen: "[::]:22"
networks:
- name: loopback
interfaces: [ { var: iface, n: 1, udev: !eq [INTERFACE, lo] } ]
script: |
ip a add 127.0.0.1/8 dev lo
ip a add ::1/128 dev lo
ip li set lo up
- name: main
interfaces:
- var: ifaces
n: -1
udev: !has ID_NET_NAME_MAC
script: |
ip link add main type bond mode active-backup
for l in $ifaces; do
ip link set $l master main
ip link set $l up
done
ip link set main up
udhcpc -b -i main
bootstrap:
dev: /dev/storage/bootstrap
+17 -96
View File
@@ -1,10 +1,10 @@
use eyre::{Result, format_err}; use eyre::{format_err, Result};
use log::{error, info, warn}; use log::{error, info, warn};
use std::collections::BTreeSet as Set; use std::collections::BTreeSet as Set;
use std::convert::Infallible; use std::convert::Infallible;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::{fs, io::AsyncReadExt, process::Command}; use tokio::{fs, process::Command};
use crate::{cmd::version::version_string, dklog, input, utils}; use crate::{cmd::version::version_string, dklog, input, utils};
use dkl::bootstrap::Config; use dkl::bootstrap::Config;
@@ -54,10 +54,10 @@ pub async fn run() {
info!("Linux version {kernel_version}"); info!("Linux version {kernel_version}");
// mount basic filesystems // mount basic filesystems
mount(None::<&str>, "/proc", "proc", None).await; mount(None, "/proc", "proc", None).await;
mount(None::<&str>, "/sys", "sysfs", None).await; mount(None, "/sys", "sysfs", None).await;
mount(None::<&str>, "/dev", "devtmpfs", None).await; mount(None, "/dev", "devtmpfs", None).await;
mount(None::<&str>, "/dev/pts", "devpts", Some("gid=5,mode=620")).await; mount(None, "/dev/pts", "devpts", Some("gid=5,mode=620")).await;
if utils::bool_param("debug") { if utils::bool_param("debug") {
log::set_max_level(log::LevelFilter::Debug); log::set_max_level(log::LevelFilter::Debug);
@@ -144,9 +144,6 @@ pub async fn run() {
warn!("failed to copy {INIT_LOG} to system: {e}"); warn!("failed to copy {INIT_LOG} to system: {e}");
} }
if let Err(e) = nix::mount::umount2("/modules", nix::mount::MntFlags::MNT_DETACH) {
warn!("failed to umount /modules: {e}");
}
retry(async || switch_root("/system").await).await; retry(async || switch_root("/system").await).await;
} }
@@ -154,8 +151,7 @@ use std::path::Path;
async fn mount_modules(modules: &str, kernel_version: &str) -> Result<()> { async fn mount_modules(modules: &str, kernel_version: &str) -> Result<()> {
info!("mounting modules"); info!("mounting modules");
mount(Some(modules), "/modules", "squashfs", None).await;
mount_ro_fs(modules, "/modules").await?;
fs::create_dir_all("/lib/modules").await?; fs::create_dir_all("/lib/modules").await?;
let modules_path = &format!("/modules/lib/modules/{kernel_version}"); let modules_path = &format!("/modules/lib/modules/{kernel_version}");
@@ -167,13 +163,6 @@ async fn mount_modules(modules: &str, kernel_version: &str) -> Result<()> {
} }
symlink(modules_path, format!("/lib/modules/{kernel_version}"))?; symlink(modules_path, format!("/lib/modules/{kernel_version}"))?;
let firmware_path = &format!("/modules/lib/firmware/{kernel_version}");
if std::fs::exists(firmware_path)? {
fs::create_dir_all("/lib/firmware").await?;
symlink(firmware_path, format!("/lib/firmware/{kernel_version}"))?;
}
Ok(()) Ok(())
} }
@@ -185,32 +174,18 @@ async fn chmod(path: impl AsRef<Path>, mode: u32) -> std::io::Result<()> {
fs::set_permissions(path, perms).await fs::set_permissions(path, perms).await
} }
async fn mount<S: AsRef<Path>>( async fn mount(src: Option<&str>, dst: &str, fstype: &str, opts: Option<&str>) {
src: Option<S>,
dst: impl AsRef<Path>,
fstype: &str,
opts: Option<&str>,
) {
let src = src.as_ref().map(|s| s.as_ref());
let src_str = src.map(|s| s.display().to_string());
let src_str = src_str.as_deref();
let dst = dst.as_ref();
let dst_str = &dst.display().to_string();
if let Err(e) = fs::create_dir_all(dst).await { if let Err(e) = fs::create_dir_all(dst).await {
error!("failed to create dir {dst_str}: {e}"); error!("failed to create dir {dst}: {e}");
} }
retry_or_ignore(async || { retry_or_ignore(async || {
let mut is_file = false; let mut is_file = false;
if let Some(src) = src_str { if let Some(src) = src {
is_file = (fs::metadata(src).await) is_file = (fs::metadata(src).await)
.map_err(|e| format_err!("stat {src} failed: {e}"))? .map_err(|e| format_err!("stat {src} failed: {e}"))?
.is_file(); .is_file();
#[allow(clippy::single_match)] // may be extended later
match fstype { match fstype {
"ext4" => { "ext4" => {
exec("fsck.ext4", &["-p", src]).await; exec("fsck.ext4", &["-p", src]).await;
@@ -219,7 +194,7 @@ async fn mount<S: AsRef<Path>>(
} }
} }
let mut args = vec![src_str.unwrap_or("none"), dst_str, "-t", fstype]; let mut args = vec![src.unwrap_or("none"), dst, "-t", fstype];
if let Some(opts) = opts { if let Some(opts) = opts {
args.extend(["-o", opts]); args.extend(["-o", opts]);
} }
@@ -231,50 +206,15 @@ async fn mount<S: AsRef<Path>>(
} }
let (cmd_str, _) = cmd_str("mount", &args); let (cmd_str, _) = cmd_str("mount", &args);
let flags = nix::mount::MsFlags::empty();
info!("# {cmd_str}",); info!("# {cmd_str}",);
nix::mount::mount(src, dst, Some(fstype), flags, opts)
let mount = |flags| nix::mount::mount(src, dst, Some(fstype), flags, opts); .map_err(|e| format_err!("mount {dst} failed: {e}"))
use nix::{errno::Errno, mount::MsFlags};
match mount(MsFlags::empty()) {
Err(Errno::EACCES) => mount(MsFlags::MS_RDONLY),
r => r,
}
.map_err(|e| format_err!("mount {dst_str} failed: {e}"))
}) })
.await .await
} }
async fn mount_ro_fs(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
let src = src.as_ref();
let dst = dst.as_ref();
// identify RO fs type
let mut buf = [0u8; 1028];
fs::File::open(src)
.await
.map_err(|e| format_err!("open {}: {e}", src.display()))?
.read_exact(&mut buf)
.await
.map_err(|e| format_err!("read {}: {e}", src.display()))?;
let fstype = if buf[1024..1028] == 0xE0F5E1E2u32.to_le_bytes() {
"erofs"
} else {
"squashfs"
};
if let Err(e) = fs::create_dir_all(dst).await {
error!("failed to create dir {dst}: {e}", dst = dst.display());
}
let mut cmd = Command::new("mount");
cmd.args(["-t", fstype]).arg(src).arg(dst);
try_exec_cmd(cmd).await
}
async fn start_daemon(prog: &str, args: &[&str]) { async fn start_daemon(prog: &str, args: &[&str]) {
let (cmd_str, mut cmd) = cmd_str(prog, args); let (cmd_str, mut cmd) = cmd_str(prog, args);
retry_or_ignore(async || { retry_or_ignore(async || {
@@ -285,25 +225,6 @@ async fn start_daemon(prog: &str, args: &[&str]) {
.await; .await;
} }
async fn try_exec_cmd(mut cmd: Command) -> Result<()> {
info!(
"# {} {}",
cmd.as_std().get_program().to_string_lossy(),
cmd.as_std()
.get_args()
.map(|a| a.to_string_lossy())
.collect::<Vec<_>>()
.join(" ")
);
let s = cmd.status().await?;
if s.success() {
Ok(())
} else {
Err(format_err!("command failed: {s}"))
}
}
async fn try_exec(prog: &str, args: &[&str]) -> Result<()> { async fn try_exec(prog: &str, args: &[&str]) -> Result<()> {
let (cmd_str, mut cmd) = cmd_str(prog, args); let (cmd_str, mut cmd) = cmd_str(prog, args);
info!("# {cmd_str}"); info!("# {cmd_str}");
@@ -387,9 +308,9 @@ fn cmd_str(prog: &str, args: &[&str]) -> (String, Command) {
#[allow(unused)] #[allow(unused)]
async fn child_reaper() { async fn child_reaper() {
use nix::sys::wait::{WaitPidFlag, waitpid}; use nix::sys::wait::{waitpid, WaitPidFlag};
use nix::unistd::Pid; use nix::unistd::Pid;
use tokio::signal::unix::{SignalKind, signal}; use tokio::signal::unix::{signal, SignalKind};
let Ok(mut sigs) = let Ok(mut sigs) =
signal(SignalKind::child()).inspect_err(|e| warn!("failed to setup SIGCHLD handler: {e}")) signal(SignalKind::child()).inspect_err(|e| warn!("failed to setup SIGCHLD handler: {e}"))
@@ -407,7 +328,7 @@ async fn switch_root(root: &str) -> Result<Infallible> {
info!("killing all processes and switching root"); info!("killing all processes and switching root");
dklog::LOG.close().await; dklog::LOG.close().await;
use nix::sys::signal::{SIGKILL, kill}; use nix::sys::signal::{kill, SIGKILL};
use nix::unistd::Pid; use nix::unistd::Pid;
if let Err(e) = kill(Pid::from_raw(-1), SIGKILL) { if let Err(e) = kill(Pid::from_raw(-1), SIGKILL) {
+68 -268
View File
@@ -1,24 +1,22 @@
use eyre::{Result, format_err}; use eyre::{format_err, Result};
use log::{debug, info, warn}; use log::{info, warn};
use std::path::{Path, PathBuf};
use tokio::{ use tokio::{
fs, fs,
io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
}; };
use dkl::{ use dkl::{
self, self,
apply::{self, chroot, set_perms}, apply::{self, chroot, set_perms},
base64_decode,
bootstrap::Config, bootstrap::Config,
}; };
use super::{exec, mount, mount_ro_fs, retry, retry_or_ignore, try_exec, try_exec_cmd}; use super::{exec, mount, retry, retry_or_ignore, try_exec};
use crate::{fs::walk_dir, utils}; use crate::{fs::walk_dir, utils};
pub async fn bootstrap(cfg: Config) { pub async fn bootstrap(cfg: Config) {
let verifier = retry(async || Verifier::from_config(&cfg)).await; let verifier = retry(async || Verifier::from_config(&cfg)).await;
let bs = &cfg.bootstrap; let bs = cfg.bootstrap;
mount(Some(&bs.dev), "/bootstrap", "ext4", None).await; mount(Some(&bs.dev), "/bootstrap", "ext4", None).await;
@@ -49,12 +47,12 @@ pub async fn bootstrap(cfg: Config) {
.await; .await;
let sys_cfg: dkl::Config = retry(async || { let sys_cfg: dkl::Config = retry(async || {
let sys_cfg_bytes = seed_config(base_dir, bs, &verifier).await?; let sys_cfg_bytes = seed_config(base_dir, &bs.seed, &verifier).await?;
Ok(serde_yaml::from_slice(&sys_cfg_bytes)?) Ok(serde_yaml::from_slice(&sys_cfg_bytes)?)
}) })
.await; .await;
mount_system(&sys_cfg, &cfg, base_dir, &verifier).await; mount_system(&sys_cfg, base_dir, &verifier).await;
retry_or_ignore(async || { retry_or_ignore(async || {
let path = "/etc/resolv.conf"; let path = "/etc/resolv.conf";
@@ -66,7 +64,7 @@ pub async fn bootstrap(cfg: Config) {
}) })
.await; .await;
retry_or_ignore(async || apply::files(&sys_cfg.files, "/system", false).await).await; retry_or_ignore(async || apply::files(&sys_cfg.files, "/system").await).await;
apply_groups(&sys_cfg.groups, "/system").await; apply_groups(&sys_cfg.groups, "/system").await;
apply_users(&sys_cfg.users, "/system").await; apply_users(&sys_cfg.users, "/system").await;
@@ -79,30 +77,7 @@ pub async fn bootstrap(cfg: Config) {
}) })
.await; .await;
exec("chroot", &["/system", "update-ca-certificates"]).await; exec("chroot", &["/system", "update-ca-certificates"]).await
// activate ttyS* consoles as needed
retry_or_ignore(async || {
const PATH: &str = "/system/etc/inittab";
let mut inittab = fs::read_to_string(PATH).await?;
let mut changed = false;
for opt in utils::cmdline().filter_map(|s| s.strip_prefix("console=ttyS")) {
info!("inittab: adding entry for ttyS{opt}");
changed = true;
let mut params = opt.split(',');
let num = params.next().unwrap();
let speed = params.next().unwrap_or("115200");
inittab.push_str(&format!(
"S{num}:12345:respawn:/sbin/agetty --noclear {speed} ttyS{num} linux\n"
));
}
if changed {
fs::write(PATH, inittab.as_bytes()).await?;
}
Ok(())
})
.await;
} }
struct Verifier { struct Verifier {
@@ -114,27 +89,24 @@ impl Verifier {
return Ok(Self { pubkey: None }); return Ok(Self { pubkey: None });
}; };
let pubkey = base64_decode(pubkey)?; use base64::{prelude::BASE64_STANDARD, Engine};
let pubkey = BASE64_STANDARD.decode(pubkey)?;
let pubkey = Some(pubkey); let pubkey = Some(pubkey);
Ok(Self { pubkey }) return Ok(Self { pubkey });
} }
async fn verify_path(&self, path: impl AsRef<Path>) -> Result<Vec<u8>> { async fn verify_path(&self, path: &str) -> Result<Vec<u8>> {
let path = path.as_ref(); let data = (fs::read(path).await).map_err(|e| format_err!("failed to read {path}: {e}"))?;
let p = path.display();
let data = (fs::read(path).await).map_err(|e| format_err!("failed to read {p}: {e}"))?;
let Some(ref pubkey) = self.pubkey else { let Some(ref pubkey) = self.pubkey else {
return Ok(data); return Ok(data);
}; };
info!("verifying {p}"); info!("verifying {path}");
let sig = path.with_added_extension("sig"); let sig = &format!("{path}.sig");
let sig = (fs::read(&sig).await) let sig = (fs::read(sig).await).map_err(|e| format_err!("failed to read {sig}: {e}"))?;
.map_err(|e| format_err!("failed to read {}: {e}", sig.display()))?;
use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier}; use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier};
let pubkey = PKey::public_key_from_der(pubkey)?; let pubkey = PKey::public_key_from_der(pubkey)?;
@@ -146,91 +118,51 @@ impl Verifier {
if sig_ok { if sig_ok {
Ok(data) Ok(data)
} else { } else {
Err(format_err!("signature verification failed for {p}")) Err(format_err!("signature verification failed for {path}"))
} }
} }
} }
async fn seed_config( async fn seed_config(
base_dir: impl Into<PathBuf>, base_dir: &str,
bs: &dkl::bootstrap::Bootstrap, seed_url: &Option<String>,
verifier: &Verifier, verifier: &Verifier,
) -> Result<Vec<u8>> { ) -> Result<Vec<u8>> {
let base_dir = base_dir.into(); let cfg_path = &format!("{base_dir}/config.yaml");
let cfg_path = base_dir.join("config.yaml"); if fs::try_exists(cfg_path).await? {
return Ok(fs::read(cfg_path).await?);
if fs::try_exists(&cfg_path).await? {
return verifier.verify_path(cfg_path).await;
} }
let bs_tar = "/bootstrap.tar"; let bs_tar = "/bootstrap.tar";
if !fs::try_exists(&bs_tar).await? { if !fs::try_exists(bs_tar).await? {
if bs.seed.is_none() { if let Some(seed_url) = seed_url.as_ref() {
fetch_bootstrap(seed_url, bs_tar).await?;
} else {
return Err(format_err!( return Err(format_err!(
"no {}, no {bs_tar} and no seed URL, can't bootstrap", "no {cfg_path}, no {bs_tar} and no seed, can't bootstrap"
cfg_path.display()
)); ));
} }
fetch_bootstrap(bs, bs_tar).await?;
} }
let tmp_dir = base_dir.with_added_extension("new"); try_exec("tar", &["xf", bs_tar, "-C", base_dir]).await?;
fs::create_dir_all(&tmp_dir).await?;
untar(bs_tar, &tmp_dir) if !fs::try_exists(cfg_path).await? {
.await return Err(format_err!("{cfg_path} does not exist after seeding"));
.map_err(|e| format_err!("untar failed: {e}"))?;
let cfg_path = tmp_dir.join("config.yaml");
if !fs::try_exists(&cfg_path).await? {
return Err(format_err!(
"{} does not exist after seeding",
cfg_path.display()
));
} }
let cfg_bytes = verifier.verify_path(&cfg_path).await?; verifier.verify_path(&cfg_path).await
fs::rename(tmp_dir, base_dir).await?;
Ok(cfg_bytes)
} }
async fn fetch_bootstrap(bs: &dkl::bootstrap::Bootstrap, output_file: &str) -> Result<()> { async fn fetch_bootstrap(seed_url: &str, output_file: &str) -> Result<()> {
let seed_url: reqwest::Url = (bs.seed.as_ref()) let seed_url: reqwest::Url = seed_url.parse()?;
.ok_or(format_err!("no seed URL"))?
.parse()
.map_err(|e| format_err!("invalid seed URL: {e}"))?;
info!( info!(
"fetching {output_file} from {}", "fetching {output_file} from {}",
seed_url.host_str().unwrap_or("<no host>") seed_url.host_str().unwrap_or("<no host>")
); );
let mut builder = reqwest::Client::builder(); let resp = reqwest::get(seed_url).await?;
if let Some(ref proxy) = bs.seed_proxy {
debug!("using proxy {proxy}");
let proxy = reqwest::Proxy::all(proxy) //
.map_err(|e| format_err!("seed proxy setup failed: {e}"))?;
builder = builder.proxy(proxy);
}
if let Some(ref ca) = bs.seed_ca {
debug!("using custom CA certificate");
let ca = base64_decode(ca).map_err(|e| format_err!("invalid seed CA: decode: {e}"))?;
let ca = reqwest::Certificate::from_der(&ca)
.map_err(|e| format_err!("invalid seed CA: parse: {e}"))?;
builder = builder.tls_certs_only([ca]);
}
if let Some(ref sn) = bs.seed_servername {
debug!("tls server name: {sn}");
builder = builder.tls_server_name(bs.seed_servername.clone());
}
let req = builder.build()?.get(seed_url);
let resp = req.send().await?;
if !resp.status().is_success() { if !resp.status().is_success() {
return Err(format_err!("HTTP request failed: {}", resp.status())); return Err(format_err!("HTTP request failed: {}", resp.status()));
@@ -244,18 +176,6 @@ async fn fetch_bootstrap(bs: &dkl::bootstrap::Bootstrap, output_file: &str) -> R
Ok(()) Ok(())
} }
async fn untar(arch: impl Into<PathBuf>, target: impl Into<PathBuf>) -> Result<()> {
let arch = arch.into();
let target = target.into();
tokio::task::spawn_blocking(move || {
let tar = std::fs::File::open(arch)?;
let mut tar = tar::Archive::new(tar);
tar.unpack(target)
})
.await??;
Ok(())
}
fn default_root_tmpfs_opts() -> Option<String> { fn default_root_tmpfs_opts() -> Option<String> {
let mem = sys_info::mem_info() let mem = sys_info::mem_info()
.inspect_err(|e| warn!("failed to get system memory info, using default tmpfs size: {e}")) .inspect_err(|e| warn!("failed to get system memory info, using default tmpfs size: {e}"))
@@ -267,117 +187,47 @@ fn default_root_tmpfs_opts() -> Option<String> {
Some(format!("size={fs_size}m")) Some(format!("size={fs_size}m"))
} }
struct LayerMounter<'t> { async fn mount_system(cfg: &dkl::Config, bs_dir: &str, verifier: &Verifier) {
bs_dir: &'t str,
layers_dir: &'t str,
verifier: &'t Verifier,
lower_dir: String,
}
impl LayerMounter<'_> {
fn src_path(&self, name: &str) -> PathBuf {
let mut p = PathBuf::from(self.bs_dir);
p.push(name);
if name != "merged" {
p.add_extension("fs");
}
p
}
async fn exists(&self, name: &str) -> bool {
retry(async || Ok(fs::try_exists(self.src_path(name)).await?)).await
}
async fn mount(&mut self, name: &str) {
self.mount_path(self.src_path(name), name, true).await
}
async fn mount_path(&mut self, src: impl AsRef<Path>, name: &str, verify: bool) {
let src = src.as_ref();
let tgt_dir = PathBuf::from(self.layers_dir).join(name);
let tgt = tgt_dir.with_added_extension("fs");
if let Err(e) = fs::create_dir_all(&tgt_dir).await {
warn!("mkdir -p {}: {e}", tgt_dir.display());
}
let mount_src = if name == "merged" {
retry(async || {
let data = self.verifier.verify_path(src).await?;
let data = MergedLayer::from_bytes(&data)
.ok_or(format_err!("{}: invalid data", src.display()))?;
data.create(&tgt)
.await
.map_err(|e| format_err!("write {}: {e}", tgt.display()))?;
let dm_name = "system";
let mut cmd = tokio::process::Command::new("veritysetup");
cmd.arg("open")
.arg(format!("--hash-offset={}", data.hash_offset()))
.arg(&tgt)
.arg(dm_name)
.arg(&tgt)
.arg(data.root_hash_hex());
try_exec_cmd(cmd).await?;
Ok(PathBuf::from("/dev/mapper").join(dm_name))
})
.await
} else {
retry(async || {
let src = if verify {
self.verifier.verify_path(src).await?
} else {
fs::read(src).await?
};
fs::write(&tgt, &src).await?;
Ok(tgt.clone())
})
.await
};
retry(async || mount_ro_fs(&mount_src, &tgt_dir).await).await;
if !self.lower_dir.is_empty() {
self.lower_dir.push(':');
}
self.lower_dir.push_str(&tgt_dir.to_string_lossy());
}
}
async fn mount_system(cfg: &dkl::Config, bs_cfg: &Config, bs_dir: &str, verifier: &Verifier) {
let opts = match utils::param("root-opts") { let opts = match utils::param("root-opts") {
Some(s) => Some(s.to_string()), Some(s) => Some(s.to_string()),
None => default_root_tmpfs_opts(), None => default_root_tmpfs_opts(),
}; };
let mem_dir = "/mem"; let mem_dir = "/mem";
mount(None::<&str>, mem_dir, "tmpfs", opts.as_deref()).await; mount(None, mem_dir, "tmpfs", opts.as_deref()).await;
let mut mounter = LayerMounter { let layers_dir = &format!("{mem_dir}/layers");
bs_dir, let mut lower_dir = String::new();
layers_dir: &format!("{mem_dir}/layers"),
verifier,
lower_dir: String::new(),
};
if mounter.exists("merged").await {
mounter.mount("merged").await;
} else {
for layer in &cfg.layers { for layer in &cfg.layers {
if layer == "modules" && bs_cfg.modules.is_some() { let src = retry(async || {
continue; // take modules from initrd if layer == "modules" {
} let src = "/modules.sqfs";
mounter.mount(layer).await; (fs::read(src).await).map_err(|e| format_err!("read {src} failed: {e}"))
} } else {
verifier.verify_path(&format!("{bs_dir}/{layer}.fs")).await
} }
})
.await;
if let Some(ref modules) = bs_cfg.modules { let tgt = &format!("{mem_dir}/{layer}.fs");
mounter.mount_path(modules, "modules", false).await; retry(async || {
info!("copying layer {layer}");
let mut out = (fs::File::create(tgt).await)
.map_err(|e| format_err!("create {tgt} failed: {e}"))?;
(out.write_all(&src).await).map_err(|e| format_err!("write failed: {e}"))?;
(out.flush().await).map_err(|e| format_err!("write failed: {e}"))
})
.await;
let layer_dir = &format!("{layers_dir}/{layer}");
mount(Some(tgt), layer_dir, "squashfs", None).await;
if !lower_dir.is_empty() {
lower_dir.push(':');
}
lower_dir.push_str(&layer_dir);
} }
let upper_dir = &format!("{mem_dir}/upper"); let upper_dir = &format!("{mem_dir}/upper");
@@ -390,9 +240,8 @@ async fn mount_system(cfg: &dkl::Config, bs_cfg: &Config, bs_dir: &str, verifier
}) })
.await; .await;
let lower_dir = &mounter.lower_dir;
let opts = format!("lowerdir={lower_dir},upperdir={upper_dir},workdir={work_dir}"); let opts = format!("lowerdir={lower_dir},upperdir={upper_dir},workdir={work_dir}");
mount(None::<&str>, "/system", "overlay", Some(&opts)).await; mount(None, "/system", "overlay", Some(&opts)).await;
// make root rshared (default in systemd, required by Kubernetes 1.10+) // make root rshared (default in systemd, required by Kubernetes 1.10+)
// equivalent to "mount --make-rshared /" // equivalent to "mount --make-rshared /"
@@ -406,53 +255,6 @@ async fn mount_system(cfg: &dkl::Config, bs_cfg: &Config, bs_dir: &str, verifier
.await; .await;
} }
struct MergedLayer<'t> {
#[allow(unused)]
root_hash_sig: &'t [u8],
root_hash: &'t [u8],
data: &'t [u8],
hash: &'t [u8],
}
impl<'t> MergedLayer<'t> {
fn from_bytes(mut src: &'t [u8]) -> Option<Self> {
let mut next = || {
let (len, rem) = src.split_at_checked(8)?;
let len = u64::from_be_bytes(len.try_into().ok()?);
let (data, rem) = rem.split_at_checked(len as usize)?;
src = rem;
Some(data)
};
Some(Self {
root_hash_sig: next()?,
root_hash: next()?,
data: next()?,
hash: next()?,
})
}
async fn create(&self, path: impl AsRef<Path>) -> std::io::Result<()> {
let mut out = fs::File::create(path).await?;
self.write_to(&mut out).await?;
out.shutdown().await
}
async fn write_to(&self, mut out: impl AsyncWrite + Unpin) -> std::io::Result<()> {
out.write_all(self.data).await?;
out.write_all(self.hash).await?;
Ok(())
}
fn hash_offset(&self) -> usize {
self.data.len()
}
fn root_hash_hex(&self) -> String {
hex::encode(self.root_hash)
}
}
async fn apply_groups(groups: &[dkl::Group], root: &str) { async fn apply_groups(groups: &[dkl::Group], root: &str) {
for group in groups { for group in groups {
let mut args = vec![root, "groupadd", "-r"]; let mut args = vec![root, "groupadd", "-r"];
@@ -504,10 +306,8 @@ async fn mount_filesystems(mounts: &[dkl::Mount], root: &str) {
} }
async fn setup_root_user(user: &dkl::RootUser, root: &str) -> Result<()> { async fn setup_root_user(user: &dkl::RootUser, root: &str) -> Result<()> {
if let Some(pw_hash) = user.password_hash.as_ref() if let Some(pw_hash) = user.password_hash.as_ref().filter(|v| !v.is_empty()) {
&& !pw_hash.is_empty() set_user_password("root", &pw_hash, root).await?;
{
set_user_password("root", pw_hash, root).await?;
} }
let mut authorized_keys = Vec::new(); let mut authorized_keys = Vec::new();
+2 -2
View File
@@ -1,4 +1,4 @@
use eyre::{Result, format_err}; use eyre::{format_err, Result};
use log::{error, info, warn}; use log::{error, info, warn};
use std::collections::BTreeSet as Set; use std::collections::BTreeSet as Set;
use std::process::Stdio; use std::process::Stdio;
@@ -6,7 +6,7 @@ use tokio::io::AsyncWriteExt;
use tokio::process::Command; use tokio::process::Command;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use super::{USED_DEVS, retry_or_ignore}; use super::{retry_or_ignore, USED_DEVS};
use crate::blockdev::{is_uninitialized, uninitialize}; use crate::blockdev::{is_uninitialized, uninitialize};
use crate::fs::walk_dir; use crate::fs::walk_dir;
use crate::input; use crate::input;
+10 -10
View File
@@ -1,8 +1,8 @@
use eyre::{Result, format_err}; use eyre::{format_err, Result};
use log::{error, info, warn}; use log::{error, info, warn};
use tokio::process::Command; use tokio::process::Command;
use super::{USED_DEVS, exec, retry, retry_or_ignore}; use super::{exec, retry, retry_or_ignore, USED_DEVS};
use crate::fs::walk_dir; use crate::fs::walk_dir;
use crate::{blockdev, lvm}; use crate::{blockdev, lvm};
use dkl::bootstrap::{Config, Filesystem, LvSize, LvmLV, LvmPV, LvmVG, TAKE_ALL}; use dkl::bootstrap::{Config, Filesystem, LvSize, LvmLV, LvmPV, LvmVG, TAKE_ALL};
@@ -31,7 +31,7 @@ pub async fn setup(cfg: &Config) {
if (lvs.iter()).any(|lv| lv.equal_name(vg_name, lv_name)) { if (lvs.iter()).any(|lv| lv.equal_name(vg_name, lv_name)) {
info!("LVM LV {vg_name}/{lv_name} exists"); info!("LVM LV {vg_name}/{lv_name} exists");
} else { } else {
retry_or_ignore(async || setup_lv(vg, lv).await).await; retry_or_ignore(async || setup_lv(&vg, &lv).await).await;
} }
} }
} }
@@ -40,7 +40,7 @@ pub async fn setup(cfg: &Config) {
for vg in &cfg.lvm { for vg in &cfg.lvm {
for lv in &vg.lvs { for lv in &vg.lvs {
retry_or_ignore(async || format_lv(vg, lv).await).await; retry_or_ignore(async || format_lv(&vg, &lv).await).await;
} }
} }
} }
@@ -115,20 +115,20 @@ async fn setup_lv(vg: &LvmVG, lv: &LvmLV) -> Result<()> {
let mut cmd = Command::new("lvcreate"); let mut cmd = Command::new("lvcreate");
cmd.arg(&vg.name); cmd.arg(&vg.name);
cmd.args(["--name", &lv.name]); cmd.args(&["--name", &lv.name]);
match &lv.size { match &lv.size {
LvSize::Size(sz) => cmd.args(["-L", sz]), LvSize::Size(sz) => cmd.args(&["-L", sz]),
LvSize::Extents(sz) => cmd.args(["-l", sz]), LvSize::Extents(sz) => cmd.args(&["-l", sz]),
}; };
let raid = lv.raid.as_ref().unwrap_or(&vg.defaults.raid); let raid = lv.raid.as_ref().unwrap_or(&vg.defaults.raid);
if let Some(mirrors) = raid.mirrors { if let Some(mirrors) = raid.mirrors {
cmd.args(["--mirrors", &mirrors.to_string()]); cmd.args(&["--mirrors", &mirrors.to_string()]);
} }
if let Some(stripes) = raid.stripes { if let Some(stripes) = raid.stripes {
cmd.args(["--stripes", &stripes.to_string()]); cmd.args(&["--stripes", &stripes.to_string()]);
} }
let status = cmd.status().await?; let status = cmd.status().await?;
@@ -147,7 +147,7 @@ async fn format_lv(vg: &LvmVG, lv: &LvmLV) -> Result<()> {
let name = &format!("{}/{}", vg.name, lv.name); let name = &format!("{}/{}", vg.name, lv.name);
let dev = &format!("/dev/{name}"); let dev = &format!("/dev/{name}");
if !blockdev::is_uninitialized(dev).await? { if !blockdev::is_uninitialized(&dev).await? {
info!("{dev} looks initialized"); info!("{dev} looks initialized");
return Ok(()); return Ok(());
} }
+3 -3
View File
@@ -3,10 +3,10 @@ use log::{info, warn};
use std::collections::BTreeSet as Set; use std::collections::BTreeSet as Set;
use tokio::process::Command; use tokio::process::Command;
use super::{Result, format_err, retry_or_ignore}; use super::{format_err, retry_or_ignore, Result};
use crate::{ use crate::{
udev, udev,
utils::{NameAliases, select_n_by_regex, select_n_by_udev}, utils::{select_n_by_regex, select_n_by_udev, NameAliases},
}; };
use dkl::bootstrap::{Config, Network}; use dkl::bootstrap::{Config, Network};
@@ -50,7 +50,7 @@ async fn setup_network(net: &Network, assigned: &mut Set<String>) -> Result<()>
"net", "net",
"INTERFACE", "INTERFACE",
&udev_filter.clone().into(), &udev_filter.clone().into(),
assigned, &assigned,
) )
.await? .await?
} else { } else {
+7 -3
View File
@@ -1,6 +1,10 @@
use log::{info, warn}; use log::{info, warn};
use std::{fs, io::Write, os::unix::fs::PermissionsExt, process::Stdio}; use std::fs;
use tokio::{net, process::Command}; use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::process::Stdio;
use tokio::net;
use tokio::process::Command;
use super::retry_or_ignore; use super::retry_or_ignore;
use dkl::bootstrap::{Config, SSHServer}; use dkl::bootstrap::{Config, SSHServer};
@@ -56,7 +60,7 @@ async fn handle_ssh_connections(listener: net::TcpListener, cfg: SSHServer) {
} }
for opt in &options { for opt in &options {
sshd_args.extend(["-o", opt]); sshd_args.extend(["-o", &opt]);
} }
let mut keygen_done = false; let mut keygen_done = false;
+1 -1
View File
@@ -3,7 +3,7 @@ use crate::input;
pub async fn run() { pub async fn run() {
tokio::spawn(async { tokio::spawn(async {
// give a bit of time for stdout // give a bit of time for stdout
use tokio::time::{Duration, sleep}; use tokio::time::{sleep, Duration};
sleep(Duration::from_millis(200)).await; sleep(Duration::from_millis(200)).await;
if let Err(e) = input::forward_requests_from_socket().await { if let Err(e) = input::forward_requests_from_socket().await {
+8 -9
View File
@@ -1,9 +1,10 @@
use std::io::Write; use std::io::Write;
use std::sync::{LazyLock, Mutex}; use std::sync::{LazyLock, Mutex};
use std::time::SystemTime; use std::time::SystemTime;
use tokio::{io::AsyncWriteExt, sync::watch, task::JoinSet}; use tokio::sync::watch;
use tokio::task::JoinSet;
pub static LOG: LazyLock<Log> = LazyLock::new(Log::default); pub static LOG: LazyLock<Log> = LazyLock::new(Log::new);
pub fn init() { pub fn init() {
log::set_logger(&*LOG).expect("set_logger should not fail"); log::set_logger(&*LOG).expect("set_logger should not fail");
@@ -18,8 +19,8 @@ pub struct Log {
tasks: Mutex<Option<JoinSet<()>>>, tasks: Mutex<Option<JoinSet<()>>>,
} }
impl Default for Log { impl Log {
fn default() -> Self { pub fn new() -> Self {
let (tx, rx) = watch::channel(0); let (tx, rx) = watch::channel(0);
Self { Self {
start: SystemTime::now(), start: SystemTime::now(),
@@ -29,9 +30,7 @@ impl Default for Log {
tasks: Mutex::new(Some(JoinSet::new())), tasks: Mutex::new(Some(JoinSet::new())),
} }
} }
}
impl Log {
pub fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) { pub fn spawn(&self, task: impl Future<Output = ()> + Send + 'static) {
if let Some(tasks) = self.tasks.lock().unwrap().as_mut() { if let Some(tasks) = self.tasks.lock().unwrap().as_mut() {
tasks.spawn(task); tasks.spawn(task);
@@ -48,6 +47,7 @@ impl Log {
pub async fn copy_to<W: tokio::io::AsyncWrite + Unpin>(&self, mut out: W) { pub async fn copy_to<W: tokio::io::AsyncWrite + Unpin>(&self, mut out: W) {
let mut log = self.subscribe(); let mut log = self.subscribe();
use tokio::io::AsyncWriteExt;
while let Some(chunk) = log.next().await { while let Some(chunk) = log.next().await {
let _ = out.write_all(&chunk).await; let _ = out.write_all(&chunk).await;
let _ = out.flush().await; let _ = out.flush().await;
@@ -56,8 +56,7 @@ impl Log {
pub async fn close(&self) { pub async fn close(&self) {
self.tx.lock().unwrap().take(); self.tx.lock().unwrap().take();
let tasks = self.tasks.lock().unwrap().take(); if let Some(tasks) = self.tasks.lock().unwrap().take() {
if let Some(tasks) = tasks {
tasks.join_all().await; tasks.join_all().await;
} }
} }
@@ -116,7 +115,7 @@ pub struct LogWatch<'t> {
impl<'t> LogWatch<'t> { impl<'t> LogWatch<'t> {
pub async fn next(&mut self) -> Option<Vec<u8>> { pub async fn next(&mut self) -> Option<Vec<u8>> {
loop { loop {
let new_pos = *self.rx.borrow_and_update(); let new_pos = self.rx.borrow_and_update().clone();
if new_pos <= self.pos { if new_pos <= self.pos {
if self.rx.changed().await.is_err() { if self.rx.changed().await.is_err() {
return None; // finished return None; // finished
+7 -8
View File
@@ -14,7 +14,7 @@ pub async fn read_password(prompt: impl Display) -> String {
} }
fn choice_char(s: &str) -> char { fn choice_char(s: &str) -> char {
s.chars().skip_while(|c| *c != '[').nth(1).unwrap() s.chars().skip_while(|c| *c != '[').skip(1).next().unwrap()
} }
#[test] #[test]
@@ -51,7 +51,7 @@ pub async fn read_choice<const N: usize>(choices: [&str; N]) -> char {
loop { loop {
let line = read_line(&prompt).await; let line = read_line(&prompt).await;
let Some(ch) = line.chars().next() else { let Some(ch) = line.chars().nth(0) else {
continue; continue;
}; };
@@ -70,9 +70,8 @@ pub struct InputRequest {
} }
pub type Reply = Arc<Mutex<Option<oneshot::Sender<String>>>>; pub type Reply = Arc<Mutex<Option<oneshot::Sender<String>>>>;
type RequestSender = watch::Sender<Option<(InputRequest, Reply)>>;
static REQ: LazyLock<Mutex<RequestSender>> = LazyLock::new(|| { static REQ: LazyLock<Mutex<watch::Sender<Option<(InputRequest, Reply)>>>> = LazyLock::new(|| {
let (tx, _) = watch::channel(None); let (tx, _) = watch::channel(None);
Mutex::new(tx) Mutex::new(tx)
}); });
@@ -118,7 +117,7 @@ pub async fn answer_requests_from_stdin() {
if req.hide { if req.hide {
match termios::Termios::from_fd(0) { match termios::Termios::from_fd(0) {
Ok(mut tio) => { Ok(mut tio) => {
saved_termios = Some(tio); saved_termios = Some(tio.clone());
tio.c_lflag &= !termios::ECHO; tio.c_lflag &= !termios::ECHO;
if let Err(e) = termios::tcsetattr(0, termios::TCSAFLUSH, &tio) { if let Err(e) = termios::tcsetattr(0, termios::TCSAFLUSH, &tio) {
warn!("password may be echoed! {e}"); warn!("password may be echoed! {e}");
@@ -161,12 +160,12 @@ pub async fn answer_requests_from_stdin() {
); );
// restore term if input was hidden // restore term if input was hidden
if let Some(tio) = saved_termios if let Some(tio) = saved_termios {
&& let Err(e) = termios::tcsetattr(0, termios::TCSAFLUSH, &tio) if let Err(e) = termios::tcsetattr(0, termios::TCSAFLUSH, &tio) {
{
warn!("failed to restore pty attrs: {e}"); warn!("failed to restore pty attrs: {e}");
} }
} }
}
} }
const SOCKET_PATH: &str = "/run/init.sock"; const SOCKET_PATH: &str = "/run/init.sock";
+3 -3
View File
@@ -1,4 +1,4 @@
use eyre::{format_err, Result}; use eyre::{Result, format_err};
use tokio::process::Command; use tokio::process::Command;
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
@@ -55,7 +55,7 @@ pub struct LV {
impl LV { impl LV {
pub fn equal_name(&self, vg_name: &str, lv_name: &str) -> bool { pub fn equal_name(&self, vg_name: &str, lv_name: &str) -> bool {
vg_name == self.vg_name && lv_name == self.lv_name vg_name == &self.vg_name && lv_name == &self.lv_name
} }
} }
@@ -90,7 +90,7 @@ async fn report_cmd<T>(cmd: &str, find: fn(ReportObj) -> Option<Vec<T>>) -> Resu
.await?; .await?;
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(output.stderr.trim_ascii()); let stderr = String::from_utf8_lossy(&output.stderr.trim_ascii());
return Err(format_err!("{cmd} failed: {}", stderr)); return Err(format_err!("{cmd} failed: {}", stderr));
} }
+15 -15
View File
@@ -1,8 +1,6 @@
use eyre::Result; use eyre::Result;
use log::warn; use log::warn;
use dkl::bootstrap::UdevFilter;
pub struct Device { pub struct Device {
sysname: String, sysname: String,
output: String, output: String,
@@ -43,7 +41,7 @@ pub fn get_devices(class: &str) -> Result<Vec<Device>> {
let path = path.to_string_lossy(); let path = path.to_string_lossy();
let output = std::process::Command::new("udevadm") let output = std::process::Command::new("udevadm")
.args(["info", &format!("--path={path}")]) .args(&["info", &format!("--path={path}")])
.stderr(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped())
.output()?; .output()?;
@@ -104,7 +102,10 @@ pub struct Devs {
impl<'t> Devs { impl<'t> Devs {
pub fn iter(&'t self) -> impl Iterator<Item = Dev<'t>> { pub fn iter(&'t self) -> impl Iterator<Item = Dev<'t>> {
self.data.split("\n\n").filter(|s| !s.is_empty()).map(Dev) self.data
.split("\n\n")
.filter(|s| !s.is_empty())
.map(|s| Dev(s))
} }
pub fn of_subsystem(&'t self, subsystem: &str) -> impl Iterator<Item = Dev<'t>> { pub fn of_subsystem(&'t self, subsystem: &str) -> impl Iterator<Item = Dev<'t>> {
@@ -173,7 +174,7 @@ pub enum Filter {
False, False,
} }
impl Filter { impl<'t> Filter {
pub fn matches(&self, dev: &Dev) -> bool { pub fn matches(&self, dev: &Dev) -> bool {
match self { match self {
Self::False => false, Self::False => false,
@@ -189,22 +190,21 @@ impl Filter {
} }
} }
impl From<UdevFilter> for Filter { impl<'t> Into<Filter> for dkl::bootstrap::UdevFilter {
fn from(filter: UdevFilter) -> Filter { fn into(self) -> Filter {
use UdevFilter::*; match self {
match filter { Self::Has(p) => Filter::Has(p),
Has(p) => Filter::Has(p), Self::Eq(p, v) => Filter::Eq(p, v),
Eq(p, v) => Filter::Eq(p, v), Self::Glob(p, pattern) => match glob::Pattern::new(&pattern) {
Glob(p, pattern) => match glob::Pattern::new(&pattern) {
Ok(pattern) => Filter::Glob(p, pattern), Ok(pattern) => Filter::Glob(p, pattern),
Err(e) => { Err(e) => {
warn!("pattern {pattern:?} will never match: {e}"); warn!("pattern {pattern:?} will never match: {e}");
Filter::False Filter::False
} }
}, },
And(ops) => Filter::And(ops.into_iter().map(|op| op.into()).collect()), Self::And(ops) => Filter::And(ops.into_iter().map(Self::into).collect()),
Or(ops) => Filter::Or(ops.into_iter().map(|op| op.into()).collect()), Self::Or(ops) => Filter::Or(ops.into_iter().map(Self::into).collect()),
Not(op) => Filter::Not(Box::new((*op).into())), Self::Not(op) => Filter::Not(Box::new((*op).into())),
} }
} }
} }
+3 -3
View File
@@ -11,7 +11,7 @@ static CMDLINE: LazyLock<String> = LazyLock::new(|| {
.unwrap_or_default() .unwrap_or_default()
}); });
pub fn cmdline() -> impl Iterator<Item = &'static str> { fn cmdline() -> impl Iterator<Item = &'static str> {
CMDLINE.split_ascii_whitespace() CMDLINE.split_ascii_whitespace()
} }
@@ -66,7 +66,7 @@ impl NameAliases {
pub fn select_n_by_regex<'t>( pub fn select_n_by_regex<'t>(
n: i16, n: i16,
regexs: &[String], regexs: &Vec<String>,
nas: impl Iterator<Item = &'t NameAliases>, nas: impl Iterator<Item = &'t NameAliases>,
) -> Vec<String> { ) -> Vec<String> {
// compile regexs // compile regexs
@@ -91,7 +91,7 @@ pub fn select_n_by_regex<'t>(
} }
} }
pub async fn select_n_by_udev( pub async fn select_n_by_udev<'t>(
n: i16, n: i16,
subsystem: &str, subsystem: &str,
result_property: &str, result_property: &str,
+17 -19
View File
@@ -21,6 +21,8 @@ auths:
sshKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICkpbU6sf4t0f6XAv9DuW3XH5iLM0AI5rc8PT2jwea1N sshKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICkpbU6sf4t0f6XAv9DuW3XH5iLM0AI5rc8PT2jwea1N
password: bXlzZWVk:HMSxrg1cYphaPuUYUbtbl/htep/tVYYIQAuvkNMVpw0 # mypass password: bXlzZWVk:HMSxrg1cYphaPuUYUbtbl/htep/tVYYIQAuvkNMVpw0 # mypass
signer_public_key: MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQA29glSqk7MqoUIjD+UQG+b4v59pTFkn8rYtNhOftTe7uiLUvGFsjNdzP3tW64t/c6YD2p6dtI3oQXGOVQO1vIWPEBc6Sq++BRpQ0FVna+dgNQx8/kLXN9Na0ZYbK7q0haCI7/EHWOX79JFFxJE9HJ67AOMmXwGJ2jrfa1CUnWvfCmT+E=
ssh: ssh:
listen: "[::]:22" listen: "[::]:22"
user_ca: /user_ca.pub user_ca: /user_ca.pub
@@ -39,24 +41,22 @@ networks:
udev: !has ID_NET_NAME_MAC udev: !has ID_NET_NAME_MAC
script: | script: |
ip li set $iface up ip li set $iface up
ip a add 192.168.12.42/24 dev $iface udhcpc -i $iface -b -t1 -T1 -A5 ||
ip a add fd12:6e76:7474::1337:2eed/64 dev $iface ip a add 2001:41d0:306:168f::1337:2eed/64 dev $iface
ip route add default via 192.168.12.254
ip route add default via fd12:6e76:7474::1 dev $iface
#pre_lvm_crypt: pre_lvm_crypt:
#- name: sys-${name} - name: sys-${name}
# udev: !glob [ DEVNAME, /dev/vd* ] udev: !glob [ DEVNAME, /dev/vd* ]
lvm: lvm:
- vg: storage - vg: storage
pvs: pvs:
n: 2 n: 2
regexps: regexps:
#- ^/dev/mapper/sys- - ^/dev/mapper/sys-
# to match full disks # to match full disks
#- /dev/nvme[0-9]+n[0-9]+ #- /dev/nvme[0-9]+n[0-9]+
- /dev/vd[a-z]+ #- /dev/vd[a-z]+
#- /dev/sd[a-z]+ #- /dev/sd[a-z]+
#- /dev/hd[a-z]+ #- /dev/hd[a-z]+
# to match partitions: # to match partitions:
@@ -72,16 +72,11 @@ lvm:
lvs: lvs:
- name: bootstrap - name: bootstrap
size: 1g size: 2g
- name: varlog - name: varlog
size: 256m extents: 10%FREE
- name: kubelet # size: 10g
size: 256m
- name: containerd
size: 1g
- name: etcd
size: 256m
- name: podman - name: podman
extents: 10%FREE extents: 10%FREE
@@ -95,8 +90,11 @@ lvm:
#- dev: /dev/storage/bootstrap #- dev: /dev/storage/bootstrap
#- dev: /dev/storage/dls #- dev: /dev/storage/dls
signer_public_key: 'MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAd5sR4NqLtjSt8ESNlYWvuufYj7v+aYGDlgxQThcKbzDPVe639IfH94hHE0l9TAfyU94qtN/GpFyKJ68F/u2pu70A/umT1m24ELFDqXlQXqhTsH91r+nYUZ7due3EqSrvru/yjchNNRkpoCCu3QkDF25KnrYfWWHqj9ZIRlBTCJE9SwM='
bootstrap: bootstrap:
#dev: /dev/mapper/bootstrap
dev: /dev/storage/bootstrap dev: /dev/storage/bootstrap
seed: http://192.168.12.254:7606/public/download-set/host/m1/bootstrap.tar?set=ICM5KUZDRAMJPMO5OWW6PSIFYF4AHMYLAQSBZVFUDNG4DQDEW6UFQQJQKMGIXPI4CFOZFVA4CXULRXCAHKX3WELVAYS246FM6SGSGHIOAQRE2GDEOC4RUAAAQA3GEZDFMUZDOMD4NA5CUOTCN5XXI43UOJQXALTUMFZAAAAAACHHUMRU # TODO seed: https://direktil.novit.io/bootstraps/dls-crypt
seed: http://192.168.10.254:7606/hosts/m1/bootstrap.tar
# TODO seed_sign_key: "..."
# TODO load_and_close: true
Binary file not shown.
BIN
View File
Binary file not shown.