installer refactored to support RealmFS

This commit is contained in:
Bruce Leidl 2019-01-30 14:15:44 -05:00
parent a0d39431e8
commit e5d0b7f6f2
2 changed files with 203 additions and 89 deletions

View File

@ -43,9 +43,7 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
} }
fn run_install(disk: Disk, passphrase: String) -> Result<()> { fn run_install(disk: Disk, passphrase: String) -> Result<()> {
let mut install = Installer::new(); let mut install = Installer::new(disk.path(), &passphrase);
install.set_target(disk.path().to_str().unwrap());
install.set_passphrase(&passphrase);
install.set_install_syslinux(true); install.set_install_syslinux(true);
install.verify()?; install.verify()?;
install.run() install.run()

View File

@ -6,6 +6,9 @@ use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::time::Instant; use std::time::Instant;
use libcitadel::util::{mount,exec_cmdline_with_output};
use libcitadel::RealmFS;
use super::util; use super::util;
use super::Result; use super::Result;
@ -24,6 +27,7 @@ const BTRFS: &str = "/bin/btrfs";
const MOUNT: &str = "/bin/mount"; const MOUNT: &str = "/bin/mount";
const UMOUNT: &str = "/bin/umount"; const UMOUNT: &str = "/bin/umount";
const CHOWN: &str = "/bin/chown"; const CHOWN: &str = "/bin/chown";
const CP: &str = "/bin/cp";
const TAR: &str = "/bin/tar"; const TAR: &str = "/bin/tar";
const XZ: &str = "/bin/xz"; const XZ: &str = "/bin/xz";
const DD: &str = "/bin/dd"; const DD: &str = "/bin/dd";
@ -40,31 +44,72 @@ const DEFAULT_ARTIFACT_DIRECTORY: &str = "/run/images";
const KERNEL_CMDLINE: &str = "add_efi_memmap intel_iommu=off cryptomgr.notests rcupdate.rcu_expedited=1 rcu_nocbs=0-64 tsc=reliable no_timer_check noreplace-smp i915.fastboot=1 quiet splash"; const KERNEL_CMDLINE: &str = "add_efi_memmap intel_iommu=off cryptomgr.notests rcupdate.rcu_expedited=1 rcu_nocbs=0-64 tsc=reliable no_timer_check noreplace-smp i915.fastboot=1 quiet splash";
const MAIN_REALM_CONFIG: &str =
r###"\
realmfs = "main"
realmfs-write = true
"###;
const LIVE_REALM_CONFIG: &str =
r###"\
realmfs = "base"
realmfs-write = false
"###;
#[derive(PartialEq)]
enum InstallType {
LiveSetup,
Install,
}
pub struct Installer { pub struct Installer {
_type: InstallType,
install_syslinux: bool, install_syslinux: bool,
target_device: String, storage_base: PathBuf,
passphrase: String, target_device: Option<PathBuf>,
passphrase: Option<String>,
artifact_directory: String, artifact_directory: String,
logfile: Option<RefCell<File>>, logfile: Option<RefCell<File>>,
} }
impl Installer { impl Installer {
pub fn new() -> Installer { pub fn new<P: AsRef<Path>>(target_device: P, passphrase: &str) -> Installer {
let target_device = Some(target_device.as_ref().to_owned());
let passphrase = Some(passphrase.to_owned());
Installer { Installer {
_type: InstallType::Install,
install_syslinux: true, install_syslinux: true,
target_device: String::new(), storage_base: PathBuf::from(INSTALL_MOUNT),
passphrase: String::new(), target_device,
passphrase,
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
logfile: None, logfile: None,
} }
} }
pub fn set_target(&mut self, target: &str) { pub fn new_livesetup() -> Installer {
self.target_device = target.to_owned() Installer {
_type: InstallType::LiveSetup,
install_syslinux: false,
storage_base: PathBuf::from("/sysroot/storage"),
target_device: None,
passphrase: None,
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
logfile: None,
}
} }
pub fn set_passphrase(&mut self, passphrase: &str) { fn target(&self) -> &Path {
self.passphrase = passphrase.to_owned() self.target_device.as_ref().expect("No target device")
}
fn passphrase(&self) -> &str {
self.passphrase.as_ref().expect("No passphrase")
}
fn storage(&self) -> &Path {
&self.storage_base
} }
pub fn set_install_syslinux(&mut self, val: bool) { pub fn set_install_syslinux(&mut self, val: bool) {
@ -83,14 +128,8 @@ impl Installer {
kernel_img.as_str(), EXTRA_IMAGE_NAME, kernel_img.as_str(), EXTRA_IMAGE_NAME,
]; ];
if self.target_device.is_empty() { if !self.target().exists() {
bail!("No target device set in install configuration"); bail!("Target device {} does not exist", self.target().display());
}
if self.passphrase.is_empty() {
bail!("No passphrase set in install configuration");
}
if !Path::new(&self.target_device).exists() {
bail!("Target device {} does not exist", self.target_device);
} }
for tool in tools { for tool in tools {
@ -105,65 +144,85 @@ impl Installer {
} }
} }
if !self.artifact_path("appimg-rootfs.tar").exists() && !self.artifact_path("appimg-rootfs.tar.xz").exists() {
bail!("Required component appimg-rootfs.tar(.xz) does not exist in {}",self.artifact_directory);
}
Ok(()) Ok(())
} }
pub fn run(&self) -> Result<()> { pub fn run(&self) -> Result<()> {
match self._type {
InstallType::Install => self.run_install(),
InstallType::LiveSetup => self.run_live_setup(),
}
}
pub fn run_install(&self) -> Result<()> {
let start = Instant::now(); let start = Instant::now();
fs::create_dir_all(INSTALL_MOUNT)?;
self.partition_disk()?; self.partition_disk()?;
self.setup_luks()?; self.setup_luks()?;
self.setup_lvm()?; self.setup_lvm()?;
self.setup_boot()?; self.setup_boot()?;
self.create_storage()?; self.create_storage()?;
self.install_rootfs_partitions()?;
self.output("\n")?; self.finish_install()?;
self.header("Installing rootfs partitions\n")?;
let rootfs = self.artifact_path("citadel-rootfs.img");
self.cmd(CITADEL_IMAGE, format!("install-rootfs --skip-sha {}", rootfs.display()))?;
self.cmd(CITADEL_IMAGE, format!("install-rootfs --skip-sha --no-prefer {}", rootfs.display()))?;
self.cmd(LSBLK, format!("-o NAME,SIZE,TYPE,FSTYPE {}", &self.target_device))?;
self.cmd(VGCHANGE, "-an citadel")?;
self.cmd(CRYPTSETUP, "luksClose luks-install")?;
self.header(format!("Install completed successfully in {} seconds", start.elapsed().as_secs()))?; self.header(format!("Install completed successfully in {} seconds", start.elapsed().as_secs()))?;
Ok(()) Ok(())
} }
pub fn live_setup(&self) -> Result<()> {
pub fn run_live_setup(&self) -> Result<()> {
self.cmd(MOUNT, "-t tmpfs var-tmpfs /sysroot/var")?; self.cmd(MOUNT, "-t tmpfs var-tmpfs /sysroot/var")?;
self.cmd(MOUNT, "-t tmpfs home-tmpfs /sysroot/home")?; self.cmd(MOUNT, "-t tmpfs home-tmpfs /sysroot/home")?;
let _ = fs::read("/sys/class/zram-control/hot_add")?; self.cmd(MOUNT, "-t tmpfs storage-tmpfs /sysroot/storage")?;
// Create an 8gb zram disk to use as /storage partition
fs::write("/sys/block/zram1/comp_algorithm", "lz4")?;
fs::write("/sys/block/zram1/disksize", "8G")?;
self.cmd(MKFS_BTRFS, "/dev/zram1")?;
self.cmd(MOUNT, "/dev/zram1 /sysroot/storage")?;
fs::create_dir_all("/sysroot/storage/realms")?; fs::create_dir_all("/sysroot/storage/realms")?;
self.cmd(MOUNT, "--bind /sysroot/storage/realms /sysroot/realms")?; self.cmd(MOUNT, "--bind /sysroot/storage/realms /sysroot/realms")?;
let cmdline = fs::read_to_string("/proc/cmdline")?; let cmdline = fs::read_to_string("/proc/cmdline")?;
if cmdline.contains("citadel.live") { if cmdline.contains("citadel.live") {
self.setup_storage(Path::new("/sysroot/storage"), false)?; self.setup_live_realm()?;
} }
Ok(())
}
fn setup_live_realm(&self) -> Result<()> {
self.cmd(CITADEL_IMAGE, format!("decompress /run/images/base-realmfs.img"))?;
let realmfs_dir = self.storage().join("realms/realmfs-images");
let base_realmfs = realmfs_dir.join("base-realmfs.img");
self.info(format!("creating directory {}", realmfs_dir.display()))?;
fs::create_dir_all(&realmfs_dir)?;
self.info(format!("creating symlink {} -> {}", base_realmfs.display(), "/run/images/base-realmfs.img"))?;
unixfs::symlink("/run/images/base-realmfs.img", &base_realmfs)?;
self.mount_realmfs()?;
self.setup_storage()?;
/*
self.setup_main_realm()?;
fs::write(self.storage().join("realms/realm-main/config"), "realmfs = \"base\"")?;
let rootfs = self.storage().join("realms/realm-main/rootfs");
fs::remove_file(&rootfs)?;
unixfs::symlink("/run/images/base-realmfs.mountpoint", &rootfs)?;
self.info("Creating /Shared realms directory")?;
fs::create_dir_all(self.storage().join("realms/Shared"))?;
self.cmd(CHOWN, format!("1000:1000 {}/realms/Shared", self.storage().display()))?;
*/
Ok(())
}
pub fn mount_realmfs(&self) -> Result<()> {
self.info("Creating loop device for /run/images/base-realmfs.img")?;
let args = format!("--offset 4096 -f --show /run/images/base-realmfs.img");
let loopdev = exec_cmdline_with_output("/sbin/losetup", args)?;
self.info("Mounting image at /run/images/base-realmfs.mountpoint")?;
fs::create_dir_all("/run/images/base-realmfs.mountpoint")?;
mount(&loopdev, "/run/images/base-realmfs.mountpoint", Some("-oro"))?;
Ok(()) Ok(())
} }
fn partition_disk(&self) -> Result<()> { fn partition_disk(&self) -> Result<()> {
self.header("Partitioning target disk")?; self.header("Partitioning target disk")?;
self.cmd(BLKDEACTIVATE, &self.target_device)?; self.cmd(BLKDEACTIVATE, self.target().display().to_string())?;
self.parted("mklabel gpt")?; self.parted("mklabel gpt")?;
self.parted("mkpart boot fat32 1MiB 513MiB")?; self.parted("mkpart boot fat32 1MiB 513MiB")?;
self.parted("set 1 boot on")?; self.parted("set 1 boot on")?;
@ -173,13 +232,14 @@ impl Installer {
} }
fn parted(&self, cmdline: &str) -> Result<()> { fn parted(&self, cmdline: &str) -> Result<()> {
let args = format!("-s {} {}", self.target_device, cmdline); let args = format!("-s {} {}", self.target().display(), cmdline);
self.cmd(PARTED, args) self.cmd(PARTED, args)
} }
fn setup_luks(&self) -> Result<()> { fn setup_luks(&self) -> Result<()> {
self.header("Setting up LUKS disk encryption")?; self.header("Setting up LUKS disk encryption")?;
fs::write(LUKS_PASSPHRASE_FILE, self.passphrase.as_bytes())?; fs::create_dir_all(INSTALL_MOUNT)?;
fs::write(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
let luks_partition = self.target_partition(2); let luks_partition = self.target_partition(2);
let args = format!( let args = format!(
@ -278,7 +338,7 @@ impl Installer {
if !mbrbin.exists() { if !mbrbin.exists() {
bail!("Could not find MBR image: {}", mbrbin.display()); bail!("Could not find MBR image: {}", mbrbin.display());
} }
let args = format!("bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target_device); let args = format!("bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display());
self.cmd(DD, args)?; self.cmd(DD, args)?;
self.parted("set 1 legacy_boot on")?; self.parted("set 1 legacy_boot on")?;
Ok(()) Ok(())
@ -304,79 +364,122 @@ impl Installer {
self.header("Setting up /storage partition")?; self.header("Setting up /storage partition")?;
self.cmd(MKFS_BTRFS, "/dev/mapper/citadel-storage")?; self.cmd(MKFS_BTRFS, "/dev/mapper/citadel-storage")?;
self.cmd(MOUNT, format!("/dev/mapper/citadel-storage {}", INSTALL_MOUNT))?; self.cmd(MOUNT, format!("/dev/mapper/citadel-storage {}", INSTALL_MOUNT))?;
self.setup_storage(Path::new(INSTALL_MOUNT), true)?; self.setup_storage()?;
self.cmd(UMOUNT, INSTALL_MOUNT)?; self.cmd(UMOUNT, INSTALL_MOUNT)?;
Ok(()) Ok(())
} }
fn setup_storage(&self, base: &Path, copy_resources: bool) -> Result<()> { fn setup_storage(&self) -> Result<()> {
if copy_resources { if self._type == InstallType::Install {
self.setup_storage_resources(base)?; self.setup_storage_resources()?;
self.setup_base_realmfs()?;
} }
self.setup_base_appimg(base)?; self.setup_main_realm()?;
self.setup_main_realm(base)?;
self.info("Creating /Shared realms directory")?; self.info("Creating /Shared realms directory")?;
fs::create_dir_all(base.join("realms/Shared"))?; fs::create_dir_all(self.storage().join("realms/Shared"))?;
self.cmd(CHOWN, format!("1000:1000 {}/realms/Shared", base.display()))?; self.cmd(CHOWN, format!("1000:1000 {}/realms/Shared", self.storage().display()))?;
Ok(()) Ok(())
} }
fn setup_base_appimg(&self, base: &Path) -> Result<()> { fn setup_base_realmfs(&self) -> Result<()> {
self.header("Unpacking appimg rootfs")?; let realmfs_dir = self.storage().join("realms/realmfs-images");
let appimg_dir = base.join("appimg"); fs::create_dir_all(&realmfs_dir)?;
fs::create_dir_all(&appimg_dir)?; self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
self.cmd(BTRFS, format!("subvolume create {}/base.appimg", appimg_dir.display()))?; self.cmd(CITADEL_IMAGE, format!("decompress {}/base-realmfs.img", realmfs_dir.display()))?;
let xz_rootfs = self.artifact_path("appimg-rootfs.tar.xz");
if xz_rootfs.exists() {
self.cmd(XZ, format!("-d {}", xz_rootfs.display()))?;
}
let rootfs_bundle = self.artifact_path("appimg-rootfs.tar");
self.cmd(TAR, format!("-C {}/base.appimg -xf {}", appimg_dir.display(), rootfs_bundle.display()))?;
self.info("Creating main-realmfs as fork of base-realmfs")?;
let base_path = realmfs_dir.join("base-realmfs.img");
let base_image = RealmFS::load_from_path(base_path, "base")?;
base_image.fork("main")?;
fs::write(self.storage().join("realms/config"), "realmfs=\"main\"\n")?;
Ok(()) Ok(())
} }
fn setup_main_realm(&self, base: &Path) -> Result<()> { fn setup_main_realm(&self) -> Result<()> {
self.header("Creating main realm")?; self.header("Creating main realm")?;
let realm = base.join("realms/realm-main"); let realm = self.storage().join("realms/realm-main");
let home = realm.join("home");
self.info("Creating home directory /realms/realm-main/home")?; self.info("Creating home directory /realms/realm-main/home")?;
let home = realm.join("home");
fs::create_dir_all(&home)?; fs::create_dir_all(&home)?;
self.cmd(BTRFS, format!("subvolume snapshot {}/appimg/base.appimg {}/rootfs",
base.display(), realm.display()))?;
self.info("Copying .bashrc and .profile into home diectory")?; self.info("Copying .bashrc and .profile into home diectory")?;
fs::copy(realm.join("rootfs/home/user/.bashrc"), home.join(".bashrc"))?; fs::copy(self.skel().join("bashrc"), home.join(".bashrc"))?;
fs::copy(realm.join("rootfs/home/user/.profile"), home.join(".profile"))?; fs::copy(self.skel().join("profile"), home.join(".profile"))?;
self.cmd(CHOWN, format!("-R 1000:1000 {}", home.display()))?; self.cmd(CHOWN, format!("-R 1000:1000 {}", home.display()))?;
self.info("Creating main realm config file")?;
fs::write(realm.join("config"), self.main_realm_config())?;
/*
self.info("Creating rootfs symlink")?;
unixfs::symlink(
format!("/run/images/{}-realmfs.mountpoint", self.main_realmfs()),
format!("{}/rootfs", realm.display()))?;
*/
self.info("Creating default.realm symlink")?; self.info("Creating default.realm symlink")?;
unixfs::symlink("realm-main", base.join("realms/default.realm"))?; unixfs::symlink("realm-main", self.storage().join("realms/default.realm"))?;
Ok(()) Ok(())
} }
fn setup_storage_resources(&self, base: &Path) -> Result<()> { fn setup_storage_resources(&self) -> Result<()> {
let channel = util::rootfs_channel(); let channel = util::rootfs_channel();
let resources = base.join("resources").join(channel); let resources = self.storage().join("resources").join(channel);
fs::create_dir_all(&resources)?; fs::create_dir_all(&resources)?;
self.copy_artifact(EXTRA_IMAGE_NAME, &resources)?; self.sparse_copy_artifact(EXTRA_IMAGE_NAME, &resources)?;
let kernel_img = self.kernel_imagename(); let kernel_img = self.kernel_imagename();
self.copy_artifact(&kernel_img, &resources)?; self.sparse_copy_artifact(&kernel_img, &resources)?;
Ok(()) Ok(())
} }
fn install_rootfs_partitions(&self) -> Result<()> {
self.header("Installing rootfs partitions")?;
let rootfs = self.artifact_path("citadel-rootfs.img");
self.cmd(CITADEL_IMAGE, format!("install-rootfs --skip-sha {}", rootfs.display()))?;
self.cmd(CITADEL_IMAGE, format!("install-rootfs --skip-sha --no-prefer {}", rootfs.display()))?;
Ok(())
}
fn finish_install(&self) -> Result<()> {
self.cmd(LSBLK, format!("-o NAME,SIZE,TYPE,FSTYPE {}", self.target().display()))?;
self.cmd(VGCHANGE, "-an citadel")?;
self.cmd(CRYPTSETUP, "luksClose luks-install")?;
Ok(())
}
fn main_realm_config(&self) -> &str {
match self._type {
InstallType::Install => MAIN_REALM_CONFIG,
InstallType::LiveSetup => LIVE_REALM_CONFIG,
}
}
/*
fn main_realmfs(&self) -> &str {
match self._type {
InstallType::Install => "main",
InstallType::LiveSetup => "base",
}
}
*/
fn skel(&self) -> &Path{
match self._type {
InstallType::Install => Path::new("/etc/skel"),
InstallType::LiveSetup => Path::new("/sysroot/etc/skel"),
}
}
fn kernel_imagename(&self) -> String { fn kernel_imagename(&self) -> String {
let utsname = util::uname(); let utsname = util::uname();
let v = utsname.release().split("-").collect::<Vec<_>>(); let v = utsname.release().split("-").collect::<Vec<_>>();
@ -384,7 +487,7 @@ impl Installer {
} }
fn target_partition(&self, num: usize) -> String { fn target_partition(&self, num: usize) -> String {
format!("{}{}", self.target_device, num) format!("{}{}", self.target().display(), num)
} }
fn artifact_path(&self, filename: &str) -> PathBuf { fn artifact_path(&self, filename: &str) -> PathBuf {
@ -392,6 +495,14 @@ impl Installer {
} }
fn copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> { fn copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
self._copy_artifact(filename, target, false)
}
fn sparse_copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
self._copy_artifact(filename, target, true)
}
fn _copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P, sparse: bool) -> Result<()> {
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?; self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
let src = self.artifact_path(filename); let src = self.artifact_path(filename);
let target = target.as_ref(); let target = target.as_ref();
@ -399,10 +510,15 @@ impl Installer {
fs::create_dir_all(target)?; fs::create_dir_all(target)?;
} }
let dst = target.join(filename); let dst = target.join(filename);
fs::copy(src, dst)?; if sparse {
self.cmd(CP, format!("--sparse=always {} {}", src.display(), dst.display()))?;
} else {
fs::copy(src, dst)?;
}
Ok(()) Ok(())
} }
fn header<S: AsRef<str>>(&self, s: S) -> Result<()> { fn header<S: AsRef<str>>(&self, s: S) -> Result<()> {
self.output(format!("\n[+] {}\n", s.as_ref())) self.output(format!("\n[+] {}\n", s.as_ref()))
} }