Documents
AetherPak Maintainer Guide: Building and Publishing Flatpak Apps with GitHub Actions
AetherPak Maintainer Guide: Building and Publishing Flatpak Apps with GitHub Actions
Type
Document
Status
Published
Created
Jun 4, 2026
Updated
Jun 4, 2026
Updated by
Dosu Bot

AetherPak Maintainer Guide: Building and Publishing Flatpak Apps with GitHub Actions#

1. Introduction#

AetherPak is a GitHub Actions-based system for building Flatpak applications and hosting them as self-hosted Flatpak repositories — entirely within GitHub's free infrastructure. It turns your existing Flatpak manifest into a fully functional, publicly installable app repository, complete with a landing page and one-click install support, with no self-hosted servers required.

How it works#

AetherPak splits the hosting responsibilities between two GitHub services :

  • GitHub Container Registry (GHCR) stores the application layers as OCI images — the large blobs that clients download when installing or updating an app.
  • GitHub Pages serves a lightweight JSON index (index/static), a landing page (index.html), a .flatpakrepo remote-config file, and per-app .flatpakref files.

When a user installs your app, the Flatpak client reads the index from Pages and pulls the actual application layers directly from GHCR in large chunks. This design solves a real scaling problem: a traditional static-HTTP OSTree repository would serve an app as hundreds of tiny objects, which is slow and quickly trips GitHub Pages' rate limits. The OCI-backed approach avoids both problems .

The full pipeline looks like this :

manifest ──build──▶ OSTree repo ──publish──▶ OCI image in GHCR
                                         └──▶ index/static + index.html on Pages

client: Pages index ──(digest)──▶ GHCR blobs

Why use AetherPak?#

BenefitDetails
Free hostingGHCR and GitHub Pages are free for public repositories
Automated buildsPush to your main branch; the workflow builds, signs, and deploys automatically
Multi-arch supportBuilds for x86_64 and aarch64 in parallel
OCI-backed distributionReliable, chunked downloads that won't hit Pages rate limits
Optional GPG signingCryptographically verified installs with --signature-lookaside
One-click installGenerated .flatpakref files let users install with a single button click
Multi-app reposOne repository can host many apps in a shared index
Zero infrastructureEverything runs in GitHub Actions; no servers to manage

Live demo#

You can see a working AetherPak deployment at https://aetherpak.github.io/actions-demo/, which publishes GNOME Sudoku for both x86_64 and aarch64 from a 20-line workflow file .

2. Prerequisites#

Before you set up AetherPak, make sure the following are in place:

A GitHub repository with a Flatpak manifest#

You need a GitHub repository containing a Flatpak manifest file (.json or .yaml). This is the same manifest you would pass to flatpak-builder. If you're starting from scratch, Flathub's app submission guide is a good reference for writing one.

Enable GitHub Pages with "GitHub Actions" source#

AetherPak deploys your repository index and landing page to GitHub Pages. To enable this:

  1. Go to your repository Settings → Pages
  2. Under Source, select GitHub Actions

This grants the workflow permission to deploy Pages directly. Without this, the publish-site job will fail at the deployment step .

Allow public package creation (organization repositories only)#

If your repository belongs to a GitHub organization, an organization owner must allow public packages before your first workflow run. Otherwise the GHCR image is created as private and cannot be switched to public after the fact.

To enable it: Organization → Settings → Packages → Package creation → enable Public .

After the first successful workflow run, you will also need to make the package public:

  1. Go to your profile or organization → Packages
  2. Open the newly created package
  3. Package settings → Change visibility → Public

Required GitHub Actions permissions#

Your workflow must declare these permissions :

permissions:
  contents: read # checkout the repository
  packages: write # push OCI images to GHCR
  pages: write # deploy to GitHub Pages
  id-token: write # OIDC token for Pages deployment

Supported platforms#

AetherPak only supports Linux runners. Builds are supported for x86_64 (amd64) and aarch64 (arm64) architectures — these are the only architectures available in the builder container image .

Basic familiarity with GitHub Actions#

You don't need to understand every detail of GitHub Actions, but knowing how to create a workflow file under .github/workflows/, how to set repository secrets, and how reusable workflows work will help you follow this guide.

3. Available Shared Workflows and Actions#

AetherPak provides both a high-level reusable workflow that orchestrates the full pipeline and a set of modular composite actions you can use individually for custom setups.

Reusable workflow: publish.yml#

aetherpak/actions/.github/workflows/publish.yml@v3

This is the primary entry point for most maintainers. It runs the complete build-publish-deploy pipeline and supports two modes :

  • Manifest mode (manifest-path): builds a single Flatpak app from a manifest
  • Config mode (config): reads aetherpak.yaml to build multiple apps with change detection

Internally, it runs five jobs in sequence :

JobWhat it does
planResolves the build matrix from a manifest or aetherpak.yaml
build-manifestCompiles each (app, arch) cell from a manifest (parallel)
prep-bundleFetches, verifies, and imports prebuilt bundle cells (parallel)
publish-ociPushes each OSTree repo as a signed OCI image to GHCR (parallel)
publish-siteAggregates all records into the shared index and deploys to Pages (single, concurrency-locked)

Reusable workflow: prune-github-container-registry.yml#

aetherpak/actions/.github/workflows/prune-github-container-registry.yml@v3

Prunes inactive container versions from GitHub Container Registry (GHCR) to save storage and maintain clean registries. This workflow fetches the active Flatpak index from GitHub Pages, compares it with container versions in GHCR, and deletes versions that are no longer referenced in the active index.

Usage: Called via workflow_call as a reusable workflow.

Inputs:

InputDefaultDescription
app-id(empty)Specific flatpak App ID to prune (e.g. org.example.App). If omitted, prunes all apps managed by aetherpak.
registryghcr.ioTarget container registry host.
oci-repository(empty)OCI repository path without registry host. Defaults to GITHUB_REPOSITORY (lowercased).
pages-url(empty)Public URL of the flatpak static index page. Defaults to the GitHub Pages URL.
dry-runfalseOnly list versions that would be pruned without actually deleting them.

