- The signed
[ryoku]pacman repository athttps://repo.ryoku.dev/stable/$arch/, which holds every Ryoku package (ryoku-shell,ryoku-hub,ryoku,ryoku-blobs,ryoku-desktop,ryoku-keyring, plus thegpkpackage manager). - The live ISO (/docs/iso-build-recipe) signed with the same release key.
release/ and
installation/iso/, and published by workflows under .github/workflows/.
This page describes only what those scripts and workflow files actually do.
Versioning
VERSION at the repo root holds the tracked semver (currently
0.1.0-beta.13). The release helpers under bin/ derive everything from it:
bin/ryoku-release-version --channel mainprints the stable display version.bin/ryoku-release-version --channel unstable-dev --build-number <run> --sha <sha>prints the rolling preview version (e.g.vX.Y.Z-...dev.<run>+g<short-sha>).bin/ryoku-release-version --pkgverprints the per-build pacman version used in the monorepo PKGBUILDs (semver plus commit count and short sha), so every published build is a strictly newer, commit-identifiable pacman version andpacman -Syuactually sees an upgrade after each push.bin/ryoku-release-bump <patch|minor|major> <alpha|beta|stable>prints the next tracked release version.
.github/workflows/release-channel-versions.yml validates these on PRs and
pushes against main and unstable-dev. It compiles all four forms above,
sanity-checks their shape with regexes, and on an unstable-dev push moves
the unstable-dev-latest tag onto the pushed commit. That tag is a marker
for the newest rolling build, not a stable release tag.
Cutting a stable release
.github/workflows/stable-release.yml is the stable release entry point.
Run it from main with three inputs:
bump_type:patch,minor, ormajor.release_stage:alpha,beta, orstable.dispatch_iso: whether to dispatch the ISO build after tagging.
- Computes the new version with
bin/ryoku-release-bumpand refuses to continue if the resulting tag already exists onorigin. - Writes the new value into
VERSION, commits asgithub-actions[bot]with subjectrelease: v<version>, and pushesHEAD+ the annotated tagv<version>toorigin. - Dispatches
publish-repo.ymlagainst the new tag explicitly (a tag pushed byGITHUB_TOKENdoes not fire the tag trigger, so the dispatch is required to republish the signed[ryoku]repo). - If
dispatch_isois true, dispatchesbuild-iso.ymlagainst the tag with the chosenrelease_stage.
stable-release group so two cuts cannot race.
Building the [ryoku] pacman repo
release/repo/build-repo.sh is the local + CI builder. It expects the
release signing key already imported into the active GNUPGHOME. The script:
- Refuses to run as root (
makepkgrefuses too). - Sanity-checks for
makepkg,repo-add, andgpg, and that the keyEB6D3C0F55A7B3CABA6B2838847B274F025DD6E3is available. - Wipes
out/<arch>/so the database is rebuilt from exactly this run’s package set (norepo-add --new). - Exports
PKGDEST=out/<arch>andPKGEXT=.pkg.tar.zst, then exportsRYOKU_PKGVERfrombin/ryoku-release-version --pkgver. The monorepo PKGBUILDs (ryoku-shell,ryoku-hub,ryoku,ryoku-blobs,ryoku-desktop) read this so every build is uniquely versioned;ryoku-keyringandgpkkeep their own pkgver (key-rotation date and upstream release). - Runs each
release/packages/*/PKGBUILDwithmakepkg --force --clean --nodeps --noconfirm --sign --key $KEY_ID.--nodepsis intentional: runtime depends and AUR depends are not resolvable here and not needed to compile; only the host build toolchain matters (base-devel,go,cmake,ninja,qt6-shadertools,qt6-declarative). - Confirms every produced package has a
.sigbeside it. - Runs
repo-add -s -k $KEY_ID out/<arch>/ryoku.db.tar.gz <pkg>...to build the signed database from the actual package set. - Materializes the bare names
ryoku.db,ryoku.db.sig,ryoku.files,ryoku.files.sigas real files (not symlinks), because object storage has no symlinks andpacmanfetches the bare names.
RYOKU_REPO_OUT, RYOKU_REPO_KEY, RYOKU_REPO_NAME,
RYOKU_REPO_ARCH, RYOKU_PACKAGES_DIR, RYOKU_PKGVER.
The final layout is exactly what the mirror serves:
Publishing the repo
.github/workflows/publish-repo.yml runs the same build-repo.sh and
uploads the result. Triggers: push to main, push of a v* tag, and
workflow_dispatch (including the dispatch issued by stable-release.yml).
A push to unstable-dev never publishes; the repo only advances when main
fast-forwards.
The job runs inside archlinux:latest (makepkg, repo-add, and pacman only
exist on Arch) and installs the build toolchain:
base-devel git go cmake ninja qt6-shadertools qt6-declarative gnupg rclone.
Secrets required (workflow fails early when any are missing):
Build flow:
- A throwaway
builderuser is created and given ownership of the checkout (makepkgrefuses to run as root). - The armored private key is written to a
0600file under$RUNNER_TEMP, chowned tobuilder, thengpg --batch --imported into a fresh ephemeralGNUPGHOMEconfigured withallow-loopback-pinentry(the release key is passphraseless; loopback keeps a headless runner from blocking on a pinentry). release/repo/build-repo.shruns asbuilder.- The key file is removed.
type = s3, provider = Cloudflare, no_check_bucket = true so the
scoped Object Read+Write token does not 403 on HeadBucket/CreateBucket),
then:
sync (not copy) is deliberate: superseded packages and rotated-out
signatures are pruned, so the bucket only ever holds the current set.
Building and publishing the ISO
.github/workflows/build-iso.yml builds the live ISO with
installation/iso/build.sh (/docs/iso-build-recipe)
and publishes it.
Triggers: workflow_dispatch (with a release_stage input of alpha,
beta, or stable, defaulting to alpha) and a v* tag push. There is no
branch-push trigger, so commits to unstable-dev never publish anything
user-facing.
The build runs on ubuntu-latest with timeout-minutes: 120 and walks
through these steps:
- Checkout with
fetch-depth: 0andpersist-credentials: false.build.shstages the payload withgit archive HEAD, so full history is cheap to keep around. - Prepare release metadata. Computes
tracking_id="r${GITHUB_RUN_NUMBER}-${short_sha}"and exportsRYOKU_ISO_*env vars (commit, run URL, channel = the inputrelease_stage,RYOKU_ISO_INSTALLER_REF=main). - Verify required secrets are present (
R2_ACCESS_KEY_ID,R2_SECRET_ACCESS_KEY,R2_ENDPOINT,GPG_PRIVATE_KEY). Fails fast with the missing list. - Free disk space on the runner. Strips preinstalled toolchains
(
/opt/hostedtoolcache,/usr/share/dotnet,/opt/ghc,/usr/local/lib/android,/usr/local/share/boost) and prunes docker. - Build ISO inside a
--privileged archlinux:latestcontainer, bind-mounting the repo at/repoand$RUNNER_TEMP/ryoku-iso-buildat/work. The container refreshesarchlinux-keyring, installs the build toolchain (archiso git go base-devel cmake ninja qt6-base qt6-declarative qt6-shadertools), and runs./build.shwithRYOKU_ISO_OUT=/repo/installation/iso/out,RYOKU_ISO_WORK=/work/work,RYOKU_ISO_STAGE=/work/staging, and a reproducibleSOURCE_DATE_EPOCH. After the build theout/dir is chowned back to the runner user. The--privilegedflag is required sopacstrapcan bind-mount/proc,/sys, and/devinto the airootfs. - Capture ISO path and rename the artifact to the tracked public name
ryoku-<YYYY.MM.DD>-r<run>-<sha>-x86_64-<installer_ref>.iso. - Mount the ISO live root for Trivy. Loop-mount the ISO read-only,
find the squashfs (
*.sfs), loop-mount it, andrsyncthe rootfs into$RUNNER_TEMP/ryoku-iso-rootfsso Trivy can read it as a regular tree. - Trivy SARIF report with
scan-type: rootfs, scannersvuln,secret,misconfig, severitiesHIGH,CRITICAL,ignore-unfixed: true.skip-dirsexcludes runtime mounts, the pacman package cache, and the baked payload’s bulky brand and wallpaper assets atusr/share/ryoku/ryoku/assets. The SARIF is uploaded to the Security tab viagithub/codeql-action/upload-sarif@v4. - Block on critical CVEs/misconfigurations. A second Trivy step with
severity: CRITICAL,exit-code: 1fails the workflow before any signing or upload if a critical finding is present. - Sign the ISO with GPG. Imports
GPG_PRIVATE_KEYinto an ephemeralGNUPGHOMEundermktemp -d, signs with--batch --pinentry-mode loopback --passphrase-fd 0ifGPG_PASSPHRASEis set or plain--batch --detach-signotherwise, then verifies withgpg --verify. Exports the public key asryoku-release-key.pub.ascnext to the ISO; this mirrors the repo-canonical copy atkeys/ryoku-release-key.pub.asc. - Generate release manifests.
bin/ryoku-iso-manifest <iso>writes<iso>.sha256,<iso>.json,<iso>.js,latest.json, andlatest.js. - Install rclone via the official installer script.
- Upload to Cloudflare R2. rclone is configured with the same
Cloudflare provider entry as the repo workflow (
no_check_bucket = true). The ISO, signature, sha256, per-ISO manifests,latest.*pointers, and the public key are copied toRyoku:$dest/where$dest=${R2_BUCKET:-ryoku}. The[ryoku]pacman repo owns per-arch prefixes in the same bucket; the ISO release lives at the root. - Upload to the workflow run as well via
actions/upload-artifact@v4withretention-days: 14, as a fallback to the R2 publish. - Notify Discord if
DISCORD_WEBHOOK_URLis set. Sends an embed with title, tracking ID, channel, ref, image name, and the brand mark fromryoku/assets/brand/logo-mark.png. Never fails the build (if: always(),continue-on-error: true).
ISO build secrets
The required set above (R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY,
R2_ENDPOINT, GPG_PRIVATE_KEY) plus the optional ones:
R2_ENDPOINT MUST be the account-scoped S3 endpoint
(https://<account>.r2.cloudflarestorage.com), not the bucket URL.
The signing key
release/packages/ryoku-keyring/PKGBUILD packages the public key material
(ryoku.gpg, ryoku-trusted, ryoku-revoked) into
/usr/share/pacman/keyrings/. The canonical public key for users to verify
against lives in the repo at keys/ryoku-release-key.pub.asc. The release
identity is Ryoku Releases <[email protected]>, and the key fingerprint
build-repo.sh signs with is EB6D3C0F55A7B3CABA6B2838847B274F025DD6E3.
Rotating the key: regenerate ryoku.gpg, ryoku-trusted, and
ryoku-revoked, bump the pkgver date in ryoku-keyring/PKGBUILD, replace
keys/ryoku-release-key.pub.asc, and update RYOKU_REPO_KEY (or the
default in build-repo.sh) to the new fingerprint.
Verifying a published ISO
The public key ships alongside each ISO in the same R2 location and is mirrored in the repo:EB6D3C0F55A7B3CABA6B2838847B274F025DD6E3 before
you trust the imported key.
