Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c4f636477 | |||
| 7b30eb4435 | |||
| 2e337f9957 | |||
| dff9142bdc | |||
| 74c8ae293d | |||
| f886692c7f | |||
| 96f801e27d | |||
| 3f7cd80a96 | |||
| 41c3f9badd | |||
| 01a0073e78 |
1084
Cargo.lock
generated
1084
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "init"
|
name = "init"
|
||||||
version = "2.5.0"
|
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"
|
||||||
|
|||||||
10
Dockerfile
10
Dockerfile
@ -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"]
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
pub mod bootstrap;
|
pub mod bootstrap;
|
||||||
pub mod connect_boot;
|
|
||||||
pub mod init;
|
pub mod init;
|
||||||
pub mod init_input;
|
pub mod init_input;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
@ -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!();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ pub async fn bootstrap(cfg: Config) {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// prepare system
|
||||||
let boot_version = utils::param("version").unwrap_or("current");
|
let boot_version = utils::param("version").unwrap_or("current");
|
||||||
let base_dir = &format!("/bootstrap/{boot_version}");
|
let base_dir = &format!("/bootstrap/{boot_version}");
|
||||||
|
|
||||||
@ -88,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 });
|
||||||
|
|||||||
@ -56,27 +56,72 @@ pub async fn setup(devs: &[CryptDev]) {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PREV_PW: Mutex<String> = Mutex::const_new(String::new());
|
struct PrevPw {
|
||||||
|
pw: String,
|
||||||
async fn crypt_open(crypt_dev: &str, dev_path: &str) -> Result<()> {
|
reuse: bool,
|
||||||
'open_loop: loop {
|
|
||||||
let mut prev_pw = PREV_PW.lock().await;
|
|
||||||
let prompt = if prev_pw.is_empty() {
|
|
||||||
format!("crypt password for {crypt_dev}? ")
|
|
||||||
} else {
|
|
||||||
format!("crypt password for {crypt_dev} (enter = reuse previous)? ")
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut pw = input::read_password(prompt).await;
|
|
||||||
if pw.is_empty() {
|
|
||||||
pw = prev_pw.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PrevPw {
|
||||||
|
fn is_set(&self) -> bool {
|
||||||
|
!self.pw.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_reuse(&self) -> bool {
|
||||||
|
self.reuse && self.is_set()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalidate(&mut self) {
|
||||||
|
self.pw = String::new();
|
||||||
|
self.reuse = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn input(&mut self, prompt: impl std::fmt::Display) -> String {
|
||||||
|
if self.can_reuse() {
|
||||||
|
info!("reusing password");
|
||||||
|
self.pw.clone()
|
||||||
|
} else if self.is_set() {
|
||||||
|
let pw =
|
||||||
|
input::read_password(format!("{prompt} (\"\" reuse, \"*\" auto-reuse)? ")).await;
|
||||||
|
|
||||||
|
match pw.as_str() {
|
||||||
|
"" => self.pw.clone(),
|
||||||
|
"*" => {
|
||||||
|
self.reuse = true;
|
||||||
|
self.pw.clone()
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.pw = pw.clone();
|
||||||
|
pw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pw = loop {
|
||||||
|
let pw = input::read_password(format!("{prompt}? ")).await;
|
||||||
if pw.is_empty() {
|
if pw.is_empty() {
|
||||||
error!("empty password provided!");
|
error!("empty password provided!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
break pw;
|
||||||
|
};
|
||||||
|
|
||||||
*prev_pw = pw.clone();
|
self.pw = pw.clone();
|
||||||
|
pw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PREV_PW: Mutex<PrevPw> = Mutex::const_new(PrevPw {
|
||||||
|
pw: String::new(),
|
||||||
|
reuse: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
async fn crypt_open(crypt_dev: &str, dev_path: &str) -> Result<()> {
|
||||||
|
'open_loop: loop {
|
||||||
|
let mut prev_pw = PREV_PW.lock().await;
|
||||||
|
|
||||||
|
let pw = prev_pw
|
||||||
|
.input(format!("crypt password for {crypt_dev}"))
|
||||||
|
.await;
|
||||||
|
|
||||||
if cryptsetup(&pw, ["open", dev_path, crypt_dev]).await? {
|
if cryptsetup(&pw, ["open", dev_path, crypt_dev]).await? {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -107,10 +152,13 @@ async fn crypt_open(crypt_dev: &str, dev_path: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// device looks initialized, don't allow format
|
// device looks initialized, don't allow format
|
||||||
warn!("{dev_path} looks initialized, formatting not allowed from init");
|
warn!("{dev_path} looks initialized, formatting not allowed from init");
|
||||||
|
|
||||||
|
prev_pw.invalidate();
|
||||||
|
|
||||||
match input::read_choice(["[r]etry", "[i]gnore"]).await {
|
match input::read_choice(["[r]etry", "[i]gnore"]).await {
|
||||||
'r' => continue 'open_loop,
|
'r' => continue 'open_loop,
|
||||||
'i' => return Ok(()),
|
'i' => return Ok(()),
|
||||||
@ -118,7 +166,6 @@ async fn crypt_open(crypt_dev: &str, dev_path: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async fn cryptsetup<const N: usize>(pw: &str, args: [&str; N]) -> Result<bool> {
|
async fn cryptsetup<const N: usize>(pw: &str, args: [&str; N]) -> Result<bool> {
|
||||||
let mut child = Command::new("cryptsetup")
|
let mut child = Command::new("cryptsetup")
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,10 @@ use crate::input;
|
|||||||
|
|
||||||
pub async fn run() {
|
pub async fn run() {
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
|
// give a bit of time for stdout
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
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 {
|
||||||
eprintln!("failed to forwards requests from socket: {e}");
|
eprintln!("failed to forwards requests from socket: {e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|||||||
Reference in New Issue
Block a user