ci(release): split canary/stable tracks + unified Gitea Releases
ci / rust (push) Failing after 37s
apple / swift (push) Successful in 56s
ci / web (push) Successful in 42s
ci / docs-site (push) Failing after 27m33s
android / android (push) Failing after 28m53s
windows-host / package (push) Failing after 28m55s
deb / build-publish (push) Successful in 2m28s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
ci / bench (push) Successful in 4m34s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m20s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 4m4s
flatpak / build-publish (push) Successful in 4m19s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m38s
release / apple (push) Successful in 4m36s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m48s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m25s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 50s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m6s
ci / rust (push) Failing after 37s
apple / swift (push) Successful in 56s
ci / web (push) Successful in 42s
ci / docs-site (push) Failing after 27m33s
android / android (push) Failing after 28m53s
windows-host / package (push) Failing after 28m55s
deb / build-publish (push) Successful in 2m28s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
ci / bench (push) Successful in 4m34s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 46s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m20s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Failing after 4m4s
flatpak / build-publish (push) Successful in 4m19s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 7m38s
release / apple (push) Successful in 4m36s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m48s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m25s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 50s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m6s
A push to main publishes canary builds to canary channels (fast iteration,
unchanged); a single vX.Y.Z tag releases every platform at one version to the
stable channels and attaches all artifacts (.deb/.rpm/.msix/.apk/.aab/.dmg +
flatpak/decky/host-installer) to one Gitea Release. Collapses the
host-v*/win-v*/host-win-v* tag namespaces into v* — the channel split makes the
version-shadow bug structurally impossible (canary and stable are separate repos,
never a shared version line).
- scripts/ci/gitea-release.{sh,ps1}: one idempotent release helper
(create-or-fetch + delete-before-upload), replacing 3 copy-pasted inline blocks
and fixing their latent 409-on-reupload bug; prerelease flag auto-derived from
the tag (an -rc tag won't shadow "Latest")
- channels: apt canary/stable distributions; rpm *-canary/base groups; flatpak
canary/stable OSTree branches + a 2nd .Canary.flatpakref; generic-registry
canary/ vs latest/ aliases; Play internal/alpha; Apple TestFlight vs notarized DMG
- android versionName threaded through gradle (versionCode stays run_number);
Apple canary = TestFlight-only (no DMG/tvOS); canary base bumped to 0.3.0
- docs: new docs-site channels.md (subscribe table + cut-a-release runbook +
box migration), refreshed ci.md workflow table + packaging READMEs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,10 @@ name: android
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# Single project version: a `vX.Y.Z` tag is THE release (uploads to Play's `alpha` closed
|
||||
# track for manual promotion + attaches the .aab/.apk to the unified Gitea Release). A main
|
||||
# push is canary (Play `internal`).
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -69,11 +73,24 @@ jobs:
|
||||
VERSION_CODE: ${{ github.run_number }}
|
||||
run: ./gradlew :app:assembleDebug --stacktrace
|
||||
|
||||
# Single source of the version name + the Play track for the release steps below. versionCode
|
||||
# stays github.run_number (monotonic across both tracks; Play rejects a regressed code).
|
||||
- name: Version + channel
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
run: |
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) VN="${GITHUB_REF_NAME#v}"; TRACK="alpha" ;; # alpha = built-in closed testing
|
||||
*) VN="0.3.0-ci${GITHUB_RUN_NUMBER}"; TRACK="internal" ;;
|
||||
esac
|
||||
echo "VERSION_NAME=$VN" >> "$GITHUB_ENV"
|
||||
echo "PLAY_TRACK=$TRACK" >> "$GITHUB_ENV"
|
||||
echo "android version $VN -> Play track '$TRACK'"
|
||||
|
||||
- name: Build Release (signed AAB + universal APK)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
working-directory: clients/android
|
||||
env:
|
||||
VERSION_CODE: ${{ github.run_number }}
|
||||
VERSION_CODE: ${{ github.run_number }} # VERSION_NAME comes from the Version+channel step (GITHUB_ENV)
|
||||
RELEASE_KEYSTORE_FILE: "../release.jks"
|
||||
RELEASE_KEYSTORE_PASSWORD: ${{ secrets.RELEASE_KEYSTORE_PASSWORD }}
|
||||
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
|
||||
@@ -85,33 +102,52 @@ jobs:
|
||||
|
||||
# Publish BEFORE the Play upload so artifacts land even while the Play step is still failing.
|
||||
# Generic registry is public for reads — matches windows-msix.yml / deb.yml (REGISTRY_TOKEN, user enricobuehler).
|
||||
- name: Publish AAB + APK to Gitea generic registry
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# main = canary store + `canary/` sideload alias; a `vX.Y.Z` tag = `latest/` alias + attached
|
||||
# to the unified Gitea Release.
|
||||
- name: Publish to generic registry + attach to Gitea release
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
PKG: punktfunk-android
|
||||
VERSION: ${{ github.run_number }}
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
AAB=clients/android/app/build/outputs/bundle/release/app-release.aab
|
||||
APK=clients/android/app/build/outputs/apk/release/app-release.apk
|
||||
base="https://$REGISTRY/api/packages/$OWNER/generic/$PKG/$VERSION"
|
||||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$AAB" "$base/punktfunk-android-r$VERSION.aab"
|
||||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/punktfunk-android-r$VERSION.apk"
|
||||
echo "Published artifacts (versionCode=$VERSION):"
|
||||
echo " $base/punktfunk-android-r$VERSION.aab"
|
||||
echo " $base/punktfunk-android-r$VERSION.apk"
|
||||
base="https://$REGISTRY/api/packages/$OWNER/generic/$PKG"
|
||||
# 1) immutable, run-number-versioned store (sideload + provenance)
|
||||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$AAB" "$base/$VERSION/punktfunk-android-r$VERSION.aab"
|
||||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$VERSION/punktfunk-android-r$VERSION.apk"
|
||||
echo "published store version $VERSION (versionCode)"
|
||||
# 2) channel alias for a predictable sideload URL: stable -> latest/, canary -> canary/
|
||||
case "$GITHUB_REF" in refs/tags/v*) ALIAS=latest ;; *) ALIAS=canary ;; esac
|
||||
curl -fsS -o /dev/null --user "enricobuehler:$REGISTRY_TOKEN" -X DELETE "$base/$ALIAS/punktfunk-android.apk" || true
|
||||
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" --upload-file "$APK" "$base/$ALIAS/punktfunk-android.apk"
|
||||
echo "sideload alias: $base/$ALIAS/punktfunk-android.apk"
|
||||
# 3) on a real release, attach the .aab + .apk to the unified Gitea Release (X.Y.Z names)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*)
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
upsert_asset "$RID" "$AAB" "punktfunk-${VERSION_NAME}.aab"
|
||||
upsert_asset "$RID" "$APK" "punktfunk-${VERSION_NAME}.apk"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Direct Publishing-API upload instead of r0adkll/upload-google-play — that action hides the
|
||||
# real API error behind "Unknown error occurred."; this prints it. stdlib + openssl only (no
|
||||
# pip), reuses SERVICE_ACCOUNT_JSON (raw JSON or base64), auto-handles changesNotSentForReview.
|
||||
- name: Upload to Google Play (Internal Testing)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
# Track: canary main -> `internal`; a vX.Y.Z release -> `alpha` (closed testing) for manual
|
||||
# promotion to production in the Play console.
|
||||
- name: Upload to Google Play
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))
|
||||
env:
|
||||
SERVICE_ACCOUNT_JSON: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
run: |
|
||||
echo "uploading to Play track '$PLAY_TRACK'"
|
||||
python3 clients/android/ci/play-upload.py \
|
||||
--package io.unom.punktfunk \
|
||||
--aab clients/android/app/build/outputs/bundle/release/app-release.aab \
|
||||
--track internal --status completed
|
||||
--track "$PLAY_TRACK" --status completed
|
||||
|
||||
+32
-14
@@ -13,16 +13,16 @@ name: deb
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# HOST-scoped tags only. The Apple client uses `v*` (release.yml); those must NOT trigger a
|
||||
# host publish — a `v0.1.1` client tag previously shipped a host package versioned 0.1.1 that
|
||||
# outranked every rolling build (the version-shadow). Host releases use `host-v*`.
|
||||
tags: ['host-v*']
|
||||
# Single project version: a `vX.Y.Z` tag is THE release for every platform (see
|
||||
# docs-site channels.md). The old version-shadow (a client tag shipping a host package
|
||||
# that outranked rolling builds) is now structurally impossible — main publishes to the
|
||||
# `canary` apt distribution, tags to `stable`, so the two never share a version line.
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
DISTRIBUTION: stable
|
||||
COMPONENT: main
|
||||
|
||||
jobs:
|
||||
@@ -34,19 +34,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Version
|
||||
# host-vX.Y.Z tag -> X.Y.Z (a real host release). A main push -> 0.2.0~ciN.g<sha>: the '~'
|
||||
# sorts it BELOW the eventual 0.2.0 tag, it climbs monotonically by run number, AND it sits
|
||||
# ABOVE the stray 0.1.1, so `apt upgrade` truly moves boxes forward. Computed BEFORE the
|
||||
# build so it's stamped into the binary (PUNKTFUNK_BUILD_VERSION -> build.rs -> --version).
|
||||
- name: Version + channel
|
||||
# vX.Y.Z tag -> X.Y.Z, published to the `stable` apt distribution (a real release).
|
||||
# A main push -> 0.3.0~ciN.g<sha>, published to the `canary` distribution: the '~' sorts
|
||||
# below the eventual 0.3.0 tag, it climbs monotonically by run number, and the canary base
|
||||
# stays one minor AHEAD of the latest stable so a stable->canary box re-point still moves
|
||||
# forward (see channels.md). Computed BEFORE the build so it's stamped into the binary
|
||||
# (PUNKTFUNK_BUILD_VERSION -> build.rs -> --version).
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/host-v*) V="${GITHUB_REF_NAME#host-v}" ;;
|
||||
*) V="0.2.0~ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; DIST=stable ;;
|
||||
*) V="0.3.0~ci${GITHUB_RUN_NUMBER}.g${SHORT}"; DIST=canary ;;
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "package version $V"
|
||||
echo "DISTRIBUTION=$DIST" >> "$GITHUB_ENV"
|
||||
echo "package version $V -> apt distribution '$DIST'"
|
||||
|
||||
# dpkg-shlibdeps (Depends resolution) + dpkg-deb live in dpkg-dev. The client's link
|
||||
# deps are also baked into the rust-ci image, but this job runs against the image
|
||||
@@ -55,7 +58,8 @@ jobs:
|
||||
- name: dpkg-dev + client link deps
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends dpkg-dev \
|
||||
# python3 is used by scripts/ci/gitea-release.sh for the stable-tag release attach.
|
||||
apt-get install -y --no-install-recommends dpkg-dev python3 \
|
||||
libgtk-4-dev libadwaita-1-dev libsdl3-dev
|
||||
|
||||
# Share ci.yml's cache keys so the release build reuses its registry + target artifacts.
|
||||
@@ -124,3 +128,17 @@ jobs:
|
||||
"https://$REGISTRY/api/packages/$OWNER/debian/pool/$DISTRIBUTION/$COMPONENT/upload"
|
||||
done
|
||||
echo "published to $OWNER/debian $DISTRIBUTION/$COMPONENT"
|
||||
|
||||
# On a real release, also attach the .debs to the unified Gitea Release so they're on the
|
||||
# downloads page next to every other platform's artifact (canary builds live in the apt
|
||||
# `canary` distribution above — no release page for those).
|
||||
- name: Attach .debs to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
for DEB in dist/*.deb; do
|
||||
upsert_asset "$RID" "$DEB"
|
||||
done
|
||||
|
||||
+20
-27
@@ -56,19 +56,20 @@ jobs:
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run build # rollup -> clients/decky/dist/index.js
|
||||
|
||||
- name: Version
|
||||
# Tag v1.2.3 -> 1.2.3; main push -> 0.0.1-ciN.g<sha>. Used only for the registry
|
||||
# version path + the zip name (the plugin.json version is the source of truth Decky
|
||||
# reads after install).
|
||||
- name: Version + channel
|
||||
# Tag vX.Y.Z -> X.Y.Z (stable `latest/` alias + Gitea Release); main push -> 0.3.0-ciN.g<sha>
|
||||
# (`canary/` alias). Used for the registry version path + the zip name (the plugin.json
|
||||
# version is the source of truth Decky reads after install — bump it in the release commit).
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
|
||||
*) V="0.0.1-ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; ALIAS=latest ;;
|
||||
*) V="0.3.0-ci${GITHUB_RUN_NUMBER}.g${SHORT}"; ALIAS=canary ;;
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "decky version $V"
|
||||
echo "ALIAS=$ALIAS" >> "$GITHUB_ENV"
|
||||
echo "decky version $V -> alias '$ALIAS'"
|
||||
|
||||
- name: Assemble store-layout zip
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
@@ -102,29 +103,21 @@ jobs:
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$RUNNER_TEMP/punktfunk.zip" \
|
||||
"$BASE/$VERSION/punktfunk.zip"
|
||||
echo "published $BASE/$VERSION/punktfunk.zip"
|
||||
# 2) Stable `latest/punktfunk.zip` — this is the link to paste into Decky's
|
||||
# "install from URL". The generic registry rejects re-uploading an existing
|
||||
# version/file (409), so delete the prior `latest` first (ignore 404 on run #1).
|
||||
# 2) Channel alias (stable release -> latest/, canary main build -> canary/) — the link
|
||||
# to paste into Decky's "install from URL". The generic registry rejects re-uploading
|
||||
# an existing version/file (409), so delete the prior alias first (ignore 404 on run #1).
|
||||
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
||||
"$BASE/latest/punktfunk.zip" || true
|
||||
"$BASE/$ALIAS/punktfunk.zip" || true
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$RUNNER_TEMP/punktfunk.zip" \
|
||||
"$BASE/latest/punktfunk.zip"
|
||||
echo "install-from-URL link: $BASE/latest/punktfunk.zip"
|
||||
"$BASE/$ALIAS/punktfunk.zip"
|
||||
echo "install-from-URL link: $BASE/$ALIAS/punktfunk.zip"
|
||||
|
||||
- name: Attach zip to the Gitea release (tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/')
|
||||
- name: Attach zip to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
working-directory: ${{ gitea.workspace }}
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
|
||||
ID=$(curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])' \
|
||||
|| curl -sf "$API/releases/tags/$GITHUB_REF_NAME" -H "Authorization: token $TOKEN" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST "$API/releases/$ID/assets?name=punktfunk-${VERSION}.zip" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$RUNNER_TEMP/punktfunk.zip" >/dev/null
|
||||
echo "attached punktfunk-${VERSION}.zip to release $GITHUB_REF_NAME"
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
upsert_asset "$RID" "$RUNNER_TEMP/punktfunk.zip" "punktfunk-${VERSION}.zip"
|
||||
|
||||
@@ -58,16 +58,21 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
# On a release tag, also tag the image vX.Y.Z so a release pins reproducible web/docs images.
|
||||
EXTRA=""
|
||||
case "$GITHUB_REF" in refs/tags/v*) EXTRA="-t $REGISTRY/$OWNER/${{ matrix.image }}:${GITHUB_REF_NAME}" ;; esac
|
||||
docker build --pull ${{ matrix.buildargs }} \
|
||||
-f "${{ matrix.dockerfile }}" \
|
||||
-t "$REGISTRY/$OWNER/${{ matrix.image }}:latest" \
|
||||
-t "$REGISTRY/$OWNER/${{ matrix.image }}:sha-${GITHUB_SHA::8}" \
|
||||
$EXTRA \
|
||||
"${{ matrix.context }}"
|
||||
|
||||
- name: Push
|
||||
run: |
|
||||
docker push "$REGISTRY/$OWNER/${{ matrix.image }}:sha-${GITHUB_SHA::8}"
|
||||
docker push "$REGISTRY/$OWNER/${{ matrix.image }}:latest"
|
||||
case "$GITHUB_REF" in refs/tags/v*) docker push "$REGISTRY/$OWNER/${{ matrix.image }}:${GITHUB_REF_NAME}" ;; esac
|
||||
|
||||
# Deploy the docs site to unom-1, the DMZ services VM website/cms also deploy to
|
||||
# (docs.punktfunk.unom.io via Caddy on home-reverse-proxy-1 -> :3220). Same secret set
|
||||
|
||||
@@ -71,19 +71,23 @@ jobs:
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
- name: Version
|
||||
# Tag v1.2.3 -> 1.2.3; a main push -> 0.0.1-ciN.g<sha> (sorts before a real release,
|
||||
# increases by run number — newest main build always wins). The generic registry
|
||||
# version string allows letters/dots/hyphens.
|
||||
- name: Version + channel
|
||||
# Tag vX.Y.Z -> X.Y.Z on the OSTree `stable` branch (a real release); a main push ->
|
||||
# 0.3.0-ciN.g<sha> on the `canary` branch. The two branches live side-by-side in one repo
|
||||
# (rsync runs without --delete), each tracked by its own .flatpakref, so `flatpak update`
|
||||
# on a stable box never jumps to a canary build. The generic-registry version string allows
|
||||
# letters/dots/hyphens.
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
|
||||
*) V="0.0.1-ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; BRANCH=stable; ALIAS=latest ;;
|
||||
*) V="0.3.0-ci${GITHUB_RUN_NUMBER}.g${SHORT}"; BRANCH=canary; ALIAS=canary ;;
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "BUNDLE=punktfunk-client-${V}.flatpak" >> "$GITHUB_ENV"
|
||||
echo "flatpak version $V"
|
||||
echo "FLATPAK_BRANCH=$BRANCH" >> "$GITHUB_ENV"
|
||||
echo "ALIAS=$ALIAS" >> "$GITHUB_ENV"
|
||||
echo "flatpak version $V -> branch '$BRANCH' alias '$ALIAS'"
|
||||
|
||||
- name: Generate offline cargo sources
|
||||
# flatpak builds with no network; vendor every crate from Cargo.lock into
|
||||
@@ -108,19 +112,20 @@ jobs:
|
||||
# runtime/SDK + the rust-stable (//25.08, rustc 1.96) and llvm20 SDK extensions, plus
|
||||
# the runtime's auto codecs-extra (HEVC libavcodec). --disable-rofiles-fuse is the
|
||||
# container-safe path (no FUSE).
|
||||
# --default-branch=stable pins the ref to app/io.unom.Punktfunk/x86_64/stable so the
|
||||
# hosted .flatpakref (Branch=stable) matches deterministically (manifest sets no branch).
|
||||
# --default-branch=$FLATPAK_BRANCH pins the ref to app/io.unom.Punktfunk/x86_64/<branch>
|
||||
# (canary or stable) so the matching hosted .flatpakref resolves deterministically
|
||||
# (manifest sets no branch).
|
||||
flatpak-builder --user --force-clean --disable-rofiles-fuse \
|
||||
--default-branch=stable \
|
||||
--default-branch="$FLATPAK_BRANCH" \
|
||||
--install-deps-from=flathub \
|
||||
--repo="$PWD/repo" \
|
||||
"$PWD/build-dir" "$MANIFEST"
|
||||
|
||||
- name: Export single-file bundle
|
||||
run: |
|
||||
# Branch must be passed explicitly now that the repo ref is `stable` (--default-branch
|
||||
# above); build-bundle otherwise defaults to `master` and errors "Refspec … not found".
|
||||
flatpak build-bundle "$PWD/repo" "$BUNDLE" "$APP_ID" stable
|
||||
# Branch must be passed explicitly (matches --default-branch above); build-bundle
|
||||
# otherwise defaults to `master` and errors "Refspec … not found".
|
||||
flatpak build-bundle "$PWD/repo" "$BUNDLE" "$APP_ID" "$FLATPAK_BRANCH"
|
||||
ls -lh "$BUNDLE"
|
||||
|
||||
- name: Publish to the Gitea generic registry
|
||||
@@ -132,14 +137,14 @@ jobs:
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \
|
||||
"$BASE/$VERSION/$BUNDLE"
|
||||
echo "published $BASE/$VERSION/$BUNDLE"
|
||||
# 2) Stable `latest/punktfunk-client.flatpak` alias for the Decky fallback + scripts.
|
||||
# The generic registry rejects re-uploading an existing version/file (409), so
|
||||
# delete the prior `latest` file first (ignore 404 on the first ever run).
|
||||
# 2) Channel alias (stable release -> latest/, canary main build -> canary/) for the
|
||||
# Decky fallback + scripts. The generic registry rejects re-uploading an existing
|
||||
# version/file (409), so delete the prior alias file first (ignore 404 on run #1).
|
||||
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
||||
"$BASE/latest/punktfunk-client.flatpak" || true
|
||||
"$BASE/$ALIAS/punktfunk-client.flatpak" || true
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \
|
||||
"$BASE/latest/punktfunk-client.flatpak"
|
||||
echo "published $BASE/latest/punktfunk-client.flatpak"
|
||||
"$BASE/$ALIAS/punktfunk-client.flatpak"
|
||||
echo "published $BASE/$ALIAS/punktfunk-client.flatpak"
|
||||
|
||||
# Sign the OSTree repo flatpak-builder already produced and publish it to flatpak.unom.io on
|
||||
# unom-1, so users get `flatpak update` (the single-file bundle above has no remote). Mirrors
|
||||
@@ -165,7 +170,7 @@ jobs:
|
||||
# build-sign signs the COMMIT objects; build-update-repo signs the SUMMARY. Both are
|
||||
# required — clients with gpg-verify=true verify the commit, so summary-only signing
|
||||
# fails the pull with "GPG verification enabled, but no signatures found".
|
||||
flatpak build-sign "$PWD/repo" "$APP_ID" stable \
|
||||
flatpak build-sign "$PWD/repo" "$APP_ID" "$FLATPAK_BRANCH" \
|
||||
--gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME"
|
||||
flatpak build-update-repo --generate-static-deltas \
|
||||
--gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME" "$PWD/repo"
|
||||
@@ -180,23 +185,33 @@ jobs:
|
||||
Comment=unom Flatpak applications
|
||||
GPGKey=$GPGKEY
|
||||
EOF
|
||||
cat > "site/${APP_ID}.flatpakref" <<EOF
|
||||
# Two refs, one per channel — both regenerated every run and rsync'd without --delete, so
|
||||
# the server always offers both (the stable ref only resolves once a release has built the
|
||||
# `stable` branch). A box installs ONE; `flatpak update` then tracks that channel's branch.
|
||||
write_ref() { # <filename> <branch> <title>
|
||||
cat > "site/$1" <<EOF
|
||||
[Flatpak Ref]
|
||||
Name=$APP_ID
|
||||
Branch=stable
|
||||
Branch=$2
|
||||
Url=$REPO_URL/repo/
|
||||
Title=Punktfunk
|
||||
Title=$3
|
||||
Homepage=https://punktfunk.unom.io
|
||||
IsRuntime=false
|
||||
GPGKey=$GPGKEY
|
||||
RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
EOF
|
||||
}
|
||||
write_ref "${APP_ID}.flatpakref" stable "Punktfunk"
|
||||
write_ref "${APP_ID}.Canary.flatpakref" canary "Punktfunk (Canary)"
|
||||
cat > site/index.html <<EOF
|
||||
<!doctype html><meta charset=utf-8><title>unom flatpak repo</title>
|
||||
<h1>unom Flatpak repository</h1>
|
||||
<p>Install the Punktfunk Linux client (auto-adds Flathub for the GNOME runtime, then tracks updates):</p>
|
||||
<p>Install the Punktfunk Linux client (auto-adds Flathub for the GNOME runtime, then tracks updates).</p>
|
||||
<p><b>Stable</b> (recommended — only moves on releases):</p>
|
||||
<pre>flatpak install --user $REPO_URL/${APP_ID}.flatpakref
|
||||
flatpak run $APP_ID</pre>
|
||||
<p><b>Canary</b> (latest main build, unstable):</p>
|
||||
<pre>flatpak install --user $REPO_URL/${APP_ID}.Canary.flatpakref</pre>
|
||||
<p>Or add the whole remote: <code>flatpak remote-add --user --if-not-exists unom $REPO_URL/unom.flatpakrepo</code></p>
|
||||
EOF
|
||||
# 3) Ship to unom-1 and (re)start the static server. rsync WITHOUT --delete keeps old
|
||||
@@ -207,24 +222,16 @@ jobs:
|
||||
DEST="${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
$SSH "$DEST" "mkdir -p ~/$DEPLOY_DIR/site/repo"
|
||||
rsync -az --info=stats1 -e "$SSH" repo/ "$DEST:$DEPLOY_DIR/site/repo/"
|
||||
rsync -az -e "$SSH" site/unom.flatpakrepo "site/${APP_ID}.flatpakref" site/index.html "$DEST:$DEPLOY_DIR/site/"
|
||||
rsync -az -e "$SSH" site/unom.flatpakrepo "site/${APP_ID}.flatpakref" "site/${APP_ID}.Canary.flatpakref" site/index.html "$DEST:$DEPLOY_DIR/site/"
|
||||
rsync -az -e "$SSH" packaging/flatpak/server/compose.production.yml packaging/flatpak/server/Caddyfile "$DEST:$DEPLOY_DIR/"
|
||||
$SSH "$DEST" "cd ~/$DEPLOY_DIR && docker compose -f compose.production.yml up -d"
|
||||
echo "deployed → $REPO_URL/${APP_ID}.flatpakref"
|
||||
|
||||
- name: Attach bundle to the Gitea release (tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/')
|
||||
- name: Attach bundle to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
|
||||
ID=$(curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])' \
|
||||
|| curl -sf "$API/releases/tags/$GITHUB_REF_NAME" -H "Authorization: token $TOKEN" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST "$API/releases/$ID/assets?name=$BUNDLE" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$BUNDLE" >/dev/null
|
||||
echo "attached $BUNDLE to release $GITHUB_REF_NAME"
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
upsert_asset "$RID" "$BUNDLE"
|
||||
|
||||
@@ -46,6 +46,19 @@ name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
# Canary: a relevant main push uploads the iOS + macOS builds to TestFlight (Apple's own
|
||||
# canary channel) — no notarized DMG, no tvOS (those are stable-only; see the per-step gates).
|
||||
# Heavy on the shared mac-mini runner, so paths-filtered; the TestFlight steps are
|
||||
# continue-on-error until the App Store Connect record exists, so this no-ops until then.
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'clients/apple/**'
|
||||
- 'crates/punktfunk-core/**'
|
||||
- 'scripts/build-xcframework.sh'
|
||||
- 'Cargo.lock'
|
||||
- '.gitea/workflows/release.yml'
|
||||
# Stable: a `vX.Y.Z` tag is THE release — notarized DMG attached to the unified Gitea Release
|
||||
# + macOS/iOS/tvOS to TestFlight for manual promotion to the App Store.
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -87,8 +100,8 @@ jobs:
|
||||
- name: Version from tag
|
||||
run: |
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}" ;;
|
||||
*) V="0.0.${GITHUB_RUN_NUMBER}" ;;
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; V="${V%%-*}" ;; # App Store marketing version is numeric X.Y.Z (drop -rc)
|
||||
*) V="0.3.0" ;; # canary marketing version; the build number disambiguates
|
||||
esac
|
||||
echo "VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "BUILD_NUM=$GITHUB_RUN_NUMBER" >> "$GITHUB_ENV"
|
||||
@@ -105,8 +118,16 @@ jobs:
|
||||
"$RUSTUP" toolchain install nightly --profile minimal
|
||||
"$RUSTUP" component add rust-src --toolchain nightly
|
||||
|
||||
- name: Build PunktfunkCore.xcframework (mac + iOS + tvOS)
|
||||
run: BUILD_IOS=1 BUILD_TVOS=1 bash scripts/build-xcframework.sh
|
||||
- name: Build PunktfunkCore.xcframework (mac + iOS; + tvOS on stable tags)
|
||||
# tvOS uses nightly -Zbuild-std (slow) — build it only for a real release, not on every
|
||||
# canary main push.
|
||||
run: |
|
||||
TV=""
|
||||
case "$GITHUB_REF" in refs/tags/v*) TV="BUILD_TVOS=1" ;; esac
|
||||
# `env` (not a bare prefix): a $TV-expanded `NAME=val` word is NOT re-promoted to a shell
|
||||
# assignment, so `BUILD_IOS=1 $TV bash …` would try to RUN `BUILD_TVOS=1` (exit 127). env
|
||||
# treats its leading NAME=val args as assignments post-expansion; empty $TV is a no-op.
|
||||
env BUILD_IOS=1 $TV bash scripts/build-xcframework.sh
|
||||
|
||||
- name: Stage App Store Connect API key
|
||||
env:
|
||||
@@ -116,6 +137,9 @@ jobs:
|
||||
chmod 600 "$RUNNER_TEMP/asc.p8"
|
||||
|
||||
- name: macOS — archive, codesign Developer ID, notarize, DMG
|
||||
# Stable releases only — the notarized DMG is a Gatekeeper/direct-download artifact, not
|
||||
# relevant to TestFlight testers (the canary channel). Skipped on canary main pushes.
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# Archive UNSIGNED, then codesign with the Developer ID Application identity from the
|
||||
# login keychain. Unsigned archive sidesteps Xcode's keychain-access-groups
|
||||
@@ -154,23 +178,14 @@ jobs:
|
||||
DEVELOPER_DIR="$XCODE_DEV_DIR" xcrun stapler staple "$DMG"
|
||||
echo "DMG=$DMG" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Attach DMG to Gitea release
|
||||
if: startsWith(gitea.ref, 'refs/tags/')
|
||||
- name: Attach DMG to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
env:
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
API="${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}"
|
||||
# Create the release (409 -> already exists, fetch it instead).
|
||||
ID=$(curl -sf -X POST "$API/releases" \
|
||||
-H "Authorization: token $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d "{\"tag_name\":\"$GITHUB_REF_NAME\",\"name\":\"$GITHUB_REF_NAME\"}" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])' \
|
||||
|| curl -sf "$API/releases/tags/$GITHUB_REF_NAME" -H "Authorization: token $TOKEN" \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["id"])')
|
||||
curl -sf -X POST "$API/releases/$ID/assets?name=Punktfunk-$VERSION.dmg" \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-F "attachment=@$DMG" >/dev/null
|
||||
echo "attached Punktfunk-$VERSION.dmg to release $GITHUB_REF_NAME"
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
upsert_asset "$RID" "$DMG" "Punktfunk-$VERSION.dmg"
|
||||
|
||||
- name: macOS App Store — archive + upload to TestFlight
|
||||
if: gitea.event_name != 'workflow_dispatch' || inputs.testflight == 'true'
|
||||
@@ -278,7 +293,9 @@ jobs:
|
||||
-authenticationKeyIssuerID "${{ secrets.ASC_API_ISSUER_ID }}"
|
||||
|
||||
- name: tvOS — archive + upload to TestFlight
|
||||
if: gitea.event_name != 'workflow_dispatch' || inputs.testflight == 'true'
|
||||
# Stable only — the tvOS xcframework slice is built just for releases (above), and the
|
||||
# App Store Connect record + runner platform are tvOS prerequisites.
|
||||
if: startsWith(gitea.ref, 'refs/tags/v') && (gitea.event_name != 'workflow_dispatch' || inputs.testflight == 'true')
|
||||
# Needs tvOS added to the App Store Connect app record + the tvOS platform installed
|
||||
# on the runner (xcodebuild -downloadPlatform tvOS).
|
||||
continue-on-error: true
|
||||
|
||||
+32
-13
@@ -13,9 +13,10 @@ name: rpm
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
# HOST-scoped tags only — the Apple client's `v*` tags (release.yml) must NOT publish a host
|
||||
# RPM (a `v0.1.1` client tag previously shipped a host 0.1.1 that shadowed every rolling build).
|
||||
tags: ['host-v*']
|
||||
# Single project version: a `vX.Y.Z` tag is THE release. main publishes to the `*-canary` rpm
|
||||
# groups, tags to the base groups (`bazzite`/`fedora-44`) — separate repos, so the old
|
||||
# version-shadow (a release outranking rolling builds in one group) is structurally gone.
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -66,20 +67,22 @@ jobs:
|
||||
key: cargo-home-${{ hashFiles('Cargo.lock') }}
|
||||
restore-keys: cargo-home-
|
||||
|
||||
- name: Version
|
||||
# host-vX.Y.Z tag -> X.Y.Z-1 (a real host release); main push -> 0.2.0-0.ciN.g<sha>, whose
|
||||
# "0." release sorts BELOW the eventual 0.2.0-1 yet climbs by run number AND outranks the
|
||||
# stray 0.1.1, so `rpm-ostree upgrade` truly moves to the newest build. The spec %build
|
||||
# stamps PUNKTFUNK_BUILD_VERSION from these macros into the binary (--version provenance).
|
||||
- name: Version + channel
|
||||
# vX.Y.Z tag -> X.Y.Z-1 in the base group (a real release); main push -> 0.3.0-0.ciN.g<sha>
|
||||
# in the `<base>-canary` group, whose "0." release sorts below the eventual 0.3.0-1 yet
|
||||
# climbs by run number. The canary base stays one minor ahead of the latest stable so a
|
||||
# stable->canary box re-point still moves forward. The spec %build stamps
|
||||
# PUNKTFUNK_BUILD_VERSION from these macros into the binary (--version provenance).
|
||||
run: |
|
||||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/host-v*) V="${GITHUB_REF_NAME#host-v}"; R="1" ;;
|
||||
*) V="0.2.0"; R="0.ci${GITHUB_RUN_NUMBER}.g${SHORT}" ;;
|
||||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; R="1"; GROUP="${{ matrix.group }}" ;;
|
||||
*) V="0.3.0"; R="0.ci${GITHUB_RUN_NUMBER}.g${SHORT}"; GROUP="${{ matrix.group }}-canary" ;;
|
||||
esac
|
||||
echo "PF_VERSION=$V" >> "$GITHUB_ENV"
|
||||
echo "PF_RELEASE=$R" >> "$GITHUB_ENV"
|
||||
echo "rpm $V-$R"
|
||||
echo "GROUP=$GROUP" >> "$GITHUB_ENV"
|
||||
echo "rpm $V-$R -> group '$GROUP'"
|
||||
|
||||
- name: Build RPM
|
||||
# PF_WITH_WEB=1 → also build the noarch punktfunk-web subpackage (the publish loop below
|
||||
@@ -101,6 +104,22 @@ jobs:
|
||||
case "$rpm" in *debuginfo*|*debugsource*) echo "skip $rpm"; continue;; esac
|
||||
echo "uploading $rpm"
|
||||
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$rpm" \
|
||||
"https://$REGISTRY/api/packages/$OWNER/rpm/${{ matrix.group }}/upload"
|
||||
"https://$REGISTRY/api/packages/$OWNER/rpm/$GROUP/upload"
|
||||
done
|
||||
echo "published to $OWNER/rpm/$GROUP"
|
||||
|
||||
# On a real release, also attach the .rpms to the unified Gitea Release. Both Fedora bases
|
||||
# (bazzite=F43, fedora-44) build the SAME filename, so suffix the asset with the base to keep
|
||||
# both on the release; canary builds live in the `*-canary` rpm groups (no release page).
|
||||
- name: Attach .rpms to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
. scripts/ci/gitea-release.sh
|
||||
RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto)
|
||||
for rpm in dist/*.rpm; do
|
||||
case "$rpm" in *debuginfo*|*debugsource*) continue;; esac
|
||||
base="$(basename "$rpm" .rpm)"
|
||||
upsert_asset "$RID" "$rpm" "${base}.${{ matrix.group }}.rpm"
|
||||
done
|
||||
echo "published to $OWNER/rpm/${{ matrix.group }}"
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
#
|
||||
# Registry (public reads, unom org): https://git.unom.io/unom/-/packages (generic group)
|
||||
#
|
||||
# Versioning (free-form; not MSIX's 4-part rule):
|
||||
# host-win-vX.Y.Z tag -> X.Y.Z (a real host release; own tag namespace, off host-v*/win-v*/v*
|
||||
# to avoid the version-shadow bug class — see deb.yml).
|
||||
# main push / dispatch -> 0.2.<run_number> (rolling; climbs monotonically by run number).
|
||||
# Versioning (free-form; not MSIX's 4-part rule) — single project version:
|
||||
# vX.Y.Z tag -> X.Y.Z (THE release; published + stable `latest/` alias + attached to the
|
||||
# unified Gitea Release).
|
||||
# main push / dispatch -> 0.3.<run_number> (canary; `canary/` alias; climbs by run number).
|
||||
#
|
||||
# Signing reuses the client's MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD secrets (CN=unom). Without them
|
||||
# an ephemeral self-signed cert is generated and its public .cer published next to the installer
|
||||
@@ -36,7 +36,7 @@ on:
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.gitea/workflows/windows-host.yml'
|
||||
tags: ['host-win-v*']
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -59,10 +59,10 @@ jobs:
|
||||
# (pwsh Out-File utf8 = no BOM, unlike Windows PowerShell 5.1 — keeps the first line clean).
|
||||
"CARGO_TARGET_DIR=C:\t" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"CARGO_WORKSPACE_DIR=$env:GITHUB_WORKSPACE" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
$v = if ($env:GITHUB_REF -like 'refs/tags/host-win-v*') {
|
||||
$env:GITHUB_REF_NAME -replace '^host-win-v', ''
|
||||
$v = if ($env:GITHUB_REF -like 'refs/tags/v*') {
|
||||
$env:GITHUB_REF_NAME -replace '^v', ''
|
||||
} else {
|
||||
"0.2.$($env:GITHUB_RUN_NUMBER)"
|
||||
"0.3.$($env:GITHUB_RUN_NUMBER)"
|
||||
}
|
||||
"HOST_VERSION=$v" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"PUNKTFUNK_BUILD_VERSION=$v" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
@@ -116,13 +116,25 @@ jobs:
|
||||
if (-not $files) { throw "pack produced no artifacts to publish" }
|
||||
$base = "https://$($env:REGISTRY)/api/packages/$($env:OWNER)/generic/$($env:PKG)"
|
||||
foreach ($f in $files) { Publish-File $f "$base/$($env:HOST_VERSION)/$(Split-Path $f -Leaf)" }
|
||||
# On a tagged release, also refresh the stable `latest/` alias (delete-then-reupload, like
|
||||
# flatpak.yml/decky.yml) so there's a predictable download URL.
|
||||
if ($env:GITHUB_REF -like 'refs/tags/host-win-v*') {
|
||||
$aliases = @{ $env:HOST_SETUP_PATH = 'punktfunk-host-setup.exe'; $env:HOST_CER_PATH = 'punktfunk-host-windows.cer' }
|
||||
foreach ($f in $files) {
|
||||
$alias = $aliases[$f]; if (-not $alias) { continue }
|
||||
curl.exe -fsS -o NUL --user "enricobuehler:$($env:REGISTRY_TOKEN)" -X DELETE "$base/latest/$alias" 2>$null
|
||||
Publish-File $f "$base/latest/$alias"
|
||||
}
|
||||
# Refresh the channel alias (delete-then-reupload, like flatpak.yml/decky.yml) for a
|
||||
# predictable download URL: stable release -> `latest/`, canary main build -> `canary/`.
|
||||
$alias = if ($env:GITHUB_REF -like 'refs/tags/v*') { 'latest' } else { 'canary' }
|
||||
$aliasNames = @{ $env:HOST_SETUP_PATH = 'punktfunk-host-setup.exe'; $env:HOST_CER_PATH = 'punktfunk-host-windows.cer' }
|
||||
foreach ($f in $files) {
|
||||
$an = $aliasNames[$f]; if (-not $an) { continue }
|
||||
curl.exe -fsS -o NUL --user "enricobuehler:$($env:REGISTRY_TOKEN)" -X DELETE "$base/$alias/$an" 2>$null
|
||||
Publish-File $f "$base/$alias/$an"
|
||||
}
|
||||
|
||||
# On a real release, also attach the signed installer (+ its .cer) to the unified Gitea Release.
|
||||
- name: Attach host installer to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
shell: pwsh
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
. scripts/ci/gitea-release.ps1
|
||||
$rid = Ensure-GiteaRelease -Tag $env:GITHUB_REF_NAME -Name $env:GITHUB_REF_NAME -Prerelease 'auto'
|
||||
foreach ($f in @($env:HOST_SETUP_PATH, $env:HOST_CER_PATH)) {
|
||||
if ($f -and (Test-Path $f)) { Upsert-GiteaAsset -ReleaseId $rid -File $f }
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
# Registry (public, unom org): https://git.unom.io/unom/-/packages (generic group)
|
||||
# Packaging internals: clients/windows/packaging/README.md.
|
||||
#
|
||||
# Versioning — MSIX requires a strictly 4-part numeric version (no ~/- suffixes), so:
|
||||
# win-vX.Y.Z tag -> X.Y.Z.0 (a real Windows-client release; `win-v*` is its own tag namespace,
|
||||
# kept off the host's `host-v*` and the Apple `v*` to avoid the
|
||||
# version-shadow class of bug — see deb.yml).
|
||||
# main push / dispatch -> 0.2.<run_number>.0 (rolling; climbs monotonically by run number).
|
||||
# Versioning — single project version; MSIX requires a strictly 4-part numeric version, so:
|
||||
# vX.Y.Z tag -> X.Y.Z.0 (THE release; any -rc/+meta pre-release suffix is dropped for MSIX).
|
||||
# Published to the generic registry + the stable `latest/` alias + attached to the
|
||||
# unified Gitea Release alongside every other platform's artifact.
|
||||
# main push / dispatch -> 0.3.<run_number>.0 (canary; climbs monotonically by run number).
|
||||
# Published to the generic registry + the `canary/` alias.
|
||||
# Both arches share the version; artifacts are arch-suffixed (..._x64.msix / ..._arm64.msix).
|
||||
#
|
||||
# Signing (packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD Actions secrets
|
||||
@@ -34,7 +35,7 @@ on:
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.gitea/workflows/windows-msix.yml'
|
||||
tags: ['win-v*']
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -72,10 +73,11 @@ jobs:
|
||||
"CARGO_TARGET_DIR=${{ matrix.td }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"FFMPEG_DIR=${{ matrix.ffmpeg }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
rustup target add ${{ matrix.target }}
|
||||
$parts = if ($env:GITHUB_REF -like 'refs/tags/win-v*') {
|
||||
($env:GITHUB_REF_NAME -replace '^win-v', '').Split('.')
|
||||
$parts = if ($env:GITHUB_REF -like 'refs/tags/v*') {
|
||||
# MSIX needs a purely-numeric 4-part version: drop any -rc/+meta pre-release suffix.
|
||||
(($env:GITHUB_REF_NAME -replace '^v', '') -replace '[-+].*$', '').Split('.')
|
||||
} else {
|
||||
@('0', '2', $env:GITHUB_RUN_NUMBER)
|
||||
@('0', '3', $env:GITHUB_RUN_NUMBER)
|
||||
}
|
||||
while ($parts.Count -lt 4) { $parts += '0' }
|
||||
$v = ($parts[0..3] -join '.')
|
||||
@@ -101,11 +103,43 @@ jobs:
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
$PSNativeCommandUseErrorActionPreference = $false
|
||||
$base = "https://$($env:REGISTRY)/api/packages/$($env:OWNER)/generic/$($env:PKG)"
|
||||
# stable release -> `latest/` alias; canary main build -> `canary/` alias.
|
||||
$alias = if ($env:GITHUB_REF -like 'refs/tags/v*') { 'latest' } else { 'canary' }
|
||||
# version-less, arch-suffixed alias names so each channel keeps one predictable URL.
|
||||
$aliasNames = @{
|
||||
"$($env:MSIX_PATH)" = "$($env:PKG)_${{ matrix.arch }}.msix"
|
||||
"$($env:MSIX_CER_PATH)" = "$($env:PKG)_${{ matrix.arch }}.cer"
|
||||
}
|
||||
$files = @($env:MSIX_PATH, $env:MSIX_CER_PATH) | Where-Object { $_ -and (Test-Path $_) }
|
||||
if (-not $files) { throw "pack produced no artifacts to publish" }
|
||||
function Put($f, $url) {
|
||||
curl.exe -fsS --user "enricobuehler:$($env:REGISTRY_TOKEN)" --upload-file "$f" "$url"
|
||||
if ($LASTEXITCODE -ne 0) { throw "upload failed ($LASTEXITCODE): $url" }
|
||||
Write-Output "published $url"
|
||||
}
|
||||
foreach ($f in $files) {
|
||||
$name = Split-Path $f -Leaf
|
||||
$url = "https://$($env:REGISTRY)/api/packages/$($env:OWNER)/generic/$($env:PKG)/$($env:MSIX_VERSION)/$name"
|
||||
curl.exe -fsS --user "enricobuehler:$($env:REGISTRY_TOKEN)" --upload-file "$f" "$url"
|
||||
Write-Output "published $name -> $url"
|
||||
# 1) immutable, versioned path
|
||||
Put $f "$base/$($env:MSIX_VERSION)/$name"
|
||||
# 2) channel alias (delete-then-reupload; the generic registry 409s on an existing file)
|
||||
$an = $aliasNames["$f"]
|
||||
curl.exe -fsS -o NUL --user "enricobuehler:$($env:REGISTRY_TOKEN)" -X DELETE "$base/$alias/$an" 2>$null
|
||||
Put $f "$base/$alias/$an"
|
||||
}
|
||||
|
||||
# On a real release, also attach the MSIX (+ its .cer) to the unified Gitea Release. Both
|
||||
# arch legs attach to the same release concurrently — the helper's create-or-fetch handles
|
||||
# the race, and x64/arm64 filenames differ so the assets don't collide.
|
||||
- name: Attach MSIX to the Gitea release (stable tags only)
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
shell: pwsh
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
. scripts/ci/gitea-release.ps1
|
||||
$rid = Ensure-GiteaRelease -Tag $env:GITHUB_REF_NAME -Name $env:GITHUB_REF_NAME -Prerelease 'auto'
|
||||
foreach ($f in @($env:MSIX_PATH, $env:MSIX_CER_PATH)) {
|
||||
if ($f -and (Test-Path $f)) { Upsert-GiteaAsset -ReleaseId $rid -File $f }
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ android {
|
||||
targetSdk = 36
|
||||
val vCode = (props.getProperty("VERSION_CODE") ?: System.getenv("VERSION_CODE"))
|
||||
versionCode = vCode?.toInt() ?: 1
|
||||
versionName = "0.0.2" // bumped for first Play Store release
|
||||
// versionName is the single project version, threaded from CI (a vX.Y.Z release or a
|
||||
// canary string). versionCode stays the monotonic run number (Play rejects regressions).
|
||||
versionName = (props.getProperty("VERSION_NAME") ?: System.getenv("VERSION_NAME")) ?: "0.0.2"
|
||||
ndk { abiFilters += listOf("arm64-v8a", "x86_64") }
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ The Windows client ships as **signed MSIX** packages so Windows boxes get a real
|
||||
tile, clean install/uninstall) instead of a loose exe. CI builds + publishes them from
|
||||
[`.gitea/workflows/windows-msix.yml`](../../../.gitea/workflows/windows-msix.yml) to Gitea's
|
||||
**generic** package registry (`https://git.unom.io/unom/-/packages`), on every `main` push that
|
||||
touches the client and on `win-v*` release tags.
|
||||
touches the client (canary) and on `vX.Y.Z` release tags (stable) — see
|
||||
[Release Channels](https://punktfunk.unom.io/docs/channels).
|
||||
|
||||
**Two architectures, one x64 runner.** Both `x64` and `arm64` packages are produced off the single
|
||||
x64 Windows runner — `x86_64-pc-windows-msvc` builds natively, `aarch64-pc-windows-msvc` is
|
||||
@@ -39,9 +40,9 @@ because it owns raw D3D11, Win32 low-level input hooks, WASAPI and SDL3.
|
||||
## Versioning
|
||||
|
||||
MSIX requires a strictly 4-part numeric version. The workflow computes:
|
||||
- `win-vX.Y.Z` tag → `X.Y.Z.0` (a real client release; `win-v*` is its own tag namespace, kept off
|
||||
the host's `host-v*` and Apple's `v*` to avoid the version-shadow bug).
|
||||
- `main` push / `workflow_dispatch` → `0.2.<run_number>.0` (rolling, climbs by run number).
|
||||
- `vX.Y.Z` tag → `X.Y.Z.0` (THE release; any `-rc`/`+meta` suffix is dropped for MSIX). Published to
|
||||
the stable `latest/` alias and attached to the unified Gitea Release.
|
||||
- `main` push / `workflow_dispatch` → `0.3.<run_number>.0` (canary, climbs by run number; `canary/` alias).
|
||||
|
||||
## Signing & install
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
title: Release Channels
|
||||
description: How punktfunk ships — the canary (every main push) and stable (vX.Y.Z) tracks, how to subscribe to each, and how to cut a release.
|
||||
---
|
||||
|
||||
punktfunk ships on **two tracks**. Every push to `main` publishes a **canary** build to the
|
||||
canary channels (fast iteration, possibly broken). A `vX.Y.Z` git tag cuts a **stable** release:
|
||||
every platform is built at that one version, published to the stable channels, and all the
|
||||
artifacts (`.deb`, `.rpm`, `.msix`, host installer, `.apk`/`.aab`, `.dmg`, flatpak, Decky zip)
|
||||
are attached to a single [Gitea Release](https://git.unom.io/unom/punktfunk/releases).
|
||||
|
||||
The two tracks are **separate repos / tracks per platform**, never a shared version line — so a
|
||||
stable box never gets pulled onto a canary build, and a canary box always moves forward. Pick the
|
||||
track per machine; switching is a one-line change.
|
||||
|
||||
## Which track should I be on?
|
||||
|
||||
- **Canary** — dev boxes, your own test fleet, "I want the latest main build." Updates land minutes
|
||||
after a merge.
|
||||
- **Stable** — anything you don't want to babysit. Only moves when a `vX.Y.Z` tag is cut.
|
||||
|
||||
## Subscribe — per platform
|
||||
|
||||
| Platform | Canary | Stable |
|
||||
|---|---|---|
|
||||
| **apt** (host/client) | `deb [signed-by=…] https://git.unom.io/api/packages/unom/debian canary main` | `… debian stable main` |
|
||||
| **rpm** (host) | baseurl `…/rpm/bazzite-canary` (or `fedora-44-canary`) | `…/rpm/bazzite` (or `fedora-44`) |
|
||||
| **Flatpak** (client) | `flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.Canary.flatpakref` | `…/io.unom.Punktfunk.flatpakref` |
|
||||
| **Decky** (Steam Deck) | install-from-URL `…/generic/punktfunk-decky/canary/punktfunk.zip` | `…/punktfunk-decky/latest/punktfunk.zip` |
|
||||
| **Windows client** (MSIX) | `…/generic/punktfunk-client-windows/canary/punktfunk-client-windows_x64.msix` | `…/latest/…` + the release page |
|
||||
| **Windows host** (installer) | `…/generic/punktfunk-host-windows/canary/punktfunk-host-setup.exe` | `…/latest/…` + the release page |
|
||||
| **Android** | Play **Internal testing** + sideload `…/generic/punktfunk-android/canary/punktfunk-android.apk` | Play **closed (alpha)** track + the release page |
|
||||
| **Apple** (mac/iOS/tvOS) | **TestFlight** | TestFlight + a notarized `.dmg` on the release page |
|
||||
|
||||
The apt distribution and the rpm group are just path segments in the URL — switching tracks is a
|
||||
one-line edit of `/etc/apt/sources.list.d/punktfunk.list` (`stable` ↔ `canary`) or
|
||||
`/etc/yum.repos.d/punktfunk.repo` (`…/rpm/bazzite` ↔ `…/rpm/bazzite-canary`), then
|
||||
`apt update` / `rpm-ostree upgrade`.
|
||||
|
||||
> The OS-package channels (apt/rpm) are how Linux hosts get canary builds — they are **not**
|
||||
> attached to a canary release page. The Gitea Releases page is stable-only.
|
||||
|
||||
## Cut a stable release (maintainer)
|
||||
|
||||
1. Make sure `main` is green.
|
||||
2. (Optional) bump any user-facing version that isn't derived from the tag — the Android
|
||||
`versionName` fallback (`clients/android/app/build.gradle.kts`) and the Decky `plugin.json`
|
||||
`version` are cosmetic self-reported strings; everything else (binaries via
|
||||
`PUNKTFUNK_BUILD_VERSION`, MSIX, apt/rpm, the `.dmg`) derives from the tag automatically.
|
||||
3. Tag and push — **one** tag releases every platform:
|
||||
```sh
|
||||
git tag v0.2.0
|
||||
git push origin v0.2.0
|
||||
```
|
||||
4. Every platform workflow fans out, builds at `0.2.0`, publishes to its **stable** channel, and
|
||||
attaches its artifact to the `v0.2.0` Gitea Release. Concurrent attaches are safe — the shared
|
||||
`scripts/ci/gitea-release.{sh,ps1}` helper creates the release once and the rest reuse it.
|
||||
5. **Promote the app stores manually** (CI only uploads to testing tracks — see below).
|
||||
6. After a release reaches the current canary base, bump the canary base one minor ahead in
|
||||
`deb.yml` / `rpm.yml` (and the `0.3.<run>` strings in the other workflows) so a stable→canary
|
||||
re-point still moves forward. Rule: **canary base = one minor ahead of the latest stable.**
|
||||
|
||||
Pre-release tags work too: `v0.2.0-rc1` builds a real release (the `-rc1` suffix is dropped where a
|
||||
strictly-numeric version is required — MSIX, the App Store marketing version).
|
||||
|
||||
### App-store promotion (manual, after the tag)
|
||||
|
||||
CI uploads stable to **testing** tracks only — it never auto-publishes to the public stores:
|
||||
|
||||
- **Apple** — the build lands in **TestFlight**. Promote to the App Store from App Store Connect
|
||||
(submit for review). The notarized `.dmg` on the release page is the direct-download path.
|
||||
- **Android** — the build lands in Play's **closed (alpha)** track. Promote alpha → production in
|
||||
the Play Console when ready.
|
||||
|
||||
## Why two tracks (the version-shadow trap)
|
||||
|
||||
apt/rpm/registries serve the **highest** version to every subscriber. If a stable release landed in
|
||||
the same channel as rolling main builds, every box would jump to it and get **stuck** — the rolling
|
||||
`0.3.0~ciN` build never climbs above a `0.3.0` release. Separate canary/stable channels remove the
|
||||
trap by construction, which is why a single `vX.Y.Z` tag can safely release the whole project at
|
||||
once (the old `host-v*` / `win-v*` / `host-win-v*` tag namespaces are retired — `v*` is the only
|
||||
release tag now).
|
||||
|
||||
## Migrating an existing box to canary
|
||||
|
||||
Boxes added before this split point at the current stable channels, which now only move on releases.
|
||||
Point your dev fleet at **canary**:
|
||||
|
||||
```sh
|
||||
# apt
|
||||
sudo sed -i 's/ stable main/ canary main/' /etc/apt/sources.list.d/punktfunk.list
|
||||
sudo apt update && sudo apt upgrade
|
||||
|
||||
# rpm-ostree (Bazzite / Fedora)
|
||||
sudo sed -i 's#/rpm/bazzite#/rpm/bazzite-canary#' /etc/yum.repos.d/punktfunk.repo # or fedora-44 → fedora-44-canary
|
||||
rpm-ostree upgrade
|
||||
|
||||
# Flatpak (Steam Deck client)
|
||||
flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.Canary.flatpakref
|
||||
```
|
||||
@@ -7,6 +7,10 @@ This page is the **install path for each client device**. For what each client *
|
||||
pick, see [Clients](/docs/clients); to install the **host**, see [Install the Host](/docs/install).
|
||||
Whichever client you install, the first connection needs a one-time [pairing](/docs/pairing).
|
||||
|
||||
> The links below are the **stable** channel (moves on `vX.Y.Z` releases). For the latest `main`
|
||||
> build, use the **canary** channel — TestFlight / Play Internal, the `…Canary.flatpakref`, or the
|
||||
> `canary/` download URLs. See [Release Channels](/docs/channels).
|
||||
|
||||
## Pick your device
|
||||
|
||||
| Device | Install |
|
||||
|
||||
@@ -21,6 +21,11 @@ Each registry is public — no auth, you just trust the repo's signing key. Addi
|
||||
one-time step covered in the linked guide; after that, normal `apt upgrade` / `rpm-ostree upgrade`
|
||||
tracks new builds automatically.
|
||||
|
||||
> **Stable vs canary.** The repos in the per-distro guides are the **stable** channel — it only
|
||||
> moves when a `vX.Y.Z` release is cut. For the latest `main` build (fast, possibly broken), point
|
||||
> at the **canary** channel instead (`canary` apt distribution / `*-canary` rpm group). See
|
||||
> [Release Channels](/docs/channels).
|
||||
|
||||
## Windows (NVIDIA)
|
||||
|
||||
punktfunk also runs as a native host on **Windows 10/11 (x64) with an NVIDIA GPU**, shipped as a
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"troubleshooting",
|
||||
"---Project---",
|
||||
"roadmap",
|
||||
"channels",
|
||||
"---Reference---",
|
||||
"[API Reference](/api)"
|
||||
]
|
||||
|
||||
+21
-5
@@ -7,15 +7,31 @@ CI runs on **Gitea Actions** (`git.unom.io`, org `unom`). The workflows live in
|
||||
`.gitea/workflows/`; they run across Linux and macOS runners and push a few images to the
|
||||
Gitea container registry.
|
||||
|
||||
## Release model
|
||||
|
||||
Two tracks (full guide: [Release Channels](https://punktfunk.unom.io/docs/channels)). A push to
|
||||
`main` publishes **canary** builds to the canary channels; a single **`vX.Y.Z` tag** is THE release
|
||||
for every platform — built at that version, published to the **stable** channels, and every artifact
|
||||
attached to one Gitea Release via the shared `scripts/ci/gitea-release.{sh,ps1}` helper (idempotent
|
||||
create-or-fetch + delete-before-upload, so concurrent cross-runner attaches don't collide). The old
|
||||
`host-v*` / `win-v*` / `host-win-v*` tag namespaces are retired — `v*` is the only release tag.
|
||||
|
||||
## Workflows
|
||||
|
||||
| Workflow | Trigger | Runner | What it does |
|
||||
|---|---|---|---|
|
||||
| `ci.yml` | push to `main`, PRs | Linux | Rust workspace (fmt · clippy `-D warnings` · build · test · C-ABI harness · generated-header drift) inside the `punktfunk-rust-ci` image; `web/` and `docs-site/` build + typecheck in `oven/bun:1` |
|
||||
| `docker.yml` | push to `main`, `v*` tags, manual | Linux | Builds + pushes the images below (`latest` + `sha-<short>` tags) |
|
||||
| `apple.yml` | push to `main`, PRs, manual | macOS | Rust core → `PunktfunkCore.xcframework` → `swift build` + `swift test` in `clients/apple` |
|
||||
| `release.yml` | `v*` tags, manual | macOS | Production Apple builds: sandboxed macOS `.dmg` (Developer ID, notarized, stapled) attached to the Gitea release + macOS/iOS/tvOS archives uploaded to TestFlight |
|
||||
| `windows-msix.yml` | push to `main`, `v*` tags, manual | Windows | Builds the Windows client for `x86_64`/`aarch64` and packages signed MSIX artifacts |
|
||||
| `ci.yml` | push `main`, PRs | Linux | Rust workspace (fmt · clippy `-D warnings` · build · test · C-ABI harness · header drift) in `punktfunk-rust-ci`; `web/` + `docs-site/` build + typecheck in `oven/bun:1` |
|
||||
| `apple.yml` | push `main`, PRs, manual | macOS | Rust core → `PunktfunkCore.xcframework` → `swift build`/`swift test` (CI gate, no publish) |
|
||||
| `windows.yml` | push `main` (paths), PRs, manual | Windows | client build · clippy · fmt · test for `x86_64`/`aarch64` (CI gate, no publish) |
|
||||
| `deb.yml` | push `main` → canary, `v*` → stable, manual | Linux | host/client/web `.deb` → apt (`canary`/`stable` distribution); `v*` attaches to the release |
|
||||
| `rpm.yml` | push `main` → canary, `v*` → stable, manual | Linux | host `.rpm` (bazzite + fedora-44 bases) → rpm (`*-canary`/base groups); `v*` attaches |
|
||||
| `windows-msix.yml` | push `main` (paths) → canary, `v*` → stable, manual | Windows | client MSIX `x64`+`arm64` → generic registry (`canary/`/`latest/`); `v*` attaches |
|
||||
| `windows-host.yml` | push `main` (paths) → canary, `v*` → stable, manual | Windows | host Inno installer → generic registry (`canary/`/`latest/`); `v*` attaches |
|
||||
| `android.yml` | push `main` → Play internal, `v*` → Play alpha, PRs, manual | Linux | signed AAB+APK → Play + generic registry; `v*` attaches |
|
||||
| `release.yml` | push `main` (paths) → TestFlight, `v*` → DMG + TestFlight, manual | macOS | Apple mac/iOS(/tvOS on stable); `v*` notarized `.dmg` attaches |
|
||||
| `flatpak.yml` | push `main` (paths) → canary branch, `v*` → stable, manual | Linux | client flatpak (OSTree repo + bundle, branch per channel); `v*` attaches |
|
||||
| `decky.yml` | push `main` → canary, `v*` → stable, manual | Linux | Decky plugin zip → generic registry (`canary/`/`latest/`); `v*` attaches |
|
||||
| `docker.yml` | push `main`, `v*`, manual | Linux | web/docs/CI images (`latest` + `sha-<short>`; `v*` adds a `vX.Y.Z` tag) |
|
||||
|
||||
## Dockerized pieces
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
`punktfunk-host` is published as a `.deb` to **Gitea's Debian package registry** in the public
|
||||
`unom` org, so the Ubuntu hosts update with plain `apt`. CI (`.gitea/workflows/deb.yml`) builds
|
||||
and publishes on every push to `main` (a rolling `0.2.0~ciN.g<sha>` build) and on `host-v*` tags
|
||||
(a clean `X.Y.Z`) — the rolling builds outrank the stray `0.1.1`, so plain `apt upgrade` always
|
||||
gets the latest (no version pin needed).
|
||||
and publishes on every push to `main` (a rolling `0.3.0~ciN.g<sha>` build to the **`canary`** apt
|
||||
distribution) and on `vX.Y.Z` tags (a clean `X.Y.Z` to the **`stable`** distribution, plus attached
|
||||
to the unified Gitea Release). The two are separate apt distributions, so a stable box never jumps
|
||||
to a canary build — see [Release Channels](https://punktfunk.unom.io/docs/channels). The repo line
|
||||
below subscribes to `stable`; swap `stable` → `canary` for the latest main builds.
|
||||
|
||||
The same workflow also publishes **`punktfunk-web`** (the browser management console — pairing +
|
||||
status) and **`punktfunk-client`** (the GTK4 couch/Deck client). `punktfunk-host` **Recommends**
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# punktfunk-host — RPM (Bazzite / Fedora Atomic) via the Gitea registry
|
||||
|
||||
`punktfunk-host` is published as an RPM to **Gitea's RPM package registry** in the public `unom`
|
||||
org (group `bazzite`), so Bazzite / Fedora Atomic hosts layer and update it with `rpm-ostree`.
|
||||
CI (`.gitea/workflows/rpm.yml`) builds and publishes on every push to `main` (a rolling
|
||||
`0.2.0-0.ciN.<sha>` build, which outranks the stray `0.1.1` so `rpm-ostree upgrade` always gets the
|
||||
latest — no version pin needed) and on **host-scoped** `host-v*` tags (a clean `X.Y.Z-1`; the Apple
|
||||
client's `v*` tags deliberately do **not** publish a host RPM). The RPM is built in the
|
||||
org (stable groups `bazzite`/`fedora-44`, canary groups `bazzite-canary`/`fedora-44-canary`), so
|
||||
Bazzite / Fedora Atomic hosts layer and update it with `rpm-ostree`. CI (`.gitea/workflows/rpm.yml`)
|
||||
builds and publishes on every push to `main` (a rolling `0.3.0-0.ciN.<sha>` build to the `*-canary`
|
||||
groups) and on `vX.Y.Z` tags (a clean `X.Y.Z-1` to the base groups, plus attached to the unified
|
||||
Gitea Release) — separate repos, so a stable box never jumps to a canary build (see
|
||||
[Release Channels](https://punktfunk.unom.io/docs/channels)). The `baseurl` below subscribes to the
|
||||
`bazzite` stable group; use `bazzite-canary` for the latest main builds. The RPM is built in the
|
||||
Fedora 43 image (`ci/fedora-rpm.Dockerfile`) so its auto-generated library Requires
|
||||
(`libavcodec.so.NN`, …) match Bazzite's sonames; the NVIDIA driver lib (`libcuda.so.1`) is
|
||||
excluded — NVENC/EGL come from whatever NVIDIA stack the host runs (a weak Recommends).
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# Output: dist/punktfunk-<version>-<release>.<arch>.rpm (+ the -debuginfo/-debugsource subpkgs)
|
||||
set -euo pipefail
|
||||
|
||||
PF_VERSION="${PF_VERSION:-0.2.0}"
|
||||
PF_VERSION="${PF_VERSION:-0.3.0}" # canary base; keep one minor ahead of the latest stable release
|
||||
PF_RELEASE="${PF_RELEASE:-1}"
|
||||
# PF_WITH_WEB=1 builds the punktfunk-web subpackage too (needs `bun` on PATH — present in the CI
|
||||
# builder image, not in a plain mock chroot). Default off so a bare `rpmbuild`/COPR still works.
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
################################################################################
|
||||
|
||||
Name: punktfunk
|
||||
# Version/Release are overridable so CI can stamp a rolling snapshot: a main build passes
|
||||
# --define "pf_version 0.2.0" --define "pf_release 0.ci42.gdeadbee"
|
||||
# (Release starting "0." sorts BEFORE the eventual "1" release; base 0.2.0 sits ABOVE the stray
|
||||
# 0.1.1), a host-v* tag passes the clean version with "pf_release 1". A plain `rpmbuild` (or COPR)
|
||||
# with no defines builds 0.2.0-1.
|
||||
Version: %{?pf_version}%{!?pf_version:0.2.0}
|
||||
# Version/Release are overridable so CI can stamp a rolling snapshot: a canary main build passes
|
||||
# --define "pf_version 0.3.0" --define "pf_release 0.ci42.gdeadbee"
|
||||
# (Release starting "0." sorts BEFORE the eventual "1" release; the canary base stays one minor
|
||||
# ahead of the latest stable), a vX.Y.Z release tag passes the clean version with "pf_release 1".
|
||||
# A plain `rpmbuild` (or COPR) with no defines builds 0.3.0-1.
|
||||
Version: %{?pf_version}%{!?pf_version:0.3.0}
|
||||
Release: %{?pf_release}%{!?pf_release:1}%{?dist}
|
||||
Summary: Low-latency desktop/game streaming host (Moonlight-compatible + punktfunk/1)
|
||||
|
||||
|
||||
@@ -83,6 +83,8 @@ pwsh -File packaging\windows\pack-host-installer.ps1 -Version 0.0.0-dev -TargetD
|
||||
|
||||
## Release
|
||||
|
||||
Push a `host-win-vX.Y.Z` tag — the workflow builds, signs, and publishes
|
||||
`punktfunk-host-setup-X.Y.Z.exe` + the public `.cer`, and refreshes the `latest/` alias. Main pushes
|
||||
publish rolling `0.2.<run>` builds (no `latest/` update).
|
||||
Push a `vX.Y.Z` tag — one tag releases every platform (see
|
||||
[Release Channels](https://punktfunk.unom.io/docs/channels)). The workflow builds, signs, and
|
||||
publishes `punktfunk-host-setup-X.Y.Z.exe` + the public `.cer`, refreshes the stable `latest/`
|
||||
alias, and attaches the installer to the unified Gitea Release. Main pushes publish rolling
|
||||
`0.3.<run>` **canary** builds to the `canary/` alias.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Shared Gitea Release helpers for the punktfunk Windows CI workflows (pwsh / PowerShell 7).
|
||||
#
|
||||
# Dot-source it, then call Ensure-GiteaRelease / Upsert-GiteaAsset:
|
||||
# . scripts/ci/gitea-release.ps1
|
||||
# Mirrors scripts/ci/gitea-release.sh; parses JSON with ConvertFrom-Json (the Windows runner
|
||||
# has no python). Same idempotent semantics: Upsert-GiteaAsset deletes an existing asset of
|
||||
# the same name before uploading, so re-runs / rolling canary uploads don't 409.
|
||||
#
|
||||
# Env (Gitea Actions sets the first two automatically):
|
||||
# GITHUB_SERVER_URL e.g. https://git.unom.io
|
||||
# GITHUB_REPOSITORY e.g. unom/punktfunk
|
||||
# GITEA_TOKEN a PAT with repository (release) write scope — set from secrets.REGISTRY_TOKEN
|
||||
# (must carry write:repository, not only write:package)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function _GiteaApi {
|
||||
if (-not $env:GITHUB_SERVER_URL) { throw 'GITHUB_SERVER_URL unset' }
|
||||
if (-not $env:GITHUB_REPOSITORY) { throw 'GITHUB_REPOSITORY unset' }
|
||||
"$($env:GITHUB_SERVER_URL)/api/v1/repos/$($env:GITHUB_REPOSITORY)"
|
||||
}
|
||||
|
||||
function _GiteaHeaders {
|
||||
if (-not $env:GITEA_TOKEN) { throw 'GITEA_TOKEN unset' }
|
||||
@{ Authorization = "token $($env:GITEA_TOKEN)" }
|
||||
}
|
||||
|
||||
# Ensure-GiteaRelease TAG NAME PRERELEASE [TARGETCOMMITISH] -> release id
|
||||
# Idempotently create or fetch the release for TAG. Prerelease is 'true', 'false', or 'auto'
|
||||
# (auto marks it a prerelease iff TAG carries a `-` pre-release suffix, e.g. v0.2.0-rc1, so an
|
||||
# rc never becomes "Latest"). TargetCommitish (optional) creates the tag if missing.
|
||||
function Ensure-GiteaRelease {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$Tag,
|
||||
[Parameter(Mandatory)] [string]$Name,
|
||||
[Parameter(Mandatory)] [string]$Prerelease,
|
||||
[string]$TargetCommitish
|
||||
)
|
||||
$pre = if ($Prerelease -eq 'auto') { [bool]($Tag -match '-') } else { [System.Convert]::ToBoolean($Prerelease) }
|
||||
$api = _GiteaApi; $h = _GiteaHeaders
|
||||
$payload = @{ tag_name = $Tag; name = $Name; prerelease = $pre }
|
||||
if ($TargetCommitish) { $payload.target_commitish = $TargetCommitish }
|
||||
try {
|
||||
$r = Invoke-RestMethod -Method Post -Uri "$api/releases" -Headers $h `
|
||||
-ContentType 'application/json' -Body ($payload | ConvertTo-Json -Compress)
|
||||
return $r.id
|
||||
} catch {
|
||||
# Almost always: the release already exists. Fetch it by tag; error if that fails too.
|
||||
$r = Invoke-RestMethod -Method Get -Uri "$api/releases/tags/$Tag" -Headers $h
|
||||
if (-not $r.id) { throw "gitea-release: could not create or find a release for tag '$Tag'" }
|
||||
return $r.id
|
||||
}
|
||||
}
|
||||
|
||||
# Upsert-GiteaAsset RELEASEID FILE [NAME]
|
||||
# Attach FILE, replacing any existing asset of the same name first (idempotent).
|
||||
function Upsert-GiteaAsset {
|
||||
param(
|
||||
[Parameter(Mandatory)] [string]$ReleaseId,
|
||||
[Parameter(Mandatory)] [string]$File,
|
||||
[string]$Name
|
||||
)
|
||||
if (-not (Test-Path $File)) { throw "gitea-release: asset file not found: $File" }
|
||||
if (-not $Name) { $Name = Split-Path $File -Leaf }
|
||||
$api = _GiteaApi; $h = _GiteaHeaders
|
||||
$assets = Invoke-RestMethod -Method Get -Uri "$api/releases/$ReleaseId/assets" -Headers $h
|
||||
$existing = $assets | Where-Object { $_.name -eq $Name } | Select-Object -First 1
|
||||
if ($existing) {
|
||||
Invoke-RestMethod -Method Delete -Uri "$api/releases/$ReleaseId/assets/$($existing.id)" -Headers $h | Out-Null
|
||||
}
|
||||
$enc = [uri]::EscapeDataString($Name)
|
||||
# curl.exe for the multipart upload — matches the rest of the windows workflows.
|
||||
curl.exe -fsS -H "Authorization: token $($env:GITEA_TOKEN)" -o NUL `
|
||||
-X POST "$api/releases/$ReleaseId/assets?name=$enc" -F "attachment=@$File"
|
||||
if ($LASTEXITCODE -ne 0) { throw "gitea-release: asset upload failed ($LASTEXITCODE): $Name" }
|
||||
Write-Output "gitea-release: uploaded '$Name' -> release $ReleaseId"
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
# shellcheck shell=bash
|
||||
# Shared Gitea Release helpers for the punktfunk CI workflows (Linux + macOS runners).
|
||||
#
|
||||
# Source this file, then call ensure_release / upsert_asset. It replaces the three
|
||||
# copy-pasted inline blocks that used to live in release.yml / flatpak.yml / decky.yml,
|
||||
# and fixes a latent bug those had: the bare asset POST returns 409 if an asset with the
|
||||
# same name already exists, so re-running a workflow — or reusing the rolling `canary`
|
||||
# release with stable filenames — would fail. upsert_asset deletes the old asset first.
|
||||
#
|
||||
# Callers run under Gitea Actions' default `bash -eo pipefail`, so a non-zero return from
|
||||
# these functions aborts the step (the desired behaviour on a real failure).
|
||||
#
|
||||
# Env (Gitea Actions sets the first two automatically in every step):
|
||||
# GITHUB_SERVER_URL e.g. https://git.unom.io
|
||||
# GITHUB_REPOSITORY e.g. unom/punktfunk
|
||||
# GITEA_TOKEN a PAT with repository (release) write scope — set from secrets.REGISTRY_TOKEN
|
||||
# (the same PAT the package uploads use; it must carry `write:repository`,
|
||||
# not only `write:package`, or the release-asset POST 403s)
|
||||
#
|
||||
# Requires: curl + python3 (python3 is already a proven dependency on every runner that
|
||||
# attaches releases today — macOS, the fedora flatpak container, the node:bookworm decky
|
||||
# image; the .deb runner installs it alongside its other apt deps).
|
||||
|
||||
_gitea_api() { printf '%s/api/v1/repos/%s' "${GITHUB_SERVER_URL:?}" "${GITHUB_REPOSITORY:?}"; }
|
||||
|
||||
# Tiny JSON / URL helpers. python3 reads the TOP-LEVEL "id" only, so there is no ambiguity
|
||||
# with the nested author.id / assets[].id fields a string-grep would trip over.
|
||||
_json_id() { python3 -c 'import json,sys;print(json.load(sys.stdin).get("id",""))' 2>/dev/null; }
|
||||
_json_asset_id() {
|
||||
python3 -c 'import json,sys
|
||||
want=sys.argv[1]
|
||||
for a in json.load(sys.stdin):
|
||||
if a.get("name")==want:
|
||||
print(a.get("id",""));break' "$1" 2>/dev/null
|
||||
}
|
||||
_urlencode() { python3 -c 'import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=""))' "$1"; }
|
||||
|
||||
# ensure_release TAG NAME PRERELEASE [TARGET_COMMITISH]
|
||||
# Idempotently create (or fetch) the release for TAG; prints its numeric id on stdout.
|
||||
# PRERELEASE is "true", "false", or "auto" — auto marks it a prerelease iff TAG carries a
|
||||
# `-` pre-release suffix (e.g. v0.2.0-rc1), so an rc never becomes the repo's "Latest" release
|
||||
# (Gitea's /releases/latest surfaces the newest non-prerelease). TARGET_COMMITISH (optional)
|
||||
# creates the git tag if it does not exist yet — for a `vX.Y.Z` release the tag already exists
|
||||
# (it is the trigger), so TARGET is omitted and create-vs-fetch hinges on the release object.
|
||||
ensure_release() {
|
||||
local tag="${1:?tag}" name="${2:?name}" prerelease="${3:?prerelease}" target="${4:-}"
|
||||
local api body id
|
||||
if [ "$prerelease" = auto ]; then
|
||||
case "$tag" in *-*) prerelease=true ;; *) prerelease=false ;; esac
|
||||
fi
|
||||
api="$(_gitea_api)"
|
||||
if [ -n "$target" ]; then
|
||||
body=$(printf '{"tag_name":"%s","name":"%s","prerelease":%s,"target_commitish":"%s"}' \
|
||||
"$tag" "$name" "$prerelease" "$target")
|
||||
else
|
||||
body=$(printf '{"tag_name":"%s","name":"%s","prerelease":%s}' "$tag" "$name" "$prerelease")
|
||||
fi
|
||||
# Try to create. On any failure (almost always "release already exists"), fall back to
|
||||
# fetching it by tag. Either path MUST yield an id, or we error loudly — so a 401/scope
|
||||
# problem can't masquerade as a successful no-op.
|
||||
id=$(curl -fsS -X POST "$api/releases" \
|
||||
-H "Authorization: token ${GITEA_TOKEN:?}" -H 'Content-Type: application/json' \
|
||||
-d "$body" 2>/dev/null | _json_id || true)
|
||||
if [ -z "$id" ]; then
|
||||
id=$(curl -fsS "$api/releases/tags/$tag" \
|
||||
-H "Authorization: token ${GITEA_TOKEN:?}" 2>/dev/null | _json_id || true)
|
||||
fi
|
||||
if [ -z "$id" ]; then
|
||||
echo "gitea-release: could not create or find a release for tag '$tag'" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$id"
|
||||
}
|
||||
|
||||
# upsert_asset RELEASE_ID FILE [NAME]
|
||||
# Attach FILE to the release, replacing any existing asset of the same name first so that
|
||||
# re-runs and rolling canary re-uploads are idempotent (a plain POST 409s on a dup name).
|
||||
upsert_asset() {
|
||||
local rid="${1:?release id}" file="${2:?file}" name="${3:-}"
|
||||
local api existing
|
||||
[ -n "$name" ] || name="$(basename "$file")"
|
||||
[ -f "$file" ] || { echo "gitea-release: asset file not found: $file" >&2; return 1; }
|
||||
api="$(_gitea_api)"
|
||||
existing=$(curl -fsS "$api/releases/$rid/assets" \
|
||||
-H "Authorization: token ${GITEA_TOKEN:?}" 2>/dev/null \
|
||||
| _json_asset_id "$name" || true)
|
||||
if [ -n "$existing" ]; then
|
||||
curl -fsS -o /dev/null -X DELETE "$api/releases/$rid/assets/$existing" \
|
||||
-H "Authorization: token ${GITEA_TOKEN:?}" || true
|
||||
fi
|
||||
curl -fsS -o /dev/null -X POST "$api/releases/$rid/assets?name=$(_urlencode "$name")" \
|
||||
-H "Authorization: token ${GITEA_TOKEN:?}" \
|
||||
-F "attachment=@$file"
|
||||
echo "gitea-release: uploaded '$name' -> release $rid"
|
||||
}
|
||||
Reference in New Issue
Block a user