diff --git a/.gitea/workflows/deploy-b9.yml b/.gitea/workflows/deploy-b9.yml new file mode 100644 index 0000000..55a2178 --- /dev/null +++ b/.gitea/workflows/deploy-b9.yml @@ -0,0 +1,105 @@ +name: Deploy B9 + +on: + push: + branches: + - b9 + workflow_dispatch: + inputs: + environment: + description: Deployment environment + type: choice + options: + - prod + - test + default: prod + required: true + +concurrency: + group: deploy-b9 + cancel-in-progress: false + +jobs: + deploy: + runs-on: windows-cross + timeout-minutes: 30 + env: + TARGET_ENV: ${{ inputs.environment || 'prod' }} + AWS_PAGER: '' + AWS_EC2_METADATA_DISABLED: 'true' + + steps: + - name: Checkout + uses: https://github.com/actions/checkout@v4 + with: + persist-credentials: false + + - name: Resolve deployment directory + env: + DEPLOY_REF: ${{ github.ref }} + DEPLOY_BRANCH: ${{ github.ref_name }} + run: | + set -eu + case "$DEPLOY_REF" in + refs/heads/*) ;; + *) echo "Deployment requires a branch"; exit 1 ;; + esac + case "$DEPLOY_BRANCH" in + main) PROJECT=x8 ;; + x9|b9|bd9|bh9) PROJECT="$DEPLOY_BRANCH" ;; + *) echo "Unsupported device branch"; exit 1 ;; + esac + echo "DEPLOY_PROJECT_NAME=$PROJECT" >> "$GITHUB_ENV" + echo "Deploying branch $DEPLOY_BRANCH to S3 directory $PROJECT" + + - name: Set up Node.js + uses: https://github.com/actions/setup-node@v4 + with: + node-version: '24' + + - name: Install pnpm + run: npm install --global pnpm@11.5.2 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build legacy and modern applications + env: + VITE_AI_API_URL: ${{ vars.VITE_AI_API_URL }} + run: | + set -eu + case "$TARGET_ENV" in + prod) pnpm run build ;; + test) pnpm run build:test ;; + *) echo "Unsupported deployment environment"; exit 1 ;; + esac + test -s dist/i.html + test -s dist/legacy/i.legacy.html + test -s dist/modern/i.modern.html + + - name: Install AWS CLI + run: | + set -eu + if ! command -v aws >/dev/null 2>&1; then + CLI_DIR=$(mktemp -d) + curl --fail --silent --show-error --location \ + --connect-timeout 20 --max-time 300 \ + https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip \ + --output "$CLI_DIR/awscliv2.zip" + unzip -q "$CLI_DIR/awscliv2.zip" -d "$CLI_DIR" + "$CLI_DIR/aws/install" --install-dir "$CLI_DIR/installed" --bin-dir /usr/local/bin + fi + aws --version + + - name: Deploy to S3 and invalidate CloudFront + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_SESSION_TOKEN: ${{ secrets.AWS_SESSION_TOKEN }} + AWS_DEFAULT_REGION: ${{ vars.AWS_REGION }} + run: | + set -eu + test -n "$AWS_ACCESS_KEY_ID" || { echo "Missing secret AWS_ACCESS_KEY_ID"; exit 1; } + test -n "$AWS_SECRET_ACCESS_KEY" || { echo "Missing secret AWS_SECRET_ACCESS_KEY"; exit 1; } + test -n "$AWS_DEFAULT_REGION" || { echo "Missing variable AWS_REGION"; exit 1; } + bash scripts/deploy.sh "$TARGET_ENV" -yr diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..7949085 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,47 @@ +# B9 自动部署 + +Gitea workflow 位于 `.gitea/workflows/deploy-b9.yml`。 + +## 触发方式 + +- 推送 `b9` 分支:构建正式版,同步到 `s3://luxsin-app-bucket/b9/v2/`,刷新 CloudFront `EUS4XQ8Y0W2ZV` 的 `/b9/v2/*`。 +- Actions 手动运行:可选择 `prod` 或 `test`;测试环境同步到 `s3://luxsin-app-bucket/b9/test/`,刷新 `/b9/test/*`。 +- 使用现有 `windows-cross` Runner(Linux x64 Docker 镜像),安装 Node.js 24、pnpm 11.5.2 和 AWS CLI。 +- 同一时间只运行一个 B9 部署,避免多个同步任务互相覆盖。 + +## 一次性配置 + +在实际托管 `b9` 分支的 Gitea 仓库中,进入「设置 → Actions」: + +### Secrets + +| 名称 | 用途 | +| --- | --- | +| `AWS_ACCESS_KEY_ID` | 部署账号的访问密钥 ID | +| `AWS_SECRET_ACCESS_KEY` | 对应的访问密钥 | +| `AWS_SESSION_TOKEN` | 仅临时凭据需要;过期后需要更新整组凭据 | + +不要提交本机 `.aws` 或把密钥写入 workflow。部署身份应仅有目标桶 `b9/v2/`、`b9/test/` 前缀所需的同步读写删除权限,以及指定 CloudFront 分配的创建失效权限;若存储桶使用自定义 KMS 加密,还需相应 KMS 权限。 + +### Variables + +| 名称 | 用途 | +| --- | --- | +| `AWS_REGION` | 填写现有 S3 存储桶所在区域,必填 | +| `VITE_AI_API_URL` | 生产可访问的 AI 服务地址,可选;未填时使用应用内置默认地址 | + +Runner 必须能领取此仓库的任务,并能够访问 GitHub Actions、npm、AWS CLI 下载站、S3 和 CloudFront API。当前本地 origin 为 `https://repo.luxsin.com.cn/audio/controller-v2.git`,需要在这台 Gitea 对应仓库配置 Runner 和上述参数;旧 Gitea 的仓库级 Runner 不会自动跨实例生效。 + +## 部署规则 + +复用 `scripts/deploy.sh`,CI 执行 `DEPLOY_PROJECT_NAME=b9 bash scripts/deploy.sh prod -yr`。`-y` 跳过人工确认,`-r` 刷新缓存;本地未加 `-y` 时仍需确认。 + +默认上传 images,与现有脚本默认行为一致。`aws s3 sync --delete` 会删除目标前缀中本次构建不存在的对象,部署前会校验入口文件避免上传空目录。需要排除 images 的手工部署仍可使用 `-i`。 + +每次部署会产生 S3 请求和 CloudFront 缓存失效请求,按账号实际用量计费。workflow 不上传或读取本机未跟踪的 `.env`,构建所需公开配置应放入 Actions Variables。 + +## 分支与型号目录 + +`main` 对应 `x8`,其余已知型号分支 `x9`、`b9`、`bd9`、`bh9` 使用分支名作为目录。CI 会设置 `DEPLOY_PROJECT_NAME`,构建路径与部署目标保持一致;本地脚本也支持相同映射。 + +目前自动触发范围仍仅为 `b9`,未扩大其他分支的自动生产部署。其他分支需包含对应 workflow,启用前需确认部署范围;不要覆盖各分支的型号专用代码。AWS 权限也应按实际启用的型号前缀配置。 diff --git a/scripts/deploy.sh b/scripts/deploy.sh index b348043..3ecfb5f 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -21,6 +21,7 @@ show_help() { 选项(可合并,如 -ir): -i 排除 images 目录 (*images/*) -r 上传完成后刷新 CloudFront 缓存 + -y 跳过确认(用于 CI) -h 显示此帮助 示例: @@ -32,20 +33,22 @@ show_help() { EOF } -# 自动识别项目名称 (x8 / x9),依据脚本所在的项目根目录名 +# CI 显式传入型号;本地按 Git 分支识别,非 Git 目录则回退到目录名。 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(basename "$(dirname "$SCRIPT_DIR")")" -PROJECT_NAME="$(echo "$PROJECT_ROOT" | grep -oE '^(x8|x9|b9)')" - -if [ -z "$PROJECT_NAME" ]; then - echo "错误: 无法从路径 '$PROJECT_ROOT' 识别项目名称 (x8/x9/b9)" - exit 1 -fi +PROJECT_BRANCH="$(git -C "$SCRIPT_DIR" symbolic-ref --quiet --short HEAD 2>/dev/null || true)" +PROJECT_NAME="${DEPLOY_PROJECT_NAME:-${PROJECT_BRANCH:-${PROJECT_ROOT%-controller}}}" +case "$PROJECT_NAME" in + main) PROJECT_NAME="x8" ;; + x8|x9|b9|bd9|bh9) ;; + *) echo "错误: 不支持的型号分支 '$PROJECT_NAME'"; exit 1 ;; +esac # 解析参数 TARGET_ENV="prod" EXCLUDE_IMAGES=false RELOAD=false +ASSUME_YES=false for arg in "$@"; do case "$arg" in @@ -70,6 +73,9 @@ for arg in "$@"; do i) EXCLUDE_IMAGES=true ;; + y) + ASSUME_YES=true + ;; r) RELOAD=true ;; @@ -121,10 +127,21 @@ echo "命令: ${CMD_ARGS[*]}" echo "==========================================" echo "" -read -r -p "确认执行部署? 输入 y 继续: " confirm -if [[ ! "$confirm" =~ ^[Yy]$ ]]; then - echo "已取消部署。" - exit 0 +# 在项目根目录执行,避免 CI 的当前目录影响同步源。 +cd "$SCRIPT_DIR/.." +for entry in i.html legacy/i.legacy.html modern/i.modern.html; do + if [ ! -s "dist/$entry" ]; then + echo "错误: 缺少构建产物 dist/$entry,请先完成构建。" + exit 1 + fi +done + +if [ "$ASSUME_YES" != true ]; then + read -r -p "确认执行部署? 输入 y 继续: " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "已取消部署。" + exit 0 + fi fi echo "" diff --git a/vite.config.ts b/vite.config.ts index 58a4ad1..046e498 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -53,9 +53,10 @@ const plugins = [ /* ── Deploy base path ── */ const DEPLOY_ENV = process.env.DEPLOY_ENV || "production"; +const DEPLOY_PROJECT = process.env.DEPLOY_PROJECT_NAME || "b9"; const isProd = process.env.NODE_ENV === "production"; const deployBase = isProd - ? (DEPLOY_ENV === "test" ? "/b9/test/modern/" : "/b9/v2/modern/") + ? (DEPLOY_ENV === "test" ? `/${DEPLOY_PROJECT}/test/modern/` : `/${DEPLOY_PROJECT}/v2/modern/`) : "/"; export default defineConfig({ diff --git a/vite.legacy.config.ts b/vite.legacy.config.ts index 98a7c1a..0807459 100644 --- a/vite.legacy.config.ts +++ b/vite.legacy.config.ts @@ -46,9 +46,10 @@ function legacySkipModernCssPlugin(): Plugin { /* ── Deploy base path ── */ const DEPLOY_ENV = process.env.DEPLOY_ENV || "production"; +const DEPLOY_PROJECT = process.env.DEPLOY_PROJECT_NAME || "b9"; const isProd = process.env.NODE_ENV === "production"; const deployBase = isProd - ? (DEPLOY_ENV === "test" ? "/b9/test/legacy/" : "/b9/v2/legacy/") + ? (DEPLOY_ENV === "test" ? `/${DEPLOY_PROJECT}/test/legacy/` : `/${DEPLOY_PROJECT}/v2/legacy/`) : "/"; export default defineConfig({