234 lines
6.1 KiB
Rust
234 lines
6.1 KiB
Rust
use eyre::{format_err, Result};
|
|
use log::{error, info, warn};
|
|
use std::os::unix::fs::symlink;
|
|
use tokio::{fs, process::Command};
|
|
|
|
use crate::{bootstrap::config::Config, cmd::version::version_string};
|
|
|
|
mod dmcrypt;
|
|
mod networks;
|
|
mod sshd;
|
|
|
|
pub async fn run() {
|
|
if std::process::id() != 1 {
|
|
error!("init must run as PID 1, not {}", std::process::id());
|
|
std::process::exit(1);
|
|
}
|
|
|
|
unsafe {
|
|
use std::env;
|
|
env::set_var("PATH", "/bin:/sbin:/usr/bin:/usr/sbin");
|
|
env::set_var("HOME", "/root");
|
|
}
|
|
|
|
info!("Welcome to {}", version_string());
|
|
|
|
let uname = nix::sys::utsname::uname().expect("uname should work");
|
|
let kernel_version = uname.release().to_string_lossy();
|
|
info!("Linux version {kernel_version}");
|
|
|
|
let cfg: Config = retry(async || {
|
|
let cfg = (fs::read("config.yaml").await)
|
|
.map_err(|e| format_err!("failed to read config: {e}"))?;
|
|
serde_yaml::from_slice(cfg.as_slice())
|
|
.map_err(|e| format_err!("failed to parse config: {e}"))
|
|
})
|
|
.await;
|
|
|
|
info!("config loaded");
|
|
info!("anti-phishing-code: {}", cfg.anti_phishing_code);
|
|
|
|
// mount basic filesystems
|
|
mount("none", "/proc", "proc", None).await;
|
|
mount("none", "/sys", "sysfs", None).await;
|
|
mount("none", "/dev", "devtmpfs", None).await;
|
|
mount("none", "/dev/pts", "devpts", Some("gid=5,mode=620")).await;
|
|
|
|
// mount modules
|
|
if let Some(ref modules) = cfg.modules {
|
|
retry_or_ignore(async || {
|
|
info!("mounting modules");
|
|
mount(modules, "/modules", "squashfs", None).await;
|
|
|
|
fs::create_dir_all("/lib/modules").await?;
|
|
let modules_path = &format!("/modules/lib/modules/{kernel_version}");
|
|
|
|
if !std::fs::exists(modules_path)? {
|
|
return Err(format_err!(
|
|
"invalid modules package: {modules_path} should exist"
|
|
));
|
|
}
|
|
|
|
symlink(modules_path, format!("/lib/modules/{kernel_version}"))?;
|
|
Ok(())
|
|
})
|
|
.await;
|
|
} else {
|
|
warn!("modules NOT mounted (not configured)");
|
|
}
|
|
|
|
// open input channels
|
|
tokio::spawn(crate::input::answer_requests_from_stdin());
|
|
tokio::spawn(crate::input::answer_requests_from_socket());
|
|
|
|
// init devices
|
|
info!("initializing devices");
|
|
start_daemon("udevd", &[]).await;
|
|
|
|
exec("udevadm", &["trigger", "-c", "add", "-t", "devices"]).await;
|
|
exec("udevadm", &["trigger", "-c", "add", "-t", "subsystems"]).await;
|
|
exec("udevadm", &["settle"]).await;
|
|
|
|
// networks
|
|
networks::setup(&cfg).await;
|
|
|
|
// Wireguard VPN
|
|
// TODO startVPN()
|
|
|
|
// SSH service
|
|
sshd::start(&cfg).await;
|
|
|
|
// dmcrypt blockdevs
|
|
dmcrypt::setup(&cfg).await;
|
|
// TODO setupCrypt(cfg.PreLVMCrypt, map[string]string{});
|
|
|
|
// LVM
|
|
// TODO setupLVM(cfg);
|
|
|
|
// bootstrap the system
|
|
// TODO bootstrap(cfg);
|
|
|
|
// finalize
|
|
// TODO finalizeBoot();
|
|
|
|
exec_shell().await;
|
|
}
|
|
|
|
async fn mount(src: &str, dst: &str, fstype: &str, opts: Option<&str>) {
|
|
if let Err(e) = fs::create_dir_all(dst).await {
|
|
error!("failed to create dir {dst}: {e}");
|
|
}
|
|
|
|
let mut args = vec![src, dst, "-t", fstype];
|
|
if let Some(opts) = opts {
|
|
args.extend(["-o", opts]);
|
|
}
|
|
exec("mount", &args).await;
|
|
}
|
|
|
|
async fn start_daemon(prog: &str, args: &[&str]) {
|
|
let (cmd_str, mut cmd) = cmd_str(prog, args);
|
|
retry_or_ignore(async || {
|
|
info!("starting as daemon: {cmd_str}");
|
|
cmd.spawn()?;
|
|
Ok(())
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn exec(prog: &str, args: &[&str]) {
|
|
let (cmd_str, mut cmd) = cmd_str(prog, args);
|
|
retry_or_ignore(async || {
|
|
info!("# {cmd_str}");
|
|
let s = cmd.status().await?;
|
|
if s.success() {
|
|
Ok(())
|
|
} else {
|
|
Err(format_err!("command failed: {s}"))
|
|
}
|
|
})
|
|
.await;
|
|
}
|
|
|
|
async fn input_line() -> String {
|
|
use tokio::io::{stdin, AsyncBufReadExt, BufReader};
|
|
let mut stdin = BufReader::new(stdin()).lines();
|
|
let Ok(line) = stdin.next_line().await else {
|
|
panic!("unable to read a line");
|
|
};
|
|
line.expect("stdin should not the closed")
|
|
}
|
|
|
|
async fn retry_or_ignore(mut action: impl AsyncFnMut() -> Result<()>) {
|
|
loop {
|
|
match action().await {
|
|
Ok(_) => return,
|
|
Err(e) => {
|
|
error!("{e}");
|
|
|
|
loop {
|
|
eprint!("[r]etry, [i]gnore, or [s]hell? ");
|
|
|
|
match input_line().await.trim() {
|
|
"r" => break,
|
|
"i" => return,
|
|
"s" => {
|
|
exec_shell().await;
|
|
break;
|
|
}
|
|
v => {
|
|
eprintln!("invalid choice: {v:?}");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn retry<T>(mut action: impl AsyncFnMut() -> Result<T>) -> T {
|
|
loop {
|
|
match action().await {
|
|
Ok(v) => return v,
|
|
Err(e) => {
|
|
error!("{e}");
|
|
|
|
loop {
|
|
eprint!("[r]etry, or [s]hell? ");
|
|
|
|
match input_line().await.trim() {
|
|
"r" => break,
|
|
"s" => {
|
|
exec_shell().await;
|
|
break;
|
|
}
|
|
v => {
|
|
eprintln!("invalid choice: {v:?}");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn exec_shell() {
|
|
let mut child = match Command::new("ash").spawn() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
error!("failed to exec shell: {e}");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let _ = child.wait().await;
|
|
}
|
|
|
|
fn cmd_str(prog: &str, args: &[&str]) -> (String, Command) {
|
|
use std::borrow::Cow;
|
|
|
|
let mut buf = String::new();
|
|
|
|
buf.push_str(&shell_escape::escape(Cow::Borrowed(prog)));
|
|
|
|
for &arg in args {
|
|
buf.push(' ');
|
|
buf.push_str(&shell_escape::escape(Cow::Borrowed(arg)));
|
|
}
|
|
|
|
let mut cmd = Command::new(prog);
|
|
cmd.args(args);
|
|
|
|
(buf, cmd)
|
|
}
|