#!/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 </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