Skip to content

ci(release): publish project-maintained pacman repo to R2 (#6334)#6352

Merged
houko merged 2 commits into
mainfrom
ci/arch-pacman-repo
Jun 29, 2026
Merged

ci(release): publish project-maintained pacman repo to R2 (#6334)#6352
houko merged 2 commits into
mainfrom
ci/arch-pacman-repo

Conversation

@houko

@houko houko commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What

Implements the project-maintained pacman repository proposed in #6334 — the path that can actually ship today.
AUR account registration is closed with no reopening date, which blocks the AUR automation in #6341 (those jobs no-op forever without an AUR account).
This publishes the same release-pinned binary packages directly, so Arch users install with pacman -Syu and track every release through normal system updates.

The two are complementary, not competing: when AUR registration reopens, #6341 serves yay users; this serves pacman users. Nothing here changes or depends on #6341.

How

A new publish_arch_repo job in release.yml runs on every v* tag (and on a channel=current re-publish), modeled on the existing sync_homebrew* jobs. It runs a composite action that executes packaging/arch-repo/publish-arch-repo.sh inside archlinux:base-devel, which:

  • Reuses the committed PKGBUILDs under packaging/aur/<package>/ as the single source of truth, deriving only the per-release values (pkgver, sha256sums, the desktop bundle version off the actual .deb asset name, the pinned ghcr.io/... tag) — the same derivation the AUR publisher in ci(release): publish AUR packages on release (#6334) #6341 does.
  • Builds and GPG-signs each package with makepkg --sign (no Rust compile — these repackage the prebuilt release artifacts).
  • Folds librefang-bin / librefang-desktop-bin / librefang-docker into one shared, signed pacman database with repo-add --sign, pulling the existing db from R2 first so the update is incremental.
  • Syncs packages, signatures, db, and the public key to Cloudflare R2 (packages.librefang.ai).
  • Prunes old package files beyond the newest 5 per package (best-effort; the db always points at the latest build).

Single job, not three (unlike #6341): all packages share one pacman db, so they must be repo-add'd together. A fixed concurrency: { group: arch-pacman-repo } serialises the read-modify-write on that db across releases. A failed build of one package is skipped with a ::warning:: and the others still publish.

Design decisions I made (please confirm)

These were the open questions from the issue thread; I picked defaults and want a maintainer's sign-off:

  • Cloudflare R2, not GitHub Pages (pavver's original suggestion). The project already runs on Cloudflare (deploy-worker.yml, CLOUDFLARE_ACCOUNT_ID); R2 has no GH-Pages 1GB/100GB soft caps, free egress, and librefang.ai DNS is presumably already there. CLOUDFLARE_ACCOUNT_ID is reused; only the R2 S3 keys are new.
  • First long-lived GPG key. pacman verifies GPG/PGP only — it cannot use the cosign keyless signatures sign_release_artifacts produces. So this is the project's first long-lived signing key. Documented as an offline primary key + passphrase-less CI subkey so a runner compromise revokes the subkey without rotating the identity users imported.
  • Retain 5 versions / x86_64 only / publish every tag (matches the committed PKGBUILDs' arch=('x86_64'), the desktop amd64-only bundle, and the Homebrew sync cadence). aarch64 and a separate [librefang-beta] channel are natural later extensions.

Maintainer setup required (the job no-ops until then)

Safe to merge first — the job degrades to a ::notice:: no-op while any secret is unset, so forks and the unconfigured repo are unaffected. To activate (full commands + rotation policy in .github/SECRETS.md):

  1. Create the signing key offline + an R2 bucket bound to packages.librefang.ai.
  2. Add secrets: ARCH_REPO_GPG_PRIVATE_KEY, ARCH_REPO_GPG_KEY_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY.
  3. Validate via workflow_dispatch on release.yml (channel=current, tag=<existing release>) — the first run cold-starts the db.

Verification

  • bash -n passes on publish-arch-repo.sh.
  • yaml.safe_load parses action.yml and release.yml.
  • The composite action's run: block contains no ${{ }} interpolation (no shell-injection surface); all values pass through env:.
  • needs: targets (cli_linux, desktop, docker-manifest) all exist; the new job id is unique (32 jobs total).
  • Walked the version round-trip, the desktop .deb version parse, the Docker image-tag re-pin, the symlink-materialisation for object storage, and the per-pkgname retention grouping against the real PKGBUILDs and pacman file-naming.

Live end-to-end (real GPG sign + R2 push + a real Arch container against real release assets) can only run the first time a maintainer dispatches a release with the secrets configured — it cannot run in CI without the key, exactly like the mobile/desktop signing jobs.

Out-of-scope follow-up

  • Docs-site (docs/src/app/...integrations/cli) integration of the install steps is deferred because it touches the Next.js site + its zh i18n mirror — a different domain than this CI/packaging change. The full install instructions live in packaging/arch-repo/README.md in the meantime; happy to do the site page in a follow-up if wanted.

Refs #6334

AUR account registration is closed with no reopening date, which blocks the AUR automation in #6341. Ship the same release-pinned binary packages directly through a project-maintained pacman repository so Arch users can install with `pacman -Syu`.

A new `publish_arch_repo` release job builds, GPG-signs, and folds librefang-bin / librefang-desktop-bin / librefang-docker into one shared pacman database, then syncs it to Cloudflare R2 (packages.librefang.ai). It reuses the committed PKGBUILDs under packaging/aur/ as the single source of truth, deriving only the per-release values (pkgver, sha256sums, desktop bundle version, pinned Docker tag). Single job, not three, because all packages share one db; a fixed concurrency group serialises the read-modify-write across releases.

Degrades to a no-op when the GPG key or R2 secrets are absent, so it is safe to merge before the secrets are configured. pacman verifies GPG/PGP only and cannot use the cosign keyless signatures the rest of the release uses, so this introduces the project's first long-lived signing key — documented as an offline primary key with a passphrase-less CI subkey.
@github-actions github-actions Bot added size/L 250-999 lines changed area/docs Documentation and guides area/ci CI/CD and build tooling labels Jun 29, 2026
repo-add --sign writes the short-name librefang.db.sig / librefang.files.sig as symlinks to their .tar.gz.sig targets, alongside the .db / .files symlinks. The publish script only materialised the latter two, so rclone (which skips symlinks) silently dropped the two .sig files. The repo would then serve librefang.db with no fetchable librefang.db.sig, breaking pacman's signed-db verification under the default SigLevel.

Materialise all four symlinks as real objects before upload.

Verified by a local dry-run against R2 (amd64 container): the prior `Can't follow symlink` rclone notices for the two .sig files are gone, the incremental repo-add replaces the same-version entries pulled from R2, and the upload completes clean.
@houko
houko merged commit 0f209a8 into main Jun 29, 2026
31 checks passed
@houko
houko deleted the ci/arch-pacman-repo branch June 29, 2026 05:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/ci CI/CD and build tooling area/docs Documentation and guides size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant