Files
voculinux/docs/FLATPAK_REMOTE.md
T
c9ea6c7de7 feat(flatpak): publish a signed self-hosted OSTree remote on releases (#875)
* feat(flatpak): publish a signed self-hosted OSTree remote on releases (#785)

release.yml gains publish-flatpak-remote: it imports the per-arch
.flatpak bundles into an OSTree repo, signs commits and the summary
with a provisioned GPG key, regenerates static deltas and the
vocahq.flatpakrepo/index page, and pushes to VocaHQ/vocalinux-flatpak
served by GitHub Pages — the Homebrew-tap analogue, so installs
auto-update via flatpak update instead of per-release downloads.

The job is secret-gated like publish-aur (FLATPAK_GPG_PRIVATE_KEY +
FLATPAK_REPO_TOKEN) and skips itself while unconfigured. Prior repo
objects are pulled back in from gh-pages so clients get incremental
deltas. The manifest now declares branch: stable and the flatpakrepo
declares DefaultBranch=stable + RuntimeRepo for the Flathub GNOME
runtime.

Docs: new docs/FLATPAK_REMOTE.md (one-time setup, rotation,
verification); README, docs/INSTALL.md, packaging/flatpak/README.md
and the release notes now lead with the remote and keep release
bundles as the no-auto-update fallback. tests/test_release_integrity.py
pins the job's shape.

Closes #785

* fix(flatpak-remote): publish a .flatpakref and gate the remote to stable tags

- needs build-and-release so the remote cannot lead the GitHub Release
- stable tags only: prereleases must not reach `flatpak update`
- probe gh-pages before the empty-repo fallback: a transient checkout
  failure no longer discards remote history
- publish com.vocalinux.Vocalinux.flatpakref (RuntimeRepo lives there, not
  on .flatpakrepo) plus vocahq.gpg, so clean installs resolve the GNOME
  runtime and key rotation works via remote-modify --gpg-import
- gate the release-notes remote instructions on the publish actually
  running; the bundle path stays primary otherwise
- document rotation via remote-modify --gpg-import / remote-delete
  --force and the explicit Flathub setup step

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
2026-09-29 22:45:49 -07:00

6.6 KiB

Self-hosted Flatpak remote

Vocalinux publishes a signed OSTree Flatpak remote on every stable v* tag — the Homebrew-tap analogue to VocaHQ/homebrew-vocamac (issue #785). The remote lives in the dedicated repo VocaHQ/vocalinux-flatpak, served by GitHub Pages:

flatpak install https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref
flatpak update   # picks up each new release

The .flatpakref adds the vocahq remote (via SuggestRemoteName=) and lets flatpak resolve org.gnome.Platform//50 through its RuntimeRepo= pointer to Flathub — one command, no separate Flathub setup. The equivalent manual steps:

flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo
flatpak install vocahq com.vocalinux.Vocalinux

Only the app is ours; the GNOME runtime still comes from Flathub — RuntimeRepo= is a .flatpakref key (it is ignored on .flatpakrepo, which is why the app ref carries it, not the remote descriptor). GitHub Release .flatpak bundles (#784) remain as a no-auto-update fallback, and are the only Flatpak path for prerelease tags, which never reach the remote.

Maintainer setup (one time)

  1. Repo — create VocaHQ/vocalinux-flatpak (public).

  2. Pages — Settings → Pages → "Deploy from a branch" → branch gh-pages, folder / (root). The first publish creates the branch, so Pages can be pointed at it afterwards; creating an empty gh-pages upfront works too.

  3. Signing key — OSTree remotes sign the summary and commits with GPG:

    gpg --quick-gen-key "Vocalinux Flatpak <flatpak@vocahq.com>" ed25519 sign never
    gpg --list-secret-keys                          # note the KEYID
    gpg --export-secret-keys --armor KEYID          # -> FLATPAK_GPG_PRIVATE_KEY
    gpg --gen-revoke KEYID > flatpak-remote-revoke.asc   # store it somewhere safe
    

    A passphrase-less key keeps CI simplest — libostree signs through gpg-agent, which has no --passphrase-fd to reach for. A passphrase works too: set FLATPAK_GPG_PASSPHRASE and the workflow presets it onto the keygrip via gpg-preset-passphrase.

  4. Secrets on VocaHQ/vocalinux (Settings → Secrets and variables → Actions):

    • FLATPAK_GPG_PRIVATE_KEY — the armored private key
    • FLATPAK_GPG_PASSPHRASE — optional
    • FLATPAK_REPO_TOKEN — fine-grained PAT scoped to VocaHQ/vocalinux-flatpak with Contents: read and write
  5. Variables (optional) — same settings page, Variables tab:

    • FLATPAK_REMOTE_REPO — publish target (default VocaHQ/vocalinux-flatpak)
    • FLATPAK_REMOTE_URL — remote URL baked into the .flatpakrepo (default https://vocahq.github.io/vocalinux-flatpak/repo; set e.g. https://flatpak.vocalinux.com/repo for a custom domain)
    • FLATPAKREPO_URL — where users fetch vocahq.flatpakrepo (default https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo)
    • FLATPAKREF_URL — where users fetch the app .flatpakref (default https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref)
  6. Trigger — push a stable v* tag (alpha/beta/rc tags do not publish to the remote), or re-run publish-flatpak-remote on an existing release run after configuring the secrets.

Without the secrets the job skips itself (same pattern as publish-aur), so an unconfigured remote never fails a release.

How a publish works

release.yml → publish-flatpak-remote runs on stable tags only, after the GitHub Release exists and the two arch builds finish:

  1. Downloads Vocalinux-<version>-{x86_64,aarch64}.flatpak from the build jobs.
  2. Probes the remote repo for an existing gh-pages branch; only a confirmed first publish may skip the history checkout — a transient failure aborts instead of seeding an empty repo over the live remote. It then checks out gh-pages and copies repo/ over, so existing objects and deltas survive — clients pull an incremental delta each release, not the whole app.
  3. flatpak build-import-bundle --gpg-sign imports each bundle under the signing key.
  4. flatpak build-update-repo --generate-static-deltas --gpg-sign --gpg-import rebuilds the summary, signs it, and generates static deltas.
  5. Regenerates vocahq.flatpakrepo (URL + base64 public key), com.vocalinux.Vocalinux.flatpakref (the one-command app ref carrying the RuntimeRepo= pointer to Flathub), vocahq.gpg (the bare public key, for the key-rotation path below), and the index page from packaging/flatpak/index.html.
  6. Publishes the directory back to gh-pages via peaceiris/actions-gh-pages.

Verifying a publish

flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --if-not-exists vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo
flatpak remote-info vocahq com.vocalinux.Vocalinux   # signed commit, both arches
flatpak install vocahq com.vocalinux.Vocalinux
flatpak run com.vocalinux.Vocalinux
# after the next tag: flatpak update pulls the delta

Key rotation and loss

Generate a fresh key, replace FLATPAK_GPG_PRIVATE_KEY (and FLATPAK_GPG_PASSPHRASE if used), then re-run publish-flatpak-remote. The published .flatpakrepo/.flatpakref then carry the new GPGKey=, so new installs trust it immediately.

Existing installs must import the new key into the remote's trusted keyring — re-running remote-add does not replace it (with --if-not-exists it does nothing at all), and flatpak update fails verification until the key is updated. Point users at the public key published beside the repo:

curl -fsSL https://vocahq.github.io/vocalinux-flatpak/vocahq.gpg -o /tmp/vocahq.gpg
flatpak remote-modify --gpg-import=/tmp/vocahq.gpg vocahq
flatpak update

remote-modify --gpg-import adds the key — the old one stays trusted, which is what makes a routine rotation seamless. To revoke a leaked key, drop the remote's keyring instead: delete and re-add the remote (installed apps keep their vocahq origin and update normally afterwards):

flatpak remote-delete --force vocahq   # --force: the remote is still in use
flatpak remote-add vocahq https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo
flatpak update

Either way is worth a line in that release's notes. The revoke certificate is for the day the key leaks, not for routine rotation.