Skip to content

Commit

Permalink
feat: .katsu.hcl format proposal, tidy up cmd!()
Browse files Browse the repository at this point in the history
Co-authored-by: Cappy Ishihara <[email protected]>
  • Loading branch information
madonuko and korewaChino committed Jul 9, 2024
1 parent e2f815c commit 4162003
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 121 deletions.
14 changes: 8 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ indexmap = "2.2.6"
ensan = "0.2.1"
hcl-rs = "0.18.0"
const_format = "0.2.32"
tiffin = ">=0.1.1"
tiffin = "0.2.0"
tempfile = "3"
lazy_format = "2.0.3"
itertools = "0.13.0"
sys-mount = "3.0.1"
thiserror = "1.0.61"
39 changes: 1 addition & 38 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,43 +133,6 @@ impl RootBuilder for DnfRootBuilder {
}
}

#[tracing::instrument(skip(chroot, is_post))]
#[deprecated(note = "Use Script::execute with pointer to tiffin container")]
pub fn run_script(script: Script, chroot: &Path, is_post: bool) -> Result<()> {
let id = script.id.as_ref().map_or("<NULL>", |s| s);
bail_let!(Some(mut data) = script.load() => "Cannot load script `{id}`");
let name = script.name.as_ref().map_or("<Untitled>", |s| s);
info!(id, name, "Running script");

let name = format!("script-{}", script.id.as_ref().map_or("untitled", |s| s));
// check if data has shebang
if !data.starts_with("#!") {
warn!("Script does not have shebang, #!/bin/sh will be added. It is recommended to add a shebang to your script.");
data.insert_str(0, "#!/bin/sh\n");
}

if script.chroot {
just_write(chroot.join("tmp").join(&name), data)?;
crate::chroot_run_cmd!(chroot,
chmod +x $chroot/tmp/$name;
unshare -R $chroot /tmp/$name 2>&1;
rm -f $chroot/tmp/$name;
)?;
} else {
just_write(PathBuf::from(format!("katsu-work/{name}")), data)?;
// export envar
std::env::set_var("CHROOT", chroot);
cmd_lib::run_cmd!(
chmod +x katsu-work/$name;
/usr/bin/env CHROOT=$chroot katsu-work/$name 2>&1;
rm -f katsu-work/$name;
)?;
}

info!(id, name, "Finished script");
Ok(())
}

