ci: publish a test APK as a pre-release for outside testers
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 29m49s
🏗️ Build and Test JellyTau / Android Compile Check (push) Waiting to run
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 57s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 9m25s
Traceability Validation / Check Requirement Traces (push) Successful in 17s

Gitea artifacts need an account with read access to download, which makes
them useless for handing a build to someone outside the project -- the
actual reason a test APK gets built in the first place.

An optional publish input attaches the APK to a pre-release instead,
whose assets are a plain public URL on a public repo. No merge to master,
no MR, no version tag, and the tester needs no account.

Safe from a feature branch on two counts. The tag is test-<branch> rather
than v*, and only v* triggers build-release.yml, so nothing else reacts
to it. And it cannot reach existing users: the desktop updater reads a
static latest.json from the updater branch, not the release list.

Re-dispatching the same branch replaces the APK on the existing
pre-release rather than accumulating one release per attempt.
This commit is contained in:
2026-08-30 20:06:40 +02:00
parent 03c0b5cd17
commit 10d77f1380
2 changed files with 106 additions and 0 deletions
+87
View File
@@ -15,6 +15,11 @@ name: '📱 Test APK'
# Both variants install as com.dtourolle.jellytau.debug ("JellyTau Debug"),
# side by side with a real install and with their own data directory. Neither
# needs the release signing key.
#
# Getting the APK to somebody else: Gitea artifacts need an account with read
# access to download, so `publish: true` also attaches the APK to a pre-release
# whose assets are a plain public URL. That is the only way an outside tester
# gets the file without being given an account.
on:
workflow_dispatch:
@@ -41,6 +46,11 @@ on:
- aarch64
- armv7
- x86_64
publish:
description: 'Also publish as a pre-release, for testers with no Gitea account'
required: false
default: false
type: boolean
concurrency:
# One test build at a time; a newer dispatch supersedes an in-flight one.
@@ -171,6 +181,83 @@ jobs:
ls -lah dist/test-apk/
# Deliberately NOT tagged `v*`: that pattern triggers build-release.yml,
# which would run the whole three-platform release matrix and publish a
# real release off a feature branch. The tag here is derived from the
# branch name and carries no version, so nothing else reacts to it.
#
# This also cannot reach existing users. The desktop updater reads a
# static latest.json from the `updater` branch, not the release list, so a
# pre-release published here is invisible to anyone without the link.
- name: Publish as a pre-release
if: ${{ inputs.publish }}
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
AUTO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
command -v jq >/dev/null || { echo "❌ jq is required on the runner"; exit 1; }
API="${GITHUB_SERVER_URL}/api/v1"
REPO="${GITHUB_REPOSITORY}"
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
BRANCH="${GITHUB_REF#refs/heads/}"
TAG="test-$(echo "$BRANCH" | tr '/' '-')"
# printf, not a heredoc: inside a YAML block scalar every line is
# indented, and a heredoc terminator has to sit at column 0.
BODY=$(printf '%s\n' \
"Test build of \`$BRANCH\` at \`${GITHUB_SHA::8}\` — **not a release**." \
"" \
"Installs as **JellyTau Debug** (\`com.dtourolle.jellytau.debug\`), alongside a" \
"normal install and with its own separate data. Uninstalling it does not touch" \
"the real app." \
"" \
"Variant: \`${{ inputs.variant }}\` · ABI: \`${{ inputs.abi }}\`" \
"" \
"Android will warn about installing from an unknown source; that is expected" \
"for a build signed with a debug key rather than the store key.")
PAYLOAD=$(jq -n \
--arg tag "$TAG" \
--arg name "Test build: $BRANCH" \
--arg body "$BODY" \
--arg target "$GITHUB_SHA" \
'{tag_name:$tag, target_commitish:$target, name:$name, body:$body, draft:false, prerelease:true}')
HTTP=$(curl -sS -o resp.json -w '%{http_code}' -X POST "$API/repos/$REPO/releases" \
-H "Authorization: token $TOKEN" -H "Content-Type: application/json" -d "$PAYLOAD")
if [ "$HTTP" = "201" ]; then
RELEASE_ID=$(jq -r '.id' resp.json)
elif [ "$HTTP" = "409" ]; then
# Re-dispatching for the same branch replaces the previous APK rather
# than accumulating one release per attempt.
echo "️ Pre-release $TAG exists; reusing it"
RELEASE_ID=$(curl -fsS "$API/repos/$REPO/releases/tags/$TAG" \
-H "Authorization: token $TOKEN" | jq -r '.id')
for id in $(curl -fsS "$API/repos/$REPO/releases/$RELEASE_ID/assets" \
-H "Authorization: token $TOKEN" | jq -r '.[].id'); do
curl -fsS -X DELETE "$API/repos/$REPO/releases/$RELEASE_ID/assets/$id" \
-H "Authorization: token $TOKEN" >/dev/null
done
else
echo "❌ Failed to create pre-release (HTTP $HTTP):"; cat resp.json; exit 1
fi
for f in dist/test-apk/*.apk; do
echo "⬆️ $(basename "$f")"
curl -fsS -X POST \
"$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
-H "Authorization: token $TOKEN" -F "attachment=@$f" >/dev/null
done
{
echo ""
echo "**Published:** ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
echo ""
echo "Public link — no Gitea account needed. Delete the release when testing is done."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload APK
uses: actions/upload-artifact@v3
with:
+19
View File
@@ -110,6 +110,25 @@ two other projects, so building on every feature-branch commit would starve
them. The APK lands as the `jellytau-test-apk` artifact (7-day retention), named
for the branch and short SHA, with its size and SHA256 in the run summary.
#### Sending a build to an outside tester
Gitea **artifacts require an account** with read access to download, so an
artifact is no use to someone outside the project. Tick **`publish`** on the
dispatch and the APK is also attached to a **pre-release**, whose assets are a
plain public URL on a public repo — no account, no MR, no merge to `master`.
Two things make that safe to do from a feature branch:
- The tag is `test-<branch>`, **not** `v*`. Only `v*` triggers
`build-release.yml`, so nothing else reacts to it.
- It cannot reach existing users. The desktop updater reads a static
`latest.json` from the `updater` branch, not the release list, so a
pre-release published this way is invisible to anyone without the link.
Re-dispatching for the same branch replaces the APK on the existing
pre-release rather than piling up one release per attempt. Delete the release
when testing is over.
### Key Files
Player-related Kotlin files: