Skip to content

ci: trigger Forgejo workflow run to verify .forgejo/workflows routing. #4

ci: trigger Forgejo workflow run to verify .forgejo/workflows routing.

ci: trigger Forgejo workflow run to verify .forgejo/workflows routing. #4

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