diff options
| -rw-r--r-- | .github/workflows/build-disk.yml | 7 | ||||
| -rw-r--r-- | .github/workflows/build-live-installer.yml | 116 | ||||
| -rw-r--r-- | Justfile | 16 | ||||
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | disk_config/iso-gnome.toml | 2 | ||||
| -rw-r--r-- | disk_config/iso-kde.toml | 2 | ||||
| -rw-r--r-- | installer/Containerfile | 15 | ||||
| -rwxr-xr-x | installer/build.sh | 60 | ||||
| -rw-r--r-- | installer/iso.yaml | 10 | ||||
| -rw-r--r-- | installer/lorax_templates/remove_root_password_prompt.tmpl | 1 | ||||
| -rw-r--r-- | installer/lorax_templates/set_default_user.tmpl | 1 | ||||
| -rwxr-xr-x | just_scripts/build-live-installer.sh | 58 | ||||
| -rwxr-xr-x | just_scripts/run-live-installer.sh | 23 |
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 @@ -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')] @@ -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 |
