Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 8 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,21 @@
# mkosi scratchpad
# Ultramarine Disk images

this repository contains an attempt to create mutable, stateful disk images from a bootc tree

don't expect this to boot, it might boot
This repository contains mkosi configs and scripts to build a Ultramarine Linux image for 44 and later

## how this works

This exploits the new Ultramarine 44 feature: Dual-format bootc images, which allows you to use the same OCI image for booting through bootc to install a traditional, stateful Ultramarine installation.

Most of the filesystem is provisioned through mkosi itself, with one `postprocess.sh` script working outside due to sandboxing limitations with `mkosi.postoutput` scripts. For some reason you cannot call postoutput scripts in a privileged context, possibly due to some sandboxing assumption, we work around this by simply going outside the container itself and doing it manually

## building
## Building

1. mount the base container
```bash
sudo podman pull ghcr.io/ultramarine-linux/base-bootc:44
sudo podman create --name um44 ghcr.io/ultramarine-linux/base-bootc:44
sudo podman mount um44
```
Run the Just recipe:

then edit `mkosi.conf` `BaseTrees=` option to the outputted mountpoint (todo: a `mkosi.sync/configure` script to ease this pain)

2. build the image
```
just build
```
```bash
just full-build
```

3. profit?
This will automatically pull OCI images from the bootc tree, and

you now have yourself a bootable, stateful artifact from an Ultramarine bootc image
49 changes: 45 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
build: mkosi-build
sudo ./postprocess.sh
profile := "base"
secondary_profile := ""

# join with `,`

profile_string := profile + if secondary_profile != "" { "," + secondary_profile } else { "" }
export PROFILES := profile_string
release := "44"
cache_dir := "mkosi.cache"
oci_image := "ghcr.io/ultramarine-linux" / profile + "-bootc:" + release
tar_export := cache_dir / profile + ".tar"

build: mkosi-build postprocess

full-build: prep build

prep: pack-images

postprocess:
sudo ./scripts/postprocess.sh

prepare_dirs:
mkdir -p {{ cache_dir }}

hash:
./scripts/hash.sh



# for some reason $PROFILES does not get passed through on configure script so we need this instead
mkosi-build:
sudo mkosi build --force
sudo mkosi --profile="{{ profile_string }}" --release="{{ release }}" -w build

clean:
mkosi clean
mkosi clean

pack-images: pull
#!/bin/bash
# create container
container=$(podman create {{ oci_image }})
echo "exporting image to {{ tar_export }}"
podman export -o {{ tar_export }} $container
echo "image exported to {{ tar_export }}"
podman rm $container > /dev/null

pull: prepare_dirs
@echo "pulling image {{ oci_image }}"
podman pull {{ oci_image }}
6 changes: 6 additions & 0 deletions mkosi.build.d/20-man.chroot
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
if [[ "$WITH_DOCS" == "1" ]]; then
echo "Building man pages..."
mkdir -p /var/cache/man
mandb
fi
16 changes: 3 additions & 13 deletions mkosi.conf
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[Distribution]
Distribution=fedora
Release=44

[Output]
ImageVersion=44
ImageId=Ultramarine
ImageId=Ultramarine-Stateful
ManifestFormat=json
Output=%i_%v_%a
Format=disk
Expand All @@ -13,22 +14,12 @@ SplitArtifacts=no


[Build]
WorkspaceDirectory=/var/cache/mkosi.workspace
Incremental=yes
CacheDirectory=mkosi.cache
ToolsTreeDistribution=fedora

[Content]
#RemovePackages=grub2-efi-x64 shim-x64
#Bootloader=systemd-boot
#BiosBootloader=grub
#Bootable=yes
#
Autologin=yes
CleanPackageMetadata=no
# podman create $img
# podman mount $ctr
BaseTrees=/var/lib/containers/storage/overlay/77b4c9c27933c25c83cf89cbc4f7c3d0118e67fb66ac276672b34d8c8f285acb/merged
UnifiedKernelImages=no
RootPassword=root
## hack below
Expand All @@ -41,8 +32,7 @@ KernelCommandLine=


[Runtime]
CPUs=2
RuntimeScratch=no
Console=gui
RAM=4G
Firmware=bios
Firmware=uefi
4 changes: 4 additions & 0 deletions mkosi.conf.d/arm/arm.chroot
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/bash -x
#install pi uboot files
cp -P /usr/share/uboot/rpi_arm64/u-boot.bin /boot/efi/rpi-u-boot.bin
cp -P /usr/share/uboot/pinebook-pro-rk3399/u-boot.bin /boot/efi/pinebook-pro-u-boot.bin
12 changes: 12 additions & 0 deletions mkosi.conf.d/arm/arm.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Match]
Architecture=arm64

