name: release

# 推送 vX.Y.Z 标签或发布 Release 时，构建并推送多架构镜像（GHCR + 可选 Docker Hub）。
# workflow_dispatch 可手动触发（只打 latest），用于验证流水线本身。
#
# 关键（v1.4.3 之后重写）：改用【原生 arm64 runner】分架构构建，不再用 QEMU 模拟。
# 教训：QEMU 下 arm64 的 apt-get update 会 OOM（getline: Cannot allocate memory），
# 让实例镜像反复发不出去（1.4.1 / 1.4.3 均折在这）。原生 runner 从根上消除该问题，也更快。
# 模式：每个 (镜像 × 架构) 在各自原生 runner 上按 digest 推送 → merge job 合成多架构 manifest 打 tag。

on:
  push:
    tags:
      - 'v*.*.*'
  release:
    types: [published]
  workflow_dispatch:

env:
  REGISTRY: ghcr.io

jobs:
  build:
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: wechat, image: wechat-on-cloud, context: ./docker, dockerfile: ./docker/Dockerfile, platform: linux/amd64, arch: amd64, runner: ubuntu-latest }
          - { name: wechat, image: wechat-on-cloud, context: ./docker, dockerfile: ./docker/Dockerfile, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm }
          - { name: panel, image: woc-panel, context: ./panel, dockerfile: ./panel/Dockerfile, platform: linux/amd64, arch: amd64, runner: ubuntu-latest }
          - { name: panel, image: woc-panel, context: ./panel, dockerfile: ./panel/Dockerfile, platform: linux/arm64, arch: arm64, runner: ubuntu-24.04-arm }
    runs-on: ${{ matrix.runner }}
    permissions:
      contents: read
      packages: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Resolve build version + lowercase owner
        id: ver
        run: |
          if [ "${{ github.ref_type }}" = "tag" ]; then
            echo "version=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
          else
            echo "version=dev-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
          fi
          # docker 镜像引用必须小写；github.repository_owner 可能含大写（Gloridust），手动构造 ref 前先转小写
          echo "owner=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT"

      # ── 产物门禁：仅在 wechat + amd64（原生 x86，可 docker run）上跑冒烟探针 ──
      # 「验证的东西和发布的东西必须是同一个 artifact」；DPI/CEF 类问题与架构无关，amd64 探针即可覆盖。
      - name: Build amd64 image for smoke test (instance only)
        if: matrix.name == 'wechat' && matrix.arch == 'amd64'
        uses: docker/build-push-action@v6
        with:
          context: ${{ matrix.context }}
          file: ${{ matrix.dockerfile }}
          platforms: linux/amd64
          load: true
          tags: woc-probe:ci
          cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch }}

      - name: Smoke probes (DPI / CEF 两次启动 / 崩溃循环 / 微信模式)
        if: matrix.name == 'wechat' && matrix.arch == 'amd64'
        run: |
          set -eu
          fail() { echo "::error::$1"; docker logs probe 2>&1 | tail -40 || true; exit 1; }
          docker run -d --name probe --security-opt seccomp=unconfined --shm-size=1g \
            -e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=chromium woc-probe:ci
          for i in $(seq 1 30); do docker exec probe pgrep xsettingsd >/dev/null 2>&1 && break; sleep 3; done
          docker exec probe bash -c 'DISPLAY=:1 dump_xsettings | grep -q "Xft/DPI 98304"' || fail "DPI 探针失败"
          echo "① DPI ✓"
          docker exec probe bash -c '
            for round in 1 2; do
              DISPLAY=:1 timeout 8 chromium --no-sandbox --disable-gpu --user-data-dir=/tmp/probe-profile about:blank >/dev/null 2>&1 &
              p=$!; sleep 7
              kill -0 $p 2>/dev/null || exit 1
              kill -9 $p 2>/dev/null; pkill -9 chromium 2>/dev/null; sleep 1
            done' || fail "CEF 探针崩溃（第 1 或第 2 次启动）"
          echo "② CEF 两次启动 ✓"
          sleep 30
          exits=$(docker logs --since 30s probe 2>&1 | grep -c '已退出' || true)
          [ "$exits" -eq 0 ] || fail "autostart 崩溃循环：30s 内退出 $exits 次"
          echo "③ 零崩溃 ✓"
          docker rm -f probe
          docker run -d --name probe --security-opt seccomp=unconfined --shm-size=1g \
            -e PUID=1000 -e PGID=1000 -e WOC_APP_TYPE=wechat woc-probe:ci
          ok=0
          for i in $(seq 1 30); do docker logs probe 2>&1 | grep -q "尚未安装" && ok=1 && break; sleep 3; done
          [ "$ok" = "1" ] || fail "微信模式未到达等待安装"
          echo "④ 微信模式 ✓"
          docker rm -f probe

      # 生成 OCI labels（含 org.opencontainers.image.version=<语义化版本>）。
      # 关键：实例镜像卡片的「镜像 vX.Y.Z」角标就读这个 label；不打 label 会露出 base 镜像的
      # 876361b9-ls121，用户以为没升级成功、也分不清版本（v1.4.4 的回归）。metadata-action 会覆盖 base label。
      - name: Docker metadata (labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ steps.ver.outputs.owner }}/${{ matrix.image }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable={{is_default_branch}}
            type=raw,value=latest,enable=${{ github.event_name == 'release' }}

      # ── 按 digest 推送到 GHCR（不打 tag）。同一内容跨仓库 digest 相同，merge 时再复制到 Docker Hub ──
      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@v6
        with:
          context: ${{ matrix.context }}
          file: ${{ matrix.dockerfile }}
          platforms: ${{ matrix.platform }}
          build-args: ${{ matrix.name == 'panel' && format('WOC_VERSION={0}', steps.ver.outputs.version) || '' }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha,scope=${{ matrix.name }}-${{ matrix.arch }}
          cache-to: type=gha,mode=max,scope=${{ matrix.name }}-${{ matrix.arch }}
          outputs: type=image,name=${{ env.REGISTRY }}/${{ steps.ver.outputs.owner }}/${{ matrix.image }},push-by-digest=true,name-canonical=true,push=true

      - name: Export digest
        run: |
          mkdir -p /tmp/digests
          digest="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: digest-${{ matrix.name }}-${{ matrix.arch }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    strategy:
      fail-fast: false
      matrix:
        include:
          - { name: wechat, image: wechat-on-cloud }
          - { name: panel, image: woc-panel }
    steps:
      - name: Download digests
        uses: actions/download-artifact@v4
        with:
          path: /tmp/digests
          pattern: digest-${{ matrix.name }}-*
          merge-multiple: true

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Log in to Docker Hub
        if: vars.DOCKERHUB_USERNAME != ''
        uses: docker/login-action@v3
        with:
          username: ${{ vars.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Lowercase owner
        id: vars
        run: echo "owner=${GITHUB_REPOSITORY_OWNER,,}" >> "$GITHUB_OUTPUT"

      - name: Docker metadata (tags + labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: |
            ${{ env.REGISTRY }}/${{ steps.vars.outputs.owner }}/${{ matrix.image }}
            ${{ vars.DOCKERHUB_USERNAME != '' && format('docker.io/{0}/{1}', vars.DOCKERHUB_USERNAME, matrix.image) || '' }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=semver,pattern={{major}}
            type=raw,value=latest,enable={{is_default_branch}}
            type=raw,value=latest,enable=${{ github.event_name == 'release' }}

      # 从两个 arch 的 digest（都在 GHCR）合成多架构 manifest，一条命令打上所有 tag（含 Docker Hub，
      # imagetools 会把 blob 从 GHCR 复制到 Docker Hub）。
      - name: Create and push multi-arch manifest
        working-directory: /tmp/digests
        run: |
          set -eux
          tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON")
          srcs=""
          for f in *; do srcs="$srcs ${REGISTRY}/${{ steps.vars.outputs.owner }}/${{ matrix.image }}@sha256:$f"; done
          docker buildx imagetools create $tags $srcs

      - name: Verify multi-arch manifest
        run: |
          set -eux
          docker buildx imagetools inspect "${REGISTRY}/${{ steps.vars.outputs.owner }}/${{ matrix.image }}:${{ steps.meta.outputs.version }}"