Secrets:

SecretDescription
tokenGitHub token (e.g. GitHub App token or PAT) with packages:read and packages:delete permissions.

Safety features:

  • Will not prune versions that are still in the active index (by digest or tag matching)
  • Has dry-run mode for testing
  • If the index cannot be fetched and no app-id is specified, the workflow fails to prevent accidental deletion of all images

Example:

name: Prune Registry
on:
  schedule:
    - cron: "0 2 * * 0" # weekly on Sunday at 2am
  workflow_dispatch:

jobs:
  prune:
    uses: aetherpak/actions/.github/workflows/prune-github-container-registry.yml@v3
    secrets:
      token: ${{ secrets.GHCR_PRUNE_TOKEN }}

Composite actions#

These are individual building blocks you can call from your own workflows. They automatically install the aetherpak CLI via aetherpak/setup-cli@v1 if it isn't already on PATH .

aetherpak/actions@v3 — Root action #

Chains build and publish in a single step. Best suited for prebuilt inputs (a .flatpak bundle or an existing OSTree repository). For manifest builds, use the reusable workflow instead — it supplies the required builder container .

aetherpak/actions/build@v3 — Build or import #

Compiles a Flatpak app from a manifest using flatpak-builder, or imports a prebuilt .flatpak bundle or an existing OSTree repository. Outputs repo-path, app-id, branch, and arch.

Key inputs:

  • manifest-path — path to the Flatpak manifest (JSON/YAML)
  • prebuilt-bundle-path — path to a pre-built .flatpak file
  • prebuilt-repo-path — path to an existing OSTree repository
  • app-id, branch, arch — coordinates (resolved from the repo when building)
  • run-linter — run flatpak-builder-lint (default true)
  • cache, cache-state, cache-ccache, cache-build-dir — caching options (see Build Caching)

aetherpak/actions/plan@v3 — Build matrix planner #

Expands an aetherpak.yaml or a single manifest path into a GitHub Actions build matrix, narrowed to apps that changed since base-sha. Outputs matrix, matrix-manifest, matrix-bundle, and per-type counts.

Key inputs:

  • config — path to aetherpak.yaml (default: aetherpak.yaml)
  • manifest — single manifest path (bypasses config)
  • arch — space-separated architectures for manifest mode
  • force'' for change detection, '<app-id>' for a single app, or 'all' for everything
  • base-sha — commit SHA to diff against

aetherpak/actions/prep-bundle@v3 — Bundle fetcher #

Fetches a .flatpak bundle from a URL, verifies its SHA-256 checksum, imports it into an OSTree repository, and rebinds the commit to the consumer-declared channel. This rebind is important: upstream bundles often carry app/<id>/<arch>/master as the branch; prep-bundle rewrites the ref to the branch you declared in aetherpak.yaml .

Key inputs: url, sha256, branch (default stable), repo-path, bundle-path

aetherpak/actions/publish-oci@v3 — OCI publisher #

Pushes an OSTree repository as a signed OCI image to GHCR (or another registry) and writes a per-cell record containing the digest, labels, and optional signature. Parallel-safe — multiple cells can run simultaneously without conflicting.

Key inputs: repo-path, app-id, branch, arch, registry, oci-repository, registry-token, signing, gpg-private-key, records-dir

aetherpak/actions/publish-site@v3 — Site builder #

Aggregates per-cell records into the complete static site: merges index/static, writes .flatpakrepo and .flatpakref files, generates index.html, and assembles the signature lookaside. This is the one job that must run with a concurrency lock.

Key inputs: records-dir, pages-url (required), site-dir, signing, gpg-private-key, remote-name, landing-page, index-template

aetherpak/actions/publish@v3 — Combined publisher #

A thin wrapper over publish-oci + publish-site. Use this when you already have an OSTree repo or .flatpak bundle and want a simple one-action publish step with Pages deployment.

aetherpak/setup-cli@v1 — CLI installer #

Downloads and installs the aetherpak CLI binary on the runner. All composite actions call this automatically if needed, but you can invoke it manually to pin to a specific CLI version.

Key inputs: version (default latest), repo (default aetherpak/cli), install-dependencies (default true — installs flatpak, ostree, flatpak-builder, and related tools)

Tip: If you prefer to use the CLI container images directly in your CI pipeline instead of the setup action, see CLI Container Images: Standard and Builder Variants for detailed usage instructions.

4. Quick Start: Single App#

This section walks you through publishing your first Flatpak app using AetherPak in a few minutes.

Step 1: Enable GitHub Pages#

In your repository, go to Settings → Pages → Source and select GitHub Actions. This allows the workflow to deploy your repository index and landing page directly .

Step 2: Create the workflow file#

Create .github/workflows/publish.yml with the following content, replacing org.example.App.json with the path to your manifest:

name: Publish Flatpak
on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  packages: write
  pages: write
  id-token: write

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json

That's the minimal configuration. By default, the workflow will:

  • Build your app for both x86_64 and aarch64
  • Set the Flatpak branch to stable on tag pushes, beta on your default branch, or the git ref name otherwise
  • Run flatpak-builder-lint on the build
  • Push OCI images to GHCR under your repository name
  • Deploy the index and landing page to GitHub Pages

Step 3: Make the GHCR package public#

After the first successful run, you need to make the container package publicly accessible so users can install without authenticating :

  1. Go to your profile or organization → Packages
  2. Open the package created by AetherPak (named after your repository)
  3. Go to Package settings → Change visibility → Public

Org repositories: An organization owner must first enable public package creation before your first run at Organization → Settings → Packages → Package creation → Public. If this step is skipped, the image is created private and cannot be switched .

Step 4: What happens on each push#

