Getting Started with the AetherPak CLI#
1. Introduction#
AetherPak CLI is a standalone, performance-focused command-line tool for building, publishing, and managing Flatpak applications using OCI-backed distribution via GitHub Container Registry (GHCR) and static index hosting via GitHub Pages . Rather than treating your Flatpak workflow as a black box, the CLI exposes the full pipeline as composable, scriptable commands you can mix and match in any environment — local workstations, CI runners, or custom automation.
Plumbing vs. Porcelain#
The CLI follows a Plumbing vs. Porcelain design philosophy, modelled directly on Git :
- Plumbing (Primitives) — Independent, highly-scoped, single-responsibility commands suited for complex workflows and custom CI matrices. These include
plan,build,import,push-oci,build-site,inspect-repo, andresolve-channel. - Porcelain (Convenience Wrappers) — High-level commands that coordinate multiple plumbing modules automatically in-memory, providing a streamlined experience for the most common workflows. These include
status,add,publish, andrelease.
The separation means you can use the high-level publish command for day-to-day work, but drop down to build + push-oci individually whenever you need fine-grained control — for example, to insert custom steps between build and publish, or to drive a dynamic CI matrix.
Configuration is Optional#
A key design principle of the CLI is that most commands work without a configuration file. You can pass all parameters as CLI flags or AETHERPAK_-prefixed environment variables (e.g. AETHERPAK_REGISTRY) . The aetherpak.yaml file becomes valuable when you want to manage multiple apps, persist defaults, or drive the automated plan/release cycle — but it is never required for one-off operations or quick testing.
2. Installation#
The AetherPak CLI is a single statically-linked binary for Linux (amd64 and arm64). There are two ways to get it: via the aetherpak/setup-cli GitHub Action for CI environments, or by downloading a release archive manually.
Using aetherpak/setup-cli (Recommended for CI)#
The setup-cli action downloads, extracts, and adds the aetherpak binary to $GITHUB_PATH in a single step . All composite actions in aetherpak/actions call this automatically when the CLI isn't already on PATH, but you can invoke it directly to pin a specific version.
- name: Set up AetherPak CLI
uses: aetherpak/setup-cli@v1
with:
version: 'latest' # or a specific tag, e.g. 'v0.15.1'
install-dependencies: 'true' # installs flatpak, ostree, flatpak-builder, etc.
Inputs :
| Input | Default | Description |
|---|---|---|
version | latest | CLI version tag to install (e.g. v0.15.1). Use latest to always pull the newest release. |
repo | aetherpak/cli | GitHub repository to download releases from. |
install-dependencies | true | When true, installs missing system packages: flatpak, ostree, flatpak-builder, patch, unzip, bzip2, and zstd. Uses apt-get, dnf, or microdnf depending on the runner. |
The action is idempotent — if the requested version is already on PATH, it skips the download entirely . It supports Linux runners only (amd64 and arm64) and installs the binary to $RUNNER_TEMP/aetherpak-bin .
Tip: When you're using the
aetherpak/actionsreusable workflow, all composite actions callsetup-cliautomatically if the CLI isn't already onPATH. You only need to invokesetup-climanually when pinning to a specific version or running on a bareubuntu-latestrunner without the AetherPak container.
To pin to a specific version and skip dependency installation (e.g. inside a container that already has the toolchain):
- uses: aetherpak/setup-cli@v1
with:
version: v0.15.1
install-dependencies: 'false'
Manual Installation#
Pre-built release archives are published to github.com/aetherpak/cli/releases for every tagged release . Archives are named aetherpak-linux-amd64.tar.gz and aetherpak-linux-arm64.tar.gz.
# Example: download and install the latest release for amd64
VERSION=$(curl -s https://api.github.com/repos/aetherpak/cli/releases/latest \
| grep '"tag_name"' | cut -d'"' -f4)
curl -sL "https://github.com/aetherpak/cli/releases/download/${VERSION}/aetherpak-linux-amd64.tar.gz" \
| tar -xz
chmod +x aetherpak
sudo mv aetherpak /usr/local/bin/
Verify the installation:
aetherpak --version
aetherpak status
3. Zero-Config Commands#
Many AetherPak CLI commands work without a configuration file. Rather than requiring an aetherpak.yaml, they accept all parameters as CLI flags or AETHERPAK_-prefixed environment variables — for example, AETHERPAK_REGISTRY=ghcr.io or AETHERPAK_OCI_REPOSITORY=myorg/myrepo . This makes them ideal for one-off operations, quick local testing, and CI/CD pipelines where configuration is supplied by the environment rather than a committed file.
The commands below are listed roughly in the order you'd use them through the pipeline.
aetherpak status#
Validates that required system dependencies are present, checks any configuration file, and optionally decrypts and verifies GPG keys .
# Quick dependency check
aetherpak status
# Verify a GPG signing key
aetherpak status --gpg-key /path/to/key.asc --gpg-key-passphrase "$GPG_PASSPHRASE"
# Machine-readable JSON output (useful in CI scripts)
aetherpak status --json
Key flags :
| Flag | Description |
|---|---|
--gpg-key <path> | GPG private key block(s) or path(s) to verify signing setup |
--gpg-key-passphrase <value> | Passphrase to test key decryption |
--json | Output raw diagnostics as JSON for script parsing |
aetherpak build#
Wraps flatpak-builder in a managed sandbox. It automatically installs/resolves flatpak-builder-lint, runs pre-build linting on the manifest, executes the build with ccache/state management, and runs post-build linting on the generated OSTree repository .
# Build for x86_64 (default)
aetherpak build --manifest apps/org.example.App/manifest.json --arch x86_64
# Build for aarch64 with linting enabled
aetherpak build --manifest apps/org.example.App/manifest.json --arch aarch64 --run-linter
# Build with an extra flatpak remote and SDK pre-installed
aetherpak build \
--manifest manifest.json \
--arch x86_64 \
--flatpak-remote flathub=https://dl.flathub.org/repo/flathub.flatpakrepo \
--flatpak-dep flathub:org.gnome.Sdk//47
Key flags :
| Flag | Default | Description |
|---|---|---|
--manifest <path> | Path to the Flatpak manifest (JSON or YAML) | |
--arch <arch> | x86_64 | Target CPU architecture |
--app-id <id> | (from manifest) | Reverse-DNS app ID (auto-detected from manifest) |
--branch <branch> | Flatpak channel/branch | |
--run-linter | Run flatpak-builder-lint before and after build | |
--repo-path <path> | repo | Destination OSTree repository path |
--ccache-dir <path> | .ccache | CCache directory |
--flatpak-remote <name=url> | Register a Flatpak remote before building (repeatable) | |
--flatpak-dep <remote:ref> | Install a Flatpak dependency before building (repeatable) |
Note:
buildis a plumbing command. For most use cases, preferaetherpak publish --manifest <path>, which chainsbuild+push-ociin a single step.
aetherpak import#
Ingests a prebuilt .flatpak bundle into a local OSTree repository. The app ID, architecture, and branch are auto-detected from the bundle's metadata when not specified — so you usually only need to provide the URL .
# Download and import a bundle (app-id/arch/branch auto-detected)
aetherpak import --bundle-url https://example.com/org.example.App_x86_64.flatpak
# Verify checksum and set an explicit branch
aetherpak import \
--bundle-url https://example.com/app.flatpak \
--bundle-sha256 abc123...64hexchars \
--branch stable
# Import a local bundle file
aetherpak import --bundle-path ./build/org.example.App.flatpak
Key flags :
| Flag | Default | Description |
|---|---|---|
--bundle-url <url> | HTTP URL of the bundle to download (repeatable) | |
--bundle-path <path> | Local path to a .flatpak file; supports globs (repeatable) | |
--bundle-sha256 <hex> | Expected SHA-256 checksum (verified; computed if omitted) | |
--app-id <id> | (from bundle) | Override the app ID |
--arch <arch> | (from bundle) | Override the architecture |
--branch <branch> | (from bundle) | Override the Flatpak channel |
--repo-path <path> | repo | Destination OSTree repository path |
aetherpak push-oci#
Converts a local OSTree repository branch to an OCI container image and pushes it to a registry. This is the plumbing command underlying the OCI publish step .
# Push to GHCR (unsigned — useful for local dev)
aetherpak push-oci \
--app-id org.example.App \
--registry ghcr.io \
--oci-repository myorg/myrepo \
--no-sign
# Push with GPG signing
aetherpak push-oci \
--app-id org.example.App \
--registry ghcr.io \
--oci-repository myorg/myrepo \
--gpg-key /path/to/key.asc
Key flags :
| Flag | Default | Description |
|---|---|---|
--app-id <id> | Reverse-DNS app ID | |
--arch <arch> | x86_64 | Target architecture |
--branch <branch> | Flatpak channel/branch | |
--registry <host> | OCI registry host (e.g. ghcr.io) | |
--oci-repository <path> | Registry repository path (e.g. myorg/myrepo) | |
--repo-path <path> | repo | Local OSTree repository path |
--gpg-key <path> | GPG private key for signing OCI manifests | |
--no-sign | Disable GPG signing entirely | |
--allow-unsigned | Allow pushing without a signing key |
aetherpak publish#
A porcelain command that chains build (or import) and push-oci sequentially in memory. This is the recommended starting point for one-off or zero-config publishing — use it instead of calling build and push-oci separately unless you need to insert custom steps between them .
# Build from a manifest and push to GHCR
aetherpak publish \
--manifest apps/org.example.App/manifest.json \
--registry ghcr.io \
--oci-repository myorg/myrepo
# Import a bundle and push
aetherpak publish \
--bundle https://example.com/app.flatpak \
--registry ghcr.io \
--oci-repository myorg/myrepo
# Disable signing for local testing
aetherpak publish \
--manifest manifest.json \
--registry ghcr.io \
--oci-repository myorg/myrepo \
--no-sign
aetherpak plan#
A plumbing command that computes the build matrix — which apps and architectures need to be built. When given a manifest directly, it expands it into the requested architectures. When given a config file with --base-sha, it compares git history to find only the apps that changed .
# Compute a build matrix for a single manifest
aetherpak plan --manifest apps/manifest.json --arch x86_64 --arch aarch64
# Config-driven planning with change detection
aetherpak plan --config aetherpak.yaml --base-sha "$GITHUB_SHA" --output matrix
# Output as a GitHub Actions matrix JSON
aetherpak plan --manifest manifest.json --arch x86_64 --output matrix
Key flags :
| Flag | Description |
|---|---|
--manifest <path> | Single manifest path (bypasses config) |
--arch <arch> | Architectures to include (repeatable) |
--base-sha <sha> | Git commit SHA to diff against for change detection |
--force <value> | all to rebuild everything, or a specific app ID |
--output <format> | json, matrix, matrix-manifest, matrix-bundle, or apps |
--branch <branch> | Branch/channel to use (defaults to stable) |
--disable-linter | Disable linting for all planned apps |
4. Bootstrapping a Multi-App Repository#
When you want to manage multiple Flatpak apps from a single repository, AetherPak uses an aetherpak.yaml configuration file. There is no init command — instead, you use aetherpak add repeatedly to incrementally build up the configuration, one app at a time .
aetherpak add#
The add command creates or modifies aetherpak.yaml by adding one application entry from one of three source types :
- Local manifest — the app ID is auto-detected from the manifest file
- Bundle URL — the bundle is downloaded, fingerprinted, and its SHA-256 is recorded
- Git repository — the repo is added as a recursively-initialized submodule with the manifest auto-detected inside it
After resolving the app, a colored diff is displayed showing exactly what will be written to aetherpak.yaml. Changes are only applied after you confirm. Pass -y / --confirm to skip the prompt in non-interactive scripts.
When run on a TTY without any source flags, add launches an interactive wizard that walks you through choosing the source type and filling in the required details .
Adding from a local manifest#
# App ID is detected automatically from the manifest
aetherpak add --manifest apps/org.example.App/manifest.json
# Skip the diff confirmation
aetherpak add --manifest apps/org.example.App/manifest.json -y
# Specify arch and branch explicitly
aetherpak add \
--manifest apps/org.example.App/manifest.json \
--arch x86_64 --arch aarch64 \
--branch stable
Adding from a bundle URL#
# SHA-256 is computed automatically on first download and recorded
aetherpak add \
--bundle-url https://example.com/org.example.App_x86_64.flatpak \
--app-id org.example.App
# Provide the checksum explicitly (skips download for verification)
aetherpak add \
--bundle-url https://example.com/org.example.App_x86_64.flatpak \
--app-id org.example.App \
--bundle-sha256 abc123...64hexchars
Adding from a Git repository#
# The repo is added as a submodule; manifest is auto-detected inside it
aetherpak add --git https://github.com/org/app-manifest.git
# Specify where to place the submodule and which manifest to use
aetherpak add \
--git https://github.com/org/app-manifest.git \
--submodule-path manifests/app-manifest \
--git-manifest org.example.App.json
If the user declines the diff confirmation after a
--gitadd, the submodule addition is automatically rolled back .
Resulting aetherpak.yaml#
After running aetherpak add for a few apps, the resulting configuration might look like this:
registry: ghcr.io
pages_url: https://myorg.github.io/myrepo
oci_repository: myorg/myrepo
apps:
# Manifest-based app: built from source on each push
- id: org.example.AppOne
manifest: apps/org.example.AppOne/org.example.AppOne.json
arches: [x86_64, aarch64]
branch: stable
# Bundle-based app: ingested from upstream prebuilt binaries
- id: com.example.AppTwo
branch: stable
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"
Each app requires exactly one of manifest or bundles — not both . For the full aetherpak.yaml schema reference, including global settings, defaults, branding, channel_mappings, and linter configuration, see the CLI README and the Maintainer Guide §5 .
Common add Flags Reference#
| Flag | Description |
|---|---|
--manifest <path> | Path to a local Flatpak manifest (source type 1) |
--bundle-url <url> | URL of a remote .flatpak bundle (source type 2) |
--git <url> | Git repository URL to add as a submodule (source type 3) |
--app-id <id> | Override app ID (required for bundle URL, auto-detected otherwise) |
--arch <arch> | Target architecture(s), repeatable (defaults to host arch) |
--branch <branch> | Flatpak channel/branch (default: stable) |
--bundle-sha256 <hex> | Expected checksum (verified; computed if omitted) |
--git-manifest <path> | Manifest path within the git repo (auto-detected if omitted) |
--submodule-path <path> | Submodule destination (default: manifests/<reponame>) |
-y, --confirm | Skip the diff confirmation prompt |
--run-linter | Enable linter checks on manifests |
--ccache | Enable compiler cache |
5. Local Testing & Container Images#
For local end-to-end testing using Docker or Podman Compose, see the Local End-to-End Testing with Podman Compose guide. For details on the CLI container image variants (standard vs. builder), tagging, and usage in CI/CD, see CLI Container Images: Standard and Builder Variants. For the underlying utility images (ghcr.io/aetherpak/flatpak and ghcr.io/aetherpak/flatpak-builder), see Utility Images: flatpak and flatpak-builder.
6. Next Steps#
You now have everything you need to run the AetherPak CLI locally and in CI. Here are the natural next steps depending on what you want to do:
Set Up a Full GitHub Actions Pipeline#
The AetherPak Maintainer Guide covers the complete end-to-end workflow setup : creating the reusable workflow, enabling GitHub Pages, configuring GHCR permissions, and going from a first push to a publicly installable Flatpak repository in minutes.
Configure GPG Signing#
Signed repositories give users cryptographic assurance that the packages they install haven't been tampered with. The Maintainer Guide's GPG Signing section walks through generating a key, adding it as a repository secret, and configuring signing: auto (or signing: gpg) in your workflow. For local testing, pass --gpg-key /path/to/key.asc to push-oci, publish, or release, or use --no-sign to skip signing entirely.
Build for Multiple Architectures#
The build and publish commands accept --arch as a repeatable flag. The plan command accepts multiple --arch values and emits a parallelizable matrix. In aetherpak.yaml, set arches: [x86_64, aarch64] per app (or in defaults) to build for both architectures automatically . The GitHub Actions reusable workflow runs each architecture on a native runner in parallel.
Customize Your Landing Page#
The generated index.html landing page can be fully customized through the branding block in aetherpak.yaml — set a custom logo, accent color, favicon, and footer text . For complete control, supply your own Go HTML template via branding.index_template (or the --index-template flag on build-site and release). See the Custom Index Templating section of the CLI README for the full template context and available helper functions .
Explore the Full CLI Reference#
Every command, flag, and environment variable is documented in the AetherPak CLI README . The --help flag on any subcommand also prints the full flag listing inline:
aetherpak --help
aetherpak build --help
aetherpak publish --help
aetherpak release --help