9 Commits

6 changed files with 912 additions and 271 deletions

1084
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package] [package]
name = "init" name = "init"
version = "2.5.1" version = "2.5.3"
edition = "2024" edition = "2024"
[profile.release] [profile.release]
@ -16,7 +16,7 @@ env_logger = "0.11.3"
eyre = { version = "0.6.12" } eyre = { version = "0.6.12" }
itertools = "0.14.0" itertools = "0.14.0"
log = "0.4.21" log = "0.4.21"
nix = { version = "0.30.1", features = ["feature", "mount", "process", "reboot", "signal"] } nix = { version = "0.31.1", features = ["feature", "mount", "process", "reboot", "signal"] }
regex = "1.11.1" regex = "1.11.1"
serde = { version = "1.0.198", features = ["derive"] } serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116" serde_json = "1.0.116"
@ -25,9 +25,8 @@ 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.12.22", features = ["native-tls"] } reqwest = { version = "0.13.1", features = ["native-tls"] }
glob = "0.3.3" glob = "0.3.3"

View File

@ -1,4 +1,4 @@
from rust:1.91.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,7 @@ 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.22.2 as initrd from alpine:3.23.3 as initrd
run apk add zstd lz4 run apk add zstd lz4
workdir /system workdir /system
@ -18,9 +18,9 @@ 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 \
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 && rm -rf usr/share/apk var/cache/apk etc/motd dev/*
copy etc/sshd_config etc/ssh/sshd_config copy etc/sshd_config etc/ssh/sshd_config
@ -34,6 +34,6 @@ run chroot . init-version
run find * |cpio -H newc -oF /initrd run find * |cpio -H newc -oF /initrd
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
from alpine:3.22.2 from alpine:3.23.2
copy --from=initrd /initrd / copy --from=initrd /initrd /
entrypoint ["base64","/initrd"] entrypoint ["base64","/initrd"]

View File

@ -1,6 +1,7 @@
use eyre::{format_err, Result}; 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::os::unix::fs::symlink; use std::os::unix::fs::symlink;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::{fs, process::Command}; use tokio::{fs, process::Command};
@ -323,13 +324,7 @@ async fn child_reaper() {
} }
} }
macro_rules! cstr { async fn switch_root(root: &str) -> Result<Infallible> {
($s:expr) => {
std::ffi::CString::new($s)?.as_c_str()
};
}
async fn switch_root(root: &str) -> Result<()> {
info!("killing all processes and switching root"); info!("killing all processes and switching root");
dklog::LOG.close().await; dklog::LOG.close().await;
@ -340,7 +335,13 @@ async fn switch_root(root: &str) -> Result<()> {
eprintln!("failed to kill processes: {e}"); eprintln!("failed to kill processes: {e}");
} }
nix::unistd::execv( macro_rules! cstr {
($s:expr) => {
std::ffi::CString::new($s)?.as_c_str()
};
}
Ok(nix::unistd::execv(
cstr!("/sbin/switch_root"), cstr!("/sbin/switch_root"),
&[ &[
cstr!("switch_root"), cstr!("switch_root"),
@ -349,8 +350,5 @@ async fn switch_root(root: &str) -> Result<()> {
cstr!(root), cstr!(root),
cstr!("/sbin/init"), cstr!("/sbin/init"),
], ],
) )?)
.unwrap();
unreachable!();
} }

View File

@ -8,6 +8,7 @@ use tokio::{
use dkl::{ use dkl::{
self, self,
apply::{self, chroot, set_perms}, apply::{self, chroot, set_perms},
base64_decode,
bootstrap::Config, bootstrap::Config,
}; };
@ -89,8 +90,7 @@ impl Verifier {
return Ok(Self { pubkey: None }); return Ok(Self { pubkey: None });
}; };
use base64::{prelude::BASE64_STANDARD, Engine}; let pubkey = base64_decode(pubkey)?;
let pubkey = BASE64_STANDARD.decode(pubkey)?;
let pubkey = Some(pubkey); let pubkey = Some(pubkey);
return Ok(Self { pubkey }); return Ok(Self { pubkey });

View File

@ -1,11 +1,11 @@
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, LvmVG, TAKE_ALL}; use dkl::bootstrap::{Config, Filesystem, LvSize, LvmLV, LvmPV, LvmVG, TAKE_ALL};
pub async fn setup(cfg: &Config) { pub async fn setup(cfg: &Config) {
if cfg.lvm.is_empty() { if cfg.lvm.is_empty() {
@ -73,24 +73,12 @@ async fn setup_vg(vg: &LvmVG) -> Result<()> {
info!("setting up LVM VG {vg_name} ({dev_done}/{dev_needed} devices configured)"); info!("setting up LVM VG {vg_name} ({dev_done}/{dev_needed} devices configured)");
} }
let regexps: Vec<regex::Regex> = (vg.pvs.regexps.iter()) let matching_devs = find_devs(&vg.pvs).await?;
.filter_map(|re_str| {
(re_str.parse())
.inspect_err(|e| error!("invalid regex ignored: {re_str:?}: {e}"))
.ok()
})
.collect();
let mut used_devs = USED_DEVS.lock().await;
let matching_devs = (walk_dir("/dev").await.into_iter())
.filter(|path| !used_devs.contains(path.as_str()))
.filter(|path| regexps.iter().any(|re| re.is_match(path)));
let devs: Vec<_> = if dev_needed == TAKE_ALL { let devs: Vec<_> = if dev_needed == TAKE_ALL {
matching_devs.collect() matching_devs
} else { } else {
matching_devs.take(missing_count!()).collect() matching_devs.into_iter().take(missing_count!()).collect()
}; };
let cmd = if dev_done == 0 { let cmd = if dev_done == 0 {
@ -109,7 +97,7 @@ async fn setup_vg(vg: &LvmVG) -> Result<()> {
} }
dev_done += devs.len(); dev_done += devs.len();
used_devs.extend(devs); USED_DEVS.lock().await.extend(devs);
if dev_needed != TAKE_ALL && dev_done < (dev_needed as usize) { if dev_needed != TAKE_ALL && dev_done < (dev_needed as usize) {
return Err(format_err!( return Err(format_err!(
@ -213,3 +201,33 @@ async fn install_package(pkg: &str) -> Result<()> {
Err(format_err!("failed to install package {pkg}: {status}")) Err(format_err!("failed to install package {pkg}: {status}"))
} }
} }
async fn find_devs(pvs: &LvmPV) -> Result<Vec<String>> {
let mut results = if let Some(ref filter) = pvs.udev {
use crate::udev;
let filter: udev::Filter = filter.clone().into();
(udev::all().await?.iter())
.filter(|dev| dev.subsystem() == Some("block") && filter.matches(dev))
.filter_map(|dev| dev.property("DEVNAME").map(|s| s.to_string()))
.collect()
} else if !pvs.regexps.is_empty() {
let regexps: Vec<regex::Regex> = (pvs.regexps.iter())
.filter_map(|re_str| {
(re_str.parse())
.inspect_err(|e| error!("invalid regex ignored: {re_str:?}: {e}"))
.ok()
})
.collect();
(walk_dir("/dev").await.into_iter())
.filter(|path| regexps.iter().any(|re| re.is_match(path)))
.collect()
} else {
warn!("no device filters, no matches");
vec![]
};
let used_devs = USED_DEVS.lock().await;
results.retain(|path| !used_devs.contains(path.as_str()));
Ok(results)
}