publish.yml 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. name: Publish
  2. run-name: Release ${{github.ref_name}}
  3. on:
  4. push:
  5. tags: [v*]
  6. permissions:
  7. contents: read
  8. jobs:
  9. publish-docker:
  10. runs-on: ubuntu-latest
  11. timeout-minutes: 150
  12. environment:
  13. name: publish
  14. url: https://hub.docker.com/r/newfuture/ddns
  15. permissions:
  16. packages: write
  17. id-token: write
  18. attestations: write
  19. env:
  20. platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x
  21. steps:
  22. - uses: actions/checkout@v4
  23. - run: python3 .github/patch.py docker
  24. env:
  25. GITHUB_REF_NAME: ${{ github.ref_name }}
  26. - uses: docker/setup-qemu-action@v3
  27. with:
  28. platforms: ${{ env.platforms }}
  29. - uses: docker/setup-buildx-action@v3
  30. - uses: docker/login-action@v3
  31. with:
  32. registry: ghcr.io
  33. username: ${{ github.actor }}
  34. password: ${{ secrets.GITHUB_TOKEN }}
  35. - uses: docker/login-action@v3
  36. with:
  37. username: ${{ secrets.DOCKERHUB_USERNAME }}
  38. password: ${{ secrets.DOCKERHUB_TOKEN }}
  39. - uses: docker/metadata-action@v5
  40. id: meta
  41. with:
  42. images: |
  43. ghcr.io/newfuture/ddns
  44. newfuture/ddns
  45. flavor: | # exclude alpha, beta, rc
  46. latest=${{ !(contains(github.ref_name, 'a') || contains(github.ref_name, 'b') || contains(github.ref_name, 'c')) }}
  47. tags: | # next for beta and rc or stable (none alpha)
  48. type=ref,event=tag
  49. type=raw,value=next,enable=${{ !contains(github.ref_name, 'alpha')}}
  50. env:
  51. DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
  52. - uses: docker/build-push-action@v6
  53. id: push
  54. with:
  55. context: .
  56. file: docker/Dockerfile
  57. platforms: ${{ env.platforms }}
  58. push: true
  59. tags: ${{ steps.meta.outputs.tags }}
  60. labels: ${{ steps.meta.outputs.labels }}
  61. annotations: ${{ steps.meta.outputs.annotations }}
  62. build-args: |
  63. BUILDER=ghcr.io/newfuture/nuitka-builder:master
  64. GITHUB_REF_NAME=${{ github.ref_name }}
  65. publish-pypi:
  66. runs-on: ubuntu-latest
  67. timeout-minutes: 5
  68. environment:
  69. name: publish
  70. url: https://pypi.org/project/ddns/
  71. permissions:
  72. # IMPORTANT: this permission is mandatory for Trusted Publishing
  73. id-token: write
  74. steps:
  75. - uses: actions/checkout@v4
  76. - uses: actions/setup-python@v5
  77. with:
  78. python-version: "3.x"
  79. - name: Install dependencies
  80. run: pip install build
  81. - run: python3 .github/patch.py version
  82. - name: Replace url in Readme
  83. run: sed -i'' -E 's#([("'\''`])(/doc/[^)"'\''`]+)\.md([)"'\''`])#\1https://ddns.newfuture.cc\2.html\3#g; s#([("'\''`])/doc/#\1https://ddns.newfuture.cc/doc/#g' README.md
  84. - name: run unit tests
  85. run: python -m unittest discover tests -v
  86. - name: test callback config
  87. run: python3 -m ddns -c tests/config/callback.json
  88. - name: test debug config
  89. run: python3 -m ddns -c tests/config/debug.json
  90. - name: test noip config
  91. run: python3 -m ddns -c tests/config/noip.json
  92. - name: Build package
  93. run: python -m build --sdist --wheel --outdir dist/
  94. - uses: pypa/gh-action-pypi-publish@release/v1
  95. with:
  96. print-hash: true
  97. publish-binary:
  98. strategy:
  99. fail-fast: false
  100. matrix:
  101. include:
  102. - os: windows-latest
  103. arch: x64
  104. - os: windows-latest
  105. arch: x86
  106. - os: windows-11-arm
  107. arch: arm64
  108. - os: ubuntu-latest
  109. arch: x64
  110. - os: ubuntu-24.04-arm
  111. arch: arm64
  112. - os: macos-13
  113. arch: x64
  114. - os: macos-latest
  115. arch: arm64
  116. runs-on: ${{ matrix.os }}
  117. timeout-minutes: ${{ matrix.arch == 'x86' && 20 || 10 }}
  118. permissions:
  119. contents: write
  120. steps:
  121. - uses: actions/checkout@v4
  122. - uses: actions/setup-python@v5
  123. with:
  124. python-version: "3.12"
  125. architecture: ${{ matrix.arch }}
  126. - run: python3 .github/patch.py
  127. - name: Set up on Linux
  128. if: runner.os == 'Linux'
  129. run: sudo apt-get install -y --no-install-recommends patchelf
  130. - name: Set up on macOS
  131. if: runner.os == 'macOS'
  132. run: python3 -m pip install imageio
  133. - name: run unit tests
  134. run: python -m unittest discover tests -v
  135. - name: test run.py
  136. run: python3 ./run.py -h
  137. - name: Build Executable
  138. uses: Nuitka/[email protected]
  139. with:
  140. nuitka-version: main
  141. script-name: run.py
  142. mode: onefile
  143. output-dir: dist
  144. lto: yes
  145. windows-icon-from-ico: ${{ runner.os == 'Windows' && 'favicon.ico' || '' }}
  146. linux-icon: ${{ runner.os == 'Linux' && 'doc/img/ddns.svg' || '' }}
  147. static-libpython: ${{ runner.os == 'Linux' && 'yes' || 'auto' }}
  148. macos-app-name: ${{ runner.os == 'macOS' && 'DDNS' || '' }}
  149. macos-app-icon: ${{ runner.os == 'macOS' && 'doc/img/ddns.png' || '' }}
  150. - run: ./dist/ddns || test -e config.json
  151. - run: ./dist/ddns -v
  152. - run: ./dist/ddns -h
  153. - name: test callback config with executable
  154. run: ./dist/ddns -c tests/config/callback.json
  155. - name: test debug config with executable
  156. run: ./dist/ddns -c tests/config/debug.json
  157. - name: test noip config with executable
  158. run: ./dist/ddns -c tests/config/noip.json
  159. - name: Build Windows App (standalone)
  160. if: runner.os == 'Windows'
  161. uses: Nuitka/[email protected]
  162. with:
  163. nuitka-version: main
  164. script-name: run.py
  165. mode: standalone
  166. output-dir: dist-app
  167. lto: yes
  168. file-description: "DDNS客户端 ${{ github.ref_name }}"
  169. windows-console-mode: attach
  170. windows-icon-from-ico: favicon.ico
  171. - name: Package Windows app as ddns.zip
  172. if: runner.os == 'Windows'
  173. shell: pwsh
  174. run: |
  175. $ErrorActionPreference = 'Stop'
  176. $appDir = Get-ChildItem -Path 'dist-app' -Directory -Filter '*.dist' | Select-Object -First 1
  177. if (-not $appDir) { throw 'Standalone app folder (*.dist) not found in dist' }
  178. $zipName = "ddns-windows-${{ matrix.arch }}.zip"
  179. if (Test-Path "dist/$zipName") { Remove-Item "dist/$zipName" -Force }
  180. # Package the contents of the .dist folder directly, not the folder itself
  181. Compress-Archive -Path "$($appDir.FullName)\*" -DestinationPath "dist/$zipName"
  182. # Clean up temporary output directory
  183. Remove-Item 'dist-app' -Recurse -Force
  184. - run: mv ./dist/ddns ./dist/ddns-ubuntu24.04-${{ matrix.arch }}
  185. if: runner.os == 'Linux'
  186. - run: mv ./dist/ddns ./dist/ddns-mac-${{ matrix.arch }}
  187. if: runner.os == 'macOS'
  188. - run: mv ./dist/ddns.exe ./dist/ddns-windows-${{ matrix.arch }}.exe
  189. if: runner.os == 'Windows'
  190. - name: Upload binary
  191. run: gh release upload ${{ github.ref_name }} dist/ddns*
  192. shell: bash
  193. env:
  194. GH_TOKEN: ${{ github.token }}
  195. publish-linux-binary:
  196. strategy:
  197. matrix:
  198. host: [ amd, arm ]
  199. libc: [ musl, glibc ]
  200. runs-on: ubuntu-${{ matrix.host == 'arm' && '24.04-arm' || 'latest' }}
  201. permissions:
  202. contents: write
  203. env:
  204. platforms: ${{ matrix.host == 'amd' && 'linux/386,linux/amd64' || matrix.libc == 'glibc' && 'linux/arm/v7,linux/arm64/v8' || 'linux/arm/v6,linux/arm/v7,linux/arm64/v8' }}
  205. timeout-minutes: 8
  206. steps:
  207. - uses: actions/checkout@v4
  208. - uses: docker/setup-buildx-action@v3
  209. - uses: docker/build-push-action@v6
  210. with:
  211. context: .
  212. file: docker/${{ matrix.libc }}.Dockerfile
  213. platforms: ${{ env.platforms }}
  214. push: false
  215. tags: ddnsbin
  216. target: export
  217. outputs: type=local,dest=./output
  218. build-args: |
  219. BUILDER=ghcr.io/newfuture/nuitka-builder:${{matrix.libc}}-master
  220. GITHUB_REF_NAME=${{ github.ref_name }}
  221. # 测试构建的二进制文件
  222. - name: Test built binaries
  223. run: |
  224. set -ex
  225. for f in output/*/ddns; do
  226. platform=$(basename $(dirname "$f") | tr '_' '/')
  227. tests/scripts/test-in-docker.sh $platform ${{matrix.libc}} "$f"
  228. done
  229. # 输出目录结构扁平化
  230. - name: Flatten output directory structure
  231. run: |
  232. set -e
  233. mkdir -p dist
  234. for f in output/*/ddns; do
  235. name=$(basename "$(dirname "$f")")
  236. mv "$f" "dist/ddns-${{ matrix.libc }}-$name"
  237. done
  238. - name: Upload binary
  239. run: gh release upload ${{ github.ref_name }} dist/ddns*
  240. shell: bash
  241. env:
  242. GH_TOKEN: ${{ github.token }}
  243. github-release:
  244. runs-on: ubuntu-latest
  245. timeout-minutes: 5
  246. environment:
  247. name: publish
  248. url: https://github.com/NewFuture/DDNS/releases/tag/${{ github.ref_name }}
  249. permissions:
  250. contents: write
  251. needs: [publish-docker, publish-pypi, publish-binary, publish-linux-binary]
  252. steps:
  253. - uses: actions/checkout@v4
  254. - run: python3 .github/patch.py release
  255. - name: Generate release notes and append README.md
  256. env:
  257. GH_TOKEN: ${{ github.token }}
  258. run: |
  259. gh release view ${{ github.ref_name }} --json body -q '.body' > changelog.md
  260. gh release upload ${{ github.ref_name }} changelog.md --clobber
  261. cat doc/release.md >> changelog.md
  262. gh release edit ${{ github.ref_name }} --notes-file changelog.md