1
0

image.yml 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. name: Build Cloud Images
  2. # Build golden cloud images from a published release, for amd64 and arm64:
  3. # * qemu -> qcow2 attached to the GitHub release (always)
  4. # * amazon-ebs -> AWS AMI (only when AWS credentials are configured)
  5. #
  6. # Images contain NO database and NO baked credentials; first boot generates
  7. # unique per-instance credentials (see deploy/firstboot + deploy/packer).
  8. on:
  9. release:
  10. types: [published]
  11. workflow_dispatch:
  12. inputs:
  13. tag:
  14. description: "Release tag to build images for (e.g. v3.3.1)"
  15. required: true
  16. type: string
  17. permissions:
  18. contents: write
  19. concurrency:
  20. group: image-${{ github.event.release.tag_name || inputs.tag }}
  21. cancel-in-progress: false
  22. jobs:
  23. # Resolve the tag and wait until BOTH arch tarballs are actually published
  24. # (the release matrix uploads assets one by one, so 'published' can fire
  25. # before the tarballs exist).
  26. setup:
  27. runs-on: ubuntu-latest
  28. outputs:
  29. tag: ${{ steps.resolve.outputs.tag }}
  30. steps:
  31. - name: Resolve tag
  32. id: resolve
  33. run: |
  34. if [ "${{ github.event_name }}" = "release" ]; then
  35. TAG="${{ github.event.release.tag_name }}"
  36. else
  37. TAG="${{ inputs.tag }}"
  38. fi
  39. [ -n "$TAG" ] || { echo "::error::no tag resolved"; exit 1; }
  40. echo "tag=$TAG" >> "$GITHUB_OUTPUT"
  41. - name: Wait for released binary assets (amd64 + arm64)
  42. env:
  43. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  44. TAG: ${{ steps.resolve.outputs.tag }}
  45. run: |
  46. want="x-ui-linux-amd64.tar.gz x-ui-linux-arm64.tar.gz"
  47. for i in $(seq 1 30); do
  48. names=$(gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json assets -q '.assets[].name')
  49. missing=""
  50. for w in $want; do
  51. echo "$names" | grep -qx "$w" || missing="$missing $w"
  52. done
  53. if [ -z "$missing" ]; then
  54. echo "All assets present on $TAG"
  55. exit 0
  56. fi
  57. echo "Waiting for$missing on $TAG ($i/30)..."
  58. sleep 20
  59. done
  60. echo "::error::missing release assets on $TAG after 10 minutes:$missing"
  61. exit 1
  62. # Gate the AWS AMI build so forks without secrets skip it cleanly
  63. # (secrets cannot be referenced directly in job-level `if`).
  64. check-aws:
  65. runs-on: ubuntu-latest
  66. outputs:
  67. enabled: ${{ steps.c.outputs.enabled }}
  68. use_oidc: ${{ steps.c.outputs.use_oidc }}
  69. steps:
  70. - id: c
  71. env:
  72. ROLE: ${{ secrets.AWS_ROLE_ARN }}
  73. KEY: ${{ secrets.AWS_ACCESS_KEY_ID }}
  74. run: |
  75. if [ -n "$ROLE" ]; then
  76. echo "enabled=true" >> "$GITHUB_OUTPUT"
  77. echo "use_oidc=true" >> "$GITHUB_OUTPUT"
  78. elif [ -n "$KEY" ]; then
  79. echo "enabled=true" >> "$GITHUB_OUTPUT"
  80. echo "use_oidc=false" >> "$GITHUB_OUTPUT"
  81. else
  82. echo "enabled=false" >> "$GITHUB_OUTPUT"
  83. echo "use_oidc=false" >> "$GITHUB_OUTPUT"
  84. echo "::notice::No AWS credentials configured; skipping the AMI build."
  85. fi
  86. qemu-image:
  87. needs: setup
  88. timeout-minutes: 90
  89. strategy:
  90. fail-fast: false
  91. matrix:
  92. include:
  93. - arch: amd64
  94. runner: ubuntu-latest
  95. qemu_pkgs: qemu-system-x86 qemu-utils
  96. - arch: arm64
  97. runner: ubuntu-24.04-arm
  98. qemu_pkgs: qemu-system-arm qemu-efi-aarch64 qemu-utils
  99. runs-on: ${{ matrix.runner }}
  100. steps:
  101. - name: Checkout
  102. uses: actions/checkout@v6
  103. - name: Install QEMU
  104. run: |
  105. sudo apt-get update
  106. sudo apt-get install -y --no-install-recommends ${{ matrix.qemu_pkgs }}
  107. - name: Setup Packer
  108. uses: hashicorp/setup-packer@v3
  109. with:
  110. version: latest
  111. - name: Verify released binary asset
  112. env:
  113. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  114. TAG: ${{ needs.setup.outputs.tag }}
  115. run: |
  116. mkdir -p _asset
  117. gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
  118. --pattern "x-ui-linux-${{ matrix.arch }}.tar.gz" --dir _asset
  119. ls -la _asset
  120. - name: Select accelerator
  121. id: accel
  122. run: |
  123. if [ -e /dev/kvm ]; then echo "value=kvm" >> "$GITHUB_OUTPUT"; else echo "value=tcg" >> "$GITHUB_OUTPUT"; fi
  124. - name: Packer init
  125. run: packer init deploy/packer/
  126. - name: Build qcow2 image
  127. env:
  128. TAG: ${{ needs.setup.outputs.tag }}
  129. ACCEL: ${{ steps.accel.outputs.value }}
  130. run: |
  131. packer build -only='qemu.x-ui' \
  132. -var "xui_version=${TAG}" \
  133. -var "xui_arch=${{ matrix.arch }}" \
  134. -var "qemu_accelerator=${ACCEL}" \
  135. deploy/packer/
  136. - name: Compress qcow2
  137. id: pack
  138. env:
  139. TAG: ${{ needs.setup.outputs.tag }}
  140. run: |
  141. cd deploy/packer/output-qemu
  142. src="3x-ui-ubuntu-24.04-${{ matrix.arch }}.qcow2"
  143. out="3x-ui-ubuntu-24.04-${TAG}-${{ matrix.arch }}.qcow2.xz"
  144. xz -T0 -6 -c "$src" > "$out"
  145. sha256sum "$out" > "${out}.sha256"
  146. echo "file=deploy/packer/output-qemu/${out}" >> "$GITHUB_OUTPUT"
  147. echo "sha=deploy/packer/output-qemu/${out}.sha256" >> "$GITHUB_OUTPUT"
  148. ls -la
  149. - name: Attach qcow2 to release
  150. env:
  151. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  152. TAG: ${{ needs.setup.outputs.tag }}
  153. run: |
  154. gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
  155. "${{ steps.pack.outputs.file }}" "${{ steps.pack.outputs.sha }}"
  156. - name: Summary
  157. env:
  158. TAG: ${{ needs.setup.outputs.tag }}
  159. ACCEL: ${{ steps.accel.outputs.value }}
  160. run: |
  161. {
  162. echo "## QEMU image (${{ matrix.arch }})"
  163. echo "- Tag: \`${TAG}\`"
  164. echo "- Accelerator: \`${ACCEL}\`"
  165. echo "- Attached: \`$(basename "${{ steps.pack.outputs.file }}")\`"
  166. } >> "$GITHUB_STEP_SUMMARY"
  167. ami-image:
  168. needs: [setup, check-aws]
  169. if: needs.check-aws.outputs.enabled == 'true'
  170. runs-on: ubuntu-latest
  171. timeout-minutes: 60
  172. permissions:
  173. contents: read
  174. id-token: write
  175. strategy:
  176. fail-fast: false
  177. matrix:
  178. include:
  179. - arch: amd64
  180. instance_type: t3.small
  181. - arch: arm64
  182. instance_type: t4g.small
  183. steps:
  184. - name: Checkout
  185. uses: actions/checkout@v6
  186. - name: Setup Packer
  187. uses: hashicorp/setup-packer@v3
  188. with:
  189. version: latest
  190. - name: Configure AWS credentials (OIDC)
  191. if: needs.check-aws.outputs.use_oidc == 'true'
  192. uses: aws-actions/configure-aws-credentials@v4
  193. with:
  194. role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
  195. aws-region: ${{ vars.AWS_REGION || 'eu-central-1' }}
  196. - name: Configure AWS credentials (access keys)
  197. if: needs.check-aws.outputs.use_oidc != 'true'
  198. uses: aws-actions/configure-aws-credentials@v4
  199. with:
  200. aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
  201. aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  202. aws-region: ${{ vars.AWS_REGION || 'eu-central-1' }}
  203. - name: Verify released binary asset
  204. env:
  205. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  206. TAG: ${{ needs.setup.outputs.tag }}
  207. run: |
  208. mkdir -p _asset
  209. gh release download "$TAG" --repo "$GITHUB_REPOSITORY" \
  210. --pattern "x-ui-linux-${{ matrix.arch }}.tar.gz" --dir _asset
  211. ls -la _asset
  212. - name: Packer init
  213. run: packer init deploy/packer/
  214. - name: Build AMI
  215. env:
  216. TAG: ${{ needs.setup.outputs.tag }}
  217. REGION: ${{ vars.AWS_REGION || 'eu-central-1' }}
  218. run: |
  219. packer build -only='amazon-ebs.x-ui' \
  220. -var "xui_version=${TAG}" \
  221. -var "xui_arch=${{ matrix.arch }}" \
  222. -var "instance_type=${{ matrix.instance_type }}" \
  223. -var "region=${REGION}" \
  224. deploy/packer/
  225. - name: Publish AMI id to summary
  226. env:
  227. REGION: ${{ vars.AWS_REGION || 'eu-central-1' }}
  228. run: |
  229. AMI_ID=$(jq -r '.builds[] | select(.builder_type=="amazon-ebs") | .artifact_id' packer-manifest.json | tail -1 | cut -d: -f2)
  230. {
  231. echo "## AWS AMI (${{ matrix.arch }})"
  232. echo "- Region: \`${REGION}\`"
  233. echo "- Instance type: \`${{ matrix.instance_type }}\`"
  234. echo "- AMI ID: \`${AMI_ID}\`"
  235. } >> "$GITHUB_STEP_SUMMARY"