use eyre::{format_err, Result}; use log::{error, info, warn}; use std::collections::BTreeSet as Set; use std::os::unix::fs::symlink; use tokio::sync::Mutex; use tokio::{fs, process::Command}; use crate::{bootstrap::config::Config, cmd::version::version_string, dklog, input}; mod bootstrap; mod dmcrypt; mod lvm; mod networks; mod sshd; // devices used by any block target (DM, LVM...) static USED_DEVS: Mutex> = Mutex::const_new(Set::new()); 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); // seem to occasionaly race with Child::wait // tokio::spawn(child_reaper()); // open input channels tokio::spawn(crate::input::answer_requests_from_stdin()); tokio::spawn(crate::input::answer_requests_from_socket()); // 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)"); } // 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; // Wireguard VPN // TODO startVPN() // SSH service sshd::start(&cfg).await; // networks networks::setup(&cfg).await; // pre-lvm dmcrypt devs dmcrypt::setup(&cfg.pre_lvm_crypt).await; // LVM lvm::setup(&cfg).await; // post-lvm dmcrypt devs dmcrypt::setup(&cfg.crypt).await; // bootstrap the system bootstrap::bootstrap(cfg).await; // finalize retry(async || switch_root("/system").await).await; } async fn switch_root(root: &str) -> Result<()> { use std::ffi::CString; macro_rules! c { ($s:expr) => { CString::new($s)?.as_c_str() }; } info!("switching root"); dklog::LOG.close().await; nix::unistd::execv( c!("/sbin/switch_root"), &[ c!("switch_root"), c!("-c"), c!("/dev/console"), c!(root), c!("/sbin/init"), ], )?; Ok(()) } 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}"); } match fstype { "ext4" => { exec("fsck", &["-p", src]).await; } _ => {} } let mut args = vec![src, dst]; if !fstype.is_empty() { args.extend(&["-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 try_exec(prog: &str, args: &[&str]) -> Result<()> { let (cmd_str, mut cmd) = cmd_str(prog, args); info!("# {cmd_str}"); let s = cmd.status().await?; if s.success() { Ok(()) } else { Err(format_err!("command failed: {s}")) } } async fn exec(prog: &str, args: &[&str]) { retry_or_ignore(async || try_exec(prog, args).await).await; } async fn retry_or_ignore(mut action: impl AsyncFnMut() -> Result<()>) { loop { match action().await { Ok(_) => return, Err(e) => { error!("{e}"); match input::read_choice(["[r]etry", "[i]gnore", "[s]hell"]).await { 'r' => {} 'i' => return, 's' => exec_shell().await, _ => unreachable!(), } } } } } async fn retry(mut action: impl AsyncFnMut() -> Result) -> T { loop { match action().await { Ok(v) => return v, Err(e) => { error!("{e}"); match input::read_choice(["[r]etry", "[s]hell"]).await { 'r' => {} 's' => exec_shell().await, _ => unreachable!(), } } } } } 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) } #[allow(unused)] async fn child_reaper() { use nix::sys::wait::{waitpid, WaitPidFlag}; use nix::unistd::Pid; use tokio::signal::unix::{signal, SignalKind}; let Ok(mut sigs) = signal(SignalKind::child()).inspect_err(|e| warn!("failed to setup SIGCHLD handler: {e}")) else { return; }; loop { sigs.recv().await; let _ = waitpid(Some(Pid::from_raw(-1)), Some(WaitPidFlag::WNOHANG)); } }