214 lines
5.4 KiB
Rust
214 lines
5.4 KiB
Rust
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<T>(mut action: impl FnMut() -> Result<T>) -> 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
|
|
}
|