Files
easyai/scripts/credential-keys.sh
T
easyai03 5cff1041a9
Test deployment public URL (Linux) / test-linux (push) Canceled after 0s
Test start.ps1 (Windows) / test-windows (push) Canceled after 0s
Test deployment public URL (Linux) / test-linux (pull_request) Canceled after 0s
Test start.ps1 (Windows) / test-windows (pull_request) Canceled after 0s
feat(deploy): 补齐凭据加密接入迁移和密钥备份恢复
首次安装默认生成独立加密密钥,更新旧部署保留原配置,通过显式开关进入迁移窗口。增加生产迁移包装入口和完整密钥环备份恢复工具,并说明旧镜像升级、严格迁移、轮换及还原流程。

已通过 Bash 安全环境及密钥备份回归;对应 PowerShell 5.1 和 7 脚本已在 Windows 原生环境各通过 49 项验收。宁波服务器验收尚未执行。
2026-10-12 22:24:16 +08:00

51 lines
3.2 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
umask 077
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/init-security-env.sh
. "$SCRIPT_DIR/init-security-env.sh"
mode="${1:-}"; keyfile="${2:-}"; envfile="${3:-$SCRIPT_DIR/../.env}"
case "$mode" in export|restore) ;; *) echo '用法: credential-keys.sh export|restore 私有密钥备份文件 [部署.env]' >&2; exit 1 ;; esac
[ -n "$keyfile" ] && [ -f "$envfile" ] || { echo '请指定密钥备份文件,并先准备部署 .env' >&2; exit 1; }
[ "$keyfile" != "$envfile" ] || { echo '密钥备份必须与部署配置分开' >&2; exit 1; }
sourcefile="$envfile"
if [ "$mode" = restore ]; then sourcefile="$keyfile"; fi
[ -f "$sourcefile" ] || { echo '密钥备份不存在' >&2; exit 1; }
active="$(read_env_value "$sourcefile" CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID)"
ring="$(read_env_value "$sourcefile" CONFIG_CREDENTIAL_ENCRYPTION_KEYRING)"
legacy="$(read_env_value "$sourcefile" CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT)"
[ -n "$active" ] && [ -n "$ring" ] || { echo '没有完整加密密钥,不能导出或恢复;不会生成替代密钥' >&2; exit 1; }
tmp="$(mktemp "${envfile}.credential-keys.XXXXXX")"
trap 'rm -f "$tmp"' EXIT
printf 'CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=%s\nCONFIG_CREDENTIAL_ENCRYPTION_KEYRING=%s\nCONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT=%s\n' "$active" "'$ring'" "${legacy:-false}" > "$tmp"
init_credential_encryption_env "$tmp" upgrade >/dev/null
# Preserve the independent master and JWT fallback used by earlier encrypted settings.
for name in CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY CONFIG_JWT_SECRET; do
if [ "$mode" = export ]; then
awk -v key="$name" 'index($0, key "=") == 1 { print; found=1; exit } END { if (!found) print key "=" }' "$sourcefile" >> "$tmp"
else
awk -v key="$name" 'index($0, key "=") == 1 { print; exit }' "$sourcefile" >> "$tmp"
fi
done
if [ "$mode" = export ]; then
# noclobber reserves an immutable backup instead of truncating an earlier keyring.
( set -o noclobber; cat "$tmp" > "$keyfile" ) || { echo '密钥备份已存在或路径不可写,请使用新文件' >&2; exit 1; }
chmod 600 "$keyfile"
echo '已导出完整 keyring(含旧密钥);请与数据库备份分开私有保管。'
else
previous="${envfile}.pre-credential-restore.$(date +%Y%m%dT%H%M%S).$$"
cp "$envfile" "$previous"; chmod 600 "$previous"
key_content="$(cat "$tmp")"
awk 'index($0,"CONFIG_CREDENTIAL_ENCRYPTION_ACTIVE_KEY_ID=")==1 || index($0,"CONFIG_CREDENTIAL_ENCRYPTION_KEYRING=")==1 || index($0,"CONFIG_CREDENTIAL_ALLOW_LEGACY_PLAINTEXT=")==1 { next } { print }' "$envfile" > "$tmp"
for name in CONFIG_SECURITY_CONFIG_ENCRYPTION_KEY CONFIG_JWT_SECRET; do
if printf '%s\n' "$key_content" | awk -v key="$name" 'index($0,key "=")==1 { found=1 } END { exit !found }'; then
filtered="$(mktemp "${envfile}.filtered.XXXXXX")"
awk -v key="$name" 'index($0,key "=")!=1 { print }' "$tmp" > "$filtered"
mv "$filtered" "$tmp"
fi
done
printf '%s\n' "$key_content" >> "$tmp"
chmod 600 "$tmp"; mv "$tmp" "$envfile"
echo '已恢复原 keyring,其他配置保留;重建全部相关容器后再恢复数据库。'
fi