[Content]
Packages=rpi-update
rpi-utils
bcm283x-firmware
bcm283x-overlays
shim-unsigned-aarch64
@arm-tools
[Build]
FinalizeScripts=arm.chroot
6 changes: 6 additions & 0 deletions mkosi.configure
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash

# use jq to replace the .BaseTrees value in the mkosi json
# takes json from stdin and outputs the updated json to stdout
FIRST_PROFILE="${PROFILES%%,*}"
jq ".BaseTrees = [\"mkosi.cache/${FIRST_PROFILE}.tar\"]"
12 changes: 12 additions & 0 deletions mkosi.finalize.d/99-kernel.chroot
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
set -xeuo pipefail
# welcome to chroot or something
source /usr/src/ultramarine-bootc/base/common.sh

# prep fresh machine state for firstboot
rm -f /etc/{machine-id,shadow,locale.conf,hostname}

# we regen dracut manually in this path
KERNEL_VERSION="$(get_kernel_version)"
echo "Rebuilding initramfs for kernel version: $KERNEL_VERSION"
dracut --no-hostonly --kver "$KERNEL_VERSION" --reproducible --zstd -v --add ostree --add bootc -f "/boot/initramfs-$KERNEL_VERSION.img"
6 changes: 0 additions & 6 deletions mkosi.finalize.d/test.chroot

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ rsync -av /usr/lib/ostree-boot/ /boot/

mkdir -p /boot/efi
rsync -av /usr/lib/efi/*/*/EFI/ /boot/efi/EFI/
rsync -av /usr/lib/grub/ /boot/grub2/
rsync -a /usr/lib/grub/ /boot/grub2/
# Regenerate GRUB configuration
# grub2-mkconfig -o /boot/grub2/grub.cfg || true
# grub2-editenv create || true
16 changes: 1 addition & 15 deletions mkosi.postoutput.d/00-manifest.chroot
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
#!/bin/bash
set -xeuo pipefail
output="$(jq -r '.Output' "$MKOSI_CONFIG")"
format="$(jq -r '.OutputFormat' "$MKOSI_CONFIG" 2>/dev/null || true)"

# crude example for a raw disk image:
img="$OUTPUTDIR/${output}.raw"

# output manifest


echo "disk image path: $img"
manifest="$OUTPUTDIR/manifest.json"
echo "manifest path: $manifest"
cp -v "$MKOSI_CONFIG" "$manifest"

env
tree /work
lsblk || true
mount || true
cp -v "$MKOSI_CONFIG" "$manifest"
4 changes: 2 additions & 2 deletions mkosi.repart/05-esp.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ Format=vfat
# poettering and his systemd-boot assumptions
CopyFiles=/boot/efi:/
MountPoint=/boot/efi
SizeMinBytes=2G
SizeMaxBytes=2G
SizeMinBytes=1G
SizeMaxBytes=1G

19 changes: 19 additions & 0 deletions mkosi.skeleton/usr/src/ultramarine-bootc/base/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# Common shell functions for use in other scripts

# Usage: source /usr/src/ultramarine-bootc/base/common.sh

get_kernel_version() {
KERNEL_VERSION="$(find "/usr/lib/modules" -maxdepth 1 -type d ! -path "/usr/lib/modules" -exec basename '{}' ';' | sort | tail -n 1)"
echo "$KERNEL_VERSION"
}

dracut_rebuild() {
KERNEL_VERSION="$(get_kernel_version)"
export DRACUT_NO_XATTR=1
echo "Rebuilding initramfs for kernel version: $KERNEL_VERSION"
dracut --no-hostonly --kver "$KERNEL_VERSION" --reproducible --zstd -v --add ostree --add bootc -f "/usr/lib/modules/$KERNEL_VERSION/initramfs.img"
chmod 0600 "/usr/lib/modules/${KERNEL_VERSION}/initramfs.img"

setfattr -n user.component -v "initramfs" "/usr/lib/modules/${KERNEL_VERSION}/initramfs.img"
}
2 changes: 2 additions & 0 deletions mkosi.sync
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#!/bin/bash -x
tree /work
43 changes: 43 additions & 0 deletions scripts/chroot-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/bin/bash
# Runs inside the chroot during postprocessing.
# Expected environment variables:
# LOOP - loop device path (e.g. /dev/loop0)
# ARCHITECTURE - target architecture (e.g. x86-64)

set -euxo pipefail

if [[ "$ARCHITECTURE" == "x86-64" ]]; then
# GRUB's blessing onto this beautiful disk!
grub2-install --target=i386-pc "$LOOP"
fi

