diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 8b6503f..8bdaf89 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -1,105 +1,152 @@ -name: Build & Push Docker image +name: CI and Docker image on: workflow_dispatch: inputs: tag: - description: 'Docker 标签' + description: 'Optional extra Docker tag to publish' required: false - default: 'latest' + default: '' type: string push: - branches: [ main, master ] + branches: + - main + - 'codex/**' + tags: + - 'v*' pull_request: - branches: [ main, master ] + branches: + - main concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: - contents: write + contents: read packages: write - actions: write + +env: + IMAGE_NAME: ghcr.io/lihaowang/orangetv + NODE_VERSION: 24.14.1 + PNPM_VERSION: 10.14.0 jobs: - build: - strategy: - matrix: - include: - - platform: linux/amd64 - os: ubuntu-latest - - platform: linux/arm64 - os: ubuntu-24.04-arm - runs-on: ${{ matrix.os }} + quality: + name: Quality gates + runs-on: ubuntu-latest steps: - - name: Prepare platform name - run: | - echo "PLATFORM_NAME=${{ matrix.platform }}" | sed 's|/|-|g' >> $GITHUB_ENV + - name: Checkout source code + uses: actions/checkout@v4 + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: ${{ env.PNPM_VERSION }} + run_install: false + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Lint + run: pnpm lint + + - name: Test + run: pnpm test --runInBand + + - name: Build + run: pnpm build + + docker-smoke: + name: Docker smoke + runs-on: ubuntu-latest + needs: quality + + steps: - name: Checkout source code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set lowercase repository owner - id: lowercase - run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - - with: - images: ghcr.io/moontechlab/lunatv - tags: | - type=raw,value=${{ github.event.inputs.tag || 'latest' }},enable={{is_default_branch}} - - - name: Build and push by digest - id: build - uses: docker/build-push-action@v5 + - name: Build local smoke image + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - tags: ghcr.io/moontechlab/lunatv:${{ github.event.inputs.tag || 'latest' }} - outputs: type=image,name=ghcr.io/moontechlab/lunatv,name-canonical=true,push=true + platforms: linux/amd64 + load: true + tags: orangetv-actions-smoke:ci + cache-from: type=gha + cache-to: type=gha,mode=max - - name: Export digest + - name: Run container smoke checks + shell: bash run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" + set -euo pipefail - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_NAME }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 + container_id="$( + docker run -d \ + -p 3000:3000 \ + -e USERNAME=admin \ + -e PASSWORD=orange \ + -e VITE_STORAGE_TYPE=localstorage \ + orangetv-actions-smoke:ci + )" - merge: + cleanup() { + docker rm -f "$container_id" >/dev/null 2>&1 || true + } + trap cleanup EXIT + + for attempt in {1..30}; do + if curl -fsS http://localhost:3000/api/health >/dev/null; then + break + fi + + if [ "$attempt" -eq 30 ]; then + docker logs "$container_id" + exit 1 + fi + + sleep 2 + done + + curl -fsS http://localhost:3000/api/health + curl -fsS http://localhost:3000/runtime-config.js | grep 'window.RUNTIME_CONFIG' + curl -fsSI http://localhost:3000/login | grep '200 OK' + + docker-publish: + name: Publish Docker image runs-on: ubuntu-latest needs: - - build + - quality + - docker-smoke + if: >- + github.event_name != 'pull_request' && + ( + github.ref == 'refs/heads/main' || + startsWith(github.ref, 'refs/heads/codex/') || + startsWith(github.ref, 'refs/tags/v') || + github.event_name == 'workflow_dispatch' + ) + steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -111,26 +158,28 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set lowercase repository owner - id: lowercase - run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create -t ghcr.io/moontechlab/lunatv:${{ github.event.inputs.tag || 'latest' }} \ - $(printf 'ghcr.io/moontechlab/lunatv@sha256:%s ' *) - - cleanup-refresh: - runs-on: ubuntu-latest - needs: - - merge - if: always() - steps: - - name: Delete workflow runs - uses: Mattraks/delete-workflow-runs@main + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 with: - token: ${{ secrets.GITHUB_TOKEN }} - repository: ${{ github.repository }} - retain_days: 0 - keep_minimum_runs: 2 + images: ${{ env.IMAGE_NAME }} + flavor: latest=false + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch,prefix=branch-,enable=${{ startsWith(github.ref, 'refs/heads/codex/') }} + type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag != '' }} + type=sha,prefix=sha-,format=short + + - name: Build and push multi-arch image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + labels: ${{ steps.meta.outputs.labels }} + tags: ${{ steps.meta.outputs.tags }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/README.md b/README.md index e77bc3b..9834b48 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,18 @@ 本项目**仅支持 Docker 或其他基于 Docker 的平台** 部署。 +主镜像发布到 `ghcr.io/lihaowang/orangetv:latest`。测试当前 refactor 分支构建时,可以先拉取分支标签: + +```bash +docker pull ghcr.io/lihaowang/orangetv:branch-codex-vite-fastify-refactor-plan +``` + ### Kvrocks 存储(推荐) ```yml services: OrangeTV-core: - image: ghcr.io/djteang/orangetv:latest + image: ghcr.io/lihaowang/orangetv:latest container_name: OrangeTV-core restart: on-failure ports: @@ -115,7 +121,7 @@ volumes: ```yml services: OrangeTV-core: - image: ghcr.io/djteang/orangetv:latest + image: ghcr.io/lihaowang/orangetv:latest container_name: OrangeTV-core restart: on-failure ports: @@ -151,7 +157,7 @@ networks: ```yml services: OrangeTV-core: - image: ghcr.io/djteang/orangetv:latest + image: ghcr.io/lihaowang/orangetv:latest container_name: OrangeTV-core restart: on-failure ports: