ci: publish Windows releases to Gitea via GitHub build bridge
Mirror release tags to GitHub build bridge / Mirror tag to GitHub (push) Failing after 15s
Mirror release tags to GitHub build bridge / Mirror tag to GitHub (push) Failing after 15s
Gitea has no Windows runner, so release tags pushed to this repo are mirrored to the public GitHub fork yorickr/plezy (SSH deploy key over ssh.github.com:443). The fork's windows-gitea-release workflow builds the Windows x64 installer + portable on GitHub-hosted runners and publishes them to the Gitea release for the same tag.
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
name: Mirror release tags to GitHub build bridge
|
||||||
|
|
||||||
|
# Gitea is the source of truth for releases, but this instance has no Windows
|
||||||
|
# runner (only ubuntu-latest on the homeserver), and Flutter Windows builds
|
||||||
|
# require a Windows host. So when a tag is pushed here, this workflow mirrors it
|
||||||
|
# to the public GitHub fork yorickr/plezy, whose only active workflow
|
||||||
|
# (.github/workflows/windows-gitea-release.yml) builds the Windows release on
|
||||||
|
# GitHub-hosted runners and publishes the artifacts back to this repo's Gitea
|
||||||
|
# release for the same tag.
|
||||||
|
#
|
||||||
|
# The write path uses an SSH deploy key (secret RELEASE_PUSH_KEY) that only has
|
||||||
|
# access to the fork. ssh.github.com:443 is used because the homeserver
|
||||||
|
# demonstrably reaches GitHub over 443, and it avoids depending on outbound 22.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "Tag to mirror to the GitHub build bridge (e.g. 2.13.0)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mirror-tag:
|
||||||
|
name: Mirror tag to GitHub
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Mirror tag to GitHub fork
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
RELEASE_PUSH_KEY: ${{ secrets.RELEASE_PUSH_KEY }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||||
|
TAG="${GITHUB_REF_NAME}"
|
||||||
|
else
|
||||||
|
TAG="${{ inputs.tag }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${TAG}" ]]; then
|
||||||
|
echo "No tag to mirror" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "${RELEASE_PUSH_KEY}" ]]; then
|
||||||
|
echo "Missing RELEASE_PUSH_KEY secret - refusing to mirror" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The tag must exist in this repository.
|
||||||
|
git rev-parse --verify "refs/tags/${TAG}" >/dev/null || {
|
||||||
|
echo "Tag ${TAG} not found in this repository" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
REMOTE="git@ssh.github.com:yorickr/plezy.git"
|
||||||
|
|
||||||
|
# Already mirrored? Nothing to do (previous run may have succeeded,
|
||||||
|
# or the tag was pushed to GitHub directly).
|
||||||
|
if git ls-remote "${REMOTE}" "refs/tags/${TAG}" 2>/dev/null | grep -q .; then
|
||||||
|
echo "Tag ${TAG} already exists on the GitHub build bridge - nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${HOME}/.ssh"
|
||||||
|
chmod 700 "${HOME}/.ssh"
|
||||||
|
printf '%s\n' "${RELEASE_PUSH_KEY}" > "${HOME}/.ssh/id_ed25519"
|
||||||
|
chmod 600 "${HOME}/.ssh/id_ed25519"
|
||||||
|
# GitHub's published ed25519 host key (ssh.github.com shares it).
|
||||||
|
printf '%s\n' \
|
||||||
|
'github.com,ssh.github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl' \
|
||||||
|
> "${HOME}/.ssh/known_hosts"
|
||||||
|
|
||||||
|
export GIT_SSH_COMMAND="ssh -p 443 -i '${HOME}/.ssh/id_ed25519' -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile='${HOME}/.ssh/known_hosts'"
|
||||||
|
|
||||||
|
git push "${REMOTE}" "refs/tags/${TAG}:refs/tags/${TAG}"
|
||||||
|
echo "Mirrored tag ${TAG} to the GitHub build bridge"
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
name: Publish Windows Release to Gitea
|
||||||
|
|
||||||
|
# Builds the Windows release on GitHub-hosted runners and publishes it to the
|
||||||
|
# release of the matching tag in the Gitea repository. It is triggered by tags
|
||||||
|
# mirrored from Gitea by .gitea/workflows/windows-release.yml (or by any tag
|
||||||
|
# pushed to this fork directly).
|
||||||
|
#
|
||||||
|
# This workflow is skipped everywhere except GitHub and except the
|
||||||
|
# yorickr/plezy fork, which owns the GITEA_TOKEN secret. It never creates a
|
||||||
|
# GitHub release; the Gitea release is the artifact channel.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Only place this workflow names the SDK; .github/actions/setup-flutter-git pins the same release.
|
||||||
|
FLUTTER_VERSION: "3.44.0"
|
||||||
|
# Same defines as build.yml, except Sentry is always enabled: these builds are
|
||||||
|
# the primary channel from the Gitea repo, and the DSN is compiled in anyway.
|
||||||
|
RELEASE_DART_DEFINES: >-
|
||||||
|
--dart-define=ENABLE_UPDATE_CHECK=true
|
||||||
|
--dart-define=ENABLE_SENTRY=true
|
||||||
|
--dart-define=GIT_COMMIT=${{ github.sha }}
|
||||||
|
--dart-define=SENTRY_ENVIRONMENT=gitea-release
|
||||||
|
--dart-define=ENABLE_DONATIONS=true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows:
|
||||||
|
name: Build Windows x64 and publish to Gitea
|
||||||
|
# Never queue on the Gitea instance (it has no Windows runner) and never
|
||||||
|
# run in random forks (they lack GITEA_TOKEN).
|
||||||
|
if: github.server_url == 'https://github.com' && github.repository == 'yorickr/plezy'
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||||
|
cache: true
|
||||||
|
cache-key: "gitea-release-flutter-:os:-:channel:-:version:-:arch:-:hash:"
|
||||||
|
pub-cache: false
|
||||||
|
|
||||||
|
- name: Cache Pub dependencies
|
||||||
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~\AppData\Local\Pub\Cache
|
||||||
|
key: gitea-release-${{ runner.os }}-${{ runner.arch }}-pub-${{ hashFiles('pubspec.yaml', 'pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: pwsh
|
||||||
|
run: flutter pub get --enforce-lockfile --no-example
|
||||||
|
|
||||||
|
- name: Install patched Flutter engine (DComp)
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
flutter precache --windows
|
||||||
|
.\windows\tool\install-patched-engine.ps1
|
||||||
|
|
||||||
|
- name: Build Windows x64
|
||||||
|
shell: pwsh
|
||||||
|
run: flutter build windows --release ${{ env.RELEASE_DART_DEFINES }} --dart-define=SENTRY_DIST=gitea-windows-x64 --split-debug-info=debug-info/windows-x64
|
||||||
|
|
||||||
|
- name: Upload symbols to bugs.plezy.app
|
||||||
|
# Only when this fork has been given the admin token (the production
|
||||||
|
# token lives on edde746/plezy, which is not the build bridge).
|
||||||
|
if: secrets.BUGS_ADMIN_TOKEN != ''
|
||||||
|
shell: pwsh
|
||||||
|
env:
|
||||||
|
BUGS_ADMIN_TOKEN: ${{ secrets.BUGS_ADMIN_TOKEN }}
|
||||||
|
SENTRY_DIST: gitea-windows-x64
|
||||||
|
run: .\scripts\upload-symbols.ps1 windows-x64
|
||||||
|
|
||||||
|
- name: Read version from pubspec
|
||||||
|
id: version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$v = (Select-String -Path pubspec.yaml -Pattern '^version:\s*(\S+)').Matches[0].Groups[1].Value -replace '\+.*'
|
||||||
|
echo "version=$v" >> $env:GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build installer and portable
|
||||||
|
shell: pwsh
|
||||||
|
run: .\windows\build-installer.ps1 -X64BuildDir "build\windows\x64\runner\Release" -Version "${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
- name: Sign installer for WinSparkle (EdDSA)
|
||||||
|
if: secrets.SPARKLE_PRIVATE_KEY != ''
|
||||||
|
env:
|
||||||
|
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$keyPath = Join-Path $env:RUNNER_TEMP "plezy-winsparkle-ed25519.pem"
|
||||||
|
try {
|
||||||
|
Set-Content -Path $keyPath -Value $env:SPARKLE_PRIVATE_KEY -Encoding ascii -NoNewline
|
||||||
|
$output = & dart run auto_updater:sign_update plezy-windows-installer.exe $keyPath
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "WinSparkle signer failed with exit code $LASTEXITCODE"
|
||||||
|
}
|
||||||
|
$match = [regex]::Match($output, 'edSignature="([^"]*)"')
|
||||||
|
if (-not $match.Success) {
|
||||||
|
throw "WinSparkle signer returned no EdDSA signature"
|
||||||
|
}
|
||||||
|
Set-Content -Path win-ed-signature.txt -Value $match.Groups[1].Value -Encoding ascii -NoNewline
|
||||||
|
Set-Content -Path win-installer-size.txt -Value (Get-Item plezy-windows-installer.exe).Length.ToString() -Encoding ascii -NoNewline
|
||||||
|
} finally {
|
||||||
|
Remove-Item -Path $keyPath -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Publish to Gitea release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
GITEA_SERVER: ${{ vars.GITEA_SERVER || 'https://git.yorickrommers.nl' }}
|
||||||
|
GITEA_REPO: ${{ vars.GITEA_REPO || 'yorickr/plezy' }}
|
||||||
|
TAG: ${{ github.ref_name }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ -z "${GITEA_TOKEN}" ]]; then
|
||||||
|
echo "Missing GITEA_TOKEN secret - cannot publish" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
API="${GITEA_SERVER}/api/v1/repos/${GITEA_REPO}"
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
|
||||||
|
# Only publish tags that exist in the Gitea repo (i.e. tags mirrored
|
||||||
|
# from there, or pushed to Gitea directly). Tags that only exist on
|
||||||
|
# GitHub are not this pipeline's business.
|
||||||
|
if ! curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/git/refs/tags/${TAG}" >/dev/null 2>&1; then
|
||||||
|
echo "Tag ${TAG} does not exist in ${GITEA_REPO} on ${GITEA_SERVER}; skipping publish"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
NOTES=$(cat <<'EOF'
|
||||||
|
Windows build for Plezy.
|
||||||
|
|
||||||
|
Artifacts:
|
||||||
|
- **plezy-windows-installer.exe** - Inno Setup installer (silent-update compatible, auto-detects architecture)
|
||||||
|
- **plezy-windows-x64-portable.7z** - portable x64 build (no installation needed)
|
||||||
|
|
||||||
|
Built automatically on GitHub-hosted runners from this tag and published to
|
||||||
|
this Gitea release.
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
if RELEASE=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/releases/tags/${TAG}" 2>/dev/null); then
|
||||||
|
RELEASE_ID=$(echo "${RELEASE}" | jq -r '.id')
|
||||||
|
echo "Release for ${TAG} already exists (id ${RELEASE_ID}); updating"
|
||||||
|
BODY=$(jq -n --arg name "Plezy ${VERSION}" --arg body "${NOTES}" \
|
||||||
|
'{name: $name, body: $body, draft: false, prerelease: false}')
|
||||||
|
RESP=$(curl -fsS -X PATCH -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" -d "${BODY}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}")
|
||||||
|
else
|
||||||
|
echo "Creating release for ${TAG}"
|
||||||
|
BODY=$(jq -n --arg tag "${TAG}" --arg name "Plezy ${VERSION}" --arg body "${NOTES}" \
|
||||||
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
||||||
|
RESP=$(curl -fsS -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" -d "${BODY}" \
|
||||||
|
"${API}/releases")
|
||||||
|
fi
|
||||||
|
|
||||||
|
RELEASE_ID=$(echo "${RESP}" | jq -r '.id')
|
||||||
|
if [[ -z "${RELEASE_ID}" || "${RELEASE_ID}" == "null" ]]; then
|
||||||
|
echo "Failed to create/update the release:" >&2
|
||||||
|
echo "${RESP}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload each asset, replacing a same-named asset from an earlier run.
|
||||||
|
upload() {
|
||||||
|
local FILE="$1"
|
||||||
|
local NAME
|
||||||
|
NAME=$(basename "${FILE}")
|
||||||
|
local EXISTING_ASSET
|
||||||
|
EXISTING_ASSET=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}/assets" | jq -r --arg name "${NAME}" \
|
||||||
|
'.[] | select(.name == $name) | .id')
|
||||||
|
if [[ -n "${EXISTING_ASSET}" ]]; then
|
||||||
|
curl -fsS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}/assets/${EXISTING_ASSET}" >/dev/null
|
||||||
|
fi
|
||||||
|
curl -fsS -X POST -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-F "attachment=@${FILE}" \
|
||||||
|
"${API}/releases/${RELEASE_ID}/assets?name=${NAME}" >/dev/null
|
||||||
|
echo "Uploaded ${NAME} ($(stat -c%s "${FILE}") bytes)"
|
||||||
|
}
|
||||||
|
|
||||||
|
upload plezy-windows-x64-portable.7z
|
||||||
|
upload plezy-windows-installer.exe
|
||||||
|
|
||||||
|
echo "Published release for ${TAG}: ${GITEA_SERVER}/${GITEA_REPO}/releases/tag/${TAG}"
|
||||||
Reference in New Issue
Block a user