kernelinstall_cleanup() {
mv /tmp/dracut.install /usr/lib/kernel/install.d/50-dracut.install
rm -f /etc/dracut.conf.d/generic.conf
# remove machine state files for one final time before we unmount
# to tell systemd that in fact, this will be the first boot
systemd-firstboot --reset
}

kernelinstall_prep() {
mv /usr/lib/kernel/install.d/50-dracut.install /tmp/dracut.install
echo 'hostonly="no"' > /etc/dracut.conf.d/generic.conf
trap kernelinstall_cleanup EXIT
}

source /usr/src/ultramarine-bootc/base/common.sh
KERNEL_VERSION=$(get_kernel_version)
grub2-mkconfig -o /boot/grub2/grub.cfg
# hack: disable dracut for now; trap restores it on exit
kernelinstall_prep
kernel-install add -v $KERNEL_VERSION /lib/modules/$KERNEL_VERSION/vmlinuz

# Change `ro` to `rw` for first boot
# Fixes a weird issue where boot gets stuck on `ro` mode when there's no
# /etc/hostname and other machine state
# However we do want machine state to be wiped before first boot
# for obvious reasons
for file in /boot/loader/entries/*.conf; do
# change `ro` to `rw` if it ends with newline
sed -i "s|ro[[:space:]]*$|rw|" "$file"
done
18 changes: 18 additions & 0 deletions scripts/hash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
set -euo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/mkosi.sh"

output_dir=$(dirname "$MKOSI_CONFIG")
shafile_content=""

for file in "$output_dir"/"$MKOSI_OUTPUT".*; do
# strip full path from sha256sum output, only keep the filename
if [[ "$file" == *"$MKOSI_OUTPUT.sha256" ]]; then
continue
fi
echo "Hashing $file"
sum=$(sha256sum "$file" | sed 's| .*/| |')

shafile_content+="$sum\n"
done
echo -e "$shafile_content" > "$output_dir/$MKOSI_OUTPUT.sha256"
8 changes: 8 additions & 0 deletions scripts/mkosi.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
bashsrc=$(dirname "${BASH_SOURCE[0]}")
export MKOSI_CONFIG="$(realpath $bashsrc/../mkosi.output/manifest.json)"
export MKOSI_OUTPUT="$(jq -r '.Output' "$MKOSI_CONFIG")"
if ! [[ -f "$MKOSI_CONFIG" ]]; then
echo "mkosi output manifest not found: $MKOSI_CONFIG"
exit 1
fi
33 changes: 8 additions & 25 deletions postprocess.sh → scripts/postprocess.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@
# are stuck in a sandbox, and we want privileged access outside it

set -euxo pipefail
. "$(dirname "${BASH_SOURCE[0]}")/mkosi.sh"

MKOSI_CONFIG="$(realpath mkosi.output/manifest.json)"

if ! [[ -f "$MKOSI_CONFIG" ]]; then
echo "mkosi output manifest not found: $MKOSI_CONFIG"
exit 1
fi

output="$(jq -r '.Output' "$MKOSI_CONFIG")"
format="$(jq -r '.OutputFormat' "$MKOSI_CONFIG" 2>/dev/null || true)"

architecture="$(jq -r '.Architecture' "$MKOSI_CONFIG")"

# crude example for a raw disk image:
img="mkosi.output/$output"
Expand Down Expand Up @@ -55,21 +50,9 @@ mount -t sysfs sysfs "$rootmnt/sys"
mount -t tmpfs tmpfs "$rootmnt/tmp"
mount -t tmpfs tmpfs "$rootmnt/run"

chroot "$rootmnt" bash -s "$loop" <<'EOF'
set -euxo pipefail
loop="$1"
grub2-install --target=i386-pc "$loop"
source /usr/src/ultramarine-bootc/base/common.sh
KERNEL_VERSION=$(get_kernel_version)
# Ensure hostname exists and is not empty for dracut
ls -la /etc/hostname || echo "hostname file does not exist"
file /etc/hostname || true
test -s /etc/hostname || echo "localhost" > /etc/hostname
grub2-mkconfig -o /boot/grub2/grub.cfg

kernel-install add -v $KERNEL_VERSION /lib/modules/$KERNEL_VERSION/vmlinuz

# then remove hostname and machine-id and everything
rm -f /etc/{machine-id,localtime,hostname,shadow,locale.conf}

EOF
# Copy the chroot script into the image's /tmp (a tmpfs) so it's cleaned up
# automatically when cleanup() unmounts the filesystems.
export LOOP="$loop"
export ARCHITECTURE="$architecture"
cp "$(dirname "${BASH_SOURCE[0]}")/chroot-setup.sh" "$rootmnt/tmp/chroot-setup.sh"
chroot "$rootmnt" bash /tmp/chroot-setup.sh