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(mut action: impl AsyncFnMut() -> Result) -> 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) }