pub fn run_all_scripts(scrs: &[Script], chroot: &Path, is_post: bool) -> Result<()> {
// name => (Script, is_executed)
let mut scrs = scrs.to_owned();
Expand Down Expand Up @@ -213,7 +176,7 @@ pub fn run_scripts(

// Run the actual script
let Some((scr, done)) = scripts.get_mut(idx) else { unreachable!() };
run_script(std::mem::take(scr), chroot, is_post)?;
scr.execute(&mut tiffin::Container::new(chroot.to_path_buf()))?;
*done = true;
}
Ok(())
Expand Down
85 changes: 37 additions & 48 deletions src/cfg/boot.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use color_eyre::Result;
use std::path::Path;
use sys_mount::Unmount;
use tracing::{debug, info, trace, warn};

use crate::bail_let;
use crate::{bail_let, cmd};

use super::manifest::Manifest;

Expand Down Expand Up @@ -38,8 +39,8 @@ impl Bootloader {
pub fn install(&self, image: &Path) -> Result<()> {
match *self {
Self::Grub => info!("GRUB is not required to be installed to image, skipping"),
Self::Limine => cmd_lib::run_cmd!(limine bios-install $image 2>&1)?,
Self::SystemdBoot => cmd_lib::run_cmd!(bootctl --image=$image install 2>&1)?,
Self::Limine => cmd!(? "limine" "bios-install" {{ image.display() }})?,
Self::SystemdBoot => cmd!(? "bootctl" ["--image={}" image.display()] "install")?,
}
Ok(())
}
Expand Down Expand Up @@ -114,15 +115,14 @@ impl Bootloader {
let limine_cfg = root.join("boot/limine.cfg");
crate::tpl!("../../templates/limine.cfg.tera" => { LIMINE_PREPEND_COMMENT, distro, vmlinuz, initramfs, cmd, volid } => &limine_cfg);

let binding = cmd_lib::run_fun!(b2sum $limine_cfg)?;
let binding = cmd!(stdout "b2sum" {{ limine_cfg.display() }});
let liminecfg_b2h = binding.split_whitespace().next().unwrap();

// enroll limine secure boot
tracing::info_span!("Enrolling Limine Secure Boot").in_scope(|| -> Result<()> {
Ok(cmd_lib::run_cmd!(
limine enroll-config $root/boot/limine-uefi-cd.bin $liminecfg_b2h 2>&1;
limine enroll-config $root/boot/limine-bios.sys $liminecfg_b2h 2>&1;
)?)
cmd!(? "limine" "enroll-config" ["{root:?}/boot/limine-uefi-cd.bin"] liminecfg_b2h)?;
cmd!(? "limine" "enroll-config" ["{root:?}/boot/limine-bios.sys"] liminecfg_b2h)?;
Ok(())
})?;

Ok(())
Expand All @@ -141,25 +141,20 @@ impl Bootloader {
// let's mount the disk as a loop device
let (ldp, hdl) = crate::util::loopdev_with_file(sparse_path)?;

cmd_lib::run_cmd!(
// Format disk with mkfs.fat
mkfs.msdos $ldp -v -n EFI 2>&1;

// Mount disk to /tmp/katsu.efiboot
mkdir -p /tmp/katsu.efiboot;
mount $ldp /tmp/katsu.efiboot;

mkdir -p /tmp/katsu.efiboot/EFI/BOOT;
cp -avr $tree/EFI/BOOT/. /tmp/katsu.efiboot/EFI/BOOT 2>&1;

umount /tmp/katsu.efiboot;
)?;
// Format disk with mkfs.fat
cmd!(? "mkfs.msdos" {{ ldp.display() }} "-v" "-n" "EFI")?;
// Mount disk to /tmp/katsu.efiboot
std::fs::create_dir("/tmp/katsu.efiboot")?;
let efimnt = sys_mount::Mount::new(&ldp, "/tmp/katsu.efiboot")?;
std::fs::create_dir("/tmp/katsu.efiboot/EFI")?;
std::fs::create_dir("/tmp/katsu.efiboot/EFI/BOOT")?;
cmd!(? "cp" "-avr" ["{tree:?}/EFI/BOOT/."] "/tmp/katsu.efiboot/EFI/BOOT")?;
efimnt.unmount(sys_mount::UnmountFlags::empty())?;

drop(hdl);
Ok(())
}


// todo: rewrite this whole thing, move ISO into a dedicated wrapper struct
fn cp_grub(&self, manifest: &Manifest, chroot: &Path) -> Result<()> {
let imgd = chroot.parent().unwrap().join(ISO_TREE);
Expand All @@ -169,7 +164,7 @@ impl Bootloader {
let (vmlinuz, initramfs) = self.cp_vmlinuz_initramfs(chroot, &imgd)?;

let _ = std::fs::remove_dir_all(imgd.join("boot"));
cmd_lib::run_cmd!(cp -r $chroot/boot $imgd/)?;
cmd!(? "cp" "-r" ["{chroot:?}/boot"] {{ imgd.display() }})?;
std::fs::rename(imgd.join("boot/grub2"), imgd.join("boot/grub"))?;

let distro = &manifest.distro.as_ref().map_or("Linux", |s| s);
Expand All @@ -193,14 +188,12 @@ impl Bootloader {

// Funny script to install GRUB
let _ = std::fs::create_dir_all(imgd.join("EFI/BOOT/fonts"));
cmd_lib::run_cmd!(
cp -av $imgd/boot/efi/EFI/fedora/. $imgd/EFI/BOOT;
cp -av $imgd/boot/grub/grub.cfg $imgd/EFI/BOOT/BOOT.conf 2>&1;
cp -av $imgd/boot/grub/grub.cfg $imgd/EFI/BOOT/grub.cfg 2>&1;
cp -av $imgd/boot/grub/fonts/unicode.pf2 $imgd/EFI/BOOT/fonts;
cp -av $imgd/EFI/BOOT/shim${arch_short}.efi $imgd/EFI/BOOT/BOOT${arch_short_upper}.efi;
cp -av $imgd/EFI/BOOT/shim.efi $imgd/EFI/BOOT/BOOT${arch_32}.efi;
)?;
cmd!(? "cp" "-av" ["{imgd:?}/boot/efi/EFI/fedora/."] ["{imgd:?}/EFI/BOOT"])?;
cmd!(? "cp" "-av" ["{imgd:?}/boot/grub/grub.cfg"] ["{imgd:?}/EFI/BOOT/BOOT.conf 2>&1"])?;
cmd!(? "cp" "-av" ["{imgd:?}/boot/grub/grub.cfg"] ["{imgd:?}/EFI/BOOT/grub.cfg 2>&1"])?;
cmd!(? "cp" "-av" ["{imgd:?}/boot/grub/fonts/unicode.pf2"] ["{imgd:?}/EFI/BOOT/fonts"])?;
cmd!(? "cp" "-av" ["{imgd:?}/EFI/BOOT/shim${arch_short}.efi"] ["{imgd:?}/EFI/BOOT/BOOT${arch_short_upper}.efi"])?;
cmd!(? "cp" "-av" ["{imgd:?}/EFI/BOOT/shim.efi"] ["{imgd:?}/EFI/BOOT/BOOT${arch_32}.efi"])?;

// and then we need to generate eltorito.img
let host_arch = std::env::consts::ARCH;
Expand All @@ -218,31 +211,27 @@ impl Bootloader {
};

let arch_modules = match manifest.dnf.arch.as_deref().unwrap_or(host_arch) {
"x86_64" => vec!["biosdisk"],
"aarch64" => vec!["efi_gop"],
"x86_64" => "biosdisk",
"aarch64" => "efi_gop",
_ => unimplemented!(),
};

debug!("Generating Grub images");
cmd_lib::run_cmd!(
// todo: uefi support
grub2-mkimage -O $arch_out -d $chroot/usr/lib/grub/$arch -o $imgd/boot/eltorito.img -p /boot/grub iso9660 $[arch_modules] 2>&1;
// make it 2.88 MB
// fallocate -l 1228800 $imgd/boot/eltorito.img;
// ^ Commented out because it just wiped the entire file - @korewaChino
// grub2-mkimage -O $arch_64-efi -d $chroot/usr/lib/grub/$arch_64-efi -o $imgd/boot/efiboot.img -p /boot/grub iso9660 efi_gop efi_uga 2>&1;
grub2-mkrescue -o $imgd/../efiboot.img;
)?;
// todo: uefi support
cmd!(? "grub2-mkimage" "-O" arch_out "-d" ["{chroot:?}/usr/lib/grub/{arch}"] "-o" ["{imgd:?}/boot/eltorito.img"] "-p" "/boot/grub" "iso9660" arch_modules)?;
// make it 2.88 MB
// fallocate -l 1228800 $imgd/boot/eltorito.img;
// ^ Commented out because it just wiped the entire file - @korewaChino
// grub2-mkimage -O $arch_64-efi -d $chroot/usr/lib/grub/$arch_64-efi -o $imgd/boot/efiboot.img -p /boot/grub iso9660 efi_gop efi_uga 2>&1;
cmd!(? "grub2-mkrescue" "-o" ["{imgd:?}/../efiboot.img"])?;

debug!("Copying EFI files from Grub rescue image");
let (ldp, hdl) = crate::util::loopdev_with_file(&imgd.join("../efiboot.img"))?;

cmd_lib::run_cmd!(
mkdir -p /tmp/katsu-efiboot;
mount $ldp /tmp/katsu-efiboot;
cp -r /tmp/katsu-efiboot/boot/grub $imgd/boot/;
umount /tmp/katsu-efiboot;
)?;
std::fs::create_dir("/tmp/katsu-efiboot")?;
let mnt = sys_mount::Mount::new(ldp, "/tmp/katsu-efiboot")?;
cmd!(? "cp" "-r" "/tmp/katsu-efiboot/boot/grub" ["{imgd:?}/boot/"])?;
mnt.unmount(sys_mount::UnmountFlags::empty())?;

drop(hdl);

Expand Down
23 changes: 10 additions & 13 deletions src/cfg/partition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,11 +277,9 @@ impl PartitionLayout {
// Some stupid hackery checks for the args of mkfs.fat
debug!(fsname, "Formatting partition");
if fsname == "efi" {
trace!("mkfs.fat -F32 {devname}");
cmd_lib::run_cmd!(mkfs.fat -F32 $devname 2>&1)?;
cmd!(? "mkfs.fat" "-F32" devname)?;
} else {
trace!("mkfs.{fsname} {devname}");
cmd_lib::run_cmd!(mkfs.$fsname $devname 2>&1)?;
cmd!(? {format!("mkfs.{fsname}")} devname)?;
}

Result::<_>::Ok((i + 1, last_end))
Expand Down Expand Up @@ -357,9 +355,9 @@ pub fn partition_name(disk: &str, partition: usize) -> String {
/// An ISO9660 partition for an ISO9660 image
#[derive(Clone, Debug)]
pub struct Iso9660Partition {
pub partno: usize,
/// UUID for partition type
pub guid: PartitionType,
pub partno: usize,
/// UUID for partition type
pub guid: PartitionType,
}

/// A partition table for an ISO9660 image
Expand All @@ -369,10 +367,9 @@ pub struct Iso9660Table {}
/// A wrapper around xorriso
#[derive(Debug, Clone)]
pub struct Xorriso {
/// Implant MD5 checksums?
/// default: true
pub md5: bool,
/// Boot catalog
pub boot_catalog: Option<PathBuf>,

/// Implant MD5 checksums?
/// default: true
pub md5: bool,
/// Boot catalog
pub boot_catalog: Option<PathBuf>,
}
25 changes: 15 additions & 10 deletions src/cfg/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,7 @@ impl Script {
let tmpfile_name = format!("katsu-script-{}", self.get_id());
if self.chroot {
tracing::trace!("chrooting to {:?}", container.root);
// you have seen this in readymade already the funny
// upcast the color_eyre::Report
let res = container.run(|| Ok(Self::_write_and_execute(&tmpfile_name, &script, None)?));
// SAFETY: downcast the dyn Error back to a Report
if let Err(e) = res {
return Err(*unsafe { Box::from_raw(Box::into_raw(e).cast::<Report>()) });
}
container.run(|| Self::_write_and_execute(&tmpfile_name, &script, None))?;
} else {
Self::_write_and_execute(&tmpfile_name, &script, Some(&container.root))?;
}
Expand All @@ -104,7 +98,9 @@ impl Script {

/// Write the script to a temporary file and execute it.
#[tracing::instrument]
fn _write_and_execute(tmpfile_name: &str, script: &str, chroot: Option<&Path>) -> Result<()> {
fn _write_and_execute(
tmpfile_name: &str, script: &str, chroot: Option<&Path>,
) -> Result<(), ScriptError> {
let mut tmpfile = tmpfile_script(tmpfile_name)?;
{
let f = tmpfile.as_file_mut();
Expand All @@ -121,8 +117,17 @@ impl Script {
if let Some(rc) = status.code() {
return Err(Report::msg("Script exited")
.warning(lzf!("Status code: {rc}"))
.note("Status: {status}"));
.note("Status: {status}")
.into());
}
Err(Report::msg("Script terminated unexpectedly").note(lzf!("Status: {status}")))
Err(Report::msg("Script terminated unexpectedly").note(lzf!("Status: {status}")).into())
}
}

#[derive(thiserror::Error, Debug)]
enum ScriptError {
#[error("IO Error: {0}")]
Io(#[from] std::io::Error),
#[error("Error while running script: {0}")]
Eyre(#[from] color_eyre::Report),
}
15 changes: 10 additions & 5 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@ macro_rules! cmd {
(@ {{$expr:expr}}) => { format!("{}", $expr) };
(@ $expr:expr) => { &$expr };
(@ $expr:literal) => { $expr };
(stdout $cmd:literal $($t:tt)+) => {{
#[allow(unused_braces)]
let cmd = cmd!($cmd $($t)+).output()?;
String::from_utf8_lossy(&cmd.stdout).to_string()
}};
($cmd:literal $($t:tt)*) => {
#[allow(unused_braces)]
std::process::Command::new($cmd)
$(.arg(cmd!(@ $t)))*
};
(stdout $cmd:literal $($t:tt)+) => {{
($cmd:block $($t:tt)*) => {
#[allow(unused_braces)]
let cmd = cmd!($cmd $($t)+).output()?;
String::from_utf8_lossy(&cmd.stdout).to_string()
}};
(?$cmd:literal $($t:tt)*) => {{
std::process::Command::new(cmd!(@ $cmd))
$(.arg(cmd!(@ $t)))*
};
(?$cmd:tt $($t:tt)*) => {{
use itertools::Itertools;
#[allow(unused_braces)]
let cmd_str = [Box::new($cmd) as Box<dyn std::fmt::Display>, $(Box::new(cmd!(@ $t))),*].iter().join(" ");
Expand Down
17 changes: 17 additions & 0 deletions tests/fs.katsu.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// squashfs outputs have no partition layout, we just throw everything and assume it's root
output "squashfs" "xfce" {
bootstrap_method = "dnf"
dnf {
package_lists = [
pkg_list.dnf.core,
pkg_list.dnf.xfce
]
}

// optional copy_files directive to copy files to the filesystem, will be relative to root
// May be redundant with partition.copy_files, but it's here for completeness
copy {
source = "./somefile"
destination = "/somefile"
}
}
Loading

0 comments on commit 4162003

Please sign in to comment.