diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0d6dbe4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,46 @@ +# docker-compose.yml (已去掉 obsolete version 行) +services: + traefik: + image: traefik:v3.5.3 + container_name: traefik-reverse + restart: unless-stopped + command: + - "--log.level=INFO" + - "--accesslog=true" + - "--api.dashboard=true" + - "--api.insecure=true" + - "--ping=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=frontend" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.myresolver.acme.email=lyzeng@hzau.edu.cn" + - "--certificatesresolvers.myresolver.acme.storage=/acme/acme.json" + - "--certificatesresolvers.myresolver.acme.httpChallenge.entrypoint=web" + - "--certificatesresolvers.myresolver.acme.caServer=https://acme-v02.api.letsencrypt.org/directory" + ports: + - "80:80" + - "443:443" + - "8080:8080" # Dashboard + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - acme-data:/acme + environment: + TZ: Asia/Shanghai + networks: + - frontend + healthcheck: + test: ["CMD", "traefik", "healthcheck", "--ping"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + acme-data: + +networks: + frontend: + driver: bridge + external: true + diff --git a/web/ws/.env b/web/ws/.env new file mode 120000 index 0000000..c7360fb --- /dev/null +++ b/web/ws/.env @@ -0,0 +1 @@ +../../.env \ No newline at end of file diff --git a/web/ws/deploy.sh b/web/ws/deploy.sh new file mode 100755 index 0000000..1d74ef0 --- /dev/null +++ b/web/ws/deploy.sh @@ -0,0 +1,88 @@ +#!/bin/bash +set -e + +# 配置变量 +VOLUME_NAME="webws-data" +SOURCE_DIR="/vol1/1000/home/ws/PBMDB" +COMPOSE_FILE="../ws/docker-compose.yml" + +echo "==========================================" +echo "部署 Traefik + Nginx 静态网站" +echo "==========================================" + +# 1. 检查源目录是否存在 +if [ ! -d "$SOURCE_DIR" ]; then + echo "❌ 错误: 源目录不存在: $SOURCE_DIR" + exit 1 +fi + +echo "✓ 源目录存在: $SOURCE_DIR" + +# 2. 创建网络(如果不存在) +if ! docker network inspect frontend >/dev/null 2>&1; then + echo "📡 创建 Docker 网络: frontend" + docker network create frontend +else + echo "✓ Docker 网络已存在: frontend" +fi + +# 3. 检查并创建外部卷 +if ! docker volume inspect "$VOLUME_NAME" >/dev/null 2>&1; then + echo "📦 创建 Docker Volume: $VOLUME_NAME" + docker volume create "$VOLUME_NAME" +else + echo "✓ Docker Volume 已存在: $VOLUME_NAME" +fi + +# 4. 复制文件到 Volume +echo "📁 复制网站文件到 Volume..." +docker run --rm \ + -v "$SOURCE_DIR:/source:ro" \ + -v "$VOLUME_NAME:/target" \ + alpine sh -c " + echo '清理旧文件...' + rm -rf /target/* + echo '复制新文件...' + cp -r /source/. /target/ + echo '设置权限...' + chmod -R 755 /target + find /target -type f -exec chmod 644 {} \; + echo '文件列表:' + ls -lah /target/ + " + +if [ $? -eq 0 ]; then + echo "✓ 文件复制成功" +else + echo "❌ 文件复制失败" + exit 1 +fi + +# 5. 启动服务 +echo "🚀 启动 Docker Compose 服务..." +docker compose -f "$COMPOSE_FILE" up -d + +# 6. 等待服务启动 +echo "⏳ 等待服务启动..." +sleep 3 + +# 7. 检查服务状态 +echo "" +echo "==========================================" +echo "服务状态:" +echo "==========================================" +docker compose -f "$COMPOSE_FILE" ps + +# 8. 验证文件 +echo "" +echo "==========================================" +echo "验证 Volume 中的文件:" +echo "==========================================" +docker run --rm -v "$VOLUME_NAME:/data" alpine ls -lah /data/ + +# 9. 显示日志 +echo "" +echo "==========================================" +echo "查看 Nginx 日志 (Ctrl+C 退出):" +echo "==========================================" +docker compose -f "$COMPOSE_FILE" logs -f nginx-webws diff --git a/web/ws/docker-compose.yml b/web/ws/docker-compose.yml new file mode 100644 index 0000000..21b11ab --- /dev/null +++ b/web/ws/docker-compose.yml @@ -0,0 +1,60 @@ +services: + # -------- Nginx 静态网站服务 -------- + nginx-webws: + image: nginx:alpine + container_name: nginx-webws + restart: unless-stopped + volumes: + - webws-data:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + networks: + - frontend + labels: + - "traefik.enable=true" + + # HTTP 路由 (端口 80) - 重定向到 HTTPS + - "traefik.http.routers.webws-http.rule=Host(`amiap.hzau.edu.cn`) && PathPrefix(`/ABM`)" + - "traefik.http.routers.webws-http.entrypoints=web" + - "traefik.http.routers.webws-http.middlewares=redirect-to-https" + + # HTTPS 路由 (端口 443) + - "traefik.http.routers.webws-https.rule=Host(`amiap.hzau.edu.cn`) && PathPrefix(`/ABM`)" + - "traefik.http.routers.webws-https.entrypoints=websecure" + - "traefik.http.routers.webws-https.tls.certresolver=myresolver" + - "traefik.http.routers.webws-https.service=webws" + + # 重定向中间件 + - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" + + # 服务定义 + - "traefik.http.services.webws.loadbalancer.server.port=80" + + # -------- PostgreSQL 数据库 -------- + postgres: + image: postgres:16-alpine + container_name: webws-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + # 使用绑定挂载到 /vol1 大容量存储 + - ./postgres_data:/var/lib/postgresql/data + networks: + - frontend + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + webws-data: + external: true # 已经手动创建好的外部卷 + +networks: + frontend: + external: true \ No newline at end of file diff --git a/web/ws/nginx.conf b/web/ws/nginx.conf new file mode 100644 index 0000000..357c0f5 --- /dev/null +++ b/web/ws/nginx.conf @@ -0,0 +1,36 @@ +server { + listen 80; + listen [::]:80; + server_name localhost; + + # ABM 项目根目录 + root /usr/share/nginx/html; + index index.html index.htm; + + # ABM 主路径 - 重写 /ABM 到根目录 + location /ABM/ { + rewrite ^/ABM/(.*)$ /$1 break; + try_files $uri $uri/ /index.html; + + # 静态资源缓存 + expires 1d; + add_header Cache-Control "public, max-age=86400"; + } + + # 处理 /ABM 不带斜杠的情况 + location = /ABM { + return 301 /ABM/; + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +}