Compare commits

...
7 Commits
Author SHA1 Message Date
naeeo d78c071818 构建状态增加平台终态只读核对,修复回调丢失后任务长期卡在构建中
工作流在脚本收尾前失败(拉取/编译/runner 异常)或 GENURL 不可达时,
结束回调会永久丢失,而等待页 30 秒轮询与列表页此前只读本地库,
任务只能等 6 小时本地超时,平台侧已失败也无法及时反映。

- GitHub/Gitea 后端新增只读 get_run(),仅在平台报告终态时返回结论,
  在途、非 200、网络异常、坏 JSON 一律返回 None;不取消、不写平台
- 等待页 30 秒轮询同步核对一次真实状态:20 秒节流、6 秒超时、异常静默
- 我的构建/后台仪表盘改为后台守护线程核对,页面渲染零等待
- 仅做非终态到终态的条件更新,回调抢先到达时不会被覆盖
- 停止/删除纯本地语义、6 小时本地超时兜底等现有逻辑保持不变
2026-09-30 12:46:05 +08:00
naeeo b0219603be 构建列表操作按钮修正:成功任务不再显示重试,统一操作列排版
- 成功状态的构建记录不再显示「重试」按钮,仅保留下载产物/日志/删除;
  失败、取消、超时仍保留一键重试(线上核对确认仅新快照成功任务误显示)
- retry_build 后端增加 success 状态拦截,防止绕过页面直接 POST
- 操作列改为统一 flex 容器(flex-wrap + gap),按钮数量随状态变化时
  间距一致、左对齐、窄屏自动换行;后台任务管理页同款统一
2026-09-30 12:03:56 +08:00
naeeo 73ca96fa67 完善 Gitea 工作流并修复移动端导航与列表按钮图标
前端:
- 「我的构建」列表「查看详情/查看进度」按钮补充与其他按钮同风格的眼睛小图标
- 顶部导航增加 768px 以下移动端适配: 顶栏纵向排列, 导航单行横向滑动, 修复手机端错位

Gitea Actions 工作流 (仅 .gitea, 不动 .github):
- 5 个生成器工作流全部补充 /updategh 终态状态回调 (成功/取消/失败均回调),
  修复无回调导致构建 6 小时后被误判为超时的 P0 缺陷
- Windows 两个工作流 job 级默认 shell 设为 pwsh, 兼容 act 在 Windows 默认 bash
- 修复 VCPKG_BINARY_SOURCES 中 \v 的 YAML 非法转义 (双引号改单引号)
- Flutter 补丁路径改为 .rdgen-src/.github/patches/, run-on-arch 容器内用 /workspace 前缀
- run-on-arch githubToken 置空, 避免 Gitea token 拉 ghcr.io 失败
- runs-on 标签对齐现场 runner: ubuntu-22.04 改为 ubuntu-24.04
- run-vcpkg/setup-ndk 钉到 node20 运行时 SHA, 规避 node24 action 不兼容
- decrypt-secrets 的 pip 安装增加三级回退; cleanzip 请求增加超时重试
- 所有产物上传 curl 增加 --fail/超时/重试参数
- macOS 动态定位 *.app 不再硬编码 RustDesk.app, DMG 路径改用 GITHUB_WORKSPACE
- 修复签名条件判断、PowerShell 变量空格等小问题
- setup.md 更新 runner 标签表及 Windows/macOS host 准备要求
2026-09-30 11:40:33 +08:00
naeeo 6e5754fb59 停止/删除改为纯本地操作,页面不再查询 Gitea
停止只在本平台标记已取消、删除只删本地记录,均不再调用 GitHub/Gitea 的查询或取消接口,操作秒响应;构建状态完全以工作流结束回调为准,未收到回调的在途任务由纯本地 6 小时超时兜底。

