Some checks failed
Forgejo Release postit-deb / release (push) Failing after 2m16s
When the apt-get install of arm64 shlibs fails silently (e.g. because the configured apt sources don't include arm64 for some packages), the next build step still tries to cross-build for arm64 and dpkg-shlibdeps aborts. The Makefile's mv pattern then matches the previous amd64 .deb (same filename prefix), exits 0, and the missing arm64 .deb is only discovered later in the 'Localiser les .deb' step. This adds: - explicit set -e so any failure aborts the step cleanly - `dpkg --add-architecture arm64` with fail-fast on error - pre-flight apt-cache show check for each arm64 package - explicit fail-fast on apt-get install failure - dpkg -l sanity check to confirm the libs are installed If the arm64 packages really are not available from the configured apt sources, the workflow will now fail with a clear message instead of producing a half-broken release.
370 lines
15 KiB
YAML
370 lines
15 KiB
YAML
# 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: the runner auto-provides a token scoped to the
|
|
# repository. We read it once into the local env var FORGEJO_TOKEN
|
|
# and never reference the runtime-level name again.
|
|
#
|
|
# 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.
|
|
#
|
|
# Inter-step state: persisted as JSON in /tmp/release-state.json,
|
|
# read at the top of each step with `jq -r .<field>`. Using JSON
|
|
# sidesteps shell parsing issues that come with sourcing a file
|
|
# that contains heredocs / markdown / colons / etc. — RELEASE_BODY
|
|
# in particular is markdown content straight from CHANGELOG.md and
|
|
# cannot be safely `source`d.
|
|
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
|
|
env:
|
|
STATE_FILE: /tmp/release-state.json
|
|
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
|
|
# dpkg-shlibdeps at arm64 build time needs the arm64
|
|
# shlibs (libc6, libstdc++6, libdl, libm, libpthread,
|
|
# libfontconfig, libgtk-3, etc.) to be present on the
|
|
# host. We add arm64 as a foreign architecture and pull
|
|
# them in. --no-install-recommends keeps the install
|
|
# surface minimal.
|
|
set -e
|
|
dpkg --add-architecture arm64 || { echo "::error::dpkg --add-architecture arm64 failed"; exit 1; }
|
|
apt-get update
|
|
# Verify each arm64 package is actually installable. Bail
|
|
# early with a clear message if any are missing from the
|
|
# configured apt sources — silent apt-get install
|
|
# failures are the worst kind of workflow bug.
|
|
for pkg in libc6:arm64 libstdc++6:arm64 \
|
|
libfontconfig1:arm64 libfreetype6:arm64 \
|
|
libgtk-3-0:arm64; do
|
|
if ! apt-cache show "$pkg" >/dev/null 2>&1; then
|
|
echo "::error::arm64 package '$pkg' is not available in apt sources"
|
|
exit 1
|
|
fi
|
|
done
|
|
apt-get install -y --no-install-recommends \
|
|
libc6:arm64 libstdc++6:arm64 \
|
|
libfontconfig1:arm64 libfreetype6:arm64 \
|
|
libgtk-3-0:arm64 || { echo "::error::Failed to install arm64 shlibs"; exit 1; }
|
|
# Sanity-check: the libs must be visible to dpkg-shlibdeps.
|
|
dpkg -l libc6:arm64 libstdc++6:arm64 | tail -3
|
|
rm -rf /var/lib/apt/lists/*
|
|
: > "$STATE_FILE"
|
|
|
|
- name: Clone du repo au tag demandé
|
|
env:
|
|
TAG: ${{ forgejo.event_name == 'push' && forgejo.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"
|
|
|
|
# Persist TAG in the state file. --arg ensures proper
|
|
# JSON escaping of any special chars.
|
|
jq -n --arg tag "$TAG" '{tag: $tag}' > "$STATE_FILE"
|
|
|
|
- name: Valider le tag et la section CHANGELOG
|
|
run: |
|
|
TAG=$(jq -r '.tag' "$STATE_FILE")
|
|
cd /src/_src
|
|
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"
|
|
|
|
# Persist validation results. Use --arg for strings (so
|
|
# jq handles escaping of backticks, asterisks, colons,
|
|
# etc.) and --argjson for booleans.
|
|
jq -n \
|
|
--arg tag "$TAG" \
|
|
--arg body "$RELEASE_BODY" \
|
|
--argjson is_prerelease "$IS_PRERELEASE" \
|
|
'{tag: $tag, body: $body, is_prerelease: $is_prerelease}' \
|
|
> "$STATE_FILE"
|
|
|
|
- name: Build .deb amd64
|
|
env:
|
|
POSTIT_RUNTIME: linux-x64
|
|
run: |
|
|
TAG=$(jq -r '.tag' "$STATE_FILE")
|
|
cd /src/_src
|
|
echo "→ Building amd64 for POSTIT_GIT_TAG=$TAG"
|
|
make deb POSTIT_GIT_TAG="$TAG" POSTIT_RUNTIME=linux-x64
|
|
|
|
- name: Build .deb arm64
|
|
env:
|
|
POSTIT_RUNTIME: linux-arm64
|
|
run: |
|
|
TAG=$(jq -r '.tag' "$STATE_FILE")
|
|
cd /src/_src
|
|
echo "→ Building arm64 for POSTIT_GIT_TAG=$TAG"
|
|
# Cross-RID .NET depuis un hôte amd64 : standard, pas
|
|
# besoin de runner arm64 natif.
|
|
make deb POSTIT_GIT_TAG="$TAG" POSTIT_RUNTIME=linux-arm64
|
|
|
|
- name: Localiser les .deb produits
|
|
run: |
|
|
TAG=$(jq -r '.tag' "$STATE_FILE")
|
|
cd /src
|
|
DEB_AMD64=$(find . -maxdepth 3 -name "postit_*${TAG}-1_amd64.deb" \
|
|
-not -path "./_src/debian/*" -printf '%p\n' | head -1)
|
|
DEB_ARM64=$(find . -maxdepth 3 -name "postit_*${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
|
|
# Merge .deb paths into state file.
|
|
jq --arg amd64 "/src/$DEB_AMD64" --arg arm64 "/src/$DEB_ARM64" \
|
|
'. + {deb_amd64: $amd64, deb_arm64: $arm64}' \
|
|
"$STATE_FILE" > "${STATE_FILE}.tmp" && mv "${STATE_FILE}.tmp" "$STATE_FILE"
|
|
echo "✓ Found both .deb files"
|
|
|
|
- name: Publier la release Forgejo via l'API REST
|
|
env:
|
|
FORGEJO_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
FORGEJO_API_URL: ${{ forgejo.api_url }}
|
|
FORGEJO_REPOSITORY: ${{ forgejo.repository }}
|
|
run: |
|
|
TAG=$(jq -r '.tag' "$STATE_FILE")
|
|
RELEASE_BODY=$(jq -r '.body' "$STATE_FILE")
|
|
IS_PRERELEASE=$(jq -r '.is_prerelease' "$STATE_FILE")
|
|
DEB_AMD64=$(jq -r '.deb_amd64' "$STATE_FILE")
|
|
DEB_ARM64=$(jq -r '.deb_arm64' "$STATE_FILE")
|
|
if [[ -z "$TAG" ]]; then
|
|
echo "::error::No tag resolved for the API call."
|
|
exit 1
|
|
fi
|
|
|
|
# Le runner Forgejo expose l'API sur forgejo.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="${FORGEJO_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 $FORGEJO_TOKEN" \
|
|
-H "Accept: application/json" \
|
|
"$API_BASE/api/v1/repos/$FORGEJO_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 $FORGEJO_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept: application/json" \
|
|
--data-binary @/tmp/patch.json \
|
|
"$API_BASE/api/v1/repos/$FORGEJO_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 $FORGEJO_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-H "Accept: application/json" \
|
|
--data-binary @/tmp/post.json \
|
|
"$API_BASE/api/v1/repos/$FORGEJO_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 $FORGEJO_TOKEN" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
-H "Accept: application/json" \
|
|
--data-binary "@$deb" \
|
|
"$API_BASE/api/v1/repos/$FORGEJO_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/$FORGEJO_REPOSITORY/releases/tag/$TAG"
|