Every push to main triggers the full pipeline :

  1. Plan — parses your manifest and emits a (app, arch) matrix
  2. Build — compiles the app inside a flatpak-builder container for each architecture
  3. Publish-OCI — converts each OSTree repo to an OCI image and pushes it to GHCR (signed if you've configured a GPG key)
  4. Publish-site — merges the per-arch records into index/static, writes .flatpakref files, regenerates index.html, and deploys to GitHub Pages

Your landing page is published at https://<owner>.github.io/<repo>/.

Common workflow options#

InputDefaultPurpose
manifest-path(required)Flatpak manifest to build
archesx86_64 aarch64Architectures to build
branchautostable on tags, beta on default branch, else ref name
run-lintertrueRun flatpak-builder-lint
deploytrueDeploy to Pages; false uploads the site as an artifact
pages-urlproject Pages URLOverride for custom domains
signingautoauto, gpg, or off — see GPG Signing
cachetrueCache Flatpak runtimes and builder state
builder-args--install-deps-from=flathubExtra flatpak-builder flags
upload-bundlefalseExport the built app as a .flatpak bundle artifact
dry-runfalseBuild and verify, but skip pushing and deploying
reconcile-onlyfalseSkip builds; only reconcile the index against the registry

Adding GPG signing#

To enable GPG signing, pass your private key as a secret (see GPG Signing for key generation):

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      signing: gpg
    secrets:
      gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}

5. Multi-App Repository Setup#

One repository can host any number of apps in a single shared index. Instead of specifying manifest-path, you declare all your apps in an aetherpak.yaml file and pass config to the reusable workflow .

Workflow configuration#

# .github/workflows/publish.yml
name: Publish Flatpaks
on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  packages: write
  pages: write
  id-token: write

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak.yaml
    secrets:
      gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}

aetherpak.yaml schema#

The configuration file has the following structure :

# aetherpak.yaml

# Global settings
registry: ghcr.io # OCI registry (default: ghcr.io)
pages_url: https://owner.github.io/repo # Public hosting URL
oci_repository: owner/repo # Registry path (defaults to GitHub repo)
remote_name: my-apps # Flatpak remote name and .flatpakrepo filename
repo_title: My App Repository
repo_homepage: https://example.org

# Optional channel mappings (glob patterns → Flatpak branch names)
channel_mappings:
  main: beta
  "release/*": stable

# Global build defaults (inherited by all apps)
defaults:
  ccache: true
  run_linter: false
  builder_args:
    - --install-deps-from=flathub

# Custom landing page branding
branding:
  logo_url: https://example.org/logo.png
  favicon_url: https://example.org/favicon.ico
  accent_color: "#3584e4"
  footer_text: "© 2025 My Project"
  index_template: custom-template.html # path to a custom HTML template

# Linter configuration (optional)
linter:
  strict: false
  ignore_rules:
    - no-appstream-screenshot-urls

# Application list
apps:
  - id: org.example.AppOne
    manifest: apps/org.example.AppOne/org.example.AppOne.json
    arches: [x86_64, aarch64]
    branch: stable
    run-linter: true

  - id: com.example.AppTwo
    bundles:
      x86_64:
        url: https://upstream.example.com/AppTwo_x86_64.flatpak
        sha256: "abc123...64hexchars"
      aarch64:
        url: https://upstream.example.com/AppTwo_aarch64.flatpak
        sha256: "def456...64hexchars"
    branch: stable

Each app requires exactly one of manifest or bundles — not both .

Key configuration fields#

FieldDescription
registryOCI registry host (default ghcr.io)
pages_urlPublic URL where the index is served
oci_repositoryRegistry image path (defaults to GITHUB_REPOSITORY)
remote_nameFlatpak remote name and .flatpakrepo filename
channel_mappingsMap git refs/branches to Flatpak channel names (supports glob wildcards)
defaultsGlobal settings inherited by all apps (ccache, state_dir, builder_args, remotes, flatpaks)
brandingLanding page appearance (logo, favicon, accent color, footer, custom template)
linterLinter strictness and ignored rules
appsList of application definitions

Per-app fields :

FieldDescription
idReverse-DNS app ID (e.g. org.gnome.Sudoku)
manifestRelative path to Flatpak manifest (mutually exclusive with bundles)
bundlesMap of arch → {url, sha256} for prebuilt bundles
archesList of architectures to build (default [x86_64] for manifest apps)
branchFlatpak channel/branch (default stable)
run-linterOverride global linter setting
ccacheEnable ccache for this app
builder_argsExtra flatpak-builder flags for this app
remotesAdditional Flatpak remotes to add before building
flatpaksFlatpak refs to pre-install from declared remotes

Change detection#

In config mode, the workflow only rebuilds apps that changed since the previous commit. For manifest-based apps, change detection uses gitlink tree diffs against the manifest's directory; for bundle-based apps, it diffs the aetherpak.yaml entry itself .

You can control this behavior with workflow inputs :

InputEffect
(default)Rebuild only changed apps
app: org.example.AppOneForce rebuild of one specific app
force-all: trueRebuild every app in the config
base-sha: <sha>Override the diff base commit
workflow-path: .github/workflows/publish.ymlTouching this file triggers a rebuild-all

Pipeline for multi-app builds#

The five-stage pipeline runs in parallel where possible :

  1. plan — expands aetherpak.yaml into a matrix of (app, arch) cells, narrowed by change detection
  2. build-manifest — parallel per-cell compilation inside the builder container; each cell uploads a repo-<app-id>-<arch> artifact
  3. prep-bundle — parallel per-cell bundle fetch + import + channel rebind; uploads the same artifact shape
  4. publish-oci — parallel OCI push per cell; each writes a aetherpak-record-<app-id>-<arch> artifact
  5. publish-site — single, concurrency-locked job that downloads all records, merges them into the shared index/static, reconciles, writes .flatpakref files, and deploys to Pages

The concurrency lock on publish-site is important: it prevents two concurrent workflow runs from racing while reading, merging, and writing back the shared index .

6. Publishing Pre-built Bundles#

AetherPak doesn't require you to build apps from source. If you already have a .flatpak bundle — whether produced by another CI system, a third-party tool, or a cross-compilation setup — AetherPak can import, sign, and publish it directly.

