Repo-level secrets creation is broken on this Forgejo instance
(InsertEncryptedSecret fails with UTF-8 byte-sequence error, likely
a text-vs-bytea column type on the secret table). The fix is in
upstream Forgejo v16; until then, ${{ secrets.GITHUB_TOKEN }} (auto-
provided by the runner, scoped to contents: write for the current
repo) keeps the release workflow operational without any UI setup.
When the instance is upgraded and the secret table is migrated,
revert this commit to switch back to ${{ secrets.RELEASE_TOKEN }}
for least-privilege.
231 lines
No EOL
8.8 KiB
YAML
231 lines
No EOL
8.8 KiB
YAML
# Build and publish a release on the Forgejo source-of-truth instance
|
|
# with the PostIt Android APK as an attached asset.
|
|
#
|
|
# Triggered by a push of a git tag. Validates the tag/changelog pair,
|
|
# builds the APK using the existing Dockerfile (--target build-env), then
|
|
# publishes a Forgejo release via rasterstate/forgejo-release-action and
|
|
# uploads the APK as an asset.
|
|
#
|
|
# Authentication uses ${{ secrets.GITHUB_TOKEN }} (auto-provided by the
|
|
# Forgejo runner, scoped to contents: write for the current repo). A
|
|
# dedicated PAT (${{ secrets.RELEASE_TOKEN }}) was the preferred option
|
|
# for least-privilege, but creating repo-level secrets is currently
|
|
# broken on this Forgejo instance (InsertEncryptedSecret fails with a
|
|
# UTF-8 byte-sequence error, probably a text-vs-bytea column type on
|
|
# the secret table). Bumping to Forgejo v16 should fix it; until then,
|
|
# the runner-provided token keeps the workflow operational.
|
|
#
|
|
# This workflow complements .github/workflows/docker-publish-android.yml
|
|
# which targets the GitHub mirror; the validate-release logic mirrors
|
|
# the GitHub-side job so the two channels stay consistent.
|
|
name: Forgejo Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- '*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Tag à publier (requis en dispatch, ex. 1.0.6 ou 1.0.7-rc1).'
|
|
required: true
|
|
type: string
|
|
force_unstable:
|
|
description: 'Publier une release avec suffixe (ex. 1.0.0-rc1) malgré le fail-fast par défaut.'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
# Parse le tag, applique la règle de parité du patch
|
|
# (pair=stable / impair=preview / suffixe=unstable), fail-fast sur
|
|
# instable sauf opt-in, et vérifie que CHANGELOG.md contient une
|
|
# section `## [TAG] - <channel>` cohérente. Le body est extrait
|
|
# dans un artifact consommé par le job release.
|
|
validate-release:
|
|
runs-on: docker
|
|
steps:
|
|
- name: Checkout du code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Valider le tag et la section CHANGELOG
|
|
env:
|
|
# En push tag : github.ref_name est le tag.
|
|
# En workflow_dispatch : on lit l'input 'tag' (obligatoire).
|
|
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
|
FORCE_UNSTABLE: ${{ inputs.force_unstable || 'false' }}
|
|
run: |
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "::error::No tag provided. In workflow_dispatch, set the 'tag' input."
|
|
exit 1
|
|
fi
|
|
|
|
# Parse semver : MAJOR.MINOR.PATCH[-SUFFIX]
|
|
if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
|
echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format."
|
|
exit 1
|
|
fi
|
|
|
|
MAJOR="${BASH_REMATCH[1]}"
|
|
MINOR="${BASH_REMATCH[2]}"
|
|
PATCH="${BASH_REMATCH[3]}"
|
|
SUFFIX="${BASH_REMATCH[4]}"
|
|
|
|
# Classification du canal par parité du patch.
|
|
# Patch pair + pas de suffixe -> stable.
|
|
# Patch impair + pas de suffixe -> preview.
|
|
# Suffixe présent -> instable.
|
|
if [[ -n "$SUFFIX" ]]; then
|
|
CHANNEL="unstable"
|
|
elif (( PATCH % 2 == 0 )); then
|
|
CHANNEL="stable"
|
|
else
|
|
CHANNEL="preview"
|
|
fi
|
|
|
|
echo "Tag $TAG classifié comme channel=$CHANNEL"
|
|
|
|
# Fail-fast sur instable sauf opt-in explicite.
|
|
if [[ "$CHANNEL" == "unstable" && "$FORCE_UNSTABLE" != "true" ]]; then
|
|
echo "::error::Tag '$TAG' is unstable (suffix '$SUFFIX'). Refusing to publish."
|
|
echo "Set force_unstable=true via workflow_dispatch to override."
|
|
exit 1
|
|
fi
|
|
|
|
# Lecture du CHANGELOG.md (doit exister à la racine du repo).
|
|
if [[ ! -f CHANGELOG.md ]]; then
|
|
echo "::error::CHANGELOG.md not found at repo root."
|
|
exit 1
|
|
fi
|
|
|
|
# Extraction de la section [TAG]. On cherche la première ligne
|
|
# commençant par '## [' qui contient '[TAG]' (entre '## [' et
|
|
# la prochaine ligne '## [' ou fin de fichier). awk en mode
|
|
# paragraphe suffit et reste POSIX.
|
|
BODY=$(awk -v tag="[$TAG]" '
|
|
/^## \[/ {
|
|
if (in_section) exit
|
|
if (index($0, tag) > 0) in_section=1
|
|
next
|
|
}
|
|
in_section { print }
|
|
' CHANGELOG.md)
|
|
|
|
if [[ -z "$BODY" ]]; then
|
|
echo "::error::No section matching '## [$TAG]' found in CHANGELOG.md."
|
|
echo "Add a '## [$TAG] - $CHANNEL' section before tagging."
|
|
exit 1
|
|
fi
|
|
|
|
# Vérification cohérence du canal déclaré dans le suffixe.
|
|
# Format attendu : "## [TAG] - stable" / "- preview" / "- unstable".
|
|
if [[ "$BODY" != *" - $CHANNEL"* ]]; then
|
|
echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity."
|
|
echo "Current section body (first 5 lines):"
|
|
echo "$BODY" | head -5
|
|
exit 1
|
|
fi
|
|
|
|
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
|
|
|
# Écrit le body dans un fichier pour transmission via artifact.
|
|
# Le body est multi-ligne, donc artifact > heredoc $GITHUB_ENV.
|
|
mkdir -p release-body
|
|
printf '%s\n' "$BODY" > release-body/body.md
|
|
|
|
- name: Uploader le body de la release comme artifact
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: release-body
|
|
path: release-body/body.md
|
|
retention-days: 1
|
|
|
|
# Construit l'APK via le Dockerfile (stage build-env), puis publie
|
|
# la release Forgejo avec le body validé et l'APK en asset.
|
|
release:
|
|
needs: validate-release
|
|
runs-on: docker
|
|
steps:
|
|
- name: Checkout du code
|
|
uses: actions/checkout@v7
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Checkout du tag (workflow_dispatch uniquement)
|
|
# En push tag, le runner checkout déjà au bon commit.
|
|
# En workflow_dispatch, on checkout explicitement le tag demandé
|
|
# pour que l'APK soit bien construit depuis ce commit.
|
|
if: github.event_name == 'workflow_dispatch'
|
|
env:
|
|
TAG: ${{ inputs.tag }}
|
|
run: |
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "::error::No tag provided. In workflow_dispatch, set the 'tag' input."
|
|
exit 1
|
|
fi
|
|
git checkout "$TAG"
|
|
|
|
- name: Build de l'image Docker (stage build-env uniquement)
|
|
run: docker build --build-arg ANDROID_TARGET_RID=android-arm64 --target build-env -t postit-android .
|
|
|
|
- name: Extraire l'APK signé du conteneur
|
|
run: |
|
|
docker create --name extractor postit-android
|
|
docker cp extractor:/src/src/PostIt/PostIt.Android/bin/Release/net10.0-android/android-arm64/com.CompanyName.PostIt-Signed.apk ./PostIt.Android.apk
|
|
docker rm extractor
|
|
|
|
- name: Récupérer le body validé
|
|
uses: actions/download-artifact@v7
|
|
with:
|
|
name: release-body
|
|
path: release-body
|
|
|
|
- name: Calculer le canal (stable / preview / unstable) depuis le tag
|
|
# On re-parse le tag ici plutôt que de transporter le channel
|
|
# via artifact. Le calcul est trivial (parité du patch + suffixe)
|
|
# et reste ainsi explicite.
|
|
id: set-channel
|
|
env:
|
|
# En push tag : github.ref_name est le tag.
|
|
# En workflow_dispatch : on lit l'input 'tag'.
|
|
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
|
run: |
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "::error::No tag provided. In workflow_dispatch, set the 'tag' input."
|
|
exit 1
|
|
fi
|
|
if [[ ! "$TAG" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then
|
|
echo "::error::Tag '$TAG' does not match MAJOR.MINOR.PATCH[-SUFFIX] format."
|
|
exit 1
|
|
fi
|
|
PATCH="${BASH_REMATCH[3]}"
|
|
SUFFIX="${BASH_REMATCH[4]}"
|
|
if [[ -n "$SUFFIX" ]]; then
|
|
CHANNEL="unstable"
|
|
elif (( PATCH % 2 == 0 )); then
|
|
CHANNEL="stable"
|
|
else
|
|
CHANNEL="preview"
|
|
fi
|
|
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
|
|
echo "is_prerelease=$([[ $CHANNEL != stable ]] && echo true || echo false)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Publier la release Forgejo et uploader l'APK
|
|
uses: https://rasterhub.com/rasterstate/forgejo-release-action@v1
|
|
with:
|
|
# tag_name defaults to the pushed tag (GITHUB_REF_NAME).
|
|
body_path: release-body/body.md
|
|
# Stable -> Latest (false).
|
|
# Preview et Unstable -> prerelease (true).
|
|
prerelease: ${{ steps.set-channel.outputs.is_prerelease }}
|
|
files: |
|
|
PostIt.Android.apk
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |