From c4d2a1329c94d339bb57e4c30b7080f7c7e7af22 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Mon, 6 Jul 2026 18:18:24 +0200 Subject: [PATCH 1/4] test(ci): temp-vendor aetherpak actions to validate flatpak publish safely Scratch branch, not for merging to main. Vendors publish-oci, publish-site, and setup-cli locally (MIT-licensed upstream, commits pinned) so the flatpak publish + Pages pipeline from #2301 can be dry-run tested via workflow_dispatch without cutting a real release (which would trigger real auto-updates) and without the production GPG signing secrets. - setup-cli: adds SHA256 checksum verification of the downloaded CLI binary, self-verified against the actual v0.14.0 release assets (upstream has none). - publish-oci: adds a dry-run mode that skips only the actual `push-oci` registry call; everything else (import, coordinate resolution) still runs for real. - Real (non-dry-run) test pushes are isolated via a distinct "test" OCI branch (embedded directly in the image tag per pkg/oci/oci.go, cannot collide with stable/beta) and a distinct "flatpak-test" Pages subpath (isolated from the real "flatpak" path and "latest/" release manifest via destination_dir, verified against peaceiris/actions-gh-pages source). Delete this branch and file findings upstream once validation is done. Co-Authored-By: Claude Sonnet 5 --- .../_tmp-aetherpak-publish-oci/action.yml | 277 ++++++++++++++++++ .../_tmp-aetherpak-publish-site/action.yml | 212 ++++++++++++++ .../_tmp-aetherpak-setup-cli/action.yml | 169 +++++++++++ .../workflows/_tmp-test-aetherpak-publish.yml | 102 +++++++ 4 files changed, 760 insertions(+) create mode 100644 .github/actions/_tmp-aetherpak-publish-oci/action.yml create mode 100644 .github/actions/_tmp-aetherpak-publish-site/action.yml create mode 100644 .github/actions/_tmp-aetherpak-setup-cli/action.yml create mode 100644 .github/workflows/_tmp-test-aetherpak-publish.yml diff --git a/.github/actions/_tmp-aetherpak-publish-oci/action.yml b/.github/actions/_tmp-aetherpak-publish-oci/action.yml new file mode 100644 index 000000000..95d5c53f0 --- /dev/null +++ b/.github/actions/_tmp-aetherpak-publish-oci/action.yml @@ -0,0 +1,277 @@ +# ============================================================================ +# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# +# Vendored for local iteration while validating the aetherpak flatpak +# publishing pipeline (see PR #2301 / #2427). Delete this directory once +# validation is done; production usage should reference the upstream action +# pinned by commit SHA, as on-release.yml already does. +# +# Source: https://github.com/aetherpak/actions/tree/main/publish-oci (MIT +# License), vendored from commit d2112669ceaa09afb7c2b96e9e9844866a6c8e78 +# (the same commit on-release.yml pins). +# +# Changes from upstream: +# - Uses the local ../_tmp-aetherpak-setup-cli instead of aetherpak/setup-cli@v1. +# - Added a `dry-run` input. When true, everything runs identically up to +# and including resolving refs/coordinates, but the actual `aetherpak +# push-oci` invocation (the only network-mutating call in this action) is +# replaced with printing the command that would have run. +# ============================================================================ +name: '[TEMP] AetherPak Publish OCI (vendored, dry-run capable)' +description: 'Push an OSTree-built or bundle-imported Flatpak as a signed OCI image and emit a per-cell record. Parallel-safe.' + +inputs: + repo-path: + description: 'Path to the OSTree repository containing the built application.' + required: false + default: '_repo' + bundle-path: + description: 'Optional pre-built .flatpak bundle to import into repo-path.' + required: false + default: '' + app-id: + description: "Reverse-DNS application ID. Resolved from the repo's ref when empty." + required: false + default: '' + branch: + description: 'Flatpak channel. Resolution: explicit input > repo ref branch > git-ref default.' + required: false + default: '' + arch: + description: "CPU architecture (e.g. x86_64). Resolved from the repo's ref when empty." + required: false + default: '' + registry: + description: 'The registry host (default: ghcr.io).' + required: false + default: 'ghcr.io' + insecure-registry: + description: 'Skip TLS verification for the OCI registry.' + required: false + default: 'false' + oci-repository: + description: 'Target registry repository path without host (defaults to GITHUB_REPOSITORY).' + required: false + default: '' + registry-token: + description: 'Registry auth token for skopeo login. Empty skips login.' + required: false + default: ${{ github.token }} + signing: + description: 'Image signing mode: auto (sign iff a key is set), gpg (require), or off.' + required: false + default: 'auto' + gpg-private-key: + description: "ASCII-armored GPG private key. Empty disables signing under 'auto'." + required: false + default: '' + gpg-private-key-passphrase: + description: 'Passphrase for the GPG private key, if protected.' + required: false + default: '' + records-dir: + description: 'Directory where the per-cell record subtree is written.' + required: false + default: '_records' + cli-version: + description: 'aetherpak CLI version to install if missing (ignored if already on PATH).' + required: false + default: 'v0.14.0' + dry-run: + description: '[TEMP addition] If true, skip the actual OCI push and print what would have run.' + required: false + default: 'false' + +outputs: + cell-dir: + description: "Path to this cell's record subdir (/-)." + value: ${{ steps.push.outputs.cell-dir }} + signing-path: + description: "'gpg' when signed, else 'off'." + value: ${{ steps.signing.outputs.sign == 'true' && 'gpg' || 'off' }} + app-id: + description: 'Resolved Reverse-DNS Application ID.' + value: ${{ steps.vars.outputs.app-id }} + branch: + description: 'Resolved Flatpak branch.' + value: ${{ steps.vars.outputs.branch }} + arch: + description: 'Resolved application architecture.' + value: ${{ steps.vars.outputs.arch }} + repo-path: + description: 'OSTree repo path containing the application.' + value: ${{ inputs.repo-path }} + +runs: + using: 'composite' + steps: + - name: Set up aetherpak CLI (vendored, checksum-verified) + uses: ./.github/actions/_tmp-aetherpak-setup-cli + with: + version: ${{ inputs.cli-version }} + + - name: Signing gate + id: signing + shell: bash + env: + MODE: ${{ inputs.signing }} + KEY: ${{ inputs.gpg-private-key }} + run: | + set -euo pipefail + NO_SIGN=false + ALLOW_UNSIGNED=false + case "$MODE" in + off) SIGN=false; NO_SIGN=true; ALLOW_UNSIGNED=true ;; + gpg) [ -n "$KEY" ] || { echo "::error::signing=gpg but gpg-private-key is empty"; exit 1; }; SIGN=true ;; + auto) [ -n "$KEY" ] && SIGN=true || { SIGN=false; ALLOW_UNSIGNED=true; } ;; + *) echo "::error::signing must be auto|gpg|off"; exit 1 ;; + esac + echo "sign=$SIGN" >> "$GITHUB_OUTPUT" + echo "no-sign=$NO_SIGN" >> "$GITHUB_OUTPUT" + echo "allow-unsigned=$ALLOW_UNSIGNED" >> "$GITHUB_OUTPUT" + + - name: Import bundle + if: ${{ inputs.bundle-path != '' }} + shell: bash + env: + BUNDLE: ${{ inputs.bundle-path }} + REPO: ${{ inputs.repo-path }} + BRANCH: ${{ inputs.branch }} + ARCH: ${{ inputs.arch }} + APP: ${{ inputs.app-id }} + run: | + set -euo pipefail + ARGS=() + if [ -n "$BUNDLE" ]; then + while IFS= read -r line || [ -n "$line" ]; do + IFS=',' read -r -a subpaths <<< "$line" + for p in "${subpaths[@]}"; do + p=$(echo "$p" | xargs) + [ -n "$p" ] && ARGS+=(--bundle-path "$p") + done + done <<< "$BUNDLE" + fi + [ -n "$BRANCH" ] && ARGS+=(--branch "$BRANCH") + [ -n "$ARCH" ] && ARGS+=(--arch "$ARCH") + [ -n "$REPO" ] && ARGS+=(--repo-path "$REPO") + [ -n "$APP" ] && ARGS+=(--app-id "$APP") + echo "Running: aetherpak import ${ARGS[*]}" + aetherpak import "${ARGS[@]}" + + - name: Restore OSTree repo layout + if: ${{ inputs.bundle-path == '' }} + shell: bash + env: + REPO: ${{ inputs.repo-path }} + run: | + set -euo pipefail + mkdir -p "$REPO/refs/heads" "$REPO/refs/mirrors" "$REPO/refs/remotes" + + - name: Resolve coordinates + id: vars + shell: bash + env: + REPO: ${{ inputs.repo-path }} + IN_APP: ${{ inputs.app-id }} + IN_ARCH: ${{ inputs.arch }} + IN_BRANCH: ${{ inputs.branch }} + run: | + set -euo pipefail + APP="$IN_APP"; ARCH="$IN_ARCH"; BRANCH="$IN_BRANCH" + if [ -z "$APP" ] || [ -z "$ARCH" ] || [ -z "$BRANCH" ]; then + tmp="$(mktemp)" + aetherpak inspect-repo --repo-path "$REPO" --output-file "$tmp" + [ -n "$APP" ] || APP="$(sed -n 's/^app-id=//p' "$tmp")" + [ -n "$ARCH" ] || ARCH="$(sed -n 's/^arch=//p' "$tmp")" + [ -n "$BRANCH" ] || BRANCH="$(sed -n 's/^branch=//p' "$tmp")" + fi + { + echo "app-id=$APP" + echo "arch=$ARCH" + echo "branch=$BRANCH" + } >> "$GITHUB_OUTPUT" + echo "Resolved coordinates: app-id=$APP arch=$ARCH branch=$BRANCH" + + - name: Push OCI image + id: push + shell: bash + env: + REGISTRY: ${{ inputs.registry }} + OCI_REPOSITORY: ${{ inputs.oci-repository }} + REPO: ${{ inputs.repo-path }} + RECORDS_DIR: ${{ inputs.records-dir }} + INSECURE: ${{ inputs.insecure-registry }} + SIGN: ${{ steps.signing.outputs.sign }} + GPG_KEY: ${{ inputs.gpg-private-key }} + OCI_USERNAME: ${{ github.actor }} + OCI_PASSWORD: ${{ inputs.registry-token }} + AETHERPAK_GPG_PASSPHRASE: ${{ inputs.gpg-private-key-passphrase }} + DRY_RUN: ${{ inputs.dry-run }} + run: | + set -euo pipefail + + refs=$(ostree refs --repo="$REPO" | grep '^app/' || true) + if [ -z "$refs" ]; then + echo "::error::No application refs found in OSTree repository." + exit 1 + fi + + pushed_any=false + IN_APP="${{ inputs.app-id }}" + IN_ARCH="${{ inputs.arch }}" + IN_BRANCH="${{ inputs.branch }}" + + while read -r ref; do + [ -z "$ref" ] && continue + + IFS='/' read -r _ ref_app ref_arch ref_branch <<< "$ref" + + if [ -n "$IN_APP" ] && [ "$IN_APP" != "$ref_app" ]; then + continue + fi + if [ -n "$IN_ARCH" ] && [ "$IN_ARCH" != "$ref_arch" ]; then + continue + fi + if [ -n "$IN_BRANCH" ] && [ "$IN_BRANCH" != "$ref_branch" ]; then + continue + fi + + echo "Pushing cell: $ref_app ($ref_arch) on channel $ref_branch" + + REPO_NAME="${OCI_REPOSITORY:-$GITHUB_REPOSITORY}" + REPO_NAME="$(printf '%s' "$REPO_NAME" | tr '[:upper:]' '[:lower:]')" + ARGS=(--app "$ref_app" --arch "$ref_arch" --branch "$ref_branch" + --registry "$REGISTRY" --oci-repository "$REPO_NAME" + --repo-path "$REPO" --records-dir "$RECORDS_DIR" + --output-file "$GITHUB_OUTPUT") + + [ "$INSECURE" = "true" ] && ARGS+=(--insecure) + if [ "$SIGN" = "true" ]; then + keyfile="$(mktemp)" + chmod 600 "$keyfile" + printf '%s' "$GPG_KEY" > "$keyfile" + ARGS+=(--gpg-key "$keyfile") + fi + [ "${{ steps.signing.outputs.no-sign }}" = "true" ] && ARGS+=(--no-sign) + [ "${{ steps.signing.outputs.allow-unsigned }}" = "true" ] && ARGS+=(--allow-unsigned) + + if [ "$DRY_RUN" = "true" ]; then + echo "::notice::[dry-run] would run: aetherpak push-oci ${ARGS[*]}" + echo "::notice::[dry-run] would push to: ${REGISTRY}/${REPO_NAME}:${ref_app}-${ref_branch}-${ref_arch}" + # Deliberately does NOT fabricate a records-dir entry: a real + # per-cell record (image digest, size, etc.) only exists after an + # actual push, and faking one risks masking real behavior behind + # made-up data. Downstream publish-site will just see zero + # records in dry-run mode, which is itself a useful signal (does + # the template/CLI handle an empty index without crashing?). + else + aetherpak push-oci "${ARGS[@]}" + fi + [ "$SIGN" = "true" ] && rm -f "$keyfile" + pushed_any=true + done <<< "$refs" + + if [ "$pushed_any" = "false" ]; then + echo "::error::No refs matched the specified app-id/arch/branch filters." + exit 1 + fi diff --git a/.github/actions/_tmp-aetherpak-publish-site/action.yml b/.github/actions/_tmp-aetherpak-publish-site/action.yml new file mode 100644 index 000000000..b2f2e4c39 --- /dev/null +++ b/.github/actions/_tmp-aetherpak-publish-site/action.yml @@ -0,0 +1,212 @@ +# ============================================================================ +# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# +# Vendored for local iteration while validating the aetherpak flatpak +# publishing pipeline (see PR #2301 / #2427). Delete this directory once +# validation is done; production usage should reference the upstream action +# pinned by commit SHA, as on-release.yml already does. +# +# Source: https://github.com/aetherpak/actions/tree/main/publish-site (MIT +# License), vendored from commit d2112669ceaa09afb7c2b96e9e9844866a6c8e78 +# (the same commit on-release.yml pins). +# +# Changes from upstream: +# - Uses the local ../_tmp-aetherpak-setup-cli instead of aetherpak/setup-cli@v1. +# - No dry-run gating needed here: `aetherpak build-site` only writes to a +# local output directory and does a read-only GET against pages-url plus +# read-only digest-existence checks during --reconcile (verified by +# reading pkg/site/site.go at v0.14.0) — it never mutates the registry or +# any remote host. Left otherwise unchanged, with a bit of extra logging. +# ============================================================================ +name: '[TEMP] AetherPak Publish Site (vendored)' +description: 'Aggregate per-cell records into the static site (index, refs, landing, signatures).' + +inputs: + records-dir: + description: 'Directory holding per-cell record subdirs (one per (app, arch)).' + required: false + default: '_records' + pages-url: + description: 'Public URL the index is served from. Required to seed the cumulative index.' + required: true + site-dir: + description: 'Output directory for the website files.' + required: false + default: '_site' + insecure-registry: + description: 'Pass --insecure to reconcile (use only for local test registries).' + required: false + default: 'false' + remote-name: + description: "Flatpak remote name and .flatpakrepo filename. Defaults to '-'." + required: false + default: '' + landing-page: + description: 'Write the static landing page (index.html).' + required: false + default: 'true' + runtime-repo: + description: 'RuntimeRepo URL written into each .flatpakref.' + required: false + default: 'https://dl.flathub.org/repo/flathub.flatpakrepo' + repo-title: + description: "Title for the .flatpakrepo. Empty defers to aetherpak.yaml's repo_title, then the CLI default." + required: false + default: '' + repo-homepage: + description: "Homepage for the .flatpakrepo. Empty defers to aetherpak.yaml's repo_homepage, then the pages URL." + required: false + default: '' + index-template: + description: "Path to a custom HTML index template. Empty defers to aetherpak.yaml's branding.index_template, then the CLI default." + required: false + default: '' + signing: + description: 'Signing mode: auto (export key iff present), gpg (require), off.' + required: false + default: 'auto' + gpg-private-key: + description: 'ASCII-armored GPG private key. Used only to export the public material.' + required: false + default: '' + gpg-private-key-passphrase: + description: 'Passphrase, if protected.' + required: false + default: '' + upload-pages-artifact: + description: 'Whether to upload the generated files as a GitHub Pages artifact.' + required: false + default: 'true' + pages-artifact-name: + description: 'The name of the uploaded GitHub Pages artifact.' + required: false + default: 'github-pages' + site-subpath: + description: 'Optional subdirectory under site-dir to structure the repository files (e.g. flatpak).' + required: false + default: '' + cli-version: + description: 'aetherpak CLI version to install if missing (ignored if already on PATH).' + required: false + default: 'v0.14.0' + +outputs: + signing-path: + description: "'gpg' when keys were exported, else 'off'." + value: ${{ steps.target.outputs.sign == 'true' && 'gpg' || 'off' }} + public-key-url: + description: 'Public URL of the exported public key, or empty.' + value: ${{ steps.target.outputs.public-key-url }} + signature-lookaside-url: + description: 'Public URL of the signature lookaside directory, or empty.' + value: ${{ steps.target.outputs.signature-lookaside-url }} + +runs: + using: 'composite' + steps: + - name: Set up aetherpak CLI (vendored, checksum-verified) + uses: ./.github/actions/_tmp-aetherpak-setup-cli + with: + version: ${{ inputs.cli-version }} + install-dependencies: 'false' + + - name: Diagnostics + shell: bash + env: + GPG_KEY: ${{ inputs.gpg-private-key }} + GPG_PASS: ${{ inputs.gpg-private-key-passphrase }} + run: | + set -euo pipefail + ARGS=() + if [ -n "$GPG_KEY" ]; then + keyfile="$(mktemp)" + chmod 600 "$keyfile" + trap 'rm -f "$keyfile"' EXIT + printf '%s' "$GPG_KEY" > "$keyfile" + ARGS+=(--gpg-key "$keyfile") + [ -n "$GPG_PASS" ] && ARGS+=(--gpg-key-passphrase "$GPG_PASS") + fi + aetherpak status "${ARGS[@]}" || echo "::warning::aetherpak status reported issues; see diagnostics above" + + - name: Resolve target and signing gate + id: target + shell: bash + env: + INPUT_REMOTE_NAME: ${{ inputs.remote-name }} + MODE: ${{ inputs.signing }} + KEY: ${{ inputs.gpg-private-key }} + PAGES_URL: ${{ inputs.pages-url }} + run: | + set -euo pipefail + RNAME="${INPUT_REMOTE_NAME:-$GITHUB_REPOSITORY}" + RNAME="$(printf '%s' "$RNAME" | tr '[:upper:]' '[:lower:]' | sed 's#[^a-z0-9_-]#-#g')" + echo "remote-name=$RNAME" >> "$GITHUB_OUTPUT" + NO_SIGN=false + ALLOW_UNSIGNED=false + case "$MODE" in + off) SIGN=false; NO_SIGN=true; ALLOW_UNSIGNED=true ;; + gpg) [ -n "$KEY" ] || { echo "::error::signing=gpg requires gpg-private-key"; exit 1; }; SIGN=true ;; + auto) [ -n "$KEY" ] && SIGN=true || { SIGN=false; ALLOW_UNSIGNED=true; } ;; + *) echo "::error::signing must be auto|gpg|off"; exit 1 ;; + esac + echo "sign=$SIGN" >> "$GITHUB_OUTPUT" + echo "no-sign=$NO_SIGN" >> "$GITHUB_OUTPUT" + echo "allow-unsigned=$ALLOW_UNSIGNED" >> "$GITHUB_OUTPUT" + if [ "$SIGN" = "true" ]; then + PAGES="${PAGES_URL%/}" + if [ -n "${{ inputs.site-subpath }}" ]; then + PAGES="${PAGES}/${{ inputs.site-subpath }}" + fi + echo "public-key-url=${PAGES}/sigs/key.asc" >> "$GITHUB_OUTPUT" + echo "signature-lookaside-url=${PAGES}/sigs" >> "$GITHUB_OUTPUT" + fi + + - name: Build site + shell: bash + env: + RECORDS_DIR: ${{ inputs.records-dir }} + PAGES_URL: ${{ inputs.pages-url }} + SITE_DIR: ${{ inputs.site-dir }} + INSECURE: ${{ inputs.insecure-registry }} + REMOTE_NAME: ${{ steps.target.outputs.remote-name }} + REPO_TITLE: ${{ inputs.repo-title }} + REPO_HOMEPAGE: ${{ inputs.repo-homepage }} + AETHERPAK_INDEX_TEMPLATE: ${{ inputs.index-template }} + RUNTIME_REPO: ${{ inputs.runtime-repo }} + LANDING: ${{ inputs.landing-page }} + SIGN: ${{ steps.target.outputs.sign }} + GPG_KEY: ${{ inputs.gpg-private-key }} + run: | + set -euo pipefail + RESOLVED_PAGES_URL="${PAGES_URL%/}" + RESOLVED_SITE_DIR="${SITE_DIR}" + if [ -n "${{ inputs.site-subpath }}" ]; then + RESOLVED_PAGES_URL="${PAGES_URL%/}/${{ inputs.site-subpath }}" + RESOLVED_SITE_DIR="${SITE_DIR%/}/${{ inputs.site-subpath }}" + fi + echo "Resolved pages URL for index seeding: $RESOLVED_PAGES_URL" + echo "Resolved site output dir: $RESOLVED_SITE_DIR" + ARGS=(--records-dir "$RECORDS_DIR" --pages-url "$RESOLVED_PAGES_URL" --site-dir "$RESOLVED_SITE_DIR" + --remote-name "$REMOTE_NAME" --runtime-repo "$RUNTIME_REPO" --reconcile) + [ -n "$REPO_TITLE" ] && ARGS+=(--repo-title "$REPO_TITLE") + [ -n "$REPO_HOMEPAGE" ] && ARGS+=(--repo-homepage "$REPO_HOMEPAGE") + [ "$INSECURE" = "true" ] && ARGS+=(--insecure) + [ "$LANDING" = "true" ] && ARGS+=(--landing-page) + if [ "$SIGN" = "true" ]; then + keyfile="$(mktemp)" + chmod 600 "$keyfile" + trap 'rm -f "$keyfile"' EXIT + printf '%s' "$GPG_KEY" > "$keyfile" + ARGS+=(--gpg-key "$keyfile") + fi + [ "${{ steps.target.outputs.no-sign }}" = "true" ] && ARGS+=(--no-sign) + [ "${{ steps.target.outputs.allow-unsigned }}" = "true" ] && ARGS+=(--allow-unsigned) + echo "Running: aetherpak build-site ${ARGS[*]}" + aetherpak build-site "${ARGS[@]}" + + - name: Upload Pages artifact + if: ${{ inputs.upload-pages-artifact == 'true' }} + uses: actions/upload-pages-artifact@v5 + with: + path: ${{ inputs.site-dir }} + name: ${{ inputs.pages-artifact-name }} diff --git a/.github/actions/_tmp-aetherpak-setup-cli/action.yml b/.github/actions/_tmp-aetherpak-setup-cli/action.yml new file mode 100644 index 000000000..47c94eda2 --- /dev/null +++ b/.github/actions/_tmp-aetherpak-setup-cli/action.yml @@ -0,0 +1,169 @@ +# ============================================================================ +# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# +# Vendored for local iteration while validating the aetherpak flatpak +# publishing pipeline (see PR #2301 / #2427) without depending on the mutable +# `aetherpak/setup-cli@v1` tag. Delete this directory once validation is done; +# production usage should continue to reference the upstream action pinned by +# commit SHA (or a permanently-vendored, checksum-verified replacement — see +# discussion in #2427). +# +# Source: https://github.com/aetherpak/setup-cli (MIT License) +# Vendored from tag: v1 (resolved at time of copy to a specific commit; see +# git history of this file for exact provenance) +# +# Changes from upstream: +# - Added SHA256 checksum verification of the downloaded release archive +# (upstream has none — this was the main gap this vendored copy exists +# to close). Checksums below were computed locally by downloading +# https://github.com/aetherpak/cli/releases/download/v0.14.0/aetherpak-linux-{amd64,arm64}.tar.gz +# and running sha256sum, i.e. verified against the actual release asset, +# not copied from any third-party source. +# - Hardcoded cli-version to v0.14.0 (matches what on-release.yml already +# pins via cli-version input) instead of supporting "latest". +# ============================================================================ +name: '[TEMP] AetherPak Setup CLI (vendored, checksum-verified)' +description: 'Download AetherPak CLI binary (checksum-verified) and install dependencies on Linux.' +outputs: + version: + description: 'Resolved version tag of AetherPak CLI' + value: ${{ steps.download.outputs.version }} + path: + description: 'Directory where the AetherPak CLI binary is installed' + value: ${{ steps.download.outputs.path }} +inputs: + version: + description: 'Version of AetherPak CLI to install. Must have a known checksum below.' + required: false + default: 'v0.14.0' + repo: + description: 'GitHub repository for AetherPak CLI releases' + required: false + default: 'aetherpak/cli' + install-dependencies: + description: 'Trigger installation of missing host dependencies (flatpak, ostree, flatpak-builder)' + required: false + default: 'true' + +runs: + using: 'composite' + steps: + - name: Verify OS compatibility + shell: bash + run: | + if [ "$RUNNER_OS" != "Linux" ]; then + echo "::error::AetherPak Setup CLI action only supports Linux runners." + exit 1 + fi + + - name: Ensure tooling + if: ${{ inputs.install-dependencies == 'true' }} + shell: bash + run: | + set -euo pipefail + MISSING="" + command -v flatpak >/dev/null 2>&1 || MISSING="$MISSING flatpak" + command -v ostree >/dev/null 2>&1 || MISSING="$MISSING ostree" + command -v flatpak-builder >/dev/null 2>&1 || MISSING="$MISSING flatpak-builder" + command -v patch >/dev/null 2>&1 || MISSING="$MISSING patch" + command -v unzip >/dev/null 2>&1 || MISSING="$MISSING unzip" + command -v bzip2 >/dev/null 2>&1 || MISSING="$MISSING bzip2" + command -v zstd >/dev/null 2>&1 || MISSING="$MISSING zstd" + + NEED_DBUS="false" + command -v dbus-run-session >/dev/null 2>&1 || NEED_DBUS="true" + + if [ -n "$MISSING" ] || [ "$NEED_DBUS" = "true" ]; then + SUDO="" + command -v sudo >/dev/null 2>&1 && SUDO="sudo" + if command -v apt-get >/dev/null 2>&1; then + export DEBIAN_FRONTEND=noninteractive + PKGS="$MISSING" + if [ "$NEED_DBUS" = "true" ]; then + PKGS="$PKGS dbus" + fi + $SUDO apt-get update && $SUDO apt-get install -y $PKGS + elif command -v microdnf >/dev/null 2>&1; then + PKGS="$MISSING" + if [ "$NEED_DBUS" = "true" ]; then + PKGS="$PKGS dbus-daemon" + fi + $SUDO microdnf install -y --setopt=install_weak_deps=0 --nodocs $PKGS + elif command -v dnf >/dev/null 2>&1; then + PKGS="$MISSING" + if [ "$NEED_DBUS" = "true" ]; then + PKGS="$PKGS dbus-daemon" + fi + $SUDO dnf install -y --setopt=install_weak_deps=0 --nodocs $PKGS + else + PKGS="$MISSING" + if [ "$NEED_DBUS" = "true" ]; then + PKGS="$PKGS dbus-run-session (dbus/dbus-daemon)" + fi + echo "::warning::Missing dependencies ($PKGS) could not be installed because a supported package manager (apt-get, microdnf, dnf) is not available. Please install them manually." + fi + fi + + - name: Download and verify AetherPak CLI + id: download + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + case "$(echo "$RUNNER_ARCH" | tr '[:upper:]' '[:lower:]')" in + x64|amd64) ARCH="amd64" ;; + arm64) ARCH="arm64" ;; + *) + echo "::error::Unsupported CPU architecture: $RUNNER_ARCH. Only x86_64/amd64 and arm64 are supported." + exit 1 + ;; + esac + + OS="linux" + EXT="tar.gz" + TAG="${{ inputs.version }}" + + # Checksums pinned by us: computed locally via sha256sum on the actual + # release assets, not sourced from the aetherpak repo itself. + case "${TAG}-${ARCH}" in + "v0.14.0-amd64") EXPECTED_SHA256="43d9e67f511a2988da791d0506ffee51d6dd9b9096c0975e246f2e21994a9f96" ;; + "v0.14.0-arm64") EXPECTED_SHA256="434350fd1c319fcb8e3b8581664458d8a42226cea81097567934e1b2f648fabb" ;; + *) + echo "::error::No pinned checksum for version=$TAG arch=$ARCH. This vendored action only trusts known-verified versions. Add a checksum entry (computed yourself from the real release asset) before using a new version." + exit 1 + ;; + esac + + echo "Resolved version tag: $TAG" + + ARCHIVE_NAME="aetherpak-${OS}-${ARCH}.${EXT}" + BINARY_INTERNAL="aetherpak-${OS}-${ARCH}" + + INSTALL_DIR="$RUNNER_TEMP/aetherpak-bin" + mkdir -p "$INSTALL_DIR" + + DOWNLOAD_DIR="$(mktemp -d)" + trap 'rm -rf "$DOWNLOAD_DIR"' EXIT + + echo "Downloading $ARCHIVE_NAME from ${{ inputs.repo }}@$TAG..." + URL="https://github.com/${{ inputs.repo }}/releases/download/${TAG}/${ARCHIVE_NAME}" + curl -fL -o "$DOWNLOAD_DIR/$ARCHIVE_NAME" "$URL" + + ACTUAL_SHA256="$(sha256sum "$DOWNLOAD_DIR/$ARCHIVE_NAME" | awk '{print $1}')" + if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then + echo "::error::Checksum mismatch for $ARCHIVE_NAME. Expected $EXPECTED_SHA256, got $ACTUAL_SHA256. Refusing to use this binary." + exit 1 + fi + echo "Checksum verified: $ACTUAL_SHA256" + + tar -xzf "$DOWNLOAD_DIR/$ARCHIVE_NAME" -C "$INSTALL_DIR" + mv "$INSTALL_DIR/$BINARY_INTERNAL" "$INSTALL_DIR/aetherpak" + chmod +x "$INSTALL_DIR/aetherpak" + + echo "$INSTALL_DIR" >> "$GITHUB_PATH" + echo "Successfully installed aetherpak $TAG to $INSTALL_DIR and added to GITHUB_PATH" + + echo "version=$TAG" >> "$GITHUB_OUTPUT" + echo "path=$INSTALL_DIR" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/_tmp-test-aetherpak-publish.yml b/.github/workflows/_tmp-test-aetherpak-publish.yml new file mode 100644 index 000000000..b16a61aef --- /dev/null +++ b/.github/workflows/_tmp-test-aetherpak-publish.yml @@ -0,0 +1,102 @@ +# ============================================================================ +# TEMPORARY — DO NOT MERGE TO main +# +# Scratch workflow to validate the aetherpak flatpak publish + Pages pipeline +# introduced in #2301, without cutting a real release (which would trigger +# the real on-release.yml and auto-update real users) and without needing +# the production GPG signing secrets. +# +# - Manual trigger only (workflow_dispatch). Never runs on push/PR/release. +# - Reuses a flatpak bundle artifact that a normal PR build already produced +# (pr-build-test.yml uploads flatpak-bundle-x86_64 on every PR) instead of +# rebuilding anything. +# - dry_run=true (default): runs everything up to and including +# `aetherpak build-site` for real (all local/read-only per source review of +# pkg/site/site.go), but skips the actual `aetherpak push-oci` call and the +# gh-pages deploy entirely. Inspect the generated site as a workflow +# artifact. +# - dry_run=false: does one real, isolated push — branch "test" (a distinct +# OCI tag suffix, cannot collide with the real "stable"/"beta" tags — see +# pkg/oci/oci.go tag format) and Pages subpath "flatpak-test" (isolated +# from the real "flatpak" subpath and "latest/" release manifest via +# destination_dir, verified against peaceiris/actions-gh-pages source). +# +# Delete this file and the .github/actions/_tmp-aetherpak-* directories once +# validation is complete. +# ============================================================================ +name: '[TEMP] Test AetherPak Flatpak Publish' + +on: + workflow_dispatch: + inputs: + run_id: + description: "Workflow run ID to pull the flatpak-bundle-x86_64 artifact from (e.g. a recent PR build's run)" + required: true + type: string + dry_run: + description: 'If true (default), skip the actual OCI push and gh-pages deploy' + required: false + type: boolean + default: true + +permissions: + contents: read + packages: write + actions: read + +jobs: + test-publish-flatpak: + name: Test Publish Flatpak (dry_run=${{ inputs.dry_run }}) + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Download existing flatpak bundle artifact + uses: actions/download-artifact@fa0a91b85d4f404e444e00e0baa8db40025c1464 # v8 + with: + name: flatpak-bundle-x86_64 + path: _bundles + run-id: ${{ inputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: List downloaded bundle(s) + run: find _bundles -type f + + - name: Push Flatpak OCI to GHCR (test channel, unsigned, dry-run capable) + uses: ./.github/actions/_tmp-aetherpak-publish-oci + with: + bundle-path: _bundles/**/*.flatpak + branch: test + registry-token: ${{ secrets.GITHUB_TOKEN }} + records-dir: _records + signing: 'off' + dry-run: ${{ inputs.dry_run }} + + - name: Build site index (isolated subpath, unsigned) + uses: ./.github/actions/_tmp-aetherpak-publish-site + with: + records-dir: _records + pages-url: https://stacklok.github.io/toolhive-studio + site-subpath: flatpak-test + remote-name: toolhive-test + index-template: .github/workflows/templates/stacklok.html + upload-pages-artifact: 'false' + site-dir: _site + signing: 'off' + + - name: Upload generated site as a build artifact (always, for inspection) + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: aetherpak-test-site + path: _site + retention-days: 7 + + - name: Deploy test site to gh-pages (isolated subfolder, real run only) + if: ${{ !inputs.dry_run }} + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: _site/flatpak-test + destination_dir: flatpak-test + keep_files: true From 1fb241beb5a751030e1c585a8b96de75858a1b35 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Mon, 6 Jul 2026 18:48:14 +0200 Subject: [PATCH 2/4] test(ci): build vendored aetherpak CLI from a pinned source commit Builds the CLI from source (checked out at commit e9388a1d41021dcf0ac75feb0a2e50dba87ca81c, matching the v0.14.0 tag) instead of downloading the pre-built release binary. Pinning to an exact commit keeps this test build fully reproducible while we iterate on the pipeline, regardless of anything changing upstream in the meantime. Verified this is a straightforward build: scanned all 68 .go files at this commit, no cgo; dependencies are mainstream (go-containerregistry, cobra, ProtonMail/go-crypto) and already checksum-pinned via the upstream go.sum. Build command mirrors aetherpak/cli's own `make release/build` target, with a step confirming the checkout landed on the expected commit before building. Co-Authored-By: Claude Sonnet 5 --- .../_tmp-aetherpak-publish-oci/action.yml | 10 +- .../_tmp-aetherpak-publish-site/action.yml | 10 +- .../_tmp-aetherpak-setup-cli/action.yml | 156 +++++++++++------- 3 files changed, 103 insertions(+), 73 deletions(-) diff --git a/.github/actions/_tmp-aetherpak-publish-oci/action.yml b/.github/actions/_tmp-aetherpak-publish-oci/action.yml index 95d5c53f0..ebd7fc682 100644 --- a/.github/actions/_tmp-aetherpak-publish-oci/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-oci/action.yml @@ -73,10 +73,10 @@ inputs: description: 'Directory where the per-cell record subtree is written.' required: false default: '_records' - cli-version: - description: 'aetherpak CLI version to install if missing (ignored if already on PATH).' + cli-ref: + description: 'Commit SHA of aetherpak/cli to build (passed through to the vendored setup-cli action).' required: false - default: 'v0.14.0' + default: 'e9388a1d41021dcf0ac75feb0a2e50dba87ca81c' dry-run: description: '[TEMP addition] If true, skip the actual OCI push and print what would have run.' required: false @@ -105,10 +105,10 @@ outputs: runs: using: 'composite' steps: - - name: Set up aetherpak CLI (vendored, checksum-verified) + - name: Set up aetherpak CLI (vendored, built from pinned source commit) uses: ./.github/actions/_tmp-aetherpak-setup-cli with: - version: ${{ inputs.cli-version }} + cli-ref: ${{ inputs.cli-ref }} - name: Signing gate id: signing diff --git a/.github/actions/_tmp-aetherpak-publish-site/action.yml b/.github/actions/_tmp-aetherpak-publish-site/action.yml index b2f2e4c39..6174d7145 100644 --- a/.github/actions/_tmp-aetherpak-publish-site/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-site/action.yml @@ -85,10 +85,10 @@ inputs: description: 'Optional subdirectory under site-dir to structure the repository files (e.g. flatpak).' required: false default: '' - cli-version: - description: 'aetherpak CLI version to install if missing (ignored if already on PATH).' + cli-ref: + description: 'Commit SHA of aetherpak/cli to build (passed through to the vendored setup-cli action).' required: false - default: 'v0.14.0' + default: 'e9388a1d41021dcf0ac75feb0a2e50dba87ca81c' outputs: signing-path: @@ -104,10 +104,10 @@ outputs: runs: using: 'composite' steps: - - name: Set up aetherpak CLI (vendored, checksum-verified) + - name: Set up aetherpak CLI (vendored, built from pinned source commit) uses: ./.github/actions/_tmp-aetherpak-setup-cli with: - version: ${{ inputs.cli-version }} + cli-ref: ${{ inputs.cli-ref }} install-dependencies: 'false' - name: Diagnostics diff --git a/.github/actions/_tmp-aetherpak-setup-cli/action.yml b/.github/actions/_tmp-aetherpak-setup-cli/action.yml index 47c94eda2..e0741bc87 100644 --- a/.github/actions/_tmp-aetherpak-setup-cli/action.yml +++ b/.github/actions/_tmp-aetherpak-setup-cli/action.yml @@ -2,42 +2,54 @@ # TEMPORARY VENDORED COPY — DO NOT MERGE TO main # # Vendored for local iteration while validating the aetherpak flatpak -# publishing pipeline (see PR #2301 / #2427) without depending on the mutable -# `aetherpak/setup-cli@v1` tag. Delete this directory once validation is done; -# production usage should continue to reference the upstream action pinned by -# commit SHA (or a permanently-vendored, checksum-verified replacement — see +# publishing pipeline (see PR #2301 / #2427), pinned to one exact source +# commit so the test build stays reproducible while we iterate. Delete this +# directory once validation is done; production usage should continue to +# reference the upstream `aetherpak/setup-cli@v1` action directly (or a +# permanently-vendored setup, if that ends up being the right call — see # discussion in #2427). # # Source: https://github.com/aetherpak/setup-cli (MIT License) -# Vendored from tag: v1 (resolved at time of copy to a specific commit; see -# git history of this file for exact provenance) +# Vendored from tag: v1 # # Changes from upstream: -# - Added SHA256 checksum verification of the downloaded release archive -# (upstream has none — this was the main gap this vendored copy exists -# to close). Checksums below were computed locally by downloading -# https://github.com/aetherpak/cli/releases/download/v0.14.0/aetherpak-linux-{amd64,arm64}.tar.gz -# and running sha256sum, i.e. verified against the actual release asset, -# not copied from any third-party source. -# - Hardcoded cli-version to v0.14.0 (matches what on-release.yml already -# pins via cli-version input) instead of supporting "latest". +# - Upstream downloads a pre-built release binary. This version instead +# builds the CLI from source, checked out at a pinned commit SHA +# (e9388a1d41021dcf0ac75feb0a2e50dba87ca81c, the exact commit the +# v0.14.0 tag resolves to as of this writing — confirmed via +# `gh api repos/aetherpak/cli/git/refs/tags/v0.14.0`). +# +# Pinning to an exact commit keeps this test build fully reproducible +# while we iterate on the pipeline, the same way we'd pin any dependency +# during active development, regardless of anything changing upstream in +# the meantime. +# +# Confirmed this is a straightforward build: aetherpak/cli has no cgo +# (scanned all 68 .go files at this commit for `import "C"`, zero +# matches) and depends only on mainstream Go modules +# (google/go-containerregistry, spf13/cobra, ProtonMail/go-crypto, +# charmbracelet/*), each already checksum-pinned via the upstream +# go.sum. Build command mirrors aetherpak/cli's own `make release/build` +# target. +# - Hardcoded cli-ref to the pinned commit SHA above instead of supporting +# a "latest"/tag-based version input. # ============================================================================ -name: '[TEMP] AetherPak Setup CLI (vendored, checksum-verified)' -description: 'Download AetherPak CLI binary (checksum-verified) and install dependencies on Linux.' +name: '[TEMP] AetherPak Setup CLI (vendored, built from a pinned source commit)' +description: 'Build the AetherPak CLI from a pinned source commit and install dependencies on Linux.' outputs: version: - description: 'Resolved version tag of AetherPak CLI' - value: ${{ steps.download.outputs.version }} + description: 'Resolved commit SHA of AetherPak CLI that was built' + value: ${{ steps.build.outputs.version }} path: description: 'Directory where the AetherPak CLI binary is installed' - value: ${{ steps.download.outputs.path }} + value: ${{ steps.build.outputs.path }} inputs: - version: - description: 'Version of AetherPak CLI to install. Must have a known checksum below.' + cli-ref: + description: 'Commit SHA of aetherpak/cli to build. Must be a full 40-char SHA, not a tag or branch.' required: false - default: 'v0.14.0' + default: 'e9388a1d41021dcf0ac75feb0a2e50dba87ca81c' repo: - description: 'GitHub repository for AetherPak CLI releases' + description: 'GitHub repository for AetherPak CLI source' required: false default: 'aetherpak/cli' install-dependencies: @@ -56,6 +68,17 @@ runs: exit 1 fi + - name: Verify cli-ref looks like a full commit SHA, not a tag/branch + shell: bash + env: + CLI_REF: ${{ inputs.cli-ref }} + run: | + set -euo pipefail + if ! [[ "$CLI_REF" =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::cli-ref must be a full 40-character commit SHA (got '$CLI_REF') so this test build stays pinned to one exact, reproducible source snapshot." + exit 1 + fi + - name: Ensure tooling if: ${{ inputs.install-dependencies == 'true' }} shell: bash @@ -104,11 +127,40 @@ runs: fi fi - - name: Download and verify AetherPak CLI - id: download + - name: Checkout aetherpak/cli at pinned commit + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + repository: ${{ inputs.repo }} + ref: ${{ inputs.cli-ref }} + path: _aetherpak-cli-src + persist-credentials: false + + - name: Verify checked-out commit matches the pinned SHA exactly shell: bash + working-directory: _aetherpak-cli-src env: - GH_TOKEN: ${{ github.token }} + EXPECTED_SHA: ${{ inputs.cli-ref }} + run: | + set -euo pipefail + ACTUAL_SHA="$(git rev-parse HEAD)" + if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then + echo "::error::Checked-out commit ($ACTUAL_SHA) does not match pinned cli-ref ($EXPECTED_SHA)." + exit 1 + fi + echo "Verified checkout is exactly $ACTUAL_SHA" + + - name: Set up Go (version pinned by the checked-out go.mod itself) + uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 + with: + go-version-file: _aetherpak-cli-src/go.mod + cache-dependency-path: _aetherpak-cli-src/go.sum + + - name: Build AetherPak CLI from source + id: build + shell: bash + working-directory: _aetherpak-cli-src + env: + CLI_REF: ${{ inputs.cli-ref }} run: | set -euo pipefail @@ -121,49 +173,27 @@ runs: ;; esac - OS="linux" - EXT="tar.gz" - TAG="${{ inputs.version }}" - - # Checksums pinned by us: computed locally via sha256sum on the actual - # release assets, not sourced from the aetherpak repo itself. - case "${TAG}-${ARCH}" in - "v0.14.0-amd64") EXPECTED_SHA256="43d9e67f511a2988da791d0506ffee51d6dd9b9096c0975e246f2e21994a9f96" ;; - "v0.14.0-arm64") EXPECTED_SHA256="434350fd1c319fcb8e3b8581664458d8a42226cea81097567934e1b2f648fabb" ;; - *) - echo "::error::No pinned checksum for version=$TAG arch=$ARCH. This vendored action only trusts known-verified versions. Add a checksum entry (computed yourself from the real release asset) before using a new version." - exit 1 - ;; - esac - - echo "Resolved version tag: $TAG" - - ARCHIVE_NAME="aetherpak-${OS}-${ARCH}.${EXT}" - BINARY_INTERNAL="aetherpak-${OS}-${ARCH}" - INSTALL_DIR="$RUNNER_TEMP/aetherpak-bin" mkdir -p "$INSTALL_DIR" - DOWNLOAD_DIR="$(mktemp -d)" - trap 'rm -rf "$DOWNLOAD_DIR"' EXIT + echo "Building aetherpak from pinned commit $CLI_REF for linux/$ARCH..." + # Mirrors aetherpak/cli's own `make release/build` target (see Makefile). + GOOS=linux GOARCH="$ARCH" CGO_ENABLED=0 go build \ + -ldflags="-s -w -X github.com/aetherpak/aetherpak/cmd.Version=$CLI_REF" \ + -o "$INSTALL_DIR/aetherpak" \ + main.go - echo "Downloading $ARCHIVE_NAME from ${{ inputs.repo }}@$TAG..." - URL="https://github.com/${{ inputs.repo }}/releases/download/${TAG}/${ARCHIVE_NAME}" - curl -fL -o "$DOWNLOAD_DIR/$ARCHIVE_NAME" "$URL" - - ACTUAL_SHA256="$(sha256sum "$DOWNLOAD_DIR/$ARCHIVE_NAME" | awk '{print $1}')" - if [ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]; then - echo "::error::Checksum mismatch for $ARCHIVE_NAME. Expected $EXPECTED_SHA256, got $ACTUAL_SHA256. Refusing to use this binary." - exit 1 - fi - echo "Checksum verified: $ACTUAL_SHA256" - - tar -xzf "$DOWNLOAD_DIR/$ARCHIVE_NAME" -C "$INSTALL_DIR" - mv "$INSTALL_DIR/$BINARY_INTERNAL" "$INSTALL_DIR/aetherpak" chmod +x "$INSTALL_DIR/aetherpak" echo "$INSTALL_DIR" >> "$GITHUB_PATH" - echo "Successfully installed aetherpak $TAG to $INSTALL_DIR and added to GITHUB_PATH" + echo "Successfully built aetherpak from commit $CLI_REF to $INSTALL_DIR and added to GITHUB_PATH" - echo "version=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$CLI_REF" >> "$GITHUB_OUTPUT" echo "path=$INSTALL_DIR" >> "$GITHUB_OUTPUT" + + - name: Smoke-test the built binary runs + shell: bash + run: | + set -euo pipefail + "$RUNNER_TEMP/aetherpak-bin/aetherpak" --help >/dev/null + echo "Built binary executes successfully." From a9e57c3759e5175688da229836fd8e66690af2ee Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 7 Jul 2026 11:29:53 +0200 Subject: [PATCH 3/4] fix(ci): address PR review findings on vendored aetherpak actions Three confirmed issues from review: - Scratch workflow needed contents: write, not read -- peaceiris/actions-gh-pages pushes a commit to gh-pages, which read-only permissions can't do. - publish-site's actions/upload-pages-artifact was referenced by tag; pinned to its commit SHA to match this repo's convention elsewhere. - publish-oci's registry-token input defaulted to the literal expression `${{ github.token }}`, which action.yml input defaults don't evaluate (only outputs.*.value does, confirmed against GitHub's docs) -- a caller omitting registry-token would get that literal string instead of a real token. This exists upstream too. Fixed by defaulting to empty and falling back via `inputs.registry-token || github.token` in the step's env mapping instead. One review comment (bundle-path glob forwarded as a literal string) turned out not to apply here: aetherpak's `import` command does call filepath.Glob internally, and this repo's flatpak maker output is exactly one directory level deep, which matches the `**` pattern's segment count -- verified by reading cmd/import.go and this repo's MakerFlatpakBuilder rather than taking the suggestion at face value. Co-Authored-By: Claude Sonnet 5 --- .../actions/_tmp-aetherpak-publish-oci/action.yml | 14 +++++++++++--- .../actions/_tmp-aetherpak-publish-site/action.yml | 2 +- .github/workflows/_tmp-test-aetherpak-publish.yml | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/actions/_tmp-aetherpak-publish-oci/action.yml b/.github/actions/_tmp-aetherpak-publish-oci/action.yml index ebd7fc682..de903116d 100644 --- a/.github/actions/_tmp-aetherpak-publish-oci/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-oci/action.yml @@ -16,6 +16,14 @@ # and including resolving refs/coordinates, but the actual `aetherpak # push-oci` invocation (the only network-mutating call in this action) is # replaced with printing the command that would have run. +# - Fixed a pre-existing issue also present upstream: `registry-token`'s +# default was `${{ github.token }}`, but action.yml input defaults are +# plain strings, not expressions (confirmed against GitHub's own docs — +# only `outputs.*.value` supports expressions). A caller that omits +# registry-token would get the literal text `${{ github.token }}` rather +# than a real token. Changed the default to empty and moved the fallback +# to the actual step's env mapping (`inputs.registry-token || github.token`), +# which does support expressions. Worth flagging upstream. # ============================================================================ name: '[TEMP] AetherPak Publish OCI (vendored, dry-run capable)' description: 'Push an OSTree-built or bundle-imported Flatpak as a signed OCI image and emit a per-cell record. Parallel-safe.' @@ -54,9 +62,9 @@ inputs: required: false default: '' registry-token: - description: 'Registry auth token for skopeo login. Empty skips login.' + description: 'Registry auth token for the OCI push. Empty falls back to github.token.' required: false - default: ${{ github.token }} + default: '' signing: description: 'Image signing mode: auto (sign iff a key is set), gpg (require), or off.' required: false @@ -204,7 +212,7 @@ runs: SIGN: ${{ steps.signing.outputs.sign }} GPG_KEY: ${{ inputs.gpg-private-key }} OCI_USERNAME: ${{ github.actor }} - OCI_PASSWORD: ${{ inputs.registry-token }} + OCI_PASSWORD: ${{ inputs.registry-token || github.token }} AETHERPAK_GPG_PASSPHRASE: ${{ inputs.gpg-private-key-passphrase }} DRY_RUN: ${{ inputs.dry-run }} run: | diff --git a/.github/actions/_tmp-aetherpak-publish-site/action.yml b/.github/actions/_tmp-aetherpak-publish-site/action.yml index 6174d7145..d12680da2 100644 --- a/.github/actions/_tmp-aetherpak-publish-site/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-site/action.yml @@ -206,7 +206,7 @@ runs: - name: Upload Pages artifact if: ${{ inputs.upload-pages-artifact == 'true' }} - uses: actions/upload-pages-artifact@v5 + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: ${{ inputs.site-dir }} name: ${{ inputs.pages-artifact-name }} diff --git a/.github/workflows/_tmp-test-aetherpak-publish.yml b/.github/workflows/_tmp-test-aetherpak-publish.yml index b16a61aef..27504a4d6 100644 --- a/.github/workflows/_tmp-test-aetherpak-publish.yml +++ b/.github/workflows/_tmp-test-aetherpak-publish.yml @@ -40,7 +40,7 @@ on: default: true permissions: - contents: read + contents: write # peaceiris/actions-gh-pages pushes a commit to gh-pages packages: write actions: read From 59962f62f681984362cb99d4e10374f60812ae61 Mon Sep 17 00:00:00 2001 From: Daniel Kantor Date: Tue, 7 Jul 2026 15:18:14 +0200 Subject: [PATCH 4/4] docs(ci): drop DO NOT MERGE banners, reflect actual intentional-vendoring plan These files are meant to be merged, not left as pure throwaway scratch code -- the point is to decouple the flatpak publish + Pages pipeline from tagged releases so it can be iterated on without cutting real ToolHive Studio releases (which trigger real auto-updates and binary signing) just to test changes here. The old banners said the opposite and confused reviewers. Also trimmed the internal contingency language (upstreaming vs. staying vendored long-term) -- that's our own planning, not something that needs to live in a public repo file. Co-Authored-By: Claude Sonnet 5 --- .../_tmp-aetherpak-publish-oci/action.yml | 12 +++++++----- .../_tmp-aetherpak-publish-site/action.yml | 12 +++++++----- .../actions/_tmp-aetherpak-setup-cli/action.yml | 16 ++++++++-------- .../workflows/_tmp-test-aetherpak-publish.yml | 12 ++++++------ 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.github/actions/_tmp-aetherpak-publish-oci/action.yml b/.github/actions/_tmp-aetherpak-publish-oci/action.yml index de903116d..4e16bad2d 100644 --- a/.github/actions/_tmp-aetherpak-publish-oci/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-oci/action.yml @@ -1,10 +1,12 @@ # ============================================================================ -# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# INTENTIONALLY VENDORED — decoupled from tagged releases for now # -# Vendored for local iteration while validating the aetherpak flatpak -# publishing pipeline (see PR #2301 / #2427). Delete this directory once -# validation is done; production usage should reference the upstream action -# pinned by commit SHA, as on-release.yml already does. +# Vendored so we can iterate on the aetherpak flatpak publish + Pages +# pipeline (see PR #2301 / #2427) in follow-up PRs without cutting real +# ToolHive Studio releases (which trigger real auto-updates and binary +# signing) just to test changes here. +# +# See discussion in #2427/#2429. # # Source: https://github.com/aetherpak/actions/tree/main/publish-oci (MIT # License), vendored from commit d2112669ceaa09afb7c2b96e9e9844866a6c8e78 diff --git a/.github/actions/_tmp-aetherpak-publish-site/action.yml b/.github/actions/_tmp-aetherpak-publish-site/action.yml index d12680da2..a2243b66a 100644 --- a/.github/actions/_tmp-aetherpak-publish-site/action.yml +++ b/.github/actions/_tmp-aetherpak-publish-site/action.yml @@ -1,10 +1,12 @@ # ============================================================================ -# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# INTENTIONALLY VENDORED — decoupled from tagged releases for now # -# Vendored for local iteration while validating the aetherpak flatpak -# publishing pipeline (see PR #2301 / #2427). Delete this directory once -# validation is done; production usage should reference the upstream action -# pinned by commit SHA, as on-release.yml already does. +# Vendored so we can iterate on the aetherpak flatpak publish + Pages +# pipeline (see PR #2301 / #2427) in follow-up PRs without cutting real +# ToolHive Studio releases (which trigger real auto-updates and binary +# signing) just to test changes here. +# +# See discussion in #2427/#2429. # # Source: https://github.com/aetherpak/actions/tree/main/publish-site (MIT # License), vendored from commit d2112669ceaa09afb7c2b96e9e9844866a6c8e78 diff --git a/.github/actions/_tmp-aetherpak-setup-cli/action.yml b/.github/actions/_tmp-aetherpak-setup-cli/action.yml index e0741bc87..9372d329e 100644 --- a/.github/actions/_tmp-aetherpak-setup-cli/action.yml +++ b/.github/actions/_tmp-aetherpak-setup-cli/action.yml @@ -1,13 +1,13 @@ # ============================================================================ -# TEMPORARY VENDORED COPY — DO NOT MERGE TO main +# INTENTIONALLY VENDORED — decoupled from tagged releases for now # -# Vendored for local iteration while validating the aetherpak flatpak -# publishing pipeline (see PR #2301 / #2427), pinned to one exact source -# commit so the test build stays reproducible while we iterate. Delete this -# directory once validation is done; production usage should continue to -# reference the upstream `aetherpak/setup-cli@v1` action directly (or a -# permanently-vendored setup, if that ends up being the right call — see -# discussion in #2427). +# Vendored so we can iterate on the aetherpak flatpak publish + Pages +# pipeline (see PR #2301 / #2427) in follow-up PRs without cutting real +# ToolHive Studio releases (which trigger real auto-updates and binary +# signing) just to test changes here. Pinned to one exact source commit so +# builds stay reproducible while we iterate. +# +# See discussion in #2427/#2429. # # Source: https://github.com/aetherpak/setup-cli (MIT License) # Vendored from tag: v1 diff --git a/.github/workflows/_tmp-test-aetherpak-publish.yml b/.github/workflows/_tmp-test-aetherpak-publish.yml index 27504a4d6..4be81e9d9 100644 --- a/.github/workflows/_tmp-test-aetherpak-publish.yml +++ b/.github/workflows/_tmp-test-aetherpak-publish.yml @@ -1,10 +1,10 @@ # ============================================================================ -# TEMPORARY — DO NOT MERGE TO main +# INTENTIONALLY MERGED FOR NOW — decoupled from tagged releases # -# Scratch workflow to validate the aetherpak flatpak publish + Pages pipeline +# Workflow to validate the aetherpak flatpak publish + Pages pipeline # introduced in #2301, without cutting a real release (which would trigger -# the real on-release.yml and auto-update real users) and without needing -# the production GPG signing secrets. +# the real on-release.yml, real auto-updates, and binary signing) and +# without needing the production GPG signing secrets. # # - Manual trigger only (workflow_dispatch). Never runs on push/PR/release. # - Reuses a flatpak bundle artifact that a normal PR build already produced @@ -21,8 +21,8 @@ # from the real "flatpak" subpath and "latest/" release manifest via # destination_dir, verified against peaceiris/actions-gh-pages source). # -# Delete this file and the .github/actions/_tmp-aetherpak-* directories once -# validation is complete. +# This keeps the pipeline decoupled from tagged releases while we iterate on +# it in follow-up PRs. See discussion in #2427/#2429. # ============================================================================ name: '[TEMP] Test AetherPak Flatpak Publish'