feat/release-page #1
2 changed files with 329 additions and 289 deletions
switch release workflow from GitHub Actions to Forgejo Actions
The GitHub channel gave nothing but chaos (failed runs on tag push, auth issues with gh CLI on this side). Forgejo is the source of truth (cf. USER.md 'Remote setup' for the yavsc mirror setup), so move the .deb release workflow there. - Remove .github/workflows/build-and-release-deb.yml. - Add .forgejo/workflows/release.yml, mirroring the structure of yavsc/.forgejo/workflows/release.yml (single job, no Node, bash + jq + curl). amd64 and arm64 .deb built sequentially (matrix is impossible because actions/upload-artifact needs Node, which the runner image pazof/yavsc-build-env doesn't ship — same constraint as documented in MEMORY.md). - Reuse existing release on tag collision (PATCH instead of POST) to keep the /releases/tag/<tag> permalink stable — re-tag = le mal, but a re-build of the same tag should not duplicate releases.
commit
e6fd22f8a1
329
.forgejo/workflows/release.yml
Normal file
329
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
# Build and publish a postit-debian release on the Forgejo instance.
|
||||
#
|
||||
# Triggered by a push of a git tag. Validates the tag/changelog pair,
|
||||
# builds the .deb for amd64 and arm64 (sequential cross-RID .NET
|
||||
# publishes on a single amd64 runner container — matrix is not used
|
||||
# here because the runner image pazof/yavsc-build-env has no Node,
|
||||
# so actions/upload-artifact and actions/download-artifact (which
|
||||
# require Node) cannot be used to pass the .deb files between jobs.
|
||||
# All in one job, like yavsc's .forgejo/workflows/release.yml.),
|
||||
# then publishes a Forgejo release via the REST API and uploads both
|
||||
# .deb files as assets.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Why bash + jq + curl, no third-party actions: the runner's docker
|
||||
# label points at pazof/yavsc-build-env, a Debian image with jq but
|
||||
# without Node.js or python3. Any action like actions/checkout,
|
||||
# rasterstate/forgejo-release-action, actions/upload-artifact,
|
||||
# actions/download-artifact, etc. fails with "executable file not
|
||||
# found in $PATH". Same constraint as yavsc's
|
||||
# .forgejo/workflows/release.yml.
|
||||
#
|
||||
# Re-tag policy (cf. AGENTS.md "Re-tag = le mal") : on push de tag
|
||||
# ou dispatch, on *réutilise* la release existante (via PATCH) au
|
||||
# lieu d'en créer une nouvelle. Un tag Git pointe vers un commit
|
||||
# fixe ; si le binaire change (rebuild après modif du packaging),
|
||||
# on met à jour la release existante plutôt que d'en multiplier
|
||||
# pour un même tag. Le permalien /releases/tag/<tag> reste stable.
|
||||
name: Forgejo Release postit-deb
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag pazof/yavsc à packager (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:
|
||||
# Job unique : validation tag/CHANGELOG + build amd64 + build
|
||||
# arm64 + publication via l'API REST Forgejo (pas d'actions
|
||||
# tierces Node).
|
||||
release:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/pazof/yavsc-build-env:debian12-dotnet10-android36-v2
|
||||
steps:
|
||||
- name: Installer les pré-requis de build (debhelper + icônes)
|
||||
# L'image runner fournit déjà dotnet-sdk-10.0, git, jq,
|
||||
# curl. On ajoute les outils spécifiques au packaging
|
||||
# Debian (debhelper, imagemagick pour les icônes .png
|
||||
# via `convert`, librsvg2-bin pour le SVG).
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential debhelper imagemagick librsvg2-bin \
|
||||
ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- name: Clone du repo au tag demandé
|
||||
env:
|
||||
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
|
||||
|
||||
cd /src
|
||||
if [[ ! -d _src/.git ]]; then
|
||||
# Clone unshallow pour préserver l'historique — utile
|
||||
# si un futur test en a besoin. Le coût est marginal
|
||||
# pour ce repo (< 50 commits).
|
||||
git clone https://forgejo.pschneider.fr/notazof/postit-debian.git _src
|
||||
fi
|
||||
|
||||
cd _src
|
||||
git fetch --tags --force --prune origin
|
||||
git checkout "$TAG"
|
||||
|
||||
echo "Checked out at $(git rev-parse HEAD) on tag $TAG"
|
||||
|
||||
- name: Valider le tag et la section CHANGELOG
|
||||
run: |
|
||||
cd /src/_src
|
||||
TAG="$(git describe --tags --exact-match HEAD 2>/dev/null || git rev-parse --short HEAD)"
|
||||
echo "Validating tag $TAG"
|
||||
|
||||
# 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.
|
||||
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:-false}" != "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 garde le titre
|
||||
# (ligne `## [TAG] - channel`) pour la vérification du
|
||||
# canal, puis on l'exclut du body envoyé à la release.
|
||||
BODY=$(awk -v tag="[$TAG]" '
|
||||
/^## \[/ {
|
||||
if (in_section) exit
|
||||
if (index($0, tag) > 0) {
|
||||
in_section=1
|
||||
print
|
||||
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 titre.
|
||||
TITLE=$(echo "$BODY" | head -1)
|
||||
if [[ "$TITLE" != *" - $CHANNEL"* ]]; then
|
||||
echo "::error::Section title '$TITLE' must declare suffix '- $CHANNEL' to match tag parity."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_BODY=$(echo "$BODY" | tail -n +2)
|
||||
IS_PRERELEASE=$([ "$CHANNEL" = "stable" ] && echo false || echo true)
|
||||
|
||||
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
||||
|
||||
# Expose channel + body pour les étapes suivantes via $GITHUB_ENV.
|
||||
echo "RELEASE_CHANNEL=$CHANNEL" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_BODY<<EOF" >> "$GITHUB_ENV"
|
||||
echo "$RELEASE_BODY" >> "$GITHUB_ENV"
|
||||
echo "EOF" >> "$GITHUB_ENV"
|
||||
echo "IS_PRERELEASE=$IS_PRERELEASE" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build .deb amd64
|
||||
env:
|
||||
POSTIT_GIT_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
||||
POSTIT_RUNTIME: linux-x64
|
||||
run: |
|
||||
cd /src/_src
|
||||
echo "→ Building amd64 for POSTIT_GIT_TAG=$POSTIT_GIT_TAG"
|
||||
make deb POSTIT_GIT_TAG="$POSTIT_GIT_TAG" POSTIT_RUNTIME=linux-x64
|
||||
|
||||
- name: Build .deb arm64
|
||||
env:
|
||||
POSTIT_GIT_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
||||
POSTIT_RUNTIME: linux-arm64
|
||||
run: |
|
||||
cd /src/_src
|
||||
echo "→ Building arm64 for POSTIT_GIT_TAG=$POSTIT_GIT_TAG"
|
||||
# Cross-RID .NET depuis un hôte amd64 : standard, pas
|
||||
# besoin de runner arm64 natif.
|
||||
make deb POSTIT_GIT_TAG="$POSTIT_GIT_TAG" POSTIT_RUNTIME=linux-arm64
|
||||
|
||||
- name: Localiser les .deb produits
|
||||
env:
|
||||
LOOKUP_TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
||||
run: |
|
||||
cd /src
|
||||
DEB_AMD64=$(find . -maxdepth 3 -name "postit_*${LOOKUP_TAG}-1_amd64.deb" \
|
||||
-not -path "./_src/debian/*" -printf '%p\n' | head -1)
|
||||
DEB_ARM64=$(find . -maxdepth 3 -name "postit_*${LOOKUP_TAG}-1_arm64.deb" \
|
||||
-not -path "./_src/debian/*" -printf '%p\n' | head -1)
|
||||
if [[ -z "$DEB_AMD64" || -z "$DEB_ARM64" ]]; then
|
||||
echo "::error::Missing .deb files. amd64='$DEB_AMD64' arm64='$DEB_ARM64'"
|
||||
ls -la /src/ 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "DEB_AMD64=/src/$DEB_AMD64" >> "$GITHUB_ENV"
|
||||
echo "DEB_ARM64=/src/$DEB_ARM64" >> "$GITHUB_ENV"
|
||||
echo "✓ Found both .deb files"
|
||||
|
||||
- name: Publier la release Forgejo via l'API REST
|
||||
env:
|
||||
GITHUB_TOKEN: *** secrets.GITHUB_TOKEN }}
|
||||
GITHUB_API_URL: ${{ github.api_url }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
|
||||
RELEASE_BODY: ${{ env.RELEASE_BODY }}
|
||||
IS_PRERELEASE: ${{ env.IS_PRERELEASE }}
|
||||
run: |
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::No tag resolved for the API call."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Le runner Forgejo expose l'API sur github.api_url (par
|
||||
# défaut http://…/api/v1). On retire le suffixe /api/v1
|
||||
# s'il est présent pour dériver la base du serveur, puis
|
||||
# on reconstruit l'URL de l'API proprement.
|
||||
API_BASE="${GITHUB_API_URL%/}"
|
||||
API_BASE="${API_BASE%/api/v1}"
|
||||
|
||||
# 1. Vérifier si la release existe déjà pour ce tag.
|
||||
# Politique : on réutilise (PATCH) plutôt que d'en
|
||||
# créer une nouvelle — cf. note "Re-tag policy" en
|
||||
# tête de fichier.
|
||||
echo "::group::Check existing release for tag $TAG"
|
||||
HTTP=$(curl -sS -o /tmp/existing.json -w '%{http_code}' \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/json" \
|
||||
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/tags/$TAG")
|
||||
echo "GET releases/tags/$TAG -> HTTP $HTTP"
|
||||
EXISTING_ID=""
|
||||
if [[ "$HTTP" == "200" ]]; then
|
||||
EXISTING_ID=$(jq -r '.id // empty' /tmp/existing.json)
|
||||
echo "Existing release id: ${EXISTING_ID:-none}"
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
# 2. Créer ou mettre à jour la release.
|
||||
if [[ -n "$EXISTING_ID" ]]; then
|
||||
echo "::group::Update release id=$EXISTING_ID"
|
||||
jq -n \
|
||||
--arg body "$RELEASE_BODY" \
|
||||
--argjson prerelease "$IS_PRERELEASE" \
|
||||
'{body: $body, prerelease: $prerelease}' \
|
||||
> /tmp/patch.json
|
||||
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
|
||||
-X PATCH \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary @/tmp/patch.json \
|
||||
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$EXISTING_ID")
|
||||
echo "PATCH release -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
else
|
||||
echo "::group::Create release"
|
||||
jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg name "$TAG" \
|
||||
--arg body "$RELEASE_BODY" \
|
||||
--argjson prerelease "$IS_PRERELEASE" \
|
||||
'{tag_name: $tag, name: $name, body: $body, prerelease: $prerelease}' \
|
||||
> /tmp/post.json
|
||||
HTTP=$(curl -sS -o /tmp/release.json -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary @/tmp/post.json \
|
||||
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases")
|
||||
echo "POST release -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
fi
|
||||
|
||||
if [[ "$HTTP" != "200" && "$HTTP" != "201" ]]; then
|
||||
echo "::error::Release creation/update failed (HTTP $HTTP):"
|
||||
cat /tmp/release.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_ID=$(jq -r '.id' /tmp/release.json)
|
||||
echo "Release id=$RELEASE_ID"
|
||||
|
||||
# 3. Upload les .deb en assets. Le nom du fichier passe
|
||||
# en query string (?name=...), pas en argument
|
||||
# positionnel entre --data-binary et l'URL.
|
||||
for entry in "amd64:$DEB_AMD64" "arm64:$DEB_ARM64"; do
|
||||
arch="${entry%%:*}"
|
||||
deb="${entry#*:}"
|
||||
echo "::group::Upload asset for arch=$arch: $deb"
|
||||
HTTP=$(curl -sS -o /tmp/asset.json -w '%{http_code}' \
|
||||
-X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
-H "Accept: application/json" \
|
||||
--data-binary "@$deb" \
|
||||
"$API_BASE/api/v1/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=$(basename "$deb")")
|
||||
echo "POST asset ($arch) -> HTTP $HTTP"
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ "$HTTP" != "201" ]]; then
|
||||
echo "::error::Asset upload failed for $arch (HTTP $HTTP):"
|
||||
cat /tmp/asset.json
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Release publiée : $API_BASE/$GITHUB_REPOSITORY/releases/tag/$TAG"
|
||||
289
.github/workflows/build-and-release-deb.yml
vendored
289
.github/workflows/build-and-release-deb.yml
vendored
|
|
@ -1,289 +0,0 @@
|
|||
name: Build and Release postit-deb
|
||||
|
||||
# Ce workflow est destiné à **GitHub Actions uniquement** (paths
|
||||
# /home/runner/..., ubuntu-latest, softprops/action-gh-release@v2).
|
||||
# Pour Forgejo Actions (pazof/yavsc-build-env, paths différents,
|
||||
# pas de Node), il faudrait un pendant dans .forgejo/workflows/ —
|
||||
# non écrit à ce jour.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag de pazof/yavsc à packager (ex. 1.0.6, 1.0.7-rc1). Requis pour un build ad-hoc.'
|
||||
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
|
||||
force_republish:
|
||||
description: 'Re-publier une release dont le tag existe déjà. Par défaut refusé (re-tag = le mal).'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# softprops/action-gh-release a besoin de contents: write
|
||||
# pour publier une release + uploader un asset.
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# Build matrix : un .deb par architecture. Le tag Git poussé sur
|
||||
# ce dépôt devient POSTIT_GIT_TAG pour `make deb`, qui clone
|
||||
# l'amont pazof/yavsc à ce tag et produit le .deb correspondant.
|
||||
# Sur amd64, la cross-compilation linux-arm64 marche nativement
|
||||
# (dotnet publish --runtime linux-arm64 depuis un hôte amd64).
|
||||
# On évite donc les runners arm64 natifs (qui existent mais sont
|
||||
# récents et plus chers en minutes).
|
||||
deb-build:
|
||||
name: Build .deb (${{ matrix.runtime }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runtime: linux-x64
|
||||
arch: amd64
|
||||
artifact_name: postit-amd64
|
||||
- runtime: linux-arm64
|
||||
arch: arm64
|
||||
artifact_name: postit-arm64
|
||||
steps:
|
||||
- name: Checkout postit-debian
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Installer les pré-requis de build (debhelper + icônes)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential debhelper imagemagick librsvg2-bin \
|
||||
git ca-certificates
|
||||
|
||||
- name: Installer .NET SDK 10
|
||||
uses: microsoft/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '10.0.x'
|
||||
|
||||
- name: Déterminer POSTIT_GIT_TAG
|
||||
id: tag
|
||||
run: |
|
||||
# Sur un push de branche (pas un tag), github.ref_name est
|
||||
# 'main' — `make deb POSTIT_GIT_TAG=main` clone pazof/yavsc
|
||||
# sur la branche main et produit un .deb à jour. Sur un push
|
||||
# de tag, c'est le numéro de tag (ex. '1.0.6'). Sur
|
||||
# workflow_dispatch, on lit l'input `tag`.
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ inputs.tag }}"
|
||||
else
|
||||
TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::POSTIT_GIT_TAG is empty. Pour workflow_dispatch, l'input 'tag' est obligatoire."
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "→ POSTIT_GIT_TAG=$TAG"
|
||||
|
||||
- name: Build du .deb via make deb
|
||||
env:
|
||||
POSTIT_GIT_TAG: ${{ steps.tag.outputs.tag }}
|
||||
POSTIT_RUNTIME: ${{ matrix.runtime }}
|
||||
run: |
|
||||
echo "→ Building for POSTIT_GIT_TAG=$POSTIT_GIT_TAG POSTIT_RUNTIME=$POSTIT_RUNTIME"
|
||||
make deb POSTIT_GIT_TAG="$POSTIT_GIT_TAG" POSTIT_RUNTIME="$POSTIT_RUNTIME"
|
||||
|
||||
- name: Localiser le .deb produit
|
||||
id: locate
|
||||
run: |
|
||||
# Le Makefile mv les .deb vers $POSTIT_OUT_DIR (par défaut
|
||||
# le répertoire parent du repo). Sur GitHub Actions, c'est
|
||||
# le workspace parent : /home/runner/work/.. Le .deb est
|
||||
# nommé d'après le tag brut (avec ou sans 'v', tel quel
|
||||
# poussé sur le remote), on cherche donc avec ref_name.
|
||||
DEB=$(find /home/runner -maxdepth 4 -name "postit_*${{ github.ref_name }}-1_${{ matrix.arch }}.deb" \
|
||||
-not -path "*/debian/*" \
|
||||
-printf '%p\n' | head -1)
|
||||
if [[ -z "$DEB" ]]; then
|
||||
echo "::error::No .deb matching postit_*${{ github.ref_name }}-1_${{ matrix.arch }}.deb found."
|
||||
echo "Files in parent dir:"
|
||||
ls -la /home/runner/work/ 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
echo "deb_path=$DEB" >> "$GITHUB_OUTPUT"
|
||||
echo "✓ Found $DEB"
|
||||
|
||||
- name: Téléverser le .deb en tant qu'Artéfact GitHub
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ steps.locate.outputs.deb_path }}
|
||||
retention-days: 7
|
||||
|
||||
# Validation : parse le tag, applique la parité patch (pair=stable /
|
||||
# impair=preview / suffixe=instable), vérifie que CHANGELOG.md
|
||||
# contient une section cohérente, et — point non négociable —
|
||||
# refuse de re-publier un tag qui existe déjà (re-tag = le mal).
|
||||
validate-release:
|
||||
# Tourne sur push de tag (release officielle) ou sur workflow_dispatch
|
||||
# avec un tag explicite (release ad-hoc). Sur push de branche, on
|
||||
# ne publie pas — les jobs de build suffisent (artefacts seulement).
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout postit-debian
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Déterminer le tag à publier
|
||||
id: pick_tag
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ inputs.tag }}"
|
||||
else
|
||||
TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "::error::Tag is empty. Sur workflow_dispatch, l'input 'tag' est obligatoire."
|
||||
exit 1
|
||||
fi
|
||||
# Strip leading 'v' (git tag convention).
|
||||
if [[ "$TAG" =~ ^v(.*)$ ]]; then
|
||||
TAG="${BASH_REMATCH[1]}"
|
||||
echo "Stripped leading 'v' — using TAG=$TAG for validation."
|
||||
fi
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Valider le tag, le CHANGELOG et l'unicité du tag
|
||||
env:
|
||||
FORCE_UNSTABLE: ${{ inputs.force_unstable || github.event.inputs.force_unstable || 'false' }}
|
||||
FORCE_REPUBLISH: ${{ inputs.force_republish || github.event.inputs.force_republish || 'false' }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG="${{ steps.pick_tag.outputs.tag }}"
|
||||
|
||||
# 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.
|
||||
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]. awk en mode paragraphe.
|
||||
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é.
|
||||
HEADER=$(grep -m1 "^## \[$TAG\]" CHANGELOG.md)
|
||||
if [[ "$HEADER" != *" - $CHANNEL"* ]]; then
|
||||
echo "::error::Section '## [$TAG]' must declare suffix '- $CHANNEL' to match tag parity."
|
||||
echo "Current section header: $HEADER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Section CHANGELOG validée pour [$TAG] - $CHANNEL"
|
||||
|
||||
# Anti-re-tag : refuse de publier si une release existe déjà
|
||||
# pour ce tag. softprops/action-gh-release créerait sinon une
|
||||
# nouvelle release par-dessus (re-tag = le mal). Opt-in via
|
||||
# workflow_dispatch + force_republish=true uniquement.
|
||||
if gh release view "$TAG" >/dev/null 2>&1; then
|
||||
if [[ "$FORCE_REPUBLISH" != "true" ]]; then
|
||||
echo "::error::Release for tag '$TAG' already exists. Refusing to re-tag."
|
||||
echo "Set force_republish=true via workflow_dispatch to override."
|
||||
exit 1
|
||||
else
|
||||
echo "::warning::Release '$TAG' already exists — force_republish=true, proceeding."
|
||||
fi
|
||||
else
|
||||
echo "✓ No existing release for tag '$TAG'."
|
||||
fi
|
||||
|
||||
# Exposition aux étapes suivantes via $GITHUB_ENV.
|
||||
{
|
||||
echo "RELEASE_BODY<<EOF"
|
||||
echo "$BODY"
|
||||
echo "EOF"
|
||||
echo "RELEASE_TAG=$TAG"
|
||||
echo "RELEASE_CHANNEL=$CHANNEL"
|
||||
if [[ "$CHANNEL" == "stable" ]]; then
|
||||
echo "IS_PRERELEASE=false"
|
||||
else
|
||||
echo "IS_PRERELEASE=true"
|
||||
fi
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
publish-release:
|
||||
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
|
||||
needs: [deb-build, validate-release]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Récupérer les .deb depuis les artefacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ./
|
||||
merge-multiple: true
|
||||
|
||||
- name: Lister les .deb téléchargés
|
||||
run: ls -la ./
|
||||
|
||||
- name: Publier la release GitHub et uploader les .deb
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ env.RELEASE_TAG }}
|
||||
files: |
|
||||
./postit-amd64/*.deb
|
||||
./postit-arm64/*.deb
|
||||
body: ${{ env.RELEASE_BODY }}
|
||||
prerelease: ${{ env.IS_PRERELEASE }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue