diff --git a/config/secrets.env.example b/config/secrets.env.example index f53050f..1f75d32 100644 --- a/config/secrets.env.example +++ b/config/secrets.env.example @@ -60,6 +60,15 @@ XUQM_JWT_SECRET= # 留空时 install.sh 自动生成 SDK_INTERNAL_TOKEN= +# ============================================================================= +# 应用商店监测 Secrets 主密钥 +# ============================================================================= + +# 32 字节随机值的 Base64。用于 AES-256-GCM 加密商店监测凭据和 Webhook 密钥。 +# 首次安装自动生成;丢失后无法解密已有凭据,禁止跨公有化/私有化部署复用。 +XUQM_STORE_SECRET_MASTER_KEY= +XUQM_STORE_SECRET_KEY_VERSION=v1 + # ============================================================================= # SMTP 邮件密码(发送验证码邮件) # ============================================================================= diff --git a/docker-compose.yml b/docker-compose.yml index 204ef51..f71fe06 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -152,6 +152,7 @@ services: env_file: - ./config/xuqm.env - ./config/secrets.env + - ./config/mail/smtp.env - ./config/vendors/store-submit.env # 各应用市场发布凭据 environment: SPRING_DATASOURCE_URL: "jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-xuqm_private}?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true" diff --git a/docs/configuration.md b/docs/configuration.md index f9ca685..9bf10bb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -29,6 +29,8 @@ | `REDIS_PASSWORD` | Redis 密码 | | `SPRING_DATASOURCE_PASSWORD` | Spring 读取的数据库密码(与 `MYSQL_PASSWORD` 值相同) | | `SPRING_DATA_REDIS_PASSWORD` | Spring 读取的 Redis 密码(与 `REDIS_PASSWORD` 值相同) | +| `XUQM_STORE_SECRET_MASTER_KEY` | 商店监测凭据 AES-256-GCM 主密钥(32 字节 Base64) | +| `XUQM_STORE_SECRET_KEY_VERSION` | 主密钥版本标识,默认 `v1` | > **设计说明**:Spring 通过 `env_file` 直接读取 `SPRING_DATASOURCE_PASSWORD`,而非通过 `docker-compose.yml` 的 `environment:` 节替换 `${MYSQL_PASSWORD}`。这是因为 compose 变量替换在 shell 中找不到该变量时会替换为空字符串,导致"Access denied (using password: NO)"。旧版部署如缺少这两个字段,`reset.sh` 和 `update.sh` 会自动补齐。 @@ -133,7 +135,9 @@ Web 代理会覆盖上游缓存头:入口 HTML、manifest 和普通路由使 ## `config/mail/smtp.env` -邮件服务配置,不填则邮件功能不可用,不影响其他服务启动。 +邮件服务配置,不填则邮件功能不可用,不影响其他服务启动。`tenant-service` +用于账号邮件,`update-service` 用于租户显式启用的商店状态邮件通知;通知默认关闭, +邮件发送失败不得影响商店状态同步和平台主流程。 --- diff --git a/scripts/configure.sh b/scripts/configure.sh index ba1c5bd..774dca9 100755 --- a/scripts/configure.sh +++ b/scripts/configure.sh @@ -157,6 +157,17 @@ set_env_value "$ROOT_DIR/config/secrets.env" "MYSQL_PASSWORD" "$MYSQL_PASSWORD" [ -n "${MYSQL_ROOT_PASSWORD:-}" ] && \ set_env_value "$ROOT_DIR/config/secrets.env" "MYSQL_ROOT_PASSWORD" "$MYSQL_ROOT_PASSWORD" set_env_value "$ROOT_DIR/config/secrets.env" "REDIS_PASSWORD" "$REDIS_PASSWORD" +set_env_value "$ROOT_DIR/config/secrets.env" "SPRING_DATASOURCE_PASSWORD" "$MYSQL_PASSWORD" +set_env_value "$ROOT_DIR/config/secrets.env" "SPRING_DATA_REDIS_PASSWORD" "$REDIS_PASSWORD" +ensure_env_value "$ROOT_DIR/config/secrets.env" "XUQM_JWT_SECRET" \ + "${XUQM_JWT_SECRET:-}" "$(random_secret)$(random_secret)" >/dev/null +ensure_env_value "$ROOT_DIR/config/secrets.env" "SDK_INTERNAL_TOKEN" \ + "${SDK_INTERNAL_TOKEN:-}" "$(random_secret)" >/dev/null +ensure_env_value "$ROOT_DIR/config/secrets.env" "XUQM_STORE_SECRET_MASTER_KEY" \ + "${XUQM_STORE_SECRET_MASTER_KEY:-}" "$(random_aes256_base64)" >/dev/null +set_env_value "$ROOT_DIR/config/secrets.env" "XUQM_STORE_SECRET_KEY_VERSION" \ + "${XUQM_STORE_SECRET_KEY_VERSION:-v1}" +chmod 600 "$ROOT_DIR/config/secrets.env" "$ROOT_DIR/scripts/render-config.sh" diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 2f72088..3158112 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -380,6 +380,15 @@ EOF ok ".env 已写入" # config/secrets.env — 敏感信息(权限 600) +if command -v openssl >/dev/null 2>&1; then + XUQM_JWT_SECRET="$(openssl rand -base64 48 | tr -d '\n')" + SDK_INTERNAL_TOKEN="$(openssl rand -base64 32 | tr -d '\n')" + XUQM_STORE_SECRET_MASTER_KEY="$(openssl rand -base64 32 | tr -d '\n')" +else + XUQM_JWT_SECRET="$(dd if=/dev/urandom bs=48 count=1 2>/dev/null | base64 | tr -d '\n')" + SDK_INTERNAL_TOKEN="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n')" + XUQM_STORE_SECRET_MASTER_KEY="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n')" +fi mkdir -p "$ROOT_DIR/config" cat > "$ROOT_DIR/config/secrets.env" </dev/null 2>&1; then + openssl rand -base64 32 | tr -d '\n' + else + dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n' + fi +} + ensure_secret_file() { if [ ! -f "$ROOT_DIR/config/secrets.env" ]; then cp "$ROOT_DIR/config/secrets.env.example" "$ROOT_DIR/config/secrets.env" diff --git a/scripts/reset.sh b/scripts/reset.sh index 6b34248..5cd1d69 100644 --- a/scripts/reset.sh +++ b/scripts/reset.sh @@ -90,6 +90,18 @@ if ! grep -q "^SPRING_DATA_REDIS_PASSWORD=" "$ROOT_DIR/config/secrets.env" 2>/de fi [ "$_patched" -eq 1 ] && ok "secrets.env 已补齐 SPRING_DATASOURCE_PASSWORD / SPRING_DATA_REDIS_PASSWORD" +if ! grep -q "^XUQM_STORE_SECRET_MASTER_KEY=." "$ROOT_DIR/config/secrets.env" 2>/dev/null; then + if command -v openssl >/dev/null 2>&1; then + _store_master_key="$(openssl rand -base64 32 | tr -d '\n')" + else + _store_master_key="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n')" + fi + printf 'XUQM_STORE_SECRET_MASTER_KEY=%s\n' "$_store_master_key" >> "$ROOT_DIR/config/secrets.env" + printf 'XUQM_STORE_SECRET_KEY_VERSION=v1\n' >> "$ROOT_DIR/config/secrets.env" + chmod 600 "$ROOT_DIR/config/secrets.env" + ok "secrets.env 已生成应用商店监测加密主密钥" +fi + _CONSOLE_DOMAIN="$(grep '^CONSOLE_DOMAIN=' "$ROOT_DIR/config/xuqm.env" 2>/dev/null \ | cut -d= -f2- | tr -d '"' | tr -d "'")" ok "CONSOLE_DOMAIN=${_CONSOLE_DOMAIN:-(未设置)}" diff --git a/scripts/update.sh b/scripts/update.sh index 6bd66c1..44789f1 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -123,6 +123,20 @@ if ! grep -q "^SPRING_DATA_REDIS_PASSWORD=" "$ROOT_DIR/config/secrets.env" 2>/de fi [ "$_patched" -eq 1 ] && ok "secrets.env 已补齐 SPRING_DATASOURCE_PASSWORD / SPRING_DATA_REDIS_PASSWORD" +# 商店监测凭据使用独立的 AES-256-GCM 主密钥。升级旧部署时只生成一次, +# 后续更新必须保留原值,否则已有商店凭据将无法解密。 +if ! grep -q "^XUQM_STORE_SECRET_MASTER_KEY=." "$ROOT_DIR/config/secrets.env" 2>/dev/null; then + if command -v openssl >/dev/null 2>&1; then + _store_master_key="$(openssl rand -base64 32 | tr -d '\n')" + else + _store_master_key="$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d '\n')" + fi + printf 'XUQM_STORE_SECRET_MASTER_KEY=%s\n' "$_store_master_key" >> "$ROOT_DIR/config/secrets.env" + printf 'XUQM_STORE_SECRET_KEY_VERSION=v1\n' >> "$ROOT_DIR/config/secrets.env" + chmod 600 "$ROOT_DIR/config/secrets.env" + ok "secrets.env 已生成应用商店监测加密主密钥" +fi + # 迁移:补齐 PRIVATE_DEPLOY_ROOT(一键更新功能需要此变量,旧版部署升级时自动写入) if ! grep -q "^PRIVATE_DEPLOY_ROOT=" "$ROOT_DIR/.env" 2>/dev/null; then printf '\nPRIVATE_DEPLOY_ROOT=%s\n' "$ROOT_DIR" >> "$ROOT_DIR/.env"