Skip to content

Commit

Permalink
Add Legacy BIOS support
Browse files Browse the repository at this point in the history
- UEFI and Legacy backends now auto-generate GRUB configuration files at /boot/grub2/grub.cfg
- Added a special bootloader option called `grub_bios` that will set the disk image to boot in Legacy BIOS mode
- Updated tests to Ultramarine 40
  • Loading branch information
korewaChino committed Oct 10, 2024
1 parent 5ccdd8e commit a0e743a
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 25 deletions.
37 changes: 35 additions & 2 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ const WORKDIR: &str = "katsu-work";
crate::prepend_comment!(GRUB_PREPEND_COMMENT: "/boot/grub/grub.cfg", "Grub configurations", katsu::builder::Bootloader::cp_grub);
crate::prepend_comment!(LIMINE_PREPEND_COMMENT: "/boot/limine.cfg", "Limine configurations", katsu::builder::Bootloader::cp_limine);

#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Bootloader {
#[default]
Grub,
GrubBios,
Limine,
SystemdBoot,
}
Expand All @@ -33,6 +34,7 @@ impl From<&str> for Bootloader {
match &*value.to_lowercase() {
"limine" => Self::Limine,
"grub" | "grub2" => Self::Grub,
"grub-bios" => Self::GrubBios,
"systemd-boot" => Self::SystemdBoot,
_ => {
warn!("Unknown bootloader: {value}, falling back to GRUB");
Expand All @@ -48,13 +50,17 @@ impl Bootloader {
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::GrubBios => {
cmd_lib::run_cmd!(grub-install --target=i386-pc --boot-directory=$image/boot 2>&1)?
},
}
Ok(())
}
pub fn get_bins(&self) -> (&'static str, &'static str) {
match *self {
Self::Grub => ("boot/efi/EFI/fedora/shim.efi", "boot/eltorito.img"),
Self::Limine => ("boot/limine-uefi-cd.bin", "boot/limine-bios-cd.bin"),
Self::GrubBios => todo!(),
Self::SystemdBoot => todo!(),
}
}
Expand Down Expand Up @@ -262,9 +268,14 @@ impl Bootloader {
Self::Grub => self.cp_grub(manifest, chroot)?,
Self::Limine => self.cp_limine(manifest, chroot)?,
Self::SystemdBoot => todo!(),
Self::GrubBios => self.cp_grub_bios(chroot)?,
}
Ok(())
}

pub fn cp_grub_bios(&self, chroot: &Path) -> Result<()> {

Check warning on line 276 in src/builder.rs

View workflow job for this annotation

GitHub Actions / unit-test

unused variable: `chroot`

Check failure on line 276 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `chroot`

Check warning on line 276 in src/builder.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `chroot`
todo!()
}
}

