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