use eyre::{format_err, Result}; use log::{error, info, warn}; use std::os::unix::fs::symlink; use std::{fs, io, process::Command}; use crate::{bootstrap::config::Config, cmd::version::version_string}; mod networks; mod sshd; pub fn run() -> Result<()> { if std::process::id() != 1 { return Err(format_err!( "init must run as PID 1, not {}", std::process::id() )); } info!("Welcome to {}", version_string()); let uname = nix::sys::utsname::uname()?; let kernel_version = uname.release().to_string_lossy(); info!("Linux version {kernel_version}"); let cfg: Config = retry(|| { let cfg = fs::read("config.yaml").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}")) }); info!("config loaded"); info!("anti-phishing-code: {}", cfg.anti_phishing_code); // mount basic filesystems mount("none", "/proc", "proc", None); mount("none", "/sys", "sysfs", None); mount("none", "/dev", "devtmpfs", None); mount("none", "/dev/pts", "devpts", Some("gid=5,mode=620")); // mount modules if let Some(ref modules) = cfg.modules { mount(modules, "/modules", "squashfs", None); fs::create_dir_all("/lib/modules")?; let modules_path = &format!("/modules/lib/modules/{kernel_version}"); if !fs::exists(modules_path)? { let e = format_err!("invalid modules package: {modules_path} should exist"); error!("{e}"); return Err(e); } symlink(modules_path, format!("/lib/modules/{kernel_version}"))?; } else { warn!("modules NOT mounted (not configured)"); } // init devices info!("initializing devices"); start_daemon("udevd", &[]); exec("udevadm", &["trigger", "-c", "add", "-t", "devices"]); exec("udevadm", &["trigger", "-c", "add", "-t", "subsystems"]); exec("udevadm", &["settle"]); // networks networks::setup(&cfg)?; // Wireguard VPN // TODO startVPN() // SSH service sshd::start(&cfg); // dmcrypt blockdevs // TODO setupCrypt(cfg.PreLVMCrypt, map[string]string{}); // LVM // TODO setupLVM(cfg); // bootstrap the system // TODO bootstrap(cfg); // finalize // TODO finalizeBoot(); exec_shell(); Ok(()) } fn mount(src: &str, dst: &str, fstype: &str, opts: Option<&str>) { if let Err(e) = fs::create_dir_all(dst) { 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); } fn start_daemon(prog: &str, args: &[&str]) { let cmd_str = sh_str(prog, args); retry_or_ignore(|| { info!("starting as daemon: {cmd_str}"); let mut cmd = Command::new(prog); cmd.args(args); cmd.spawn()?; Ok(()) }); } fn exec(prog: &str, args: &[&str]) { let cmd_str = sh_str(prog, args); retry_or_ignore(|| { info!("# {cmd_str}"); let mut cmd = Command::new(prog); cmd.args(args); let s = cmd.status()?; if s.success() { Ok(()) } else { Err(format_err!("command failed: {s}")) } }); } fn retry_or_ignore(mut action: impl FnMut() -> Result<()>) { loop { match action() { Ok(_) => return, Err(e) => { error!("{e}"); loop { eprint!("[r]etry, [i]gnore, or [s]hell? "); let mut line = String::new(); io::stdin().read_line(&mut line).unwrap(); match line.trim() { "r" => break, "i" => return, "s" => { exec_shell(); break; } v => { eprintln!("invalid choice: {v:?}"); } }; } } } } } fn retry(mut action: impl FnMut() -> Result) -> T { loop { match action() { Ok(v) => return v, Err(e) => { error!("{e}"); loop { eprint!("[r]etry, or [s]hell? "); let mut line = String::new(); io::stdin().read_line(&mut line).unwrap(); match line.trim() { "r" => break, "s" => { exec_shell(); break; } v => { eprintln!("invalid choice: {v:?}"); } }; } } } } } 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(); } fn sh_str(prog: &str, args: &[&str]) -> String { 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))); } buf }