All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m54s
latest.json only had linux-x86_64, built from the .deb. An .rpm install looks up linux-x86_64-rpm first, then fell back on linux-x86_64, downloaded the .deb and refused it (InvalidUpdaterFormat). - scripts/build-latest-json.sh writes linux-x86_64-deb, linux-x86_64-rpm, linux-x86_64 (the .deb, as a fallback) and windows-x86_64 from the release assets. It fails unless it finds exactly one .deb, one .rpm and one -setup.exe, each with a non-empty .sig. The JSON comes from jq alone, notes read with --rawfile, file names encoded with @uri like the upload step. - scripts/build-latest-json.test.mjs (vitest) runs it under bash -e -o pipefail on a temporary assets dir; check-frontend installs jq. - release.yml calls the script, writes the notes output under a random delimiter, checks the manifest with jq before the registry DELETE, and names the .rpm instead of an AppImage in the release notes. - verify-release.sh requires the four keys, the extension of each URL and the minisign file: of each signature, and exits 1 on any failure. - Docs: release skill, qa-update-cycle section 3 (rpm procedure, keys, reason and removal condition of the fallback), architecture, README, CHANGELOG, STATE. Resolves #320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
241 lines
10 KiB
YAML
241 lines
10 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
jobs:
|
|
build-and-release:
|
|
runs-on: ubuntu
|
|
container: ubuntu:22.04
|
|
env:
|
|
PATH: /root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
steps:
|
|
- name: Install base tools and runtimes
|
|
run: |
|
|
apt-get update
|
|
apt-get install -y curl wget git sudo ca-certificates gnupg
|
|
# Install Node.js 24 (#376). Trust note, kept on purpose without a
|
|
# pinned tarball: NodeSource's script runs as root in this
|
|
# container, and the Node.js it installs then runs `npx tauri build`
|
|
# with TAURI_SIGNING_PRIVATE_KEY in its environment. A compromised
|
|
# script or package would reach the updater signing key, and the
|
|
# publishing tokens of the later steps. See docs/architecture.md
|
|
# (CI/CD, release.yml).
|
|
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
|
apt-get install -y nodejs
|
|
# Install Rust
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
|
rustc --version
|
|
cargo --version
|
|
node --version
|
|
npm --version
|
|
|
|
- name: Checkout
|
|
uses: https://github.com/actions/checkout@v4
|
|
|
|
- name: Install Linux dependencies
|
|
run: |
|
|
apt-get install -y build-essential libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf jq libssl-dev xdg-utils libdbus-1-dev
|
|
|
|
- name: Install Windows cross-compile dependencies
|
|
run: |
|
|
apt-get install -y lld llvm clang nsis
|
|
rustup target add x86_64-pc-windows-msvc
|
|
cargo install --locked cargo-xwin
|
|
|
|
- name: Install frontend dependencies
|
|
run: npm ci
|
|
|
|
- name: Build Tauri
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
npx tauri build --bundles deb,rpm
|
|
|
|
- name: Build Tauri Windows
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
npx tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc
|
|
|
|
- name: Collect release files
|
|
run: |
|
|
mkdir -p release-assets
|
|
cp src-tauri/target/release/bundle/deb/*.deb release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/deb/*.deb.sig release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/rpm/*.rpm release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/release/bundle/rpm/*.rpm.sig release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe release-assets/ 2>/dev/null || true
|
|
cp src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe.sig release-assets/ 2>/dev/null || true
|
|
ls -la release-assets/
|
|
|
|
- name: Copy changelogs to public
|
|
run: |
|
|
cp CHANGELOG.md public/CHANGELOG.md
|
|
cp CHANGELOG.fr.md public/CHANGELOG.fr.md
|
|
|
|
- name: Extract changelog
|
|
id: changelog
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME#v}"
|
|
NOTES=$(sed -n "/^## \[${TAG}\]/,/^## /{/^## \[${TAG}\]/d;/^## /d;p}" CHANGELOG.md)
|
|
if [ -z "$NOTES" ]; then
|
|
NOTES=$(sed -n "/^## ${TAG}/,/^## /{/^## ${TAG}/d;/^## /d;p}" CHANGELOG.md)
|
|
fi
|
|
NOTES=$(echo "$NOTES" | sed -e '/./,$!d' -e :a -e '/^\n*$/{$d;N;ba}')
|
|
# Random delimiter (#320): a changelog line equal to a fixed one
|
|
# would end the value there and turn the lines after it into step
|
|
# outputs.
|
|
DELIM="CHANGELOG_EOF_$(od -An -N16 -tx1 /dev/urandom | tr -d ' \n')"
|
|
{
|
|
echo "notes<<${DELIM}"
|
|
echo "$NOTES"
|
|
echo "${DELIM}"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Generate latest.json
|
|
env:
|
|
CHANGELOG_NOTES: ${{ steps.changelog.outputs.notes }}
|
|
run: |
|
|
# One key per Linux package, a .deb fallback and the NSIS installer;
|
|
# fails unless it finds exactly one .deb, .rpm and -setup.exe, each
|
|
# with a non-empty .sig (#320). Notes reach jq as a file, as data.
|
|
# Called as scripts/build-latest-json.test.mjs calls it.
|
|
printf '%s' "$CHANGELOG_NOTES" > /tmp/release-notes.md
|
|
bash -e -o pipefail scripts/build-latest-json.sh \
|
|
release-assets \
|
|
"${GITHUB_REF_NAME#v}" \
|
|
/tmp/release-notes.md \
|
|
"${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}" \
|
|
> release-assets/latest.json
|
|
|
|
echo "Generated latest.json:"
|
|
cat release-assets/latest.json
|
|
|
|
- name: Create release and upload assets
|
|
env:
|
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
CHANGELOG_NOTES: ${{ steps.changelog.outputs.notes }}
|
|
run: |
|
|
TAG="${GITHUB_REF_NAME}"
|
|
API_URL="${GITHUB_SERVER_URL}/api/v1"
|
|
REPO="${GITHUB_REPOSITORY}"
|
|
|
|
BODY="${CHANGELOG_NOTES}
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
**Windows** : Téléchargez le fichier \`.exe\` ci-dessous.
|
|
**Linux** : Téléchargez le fichier \`.deb\` (Debian, Ubuntu) ou \`.rpm\` (Fedora, openSUSE) ci-dessous."
|
|
|
|
# Delete existing release for this tag (if any) to allow re-creation
|
|
EXISTING=$(curl -s "${API_URL}/repos/${REPO}/releases/tags/${TAG}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}")
|
|
EXISTING_ID=$(echo "$EXISTING" | jq -r '.id // empty')
|
|
if [ -n "$EXISTING_ID" ]; then
|
|
echo "Deleting existing release ID: $EXISTING_ID"
|
|
curl -s -X DELETE \
|
|
"${API_URL}/repos/${REPO}/releases/${EXISTING_ID}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}"
|
|
fi
|
|
|
|
# Create release
|
|
RELEASE_RESPONSE=$(curl -s -X POST \
|
|
"${API_URL}/repos/${REPO}/releases" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(jq -n \
|
|
--arg tag "$TAG" \
|
|
--arg name "Simpl'Résultat ${TAG}" \
|
|
--arg body "$BODY" \
|
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')")
|
|
|
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
|
echo "Created release ID: $RELEASE_ID"
|
|
|
|
if [ "$RELEASE_ID" = "null" ] || [ -z "$RELEASE_ID" ]; then
|
|
echo "ERROR: Failed to create release"
|
|
echo "$RELEASE_RESPONSE"
|
|
exit 1
|
|
fi
|
|
|
|
# Upload all assets
|
|
for file in release-assets/*; do
|
|
FILENAME=$(basename "$file")
|
|
ENCODED_FILENAME=$(printf '%s' "$FILENAME" | jq -sRr @uri)
|
|
FILESIZE=$(stat -c%s "$file")
|
|
echo "Uploading: $FILENAME (${FILESIZE} bytes)"
|
|
HTTP_CODE=$(curl -w "%{http_code}" --max-time 300 -X POST \
|
|
"${API_URL}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${ENCODED_FILENAME}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/octet-stream" \
|
|
--data-binary "@${file}" \
|
|
-o /tmp/upload_response.json)
|
|
echo "HTTP $HTTP_CODE"
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "Upload failed:"
|
|
cat /tmp/upload_response.json
|
|
echo ""
|
|
fi
|
|
done
|
|
|
|
echo "Release created: ${GITHUB_SERVER_URL}/${REPO}/releases/tag/${TAG}"
|
|
|
|
- name: Publish latest.json to package registry
|
|
env:
|
|
FORGEJO_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
|
|
run: |
|
|
# Check the manifest before the DELETE below (#320): once the old
|
|
# version is deleted, installed apps find no update until a valid
|
|
# one is uploaded. A failed check stops the job, registry untouched.
|
|
# Same four keys as scripts/build-latest-json.sh writes.
|
|
if ! jq -e --arg version "${GITHUB_REF_NAME#v}" '
|
|
.version == $version
|
|
and (.platforms | keys) == ["linux-x86_64", "linux-x86_64-deb", "linux-x86_64-rpm", "windows-x86_64"]
|
|
and all(.platforms[]; (.signature | type) == "string" and (.signature | length) > 0)
|
|
and (.platforms["linux-x86_64"].url | endswith(".deb"))
|
|
and (.platforms["linux-x86_64-deb"].url | endswith(".deb"))
|
|
and (.platforms["linux-x86_64-rpm"].url | endswith(".rpm"))
|
|
and (.platforms["windows-x86_64"].url | endswith(".exe"))
|
|
' release-assets/latest.json > /dev/null; then
|
|
echo "ERROR: release-assets/latest.json failed its checks, registry left as is:"
|
|
cat release-assets/latest.json
|
|
exit 1
|
|
fi
|
|
|
|
# DELETE uses API v1, PUT uses the package upload API
|
|
DELETE_URL="${GITHUB_SERVER_URL}/api/v1/packages/${GITHUB_REPOSITORY_OWNER}/generic/simpl-resultat/latest"
|
|
UPLOAD_URL="${GITHUB_SERVER_URL}/api/packages/${GITHUB_REPOSITORY_OWNER}/generic/simpl-resultat/latest"
|
|
|
|
# Delete the old package version to avoid 409 conflicts
|
|
echo "Deleting old package version (if any)..."
|
|
DEL_CODE=$(curl -s -w "%{http_code}" -X DELETE \
|
|
"${DELETE_URL}" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-o /tmp/del_response.json)
|
|
echo "Delete HTTP $DEL_CODE"
|
|
# 204 = deleted, 404 = didn't exist (both OK)
|
|
if [ "$DEL_CODE" != "204" ] && [ "$DEL_CODE" != "404" ]; then
|
|
echo "WARNING: Unexpected delete response:"
|
|
cat /tmp/del_response.json
|
|
fi
|
|
|
|
echo "Uploading latest.json to package registry..."
|
|
HTTP_CODE=$(curl -w "%{http_code}" -X PUT \
|
|
"${UPLOAD_URL}/latest.json" \
|
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data-binary "@release-assets/latest.json" \
|
|
-o /tmp/pkg_response.json)
|
|
echo "Upload HTTP $HTTP_CODE"
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "ERROR: Failed to publish latest.json:"
|
|
cat /tmp/pkg_response.json
|
|
exit 1
|
|
fi
|