name: Release on: push: tags: - "v*" workflow_dispatch: inputs: ref: description: "Git ref to build (branch/tag/SHA)" required: false default: "master" tag: description: "Release tag to publish assets to (for example: v4.14.6)" required: false permissions: contents: write jobs: build-dashboard: name: Build Dashboard runs-on: ubuntu-24.04 env: R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ inputs.ref || github.ref }} - name: Resolve tag id: tag shell: bash run: | if [ "${{ github.event_name }}" = "push" ]; then tag="${GITHUB_REF_NAME}" elif [ -n "${{ inputs.tag }}" ]; then tag="${{ inputs.tag }}" else tag="$(git describe --tags --abbrev=0)" fi if [ -z "$tag" ]; then echo "Failed to resolve tag." >&2 exit 1 fi echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 10.28.2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 20 cache: "pnpm" cache-dependency-path: dashboard/pnpm-lock.yaml - name: Build dashboard dist shell: bash run: | pnpm --dir dashboard install --frozen-lockfile pnpm --dir dashboard run build echo "${{ steps.tag.outputs.tag }}" > dashboard/dist/assets/version cd dashboard zip -r "AstrBot-${{ steps.tag.outputs.tag }}-dashboard.zip" dist - name: Upload dashboard artifact uses: actions/upload-artifact@v6 with: name: Dashboard-${{ steps.tag.outputs.tag }} if-no-files-found: error path: dashboard/AstrBot-${{ steps.tag.outputs.tag }}-dashboard.zip - name: Upload dashboard package to Cloudflare R2 if: ${{ env.R2_ACCOUNT_ID != '' && env.R2_ACCESS_KEY_ID != '' && env.R2_SECRET_ACCESS_KEY != '' }} env: R2_BUCKET_NAME: "astrbot" R2_OBJECT_NAME: "astrbot-webui-latest.zip" VERSION_TAG: ${{ steps.tag.outputs.tag }} shell: bash run: | curl https://rclone.org/install.sh | sudo bash mkdir -p ~/.config/rclone cat < ~/.config/rclone/rclone.conf [r2] type = s3 provider = Cloudflare access_key_id = $R2_ACCESS_KEY_ID secret_access_key = $R2_SECRET_ACCESS_KEY endpoint = https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com EOF cp "dashboard/AstrBot-${VERSION_TAG}-dashboard.zip" "dashboard/${R2_OBJECT_NAME}" rclone copy "dashboard/${R2_OBJECT_NAME}" "r2:${R2_BUCKET_NAME}" --progress cp "dashboard/AstrBot-${VERSION_TAG}-dashboard.zip" "dashboard/astrbot-webui-${VERSION_TAG}.zip" rclone copy "dashboard/astrbot-webui-${VERSION_TAG}.zip" "r2:${R2_BUCKET_NAME}" --progress build-desktop: name: Build ${{ matrix.name }} runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - name: linux-x64 runner: ubuntu-24.04 os: linux arch: amd64 - name: linux-arm64 runner: ubuntu-24.04-arm os: linux arch: arm64 - name: windows-x64 runner: windows-2022 os: win arch: amd64 - name: windows-arm64 runner: windows-11-arm os: win arch: arm64 - name: macos-x64 runner: macos-15-intel os: mac arch: amd64 - name: macos-arm64 runner: macos-15 os: mac arch: arm64 env: CSC_IDENTITY_AUTO_DISCOVERY: "false" steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ inputs.ref || github.ref }} - name: Resolve tag id: tag shell: bash run: | if [ "${{ github.event_name }}" = "push" ]; then tag="${GITHUB_REF_NAME}" elif [ -n "${{ inputs.tag }}" ]; then tag="${{ inputs.tag }}" else tag="$(git describe --tags --abbrev=0)" fi if [ -z "$tag" ]; then echo "Failed to resolve tag." >&2 exit 1 fi echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Setup uv uses: astral-sh/setup-uv@v7 - name: Setup Python uses: actions/setup-python@v6 with: python-version: "3.12" - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 10.28.2 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: 20 cache: "pnpm" cache-dependency-path: | dashboard/pnpm-lock.yaml desktop/pnpm-lock.yaml - name: Prepare OpenSSL for Windows ARM64 if: ${{ matrix.os == 'win' && matrix.arch == 'arm64' }} shell: pwsh run: | git clone https://github.com/microsoft/vcpkg.git C:\vcpkg & C:\vcpkg\bootstrap-vcpkg.bat -disableMetrics & C:\vcpkg\vcpkg.exe install openssl:arm64-windows "VCPKG_ROOT=C:\vcpkg" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "VCPKGRS_TRIPLET=arm64-windows" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "OPENSSL_DIR=C:\vcpkg\installed\arm64-windows" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "OPENSSL_ROOT_DIR=C:\vcpkg\installed\arm64-windows" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "OPENSSL_LIB_DIR=C:\vcpkg\installed\arm64-windows\lib" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "OPENSSL_INCLUDE_DIR=C:\vcpkg\installed\arm64-windows\include" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Install dependencies shell: bash run: | uv sync pnpm --dir dashboard install --frozen-lockfile pnpm --dir desktop install --frozen-lockfile - name: Build desktop package shell: bash run: | pnpm --dir dashboard run build pnpm --dir desktop run build:webui pnpm --dir desktop run build:backend pnpm --dir desktop run sync:version pnpm --dir desktop exec electron-builder --publish never - name: Normalize artifact names shell: bash env: NAME_PREFIX: AstrBot-${{ steps.tag.outputs.tag }}-${{ matrix.arch }}-${{ matrix.os }} run: | shopt -s nullglob out_dir="desktop/dist/release" mkdir -p "$out_dir" files=( desktop/dist/*.AppImage desktop/dist/*.dmg desktop/dist/*.zip desktop/dist/*.exe ) if [ ${#files[@]} -eq 0 ]; then echo "No desktop artifacts found to rename." >&2 exit 1 fi for src in "${files[@]}"; do file="$(basename "$src")" case "$file" in *.AppImage) dest="$out_dir/${NAME_PREFIX}.AppImage" ;; *.dmg) dest="$out_dir/${NAME_PREFIX}.dmg" ;; *.exe) dest="$out_dir/${NAME_PREFIX}.exe" ;; *.zip) dest="$out_dir/${NAME_PREFIX}.zip" ;; *) continue ;; esac cp "$src" "$dest" done ls -la "$out_dir" - name: Upload desktop artifacts uses: actions/upload-artifact@v6 with: name: AstrBot-${{ steps.tag.outputs.tag }}-${{ matrix.arch }}-${{ matrix.os }} if-no-files-found: error path: desktop/dist/release/* publish-release: name: Publish GitHub Release runs-on: ubuntu-24.04 needs: - build-dashboard - build-desktop steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ inputs.ref || github.ref }} - name: Resolve tag id: tag shell: bash run: | if [ "${{ github.event_name }}" = "push" ]; then tag="${GITHUB_REF_NAME}" elif [ -n "${{ inputs.tag }}" ]; then tag="${{ inputs.tag }}" else tag="$(git describe --tags --abbrev=0)" fi if [ -z "$tag" ]; then echo "Failed to resolve tag." >&2 exit 1 fi echo "tag=$tag" >> "$GITHUB_OUTPUT" - name: Download dashboard artifact uses: actions/download-artifact@v7 with: name: Dashboard-${{ steps.tag.outputs.tag }} path: release-assets - name: Download desktop artifacts uses: actions/download-artifact@v7 with: pattern: AstrBot-${{ steps.tag.outputs.tag }}-* path: release-assets merge-multiple: true - name: Resolve release notes id: notes shell: bash run: | note_file="changelogs/${{ steps.tag.outputs.tag }}.md" if [ ! -f "$note_file" ]; then note_file="$(mktemp)" echo "Release ${{ steps.tag.outputs.tag }}" > "$note_file" fi echo "file=$note_file" >> "$GITHUB_OUTPUT" - name: Ensure release exists env: GH_TOKEN: ${{ github.token }} shell: bash run: | tag="${{ steps.tag.outputs.tag }}" if ! gh release view "$tag" >/dev/null 2>&1; then gh release create "$tag" --title "$tag" --notes-file "${{ steps.notes.outputs.file }}" fi - name: Remove stale assets from release env: GH_TOKEN: ${{ github.token }} shell: bash run: | tag="${{ steps.tag.outputs.tag }}" while IFS= read -r asset; do case "$asset" in *.AppImage|*.dmg|*.zip|*.exe|*.blockmap) gh release delete-asset "$tag" "$asset" -y || true ;; esac done < <(gh release view "$tag" --json assets --jq '.assets[].name') - name: Upload assets to release env: GH_TOKEN: ${{ github.token }} shell: bash run: | tag="${{ steps.tag.outputs.tag }}" gh release upload "$tag" release-assets/* --clobber publish-pypi: name: Publish PyPI runs-on: ubuntu-24.04 needs: publish-release steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ inputs.ref || github.ref }} - name: Set up Python uses: actions/setup-python@v6 with: python-version: "3.10" - name: Install uv shell: bash run: python -m pip install uv - name: Build package shell: bash run: uv build - name: Publish to PyPI env: UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} shell: bash run: uv publish