b8d3f3aa16
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 3m32s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 3m43s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m35s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 4m47s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m55s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m58s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 5m36s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m41s
AI Code Review / AI Code Review (pull_request) Successful in 9m59s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 10m41s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 21m53s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 3m34s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 6m14s
1. Production 模板 SMTP_HOST 改为空值(与实际一致)
2. ci-pipeline.yml MinIO Bucket Secret 名称修正为 STAGING_MINIO_BUCKET
- render_env.sh 增加特殊映射:STAGING_MINIO_BUCKET → MINIO_BUCKET_NAME
3. 修正模板中与实际运行不一致的配置值:
Staging: JWT_ACCESS_TOKEN_EXPIRE_MINUTES=1440, COSYVOICE_VOICE=longxiaoxia_v3,
COSYVOICE_FORMAT=wav, CORS_ORIGINS_RAW 使用 staging-api
Production: COSYVOICE_VOICE=longxiaoxia_v3, COSYVOICE_FORMAT=wav,
CORS_ORIGINS_RAW 使用 api(非 www)
143 lines
4.4 KiB
Bash
143 lines
4.4 KiB
Bash
#!/usr/bin/env bash
|
||
# ===========================================================
|
||
# render_env.sh — 从模板 + Secrets 渲染 .env 文件
|
||
# ===========================================================
|
||
# 用法: scripts/render_env.sh <staging|production>
|
||
#
|
||
# 输入: deploy/configs/.env.staging 或 .env.production 模板
|
||
# 输出: .env.rendered(包含真实密钥,切勿提交或打印)
|
||
#
|
||
# 环境变量映射规则:
|
||
# STAGING_xxx / PRODUCTION_xxx → xxx(去掉环境前缀)
|
||
# 共用 secrets 直接使用(如 OSS_ACCESS_KEY_ID)
|
||
# ===========================================================
|
||
set -eu
|
||
|
||
TARGET_ENV="${1:-}"
|
||
|
||
if [ -z "$TARGET_ENV" ] || { [ "$TARGET_ENV" != "staging" ] && [ "$TARGET_ENV" != "production" ]; }; then
|
||
echo "ERROR: 用法: $0 <staging|production>" >&2
|
||
exit 1
|
||
fi
|
||
|
||
TEMPLATE_FILE="deploy/configs/.env.${TARGET_ENV}"
|
||
OUTPUT_FILE=".env.rendered"
|
||
|
||
if [ ! -f "$TEMPLATE_FILE" ]; then
|
||
echo "ERROR: 模板文件不存在: $TEMPLATE_FILE" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 构建环境变量映射(带环境前缀的 secrets → 模板变量名)
|
||
ENV_PREFIX=$(echo "$TARGET_ENV" | tr '[:lower:]' '[:upper:]')
|
||
|
||
# 需要映射的带环境前缀变量
|
||
MAPPED_VARS="DATABASE_URL REDIS_URL CELERY_BROKER_URL CELERY_RESULT_BACKEND JWT_SECRET_KEY"
|
||
|
||
# Staging 独有的 MinIO 变量
|
||
if [ "$TARGET_ENV" = "staging" ]; then
|
||
MAPPED_VARS="$MAPPED_VARS MINIO_ENDPOINT MINIO_ACCESS_KEY MINIO_SECRET_KEY"
|
||
fi
|
||
|
||
# 将带前缀的 secrets 导出为无前缀的环境变量
|
||
for var in $MAPPED_VARS; do
|
||
prefixed_var="${ENV_PREFIX}_${var}"
|
||
value="${!prefixed_var:-}"
|
||
if [ -n "$value" ]; then
|
||
export "$var=$value"
|
||
fi
|
||
done
|
||
|
||
# 特殊映射:CI secret 名称与模板占位符不一致的变量
|
||
# STAGING_MINIO_BUCKET → MINIO_BUCKET_NAME
|
||
if [ "$TARGET_ENV" = "staging" ]; then
|
||
if [ -n "${STAGING_MINIO_BUCKET:-}" ]; then
|
||
export "MINIO_BUCKET_NAME=$STAGING_MINIO_BUCKET"
|
||
fi
|
||
fi
|
||
|
||
# 共用 secrets 直接导出(如果存在)
|
||
SHARED_SECRETS="OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET COSYVOICE_API_KEY DASHSCOPE_API_KEY MEDIAKIT_API_KEY"
|
||
for var in $SHARED_SECRETS; do
|
||
value="${!var:-}"
|
||
# 已经在环境中了,无需额外操作
|
||
done
|
||
|
||
# 使用 Python 进行变量替换(Python 在 CI runner 中一定存在)
|
||
python3 - "$TEMPLATE_FILE" "$OUTPUT_FILE" "$ENV_PREFIX" "$MAPPED_VARS" "$SHARED_SECRETS" <<'PYTHON_SCRIPT'
|
||
import sys
|
||
import os
|
||
import re
|
||
|
||
template_file = sys.argv[1]
|
||
output_file = sys.argv[2]
|
||
env_prefix = sys.argv[3]
|
||
mapped_vars_str = sys.argv[4]
|
||
shared_secrets_str = sys.argv[5]
|
||
|
||
# 收集所有可用的替换变量
|
||
all_vars = set()
|
||
for v in mapped_vars_str.split():
|
||
all_vars.add(v)
|
||
for v in shared_secrets_str.split():
|
||
all_vars.add(v)
|
||
|
||
# 读取模板
|
||
with open(template_file, 'r') as f:
|
||
template = f.read()
|
||
|
||
# 找出模板中所有的 ${VAR} 占位符(仅检查非注释行)
|
||
pattern = re.compile(r'\$\{(\w+)\}')
|
||
placeholders = set()
|
||
for line in template.splitlines():
|
||
stripped = line.strip()
|
||
if stripped.startswith('#'):
|
||
continue
|
||
placeholders.update(pattern.findall(line))
|
||
|
||
# 检查必需变量是否已设置
|
||
missing = []
|
||
for var in placeholders:
|
||
value = os.environ.get(var, '')
|
||
if not value:
|
||
missing.append(var)
|
||
|
||
if missing:
|
||
print(f"ERROR: 以下变量未设置或为空: {', '.join(sorted(missing))}", file=sys.stderr)
|
||
print(f"请确认对应的 {env_prefix}_xxx 或共用 secrets 已在 Gitea Secrets 中配置", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
# 执行替换
|
||
def replace_var(match):
|
||
var_name = match.group(1)
|
||
return os.environ.get(var_name, match.group(0))
|
||
|
||
rendered = pattern.sub(replace_var, template)
|
||
|
||
# 写入输出文件
|
||
with open(output_file, 'w') as f:
|
||
f.write(rendered)
|
||
|
||
# 设置文件权限为仅 owner 可读写
|
||
os.chmod(output_file, 0o600)
|
||
|
||
print(f"✅ .env 渲染完成: {template_file} → {output_file}")
|
||
print(f" 替换了 {len(placeholders)} 个变量")
|
||
PYTHON_SCRIPT
|
||
|
||
# 验证输出文件
|
||
if [ ! -f "$OUTPUT_FILE" ]; then
|
||
echo "ERROR: 渲染失败,输出文件不存在" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 检查输出文件中是否还有未替换的占位符(仅检查非注释行)
|
||
if grep -vE '^\s*#' "$OUTPUT_FILE" | grep -qE '\$\{[A-Z_]+\}'; then
|
||
echo "ERROR: 输出文件中仍有未替换的占位符:" >&2
|
||
grep -nE '\$\{[A-Z_]+\}' "$OUTPUT_FILE" | grep -v '^\s*#' >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ 渲染文件校验通过,无残留占位符"
|
||
echo "⚠️ $OUTPUT_FILE 包含敏感信息,请勿提交或打印到日志"
|