summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortranch <tranch.xiao@gmail.com>2026-07-12 23:33:44 +0800
committertranch <tranch.xiao@gmail.com>2026-07-12 23:33:44 +0800
commit42a6ff3286804854c1c21a7e62158f09e19a9b09 (patch)
tree1276369ae98f138054878e04dac49b8a9c3ca4d1
parent7280cc006eefe14a71ee19a66bc8b2c6d55c4887 (diff)
downloadAeroCore-OS-42a6ff3286804854c1c21a7e62158f09e19a9b09.tar.gz
AeroCore-OS-42a6ff3286804854c1c21a7e62158f09e19a9b09.zip
Add live installer build workflow
Add a Bazzite-style installer payload, CI workflow, and local\nPodman commands for building and testing the live ISO.\n\nFix stale disk image configuration references and installer targets.
-rw-r--r--.github/workflows/build-disk.yml7
-rw-r--r--.github/workflows/build-live-installer.yml116
-rw-r--r--Justfile16
-rw-r--r--README.md15
-rw-r--r--disk_config/iso-gnome.toml2
-rw-r--r--disk_config/iso-kde.toml2
-rw-r--r--installer/Containerfile15
-rwxr-xr-xinstaller/build.sh60
-rw-r--r--installer/iso.yaml10
-rw-r--r--installer/lorax_templates/remove_root_password_prompt.tmpl1
-rw-r--r--installer/lorax_templates/set_default_user.tmpl1
-rwxr-xr-xjust_scripts/build-live-installer.sh58
-rwxr-xr-xjust_scripts/run-live-installer.sh23
13 files changed, 317 insertions, 9 deletions
diff --git a/.github/workflows/build-disk.yml b/.github/workflows/build-disk.yml
index 1de6c6e..02ef44a 100644
--- a/.github/workflows/build-disk.yml
+++ b/.github/workflows/build-disk.yml
@@ -20,7 +20,8 @@ on:
- main
paths:
- './disk_config/disk.toml'
- - './disk_config/iso.toml'
+ - './disk_config/iso-kde.toml'
+ - './disk_config/iso-gnome.toml'
- './.github/workflows/build-disk.yml'
env:
@@ -36,7 +37,7 @@ concurrency:
jobs:
build:
name: Build disk images
- runs-on: ${{ inputs.platform == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }}
+ runs-on: ${{ inputs.platform == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
strategy:
fail-fast: false
matrix:
@@ -82,7 +83,7 @@ jobs:
uses: osbuild/bootc-image-builder-action@30960621b85c9e76c653082f9ef4cab5a10210d5 # main
with:
builder-image: ${{ env.BIB_IMAGE }}
- config-file: ${{ matrix.disk-type == 'anaconda-iso' && './disk_config/iso.toml' || './disk_config/disk.toml' }}
+ config-file: ${{ matrix.disk-type == 'anaconda-iso' && './disk_config/iso-kde.toml' || './disk_config/disk.toml' }}
image: ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}
chown: ${{ env.USER_UID }}:${{ env.USER_GID }}
types: ${{ matrix.disk-type }}
diff --git a/.github/workflows/build-live-installer.yml b/.github/workflows/build-live-installer.yml
new file mode 100644
index 0000000..870699e
--- /dev/null
+++ b/.github/workflows/build-live-installer.yml
@@ -0,0 +1,116 @@
+name: Build live installer
+
+on:
+ workflow_dispatch:
+ inputs:
+ image_tag:
+ description: "AeroCore image tag to install"
+ required: false
+ default: latest
+ type: string
+ workflow_call:
+ push:
+ branches:
+ - main
+ paths:
+ - "installer/**"
+ - "just_scripts/build-live-installer.sh"
+ - ".github/workflows/build-live-installer.yml"
+
+permissions: {}
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ build-live-installer:
+ name: Build KDE live installer
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ packages: read
+
+ env:
+ IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }}
+ IMAGE_TAG: ${{ inputs.image_tag || 'latest' }}
+ BASE_IMAGE: ghcr.io/ublue-os/bazzite:stable
+ FEDORA_VERSION: "44"
+
+ steps:
+ - name: Maximize build space
+ uses: ublue-os/remove-unwanted-software@cc0becac701cf642c8f0a6613bbdaf5dc36b259e # v9
+ with:
+ remove-codeql: true
+
+ - name: Mount container storage BTRFS loopback
+ run: |
+ sudo truncate -s 70G /mnt/podman-storage.img
+ sudo mkfs.btrfs -f /mnt/podman-storage.img
+ sudo podman system reset --force
+ sudo systemctl stop podman.service
+ sudo mount -o compress-force=zstd:2 /mnt/podman-storage.img /var/lib/containers/storage
+ sudo systemctl start podman.service
+
+ - name: Checkout
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
+
+ - name: Install just
+ uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
+
+ - name: Prepare image references
+ run: |
+ image_name="$(just image_name)"
+ image_registry="${IMAGE_REGISTRY,,}"
+ echo "IMAGE_NAME=${image_name,,}" >> "$GITHUB_ENV"
+ echo "IMAGE_REGISTRY=${image_registry}" >> "$GITHUB_ENV"
+
+ - name: Login to GHCR
+ run: |
+ echo '${{ secrets.GITHUB_TOKEN }}' | sudo podman login ghcr.io \
+ --username '${{ github.actor }}' --password-stdin
+
+ - name: Pull AeroCore install image
+ run: |
+ sudo podman pull "${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}"
+
+ - name: Build live installer payload
+ run: |
+ sudo podman build \
+ --cap-add sys_admin \
+ --security-opt label=disable \
+ --build-arg "BASE_IMAGE=${BASE_IMAGE}" \
+ --build-arg "INSTALL_IMAGE_PAYLOAD=${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \
+ --tag localhost/aerocore-live-payload:latest \
+ --file installer/Containerfile \
+ .
+
+ - name: Build ISO
+ id: build
+ uses: Zeglius/titanoboa@revamp-pr
+ with:
+ arch: x86_64
+ image_name: aerocore-live-payload
+ image_repo: localhost
+ variant: Kinoite
+ version: ${{ env.FEDORA_VERSION }}
+ image_tag: latest
+ iso_name: aerocore-os-${{ env.IMAGE_TAG }}-amd64.iso
+ enable_cache_dnf: "false"
+ enable_cache_skopeo: "false"
+ enable_flatpak_dependencies: "false"
+ additional_templates: /github/workspace/installer/lorax_templates/remove_root_password_prompt.tmpl /github/workspace/installer/lorax_templates/set_default_user.tmpl
+
+ - name: Display checksum
+ run: |
+ cat "${{ steps.build.outputs.iso_path }}/${{ steps.build.outputs.iso_name }}-CHECKSUM"
+
+ - name: Upload ISO artifact
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
+ with:
+ name: aerocore-os-live-installer-${{ env.IMAGE_TAG }}
+ path: |
+ ${{ steps.build.outputs.iso_path }}/${{ steps.build.outputs.iso_name }}
+ ${{ steps.build.outputs.iso_path }}/${{ steps.build.outputs.iso_name }}-CHECKSUM
+ if-no-files-found: error
+ compression-level: 0
diff --git a/Justfile b/Justfile
index e83466d..9ec4dd1 100644
--- a/Justfile
+++ b/Justfile
@@ -16,11 +16,21 @@ export HOMEBREW_API_DOMAIN := env_var_or_default("HOMEBREW_API_DOMAIN", "")
alias build-vm := build-qcow2
alias rebuild-vm := rebuild-qcow2
alias run-vm := run-vm-qcow2
+alias build-live-iso := build-live-installer
+alias run-live-iso := run-live-installer
[private]
default:
@just --list
+# Build a Bazzite-style live installer ISO
+build-live-installer:
+ @./just_scripts/build-live-installer.sh
+
+# Boot the live installer ISO with QEMU
+run-live-installer:
+ @./just_scripts/run-live-installer.sh
+
# Check Just Syntax
[group('Just')]
check:
@@ -348,7 +358,7 @@ build-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_build
# Build an ISO virtual machine image
[group('Build Virtal Machine Image')]
-build-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "iso" "disk_config/iso.toml")
+build-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "iso" "disk_config/iso-kde.toml")
# Rebuild a QCOW2 virtual machine image
[group('Build Virtal Machine Image')]
@@ -360,7 +370,7 @@ rebuild-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_reb
# Rebuild an ISO virtual machine image
[group('Build Virtal Machine Image')]
-rebuild-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "iso" "disk_config/iso.toml")
+rebuild-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_rebuild-bib target_image tag "iso" "disk_config/iso-kde.toml")
# Run a virtual machine with the specified image type and configuration
_run-vm $target_image $tag $type $config:
@@ -414,7 +424,7 @@ run-vm-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-
# Run a virtual machine from an ISO
[group('Run Virtal Machine')]
-run-vm-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "iso" "disk_config/iso.toml")
+run-vm-iso $target_image=("localhost/" + image_name) $tag=default_tag: && (_run-vm target_image tag "iso" "disk_config/iso-kde.toml")
# Run a virtual machine using systemd-vmspawn
[group('Run Virtal Machine')]
diff --git a/README.md b/README.md
index fdd7c76..0fc3ba4 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ By focusing strictly on file overlays and configuration writing, this build inte
To build AeroCore OS locally, edit `image-template.env` to match your target configurations and then run:
```bash
-just build
+sudo just build
```
@@ -35,3 +35,16 @@ To test a different upstream base image directly without modifying the environme
BASE_IMAGE=ghcr.io/ublue-os/bazzite-deck:testing just build
```
+
+To build the Bazzite-style live installer locally, first build the AeroCore
+image and then run:
+
+```bash
+sudo just build
+just build-live-installer
+just run-live-installer
+```
+
+This requires privileged Podman, a rootful Podman API socket, and roughly
+70 GiB of free space. The live installer workflow consumes the published
+`latest` AeroCore image and uploads the ISO as a GitHub Actions artifact.
diff --git a/disk_config/iso-gnome.toml b/disk_config/iso-gnome.toml
index 0a9c84f..0fa5652 100644
--- a/disk_config/iso-gnome.toml
+++ b/disk_config/iso-gnome.toml
@@ -1,7 +1,7 @@
[customizations.installer.kickstart]
contents = """
%post
-bootc switch --mutate-in-place --transport registry ghcr.io/ublue-os/image-template:latest
+bootc switch --mutate-in-place --transport registry ghcr.io/aerocore-io/aerocore-bazzite-image:latest
%end
"""
diff --git a/disk_config/iso-kde.toml b/disk_config/iso-kde.toml
index 17d2787..e6e15a0 100644
--- a/disk_config/iso-kde.toml
+++ b/disk_config/iso-kde.toml
@@ -1,7 +1,7 @@
[customizations.installer.kickstart]
contents = """
%post
-bootc switch --mutate-in-place --transport registry ghcr.io/ublue-os/image-template:latest
+bootc switch --mutate-in-place --transport registry ghcr.io/aerocore-io/aerocore-bazzite-image:latest
%end
"""
diff --git a/installer/Containerfile b/installer/Containerfile
new file mode 100644
index 0000000..fa817b6
--- /dev/null
+++ b/installer/Containerfile
@@ -0,0 +1,15 @@
+# Build the live installer payload from a desktop Bazzite runtime.
+# The image installed to disk is supplied separately as INSTALL_IMAGE_PAYLOAD.
+
+ARG BASE_IMAGE=ghcr.io/ublue-os/bazzite:stable
+ARG INSTALL_IMAGE_PAYLOAD=ghcr.io/AeroCore-IO/aerocore-bazzite-image:stable
+
+FROM ${BASE_IMAGE}
+
+ARG BASE_IMAGE
+ARG INSTALL_IMAGE_PAYLOAD
+
+RUN --mount=type=bind,source=.,target=/src \
+ env BASE_IMAGE="${BASE_IMAGE}" \
+ INSTALL_IMAGE_PAYLOAD="${INSTALL_IMAGE_PAYLOAD}" \
+ /src/installer/build.sh
diff --git a/installer/build.sh b/installer/build.sh
new file mode 100755
index 0000000..7532df8
--- /dev/null
+++ b/installer/build.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+
+set -euxo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+BASE_IMAGE=${BASE_IMAGE:?}
+INSTALL_IMAGE_PAYLOAD=${INSTALL_IMAGE_PAYLOAD:?}
+
+# The installer is a live KDE environment. Keep the installed system payload
+# separate: it is the AeroCore image, while BASE_IMAGE provides the live UI.
+dnf install -y \
+ anaconda-live \
+ libblockdev-btrfs \
+ libblockdev-dm \
+ libblockdev-lvm \
+ dracut-live \
+ livesys-scripts \
+ grub2-efi-x64-cdboot
+
+# Apply the same AeroCore overlay to the live session where possible.
+if [[ -d /src/system_files ]]; then
+ cp -a /src/system_files/. /
+fi
+
+# Make the install payload available to Anaconda. When the build is running
+# with the host container store mounted, this avoids a second registry pull.
+if mountpoint -q /usr/lib/containers/storage; then
+ podman save --format oci-archive "${INSTALL_IMAGE_PAYLOAD}" | \
+ podman load --storage-opt additionalimagestore=''
+else
+ podman pull "${INSTALL_IMAGE_PAYLOAD}"
+fi
+
+# Tell Anaconda to install the bootc container that was loaded above.
+mkdir -p /var/lib/rpm-state
+cat >> /usr/share/anaconda/interactive-defaults.ks <<EOF
+ostreecontainer --url=${INSTALL_IMAGE_PAYLOAD} --transport=containers-storage --no-signature-verification
+EOF
+
+mkdir -p /usr/lib/bootc-image-builder
+cp "${SCRIPT_DIR}/iso.yaml" /usr/lib/bootc-image-builder/iso.yaml
+
+kernel="$(kernel-install list --json pretty | jq -r \
+ '.[] | select(.has_kernel == true) | .version' | head -n1)"
+if [[ -n "${kernel}" && -d "/usr/lib/modules/${kernel}" ]]; then
+ DRACUT_NO_XATTR=1 dracut -v --force --zstd --reproducible --no-hostonly \
+ --add "dmsquash-live dmsquash-live-autooverlay" \
+ "/usr/lib/modules/${kernel}/initramfs.img" "${kernel}"
+fi
+
+sed -i 's/^livesys_session=.*/livesys_session=kde/' /etc/sysconfig/livesys
+systemctl enable livesys.service livesys-late.service
+
+mkdir -p /boot/efi
+if compgen -G '/usr/lib/efi/*/*/EFI' >/dev/null; then
+ cp -a /usr/lib/efi/*/*/EFI /boot/efi/
+fi
+
+systemd-firstboot --timezone UTC
+dnf clean all
diff --git a/installer/iso.yaml b/installer/iso.yaml
new file mode 100644
index 0000000..8e6bd73
--- /dev/null
+++ b/installer/iso.yaml
@@ -0,0 +1,10 @@
+label: "AeroCore-Live"
+grub2:
+ timeout: 3
+ entries:
+ - name: "Launch AeroCore Installer"
+ linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=AeroCore-Live enforcing=0 rd.live.image"
+ initrd: "/images/pxeboot/initrd.img"
+ - name: "Launch AeroCore Installer (Basic Graphics Mode)"
+ linux: "/images/pxeboot/vmlinuz quiet rhgb root=live:CDLABEL=AeroCore-Live enforcing=0 rd.live.image nomodeset"
+ initrd: "/images/pxeboot/initrd.img"
diff --git a/installer/lorax_templates/remove_root_password_prompt.tmpl b/installer/lorax_templates/remove_root_password_prompt.tmpl
new file mode 100644
index 0000000..3bd0342
--- /dev/null
+++ b/installer/lorax_templates/remove_root_password_prompt.tmpl
@@ -0,0 +1 @@
+append etc/anaconda/profile.d/fedora-kinoite.conf "\\n[User Interface]\\nhidden_spokes =\\n PasswordSpoke"
diff --git a/installer/lorax_templates/set_default_user.tmpl b/installer/lorax_templates/set_default_user.tmpl
new file mode 100644
index 0000000..b4cf2c8
--- /dev/null
+++ b/installer/lorax_templates/set_default_user.tmpl
@@ -0,0 +1 @@
+append usr/share/anaconda/interactive-defaults.ks "\\nuser --name=aerocore --password=aerocore --plaintext --groups=wheel"
diff --git a/just_scripts/build-live-installer.sh b/just_scripts/build-live-installer.sh
new file mode 100755
index 0000000..16b371e
--- /dev/null
+++ b/just_scripts/build-live-installer.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+project_root="$(git rev-parse --show-toplevel)"
+cd "${project_root}"
+
+image_name="${IMAGE_NAME:-$(just image_name)}"
+image_tag="${IMAGE_TAG:-$(just generate-default-tag)}"
+base_image="${BASE_IMAGE:-ghcr.io/ublue-os/bazzite:stable}"
+payload="localhost/${image_name}:live-payload"
+output_dir="${project_root}/output"
+api_socket="${PODMAN_SOCKET:-/run/podman/podman.sock}"
+if [[ "${EUID}" -eq 0 ]]; then
+ podman_cmd=(podman)
+else
+ podman_cmd=(sudo podman)
+fi
+
+if [[ ! -S "${api_socket}" ]]; then
+ echo "Podman API socket not found: ${api_socket}" >&2
+ echo "Set PODMAN_SOCKET to the rootful Podman socket and retry." >&2
+ exit 1
+fi
+
+mkdir -p "${output_dir}"
+"${podman_cmd[@]}" tag "${image_name}:${image_tag}" "${payload}"
+
+"${podman_cmd[@]}" build \
+ --cap-add sys_admin \
+ --security-opt label=disable \
+ --build-arg "BASE_IMAGE=${base_image}" \
+ --build-arg "INSTALL_IMAGE_PAYLOAD=${payload}" \
+ --tag localhost/aerocore-live-payload:latest \
+ --file installer/Containerfile \
+ .
+
+"${podman_cmd[@]}" run --rm --privileged \
+ --volume "${api_socket}:/var/run/docker.sock" \
+ --volume "${project_root}/output:/build-container-installer/build" \
+ --volume "${project_root}/installer/lorax_templates:/additional_lorax_templates:ro" \
+ docker.io/jasonn3/build-container-installer:latest \
+ ADDITIONAL_TEMPLATES="/additional_lorax_templates/remove_root_password_prompt.tmpl /additional_lorax_templates/set_default_user.tmpl" \
+ ARCH="x86_64" \
+ ENABLE_CACHE_DNF="false" \
+ ENABLE_CACHE_SKOPEO="false" \
+ ENABLE_FLATPAK_DEPENDENCIES="false" \
+ IMAGE_NAME="aerocore-live-payload" \
+ IMAGE_REPO="localhost" \
+ IMAGE_TAG="latest" \
+ ISO_NAME="build/aerocore-os-${image_tag}-amd64.iso" \
+ VARIANT="Kinoite" \
+ VERSION="${FEDORA_VERSION:-44}"
+
+iso="${output_dir}/aerocore-os-${image_tag}-amd64.iso"
+if [[ -f "${iso}" ]]; then
+ sha256sum "${iso}" | tee "${iso}-CHECKSUM"
+fi
diff --git a/just_scripts/run-live-installer.sh b/just_scripts/run-live-installer.sh
new file mode 100755
index 0000000..2723aae
--- /dev/null
+++ b/just_scripts/run-live-installer.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+project_root="$(git rev-parse --show-toplevel)"
+cd "${project_root}"
+
+image_tag="${IMAGE_TAG:-$(just generate-default-tag)}"
+iso="${project_root}/output/aerocore-os-${image_tag}-amd64.iso"
+
+if [[ ! -f "${iso}" ]]; then
+ just build-live-installer
+fi
+
+podman run --rm --cap-add NET_ADMIN \
+ --publish 127.0.0.1:8006:8006 \
+ --env CPU_CORES=2 \
+ --env RAM_SIZE=4G \
+ --env DISK_SIZE=64G \
+ --env BOOT_MODE=uefi \
+ --device /dev/kvm \
+ --volume "${iso}:/boot.iso:ro" \
+ docker.io/qemux/qemu-docker