From dd9d8b89e8bb28eac34654536525916deddd9a9d Mon Sep 17 00:00:00 2001 From: tranch Date: Sun, 20 Sep 2026 10:56:18 +0800 Subject: Pin testing base to Bazzite 44.20260919 --- .github/workflows/build.yml | 6 +++--- Containerfile | 4 ++-- Justfile | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b6b8df..1639aab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ env: IMAGE_LOGO_URL: ${{ vars.IMAGE_LOGO_URL || 'https://avatars.githubusercontent.com/u/120078124?s=200&v=4' }} DEFAULT_TAG: ${{ vars.DEFAULT_TAG || 'stable' }} BIB_IMAGE: ${{ vars.BIB_IMAGE || 'quay.io/centos-bootc/bootc-image-builder:latest' }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260908' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} @@ -95,7 +95,7 @@ jobs: - name: Generate Version and Tags id: gen-version env: - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260908' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} run: | set -euo pipefail @@ -162,7 +162,7 @@ jobs: id: build-image env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260908' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} diff --git a/Containerfile b/Containerfile index 11d6ebf..450ade7 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,5 @@ # Allow build scripts to be referenced without being copied into the final image -ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260908" +ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260919" FROM scratch AS ctx COPY build_files / @@ -15,7 +15,7 @@ ARG VERSION_TAG="latest" ARG VERSION_PRETTY="latest" ARG OSTREE_IMAGE_REF="" ## Other possible base images include: -# ghcr.io/ublue-os/bazzite-deck:stable-44.20260908 +# ghcr.io/ublue-os/bazzite-deck:stable-44.20260919 # ghcr.io/ublue-os/bazzite-deck:testing-44.20260908.3 ### [IM]MUTABLE /opt diff --git a/Justfile b/Justfile index a83ba0a..84dd133 100644 --- a/Justfile +++ b/Justfile @@ -11,7 +11,7 @@ export bib_image := env_var("BIB_IMAGE") export IMAGE_BRANCH := env_var_or_default("IMAGE_BRANCH", "testing-candidate") export VERSION_TAG := env_var_or_default("VERSION_TAG", default_tag) export VERSION_PRETTY := env_var_or_default("VERSION_PRETTY", VERSION_TAG) -export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20260908") +export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20260919") export OSTREE_IMAGE_REF := env_var_or_default("OSTREE_IMAGE_REF", "ostree-image-signed:docker://ghcr.io/" + repo_organization + "/" + image_name) export LIVE_BASE_IMAGE := env_var_or_default("LIVE_BASE_IMAGE", "quay.io/fedora/fedora-kinoite:44") export TITANOBOA_REPOSITORY := env_var_or_default("TITANOBOA_REPOSITORY", "https://github.com/Zeglius/titanoboa.git") -- cgit v1.2.3 From d571c3ddf55118e35f4cf5a567a52249e0a9a802 Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 23 Sep 2026 14:57:27 +0800 Subject: build: Update base image tag Update the `BASE_IMAGE` argument to 44.20260921 in the `Containerfile` to reference the latest stable tag. --- Containerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index 450ade7..0f4577c 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,5 @@ # Allow build scripts to be referenced without being copied into the final image -ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260919" +ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260921" FROM scratch AS ctx COPY build_files / @@ -15,8 +15,8 @@ ARG VERSION_TAG="latest" ARG VERSION_PRETTY="latest" ARG OSTREE_IMAGE_REF="" ## Other possible base images include: -# ghcr.io/ublue-os/bazzite-deck:stable-44.20260919 -# ghcr.io/ublue-os/bazzite-deck:testing-44.20260908.3 +# ghcr.io/ublue-os/bazzite-deck:stable-44.20260921 +# ghcr.io/ublue-os/bazzite-deck:testing-44.202609019 ### [IM]MUTABLE /opt ## Some bootable images, like Fedora, have /opt symlinked to /var/opt, in order to -- cgit v1.2.3 From f51379753a1474154760fb8cef295dd8ec4c984d Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 23 Sep 2026 15:27:27 +0800 Subject: fix: adapt instruments release layout --- build_files/install-instruments.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/build_files/install-instruments.sh b/build_files/install-instruments.sh index f85bd3b..79f5066 100755 --- a/build_files/install-instruments.sh +++ b/build_files/install-instruments.sh @@ -129,13 +129,12 @@ required_paths=( install.sh appimage/AppRun appimage/AppRun.wrapped - sidecar/hhd-input-helper.cjs - sidecar/vendor/hhd-input-helper/instruments_hhd_input/__init__.py + sidecar/sidecar-main.cjs + sidecar/aerocore-bridge.cjs runtime/avionics runtime/cabin runtime/wing runtime/node/bin/node - python-wheelhouse pkg/systemd/ac-avionics.service pkg/systemd/ac-cabin.service pkg/systemd/ac-wing.service @@ -151,8 +150,7 @@ for path in "${required_paths[@]}"; do fi done -if [[ ! -f "$payload/pkg/aerocore-booster.sh" && - ! -f "$payload/pkg/instruments-ng.sh" ]]; then +if [[ ! -f "$payload/pkg/aerocore-booster.sh" ]]; then echo "Booster launcher is missing from the release payload" >&2 exit 1 fi -- cgit v1.2.3 From 0134725c60a6ef58c270fe7c5ac17e07521c09ba Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 23 Sep 2026 15:34:35 +0800 Subject: fix: fall back to anonymous release downloads --- build_files/install-instruments.sh | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/build_files/install-instruments.sh b/build_files/install-instruments.sh index 79f5066..0cf5fe6 100755 --- a/build_files/install-instruments.sh +++ b/build_files/install-instruments.sh @@ -48,7 +48,29 @@ github_curl() { args+=(--header "Authorization: Bearer $( Date: Wed, 23 Sep 2026 16:50:28 +0800 Subject: feat: organize upstream release notes --- .github/scripts/generate_changelog.py | 170 ++++++++++++++++++++++++++++++---- Justfile | 3 +- 2 files changed, 156 insertions(+), 17 deletions(-) diff --git a/.github/scripts/generate_changelog.py b/.github/scripts/generate_changelog.py index 57f7ba9..0bcf369 100644 --- a/.github/scripts/generate_changelog.py +++ b/.github/scripts/generate_changelog.py @@ -7,6 +7,8 @@ import argparse import json import os import re +import urllib.error +import urllib.request import subprocess import tempfile from pathlib import Path @@ -73,6 +75,92 @@ def markdown_version(value: str | None) -> str: return value or "—" +MAJOR_PACKAGES = { + "kernel": "Kernel", + "kernel-core": "Kernel", + "kernel-modules": "Kernel", + "kernel-modules-core": "Kernel", + "kernel-modules-extra": "Kernel", + "kernel-tools": "Kernel", + "kernel-devel": "Kernel", + "kernel-headers": "Kernel", + "kernel-lts": "Kernel (Nvidia LTS)", + "linux-firmware": "Firmware", + "mesa-dri-drivers": "Mesa", + "mesa-filesystem": "Mesa", + "mesa-libEGL": "Mesa", + "mesa-libGL": "Mesa", + "mesa-libgbm": "Mesa", + "mesa-va-drivers": "Mesa", + "mesa-vulkan-drivers": "Mesa", + "gamescope": "Gamescope", + "gamescope-session": "Gamescope Session", + "mangohud": "MangoHUD", + "inputplumber": "InputPlumber", + "opengamepadui": "OpenGamepadUI", + "powerstation": "PowerStation", + "steamos-manager": "SteamOS-Manager", + "umu-launcher": "UMU Launcher", + "bazaar": "Bazaar", + "distrobox": "Distrobox", + "gnome-shell": "Gnome", + "plasma-desktop": "KDE", + "waydroid": "Waydroid", +} + + +def major_package_rows( + current_packages: dict[str, str], + previous_packages: dict[str, str] | None, +) -> list[str]: + selected: dict[str, tuple[str | None, str | None]] = {} + for name, current in current_packages.items(): + display_name = MAJOR_PACKAGES.get(name) + if not display_name: + continue + previous = (previous_packages or {}).get(name) + selected[display_name] = (previous, current) + + rows = [] + for display_name, (old, new) in selected.items(): + version = markdown_version(new) + if old and new and old != new: + version = f"{old} ➡️ {new}" + rows.append(f"| **{display_name}** | {version} |") + return rows + + +def all_image_rows(changes: list[tuple[str, str | None, str | None]]) -> list[str]: + rows = [] + for name, old, new in changes: + icon = "➕" if old is None else "➖" if new is None else "🔄" + rows.append( + f"| {icon} | `{name}` | {markdown_version(old)} | {markdown_version(new)} |" + ) + return rows + + +def upstream_release_commit(tag: str) -> str | None: + try: + request = urllib.request.Request( + f"https://api.github.com/repos/ublue-os/bazzite/git/ref/tags/{tag}", + headers={"Accept": "application/vnd.github+json", "User-Agent": "AeroCore-OS-release-notes"}, + ) + with urllib.request.urlopen(request, timeout=30) as response: + data = json.load(response) + obj = data["object"] + if obj.get("type") == "tag": + request = urllib.request.Request( + f"https://api.github.com/repos/ublue-os/bazzite/git/tags/{obj['sha']}", + headers={"Accept": "application/vnd.github+json", "User-Agent": "AeroCore-OS-release-notes"}, + ) + with urllib.request.urlopen(request, timeout=30) as response: + obj = json.load(response)["object"] + return obj.get("sha") + except (urllib.error.URLError, urllib.error.HTTPError, KeyError, json.JSONDecodeError): + return None + + def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--image", required=True, help="registry/repository without tag") @@ -89,6 +177,7 @@ def main() -> None: current_info = image_info(args.image, args.current) current_packages = package_map(fetch_sbom(args.image, current_info["Digest"])) + base_image = current_info.get("Labels", {}).get("org.opencontainers.image.base.name") previous_packages: dict[str, str] | None = None if previous: try: @@ -107,34 +196,83 @@ def main() -> None: changes.append((name, old, new)) previous_info = image_info(args.image, previous) if previous else {} + previous_base = previous_info.get("Labels", {}).get("org.opencontainers.image.base.name", "") + previous_base_tag = previous_base.rpartition(":")[2] + current_base_tag = base_image.rpartition(":")[2] if base_image else "" + start_revision = upstream_release_commit(previous_base_tag) if previous_base_tag else None + end_revision = upstream_release_commit(current_base_tag) if current_base_tag else None + upstream_commits = [] + if start_revision and end_revision and start_revision != end_revision: + request = urllib.request.Request( + f"https://api.github.com/repos/ublue-os/bazzite/compare/{start_revision}...{end_revision}", + headers={"Accept": "application/vnd.github+json", "User-Agent": "AeroCore-OS-release-notes"}, + ) + with urllib.request.urlopen(request, timeout=60) as response: + compare = json.load(response) + upstream_commits = [ + ( + item["sha"], + item["commit"]["message"].splitlines()[0], + item["commit"]["author"].get("name", "Unknown"), + ) + for item in compare.get("commits", []) + ] + start_revision = previous_info.get("Labels", {}).get("org.opencontainers.image.revision") end_revision = current_info.get("Labels", {}).get("org.opencontainers.image.revision") - commits = ( - run("git", "log", "--format=%H%x09%s%x09%an", f"{start_revision}..{end_revision}").splitlines() - if start_revision and end_revision + aerocore_commits = ( + [ + (item.split("\t", 2)[0], item.split("\t", 2)[1], item.split("\t", 2)[2]) + for item in run( + "git", "log", "--format=%H%x09%s%x09%an", f"{start_revision}..{end_revision}" + ).splitlines() + ] + if start_revision and end_revision and start_revision != end_revision else [] ) lines = [args.handwritten.strip() or f"This is an automatically generated changelog for release `{args.current}`.", ""] + if base_image: + base_name, _, base_tag = base_image.rpartition(":") + if base_name and base_tag: + base_repo = base_name.removeprefix("docker://") + upstream_version = re.sub(r"^(?:stable|testing|testing-candidate)-", "", base_tag) + lines.append( + f"Based on upstream [{upstream_version}](https://github.com/ublue-os/bazzite/releases/tag/{base_tag}) " + f"(`{base_repo}`)." + ) + else: + lines.append(f"Based on upstream image `{base_image}`.") + lines.append("") if previous: - lines.append(f"From previous `{args.channel}` version `{previous}` there have been the following package changes.") - else: - lines.append("No previous release was found for this channel; package changes are shown against an empty baseline.") - lines += ["", "### Package changes", "", "| Name | Previous | New |", "| --- | --- | --- |"] - if previous and previous_packages is None: - lines.append("| — | Previous release has no attached SBOM | Diff starts with the next release |") - elif not changes: - lines.append("| — | — | — |") + lines.append(f"From previous `{args.channel}` version `{previous}` there have been the following changes. One package per new version shown.") else: - lines += [f"| `{name}` | {markdown_version(old)} | {markdown_version(new)} |" for name, old, new in changes] + lines.append("No previous release was found for this channel; changes are shown against an empty baseline.") + + lines += ["", "### Major packages", "", "| Name | Version |", "| --- | --- |"] + major_rows = major_package_rows(current_packages, previous_packages) + lines.extend(major_rows or ["| — | No major package changes |"]) lines += ["", "### Commits", "", "| Hash | Subject | Author |", "| --- | --- | --- |"] - for commit in commits: - commit_hash, subject, author = commit.split("\t", 2) - lines.append(f"| [{commit_hash[:7]}](https://github.com/{args.repository}/commit/{commit_hash}) | {subject} | {author} |") - if not commits: + if upstream_commits: + lines.append("| | **Upstream Bazzite** | | |") + for commit_hash, subject, author in upstream_commits: + lines.append(f"| | **[{commit_hash[:7]}](https://github.com/ublue-os/bazzite/commit/{commit_hash})** | {subject} | {author} |") + if aerocore_commits: + lines.append("| | **AeroCore OS** | | |") + for commit_hash, subject, author in aerocore_commits: + lines.append(f"| | **[{commit_hash[:7]}](https://github.com/{args.repository}/commit/{commit_hash})** | {subject} | {author} |") + if not upstream_commits and not aerocore_commits: lines.append("| — | No commit range available | — |") + lines += ["", "### All Images", "", "| | Name | Previous | New |", "| --- | --- | --- | --- |"] + if previous and previous_packages is None: + lines.append("| — | Previous release has no attached SBOM | — | Diff starts with the next release |") + elif not changes: + lines.append("| — | No package changes | — | — |") + else: + lines.extend(all_image_rows(changes)) + lines += ["", "### How to switch", "", "For current users, switch to the channel image with:", "", "```bash", f"sudo bootc switch {args.image}:{args.channel}", "", "# Or switch to this exact image:", f"sudo bootc switch {args.image}:{args.current}", "```", ""] args.output.write_text("\n".join(lines)) diff --git a/Justfile b/Justfile index 84dd133..89d8b06 100644 --- a/Justfile +++ b/Justfile @@ -11,7 +11,7 @@ export bib_image := env_var("BIB_IMAGE") export IMAGE_BRANCH := env_var_or_default("IMAGE_BRANCH", "testing-candidate") export VERSION_TAG := env_var_or_default("VERSION_TAG", default_tag) export VERSION_PRETTY := env_var_or_default("VERSION_PRETTY", VERSION_TAG) -export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20260919") +export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20260921") export OSTREE_IMAGE_REF := env_var_or_default("OSTREE_IMAGE_REF", "ostree-image-signed:docker://ghcr.io/" + repo_organization + "/" + image_name) export LIVE_BASE_IMAGE := env_var_or_default("LIVE_BASE_IMAGE", "quay.io/fedora/fedora-kinoite:44") export TITANOBOA_REPOSITORY := env_var_or_default("TITANOBOA_REPOSITORY", "https://github.com/Zeglius/titanoboa.git") @@ -138,6 +138,7 @@ build $target_image=image_name $tag=default_tag: "--build-arg" "IMAGE_BRANCH={{ tag }}" "--build-arg" "VERSION_TAG=${VERSION_TAG:-{{ tag }}}" "--build-arg" "VERSION_PRETTY=${VERSION_PRETTY:-${VERSION_TAG:-{{ tag }}}}" + "--label" "org.opencontainers.image.base.name=${BASE_IMAGE:-ghcr.io/ublue-os/bazzite-deck:stable-44.20260921}" ) for arg in BASE_IMAGE FLATPAK_REMOTE_URL HOMEBREW_BOTTLE_DOMAIN HOMEBREW_API_DOMAIN OSTREE_IMAGE_REF INSTRUMENTS_ENABLED INSTRUMENTS_RELEASE_REPOSITORY INSTRUMENTS_VERSION INSTRUMENTS_RELEASE_API_BASE; do -- cgit v1.2.3 From fa7a54f7d51f3086f35239d7117b9c5988a96c68 Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 23 Sep 2026 16:50:28 +0800 Subject: build: update Bazzite base to 44.20260921 --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1639aab..0bb6f79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ env: IMAGE_LOGO_URL: ${{ vars.IMAGE_LOGO_URL || 'https://avatars.githubusercontent.com/u/120078124?s=200&v=4' }} DEFAULT_TAG: ${{ vars.DEFAULT_TAG || 'stable' }} BIB_IMAGE: ${{ vars.BIB_IMAGE || 'quay.io/centos-bootc/bootc-image-builder:latest' }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} @@ -95,7 +95,7 @@ jobs: - name: Generate Version and Tags id: gen-version env: - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} run: | set -euo pipefail @@ -162,7 +162,7 @@ jobs: id: build-image env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260919' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} -- cgit v1.2.3 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 From 9fcb843f225e9618155a6b9228006859e91f7f0b Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 23 Sep 2026 17:27:25 +0800 Subject: fix: correct release commits section --- .github/scripts/generate_changelog.py | 53 ++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/scripts/generate_changelog.py b/.github/scripts/generate_changelog.py index 0bcf369..2486fb6 100644 --- a/.github/scripts/generate_changelog.py +++ b/.github/scripts/generate_changelog.py @@ -161,6 +161,25 @@ def upstream_release_commit(tag: str) -> str | None: return None +def upstream_compare_commits(start_revision: str | None, end_revision: str | None) -> list[tuple[str, str, str]]: + if not start_revision or not end_revision or start_revision == end_revision: + return [] + request = urllib.request.Request( + f"https://api.github.com/repos/ublue-os/bazzite/compare/{start_revision}...{end_revision}", + headers={"Accept": "application/vnd.github+json", "User-Agent": "AeroCore-OS-release-notes"}, + ) + with urllib.request.urlopen(request, timeout=60) as response: + compare = json.load(response) + return [ + ( + item["sha"], + item["commit"]["message"].splitlines()[0], + item["commit"].get("author", {}).get("name", "Unknown"), + ) + for item in compare.get("commits", []) + ] + + def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--image", required=True, help="registry/repository without tag") @@ -201,22 +220,8 @@ def main() -> None: current_base_tag = base_image.rpartition(":")[2] if base_image else "" start_revision = upstream_release_commit(previous_base_tag) if previous_base_tag else None end_revision = upstream_release_commit(current_base_tag) if current_base_tag else None - upstream_commits = [] - if start_revision and end_revision and start_revision != end_revision: - request = urllib.request.Request( - f"https://api.github.com/repos/ublue-os/bazzite/compare/{start_revision}...{end_revision}", - headers={"Accept": "application/vnd.github+json", "User-Agent": "AeroCore-OS-release-notes"}, - ) - with urllib.request.urlopen(request, timeout=60) as response: - compare = json.load(response) - upstream_commits = [ - ( - item["sha"], - item["commit"]["message"].splitlines()[0], - item["commit"]["author"].get("name", "Unknown"), - ) - for item in compare.get("commits", []) - ] + upstream_commits = upstream_compare_commits(start_revision, end_revision) + upstream_range_available = bool(start_revision and end_revision) start_revision = previous_info.get("Labels", {}).get("org.opencontainers.image.revision") end_revision = current_info.get("Labels", {}).get("org.opencontainers.image.revision") @@ -255,15 +260,19 @@ def main() -> None: lines += ["", "### Commits", "", "| Hash | Subject | Author |", "| --- | --- | --- |"] if upstream_commits: - lines.append("| | **Upstream Bazzite** | | |") + lines.append("| **Upstream Bazzite** | | |") for commit_hash, subject, author in upstream_commits: - lines.append(f"| | **[{commit_hash[:7]}](https://github.com/ublue-os/bazzite/commit/{commit_hash})** | {subject} | {author} |") + lines.append(f"| [{commit_hash[:7]}](https://github.com/ublue-os/bazzite/commit/{commit_hash}) | {subject} | {author} |") + elif upstream_range_available: + lines.append("| **Upstream Bazzite** | No commits between the selected base versions | — |") + else: + lines.append("| **Upstream Bazzite** | Commit range unavailable (previous image may predate base image metadata) | — |") if aerocore_commits: - lines.append("| | **AeroCore OS** | | |") + lines.append("| **AeroCore OS** | | |") for commit_hash, subject, author in aerocore_commits: - lines.append(f"| | **[{commit_hash[:7]}](https://github.com/{args.repository}/commit/{commit_hash})** | {subject} | {author} |") - if not upstream_commits and not aerocore_commits: - lines.append("| — | No commit range available | — |") + lines.append(f"| [{commit_hash[:7]}](https://github.com/{args.repository}/commit/{commit_hash}) | {subject} | {author} |") + if not aerocore_commits: + lines.append("| **AeroCore OS** | No commits between the selected image versions | — |") lines += ["", "### All Images", "", "| | Name | Previous | New |", "| --- | --- | --- | --- |"] if previous and previous_packages is None: -- cgit v1.2.3 From f7c095592475d22e6d1d263b988c8a7974882521 Mon Sep 17 00:00:00 2001 From: tranch Date: Wed, 14 Oct 2026 17:03:33 +0800 Subject: chore: align testing with Bazzite 44.20261012 --- .github/workflows/build.yml | 6 +++--- Containerfile | 4 ++-- Justfile | 8 ++++---- image-template.env | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0bb6f79..5ec0da4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ env: IMAGE_LOGO_URL: ${{ vars.IMAGE_LOGO_URL || 'https://avatars.githubusercontent.com/u/120078124?s=200&v=4' }} DEFAULT_TAG: ${{ vars.DEFAULT_TAG || 'stable' }} BIB_IMAGE: ${{ vars.BIB_IMAGE || 'quay.io/centos-bootc/bootc-image-builder:latest' }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20261012' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} @@ -95,7 +95,7 @@ jobs: - name: Generate Version and Tags id: gen-version env: - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20261012' }} run: | set -euo pipefail @@ -162,7 +162,7 @@ jobs: id: build-image env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20260921' }} + BASE_IMAGE: ${{ vars.BASE_IMAGE || 'ghcr.io/ublue-os/bazzite-deck:stable-44.20261012' }} FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL }} HOMEBREW_BOTTLE_DOMAIN: ${{ vars.HOMEBREW_BOTTLE_DOMAIN }} HOMEBREW_API_DOMAIN: ${{ vars.HOMEBREW_API_DOMAIN }} diff --git a/Containerfile b/Containerfile index 0f4577c..794b46d 100644 --- a/Containerfile +++ b/Containerfile @@ -1,5 +1,5 @@ # Allow build scripts to be referenced without being copied into the final image -ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260921" +ARG BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20261012" FROM scratch AS ctx COPY build_files / @@ -15,7 +15,7 @@ ARG VERSION_TAG="latest" ARG VERSION_PRETTY="latest" ARG OSTREE_IMAGE_REF="" ## Other possible base images include: -# ghcr.io/ublue-os/bazzite-deck:stable-44.20260921 +# ghcr.io/ublue-os/bazzite-deck:stable-44.20261012 # ghcr.io/ublue-os/bazzite-deck:testing-44.202609019 ### [IM]MUTABLE /opt diff --git a/Justfile b/Justfile index 1b94df0..643837a 100644 --- a/Justfile +++ b/Justfile @@ -9,9 +9,9 @@ export image_logo_url := env_var("IMAGE_LOGO_URL") export default_tag := env_var("DEFAULT_TAG") export bib_image := env_var("BIB_IMAGE") export IMAGE_BRANCH := env_var_or_default("IMAGE_BRANCH", "testing-candidate") -export VERSION_TAG := env_var_or_default("VERSION_TAG", default_tag) -export VERSION_PRETTY := env_var_or_default("VERSION_PRETTY", VERSION_TAG) -export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20260921") +export VERSION_TAG := env_var_or_default("VERSION_TAG", "") +export VERSION_PRETTY := env_var_or_default("VERSION_PRETTY", "") +export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable-44.20261012") export OSTREE_IMAGE_REF := env_var_or_default("OSTREE_IMAGE_REF", "ostree-image-signed:docker://ghcr.io/" + repo_organization + "/" + image_name) export LIVE_BASE_IMAGE := env_var_or_default("LIVE_BASE_IMAGE", "quay.io/fedora/fedora-kinoite:44") export TITANOBOA_REPOSITORY := env_var_or_default("TITANOBOA_REPOSITORY", "https://github.com/Zeglius/titanoboa.git") @@ -137,7 +137,7 @@ build $target_image=image_name $tag=default_tag: "--build-arg" "IMAGE_BRANCH={{ tag }}" "--build-arg" "VERSION_TAG=${VERSION_TAG:-{{ tag }}}" "--build-arg" "VERSION_PRETTY=${VERSION_PRETTY:-${VERSION_TAG:-{{ tag }}}}" - "--label" "org.opencontainers.image.base.name=${BASE_IMAGE:-ghcr.io/ublue-os/bazzite-deck:stable-44.20260921}" + "--label" "org.opencontainers.image.base.name=${BASE_IMAGE:-ghcr.io/ublue-os/bazzite-deck:stable-44.20261012}" ) for arg in BASE_IMAGE FLATPAK_REMOTE_URL HOMEBREW_BOTTLE_DOMAIN HOMEBREW_API_DOMAIN OSTREE_IMAGE_REF INSTRUMENTS_ENABLED INSTRUMENTS_RELEASE_REPOSITORY INSTRUMENTS_VERSION INSTRUMENTS_RELEASE_API_BASE; do diff --git a/image-template.env b/image-template.env index 3a5a03f..ea5dead 100644 --- a/image-template.env +++ b/image-template.env @@ -16,7 +16,7 @@ BIB_IMAGE="quay.io/centos-bootc/bootc-image-builder:latest" # Keep this on bazzite-deck for Steam Deck, game mode, and handheld customizations. # Use a concrete tag during transition windows if stable/testing moves underneath you. -BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20260908" +BASE_IMAGE="ghcr.io/ublue-os/bazzite-deck:stable-44.20261012" # Optional mirror settings. FLATPAK_REMOTE_URL="" -- cgit v1.2.3 From 0891c70f559c7d23a306290334388853ff317181 Mon Sep 17 00:00:00 2001 From: tranch Date: Fri, 16 Oct 2026 01:18:48 +0800 Subject: fix: tolerate unavailable changelog revisions --- .github/scripts/generate_changelog.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/scripts/generate_changelog.py b/.github/scripts/generate_changelog.py index 2486fb6..4c5d7c1 100644 --- a/.github/scripts/generate_changelog.py +++ b/.github/scripts/generate_changelog.py @@ -225,16 +225,23 @@ def main() -> None: start_revision = previous_info.get("Labels", {}).get("org.opencontainers.image.revision") end_revision = current_info.get("Labels", {}).get("org.opencontainers.image.revision") - aerocore_commits = ( - [ - (item.split("\t", 2)[0], item.split("\t", 2)[1], item.split("\t", 2)[2]) - for item in run( - "git", "log", "--format=%H%x09%s%x09%an", f"{start_revision}..{end_revision}" - ).splitlines() - ] - if start_revision and end_revision and start_revision != end_revision - else [] - ) + aerocore_commits: list[tuple[str, str, str]] = [] + if start_revision and end_revision and start_revision != end_revision: + try: + aerocore_commits = [ + (item.split("\t", 2)[0], item.split("\t", 2)[1], item.split("\t", 2)[2]) + for item in run( + "git", "log", "--format=%H%x09%s%x09%an", f"{start_revision}..{end_revision}" + ).splitlines() + ] + except subprocess.CalledProcessError as error: + # Release metadata can point to revisions outside this checkout's + # history after histories are joined or rewritten. Keep the package + # and upstream changelog sections and omit only AeroCore commits. + print( + "Warning: unable to determine AeroCore commit range " + f"{start_revision}..{end_revision}: {error}" + ) lines = [args.handwritten.strip() or f"This is an automatically generated changelog for release `{args.current}`.", ""] if base_image: -- cgit v1.2.3