Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 84 additions & 46 deletions .github/workflows/deploy-tencent.yml
Original file line number Diff line number Diff line change
@@ -1,78 +1,116 @@
name: Deploy Tencent
name: Deploy Tencent Docker

permissions:
contents: read

on:
pull_request:
paths:
- apps/web/.dockerignore
- apps/web/Dockerfile
- apps/web/next.config.ts
- apps/web/app/api/health/**
- apps/web/package.json
- apps/web/package-lock.json
- deploy/**
- .github/workflows/deploy-tencent.yml
push:
branches:
- main
- release
workflow_dispatch:

concurrency:
group: deploy-tencent-${{ github.ref }}
group: deploy-tencent-docker-${{ github.ref }}
cancel-in-progress: false

env:
IMAGE_NAME: meteortest-web

jobs:
build:
name: Validate deployment image
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

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

- name: Build image
if: github.event_name == 'pull_request'
uses: docker/build-push-action@v6
with:
context: apps/web
file: apps/web/Dockerfile
platforms: linux/amd64
tags: ${{ env.IMAGE_NAME }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Upload validated source artifact
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v4
with:
name: meteortest-source-${{ github.sha }}
path: |
apps/web
deploy/docker-compose.yml
deploy/deploy-container.sh
!apps/web/node_modules
!apps/web/.next
retention-days: 7

deploy:
name: Deploy Web
name: Deploy Web container
if: github.event_name != 'pull_request'
needs: build
runs-on: [self-hosted, linux, x64, tencent, meteortest]
environment: tencent
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download validated source artifact
uses: actions/download-artifact@v4
with:
name: meteortest-source-${{ github.sha }}
path: source

- name: Resolve target
id: target
shell: bash
run: |
if [ "${GITHUB_REF_NAME}" = "release" ]; then
echo "app_dir=/srv/meteortest-release" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteortest-release" >> "$GITHUB_OUTPUT"
echo "environment=production" >> "$GITHUB_OUTPUT"
echo "port=3200" >> "$GITHUB_OUTPUT"
echo "shadow_port=3210" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteortest-release" >> "$GITHUB_OUTPUT"
echo "public_url=https://meteortest.jcmeteor.com/" >> "$GITHUB_OUTPUT"
else
echo "app_dir=/srv/meteortest" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteortest-web" >> "$GITHUB_OUTPUT"
echo "environment=preview" >> "$GITHUB_OUTPUT"
echo "port=3201" >> "$GITHUB_OUTPUT"
echo "shadow_port=3211" >> "$GITHUB_OUTPUT"
echo "pm2_name=meteortest-web" >> "$GITHUB_OUTPUT"
echo "public_url=https://mt-pre.jcmeteor.com/" >> "$GITHUB_OUTPUT"
fi

- name: Build image from artifact
run: >-
docker build
--build-arg NPM_REGISTRY=https://registry.npmmirror.com
--tag "meteortest-web:${GITHUB_SHA}"
--file source/apps/web/Dockerfile
source/apps/web

- name: Deploy
shell: bash
env:
APP_NAME: meteortest
ENVIRONMENT: ${{ steps.target.outputs.environment }}
HOST_PORT: ${{ steps.target.outputs.port }}
IMAGE_URI: meteortest-web:${{ github.sha }}
PM2_NAME: ${{ steps.target.outputs.pm2_name }}
PUBLIC_URL: ${{ steps.target.outputs.public_url }}
RUNTIME_ENV_FILE: /etc/meteortest/meteortest-web.env
SHADOW_PORT: ${{ steps.target.outputs.shadow_port }}
run: |
retry() {
local attempts="$1"
local delay="$2"
shift 2

local attempt=1
until "$@"; do
if [ "$attempt" -ge "$attempts" ]; then
return 1
fi

echo "Command failed. Retrying in ${delay}s (${attempt}/${attempts})..."
sleep "$delay"
attempt=$((attempt + 1))
done
}

mkdir -p '${{ steps.target.outputs.app_dir }}'
rsync -a --delete \
--exclude '.git' \
--exclude 'apps/web/.next' \
--exclude 'apps/web/node_modules' \
./ '${{ steps.target.outputs.app_dir }}/'

cd '${{ steps.target.outputs.app_dir }}/apps/web'
retry 3 15 npm ci
set -a
. /etc/meteortest/meteortest-web.env
set +a
retry 2 15 npm run build

pm2 delete '${{ steps.target.outputs.pm2_name }}' || true
unset RUNNER_TRACKING_ID
pm2 start npm --name '${{ steps.target.outputs.pm2_name }}' -- run start -- --hostname 127.0.0.1 --port '${{ steps.target.outputs.port }}'
pm2 save
retry 12 5 curl -fsS 'http://127.0.0.1:${{ steps.target.outputs.port }}/' >/dev/null
chmod +x source/deploy/deploy-container.sh
source/deploy/deploy-container.sh
51 changes: 51 additions & 0 deletions .github/workflows/release-manager.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Release Manager

permissions:
contents: write
issues: write
pull-requests: write
actions: read
checks: read

on:
workflow_dispatch:
inputs:
version:
description: Release version, for example 0.1.3
required: true
type: string
action:
description: Release action
required: true
default: full
type: choice
options:
- full
- prepare
- promote
- verify

concurrency:
group: release-manager
cancel-in-progress: false

jobs:
release:
name: Release Manager
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_MANAGER_TOKEN || github.token }}

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24

- name: Run release manager
env:
GH_TOKEN: ${{ secrets.RELEASE_MANAGER_TOKEN || github.token }}
run: node scripts/release-manager.mjs "${{ inputs.action }}" --version "${{ inputs.version }}"
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ Release policy:
- `release` is the Tencent production deployment branch.
- Keep feature work flowing into `main` first, then promote `main` to `release` through a PR after validation.
- Do not cherry-pick selectively into `release` unless the user explicitly requests an emergency hotfix; sync any hotfix back to `main`.
- Tencent deployment mapping is documented in `docs/tencent-release-deployment.md`.
- Tencent Docker deployment mapping and PM2 migration are documented in `docs/tencent-docker-deployment.md`.

Branch names should use:

Expand Down
2 changes: 1 addition & 1 deletion PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
- Supabase 执行手册:`docs/supabase-account-data-runbook.zh-CN.md`
- Local Agent 运维:`docs/local-agent-operations.zh-CN.md`
- 公网预览部署:`docs/vercel-public-preview.zh-CN.md`
- 腾讯云 Release 部署:`docs/tencent-release-deployment.zh-CN.md`
- 腾讯云 Docker 部署:`docs/tencent-docker-deployment.zh-CN.md`
- 私有 Agent 闭环验证:`docs/private-agent-preview-loop.zh-CN.md`
- 内部 ID / DTO 边界:`docs/internal-id-exposure-hardening.zh-CN.md`

Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,15 @@ main -> mt-pre.jcmeteor.com
release -> meteortest.jcmeteor.com
```

The detailed runner, branch, ruleset, environment, and port mapping lives in `docs/tencent-release-deployment.md`.
The detailed Docker/source artifact architecture, runner, branch, environment, port mapping, PM2 migration, and rollback flow lives in `docs/tencent-docker-deployment.md`.

Production publishing is automated by the GitHub Actions `Release Manager` workflow:

```text
GitHub -> Actions -> Release Manager -> Run workflow
```

Use `action=full` with a semantic version such as `0.1.3`. The workflow prepares release files, promotes `main` to `release`, waits for Tencent deployment, verifies public URLs, and creates the GitHub Release. Detailed operation and recovery steps live in `docs/release-manager.md`.

## Recommended Validation Flow

Expand Down
10 changes: 9 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,15 @@ main -> mt-pre.jcmeteor.com
release -> meteortest.jcmeteor.com
```

runner、分支、ruleset、环境变量和端口映射详见 `docs/tencent-release-deployment.zh-CN.md`。
Docker/Actions Artifact 架构、runner、分支、环境变量、端口映射、PM2 迁移和回滚流程详见 `docs/tencent-docker-deployment.zh-CN.md`。

生产发布通过 GitHub Actions 的 `Release Manager` workflow 自动化:

```text
GitHub -> Actions -> Release Manager -> Run workflow
```

选择 `action=full`,输入类似 `0.1.3` 的语义化版本号。workflow 会准备 release 文件、将 `main` 提升到 `release`、等待腾讯云部署、验证公网 URL,并创建 GitHub Release。详细操作和中断恢复步骤见 `docs/release-manager.md`。

## 推荐验证流程

Expand Down
10 changes: 10 additions & 0 deletions apps/web/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.git
.next
node_modules
.env
.env.*
!.env.example
*.log
coverage
playwright-report
test-results
33 changes: 33 additions & 0 deletions apps/web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
FROM node:24-bookworm-slim AS dependencies

WORKDIR /app
ARG NPM_REGISTRY=https://registry.npmjs.org
COPY package.json package-lock.json ./
RUN npm ci --registry="${NPM_REGISTRY}"

FROM dependencies AS builder

COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

FROM node:24-bookworm-slim AS runtime

ENV NODE_ENV=production \
NEXT_TELEMETRY_DISABLED=1 \
HOSTNAME=0.0.0.0 \
PORT=3000

WORKDIR /app

RUN groupadd --system --gid 1001 nodejs \
&& useradd --system --uid 1001 --gid nodejs nextjs

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/public ./public

USER nextjs
EXPOSE 3000

CMD ["node", "server.js"]
8 changes: 8 additions & 0 deletions apps/web/app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Process-local health endpoint used by Docker and deployment checks.
*/
import { NextResponse } from 'next/server'

export function GET() {
return NextResponse.json({ service: 'meteortest-web', status: 'ok' })
}
1 change: 1 addition & 0 deletions apps/web/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { NextConfig } from "next";

const nextConfig: NextConfig = {
devIndicators: false,
output: "standalone",
};

if (process.env.VERCEL === "1") {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "meteortest-web",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { createServerClient } from '@supabase/ssr'

const publicPaths = new Set(['/login', '/api/auth/logout', '/favicon.ico'])
const publicPaths = new Set(['/login', '/api/auth/logout', '/api/health', '/favicon.ico'])
const authOptionalPaths = new Set(['/api/agent/status'])

async function getSessionResponse(request: NextRequest) {
Expand Down
Loading
Loading