Compare commits
7
Commits
7168c71d34
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d78c071818 | ||
|
|
b0219603be | ||
|
|
73ca96fa67 | ||
|
|
6e5754fb59 | ||
|
|
c8f41a34c0 | ||
|
|
5ff6bcfe51 | ||
|
|
4fd930aa0b |
@@ -15,7 +15,12 @@ runs:
|
|||||||
- name: install python deps
|
- name: install python deps
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pip install pyzipper
|
# Ubuntu 24.04 enforces PEP 668 (externally-managed-environment) and
|
||||||
|
# minimal macOS runners ship no pip by default: try the system installer
|
||||||
|
# first, then a --user install, finally bootstrap pip via ensurepip.
|
||||||
|
python3 -m pip install --break-system-packages -q pyzipper 2>/dev/null \
|
||||||
|
|| python3 -m pip install -q --user pyzipper 2>/dev/null \
|
||||||
|
|| { python3 -m ensurepip --user && python3 -m pip install -q --user pyzipper; }
|
||||||
- name: Decrypt and Mask
|
- name: Decrypt and Mask
|
||||||
shell: python
|
shell: python
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
- name: Free Disk Space (Ubuntu)
|
- name: Free Disk Space (Ubuntu)
|
||||||
uses: jlumbroso/free-disk-space@main
|
uses: jlumbroso/free-disk-space@main
|
||||||
@@ -195,17 +197,17 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
cd $(dirname $(dirname $(which flutter)))
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
[[ "3.24.5" == ${{env.ANDROID_FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.rdgen-src/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
|
|
||||||
- uses: nttld/setup-ndk@v1
|
- uses: nttld/setup-ndk@afb4c9964b521afb97c864b7d40b11e6911bd410 # v1 pinned to node20 runtime
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: ${{ env.NDK_VERSION }}
|
ndk-version: ${{ env.NDK_VERSION }}
|
||||||
add-to-path: true
|
add-to-path: true
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
@@ -599,7 +601,7 @@ jobs:
|
|||||||
- uses: r0adkll/sign-android-release@v1
|
- uses: r0adkll/sign-android-release@v1
|
||||||
name: Sign app APK
|
name: Sign app APK
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
if: env.ANDROID_SIGNING_KEY != null
|
if: env.ANDROID_SIGNING_KEY != ''
|
||||||
id: sign-rustdesk
|
id: sign-rustdesk
|
||||||
with:
|
with:
|
||||||
releaseDirectory: ./signed-apk
|
releaseDirectory: ./signed-apk
|
||||||
@@ -639,7 +641,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./signed-apk/${{ env.filename }}-${{ matrix.job.arch }}.apk" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./signed-apk/${{ env.filename }}-${{ matrix.job.arch }}.apk" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
@@ -650,10 +652,11 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./signed-apk/${{ env.filename }}-${{ matrix.job.arch }}.apk" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./signed-apk/${{ env.filename }}-${{ matrix.job.arch }}.apk" ${{ env.apiServer }}/api/save_custom_client
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
needs: [build-rustdesk-android]
|
needs: [build-rustdesk-android]
|
||||||
|
if: always()
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
@@ -679,6 +682,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
- name: Set rdgen value
|
- name: Set rdgen value
|
||||||
if: ${{ env.rdgen == 'true' }}
|
if: ${{ env.rdgen == 'true' }}
|
||||||
@@ -690,6 +695,30 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "STATUS_URL=${{ env.apiServer }}/api/updategh" >> $GITHUB_ENV
|
echo "STATUS_URL=${{ env.apiServer }}/api/updategh" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Report build status
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_RESULT: ${{ needs.build-rustdesk-android.result }}
|
||||||
|
run: |
|
||||||
|
# Single aggregated callback for the whole matrix: per-leg callbacks
|
||||||
|
# could flip a failed run back to success when legs finish out of order.
|
||||||
|
case "$BUILD_RESULT" in
|
||||||
|
success) STATUS=success ;;
|
||||||
|
cancelled) STATUS=cancelled ;;
|
||||||
|
*) STATUS=failure ;;
|
||||||
|
esac
|
||||||
|
if [ -z "${{ env.uuid }}" ] || [ -z "$STATUS_URL" ]; then
|
||||||
|
echo "::warning::uuid or STATUS_URL missing, skip status callback"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Reporting status=$STATUS for uuid=${{ env.uuid }}"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"uuid\":\"${{ env.uuid }}\",\"status\":\"$STATUS\"}" \
|
||||||
|
"$STATUS_URL" || echo "::warning::status callback failed"
|
||||||
|
|
||||||
- uses: geekyeggo/delete-artifact@v4
|
- uses: geekyeggo/delete-artifact@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
distro: ubuntu18.04,
|
distro: ubuntu18.04,
|
||||||
on: ubuntu-22.04,
|
on: ubuntu-24.04,
|
||||||
deb_arch: amd64,
|
deb_arch: amd64,
|
||||||
vcpkg-triplet: x64-linux,
|
vcpkg-triplet: x64-linux,
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
arch: aarch64,
|
arch: aarch64,
|
||||||
target: aarch64-unknown-linux-gnu,
|
target: aarch64-unknown-linux-gnu,
|
||||||
distro: ubuntu18.04,
|
distro: ubuntu18.04,
|
||||||
on: ubuntu-22.04-arm,
|
on: ubuntu-24.04-arm,
|
||||||
deb_arch: arm64,
|
deb_arch: arm64,
|
||||||
vcpkg-triplet: arm64-linux,
|
vcpkg-triplet: arm64-linux,
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
|
if: matrix.job.arch == 'x86_64' || env.UPLOAD_ARTIFACT == 'true'
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
@@ -402,7 +402,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
arch: ${{ matrix.job.arch }}
|
arch: ${{ matrix.job.arch }}
|
||||||
distro: ${{ matrix.job.distro }}
|
distro: ${{ matrix.job.distro }}
|
||||||
githubToken: ${{ github.token }}
|
# Gitea Actions tokens cannot authenticate against ghcr.io; leave it
|
||||||
|
# empty so run-on-arch pulls the image anonymously.
|
||||||
|
githubToken: ''
|
||||||
setup: |
|
setup: |
|
||||||
ls -l "${PWD}"
|
ls -l "${PWD}"
|
||||||
ls -l /opt/artifacts/vcpkg/installed
|
ls -l /opt/artifacts/vcpkg/installed
|
||||||
@@ -533,7 +535,9 @@ jobs:
|
|||||||
pushd /opt/flutter
|
pushd /opt/flutter
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
# Inside the run-on-arch container the host workspace is mounted
|
||||||
|
# at /workspace, and rdgen itself lives in .rdgen-src.
|
||||||
|
git apply /workspace/.rdgen-src/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -686,10 +690,10 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.deb" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.deb" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.rpm" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.rpm" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-suse-${{ matrix.job.arch }}.rpm" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-suse-${{ matrix.job.arch }}.rpm" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.pkg.tar.zst" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client || true
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.pkg.tar.zst" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client || true
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
@@ -700,10 +704,10 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.deb" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.deb" ${{ env.apiServer }}/api/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.rpm" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.rpm" ${{ env.apiServer }}/api/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-suse-${{ matrix.job.arch }}.rpm" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-suse-${{ matrix.job.arch }}.rpm" ${{ env.apiServer }}/api/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.pkg.tar.zst" ${{ env.apiServer }}/api/save_custom_client || true
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-${{ matrix.job.arch }}.pkg.tar.zst" ${{ env.apiServer }}/api/save_custom_client || true
|
||||||
|
|
||||||
- name: Upload deb
|
- name: Upload deb
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -715,7 +719,7 @@ jobs:
|
|||||||
build-rustdesk-linux-drm:
|
build-rustdesk-linux-drm:
|
||||||
needs: [generate-bridge-linux, setup]
|
needs: [generate-bridge-linux, setup]
|
||||||
name: build rustdesk linux drm x86_64
|
name: build rustdesk linux drm x86_64
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
# Only rustdesk's master branch carries the drm/libdrmtap support this job
|
# Only rustdesk's master branch carries the drm/libdrmtap support this job
|
||||||
# drives: build.py at tag 1.4.9 has neither build_libdrmtap_so nor
|
# drives: build.py at tag 1.4.9 has neither build_libdrmtap_so nor
|
||||||
# assert_staged_binary_is_drm, so a versioned build dies at
|
# assert_staged_binary_is_drm, so a versioned build dies at
|
||||||
@@ -824,7 +828,7 @@ jobs:
|
|||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgDirectory: /opt/artifacts/vcpkg
|
vcpkgDirectory: /opt/artifacts/vcpkg
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
@@ -1072,7 +1076,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
arch: x86_64
|
arch: x86_64
|
||||||
distro: ubuntu18.04
|
distro: ubuntu18.04
|
||||||
githubToken: ${{ github.token }}
|
# Gitea Actions tokens cannot authenticate against ghcr.io; leave it
|
||||||
|
# empty so run-on-arch pulls the image anonymously.
|
||||||
|
githubToken: ''
|
||||||
setup: |
|
setup: |
|
||||||
ls -l "${PWD}"
|
ls -l "${PWD}"
|
||||||
ls -l /opt/artifacts/vcpkg/installed
|
ls -l /opt/artifacts/vcpkg/installed
|
||||||
@@ -1165,7 +1171,9 @@ jobs:
|
|||||||
|
|
||||||
if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then
|
if [[ "3.24.5" == ${{ env.FLUTTER_VERSION }} ]]; then
|
||||||
pushd /opt/flutter
|
pushd /opt/flutter
|
||||||
git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
# Inside the run-on-arch container the host workspace is mounted
|
||||||
|
# at /workspace, and rdgen itself lives in .rdgen-src.
|
||||||
|
git apply /workspace/.rdgen-src/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
popd
|
popd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1237,7 +1245,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-unattended-wayland-x86_64.deb" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-unattended-wayland-x86_64.deb" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' && env.DRM_SUPPORTED == 'true' }}
|
if: ${{ env.rdgen == 'false' && env.DRM_SUPPORTED == 'true' }}
|
||||||
@@ -1248,7 +1256,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-unattended-wayland-x86_64.deb" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./output/${{ env.filename }}-unattended-wayland-x86_64.deb" ${{ env.apiServer }}/api/save_custom_client
|
||||||
|
|
||||||
- name: Upload deb
|
- name: Upload deb
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
@@ -1260,7 +1268,7 @@ jobs:
|
|||||||
build-appimage:
|
build-appimage:
|
||||||
name: Build appimage ${{ matrix.job.target }}
|
name: Build appimage ${{ matrix.job.target }}
|
||||||
needs: [build-rustdesk-linux]
|
needs: [build-rustdesk-linux]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -1388,7 +1396,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./appimage/${{ env.filename }}-${{ matrix.job.arch }}.AppImage" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./appimage/${{ env.filename }}-${{ matrix.job.arch }}.AppImage" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
@@ -1399,7 +1407,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./appimage/${{ env.filename }}-${{ matrix.job.arch }}.AppImage" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./appimage/${{ env.filename }}-${{ matrix.job.arch }}.AppImage" ${{ env.apiServer }}/api/save_custom_client
|
||||||
|
|
||||||
build-flatpak:
|
build-flatpak:
|
||||||
name: Build flatpak ${{ matrix.job.target }}${{ matrix.job.suffix }}
|
name: Build flatpak ${{ matrix.job.target }}${{ matrix.job.suffix }}
|
||||||
@@ -1413,7 +1421,7 @@ jobs:
|
|||||||
- {
|
- {
|
||||||
target: x86_64-unknown-linux-gnu,
|
target: x86_64-unknown-linux-gnu,
|
||||||
distro: ubuntu22.04,
|
distro: ubuntu22.04,
|
||||||
on: ubuntu-22.04,
|
on: ubuntu-24.04,
|
||||||
arch: x86_64,
|
arch: x86_64,
|
||||||
suffix: "",
|
suffix: "",
|
||||||
}
|
}
|
||||||
@@ -1421,7 +1429,7 @@ jobs:
|
|||||||
target: aarch64-unknown-linux-gnu,
|
target: aarch64-unknown-linux-gnu,
|
||||||
# try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub"
|
# try out newer flatpak since error of "error: Nothing matches org.freedesktop.Platform in remote flathub"
|
||||||
distro: ubuntu22.04,
|
distro: ubuntu22.04,
|
||||||
on: ubuntu-22.04-arm,
|
on: ubuntu-24.04-arm,
|
||||||
arch: aarch64,
|
arch: aarch64,
|
||||||
suffix: "",
|
suffix: "",
|
||||||
}
|
}
|
||||||
@@ -1473,7 +1481,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
arch: ${{ matrix.job.arch }}
|
arch: ${{ matrix.job.arch }}
|
||||||
distro: ${{ matrix.job.distro }}
|
distro: ${{ matrix.job.distro }}
|
||||||
githubToken: ${{ github.token }}
|
# Gitea Actions tokens cannot authenticate against ghcr.io; leave it
|
||||||
|
# empty so run-on-arch pulls the image anonymously.
|
||||||
|
githubToken: ''
|
||||||
setup: |
|
setup: |
|
||||||
ls -l "${PWD}"
|
ls -l "${PWD}"
|
||||||
dockerRunArgs: |
|
dockerRunArgs: |
|
||||||
@@ -1529,7 +1539,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./flatpak/${{ env.filename }}-${{ matrix.job.arch }}.flatpak" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./flatpak/${{ env.filename }}-${{ matrix.job.arch }}.flatpak" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
@@ -1540,7 +1550,7 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./flatpak/${{ env.filename }}-${{ matrix.job.arch }}.flatpak" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./flatpak/${{ env.filename }}-${{ matrix.job.arch }}.flatpak" ${{ env.apiServer }}/api/save_custom_client
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
needs: [build-rustdesk-linux,build-flatpak,build-appimage]
|
needs: [build-rustdesk-linux,build-flatpak,build-appimage]
|
||||||
@@ -1570,6 +1580,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
- name: Set rdgen value
|
- name: Set rdgen value
|
||||||
if: ${{ env.rdgen == 'true' }}
|
if: ${{ env.rdgen == 'true' }}
|
||||||
@@ -1581,6 +1593,33 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "STATUS_URL=${{ env.apiServer }}/api/updategh" >> $GITHUB_ENV
|
echo "STATUS_URL=${{ env.apiServer }}/api/updategh" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Report build status
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_RESULTS: "${{ needs.build-rustdesk-linux.result }}/${{ needs.build-flatpak.result }}/${{ needs.build-appimage.result }}"
|
||||||
|
run: |
|
||||||
|
# Aggregate the deb/rpm, flatpak and appimage jobs. The optional drm
|
||||||
|
# job is not a gating artifact and is intentionally excluded.
|
||||||
|
if [ "$BUILD_RESULTS" = "success/success/success" ]; then
|
||||||
|
STATUS=success
|
||||||
|
elif echo "$BUILD_RESULTS" | grep -q "cancelled"; then
|
||||||
|
STATUS=cancelled
|
||||||
|
else
|
||||||
|
# failure / skipped (e.g. setup failed and all builds were skipped)
|
||||||
|
STATUS=failure
|
||||||
|
fi
|
||||||
|
if [ -z "${{ env.uuid }}" ] || [ -z "$STATUS_URL" ]; then
|
||||||
|
echo "::warning::uuid or STATUS_URL missing, skip status callback"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Reporting status=$STATUS ($BUILD_RESULTS) for uuid=${{ env.uuid }}"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"uuid\":\"${{ env.uuid }}\",\"status\":\"$STATUS\"}" \
|
||||||
|
"$STATUS_URL" || echo "::warning::status callback failed"
|
||||||
|
|
||||||
- uses: geekyeggo/delete-artifact@v4
|
- uses: geekyeggo/delete-artifact@v4
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
@@ -294,7 +296,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
cd $(dirname $(dirname $(which flutter)))
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply ${{ github.workspace }}/.rdgen-src/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
- name: Workaround for flutter issue
|
- name: Workaround for flutter issue
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -500,7 +502,7 @@ jobs:
|
|||||||
path: ./
|
path: ./
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
doNotCache: false
|
doNotCache: false
|
||||||
@@ -550,7 +552,7 @@ jobs:
|
|||||||
ls -l "$APP/Contents/MacOS/custom_.txt"
|
ls -l "$APP/Contents/MacOS/custom_.txt"
|
||||||
|
|
||||||
- name: Install rcodesign tool
|
- name: Install rcodesign tool
|
||||||
if: env.MACOS_P12_BASE64 != null
|
if: env.MACOS_P12_BASE64 != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pushd /tmp
|
pushd /tmp
|
||||||
@@ -584,7 +586,11 @@ jobs:
|
|||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ASSETS_DIR="build/macos/Build/Products/Release/RustDesk.app/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets"
|
# build.py renames the bundle to the custom app name: locate *.app
|
||||||
|
# dynamically instead of assuming RustDesk.app still exists.
|
||||||
|
APP=$(find ./flutter/build/macos/Build/Products/Release -maxdepth 1 -name "*.app" | head -n 1)
|
||||||
|
echo "App bundle: $APP"
|
||||||
|
ASSETS_DIR="$APP/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets"
|
||||||
mkdir -p "$ASSETS_DIR"
|
mkdir -p "$ASSETS_DIR"
|
||||||
if [ -f "$ASSETS_DIR/icon.svg" ]; then
|
if [ -f "$ASSETS_DIR/icon.svg" ]; then
|
||||||
mv "$ASSETS_DIR/icon.svg" "$ASSETS_DIR/icon.svg.bak"
|
mv "$ASSETS_DIR/icon.svg" "$ASSETS_DIR/icon.svg.bak"
|
||||||
@@ -600,7 +606,11 @@ jobs:
|
|||||||
continue-on-error: false
|
continue-on-error: false
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ASSETS_DIR="build/macos/Build/Products/Release/RustDesk.app/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets"
|
# build.py renames the bundle to the custom app name: locate *.app
|
||||||
|
# dynamically instead of assuming RustDesk.app still exists.
|
||||||
|
APP=$(find ./flutter/build/macos/Build/Products/Release -maxdepth 1 -name "*.app" | head -n 1)
|
||||||
|
echo "App bundle: $APP"
|
||||||
|
ASSETS_DIR="$APP/Contents/Frameworks/App.framework/Versions/Current/Resources/flutter_assets/assets"
|
||||||
mkdir -p "$ASSETS_DIR"
|
mkdir -p "$ASSETS_DIR"
|
||||||
curl -k -L --tlsv1.2 --proto =https --ssl-reqd \
|
curl -k -L --tlsv1.2 --proto =https --ssl-reqd \
|
||||||
-H "User-Agent: Mozilla/5.0" \
|
-H "User-Agent: Mozilla/5.0" \
|
||||||
@@ -696,7 +706,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create DMG
|
- name: Create DMG
|
||||||
run: |
|
run: |
|
||||||
cd /Users/runner/work/${{ github.event.repository.name }}/${{ github.event.repository.name }}/flutter/build/macos/Build/Products/Release
|
cd $GITHUB_WORKSPACE/flutter/build/macos/Build/Products/Release
|
||||||
# Print directory contents for debugging
|
# Print directory contents for debugging
|
||||||
echo "Directory contents:"
|
echo "Directory contents:"
|
||||||
ls -la
|
ls -la
|
||||||
@@ -745,7 +755,7 @@ jobs:
|
|||||||
if: ${{ env.rdgen == 'true' }}
|
if: ${{ env.rdgen == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -i -X POST \
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-H "Authorization: Bearer ${{ env.token }}" \
|
-H "Authorization: Bearer ${{ env.token }}" \
|
||||||
-F "file=@$GITHUB_WORKSPACE/${{ env.filename }}-${{ matrix.job.arch }}.dmg" \
|
-F "file=@$GITHUB_WORKSPACE/${{ env.filename }}-${{ matrix.job.arch }}.dmg" \
|
||||||
@@ -757,7 +767,7 @@ jobs:
|
|||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -i -X POST \
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 \
|
||||||
-H "Content-Type: multipart/form-data" \
|
-H "Content-Type: multipart/form-data" \
|
||||||
-H "Authorization: Bearer ${{ env.token }}" \
|
-H "Authorization: Bearer ${{ env.token }}" \
|
||||||
-F "file=@$GITHUB_WORKSPACE/${{ env.filename }}-${{ matrix.job.arch }}.dmg" \
|
-F "file=@$GITHUB_WORKSPACE/${{ env.filename }}-${{ matrix.job.arch }}.dmg" \
|
||||||
@@ -769,6 +779,48 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: .rdgen-src
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-secrets-zip
|
||||||
|
|
||||||
|
- name: Load Secrets
|
||||||
|
uses: ./.gitea/actions/decrypt-secrets
|
||||||
|
with:
|
||||||
|
zip_password: ${{ secrets.ZIP_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Report build status
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_RESULT: ${{ needs.build-for-macos.result }}
|
||||||
|
run: |
|
||||||
|
# Single aggregated callback for the x86_64/aarch64 matrix.
|
||||||
|
case "$BUILD_RESULT" in
|
||||||
|
success) STATUS=success ;;
|
||||||
|
cancelled) STATUS=cancelled ;;
|
||||||
|
*) STATUS=failure ;;
|
||||||
|
esac
|
||||||
|
if [ "${{ env.rdgen }}" = "false" ]; then
|
||||||
|
STATUS_URL="${{ env.apiServer }}/api/updategh"
|
||||||
|
else
|
||||||
|
STATUS_URL="${{ secrets.GENURL }}/updategh"
|
||||||
|
fi
|
||||||
|
if [ -z "${{ env.uuid }}" ]; then
|
||||||
|
echo "::warning::uuid missing, skip status callback"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Reporting status=$STATUS for uuid=${{ env.uuid }}"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"uuid\":\"${{ env.uuid }}\",\"status\":\"$STATUS\"}" \
|
||||||
|
"$STATUS_URL" || echo "::warning::status callback failed"
|
||||||
|
|
||||||
- name: Delete secrets artifact
|
- name: Delete secrets artifact
|
||||||
uses: geekyeggo/delete-artifact@v4
|
uses: geekyeggo/delete-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ env:
|
|||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||||
VCPKG_BINARY_SOURCES: "clear;files,D:\vcpkg-cache,readwrite"
|
VCPKG_BINARY_SOURCES: 'clear;files,D:\vcpkg-cache,readwrite'
|
||||||
# vcpkg version: 2025.08.27
|
# vcpkg version: 2025.08.27
|
||||||
# If we change the `VCPKG COMMIT_ID`, please remember:
|
# If we change the `VCPKG COMMIT_ID`, please remember:
|
||||||
# 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`.
|
# 1. Call `$VCPKG_ROOT/vcpkg x-update-baseline` to update the baseline in `vcpkg.json`.
|
||||||
@@ -56,6 +56,11 @@ jobs:
|
|||||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
name: ${{ matrix.job.target }} (${{ matrix.job.os }})
|
||||||
needs: setup
|
needs: setup
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
# Gitea act defaults to bash on Windows hosts; most unmarked steps here are
|
||||||
|
# PowerShell. Requires PowerShell 7 (pwsh) installed on the self-hosted runner.
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: pwsh
|
||||||
# Temporarily disable this action due to additional test is needed.
|
# Temporarily disable this action due to additional test is needed.
|
||||||
# if: false
|
# if: false
|
||||||
strategy:
|
strategy:
|
||||||
@@ -95,6 +100,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
|
|
||||||
- name: Set rdgen value
|
- name: Set rdgen value
|
||||||
@@ -277,7 +284,7 @@ jobs:
|
|||||||
prefix-key: ${{ matrix.job.os }}-sciter
|
prefix-key: ${{ matrix.job.os }}-sciter
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgDirectory: C:\vcpkg
|
vcpkgDirectory: C:\vcpkg
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
@@ -422,9 +429,9 @@ jobs:
|
|||||||
cargo build --target ${{ matrix.job.target }} --features inline,vram,hwcodec --release --bins
|
cargo build --target ${{ matrix.job.target }} --features inline,vram,hwcodec --release --bins
|
||||||
mkdir -p ./Release
|
mkdir -p ./Release
|
||||||
mv ./target/${{ matrix.job.target }}/release/rustdesk.exe ./Release/rustdesk.exe
|
mv ./target/${{ matrix.job.target }}/release/rustdesk.exe ./Release/rustdesk.exe
|
||||||
curl -LJ -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll
|
curl --fail -LJ --connect-timeout 30 --max-time 300 --retry 3 --retry-delay 5 -o ./Release/sciter.dll https://github.com/c-smile/sciter-sdk/raw/master/bin.win/x32/sciter.dll
|
||||||
echo "output_folder=./Release" >> $GITHUB_OUTPUT
|
echo "output_folder=./Release" >> $GITHUB_OUTPUT
|
||||||
curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip
|
curl --fail -LJ --connect-timeout 30 --max-time 300 --retry 3 --retry-delay 5 -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip
|
||||||
unzip usbmmidd_v2.zip
|
unzip usbmmidd_v2.zip
|
||||||
# Do not remove x64 files, because the user may run the 32bit version on a 64bit system.
|
# Do not remove x64 files, because the user may run the 32bit version on a 64bit system.
|
||||||
# Do not remove ./usbmmidd_v2/deviceinstaller64.exe, as x86 exe cannot install and uninstall drivers when running on x64,
|
# Do not remove ./usbmmidd_v2/deviceinstaller64.exe, as x86 exe cannot install and uninstall drivers when running on x64,
|
||||||
@@ -534,13 +541,13 @@ jobs:
|
|||||||
if: ${{ env.rdgen == 'true' }}
|
if: ${{ env.rdgen == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" ${{ env.apiServer }}/api/save_custom_client
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
needs: [build-for-windows-sciter]
|
needs: [build-for-windows-sciter]
|
||||||
@@ -548,6 +555,48 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: .rdgen-src
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-secrets-zip
|
||||||
|
|
||||||
|
- name: Load Secrets
|
||||||
|
uses: ./.gitea/actions/decrypt-secrets
|
||||||
|
with:
|
||||||
|
zip_password: ${{ secrets.ZIP_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Report build status
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_RESULT: ${{ needs.build-for-windows-sciter.result }}
|
||||||
|
run: |
|
||||||
|
# Single aggregated callback (the build job may be skipped/cancelled).
|
||||||
|
case "$BUILD_RESULT" in
|
||||||
|
success) STATUS=success ;;
|
||||||
|
cancelled) STATUS=cancelled ;;
|
||||||
|
*) STATUS=failure ;;
|
||||||
|
esac
|
||||||
|
if [ "${{ env.rdgen }}" = "false" ]; then
|
||||||
|
STATUS_URL="${{ env.apiServer }}/api/updategh"
|
||||||
|
else
|
||||||
|
STATUS_URL="${{ secrets.GENURL }}/updategh"
|
||||||
|
fi
|
||||||
|
if [ -z "${{ env.uuid }}" ]; then
|
||||||
|
echo "::warning::uuid missing, skip status callback"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Reporting status=$STATUS for uuid=${{ env.uuid }}"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"uuid\":\"${{ env.uuid }}\",\"status\":\"$STATUS\"}" \
|
||||||
|
"$STATUS_URL" || echo "::warning::status callback failed"
|
||||||
|
|
||||||
- name: Delete secrets artifact
|
- name: Delete secrets artifact
|
||||||
uses: geekyeggo/delete-artifact@v4
|
uses: geekyeggo/delete-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ env:
|
|||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||||
VCPKG_BINARY_SOURCES: "clear;files,D:\vcpkg-cache,readwrite"
|
VCPKG_BINARY_SOURCES: 'clear;files,D:\vcpkg-cache,readwrite'
|
||||||
# vcpkg version: 2024.07.12
|
# vcpkg version: 2024.07.12
|
||||||
VCPKG_COMMIT_ID: "${{ inputs.version == 'master' && '9e593bb18ea69cc5095e012465dcd675a822ed0d' || '120deac3062162151622ca4860575a33844ba10b' }}"
|
VCPKG_COMMIT_ID: "${{ inputs.version == 'master' && '9e593bb18ea69cc5095e012465dcd675a822ed0d' || '120deac3062162151622ca4860575a33844ba10b' }}"
|
||||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||||
@@ -70,6 +70,11 @@ jobs:
|
|||||||
name: Build Windows
|
name: Build Windows
|
||||||
needs: [build-RustDeskTempTopMostWindow, generate-bridge, setup]
|
needs: [build-RustDeskTempTopMostWindow, generate-bridge, setup]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
# Gitea act defaults to bash on Windows hosts; most unmarked steps here are
|
||||||
|
# PowerShell. Requires PowerShell 7 (pwsh) installed on the self-hosted runner.
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: pwsh
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -107,6 +112,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
|
|
||||||
- name: Set rdgen value
|
- name: Set rdgen value
|
||||||
@@ -385,7 +392,7 @@ jobs:
|
|||||||
- name: Patch flutter
|
- name: Patch flutter
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
cp .github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
|
cp .rdgen-src/.github/patches/flutter_3.24.4_dropdown_menu_enableFilter.diff $(dirname $(dirname $(which flutter)))
|
||||||
cd $(dirname $(dirname $(which flutter)))
|
cd $(dirname $(dirname $(which flutter)))
|
||||||
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
|
[[ "3.24.5" == ${{env.FLUTTER_VERSION}} ]] && git apply flutter_3.24.4_dropdown_menu_enableFilter.diff
|
||||||
|
|
||||||
@@ -401,7 +408,7 @@ jobs:
|
|||||||
prefix-key: ${{ matrix.job.os }}
|
prefix-key: ${{ matrix.job.os }}
|
||||||
|
|
||||||
- name: Setup vcpkg with Github Actions binary cache
|
- name: Setup vcpkg with Github Actions binary cache
|
||||||
uses: lukka/run-vcpkg@v11
|
uses: lukka/run-vcpkg@1737e223b9c3f4fb07a7cf1cfa123cc243fcc0dd # v11 pinned to node20 runtime
|
||||||
with:
|
with:
|
||||||
vcpkgDirectory: C:\vcpkg
|
vcpkgDirectory: C:\vcpkg
|
||||||
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
vcpkgGitCommitId: ${{ env.VCPKG_COMMIT_ID }}
|
||||||
@@ -723,8 +730,8 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.msi" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client || true
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.msi" -F "uuid=${{ env.uuid }}" ${{ secrets.GENURL }}/save_custom_client || true
|
||||||
|
|
||||||
- name: send file to api server
|
- name: send file to api server
|
||||||
if: ${{ env.rdgen == 'false' }}
|
if: ${{ env.rdgen == 'false' }}
|
||||||
@@ -735,8 +742,8 @@ jobs:
|
|||||||
retry_wait_seconds: 15
|
retry_wait_seconds: 15
|
||||||
shell: bash
|
shell: bash
|
||||||
command: |
|
command: |
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" ${{ env.apiServer }}/api/save_custom_client
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.exe" ${{ env.apiServer }}/api/save_custom_client
|
||||||
curl -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.msi" ${{ env.apiServer }}/api/save_custom_client || true
|
curl --fail -i -X POST --connect-timeout 10 --max-time 300 --retry 3 --retry-delay 5 -H "Content-Type: multipart/form-data" -H "Authorization: Bearer ${{ env.token }}" -F "file=@./SignOutput/${{ env.filename }}.msi" ${{ env.apiServer }}/api/save_custom_client || true
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
needs: [build-for-windows-flutter]
|
needs: [build-for-windows-flutter]
|
||||||
@@ -744,6 +751,49 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: .rdgen-src
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-secrets-zip
|
||||||
|
|
||||||
|
- name: Load Secrets
|
||||||
|
uses: ./.gitea/actions/decrypt-secrets
|
||||||
|
with:
|
||||||
|
zip_password: ${{ secrets.ZIP_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Report build status
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
BUILD_RESULT: ${{ needs.build-for-windows-flutter.result }}
|
||||||
|
run: |
|
||||||
|
# Single aggregated callback. needs.result also covers the upstream
|
||||||
|
# reusable jobs: if third-party/bridge/setup fail the build is "skipped".
|
||||||
|
case "$BUILD_RESULT" in
|
||||||
|
success) STATUS=success ;;
|
||||||
|
cancelled) STATUS=cancelled ;;
|
||||||
|
*) STATUS=failure ;;
|
||||||
|
esac
|
||||||
|
if [ "${{ env.rdgen }}" = "false" ]; then
|
||||||
|
STATUS_URL="${{ env.apiServer }}/api/updategh"
|
||||||
|
else
|
||||||
|
STATUS_URL="${{ secrets.GENURL }}/updategh"
|
||||||
|
fi
|
||||||
|
if [ -z "${{ env.uuid }}" ]; then
|
||||||
|
echo "::warning::uuid missing, skip status callback"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Reporting status=$STATUS for uuid=${{ env.uuid }}"
|
||||||
|
curl --fail --silent --show-error \
|
||||||
|
--connect-timeout 10 --max-time 30 --retry 3 --retry-delay 5 \
|
||||||
|
-X POST -H "Content-Type: application/json" \
|
||||||
|
-d "{\"uuid\":\"${{ env.uuid }}\",\"status\":\"$STATUS\"}" \
|
||||||
|
"$STATUS_URL" || echo "::warning::status callback failed"
|
||||||
|
|
||||||
- name: Delete secrets artifact
|
- name: Delete secrets artifact
|
||||||
uses: geekyeggo/delete-artifact@v4
|
uses: geekyeggo/delete-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ env:
|
|||||||
# for arm64 linux because official Dart SDK does not work
|
# for arm64 linux because official Dart SDK does not work
|
||||||
FLUTTER_ELINUX_VERSION: "3.16.9"
|
FLUTTER_ELINUX_VERSION: "3.16.9"
|
||||||
TAG_NAME: "${{ inputs.upload-tag }}"
|
TAG_NAME: "${{ inputs.upload-tag }}"
|
||||||
VCPKG_BINARY_SOURCES: "clear;files,D:\vcpkg-cache,readwrite"
|
VCPKG_BINARY_SOURCES: 'clear;files,D:\vcpkg-cache,readwrite'
|
||||||
# vcpkg version: 2024.07.12
|
# vcpkg version: 2024.07.12
|
||||||
VCPKG_COMMIT_ID: "${{ inputs.version == 'master' && '9e593bb18ea69cc5095e012465dcd675a822ed0d' || '120deac3062162151622ca4860575a33844ba10b' }}"
|
VCPKG_COMMIT_ID: "${{ inputs.version == 'master' && '9e593bb18ea69cc5095e012465dcd675a822ed0d' || '120deac3062162151622ca4860575a33844ba10b' }}"
|
||||||
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
ARMV7_VCPKG_COMMIT_ID: "6f29f12e82a8293156836ad81cc9bf5af41fe836"
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ on:
|
|||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
default: 'Windows10'
|
default: 'Windows10'
|
||||||
|
secrets:
|
||||||
|
ZIP_PASSWORD:
|
||||||
|
description: 'Password for the encrypted secrets zip'
|
||||||
|
required: true
|
||||||
|
GENURL:
|
||||||
|
description: 'rdgen platform base URL'
|
||||||
|
required: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
project_path: WindowInjection/WindowInjection.vcxproj
|
project_path: WindowInjection/WindowInjection.vcxproj
|
||||||
@@ -63,6 +70,8 @@ jobs:
|
|||||||
method: 'POST'
|
method: 'POST'
|
||||||
customHeaders: '{"Content-Type": "application/json"}'
|
customHeaders: '{"Content-Type": "application/json"}'
|
||||||
data: '{"uuid": "${{ env.uuid }}"}'
|
data: '{"uuid": "${{ env.uuid }}"}'
|
||||||
|
timeout: '15000'
|
||||||
|
retry: '3'
|
||||||
|
|
||||||
- name: Download the source code
|
- name: Download the source code
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ urlpatterns = [
|
|||||||
path('', views.generator_view, name='generator'),
|
path('', views.generator_view, name='generator'),
|
||||||
path('my-builds/', web_views.my_builds, name='my_builds'),
|
path('my-builds/', web_views.my_builds, name='my_builds'),
|
||||||
path('my-builds/<int:run_id>/retry/', web_views.retry_build, name='retry_build'),
|
path('my-builds/<int:run_id>/retry/', web_views.retry_build, name='retry_build'),
|
||||||
|
path('my-builds/<int:run_id>/stop/', web_views.stop_build, name='stop_build'),
|
||||||
path('my-builds/<int:run_id>/delete/', web_views.delete_my_build, name='delete_my_build'),
|
path('my-builds/<int:run_id>/delete/', web_views.delete_my_build, name='delete_my_build'),
|
||||||
path('configs/', web_views.saved_configs, name='saved_configs'),
|
path('configs/', web_views.saved_configs, name='saved_configs'),
|
||||||
path('configs/save/', web_views.save_config, name='save_config'),
|
path('configs/save/', web_views.save_config, name='save_config'),
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ class DispatchResult:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RunInfo:
|
class RunInfo:
|
||||||
finished: bool # 是否已进入终态
|
"""平台单次运行的状态快照。"""
|
||||||
status: str = '' # 终态结论:success/failure/cancelled/skipped;未结束为进行中状态
|
finished: bool = False # 是否已结束(拿到终态结论)
|
||||||
html_url: str = ''
|
status: str = '' # finished=True 时为 rdgen 内部终态码
|
||||||
|
html_url: str = '' # 平台上的运行详情页地址
|
||||||
|
|
||||||
|
|
||||||
# rdgen 目标平台 -> 工作流文件名(两平台同名)
|
# rdgen 目标平台 -> 工作流文件名(两平台同名)
|
||||||
@@ -57,13 +58,14 @@ class BuildBackend:
|
|||||||
"""触发工作流。inputs 为完整输入字典(GitHub/Gitea 均为 {ref, inputs})。"""
|
"""触发工作流。inputs 为完整输入字典(GitHub/Gitea 均为 {ref, inputs})。"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_run(self, run_id, *, timeout=12):
|
|
||||||
"""查询单次运行;网络异常或非 200 返回 None(调用方按「仍在进行」处理)。"""
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def log_url(self, run_id) -> str:
|
def log_url(self, run_id) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_run(self, run_id, *, timeout=8):
|
||||||
|
"""只读查询单次运行:已结束返回 RunInfo(finished=True, status=内部终态码);
|
||||||
|
仍在途、查询失败或不支持时返回 None(调用方按「仍在进行」处理)。"""
|
||||||
|
return None
|
||||||
|
|
||||||
# ---- 后台连接测试 ----
|
# ---- 后台连接测试 ----
|
||||||
def test_connection(self) -> dict:
|
def test_connection(self) -> dict:
|
||||||
"""返回 {'ok': bool, 'message': 中文说明},不抛异常。"""
|
"""返回 {'ok': bool, 'message': 中文说明},不抛异常。"""
|
||||||
|
|||||||
@@ -4,22 +4,26 @@ API 与 GitHub Actions 几乎同形(已对照 Gitea 官方 Swagger 核实)
|
|||||||
- 触发:POST /api/v1/repos/{owner}/{repo}/actions/workflows/{file}/dispatches
|
- 触发:POST /api/v1/repos/{owner}/{repo}/actions/workflows/{file}/dispatches
|
||||||
?return_run_details=true,body {ref, inputs},200 返回
|
?return_run_details=true,body {ref, inputs},200 返回
|
||||||
{workflow_run_id, html_url, run_url},不传该参数为 204;
|
{workflow_run_id, html_url, run_url},不传该参数为 204;
|
||||||
- 状态:GET /api/v1/repos/{owner}/{repo}/actions/runs/{id},
|
|
||||||
status/conclusion 字段仿 GitHub;
|
|
||||||
- 鉴权:Authorization: token <PAT>。
|
- 鉴权:Authorization: token <PAT>。
|
||||||
|
|
||||||
|
注意:本平台不主动取消 Gitea 上的运行,停止/删除只作用于本平台本地记录。
|
||||||
|
构建状态以工作流结束时的回调(update_github_run)为第一来源;get_run 仅做
|
||||||
|
只读终态核对,兜底回调丢失(脚本未跑到收尾或 GENURL 不可达)的情况。
|
||||||
"""
|
"""
|
||||||
from .. import app_settings, ghnet
|
from .. import app_settings, ghnet
|
||||||
from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend
|
from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend
|
||||||
|
|
||||||
# Gitea 未结束状态(blocked=等待审批,waiting=等待 runner)
|
# 部分 Gitea 版本 run.status 直接返回终态(无 completed+conclusion 包裹)
|
||||||
_GITEA_ACTIVE = {'queued', 'in_progress', 'waiting', 'blocked', 'requested', 'pending'}
|
_GITEA_TERMINAL = {'success', 'failure', 'cancelled', 'skipped', 'timed_out', 'stopped', 'dead'}
|
||||||
# Gitea 结论 -> rdgen 内部状态
|
# Gitea 结论 -> rdgen 内部终态
|
||||||
_CONCLUSION_MAP = {
|
_GITEA_CONCLUSION_MAP = {
|
||||||
'success': 'success',
|
'success': 'success',
|
||||||
'failure': 'failure',
|
'failure': 'failure',
|
||||||
'cancelled': 'cancelled',
|
'cancelled': 'cancelled',
|
||||||
'skipped': 'skipped',
|
'skipped': 'skipped',
|
||||||
'timed_out': 'timed_out',
|
'timed_out': 'timed_out',
|
||||||
|
'stopped': 'cancelled',
|
||||||
|
'dead': 'failure',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -140,8 +144,8 @@ class GiteaBackend(BuildBackend):
|
|||||||
print(f'查询 Gitea 最新运行失败:{e}')
|
print(f'查询 Gitea 最新运行失败:{e}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# ---- 状态查询 ----
|
# ---- 只读状态查询(回调丢失时的终态兜底) ----
|
||||||
def get_run(self, run_id, *, timeout=12):
|
def get_run(self, run_id, *, timeout=8):
|
||||||
url = self._api(f'/repos/{self._owner()}/{self._repo()}/actions/runs/{run_id}')
|
url = self._api(f'/repos/{self._owner()}/{self._repo()}/actions/runs/{run_id}')
|
||||||
try:
|
try:
|
||||||
resp = ghnet.get(
|
resp = ghnet.get(
|
||||||
@@ -153,18 +157,28 @@ class GiteaBackend(BuildBackend):
|
|||||||
return None
|
return None
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
html_url = data.get('html_url') or self.log_url(run_id)
|
html_url = data.get('html_url') or self.log_url(run_id)
|
||||||
status = (data.get('status') or '').lower()
|
status = (data.get('status') or '').lower()
|
||||||
conclusion = (data.get('conclusion') or '').lower()
|
conclusion = (data.get('conclusion') or '').lower()
|
||||||
if status == 'completed':
|
if status == 'completed':
|
||||||
return RunInfo(
|
return RunInfo(
|
||||||
finished=True,
|
finished=True,
|
||||||
status=_CONCLUSION_MAP.get(conclusion, 'failure'),
|
status=_GITEA_CONCLUSION_MAP.get(conclusion, 'failure'),
|
||||||
html_url=html_url,
|
html_url=html_url,
|
||||||
)
|
)
|
||||||
# blocked(等待审批)等也算进行中
|
# 部分 Gitea 版本直接在 status 返回终态(无 completed 包裹)
|
||||||
return RunInfo(finished=False, status=status or 'in_progress', html_url=html_url)
|
if status in _GITEA_TERMINAL:
|
||||||
|
return RunInfo(
|
||||||
|
finished=True,
|
||||||
|
status=_GITEA_CONCLUSION_MAP.get(status, 'failure'),
|
||||||
|
html_url=html_url,
|
||||||
|
)
|
||||||
|
# queued/in_progress/waiting/blocked 等在途状态返回 None,交给回调与下次核对
|
||||||
|
return None
|
||||||
|
|
||||||
# ---- 日志链接 ----
|
# ---- 日志链接 ----
|
||||||
def log_url(self, run_id):
|
def log_url(self, run_id):
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
"""GitHub Actions 构建后端(迁自 views.py / web_views.py 的原有逻辑,行为保持一致)。"""
|
"""GitHub Actions 构建后端(迁自 views.py / web_views.py 的原有逻辑,行为保持一致)。
|
||||||
|
|
||||||
|
注意:本平台不主动取消 GitHub 上的运行,停止/删除只作用于本平台本地记录。
|
||||||
|
构建状态以工作流结束时的回调(update_github_run)为第一来源;get_run 仅做
|
||||||
|
只读终态核对,兜底回调丢失(脚本未跑到收尾或 GENURL 不可达)的情况。
|
||||||
|
"""
|
||||||
from .. import app_settings, ghnet
|
from .. import app_settings, ghnet
|
||||||
from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend
|
from .base import BackendError, DispatchResult, RunInfo, WORKFLOW_FILES, BuildBackend
|
||||||
|
|
||||||
API = 'https://api.github.com'
|
API = 'https://api.github.com'
|
||||||
|
|
||||||
# 未进入 completed 的状态一律视为进行中
|
# GitHub conclusion -> rdgen 内部终态;无人值守构建不会有人处理审批,
|
||||||
_GITHUB_ACTIVE = {'queued', 'in_progress', 'waiting', 'requested', 'pending'}
|
# action_required/neutral/stale/startup_failure 等异常结论一律按失败呈现
|
||||||
|
_GH_CONCLUSION_MAP = {
|
||||||
|
'success': 'success',
|
||||||
|
'failure': 'failure',
|
||||||
|
'cancelled': 'cancelled',
|
||||||
|
'skipped': 'skipped',
|
||||||
|
'timed_out': 'timed_out',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class GitHubBackend(BuildBackend):
|
class GitHubBackend(BuildBackend):
|
||||||
@@ -56,8 +68,8 @@ class GitHubBackend(BuildBackend):
|
|||||||
pass
|
pass
|
||||||
return DispatchResult(run_id=data.get('workflow_run_id'), html_url=data.get('html_url') or '')
|
return DispatchResult(run_id=data.get('workflow_run_id'), html_url=data.get('html_url') or '')
|
||||||
|
|
||||||
# ---- 状态查询 ----
|
# ---- 只读状态查询(回调丢失时的终态兜底) ----
|
||||||
def get_run(self, run_id, *, timeout=12):
|
def get_run(self, run_id, *, timeout=8):
|
||||||
user = app_settings.get_value('GHUSER')
|
user = app_settings.get_value('GHUSER')
|
||||||
repo = app_settings.get_value('REPONAME')
|
repo = app_settings.get_value('REPONAME')
|
||||||
url = f'{API}/repos/{user}/{repo}/actions/runs/{run_id}'
|
url = f'{API}/repos/{user}/{repo}/actions/runs/{run_id}'
|
||||||
@@ -68,12 +80,19 @@ class GitHubBackend(BuildBackend):
|
|||||||
return None
|
return None
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
return None
|
return None
|
||||||
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
if data.get('status') == 'completed':
|
except ValueError:
|
||||||
return RunInfo(finished=True, status=data.get('conclusion') or 'failure',
|
return None
|
||||||
html_url=data.get('html_url') or self.log_url(run_id))
|
# 只关心终态:在途(queued/in_progress/waiting)返回 None,交给回调与下次核对
|
||||||
return RunInfo(finished=False, status=data.get('status') or 'in_progress',
|
if data.get('status') != 'completed':
|
||||||
html_url=data.get('html_url') or self.log_url(run_id))
|
return None
|
||||||
|
conclusion = (data.get('conclusion') or 'failure').lower()
|
||||||
|
return RunInfo(
|
||||||
|
finished=True,
|
||||||
|
status=_GH_CONCLUSION_MAP.get(conclusion, 'failure'),
|
||||||
|
html_url=data.get('html_url') or self.log_url(run_id),
|
||||||
|
)
|
||||||
|
|
||||||
# ---- 日志链接 ----
|
# ---- 日志链接 ----
|
||||||
def log_url(self, run_id):
|
def log_url(self, run_id):
|
||||||
|
|||||||
@@ -134,6 +134,26 @@ a:hover { text-decoration: underline; }
|
|||||||
}
|
}
|
||||||
.btn-logout:hover { border-color: var(--danger); color: var(--danger); }
|
.btn-logout:hover { border-color: var(--danger); color: var(--danger); }
|
||||||
|
|
||||||
|
/* ===== 顶部导航移动端适配:窄屏上下两行,导航单行横向滑动不折行 ===== */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.topbar-inner {
|
||||||
|
height: auto;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
}
|
||||||
|
.topbar nav {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scrollbar-width: none;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
.topbar nav::-webkit-scrollbar { display: none; }
|
||||||
|
.nav-link, .nav-user, .btn-logout { flex: none; }
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== 页面容器与标题 ===== */
|
/* ===== 页面容器与标题 ===== */
|
||||||
.page {
|
.page {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -367,6 +387,8 @@ input[type="file"].form-control::-webkit-file-upload-button {
|
|||||||
.btn-secondary:hover { border-color: var(--primary-border); color: var(--primary); }
|
.btn-secondary:hover { border-color: var(--primary-border); color: var(--primary); }
|
||||||
.btn-danger { background: #fff; color: var(--danger); border-color: #f3c2c2; }
|
.btn-danger { background: #fff; color: var(--danger); border-color: #f3c2c2; }
|
||||||
.btn-danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
|
.btn-danger:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
|
||||||
|
.btn-warning { background: #fff; color: var(--warning); border-color: #f3d9a6; }
|
||||||
|
.btn-warning:hover { background: var(--warning); color: #fff; border-color: var(--warning); }
|
||||||
.btn-sm { min-height: 30px; padding: 0 12px; font-size: 12.5px; border-radius: 6px; }
|
.btn-sm { min-height: 30px; padding: 0 12px; font-size: 12.5px; border-radius: 6px; }
|
||||||
.btn-sm .icon { font-size: 13px; }
|
.btn-sm .icon { font-size: 13px; }
|
||||||
.btn-block { width: 100%; }
|
.btn-block { width: 100%; }
|
||||||
@@ -374,6 +396,14 @@ input[type="file"].form-control::-webkit-file-upload-button {
|
|||||||
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||||
.inline-form { display: inline-flex; vertical-align: middle; }
|
.inline-form { display: inline-flex; vertical-align: middle; }
|
||||||
.table .btn + .btn, .table .btn + .inline-form { margin-left: 8px; }
|
.table .btn + .btn, .table .btn + .inline-form { margin-left: 8px; }
|
||||||
|
/* 表格操作列:按钮数量随任务状态变化,用单一 flex 容器统一间距与换行,
|
||||||
|
避免各行 2~4 个按钮时排列参差 */
|
||||||
|
.col-actions { min-width: 168px; }
|
||||||
|
.table .row-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
||||||
|
.table .row-actions > .btn,
|
||||||
|
.table .row-actions > .inline-form { margin-left: 0; }
|
||||||
|
.table .row-actions .inline-form { margin: 0; }
|
||||||
|
.table .row-actions .btn { white-space: nowrap; }
|
||||||
|
|
||||||
/* ===== 平台选择卡 ===== */
|
/* ===== 平台选择卡 ===== */
|
||||||
.platform-grid {
|
.platform-grid {
|
||||||
|
|||||||
@@ -76,6 +76,9 @@
|
|||||||
<symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
<symbol id="i-trash" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>
|
<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="i-stop" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<rect x="6" y="6" width="12" height="12" rx="2"/>
|
||||||
|
</symbol>
|
||||||
<symbol id="i-check-circle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
<symbol id="i-check-circle" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="12" cy="12" r="10"/><path d="m8.5 12 2.5 2.5 5-5"/>
|
<circle cx="12" cy="12" r="10"/><path d="m8.5 12 2.5 2.5 5-5"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
@@ -108,6 +111,9 @@
|
|||||||
<circle cx="12" cy="12" r="9"/>
|
<circle cx="12" cy="12" r="9"/>
|
||||||
<path d="M12 7v5l3 2"/>
|
<path d="M12 7v5l3 2"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
<symbol id="i-eye" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>
|
||||||
|
</symbol>
|
||||||
<symbol id="i-database" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
<symbol id="i-database" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<ellipse cx="12" cy="5" rx="8" ry="3"/>
|
<ellipse cx="12" cy="5" rx="8" ry="3"/>
|
||||||
<path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/>
|
<path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5"/>
|
||||||
|
|||||||
@@ -49,7 +49,8 @@
|
|||||||
<td>{{ run.platform_label }}</td>
|
<td>{{ run.platform_label }}</td>
|
||||||
<td><span class="badge {{ run.status_badge }} {% if not run.is_finished %}badge-dot{% endif %}">{{ run.status_label }}</span></td>
|
<td><span class="badge {{ run.status_badge }} {% if not run.is_finished %}badge-dot{% endif %}">{{ run.status_label }}</span></td>
|
||||||
<td class="mono text-muted">{{ run.short_uuid }}</td>
|
<td class="mono text-muted">{{ run.short_uuid }}</td>
|
||||||
<td style="white-space:nowrap">
|
<td class="col-actions">
|
||||||
|
<div class="row-actions">
|
||||||
<a class="btn btn-secondary btn-sm"
|
<a class="btn btn-secondary btn-sm"
|
||||||
href="/check_for_file?filename={{ run.filename|urlencode }}&uuid={{ run.uuid|urlencode }}&platform={{ run.platform|urlencode }}">查看</a>
|
href="/check_for_file?filename={{ run.filename|urlencode }}&uuid={{ run.uuid|urlencode }}&platform={{ run.platform|urlencode }}">查看</a>
|
||||||
<form method="post" class="inline-form" action="{% url 'delete_build' run.id %}"
|
<form method="post" class="inline-form" action="{% url 'delete_build' run.id %}"
|
||||||
@@ -59,6 +60,7 @@
|
|||||||
<svg class="icon"><use href="#i-trash"/></svg> 删除
|
<svg class="icon"><use href="#i-trash"/></svg> 删除
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -32,17 +32,18 @@
|
|||||||
<td><span class="badge {{ run.backend_badge_class }}">{{ run.backend_label }}</span></td>
|
<td><span class="badge {{ run.backend_badge_class }}">{{ run.backend_label }}</span></td>
|
||||||
<td><span class="badge {{ run.status_badge }} {% if not run.is_finished %}badge-dot{% endif %}">{{ run.status_label }}</span></td>
|
<td><span class="badge {{ run.status_badge }} {% if not run.is_finished %}badge-dot{% endif %}">{{ run.status_label }}</span></td>
|
||||||
<td class="mono text-muted">{{ run.short_uuid }}</td>
|
<td class="mono text-muted">{{ run.short_uuid }}</td>
|
||||||
<td style="white-space:nowrap">
|
<td class="col-actions">
|
||||||
|
<div class="row-actions">
|
||||||
<a class="btn btn-secondary btn-sm"
|
<a class="btn btn-secondary btn-sm"
|
||||||
href="/check_for_file?filename={{ run.filename|urlencode }}&uuid={{ run.uuid|urlencode }}&platform={{ run.platform|urlencode }}">
|
href="/check_for_file?filename={{ run.filename|urlencode }}&uuid={{ run.uuid|urlencode }}&platform={{ run.platform|urlencode }}">
|
||||||
{% if run.status == 'success' %}<svg class="icon"><use href="#i-download"/></svg> 下载产物{% elif run.is_finished %}查看详情{% else %}查看进度{% endif %}
|
{% if run.status == 'success' %}<svg class="icon"><use href="#i-download"/></svg> 下载产物{% elif run.is_finished %}<svg class="icon"><use href="#i-eye"/></svg> 查看详情{% else %}<svg class="icon"><use href="#i-eye"/></svg> 查看进度{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% if run.github_log_url %}
|
{% if run.github_log_url %}
|
||||||
<a class="btn btn-secondary btn-sm" href="{{ run.github_log_url }}" target="_blank" rel="noopener">
|
<a class="btn btn-secondary btn-sm" href="{{ run.github_log_url }}" target="_blank" rel="noopener">
|
||||||
<svg class="icon"><use href="#i-external"/></svg> 日志
|
<svg class="icon"><use href="#i-external"/></svg> 日志
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if run.is_finished and run.config_data %}
|
{% if run.is_finished and run.status != 'success' and run.config_data %}
|
||||||
<form method="post" class="inline-form" action="{% url 'retry_build' run.id %}"
|
<form method="post" class="inline-form" action="{% url 'retry_build' run.id %}"
|
||||||
onsubmit="return confirm('将使用该任务的原始配置重新提交一次构建(原任务记录保留),是否继续?');">
|
onsubmit="return confirm('将使用该任务的原始配置重新提交一次构建(原任务记录保留),是否继续?');">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@@ -51,6 +52,15 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if not run.is_finished %}
|
||||||
|
<form method="post" class="inline-form" action="{% url 'stop_build' run.id %}"
|
||||||
|
onsubmit="return confirm('确定停止构建任务「{{ run.filename|default:run.short_uuid|escapejs }}」吗?停止后可在列表中使用「重试」重新发起。');">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-warning btn-sm">
|
||||||
|
<svg class="icon"><use href="#i-stop"/></svg> 停止
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
{% if run.is_finished %}
|
{% if run.is_finished %}
|
||||||
<form method="post" class="inline-form" action="{% url 'delete_my_build' run.id %}"
|
<form method="post" class="inline-form" action="{% url 'delete_my_build' run.id %}"
|
||||||
onsubmit="return confirm('确定删除任务「{{ run.filename|default:run.short_uuid|escapejs }}」吗?将同时删除服务器上的安装包文件,且不可恢复。');">
|
onsubmit="return confirm('确定删除任务「{{ run.filename|default:run.short_uuid|escapejs }}」吗?将同时删除服务器上的安装包文件,且不可恢复。');">
|
||||||
@@ -60,6 +70,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
+123
-8
@@ -2,7 +2,10 @@ import io
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
@@ -22,12 +25,128 @@ from django.http import (
|
|||||||
Http404,
|
Http404,
|
||||||
)
|
)
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.utils import timezone
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
from . import app_settings, build_backends, versions as rdeskVersions
|
from . import app_settings, build_backends, versions as rdeskVersions
|
||||||
from .forms import GenerateForm
|
from .forms import GenerateForm
|
||||||
from .models import GithubRun, STATUS_LABELS, STATUS_BADGE
|
from .models import GithubRun, STATUS_LABELS, STATUS_BADGE
|
||||||
|
|
||||||
|
# 终态状态集合
|
||||||
|
TERMINAL_STATUSES = ('success', 'failure', 'cancelled', 'timed_out', 'skipped')
|
||||||
|
# 正常构建约 30~45 分钟;超过该时长仍在进行中的,视为 runner 离线/作业僵死。
|
||||||
|
# 状态以构建脚本结束回调为第一来源;回调丢失时由 get_run 只读核对平台终态兜底,
|
||||||
|
# 超时判断只做本地兜底。
|
||||||
|
STALE_BUILD_HOURS = 6
|
||||||
|
# 主动核对节流:同一在途任务两次平台查询的最小间隔,避免轮询/刷页面打满 API
|
||||||
|
REFRESH_THROTTLE_SECONDS = 20
|
||||||
|
# 平台查询短超时,平台抖动时等待页轮询最多多等这几秒,不拖垮页面
|
||||||
|
REFRESH_TIMEOUT = 6
|
||||||
|
_refresh_stamps = {}
|
||||||
|
_refresh_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def _claim_refresh_slot(run_pk):
|
||||||
|
"""节流:到间隔才占位成功;先占位再请求,避免并发页面/线程重复打平台。"""
|
||||||
|
now = time.monotonic()
|
||||||
|
with _refresh_lock:
|
||||||
|
if now - _refresh_stamps.get(run_pk, 0) >= REFRESH_THROTTLE_SECONDS:
|
||||||
|
_refresh_stamps[run_pk] = now
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_remote_terminal(gh_run, info):
|
||||||
|
"""平台明确终态且本地仍在途时落库(条件更新,避免覆盖刚到达的回调)。返回是否变化。"""
|
||||||
|
if info is None or not info.finished:
|
||||||
|
return False
|
||||||
|
new_status = info.status
|
||||||
|
if new_status not in TERMINAL_STATUSES or new_status == gh_run.status:
|
||||||
|
return False
|
||||||
|
updated = GithubRun.objects.filter(pk=gh_run.pk) \
|
||||||
|
.exclude(status__in=TERMINAL_STATUSES) \
|
||||||
|
.update(status=new_status, updated_at=timezone.now())
|
||||||
|
if updated:
|
||||||
|
gh_run.status = new_status
|
||||||
|
return bool(updated)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_active_run(gh_run):
|
||||||
|
"""同步向构建平台核对单个在途任务(等待页 30 秒轮询 / 状态接口用)。
|
||||||
|
|
||||||
|
20 秒节流、6 秒超时;任何网络异常静默。仅当平台报告终态时落库,
|
||||||
|
工作流结束回调仍是第一状态来源,本核对只兜底回调丢失。
|
||||||
|
"""
|
||||||
|
if gh_run.is_finished() or not gh_run.github_run_id:
|
||||||
|
return False
|
||||||
|
if not _claim_refresh_slot(gh_run.pk):
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
backend = build_backends.get_backend(gh_run.backend or 'github')
|
||||||
|
info = backend.get_run(gh_run.github_run_id, timeout=REFRESH_TIMEOUT)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"核对构建状态出错:{e}")
|
||||||
|
return False
|
||||||
|
return _apply_remote_terminal(gh_run, info)
|
||||||
|
|
||||||
|
|
||||||
|
def _refresh_runs_in_background(run_pks):
|
||||||
|
"""后台线程体:逐个核对终态;与请求/渲染完全解耦,异常不影响任何页面。"""
|
||||||
|
try:
|
||||||
|
for pk in run_pks:
|
||||||
|
gh_run = GithubRun.objects.filter(pk=pk).first()
|
||||||
|
if gh_run is None or gh_run.is_finished() or not gh_run.github_run_id:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
backend = build_backends.get_backend(gh_run.backend or 'github')
|
||||||
|
info = backend.get_run(gh_run.github_run_id, timeout=REFRESH_TIMEOUT)
|
||||||
|
_apply_remote_terminal(gh_run, info)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"后台核对构建状态出错:{e}")
|
||||||
|
finally:
|
||||||
|
# 子线程使用独立数据库连接,结束时必须关闭
|
||||||
|
from django.db import connections
|
||||||
|
connections.close_all()
|
||||||
|
|
||||||
|
|
||||||
|
def schedule_refresh_for_runs(gh_runs):
|
||||||
|
"""列表/仪表盘用:为在途任务安排后台核对,页面渲染零等待。
|
||||||
|
|
||||||
|
与等待页共用 20 秒节流;无在途任务或刚核对过时不创建线程。
|
||||||
|
"""
|
||||||
|
due_pks = [
|
||||||
|
r.pk for r in gh_runs
|
||||||
|
if not r.is_finished() and r.github_run_id and _claim_refresh_slot(r.pk)
|
||||||
|
]
|
||||||
|
if not due_pks:
|
||||||
|
return
|
||||||
|
threading.Thread(
|
||||||
|
target=_refresh_runs_in_background, args=(due_pks,), daemon=True).start()
|
||||||
|
|
||||||
|
|
||||||
|
def mark_stale_builds(gh_runs=None):
|
||||||
|
"""纯本地兜底:在途任务超过 STALE_BUILD_HOURS 未收到结束回调的,标记为超时。
|
||||||
|
|
||||||
|
不产生任何网络请求。传入任务列表时只处理这些任务;不传则扫描全表。
|
||||||
|
"""
|
||||||
|
cutoff = timezone.now() - timedelta(hours=STALE_BUILD_HOURS)
|
||||||
|
qs = GithubRun.objects.exclude(status__in=TERMINAL_STATUSES)
|
||||||
|
if gh_runs is not None:
|
||||||
|
ids = [r.pk for r in gh_runs if not r.is_finished()]
|
||||||
|
qs = qs.filter(pk__in=ids)
|
||||||
|
qs.filter(created_at__lt=cutoff).update(
|
||||||
|
status='timed_out', updated_at=timezone.now())
|
||||||
|
|
||||||
|
|
||||||
|
def mark_run_stale_if_needed(gh_run):
|
||||||
|
"""单个任务的纯本地超时兜底(详情/状态接口用)。"""
|
||||||
|
if not gh_run.is_finished() and gh_run.created_at < \
|
||||||
|
timezone.now() - timedelta(hours=STALE_BUILD_HOURS):
|
||||||
|
gh_run.status = 'timed_out'
|
||||||
|
gh_run.save(update_fields=['status', 'updated_at'])
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _webhook_denied(request):
|
def _webhook_denied(request):
|
||||||
"""配置了 WEBHOOK_SECRET 时,校验 GitHub Actions 回调请求头;未配置则放行(兼容旧工作流)。"""
|
"""配置了 WEBHOOK_SECRET 时,校验 GitHub Actions 回调请求头;未配置则放行(兼容旧工作流)。"""
|
||||||
@@ -390,14 +509,10 @@ def _get_run_status(uuid_val):
|
|||||||
backend = build_backends.get_backend(gh_run.backend or 'github')
|
backend = build_backends.get_backend(gh_run.backend or 'github')
|
||||||
github_log_url = backend.log_url(gh_run.github_run_id) if gh_run.github_run_id else ''
|
github_log_url = backend.log_url(gh_run.github_run_id) if gh_run.github_run_id else ''
|
||||||
|
|
||||||
if gh_run.status not in ['success', 'failure', 'cancelled', 'timed_out', 'skipped'] and gh_run.github_run_id:
|
# 状态以构建脚本结束回调为准;此处先做平台终态只读核对(20 秒节流、6 秒超时,
|
||||||
try:
|
# 回调丢失时能及时发现平台侧已失败/取消),再做纯本地的 6 小时超时兜底
|
||||||
info = backend.get_run(gh_run.github_run_id)
|
refresh_active_run(gh_run)
|
||||||
if info is not None and info.finished:
|
mark_run_stale_if_needed(gh_run)
|
||||||
gh_run.status = info.status
|
|
||||||
gh_run.save()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"查询构建状态出错:{e}")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"found": True,
|
"found": True,
|
||||||
|
|||||||
@@ -31,7 +31,11 @@ def staff_required(view_func):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def my_builds(request):
|
def my_builds(request):
|
||||||
builds = GithubRun.objects.filter(created_by=request.user)
|
builds = list(GithubRun.objects.filter(created_by=request.user))
|
||||||
|
# 回调为第一状态来源;本地 6 小时超时兜底 + 后台线程只读核对平台终态(不阻塞页面)
|
||||||
|
from .views import mark_stale_builds, schedule_refresh_for_runs
|
||||||
|
mark_stale_builds(builds)
|
||||||
|
schedule_refresh_for_runs(builds)
|
||||||
return render(request, 'my_builds.html', {'builds': builds})
|
return render(request, 'my_builds.html', {'builds': builds})
|
||||||
|
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ def delete_my_build(request, run_id):
|
|||||||
"""用户删除自己的已结束任务;进行中的任务不允许删除(避免与 GitHub 回调竞态)。"""
|
"""用户删除自己的已结束任务;进行中的任务不允许删除(避免与 GitHub 回调竞态)。"""
|
||||||
run = get_object_or_404(GithubRun, pk=run_id, created_by=request.user)
|
run = get_object_or_404(GithubRun, pk=run_id, created_by=request.user)
|
||||||
if not run.is_finished():
|
if not run.is_finished():
|
||||||
messages.error(request, "该任务仍在进行中,请等待结束(或先在 GitHub Actions 中取消)后再删除。")
|
messages.error(request, "该任务仍在进行中,请先点「停止」取消构建(或等待结束)后再删除。")
|
||||||
else:
|
else:
|
||||||
_remove_run_artifacts(run)
|
_remove_run_artifacts(run)
|
||||||
run.delete()
|
run.delete()
|
||||||
@@ -56,6 +60,30 @@ def delete_my_build(request, run_id):
|
|||||||
return redirect('my_builds')
|
return redirect('my_builds')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@require_POST
|
||||||
|
def stop_build(request, run_id):
|
||||||
|
"""停止构建:仅在本平台把任务标记为已取消,不调用 GitHub/Gitea 的远程取消接口。
|
||||||
|
|
||||||
|
构建平台上的流水线可能仍在继续运行,需要真正停止时请到对应平台的运行页手动 Cancel。
|
||||||
|
"""
|
||||||
|
run = get_object_or_404(GithubRun, pk=run_id, created_by=request.user)
|
||||||
|
if run.is_finished():
|
||||||
|
messages.info(request, "该任务已经结束,无需停止。")
|
||||||
|
else:
|
||||||
|
run.status = 'cancelled'
|
||||||
|
run.save(update_fields=['status', 'updated_at'])
|
||||||
|
tip = "已在本平台停止构建任务"
|
||||||
|
if run.github_run_id:
|
||||||
|
from . import build_backends
|
||||||
|
backend = build_backends.get_backend(run.backend or 'github')
|
||||||
|
tip += f"(仅本地标记;{run.backend_label}上的流水线若仍在运行,请前往运行页手动 Cancel:{backend.log_url(run.github_run_id)})"
|
||||||
|
else:
|
||||||
|
tip += "(仅本地标记)"
|
||||||
|
messages.success(request, tip)
|
||||||
|
return redirect('my_builds')
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@require_POST
|
@require_POST
|
||||||
def retry_build(request, run_id):
|
def retry_build(request, run_id):
|
||||||
@@ -66,6 +94,9 @@ def retry_build(request, run_id):
|
|||||||
if not run.is_finished():
|
if not run.is_finished():
|
||||||
messages.error(request, "该任务尚未结束,无需重试。")
|
messages.error(request, "该任务尚未结束,无需重试。")
|
||||||
return redirect('my_builds')
|
return redirect('my_builds')
|
||||||
|
if run.status == 'success':
|
||||||
|
messages.info(request, "该任务已构建成功,请直接下载产物;如需重新生成请重新提交配置。")
|
||||||
|
return redirect('my_builds')
|
||||||
if not run.config_data:
|
if not run.config_data:
|
||||||
messages.error(request, "该任务提交时未保存配置快照(较早的历史任务),无法一键重试,请重新填写配置后提交。")
|
messages.error(request, "该任务提交时未保存配置快照(较早的历史任务),无法一键重试,请重新填写配置后提交。")
|
||||||
return redirect('my_builds')
|
return redirect('my_builds')
|
||||||
@@ -118,7 +149,11 @@ def delete_token(request, key):
|
|||||||
|
|
||||||
@staff_required
|
@staff_required
|
||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
builds = GithubRun.objects.select_related('created_by').all()[:100]
|
builds = list(GithubRun.objects.select_related('created_by').all()[:100])
|
||||||
|
# 回调为第一状态来源;本地 6 小时超时兜底 + 后台线程只读核对平台终态(不阻塞页面)
|
||||||
|
from .views import mark_stale_builds, schedule_refresh_for_runs
|
||||||
|
mark_stale_builds(builds)
|
||||||
|
schedule_refresh_for_runs(builds)
|
||||||
stats = {
|
stats = {
|
||||||
'total': GithubRun.objects.count(),
|
'total': GithubRun.objects.count(),
|
||||||
'in_progress': GithubRun.objects.exclude(
|
'in_progress': GithubRun.objects.exclude(
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ action)。
|
|||||||
### 1. Gitea 服务器与仓库
|
### 1. Gitea 服务器与仓库
|
||||||
|
|
||||||
1. 部署 Gitea **1.27 或更高版本**(最低 1.23;1.27 随附 act_runner 2.0,对
|
1. 部署 Gitea **1.27 或更高版本**(最低 1.23;1.27 随附 act_runner 2.0,对
|
||||||
artifact/cache 与第三方 action 的兼容性最好)。
|
artifact/cache 与第三方 action 的兼容性最好)。注意:列表页的一键「停止」依赖
|
||||||
|
Gitea 1.28+ 的取消运行 API;1.27 及更早版本点击停止会给出运行页链接,需到
|
||||||
|
Gitea 界面手动 Cancel。
|
||||||
2. 把本仓库整体推送到 Gitea(保留 `.gitea/` 目录与默认分支名,后台填写的
|
2. 把本仓库整体推送到 Gitea(保留 `.gitea/` 目录与默认分支名,后台填写的
|
||||||
GITEA_BRANCH 必须与实际分支一致)。
|
GITEA_BRANCH 必须与实际分支一致)。
|
||||||
3. 进入仓库 **Settings**,勾选 **Enable Repository Actions**。
|
3. 进入仓库 **Settings**,勾选 **Enable Repository Actions**。
|
||||||
@@ -123,11 +125,18 @@ Gitea 不提供云构建机,每个需要构建的平台都要自行注册 runn
|
|||||||
|
|
||||||
| 标签 | 用途 | 运行方式 |
|
| 标签 | 用途 | 运行方式 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `ubuntu-22.04` | Linux x86_64、Android 构建 | Linux 主机 Docker 模式 |
|
| `ubuntu-24.04` | Linux x86_64、Android(armv7/aarch64 走 NDK 交叉编译)、drm/appimage/flatpak x86_64 | Linux x86_64 主机 Docker 模式 |
|
||||||
| `ubuntu-22.04-arm` | Linux arm64、Android arm 构建 | arm64 Linux 主机 Docker 模式 |
|
| `ubuntu-24.04-arm` | Linux arm64 的 deb/flatpak 原生构建 | arm64 Linux 主机 Docker 模式(需另注册) |
|
||||||
| `windows-2022` | Windows x64/x86 构建 | Windows 主机 host 模式(真机/虚拟机) |
|
| `windows-2022` | Windows x64/x86 构建 | Windows 主机 host 模式(真机/虚拟机,需另注册) |
|
||||||
| `macos-14` | macOS arm64 构建 | Apple Silicon Mac host 模式 |
|
| `macos-14` | macOS arm64 构建 | Apple Silicon Mac host 模式(需另注册) |
|
||||||
| `macos-15-intel` | macOS x64 构建 | Intel Mac host 模式 |
|
| `macos-15-intel` | macOS x64 构建 | Intel Mac host 模式(需另注册) |
|
||||||
|
|
||||||
|
另外大量轻量步骤(拉取加密配置、收尾回调、artifact 清理)运行在
|
||||||
|
`ubuntu-latest` 标签上——act_runner 默认自带的 `ubuntu-latest`/`docker`/`amd64`
|
||||||
|
标签即可承接,但必须确保有 runner 显式提供 `ubuntu-24.04` 标签,否则主构建会
|
||||||
|
一直处于 waiting。只注册一台 x86_64 Linux runner 时 Android 与 Linux x86_64
|
||||||
|
构建即可工作;arm64 Linux、Windows、macOS 必须各自再注册对应机器,对应任务在
|
||||||
|
机器就绪前会排队等待。
|
||||||
|
|
||||||
**Linux(Docker 模式,推荐)**:
|
**Linux(Docker 模式,推荐)**:
|
||||||
|
|
||||||
@@ -141,13 +150,29 @@ docker run -d --name gitea-runner --restart always \
|
|||||||
```
|
```
|
||||||
|
|
||||||
首次启动生成 `/data/config.yaml` 后,按需把 `labels` 改为上面的标签,例如
|
首次启动生成 `/data/config.yaml` 后,按需把 `labels` 改为上面的标签,例如
|
||||||
`- "ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04"`,再重启容器。
|
`- "ubuntu-24.04:docker://ghcr.io/catthehacker/ubuntu:act-24.04"`(act 官方
|
||||||
|
镜像还有 `ubuntu:act-latest` 可挂到 `ubuntu-latest` 标签),再重启容器。
|
||||||
工作流把 vcpkg 二进制缓存放在容器内 `/opt/vcpkg-cache`,如需跨任务复用,可在
|
工作流把 vcpkg 二进制缓存放在容器内 `/opt/vcpkg-cache`,如需跨任务复用,可在
|
||||||
job 容器配置中把该路径挂为持久卷。
|
job 容器配置中把该路径挂为持久卷。
|
||||||
|
|
||||||
**Windows(host 模式)**:在准备好构建环境的 Windows 机器上
|
> **坑:注册地址必须是作业容器也能访问的地址。** 若 Gitea 与 act_runner 用
|
||||||
(Git、PowerShell、Visual Studio 2022、vcpkg 位于 `C:\vcpkg`,并预创建
|
> docker-compose 部署,千万不要用 `http://gitea:3000` 这类 compose 内部服务名
|
||||||
`D:\vcpkg-cache`;ImageMagick 等由工作流自动安装):
|
> 注册(act_runner 容器自身可达,但 act_runner v3+ 的作业容器默认 host 网络,
|
||||||
|
> 解析不了该名)。否则上传/下载 artifact 时报
|
||||||
|
> `Failed to CreateArtifact: ... ENOTFOUND`。`GITEA_INSTANCE_URL` 用公网地址
|
||||||
|
> (如 `https://gitea.example.com`)或宿主机映射地址(如 `http://10.0.0.10:39630`),
|
||||||
|
> 改完编辑 `/data/config.yaml` 的 `runner.address` 重启即可,无需重新注册。
|
||||||
|
|
||||||
|
**Windows(host 模式)**:在准备好构建环境的 Windows 机器上构建。Gitea act
|
||||||
|
在 Windows 上默认 shell 是 bash,而本仓库工作流已显式把默认 shell 设为
|
||||||
|
**PowerShell 7(pwsh)**,因此机器上必须安装:
|
||||||
|
|
||||||
|
* **PowerShell 7+**(`pwsh` 必须在 PATH 中;仅有 Windows 自带的 5.1 不够)
|
||||||
|
* **Git for Windows**(提供 Git Bash,部分步骤显式使用 `shell: bash`)
|
||||||
|
* **Python 3**(decrypt-secrets composite action 需要,且能 `python -m pip`)
|
||||||
|
* **Visual Studio 2022**(含 C++ 桌面开发、MSVC、Windows SDK)
|
||||||
|
* **vcpkg** 固定位于 `C:\vcpkg`,并**预创建 `D:\vcpkg-cache`** 二进制缓存目录
|
||||||
|
* **Chocolatey / NuGet**(工作流用其安装 ImageMagick、WiX 等构建依赖)
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# 下载 https://gitea.com/gitea/act_runner/releases 的 act_runner.exe
|
# 下载 https://gitea.com/gitea/act_runner/releases 的 act_runner.exe
|
||||||
@@ -158,8 +183,11 @@ job 容器配置中把该路径挂为持久卷。
|
|||||||
确认稳定后再用任务计划程序/NSSM 注册为开机服务。
|
确认稳定后再用任务计划程序/NSSM 注册为开机服务。
|
||||||
|
|
||||||
**macOS(host 模式)**:使用 Apple 硬件(arm64 对应 `macos-14`,Intel 对应
|
**macOS(host 模式)**:使用 Apple 硬件(arm64 对应 `macos-14`,Intel 对应
|
||||||
`macos-15-intel`,安装 Xcode 命令行工具),同样下载 act_runner 二进制后以
|
`macos-15-intel`)。仅安装 Xcode 命令行工具不够,必须安装**完整版 Xcode**
|
||||||
`macos-14:host` 标签注册并 launchd 守护。
|
(Flutter macOS 构建与代码签名需要),并具备 Homebrew、CocoaPods
|
||||||
|
(`sudo gem install cocoapods` 或 brew 版)、运行 runner 的用户可**免密
|
||||||
|
sudo**。同样下载 act_runner 二进制后以 `macos-14:host` 标签注册并 launchd
|
||||||
|
守护。
|
||||||
|
|
||||||
### 3. 内网网络说明
|
### 3. 内网网络说明
|
||||||
|
|
||||||
@@ -178,6 +206,10 @@ job 容器配置中把该路径挂为持久卷。
|
|||||||
(`.rdgen-src/.github/patches/`),不依赖 raw.githubusercontent.com。
|
(`.rdgen-src/.github/patches/`),不依赖 raw.githubusercontent.com。
|
||||||
* rdgen 后台的 `GITEA_PROXY` 仅用于「服务端 → Gitea API」的调用;Gitea 通常
|
* rdgen 后台的 `GITEA_PROXY` 仅用于「服务端 → Gitea API」的调用;Gitea 通常
|
||||||
与 rdgen 在同一内网,留空直连即可。
|
与 rdgen 在同一内网,留空直连即可。
|
||||||
|
* **runner 必须能反向访问 rdgen(`GENURL`)**:每个工作流结束时(无论成功、
|
||||||
|
失败还是取消)都会向 `${GENURL}/updategh` 回调最终状态。收不到回调的构建
|
||||||
|
会在 6 小时后被服务端误判为超时,因此 `GENURL` 要填 runner 实际可达的地址,
|
||||||
|
不能只填浏览器端能访问的地址。
|
||||||
|
|
||||||
### 4. 在 rdgen 侧启用
|
### 4. 在 rdgen 侧启用
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user