Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
e30a46d62b | |||
f45fbe116e |
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -91,6 +91,12 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
@ -225,8 +231,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "init"
|
name = "init"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"cpio",
|
"cpio",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"eyre",
|
"eyre",
|
||||||
@ -240,6 +247,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"shell-escape",
|
"shell-escape",
|
||||||
|
"sys-info",
|
||||||
"termios",
|
"termios",
|
||||||
"tokio",
|
"tokio",
|
||||||
"unix_mode",
|
"unix_mode",
|
||||||
@ -570,6 +578,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sys-info"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b3a0d0aba8bf96a0e1ddfdc352fc53b3df7f39318c71854910c3c4b024ae52c"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termios"
|
name = "termios"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "init"
|
name = "init"
|
||||||
version = "2.4.0"
|
version = "2.4.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
@ -28,3 +28,5 @@ zstd = "0.13.3"
|
|||||||
unix_mode = "0.1.4"
|
unix_mode = "0.1.4"
|
||||||
cpio = "0.4.1"
|
cpio = "0.4.1"
|
||||||
lz4 = "1.28.1"
|
lz4 = "1.28.1"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
sys-info = "0.9.1"
|
||||||
|
@ -19,7 +19,7 @@ run . /etc/os-release \
|
|||||||
|
|
||||||
run apk add --no-cache --update -p . musl coreutils \
|
run apk add --no-cache --update -p . musl coreutils \
|
||||||
lvm2 lvm2-extra lvm2-dmeventd udev cryptsetup \
|
lvm2 lvm2-extra lvm2-dmeventd udev cryptsetup \
|
||||||
e2fsprogs lsblk 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
|
||||||
|
|
||||||
copy etc/sshd_config etc/ssh/sshd_config
|
copy etc/sshd_config etc/ssh/sshd_config
|
||||||
@ -36,7 +36,7 @@ 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 -o >/initrd
|
run find * |cpio -H newc -oF /initrd
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
from alpine:3.22.0
|
from alpine:3.22.0
|
||||||
|
21
build-test-initrd
Executable file
21
build-test-initrd
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
dir=tmp/test-initrd
|
||||||
|
base_initrd=dist/initrd
|
||||||
|
test_initrd=test-initrd
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
rm -fr $dir
|
||||||
|
mkdir $dir
|
||||||
|
|
||||||
|
cpio --quiet --extract --file $base_initrd --directory $dir
|
||||||
|
(cd $test_initrd && find * |cpio --quiet --create -H newc) |cpio --quiet --extract --directory $dir
|
||||||
|
|
||||||
|
(cd $dir && find * |cpio --create -H newc -R 0:0) >test-initrd.cpio
|
||||||
|
|
||||||
|
cpio --quiet -tF test-initrd.cpio
|
||||||
|
if cpio -tF test-initrd.cpio 2>&1 |grep bytes.of.junk; then echo "bad cpio archive"; exit 1; fi
|
||||||
|
|
||||||
|
lz4 -l9v test-initrd.cpio && mv test-initrd.cpio.lz4 test-initrd.cpio
|
||||||
|
|
@ -1,9 +1,6 @@
|
|||||||
modd.test.conf {}
|
modd.test.conf {}
|
||||||
|
|
||||||
dist/initrd test-initrd/**/* {
|
dist/initrd build-test-initrd test-initrd/**/* {
|
||||||
prep: cp -f dist/initrd test-initrd.cpio
|
prep: ./build-test-initrd
|
||||||
prep: cd test-initrd && find |cpio -oAv -H newc -F ../test-initrd.cpio
|
|
||||||
prep: lz4 -l9v test-initrd.cpio && mv test-initrd.cpio.lz4 test-initrd.cpio
|
|
||||||
prep: if lz4cat test-initrd.cpio | cpio -t 2>&1 |grep bytes.of.junk; then echo "bad cpio archive"; exit 1; fi
|
|
||||||
prep: kill $(<qemu.pid)
|
prep: kill $(<qemu.pid)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,9 @@ pub struct Config {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub crypt: Vec<CryptDev>,
|
pub crypt: Vec<CryptDev>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub signer_public_key: Option<String>,
|
||||||
|
|
||||||
pub bootstrap: Bootstrap,
|
pub bootstrap: Bootstrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,10 +257,19 @@ async fn mount(src: Option<&str>, dst: &str, fstype: &str, opts: Option<&str>) {
|
|||||||
error!("failed to create dir {dst}: {e}");
|
error!("failed to create dir {dst}: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut is_file = false;
|
||||||
|
|
||||||
if let Some(src) = src {
|
if let Some(src) = src {
|
||||||
|
retry_or_ignore(async || {
|
||||||
|
is_file = (fs::metadata(src).await)
|
||||||
|
.map_err(|e| format_err!("stat {src} failed: {e}"))?
|
||||||
|
.is_file();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
match fstype {
|
match fstype {
|
||||||
"ext4" => {
|
"ext4" => {
|
||||||
exec("fsck", &["-p", src]).await;
|
exec("fsck.ext4", &["-p", src]).await;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -272,13 +281,11 @@ async fn mount(src: Option<&str>, dst: &str, fstype: &str, opts: Option<&str>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
retry_or_ignore(async || {
|
retry_or_ignore(async || {
|
||||||
// check source, use a loopdev if needed
|
// if it's a file, we need to use a loopdev
|
||||||
if let Some(src) = src {
|
if is_file {
|
||||||
if fs::metadata(src).await?.is_file() {
|
|
||||||
// loopdev crate has annoying dependencies, just use the normal mount program
|
// loopdev crate has annoying dependencies, just use the normal mount program
|
||||||
return try_exec("mount", &args).await;
|
return try_exec("mount", &args).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let (cmd_str, _) = cmd_str("mount", &args);
|
let (cmd_str, _) = cmd_str("mount", &args);
|
||||||
let flags = nix::mount::MsFlags::empty();
|
let flags = nix::mount::MsFlags::empty();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use eyre::{format_err, Result};
|
use eyre::{format_err, Result};
|
||||||
use log::info;
|
use log::{info, warn};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs,
|
fs,
|
||||||
@ -11,16 +11,16 @@ use crate::bootstrap::config::Config;
|
|||||||
use crate::{dkl, utils};
|
use crate::{dkl, utils};
|
||||||
|
|
||||||
pub async fn bootstrap(cfg: Config) {
|
pub async fn bootstrap(cfg: Config) {
|
||||||
|
let verifier = retry(async || Verifier::from_config(&cfg)).await;
|
||||||
let bs = cfg.bootstrap;
|
let bs = cfg.bootstrap;
|
||||||
|
|
||||||
retry_or_ignore(async || {
|
retry_or_ignore(async || {
|
||||||
fs::create_dir_all("/boostrap").await?;
|
|
||||||
mount(Some(&bs.dev), "/bootstrap", "ext4", None).await;
|
mount(Some(&bs.dev), "/bootstrap", "ext4", None).await;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let boot_version = utils::param("version", "current");
|
let boot_version = utils::param("version").unwrap_or("current");
|
||||||
let base_dir = &format!("/bootstrap/{boot_version}");
|
let base_dir = &format!("/bootstrap/{boot_version}");
|
||||||
|
|
||||||
retry_or_ignore(async || {
|
retry_or_ignore(async || {
|
||||||
@ -33,12 +33,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.seed).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, base_dir).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";
|
||||||
@ -68,7 +68,67 @@ pub async fn bootstrap(cfg: Config) {
|
|||||||
exec("chroot", &["/system", "update-ca-certificates"]).await
|
exec("chroot", &["/system", "update-ca-certificates"]).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn seed_config(base_dir: &str, seed_url: &Option<String>) -> Result<Vec<u8>> {
|
struct Verifier {
|
||||||
|
pubkey: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
impl Verifier {
|
||||||
|
fn from_config(cfg: &Config) -> Result<Self> {
|
||||||
|
let Some(ref pubkey) = cfg.signer_public_key else {
|
||||||
|
return Ok(Self { pubkey: None });
|
||||||
|
};
|
||||||
|
|
||||||
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
|
let pubkey = BASE64_STANDARD.decode(pubkey)?;
|
||||||
|
let pubkey = Some(pubkey);
|
||||||
|
|
||||||
|
return Ok(Self { pubkey });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_path(&self, path: &str) -> Result<()> {
|
||||||
|
let Some(ref pubkey) = self.pubkey else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("verifying {path}");
|
||||||
|
|
||||||
|
let mut pubkey = std::io::Cursor::new(pubkey);
|
||||||
|
|
||||||
|
let sig = format!("{path}.sig");
|
||||||
|
|
||||||
|
use std::process::Stdio;
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
let mut openssl = Command::new("openssl")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.args(&[
|
||||||
|
"dgst",
|
||||||
|
"-sha512",
|
||||||
|
"-verify",
|
||||||
|
"/dev/stdin",
|
||||||
|
"-signature",
|
||||||
|
&sig,
|
||||||
|
path,
|
||||||
|
])
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
tokio::io::copy(&mut pubkey, openssl.stdin.as_mut().unwrap()).await?;
|
||||||
|
|
||||||
|
let status = openssl.wait().await?;
|
||||||
|
if status.success() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format_err!(
|
||||||
|
"signature verification failed for {path}: {status}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn seed_config(
|
||||||
|
base_dir: &str,
|
||||||
|
seed_url: &Option<String>,
|
||||||
|
verifier: &Verifier,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
let cfg_path = &format!("{base_dir}/config.yaml");
|
let cfg_path = &format!("{base_dir}/config.yaml");
|
||||||
|
|
||||||
if fs::try_exists(cfg_path).await? {
|
if fs::try_exists(cfg_path).await? {
|
||||||
@ -92,6 +152,8 @@ async fn seed_config(base_dir: &str, seed_url: &Option<String>) -> Result<Vec<u8
|
|||||||
return Err(format_err!("{cfg_path} does not exist after seeding"));
|
return Err(format_err!("{cfg_path} does not exist after seeding"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verifier.verify_path(&cfg_path).await?;
|
||||||
|
|
||||||
Ok(fs::read(cfg_path).await?)
|
Ok(fs::read(cfg_path).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,23 +169,42 @@ async fn fetch_bootstrap(seed_url: &str, output_file: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mount_system(cfg: &dkl::Config, bs_dir: &str) {
|
fn default_root_tmpfs_opts() -> Option<String> {
|
||||||
|
let mem = sys_info::mem_info()
|
||||||
|
.inspect_err(|e| warn!("failed to get system memory info, using default tmpfs size: {e}"))
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
let mem_size = mem.total /* kiB */ / 1024;
|
||||||
|
let fs_size = 1024.min(mem_size / 2);
|
||||||
|
info!("system has {mem_size} MiB of memory, allowing {fs_size} MiB for root tmpfs");
|
||||||
|
Some(format!("size={fs_size}m"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn mount_system(cfg: &dkl::Config, bs_dir: &str, verifier: &Verifier) {
|
||||||
|
let opts = match utils::param("root-opts") {
|
||||||
|
Some(s) => Some(s.to_string()),
|
||||||
|
None => default_root_tmpfs_opts(),
|
||||||
|
};
|
||||||
|
|
||||||
let mem_dir = "/mem";
|
let mem_dir = "/mem";
|
||||||
mount(None, mem_dir, "tmpfs", Some("size=512m")).await;
|
mount(None, mem_dir, "tmpfs", opts.as_deref()).await;
|
||||||
|
|
||||||
let layers_dir = &format!("{mem_dir}/layers");
|
let layers_dir = &format!("{mem_dir}/layers");
|
||||||
let mut lower_dir = String::new();
|
let mut lower_dir = String::new();
|
||||||
|
|
||||||
for layer in &cfg.layers {
|
for layer in &cfg.layers {
|
||||||
let src = if layer == "modules" {
|
let src = if layer == "modules" {
|
||||||
"/modules.sqfs"
|
"/modules.sqfs".to_string()
|
||||||
} else {
|
} else {
|
||||||
&format!("{bs_dir}/{layer}.fs")
|
let p = format!("{bs_dir}/{layer}.fs");
|
||||||
|
retry(async || verifier.verify_path(&p).await).await;
|
||||||
|
p
|
||||||
};
|
};
|
||||||
|
|
||||||
let tgt = &format!("{mem_dir}/{layer}.fs");
|
let tgt = &format!("{mem_dir}/{layer}.fs");
|
||||||
retry(async || {
|
retry(async || {
|
||||||
info!("copying layer {layer} from {src}");
|
info!("copying layer {layer} from {src}");
|
||||||
fs::copy(src, tgt).await?;
|
fs::copy(&src, tgt).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
@ -251,14 +332,10 @@ async fn mount_filesystems(mounts: &[dkl::Mount], root: &str) {
|
|||||||
mount(
|
mount(
|
||||||
Some(&m.dev),
|
Some(&m.dev),
|
||||||
&path,
|
&path,
|
||||||
m.r#type
|
(m.r#type.as_deref())
|
||||||
.as_ref()
|
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map_or("ext4", |s| s.as_str()),
|
.unwrap_or("ext4"),
|
||||||
m.options
|
m.options.as_deref().filter(|v| !v.is_empty()),
|
||||||
.as_ref()
|
|
||||||
.filter(|v| !v.is_empty())
|
|
||||||
.map(|s| s.as_str()),
|
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,9 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
dklog::init();
|
dklog::init();
|
||||||
|
|
||||||
let call_name = env::args().next().unwrap_or("init".into());
|
let call_name = env::args().next();
|
||||||
let call_name = (call_name.rsplit_once('/').map(|(_, n)| n)).unwrap_or(call_name.as_str());
|
let call_name = call_name.as_deref().unwrap_or("init");
|
||||||
|
let call_name = call_name.rsplit_once('/').map_or(call_name, |(_, n)| n);
|
||||||
|
|
||||||
let result = match call_name {
|
let result = match call_name {
|
||||||
"init" => {
|
"init" => {
|
||||||
|
@ -20,16 +20,14 @@ pub fn bool_param(name: &str) -> bool {
|
|||||||
cmdline().any(|part| part == name1 || part == name2)
|
cmdline().any(|part| part == name1 || part == name2)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn param<'t>(name: &'t str, default: &'t str) -> &'t str {
|
pub fn param(name: &str) -> Option<&'static str> {
|
||||||
let prefix1 = &format!("dkl.{name}=");
|
let prefix1 = &format!("dkl.{name}=");
|
||||||
let prefix2 = &format!("direktil.{name}=");
|
let prefix2 = &format!("direktil.{name}=");
|
||||||
|
|
||||||
cmdline()
|
cmdline().find_map(|part| {
|
||||||
.find_map(|part| {
|
|
||||||
part.strip_prefix(prefix1)
|
part.strip_prefix(prefix1)
|
||||||
.or_else(|| part.strip_prefix(prefix2))
|
.or_else(|| part.strip_prefix(prefix2))
|
||||||
})
|
})
|
||||||
.unwrap_or(default)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NameAliases {
|
pub struct NameAliases {
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user