mirror of
https://github.com/ublue-os/titanoboa.git
synced 2026-11-03 21:49:46 -09:00
feat!: Only use container images as the only source of truth (#138)
Signed-off-by: Zeglius <33781398+Zeglius@users.noreply.github.com>
This commit is contained in:
parent
840217d97b
commit
5c457c3d05
29 changed files with 1225 additions and 1257 deletions
73
.github/workflows/ci.yml
vendored
73
.github/workflows/ci.yml
vendored
|
|
@ -1,4 +1,5 @@
|
|||
name: CI
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow
|
||||
name: Build Examples CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -6,68 +7,70 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "examples/**"
|
||||
- ".github/workflows/ci.yml"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ISO
|
||||
runs-on: ${{ matrix.platform == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }}
|
||||
name: Build ${{ matrix.example }} ${{ matrix.arch }}
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ${{ matrix.arch == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm64' }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [amd64, arm64]
|
||||
image:
|
||||
# cool images list :)
|
||||
- ghcr.io/ublue-os/bluefin:lts
|
||||
example: [bazzite, zirconium]
|
||||
arch: [amd64]
|
||||
include:
|
||||
# big dumdums that dont have aarch64 :P
|
||||
- platform: amd64
|
||||
image: ghcr.io/ublue-os/bluefin:stable
|
||||
- platform: amd64
|
||||
image: ghcr.io/ublue-os/bluefin-nvidia-open:stable
|
||||
- platform: amd64
|
||||
image: ghcr.io/ublue-os/bazzite:stable
|
||||
container-image: ghcr.io/ublue-os/bazzite-deck:stable
|
||||
- platform: amd64
|
||||
image: ghcr.io/ublue-os/aurora:stable
|
||||
- example: zirconium
|
||||
arch: arm64
|
||||
steps:
|
||||
- name: Maximize Build Space
|
||||
if: matrix.platform == 'amd64'
|
||||
uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9
|
||||
if: matrix.arch == 'amd64'
|
||||
uses: ublue-os/remove-unwanted-software@v9
|
||||
with:
|
||||
remove-codeql: true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Container Image
|
||||
id: build-container
|
||||
shell: bash
|
||||
run: |
|
||||
IMAGE_NAME="titanoboa-example-${{ matrix.example }}:${{ github.sha }}"
|
||||
sudo podman build \
|
||||
--cap-add sys_admin \
|
||||
--security-opt label=disable \
|
||||
--volume /var/lib/containers/storage:/usr/lib/containers/storage:ro \
|
||||
--tag "${IMAGE_NAME}" \
|
||||
"./examples/${{ matrix.example }}"
|
||||
echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build ISO
|
||||
id: build-iso
|
||||
uses: ./
|
||||
with:
|
||||
livesys: "true"
|
||||
flatpaks-list: ./src/flatpaks.example.txt
|
||||
container-image: ${{ matrix.container-image }}
|
||||
image-ref: ${{ matrix.image }}
|
||||
add-polkit: "true"
|
||||
hook-pre-initramfs: ${{ github.workspace }}/.github/workflows/ci_dummy_hook_preinitramfs.sh
|
||||
hook-post-rootfs: ${{ github.workspace }}/.github/workflows/ci_dummy_hook_postrootfs.sh
|
||||
kargs: "NONE"
|
||||
image-ref: ${{ steps.build-container.outputs.IMAGE_NAME }}
|
||||
|
||||
- name: Create Image Name
|
||||
id: name
|
||||
shell: bash
|
||||
env:
|
||||
IMAGE_NAME: ${{ matrix.container-image || matrix.image }}
|
||||
run: |
|
||||
set -eoux pipefail
|
||||
IMAGE_NAME="${IMAGE_NAME##*/}"
|
||||
IMAGE_NAME="${IMAGE_NAME/:/-}"
|
||||
IMAGE_NAME="titanoboa-example-${{ matrix.example }}"
|
||||
echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload Output Artifacts
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.name.outputs.IMAGE_NAME }}-${{ matrix.platform }}
|
||||
name: ${{ steps.name.outputs.IMAGE_NAME }}-${{ matrix.arch }}
|
||||
retention-days: 5
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
|
|
|
|||
134
.github/workflows/ci_dummy_hook_postrootfs.sh
vendored
134
.github/workflows/ci_dummy_hook_postrootfs.sh
vendored
|
|
@ -1,134 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# We need to make our own Profiles. This makes anaconda think we are a Kinoite Install
|
||||
. /etc/os-release
|
||||
if [[ "$ID_LIKE" =~ rhel ]]; then
|
||||
echo 'VARIANT_ID="kinoite"' >>/usr/lib/os-release
|
||||
else
|
||||
sed -i "s/^VARIANT_ID=.*/VARIANT_ID=kinoite/" /usr/lib/os-release
|
||||
fi
|
||||
sed -i "s/^ID=.*/ID=fedora/" /usr/lib/os-release
|
||||
|
||||
# Install Anaconda, Webui if >= F42
|
||||
if [[ "$ID_LIKE" =~ rhel ]]; then
|
||||
dnf copr enable -y jreilly1821/anaconda-webui
|
||||
dnf install -y anaconda-webui anaconda
|
||||
dnf install -y anaconda-live
|
||||
HIDE_SPOKE="1"
|
||||
else
|
||||
dnf install -y anaconda-live libblockdev-{btrfs,lvm,dm}
|
||||
if [[ "$(rpm -E %fedora)" -ge 42 ]]; then
|
||||
# Needed for Anaconda Web UI
|
||||
mkdir -p /var/lib/rpm-state
|
||||
dnf install -y anaconda-webui
|
||||
else
|
||||
HIDE_SPOKE="1"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${HIDE_SPOKE:-}" ]]; then
|
||||
# Hide Root Spoke
|
||||
cat <<EOF >>/etc/anaconda/conf.d/anaconda.conf
|
||||
[User Interface]
|
||||
hidden_spokes =
|
||||
PasswordSpoke
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Get Artwork
|
||||
git clone https://github.com/ublue-os/packages.git /root/packages
|
||||
case "${PRETTY_NAME,,}" in
|
||||
"aurora"*)
|
||||
mkdir -p /usr/share/anaconda/pixmaps/silverblue
|
||||
cp -r /root/packages/aurora/fedora-logos/src/anaconda/* /usr/share/anaconda/pixmaps/
|
||||
cp -r /root/packages/aurora/fedora-logos/src/anaconda/* /usr/share/anaconda/pixmaps/silverblue/
|
||||
;;
|
||||
"bazzite"*)
|
||||
mkdir -p /usr/share/anaconda/pixmaps/silverblue
|
||||
cp -r /root/packages/bazzite/fedora-logos/* /usr/share/anaconda/pixmaps/
|
||||
;;
|
||||
"bluefin"*)
|
||||
mkdir -p /usr/share/anaconda/pixmaps/silverblue
|
||||
cp -r /root/packages/bluefin/fedora-logos/src/anaconda/* /usr/share/anaconda/pixmaps/
|
||||
cp -r /root/packages/bluefin/fedora-logos/src/anaconda/* /usr/share/anaconda/pixmaps/silverblue/
|
||||
;;
|
||||
esac
|
||||
rm -rf /root/packages
|
||||
|
||||
# Variables
|
||||
imageref="$(jq -r '."image-ref"' </usr/share/ublue-os/image-info.json)"
|
||||
imageref="${imageref##*://}"
|
||||
imagetag="$(jq -r 'if ."image-branch" then ."image-branch" else ."image-tag" end' </usr/share/ublue-os/image-info.json)"
|
||||
sbkey='https://github.com/ublue-os/akmods/raw/main/certs/public_key.der'
|
||||
|
||||
# Secureboot Key Fetch
|
||||
mkdir -p /run/install/repo
|
||||
curl -Lo /run/install/repo/sb_pubkey.der "$sbkey"
|
||||
|
||||
# Default Kickstart
|
||||
cat <<EOF >>/usr/share/anaconda/interactive-defaults.ks
|
||||
ostreecontainer --url=$imageref:$imagetag --transport=containers-storage --no-signature-verification
|
||||
%include /usr/share/anaconda/post-scripts/install-configure-upgrade.ks
|
||||
%include /usr/share/anaconda/post-scripts/disable-fedora-flatpak.ks
|
||||
%include /usr/share/anaconda/post-scripts/install-flatpaks.ks
|
||||
%include /usr/share/anaconda/post-scripts/secureboot-enroll-key.ks
|
||||
EOF
|
||||
|
||||
# Signed Images
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/install-configure-upgrade.ks
|
||||
%post --erroronfail
|
||||
bootc switch --mutate-in-place --enforce-container-sigpolicy --transport registry $imageref:$imagetag
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Enroll Secureboot Key
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/secureboot-enroll-key.ks
|
||||
%post --erroronfail --nochroot
|
||||
set -oue pipefail
|
||||
|
||||
readonly ENROLLMENT_PASSWORD="universalblue"
|
||||
readonly SECUREBOOT_KEY="/run/install/repo/sb_pubkey.der"
|
||||
|
||||
if [[ ! -d "/sys/firmware/efi" ]]; then
|
||||
echo "EFI mode not detected. Skipping key enrollment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "\$SECUREBOOT_KEY" ]]; then
|
||||
echo "Secure boot key not provided: \$SECUREBOOT_KEY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SYS_ID="\$(cat /sys/devices/virtual/dmi/id/product_name)"
|
||||
if [[ ":Jupiter:Galileo:" =~ ":\$SYS_ID:" ]]; then
|
||||
echo "Steam Deck hardware detected. Skipping key enrollment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mokutil --timeout -1 || :
|
||||
echo -e "\$ENROLLMENT_PASSWORD\n\$ENROLLMENT_PASSWORD" | mokutil --import "\$SECUREBOOT_KEY" || :
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Install Flatpaks
|
||||
cat <<'EOF' >>/usr/share/anaconda/post-scripts/install-flatpaks.ks
|
||||
%post --erroronfail --nochroot
|
||||
deployment="$(ostree rev-parse --repo=/mnt/sysimage/ostree/repo ostree/0/1/0)"
|
||||
target="/mnt/sysimage/ostree/deploy/default/deploy/$deployment.0/var/lib/"
|
||||
mkdir -p "$target"
|
||||
rsync -aAXUHKP /var/lib/flatpak "$target"
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Disable Fedora Flatpak Repo
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/disable-fedora-flatpak.ks
|
||||
%post --erroronfail
|
||||
systemctl disable flatpak-add-fedora-repos.service
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Set Anaconda Payload to use flathub
|
||||
cat <<EOF >>/etc/anaconda/conf.d/anaconda.conf
|
||||
[Payload]
|
||||
flatpak_remote = flathub https://dl.flathub.org/repo/
|
||||
EOF
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
echo "This is a hook being ran right before building the initramfs."
|
||||
42
.github/workflows/publish-container.yml
vendored
Normal file
42
.github/workflows/publish-container.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: Publish Titanoboa Container
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Publish Container
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=sha
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
22
Containerfile
Normal file
22
Containerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
FROM quay.io/fedora/fedora:latest
|
||||
|
||||
ENV TITANOBOA_INSIDE_CONTAINER="true"
|
||||
|
||||
RUN dnf install -yq \
|
||||
bash \
|
||||
dosfstools \
|
||||
e2fsprogs \
|
||||
mtools \
|
||||
squashfs-tools \
|
||||
util-linux-core \
|
||||
xorriso \
|
||||
yq \
|
||||
;
|
||||
RUN mkdir -p /rootfs
|
||||
|
||||
COPY ./main.sh /app/bin/main.sh
|
||||
COPY ./build_iso.sh /app/bin/build_iso.sh
|
||||
|
||||
VOLUME [ "/output", "/usr/lib/containers/storage" ]
|
||||
|
||||
CMD ["/app/bin/main.sh"]
|
||||
554
Justfile
554
Justfile
|
|
@ -1,554 +0,0 @@
|
|||
set unstable := true
|
||||
PODMAN := which("podman") || require("podman-remote")
|
||||
workdir := env("TITANOBOA_WORKDIR", "work")
|
||||
isoroot := env("TITANOBOA_ISO_ROOT", "work/iso-root")
|
||||
rootfs := workdir/"rootfs"
|
||||
default_image := "ghcr.io/ublue-os/bluefin:lts"
|
||||
arch := arch()
|
||||
### BUILDER CONFIGURATION ###
|
||||
# Distribution to use for the builder container (for tools and dependencies)
|
||||
# Supported values: fedora, centos, almalinux
|
||||
# Set via TITANOBOA_BUILDER_DISTRO environment variable (default: fedora)
|
||||
builder_distro := env("TITANOBOA_BUILDER_DISTRO", "fedora")
|
||||
##############################
|
||||
|
||||
### HOOKS SCRIPT PATHS ###
|
||||
# Path to scripts used as hooks in between steps, used in 'hook-*' recipes.
|
||||
# Must follow the naming convention HOOK_<recipe name without 'hook_' prefix>
|
||||
|
||||
# Hook used for custom operations done in the rootfs before it is squashed.
|
||||
HOOK_post_rootfs := env("HOOK_post_rootfs", "")
|
||||
|
||||
# Hook used for custom operations done before the initramfs is generated.
|
||||
HOOK_pre_initramfs := env("HOOK_pre_initramfs", "")
|
||||
##########################
|
||||
|
||||
### UTILS ###
|
||||
_ci_grouping := '''
|
||||
if [[ -n "${CI:-}" ]]; then
|
||||
echo "::group::${BASH_SOURCE[0]##*/} step"
|
||||
trap 'echo ::endgroup::' EXIT
|
||||
fi
|
||||
'''
|
||||
[private]
|
||||
just := just_executable() + " -f " + source_file()
|
||||
|
||||
[private]
|
||||
git_root := source_dir()
|
||||
|
||||
[private]
|
||||
builder_image := if builder_distro == "fedora" { "quay.io/fedora/fedora:latest" } else if builder_distro == "centos" { "ghcr.io/hanthor/centos-anaconda-builder:main" } else if builder_distro == "almalinux-kitten" { "quay.io/almalinuxorg/almalinux:10-kitten" } else if builder_distro == "almalinux" { "quay.io/almalinuxorg/almalinux:10" } else { error("Unsupported builder distribution: " + builder_distro + ". Supported: fedora, centos, almalinux") }
|
||||
|
||||
|
||||
[private]
|
||||
chroot_function := '
|
||||
function chroot(){
|
||||
local command="$1"
|
||||
shift
|
||||
local args="$*"
|
||||
' + PODMAN + ' run --rm -it \
|
||||
--privileged \
|
||||
--security-opt label=type:unconfined_t \
|
||||
$args \
|
||||
--tmpfs /tmp:rw \
|
||||
--tmpfs /run:rw \
|
||||
--volume ' + git_root + ':/app \
|
||||
--rootfs ' + git_root/rootfs + ' \
|
||||
/usr/bin/bash -c "$command"
|
||||
}'
|
||||
|
||||
[private]
|
||||
builder_function := '
|
||||
function builder(){
|
||||
local command="$1"
|
||||
shift
|
||||
local args="$*"
|
||||
' + PODMAN + ' run --rm -it \
|
||||
--privileged \
|
||||
--security-opt label=disable \
|
||||
--volume ' + git_root + ':/app \
|
||||
' + builder_image + ' \
|
||||
/usr/bin/bash -c "$command" $args
|
||||
}'
|
||||
|
||||
[private]
|
||||
compress_dependencies := '''
|
||||
function compress_dependencies(){
|
||||
local MISSING=()
|
||||
local DEPS=(
|
||||
mksquashfs
|
||||
mkfs.erofs
|
||||
)
|
||||
for dep in "${DEPS[@]}"; do
|
||||
if ! command -v $dep >/dev/null; then
|
||||
MISSING+=($dep)
|
||||
fi
|
||||
done
|
||||
echo "${#MISSING[@]}"
|
||||
}
|
||||
'''
|
||||
|
||||
[private]
|
||||
iso_dependencies := '
|
||||
function iso_dependencies(){
|
||||
local MISSING=()
|
||||
local RPMS=(
|
||||
dosfstools
|
||||
grub2
|
||||
grub2-efi
|
||||
grub2-tools
|
||||
grub2-tools-extra
|
||||
shim
|
||||
xorriso
|
||||
)
|
||||
if [[ "' + arch + '" == "x86_64" ]]; then
|
||||
RPMS+=(
|
||||
grub2-efi-x64
|
||||
grub2-efi-x64-cdboot
|
||||
grub2-efi-x64-modules
|
||||
)
|
||||
elif [[ "' + arch + '" == "aarch64" ]]; then
|
||||
RPMS+=(grub2-efi-aa64-modules)
|
||||
fi
|
||||
if ! command -v rpm >/dev/null; then
|
||||
echo "1"
|
||||
return
|
||||
fi
|
||||
for rpm in "${RPMS[@]}"; do
|
||||
if ! rpm -q $rpm >/dev/null; then
|
||||
MISSING+=($rpm)
|
||||
fi
|
||||
done
|
||||
echo "${#MISSING[@]}"
|
||||
}'
|
||||
#############
|
||||
|
||||
# Default
|
||||
@default:
|
||||
{{ just }} --list
|
||||
|
||||
# Create Directories
|
||||
init-work:
|
||||
@echo "{{ style('command') }}Creating Work Directories...{{ NORMAL }}" >&2
|
||||
mkdir -p {{ workdir }}
|
||||
mkdir -p {{ isoroot }}
|
||||
mkdir -p {{ rootfs }}
|
||||
|
||||
# Extract rootfs
|
||||
rootfs image=default_image:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
set -xeuo pipefail
|
||||
# Pull and Extract Filesystem
|
||||
{{ PODMAN }} pull {{ image }} # Pull newer image
|
||||
ctr="$({{ PODMAN }} create --rm {{ image }} /usr/bin/bash)" && trap "{{ PODMAN }} rm $ctr" EXIT
|
||||
{{ PODMAN }} export $ctr | tar --xattrs-include='*' -p -xf - -C {{ rootfs }}
|
||||
|
||||
# Make /var/tmp be a tmpfs by symlinking to /tmp,
|
||||
# in order to make bootc work at runtime.
|
||||
rm -rf {{ rootfs }}/var/tmp
|
||||
ln -sr {{ rootfs }}/tmp {{ rootfs }}/var/tmp
|
||||
|
||||
# Generate initramfs with live modules
|
||||
initramfs:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
CMD='set -xeuo pipefail
|
||||
dnf install -y dracut-live
|
||||
INSTALLED_KERNEL=$(rpm -q kernel-core --queryformat "%{evr}.%{arch}" | tail -n 1)
|
||||
mkdir -p $(realpath /root)
|
||||
export DRACUT_NO_XATTR=1
|
||||
dracut --zstd --reproducible --no-hostonly --kver "$INSTALLED_KERNEL" --add "dmsquash-live dmsquash-live-autooverlay" --force /app/{{ workdir }}/initramfs.img |& grep -v -e "Operation not supported"'
|
||||
chroot "$CMD"
|
||||
|
||||
# Embed the container
|
||||
rootfs-include-container container_image=default_image image=default_image:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
CMD="set -xeuo pipefail
|
||||
mkdir -p /var/lib/containers/storage
|
||||
podman pull {{ container_image || image }}
|
||||
dnf install -y fuse-overlayfs"
|
||||
chroot "$CMD"
|
||||
|
||||
# Install Flatpaks into the live system
|
||||
rootfs-include-flatpaks FLATPAKS_FILE="src/flatpaks.example.txt":
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if FLATPAKS_FILE =~ '(^$|^(?i)\bnone\b$)' { 'exit 0' } else if path_exists(FLATPAKS_FILE) == 'false' { error('Flatpak file inaccessible: ' + FLATPAKS_FILE) } else { '' } }}
|
||||
{{ chroot_function }}
|
||||
CMD='set -xeuo pipefail
|
||||
mkdir -p /var/lib/flatpak
|
||||
dnf install -y flatpak
|
||||
|
||||
# Get Flatpaks
|
||||
flatpak remote-add --if-not-exists flathub "https://dl.flathub.org/repo/flathub.flatpakrepo"
|
||||
grep -v "#.*" /flatpak-list/$(basename {{ FLATPAKS_FILE }}) | sort --reverse | xargs "-i{}" -d "\n" sh -c "flatpak remote-info --arch={{ arch }} --system flathub {} &>/dev/null && flatpak install --noninteractive -y {}" || true'
|
||||
set -euo pipefail
|
||||
chroot "$CMD" --volume "$(realpath "$(dirname {{ FLATPAKS_FILE }})")":/flatpak-list
|
||||
|
||||
# Install polkit rules
|
||||
rootfs-include-polkit polkit="1":
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if polkit == "0" { 'exit 0' } else { '' } }}
|
||||
set -euo pipefail
|
||||
install -D -m 0644 {{ git_root }}/src/polkit-1/rules.d/*.rules -t {{ rootfs }}/etc/polkit-1/rules.d
|
||||
|
||||
# Install Livesys Scripts
|
||||
rootfs-install-livesys-scripts livesys="1":
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if livesys == "0" { 'exit 0' } else { '' } }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
CMD='set -xeuo pipefail
|
||||
dnf="$({ which dnf5 || which dnf; } 2>/dev/null)"
|
||||
$dnf install -y livesys-scripts
|
||||
|
||||
# Determine desktop environment. Must match one of /usr/libexec/livesys/sessions.d/livesys-{desktop_env}
|
||||
desktop_env=""
|
||||
_session_file="$(find /usr/share/wayland-sessions/ /usr/share/xsessions \
|
||||
-maxdepth 1 -type f -not -name '*gamescope*.desktop' -and -name '*.desktop' -printf '%P' -quit)"
|
||||
case $_session_file in
|
||||
budgie*) desktop_env=budgie ;;
|
||||
cosmic*) desktop_env=cosmic ;;
|
||||
gnome*) desktop_env=gnome ;;
|
||||
plasma*) desktop_env=kde ;;
|
||||
sway*) desktop_env=sway ;;
|
||||
xfce*) desktop_env=xfce ;;
|
||||
*) echo "\
|
||||
{{ style('error') }}ERROR[rootfs-install-livesys-scripts]{{ NORMAL }}\
|
||||
: No Livesys Environment Found"; exit 1 ;;
|
||||
esac && unset -v _session_file
|
||||
sed -i "s/^livesys_session=.*/livesys_session=${desktop_env}/" /etc/sysconfig/livesys
|
||||
|
||||
# Enable services
|
||||
systemctl enable livesys.service livesys-late.service
|
||||
|
||||
# Set default time zone to prevent oddities with KDE clock
|
||||
echo "C /var/lib/livesys/livesys-session-extra 0755 root root - /usr/share/factory/var/lib/livesys/livesys-session-extra" > \
|
||||
/usr/lib/tmpfiles.d/livesys-session-extra.conf'
|
||||
chroot "$CMD"
|
||||
install -D -m 0644 {{ git_root }}/src/livesys-session-extra {{ rootfs }}/usr/share/factory/var/lib/livesys/livesys-session-extra
|
||||
|
||||
# Hook used for custom operations done in the rootfs before it is squashed.
|
||||
# Meant to be used in a GH action.
|
||||
hook-post-rootfs hook=HOOK_post_rootfs:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if hook == '' { 'exit 0' } else { '' } }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
chroot "$(cat '{{ hook }}')"
|
||||
|
||||
# Hook used for custom operations done before the initramfs is generated.
|
||||
# Meant to be used in a GH action.
|
||||
hook-pre-initramfs hook=HOOK_pre_initramfs:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if hook == '' { 'exit 0' } else { '' } }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
chroot "$(cat '{{ hook }}')"
|
||||
|
||||
# Remove the sysroot tree
|
||||
rootfs-clean-sysroot:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ chroot_function }}
|
||||
set -euo pipefail
|
||||
CMD='set -xeuo pipefail
|
||||
if [[ -d /app ]]; then
|
||||
rm -rf /sysroot /ostree
|
||||
dnf autoremove -y
|
||||
dnf clean all -y
|
||||
fi'
|
||||
chroot "$CMD"
|
||||
|
||||
# Fix SELinux Permissions
|
||||
rootfs-selinux-fix image=default_image:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
set -euo pipefail
|
||||
CMD='set -xeuo pipefail
|
||||
cd /app/{{ rootfs }}
|
||||
setfiles -F -r . /etc/selinux/targeted/contexts/files/file_contexts .
|
||||
chcon --user=system_u --recursive .'
|
||||
{{ PODMAN }} run --rm -it \
|
||||
--volume {{ git_root }}:/app \
|
||||
--workdir "/app" \
|
||||
--security-opt label=disable \
|
||||
--privileged \
|
||||
{{ image }} \
|
||||
/usr/bin/bash -c "$CMD"
|
||||
rmdir {{ rootfs }}/app || true
|
||||
|
||||
# Compress rootfs into a compressed image
|
||||
squash fs_type="squashfs":
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
CMD='{{ if fs_type == "squashfs" { "mksquashfs $0 $1/squashfs.img -all-root -noappend" } else if fs_type == "erofs" { "mkfs.erofs -d0 --quiet --all-root -zlz4hc,6 -Eall-fragments,fragdedupe=inode -C1048576 $1/squashfs.img $0" } else { error(style('error') + "ERROR[squash]" + NORMAL + ": Invalid Compression") } }}'
|
||||
{{ compress_dependencies }}
|
||||
{{ builder_function }}
|
||||
set -euo pipefail
|
||||
BUILDER="$(compress_dependencies)"
|
||||
if ! (( BUILDER )); then
|
||||
bash -c "$CMD" "$(realpath {{ rootfs }})" "$(realpath {{ workdir }})"
|
||||
else
|
||||
CMD="dnf install -y {{ if fs_type == 'squashfs' { 'squashfs-tools' } else if fs_type == 'erofs' { 'erofs-utils' } else { '' } }} ; $CMD"
|
||||
builder "$CMD" "/app/{{ rootfs }}" "/app/{{ workdir }}"
|
||||
fi
|
||||
|
||||
# Expand grub templace, according to the image os-release.
|
||||
process-grub-template $extra_kargs="NONE":
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
set -xeuo pipefail
|
||||
kargs=()
|
||||
IFS=',' read -r -a kargs <<< "$extra_kargs"
|
||||
if [[ "$extra_kargs" == "NONE" ]]; then
|
||||
kargs=()
|
||||
fi
|
||||
|
||||
OS_RELEASE="{{ rootfs }}/usr/lib/os-release"
|
||||
TMPL="src/grub.cfg.tmpl"
|
||||
DEST="{{ isoroot }}/boot/grub/grub.cfg"
|
||||
# TODO figure out a better mechanism
|
||||
PRETTY_NAME="$(source "$OS_RELEASE" >/dev/null && echo "${PRETTY_NAME/ (*)}")"
|
||||
sed \
|
||||
-e "s|@PRETTY_NAME@|${PRETTY_NAME}|g" \
|
||||
-e "s|@EXTRA_KARGS@|${kargs[*]}|g" \
|
||||
"$TMPL" >"$DEST"
|
||||
|
||||
# Prep the environment for the ISO
|
||||
iso-organize extra_kargs: && (process-grub-template extra_kargs)
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
set -xeuo pipefail
|
||||
mkdir -p {{ isoroot }}/boot/grub {{ isoroot }}/LiveOS
|
||||
cp {{ rootfs }}/lib/modules/*/vmlinuz {{ isoroot }}/boot
|
||||
cp {{ workdir }}/initramfs.img {{ isoroot }}/boot
|
||||
# Hardcoded on the dmsquash-live source code unless specified otherwise via kargs
|
||||
# https://github.com/dracut-ng/dracut-ng/blob/0ffc61e536d1193cb837917d6a283dd6094cb06d/modules.d/90dmsquash-live/dmsquash-live-root.sh#L23
|
||||
{{ if env('CI', '') == '' { 'cp' } else { 'mv' } }} {{ workdir }}/squashfs.img {{ isoroot }}/LiveOS/squashfs.img
|
||||
|
||||
# Build the ISO from the compressed image
|
||||
iso:
|
||||
#!/usr/bin/env bash
|
||||
{{ _ci_grouping }}
|
||||
{{ if env('CI', '') != '' { "echo '" + style('warning') + "In CI - Deleting: " + rootfs + "...' " + NORMAL +"; rm -rf " + rootfs } else { '' } }}
|
||||
{{ iso_dependencies }}
|
||||
BUILDER="$(iso_dependencies)"
|
||||
CMD='set -xeuo pipefail
|
||||
ISOROOT="$0"
|
||||
WORKDIR="$1"
|
||||
|
||||
mkdir -p $ISOROOT/EFI/BOOT
|
||||
# ARCH_SHORT needs to be uppercase
|
||||
ARCH_SHORT="$(echo {{ arch }} | sed 's/x86_64/x64/g' | sed 's/aarch64/aa64/g')"
|
||||
ARCH_32="$(echo {{ arch }} | sed 's/x86_64/ia32/g' | sed 's/aarch64/arm/g')"
|
||||
if [[ "$(rpm -E %centos)" -ge 10 ]]; then
|
||||
cp -avf /boot/efi/EFI/centos/. $ISOROOT/EFI/BOOT
|
||||
elif [[ "$(rpm -E %fedora)" -ge 41 ]]; then
|
||||
cp -avf /boot/efi/EFI/fedora/. $ISOROOT/EFI/BOOT
|
||||
fi
|
||||
cp -avf $ISOROOT/boot/grub/grub.cfg $ISOROOT/EFI/BOOT/BOOT.conf
|
||||
cp -avf $ISOROOT/boot/grub/grub.cfg $ISOROOT/EFI/BOOT/grub.cfg
|
||||
cp -avf /boot/grub*/fonts/unicode.pf2 $ISOROOT/EFI/BOOT/fonts
|
||||
cp -avf $ISOROOT/EFI/BOOT/shim${ARCH_SHORT}.efi "$ISOROOT/EFI/BOOT/BOOT${ARCH_SHORT^^}.efi"
|
||||
cp -avf $ISOROOT/EFI/BOOT/shim.efi "$ISOROOT/EFI/BOOT/BOOT${ARCH_32}.efi"
|
||||
|
||||
ARCH_GRUB="$(echo {{ arch }} | sed 's/x86_64/i386-pc/g' | sed 's/aarch64/arm64-efi/g')"
|
||||
ARCH_OUT="$(echo {{ arch }} | sed 's/x86_64/i386-pc-eltorito/g' | sed 's/aarch64/arm64-efi/g')"
|
||||
ARCH_MODULES="$(echo {{ arch }} | sed 's/x86_64/biosdisk/g' | sed 's/aarch64/efi_gop/g')"
|
||||
|
||||
grub2-mkimage -O $ARCH_OUT -d /usr/lib/grub/$ARCH_GRUB -o $ISOROOT/boot/eltorito.img -p /boot/grub iso9660 $ARCH_MODULES
|
||||
grub2-mkrescue -o $ISOROOT/../efiboot.img
|
||||
|
||||
EFI_BOOT_MOUNT=$(mktemp -d)
|
||||
mount $ISOROOT/../efiboot.img $EFI_BOOT_MOUNT
|
||||
cp -r $EFI_BOOT_MOUNT/boot/grub $ISOROOT/boot/
|
||||
umount $EFI_BOOT_MOUNT
|
||||
rm -rf $EFI_BOOT_MOUNT
|
||||
|
||||
# https://github.com/FyraLabs/katsu/blob/1e26ecf74164c90bc24299a66f8495eb2aef4845/src/builder.rs#L145
|
||||
EFI_BOOT_PART=$(mktemp -d)
|
||||
fallocate $WORKDIR/efiboot.img -l 25M
|
||||
mkfs.msdos -v -n EFI $WORKDIR/efiboot.img
|
||||
mount $WORKDIR/efiboot.img $EFI_BOOT_PART
|
||||
mkdir -p $EFI_BOOT_PART/EFI/BOOT
|
||||
cp -dRvf $ISOROOT/EFI/BOOT/. $EFI_BOOT_PART/EFI/BOOT
|
||||
umount $EFI_BOOT_PART
|
||||
|
||||
ARCH_SPECIFIC=()
|
||||
if [ "{{ arch }}" == "x86_64" ] ; then
|
||||
ARCH_SPECIFIC=("--grub2-mbr" "/usr/lib/grub/i386-pc/boot_hybrid.img")
|
||||
fi
|
||||
|
||||
xorrisofs \
|
||||
-R \
|
||||
-V titanoboa_boot \
|
||||
-partition_offset 16 \
|
||||
-appended_part_as_gpt \
|
||||
-append_partition 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B \
|
||||
$ISOROOT/../efiboot.img \
|
||||
-iso_mbr_part_type EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 \
|
||||
-c boot.cat --boot-catalog-hide \
|
||||
-b boot/eltorito.img \
|
||||
-no-emul-boot \
|
||||
-boot-load-size 4 \
|
||||
-boot-info-table \
|
||||
--grub2-boot-info \
|
||||
-eltorito-alt-boot \
|
||||
-e \
|
||||
--interval:appended_partition_2:all:: \
|
||||
-no-emul-boot \
|
||||
-vvvvv \
|
||||
-iso-level 3 \
|
||||
-o /app/output.iso \
|
||||
"${ARCH_SPECIFIC[@]}" \
|
||||
$ISOROOT'
|
||||
set -euo pipefail
|
||||
if ! (( BUILDER )); then
|
||||
bash -c "$CMD" "$(realpath {{ isoroot }})" "$(realpath {{ workdir }})"
|
||||
else
|
||||
{{ if `systemd-detect-virt -c || true` != 'none' { "echo '" + style('error') + "ERROR[iso]" + NORMAL + ": Cannot run in nested containers'; exit 1" } else { '' } }}
|
||||
{{ builder_function }}
|
||||
CMD="dnf install -y grub2 grub2-efi grub2-tools grub2-tools-extra xorriso shim dosfstools {{ if arch == "x86_64" { 'grub2-efi-x64-modules grub2-efi-x64-cdboot grub2-efi-x64' } else if arch == "aarch64" { 'grub2-efi-aa64-modules' } else { '' } }}; $CMD"
|
||||
builder "$CMD" "/app/{{ isoroot }}" "/app/{{ workdir }}"
|
||||
fi
|
||||
|
||||
# TODO update this recipe parameters. Make it actually usable
|
||||
[no-exit-message]
|
||||
[doc('Build a live-iso')]
|
||||
@build image=default_image livesys="1" flatpaks_file="src/flatpaks.example.txt" compression="squashfs" extra_kargs="NONE" container_image=image polkit="1": \
|
||||
checkroot \
|
||||
(show-config image livesys flatpaks_file compression extra_kargs container_image polkit) \
|
||||
clean \
|
||||
init-work \
|
||||
(rootfs image) \
|
||||
(hook-pre-initramfs HOOK_pre_initramfs) \
|
||||
initramfs \
|
||||
(rootfs-include-flatpaks flatpaks_file) \
|
||||
(rootfs-include-polkit polkit) \
|
||||
(rootfs-install-livesys-scripts livesys) \
|
||||
(rootfs-include-container container_image image) \
|
||||
(hook-post-rootfs HOOK_post_rootfs) \
|
||||
rootfs-clean-sysroot \
|
||||
(rootfs-selinux-fix image) \
|
||||
(ci-delete-image image) \
|
||||
(squash compression) \
|
||||
(iso-organize extra_kargs) \
|
||||
iso
|
||||
mv ./output.iso {{ justfile_dir() }} &>/dev/null
|
||||
|
||||
|
||||
@show-config image livesys flatpaks_file compression extra_kargs container_image polkit:
|
||||
echo "Using the following configuration:"
|
||||
echo "{{ style('warning') }}################################################################################{{ NORMAL }}"
|
||||
echo "PODMAN := {{ PODMAN }}"
|
||||
echo "workdir := {{ workdir }}"
|
||||
echo "isoroot := {{ isoroot }}"
|
||||
echo "rootfs := {{ rootfs }}"
|
||||
echo "builder_distro := {{ builder_distro }}"
|
||||
echo "builder_image := {{ builder_image }}"
|
||||
echo "HOOK_post_rootfs := {{ if HOOK_post_rootfs =~ '(^$|^(?i)\bnone\b$)' { '' } else { canonicalize(HOOK_post_rootfs) } }}"
|
||||
echo "HOOK_pre_initramfs := {{ if HOOK_pre_initramfs =~ '(^$|^(?i)\bnone\b$)' { '' } else { canonicalize(HOOK_pre_initramfs) } }}"
|
||||
echo "image := {{ image }}"
|
||||
echo "livesys := {{ livesys }}"
|
||||
echo "flatpaks_file := {{ if flatpaks_file =~ '(^$|^(?i)\bnone\b$)' { '' } else { canonicalize(flatpaks_file) } }}"
|
||||
echo "compression := {{ compression }}"
|
||||
echo "extra_kargs := {{ extra_kargs }}"
|
||||
echo "container_image := {{ container_image || image }}"
|
||||
echo "polkit := {{ polkit }}"
|
||||
echo "CI := {{ env('CI', '') }}"
|
||||
echo "ARCH := {{ arch }}"
|
||||
echo "{{ style('warning') }}################################################################################{{ NORMAL }}"
|
||||
sleep 1
|
||||
|
||||
|
||||
[no-exit-message]
|
||||
@checkroot:
|
||||
if [ `id -u` -gt 0 ]; then echo '{{ style("error") }}ERROR[build]{{ NORMAL }}: Must be root to build ISO' >&2 && exit 1; fi
|
||||
|
||||
@clean:
|
||||
echo "{{ style('command') }}cleaning {{ absolute_path(workdir) }}...{{ NORMAL }}" >&2
|
||||
rm -rf {{ absolute_path(workdir) }}
|
||||
|
||||
[private]
|
||||
delete-image image:
|
||||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
{{ PODMAN }} rmi --force "{{ image }}" || :
|
||||
|
||||
[private]
|
||||
ci-delete-image image:
|
||||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
if [[ -n "${CI:-}" ]]; then
|
||||
{{ PODMAN }} rmi --force {{ image }} || :
|
||||
fi
|
||||
|
||||
# Run VM with qemu
|
||||
vm ISO_FILE *ARGS:
|
||||
#!/usr/bin/env bash
|
||||
qemu="qemu-system-{{ arch }}"
|
||||
if [[ ! $(type -P "$qemu") ]]; then
|
||||
qemu="flatpak run --command=$qemu org.virt_manager.virt-manager"
|
||||
fi
|
||||
$qemu \
|
||||
-enable-kvm \
|
||||
-M q35 \
|
||||
-cpu host \
|
||||
-smp $(( $(nproc) / 2 > 0 ? $(nproc) / 2 : 1 )) \
|
||||
-m 4G \
|
||||
-net nic,model=virtio \
|
||||
-net user,hostfwd=tcp::2222-:22 \
|
||||
-display gtk,show-cursor=on \
|
||||
-boot d \
|
||||
-cdrom {{ ISO_FILE }} {{ ARGS }}
|
||||
|
||||
# Run VM with a container and web vnc
|
||||
container-run-vm ISO_FILE:
|
||||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
# Determine an available port to use
|
||||
port=8006
|
||||
while grep -q :${port} <<< $(ss -tunalp); do
|
||||
port=$(( port + 1 ))
|
||||
done
|
||||
echo "Using Port: ${port}"
|
||||
echo "Connect to http://localhost:${port}"
|
||||
|
||||
# Ram Size
|
||||
mem_free=$(awk '/MemAvailable/ { printf "%.0f\n", $2/1024/1024 - 1 }' /proc/meminfo)
|
||||
ram_size=$(( mem_free > 64 ? mem_free / 2 : (mem_free > 8 ? 8 : (mem_free < 3 ? 3 : mem_free)) ))
|
||||
|
||||
# Set up the arguments for running the VM
|
||||
run_args=()
|
||||
run_args+=(--rm --privileged)
|
||||
run_args+=(--pull=newer)
|
||||
run_args+=(--publish "127.0.0.1:${port}:8006")
|
||||
run_args+=(--env "CPU_CORES=$(( $(nproc) / 2 > 0 ? $(nproc) / 2 : 1 ))")
|
||||
run_args+=(--env "RAM_SIZE=${ram_size}G")
|
||||
run_args+=(--env "DISK_SIZE=64G")
|
||||
run_args+=(--env "TPM=Y")
|
||||
run_args+=(--env "GPU=Y")
|
||||
run_args+=(--env "BOOT_MODE=windows_secure")
|
||||
run_args+=(--device=/dev/kvm)
|
||||
run_args+=(--volume "{{ canonicalize(ISO_FILE) }}":"/boot.iso")
|
||||
run_args+=(ghcr.io/qemus/qemu)
|
||||
|
||||
# Run the VM and open the browser to connect
|
||||
{{ PODMAN }} run "${run_args[@]}" &
|
||||
xdg-open http://localhost:${port}
|
||||
|
||||
# Print the absolute of the files relative to the project dir.
|
||||
[private]
|
||||
whereis +FILE_PATHS:
|
||||
@realpath -e {{ FILE_PATHS }}
|
||||
420
README.md
420
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# Titanoboa (Beta)
|
||||
# Titanoboa
|
||||
|
||||
A [bootc](https://github.com/bootc-dev/bootc) installer designed to install an image as quickly as possible. Handles a live user session and then hands off to Anaconda or Readymade for installation.
|
||||
A [bootc](https://github.com/bootc-dev/bootc) installer designed to install an image as quickly as possible. This project enables the creation of bootable ISOs directly from bootc container images, where all customizations are embedded within the container image itself.
|
||||
|
||||
## Mission
|
||||
|
||||
|
|
@ -8,41 +8,78 @@ This is an experiment to see how far we can get building our own ISOs. The objec
|
|||
|
||||
- Generate a LiveCD so users can try out an image before committing
|
||||
- Install the image and flatpaks to a selected disk with minimal user-input
|
||||
- Basically be an MVP for `bootc install`
|
||||
- Basically be an MVP for `bootc install`
|
||||
|
||||
## Why?
|
||||
|
||||
Waiting for existing installers to move to cloud native is untenable, let's see if we can remove that external dependency forever. 😈
|
||||
|
||||
## Components
|
||||
|
||||
- LiveCD
|
||||
|
||||
---
|
||||
|
||||
## End-User Documentation
|
||||
|
||||
This guide explains how to consume Titanoboa to create a live ISO image of your custom bootc container image. The [@ublue-os/bluefin](https://github.com/ublue-os/bluefin) repository is the canonical example of this integration.
|
||||
This guide explains how to consume Titanoboa to create a live ISO image of your custom bootc container image.
|
||||
|
||||
### Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Container Image Specification](#container-image-specification)
|
||||
- [GitHub Actions Integration](#github-actions-integration)
|
||||
- [Action Inputs Reference](#action-inputs-reference)
|
||||
- [Configuration Files](#configuration-files)
|
||||
- [Local Usage with Just](#local-usage-with-just)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Complete Example Workflow](#complete-example-workflow)
|
||||
- [Local ISO Generation](#local-iso-generation)
|
||||
- [Testing Your ISO](#testing-your-iso)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before using Titanoboa, ensure you have:
|
||||
|
||||
1. **A bootc-compatible container image** hosted in a container registry (e.g., GitHub Container Registry, Docker Hub, Quay.io)
|
||||
2. **GitHub Actions** (for automated builds) or **Just** + **Podman** (for local builds)
|
||||
3. **Root/sudo access** when building locally
|
||||
4. **Sufficient disk space** (at least 20-30 GB free for building ISOs)
|
||||
1. **A bootc-compatible container image** hosted in a container registry (e.g., GitHub Container Registry, Docker Hub, Quay.io) or locally stored in podman's container storage. This image _must_ adhere to the [Container Image Specification](#container-image-specification).
|
||||
2. **GitHub Actions** (for automated builds).
|
||||
|
||||
### Container Image Specification
|
||||
|
||||
Your `bootc` container image must adhere to a specific contract for Titanoboa to successfully build an ISO. All customizations for the ISO (bootloader entries, kernel arguments, Flatpaks, etc.) are expected to be embedded within your container image.
|
||||
|
||||
This contract is also known as the [Container-native ISO contract v0.1.0 spec](https://github.com/ondrejbudai/bootc-isos/blob/3b3a185e4a57947f57baf53d2be5aee469274f98/README.md#container-native-iso-contract-v010).
|
||||
|
||||
#### `iso.yaml`
|
||||
|
||||
Your container image must contain a file at `/usr/lib/bootc-image-builder/iso.yaml` in YAML format. This file configures the ISO's label and GRUB2 boot entries.
|
||||
|
||||
The `iso.yaml` file supports the following top-level keys:
|
||||
|
||||
- `label` (string): The label for the ISO.
|
||||
- `grub2` (object): GRUB2 configuration, supporting these keys:
|
||||
- `default` (integer): The default menu item (0-indexed).
|
||||
- `timeout` (integer): Default timeout in seconds.
|
||||
- `entries` (array of objects): GRUB2 menu entries. Each entry must have:
|
||||
- `name` (string): Name of the entry.
|
||||
- `linux` (string): Path to the kernel + kernel arguments (e.g., `/images/pxeboot/vmlinuz quiet rhgb`).
|
||||
- `initrd` (string): Path to the initramfs (e.g., `/images/pxeboot/initrd.img`).
|
||||
|
||||
**Example `iso.yaml` (inside your container image):**
|
||||
|
||||
```yaml
|
||||
label: MyCustomImage-ISO
|
||||
grub2:
|
||||
default: 0
|
||||
timeout: 10
|
||||
entries:
|
||||
- name: "My Custom Image Live"
|
||||
linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=MyCustomImage-ISO enforcing=0 rd.live.image"
|
||||
initrd: "/images/pxeboot/initrd.img"
|
||||
- name: "My Custom Image Live (Basic Graphics)"
|
||||
linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=MyCustomImage-ISO enforcing=0 rd.live.image nomodeset"
|
||||
initrd: "/images/pxeboot/initrd.img"
|
||||
```
|
||||
|
||||
#### Required Files
|
||||
|
||||
In addition to `iso.yaml`, your container image is expected to contain:
|
||||
|
||||
- **Kernel**: In `/usr/lib/modules/*/vmlinuz`
|
||||
- **Initramfs**: Next to the kernel, named `initramfs.img`
|
||||
- **UEFI EFI binaries**: In `/boot/efi/EFI/$VENDOR` (e.g., `shimx64.efi`, `mmx64.efi`, `gcdx64.efi`)
|
||||
- **GRUB2 modules**: In `/usr/lib/grub/i386-pc`
|
||||
|
||||
### GitHub Actions Integration
|
||||
|
||||
|
|
@ -59,9 +96,9 @@ Add Titanoboa as a step in your GitHub Actions workflow:
|
|||
image-ref: ghcr.io/your-org/your-image:latest
|
||||
```
|
||||
|
||||
#### Real-World Example from Bluefin
|
||||
#### Real-World Example (from `ublue-os/bluefin`)
|
||||
|
||||
This example shows how [@ublue-os/bluefin](https://github.com/ublue-os/bluefin) consumes Titanoboa:
|
||||
This example shows how an image like `ublue-os/bluefin` consumes Titanoboa, assuming the `bluefin-dx:gts` image already contains its full ISO configuration.
|
||||
|
||||
```yaml
|
||||
- name: Build ISO
|
||||
|
|
@ -69,341 +106,66 @@ This example shows how [@ublue-os/bluefin](https://github.com/ublue-os/bluefin)
|
|||
uses: ublue-os/titanoboa@main
|
||||
with:
|
||||
image-ref: ghcr.io/ublue-os/bluefin-dx:gts
|
||||
flatpaks-list: ${{ github.workspace }}/flatpaks/system-flatpaks.list
|
||||
hook-post-rootfs: ${{ github.workspace }}/iso_files/configure_iso_anaconda.sh
|
||||
kargs: ""
|
||||
builder-distro: fedora
|
||||
|
||||
- name: Rename and Checksum ISO
|
||||
run: |
|
||||
mkdir -p output
|
||||
mv ${{ steps.build.outputs.iso-dest }} output/my-custom-image.iso
|
||||
(cd output && sha256sum my-custom-image.iso | tee my-custom-image.iso-CHECKSUM)
|
||||
|
||||
- name: Upload ISO
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: custom-iso
|
||||
path: output/
|
||||
```
|
||||
|
||||
### Action Inputs Reference
|
||||
### Local ISO Generation
|
||||
|
||||
All inputs for the Titanoboa action:
|
||||
For local development and testing, you can build ISOs directly from a local container image that adheres to the [Container Image Specification](#container-image-specification).
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `image-ref` | **Yes** | - | Reference to the bootc container image (e.g., `ghcr.io/ublue-os/bluefin:lts`) |
|
||||
| `livesys` | No | `true` | Install livesys helpers on the rootfs for live environment features |
|
||||
| `compression` | No | `squashfs` | Compression type: `squashfs` (smaller, slower) or `erofs` (faster) |
|
||||
| `hook-post-rootfs` | No | `""` | Path to a script to run in the rootfs before it's squashed (see [Hook Scripts](#hook-scripts)) |
|
||||
| `hook-pre-initramfs` | No | `""` | Path to a script to run before building the initramfs (e.g., to swap kernels) |
|
||||
| `iso-dest` | No | `${{ github.workspace }}/output.iso` | Where the ISO will be placed |
|
||||
| `flatpaks-list` | No | `none` | Path to a newline-separated list of Flatpak apps to install in the ISO |
|
||||
| `container-image` | No | `""` | Container image to install on target system (can differ from the ISO rootfs image) |
|
||||
| `add-polkit` | No | `true` | Add default polkit rules for the container |
|
||||
| `kargs` | No | `""` | Comma-separated kernel arguments for the live ISO |
|
||||
| `builder-distro` | No | `fedora` | Builder distribution: `fedora`, `centos`, or `almalinux` |
|
||||
|
||||
#### Action Outputs
|
||||
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| `iso-dest` | Absolute path where the ISO was placed |
|
||||
|
||||
### Configuration Files
|
||||
|
||||
#### Flatpaks List
|
||||
|
||||
A text file containing Flatpak application IDs to be preinstalled in your ISO, one per line. Comments are supported with `#`.
|
||||
|
||||
**Example** (`flatpaks/system-flatpaks.list`):
|
||||
```text
|
||||
# Web browsers
|
||||
app/org.mozilla.firefox
|
||||
app/org.mozilla.Thunderbird
|
||||
|
||||
# GNOME apps
|
||||
app/org.gnome.Calculator
|
||||
app/org.gnome.Calendar
|
||||
app/org.gnome.TextEditor
|
||||
app/org.gnome.Loupe
|
||||
|
||||
# Themes
|
||||
runtime/org.gtk.Gtk3theme.adw-gtk3
|
||||
runtime/org.gtk.Gtk3theme.adw-gtk3-dark
|
||||
|
||||
# Development tools
|
||||
app/com.github.tchx84.Flatseal
|
||||
app/io.github.flattool.Warehouse
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```yaml
|
||||
with:
|
||||
flatpaks-list: ${{ github.workspace }}/flatpaks/system-flatpaks.list
|
||||
```
|
||||
|
||||
#### Hook Scripts
|
||||
|
||||
Hook scripts allow you to customize the ISO build process at different stages.
|
||||
|
||||
##### `hook-post-rootfs`
|
||||
|
||||
A bash script that runs **inside the rootfs** before it's compressed. Use this to:
|
||||
- Install additional packages
|
||||
- Configure Anaconda
|
||||
- Customize the live environment
|
||||
- Set up default desktop settings
|
||||
- Disable unnecessary services
|
||||
|
||||
**Example** (`iso_files/configure_iso.sh`):
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -eoux pipefail
|
||||
|
||||
# Remove packages to save space
|
||||
dnf remove -y google-noto-fonts-all ublue-brew || true
|
||||
|
||||
# Configure GNOME defaults for live session
|
||||
tee /usr/share/glib-2.0/schemas/zz2-org.gnome.shell.gschema.override <<EOF
|
||||
[org.gnome.shell]
|
||||
favorite-apps = ['anaconda.desktop', 'org.mozilla.firefox.desktop', 'org.gnome.Nautilus.desktop']
|
||||
EOF
|
||||
|
||||
glib-compile-schemas /usr/share/glib-2.0/schemas
|
||||
|
||||
# Disable services not needed in live environment
|
||||
systemctl disable rpm-ostree-countme.service
|
||||
systemctl disable tailscaled.service
|
||||
systemctl disable bootloader-update.service
|
||||
|
||||
# Install Anaconda for installation
|
||||
dnf install -y anaconda-live libblockdev-btrfs
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```yaml
|
||||
with:
|
||||
hook-post-rootfs: ${{ github.workspace }}/iso_files/configure_iso.sh
|
||||
```
|
||||
|
||||
##### `hook-pre-initramfs`
|
||||
|
||||
A bash script that runs **before generating the initramfs**. Use this to:
|
||||
- Replace the kernel
|
||||
- Add custom kernel modules
|
||||
- Modify dracut configuration
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -eoux pipefail
|
||||
|
||||
# Install a different kernel
|
||||
dnf swap -y kernel kernel-lts
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```yaml
|
||||
with:
|
||||
hook-pre-initramfs: ${{ github.workspace }}/iso_files/pre_initramfs.sh
|
||||
```
|
||||
|
||||
### Local Usage with Just
|
||||
|
||||
For local development and testing, you can build ISOs using the `just` command directly.
|
||||
|
||||
#### Basic Build Command
|
||||
First, build your container image:
|
||||
|
||||
```bash
|
||||
sudo just build ghcr.io/ublue-os/bluefin:lts
|
||||
sudo podman build --cap-add sys_admin --security-opt label=disable --squash -t your-local-image-name .
|
||||
```
|
||||
|
||||
This creates `output.iso` in the current directory.
|
||||
|
||||
#### Advanced Build Options
|
||||
|
||||
The `just build` command accepts multiple parameters:
|
||||
Then, run the `main.sh` script, providing your locally built image:
|
||||
|
||||
```bash
|
||||
just build <image> [livesys] [flatpaks_file] [compression] [extra_kargs] [container_image] [polkit]
|
||||
sudo TITANOBOA_CTR_IMAGE="your-local-image-name" ./main.sh
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `image`: Container image reference (required)
|
||||
- `livesys`: Enable livesys scripts (`1` or `0`, default: `1`)
|
||||
- `flatpaks_file`: Path to flatpaks list (default: `src/flatpaks.example.txt`)
|
||||
- `compression`: Compression type (`squashfs` or `erofs`, default: `squashfs`)
|
||||
- `extra_kargs`: Comma-separated kernel arguments (default: `NONE`)
|
||||
- `container_image`: Container to install vs. the ISO rootfs image (default: same as `image`)
|
||||
- `polkit`: Add polkit rules (`1` or `0`, default: `1`)
|
||||
|
||||
**Examples:**
|
||||
You can also use the titanoboa container directly to build your ISOs:
|
||||
|
||||
```bash
|
||||
# Build with custom flatpaks list
|
||||
sudo just build ghcr.io/ublue-os/bluefin:lts 1 ./my-flatpaks.txt
|
||||
|
||||
# Build with erofs compression
|
||||
sudo just build ghcr.io/ublue-os/bluefin:lts 1 src/flatpaks.example.txt erofs
|
||||
|
||||
# Build without livesys scripts
|
||||
sudo just build ghcr.io/ublue-os/bluefin:lts 0
|
||||
|
||||
# Build with custom kernel arguments
|
||||
sudo just build ghcr.io/ublue-os/bluefin:lts 1 src/flatpaks.example.txt squashfs "rd.live.overlay.size=8192"
|
||||
sudo podman run --rm -it \
|
||||
--security-opt label=disable \
|
||||
-v ./output:/output \
|
||||
-v /var/lib/containers/storage:/usr/lib/containers/storage:ro \
|
||||
--mount type=image,source="your-local-image-name",dst=/rootfs \
|
||||
ghcr.io/ublue-os/titanoboa:latest
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Titanoboa can be customized using environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `TITANOBOA_WORKDIR` | `work` | Working directory for build artifacts |
|
||||
| `TITANOBOA_ISO_ROOT` | `work/iso-root` | ISO root directory |
|
||||
| `TITANOBOA_BUILDER_DISTRO` | `fedora` | Builder container distribution (`fedora`, `centos`, `almalinux`) |
|
||||
| `HOOK_post_rootfs` | `""` | Path to post-rootfs hook script |
|
||||
| `HOOK_pre_initramfs` | `""` | Path to pre-initramfs hook script |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
# Use CentOS Stream 10 builder
|
||||
TITANOBOA_BUILDER_DISTRO=centos sudo just build ghcr.io/ublue-os/bluefin:lts
|
||||
|
||||
# Use custom working directory
|
||||
TITANOBOA_WORKDIR=/mnt/scratch sudo just build ghcr.io/your-org/your-image:latest
|
||||
|
||||
# Use hook scripts
|
||||
HOOK_post_rootfs=/path/to/script.sh sudo just build ghcr.io/your-org/your-image:latest
|
||||
```
|
||||
|
||||
### Complete Example Workflow
|
||||
|
||||
Here's a complete GitHub Actions workflow that builds ISOs for your custom image:
|
||||
|
||||
```yaml
|
||||
name: Build Custom ISO
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Weekly on Sunday
|
||||
|
||||
jobs:
|
||||
build-iso:
|
||||
name: Build ISO
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
|
||||
steps:
|
||||
- name: Maximize build space
|
||||
uses: ublue-os/remove-unwanted-software@v9
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build ISO
|
||||
id: build
|
||||
uses: ublue-os/titanoboa@main
|
||||
with:
|
||||
image-ref: ghcr.io/your-org/your-custom-image:latest
|
||||
flatpaks-list: ${{ github.workspace }}/config/flatpaks.list
|
||||
hook-post-rootfs: ${{ github.workspace }}/config/configure-iso.sh
|
||||
compression: squashfs
|
||||
builder-distro: fedora
|
||||
|
||||
- name: Rename ISO
|
||||
run: |
|
||||
mkdir -p output
|
||||
mv ${{ steps.build.outputs.iso-dest }} output/custom-image-$(date +%Y%m%d).iso
|
||||
(cd output && sha256sum *.iso | tee CHECKSUMS)
|
||||
|
||||
- name: Upload ISO
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: custom-iso
|
||||
path: output/
|
||||
```
|
||||
The generated ISO will be placed in the `output/` directory by default.
|
||||
|
||||
### Testing Your ISO
|
||||
|
||||
After building your ISO, test it using the built-in VM command:
|
||||
After building your ISO via the GitHub Action, you can download the artifact and test it in a virtual machine locally. For local testing during development, you can adapt the `main.sh` script to build the ISO from a local bootc image.
|
||||
|
||||
Example of how to run the VM locally (if you have QEMU installed):
|
||||
|
||||
```bash
|
||||
# Test in a VM
|
||||
just vm ./output.iso
|
||||
|
||||
# Or use the container-based VM with web VNC
|
||||
just container-run-vm ./output.iso
|
||||
# Example: Assuming you downloaded your ISO to ./output/my-custom-image.iso
|
||||
qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
-M q35 \
|
||||
-cpu host \
|
||||
-smp 2 \
|
||||
-m 4G \
|
||||
-net nic,model=virtio \
|
||||
-net user,hostfwd=tcp::2222-:22 \
|
||||
-display gtk,show-cursor=on \
|
||||
-boot d \
|
||||
-cdrom ./output/my-custom-image.iso
|
||||
```
|
||||
|
||||
The `just vm` command launches a QEMU virtual machine with:
|
||||
- TPM 2.0 support
|
||||
- UEFI Secure Boot
|
||||
- Automatic memory allocation
|
||||
- GPU passthrough (when available)
|
||||
|
||||
The `container-run-vm` command:
|
||||
- Runs QEMU in a container
|
||||
- Provides web-based VNC access (auto-opens in browser)
|
||||
- Useful for headless systems or CI environments
|
||||
|
||||
### Builder Distribution Support
|
||||
|
||||
By default, Titanoboa uses Fedora containers for building tools and dependencies. You can specify different builder distributions:
|
||||
|
||||
- **fedora** (default): Uses `quay.io/fedora/fedora:latest` - Best for Fedora-based images
|
||||
- **centos**: Uses `ghcr.io/hanthor/centos-anaconda-builder:main` - For CentOS Stream 10 / LTS images
|
||||
- **almalinux**: Uses `quay.io/almalinux/almalinux:10` - For AlmaLinux-based images
|
||||
|
||||
**When to use different builders:**
|
||||
- Use `centos` when building LTS images based on CentOS Stream
|
||||
- Use `fedora` for standard Fedora-based images (default)
|
||||
- Use `almalinux` for AlmaLinux-based custom images
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# GitHub Actions
|
||||
with:
|
||||
builder-distro: centos
|
||||
|
||||
# Local with just
|
||||
TITANOBOA_BUILDER_DISTRO=centos sudo just build ghcr.io/ublue-os/bluefin:lts
|
||||
```
|
||||
|
||||
### Tips and Best Practices
|
||||
|
||||
1. **Minimize ISO size**: Remove unnecessary packages in your `hook-post-rootfs` script to keep the ISO smaller
|
||||
2. **Test incrementally**: Build and test ISOs frequently during development
|
||||
3. **Use proper compression**: Use `squashfs` for smaller ISOs (better for distribution), `erofs` for faster boot times
|
||||
4. **Flatpak selection**: Only include essential Flatpaks in your ISO; users can install more after installation
|
||||
5. **Builder matching**: Use the builder distribution that matches your base image for best compatibility
|
||||
6. **Clean builds**: Run `just clean` between builds to ensure a fresh start
|
||||
7. **Hook scripts**: Make your hook scripts idempotent and include error handling
|
||||
8. **Version pinning**: Pin to specific Titanoboa releases in production workflows (e.g., `ublue-os/titanoboa@v1.0.0`)
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
**Build fails with "out of space":**
|
||||
- Free up disk space or use a larger volume
|
||||
- Remove unnecessary files in your `hook-post-rootfs` script
|
||||
- Consider using `erofs` compression instead of `squashfs`
|
||||
|
||||
**ISO doesn't boot:**
|
||||
- Verify your base container image is bootc-compatible
|
||||
- Check that the image exists and is accessible
|
||||
- Ensure UEFI Secure Boot settings are correct for your hardware
|
||||
|
||||
**Flatpaks don't install:**
|
||||
- Verify Flatpak IDs are correct (use `flatpak search <name>` to find IDs)
|
||||
- Check network connectivity during build
|
||||
- Review build logs for specific Flatpak installation errors
|
||||
|
||||
**Hook script fails:**
|
||||
- Ensure scripts have executable permissions: `chmod +x script.sh`
|
||||
- Use `set -eoux pipefail` at the start of scripts for better error reporting
|
||||
- Test scripts in a container before using them in builds
|
||||
|
||||
## Contributor Metrics
|
||||
|
||||

|
||||
|
|
|
|||
97
action.yml
97
action.yml
|
|
@ -4,49 +4,12 @@ description: >-
|
|||
The resulting iso will be placed at `output.iso`.
|
||||
inputs:
|
||||
image-ref:
|
||||
description: Reference to the bootc container image.
|
||||
description: Reference to the bootc container image to build the ISO from.
|
||||
required: true
|
||||
livesys:
|
||||
description: Install livesys helpers on the rootfs for the ISO
|
||||
required: false
|
||||
default: "true"
|
||||
compression:
|
||||
description: Which type of compression will the ISO will use (erofs (default), or squashfs (smaller sizes, slower read))
|
||||
required: false
|
||||
default: squashfs
|
||||
hook-post-rootfs:
|
||||
description: >-
|
||||
Path to a script ran in the rootfs of the container image before
|
||||
being squashed in `squashfs.img`.
|
||||
default: ""
|
||||
hook-pre-initramfs:
|
||||
description: >-
|
||||
Path to a script to be ran before building the initramfs (per example, to swap the kernel).
|
||||
default: ""
|
||||
iso-dest:
|
||||
description: Where the iso will be placed
|
||||
required: false
|
||||
default: ${{ github.workspace }}/output.iso
|
||||
flatpaks-list:
|
||||
description: Path to a file with a newline separated flatpak apps list to be installed in the rootfs.
|
||||
required: false
|
||||
default: none
|
||||
container-image:
|
||||
description: Container image that will be installed onto the target system (can be different from rootfs)
|
||||
required: false
|
||||
default: ""
|
||||
add-polkit:
|
||||
description: Add default polkit rules for the container
|
||||
required: false
|
||||
default: "true"
|
||||
kargs:
|
||||
description: Kernel arguments to be supplied for the live ISO, comma separated
|
||||
required: false
|
||||
default: ""
|
||||
builder-distro:
|
||||
description: Distribution to use for the builder container (for tools and dependencies)
|
||||
required: false
|
||||
default: fedora
|
||||
outputs:
|
||||
iso-dest:
|
||||
description: Where the iso was be placed
|
||||
|
|
@ -55,73 +18,35 @@ outputs:
|
|||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install Just
|
||||
id: install-just
|
||||
uses: extractions/setup-just@e33e0265a09d6d736e2ee1e0eb685ef1de4669ff # v3
|
||||
|
||||
- name: Generate iso
|
||||
id: generate-iso
|
||||
env:
|
||||
WORKSPACE: ${{ github.workspace }}
|
||||
DESTINATION_PATH: ${{ inputs.iso-dest }}
|
||||
COMPRESSION: ${{ inputs.compression }}
|
||||
IMAGE_REF: ${{ inputs.image-ref }}
|
||||
LIVESYS: ${{ inputs.livesys }}
|
||||
ACTION_PATH: ${{ github.action_path }}
|
||||
FLATPAKS_LIST: ${{ inputs.flatpaks-list }}
|
||||
ADD_POLKIT: ${{ inputs.add-polkit }}
|
||||
HOOK_POST_ROOTFS: ${{ inputs.hook-post-rootfs }}
|
||||
HOOK_PRE_INITRAMFS: ${{ inputs.hook-pre-initramfs }}
|
||||
CONTAINER_IMAGE: ${{ inputs.container-image }}
|
||||
EXTRA_KARGS: ${{ inputs.kargs }}
|
||||
TITANOBOA_BUILDER_DISTRO: ${{ inputs.builder-distro }}
|
||||
TITANOBOA_CTR_IMAGE: ${{ inputs.image-ref }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cd "${ACTION_PATH}"
|
||||
cd "$GITHUB_ACTION_PATH" || exit 1
|
||||
|
||||
just=$(which just)
|
||||
|
||||
USE_LIVESYS=$(echo "${LIVESYS}" | sed -e 's/true/1/g' -e 's/false/0/g')
|
||||
ADD_POLKIT=$(echo "${ADD_POLKIT}" | sed -e 's/true/1/g' -e 's/false/0/g')
|
||||
|
||||
sudo \
|
||||
PATH="$PATH" \
|
||||
CI="$CI" \
|
||||
HOOK_post_rootfs="${HOOK_POST_ROOTFS}" \
|
||||
HOOK_pre_initramfs="${HOOK_PRE_INITRAMFS}" \
|
||||
TITANOBOA_BUILDER_DISTRO="${TITANOBOA_BUILDER_DISTRO}" \
|
||||
$just build \
|
||||
"${IMAGE_REF}" "${USE_LIVESYS}" "${FLATPAKS_LIST}" "${COMPRESSION}" "${EXTRA_KARGS}" "${CONTAINER_IMAGE}" "${ADD_POLKIT}"
|
||||
# Build ISO
|
||||
ISO_FILENAME="$(env TITANOBOA_CTR_IMAGE="$TITANOBOA_CTR_IMAGE" "$GITHUB_ACTION_PATH"/main.sh)"
|
||||
|
||||
# Fix iso file permisions
|
||||
sudo chown $(id -u):$(id -g) ./output.iso
|
||||
sudo chown $(id -u):$(id -g) "$ISO_FILENAME"
|
||||
|
||||
# Move iso to iso-dest
|
||||
dest="$(realpath --relative-base="${WORKSPACE}" "${DESTINATION_PATH}")"
|
||||
dest="$(realpath $dest)"
|
||||
if [[ "$(realpath ./output.iso)" != "${dest}" ]]; then
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
mv ./output.iso "$dest"
|
||||
fi
|
||||
dest="$(realpath --relative-base="${GITHUB_WORKSPACE}" "${DESTINATION_PATH}")"
|
||||
dest="$(realpath -m $dest)"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
mv "$ISO_FILENAME" "$dest"
|
||||
echo "iso_dest=$dest" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cleanup
|
||||
id: titanoboa-cleanup
|
||||
shell: bash
|
||||
env:
|
||||
ACTION_PATH: ${{ github.action_path }}
|
||||
run: |
|
||||
cd "${ACTION_PATH}"
|
||||
# Cleanup
|
||||
sudo PATH="$PATH" $(which just) clean
|
||||
|
||||
- name: Check the iso is there
|
||||
shell: bash
|
||||
env:
|
||||
DEST_ISO: ${{ steps.generate-iso.outputs.iso_dest }}
|
||||
run: |
|
||||
[[ -f "${DEST_ISO}" ]] || {
|
||||
echo "::error:Iso file does not exist at $dest"
|
||||
echo "::error:Iso file does not exist at ${DEST_ISO}"
|
||||
exit 1
|
||||
}
|
||||
|
|
|
|||
94
build_iso.sh
Executable file
94
build_iso.sh
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env -S bash -exo pipefail
|
||||
|
||||
{ export PS4='+( ${BASH_SOURCE}:${LINENO} ): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; } 2>/dev/null
|
||||
|
||||
dnf install -y squashfs-tools xorriso yq mtools dosfstools
|
||||
|
||||
mkdir -p \
|
||||
/work \
|
||||
/work/iso-root \
|
||||
/work/iso-root/boot/grub2 \
|
||||
/work/iso-root/images/pxeboot \
|
||||
/work/iso-root/LiveOS
|
||||
cd /work || exit 1
|
||||
|
||||
# Create the squashfs image of the container image
|
||||
mksquashfs /rootfs /work/iso-root/LiveOS/squashfs.img -all-root -noappend -e sysroot -e ostree -comp zstd -Xcompression-level 19
|
||||
|
||||
iso_config_file=/rootfs/usr/lib/bootc-image-builder/iso.yaml
|
||||
if [[ ! -f $iso_config_file ]]; then
|
||||
echo >&2 "ERROR: Missing /usr/lib/bootc-image-builder/iso.yaml file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
iso_label=$(yq '.label' <$iso_config_file)
|
||||
|
||||
# Copy initrd and kernel
|
||||
cp -av /rootfs/usr/lib/modules/*/initramfs.img /work/iso-root/images/pxeboot/initrd.img
|
||||
cp -av /rootfs/usr/lib/modules/*/vmlinuz /work/iso-root/images/pxeboot/vmlinuz
|
||||
|
||||
# Copy GRUB modules
|
||||
for grub_arch in i386-pc arm64-efi; do
|
||||
[ -f "/rootfs/usr/lib/grub/$grub_arch" ] || continue
|
||||
echo >&2 "Found $grub_arch files, copying to /work/iso-root/boot/grub2/$grub_arch ..."
|
||||
cp -avT /rootfs/usr/lib/grub/$grub_arch /work/iso-root/boot/grub2/$grub_arch
|
||||
done
|
||||
|
||||
# Copy efi dir
|
||||
cp -avT /rootfs/boot/efi/EFI /work/EFI
|
||||
|
||||
# Generate grub.cfg
|
||||
{ grub_cfg="$(</dev/stdin)"; } <<EOF
|
||||
set timeout=$(yq '.grub2.timeout // 10' <$iso_config_file)
|
||||
set default="$(yq '.grub2.default // 0' <$iso_config_file)"
|
||||
set menu_auto_hide=false
|
||||
|
||||
function load_video {
|
||||
insmod all_video
|
||||
}
|
||||
|
||||
load_video
|
||||
set gfxpayload=keep
|
||||
insmod gzio
|
||||
insmod part_gpt
|
||||
insmod chain
|
||||
|
||||
search --no-floppy --set=root -l '$iso_label'
|
||||
|
||||
EOF
|
||||
for i in $(yq '.grub2.entries | keys | .[]' <"$iso_config_file"); do
|
||||
entry_name=$(yq ".grub2.entries[$i].name" <"$iso_config_file")
|
||||
entry_linux=$(yq ".grub2.entries[$i].linux" <"$iso_config_file")
|
||||
entry_initrd=$(yq ".grub2.entries[$i].initrd" <"$iso_config_file")
|
||||
{ grub_cfg+=$'\n'"$(</dev/stdin)"; } <<EOF
|
||||
menuentry '$entry_name' {
|
||||
linux $entry_linux
|
||||
initrd $entry_initrd
|
||||
}
|
||||
EOF
|
||||
done
|
||||
|
||||
for dir in /work/EFI/* /work/iso-root/boot/grub2; do
|
||||
echo "$grub_cfg" >"$dir/grub.cfg"
|
||||
done
|
||||
|
||||
# For some reason, fedora also copies EFI into /boot/EFI (?), probably because of hardcoded prefix in grub/shim
|
||||
cp -avT /work/EFI /work/iso-root/EFI
|
||||
|
||||
# Generate uefi.img
|
||||
pushd /work || exit 1
|
||||
truncate -s 100M /work/uefi.img
|
||||
mkfs.fat -F32 /work/uefi.img
|
||||
mcopy -v -i /work/uefi.img -s /work/EFI ::
|
||||
xorriso -as mkisofs \
|
||||
-R \
|
||||
-V "$iso_label" \
|
||||
-partition_offset 16 \
|
||||
-appended_part_as_gpt \
|
||||
-append_partition 2 C12A7328-F81F-11D2-BA4B-00A0C93EC93B ./uefi.img \
|
||||
-iso_mbr_part_type EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 \
|
||||
-e --interval:appended_partition_2:all:: \
|
||||
-no-emul-boot \
|
||||
-iso-level 3 \
|
||||
-o "/output/$iso_label.iso" \
|
||||
iso-root
|
||||
7
examples/README.md
Normal file
7
examples/README.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Examples of container images prepared to be embeded into an ISO.
|
||||
|
||||
## References:
|
||||
|
||||
- [bootc-isos](https://github.com/ondrejbudai/bootc-isos/tree/3b3a185e4a57947f57baf53d2be5aee469274f98).
|
||||
- [Container-native ISO contract v0.1.0](https://github.com/ondrejbudai/bootc-isos/blob/3b3a185e4a57947f57baf53d2be5aee469274f98/README.md#container-native-iso-contract-v010)
|
||||
- [FOSDEM 2026](https://fosdem.org/2026/schedule/event/8MUFCA-building_isos_from_oci_containers/)
|
||||
7
examples/bazzite/Containerfile
Normal file
7
examples/bazzite/Containerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# run with --cap-add sys_admin --security-opt label=disable --squash
|
||||
|
||||
FROM ghcr.io/ublue-os/bazzite:latest
|
||||
COPY ./src /src
|
||||
RUN --mount=type=cache,target=/var/tmp/libdnf5,id=libdnf5 \
|
||||
/src/build.sh
|
||||
RUN rm -fr /src
|
||||
85
examples/bazzite/src/build.sh
Executable file
85
examples/bazzite/src/build.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/bash
|
||||
|
||||
set -exo pipefail
|
||||
|
||||
{ export PS4='+( ${BASH_SOURCE}:${LINENO} ): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; } 2>/dev/null
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Create the directory that /root is symlinked to
|
||||
mkdir -p "$(realpath /root)"
|
||||
|
||||
# bwrap tries to write /proc/sys/user/max_user_namespaces which is mounted as ro
|
||||
# so we need to remount it as rw
|
||||
mount -o remount,rw /proc/sys
|
||||
|
||||
# Install flatpaks
|
||||
curl --retry 3 -Lo /etc/flatpak/remotes.d/flathub.flatpakrepo https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
xargs -r flatpak install -y --noninteractive <"$SCRIPT_DIR/flatpaks"
|
||||
|
||||
# Pull the container image to be installed
|
||||
podman pull "ghcr.io/ublue-os/bazzite:stable"
|
||||
|
||||
# Run the preinitramfs hook
|
||||
"$SCRIPT_DIR/titanoboa_hook_preinitramfs.sh"
|
||||
|
||||
# Install dracut-live and regenerate the initramfs
|
||||
dnf install -y dracut-live
|
||||
kernel=$(kernel-install list --json pretty | jq -r '.[] | select(.has_kernel == true) | .version')
|
||||
DRACUT_NO_XATTR=1 dracut -v --force --zstd --reproducible --no-hostonly \
|
||||
--add "dmsquash-live dmsquash-live-autooverlay" \
|
||||
"/usr/lib/modules/${kernel}/initramfs.img" "${kernel}"
|
||||
|
||||
# Install livesys-scripts and configure them
|
||||
dnf install -y livesys-scripts
|
||||
sed -i "s/^livesys_session=.*/livesys_session=kde/" /etc/sysconfig/livesys
|
||||
systemctl enable livesys.service livesys-late.service
|
||||
|
||||
# Run the postrootfs hook
|
||||
"$SCRIPT_DIR/titanoboa_hook_postrootfs.sh"
|
||||
|
||||
# image-builder needs gcdx64.efi
|
||||
dnf install -y grub2-efi-x64-cdboot
|
||||
|
||||
# image-builder expects the EFI directory to be in /boot/efi
|
||||
mkdir -p /boot/efi
|
||||
cp -av /usr/lib/efi/*/*/EFI /boot/efi/
|
||||
|
||||
# Remove fallback efi
|
||||
cp -v /boot/efi/EFI/fedora/grubx64.efi /boot/efi/EFI/BOOT/fbx64.efi # NOTE: remove this line if breaks bootloader
|
||||
|
||||
# needed for image-builder's buildroot
|
||||
dnf install -y xorriso isomd5sum
|
||||
|
||||
# Set the timezone to UTC
|
||||
rm -f /etc/localtime
|
||||
systemd-firstboot --timezone UTC
|
||||
|
||||
# / in a booted live ISO is an overlayfs with upperdir pointed somewhere under /run
|
||||
# This means that /var/tmp is also technically under /run.
|
||||
# /run is of course a tmpfs, but set with quite a small size.
|
||||
# ostree needs quite a lot of space on /var/tmp for temporary files so /run is not enough.
|
||||
# Mount a larger tmpfs to /var/tmp at boot time to avoid this issue.
|
||||
rm -rf /var/tmp || :
|
||||
mkdir -p /var/tmp
|
||||
cat >/etc/systemd/system/var-tmp.mount <<'EOF'
|
||||
[Unit]
|
||||
Description=Larger tmpfs for /var/tmp on live system
|
||||
|
||||
[Mount]
|
||||
What=tmpfs
|
||||
Where=/var/tmp
|
||||
Type=tmpfs
|
||||
Options=size=50%%,nr_inodes=1m,x-systemd.graceful-option=usrquota
|
||||
|
||||
[Install]
|
||||
WantedBy=local-fs.target
|
||||
EOF
|
||||
systemctl enable var-tmp.mount
|
||||
|
||||
# Copy in the iso config for image-builder
|
||||
mkdir -p /usr/lib/bootc-image-builder
|
||||
cp /src/iso.yaml /usr/lib/bootc-image-builder/iso.yaml
|
||||
|
||||
# Clean up dnf cache to save space
|
||||
# dnf clean all
|
||||
17
examples/bazzite/src/flatpaks
Normal file
17
examples/bazzite/src/flatpaks
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
app/org.mozilla.firefox/x86_64/stable
|
||||
app/org.kde.gwenview/x86_64/stable
|
||||
app/org.kde.okular/x86_64/stable
|
||||
app/org.kde.kcalc/x86_64/stable
|
||||
app/org.kde.haruna/x86_64/stable
|
||||
app/org.kde.filelight/x86_64/stable
|
||||
app/com.ranfdev.DistroShelf/x86_64/stable
|
||||
app/com.github.tchx84.Flatseal/x86_64/stable
|
||||
app/com.github.Matoking.protontricks/x86_64/stable
|
||||
app/io.github.flattool.Warehouse/x86_64/stable
|
||||
app/com.vysp3r.ProtonPlus/x86_64/stable
|
||||
runtime/org.freedesktop.Platform.VulkanLayer.MangoHud/x86_64/23.08
|
||||
runtime/org.freedesktop.Platform.VulkanLayer.vkBasalt/x86_64/23.08
|
||||
runtime/org.freedesktop.Platform.VulkanLayer.OBSVkCapture/x86_64/23.08
|
||||
runtime/com.obsproject.Studio.Plugin.OBSVkCapture/x86_64/stable
|
||||
runtime/com.obsproject.Studio.Plugin.Gstreamer/x86_64/stable
|
||||
runtime/com.obsproject.Studio.Plugin.GStreamerVaapi/x86_64/stable
|
||||
7
examples/bazzite/src/iso.yaml
Normal file
7
examples/bazzite/src/iso.yaml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
label: "Bazzite-Live"
|
||||
grub2:
|
||||
timeout: 10
|
||||
entries:
|
||||
- name: "Bazzite Live ISO"
|
||||
linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=Bazzite-Live enforcing=0 rd.live.image"
|
||||
initrd: "/images/pxeboot/initrd.img"
|
||||
592
examples/bazzite/src/titanoboa_hook_postrootfs.sh
Executable file
592
examples/bazzite/src/titanoboa_hook_postrootfs.sh
Executable file
|
|
@ -0,0 +1,592 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# This is just an example. Is not meant to be used verbatim on production.
|
||||
|
||||
set -exo pipefail
|
||||
|
||||
source /etc/os-release
|
||||
|
||||
# Remove all versionlocks, in order to avoid dependency issues
|
||||
dnf -qy versionlock clear
|
||||
|
||||
# Install Anaconda
|
||||
dnf install -qy --enable-repo=fedora-cisco-openh264 --allowerasing firefox anaconda-live libblockdev-{btrfs,lvm,dm}
|
||||
|
||||
mkdir -p /var/lib/rpm-state # Needed for Anaconda Web UI
|
||||
|
||||
# Utilities for displaying a dialog prompting users to review secure boot documentation
|
||||
dnf install -qy --setopt=install_weak_deps=0 qrencode yad
|
||||
|
||||
# Variables
|
||||
## TODO: Make this configurable!
|
||||
imageref="ghcr.io/ublue-os/bazzite"
|
||||
imagetag="latest"
|
||||
sbkey='https://github.com/ublue-os/akmods/raw/main/certs/public_key.der'
|
||||
SECUREBOOT_KEY="/usr/share/ublue-os/sb_pubkey.der"
|
||||
SECUREBOOT_DOC_URL="https://docs.bazzite.gg/sb"
|
||||
SECUREBOOT_DOC_URL_QR="/usr/share/ublue-os/secure_boot_qr.png"
|
||||
|
||||
# Bazzite anaconda profile
|
||||
: ${VARIANT_ID:?}
|
||||
cat >/etc/anaconda/profile.d/bazzite.conf <<EOF
|
||||
# Anaconda configuration file for bazzite
|
||||
|
||||
[Profile]
|
||||
# Define the profile.
|
||||
profile_id = bazzite
|
||||
|
||||
[Profile Detection]
|
||||
# Match os-release values
|
||||
os_id = bazzite
|
||||
|
||||
[Network]
|
||||
default_on_boot = FIRST_WIRED_WITH_LINK
|
||||
|
||||
[Bootloader]
|
||||
efi_dir = fedora
|
||||
menu_auto_hide = True
|
||||
|
||||
[Storage]
|
||||
default_scheme = BTRFS
|
||||
btrfs_compression = zstd:1
|
||||
default_partitioning =
|
||||
/ (min 1 GiB, max 70 GiB)
|
||||
/home (min 500 MiB, free 50 GiB)
|
||||
/var (btrfs)
|
||||
|
||||
[User Interface]
|
||||
custom_stylesheet = /usr/share/anaconda/pixmaps/fedora.css
|
||||
hidden_spokes =
|
||||
NetworkSpoke
|
||||
PasswordSpoke
|
||||
|
||||
hidden_webui_pages =
|
||||
root-password
|
||||
network
|
||||
|
||||
[Localization]
|
||||
use_geolocation = False
|
||||
EOF
|
||||
|
||||
echo "Bazzite release $VERSION_ID ($VERSION_CODENAME)" >/etc/system-release
|
||||
|
||||
# Get Artwork
|
||||
git clone --depth 1 --quiet https://github.com/ublue-os/bazzite.git /root/packages
|
||||
case "${PRETTY_NAME,,}" in
|
||||
"bazzite"*)
|
||||
mkdir -p /usr/share/anaconda/pixmaps/silverblue
|
||||
cp -r /root/packages/installer/branding/* /usr/share/anaconda/pixmaps/
|
||||
;;
|
||||
esac
|
||||
|
||||
# Installer icon
|
||||
_icon=/root/packages/installer/branding/bazzite-installer.svg
|
||||
_icon_symbol=/root/packages/installer/branding/bazzite-installer-symbolic.svg
|
||||
if [[ -f $_icon ]]; then
|
||||
for f in \
|
||||
/usr/share/icons/hicolor/48x48/apps/org.fedoraproject.AnacondaInstaller.svg \
|
||||
/usr/share/icons/hicolor/scalable/apps/org.fedoraproject.AnacondaInstaller.svg; do
|
||||
cp "$_icon" "$f"
|
||||
done
|
||||
cp "$_icon_symbol" /usr/share/icons/hicolor/symbolic/apps/org.fedoraproject.AnacondaInstaller-symbolic.svg
|
||||
fi
|
||||
unset -v _icon
|
||||
unset -v _icon_symbol
|
||||
rm -rf /root/packages
|
||||
|
||||
# Secureboot Key Fetch
|
||||
mkdir -p /usr/share/ublue-os
|
||||
curl -Lo /usr/share/ublue-os/sb_pubkey.der "$sbkey"
|
||||
|
||||
# Default Kickstart
|
||||
cat <<EOF >>/usr/share/anaconda/interactive-defaults.ks
|
||||
|
||||
# Create log directory
|
||||
%pre
|
||||
mkdir -p /tmp/anacoda_custom_logs
|
||||
%end
|
||||
|
||||
# Check if there is a bitlocker partition and ask the user to disable it
|
||||
%pre --erroronfail --log=/tmp/anacoda_custom_logs/detect_bitlocker.log
|
||||
DOCS_QR=/tmp/detect_bitlocker_qr.png
|
||||
IS_BITLOCKER=\$(lsblk -o FSTYPE --json | jq '.blockdevices | map(select(.fstype == "BitLocker")) | . != []')
|
||||
{ WARNING_MSG="\$(</dev/stdin)"; } << 'WARNINGEOF'
|
||||
<span size="x-large">Windows Bitlocker partition detected</span>
|
||||
|
||||
It might interrupt the installation process.
|
||||
In such case, please, do <b>one</b> of the following:
|
||||
a) Disconnect its storage drive.
|
||||
b) Disable Bitlocker in Windows.
|
||||
c) Delete it in GNOME Disks.
|
||||
|
||||
Do you wish to continue?
|
||||
WARNINGEOF
|
||||
|
||||
if [[ \$IS_BITLOCKER =~ true ]]; then
|
||||
qrencode -o \$DOCS_QR "https://www.wikihow.com/Turn-Off-BitLocker"
|
||||
_EXITLOCK=1
|
||||
_RETCODE=0
|
||||
while [[ \$_EXITLOCK -ne 0 ]]; do
|
||||
run0 --user=liveuser yad \
|
||||
--on-top \
|
||||
--timeout=10 \
|
||||
--image=\$DOCS_QR \
|
||||
--text="\$WARNING_MSG" \
|
||||
--button="Yes, I'm aware, continue":0 --button="Cancel installation":10
|
||||
_RETCODE=\$?
|
||||
case \$_RETCODE in
|
||||
0) _EXITLOCK=0; ;;
|
||||
10) _EXITLOCK=0; pkill liveinst; pkill firefox; exit 0 ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
%end
|
||||
|
||||
# Remove the efi dir, must match efi_dir from the profile config
|
||||
%pre-install --erroronfail
|
||||
rm -rf /mnt/sysroot/boot/efi/EFI/fedora
|
||||
%end
|
||||
|
||||
# Relabel the boot partition for the
|
||||
%pre-install --erroronfail --log=/tmp/anacoda_custom_logs/repartitioning.log
|
||||
set -x
|
||||
xboot_dev=\$(findmnt -o SOURCE --nofsroot --noheadings -f --target /mnt/sysroot/boot)
|
||||
if [[ -z \$xboot_dev ]]; then
|
||||
echo "ERROR: xboot_dev not found"
|
||||
exit 1
|
||||
fi
|
||||
e2label "\$xboot_dev" "bazzite_xboot"
|
||||
%end
|
||||
|
||||
# Open a dialog with the installation logs
|
||||
%onerror
|
||||
run0 --user=liveuser yad \
|
||||
--timeout=0 \
|
||||
--text-info \
|
||||
--no-buttons \
|
||||
--width=600 \
|
||||
--height=400 \
|
||||
--text="An error occurred during installation. Please report this issue to the developers." \
|
||||
< /tmp/anaconda.log
|
||||
%end
|
||||
|
||||
$(
|
||||
if [[ $imageref == *-deck* ]]; then
|
||||
cat <<EOCAT
|
||||
# Set default user
|
||||
user --name=bazzite --password=bazzite --plaintext --groups=wheel
|
||||
EOCAT
|
||||
fi
|
||||
)
|
||||
|
||||
ostreecontainer --url=$imageref:$imagetag --transport=containers-storage --no-signature-verification
|
||||
%include /usr/share/anaconda/post-scripts/install-configure-upgrade.ks
|
||||
%include /usr/share/anaconda/post-scripts/disable-fedora-flatpak.ks
|
||||
%include /usr/share/anaconda/post-scripts/install-flatpaks.ks
|
||||
%include /usr/share/anaconda/post-scripts/secureboot-enroll-key.ks
|
||||
%include /usr/share/anaconda/post-scripts/secureboot-docs.ks
|
||||
|
||||
EOF
|
||||
|
||||
# Signed Images
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/install-configure-upgrade.ks
|
||||
%post --erroronfail --log=/tmp/anacoda_custom_logs/bootc-switch.log
|
||||
# bootc switch --mutate-in-place --enforce-container-sigpolicy --transport registry $imageref:$imagetag
|
||||
|
||||
# DELETEME: This is a nasty hack. Remove whenever http://github.com/bootc-dev/bootc/commit/f7b41cc1ebfc823e9de848b55773faddc59ecf88 makes it into a release
|
||||
sed -i 's|container-image-reference=.*|container-image-reference=ostree-image-signed:docker://$imageref:$imagetag|' /ostree/deploy/default/deploy/*.origin
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Enroll Secureboot Key
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/secureboot-enroll-key.ks
|
||||
%post --erroronfail --nochroot --log=/tmp/anacoda_custom_logs/secureboot-enroll-key.log
|
||||
set -oue pipefail
|
||||
|
||||
readonly ENROLLMENT_PASSWORD="universalblue"
|
||||
readonly SECUREBOOT_KEY="$SECUREBOOT_KEY"
|
||||
|
||||
if [[ ! -d "/sys/firmware/efi" ]]; then
|
||||
echo "EFI mode not detected. Skipping key enrollment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ ! -f "\$SECUREBOOT_KEY" ]]; then
|
||||
echo "Secure boot key not provided: \$SECUREBOOT_KEY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SYS_ID="\$(cat /sys/devices/virtual/dmi/id/product_name)"
|
||||
if [[ ":Jupiter:Galileo:" =~ ":\$SYS_ID:" ]]; then
|
||||
echo "Steam Deck hardware detected. Skipping key enrollment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mokutil --timeout -1 || :
|
||||
echo -e "\$ENROLLMENT_PASSWORD\n\$ENROLLMENT_PASSWORD" | mokutil --import "\$SECUREBOOT_KEY" || :
|
||||
%end
|
||||
EOF
|
||||
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/secureboot-docs.ks
|
||||
%post --nochroot --log=/tmp/anacoda_custom_logs/secureboot-docs.log
|
||||
SECUREBOOT_KEY="$SECUREBOOT_KEY"
|
||||
SECUREBOOT_DOC_URL="$SECUREBOOT_DOC_URL"
|
||||
SECUREBOOT_DOC_URL_QR="$SECUREBOOT_DOC_URL_QR"
|
||||
|
||||
LC_ALL=C mokutil -t "\$SECUREBOOT_KEY" | grep -q "is already in the enrollment request" && \
|
||||
run0 --user=liveuser yad --timeout=0 --on-top --button=Ok:0 --image="\$SECUREBOOT_DOC_URL_QR" --text="<b>Secure Boot Key added:</b>\nPlease check the documentation to finish enrolling the key\n\$SECUREBOOT_DOC_URL"
|
||||
%end
|
||||
EOF
|
||||
|
||||
qrencode -o "$SECUREBOOT_DOC_URL_QR" "$SECUREBOOT_DOC_URL"
|
||||
|
||||
# Install Flatpaks
|
||||
cat <<'EOF' >>/usr/share/anaconda/post-scripts/install-flatpaks.ks
|
||||
%post --erroronfail --nochroot --log=/tmp/anacoda_custom_logs/install-flatpaks.log
|
||||
deployment="$(ostree rev-parse --repo=/mnt/sysimage/ostree/repo ostree/0/1/0)"
|
||||
target="/mnt/sysimage/ostree/deploy/default/deploy/$deployment.0/var/lib/"
|
||||
mkdir -p "$target"
|
||||
rsync -aAXUHKP --filter='-x security.selinux' /var/lib/flatpak "$target"
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Disable Fedora Flatpak Repo
|
||||
cat <<EOF >>/usr/share/anaconda/post-scripts/disable-fedora-flatpak.ks
|
||||
%post --erroronfail --log=/tmp/anacoda_custom_logs/disable-fedora-flatpak.log
|
||||
systemctl disable flatpak-add-fedora-repos.service || :
|
||||
%end
|
||||
EOF
|
||||
|
||||
# Set Anaconda Payload to use flathub
|
||||
cat <<EOF >>/etc/anaconda/conf.d/anaconda.conf
|
||||
[Payload]
|
||||
flatpak_remote = flathub https://dl.flathub.org/repo/
|
||||
EOF
|
||||
|
||||
# TODO (@Zeglius): Hide grub by default and set timeout to 5 seconds
|
||||
# # Hide grub by default and set timeout to 5 seconds
|
||||
# mkdir -p /boot/grub2
|
||||
# cat >>/boot/grub2/grub.cfg <<'EOF'
|
||||
|
||||
# # Setup for liveisos
|
||||
# set menu_auto_hide=2
|
||||
# set timeout_style=hidden
|
||||
# set timeout=5
|
||||
# EOF
|
||||
|
||||
### Livecds runtime tweaks ###
|
||||
|
||||
# Disable services
|
||||
(
|
||||
set +e
|
||||
for s in \
|
||||
rpm-ostree-countme.service \
|
||||
tailscaled.service \
|
||||
bazzite-hardware-setup.service \
|
||||
bootloader-update.service \
|
||||
brew-upgrade.timer \
|
||||
brew-update.timer \
|
||||
brew-setup.service \
|
||||
rpm-ostreed-automatic.timer \
|
||||
uupd.timer \
|
||||
ublue-guest-user.service \
|
||||
ublue-os-media-automount.service \
|
||||
ublue-system-setup.service \
|
||||
check-sb-key.service; do
|
||||
systemctl disable $s
|
||||
done
|
||||
|
||||
for s in \
|
||||
ublue-flatpak-manager.service \
|
||||
podman-auto-update.timer \
|
||||
ublue-user-setup.service; do
|
||||
systemctl --global disable $s
|
||||
done
|
||||
)
|
||||
|
||||
# Add bootloader restoring script
|
||||
cat >/usr/bin/bootloader_restore.sh <<'SCRIPTEOF'
|
||||
#!/usr/bin/env -S /usr/bin/pkexec --keep-cwd /usr/bin/bash
|
||||
|
||||
set -o pipefail
|
||||
if [[ $DEBUG -eq 1 ]]; then
|
||||
set -x
|
||||
fi
|
||||
exec > >(tee -a /tmp/bootloader_restore.log) 2>&1
|
||||
echo >&2 "### START LOG $(date -u) ###"
|
||||
|
||||
######################################################
|
||||
|
||||
yad() {
|
||||
command run0 --user="$PKEXEC_UID" -- command yad \
|
||||
--title="$_APP_NAME" \
|
||||
--separator=$'\n' \
|
||||
--timeout=0 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
info() {
|
||||
echo >&2 "INFO [${0##*/}:${BASH_LINENO[0]}]: $*"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo >&2 "ERROR [${0##*/}:${BASH_LINENO[0]}]: $*"
|
||||
}
|
||||
|
||||
die() {
|
||||
error "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
die_gui() {
|
||||
yad --title="Error" --text="$(error "$*" 2>&1)" --button="OK:0"
|
||||
die "$*"
|
||||
}
|
||||
|
||||
######################################################
|
||||
|
||||
if [[ $PKEXEC_UID -eq 0 ]]; then
|
||||
die "You must not execute this script as root."
|
||||
fi
|
||||
|
||||
_APP_NAME="Bazzite Bootloader Restoring Tool"
|
||||
DRY_RUN=${DRY_RUN:-0}
|
||||
MNT=/tmp/mnt
|
||||
trap 'umount --recursive $MNT/boot 2>/dev/null' EXIT
|
||||
|
||||
DISK_PATH=$(lsblk -d -n -o NAME,SIZE,MODEL | while read -r name size model; do
|
||||
echo "$name"
|
||||
echo "$size"
|
||||
echo "$model"
|
||||
done | yad --list --no-buttons \
|
||||
--text="Double-click the disk where you installed Bazzite:" --width=500 --height=300 \
|
||||
--column="Device" \
|
||||
--column="Size" \
|
||||
--column="Model" \
|
||||
--print-column=1) || {
|
||||
info "User cancelled during disk selection"
|
||||
exit 0
|
||||
}
|
||||
: "${DISK_PATH:?}"
|
||||
DISK_PATH=/dev/${DISK_PATH}
|
||||
|
||||
efi_dev=$(systemd-repart --json=short "$DISK_PATH" 2>/dev/null |
|
||||
jq -r '.[] | select(.type == "esp").node')
|
||||
[[ -n ${efi_dev} ]] || { die_gui "EFI partition not found"; }
|
||||
xboot_dev=$(
|
||||
lsblk -J -p -f -o NAME,LABEL,SIZE,FSTYPE "$DISK_PATH" 2>/dev/null |
|
||||
jq -r '.blockdevices[0].children[] | select(.fstype == "ext4") | "\(.name)\n\(.label // "")\n\(.size)"' |
|
||||
yad --list --no-buttons \
|
||||
--text="Double-click the XBOOT partition:" --width=500 --height=300 \
|
||||
--column="Device" \
|
||||
--column="Label" \
|
||||
--column="Size" \
|
||||
--print-column=1
|
||||
) || {
|
||||
info "User cancelled during XBOOT partition selection"
|
||||
exit 0
|
||||
}
|
||||
[[ -n "${xboot_dev}" ]] || die_gui "You must select an XBOOT partition."
|
||||
|
||||
yad --text="This will restore the boot in the device $DISK_PATH, using $xboot_dev as the XBOOT partition. Proceed?" || {
|
||||
info "User cancelled during restoration confirmation"
|
||||
exit 0
|
||||
}
|
||||
|
||||
mount --mkdir "$xboot_dev" "$MNT"/boot || die "Failed to mount XBOOT partition"
|
||||
mount "$efi_dev" "$MNT"/boot/efi || die "Failed to mount EFI partition"
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
info "Script was executed with ${DRY_RUN@A}, skipping bootloader restoration..."
|
||||
yad --text="Script was executed with ${DRY_RUN@A}, skipping bootloader restoration...." --button="OK:0"
|
||||
else
|
||||
if [[ -f $MNT/boot/bootupd-state.json ]]; then
|
||||
rm -vf $MNT/boot/bootupd-state.json &&
|
||||
info "Removed existing bootupd-state.json"
|
||||
fi
|
||||
run0 --user="$PKEXEC_UID" -- \
|
||||
ptyxis --title="$_APP_NAME - Restoring bootloader" -- \
|
||||
pkexec bash -c "bootupctl backend install \
|
||||
-vvvv \
|
||||
--auto \
|
||||
--write-uuid \
|
||||
--update-firmware \
|
||||
--device \"$DISK_PATH\" \"$MNT\"" &&
|
||||
info "Bootloader restored successfully." &&
|
||||
yad --text "Bootloader restored successfully." --button="OK:0"
|
||||
fi
|
||||
|
||||
SCRIPTEOF
|
||||
chmod +x /usr/bin/bootloader_restore.sh
|
||||
cat >/usr/share/applications/bazzite_bootloader_restoring_tool.desktop <<'EOF'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Icon=tools-wizard-symbolic
|
||||
Name=Bazzite Bootloader Restoring Tool BETA
|
||||
Comment=Restore the bootloader of an installation if has been overriden by Windows
|
||||
Keywords=bootloader;fix;grub;windows
|
||||
Categories=System;Utility
|
||||
Exec=/usr/bin/bootloader_restore.sh
|
||||
Hidden=false
|
||||
NoDisplay=false
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
EOF
|
||||
|
||||
### Desktop-enviroment specific tweaks ###
|
||||
|
||||
# Setup script to show dialog popups at login
|
||||
echo '#!/usr/bin/bash' >/usr/bin/on_gui_login.sh
|
||||
chmod +x /usr/bin/on_gui_login.sh
|
||||
mkdir -p /etc/skel/.config/autostart
|
||||
cat >/etc/skel/.config/autostart/on_gui_login.desktop <<'EOF'
|
||||
[Desktop Entry]
|
||||
Exec=/usr/bin/on_gui_login.sh
|
||||
Icon=application-x-shellscript
|
||||
Type=Application
|
||||
EOF
|
||||
|
||||
# Warn the user about non functional Nvidia drivers
|
||||
if [[ $imageref == *-nvidia* ]]; then
|
||||
cat >>/usr/bin/on_gui_login.sh <<'EOF'
|
||||
{ yad --title="Warning" --text="$(</dev/stdin)" || true; } <<'WARNINGEOF'
|
||||
Nvidia drivers might not be functional on live isos.
|
||||
Please do not use them in benchmarks.
|
||||
WARNINGEOF
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Use GSK_RENDERER=gl for nvidia, workaround for GTK apps not opening.
|
||||
if [[ $imageref == *-nvidia* ]]; then
|
||||
mkdir -p /etc/environment.d /etc/skel/.config/environment.d
|
||||
echo "GSK_RENDERER=gl" >>/etc/environment.d/99-nvidia-fix.conf
|
||||
echo "GSK_RENDERER=gl" >>/etc/skel/.config/environment.d/99-nvidia-fix.conf
|
||||
fi
|
||||
|
||||
# Reenable noveau.
|
||||
if [[ $imageref == *-nvidia* ]]; then
|
||||
for pkg in nvidia-gpu-firmware mesa-vulkan-drivers; do
|
||||
dnf -yq reinstall --allowerasing $pkg ||
|
||||
dnf -yq install --allowerasing $pkg
|
||||
done
|
||||
# Ensure noveau vulkan icds exist
|
||||
(
|
||||
shopt -u nullglob
|
||||
ls /usr/share/vulkan/icd.d/nouveau_icd.*.json >/dev/null
|
||||
) || {
|
||||
echo >&2 "::error::No nouveau vulkan icds found at /usr/share/vulkan/icd.d/nouveau_icd.*.json"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Determine desktop environment. Must match one of /usr/libexec/livesys/sessions.d/livesys-{desktop_env}
|
||||
# See https://github.com/ublue-os/titanoboa/blob/6c2e8ba58c7534b502081fe24363d2a60e7edca9/Justfile#L199-L213
|
||||
desktop_env=""
|
||||
_session_file="$(find /usr/share/wayland-sessions/ /usr/share/xsessions \
|
||||
-maxdepth 1 -type f -not -name '*gamescope*.desktop' -and -name '*.desktop' -printf '%P' -quit)"
|
||||
case $_session_file in
|
||||
budgie*) desktop_env=budgie ;;
|
||||
cosmic*) desktop_env=cosmic ;;
|
||||
gnome*) desktop_env=gnome ;;
|
||||
plasma*) desktop_env=kde ;;
|
||||
sway*) desktop_env=sway ;;
|
||||
xfce*) desktop_env=xfce ;;
|
||||
esac
|
||||
|
||||
# Dont start Steam at login
|
||||
rm -vf /etc/skel/.config/autostart/steam*.desktop
|
||||
|
||||
# Remove packages that shouldnt be used in a live session
|
||||
dnf -yq remove steam lutris || :
|
||||
|
||||
# Warn about limited capabilities of live sessions, and also show buttons to:
|
||||
# - Install Bazzite
|
||||
# - Launch Bootloader Restoring tool
|
||||
# - Close dialog
|
||||
cat >>/usr/bin/on_gui_login.sh <<'EOF'
|
||||
_EXITLOCK=1
|
||||
_RETVAL=0
|
||||
while [[ $_EXITLOCK -eq 1 ]]; do
|
||||
yad \
|
||||
--no-escape \
|
||||
--on-top \
|
||||
--timeout-indicator=bottom \
|
||||
--text-align=center \
|
||||
--buttons-layout=center \
|
||||
--title="Welcome" \
|
||||
--text="\nWelcome to the Live ISO for Bazzite\!\n\nThe Live ISO is designed for installation and troubleshooting.\nBecause of this, it is <b>not capable of playing games.</b>\n\nPlease do not use it for benchmarks as it\ndoes not represent the installed experience.\n" \
|
||||
--button="Install Bazzite":10 \
|
||||
--button="Launch Bootloader Restoring tool":20 \
|
||||
--button="Close dialog":0
|
||||
_RETVAL=$?
|
||||
|
||||
case $_RETVAL in
|
||||
10)
|
||||
liveinst & disown $!
|
||||
_EXITLOCK=0
|
||||
;;
|
||||
20)
|
||||
/usr/bin/bootloader_restore.sh & disown $!
|
||||
_EXITLOCK=0
|
||||
;;
|
||||
0) _EXITLOCK=0 ;;
|
||||
esac
|
||||
done
|
||||
unset -v _EXITLOCK
|
||||
unset -v _RETVAL
|
||||
EOF
|
||||
|
||||
(
|
||||
wallpaper_url=https://github.com/ublue-os/bazzite/raw/refs/heads/main/press_kit/art/Convergence_Wallpaper_DX.jxl
|
||||
wallpaper_file=/usr/share/wallpapers/convergence.jxl
|
||||
wget -nv -O "$wallpaper_file" "$wallpaper_url"
|
||||
cp 2>/dev/null "$wallpaper_file" /usr/share/backgrounds/convergence.jxl || :
|
||||
cp 2>/dev/null "$wallpaper_file" /usr/share/backgrounds/convergence/convergence_morn.jxl || :
|
||||
rm -f /usr/share/backgrounds/default.xml
|
||||
)
|
||||
|
||||
# Enable on-screen keyboard
|
||||
if [[ $imageref == *-deck* ]]; then
|
||||
# Enable keyboard here
|
||||
if [[ $desktop_env == gnome ]]; then
|
||||
echo >>/etc/skel/.bash_profile \
|
||||
"gsettings set org.gnome.desktop.a11y.applications screen-keyboard-enabled true >/dev/null 2>&1 || :"
|
||||
rm -rf /usr/share/gnome-shell/extensions/block-caribou-36@lxylxy123456.ercli.dev
|
||||
elif [[ $desktop_env == kde ]]; then
|
||||
mv /usr/share/ublue-os/backup/com.github.maliit.keyboard.desktop \
|
||||
/usr/share/applications/com.github.maliit.keyboard.desktop || :
|
||||
fi
|
||||
fi
|
||||
|
||||
# Tweak the fedora-welcome app (gnome only) with our own text/icons
|
||||
if [[ $desktop_env == gnome ]]; then
|
||||
sed -i 's| Fedora| Bazzite|' /usr/share/anaconda/gnome/fedora-welcome || :
|
||||
cp -f /usr/share/pixmaps/{fedora-logo-sprite,fedora-logo-icon}.png || :
|
||||
fi
|
||||
|
||||
# Let only browser/installer in the task-bar/dock
|
||||
if [[ $desktop_env == kde ]]; then
|
||||
sed -i '/<entry name="launchers" type="StringList">/,/<\/entry>/ s/<default>[^<]*<\/default>/<default>preferred:\/\/browser,applications:liveinst.desktop,preferred:\/\/filemanager<\/default>/' \
|
||||
/usr/share/plasma/plasmoids/org.kde.plasma.taskmanager/contents/config/main.xml || :
|
||||
elif [[ $desktop_env == gnome ]]; then
|
||||
cat >/usr/share/glib-2.0/schemas/zz2-org.gnome.shell.gschema.override <<EOF
|
||||
[org.gnome.shell]
|
||||
welcome-dialog-last-shown-version='4294967295'
|
||||
favorite-apps = ['liveinst.desktop', 'org.mozilla.firefox.desktop', 'org.gnome.Nautilus.desktop']
|
||||
EOF
|
||||
glib-compile-schemas /usr/share/glib-2.0/schemas
|
||||
fi
|
||||
|
||||
# Disable kde wallet
|
||||
if [[ $desktop_env == kde ]]; then
|
||||
mkdir -p /etc/skel/.config
|
||||
cat >/etc/skel/.config/kwalletrc <<'EOF'
|
||||
[Wallet]
|
||||
Enabled=false
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Install Gparted
|
||||
dnf -yq install gparted
|
||||
|
||||
###############################
|
||||
31
examples/bazzite/src/titanoboa_hook_preinitramfs.sh
Executable file
31
examples/bazzite/src/titanoboa_hook_preinitramfs.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
set -exo pipefail
|
||||
|
||||
# Swap kernel with vanilla and rebuild initramfs.
|
||||
#
|
||||
# This is done because we want the initramfs to use a signed
|
||||
# kernel for secureboot.
|
||||
kernel_pkgs=(
|
||||
kernel
|
||||
kernel-core
|
||||
kernel-devel
|
||||
kernel-devel-matched
|
||||
kernel-modules
|
||||
kernel-modules-core
|
||||
kernel-modules-extra
|
||||
)
|
||||
dnf -y versionlock delete "${kernel_pkgs[@]}"
|
||||
dnf --setopt=protect_running_kernel=False -y remove "${kernel_pkgs[@]}"
|
||||
(cd /usr/lib/modules && rm -rf -- ./*)
|
||||
dnf -y --repo fedora,updates --setopt=tsflags=noscripts install kernel kernel-core
|
||||
kernel=$(find /usr/lib/modules -maxdepth 1 -type d -printf '%P\n' | grep .)
|
||||
depmod "$kernel"
|
||||
|
||||
imageref="$(podman images --format '{{ index .Names 0 }}\n' 'bazzite*' | head -1)"
|
||||
imageref="${imageref##*://}"
|
||||
imageref="${imageref%%:*}"
|
||||
|
||||
# Include nvidia-gpu-firmware package.
|
||||
dnf install -yq nvidia-gpu-firmware || :
|
||||
dnf clean all -yq
|
||||
7
examples/zirconium/Containerfile
Normal file
7
examples/zirconium/Containerfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# run with --cap-add sys_admin --security-opt label=disable --squash
|
||||
|
||||
FROM ghcr.io/zirconium-dev/zirconium:latest
|
||||
COPY ./src /src
|
||||
RUN --mount=type=cache,target=/var/tmp/libdnf5,id=libdnf5 \
|
||||
/src/build.sh
|
||||
RUN rm -fr /src
|
||||
85
examples/zirconium/src/build.sh
Executable file
85
examples/zirconium/src/build.sh
Executable file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/bash
|
||||
|
||||
set -exo pipefail
|
||||
|
||||
{ export PS4='+( ${BASH_SOURCE}:${LINENO} ): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'; } 2>/dev/null
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Create the directory that /root is symlinked to
|
||||
mkdir -p "$(realpath /root)"
|
||||
|
||||
# bwrap tries to write /proc/sys/user/max_user_namespaces which is mounted as ro
|
||||
# so we need to remount it as rw
|
||||
mount -o remount,rw /proc/sys
|
||||
|
||||
# Install flatpaks
|
||||
curl --retry 3 -Lo /etc/flatpak/remotes.d/flathub.flatpakrepo https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
xargs -r flatpak install -y --noninteractive <"$SCRIPT_DIR/flatpaks"
|
||||
|
||||
# Install dracut-live and regenerate the initramfs
|
||||
dnf install -y dracut-live
|
||||
kernel=$(kernel-install list --json pretty | jq -r '.[] | select(.has_kernel == true) | .version')
|
||||
DRACUT_NO_XATTR=1 dracut -v --force --zstd --reproducible --no-hostonly \
|
||||
--add "dmsquash-live dmsquash-live-autooverlay" \
|
||||
"/usr/lib/modules/${kernel}/initramfs.img" "${kernel}"
|
||||
|
||||
# Install livesys-scripts and configure them
|
||||
dnf install -y livesys-scripts
|
||||
# sed -i "s/^livesys_session=.*/livesys_session=kde/" /etc/sysconfig/livesys
|
||||
systemctl enable livesys.service livesys-late.service
|
||||
|
||||
# Run the postrootfs hook
|
||||
"$SCRIPT_DIR/titanoboa_hook_postrootfs.sh"
|
||||
|
||||
# image-builder needs gcdx64.efi
|
||||
_arch=$(uname -m)
|
||||
if [[ $_arch == "x86_64" ]]; then
|
||||
dnf install -y grub2-efi-x64-cdboot
|
||||
elif [[ $_arch == "aarch64" ]]; then
|
||||
dnf install -y grub2-efi-aa64-modules
|
||||
fi
|
||||
|
||||
# image-builder expects the EFI directory to be in /boot/efi
|
||||
mkdir -p /boot/efi
|
||||
cp -av /usr/lib/efi/*/*/EFI /boot/efi/
|
||||
|
||||
# Remove fallback efi
|
||||
_arch=$(uname -m)
|
||||
if [[ $_arch == "x86_64" ]]; then
|
||||
cp -v /boot/efi/EFI/fedora/grubx64.efi /boot/efi/EFI/BOOT/fbx64.efi
|
||||
elif [[ $_arch == "aarch64" ]]; then
|
||||
cp -v /boot/efi/EFI/fedora/grubaa64.efi /boot/efi/EFI/BOOT/fbaa64.efi
|
||||
fi
|
||||
|
||||
# Set the timezone to UTC
|
||||
rm -f /etc/localtime
|
||||
systemd-firstboot --timezone UTC
|
||||
|
||||
# / in a booted live ISO is an overlayfs with upperdir pointed somewhere under /run
|
||||
# This means that /var/tmp is also technically under /run.
|
||||
# /run is of course a tmpfs, but set with quite a small size.
|
||||
# ostree needs quite a lot of space on /var/tmp for temporary files so /run is not enough.
|
||||
# Mount a larger tmpfs to /var/tmp at boot time to avoid this issue.
|
||||
mkdir -p /var/tmp
|
||||
cat >/etc/systemd/system/var-tmp.mount <<'EOF'
|
||||
[Unit]
|
||||
Description=Larger tmpfs for /var/tmp on live system
|
||||
|
||||
[Mount]
|
||||
What=tmpfs
|
||||
Where=/var/tmp
|
||||
Type=tmpfs
|
||||
Options=size=50%%,nr_inodes=1m,x-systemd.graceful-option=usrquota
|
||||
|
||||
[Install]
|
||||
WantedBy=local-fs.target
|
||||
EOF
|
||||
systemctl enable var-tmp.mount
|
||||
|
||||
# Copy in the iso config for image-builder
|
||||
mkdir -p /usr/lib/bootc-image-builder
|
||||
cp /src/iso.yaml /usr/lib/bootc-image-builder/iso.yaml
|
||||
|
||||
# Clean up dnf cache to save space
|
||||
# dnf clean all
|
||||
0
examples/zirconium/src/flatpaks
Normal file
0
examples/zirconium/src/flatpaks
Normal file
10
examples/zirconium/src/iso.yaml
Normal file
10
examples/zirconium/src/iso.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
label: "Zirconium-Live"
|
||||
grub2:
|
||||
timeout: 10
|
||||
entries:
|
||||
- name: "Zirconium Live ISO"
|
||||
linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=Zirconium-Live enforcing=0 rd.live.image"
|
||||
initrd: "/images/pxeboot/initrd.img"
|
||||
- name: "Zirconium Live ISO (Safe Graphics Mode)"
|
||||
linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=Zirconium-Live enforcing=0 rd.live.image nomodeset"
|
||||
initrd: "/images/pxeboot/initrd.img"
|
||||
3
examples/zirconium/src/titanoboa_hook_postrootfs.sh
Executable file
3
examples/zirconium/src/titanoboa_hook_postrootfs.sh
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# TODO: Setup anaconda
|
||||
76
main.sh
Executable file
76
main.sh
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env -S bash -eo pipefail
|
||||
#
|
||||
# Project Titanoboa
|
||||
#
|
||||
# Description: Create bootable ISOs from bootc container images.
|
||||
|
||||
# Container-native ISO contract v0.1.0
|
||||
# See https://github.com/ondrejbudai/bootc-isos/tree/main
|
||||
#
|
||||
# This spec is inspired by the layout of Fedora bootc images and Fedora live ISO images.
|
||||
#
|
||||
# - The kernel is expected to be in /usr/lib/module/*/vmlinuz. If there are multiple kernels, the behavior is unspecified. This is to be specified in a future version of this contract. The kernel is put in /images/pxeboot/vmlinuz in the ISO.
|
||||
# - The initramfs is expected to be next to the kernel with the filename initramfs.img. The initramfs is put in /images/pxeboot/initrd.img.
|
||||
# - The UEFI vendor is specified by a directory name in /usr/lib/efi/shim/*/EFI/$VENDOR. If there are multiple directories, the behaviour is unspecified. The BOOT directory is always ignored.
|
||||
# - Shim and grub2 EFI binaries (shimx64.efi, mmx64.efi, gcdx64.efi) are expected to be in /boot/efi/EFI/$VENDOR.
|
||||
# - GRUB2 modules are expected to be in /usr/lib/grub/i386-pc.
|
||||
# - Required executables are podman, mksquashfs, xorriso, implantisomd5, grub2-mkimage, and python.
|
||||
# - The container image is converted to a squashfs archive and put into /LiveOS/squashfs.img in the ISO.
|
||||
# - Additional configuration can be written into /usr/lib/bootc-image-builder/iso.yaml in YAML format. The file currently supports 2 top-level keys:
|
||||
# - label (string): Label of the ISO
|
||||
# - grub2 (object): GRUB2 configuration, supports the following keys:
|
||||
# - default (string): Default menu item
|
||||
# - timeout (string): Default timeout (in seconds)
|
||||
# - entries (array of objects): GRUB2 menu entries with the following keys (all are required):
|
||||
# - name (string): Name of the entry
|
||||
# - linux (string): Path to the kernel + kernel arguments (the path is always /images/pxeboot/vmlinuz in this version of this spec)
|
||||
# - initrd (string): Path to the initramfs (the path is always /images/pxeboot/initrd.img in this version of this spec)
|
||||
# - The --bootc-installer-payload-ref argument to image-builder can optionally be used to copy a container image from the host's container storage to /var/lib/containers/storage in the squashfs archive.
|
||||
|
||||
{
|
||||
# Image to be injected on the iso as squashfs. Example: localhost/live_env:latest
|
||||
TITANOBOA_CTR_IMAGE=${1:-${TITANOBOA_CTR_IMAGE}}
|
||||
|
||||
# Directory where the ISO will be stored
|
||||
TITANOBOA_OUTPUT_DIR=${TITANOBOA_OUTPUT_DIR:-./output}
|
||||
|
||||
# Whenever Titanoboa is running inside a container
|
||||
TITANOBOA_INSIDE_CONTAINER=${TITANOBOA_INSIDE_CONTAINER:-false}
|
||||
|
||||
SCRIPT_DIR=$(dirname "$0")
|
||||
|
||||
mkdir -p "$TITANOBOA_OUTPUT_DIR"
|
||||
|
||||
# If we are running inside a container
|
||||
if [ "$TITANOBOA_INSIDE_CONTAINER" = "true" ]; then
|
||||
TITANOBOA_OUTPUT_DIR=/output
|
||||
if ! mountpoint -q "$TITANOBOA_OUTPUT_DIR"; then
|
||||
echo "Error: output directory must be a volume mountpoint"
|
||||
exit 1
|
||||
fi
|
||||
if ! mountpoint -q /usr/lib/containers/storage; then
|
||||
echo "Error: /usr/lib/containers/storage must be a volume mountpoint"
|
||||
exit 1
|
||||
fi
|
||||
if ! mountpoint -q /rootfs; then
|
||||
echo "Error: /rootfs must be a volume mountpoint"
|
||||
exit 1
|
||||
fi
|
||||
/app/bin/build_iso.sh
|
||||
else
|
||||
|
||||
if [ -z "$TITANOBOA_CTR_IMAGE" ]; then
|
||||
echo "Error: container image in param 1 nor TITANOBOA_CTR_IMAGE environment variable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo podman run --rm -i \
|
||||
--cap-add sys_admin --security-opt label=disable \
|
||||
-v "$SCRIPT_DIR"/build_iso.sh:/src/build_iso.sh:ro \
|
||||
--mount type=image,source="$TITANOBOA_CTR_IMAGE",dst=/rootfs \
|
||||
-v "$TITANOBOA_OUTPUT_DIR":/output \
|
||||
quay.io/fedora/fedora:latest /src/build_iso.sh
|
||||
fi
|
||||
} >&2
|
||||
|
||||
realpath "$TITANOBOA_OUTPUT_DIR"/*.iso | head -1
|
||||
1
src/.gitignore
vendored
1
src/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
grub.cfg
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# org.gnome.Platform
|
||||
org.gtk.Gtk3theme.adw-gtk3
|
||||
org.gtk.Gtk3theme.adw-gtk3-dark
|
||||
com.github.rafostar.Clapper
|
||||
io.gitlab.adhami3310.Impression
|
||||
com.github.tchx84.Flatseal
|
||||
io.github.flattool.Ignition
|
||||
io.github.flattool.Warehouse
|
||||
org.gnome.baobab
|
||||
org.gnome.Calculator
|
||||
org.gnome.Calendar
|
||||
org.gnome.Characters
|
||||
org.gnome.clocks
|
||||
org.gnome.Connections
|
||||
org.gnome.Contacts
|
||||
org.gnome.DejaDup
|
||||
org.gnome.Papers
|
||||
com.mattjakeman.ExtensionManager
|
||||
org.gnome.FileRoller
|
||||
org.gnome.font-viewer
|
||||
org.gnome.Logs
|
||||
org.gnome.Loupe
|
||||
org.gnome.Maps
|
||||
org.gnome.NautilusPreviewer
|
||||
org.gnome.World.PikaBackup
|
||||
org.gnome.TextEditor
|
||||
org.gnome.Weather
|
||||
io.missioncenter.MissionCenter
|
||||
org.mozilla.firefox
|
||||
# org.mozilla.Thunderbird
|
||||
page.tesk.Refine
|
||||
io.github.pwr_solaar.solaar
|
||||
# io.github.input_leap.input-leap
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
set default="0"
|
||||
|
||||
function load_video {
|
||||
insmod all_video
|
||||
}
|
||||
|
||||
load_video
|
||||
set gfxpayload=keep
|
||||
insmod gzio
|
||||
insmod part_gpt
|
||||
insmod chain
|
||||
|
||||
set timeout=10
|
||||
|
||||
search --no-floppy --set=root -l 'titanoboa_boot'
|
||||
|
||||
menuentry '@PRETTY_NAME@ Live ISO' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux ($root)/boot/vmlinuz quiet rhgb root=live:CDLABEL=titanoboa_boot enforcing=0 rd.live.image @EXTRA_KARGS@
|
||||
initrd ($root)/boot/initramfs.img
|
||||
}
|
||||
|
||||
menuentry '@PRETTY_NAME@ Live ISO (Basic Graphics Mode)' --class fedora --class gnu-linux --class gnu --class os {
|
||||
linux ($root)/boot/vmlinuz quiet rhgb root=live:CDLABEL=titanoboa_boot enforcing=0 rd.live.image nomodeset @EXTRA_KARGS@
|
||||
initrd ($root)/boot/initramfs.img
|
||||
}
|
||||
|
||||
# menuentry 'Try @PRETTY_NAME@ (RAM Only)' --class fedora --class gnu-linux --class gnu --class os {
|
||||
# linux ($root)/boot/vmlinuz quiet rhgb root=live:CDLABEL=titanoboa_boot enforcing=0 rd.live.ram rd.live.image @EXTRA_KARGS@
|
||||
# initrd ($root)/boot/initramfs.img
|
||||
# }
|
||||
# submenu 'Troubleshooting -->' {
|
||||
# menuentry 'Install @PRETTY_NAME@ in basic graphics mode' --class fedora --class gnu-linux --class gnu --class os {
|
||||
# linux ($root)/boot/vmlinuz quiet rhgb root=live:CDLABEL=titanoboa_boot enforcing=0 rd.live.image nomodeset
|
||||
# initrd ($root)/boot/initramfs.img
|
||||
# }
|
||||
# }
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
. /etc/os-release
|
||||
|
||||
usermod -c "$NAME User" liveuser
|
||||
hostnamectl hostname "$ID"
|
||||
|
||||
timedatectl set-timezone UTC
|
||||
ln -s /usr/share/zoneinfo/UTC /etc/localtime
|
||||
|
||||
if [ -d /var/lib/livesys/livesys-session-extra.d ]; then
|
||||
find /var/lib/livesys/livesys-session-extra.d -type f -exec /bin/bash {} \;
|
||||
fi
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
polkit.addRule(function(action,subject) {
|
||||
if ( (action.id == "org.freedesktop.policykit.exec") &&
|
||||
(action.lookup("program") == "/usr/bin/bootc") &&
|
||||
(subject.isInGroup("wheel") ) ) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
|
||||
return polkit.Result.NOT_HANDLED;
|
||||
}
|
||||
);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
polkit.addRule(function(action,subject) {
|
||||
if ( (action.id == "org.freedesktop.policykit.exec") &&
|
||||
(action.lookup("program") == "/usr/sbin/liveinst") &&
|
||||
(subject.isInGroup("wheel") ) ) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
|
||||
return polkit.Result.NOT_HANDLED;
|
||||
}
|
||||
);
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
polkit.addRule(function(action,subject) {
|
||||
if ( (action.id == "org.freedesktop.policykit.exec") &&
|
||||
(action.lookup("program") == "/usr/bin/podman") &&
|
||||
(subject.isInGroup("wheel") ) ) {
|
||||
return polkit.Result.YES;
|
||||
}
|
||||
|
||||
return polkit.Result.NOT_HANDLED;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Loading…
Reference in a new issue