chore(api): use .env.production on server, .env for local dev only
Load production env without .env override; sync-api-env targets .env.production; PM2 NODE_ENV=production; Prisma uses with-api-env.cjs on deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ dist/
|
||||
server/dukang-api/.env
|
||||
server/dukang-api/.env.development
|
||||
server/dukang-api/.env.production
|
||||
server/dukang-api/.env.production.local
|
||||
deploy/deploy.env
|
||||
.DS_Store
|
||||
coverage/
|
||||
|
||||
@@ -50,24 +50,18 @@ fi
|
||||
|
||||
log "PULL $LOCAL_SHA -> $REMOTE_SHA"
|
||||
|
||||
# 保留生产密钥,避免 reset 覆盖
|
||||
# 保留服务器生产密钥,避免 reset 覆盖
|
||||
ENV_BACKUP="$(mktemp)"
|
||||
API_ENV_BACKUP="$(mktemp)"
|
||||
API_ENV_DEV_BACKUP="$(mktemp)"
|
||||
API_ENV_PROD_BACKUP="$(mktemp)"
|
||||
cp "$ENV_FILE" "$ENV_BACKUP" 2>/dev/null || true
|
||||
cp "$APP_ROOT/server/dukang-api/.env" "$API_ENV_BACKUP" 2>/dev/null || true
|
||||
cp "$APP_ROOT/server/dukang-api/.env.development" "$API_ENV_DEV_BACKUP" 2>/dev/null || true
|
||||
cp "$APP_ROOT/server/dukang-api/.env.production" "$API_ENV_PROD_BACKUP" 2>/dev/null || true
|
||||
|
||||
git checkout "$GIT_BRANCH"
|
||||
git reset --hard "$GIT_REMOTE/$GIT_BRANCH"
|
||||
|
||||
cp "$ENV_BACKUP" "$ENV_FILE" 2>/dev/null || true
|
||||
cp "$API_ENV_BACKUP" "$APP_ROOT/server/dukang-api/.env" 2>/dev/null || true
|
||||
cp "$API_ENV_DEV_BACKUP" "$APP_ROOT/server/dukang-api/.env.development" 2>/dev/null || true
|
||||
cp "$API_ENV_PROD_BACKUP" "$APP_ROOT/server/dukang-api/.env.production" 2>/dev/null || true
|
||||
rm -f "$ENV_BACKUP" "$API_ENV_BACKUP" "$API_ENV_DEV_BACKUP" "$API_ENV_PROD_BACKUP"
|
||||
rm -f "$ENV_BACKUP" "$API_ENV_PROD_BACKUP"
|
||||
sed -i 's/\r$//' "$ENV_FILE" 2>/dev/null || true
|
||||
|
||||
# 修复 Windows 换行
|
||||
|
||||
@@ -10,8 +10,7 @@ module.exports = {
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
// 预发/联调阶段用 development;正式上线后改回 production
|
||||
NODE_ENV: 'development',
|
||||
NODE_ENV: 'production',
|
||||
PORT: 8090,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
echo "==> 3. 更新 USER_H5_URL"
|
||||
ENV_FILE="$APP_ROOT/server/dukang-api/.env"
|
||||
ENV_FILE="$APP_ROOT/server/dukang-api/.env.production"
|
||||
if grep -q '^USER_H5_URL=' "$ENV_FILE"; then
|
||||
sed -i "s|^USER_H5_URL=.*|USER_H5_URL=${H5_BASE}/user|" "$ENV_FILE"
|
||||
else
|
||||
|
||||
@@ -10,7 +10,7 @@ export NODE_OPTIONS="--max-old-space-size=1024"
|
||||
pnpm install --filter @dukang/api... --filter @dukang/domain --filter @dukang/shared-types
|
||||
|
||||
echo "==> env"
|
||||
cat > "$APP_ROOT/server/dukang-api/.env" <<'ENV'
|
||||
cat > "$APP_ROOT/server/dukang-api/.env.production" <<'ENV'
|
||||
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
JWT_SECRET="dukang-prod-jwt-secret-2026"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在服务器上执行:拉取后的常规发版(不覆盖 .env、默认不 seed)
|
||||
# 在服务器上执行:拉取后的常规发版(不覆盖 .env.production、默认不 seed)
|
||||
set -euo pipefail
|
||||
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang-haoke}"
|
||||
@@ -46,17 +46,21 @@ pnpm approve-builds --all 2>/dev/null || true
|
||||
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
||||
|
||||
if [[ "$SKIP_DB" == false ]]; then
|
||||
echo "==> 3. 数据库 schema 同步(构建前)"
|
||||
echo "==> 3. 数据库 schema 同步(构建前,读取 .env.production)"
|
||||
cd "$APP_ROOT/server/dukang-api"
|
||||
pnpm prisma:generate
|
||||
if [[ ! -f .env.production ]]; then
|
||||
echo "错误: 未找到 server/dukang-api/.env.production" >&2
|
||||
exit 1
|
||||
fi
|
||||
node scripts/with-api-env.cjs pnpm prisma:generate
|
||||
if [[ "$DB_PUSH_ACCEPT_DATA_LOSS" == true ]]; then
|
||||
npx prisma db push --accept-data-loss
|
||||
node scripts/with-api-env.cjs npx prisma db push --accept-data-loss
|
||||
else
|
||||
npx prisma db push
|
||||
node scripts/with-api-env.cjs npx prisma db push
|
||||
fi
|
||||
if [[ "$RUN_SEED" == true ]]; then
|
||||
echo "==> 执行 seed"
|
||||
pnpm prisma:seed
|
||||
node scripts/with-api-env.cjs pnpm prisma:seed
|
||||
fi
|
||||
cd "$APP_ROOT"
|
||||
else
|
||||
|
||||
@@ -27,7 +27,7 @@ pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
||||
pnpm build
|
||||
|
||||
echo "==> 5. 写入 API 生产环境变量"
|
||||
cat > "$APP_ROOT/server/dukang-api/.env" <<'ENV'
|
||||
cat > "$APP_ROOT/server/dukang-api/.env.production" <<'ENV'
|
||||
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
JWT_SECRET="dukang-prod-jwt-secret-2026"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 服务器机器相关(覆盖 .env.development 中的同名项)
|
||||
# 服务器机器相关(可合并到 .env.production 的参考项)
|
||||
PORT=8090
|
||||
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
|
||||
+11
-8
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# 同步 API 环境变量到服务器
|
||||
# 用法: deploy/sync-api-env.sh [development|production]
|
||||
# 同步 API 生产环境变量到服务器 .env.production
|
||||
# 用法: deploy/sync-api-env.sh [production]
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
@@ -13,12 +13,15 @@ DEPLOY_PORT="22"
|
||||
DEPLOY_SSH_KEY=""
|
||||
APP_ROOT="/opt/dukang-haoke"
|
||||
|
||||
TARGET="${1:-development}"
|
||||
TARGET="${1:-production}"
|
||||
case "$TARGET" in
|
||||
development|dev) SRC_ENV="$REPO_ROOT/server/dukang-api/.env.development" ;;
|
||||
production|prod) SRC_ENV="$REPO_ROOT/server/dukang-api/.env.production" ;;
|
||||
development|dev)
|
||||
echo "错误: development 仅用于本地开发,请使用 .env / .env.development,不同步到服务器" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 [development|production]" >&2
|
||||
echo "用法: $0 [production]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -29,7 +32,7 @@ if [[ -f "$ENV_FILE" ]]; then
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SRC_ENV" ]]; then
|
||||
echo "错误: 未找到 $SRC_ENV" >&2
|
||||
echo "错误: 未找到 $SRC_ENV(可从 .env.production.example 复制)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -42,7 +45,7 @@ SSH_OPTS=(-o "StrictHostKeyChecking=accept-new" -p "$DEPLOY_PORT")
|
||||
[[ -n "$DEPLOY_SSH_KEY" ]] && SSH_OPTS+=(-i "$DEPLOY_SSH_KEY")
|
||||
REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
|
||||
echo "==> 同步 $TARGET 环境到 $REMOTE:$APP_ROOT/server/dukang-api/.env"
|
||||
scp "${SSH_OPTS[@]}" "$SRC_ENV" "$REMOTE:$APP_ROOT/server/dukang-api/.env"
|
||||
echo "==> 同步 production 环境到 $REMOTE:$APP_ROOT/server/dukang-api/.env.production"
|
||||
scp "${SSH_OPTS[@]}" "$SRC_ENV" "$REMOTE:$APP_ROOT/server/dukang-api/.env.production"
|
||||
ssh "${SSH_OPTS[@]}" "$REMOTE" "pm2 restart dukang-api && sleep 2 && curl -sf -o /dev/null -w 'api-health:%{http_code}\n' http://127.0.0.1:8090/api/v1/health"
|
||||
echo "==> 完成"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 杜康 API 环境变量模板
|
||||
# 本地开发:复制为 .env 后按需修改(pnpm dev:api 仅读取 .env / .env.local)
|
||||
# 可选 .env.local — 本机覆盖(gitignore)
|
||||
# 服务器部署:bash deploy/sync-api-env.sh development|production(同步到远端 .env)
|
||||
# .env.development / .env.production — 部署源模板,本地 dev 不自动加载
|
||||
# 服务器:仅使用 .env.production(bash deploy/sync-api-env.sh production)
|
||||
# .env.development — 本地参考模板,不部署到服务器
|
||||
|
||||
DATABASE_URL="mysql://root:root@localhost:3306/dukang_haoke"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 生产环境(真实第三方,关闭 Mock)
|
||||
# 用途:deploy/sync-api-env.sh production 的源文件,同步到服务器 .env
|
||||
# 本地开发请使用 .env(从 .env.example 复制)
|
||||
# 用途:服务器 runtime 配置;bash deploy/sync-api-env.sh production 同步到远端 .env.production
|
||||
# 本地开发请使用 .env(从 .env.example 复制),勿与生产混用
|
||||
|
||||
NODE_ENV=production
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { config } from 'dotenv';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const apiRoot = resolve(__dirname, '..');
|
||||
const envFile = process.env.DUKANG_ENV_FILE ?? '.env.production';
|
||||
const envPath = resolve(apiRoot, envFile);
|
||||
|
||||
if (!existsSync(envPath)) {
|
||||
console.error(`[with-api-env] 未找到 ${envPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
config({ path: envPath, override: true });
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error('用法: node scripts/with-api-env.cjs <command> [args...]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const result = spawnSync(args[0], args.slice(1), {
|
||||
cwd: apiRoot,
|
||||
stdio: 'inherit',
|
||||
env: process.env,
|
||||
shell: true,
|
||||
});
|
||||
|
||||
process.exit(result.status ?? 1);
|
||||
@@ -5,22 +5,18 @@ import { resolve } from 'path';
|
||||
/**
|
||||
* 环境变量加载策略(后加载的文件覆盖先前的同名键):
|
||||
*
|
||||
* - 本地开发(NODE_ENV !== production):仅 `.env` + `.env.local`
|
||||
* `.env.development` 仅作 deploy/sync-api-env.sh 的源模板,不在本地 dev 自动加载
|
||||
* - 本地开发:仅 `.env` + `.env.local`
|
||||
* `.env.development` 仅作参考模板,不自动加载
|
||||
*
|
||||
* - 线上(NODE_ENV=production):`.env.production` → `.env.production.local` → `.env`
|
||||
* 服务器实际运行时以 sync 脚本写入的 `.env` 为准
|
||||
* - 服务器(NODE_ENV=production):仅 `.env.production` + `.env.production.local`
|
||||
* 不使用 `.env`,避免与本地开发配置混淆
|
||||
*/
|
||||
const apiRoot = resolve(__dirname, '..');
|
||||
const nodeEnv = process.env.NODE_ENV ?? 'development';
|
||||
const isProduction = nodeEnv === 'production';
|
||||
|
||||
const layers = isProduction
|
||||
? [
|
||||
resolve(apiRoot, '.env.production'),
|
||||
resolve(apiRoot, '.env.production.local'),
|
||||
resolve(apiRoot, '.env'),
|
||||
]
|
||||
? [resolve(apiRoot, '.env.production'), resolve(apiRoot, '.env.production.local')]
|
||||
: [resolve(apiRoot, '.env'), resolve(apiRoot, '.env.local')];
|
||||
|
||||
for (const file of layers) {
|
||||
|
||||
Reference in New Issue
Block a user