From 663082efe160c5c4cd4f7f3e220dc406c6e962dc Mon Sep 17 00:00:00 2001 From: david_bai Date: Wed, 8 Oct 2025 15:58:48 +0800 Subject: [PATCH] chore(doc): Replace Chinese comments with English comments --- backend/Dockerfile | 10 +- backend/src/config/server.ts | 4 +- deploy.sh | 350 ++++++++++++------------ docker-compose.yml | 10 +- docker/scripts/detect-environment.sh | 152 +++++------ docker/scripts/generate-config.sh | 382 +++++++++++++-------------- docker/scripts/test-deployment.sh | 258 +++++++++--------- frontend/Dockerfile | 36 +-- frontend/components/ClipboardApp.tsx | 4 +- test-docker-deployment.sh | 41 --- test-health-apis.sh | 120 ++++----- 11 files changed, 663 insertions(+), 704 deletions(-) delete mode 100644 test-docker-deployment.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index 3022332..a6ad207 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -# 构建阶段 +# Build stage FROM node:18-alpine AS builder ARG HTTP_PROXY @@ -15,18 +15,18 @@ RUN npm ci COPY . . RUN npm run build -# 运行阶段 +# Run stage FROM node:18-alpine AS runtime WORKDIR /app -# 复制预构建的文件 +# Copy prebuilt artifacts COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/package.json ./ COPY health-check.js ./ -# 创建用户并设置权限 +# Create user and set permissions RUN addgroup -g 1001 -S nodejs && \ adduser -S backend -u 1001 -G nodejs && \ chown -R backend:nodejs /app @@ -34,7 +34,7 @@ RUN addgroup -g 1001 -S nodejs && \ USER backend EXPOSE 3001 -# 使用Node.js脚本做健康检查(替代curl) +# Use a Node.js script for health checks (instead of curl) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD node health-check.js diff --git a/backend/src/config/server.ts b/backend/src/config/server.ts index 3f3568c..37e14f4 100644 --- a/backend/src/config/server.ts +++ b/backend/src/config/server.ts @@ -9,10 +9,10 @@ const DEV_ORIGINS = [ /^http:\/\/192\.168\.\d+\.\d+:3002$/, // LAN addresses with new port ]; -// 解析生产环境下的多来源配置(逗号分隔) +// Parse multi-origin config in production (comma-separated) const parseProdOrigins = (): string | RegExp | (string | RegExp)[] => { const v = CONFIG.CORS_ORIGIN?.trim(); - if (!v) return DEV_ORIGINS; // 回退到开发白名单 + if (!v) return DEV_ORIGINS; // Fallback to the development whitelist if (v.includes(",")) { return v .split(",") diff --git a/deploy.sh b/deploy.sh index aa93736..d745a14 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,19 +1,19 @@ #!/bin/bash -set -e # 遇到错误立即退出 +set -e # Exit immediately on error -# 颜色定义 +# Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' -# 脚本目录 +# Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOCKER_SCRIPTS_DIR="$SCRIPT_DIR/docker/scripts" -# 日志函数 +# Logging helpers log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } @@ -30,39 +30,39 @@ log_error() { echo -e "${RED}❌ $1${NC}" } -# 显示帮助信息 +# Show help show_help() { cat << EOF -PrivyDrop Docker 一键部署脚本 +PrivyDrop Docker Deployment Script -用法: $0 [选项] +Usage: $0 [options] -选项: - --domain DOMAIN 指定域名 (用于HTTPS部署) - --mode MODE 部署模式: basic|public|full|private - basic/private: 内网HTTP部署 (默认,private 将跳过网络检测) - public: 公网HTTP部署 + TURN服务器 - full: 完整HTTPS部署 + TURN服务器 - --with-nginx 启用Nginx反向代理 - --with-turn 启用TURN服务器 - --with-sni443 启用 443 SNI 分流 (full 模式默认启用) - --le-email EMAIL 使用 Let's Encrypt 时的证书邮箱(full 模式推荐传入) - --clean 清理现有容器和数据 - --help 显示帮助信息 +Options: + --domain DOMAIN Specify domain (for HTTPS deployments) + --mode MODE Deployment mode: basic|public|full|private + basic/private: Intranet HTTP (default; private skips network detection) + public: Public HTTP + TURN server + full: Full HTTPS + TURN server + --with-nginx Enable Nginx reverse proxy + --with-turn Enable TURN server + --with-sni443 Enable 443 SNI routing (enabled by default in full mode) + --le-email EMAIL Email for Let's Encrypt (recommended in full mode) + --clean Clean existing containers and data + --help Show help -示例: - $0 # 基础部署 - $0 --mode public --with-turn # 公网部署 + TURN服务器 - $0 --domain example.com --mode full # 完整HTTPS部署 - $0 --clean # 清理部署 +Examples: + $0 # Basic deployment + $0 --mode public --with-turn # Public deployment + TURN server + $0 --domain example.com --mode full # Full HTTPS deployment + $0 --clean # Clean deployment -要求: - - Docker Engine 和 Docker Compose V2(命令为 `docker compose`) +Requirements: + - Docker Engine and Docker Compose V2 (command `docker compose`) EOF } -# 解析命令行参数 +# Parse command-line arguments parse_arguments() { DOMAIN_NAME="" DEPLOYMENT_MODE="" @@ -107,23 +107,23 @@ parse_arguments() { exit 0 ;; *) - log_error "未知参数: $1" + log_error "Unknown argument: $1" show_help exit 1 ;; esac done - # 导出变量供其他脚本使用 + # Export variables for other scripts export DOMAIN_NAME export DEPLOYMENT_MODE export WITH_NGINX export WITH_TURN } -# 检查依赖 +# Check dependencies check_dependencies() { - log_info "检查依赖..." + log_info "Checking dependencies..." local missing_deps=() @@ -144,16 +144,16 @@ check_dependencies() { fi if [[ ${#missing_deps[@]} -gt 0 ]]; then - log_error "缺少依赖: ${missing_deps[*]}" + log_error "Missing dependencies: ${missing_deps[*]}" echo "" - echo "请安装缺少的依赖:" + echo "Please install the missing dependencies:" for dep in "${missing_deps[@]}"; do case $dep in docker) echo " Docker: https://docs.docker.com/get-docker/" ;; "docker compose (V2)") - echo " Docker Compose V2 插件: https://docs.docker.com/compose/install/" + echo " Docker Compose V2 plugin: https://docs.docker.com/compose/install/" ;; curl) echo " curl: sudo apt-get install curl (Ubuntu/Debian)" @@ -166,24 +166,24 @@ check_dependencies() { exit 1 fi - log_success "依赖检查通过" + log_success "Dependency checks passed" } -# 安装并准备 Let's Encrypt(certbot) +# Install and prepare Let's Encrypt (certbot) ensure_certbot() { if command -v certbot >/dev/null 2>&1; then return 0 fi - log_info "安装 certbot (需要 sudo 权限)..." + log_info "Installing certbot (requires sudo)..." if command -v apt-get >/dev/null 2>&1; then sudo apt-get update -y && sudo apt-get install -y certbot else - log_error "未检测到 apt-get,请手动安装 certbot 或在支持的系统上运行" + log_error "apt-get not found. Please install certbot manually or run on a supported system" exit 1 fi } -# 写入 certbot 部署钩子:续期后复制证书并热重载服务 +# Write certbot deploy hook: copy certs and hot-reload services after renewal install_certbot_deploy_hook() { local repo_dir="$SCRIPT_DIR" local hook_dir="/etc/letsencrypt/renewal-hooks/deploy" @@ -197,7 +197,7 @@ set -e REPO_DIR="$repo_dir" COMPOSE_FILE="$compose_file" -# RENEWED_LINEAGE 由 certbot 传入,指向 live/ +# RENEWED_LINEAGE is provided by certbot and points to live/ if [[ -z "\$RENEWED_LINEAGE" ]]; then exit 0 fi @@ -206,34 +206,34 @@ cp "\$RENEWED_LINEAGE/fullchain.pem" "\$REPO_DIR/docker/ssl/server-cert.pem" cp "\$RENEWED_LINEAGE/privkey.pem" "\$REPO_DIR/docker/ssl/server-key.pem" chmod 600 "\$REPO_DIR/docker/ssl/server-key.pem" || true -# 热重载 nginx,如失败则重启 +# Hot-reload nginx; restart if it fails docker compose -f "\$COMPOSE_FILE" exec -T nginx nginx -s reload 2>/dev/null || \ docker compose -f "\$COMPOSE_FILE" restart nginx || true -# 优先向 coturn 发送 HUP,不行则重启(若未启用则忽略) +# Prefer sending HUP to coturn; restart if needed (ignore if disabled) docker compose -f "\$COMPOSE_FILE" exec -T coturn sh -c 'kill -HUP 1' 2>/dev/null || \ docker compose -f "\$COMPOSE_FILE" restart coturn || true EOF sudo chmod +x "$hook_file" - # 尝试启用 systemd 定时器 + # Attempt to enable systemd timer if command -v systemctl >/dev/null 2>&1; then sudo systemctl enable --now certbot.timer 2>/dev/null || true fi } -# 使用 webroot 首次签发并启用 443 配置 +# Issue via webroot and enable 443 config provision_letsencrypt_cert() { - # 仅在 full 模式且启用 nginx 且存在域名时执行 + # Only in full mode with nginx enabled and domain set if [[ "$DEPLOYMENT_MODE" != "full" || "$WITH_NGINX" != "true" ]]; then return 0 fi if [[ -z "$DOMAIN_NAME" ]]; then - log_warning "full 模式未指定 --domain,跳过 Let’s Encrypt" + log_warning "Full mode without --domain; skipping Let's Encrypt" return 0 fi if [[ -z "$LE_EMAIL" ]]; then - log_warning "未指定 --le-email,将使用 --register-unsafely-without-email" + log_warning "No --le-email specified; using --register-unsafely-without-email" fi ensure_certbot @@ -241,32 +241,32 @@ provision_letsencrypt_cert() { mkdir -p docker/letsencrypt-www docker/ssl - # 如果证书已存在(含 -0001 谱系),跳过签发 + # If certificates already exist (including -0001 lineage), skip issuance if [[ -f "/etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem" ]] || ls -1d /etc/letsencrypt/live/${DOMAIN_NAME}* >/dev/null 2>&1; then - log_info "检测到已存在的证书/谱系,跳过首次签发" + log_info "Detected existing certificates/lineage; skipping initial issuance" else - log_info "通过 webroot 模式签发 Let's Encrypt 证书..." + log_info "Issuing Let's Encrypt certificate via webroot..." local email_args="--email $LE_EMAIL" if [[ -z "$LE_EMAIL" ]]; then email_args="--register-unsafely-without-email" fi - # 需要 80 端口可达且 nginx 已启动 + # Requires port 80 reachable and nginx running sudo certbot certonly --webroot -w "$(pwd)/docker/letsencrypt-www" \ -d "$DOMAIN_NAME" -d "turn.$DOMAIN_NAME" \ $email_args --agree-tos --non-interactive || { - log_error "证书签发失败,请查看 certbot 输出" + log_error "Certificate issuance failed; please check certbot output" return 1 } fi - # 解析谱系目录(兼容 -0001/-0002 后缀)并复制到 docker/ssl + # Resolve lineage directory (supports -0001/-0002 suffixes) and copy to docker/ssl local lineage_dir lineage_dir=$(readlink -f "/etc/letsencrypt/live/$DOMAIN_NAME" 2>/dev/null || true) if [[ -z "$lineage_dir" || ! -d "$lineage_dir" ]]; then lineage_dir=$(ls -1d /etc/letsencrypt/live/${DOMAIN_NAME}* 2>/dev/null | sort | tail -1) fi if [[ -z "$lineage_dir" || ! -f "$lineage_dir/fullchain.pem" ]]; then - log_error "未找到有效证书谱系目录,请检查 /etc/letsencrypt/live/${DOMAIN_NAME}*" + log_error "No valid certificate lineage directory found. Check /etc/letsencrypt/live/${DOMAIN_NAME}*" return 1 fi @@ -274,99 +274,99 @@ provision_letsencrypt_cert() { sudo cp "$lineage_dir/privkey.pem" docker/ssl/server-key.pem sudo chmod 600 docker/ssl/server-key.pem || true - # 启用 443 配置(证书已就绪):不清理,仅追加;传递 SNI 开关(默认 full 启用) + # Enable 443 config (certs ready): append only; pass SNI flag (enabled by default in full) local gen_args=(--mode full --domain "$DOMAIN_NAME" --no-clean --ssl-mode letsencrypt) [[ "$WITH_SNI443" == "true" ]] && gen_args+=(--enable-sni443) bash "$DOCKER_SCRIPTS_DIR/generate-config.sh" "${gen_args[@]}" || true - # 热重载 nginx 以启用 443 + # Hot-reload nginx to enable 443 docker compose exec -T nginx nginx -s reload || docker compose restart nginx } -# 清理现有部署 +# Clean existing deployment clean_deployment() { if [[ "$CLEAN_MODE" == "true" ]]; then - log_warning "清理现有部署..." + log_warning "Cleaning existing deployment..." - # 停止并删除容器 + # Stop and remove containers if [[ -f "docker-compose.yml" ]]; then docker compose down -v --remove-orphans 2>/dev/null || true fi - # 优雅停止后兜底强制清理命名容器 + # After graceful stop, force-clean named containers as fallback docker stop -t 10 privydrop-nginx privydrop-coturn 2>/dev/null || true docker rm -f privydrop-nginx privydrop-coturn 2>/dev/null || true - # 兜底清理项目网络(若存在) + # Fallback: remove project network (if present) docker network rm privydrop_privydrop-network 2>/dev/null || true - # 删除镜像 + # Remove images docker images | grep privydrop | awk '{print $3}' | xargs -r docker rmi -f 2>/dev/null || true - # 清理配置文件 + # Clean configuration files rm -rf docker/nginx/conf.d/*.conf docker/ssl/* logs/* .env 2>/dev/null || true - log_success "清理完成" + log_success "Cleanup complete" - if [[ $# -eq 1 ]]; then # 如果只有--clean参数 + if [[ $# -eq 1 ]]; then # If only --clean parameter exit 0 fi fi } -# 确保 TURN 服务按需启动(当指定 --with-turn 时) +# Ensure TURN service starts when requested (--with-turn) ensure_turn_running() { if [[ "$WITH_TURN" != "true" ]]; then return 0 fi - # 未运行则显式启用 profile 启动 coturn + # If not running, start coturn via profile if ! docker compose ps | grep -q "privydrop-coturn"; then - log_info "启动 TURN 服务 (profile: turn)..." + log_info "Starting TURN service (profile: turn)..." docker compose --profile turn up -d coturn || true fi } -# 环境检测和配置生成 +# Environment detection and configuration generation setup_environment() { - log_info "设置环境..." + log_info "Setting up environment..." - # 确保脚本可执行 + # Ensure scripts are executable chmod +x "$DOCKER_SCRIPTS_DIR"/*.sh 2>/dev/null || true - # 运行环境检测 + # Run environment detection local detect_args="" [[ -n "$DOMAIN_NAME" ]] && detect_args="--domain $DOMAIN_NAME" [[ -n "$DEPLOYMENT_MODE" ]] && detect_args="$detect_args --mode $DEPLOYMENT_MODE" [[ "$WITH_SNI443" == "true" ]] && detect_args="$detect_args --enable-sni443" if ! bash "$DOCKER_SCRIPTS_DIR/detect-environment.sh" $detect_args; then - log_error "环境检测失败" + log_error "Environment detection failed" exit 1 fi - # 生成配置文件 + # Generate configuration files if ! bash "$DOCKER_SCRIPTS_DIR/generate-config.sh" $detect_args; then - log_error "配置生成失败" + log_error "Configuration generation failed" exit 1 fi - log_success "环境设置完成" + log_success "Environment setup complete" } -# 构建和启动服务 +# Build and start services deploy_services() { - log_info "构建和启动服务..." + log_info "Building and starting services..." - # 确保日志目录存在并放宽权限,避免容器无法写日志(coturn/nginx 等) + # Ensure log directories exist and relax permissions so containers (coturn/nginx etc.) can write logs mkdir -p logs logs/nginx logs/backend logs/frontend logs/coturn 2>/dev/null || true chmod 777 -R logs 2>/dev/null || true - log_info "日志目录已准备并授权: ./logs (权限 777)" + log_info "Log directories prepared and permissions set: ./logs (mode 777)" - # 停止现有服务 + # Stop existing services if docker compose ps | grep -q "Up"; then - log_info "停止现有服务..." + log_info "Stopping existing services..." docker compose down fi - # 确定启用的服务(Compose V2 需要将 --profile 放在子命令之前) + # Determine enabled services (Compose V2 requires --profile before the subcommand) local profiles="" if [[ "$WITH_NGINX" == "true" ]]; then profiles="$profiles --profile nginx" @@ -375,28 +375,28 @@ deploy_services() { profiles="$profiles --profile turn" fi - # 构建镜像(先并行,失败则串行回退) - log_info "构建Docker镜像..." + # Build images (parallel first, fall back to serial on failure) + log_info "Building Docker images..." set +e docker compose build --parallel local build_status=$? set -e if [[ $build_status -ne 0 ]]; then - log_warning "并行构建失败,回退为串行构建..." + log_warning "Parallel build failed; falling back to serial build..." docker compose build fi - # 启动服务(--profile 需置于 up 之前) - log_info "启动服务..." + # Start services (--profile must precede up) + log_info "Starting services..." # shellcheck disable=SC2086 docker compose $profiles up -d - log_success "服务启动完成" + log_success "Services started" } -# 等待服务就绪 +# Wait for services to be ready wait_for_services() { - log_info "等待服务就绪..." + log_info "Waiting for services to be ready..." local max_attempts=60 local attempt=0 @@ -406,12 +406,12 @@ wait_for_services() { local backend_ready=false local frontend_ready=false - # 检查后端健康状态 + # Check backend health if curl -f http://localhost:3001/health &> /dev/null; then backend_ready=true fi - # 检查前端健康状态 + # Check frontend health if curl -f http://localhost:3002/api/health &> /dev/null; then frontend_ready=true fi @@ -429,58 +429,58 @@ wait_for_services() { echo "" if [[ "$services_ready" == "true" ]]; then - log_success "所有服务已就绪" + log_success "All services are ready" return 0 else - log_error "服务启动超时" - log_info "查看服务状态: docker compose ps" - log_info "查看服务日志: docker compose logs -f" + log_error "Service startup timed out" + log_info "View service status: docker compose ps" + log_info "View service logs: docker compose logs -f" return 1 fi } -# 运行部署后检查 +# Run post-deployment checks post_deployment_checks() { - log_info "运行部署后检查..." + log_info "Running post-deployment checks..." - # 检查容器状态 - log_info "检查容器状态..." + # Check container status + log_info "Checking container status..." docker compose ps - # full+nginx 场景追加 HTTPS 健康检查(若定义了域名) + # In full+nginx, add HTTPS health check (if domain defined) if [[ -f ".env" ]]; then local dep_mode="$(grep "DEPLOYMENT_MODE=" .env | cut -d'=' -f2)" local dname="$(grep "DOMAIN_NAME=" .env | cut -d'=' -f2)" if [[ "$dep_mode" == "full" && -n "$dname" ]]; then - log_info "测试: HTTPS 健康检查 https://$dname/api/health" + log_info "Test: HTTPS health check https://$dname/api/health" if curl -fsS "https://$dname/api/health" >/dev/null; then - log_success "HTTPS 健康检查通过" + log_success "HTTPS health check passed" else - log_warning "HTTPS 健康检查失败。若证书刚签发,请稍等或执行: bash docker/scripts/generate-config.sh --mode full --domain $dname --no-clean && docker compose exec -T nginx nginx -s reload" + log_warning "HTTPS health check failed. If the certificate was just issued, wait a bit or run: bash docker/scripts/generate-config.sh --mode full --domain $dname --no-clean && docker compose exec -T nginx nginx -s reload" fi fi fi - # 运行健康检查测试 + # Run health-check tests if [[ -f "test-health-apis.sh" ]]; then - log_info "运行健康检查测试..." + log_info "Running health-check tests..." if bash test-health-apis.sh; then - log_success "健康检查测试通过" + log_success "Health-check tests passed" else - log_warning "健康检查测试失败,但服务可能仍然正常" + log_warning "Health-check tests failed, but services may still be working" fi fi - log_success "部署后检查完成" + log_success "Post-deployment checks complete" } -# 显示部署结果 +# Show deployment results show_deployment_info() { echo "" - echo -e "${GREEN}🎉 PrivyDrop 部署完成!${NC}" + echo -e "${GREEN}🎉 PrivyDrop deployment complete!${NC}" echo "" - # 读取配置信息 + # Read configuration local local_ip="" local public_ip="" local frontend_port="" @@ -501,47 +501,47 @@ show_deployment_info() { turn_enabled_env=$(grep "TURN_ENABLED=" .env | cut -d'=' -f2) fi - echo -e "${BLUE}📋 访问信息:${NC}" + echo -e "${BLUE}📋 Access Info:${NC}" - # 判定是否公网场景(public/full) + # Determine if public scenario (public/full) local is_public="false" if [[ "$deployment_mode" == "public" || "$deployment_mode" == "full" || "$network_mode" == "public" ]]; then is_public="true" fi if [[ "$is_public" == "true" ]]; then - # 公网展示优先域名,其次公网IP + # For public scenarios, prefer domain, then public IP if [[ -n "$domain_name" ]]; then if [[ "$WITH_NGINX" == "true" || "$deployment_mode" == "full" ]]; then - echo " 公网访问: https://$domain_name" - echo " API 地址: https://$domain_name" + echo " Public access: https://$domain_name" + echo " API: https://$domain_name" else - echo " 公网访问: http://$domain_name:${frontend_port:-3002}" - echo " API 地址: http://$domain_name:${backend_port:-3001}" + echo " Public access: http://$domain_name:${frontend_port:-3002}" + echo " API: http://$domain_name:${backend_port:-3001}" fi elif [[ -n "$public_ip" ]]; then - echo " 公网访问: http://$public_ip:${frontend_port:-3002}" - echo " API 地址: http://$public_ip:${backend_port:-3001}" + echo " Public access: http://$public_ip:${frontend_port:-3002}" + echo " API: http://$public_ip:${backend_port:-3001}" else - # 回退:无法获取公网IP时给出局域网与本机 - echo " 前端应用: http://localhost:${frontend_port:-3002}" - echo " 后端API: http://localhost:${backend_port:-3001}" + # Fallback: show LAN and localhost if public IP is unavailable + echo " Frontend: http://localhost:${frontend_port:-3002}" + echo " Backend API: http://localhost:${backend_port:-3001}" fi else - # 内网/基础模式:本机+局域网 - echo " 前端应用: http://localhost:${frontend_port:-3002}" - echo " 后端API: http://localhost:${backend_port:-3001}" + # Private/basic: localhost + LAN + echo " Frontend: http://localhost:${frontend_port:-3002}" + echo " Backend API: http://localhost:${backend_port:-3001}" if [[ -n "$local_ip" ]] && [[ "$local_ip" != "127.0.0.1" ]]; then echo "" - echo -e "${BLUE}🌐 局域网访问:${NC}" - echo " 前端应用: http://$local_ip:${frontend_port:-3002}" - echo " 后端API: http://$local_ip:${backend_port:-3001}" + echo -e "${BLUE}🌐 LAN Access:${NC}" + echo " Frontend: http://$local_ip:${frontend_port:-3002}" + echo " Backend API: http://$local_ip:${backend_port:-3001}" fi fi if [[ "$WITH_NGINX" == "true" ]]; then echo "" - echo -e "${BLUE}🔀 Nginx代理:${NC}" + echo -e "${BLUE}🔀 Nginx Proxy:${NC}" if [[ -n "$domain_name" ]]; then echo " HTTP: http://$domain_name" [[ -f "docker/ssl/server-cert.pem" ]] && echo " HTTPS: https://$domain_name" @@ -555,18 +555,18 @@ show_deployment_info() { fi echo "" - echo -e "${BLUE}🔧 管理命令:${NC}" - echo " 查看状态: docker compose ps" - echo " 查看日志: docker compose logs -f [服务名]" - echo " 重启服务: docker compose restart [服务名]" - echo " 停止服务: docker compose down" - echo " 完全清理: $0 --clean" + echo -e "${BLUE}🔧 Management Commands:${NC}" + echo " Status: docker compose ps" + echo " Logs: docker compose logs -f [service]" + echo " Restart: docker compose restart [service]" + echo " Stop: docker compose down" + echo " Full cleanup: $0 --clean" if [[ -f "docker/ssl/ca-cert.pem" ]]; then echo "" - echo -e "${BLUE}🔒 SSL证书:${NC}" - echo " CA证书: docker/ssl/ca-cert.pem" - echo " 要信任HTTPS连接,请将CA证书导入浏览器" + echo -e "${BLUE}🔒 SSL Certificates:${NC}" + echo " CA certificate: docker/ssl/ca-cert.pem" + echo " To trust HTTPS, import the CA certificate into your browser" fi if [[ "$WITH_TURN" == "true" || "$turn_enabled_env" == "true" ]]; then @@ -578,12 +578,12 @@ show_deployment_info() { fi echo "" - echo -e "${BLUE}🔄 TURN服务器:${NC}" - # 展示优先域名的 TURN 信息,否则展示公网IP + echo -e "${BLUE}🔄 TURN Server:${NC}" + # Prefer domain for TURN info; otherwise show public IP if [[ -n "$domain_name" ]]; then echo " STUN: stun:${domain_name}:3478" echo " TURN (UDP): turn:${domain_name}:3478" - echo " TURN (TLS): turns:turn.${domain_name}:443 (如已配置 443 SNI 分流)" + echo " TURN (TLS): turns:turn.${domain_name}:443 (if 443 SNI split is configured)" elif [[ -n "$public_ip" ]]; then echo " STUN: stun:${public_ip}:3478" echo " TURN: turn:${public_ip}:3478" @@ -591,77 +591,77 @@ show_deployment_info() { echo " STUN: stun:${local_ip}:3478" echo " TURN: turn:${local_ip}:3478" fi - echo " 用户名: ${turn_username:-privydrop}" - echo " 密码: (保存在.env文件中)" + echo " Username: ${turn_username:-privydrop}" + echo " Password: (stored in .env)" fi echo "" - echo -e "${YELLOW}💡 提示:${NC}" - echo " - 首次启动可能需要几分钟来下载和构建镜像" - echo " - 如遇问题,请查看日志: docker compose logs -f" - echo " - 更多帮助: $0 --help" + echo -e "${YELLOW}💡 Tips:${NC}" + echo " - First run may take several minutes to download and build images" + echo " - If issues occur, check logs: docker compose logs -f" + echo " - More help: $0 --help" echo "" - # 公网场景追加:如何测试域名(HTTPS+Nginx) + # Public scenario: how to test a domain (HTTPS+Nginx) if [[ "$is_public" == "true" && -z "$domain_name" ]]; then - echo -e "${BLUE}🌍 公网域名部署(HTTPS + Nginx)快速测试:${NC}" - echo " 1) 将你的域名 A 记录解析到 ${public_ip:-}" - echo " 可选:将 turn. 也解析到同一IP,用于 TURN 主机名" - echo " 2) 运行: ./deploy.sh --mode full --domain --with-nginx --with-turn" - echo " 3) 放行端口: 80, 443, 3478/udp, 5349/tcp, 5349/udp" - echo " 4) 验证: https:// 正常打开,/api/health 返回 200" - echo " WebRTC: 打开 webrtc-internals,观察是否出现 relay 候选 (TURN)" - echo " 注: 目前 Docker 版本未启用 443 SNI 转发至 coturn,如需 turns:443 请后续启用 stream 分流。" + echo -e "${BLUE}🌍 Public domain deployment (HTTPS + Nginx) quick test:${NC}" + echo " 1) Point your domain A record to ${public_ip:-}" + echo " Optional: also point turn. to the same IP for TURN hostname" + echo " 2) Run: ./deploy.sh --mode full --domain --with-nginx --with-turn" + echo " 3) Open ports: 80, 443, 3478/udp, 5349/tcp, 5349/udp" + echo " 4) Verify: https:// opens, /api/health returns 200" + echo " WebRTC: open chrome://webrtc-internals and check for relay candidates (TURN)" + echo " Note: The Docker setup does not enable 443 SNI to coturn by default; enable stream SNI if you need turns:443." echo "" fi } -# 主函数 +# Main function main() { - echo -e "${BLUE}=== PrivyDrop Docker 一键部署 ===${NC}" + echo -e "${BLUE}=== PrivyDrop Docker One-Click Deployment ===${NC}" echo "" - # 解析命令行参数 + # Parse command-line arguments parse_arguments "$@" - # 检查依赖 + # Check dependencies check_dependencies echo "" - # 清理模式 + # Clean mode clean_deployment - # 若仅执行清理(未指定其它参数),直接退出,避免进入环境检测 + # If only cleaning (no other args), exit early to skip env detection if [[ "$CLEAN_MODE" == "true" && -z "$DEPLOYMENT_MODE" && "$WITH_NGINX" == "false" && "$WITH_TURN" == "false" && -z "$DOMAIN_NAME" ]]; then - log_success "清理完成(仅清理模式),已退出。" + log_success "Cleanup complete (clean-only mode). Exiting." exit 0 fi - # 环境设置 + # Environment setup setup_environment echo "" - # 部署服务 + # Deploy services deploy_services echo "" - # 若为 full + nginx,自动签发证书并启用 443 + # If full + nginx, automatically issue certs and enable 443 provision_letsencrypt_cert || true - # 确保 TURN 启动(当请求了 --with-turn 时) + # Ensure TURN is running (when requested with --with-turn) ensure_turn_running || true - # 等待服务就绪 + # Wait for services to be ready if wait_for_services; then echo "" post_deployment_checks show_deployment_info else - log_error "部署失败,请检查日志: docker compose logs" + log_error "Deployment failed. Please check logs: docker compose logs" exit 1 fi } -# 捕获中断信号 -trap 'log_warning "部署被中断"; exit 1' INT TERM +# Trap interrupt signals +trap 'log_warning "Deployment interrupted"; exit 1' INT TERM -# 运行主函数 +# Run main function main "$@" diff --git a/docker-compose.yml b/docker-compose.yml index f5f786f..4bc9527 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - # Redis缓存服务 + # Redis cache service redis: image: redis:7-alpine container_name: privydrop-redis @@ -16,7 +16,7 @@ services: retries: 3 start_period: 5s - # 后端信令服务 + # Backend signaling service backend: build: context: ./backend @@ -49,7 +49,7 @@ services: retries: 3 start_period: 40s - # 前端应用 + # Frontend app frontend: build: context: ./frontend @@ -83,7 +83,7 @@ services: retries: 3 start_period: 120s - # Nginx反向代理 + # Nginx reverse proxy nginx: image: nginx:alpine container_name: privydrop-nginx @@ -105,7 +105,7 @@ services: profiles: - nginx - # TURN/STUN服务器 (可选,用于NAT穿透) + # TURN/STUN server (optional, for NAT traversal) coturn: image: coturn/coturn:4.6.2 container_name: privydrop-coturn diff --git a/docker/scripts/detect-environment.sh b/docker/scripts/detect-environment.sh index 267cb6d..3c6b6d4 100755 --- a/docker/scripts/detect-environment.sh +++ b/docker/scripts/detect-environment.sh @@ -1,13 +1,13 @@ #!/bin/bash -# 颜色定义 +# Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color -# 全局变量 +# Global variables NETWORK_MODE="" LOCAL_IP="" PUBLIC_IP="" @@ -181,7 +181,7 @@ resolve_local_ip() { LOCAL_IP="${IP_CANDIDATES[0]}" } -# 日志函数 +# Logging helpers log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } @@ -198,22 +198,22 @@ log_error() { echo -e "${RED}❌ $1${NC}" } -# 检测网络环境 +# Detect network environment detect_network_environment() { - log_info "检测网络环境..." + log_info "Detecting network environment..." resolve_local_ip if [[ -z "$LOCAL_IP" ]]; then LOCAL_IP="127.0.0.1" - log_warning "无法自动检测本机IP,使用默认值: $LOCAL_IP" + log_warning "Unable to detect host IP; using default: $LOCAL_IP" fi if [[ "$FORCED_MODE" == "private" ]]; then NETWORK_MODE="private" PUBLIC_IP="" - log_info "已通过参数指定网络模式: $NETWORK_MODE" - echo " 本机IP: $LOCAL_IP" + log_info "Network mode set via parameters: $NETWORK_MODE" + echo " Local IP: $LOCAL_IP" return 0 fi @@ -227,26 +227,26 @@ detect_network_environment() { if is_routable_public_ip "$PUBLIC_IP"; then mode_guess="public" else - log_warning "检测到测试或保留网段公网IP,按内网环境处理" + log_warning "Public IP is test/reserved range; treating as private" fi else - log_warning "公网连接不稳定,按内网环境处理" + log_warning "Public connectivity unstable; treating as private" fi fi if [[ -z "$FORCED_MODE" ]]; then if [[ "$mode_guess" == "public" ]]; then - echo " 本机IP: $LOCAL_IP" - echo " 公网IP: $PUBLIC_IP" + echo " Local IP: $LOCAL_IP" + echo " Public IP: $PUBLIC_IP" printed_prompt_info="true" - read -r -p "是否按公网模式继续?(Y/n): " confirm /dev/null || confirm="Y" + read -r -p "Continue in public mode? (Y/n): " confirm /dev/null || confirm="Y" confirm=${confirm:-Y} if [[ "$confirm" =~ ^[Yy]$ ]]; then NETWORK_MODE="public" else NETWORK_MODE="private" PUBLIC_IP="" - log_warning "按用户选择,已切换为内网模式" + log_warning "Switched to private mode per user choice" fi else NETWORK_MODE="private" @@ -254,7 +254,7 @@ detect_network_environment() { else NETWORK_MODE="$FORCED_MODE" if [[ "$FORCED_MODE" == "public" && -z "$PUBLIC_IP" ]]; then - log_warning "未能检测到公网IP,仍按公网模式继续,请确认网络配置" + log_warning "Could not detect public IP; continuing as public mode. Please verify network config" fi fi @@ -263,121 +263,121 @@ detect_network_environment() { fi if [[ "$FORCED_MODE" == "public" ]]; then - log_info "已通过参数指定网络模式: $NETWORK_MODE" + log_info "Network mode set via parameters: $NETWORK_MODE" elif [[ "$NETWORK_MODE" == "public" ]]; then - log_success "检测到公网环境" + log_success "Public network detected" else - log_success "检测到内网环境" + log_success "Private network detected" fi if [[ "$printed_prompt_info" == "false" ]]; then - echo " 本机IP: $LOCAL_IP" + echo " Local IP: $LOCAL_IP" if [[ "$NETWORK_MODE" == "public" && -n "$PUBLIC_IP" ]]; then - echo " 公网IP: $PUBLIC_IP" + echo " Public IP: $PUBLIC_IP" fi fi } -# 检查系统资源 +# Check system resources check_system_resources() { - log_info "检查系统资源..." + log_info "Checking system resources..." local warnings=0 - # 检查内存 + # Check memory if command -v free >/dev/null 2>&1; then TOTAL_MEM=$(free -m | awk 'NR==2{print $2}') if [[ $TOTAL_MEM -lt 512 ]]; then - log_error "内存不足: ${TOTAL_MEM}MB (建议至少512MB)" + log_error "Insufficient memory: ${TOTAL_MEM}MB (512MB+ recommended)" return 1 elif [[ $TOTAL_MEM -lt 1024 ]]; then - log_warning "内存较少: ${TOTAL_MEM}MB (建议至少1GB)" + log_warning "Low memory: ${TOTAL_MEM}MB (1GB+ recommended)" warnings=$((warnings + 1)) else - log_success "内存充足: ${TOTAL_MEM}MB" + log_success "Memory OK: ${TOTAL_MEM}MB" fi else - log_warning "无法检测内存使用情况" + log_warning "Unable to read memory usage" warnings=$((warnings + 1)) fi - # 检查磁盘空间 + # Check disk usage DISK_USAGE=$(df -h / | awk 'NR==2{print $5}' | sed 's/%//') if [[ $DISK_USAGE -gt 95 ]]; then - log_error "磁盘空间不足: ${DISK_USAGE}%已使用" + log_error "Insufficient disk space: ${DISK_USAGE}% used" return 1 elif [[ $DISK_USAGE -gt 80 ]]; then - log_warning "磁盘空间紧张: ${DISK_USAGE}%已使用" + log_warning "Disk space tight: ${DISK_USAGE}% used" warnings=$((warnings + 1)) else - log_success "磁盘空间充足: ${DISK_USAGE}%已使用" + log_success "Disk space OK: ${DISK_USAGE}% used" fi - # 检查可用磁盘空间 + # Check available disk space AVAILABLE_SPACE=$(df -BG / | awk 'NR==2{print $4}' | sed 's/G//') if [[ $AVAILABLE_SPACE -lt 2 ]]; then - log_error "可用磁盘空间不足: ${AVAILABLE_SPACE}GB (建议至少2GB)" + log_error "Not enough free disk space: ${AVAILABLE_SPACE}GB (2GB+ recommended)" return 1 fi if [[ $warnings -gt 0 ]]; then - log_warning "系统资源检查通过,但有 $warnings 个警告" + log_warning "System resource check passed with $warnings warning(s)" else - log_success "系统资源检查通过" + log_success "System resource check passed" fi return 0 } -# 验证Docker环境 +# Validate Docker environment verify_docker_installation() { - log_info "检查Docker环境..." + log_info "Checking Docker environment..." if ! command -v docker &> /dev/null; then - log_error "Docker未安装" - echo "请安装Docker: https://docs.docker.com/get-docker/" + log_error "Docker is not installed" + echo "Please install Docker: https://docs.docker.com/get-docker/" return 1 fi if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then - log_error "Docker Compose未安装" - echo "请安装Docker Compose: https://docs.docker.com/compose/install/" + log_error "Docker Compose is not installed" + echo "Please install Docker Compose: https://docs.docker.com/compose/install/" return 1 fi - # 检查Docker服务状态 + # Check Docker service status if ! docker info &> /dev/null; then - log_error "Docker服务未运行" - echo "请启动Docker服务" + log_error "Docker service is not running" + echo "Please start the Docker service" return 1 fi - # 检查Docker版本 + # Check Docker version DOCKER_VERSION=$(docker --version | grep -oE '[0-9]+\.[0-9]+' | head -1) - log_success "Docker版本: $DOCKER_VERSION" + log_success "Docker version: $DOCKER_VERSION" - # 检查Docker Compose版本 + # Check Docker Compose version if command -v docker-compose &> /dev/null; then COMPOSE_VERSION=$(docker-compose --version | grep -oE '[0-9]+\.[0-9]+' | head -1) - log_success "Docker Compose版本: $COMPOSE_VERSION" + log_success "Docker Compose version: $COMPOSE_VERSION" else - COMPOSE_VERSION=$(docker compose version --short 2>/dev/null || echo "内置") - log_success "Docker Compose版本: $COMPOSE_VERSION" + COMPOSE_VERSION=$(docker compose version --short 2>/dev/null || echo "built-in") + log_success "Docker Compose version: $COMPOSE_VERSION" fi return 0 } -# 检查端口占用 +# Check port usage check_port_availability() { local ports="$1" - log_info "检查端口占用..." + log_info "Checking port usage..." local occupied_ports=() IFS=',' read -ra PORT_ARRAY <<< "$ports" for port in "${PORT_ARRAY[@]}"; do - port=$(echo "$port" | xargs) # 去除空格 + port=$(echo "$port" | xargs) # Trim spaces if command -v ss >/dev/null 2>&1; then if ss -tuln | grep -q ":$port "; then occupied_ports+=("$port") @@ -387,40 +387,40 @@ check_port_availability() { occupied_ports+=("$port") fi else - log_warning "无法检查端口占用情况 (缺少ss和netstat命令)" + log_warning "Unable to check port usage (missing ss and netstat)" return 0 fi done if [[ ${#occupied_ports[@]} -gt 0 ]]; then - log_warning "以下端口已被占用: ${occupied_ports[*]}" - log_info "可以通过修改 .env 中的端口,或先执行 './deploy.sh --clean' / 'docker-compose down' 清理旧容器" + log_warning "Ports in use: ${occupied_ports[*]}" + log_info "Change ports in .env, or run './deploy.sh --clean' / 'docker-compose down' to clean old containers" else - log_success "所有端口都可用" + log_success "All ports available" fi } -# 检测部署模式 +# Detect deployment mode detect_deployment_mode() { - log_info "确定部署模式..." + log_info "Determining deployment mode..." if [[ "$NETWORK_MODE" == "public" ]] && [[ -n "$DOMAIN_NAME" ]]; then DEPLOYMENT_MODE="full" - log_success "部署模式: 完整模式 (HTTPS + TURN服务器)" + log_success "Deployment mode: full (HTTPS + TURN server)" elif [[ "$NETWORK_MODE" == "public" ]]; then DEPLOYMENT_MODE="public" - log_success "部署模式: 公网模式 (HTTP + 自签证书)" + log_success "Deployment mode: public (HTTP + self-signed)" else DEPLOYMENT_MODE="basic" - log_success "部署模式: 基础模式 (内网HTTP)" + log_success "Deployment mode: basic (intranet HTTP)" fi } -# 主函数 +# Main function main() { - echo -e "${BLUE}=== PrivyDrop Docker 环境检测 ===${NC}\n" + echo -e "${BLUE}=== PrivyDrop Docker Environment Check ===${NC}\n" - # 读取命令行参数 + # Parse command-line arguments while [[ $# -gt 0 ]]; do case $1 in --domain) @@ -452,18 +452,18 @@ main() { esac done - # 执行检测 + # Run checks detect_network_environment echo "" if ! check_system_resources; then - log_error "系统资源检查失败,请解决资源问题后重试" + log_error "System resource check failed; resolve resource issues and retry" exit 1 fi echo "" if ! verify_docker_installation; then - log_error "Docker环境检查失败,请安装并启动Docker" + log_error "Docker environment check failed; please install and start Docker" exit 1 fi echo "" @@ -474,14 +474,14 @@ main() { detect_deployment_mode echo "" - log_success "环境检测完成!" - echo -e "${BLUE}检测结果:${NC}" - echo " 网络模式: $NETWORK_MODE" - echo " 本机IP: $LOCAL_IP" - [[ -n "$PUBLIC_IP" ]] && echo " 公网IP: $PUBLIC_IP" - echo " 部署模式: $DEPLOYMENT_MODE" + log_success "Environment check complete!" + echo -e "${BLUE}Results:${NC}" + echo " Network mode: $NETWORK_MODE" + echo " Local IP: $LOCAL_IP" + [[ -n "$PUBLIC_IP" ]] && echo " Public IP: $PUBLIC_IP" + echo " Deployment mode: $DEPLOYMENT_MODE" - # 导出环境变量供其他脚本使用 + # Export env vars for other scripts export NETWORK_MODE export LOCAL_IP export PUBLIC_IP @@ -492,7 +492,7 @@ main() { return 0 } -# 如果脚本被直接执行 +# If the script is executed directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi diff --git a/docker/scripts/generate-config.sh b/docker/scripts/generate-config.sh index 4c17a32..8036910 100755 --- a/docker/scripts/generate-config.sh +++ b/docker/scripts/generate-config.sh @@ -1,17 +1,17 @@ #!/bin/bash -# 导入环境检测脚本 +# Import environment detection script SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/detect-environment.sh" -# 颜色定义 +# Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color -# 日志函数 +# Logging helpers log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } @@ -28,7 +28,7 @@ log_error() { echo -e "${RED}❌ $1${NC}" } -# 默认与全局参数 +# Defaults and global parameters WITH_TURN="${WITH_TURN:-false}" TURN_EXTERNAL_IP_OVERRIDE="" TURN_MIN_PORT_DEFAULT=49152 @@ -43,13 +43,13 @@ parse_turn_port_range() { return 0 fi if [[ ! "$range" =~ ^([0-9]{2,5})-([0-9]{2,5})$ ]]; then - log_error "--turn-port-range 格式应为 MIN-MAX,例如 49152-49252" + log_error "--turn-port-range must be MIN-MAX, e.g., 49152-49252" exit 1 fi local min="${BASH_REMATCH[1]}" local max="${BASH_REMATCH[2]}" if (( min < 1 || max > 65535 || min >= max )); then - log_error "无效端口段:$min-$max,应在 1-65535 且 MIN/dev/null || true rm -f docker/nginx/nginx.conf 2>/dev/null || true rm -f docker/nginx/conf.d/*.conf 2>/dev/null || true rm -f docker/coturn/turnserver.conf 2>/dev/null || true - # 默认不清理 docker/ssl,除非显式 --reset-ssl + # Do not clean docker/ssl by default unless --reset-ssl is set if [[ "$RESET_SSL" == "true" ]]; then - log_warning "按请求重置 SSL 证书目录: docker/ssl/*" + log_warning "Resetting SSL directory as requested: docker/ssl/*" rm -f docker/ssl/* 2>/dev/null || true fi } -# 显示帮助信息 +# Show help show_help() { cat << 'EOF' -PrivyDrop 配置生成脚本(Docker 版) +PrivyDrop Config Generator (Docker) -用法: bash docker/scripts/generate-config.sh [选项] +Usage: bash docker/scripts/generate-config.sh [options] -选项: - --mode MODE 生成模式: private|basic|public|full - private/basic: 内网HTTP;默认不启用TURN,前端直连后端 - public: 公网HTTP + 启用TURN(无域名也可,TURN host=公网IP优先) - full: 完整HTTPS + 启用TURN(建议配合域名,前端走域名HTTPS) - --with-turn 在任意模式下启用TURN(含private/basic)。默认 external-ip=LOCAL_IP - --turn-external-ip IP 显式指定TURN external-ip;不指定则使用 PUBLIC_IP,否则回退 LOCAL_IP - --turn-port-range R 指定TURN端口段(UDP),格式 MIN-MAX;默认 49152-49252 - --domain DOMAIN 指定域名(用于 Nginx/证书/TURN realm,如 turn.DOMAIN) - --local-ip IP 指定本机局域网IP(不传则自动探测) - --enable-sni443 启用 443 SNI 分流(turn.DOMAIN → coturn:5349,其余 → web:8443) - --no-sni443 关闭 443 SNI 分流(HTTPS 直接监听 443) - --help 显示本帮助 - --no-clean 跳过清理历史生成物(推荐用于二次生成避免清理 SSL) - --reset-ssl 强制清理 docker/ssl/*(默认不清理) - --ssl-mode MODE 证书模式:letsencrypt|self-signed|provided - - full 模式默认 letsencrypt;private/public 默认 self-signed +Options: + --mode MODE Generation mode: private|basic|public|full + private/basic: Intranet HTTP; TURN disabled by default; frontend talks directly to backend + public: Public HTTP + TURN enabled (works without domain; TURN host prefers public IP) + full: Full HTTPS + TURN enabled (domain recommended; frontend via domain HTTPS) + --with-turn Enable TURN in any mode (including private/basic). Default external-ip=LOCAL_IP + --turn-external-ip IP Explicit TURN external-ip; if not set, use PUBLIC_IP, otherwise fallback to LOCAL_IP + --turn-port-range R TURN UDP port range, format MIN-MAX; default 49152-49252 + --domain DOMAIN Domain (for Nginx/certs/TURN realm, e.g., turn.DOMAIN) + --local-ip IP Local intranet IP (auto-detected if omitted) + --enable-sni443 Enable 443 SNI split (turn.DOMAIN → coturn:5349, others → web:8443) + --no-sni443 Disable 443 SNI split (HTTPS listens directly on 443) + --help Show this help + --no-clean Skip cleaning previous outputs (useful for regeneration without wiping SSL) + --reset-ssl Force clean docker/ssl/* (not cleaned by default) + --ssl-mode MODE Cert mode: letsencrypt|self-signed|provided + - full defaults to letsencrypt; private/public default to self-signed -环境变量(可选): - PUBLIC_IP 显式指定公网IP;仅在 public/full 模式有效。 - TURN external-ip 写入优先使用 PUBLIC_IP, - 留空则回退为 LOCAL_IP(仅同局域网可用,穿透受限)。 +Environment variables (optional): + PUBLIC_IP Explicit public IP; only used in public/full. + TURN external-ip prefers PUBLIC_IP, + fallback to LOCAL_IP (LAN-only; NAT traversal limited). -生成内容(自动写入关键变量): - - .env 核心环境变量(含 NEXT_PUBLIC_API_URL/CORS 等) - - docker/nginx/* Nginx 反向代理配置(private/basic 也会生成 HTTP 配置) - - docker/ssl/* 自签证书(private/basic/public 生成;full 可替换为正式证书) - - docker/coturn/turnserver.conf 在 public/full 或使用 --with-turn 时生成/覆盖 +Outputs (with key variables set automatically): + - .env Core env vars (including NEXT_PUBLIC_API_URL/CORS) + - docker/nginx/* Nginx reverse proxy configs (HTTP also generated for private/basic) + - docker/ssl/* Self-signed certs (generated for private/basic/public; replace with real certs for full) + - docker/coturn/turnserver.conf Generated/overwritten in public/full or when --with-turn is set -重要说明: - - TURN external-ip 赋值逻辑为 external-ip=${PUBLIC_IP:-${LOCAL_IP}} - 即优先使用 PUBLIC_IP,否则回退 LOCAL_IP。 - - private/basic 模式不会覆盖 docker/coturn/turnserver.conf, - 若此前生成过 TURN 配置,该文件可能保留历史 external-ip。 +Notes: + - TURN external-ip is set as external-ip=${PUBLIC_IP:-${LOCAL_IP}} + i.e., prefer PUBLIC_IP, otherwise fallback to LOCAL_IP. + - private/basic does not overwrite docker/coturn/turnserver.conf; + if TURN was generated before, that file may retain a previous external-ip. -示例: - # 1) 纯内网(推荐开发/内网快速跑通) +Examples: + # 1) Pure intranet (recommended for dev/quick LAN testing) bash docker/scripts/generate-config.sh --mode private [--local-ip 192.168.0.113] - # 2) 内网 + 启用TURN(默认 external-ip=LOCAL_IP,端口段=49152-49252) + # 2) Intranet + TURN (default external-ip=LOCAL_IP, ports=49152-49252) bash docker/scripts/generate-config.sh --mode private --with-turn [--local-ip 192.168.0.113] - # 3) 内网 + 启用TURN(自定义端口段/显式external-ip) + # 3) Intranet + TURN (custom port range / explicit external-ip) bash docker/scripts/generate-config.sh --mode private --with-turn \ --turn-port-range 56000-56100 --turn-external-ip 192.168.0.113 \ [--local-ip 192.168.0.113] - # 4) 公网HTTP + TURN(自动探测公网IP,不带域名也可;自动注入 NEXT_PUBLIC_API_URL) + # 4) Public HTTP + TURN (auto-detect public IP; inject NEXT_PUBLIC_API_URL) bash docker/scripts/generate-config.sh --mode public --local-ip 192.168.0.113 - # 5) 公网HTTP + TURN(指定公网IP,避免外网探测) + # 5) Public HTTP + TURN (explicit public IP; avoid external detection) PUBLIC_IP=1.2.3.4 bash docker/scripts/generate-config.sh --mode public --local-ip 192.168.0.113 - # 6) HTTPS + TURN(有域名) + # 6) HTTPS + TURN (with domain) bash docker/scripts/generate-config.sh --mode full --domain example.com --local-ip 192.168.0.113 -内网带TURN测试提示(不改脚本的最小步骤): - A) 一步生成(推荐): +Intranet with TURN quick tip (minimal changes): + A) One-step (recommended): bash docker/scripts/generate-config.sh --mode private --with-turn --local-ip 192.168.0.113 - 然后 bash ./deploy.sh --mode private --with-turn - B) 分步生成: - 先按 private 生成部署前后端,再 docker compose up -d coturn + then bash ./deploy.sh --mode private --with-turn + B) Step-by-step: + Generate private for web/backend first, then docker compose up -d coturn EOF } -# 生成环境变量文件 +# Generate environment variables file generate_env_file() { - log_info "生成环境变量配置..." + log_info "Generating environment variable config..." local env_file=".env" - # 读取已有配置以保留用户自定义字段(如代理、TURN) + # Read existing config to keep user-defined fields (e.g., proxy, TURN) declare -A existing_env=() if [[ -f "$env_file" ]]; then while IFS= read -r line; do @@ -168,14 +168,14 @@ generate_env_file() { done < "$env_file" fi - # 生成随机密码(同时保存到全局变量,供后续生成 TURN 配置使用) + # Generate a random password (also saved globally for TURN configuration later) local turn_password="${existing_env[TURN_PASSWORD]}" if [[ -z "$turn_password" ]]; then turn_password=$(openssl rand -base64 32 2>/dev/null || echo "privydrop$(date +%s)") fi - # 计算不同部署模式下的访问入口 - # 同时支持 localhost 与 本机IP,两者都可用于浏览器访问,便于Docker直连或本机调试 + # Compute access endpoints for different deployment modes + # Support both localhost and host IP for browser access; helpful for Docker direct access or local debugging local cors_origin="http://${LOCAL_IP}:3002,http://localhost:3002" local api_url="http://${LOCAL_IP}:3001" local ssl_mode="self-signed" @@ -188,20 +188,20 @@ generate_env_file() { local next_public_turn_password="" if [[ "$DEPLOYMENT_MODE" == "public" ]]; then - # 公网无域名:前端直连后端,自动写入基于 PUBLIC_IP(无则回退 LOCAL_IP) + # Public without domain: frontend connects directly to backend; use PUBLIC_IP (fallback LOCAL_IP) local effective_public_host="${PUBLIC_IP:-$LOCAL_IP}" cors_origin="http://${effective_public_host}:3002,http://localhost:3002" api_url="http://${effective_public_host}:3001" turn_enabled="true" elif [[ "$DEPLOYMENT_MODE" == "full" ]]; then - # 有域名HTTPS:前端与后端都走域名,由 Nginx /api 转发 + # With domain + HTTPS: both frontend and backend via domain; Nginx proxies /api cors_origin="https://${DOMAIN_NAME:-$LOCAL_IP}" api_url="https://${DOMAIN_NAME:-$LOCAL_IP}" ssl_mode="letsencrypt" turn_enabled="true" fi - # 若显式启用 TURN,则覆盖模式默认 + # If TURN explicitly enabled, override mode defaults if [[ "$WITH_TURN" == "true" ]]; then turn_enabled="true" fi @@ -211,7 +211,7 @@ generate_env_file() { turn_host_value="turn.${DOMAIN_NAME}" turn_realm_value="turn.${DOMAIN_NAME}" else - # 无域名时:主机优先使用 PUBLIC_IP,其次回退 LOCAL_IP + # Without domain: prefer PUBLIC_IP; fallback to LOCAL_IP turn_host_value="${PUBLIC_IP:-$LOCAL_IP}" turn_realm_value="turn.local" fi @@ -221,7 +221,7 @@ generate_env_file() { next_public_turn_password="$turn_password" fi - # 端口段(默认 49152-49252,可被 --turn-port-range 覆盖) + # Port range (default 49152-49252; overridable via --turn-port-range) local turn_min_port_value="${TURN_MIN_PORT:-$TURN_MIN_PORT_DEFAULT}" local turn_max_port_value="${TURN_MAX_PORT:-$TURN_MAX_PORT_DEFAULT}" @@ -230,7 +230,7 @@ generate_env_file() { local https_proxy_value="${HTTPS_PROXY:-${existing_env[HTTPS_PROXY]}}" local no_proxy_value="${NO_PROXY:-${existing_env[NO_PROXY]:-$default_no_proxy}}" - # 将关键 TURN 参数暴露给后续步骤 + # Expose key TURN parameters to later steps TURN_ENABLED="$turn_enabled" TURN_USERNAME="$turn_username_value" TURN_PASSWORD="$turn_password" @@ -240,13 +240,13 @@ generate_env_file() { TURN_MAX_PORT="$turn_max_port_value" cat > "$env_file" << EOF -# PrivyDrop Docker 配置文件 -# 自动生成时间: $(date) -# 网络模式: $NETWORK_MODE -# 部署模式: $DEPLOYMENT_MODE +# PrivyDrop Docker configuration +# Generated at: $(date) +# Network mode: $NETWORK_MODE +# Deployment mode: $DEPLOYMENT_MODE # ============================================================================= -# 网络配置 +# Network config # ============================================================================= CORS_ORIGIN=${cors_origin} NEXT_PUBLIC_API_URL=${api_url} @@ -255,7 +255,7 @@ NEXT_PUBLIC_TURN_USERNAME=${next_public_turn_username} NEXT_PUBLIC_TURN_PASSWORD=${next_public_turn_password} # ============================================================================= -# 端口配置 +# Port config # ============================================================================= FRONTEND_PORT=3002 BACKEND_PORT=3001 @@ -263,13 +263,13 @@ HTTP_PORT=80 HTTPS_PORT=443 # ============================================================================= -# Redis配置 +# Redis config # ============================================================================= REDIS_HOST=redis REDIS_PORT=6379 # ============================================================================= -# 部署配置 +# Deployment config # ============================================================================= DEPLOYMENT_MODE=${DEPLOYMENT_MODE} NETWORK_MODE=${NETWORK_MODE} @@ -277,13 +277,13 @@ LOCAL_IP=${LOCAL_IP} PUBLIC_IP=${PUBLIC_IP:-} # ============================================================================= -# SSL配置 +# SSL config # ============================================================================= SSL_MODE=${ssl_mode} DOMAIN_NAME=${DOMAIN_NAME:-} # ============================================================================= -# TURN服务器配置 (可选) +# TURN server config (optional) # ============================================================================= TURN_ENABLED=${turn_enabled} TURN_USERNAME=${turn_username_value} @@ -293,29 +293,29 @@ TURN_MIN_PORT=${turn_min_port_value} TURN_MAX_PORT=${turn_max_port_value} # ============================================================================= -# Nginx配置 +# Nginx config # ============================================================================= NGINX_SERVER_NAME=${DOMAIN_NAME:-${LOCAL_IP}} # ============================================================================= -# 日志配置 +# Logging config # ============================================================================= LOG_LEVEL=info # ============================================================================= -# 代理配置 (可选) +# Proxy config (optional) # ============================================================================= HTTP_PROXY=${http_proxy_value} HTTPS_PROXY=${https_proxy_value} NO_PROXY=${no_proxy_value} EOF - log_success "环境变量配置已生成: $env_file" + log_success "Environment variable config generated: $env_file" } -# 生成Nginx配置 +# Generate Nginx config generate_nginx_config() { - log_info "生成Nginx配置..." + log_info "Generating Nginx config..." mkdir -p docker/nginx/conf.d @@ -323,7 +323,7 @@ generate_nginx_config() { local upstream_backend="backend:3001" local upstream_frontend="frontend:3002" - # 生成主Nginx配置 + # Generate main Nginx config cat > docker/nginx/nginx.conf << 'EOF' user nginx; worker_processes auto; @@ -340,14 +340,14 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; - # 日志格式 + # Log format log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; - # 基础配置 + # Basic settings sendfile on; tcp_nopush on; tcp_nodelay on; @@ -355,12 +355,12 @@ http { types_hash_max_size 2048; server_tokens off; - # 客户端配置 + # Client settings client_max_body_size 100M; client_header_timeout 60s; client_body_timeout 60s; - # Gzip配置 + # Gzip settings gzip on; gzip_vary on; gzip_min_length 1000; @@ -374,15 +374,15 @@ http { application/xml+rss application/json; - # 包含站点配置 + # Include site configs include /etc/nginx/conf.d/*.conf; } EOF - # 生成站点配置 + # Generate site config mkdir -p docker/letsencrypt-www cat > docker/nginx/conf.d/default.conf << EOF -# 上游服务定义 +# Upstream definitions upstream backend { server ${upstream_backend}; keepalive 32; @@ -393,29 +393,29 @@ upstream frontend { keepalive 32; } -# HTTP服务器配置 +# HTTP server config server { listen 80; server_name ${server_name}; - # 安全头 + # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; - # ACME 回源,用于 Let's Encrypt 签发/续期 + # ACME upstream for Let's Encrypt issuance/renewal location /.well-known/acme-challenge/ { root /var/www/certbot; } - # 健康检查端点 + # Health check endpoint location /nginx-health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } - # 后端API代理 + # Backend API proxy location /api/ { proxy_pass http://backend/api/; proxy_http_version 1.1; @@ -427,13 +427,13 @@ server { proxy_set_header X-Forwarded-Proto \$scheme; proxy_cache_bypass \$http_upgrade; - # 超时配置 + # Timeout settings proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; } - # 后端健康检查代理 + # Backend health-check proxy location /health { proxy_pass http://backend/health; proxy_http_version 1.1; @@ -443,7 +443,7 @@ server { proxy_set_header X-Forwarded-Proto \$scheme; } - # Socket.IO代理 + # Socket.IO proxy location /socket.io/ { proxy_pass http://backend; proxy_http_version 1.1; @@ -454,12 +454,12 @@ server { proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; - # WebSocket特殊配置 + # WebSocket-specific settings proxy_buffering off; proxy_cache off; } - # 前端应用代理 + # Frontend app proxy location / { proxy_pass http://frontend; proxy_http_version 1.1; @@ -471,41 +471,41 @@ server { proxy_set_header X-Forwarded-Proto \$scheme; proxy_cache_bypass \$http_upgrade; - # Next.js特殊配置 + # Next.js-specific settings proxy_buffering off; } } EOF - log_success "Nginx配置已生成" - echo " 主配置: docker/nginx/nginx.conf" - echo " 站点配置: docker/nginx/conf.d/default.conf" + log_success "Nginx config generated" + echo " Main config: docker/nginx/nginx.conf" + echo " Site config: docker/nginx/conf.d/default.conf" } -# 生成SSL证书 +# Generate SSL certificates generate_ssl_certificates() { if [[ "$SSL_MODE" == "self-signed" ]] || [[ "$NETWORK_MODE" == "private" ]]; then - log_info "生成自签名SSL证书..." + log_info "Generating self-signed SSL certificates..." mkdir -p docker/ssl - # 生成CA私钥 + # Generate CA private key openssl genrsa -out docker/ssl/ca-key.pem 4096 2>/dev/null - # 生成CA证书 + # Generate CA certificate openssl req -new -x509 -days 365 -key docker/ssl/ca-key.pem \ -out docker/ssl/ca-cert.pem \ -subj "/C=CN/ST=Local/L=Local/O=PrivyDrop/CN=PrivyDrop-CA" 2>/dev/null - # 生成服务器私钥 + # Generate server private key openssl genrsa -out docker/ssl/server-key.pem 4096 2>/dev/null - # 生成服务器证书请求 + # Generate server CSR openssl req -new -key docker/ssl/server-key.pem \ -out docker/ssl/server.csr \ -subj "/C=CN/ST=Local/L=Local/O=PrivyDrop/CN=${LOCAL_IP}" 2>/dev/null - # 创建扩展配置 + # Create extensions config cat > docker/ssl/server.ext << EOF authorityKeyIdentifier=keyid,issuer basicConstraints=CA:FALSE @@ -520,32 +520,32 @@ IP.1 = ${LOCAL_IP} IP.2 = 127.0.0.1 EOF - # 签名服务器证书 + # Sign server certificate openssl x509 -req -days 365 -in docker/ssl/server.csr \ -CA docker/ssl/ca-cert.pem -CAkey docker/ssl/ca-key.pem \ -out docker/ssl/server-cert.pem -CAcreateserial \ -extensions v3_req -extfile docker/ssl/server.ext 2>/dev/null - # 清理临时文件 + # Clean temporary files rm -f docker/ssl/server.csr docker/ssl/server.ext docker/ssl/ca-cert.srl - # 设置权限 + # Set permissions chmod 600 docker/ssl/*-key.pem chmod 644 docker/ssl/*-cert.pem - log_success "SSL证书已生成: docker/ssl/" - log_info "要信任证书,请导入CA证书: docker/ssl/ca-cert.pem" + log_success "SSL certificates generated: docker/ssl/" + log_info "To trust the cert, import the CA cert: docker/ssl/ca-cert.pem" - # 自签场景直接生成 443 配置 + # For self-signed, generate 443 config immediately if [[ "$DEPLOYMENT_MODE" != "basic" ]]; then generate_https_nginx_config fi fi } -# 生成HTTPS Nginx配置 +# Generate HTTPS Nginx config generate_https_nginx_config() { - log_info "生成HTTPS Nginx配置..." + log_info "Generating HTTPS Nginx config..." local https_port="443" if [[ "$ENABLE_SNI443" == "true" ]]; then https_port="8443" @@ -553,12 +553,12 @@ generate_https_nginx_config() { cat >> docker/nginx/conf.d/default.conf << EOF -# HTTPS服务器配置 +# HTTPS server config server { listen ${https_port} ssl http2; server_name ${DOMAIN_NAME:-${LOCAL_IP}}; - # SSL配置 + # SSL settings ssl_certificate /etc/nginx/ssl/server-cert.pem; ssl_certificate_key /etc/nginx/ssl/server-key.pem; ssl_protocols TLSv1.2 TLSv1.3; @@ -567,20 +567,20 @@ server { ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; - # 安全头 + # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; - # 健康检查端点 + # Health check endpoint location /nginx-health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } - # 后端API代理 + # Backend API proxy location /api/ { proxy_pass http://backend/api/; proxy_http_version 1.1; @@ -593,7 +593,7 @@ server { proxy_cache_bypass \$http_upgrade; } - # 后端健康检查代理 + # Backend health-check proxy location /health { proxy_pass http://backend/health; proxy_http_version 1.1; @@ -603,7 +603,7 @@ server { proxy_set_header X-Forwarded-Proto https; } - # Socket.IO代理 + # Socket.IO proxy location /socket.io/ { proxy_pass http://backend; proxy_http_version 1.1; @@ -617,7 +617,7 @@ server { proxy_cache off; } - # 前端应用代理 + # Frontend app proxy location / { proxy_pass http://frontend; proxy_http_version 1.1; @@ -633,24 +633,24 @@ server { } EOF - log_success "HTTPS配置已添加" + log_success "HTTPS config added" } -# 生成 Nginx stream SNI 分流(443) +# Generate Nginx stream SNI split (443) generate_stream_sni443() { if [[ "$ENABLE_SNI443" != "true" ]]; then return 0 fi if [[ -z "$DOMAIN_NAME" ]]; then - log_warning "SNI 443 需要域名,未指定域名,跳过 stream 配置" + log_warning "SNI 443 requires a domain; none specified, skipping stream config" return 0 fi - # 避免重复追加 + # Avoid duplicate appends if grep -q "## SNI 443 stream" docker/nginx/nginx.conf 2>/dev/null; then - log_info "已存在 SNI 443 stream 配置,跳过追加" + log_info "SNI 443 stream config already exists; skipping" return 0 fi - log_info "追加 SNI 443 stream 配置到 nginx.conf" + log_info "Append SNI 443 stream config to nginx.conf" cat >> docker/nginx/nginx.conf << EOF ## SNI 443 stream @@ -672,34 +672,34 @@ stream { EOF } -# 当证书存在时再启用 443 配置(适用于 letsencrypt/provided) +# Enable 443 only when certs exist (for letsencrypt/provided) enable_https_if_cert_present() { if [[ -f "docker/ssl/server-cert.pem" && -f "docker/ssl/server-key.pem" ]]; then - # SNI 开启时,先追加 stream 分流,再生成 8443/443 的 HTTPS + # With SNI enabled, append stream split first, then generate HTTPS on 8443/443 if [[ "$ENABLE_SNI443" == "true" && -n "$DOMAIN_NAME" ]]; then generate_stream_sni443 fi - # 若 default.conf 中尚未存在 HTTPS server,则追加(端口根据 SNI 开关决定) + # If HTTPS server is not present in default.conf, append it (port depends on SNI flag) local expected="listen 443 ssl" [[ "$ENABLE_SNI443" == "true" ]] && expected="listen 8443 ssl" if ! grep -q "$expected" docker/nginx/conf.d/default.conf 2>/dev/null; then generate_https_nginx_config else - log_info "检测到已存在 HTTPS(${ENABLE_SNI443:+SNI=on}) 配置,跳过追加" + log_info "Existing HTTPS (${ENABLE_SNI443:+SNI=on}) config detected; skipping" fi else - log_warning "未检测到证书 (docker/ssl/server-*.pem),暂不启用 443 配置" + log_warning "No certificates detected (docker/ssl/server-*.pem); 443 config not enabled yet" fi } -# 生成Coturn配置 +# Generate Coturn config generate_coturn_config() { if [[ "$TURN_ENABLED" == "true" ]]; then - log_info "生成Coturn TURN服务器配置..." + log_info "Generating Coturn TURN server config..." mkdir -p docker/coturn - # 计算 external-ip:优先 --turn-external-ip,再次 PUBLIC_IP,最后 LOCAL_IP + # Compute external-ip: prefer --turn-external-ip, then PUBLIC_IP, then LOCAL_IP local external_ip_value if [[ -n "$TURN_EXTERNAL_IP_OVERRIDE" ]]; then external_ip_value="$TURN_EXTERNAL_IP_OVERRIDE" @@ -713,68 +713,68 @@ generate_coturn_config() { local max_port_value="${TURN_MAX_PORT:-$TURN_MAX_PORT_DEFAULT}" cat > docker/coturn/turnserver.conf << EOF -# PrivyDrop TURN服务器配置 -# 自动生成时间: $(date) +# PrivyDrop TURN server configuration +# Generated at: $(date) -# 监听端口 +# Listen ports listening-port=3478 tls-listening-port=5349 -# 监听IP +# Listen IPs listening-ip=0.0.0.0 relay-ip=0.0.0.0 -# 外部IP (用于NAT环境) +# External IP (for NAT) external-ip=${external_ip_value} -# 服务器域名 +# Server domain realm=${TURN_REALM} server-name=${TURN_REALM} -# 认证方式 +# Authentication method lt-cred-mech -# 用户认证 +# User authentication user=${TURN_USERNAME}:${TURN_PASSWORD} -# SSL证书 (如果启用TLS) +# SSL certificates (if TLS enabled) cert=/etc/ssl/certs/server-cert.pem pkey=/etc/ssl/certs/server-key.pem -# 日志配置 +# Logging configuration no-stdout-log log-file=/var/log/turnserver.log verbose -# 安全配置 +# Security settings no-cli no-loopback-peers no-multicast-peers -# 性能配置 +# Performance settings min-port=${min_port_value} max-port=${max_port_value} -# 数据库 (可选) +# Database (optional) # userdb=/var/lib/turn/turndb -# 其他配置 +# Miscellaneous mobility no-tlsv1 no-tlsv1_1 EOF - log_success "Coturn配置已生成: docker/coturn/turnserver.conf" - log_info "TURN服务器用户名: ${TURN_USERNAME}" - log_warning "TURN服务器密码已保存在.env文件中" + log_success "Coturn config generated: docker/coturn/turnserver.conf" + log_info "TURN server username: ${TURN_USERNAME}" + log_warning "TURN server password saved in .env" fi } -# 生成Docker忽略文件 +# Generate Docker ignore files generate_dockerignore() { - log_info "生成Docker忽略文件..." + log_info "Generating Docker ignore files..." - # 后端.dockerignore + # Backend .dockerignore cat > backend/.dockerignore << EOF node_modules npm-debug.log* @@ -791,7 +791,7 @@ logs *.log EOF - # 前端.dockerignore + # Frontend .dockerignore cat > frontend/.dockerignore << EOF node_modules .next @@ -810,28 +810,28 @@ public/sw.js public/workbox-*.js EOF - log_success "Docker忽略文件已生成" + log_success "Docker ignore files generated" } -# 创建日志目录 +# Create log directories create_log_directories() { - log_info "创建日志目录..." + log_info "Creating log directories..." mkdir -p logs/{nginx,backend,frontend,coturn} - # 设置权限 + # Set permissions chmod 755 logs chmod 755 logs/* - log_success "日志目录已创建: logs/" + log_success "Log directories created: logs/" } -# 主函数 +# Main function main() { - echo -e "${BLUE}=== PrivyDrop 配置生成 ===${NC}" + echo -e "${BLUE}=== PrivyDrop Config Generation ===${NC}" echo "" - # 解析参数(与环境检测脚本保持一致) + # Parse arguments (consistent with the environment detection script) while [[ $# -gt 0 ]]; do case $1 in --domain) @@ -893,32 +893,32 @@ main() { esac done - # 先清理上一次生成物(避免历史残留误导) + # Clean previous outputs first (avoid stale leftovers) cleanup_previous_artifacts - # 首先运行环境检测 + # Run environment detection first if ! detect_network_environment; then - log_error "环境检测失败" + log_error "Environment detection failed" exit 1 fi if ! check_system_resources; then - log_error "系统资源检查失败" + log_error "System resource check failed" exit 1 fi detect_deployment_mode echo "" - # 生成所有配置文件 + # Generate all configuration files generate_env_file echo "" generate_nginx_config echo "" - # 证书生成策略: - # - private/public 默认自签;full 默认 letsencrypt(由部署脚本触发签发与复制) + # Certificate generation policy: + # - private/public use self-signed; full uses letsencrypt (issued/copied by deploy script) if [[ -z "$SSL_MODE" ]]; then if [[ "$DEPLOYMENT_MODE" == "full" ]]; then SSL_MODE="letsencrypt" @@ -927,7 +927,7 @@ main() { fi fi - # SNI 443 默认启用:full 模式且有域名,除非显式 --no-sni443 + # SNI on 443 enabled by default: full mode with domain, unless --no-sni443 if [[ -z "$ENABLE_SNI443" ]]; then if [[ "$DEPLOYMENT_MODE" == "full" && -n "$DOMAIN_NAME" ]]; then ENABLE_SNI443=true @@ -939,7 +939,7 @@ main() { generate_ssl_certificates echo "" - # full/provided/letsencrypt:仅在证书就绪时启用 443 + # full/provided/letsencrypt: enable 443 only when certs are ready if [[ "$DEPLOYMENT_MODE" == "full" ]]; then enable_https_if_cert_present echo "" @@ -954,20 +954,20 @@ main() { create_log_directories echo "" - log_success "🎉 所有配置文件生成完成!" + log_success "🎉 All configuration files generated!" echo "" - echo -e "${BLUE}生成的文件:${NC}" - echo " .env - 环境变量配置" - echo " docker/nginx/ - Nginx配置" - echo " docker/ssl/ - SSL证书" - [[ "$TURN_ENABLED" == "true" ]] && echo " docker/coturn/ - TURN服务器配置" - echo " logs/ - 日志目录" + echo -e "${BLUE}Generated files:${NC}" + echo " .env - Environment variables" + echo " docker/nginx/ - Nginx config" + echo " docker/ssl/ - SSL certificates" + [[ "$TURN_ENABLED" == "true" ]] && echo " docker/coturn/ - TURN server config" + echo " logs/ - Log directories" echo "" - echo -e "${BLUE}下一步:${NC}" - echo " 运行 './deploy.sh' 开始部署" + echo -e "${BLUE}Next steps:${NC}" + echo " Run './deploy.sh' to start deployment" } -# 如果脚本被直接执行 +# If the script is executed directly if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" fi diff --git a/docker/scripts/test-deployment.sh b/docker/scripts/test-deployment.sh index 7d35b34..4785b63 100755 --- a/docker/scripts/test-deployment.sh +++ b/docker/scripts/test-deployment.sh @@ -1,21 +1,21 @@ #!/bin/bash -# PrivyDrop Docker 部署测试脚本 -# 用于验证部署的完整性和功能 +# PrivyDrop Docker deployment test script +# Validate deployment integrity and functionality -# 颜色定义 +# Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' -# 测试结果统计 +# Test result counters TESTS_PASSED=0 TESTS_FAILED=0 TOTAL_TESTS=0 -# 日志函数 +# Logging helpers log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } @@ -34,13 +34,13 @@ log_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } -# 测试函数 +# Test functions run_test() { local test_name="$1" local test_command="$2" TOTAL_TESTS=$((TOTAL_TESTS + 1)) - log_info "测试: $test_name" + log_info "Test: $test_name" if eval "$test_command" >/dev/null 2>&1; then log_success "$test_name" @@ -51,35 +51,35 @@ run_test() { fi } -# Docker环境测试 +# Docker environment tests test_docker_environment() { - echo -e "${BLUE}=== Docker环境测试 ===${NC}" + echo -e "${BLUE}=== Docker Environment Tests ===${NC}" - run_test "Docker已安装" "command -v docker" - run_test "Docker服务运行中" "docker info" - run_test "Docker Compose可用" "docker-compose --version || docker compose version" + run_test "Docker installed" "command -v docker" + run_test "Docker daemon running" "docker info" + run_test "Docker Compose available" "docker-compose --version || docker compose version" echo "" } -# 容器状态测试 +# Container status tests test_container_status() { - echo -e "${BLUE}=== 容器状态测试 ===${NC}" + echo -e "${BLUE}=== Container Status Tests ===${NC}" - # 检查容器是否存在和运行 + # Check if containers exist and are running local containers=("privydrop-redis" "privydrop-backend" "privydrop-frontend") for container in "${containers[@]}"; do - run_test "容器 $container 运行中" "docker ps | grep -q $container" + run_test "Container $container is running" "docker ps | grep -q $container" done - # 检查容器健康状态 + # Check container health for container in "${containers[@]}"; do if docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q "$container.*healthy"; then - log_success "容器 $container 健康状态正常" + log_success "Container $container health OK" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_warning "容器 $container 健康状态未知或不健康" + log_warning "Container $container health unknown or unhealthy" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) done @@ -87,78 +87,78 @@ test_container_status() { echo "" } -# 网络连接测试 +# Network connectivity tests test_network_connectivity() { - echo -e "${BLUE}=== 网络连接测试 ===${NC}" + echo -e "${BLUE}=== Network Connectivity Tests ===${NC}" - # 测试端口连通性 - local ports=("3002:前端" "3001:后端" "6379:Redis") + # Test port connectivity + local ports=("3002:Frontend" "3001:Backend" "6379:Redis") for port_info in "${ports[@]}"; do local port=$(echo "$port_info" | cut -d':' -f1) local service=$(echo "$port_info" | cut -d':' -f2) - run_test "$service 端口 $port 可访问" "nc -z localhost $port" + run_test "$service port $port reachable" "nc -z localhost $port" done - # 测试容器间网络 - run_test "后端可连接Redis" "docker-compose exec -T backend sh -c 'nc -z redis 6379'" - run_test "前端可连接后端" "curl -f http://localhost:3001/health" + # Test inter-container networking + run_test "Backend can connect to Redis" "docker-compose exec -T backend sh -c 'nc -z redis 6379'" + run_test "Frontend can reach backend" "curl -f http://localhost:3001/health" echo "" } -# API功能测试 +# API functionality tests test_api_functionality() { - echo -e "${BLUE}=== API功能测试 ===${NC}" + echo -e "${BLUE}=== API Functionality Tests ===${NC}" - # 健康检查API - run_test "后端健康检查API" "curl -f http://localhost:3001/health" - run_test "前端健康检查API" "curl -f http://localhost:3002/api/health" + # Health check APIs + run_test "Backend health check API" "curl -f http://localhost:3001/health" + run_test "Frontend health check API" "curl -f http://localhost:3002/api/health" - # 后端详细健康检查 + # Backend detailed health check if curl -f http://localhost:3001/health/detailed >/dev/null 2>&1; then local redis_status=$(curl -s http://localhost:3001/health/detailed | jq -r '.dependencies.redis.status' 2>/dev/null) if [[ "$redis_status" == "connected" ]]; then - log_success "Redis连接状态正常" + log_success "Redis connection OK" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "Redis连接状态异常" + log_error "Redis connection issue" TESTS_FAILED=$((TESTS_FAILED + 1)) fi else - log_error "详细健康检查API不可用" + log_error "Detailed health check API unavailable" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) - # 业务API测试 - run_test "获取房间API" "curl -f http://localhost:3001/api/get_room" - run_test "创建房间API" "curl -f -X POST -H 'Content-Type: application/json' -d '{\"roomId\":\"test123\"}' http://localhost:3001/api/create_room" + # Application API tests + run_test "Get room API" "curl -f http://localhost:3001/api/get_room" + run_test "Create room API" "curl -f -X POST -H 'Content-Type: application/json' -d '{\"roomId\":\"test123\"}' http://localhost:3001/api/create_room" echo "" } -# WebRTC功能测试 +# WebRTC functionality tests test_webrtc_functionality() { - echo -e "${BLUE}=== WebRTC功能测试 ===${NC}" + echo -e "${BLUE}=== WebRTC Functionality Tests ===${NC}" - # 测试前端页面加载 + # Test frontend page load if curl -f http://localhost:3002 >/dev/null 2>&1; then - log_success "前端页面可访问" + log_success "Frontend page reachable" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "前端页面不可访问" + log_error "Frontend page not reachable" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) - # 测试Socket.IO连接 (简单测试) + # Test Socket.IO connection (basic) if curl -f http://localhost:3001/socket.io/socket.io.js >/dev/null 2>&1; then - log_success "Socket.IO客户端脚本可访问" + log_success "Socket.IO client script reachable" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "Socket.IO客户端脚本不可访问" + log_error "Socket.IO client script not reachable" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) @@ -166,64 +166,64 @@ test_webrtc_functionality() { echo "" } -# 性能测试 +# Performance tests test_performance() { - echo -e "${BLUE}=== 性能测试 ===${NC}" + echo -e "${BLUE}=== Performance Tests ===${NC}" - # 内存使用测试 + # Memory usage test local backend_memory=$(docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}" | grep privydrop-backend | awk '{print $2}' | cut -d'/' -f1) local frontend_memory=$(docker stats --no-stream --format "table {{.Container}}\t{{.MemUsage}}" | grep privydrop-frontend | awk '{print $2}' | cut -d'/' -f1) if [[ -n "$backend_memory" ]]; then - log_info "后端内存使用: $backend_memory" + log_info "Backend memory usage: $backend_memory" fi if [[ -n "$frontend_memory" ]]; then - log_info "前端内存使用: $frontend_memory" + log_info "Frontend memory usage: $frontend_memory" fi - # 响应时间测试 + # Response time test local response_time=$(curl -o /dev/null -s -w '%{time_total}' http://localhost:3001/health) if (( $(echo "$response_time < 1.0" | bc -l) )); then - log_success "API响应时间正常: ${response_time}s" + log_success "API response time OK: ${response_time}s" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_warning "API响应时间较慢: ${response_time}s" + log_warning "API response time slow: ${response_time}s" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) echo "" } -# 安全测试 +# Security tests test_security() { - echo -e "${BLUE}=== 安全测试 ===${NC}" + echo -e "${BLUE}=== Security Tests ===${NC}" - # 检查容器用户 + # Check container users local backend_user=$(docker-compose exec -T backend whoami 2>/dev/null || echo "unknown") local frontend_user=$(docker-compose exec -T frontend whoami 2>/dev/null || echo "unknown") if [[ "$backend_user" != "root" ]]; then - log_success "后端容器使用非root用户: $backend_user" + log_success "Backend container uses non-root user: $backend_user" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_warning "后端容器使用root用户" + log_warning "Backend container runs as root" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) if [[ "$frontend_user" != "root" ]]; then - log_success "前端容器使用非root用户: $frontend_user" + log_success "Frontend container uses non-root user: $frontend_user" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_warning "前端容器使用root用户" + log_warning "Frontend container runs as root" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) - # 检查敏感信息泄露 + # Check for sensitive info leakage if curl -s http://localhost:3001/health/detailed | grep -q "password\|secret\|key" >/dev/null 2>&1; then - log_warning "健康检查API可能泄露敏感信息" + log_warning "Health check API may leak sensitive info" else - log_success "健康检查API未泄露敏感信息" + log_success "Health check API does not leak sensitive info" TESTS_PASSED=$((TESTS_PASSED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) @@ -231,27 +231,27 @@ test_security() { echo "" } -# 日志测试 +# Logging tests test_logging() { - echo -e "${BLUE}=== 日志测试 ===${NC}" + echo -e "${BLUE}=== Logging Tests ===${NC}" - # 检查日志目录 + # Check log directories if [[ -d "logs" ]]; then - log_success "日志目录存在" + log_success "Log directory exists" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_warning "日志目录不存在" + log_warning "Log directory does not exist" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) - # 检查日志文件 + # Check log files local log_files=("logs/backend" "logs/frontend") for log_dir in "${log_files[@]}"; do if [[ -d "$log_dir" ]]; then - log_success "日志目录 $log_dir 存在" + log_success "Log directory $log_dir exists" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_info "日志目录 $log_dir 不存在 (可能正常)" + log_info "Log directory $log_dir not found (may be normal)" fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) done @@ -259,39 +259,39 @@ test_logging() { echo "" } -# 配置文件测试 +# Configuration file tests test_configuration() { - echo -e "${BLUE}=== 配置文件测试 ===${NC}" + echo -e "${BLUE}=== Configuration File Tests ===${NC}" - # 检查环境变量文件 + # Check env file if [[ -f ".env" ]]; then - log_success ".env 文件存在" + log_success ".env file exists" TESTS_PASSED=$((TESTS_PASSED + 1)) - # 检查关键配置项 + # Check key configuration entries local required_vars=("LOCAL_IP" "CORS_ORIGIN" "NEXT_PUBLIC_API_URL") for var in "${required_vars[@]}"; do if grep -q "^$var=" .env; then - log_success "配置项 $var 已设置" + log_success "Config $var is set" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "配置项 $var 未设置" + log_error "Config $var is not set" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) done else - log_error ".env 文件不存在" + log_error ".env file not found" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) - # 检查Docker Compose文件 + # Check Docker Compose file if [[ -f "docker-compose.yml" ]]; then - log_success "docker-compose.yml 文件存在" + log_success "docker-compose.yml exists" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "docker-compose.yml 文件不存在" + log_error "docker-compose.yml not found" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 1)) @@ -299,25 +299,25 @@ test_configuration() { echo "" } -# 清理测试 +# Cleanup tests test_cleanup() { - echo -e "${BLUE}=== 清理功能测试 ===${NC}" + echo -e "${BLUE}=== Cleanup Tests ===${NC}" - # 测试清理命令是否可用 + # Verify cleanup commands work if [[ -f "deploy.sh" ]]; then - log_success "部署脚本存在" + log_success "Deployment script exists" TESTS_PASSED=$((TESTS_PASSED + 1)) - # 测试帮助命令 + # Test help command if bash deploy.sh --help >/dev/null 2>&1; then - log_success "部署脚本帮助功能正常" + log_success "Deployment script help works" TESTS_PASSED=$((TESTS_PASSED + 1)) else - log_error "部署脚本帮助功能异常" + log_error "Deployment script help fails" TESTS_FAILED=$((TESTS_FAILED + 1)) fi else - log_error "部署脚本不存在" + log_error "Deployment script not found" TESTS_FAILED=$((TESTS_FAILED + 1)) fi TOTAL_TESTS=$((TOTAL_TESTS + 2)) @@ -325,65 +325,65 @@ test_cleanup() { echo "" } -# 生成测试报告 +# Generate test report generate_report() { - echo -e "${BLUE}=== 测试报告 ===${NC}" + echo -e "${BLUE}=== Test Report ===${NC}" echo "" - echo "📊 测试统计:" - echo " 总测试数: $TOTAL_TESTS" - echo -e " 通过: ${GREEN}$TESTS_PASSED${NC}" - echo -e " 失败: ${RED}$TESTS_FAILED${NC}" + echo "📊 Test stats:" + echo " Total tests: $TOTAL_TESTS" + echo -e " Passed: ${GREEN}$TESTS_PASSED${NC}" + echo -e " Failed: ${RED}$TESTS_FAILED${NC}" local success_rate=$((TESTS_PASSED * 100 / TOTAL_TESTS)) - echo " 成功率: $success_rate%" + echo " Success rate: $success_rate%" echo "" - echo "📋 系统信息:" - echo " Docker版本: $(docker --version)" - echo " Docker Compose版本: $(docker-compose --version 2>/dev/null || docker compose version 2>/dev/null || echo '未知')" - echo " 操作系统: $(uname -s) $(uname -r)" - echo " 测试时间: $(date)" + echo "📋 System info:" + echo " Docker version: $(docker --version)" + echo " Docker Compose version: $(docker-compose --version 2>/dev/null || docker compose version 2>/dev/null || echo 'unknown')" + echo " OS: $(uname -s) $(uname -r)" + echo " Test time: $(date)" echo "" if [[ $TESTS_FAILED -eq 0 ]]; then - echo -e "${GREEN}🎉 所有测试通过!PrivyDrop 部署成功!${NC}" + echo -e "${GREEN}🎉 All tests passed! PrivyDrop deployment successful!${NC}" echo "" - echo "🔗 访问链接:" - echo " 前端应用: http://localhost:3002" - echo " 后端API: http://localhost:3001" + echo "🔗 Access links:" + echo " Frontend: http://localhost:3002" + echo " Backend API: http://localhost:3001" - # 显示局域网访问地址 + # Show LAN access addresses if [[ -f ".env" ]]; then local local_ip=$(grep "LOCAL_IP=" .env | cut -d'=' -f2) if [[ -n "$local_ip" && "$local_ip" != "127.0.0.1" ]]; then echo "" - echo "🌐 局域网访问:" - echo " 前端应用: http://$local_ip:3002" - echo " 后端API: http://$local_ip:3001" + echo "🌐 LAN access:" + echo " Frontend: http://$local_ip:3002" + echo " Backend API: http://$local_ip:3001" fi fi return 0 else - echo -e "${RED}❌ 有 $TESTS_FAILED 个测试失败${NC}" + echo -e "${RED}❌ $TESTS_FAILED test(s) failed${NC}" echo "" - echo "🔧 故障排除建议:" - echo " 1. 查看容器状态: docker-compose ps" - echo " 2. 查看容器日志: docker-compose logs -f" - echo " 3. 重新部署: bash deploy.sh" - echo " 4. 完全清理后重新部署: bash deploy.sh --clean" + echo "🔧 Troubleshooting tips:" + echo " 1. View container status: docker-compose ps" + echo " 2. View container logs: docker-compose logs -f" + echo " 3. Redeploy: bash deploy.sh" + echo " 4. Clean and redeploy: bash deploy.sh --clean" return 1 fi } -# 主函数 +# Main function main() { - echo -e "${BLUE}=== PrivyDrop Docker 部署测试开始 ===${NC}" + echo -e "${BLUE}=== PrivyDrop Docker Deployment Tests ===${NC}" echo "" - # 检查必要工具 + # Check required tools local missing_tools=() for tool in curl jq bc nc; do if ! command -v "$tool" >/dev/null 2>&1; then @@ -392,12 +392,12 @@ main() { done if [[ ${#missing_tools[@]} -gt 0 ]]; then - log_warning "缺少测试工具: ${missing_tools[*]}" - log_info "建议安装: sudo apt-get install curl jq bc netcat" + log_warning "Missing test tools: ${missing_tools[*]}" + log_info "Suggested install: sudo apt-get install curl jq bc netcat" echo "" fi - # 运行所有测试 + # Run all tests test_docker_environment test_container_status test_network_connectivity @@ -409,12 +409,12 @@ main() { test_configuration test_cleanup - # 生成报告 + # Generate report generate_report } -# 捕获中断信号 -trap 'echo -e "\n${YELLOW}测试被中断${NC}"; exit 1' INT TERM +# Trap interrupt signals +trap 'echo -e "\n${YELLOW}Tests interrupted${NC}"; exit 1' INT TERM -# 运行主函数 -main "$@" \ No newline at end of file +# Run main function +main "$@" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 5912025..4142b86 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,4 +1,4 @@ -# 多阶段构建 - 构建阶段 +# Multi-stage build — build stage FROM node:18-alpine AS builder ARG HTTP_PROXY @@ -10,54 +10,54 @@ ENV http_proxy ${HTTP_PROXY} \ no_proxy ${NO_PROXY} WORKDIR /app -# 复制package文件 +# Copy package files COPY package*.json ./ COPY pnpm-lock.yaml ./ -# 安装pnpm(使用npm,避免网络问题) +# Install pnpm RUN npm install -g pnpm --no-audit --no-fund -# 安装依赖 +# Install dependencies RUN pnpm install --frozen-lockfile -# 复制源代码 +# Copy source code COPY . . -# 在依赖安装之后再声明与使用构建期公开变量,避免仅 API/TURN 改动导致依赖层缓存失效 +# Declare and use build-time public vars after deps installation to avoid cache invalidation when only API/TURN change ARG NEXT_PUBLIC_API_URL ARG NEXT_PUBLIC_TURN_HOST ARG NEXT_PUBLIC_TURN_USERNAME ARG NEXT_PUBLIC_TURN_PASSWORD -# 前端构建期注入可公开环境变量(用于客户端直连后端与 TURN) +# Inject public env vars during frontend build (for client direct access to backend and TURN) ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} ENV NEXT_PUBLIC_TURN_HOST=${NEXT_PUBLIC_TURN_HOST} ENV NEXT_PUBLIC_TURN_USERNAME=${NEXT_PUBLIC_TURN_USERNAME} ENV NEXT_PUBLIC_TURN_PASSWORD=${NEXT_PUBLIC_TURN_PASSWORD} -# 设置环境变量 +# Set environment variables ENV NEXT_TELEMETRY_DISABLED 1 ENV NODE_ENV production -# 构建应用 +# Build the app RUN pnpm build -# 生产阶段 +# Production stage FROM node:18-alpine AS runner WORKDIR /app -# 创建非root用户 +# Create a non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nextjs -u 1001 -G nodejs -# 复制构建产物 +# Copy build artifacts COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY health-check.js ./ -# 设置环境变量 +# Set environment variables ENV NODE_ENV production ENV NEXT_TELEMETRY_DISABLED 1 ENV PORT 3002 @@ -65,18 +65,18 @@ ENV HOSTNAME "0.0.0.0" USER nextjs -# 暴露端口 +# Expose ports EXPOSE 3002 -# 使用Node.js脚本做健康检查(替代curl) +# Use a Node.js script for health checks (instead of curl) HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD node health-check.js -# 启动应用 +# Start the app CMD ["node", "server.js"] -# 运行期保留公开变量(非必需,但便于服务端渲染读取) -# 需在当前阶段重新声明 ARG,以便在此阶段展开到 ENV +# Keep public env vars at runtime (optional; helps SSR read them) +# Re-declare ARGs in this stage so they can expand into ENV ARG NEXT_PUBLIC_API_URL ARG NEXT_PUBLIC_TURN_HOST ARG NEXT_PUBLIC_TURN_USERNAME diff --git a/frontend/components/ClipboardApp.tsx b/frontend/components/ClipboardApp.tsx index 5b8ef19..f5a9b8d 100644 --- a/frontend/components/ClipboardApp.tsx +++ b/frontend/components/ClipboardApp.tsx @@ -49,7 +49,7 @@ const ClipboardApp = () => { handleDownloadFile, } = useFileTransferHandler({ messages, putMessageInMs }); - // 简化的 WebRTC 连接初始化 + // Simplified WebRTC connection initialization const { requestFile, requestFolder, @@ -60,7 +60,7 @@ const ClipboardApp = () => { putMessageInMs, }); - // 大大简化的房间管理 - 不再需要传递任何 WebRTC 依赖 + // Greatly simplified room management - No longer need to pass any WebRTC dependencies const { processRoomIdInput, joinRoom, diff --git a/test-docker-deployment.sh b/test-docker-deployment.sh deleted file mode 100644 index f616e05..0000000 --- a/test-docker-deployment.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# PrivyDrop Docker 部署快速测试脚本 -# 这是对 docker/scripts/test-deployment.sh 的简化版本 - -echo "🧪 运行 PrivyDrop Docker 部署测试..." -echo "" - -# 检查是否存在详细测试脚本 -if [[ -f "docker/scripts/test-deployment.sh" ]]; then - echo "📋 运行详细测试..." - bash docker/scripts/test-deployment.sh -else - echo "⚠️ 详细测试脚本不存在,运行基础测试..." - - # 基础测试 - echo "🔍 检查容器状态..." - docker-compose ps - - echo "" - echo "🏥 检查健康状态..." - - # 检查后端健康 - if curl -f http://localhost:3001/health >/dev/null 2>&1; then - echo "✅ 后端服务正常" - else - echo "❌ 后端服务异常" - fi - - # 检查前端健康 - if curl -f http://localhost:3002/api/health >/dev/null 2>&1; then - echo "✅ 前端服务正常" - else - echo "❌ 前端服务异常" - fi - - echo "" - echo "🔗 访问链接:" - echo " 前端应用: http://localhost:3002" - echo " 后端API: http://localhost:3001" -fi \ No newline at end of file diff --git a/test-health-apis.sh b/test-health-apis.sh index 6232951..8639e14 100644 --- a/test-health-apis.sh +++ b/test-health-apis.sh @@ -1,18 +1,18 @@ #!/bin/bash -# 颜色定义 +# Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color -# 测试结果统计 +# Test result counters TESTS_PASSED=0 TESTS_FAILED=0 TOTAL_TESTS=0 -# 日志函数 +# Logging helpers log_info() { echo -e "${BLUE}ℹ️ $1${NC}" } @@ -31,7 +31,7 @@ log_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } -# 测试函数 +# Test functions test_api() { local url="$1" local description="$2" @@ -39,84 +39,84 @@ test_api() { TOTAL_TESTS=$((TOTAL_TESTS + 1)) echo "" - log_info "测试: $description" + log_info "Test: $description" log_info "URL: $url" - # 发送请求并获取响应 + # Send request and capture response response=$(curl -s -w "\n%{http_code}" "$url" 2>/dev/null) if [ $? -ne 0 ]; then - log_error "请求失败 - 无法连接到服务" + log_error "Request failed - unable to connect to service" return 1 fi - # 分离响应体和状态码 + # Split response body and status code http_code=$(echo "$response" | tail -n1) response_body=$(echo "$response" | head -n -1) - # 检查HTTP状态码 + # Check HTTP status code if [ "$http_code" -eq "$expected_status" ]; then - log_success "HTTP状态码正确: $http_code" + log_success "HTTP status code OK: $http_code" else - log_error "HTTP状态码错误: 期望 $expected_status, 实际 $http_code" + log_error "HTTP status code mismatch: expected $expected_status, got $http_code" return 1 fi - # 检查JSON格式 + # Validate JSON format if echo "$response_body" | jq . >/dev/null 2>&1; then - log_success "响应格式为有效JSON" + log_success "Response is valid JSON" - # 显示格式化的JSON响应 - echo -e "${BLUE}响应内容:${NC}" + # Pretty-print JSON response + echo -e "${BLUE}Response body:${NC}" echo "$response_body" | jq . - # 检查必要字段 + # Verify required fields status=$(echo "$response_body" | jq -r '.status // empty') service=$(echo "$response_body" | jq -r '.service // empty') timestamp=$(echo "$response_body" | jq -r '.timestamp // empty') if [ -n "$status" ] && [ -n "$service" ] && [ -n "$timestamp" ]; then - log_success "包含必要字段: status, service, timestamp" + log_success "Contains required fields: status, service, timestamp" else - log_error "缺少必要字段" + log_error "Missing required fields" return 1 fi else - log_error "响应不是有效的JSON格式" - echo "响应内容: $response_body" + log_error "Response is not valid JSON" + echo "Response body: $response_body" return 1 fi return 0 } -# 检查服务是否运行 +# Check if service is running check_service() { local port="$1" local service_name="$2" if nc -z localhost "$port" 2>/dev/null; then - log_success "$service_name 服务运行中 (端口 $port)" + log_success "$service_name is running (port $port)" return 0 else - log_error "$service_name 服务未运行 (端口 $port)" + log_error "$service_name is not running (port $port)" return 1 fi } -# 等待服务启动 +# Wait for service to start wait_for_service() { local port="$1" local service_name="$2" local max_attempts=30 local attempt=0 - log_info "等待 $service_name 服务启动..." + log_info "Waiting for $service_name to start..." while [ $attempt -lt $max_attempts ]; do if nc -z localhost "$port" 2>/dev/null; then - log_success "$service_name 服务已启动" + log_success "$service_name started" return 0 fi @@ -125,94 +125,94 @@ wait_for_service() { sleep 2 done - log_error "$service_name 服务启动超时" + log_error "$service_name startup timed out" return 1 } -# 主测试函数 +# Main test function main() { - echo -e "${BLUE}=== PrivyDrop 健康检查API测试 ===${NC}" + echo -e "${BLUE}=== PrivyDrop Health Check API Tests ===${NC}" echo "" - # 检查必要工具 + # Check required tools if ! command -v curl &> /dev/null; then - log_error "curl 未安装,请先安装 curl" + log_error "curl is not installed; please install curl" exit 1 fi if ! command -v jq &> /dev/null; then - log_error "jq 未安装,请先安装 jq 用于JSON解析" + log_error "jq is not installed; please install jq for JSON parsing" exit 1 fi if ! command -v nc &> /dev/null; then - log_error "netcat 未安装,请先安装 nc 用于端口检查" + log_error "netcat is not installed; please install nc for port checks" exit 1 fi - # 检查服务状态 - echo -e "${BLUE}=== 检查服务状态 ===${NC}" + # Check service status + echo -e "${BLUE}=== Check Service Status ===${NC}" backend_running=false frontend_running=false - if check_service 3001 "后端"; then + if check_service 3001 "Backend"; then backend_running=true fi - if check_service 3002 "前端"; then + if check_service 3002 "Frontend"; then frontend_running=true fi - # 如果服务未运行,提供启动提示 + # Show startup hints if services are not running if [ "$backend_running" = false ]; then echo "" - log_warning "后端服务未运行,请先启动后端服务:" + log_warning "Backend is not running; please start it:" echo " cd backend && npm run dev" echo "" fi if [ "$frontend_running" = false ]; then echo "" - log_warning "前端服务未运行,请先启动前端服务:" + log_warning "Frontend is not running; please start it:" echo " cd frontend && pnpm dev" echo "" fi - # 测试后端健康检查API + # Test backend health check APIs if [ "$backend_running" = true ]; then - echo -e "${BLUE}=== 测试后端健康检查API ===${NC}" + echo -e "${BLUE}=== Test Backend Health Check APIs ===${NC}" - test_api "http://localhost:3001/health" "后端基础健康检查" - test_api "http://localhost:3001/api/health" "后端API路径健康检查" - test_api "http://localhost:3001/health/detailed" "后端详细健康检查" + test_api "http://localhost:3001/health" "Backend basic health check" + test_api "http://localhost:3001/api/health" "Backend API path health check" + test_api "http://localhost:3001/health/detailed" "Backend detailed health check" fi - # 测试前端健康检查API + # Test frontend health check APIs if [ "$frontend_running" = true ]; then - echo -e "${BLUE}=== 测试前端健康检查API ===${NC}" + echo -e "${BLUE}=== Test Frontend Health Check APIs ===${NC}" - test_api "http://localhost:3002/api/health" "前端基础健康检查" - test_api "http://localhost:3002/api/health/detailed" "前端详细健康检查" + test_api "http://localhost:3002/api/health" "Frontend basic health check" + test_api "http://localhost:3002/api/health/detailed" "Frontend detailed health check" fi - # 测试结果汇总 + # Test results summary echo "" - echo -e "${BLUE}=== 测试结果汇总 ===${NC}" - echo "总测试数: $TOTAL_TESTS" - echo -e "通过: ${GREEN}$TESTS_PASSED${NC}" - echo -e "失败: ${RED}$TESTS_FAILED${NC}" + echo -e "${BLUE}=== Test Results Summary ===${NC}" + echo "Total tests: $TOTAL_TESTS" + echo -e "Passed: ${GREEN}$TESTS_PASSED${NC}" + echo -e "Failed: ${RED}$TESTS_FAILED${NC}" if [ $TESTS_FAILED -eq 0 ]; then - echo -e "${GREEN}🎉 所有测试通过!${NC}" + echo -e "${GREEN}🎉 All tests passed!${NC}" exit 0 else - echo -e "${RED}❌ 有 $TESTS_FAILED 个测试失败${NC}" + echo -e "${RED}❌ $TESTS_FAILED test(s) failed${NC}" exit 1 fi } -# 捕获中断信号 -trap 'echo -e "\n${YELLOW}测试被中断${NC}"; exit 1' INT TERM +# Trap interrupt signals +trap 'echo -e "\n${YELLOW}Tests interrupted${NC}"; exit 1' INT TERM -# 运行主函数 -main "$@" \ No newline at end of file +# Run main function +main "$@"