ci: trigger Forgejo workflow run to verify .forgejo/workflows routing. #4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release (deb, exe, flatpak, tar.gz, AUR) — GitHub mirror | |
| # Primary release pipeline: .forgejo/workflows/release.yml on git.atlastechsolutions.co.uk | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - "v*.*.*" | |
| - "v*.*.*-*" | |
| - "v*.*" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| env: | |
| APP_ID: io.streambooru.StreamBooru | |
| APP_NAME: StreamBooru | |
| jobs: | |
| prep: | |
| if: contains(github.server_url, 'github.com') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.meta.outputs.version }} | |
| owner: ${{ steps.meta.outputs.owner }} | |
| repo: ${{ steps.meta.outputs.repo }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - id: meta | |
| run: | | |
| if [[ "$GITHUB_REF" == refs/tags/* ]]; then | |
| REF="${GITHUB_REF##*/}" | |
| VERSION="${REF#v}" | |
| else | |
| VERSION=$(node -p "require('./package.json').version") | |
| fi | |
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | |
| echo "owner=${GITHUB_REPOSITORY%%/*}" >> "$GITHUB_OUTPUT" | |
| echo "repo=${GITHUB_REPOSITORY##*/}" >> "$GITHUB_OUTPUT" | |
| linux-deb-and-tar: | |
| if: contains(github.server_url, 'github.com') | |
| needs: prep | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install deps | |
| run: npm ci | |
| - name: Ensure electron-builder installed | |
| run: npm i -D electron-builder@^24 | |
| - name: Build .deb and linux tar.gz (no publish) | |
| run: npx electron-builder --publish=never --config electron-builder.yml --linux deb tar.gz | |
| - name: Create README_INSTALL for tarballs | |
| run: cp docs/INSTALL.md dist/INSTALL.md || true | |
| - name: Collect artifacts | |
| run: | | |
| mkdir -p release-linux | |
| shopt -s nullglob | |
| cp dist/*.deb release-linux/ || true | |
| cp dist/*.tar.gz release-linux/ || true | |
| cp dist/INSTALL.md release-linux/ || true | |
| ls -la release-linux | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: linux | |
| path: release-linux | |
| windows-exe: | |
| if: contains(github.server_url, 'github.com') && startsWith(github.ref, 'refs/tags/') | |
| continue-on-error: true | |
| needs: prep | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install deps | |
| run: npm ci | |
| - name: Ensure electron-builder installed | |
| run: npm i -D electron-builder@^24 | |
| - name: Install Wine and NSIS (cross-build .exe on Linux) | |
| run: | | |
| set -euo pipefail | |
| if command -v sudo >/dev/null 2>&1; then SUDO=sudo; else SUDO=""; fi | |
| export DEBIAN_FRONTEND=noninteractive | |
| $SUDO dpkg --add-architecture i386 2>/dev/null || true | |
| $SUDO apt-get update -qq | |
| $SUDO apt-get install -y -qq wine64 wine32 nsis | |
| - name: Build NSIS .exe installer (no publish) | |
| env: | |
| USE_SYSTEM_NSIS: "true" | |
| run: npx electron-builder --publish=never --config electron-builder.yml --win nsis | |
| - name: Collect artifacts | |
| run: | | |
| mkdir -p release-win | |
| shopt -s nullglob | |
| cp dist/*.exe release-win/ || true | |
| cp docs/INSTALL.md release-win/INSTALL.md || true | |
| ls -la release-win | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: windows | |
| path: release-win | |
| flatpak: | |
| if: contains(github.server_url, 'github.com') && startsWith(github.ref, 'refs/tags/') | |
| continue-on-error: true | |
| needs: prep | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install deps | |
| run: npm ci | |
| - name: Install Flatpak tooling | |
| run: | | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update | |
| apt-get install -y flatpak flatpak-builder | |
| flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo | |
| flatpak --user install -y --noninteractive flathub \ | |
| org.freedesktop.Platform//24.08 \ | |
| org.freedesktop.Sdk//24.08 \ | |
| org.electronjs.Electron2.BaseApp//24.08 | |
| - name: Build Flatpak bundle | |
| run: npx electron-builder --publish=never --config electron-builder.yml --linux flatpak | |
| - name: Collect artifacts | |
| run: | | |
| mkdir -p release-flatpak | |
| find dist -maxdepth 1 -name '*.flatpak' -exec cp -v {} release-flatpak/ \; | |
| cp docs/INSTALL.md release-flatpak/INSTALL.md 2>/dev/null || cp INSTALL.md release-flatpak/INSTALL.md 2>/dev/null || true | |
| ls -la release-flatpak | |
| test "$(find release-flatpak -maxdepth 1 -name '*.flatpak' | wc -l)" -gt 0 | |
| - uses: actions/upload-artifact@v6 | |
| with: | |
| name: flatpak | |
| path: release-flatpak | |
| android: | |
| if: contains(github.server_url, 'github.com') | |
| continue-on-error: true | |
| needs: prep | |
| runs-on: ubuntu-latest | |
| env: | |
| RELEASE_VERSION: ${{ needs.prep.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup Node | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install deps | |
| run: npm ci | |
| - name: Sync Android version from release tag | |
| run: node scripts/sync-android-version.mjs | |
| - name: Add & Sync Capacitor Android | |
| run: | | |
| npx cap sync | |
| if [ ! -d android ]; then | |
| npx cap add android | |
| fi | |
| npx cap sync android | |
| - name: Setup Java | |
| uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - name: Make Gradle wrapper executable | |
| working-directory: android | |
| run: chmod +x gradlew | |
| # No Debug build in release workflow | |
| - name: Decode Keystore (PKCS#12) | |
| run: | | |
| if [ -n "${ANDROID_KEYSTORE_BASE64}" ]; then | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > android/keystore.p12 | |
| echo "Keystore decoded to android/keystore.p12" | |
| else | |
| echo "ANDROID_KEYSTORE_BASE64 not set; skipping keystore decode" | |
| fi | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| - name: Build Signed Release (AAB + APK) | |
| working-directory: android | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| KEYSTORE_PATH: keystore.p12 | |
| KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| run: | | |
| if [ -n "${ANDROID_KEYSTORE_BASE64}" ]; then | |
| ./gradlew clean | |
| ./gradlew \ | |
| bundleRelease \ | |
| assembleRelease \ | |
| -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/android/$KEYSTORE_PATH \ | |
| -Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \ | |
| -Pandroid.injected.signing.store.type=PKCS12 \ | |
| -Pandroid.injected.signing.key.alias=$KEY_ALIAS \ | |
| -Pandroid.injected.signing.key.password=$KEY_PASSWORD | |
| else | |
| echo "No keystore secret; skipping signed release build" | |
| fi | |
| - name: Prepare Android artifacts (rename APK; keep AAB out of release) | |
| run: | | |
| set -e | |
| mkdir -p out-apk out-aab | |
| APK_BASENAME="$(grep '^APK_BASENAME=' android/version.properties | cut -d= -f2-)" | |
| if [ -z "$APK_BASENAME" ]; then APK_BASENAME="streambooru"; fi | |
| if compgen -G "android/app/build/outputs/apk/release/*.apk" > /dev/null; then | |
| cp android/app/build/outputs/apk/release/*.apk "out-apk/${APK_BASENAME}.apk" | |
| ls -la out-apk || true | |
| else | |
| echo "No release APK found." | |
| fi | |
| # Keep AAB only as Actions artifact (not attached to Release) | |
| if compgen -G "android/app/build/outputs/bundle/release/*.aab" > /dev/null; then | |
| cp android/app/build/outputs/bundle/release/*.aab out-aab/ # you can rename if you prefer | |
| ls -la out-aab || true | |
| else | |
| echo "No release AAB found." | |
| fi | |
| - name: Upload APK artifact (for Release attachment) | |
| if: hashFiles('out-apk/*.apk') != '' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: android-apk | |
| path: out-apk/*.apk | |
| if-no-files-found: warn | |
| - name: Skip APK upload (no signed build) | |
| if: hashFiles('out-apk/*.apk') == '' | |
| run: echo "No release APK produced (keystore missing or Gradle build failed)." >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload AAB artifact (private to Actions; not attached to Release) | |
| if: hashFiles('out-aab/*.aab') != '' | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: android-aab | |
| path: out-aab/*.aab | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| create-release: | |
| if: contains(github.server_url, 'github.com') && startsWith(github.ref, 'refs/tags/') | |
| needs: [prep, linux-deb-and-tar, windows-exe, flatpak, android] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout (to read CHANGELOG.md) | |
| uses: actions/checkout@v5 | |
| with: | |
| fetch-depth: 0 | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v7 | |
| with: | |
| path: ./artifacts | |
| - name: List artifacts (some may be missing if a job failed) | |
| run: find ./artifacts -type f -maxdepth 3 -print || true | |
| - name: Setup Node (for extractor) | |
| uses: actions/setup-node@v5 | |
| with: | |
| node-version: 24 | |
| - name: Extract release notes from CHANGELOG.md | |
| id: extract | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| node ./scripts/extract-changelog.mjs "$TAG" "./CHANGELOG.md" "./CHANGELOG_RELEASE.md" || true | |
| if [ ! -s "./CHANGELOG_RELEASE.md" ] && [ -f "./docs/CHANGELOG.md" ]; then | |
| node ./scripts/extract-changelog.mjs "$TAG" "./docs/CHANGELOG.md" "./CHANGELOG_RELEASE.md" || true | |
| fi | |
| - name: Generate GitHub release notes | |
| id: ghnotes | |
| if: contains(github.server_url, 'github.com') | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| TAG="${GITHUB_REF_NAME}" | |
| PREV_TAG="$(git describe --tags --abbrev=0 "${TAG}^" 2>/dev/null || true)" | |
| ARGS=(-f "tag_name=${TAG}" -f "target_commitish=${TAG}") | |
| if [ -n "$PREV_TAG" ]; then | |
| ARGS+=(-f "previous_tag_name=${PREV_TAG}") | |
| fi | |
| if gh api "repos/${GITHUB_REPOSITORY}/releases/generate-release-notes" "${ARGS[@]}" --jq '.body' > GITHUB_NOTES.md 2>/dev/null; then | |
| echo "generated=true" >> "$GITHUB_OUTPUT" | |
| else | |
| : > GITHUB_NOTES.md | |
| echo "generated=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build combined release body | |
| id: body | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| node ./scripts/build-release-notes.mjs "./RELEASE_BODY.md" "./CHANGELOG_RELEASE.md" "./GITHUB_NOTES.md" | |
| if [ -s "./RELEASE_BODY.md" ]; then | |
| echo "found=true" >> "$GITHUB_OUTPUT" | |
| echo "notes_path=RELEASE_BODY.md" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "found=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Detect pre-release tag | |
| id: flags | |
| shell: bash | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| # SemVer pre-releases: v1.2.3-beta.1, v1.2.3-rc.1, etc. | |
| if [[ "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-.+ ]]; then | |
| echo "prerelease=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "prerelease=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Upload release notes (debug) | |
| if: always() | |
| uses: actions/upload-artifact@v6 | |
| with: | |
| name: release-notes-${{ github.ref_name }} | |
| path: | | |
| CHANGELOG_RELEASE.md | |
| GITHUB_NOTES.md | |
| RELEASE_BODY.md | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| - name: Create/Update GitHub Release | |
| if: steps.body.outputs.found == 'true' | |
| uses: softprops/action-gh-release@v3 | |
| env: | |
| GITHUB_API_URL: ${{ contains(github.server_url, 'github.com') && 'https://api.github.com' || format('{0}/api/v1', github.server_url) }} | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| body_path: ${{ steps.body.outputs.notes_path }} | |
| generate_release_notes: false | |
| prerelease: ${{ steps.flags.outputs.prerelease == 'true' }} | |
| make_latest: ${{ steps.flags.outputs.prerelease != 'true' }} | |
| overwrite_files: true | |
| fail_on_unmatched_files: false | |
| files: | | |
| artifacts/linux/** | |
| artifacts/windows/** | |
| artifacts/flatpak/** | |
| artifacts/android-apk/*.apk | |
| - name: Create/Update GitHub Release (auto-generated notes only) | |
| if: steps.body.outputs.found != 'true' | |
| uses: softprops/action-gh-release@v3 | |
| env: | |
| GITHUB_API_URL: ${{ contains(github.server_url, 'github.com') && 'https://api.github.com' || format('{0}/api/v1', github.server_url) }} | |
| with: | |
| tag_name: ${{ github.ref_name }} | |
| name: ${{ github.ref_name }} | |
| generate_release_notes: true | |
| prerelease: ${{ steps.flags.outputs.prerelease == 'true' }} | |
| make_latest: ${{ steps.flags.outputs.prerelease != 'true' }} | |
| overwrite_files: true | |
| fail_on_unmatched_files: false | |
| files: | | |
| artifacts/linux/** | |
| artifacts/windows/** | |
| artifacts/flatpak/** | |
| artifacts/android-apk/*.apk | |
| aur-stable: | |
| if: contains(github.server_url, 'github.com') && startsWith(github.ref, 'refs/tags/') && !contains(github.ref_name, '-') | |
| needs: [prep, create-release] | |
| runs-on: ubuntu-latest | |
| env: | |
| VERSION: ${{ needs.prep.outputs.version }} | |
| OWNER: ${{ needs.prep.outputs.owner }} | |
| REPO: ${{ needs.prep.outputs.repo }} | |
| AUR_PACKAGE: ${{ secrets.AUR_PACKAGE }} | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Check AUR secrets | |
| id: check | |
| shell: bash | |
| run: | | |
| publish=true | |
| if [ -z "${AUR_SSH_PRIVATE_KEY}" ] || [ -z "${AUR_USERNAME}" ] || [ -z "${AUR_PACKAGE}" ]; then | |
| publish=false | |
| fi | |
| echo "publish=${publish}" >> "$GITHUB_OUTPUT" | |
| if [ "${publish}" != "true" ]; then | |
| echo "AUR stable secrets missing; skipping." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Generate and publish stable AUR package | |
| if: steps.check.outputs.publish == 'true' | |
| run: | | |
| chmod +x scripts/publish-aur.sh | |
| ./scripts/publish-aur.sh "${VERSION}" "${OWNER}" "${REPO}" "${AUR_PACKAGE}" "'streambooru' 'streambooru-bin-beta'" | |
| - name: Publish to AUR | |
| if: steps.check.outputs.publish == 'true' | |
| uses: KSXGitHub/github-actions-deploy-aur@v2.7.1 | |
| with: | |
| pkgname: ${{ secrets.AUR_PACKAGE }} | |
| pkgbuild: aur-out/PKGBUILD | |
| assets: | | |
| aur-out/.SRCINFO | |
| aur/streambooru.sh | |
| aur/streambooru.desktop | |
| commit_username: "github-actions[bot]" | |
| commit_email: "41898282+github-actions[bot]@users.noreply.github.com" | |
| ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| ssh_keyscan_types: rsa,ecdsa,ed25519 | |
| aur-beta: | |
| if: contains(github.server_url, 'github.com') && startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, '-') | |
| needs: [prep, create-release] | |
| runs-on: ubuntu-latest | |
| env: | |
| VERSION: ${{ needs.prep.outputs.version }} | |
| OWNER: ${{ needs.prep.outputs.owner }} | |
| REPO: ${{ needs.prep.outputs.repo }} | |
| AUR_PACKAGE_BETA: ${{ secrets.AUR_PACKAGE_BETA }} | |
| AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Check AUR beta secrets | |
| id: check | |
| shell: bash | |
| run: | | |
| publish=true | |
| if [ -z "${AUR_SSH_PRIVATE_KEY}" ] || [ -z "${AUR_USERNAME}" ] || [ -z "${AUR_PACKAGE_BETA}" ]; then | |
| publish=false | |
| fi | |
| echo "publish=${publish}" >> "$GITHUB_OUTPUT" | |
| if [ "${publish}" != "true" ]; then | |
| echo "AUR beta secrets missing; skipping." >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| - name: Generate and publish beta AUR package | |
| if: steps.check.outputs.publish == 'true' | |
| run: | | |
| chmod +x scripts/publish-aur.sh | |
| ./scripts/publish-aur.sh "${VERSION}" "${OWNER}" "${REPO}" "${AUR_PACKAGE_BETA}" "'streambooru' 'streambooru-bin'" " (pre-release)" | |
| - name: Publish to AUR | |
| if: steps.check.outputs.publish == 'true' | |
| uses: KSXGitHub/github-actions-deploy-aur@v2.7.1 | |
| with: | |
| pkgname: ${{ secrets.AUR_PACKAGE_BETA }} | |
| pkgbuild: aur-out/PKGBUILD | |
| assets: | | |
| aur-out/.SRCINFO | |
| aur/streambooru.sh | |
| aur/streambooru.desktop | |
| commit_username: "github-actions[bot]" | |
| commit_email: "41898282+github-actions[bot]@users.noreply.github.com" | |
| ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| ssh_keyscan_types: rsa,ecdsa,ed25519 |