From 2e511fa6f83c740adcfa2e6b1d7f1fe9d0c77f51 Mon Sep 17 00:00:00 2001 From: shaw Date: Thu, 24 Jul 2025 15:50:33 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=AE=80=E5=8C=96Docker=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=EF=BC=8C=E4=BD=BF=E7=94=A8=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=9B=BF=E4=BB=A3.env=E6=96=87=E4=BB=B6=E6=98=A0?= =?UTF-8?q?=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 docker-compose.yml 中的 .env 文件映射 - 添加所有必要的环境变量到 docker-compose.yml - 简化 docker-entrypoint.sh,直接使用环境变量 - 更新 README,说明通过环境变量配置的方式 - 删除不再需要的初始化脚本 - 解决了 sed -i 在某些 Docker 环境下的 'Resource busy' 错误 --- README.md | 69 ++++++++++++++++++++++--------------- docker-compose-init.sh | 62 --------------------------------- docker-compose.yml | 61 ++++++++++++++++++++++++++++----- docker-entrypoint.sh | 78 ++++++++++-------------------------------- scripts/docker-init.sh | 52 ---------------------------- 5 files changed, 112 insertions(+), 210 deletions(-) delete mode 100644 docker-compose-init.sh delete mode 100644 scripts/docker-init.sh diff --git a/README.md b/README.md index 609defe0..b9eb9ce7 100644 --- a/README.md +++ b/README.md @@ -224,18 +224,6 @@ npm run service:status ## 🐳 Docker 部署(推荐) -### ⚠️ 首次部署必须执行 - -```bash -# 创建空的 .env 文件(非常重要!) -touch .env -``` - -> 🔴 **为什么必须先创建 .env 文件?** -> - Docker 在映射不存在的文件时会创建成**目录**而非文件 -> - .env 文件用于存储加密密钥,必须持久化保存 -> - 如果变成目录,容器将无法启动 - ### 使用 Docker Hub 镜像(最简单) > 🚀 推荐使用官方镜像,自动构建,始终保持最新版本 @@ -244,18 +232,31 @@ touch .env # 拉取镜像(支持 amd64 和 arm64) docker pull weishaw/claude-relay-service:latest -# 使用 docker run 运行 +# 使用 docker run 运行(注意设置必需的环境变量) docker run -d \ --name claude-relay \ -p 3000:3000 \ -v $(pwd)/data:/app/data \ -v $(pwd)/logs:/app/logs \ - -v $(pwd)/.env:/app/.env \ + -e JWT_SECRET=your-random-secret-key-at-least-32-chars \ + -e ENCRYPTION_KEY=your-32-character-encryption-key \ + -e REDIS_HOST=redis \ -e ADMIN_USERNAME=my_admin \ -e ADMIN_PASSWORD=my_secure_password \ weishaw/claude-relay-service:latest # 或使用 docker-compose(推荐) +# 创建 .env 文件用于 docker-compose 的环境变量: +cat > .env << 'EOF' +# 必填:安全密钥(请修改为随机值) +JWT_SECRET=your-random-secret-key-at-least-32-chars +ENCRYPTION_KEY=your-32-character-encryption-key + +# 可选:管理员凭据 +ADMIN_USERNAME=cr_admin +ADMIN_PASSWORD=your-secure-password +EOF + # 创建 docker-compose.yml 文件: cat > docker-compose.yml << 'EOF' version: '3.8' @@ -267,13 +268,14 @@ services: ports: - "3000:3000" environment: + - JWT_SECRET=${JWT_SECRET} + - ENCRYPTION_KEY=${ENCRYPTION_KEY} - REDIS_HOST=redis - ADMIN_USERNAME=${ADMIN_USERNAME:-} - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} volumes: - ./logs:/app/logs - ./data:/app/data - - ./.env:/app/.env # 重要:持久化加密密钥 depends_on: - redis @@ -299,19 +301,18 @@ docker-compose up -d git clone https://github.com/Wei-Shaw//claude-relay-service.git cd claude-relay-service -# 2. 初始化环境(重要!首次部署必须执行) -touch .env # 创建空文件,防止 Docker 创建成目录 +# 2. 创建环境变量文件 +cat > .env << 'EOF' +# 必填:安全密钥(请修改为随机值) +JWT_SECRET=your-random-secret-key-at-least-32-chars +ENCRYPTION_KEY=your-32-character-encryption-key -# 如果 .env 已经错误地变成了目录,先删除: -# rm -rf .env && touch .env +# 可选:管理员凭据 +ADMIN_USERNAME=cr_admin_custom +ADMIN_PASSWORD=your-secure-password +EOF -# 3. 设置管理员账号密码(可选) -# 方式一:自动生成(查看容器日志获取) -docker-compose up -d - -# 方式二:预设账号密码 -export ADMIN_USERNAME=cr_admin_custom -export ADMIN_PASSWORD=your-secure-password +# 3. 启动服务 docker-compose up -d # 4. 查看管理员凭据 @@ -326,12 +327,24 @@ cat ./data/init.json docker-compose.yml 已包含: - ✅ 自动初始化管理员账号 -- ✅ 数据持久化(logs、data目录和.env文件自动挂载) +- ✅ 数据持久化(logs和data目录自动挂载) - ✅ Redis数据库 - ✅ 健康检查 - ✅ 自动重启 +- ✅ 所有配置通过环境变量管理 -> ⚠️ **重要提示**:从 v1.1.15 版本开始,`.env` 文件必须映射到本地以持久化加密密钥。如果不映射,每次重建容器都会生成新的加密密钥,导致之前加密的数据无法解密! +### 环境变量说明 + +#### 必填项 +- `JWT_SECRET`: JWT密钥,至少32个字符 +- `ENCRYPTION_KEY`: 加密密钥,必须是32个字符 + +#### 可选项 +- `ADMIN_USERNAME`: 管理员用户名(不设置则自动生成) +- `ADMIN_PASSWORD`: 管理员密码(不设置则自动生成) +- `LOG_LEVEL`: 日志级别(默认:info) +- `DEFAULT_TOKEN_LIMIT`: 默认Token限制(默认:1000000) +- 更多配置项请参考 `.env.example` 文件 ### 管理员凭据获取方式 diff --git a/docker-compose-init.sh b/docker-compose-init.sh deleted file mode 100644 index 37dee9a5..00000000 --- a/docker-compose-init.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Docker Compose 初始化脚本 - 用于 Docker Hub 镜像部署 - -echo "🚀 Claude Relay Service Docker 初始化脚本" -echo "============================================" - -# 检查是否在正确的目录 -if [ -f "docker-compose.yml" ]; then - echo "✅ 检测到 docker-compose.yml,继续初始化..." -else - echo "⚠️ 未检测到 docker-compose.yml 文件" - echo " 请确保在包含 docker-compose.yml 的目录下运行此脚本" - echo "" - echo "如果您是从 Docker Hub 部署,请先创建 docker-compose.yml:" - echo " 参考文档:https://github.com/Wei-Shaw/claude-relay-service#docker-部署推荐" - exit 1 -fi - -# 确保 .env 文件正确创建 -echo "" -echo "📋 检查 .env 文件..." - -if [ -d ".env" ]; then - echo "❌ 检测到 .env 是目录(Docker 创建错误)" - echo " 正在修复..." - rm -rf .env - touch .env - echo "✅ 已删除目录并创建正确的 .env 文件" -elif [ ! -f ".env" ]; then - echo "📝 创建 .env 文件..." - touch .env - echo "✅ .env 文件已创建" -else - echo "✅ .env 文件已存在" -fi - -# 创建必要的目录 -echo "" -echo "📁 创建必要的目录..." -mkdir -p data logs redis_data -echo "✅ 目录创建完成" - -# 显示文件状态 -echo "" -echo "📊 当前文件状态:" -echo " .env: $([ -f .env ] && echo "✅ 文件" || echo "❌ 不存在")" -echo " data/: $([ -d data ] && echo "✅ 目录" || echo "❌ 不存在")" -echo " logs/: $([ -d logs ] && echo "✅ 目录" || echo "❌ 不存在")" -echo " redis_data/: $([ -d redis_data ] && echo "✅ 目录" || echo "❌ 不存在")" - -echo "" -echo "🎉 初始化完成!" -echo "" -echo "下一步操作:" -echo "1. 启动服务:" -echo " docker-compose up -d" -echo "" -echo "2. 查看日志获取管理员密码:" -echo " docker-compose logs claude-relay | grep '管理员'" -echo "" -echo "3. 访问管理界面:" -echo " http://your-server:3000/web" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4d28380a..8f75f151 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,7 @@ version: '3.8' -# ⚠️ 重要提示:首次运行前必须执行以下命令 -# touch .env -# -# 说明:如果不先创建 .env 文件,Docker 会将其创建为目录而非文件, -# 导致容器无法正常启动。该文件用于存储加密密钥,必须持久化。 +# Claude Relay Service Docker Compose 配置 +# 所有配置通过环境变量设置,无需映射 .env 文件 services: # 🚀 Claude Relay Service @@ -15,16 +12,64 @@ services: ports: - "${PORT:-3000}:3000" environment: + # 🌐 服务器配置 - NODE_ENV=production - PORT=3000 + - HOST=0.0.0.0 + + # 🔐 安全配置(必填) + - JWT_SECRET=${JWT_SECRET} # 必填:至少32字符的随机字符串 + - ENCRYPTION_KEY=${ENCRYPTION_KEY} # 必填:32字符的加密密钥 + - ADMIN_SESSION_TIMEOUT=${ADMIN_SESSION_TIMEOUT:-86400000} + - API_KEY_PREFIX=${API_KEY_PREFIX:-cr_} + + # 👤 管理员凭据(可选) + - ADMIN_USERNAME=${ADMIN_USERNAME:-} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} + + # 📊 Redis 配置 - REDIS_HOST=redis - REDIS_PORT=6379 - - ADMIN_USERNAME=${ADMIN_USERNAME:-} # 可选:预设管理员用户名 - - ADMIN_PASSWORD=${ADMIN_PASSWORD:-} # 可选:预设管理员密码 + - REDIS_PASSWORD=${REDIS_PASSWORD:-} + - REDIS_DB=${REDIS_DB:-0} + - REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-} + + # 🎯 Claude API 配置 + - CLAUDE_API_URL=${CLAUDE_API_URL:-https://api.anthropic.com/v1/messages} + - CLAUDE_API_VERSION=${CLAUDE_API_VERSION:-2023-06-01} + - CLAUDE_BETA_HEADER=${CLAUDE_BETA_HEADER:-claude-code-20250219,oauth-2025-04-20,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14} + + # 🌐 代理配置 + - DEFAULT_PROXY_TIMEOUT=${DEFAULT_PROXY_TIMEOUT:-60000} + - MAX_PROXY_RETRIES=${MAX_PROXY_RETRIES:-3} + + # 📈 使用限制 + - DEFAULT_TOKEN_LIMIT=${DEFAULT_TOKEN_LIMIT:-1000000} + + # 📝 日志配置 + - LOG_LEVEL=${LOG_LEVEL:-info} + - LOG_MAX_SIZE=${LOG_MAX_SIZE:-10m} + - LOG_MAX_FILES=${LOG_MAX_FILES:-5} + + # 🔧 系统配置 + - CLEANUP_INTERVAL=${CLEANUP_INTERVAL:-3600000} + - TOKEN_USAGE_RETENTION=${TOKEN_USAGE_RETENTION:-2592000000} + - HEALTH_CHECK_INTERVAL=${HEALTH_CHECK_INTERVAL:-60000} + - SYSTEM_TIMEZONE=${SYSTEM_TIMEZONE:-Asia/Shanghai} + - TIMEZONE_OFFSET=${TIMEZONE_OFFSET:-8} + + # 🎨 Web 界面配置 + - WEB_TITLE=${WEB_TITLE:-Claude Relay Service} + - WEB_DESCRIPTION=${WEB_DESCRIPTION:-Multi-account Claude API relay service} + - WEB_LOGO_URL=${WEB_LOGO_URL:-/assets/logo.png} + + # 🛠️ 开发配置 + - DEBUG=${DEBUG:-false} + - ENABLE_CORS=${ENABLE_CORS:-true} + - TRUST_PROXY=${TRUST_PROXY:-true} volumes: - ./logs:/app/logs - ./data:/app/data - - ./.env:/app/.env # 必须映射,用于持久化加密密钥 depends_on: - redis networks: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 9b949e59..b4e37c50 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -3,25 +3,20 @@ set -e echo "🚀 Claude Relay Service 启动中..." -# 检查 .env 文件 -if [ ! -f "/app/.env" ]; then - echo "📋 检测到 .env 不存在,从模板创建..." - if [ -f "/app/.env.example" ]; then - cp /app/.env.example /app/.env - echo "✅ .env 已从模板创建" - echo "⚠️ 注意:.env 文件将在容器内生成,请确保已映射到宿主机以持久化" - else - echo "❌ 错误: .env.example 不存在" - exit 1 - fi +# 检查关键环境变量 +if [ -z "$JWT_SECRET" ]; then + echo "❌ 错误: JWT_SECRET 环境变量未设置" + echo " 请在 docker-compose.yml 中设置 JWT_SECRET" + echo " 例如: JWT_SECRET=your-random-secret-key-at-least-32-chars" + exit 1 fi -# 生成随机字符串的函数 -generate_random_string() { - length=$1 - # 使用 /dev/urandom 生成随机字符串 - tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c $length -} +if [ -z "$ENCRYPTION_KEY" ]; then + echo "❌ 错误: ENCRYPTION_KEY 环境变量未设置" + echo " 请在 docker-compose.yml 中设置 ENCRYPTION_KEY" + echo " 例如: ENCRYPTION_KEY=your-32-character-encryption-key" + exit 1 +fi # 检查并复制配置文件 if [ ! -f "/app/config/config.js" ]; then @@ -35,54 +30,17 @@ if [ ! -f "/app/config/config.js" ]; then fi fi -# 检查并配置 .env 文件(文件已在构建时创建) -if [ -f "/app/.env" ]; then - echo "📋 配置 .env 文件..." - - # 生成随机的 JWT_SECRET (64字符) - if [ -z "$JWT_SECRET" ]; then - JWT_SECRET=$(grep "^JWT_SECRET=" /app/.env | cut -d'=' -f2) - if [ -z "$JWT_SECRET" ] || [ "$JWT_SECRET" = "your-jwt-secret-here" ]; then - JWT_SECRET=$(generate_random_string 64) - echo "🔑 生成新的 JWT_SECRET" - # 更新 .env 文件 - sed -i "s/JWT_SECRET=.*/JWT_SECRET=${JWT_SECRET}/" /app/.env - else - echo "✅ 使用现有的 JWT_SECRET" - fi - fi - - # 生成随机的 ENCRYPTION_KEY (32字符) - if [ -z "$ENCRYPTION_KEY" ]; then - ENCRYPTION_KEY=$(grep "^ENCRYPTION_KEY=" /app/.env | cut -d'=' -f2) - if [ -z "$ENCRYPTION_KEY" ] || [ "$ENCRYPTION_KEY" = "your-encryption-key-here" ]; then - ENCRYPTION_KEY=$(generate_random_string 32) - echo "🔑 生成新的 ENCRYPTION_KEY" - # 更新 .env 文件 - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=${ENCRYPTION_KEY}/" /app/.env - else - echo "✅ 使用现有的 ENCRYPTION_KEY" - fi - fi - - # 更新 Redis 主机配置 - sed -i "s/REDIS_HOST=.*/REDIS_HOST=redis/" /app/.env - - echo "✅ .env 已配置" -else - echo "❌ 错误: .env 文件处理失败" - exit 1 -fi - -# 导出环境变量 -export JWT_SECRET -export ENCRYPTION_KEY +# 显示配置信息 +echo "✅ 环境配置已就绪" +echo " JWT_SECRET: [已设置]" +echo " ENCRYPTION_KEY: [已设置]" +echo " REDIS_HOST: ${REDIS_HOST:-localhost}" +echo " PORT: ${PORT:-3000}" # 检查是否需要初始化 if [ ! -f "/app/data/init.json" ]; then echo "📋 首次启动,执行初始化设置..." - # 如果设置了环境变量,显示提示 if [ -n "$ADMIN_USERNAME" ] || [ -n "$ADMIN_PASSWORD" ]; then echo "📌 检测到预设的管理员凭据" diff --git a/scripts/docker-init.sh b/scripts/docker-init.sh deleted file mode 100644 index ad81c254..00000000 --- a/scripts/docker-init.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -# Docker 初始化脚本 - 在宿主机上运行 - -echo "🚀 Claude Relay Service Docker 初始化" - -# 检查 .env 文件 -if [ ! -f ".env" ]; then - echo "📋 检测到 .env 文件不存在,从模板创建..." - - if [ -f ".env.example" ]; then - cp .env.example .env - echo "✅ .env 文件已创建" - - # 生成随机密钥 - echo "🔑 生成安全密钥..." - - # 生成64字符的JWT_SECRET - JWT_SECRET=$(openssl rand -base64 48 | tr -d "=+/" | cut -c1-64) - # 生成32字符的ENCRYPTION_KEY - ENCRYPTION_KEY=$(openssl rand -base64 24 | tr -d "=+/" | cut -c1-32) - - # 替换默认值 - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - sed -i '' "s/JWT_SECRET=.*/JWT_SECRET=$JWT_SECRET/" .env - sed -i '' "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=$ENCRYPTION_KEY/" .env - else - # Linux - sed -i "s/JWT_SECRET=.*/JWT_SECRET=$JWT_SECRET/" .env - sed -i "s/ENCRYPTION_KEY=.*/ENCRYPTION_KEY=$ENCRYPTION_KEY/" .env - fi - - echo "✅ 密钥已生成并保存到 .env 文件" - echo "" - echo "📌 请妥善保管 .env 文件,它包含重要的加密密钥!" - else - echo "❌ 错误:.env.example 文件不存在" - echo "请确保在项目根目录下运行此脚本" - exit 1 - fi -else - echo "✅ .env 文件已存在,跳过创建" -fi - -# 创建必要的目录 -echo "📁 创建必要的目录..." -mkdir -p data logs redis_data -echo "✅ 目录创建完成" - -echo "" -echo "🎉 初始化完成!现在可以运行:" -echo " docker-compose up -d" \ No newline at end of file