pub trait RootBuilder {
Expand Down Expand Up @@ -361,6 +372,15 @@ impl RootBuilder for DnfRootBuilder {
manifest.users.iter().try_for_each(|user| user.add_to_chroot(&chroot))?;
}

if manifest.bootloader == Bootloader::GrubBios || manifest.bootloader == Bootloader::Grub {
info!("Generating GRUB configuration");
crate::chroot_run_cmd!(&chroot,
echo "GRUB_DISABLE_OS_PROBER=true" > /etc/default/grub;
grub2-mkconfig -o /boot/grub2/grub.cfg;
rm -f /etc/default/grub;
)?;
}

// now, let's run some funny post-install scripts

info!("Running post-install scripts");
Expand Down Expand Up @@ -482,17 +502,30 @@ impl ImageBuilder for DiskImageBuilder {
// disk.mount_to_chroot(&loopdev.path().unwrap(), &chroot)?;
// disk.unmount_from_chroot(&loopdev.path().unwrap(), &chroot)?;
// }
let uefi = { self.bootloader != Bootloader::GrubBios };
let arch = manifest.dnf.arch.as_deref().unwrap_or(std::env::consts::ARCH);

let (ldp, hdl) = loopdev_with_file(sparse_path)?;

// Partition disk
disk.apply(&ldp, manifest.dnf.arch.as_deref().unwrap_or(std::env::consts::ARCH))?;
disk.apply(&ldp, arch, uefi)?;

// Mount partitions to chroot
disk.mount_to_chroot(&ldp, chroot)?;

self.root_builder.build(&chroot.canonicalize()?, manifest)?;

if !uefi {
info!("Not UEFI, Setting up extra configs");

// Let's use grub2-install to bless the disk

info!("Blessing disk image with MBR");
cmd_lib::run_cmd!(
grub2-install --target=i386-pc --boot-directory=$chroot/boot $ldp 2>&1;
)?;
}

disk.unmount_from_chroot(chroot)?;

drop(hdl);
Expand Down
53 changes: 36 additions & 17 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::{
io::Write,
path::{Path, PathBuf},
};
use tracing::{debug, info, trace};
use tracing::{debug, info, trace, warn};
const DEFAULT_VOLID: &str = "KATSU-LIVEOS";

#[derive(Deserialize, Debug, Clone, Serialize)]
Expand Down Expand Up @@ -334,7 +334,13 @@ impl PartitionLayout {
// trim trailing slashes
let am = a.mountpoint.trim_end_matches('/').matches('/').count();
let bm = b.mountpoint.trim_end_matches('/').matches('/').count();
if a.mountpoint == "/" {
if a.mountpoint.is_empty() {
// empty mountpoint should always come first
std::cmp::Ordering::Less
} else if b.mountpoint.is_empty() {
// empty mountpoint should always come first
std::cmp::Ordering::Greater
} else if a.mountpoint == "/" {
// / should always come first
std::cmp::Ordering::Less
} else if b.mountpoint == "/" {
Expand All @@ -358,6 +364,17 @@ impl PartitionLayout {

// Ok, so for some reason the partitions are swapped?
for (index, part) in &ordered {
// println!("Partition {index}: {part:#?}");

if part.mountpoint.is_empty()
|| part.filesystem == "none"
|| part.filesystem == "swap"
|| part.mountpoint == "-"
{
// skip empty mountpoints
warn!(?part, "This partition is not supposed to be mounted! Skipping... If you want this partition to be mounted, please specify a mountpoint starting with /");
continue;
}
let devname = partition_name(&disk.to_string_lossy(), *index);

// clean the mountpoint so we don't have the slash at the start
Expand All @@ -378,6 +395,9 @@ impl PartitionLayout {
// unmount partitions from chroot
// sort partitions by mountpoint
for mp in self.sort_partitions().into_iter().rev().map(|(_, p)| p.mountpoint) {
if mp.is_empty() || mp == "-" {
continue;
}
let mp = chroot.join(mp.trim_start_matches('/'));
trace!("umount {mp:?}");
cmd_lib::run_cmd!(umount $mp 2>&1)?;
Expand All @@ -395,21 +415,20 @@ impl PartitionLayout {
let mut entries = vec![];

ordered.iter().try_for_each(|(_, part)| -> Result<()> {
let mp = PathBuf::from(&part.mountpoint).to_string_lossy().to_string();
let mountpoint_chroot = part.mountpoint.trim_start_matches('/');
let mountpoint_chroot = chroot.join(mountpoint_chroot);
let devname = cmd_lib::run_fun!(findmnt -n -o SOURCE $mountpoint_chroot)?;
if part.filesystem != "none" {
let mp = PathBuf::from(&part.mountpoint).to_string_lossy().to_string();
let mountpoint_chroot = part.mountpoint.trim_start_matches('/');
let mountpoint_chroot = chroot.join(mountpoint_chroot);
let devname = cmd_lib::run_fun!(findmnt -n -o SOURCE $mountpoint_chroot)?;

// We will generate by UUID
let uuid = cmd_lib::run_fun!(blkid -s UUID -o value $devname)?;
// We will generate by UUID
let uuid = cmd_lib::run_fun!(blkid -s UUID -o value $devname)?;

// clean the mountpoint so we don't have the slash at the start
// let mp_cleaned = part.mountpoint.trim_start_matches('/');

let fsname = if part.filesystem == "efi" { "vfat" } else { &part.filesystem };
let fsck = if part.filesystem == "efi" { 0 } else { 2 };
let fsname = if part.filesystem == "efi" { "vfat" } else { &part.filesystem };
let fsck = if part.filesystem == "efi" { 0 } else { 2 };

entries.push(TplFstabEntry { uuid, mp, fsname, fsck });
entries.push(TplFstabEntry { uuid, mp, fsname, fsck });
}
Ok(())
})?;

Expand All @@ -418,7 +437,7 @@ impl PartitionLayout {
Ok(crate::tpl!("fstab.tera" => { PREPEND, entries }))
}

pub fn apply(&self, disk: &PathBuf, target_arch: &str) -> Result<()> {
pub fn apply(&self, disk: &PathBuf, target_arch: &str, uefi: bool) -> Result<()> {

Check warning on line 440 in src/config.rs

View workflow job for this annotation

GitHub Actions / unit-test

unused variable: `uefi`

Check failure on line 440 in src/config.rs

View workflow job for this annotation

GitHub Actions / Clippy

unused variable: `uefi`

Check warning on line 440 in src/config.rs

View workflow job for this annotation

GitHub Actions / Check

unused variable: `uefi`
// This is a destructive operation, so we need to make sure we don't accidentally wipe the wrong disk

info!("Applying partition layout to disk: {disk:#?}");
Expand All @@ -439,7 +458,7 @@ impl PartitionLayout {

let start_string = if i == 1 {
// create partition at start of disk
"1MiB".to_string()
"0".to_string()
} else {
// create partition after last partition
ByteSize::b(last_end).to_string_as(true).replace(' ', "")
Expand All @@ -453,7 +472,6 @@ impl PartitionLayout {
ByteSize::b(last_end).to_string_as(true).replace(' ', "")
});

// TODO: primary/extended/logical is a MBR concept, since we're using GPT, we should be using this field to set the label
// not going to change this for now though, but will revisit
debug!(start = start_string, end = end_string, "Creating partition");
trace!("parted -s {disk:?} mkpart primary fat32 {start_string} {end_string}");
Expand Down Expand Up @@ -497,6 +515,7 @@ impl PartitionLayout {
if fsname == "efi" {
trace!("mkfs.fat -F32 {devname}");
cmd_lib::run_cmd!(mkfs.fat -F32 $devname 2>&1)?;
} else if fsname == "none" {
} else {
trace!("mkfs.{fsname} {devname}");
cmd_lib::run_cmd!(mkfs.$fsname $devname 2>&1)?;
Expand Down
97 changes: 97 additions & 0 deletions tests/ng/katsu-bios.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Example manifest for a Katsu build
builder: dnf
distro: Katsu Ultramarine
bootloader: grub-bios

scripts:
post:
- id: post-test
name: Postinstall test
inline: |
echo "Hello from post.sh"
- id: image-cleanup
name: Clean up root filesystem
file: modules/scripts/image-cleanup.sh

- id: selinux-label
name: Relabel SELinux for new filesystem
file: modules/scripts/selinux.sh


disk:
size: 8GiB
partitions:
- label: mbr_grub
type: 21686148-6449-6E6F-744E-656564454649
size: 1MiB
filesystem: none # no filesystem
mountpoint: ""

- label: boot
type: xbootldr
size: 1GiB
filesystem: ext4
mountpoint: /boot

- label: root
type: xbootldr
flags:
- grow-fs
# size: 2.5MiB
filesystem: ext4
mountpoint: /

users:
- username: ultramarine
# plaintext password: ultramarine
password: "$y$j9T$6/DebcxXazPrtBYnNXtEM.$yaUJHww5Mo1L8xNJ9IDJ.bvKOrIJxAG9PGQKWioBMx3"
groups:
- wheel


dnf:
dnf5: false
releasever: 40
repodir: modules/repodir/
options:
- --setopt=cachedir=/var/cache/dnf
- --nogpgcheck
- --setopt=keepcache=True
- --verbose
exclude:
- fedora-release*
- generic-release*
packages:
- filesystem
- setup
- lvm2
- btrfs-progs
- dmraid
- nvme-cli
- kernel
- glibc
- glibc-common
- dracut-config-generic
- dracut-tools
- dnf
- dracut
# - mkpasswd # maybe not needed soon
- "@core"
- "@standard"
- grub2-tools
- "@hardware-support"
- NetworkManager
- rpm
- libgomp
- ultramarine-release-identity-basic
- ultramarine-release-basic
- fedora-repos

arch_packages:
x86_64:
- grub2-pc
- grub2-pc-modules
- shim-x64
- shim-unsigned-x64

2 changes: 1 addition & 1 deletion tests/ng/modules/base.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ scripts:


dnf:
releasever: 39
releasever: 40
repodir: repodir/
options:
- --setopt=cachedir=/var/cache/dnf
Expand Down
3 changes: 3 additions & 0 deletions tests/ng/modules/scripts/grub-confgen.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash -x

grub2-mkconfig -o /boot/grub2/grub.cfg
5 changes: 0 additions & 5 deletions tests/ng/modules/scripts/grub-install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@
set -x
# Disable os-prober for now

echo "Disabling os-prober..."

echo "GRUB_DISABLE_OS_PROBER=true" > /etc/default/grub
grub2-mkconfig > /boot/grub2/grub.cfg
rm /etc/default/grub


# get /dev/ of /boot, or / if /boot is not a separate partition
Expand Down

0 comments on commit a0e743a

Please sign in to comment.