移除上一版的 Gitea 网页登录取消、网页账密配置项、runs 批量状态查询及两后端 get_run/cancel_run 接口,消除页面加载时的远程等待。
2026-09-30 09:20:28 +08:00
naeeo c8f41a34c0 修复列表页加载慢并支持 Gitea 1.27 一键停止
1. 状态同步加 20 秒节流、超时缩短到 8 秒;Gitea 改用 runs 列表接口一次批量取回全部任务状态,在途任务并发补查作业,消除逐任务串行 API 请求导致的后台/我的构建/详情页慢。

2. 停止时先区分 404:运行已删除则本地标记取消;Gitea 1.27 无取消 API 时,若后台配置了 Gitea 网页账号密码则模拟网页登录取消,否则回退手动链接提示。

3. 后台 Gitea 连接设置新增网页登录账号/密码两个可选项。
2026-09-30 01:04:16 +08:00
naeeo 5ff6bcfe51 docs: 补充 act_runner 注册地址必须为作业容器可达地址的说明
使用 compose 内部服务名(如 http://gitea:3000)注册会导致 host 网络作业容器无法解析,artifact 上传报 ENOTFOUND。
2026-09-30 00:20:30 +08:00
naeeo 4fd930aa0b feat: 同步构建真实状态并支持在列表停止在途任务
- Gitea 运行未结束时探测作业级结论,修复前置作业失败后可复用工作流父作业/等待标签作业长期挂起导致状态永远构建中

- 打开我的构建/管理仪表盘时主动向构建平台同步在途任务状态;创建超 6 小时仍在途的任务判超时

- 构建中任务列表增加停止按钮(Gitea 需 1.28+ 取消 API,旧版本提示去运行页手动 Cancel),终态任务保留删除

- GitHub/Gitea 后端统一 cancel_run 接口
2026-09-29 23:45:25 +08:00
19 changed files with 615 additions and 115 deletions
+6 -1
View File
@@ -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:
+35 -6
View File
@@ -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:
+66 -27
View File
@@ -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:
+60 -8
View File
@@ -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:
+55 -6
View File
@@ -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:
+57 -7
View File
@@ -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:
+1 -1
View File
@@ -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: |
@@ -78,7 +87,7 @@ jobs:
shell: pwsh shell: pwsh
command: | command: |
cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796 cd RustDeskTempTopMostWindow && git checkout 53b548a5398624f7149a382000397993542ad796
if ($env:privacylink_url-ne "false") { if ($env:privacylink_url -ne "false") {
Invoke-WebRequest -Uri ${{ env.privacylink_url }}/get_png?filename=${{ env.privacylink_file }}"&"uuid=${{ env.privacylink_uuid }} -OutFile privacy.png Invoke-WebRequest -Uri ${{ env.privacylink_url }}/get_png?filename=${{ env.privacylink_file }}"&"uuid=${{ env.privacylink_uuid }} -OutFile privacy.png
Copy-Item ../.github/patches/privacyScreen.py privacyScreen.py Copy-Item ../.github/patches/privacyScreen.py privacyScreen.py
python privacyScreen.py python privacyScreen.py
+1
View File
@@ -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'),
+9 -7
View File
@@ -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': 中文说明},不抛异常。"""
+26 -12
View File
@@ -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
data = resp.json() try:
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):
+30 -11
View File
@@ -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
data = resp.json() try:
if data.get('status') == 'completed': data = resp.json()
return RunInfo(finished=True, status=data.get('conclusion') or 'failure', except ValueError:
html_url=data.get('html_url') or self.log_url(run_id)) return None
return RunInfo(finished=False, status=data.get('status') or 'in_progress', # 只关心终态:在途(queued/in_progress/waiting)返回 None,交给回调与下次核对
html_url=data.get('html_url') or self.log_url(run_id)) if data.get('status') != 'completed':
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 {
+6
View File
@@ -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"/>
+3 -1
View File
@@ -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 %}
+14 -3
View File
@@ -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
View File
@@ -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,
+38 -3
View File
@@ -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(
+44 -12
View File
@@ -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 侧启用