Method 1: Local bundle file with the publish action#

If you produce a .flatpak bundle in a previous job and want to publish it directly, use the aetherpak/actions/publish composite action with bundle-path :

name: Publish Pre-built Flatpak
on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  pages: write
  id-token: write

jobs:
  publish:
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deploy.outputs.page_url }}
    steps:
      - uses: actions/checkout@v4

      # Download or build your .flatpak bundle here
      - name: Download bundle
        run: curl -fLO https://example.org/releases/app.flatpak

      - uses: aetherpak/actions/publish@v3
        with:
          bundle-path: app.flatpak
          pages-url: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}

      - id: deploy
        uses: actions/deploy-pages@v5

Multiple bundles at once#

You can publish several bundles in a single step by providing a multiline or comma-separated list of paths or glob patterns. When doing this, do not provide app-id or arch — AetherPak reads these coordinates directly from each bundle's internal metadata :

- uses: aetherpak/actions/publish@v3
  with:
    bundle-path: |
      build/*.flatpak
    pages-url: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}

Method 2: Prebuilt workflow artifacts with the reusable workflow#

If your build job uploads .flatpak bundles as GitHub Actions artifacts, you can pass the artifact name pattern to the reusable workflow via prebuilt-bundle-artifact. The pattern supports {arch}, {app-id}, and {branch} placeholders :

name: Build and Publish
on:
  push:
    branches: [main]

permissions:
  contents: read
  packages: write
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        arch: [x86_64, aarch64]
    steps:
      - uses: actions/checkout@v4
      # ... your build steps produce out/make/app.flatpak ...
      - uses: actions/upload-artifact@v4
        with:
          name: flatpak-bundle-${{ matrix.arch }}
          path: out/make/*.flatpak

  publish:
    needs: build
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak.yaml
      prebuilt-bundle-artifact: "flatpak-bundle-{arch}"

The workflow resolves the {arch} placeholder for each matrix cell, downloads the artifact, and uses flatpak info --show-ref to select the matching .flatpak file when the artifact contains multiple bundles .

Method 3: URL bundles in aetherpak.yaml#

For third-party bundles hosted at stable URLs, declare them in aetherpak.yaml using the bundles field :

apps:
  - id: com.example.ThirdPartyApp
    branch: stable
    bundles:
      x86_64:
        url: https://releases.example.com/ThirdPartyApp_x86_64.flatpak
        sha256: "2159fc643175dcf54f8b9293f48fb8b11c41a87fe31684ee4c5d4e94c6f37a42"
      aarch64:
        url: https://releases.example.com/ThirdPartyApp_aarch64.flatpak
        sha256: "a7b3c9d2e1f04586738c9d2b4f1e3a6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2"

The SHA-256 must be exactly 64 lowercase hex characters. AetherPak uses the prep-bundle action to fetch each bundle, verify its checksum, import it into an OSTree repository, and rebind the commit to the declared branch . This rebind step is critical — upstream bundles often carry app/<id>/<arch>/master as their embedded branch name, and prep-bundle rewrites both the ref name and the xa.ref binding to match what you declared, so flatpak install accepts it.

Use cases#

  • Third-party apps: publish and redistribute an upstream .flatpak under your own repository
  • Electron/Tauri apps: your framework produces a .flatpak bundle; feed it straight to AetherPak without flatpak-builder
  • Multi-system CI: build on a specialized runner (e.g., native Raspberry Pi), upload the artifact, and publish from a standard ubuntu-latest runner
  • Import from release assets: mirror another project's GitHub Releases .flatpak to your own Flatpak repository

Targeting a custom registry#

The publish action also supports non-GHCR registries. Pass registry, oci-repository, and registry-token directly :

- uses: aetherpak/setup-cli@v1 # installs aetherpak CLI on PATH
- uses: aetherpak/actions/publish@v3
  with:
    bundle-path: app.flatpak
    registry: registry.example.com
    oci-repository: my-org/my-app
    registry-token: ${{ secrets.REGISTRY_TOKEN }}
    pages-url: https://flatpak.example.com

Add insecure-registry: true for local or HTTP-only registries.

7. GPG Signing#

Signing is entirely optional. Without a GPG key, AetherPak publishes unsigned repositories and users install with --no-gpg-verify. Configure a key and every OCI image is cryptographically signed; users can then install with verified signatures using Flatpak's --signature-lookaside mechanism .

Note: AetherPak uses GPG-based signing only — this is what Flatpak's OCI verifier supports (via the containers/image simple-signing lookaside). Cosign / keyless signing is not used .

Step 1: Generate a GPG key#

For CI use, generate a passphrase-less RSA key :

gpg --batch --gen-key <<EOF
%no-protection
Key-Type: RSA
Key-Length: 4096
Name-Real: My App Releases
Name-Email: releases@example.org
Expire-Date: 0
%commit
EOF

# Export the private key
gpg --armor --export-secret-keys releases@example.org

Copy the entire output including the -----BEGIN PGP PRIVATE KEY BLOCK----- and -----END PGP PRIVATE KEY BLOCK----- lines.

If you prefer a passphrase-protected key, omit the %no-protection line and store the passphrase as an additional secret.

Step 2: Store the key as a GitHub secret#

  1. Go to your repository Settings → Secrets and variables → Actions
  2. Click New repository secret
  3. Name it GPG_PRIVATE_KEY (or AETHERPAK_GPG_KEY as used in the demo)
  4. Paste the armored private key as the value

If your key has a passphrase, create a second secret named GPG_PRIVATE_KEY_PASSPHRASE .

Step 3: Pass the secret to the workflow#

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      signing: gpg # 'gpg' = require key; 'auto' = sign if key is present
    secrets:
      gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
      # gpg-private-key-passphrase: ${{ secrets.GPG_PRIVATE_KEY_PASSPHRASE }}

Signing modes#

ModeBehavior
auto (default)Sign if gpg-private-key is provided; skip silently if not
gpgRequire a key; fail the workflow if no key is set
offNever sign, even if a key is provided

What gets published#

When signing is enabled, aetherpak build-site assembles a signature lookaside directory alongside your index :

FileContents
sigs/<repo>@sha256=<digest>/signature-1Detached GPG signature for each OCI image
sigs/key.ascExported public key for import
sigs/signing.jsonManifest read by the landing page to show verified-install commands

Each .flatpakref file embeds the public key in base64 (GPGKey field) and the SignatureLookaside URL, so installs via the Install button are automatically verified. This is something the .flatpakrepo format cannot carry, which is why .flatpakref files offer the most seamless verified install experience .

Key rotation#

To rotate your signing key :

  1. Generate a new key following the steps above
  2. Replace the GPG_PRIVATE_KEY repository secret with the new key
  3. Re-publish every channel — rotation only fully takes effect once every image still listed in the index has been re-signed with the new key

The new public key replaces the old one on the next deploy. Existing images remain accessible but will show as unverified under the new key until they are re-published.

Technical note: OCI tag encoding#

Flatpak's OCI verifier strips a [0-9A-Za-z_-]-only regex from the embedded docker-reference tag. App IDs containing dots (e.g. org.gnome.Sudoku) would fail this strip and reject otherwise-valid signatures. AetherPak works around this by encoding . as _ in the OCI image tag portion of the reference; the canonical app ID is preserved in the org.flatpak.ref label .

8. Independent Actions for Custom Pipelines#

The reusable workflow covers most use cases, but AetherPak's composite actions can also be used individually when you need finer-grained control — for example, inserting custom build steps, integrating with an existing workflow structure, or targeting a different publishing target.

Auto-installation of the CLI#

All composite actions automatically invoke aetherpak/setup-cli@v1 to install the aetherpak CLI if it isn't already on PATH. Non-build actions (plan, publish-site) skip system dependency installation to keep setup fast .

To pin a specific CLI version, call setup-cli explicitly before any action step:

- uses: aetherpak/setup-cli@v1
  with:
    version: v0.15.1

Using build for custom build steps#

Use aetherpak/actions/build when you want to control the runner, inject custom environment variables, or run additional steps between build and publish:

jobs:
  build-and-sign:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/aetherpak/cli:v0.15.1-builder
      options: --privileged

    steps:
      - uses: actions/checkout@v4

      - name: Build Flatpak
        id: build
        uses: aetherpak/actions/build@v3
        with:
          manifest-path: org.example.App.json
          arch: x86_64
          branch: stable
          run-linter: true

      - name: Custom post-build step
        run: echo "Built ${{ steps.build.outputs.app-id }} at ${{ steps.build.outputs.repo-path }}"

      - name: Publish
        uses: aetherpak/actions/publish@v3
        with:
          repo-path: ${{ steps.build.outputs.repo-path }}
          pages-url: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}

Important: Manifest builds require the -builder container image with --privileged so flatpak-builder can install SDK/runtime from the baked Flathub remote without the polkit/dbus system helper .

Using plan for matrix generation#

Use aetherpak/actions/plan to drive a dynamic matrix in your own workflow without using the full reusable workflow:

jobs:
  plan:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/aetherpak/cli:v0.15.1
    outputs:
      matrix: ${{ steps.plan.outputs.matrix }}
      count: ${{ steps.plan.outputs.count }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - id: plan
        uses: aetherpak/actions/plan@v3
        with:
          config: aetherpak.yaml
          base-sha: ${{ github.event.before }}

  build:
    needs: plan
    if: needs.plan.outputs.count != '0'
    strategy:
      matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build ${{ matrix.app-id }} (${{ matrix.arch }})
        uses: aetherpak/actions/build@v3
        with:
          manifest-path: ${{ matrix.manifest }}
          app-id: ${{ matrix.app-id }}
          arch: ${{ matrix.arch }}
          branch: ${{ matrix.branch }}

Using prep-bundle for bundle import workflows#

- name: Fetch and import bundle
  uses: aetherpak/actions/prep-bundle@v3
  with:
    url: https://releases.example.com/app_x86_64.flatpak
    sha256: "abc123...64hexchars"
    branch: stable
    repo-path: _repo

For local bundle files (e.g., from a previous build step), use bundle-path instead of url + sha256:

- uses: aetherpak/actions/prep-bundle@v3
  with:
    bundle-path: ./out/app.flatpak
    branch: stable
    repo-path: _repo

Using publish-oci and publish-site separately#

Staged publishing lets you push OCI images in parallel and then run a single, concurrency-controlled site build:

jobs:
  push-oci:
    strategy:
      matrix:
        arch: [x86_64, aarch64]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: repo-${{ matrix.arch }}
          path: _repo

      - name: Push OCI image
        id: push
        uses: aetherpak/actions/publish-oci@v3
        with:
          repo-path: _repo
          arch: ${{ matrix.arch }}
          branch: stable
          registry-token: ${{ github.token }}
          signing: auto
          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}
          records-dir: _records

      - uses: actions/upload-artifact@v4
        with:
          name: record-${{ matrix.arch }}
          path: _records
          include-hidden-files: true

  publish-site:
    needs: push-oci
    concurrency:
      group: publish-${{ github.repository }}
      cancel-in-progress: false
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: record-*
          path: _records_in
          merge-multiple: true

      - uses: aetherpak/actions/publish-site@v3
        with:
          records-dir: _records_in
          pages-url: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}
          signing: auto
          gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }}

      - uses: actions/deploy-pages@v5

Using aetherpak/setup-cli directly#

Use setup-cli when you want to call aetherpak CLI commands directly in your workflow steps :

- uses: aetherpak/setup-cli@v1
  with:
    version: v0.15.1 # pin to a specific version
    install-dependencies: true # install flatpak, ostree, etc.

- name: Check repository status
  run: aetherpak status

- name: Plan changed apps
  run: aetherpak plan --config aetherpak.yaml --base-sha ${{ github.event.before }}

The setup-cli action supports Linux only (amd64 and arm64) and installs the binary to $RUNNER_TEMP/aetherpak-bin, adding it to $GITHUB_PATH. It is idempotent: if the correct version is already on PATH, it skips the download .

9. Build Caching#

Compiling a Flatpak app from source can take several minutes per architecture, especially when the runtime and SDK need to be downloaded. AetherPak provides several independent cache controls to dramatically reduce subsequent build times.

Cache inputs#

InputDefaultDescription
cachetrueMaster toggle — enables caching of Flatpak runtimes and builder state
cache-key(empty)Custom prefix for the cache key; when unset, a default prefix including the manifest hash is used
cache-statetrueCache flatpak-builder state: downloaded modules and their build artifacts
cache-ccachetrueCache ccache compiler artifacts — dramatically speeds up C/C++ recompiles
cache-build-dirfalseCache flatpak-builder build directories; requires --keep-build-dirs in builder-args

Configuring caches#

In manifest mode (reusable workflow), pass these as top-level inputs:

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      cache: true
      cache-key: "my-app-v2" # bump this to invalidate all caches
      cache-state: true
      cache-ccache: true
      cache-build-dir: false

In multi-app mode, these inputs apply globally to all manifest builds. Per-app ccache control is available in aetherpak.yaml via the ccache field.

What each cache covers#

cache-state is typically the most impactful cache. It stores the flatpak-builder state directory (.state/ by default), which contains downloaded sources and already-built modules. On a cache hit, flatpak-builder skips modules that haven't changed, potentially reducing a 10-minute build to under a minute.

cache-ccache speeds up C/C++ compilation by caching compiled object files. Very effective for apps with large C/C++ codebases. The ccache directory (.ccache/ by default) is shared across builds, so even a full rebuild is faster on cache hits.

cache-build-dir stores flatpak-builder's per-module build directories. This is off by default because it requires --keep-build-dirs in builder-args to be useful, and the cache can grow large. Only enable it if you have modules that benefit from incremental builds:

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      cache-build-dir: true
      builder-args: |
        --install-deps-from=flathub
        --keep-build-dirs

Cache key strategy#

The default cache key is derived from the manifest file's hash, so changing any dependency or module in your manifest automatically invalidates the cache. Use cache-key when you want to:

  • Force a cache bust (e.g., after a major dependency upgrade): change the prefix to any new string
  • Isolate caches per branch: use cache-key: "${{ github.ref_name }}" to keep stable and beta caches separate
  • Share a cache across related apps: point multiple apps at the same key prefix (use with care)

Recommendations#

For most apps, the defaults (cache-state: true, cache-ccache: true, cache-build-dir: false) provide the best balance of build speed and storage. Only disable caches if you experience cache corruption or are debugging build reproducibility issues.

10. End-User Installation#

Once you've published your app, users can install it in several ways. AetherPak generates all the necessary files automatically — you don't need to write installation instructions by hand.

Method 1: One-click install via .flatpakref (easiest)#

The landing page deployed to your GitHub Pages URL lists every app and channel with an Install button. Clicking it downloads a .flatpakref file, which when opened adds the Flatpak remote and installs the app in a single step .

The .flatpakref files are located at refs/<app-id>-<channel>.flatpakref under your pages URL. When signing is enabled, each .flatpakref embeds the public key (GPGKey field) and the signature lookaside URL (SignatureLookaside field), so the install is automatically verified.

Each .flatpakref also includes RuntimeRepo: https://dl.flathub.org/repo/flathub.flatpakrepo by default, so the app's runtime is fetched from Flathub if not already installed. Override this with the runtime-repo input, or set it to empty to omit it .

Method 2: Command-line installation#

For users who prefer the terminal, AetherPak publishes the exact commands on the landing page. The remote name defaults to <owner>-<repo> (override with the remote-name workflow input).

Without signing (or flatpak < 1.17):

flatpak remote-add --if-not-exists --user --no-gpg-verify \
  <owner>-<repo> oci+https://<owner>.github.io/<repo>

flatpak install --user <owner>-<repo> org.example.App

With signing (flatpak ≥ 1.17, recommended):

# Fetch the public key (shown on the landing page)
curl -fsSLO https://<owner>.github.io/<repo>/sigs/key.asc

flatpak remote-add --user \
  --gpg-import=key.asc \
  --signature-lookaside=https://<owner>.github.io/<repo>/sigs \
  <owner>-<repo> oci+https://<owner>.github.io/<repo>

flatpak install --user <owner>-<repo> org.example.App

For the live demo repository, the commands look like :

curl -fsSLO https://aetherpak.github.io/actions-demo/sigs/key.asc
flatpak remote-add --user \
  --gpg-import=key.asc \
  --signature-lookaside=https://aetherpak.github.io/actions-demo/sigs \
  actions-demo oci+https://aetherpak.github.io/actions-demo
flatpak install --user actions-demo org.gnome.Sudoku

Method 3: .flatpakrepo file#

The landing page also links to a <remote-name>.flatpakrepo file that configures the remote for all apps and channels in your repository at once. Users can click the link to add the remote in their Flatpak GUI client .

Limitation: The .flatpakrepo format cannot embed a SignatureLookaside URL. Adding the remote through a GUI therefore leaves signature verification incomplete. The landing page shows a remote-modify command to fix this:

flatpak remote-modify --user \
  --signature-lookaside=https://<owner>.github.io/<repo>/sigs \
  <owner>-<repo>

Troubleshooting installation issues#

"Package not found" after install:
Make sure the GHCR package is set to Public (see Prerequisites). Anonymous Flatpak installs fail against private packages .

"Signature verification failed":
The signature lookaside may not be configured on the remote. Run the remote-modify command from the landing page to add it. Also confirm the GHCR package was re-published after any key rotation.

"flatpak: unrecognized option '--signature-lookaside'":
The --signature-lookaside flag requires Flatpak ≥ 1.17. On older versions, use the unsigned remote-add command with --no-gpg-verify.

App runtime not found:
The .flatpakref includes RuntimeRepo pointing to Flathub by default. If this is empty or points elsewhere, the user may need to add Flathub manually:

flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

Updates not appearing:
Flatpak checks for updates via the OCI index. Run flatpak update to pull the latest version. If the index is stale, confirm that the latest workflow run completed the publish-site job successfully.

11. Repository Maintenance#

Removing apps, channels, or architectures#

AetherPak uses reconcile-based removal: there is no explicit delete command. To remove an app, channel, or architecture from your published repository :

  1. Delete the OCI image from GHCR
  2. Re-run the publish workflow — the next run reconciles index/static against the registry and automatically drops any entries whose image no longer exists

The index only removes entries when the registry returns a definitive "not found" response. Transient errors or authentication failures leave entries intact to avoid accidental removal .

Deleting images from GHCR#

Via the web UI (no token needed) :

  1. Go to your profile or organization → Packages
  2. Open the package (named after your repository)
  3. Find the version tagged <branch>-<arch> (e.g. stable-x86_64) or locate it by digest
  4. Click the version → Delete version → confirm

Via the GitHub CLI:

# Delete by version ID (requires a token with delete:packages scope)
gh api -X DELETE /orgs/OWNER/packages/container/PKG/versions/ID

Via Skopeo (registries that support the OCI delete API):

skopeo login ghcr.io
skopeo delete docker://ghcr.io/OWNER/REPO@sha256:<digest>

Reconcile-only mode#

After deleting one or more images, you can update the landing page without running a full rebuild by triggering the workflow with reconcile-only: true :

# Via workflow_dispatch, adding this input:
jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak.yaml
      reconcile-only: true

Or trigger it manually from Actions → your workflow → Run workflow and enable the reconcile-only toggle if you've defined it as a workflow_dispatch input:

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      reconcile-only:
        description: "Skip builds; only reconcile the index"
        type: boolean
        default: false

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak.yaml
      reconcile-only: ${{ inputs.reconcile-only || false }}

Dry-run mode#

Use dry-run: true to build and validate your manifest without publishing anything. All build, lint, and import steps run normally, but the publish-oci and publish-site jobs are skipped entirely :

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      dry-run: true

This is useful for validating manifest changes in pull requests without modifying any remote state.

How the index works#

The index/static file is the heart of your repository — it's a JSON document that Flatpak reads to discover available apps and their OCI image digests :

{
  "Registry": "https://ghcr.io",
  "Results": [
    {
      "Name": "owner/repo",
      "Images": [
        {
          "Digest": "sha256:...",
          "Architecture": "amd64",
          "Tags": ["stable"],
          "Labels": {
            "org.flatpak.ref": "app/org.example.App/x86_64/stable",
            "org.flatpak.commit": "...",
            "org.flatpak.metadata": "..."
          }
        }
      ]
    }
  ]
}

Each publish run seeds this index from the current deployed site, then merges in new records. The index accumulates entries across architectures, channels, and apps — all from separate publish-oci cells — into a single file. When the index is deployed, any entry whose OCI image has been deleted from the registry is dropped from the next version of the index.

OCI tags follow the pattern <app-id>-<branch>-<arch> (with dots in the app-id encoded as underscores), so multiple apps in the same repository share a single OCI repository path without overwriting each other's tags .

12. Customization#

AetherPak generates a functional, clean landing page by default, but you have several options to brand and customize the output to match your project's identity.

Custom branding via aetherpak.yaml#

The branding section of aetherpak.yaml lets you tune the default landing page appearance without replacing it :

branding:
  logo_url: https://example.org/logo.png # full URL to your project logo
  favicon_url: https://example.org/favicon.ico # browser tab favicon
  accent_color: "#3584e4" # primary accent color (CSS hex)
  footer_text: "© 2025 My App Project" # footer text on the landing page
  index_template: templates/custom-index.html # path to a fully custom HTML template

Branding via workflow inputs#

In single-app manifest mode, you can pass branding overrides as workflow inputs without modifying aetherpak.yaml :

InputDescription
landing-pagetrue (default) to render index.html; false to skip it and serve your own page from index/static
index-templatePath to a custom HTML template file; overrides branding.index_template in aetherpak.yaml
remote-nameFriendlier name for the Flatpak remote and .flatpakrepo filename (e.g. my-apps instead of owner-repo)
pages-urlOverride the default Pages URL — use this for custom domains
site-subpathServe the Flatpak repository under a subdirectory (e.g. flatpak)

Custom domain#

To serve your repository from a custom domain (e.g. https://apps.example.org):

  1. Configure the custom domain in your repository Settings → Pages → Custom domain
  2. Pass it explicitly via the pages-url input :
jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      pages-url: https://apps.example.org

Hosting under a subpath#

If your Pages site is shared with other content (e.g. a project website), you can publish the Flatpak repository under a subdirectory using site-subpath:

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      pages-url: https://owner.github.io/repo/flatpak
      site-subpath: flatpak

Self-hosting the site#

Set deploy: false to skip GitHub Pages deployment. The workflow uploads the built site as an artifact instead, which you can download and serve from any web host :

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      deploy: false
      pages-url: https://flatpak.example.org # where you'll host it
      artifact-name: my-flatpak-site # name of the uploaded artifact

Channel mappings#

Map git branches or tags to Flatpak channel names using channel_mappings in aetherpak.yaml. Glob patterns are supported :

channel_mappings:
  main: beta # pushes to 'main' branch → 'beta' channel
  "release/*": stable # 'release/1.0' → 'stable' channel
  "v*": stable # version tags → 'stable' channel

Concurrency group#

By default, the publish-site job uses the repository name as its concurrency group key, so only one publish-site job runs at a time per repository. If you have multiple independent AetherPak deployments in the same repository (e.g. publishing to different subpaths), give each its own concurrency group :

jobs:
  publish-stable:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak.yaml
      concurrency-group: publish-stable

  publish-nightly:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      config: aetherpak-nightly.yaml
      concurrency-group: publish-nightly

Custom landing page template#

For complete control over the landing page HTML, supply a custom template via index-template (or branding.index_template). AetherPak's build-site command passes the index data to the template, letting you render it however you like. When landing-page: false is set, index.html is omitted entirely and you can consume index/static directly to build your own page.

13. Container Images#

AetherPak's CI pipeline runs inside pre-built container images that ship all the necessary Flatpak tooling. Understanding which image is used where helps you when debugging builds or setting up custom workflows.

Images in use#

The reusable workflow uses CLI container images from ghcr.io/aetherpak/cli, not the flatpak-containers images directly. These CLI images bundle the aetherpak binary alongside the Flatpak toolchain :

ImageTagUsed byContents
ghcr.io/aetherpak/cli:<cli-version>plan, prep-bundle, publish-oci, publish-siteaetherpak, flatpak, ostree, git, jq, gpg
ghcr.io/aetherpak/cli:<cli-version>-builderbuild-manifestEverything above + flatpak-builder, flatpak-builder-lint, elfutils, ccache tools

The cli-version workflow input controls which version is pulled (default v0.15.1) . Pin to a specific version tag to ensure reproducible builds:

jobs:
  publish:
    uses: aetherpak/actions/.github/workflows/publish.yml@v3
    with:
      manifest-path: org.example.App.json
      cli-version: v0.15.1

Underlying base images#

The CLI images are built on top of the Fedora-based container images from aetherpak/flatpak-containers :

ghcr.io/aetherpak/flatpak — the base image :

  • Base: registry.fedoraproject.org/fedora-minimal (pinned by digest for reproducibility)
  • Includes: flatpak, ostree, git, jq, curl, tar, gzip, gnupg2, ca-certificates, shared-mime-tools
  • Pre-configured Flathub repository remote

ghcr.io/aetherpak/flatpak-builder — the builder image :

  • Extends ghcr.io/aetherpak/flatpak via multi-stage build
  • Adds: flatpak-builder, elfutils, appstream, desktop-file-utils, python3, gobject-introspection, cairo, patch, unzip, bzip2, zstd
  • Includes flatpak-builder-lint (compiled from source)

Both images are published for linux/amd64 and linux/arm64 as multi-arch manifests, enabling automatic architecture selection .

Why --privileged for builds#

The build-manifest job runs with --privileged . This is required because flatpak-builder installs the app's runtime and SDK from the baked Flathub remote. Running as root inside a privileged container lets flatpak-builder write directly to the system install location without needing the polkit/dbus system helper — which is not available in a container environment. Non-build jobs (plan, publish-oci, publish-site) do not need --privileged.

Architecture limitations#

Build support is limited to x86_64 and aarch64 — these are the only architectures for which the builder container image is published . There is no cross-compilation support; each architecture is built on a native runner.

What happens if cli-version has no matching container#

If you specify a cli-version that doesn't have a corresponding published container image tag, the non-build jobs fall back to aetherpak/setup-cli for CLI installation instead of pulling the CLI container . This fallback runs on ubuntu-latest and installs dependencies via apt-get.

14. Reference#

Live demos#

DemoLanding pageRepository
Single app (GNOME Sudoku)aetherpak.github.io/actions-demoaetherpak/actions-demo
Multi-app repositoryabn.github.io/flatpakrepoabn/flatpakrepo

Key repositories#

RepositoryDescription
aetherpak/actionsComposite actions and reusable workflow
aetherpak/cliThe aetherpak CLI tool
aetherpak/setup-cliGitHub Action to install the CLI
aetherpak/flatpak-containersBase container images
aetherpak/actions-demoMinimal working demo

Additional documentation#

  • ARCHITECTURE.md — deep dive into how the pipeline, index, OCI signing, and multi-app orchestration work
  • CONTRIBUTING.md — development setup, testing procedures, and code style guidelines
  • aetherpak/actions README — quick-start reference, full input tables, and additional usage examples

Getting help and reporting issues#

Quick reference: reusable workflow inputs#

The full input reference for aetherpak/actions/.github/workflows/publish.yml@v3 :

Manifest mode inputs (single app):

InputDefaultDescription
manifest-pathPath to the Flatpak manifest
archesx86_64 aarch64Architectures to build
branchautostable on tags, beta on default branch, else ref name
run-lintertrueRun flatpak-builder-lint
builder-args--install-deps-from=flathubExtra flatpak-builder flags

Config mode inputs (multi-app):

InputDefaultDescription
configaetherpak.yamlPath to aetherpak.yaml
appForce rebuild of one specific app ID
force-allfalseRebuild every app
base-shagithub.event.beforeDiff base commit for change detection
workflow-pathCaller workflow path; touching it triggers rebuild-all

Shared inputs:

InputDefaultDescription
reconcile-onlyfalseSkip builds; only reconcile index
registryghcr.ioOCI registry host
oci-repositoryGitHub repoRegistry image path
remote-name<owner>-<repo>Flatpak remote name
pages-urlproject Pages URLPublic hosting URL
landing-pagetrueWrite index.html
deploytrueDeploy to GitHub Pages
signingautoauto, gpg, or off — see GPG Signing
runtime-repoFlathub URLRuntimeRepo in .flatpakref files
cachetrueEnable build caching
cache-keyCustom cache key prefix
cache-statetrueCache builder state
cache-ccachetrueCache ccache artifacts
cache-build-dirfalseCache build directories
site-subpathSubdirectory under pages-url
cli-versionv0.15.1aetherpak CLI version to use
upload-bundlefalseUpload .flatpak bundle as artifact
prebuilt-bundle-artifactArtifact name pattern for prebuilt bundles
dry-runfalseBuild only; skip push and deploy
submodulesrecursiveSubmodule fetch mode

Secrets:

SecretDescription
gpg-private-keyASCII-armored GPG private key for signing
gpg-private-key-passphrasePassphrase for the GPG key (if protected)
registry-tokenRegistry auth token (defaults to github.token for GHCR)
AetherPak Maintainer Guide: Building and Publishing Flatpak Apps with GitHub Actions | Dosu