2018-11-04 17:04:22 +02:00

235 lines
6.2 KiB
Rust

#[macro_use]
extern crate log;
extern crate failure;
extern crate nix;
extern crate simplelog;
extern crate structopt;
extern crate tempfile;
extern crate which;
use nix::sys::signal;
mod error;
mod mountstack;
mod process;
mod tool;
use error::*;
use failure::{Fail, ResultExt};
use mountstack::{Filesystem, MountStack};
use process::CommandExt;
use simplelog::*;
use std::fs;
use std::path::PathBuf;
use std::process::exit;
use std::thread;
use std::time::Duration;
use structopt::StructOpt;
use tempfile::tempdir;
use tool::Tool;
static MKINITCPIO: &'static str = "MODULES=()
BINARIES=()
FILES=()
HOOKS=(base udev block filesystems keyboard fsck)";
static JOURNALD_CONF: &'static str = "
[Journal]
Storage=volatile
SystemMaxUse=16M
";
#[derive(StructOpt)]
#[structopt(name = "alma", about = "Arch Linux Mobile Appliance")]
enum App {
#[structopt(name = "create", about = "Create a new Arch Linux USB")]
Create {
#[structopt(
parse(from_os_str),
help = "Path starting with /dev/disk/by-id for the USB drive"
)]
disk: PathBuf,
#[structopt(
short = "p",
long = "extra-packages",
value_name = "package",
help = "Additional pacakges to install"
)]
extra_packages: Vec<String>,
},
}
fn create(disk: PathBuf, extra_packages: Vec<String>) -> Result<(), Error> {
let sgdisk = Tool::find("sgdisk")?;
let sync = Tool::find("sync")?;
let pacstrap = Tool::find("pacstrap")?;
let arch_chroot = Tool::find("arch-chroot")?;
let genfstab = Tool::find("genfstab")?;
let mkfat = Tool::find("mkfs.fat")?;
let mkbtrfs = Tool::find("mkfs.btrfs")?;
let mut mount_stack = MountStack::new();
if !(disk.starts_with("/dev/disk/by-id")
&& (disk
.file_name()
.and_then(|s| s.to_str())
.filter(|ref f| f.starts_with("usb-"))
.is_some()))
{
return Err(ErrorKind::NotUSB.into());
}
let mount_point = tempdir().context(ErrorKind::TmpDirError)?;
info!("Partitioning the disk");
sgdisk
.execute()
.args(&[
"-Z",
"-o",
"--new=1::+10M",
"--new=2::+500M",
"--largest-new=3",
"--typecode=1:EF02",
"--typecode=2:EF00",
]).arg(&disk)
.run(ErrorKind::Partitioning)?;
thread::sleep(Duration::from_millis(1000));
info!("Formatting filesystems");
mkfat
.execute()
.arg("-F32")
.arg(format!("{}-part2", disk.display()))
.run(ErrorKind::Formatting)?;
mkbtrfs
.execute()
.arg("-f")
.arg(format!("{}-part3", disk.display()))
.run(ErrorKind::Formatting)?;
info!("Mounting filesystems to {}", mount_point.path().display());
mount_stack
.mount(
&PathBuf::from(format!("{}-part3", disk.display())),
&mount_point.path(),
Filesystem::Btrfs,
Some("compress=zstd"),
).context(ErrorKind::Mounting)?;
let boot_point = mount_point.path().join("boot");
fs::create_dir(&boot_point).context(ErrorKind::CreateBoot)?;
mount_stack
.mount(
&PathBuf::from(format!("{}-part2", disk.display())),
&boot_point,
Filesystem::Vfat,
None,
).context(ErrorKind::Mounting)?;
info!("Bootstrapping system");
pacstrap
.execute()
.arg("-c")
.arg(mount_point.path())
.args(&[
"base",
"grub",
"efibootmgr",
"intel-ucode",
"networkmanager",
"btrfs-progs",
"broadcom-wl",
]).args(extra_packages)
.run(ErrorKind::Pacstrap)?;
let fstab = genfstab
.execute()
.arg("-U")
.arg(mount_point.path())
.run_text_output(ErrorKind::Fstab)?
.replace("relatime", "noatime");
debug!("fstab:\n{}", fstab);
fs::write(mount_point.path().join("etc/fstab"), fstab).context(ErrorKind::Fstab)?;
arch_chroot
.execute()
.arg(mount_point.path())
.args(&["systemctl", "enable", "NetworkManager"])
.run(ErrorKind::PostInstallation)?;
info!("Configuring journald");
fs::write(
mount_point.path().join("etc/systemd/journald.conf"),
JOURNALD_CONF,
).context(ErrorKind::PostInstallation)?;
info!("Generating initramfs");
fs::write(mount_point.path().join("etc/mkinitcpio.conf"), MKINITCPIO)
.context(ErrorKind::Initramfs)?;
arch_chroot
.execute()
.arg(mount_point.path())
.args(&["mkinitcpio", "-p", "linux"])
.run(ErrorKind::Initramfs)?;
info!("Installing the Bootloader");
arch_chroot
.execute()
.arg(mount_point.path())
.args(&["bash", "-c"])
.arg(format!("grub-install --target=i386-pc --boot-directory /boot {} && grub-install --target=x86_64-efi --efi-directory /boot --boot-directory /boot --removable && grub-mkconfig -o /boot/grub/grub.cfg", disk.display()))
.run(ErrorKind::Bootloader)?;
info!("Unmounting filesystems");
drop(mount_stack);
sync.execute().run(ErrorKind::Sync)?;
Ok(())
}
extern "C" fn handle_sigint(_: i32) {
warn!("Interrupted");
}
fn main() {
let app = App::from_args();
CombinedLogger::init(vec![
TermLogger::new(LevelFilter::Debug, Config::default()).unwrap(),
]).unwrap();
let sig_action = signal::SigAction::new(
signal::SigHandler::Handler(handle_sigint),
signal::SaFlags::empty(),
signal::SigSet::empty(),
);
unsafe {
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
signal::sigaction(signal::SIGTERM, &sig_action).unwrap();
signal::sigaction(signal::SIGQUIT, &sig_action).unwrap();
}
let result = match app {
App::Create {
disk,
extra_packages,
} => create(disk, extra_packages),
};
match result {
Ok(()) => {
exit(0);
}
Err(error) => {
error!("{}", error);
if let Some(cause) = error.cause() {
error!(" {}", cause);
}
exit(1);
}
}
}