diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml new file mode 100644 index 00000000..c0edbe8c --- /dev/null +++ b/docker-compose.dev.yaml @@ -0,0 +1,55 @@ +services: + db: + extends: + file: docker-compose.yaml + service: db + + backend: + extends: + file: docker-compose.yaml + service: backend + + backend-init: + extends: + file: docker-compose.yaml + service: backend-init + + frontend-dev: + image: node:20-alpine + container_name: ctms_frontend_dev + restart: always + working_dir: /app + command: sh -c "npm ci && npm run dev -- --host 0.0.0.0" + environment: + VITE_RUNTIME_ENV: ${ENV:-development} + VITE_ALLOW_INSECURE_DEV_LOGIN: ${VITE_ALLOW_INSECURE_DEV_LOGIN:-true} + volumes: + - ./frontend:/app + - frontend_node_modules:/app/node_modules + depends_on: + - backend + networks: + - ctms_net + + nginx: + image: nginx:1.27-alpine + container_name: ctms_nginx + restart: always + ports: + - "8888:80" + volumes: + - ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf:ro + - ./nginx/certs:/etc/nginx/certs:ro + - ./frontend/public/favicon.ico:/usr/share/nginx/html/favicon.ico:ro + depends_on: + - backend + - frontend-dev + networks: + - ctms_net + +volumes: + frontend_node_modules: + +networks: + ctms_net: + driver: bridge diff --git a/nginx/nginx.dev.conf b/nginx/nginx.dev.conf new file mode 100644 index 00000000..a5acc638 --- /dev/null +++ b/nginx/nginx.dev.conf @@ -0,0 +1,82 @@ +worker_processes 1; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + resolver 127.0.0.11 valid=10s ipv6=off; + + 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; + + sendfile on; + keepalive_timeout 65; + + upstream backend { + zone backend 64k; + server backend:8000 resolve; + } + + upstream frontend_dev { + zone frontend_dev 64k; + server frontend-dev:5173 resolve; + } + + map $http_upgrade $connection_upgrade { + default upgrade; + "" close; + } + + server { + listen 80; + server_name _; + client_max_body_size 20m; + + location = /favicon.ico { + root /usr/share/nginx/html; + try_files /favicon.ico =204; + access_log off; + log_not_found off; + expires 1d; + add_header Cache-Control "public, max-age=86400" always; + } + + location /api/ { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + proxy_pass http://frontend_dev; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 60s; + proxy_send_timeout 60s; + } + } +} diff --git a/scripts/install-ctms.sh b/scripts/install-ctms.sh index 42a614fe..5ad9d18e 100755 --- a/scripts/install-ctms.sh +++ b/scripts/install-ctms.sh @@ -71,6 +71,14 @@ run() { "$@" } +compose_cmd() { + if [[ "$TARGET_ENV" == "dev" ]]; then + docker compose -f "$ROOT_DIR/docker-compose.dev.yaml" "$@" + else + docker compose "$@" + fi +} + # 运行长命令,默认折叠输出。失败或 --verbose 时回放完整日志。 # 用法: run_quiet <提示语> -- <命令...> run_quiet() { @@ -144,6 +152,7 @@ ${C_BOLD}用法:${C_RESET} ${C_BOLD}环境:${C_RESET} ${C_CYAN}dev${C_RESET} 本地开发环境(使用默认密钥,不生成 RSA 密钥对) + 前端通过 Vite dev server 热加载,入口仍为 http://127.0.0.1:8888 ${C_CYAN}main${C_RESET} 内网测试 / 预发布环境 ${C_CYAN}release${C_RESET} 生产环境(强制 HTTPS,自动生成密钥对) @@ -451,22 +460,22 @@ prepare_env_file() { run_compose_config() { step "校验 Docker Compose 配置" - run_quiet "Docker Compose 配置语法校验" -- docker compose config + run_quiet "Docker Compose 配置语法校验" -- compose_cmd config } run_backend_init() { [[ "$TARGET_ENV" != "dev" ]] || return 0 step "初始化数据库与管理员账号" - run_quiet "运行 backend-init 容器" -- docker compose run --rm backend-init + run_quiet "运行 backend-init 容器" -- compose_cmd run --rm backend-init } run_build_and_start() { if [[ "$SKIP_BUILD" -eq 1 ]]; then step "启动服务(跳过镜像构建)" - run_quiet "启动容器" -- docker compose up -d + run_quiet "启动容器" -- compose_cmd up -d else step "构建镜像并启动服务" - run_quiet "构建镜像并启动容器(首次较慢)" -- docker compose up -d --build + run_quiet "构建镜像并启动容器(首次较慢)" -- compose_cmd up -d --build fi } @@ -476,7 +485,7 @@ run_migrations() { return fi step "执行数据库迁移" - run_quiet "Alembic upgrade head" -- docker compose run --rm backend python -m alembic upgrade head + run_quiet "Alembic upgrade head" -- compose_cmd run --rm backend python -m alembic upgrade head } # ── 健康检查 ───────────────────────────────── @@ -484,13 +493,15 @@ run_migrations() { check_container_status() { step "检查容器运行状态" local running - running="$(docker compose ps --services --filter status=running 2>/dev/null || true)" - for svc in db backend nginx; do + running="$(compose_cmd ps --services --filter status=running 2>/dev/null || true)" + local services=(db backend nginx) + [[ "$TARGET_ENV" != "dev" ]] || services+=(frontend-dev) + for svc in "${services[@]}"; do if printf '%s\n' "$running" | grep -qx "$svc"; then printf ' %s✔%s %-8s 运行中\n' "${C_GREEN}${C_BOLD}" "${C_RESET}" "$svc" else printf ' %s✖%s %-8s %s未运行%s\n' "${C_RED}${C_BOLD}" "${C_RESET}" "$svc" "${C_RED}" "${C_RESET}" - fail "$svc 容器未正常运行,请执行 'docker compose ps' 排查" + fail "$svc 容器未正常运行,请执行 'bash scripts/install-ctms.sh $TARGET_ENV --skip-build --skip-migrate --verbose' 或对应 compose ps 命令排查" fi done } @@ -524,7 +535,7 @@ if expect_rsa: ) PY ) - run_quiet "执行后端配置自检" -- docker compose exec -T \ + run_quiet "执行后端配置自检" -- compose_cmd exec -T \ -e EXPECTED_ENV="$expected_env" \ -e EXPECTED_KEY_ID="$expected_key_id" \ -e EXPECT_RSA="$expect_rsa" \