From 41620035d3c85d31365e2f69b77a1bbf18de7354 Mon Sep 17 00:00:00 2001 From: madonuko Date: Wed, 10 Jul 2024 02:10:30 +0800 Subject: [PATCH] feat: .katsu.hcl format proposal, tidy up cmd!() Co-authored-by: Cappy Ishihara --- Cargo.lock | 14 ++- Cargo.toml | 4 +- src/builder.rs | 39 +----- src/cfg/boot.rs | 85 ++++++------- src/cfg/partition.rs | 23 ++-- src/cfg/script.rs | 25 ++-- src/util.rs | 15 ++- tests/fs.katsu.hcl | 17 +++ tests/main.katsu.hcl | 285 +++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 386 insertions(+), 121 deletions(-) create mode 100644 tests/fs.katsu.hcl create mode 100644 tests/main.katsu.hcl diff --git a/Cargo.lock b/Cargo.lock index b60e22c..9928cfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -859,8 +859,10 @@ dependencies = [ "serde_derive", "serde_yaml", "sudo", + "sys-mount", "tempfile", "tera", + "thiserror", "tiffin", "tracing", "tracing-error", @@ -1603,18 +1605,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", @@ -1633,9 +1635,9 @@ dependencies = [ [[package]] name = "tiffin" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3e34f1ffdf8324b2aefe135577a731d78ccb6e6e63fab1f925e94a174a1026" +checksum = "a998ba3345b0b65bb181ca44968cb9654a89279c52b3d2fc79097e8953bdd17f" dependencies = [ "nix", "sys-mount", diff --git a/Cargo.toml b/Cargo.toml index a714627..f04a121 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/builder.rs b/src/builder.rs index 335f12a..cedeb81 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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("", |s| s); - bail_let!(Some(mut data) = script.load() => "Cannot load script `{id}`"); - let name = script.name.as_ref().map_or("", |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(); @@ -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(()) diff --git a/src/cfg/boot.rs b/src/cfg/boot.rs index 1ceb8fc..3717b15 100644 --- a/src/cfg/boot.rs +++ b/src/cfg/boot.rs @@ -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; @@ -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(()) } @@ -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(()) @@ -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); @@ -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); @@ -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; @@ -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); diff --git a/src/cfg/partition.rs b/src/cfg/partition.rs index 8da665b..4bfda52 100644 --- a/src/cfg/partition.rs +++ b/src/cfg/partition.rs @@ -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)) @@ -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 @@ -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, - + /// Implant MD5 checksums? + /// default: true + pub md5: bool, + /// Boot catalog + pub boot_catalog: Option, } diff --git a/src/cfg/script.rs b/src/cfg/script.rs index e951ed1..38b5280 100644 --- a/src/cfg/script.rs +++ b/src/cfg/script.rs @@ -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::()) }); - } + container.run(|| Self::_write_and_execute(&tmpfile_name, &script, None))?; } else { Self::_write_and_execute(&tmpfile_name, &script, Some(&container.root))?; } @@ -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(); @@ -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), +} diff --git a/src/util.rs b/src/util.rs index 4bb1607..e86a1e4 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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, $(Box::new(cmd!(@ $t))),*].iter().join(" "); diff --git a/tests/fs.katsu.hcl b/tests/fs.katsu.hcl new file mode 100644 index 0000000..e3b7171 --- /dev/null +++ b/tests/fs.katsu.hcl @@ -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" + } +} \ No newline at end of file diff --git a/tests/main.katsu.hcl b/tests/main.katsu.hcl new file mode 100644 index 0000000..6749d3d --- /dev/null +++ b/tests/main.katsu.hcl @@ -0,0 +1,285 @@ +// This is a draft of the custom HCL configuration format for Katsu +// Extensions should be .katsu.hcl' +// +// non-blocks in the top level are considered variables, however if you want to be more explicit and +// type them, you can use the `var` keyword + +/* + +---------------------------+ + | | + | | + | | + | base | + | | + | +------------------------------+ + | | | + | | | + +-----------+-------+-------------------+------+ | + | | | | + | | | | + | | | | + | | | | + | | | | + | | | | + | | | | + | | | | + | | +----------v----------+ +---------v----------+ + +--------v---------+ +-------v--------+ | | | | + | | | | | | | | + | | | | | | | | + | | | | | | | | + | desktop | | minimal | | oci | | minimal disk | + | | | squash | | | | | + | | | | | | | | + | | | | | | | | + | | | | | | | | + +----------+-------+----------+-+----------------+ | | | | + | | | +---------------------+ +--------------------+ + | | | + | | | + +-------+-------+ +-------v------+ +--v-----------+ + | v | | | | | + | | | | | | + | | | | | Spin 3 | + | Spin 1 | | Spin 2 | | | + | | | | | | + | | | | | | + | +-+ | | | | | + +-+---------+-+---+ +---+---------++ +--+---------+-+ + | | | | | | ++-------v---+ +-------v----+ +---+---++----v-+ +--+---++----+-+ +| | | | | v || | | v || v | +| | | | |squash ||disk | | sqsh || disk | +| squash | | | | || | | || | +| | | disk | | || | +------++------+ +| | | | ++------++------+ | | +| | | | | | | iso | +++----------+ +------------+ | iso | | | + +----------+ | | +------+ + | | +------+ + | | + | iso | + | | + | | + +----------+ + */ + +var "foo" { + type = "string" + default = "bar" +} + +var "dnf_releasever" { + default = 40 +} + +// import a subdirectory by doing `import {}` +# module { +# source = "./module" +# } + +// you should reference data from a submodule by doing module.data_name + + +// in Rust code we can keep all of this in the heap, it's not a big deal +// (Box) or something like that, Arc maybe? + +pkg_list "dnf" "core" { + default = [ + "@core", + "kernel-*" // you can glob here because DNF lets you do that + ] + arch_specific = { + "x86_64" = [ + "grub2-efi-x64", + "grub2-efi-x64-modules" + ] + "aarch64" = [ + "grub2-efi-aarch64", + "grub2-efi-aarch64-modules" + ] + } + exclude_arch = { + "x86_64" = [ + "grub2-efi-aarch64", + "grub2-efi-aarch64-modules" + ] + "aarch64" = [ + "grub2-efi-x64", + "grub2-efi-x64-modules" + ] + } +} +// Output block +// +// There will be an `output.output_file` key that will be evaluated to the output file +// +// So one could create a new `squashfs` output that bootstraps the system and packs it into a squashfs +// +// Then do an `iso` output that copies output.squashfs to root +// +// First field is the output type, second field is the output name +// +// For "disk" and "iso" types, you may mount the output file to a directory, +// +// For "iso" and disk types you may want to put custom trees as URIs like `$part:/path_to_file` or something +// if you are not expecting to make it a full root filesystem +// +// Planned types: +// - disk - Partitioned disk images with optional mountpoints for post-processing and scripting +// - dir - Outputs an entire directory tree, useful as a pipeline to further outputs +// - squashfs - Squashfs filesystems, Can be bootstrapped standalone or from a dir output +// - iso - ISO images, can be bootstrapped from a squashfs or dir output, Needs manual layout setup (we need helpers for this) +// - tar - Tarballs, can be bootstrapped from a dir output +// - oci - OCI base image, works similarly to a `tar` output, but writes additional OCI metadata JSONs +// +// `disk` outputs should always have its own partition layout, and should be used as the final pass, +// we will not be supporting disk bootstrap types +output "disk" "xfce" { + + // dev note: the output object is gonna be crazy, we would need like a million optional fields with validation... + + // Method to bootstrap the system. + // Can be a package manager like `dnf`, or from a docker image, tarball or squashfs + + + // Planned possible bootstrap methods: + // - oci - Copies data from an OCI image using `podman export` or `skopeo` or something, behaviour will be similar to a `dir` or `tar` input + // - tar - Extracts a tarball to the tree + // - squashfs - unsquash the squashfs to the tree + // - dir - Copy files from a directory to the tree + // - dnf - Install packages from a package list + // + bootstrap_method = "dnf" + dnf { + // there will be an `arch` key here, implied by the host environment or explicitly set by an argument + + // import package lists from this + package_lists = [ + pkg_list.dnf.core, + pkg_list.dnf.xfce + // module.foo.pkg_list.dnf.bar + ] + + // or just list packages here + + packages { + default = [ + + ] + exclude = [] + arch_specific = { + "x86_64" = [ + + ] + "aarch64" = [ + + ] + } + exclude_arch = { + "x86_64" = [ + + ] + "aarch64" = [ + + ] + } + } + } + partition_layout = { + // layout here, partno is sorted by order of appearance + // the table here will be then passed to a respective partitioning table + // structure (i.e Partition for disks, Iso9660, etc.) + partition { + label = "ESP" + type = "esp" // or guid = "$guid" + filesystem = "fat32" + size = "512M" + mountpoint = "/boot/efi" + } + partition { + label = "boot" + type = "xbootldr" + filesystem = "ext4" + size = "2G" + mountpoint = "/boot" + } + partition { + type = "cros-kernel" + // optional copy_blocks directive to `dd` blocks to the filesystem + // instead of copying files to the filesystem + size = "16M" + // copy_blocks takes source file as abspath, or relative to the current manifest + copy_blocks = "/usr/share/submarine/submarine.kpart" + } + partition { + label = "root" + type = "root" // will be inferred from system arch + filesystem = "ext4" + size = "rest" + mountpoint = "/" + } + } + // optional copy_files directive to copy files to the filesystem, will be relative to + // root partition or something like systemd-repart? + // + // File copying should be done after bootstrapping the system + + copy { + // Path should be relative to the root partition + source = "./somefile" + // Destination should be relative to the root of the target directory + // It can be a full root filesystem or just a normal directory tree (Accomodating a custom file layout + // possibly may want for a live image where you can place grub configs and squashfs images in the root) + destination = "/somefile" + } + + // Sort execution of scripts by priority or placement, whichever comes first + // Priority should be integer, the lower the number, the earlier it is executed + // + // note: needs custom sorting function to sort by index or priority, selecting priority over index if exists + + // index will always be defined, so it will be used as a fallback if priority is not defined + script { + id = "grub" + source = file("./grub.cfg") + priority = 10 + } +} + + +// If you do an output based on an existing rootfs, you can even do multiple passes :3 +// So say, new disk output based on squashfs output +// - squashfs output's post scripts sets up some initial filesystem structure +// - disk output unpacks squashfs into the disk image, with the mountpoints in place +// - disk output now has a new set of post scripts that do some additional setup, like bootloader setup +// and such +// +// - or an ISO output that copies the squash image itself to the ISO and sets up the bootloader +// to load that squash image as root + + +output "iso" "xfce" { + // There will be no bootstrapping, we're just repacking an existing output + bootstrap_method = "none" + + // We do not mount ISO files like a rootfs, so we will be using the custom URI paths scheme + + partition_layout = { + partition { + // partno is used, label may not be included in the resulting ISO + // We can use the label to refer to the partition in the copy_files directive + label = "esp" + type = "esp" + filesystem = "fat32" + } + partition { + label = "content" + type = "isofs" + } + } + copy { + source = outputs.squashfs.xfce.output_file // evaluated from fs.katsu.hcl + destination = "content:/LiveOS/xfce.iso" // somehow evaluate from context of current scope from partition_layout? + } +} \ No newline at end of file