name: Flatpak # One job, three outcomes: # pull request -> build the Flatpak and run the suite inside it # push master -> also publish the result as the `beta` branch of the # Catcrafts repo and refresh the rolling `latest` release # tag X.Y.Z -> publish as `stable` and create release X.Y.Z # # The `flatpak` runner label is the privileged runner on the Hetzner box # (bubblewrap needs it) with four volumes allowed: the Sdk install, the # flatpak-builder state (ccache and the LLVM module, so only crafter-build # itself rebuilds on a normal run), the published repo's web root, and the # repo signing key, which lives on the runner host and never in the forge. on: pull_request: branches: [master] push: branches: [master] # Bare semver tags. Spelled with globs so it means the same whether the # forge reads it as a workflow pattern or as a plain glob; `latest`, the # rolling tag this workflow moves itself, does not start with a digit. tags: ['[0-9]*.[0-9]*.[0-9]*'] workflow_dispatch: env: APP_ID: net.catcrafts.CrafterBuild MANIFEST: packaging/flatpak/net.catcrafts.CrafterBuild.yaml CI_MANIFEST: packaging/flatpak/ci-manifest.yaml RUNTIME: org.freedesktop.Sdk//26.08 REPO_DIR: /srv/flatpak/repo WEB_DIR: /srv/flatpak jobs: flatpak: runs-on: flatpak container: image: archlinux:latest options: >- -v crafter-flatpak-system:/var/lib/flatpak -v crafter-flatpak-builder:/root/.cache/flatpak-builder -v /var/www/flatpak:/srv/flatpak -v /etc/crafter-flatpak:/run/crafter-flatpak:ro steps: - name: Install tools run: | pacman-key --init pacman-key --populate archlinux pacman -Sy --noconfirm --needed archlinux-keyring pacman -Syu --noconfirm --needed flatpak flatpak-builder ccache git nodejs jq gnupg go-yq ostree python git config --global --add safe.directory '*' - name: Sdk # A no-op once the volume is warm; `update` follows point releases. run: | flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak install -y --noninteractive flathub "$RUNTIME" flatpak update -y --noninteractive - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 persist-credentials: true - name: Version and channel id: meta run: | set -eu if [ "${GITHUB_REF_TYPE}" = tag ]; then version="$GITHUB_REF_NAME" branch=stable # The metainfo's newest release is the release notes and must be # the version being tagged. top=$(grep -m1 -oP '&2 exit 1 fi else # A master build names itself after the release it follows, the # way a local checkout does: 2.0.0-3-g21746b1 is three commits # past 2.0.0. Before the first tag there is nothing to describe # from and the bare hash gets a placeholder in front. described=$(git describe --tags --always --exclude=latest) case "$described" in [0-9]*.[0-9]*.[0-9]*) version="$described" ;; *) version="0.0.0-dev+$described" ;; esac branch=beta fi echo "version=$version" >> "$GITHUB_OUTPUT" echo "branch=$branch" >> "$GITHUB_OUTPUT" echo "version $version, flatpak branch $branch" - name: Build # The manifest is rewritten beside the original so its relative # source paths still resolve. The suite runs inside this build. env: VERSION: ${{ steps.meta.outputs.version }} run: | set -eu yq '."build-options".env.CRAFTER_BUILD_VERSION = strenv(VERSION)' "$MANIFEST" > "$CI_MANIFEST" grep -n 'CRAFTER_BUILD_VERSION' "$CI_MANIFEST" flatpak-builder --force-clean --ccache --disable-rofiles-fuse \ --state-dir=/root/.cache/flatpak-builder \ --repo=repo --default-branch="${{ steps.meta.outputs.branch }}" \ build-dir "$CI_MANIFEST" - name: Smoke test run: | flatpak-builder --run build-dir "$CI_MANIFEST" crafter-build --version flatpak-builder --run build-dir "$CI_MANIFEST" sh -c 'cd examples/hello-world && crafter-build && ls bin/*/hello*' # Everything below publishes and only runs on master and on tags. - name: Bundle if: github.event_name != 'pull_request' run: | mkdir -p dist flatpak build-bundle repo "dist/crafter-build-${{ steps.meta.outputs.version }}.flatpak" "$APP_ID" "${{ steps.meta.outputs.branch }}" cp packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref dist/ (cd dist && sha256sum * > SHA256SUMS) ls -la dist - name: Upload bundle if: github.event_name != 'pull_request' uses: actions/upload-artifact@v3 with: name: crafter-build-${{ steps.meta.outputs.version }} path: dist/*.flatpak if-no-files-found: error - name: Publish to the Catcrafts repo if: github.event_name != 'pull_request' run: | set -eu # The signing key is generated once on the runner host # (/etc/crafter-flatpak: signing-key.asc and key-id) and mounted # read-only; it is imported into a throwaway keyring per run. if [ ! -s /run/crafter-flatpak/signing-key.asc ] || [ ! -s /run/crafter-flatpak/key-id ]; then echo "no signing key under /etc/crafter-flatpak on the runner host; generate it as described in AGENTS.md (CI and publishing)" >&2 exit 1 fi export GNUPGHOME=$(mktemp -d) gpg --batch --quiet --import /run/crafter-flatpak/signing-key.asc FLATPAK_GPG_KEY_ID=$(cat /run/crafter-flatpak/key-id) # The live repo is the canonical copy; builds are promoted into it, # never built into it. # Keyed on the repo's config file, not the directory: the web root # may exist as an empty directory before the first publish. [ -f "$REPO_DIR/config" ] || ostree init --repo="$REPO_DIR" --mode=archive flatpak build-commit-from --src-repo=repo \ --gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \ "$REPO_DIR" "app/$APP_ID/x86_64/${{ steps.meta.outputs.branch }}" flatpak build-update-repo --gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \ --generate-static-deltas --prune "$REPO_DIR" # The repo description and the app ref carry the public key. key=$(gpg --export "$FLATPAK_GPG_KEY_ID" | base64 --wrap=0) sed "s|GPGKey=.*|GPGKey=$key|" packaging/flatpak/catcrafts.flatpakrepo > "$WEB_DIR/catcrafts.flatpakrepo" sed "s|GPGKey=.*|GPGKey=$key|" packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref > "$WEB_DIR/net.catcrafts.CrafterBuild.flatpakref" - name: Rolling 'latest' release if: github.event_name != 'pull_request' && github.ref_type != 'tag' run: | git config user.email "ci@catcrafts.net" git config user.name "Crafter Build CI" git tag -f latest git push origin latest --force - name: Publish rolling 'latest' release if: github.event_name != 'pull_request' && github.ref_type != 'tag' uses: https://code.forgejo.org/actions/forgejo-release@v2 with: direction: upload url: ${{ github.server_url }} repo: ${{ github.repository }} tag: latest title: Latest master build prerelease: true override: true release-dir: dist token: ${{ secrets.GITHUB_TOKEN }} - name: Release notes if: github.ref_type == 'tag' run: | # The newest block's description, as plain text. python3 - > release-notes.md <<'EOF' import re, html text = open("packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml").read() block = re.search(r"]*>(.*?)", text, re.S).group(1) paras = re.findall(r"

(.*?)

", block, re.S) print("\n\n".join(html.unescape(" ".join(p.split())) for p in paras)) print("\nInstall:\n\n flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo\n flatpak install catcrafts net.catcrafts.CrafterBuild\n") EOF - name: Publish release if: github.ref_type == 'tag' uses: https://code.forgejo.org/actions/forgejo-release@v2 with: direction: upload url: ${{ github.server_url }} repo: ${{ github.repository }} tag: ${{ github.ref_name }} title: crafter-build ${{ github.ref_name }} release-notes-file: release-notes.md release-dir: dist token: ${{ secrets.GITHUB_TOKEN }}