145bf06ff8
2026-07-04 二维码 IP 事故复盘:env 不随发版同步(by design,密钥走 render-env 手动通道),但 render-env 还停在 EC2 时代且割接后没跑过, 线上 env 的 STORAGE_*/CORS 一直是割接临时 IP。三处修复: - deploy/render-env.sh:目标改 ssh 别名 ali(可 JIU_DEPLOY_SSH 覆盖)、 SECRET_KEYS 补 PAY_SECRET(旧模板缺此行,直接 deploy 会删掉线上支付 密钥)、健康检查改 :8081、覆盖前先备份线上 env - deploy/production.env.template:SERVER_PORT 8080→8081(对齐 ali nginx 反代口径)、补 PAY_SECRET 占位 - scripts/ci/deploy-server.sh(ali 分支):部署后比对模板与线上 env 的 非密钥项,漂移只告警不覆盖——手改线上配置不再长期无人发现 线上 env 已手工修正(STORAGE_BASE_URL/STORAGE_PUBLIC_URL/SERVER_CORS_ORIGIN → https://jiu.51yanmei.com,退役 LICENSE_* 三行清除,改前有备份) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
117 lines
3.6 KiB
Bash
Executable File
117 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# render-env.sh — 从 Bitwarden 渲染 jiu 的 production.env(方案一)
|
||
#
|
||
# 配置基底 = production.env.template(非密钥项的权威来源,随仓库维护)。
|
||
# 密钥 = 从 Bitwarden 条目 DB_PASSWORD 的同名字段按需取,Mac 上不常驻明文。
|
||
#
|
||
# 用法:
|
||
# sh deploy/render-env.sh --check # 只校验:4 个密钥都能从金库取到、长度非空
|
||
# sh deploy/render-env.sh --print # 渲染到 stdout 预览(密钥打码,可安全粘贴)
|
||
# sh deploy/render-env.sh --out FILE # 渲染到指定文件(含明文,慎用,自负保管)
|
||
# sh deploy/render-env.sh --deploy # 渲染→scp 到目标机(默认 ali)→重启 jiu→健康检查→删本地临时文件
|
||
#
|
||
# 前提:rbw 已 unlock。
|
||
set -euo pipefail
|
||
|
||
# ---- 可配置项 ----
|
||
RBW="${RBW_BIN:-/opt/homebrew/bin/rbw}"
|
||
BW_ITEM="${JIU_BW_ITEM:-jiu db password}" # 存放 jiu 密钥字段的 Bitwarden 条目
|
||
SECRET_KEYS="DATABASE_DSN JWT_SECRET DB_PASSWORD PAY_SECRET"
|
||
|
||
# 目标机:~/.ssh/config 别名(2026-07-02 割接后主站 = ali;EC2 已退役)
|
||
TARGET="${JIU_DEPLOY_SSH:-ali}"
|
||
REMOTE_ENV="/opt/jiu/config/production.env"
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
TEMPLATE="$SCRIPT_DIR/production.env.template"
|
||
|
||
# ---- 工具函数 ----
|
||
is_secret() {
|
||
case " $SECRET_KEYS " in *" $1 "*) return 0;; *) return 1;; esac
|
||
}
|
||
|
||
require_unlocked() {
|
||
if ! "$RBW" unlocked >/dev/null 2>&1; then
|
||
echo "render-env: 金库未解锁,请先运行: rbw unlock" >&2
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# 渲染:逐行读模板,密钥行用金库值替换。$1=mask 时密钥打码。
|
||
render() {
|
||
local mask="${1:-}"
|
||
local line key val
|
||
while IFS= read -r line || [ -n "$line" ]; do
|
||
case "$line" in
|
||
''|\#*) printf '%s\n' "$line"; continue ;;
|
||
esac
|
||
key="${line%%=*}"
|
||
if is_secret "$key"; then
|
||
val="$("$RBW" get --field "$key" "$BW_ITEM")"
|
||
if [ -z "$val" ]; then
|
||
echo "render-env: 金库条目 '$BW_ITEM' 的字段 '$key' 为空" >&2
|
||
exit 1
|
||
fi
|
||
if [ "$mask" = "mask" ]; then
|
||
printf '%s=<%d 字符·已打码>\n' "$key" "${#val}"
|
||
else
|
||
printf '%s=%s\n' "$key" "$val"
|
||
fi
|
||
else
|
||
printf '%s\n' "$line"
|
||
fi
|
||
done < "$TEMPLATE"
|
||
}
|
||
|
||
# ---- 主流程 ----
|
||
[ -f "$TEMPLATE" ] || { echo "render-env: 找不到模板 $TEMPLATE" >&2; exit 1; }
|
||
MODE="${1:---print}"
|
||
require_unlocked
|
||
|
||
case "$MODE" in
|
||
--check)
|
||
for k in $SECRET_KEYS; do
|
||
v="$("$RBW" get --field "$k" "$BW_ITEM")"
|
||
printf ' %-22s %s\n' "$k" "$([ -n "$v" ] && echo "OK (${#v} 字符)" || echo "缺失 ❌")"
|
||
done
|
||
;;
|
||
|
||
--print)
|
||
render mask
|
||
;;
|
||
|
||
--out)
|
||
OUT="${2:?用法: render-env.sh --out <文件路径>}"
|
||
umask 077
|
||
render > "$OUT"
|
||
echo "render-env: 已写入 $OUT(含明文,注意保管)"
|
||
;;
|
||
|
||
--deploy)
|
||
TMP="$(mktemp -t jiu-prod-env)"
|
||
trap 'rm -f "$TMP"' EXIT
|
||
umask 077
|
||
render > "$TMP"
|
||
echo "render-env: 已本地渲染(临时文件,退出即删)"
|
||
echo "render-env: 上传到 $TARGET:$REMOTE_ENV"
|
||
scp "$TMP" "$TARGET:/tmp/production.env.new"
|
||
ssh "$TARGET" bash <<REMOTE
|
||
set -e
|
||
mkdir -p /opt/jiu/config
|
||
cp "$REMOTE_ENV" "$REMOTE_ENV.bak-\$(date +%Y%m%d%H%M%S)" 2>/dev/null || true
|
||
mv /tmp/production.env.new "$REMOTE_ENV"
|
||
chmod 600 "$REMOTE_ENV"
|
||
systemctl restart jiu
|
||
sleep 3
|
||
curl -sf http://localhost:8081/health >/dev/null && echo " 健康检查通过 ✅" || { echo " 健康检查失败 ❌"; exit 1; }
|
||
REMOTE
|
||
echo "render-env: 部署完成,$TARGET 上的 production.env 已更新并重启"
|
||
;;
|
||
|
||
*)
|
||
echo "未知参数: $MODE" >&2
|
||
sed -n '2,16p' "$0" >&2
|
||
exit 1
|
||
;;
|
||
esac
|