diff --git a/.dockerignore b/.dockerignore index 2d1feac..d74733c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,5 @@ .env -.env*.local \ No newline at end of file +.env*.local +public/screenshot1.png +public/screenshot2.png +public/screenshot3.png diff --git a/.github/workflows/docker-image-lite.yml b/.github/workflows/docker-image-lite.yml new file mode 100644 index 0000000..f42b747 --- /dev/null +++ b/.github/workflows/docker-image-lite.yml @@ -0,0 +1,125 @@ +name: Build & Push Docker lite image + +on: + workflow_dispatch: + inputs: + tag: + description: 'Docker 标签' + required: false + default: 'latest' + type: string + push: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + packages: write + actions: write + +jobs: + build: + strategy: + matrix: + include: + - platform: linux/amd64 + os: ubuntu-latest + - platform: linux/arm64 + os: ubuntu-24.04-arm + runs-on: ${{ matrix.os }} + + 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 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: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/mtvpls/moontvplus-lite + tags: | + type=raw,value=${{ github.event.inputs.tag || 'latest' }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile.lite + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: ghcr.io/mtvpls/moontvplus-lite:${{ github.event.inputs.tag || 'latest' }} + outputs: type=image,name=ghcr.io/mtvpls/moontvplus-lite,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: digests-${{ env.PLATFORM_NAME }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - 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: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create -t ghcr.io/mtvpls/moontvplus-lite:${{ github.event.inputs.tag || 'latest' }} \ + $(printf 'ghcr.io/mtvpls/moontvplus-lite@sha256:%s ' *) + + cleanup-refresh: + runs-on: ubuntu-latest + needs: + - merge + if: always() + steps: + - name: Delete workflow runs + uses: Mattraks/delete-workflow-runs@main + with: + token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + retain_days: 0 + keep_minimum_runs: 2 diff --git a/Dockerfile.lite b/Dockerfile.lite new file mode 100644 index 0000000..85088d7 --- /dev/null +++ b/Dockerfile.lite @@ -0,0 +1,52 @@ +# ---- 第 1 阶段:安装依赖 ---- +FROM node:24-alpine AS deps + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml ./ + +RUN pnpm install --frozen-lockfile + +# ---- 第 2 阶段:构建项目 ---- +FROM node:24-alpine AS builder +RUN corepack enable && corepack prepare pnpm@latest --activate +WORKDIR /app + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +ENV DOCKER_ENV=true +ENV MOONTV_LITE=true +ENV WATCH_ROOM_ENABLED=false +ENV WATCH_ROOM_SERVER_TYPE=external + +RUN pnpm run build + +# ---- 第 3 阶段:生成 lite 运行时镜像 ---- +FROM node:24-alpine AS runner + +RUN addgroup -g 1001 -S nodejs && adduser -u 1001 -S nextjs -G nodejs + +WORKDIR /app +ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 +ENV DOCKER_ENV=true +ENV MOONTV_LITE=true +ENV WATCH_ROOM_ENABLED=false +ENV WATCH_ROOM_SERVER_TYPE=external + +# standalone 输出自带运行所需的最小服务端依赖 +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts +COPY --from=builder --chown=nextjs:nodejs /app/start.js ./start.js +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "start.js"] diff --git a/README.md b/README.md index be691e4..7f2d057 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,44 @@ services: - UPSTASH_TOKEN=上面的 TOKEN ``` +#### Lite 镜像说明 + +`ghcr.io/mtvpls/moontvplus-lite:latest` 为更小的镜像,但不支持启动内置观影室服务。 + +示例: + +```yml +services: + moontv-core: + image: ghcr.io/mtvpls/moontvplus-lite:latest + container_name: moontv-core + restart: on-failure + ports: + - '3000:3000' + environment: + - USERNAME=admin + - PASSWORD=admin_password + - NEXT_PUBLIC_STORAGE_TYPE=kvrocks + - KVROCKS_URL=redis://moontv-kvrocks:6666 + networks: + - moontv-network + depends_on: + - moontv-kvrocks + moontv-kvrocks: + image: apache/kvrocks + container_name: moontv-kvrocks + restart: unless-stopped + volumes: + - kvrocks-data:/var/lib/kvrocks/data + networks: + - moontv-network +networks: + moontv-network: + driver: bridge +volumes: + kvrocks-data: +``` + ## 配置文件 完成部署后为空壳应用,无播放源,需要站长在管理后台的配置文件设置中填写配置文件,本版本已不支持无数据库运行。 diff --git a/src/app/api/server-config/route.ts b/src/app/api/server-config/route.ts index 1e60a95..def5431 100644 --- a/src/app/api/server-config/route.ts +++ b/src/app/api/server-config/route.ts @@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { getConfig } from '@/lib/config'; -import { CURRENT_VERSION } from '@/lib/version' +import { CURRENT_VERSION } from '@/lib/version'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; // 禁用缓存 @@ -13,14 +13,22 @@ export async function GET(request: NextRequest) { const storageType = process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage'; - // 观影室配置从环境变量读取 + const isLiteMode = process.env.MOONTV_LITE === 'true'; + + // Lite 镜像不暴露内置观影室能力,避免前端尝试连接本地 Socket.IO 服务 // 注意:不要暴露 externalServerAuth 到前端,这是敏感凭据 - const watchRoomConfig = { - enabled: process.env.WATCH_ROOM_ENABLED === 'true', - serverType: (process.env.WATCH_ROOM_SERVER_TYPE as 'internal' | 'external') || 'internal', - externalServerUrl: process.env.WATCH_ROOM_EXTERNAL_SERVER_URL, - // externalServerAuth 不应该暴露给前端 - }; + const watchRoomConfig = isLiteMode + ? { + enabled: false, + serverType: 'external' as const, + externalServerUrl: undefined, + } + : { + enabled: process.env.WATCH_ROOM_ENABLED === 'true', + serverType: + (process.env.WATCH_ROOM_SERVER_TYPE as 'internal' | 'external') || 'internal', + externalServerUrl: process.env.WATCH_ROOM_EXTERNAL_SERVER_URL, + }; // 如果使用 localStorage,返回默认配置 if (storageType === 'localstorage') {