From 01f191a5e2e3ca5ac6884b09f6ffcbfd5094f555 Mon Sep 17 00:00:00 2001 From: tranch Date: Mon, 28 Sep 2026 11:14:04 +0800 Subject: Align Live and installer ISO commands Use build-live-iso for the Titanoboa Live ISO workflow and build-installer-iso for bootc-image-builder installer ISO images. Rename the associated workflow and build script, and update local run instructions accordingly. --- .github/workflows/build-installer-iso.yml | 167 ---------------------------- .github/workflows/build-live-iso.yml | 167 ++++++++++++++++++++++++++++ Justfile | 11 +- README.md | 2 +- just_scripts/build-installer-iso.sh | 174 ------------------------------ just_scripts/build-live-iso.sh | 174 ++++++++++++++++++++++++++++++ just_scripts/run-installer-iso.sh | 2 +- 7 files changed, 348 insertions(+), 349 deletions(-) delete mode 100644 .github/workflows/build-installer-iso.yml create mode 100644 .github/workflows/build-live-iso.yml delete mode 100755 just_scripts/build-installer-iso.sh create mode 100755 just_scripts/build-live-iso.sh diff --git a/.github/workflows/build-installer-iso.yml b/.github/workflows/build-installer-iso.yml deleted file mode 100644 index da3ef10..0000000 --- a/.github/workflows/build-installer-iso.yml +++ /dev/null @@ -1,167 +0,0 @@ -name: Build Live ISOs - -on: - workflow_dispatch: - inputs: - image_tag: - description: "AeroCore image tag to install" - required: false - default: latest - type: string - workflow_call: - -permissions: {} - -concurrency: - group: ${{ github.workflow }}-${{ github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - build-installer-iso: - name: Build KDE live ISO - runs-on: ubuntu-24.04 - permissions: - contents: read - packages: read - - env: - IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }} - IMAGE_NAME: ${{ vars.IMAGE_NAME || github.event.repository.name }} - IMAGE_TAG: ${{ inputs.image_tag || 'latest' }} - ISO_FILE_TAG: ${{ github.ref_name == 'testing' && 'testing' || (inputs.image_tag || 'latest') }} - # Keep these aligned with the Fedora major used by the target bazzite-deck base image. - LIVE_BASE_IMAGE: quay.io/fedora/fedora-kinoite:44 - FEDORA_VERSION: "44" - OSTREE_IMAGE_REF: ${{ vars.OSTREE_IMAGE_REF }} - - 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: Prepare image references - run: | - image_name="${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 installer ISO payload - run: | - mkdir -p output - ostree_image_ref="${OSTREE_IMAGE_REF:-ostree-image-signed:docker://${IMAGE_REGISTRY}/${IMAGE_NAME}}" - install_image_ref="${ostree_image_ref#ostree-image-signed:docker://}" - install_image_ref="${install_image_ref#ostree-unverified-registry:docker://}" - install_image_ref="${install_image_ref#docker://}" - sudo podman tag \ - "${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \ - "${install_image_ref}:${IMAGE_TAG}" - sudo podman save \ - --format oci-archive \ - --output output/install-payload.oci \ - "${install_image_ref}:${IMAGE_TAG}" - - sudo podman build \ - --cap-add sys_admin \ - --security-opt label=disable \ - --build-arg "BASE_IMAGE=${LIVE_BASE_IMAGE}" \ - --build-arg "INSTALL_IMAGE_PAYLOAD=${install_image_ref}:${IMAGE_TAG}" \ - --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ - --build-arg "INSTALL_IMAGE_TAG=${IMAGE_TAG}" \ - --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ - --tag localhost/aerocore-os-installer:latest \ - --file installer/Containerfile \ - . - - - name: Build ISO - id: build - uses: Zeglius/titanoboa@7737f4748458252ac827dca14b3d6dd09298472a # revamp-pr - with: - image-ref: localhost/aerocore-os-installer:latest - iso-dest: ${{ github.workspace }}/output/aerocore-os-${{ env.ISO_FILE_TAG }}-live-amd64.iso - - - name: Normalize ISO output path - shell: bash - env: - GENERATED_ISO: ${{ steps.build.outputs.iso-dest }} - EXPECTED_ISO: ${{ github.workspace }}/output/aerocore-os-${{ env.ISO_FILE_TAG }}-live-amd64.iso - run: | - if [[ ! -f "${GENERATED_ISO}" ]]; then - echo "::error::Generated ISO does not exist at ${GENERATED_ISO}" - exit 1 - fi - if [[ "${GENERATED_ISO}" != "${EXPECTED_ISO}" ]]; then - mkdir -p "$(dirname "${EXPECTED_ISO}")" - mv "${GENERATED_ISO}" "${EXPECTED_ISO}" - fi - - - name: Display checksum - shell: bash - run: | - set -o pipefail - iso_path="output/aerocore-os-${ISO_FILE_TAG}-live-amd64.iso" - checksum_path="${iso_path}-CHECKSUM" - sha256sum "${iso_path}" | tee "${checksum_path}" - { - echo "# ISO Checksum (SHA256)" - echo - echo "ISO: $(basename "${iso_path}")" - echo - echo '```' - cat "${checksum_path}" - echo '```' - } >> "$GITHUB_STEP_SUMMARY" - - - name: Move ISO to upload directory - id: upload-directory - shell: bash - run: | - iso_upload_dir="${{ github.workspace }}/output/upload" - iso_path="${{ github.workspace }}/output/aerocore-os-${ISO_FILE_TAG}-live-amd64.iso" - iso_name="$(basename "${iso_path}")" - - mkdir -p "${iso_upload_dir}" - mv "${iso_path}" "${iso_upload_dir}/" - ( - cd "${iso_upload_dir}" - sha256sum "${iso_name}" | tee "${iso_name}-CHECKSUM" - ) - echo "iso-upload-dir=${iso_upload_dir}" >> "$GITHUB_OUTPUT" - - - name: Upload ISO and checksum to R2 - if: github.event_name == 'workflow_dispatch' && (github.ref_name == 'main' || github.ref_name == 'testing') - shell: bash - env: - RCLONE_CONFIG_R2_TYPE: s3 - RCLONE_CONFIG_R2_PROVIDER: Cloudflare - RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} - RCLONE_CONFIG_R2_REGION: auto - RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} - SOURCE_DIR: ${{ steps.upload-directory.outputs.iso-upload-dir }} - run: | - sudo apt-get update - sudo apt-get install -y rclone - rclone copy "$SOURCE_DIR" R2:bazzite diff --git a/.github/workflows/build-live-iso.yml b/.github/workflows/build-live-iso.yml new file mode 100644 index 0000000..a364f6c --- /dev/null +++ b/.github/workflows/build-live-iso.yml @@ -0,0 +1,167 @@ +name: Build Live ISOs + +on: + workflow_dispatch: + inputs: + image_tag: + description: "AeroCore image tag to install" + required: false + default: latest + type: string + workflow_call: + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} + cancel-in-progress: true + +jobs: + build-live-iso: + name: Build KDE live ISO + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: read + + env: + IMAGE_REGISTRY: ghcr.io/${{ github.repository_owner }} + IMAGE_NAME: ${{ vars.IMAGE_NAME || github.event.repository.name }} + IMAGE_TAG: ${{ inputs.image_tag || 'latest' }} + ISO_FILE_TAG: ${{ github.ref_name == 'testing' && 'testing' || (inputs.image_tag || 'latest') }} + # Keep these aligned with the Fedora major used by the target bazzite-deck base image. + LIVE_BASE_IMAGE: quay.io/fedora/fedora-kinoite:44 + FEDORA_VERSION: "44" + OSTREE_IMAGE_REF: ${{ vars.OSTREE_IMAGE_REF }} + + 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: Prepare image references + run: | + image_name="${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 installer ISO payload + run: | + mkdir -p output + ostree_image_ref="${OSTREE_IMAGE_REF:-ostree-image-signed:docker://${IMAGE_REGISTRY}/${IMAGE_NAME}}" + install_image_ref="${ostree_image_ref#ostree-image-signed:docker://}" + install_image_ref="${install_image_ref#ostree-unverified-registry:docker://}" + install_image_ref="${install_image_ref#docker://}" + sudo podman tag \ + "${IMAGE_REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}" \ + "${install_image_ref}:${IMAGE_TAG}" + sudo podman save \ + --format oci-archive \ + --output output/install-payload.oci \ + "${install_image_ref}:${IMAGE_TAG}" + + sudo podman build \ + --cap-add sys_admin \ + --security-opt label=disable \ + --build-arg "BASE_IMAGE=${LIVE_BASE_IMAGE}" \ + --build-arg "INSTALL_IMAGE_PAYLOAD=${install_image_ref}:${IMAGE_TAG}" \ + --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ + --build-arg "INSTALL_IMAGE_TAG=${IMAGE_TAG}" \ + --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ + --tag localhost/aerocore-os-installer:latest \ + --file installer/Containerfile \ + . + + - name: Build ISO + id: build + uses: Zeglius/titanoboa@7737f4748458252ac827dca14b3d6dd09298472a # revamp-pr + with: + image-ref: localhost/aerocore-os-installer:latest + iso-dest: ${{ github.workspace }}/output/aerocore-os-${{ env.ISO_FILE_TAG }}-live-amd64.iso + + - name: Normalize ISO output path + shell: bash + env: + GENERATED_ISO: ${{ steps.build.outputs.iso-dest }} + EXPECTED_ISO: ${{ github.workspace }}/output/aerocore-os-${{ env.ISO_FILE_TAG }}-live-amd64.iso + run: | + if [[ ! -f "${GENERATED_ISO}" ]]; then + echo "::error::Generated ISO does not exist at ${GENERATED_ISO}" + exit 1 + fi + if [[ "${GENERATED_ISO}" != "${EXPECTED_ISO}" ]]; then + mkdir -p "$(dirname "${EXPECTED_ISO}")" + mv "${GENERATED_ISO}" "${EXPECTED_ISO}" + fi + + - name: Display checksum + shell: bash + run: | + set -o pipefail + iso_path="output/aerocore-os-${ISO_FILE_TAG}-live-amd64.iso" + checksum_path="${iso_path}-CHECKSUM" + sha256sum "${iso_path}" | tee "${checksum_path}" + { + echo "# ISO Checksum (SHA256)" + echo + echo "ISO: $(basename "${iso_path}")" + echo + echo '```' + cat "${checksum_path}" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + - name: Move ISO to upload directory + id: upload-directory + shell: bash + run: | + iso_upload_dir="${{ github.workspace }}/output/upload" + iso_path="${{ github.workspace }}/output/aerocore-os-${ISO_FILE_TAG}-live-amd64.iso" + iso_name="$(basename "${iso_path}")" + + mkdir -p "${iso_upload_dir}" + mv "${iso_path}" "${iso_upload_dir}/" + ( + cd "${iso_upload_dir}" + sha256sum "${iso_name}" | tee "${iso_name}-CHECKSUM" + ) + echo "iso-upload-dir=${iso_upload_dir}" >> "$GITHUB_OUTPUT" + + - name: Upload ISO and checksum to R2 + if: github.event_name == 'workflow_dispatch' && (github.ref_name == 'main' || github.ref_name == 'testing') + shell: bash + env: + RCLONE_CONFIG_R2_TYPE: s3 + RCLONE_CONFIG_R2_PROVIDER: Cloudflare + RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + RCLONE_CONFIG_R2_REGION: auto + RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} + SOURCE_DIR: ${{ steps.upload-directory.outputs.iso-upload-dir }} + run: | + sudo apt-get update + sudo apt-get install -y rclone + rclone copy "$SOURCE_DIR" R2:bazzite diff --git a/Justfile b/Justfile index 89d8b06..1b94df0 100644 --- a/Justfile +++ b/Justfile @@ -30,9 +30,8 @@ alias run-vm := run-vm-qcow2 alias build-installer := build-installer-iso alias run-installer := run-installer-iso # Backward-compatible aliases for the Titanoboa live ISO build. -alias build-live-iso := build-installer-iso +alias build-live-installer := build-live-iso alias run-live-iso := run-installer-iso -alias build-live-installer := build-installer-iso alias run-live-installer := run-installer-iso [private] @@ -40,8 +39,8 @@ default: @just --list # Build the same Titanoboa-based live ISO as CI -build-installer-iso $target_image=("localhost/" + image_name) $tag=default_tag: - @IMAGE_NAME="{{ target_image }}" IMAGE_TAG="{{ tag }}" ./just_scripts/build-installer-iso.sh +build-live-iso $target_image=("localhost/" + image_name) $tag=default_tag: + @IMAGE_NAME="{{ target_image }}" IMAGE_TAG="{{ tag }}" ./just_scripts/build-live-iso.sh # Boot the live ISO with QEMU run-installer-iso: @@ -386,9 +385,9 @@ build-qcow2 $target_image=("localhost/" + image_name) $tag=default_tag: && (_bui [group('Build Virtal Machine Image')] build-raw $target_image=("localhost/" + image_name) $tag=default_tag: && (_build-bib target_image tag "raw" "disk_config/disk.toml") -# Build an ISO virtual machine image +# Build an installer ISO 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-kde.toml") +build-installer-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')] diff --git a/README.md b/README.md index ac56ad9..644d8ce 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ image and then run: ```bash sudo just build -just build-installer-iso +just build-live-iso just run-installer-iso ``` diff --git a/just_scripts/build-installer-iso.sh b/just_scripts/build-installer-iso.sh deleted file mode 100755 index 5d40a05..0000000 --- a/just_scripts/build-installer-iso.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -project_root="$(git rev-parse --show-toplevel)" -cd "${project_root}" - -image_name="${IMAGE_NAME:-localhost/$(just image_name)}" -image_tag="${IMAGE_TAG:-$(just generate-default-tag)}" -live_base_image="${LIVE_BASE_IMAGE:-quay.io/fedora/fedora-kinoite:44}" -titanoboa_repository="${TITANOBOA_REPOSITORY:-https://github.com/Zeglius/titanoboa.git}" -titanoboa_revision="${TITANOBOA_REVISION:-7737f4748458252ac827dca14b3d6dd09298472a}" -source_image="${image_name}:${image_tag}" -# Keep the payload in local container storage, but use the public update -# reference as its local tag so Anaconda writes that reference as the boot -# origin instead of persisting the temporary localhost name. OSTREE_IMAGE_REF -# is intentionally tagless, matching the image-info contract. -image_basename="${image_name##*/}" -registry_organization="${REPO_ORGANIZATION:-aerocore-io}" -registry_organization="${registry_organization,,}" -ostree_image_ref="${OSTREE_IMAGE_REF:-ostree-image-signed:docker://ghcr.io/${registry_organization}/${image_basename}}" -ostree_image_ref="${ostree_image_ref,,}" -install_image_ref="${ostree_image_ref#ostree-image-signed:docker://}" -install_image_ref="${install_image_ref#ostree-unverified-registry:docker://}" -install_image_ref="${install_image_ref#docker://}" -payload="${install_image_ref}:${image_tag}" -output_dir="${project_root}/output" -payload_archive="${output_dir}/install-payload.oci" -iso="${output_dir}/aerocore-os-${image_tag}-live-amd64.iso" -checksum="${iso}-CHECKSUM" -api_socket="${PODMAN_SOCKET:-/run/podman/podman.sock}" -if [[ "${EUID}" -eq 0 ]]; then - podman_cmd=(podman) - socket_test=(test -S "${api_socket}") -else - podman_cmd=(sudo podman) - socket_test=(sudo test -S "${api_socket}") -fi - -if ! "${socket_test[@]}"; 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 - -ensure_rootful_image() { - local root_image_id user_image_id user_name user_uid copy_tmp - - root_image_id="$("${podman_cmd[@]}" images \ - --filter "reference=${source_image}" \ - --format "{{.ID}}" | head -n1)" - - if [[ -n "${root_image_id}" ]]; then - return 0 - fi - - if [[ "${EUID}" -ne 0 ]]; then - just _rootful_load_image "${image_name}" "${image_tag}" - return - fi - - if [[ -z "${SUDO_USER:-}" ]]; then - return - fi - - user_name="${SUDO_USER}" - user_uid="$(id -u "${user_name}")" - user_image_id="$(sudo -u "${user_name}" \ - env XDG_RUNTIME_DIR="/run/user/${user_uid}" \ - podman images \ - --filter "reference=${source_image}" \ - --format "{{.ID}}" | head -n1)" - - if [[ -z "${user_image_id}" ]]; then - return - fi - - copy_tmp="$(mktemp -p "${project_root}" -d -t _build_podman_scp.XXXXXXXXXX)" - TMPDIR="${copy_tmp}" podman image scp \ - "${user_uid}@localhost::${source_image}" \ - "root@localhost::${source_image}" - rm -rf "${copy_tmp}" -} - -validate_install_payload() { - # Run on the build host rather than from installer/Containerfile. Nested - # Podman has no /dev/fuse in the build sandbox, while this check needs to - # mount the payload rootfs to inspect the bootupd label source. - "${podman_cmd[@]}" run --rm --network none --entrypoint /usr/bin/bash \ - "${source_image}" -ceu ' - system_release_target=$(readlink -f /etc/system-release) - if od -An -v -tx1 "$system_release_target" | awk '\''{ for (i = 1; i <= NF; i++) if ($i == "00") found = 1 } END { exit(found ? 0 : 1) }'\''; then - echo "Install payload has a NUL byte in ${system_release_target}" >&2 - exit 1 - fi - case "$(<"$system_release_target")" in - "AeroCore OS release $(rpm -E %fedora) ("*) ;; - *) - echo "Unexpected system-release: $(<"$system_release_target")" >&2 - exit 1 - ;; - esac - jq -e '\''.["image-name"] == "aerocore-os" and .["image-vendor"] == "aerocore-io"'\'' \ - /usr/share/ublue-os/image-info.json >/dev/null - ' -} - -mkdir -p "${output_dir}" -ensure_rootful_image -validate_install_payload - -"${podman_cmd[@]}" tag "${source_image}" "${payload}" -"${podman_cmd[@]}" save --format oci-archive --output "${payload_archive}" "${payload}" - -"${podman_cmd[@]}" build \ - --cap-add sys_admin \ - --security-opt label=disable \ - --build-arg "BASE_IMAGE=${live_base_image}" \ - --build-arg "INSTALL_IMAGE_PAYLOAD=${payload}" \ - --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ - --build-arg "INSTALL_IMAGE_TAG=${image_tag}" \ - --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ - --tag "${payload}" \ - --file installer/Containerfile \ - . - -if [[ "${EUID}" -eq 0 ]]; then - rm -f "${iso}" "${checksum}" -else - sudo rm -f "${iso}" "${checksum}" -fi - -tmp_titanoboa="$(mktemp -d -t aerocore-titanoboa.XXXXXXXXXX)" -trap 'rm -rf "${tmp_titanoboa}"' EXIT - -git clone --quiet --filter=blob:none --no-checkout \ - "${titanoboa_repository}" "${tmp_titanoboa}" -git -C "${tmp_titanoboa}" fetch --quiet origin "${titanoboa_revision}" -git -C "${tmp_titanoboa}" checkout --quiet "${titanoboa_revision}" - -# Keep this invocation aligned with the pinned revision's GitHub Action. -# This revision exposes main.sh rather than a Justfile. -( - cd "${tmp_titanoboa}" - env \ - CI=1 \ - TITANOBOA_CTR_IMAGE="${payload}" \ - TITANOBOA_OUTPUT_DIR="${tmp_titanoboa}/output" \ - "${tmp_titanoboa}/main.sh" -) - -titanoboa_iso="$(find "${tmp_titanoboa}/output" -maxdepth 1 -type f -name '*.iso' -print -quit)" -if [[ ! -f "${titanoboa_iso}" ]]; then - echo "Titanoboa did not produce an ISO in ${tmp_titanoboa}/output" >&2 - exit 1 -fi -if [[ "${EUID}" -eq 0 ]]; then - rm -f "${iso}" "${checksum}" - mv "${titanoboa_iso}" "${iso}" -else - sudo rm -f "${iso}" "${checksum}" - sudo mv "${titanoboa_iso}" "${iso}" -fi - -if [[ -f "${iso}" ]]; then - if [[ "${EUID}" -ne 0 ]]; then - sudo chown "$(id -u):$(id -g)" "${iso}" - sudo rm -f "${checksum}" - (cd "${output_dir}" && sha256sum "$(basename "${iso}")") | sudo tee "${checksum}" - sudo chown "$(id -u):$(id -g)" "${checksum}" - else - (cd "${output_dir}" && sha256sum "$(basename "${iso}")") | tee "${checksum}" - fi -fi diff --git a/just_scripts/build-live-iso.sh b/just_scripts/build-live-iso.sh new file mode 100755 index 0000000..5d40a05 --- /dev/null +++ b/just_scripts/build-live-iso.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -euo pipefail + +project_root="$(git rev-parse --show-toplevel)" +cd "${project_root}" + +image_name="${IMAGE_NAME:-localhost/$(just image_name)}" +image_tag="${IMAGE_TAG:-$(just generate-default-tag)}" +live_base_image="${LIVE_BASE_IMAGE:-quay.io/fedora/fedora-kinoite:44}" +titanoboa_repository="${TITANOBOA_REPOSITORY:-https://github.com/Zeglius/titanoboa.git}" +titanoboa_revision="${TITANOBOA_REVISION:-7737f4748458252ac827dca14b3d6dd09298472a}" +source_image="${image_name}:${image_tag}" +# Keep the payload in local container storage, but use the public update +# reference as its local tag so Anaconda writes that reference as the boot +# origin instead of persisting the temporary localhost name. OSTREE_IMAGE_REF +# is intentionally tagless, matching the image-info contract. +image_basename="${image_name##*/}" +registry_organization="${REPO_ORGANIZATION:-aerocore-io}" +registry_organization="${registry_organization,,}" +ostree_image_ref="${OSTREE_IMAGE_REF:-ostree-image-signed:docker://ghcr.io/${registry_organization}/${image_basename}}" +ostree_image_ref="${ostree_image_ref,,}" +install_image_ref="${ostree_image_ref#ostree-image-signed:docker://}" +install_image_ref="${install_image_ref#ostree-unverified-registry:docker://}" +install_image_ref="${install_image_ref#docker://}" +payload="${install_image_ref}:${image_tag}" +output_dir="${project_root}/output" +payload_archive="${output_dir}/install-payload.oci" +iso="${output_dir}/aerocore-os-${image_tag}-live-amd64.iso" +checksum="${iso}-CHECKSUM" +api_socket="${PODMAN_SOCKET:-/run/podman/podman.sock}" +if [[ "${EUID}" -eq 0 ]]; then + podman_cmd=(podman) + socket_test=(test -S "${api_socket}") +else + podman_cmd=(sudo podman) + socket_test=(sudo test -S "${api_socket}") +fi + +if ! "${socket_test[@]}"; 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 + +ensure_rootful_image() { + local root_image_id user_image_id user_name user_uid copy_tmp + + root_image_id="$("${podman_cmd[@]}" images \ + --filter "reference=${source_image}" \ + --format "{{.ID}}" | head -n1)" + + if [[ -n "${root_image_id}" ]]; then + return 0 + fi + + if [[ "${EUID}" -ne 0 ]]; then + just _rootful_load_image "${image_name}" "${image_tag}" + return + fi + + if [[ -z "${SUDO_USER:-}" ]]; then + return + fi + + user_name="${SUDO_USER}" + user_uid="$(id -u "${user_name}")" + user_image_id="$(sudo -u "${user_name}" \ + env XDG_RUNTIME_DIR="/run/user/${user_uid}" \ + podman images \ + --filter "reference=${source_image}" \ + --format "{{.ID}}" | head -n1)" + + if [[ -z "${user_image_id}" ]]; then + return + fi + + copy_tmp="$(mktemp -p "${project_root}" -d -t _build_podman_scp.XXXXXXXXXX)" + TMPDIR="${copy_tmp}" podman image scp \ + "${user_uid}@localhost::${source_image}" \ + "root@localhost::${source_image}" + rm -rf "${copy_tmp}" +} + +validate_install_payload() { + # Run on the build host rather than from installer/Containerfile. Nested + # Podman has no /dev/fuse in the build sandbox, while this check needs to + # mount the payload rootfs to inspect the bootupd label source. + "${podman_cmd[@]}" run --rm --network none --entrypoint /usr/bin/bash \ + "${source_image}" -ceu ' + system_release_target=$(readlink -f /etc/system-release) + if od -An -v -tx1 "$system_release_target" | awk '\''{ for (i = 1; i <= NF; i++) if ($i == "00") found = 1 } END { exit(found ? 0 : 1) }'\''; then + echo "Install payload has a NUL byte in ${system_release_target}" >&2 + exit 1 + fi + case "$(<"$system_release_target")" in + "AeroCore OS release $(rpm -E %fedora) ("*) ;; + *) + echo "Unexpected system-release: $(<"$system_release_target")" >&2 + exit 1 + ;; + esac + jq -e '\''.["image-name"] == "aerocore-os" and .["image-vendor"] == "aerocore-io"'\'' \ + /usr/share/ublue-os/image-info.json >/dev/null + ' +} + +mkdir -p "${output_dir}" +ensure_rootful_image +validate_install_payload + +"${podman_cmd[@]}" tag "${source_image}" "${payload}" +"${podman_cmd[@]}" save --format oci-archive --output "${payload_archive}" "${payload}" + +"${podman_cmd[@]}" build \ + --cap-add sys_admin \ + --security-opt label=disable \ + --build-arg "BASE_IMAGE=${live_base_image}" \ + --build-arg "INSTALL_IMAGE_PAYLOAD=${payload}" \ + --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ + --build-arg "INSTALL_IMAGE_TAG=${image_tag}" \ + --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ + --tag "${payload}" \ + --file installer/Containerfile \ + . + +if [[ "${EUID}" -eq 0 ]]; then + rm -f "${iso}" "${checksum}" +else + sudo rm -f "${iso}" "${checksum}" +fi + +tmp_titanoboa="$(mktemp -d -t aerocore-titanoboa.XXXXXXXXXX)" +trap 'rm -rf "${tmp_titanoboa}"' EXIT + +git clone --quiet --filter=blob:none --no-checkout \ + "${titanoboa_repository}" "${tmp_titanoboa}" +git -C "${tmp_titanoboa}" fetch --quiet origin "${titanoboa_revision}" +git -C "${tmp_titanoboa}" checkout --quiet "${titanoboa_revision}" + +# Keep this invocation aligned with the pinned revision's GitHub Action. +# This revision exposes main.sh rather than a Justfile. +( + cd "${tmp_titanoboa}" + env \ + CI=1 \ + TITANOBOA_CTR_IMAGE="${payload}" \ + TITANOBOA_OUTPUT_DIR="${tmp_titanoboa}/output" \ + "${tmp_titanoboa}/main.sh" +) + +titanoboa_iso="$(find "${tmp_titanoboa}/output" -maxdepth 1 -type f -name '*.iso' -print -quit)" +if [[ ! -f "${titanoboa_iso}" ]]; then + echo "Titanoboa did not produce an ISO in ${tmp_titanoboa}/output" >&2 + exit 1 +fi +if [[ "${EUID}" -eq 0 ]]; then + rm -f "${iso}" "${checksum}" + mv "${titanoboa_iso}" "${iso}" +else + sudo rm -f "${iso}" "${checksum}" + sudo mv "${titanoboa_iso}" "${iso}" +fi + +if [[ -f "${iso}" ]]; then + if [[ "${EUID}" -ne 0 ]]; then + sudo chown "$(id -u):$(id -g)" "${iso}" + sudo rm -f "${checksum}" + (cd "${output_dir}" && sha256sum "$(basename "${iso}")") | sudo tee "${checksum}" + sudo chown "$(id -u):$(id -g)" "${checksum}" + else + (cd "${output_dir}" && sha256sum "$(basename "${iso}")") | tee "${checksum}" + fi +fi diff --git a/just_scripts/run-installer-iso.sh b/just_scripts/run-installer-iso.sh index c495215..dc75c42 100755 --- a/just_scripts/run-installer-iso.sh +++ b/just_scripts/run-installer-iso.sh @@ -9,7 +9,7 @@ image_tag="${IMAGE_TAG:-$(just generate-default-tag)}" iso="${project_root}/output/aerocore-os-${image_tag}-live-amd64.iso" if [[ ! -f "${iso}" ]]; then - just build-installer-iso + just build-live-iso fi podman run --rm --cap-add NET_ADMIN \ -- cgit v1.2.3