From 58d65188a18ea1916b47a82fc4cfa97a0064a210 Mon Sep 17 00:00:00 2001 From: awei Date: Tue, 8 Sep 2026 11:43:34 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20Let's=20Encrypt=20?= =?UTF-8?q?SSL=20=E8=AF=81=E4=B9=A6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增独立 certbot 服务项目 - nginx 配置添加 HTTPS 8443 端口监听 - frpc.toml HTTPS 代理指向 nginx:8443 - 支持自动续签并重载 nginx - 共享 Docker volume 传递证书 --- certbot/Dockerfile | 20 +++++++ certbot/README.md | 117 +++++++++++++++++++++++++++++++++++++ certbot/crontab.example | 7 +++ certbot/docker-compose.yml | 24 ++++++++ certbot/init-cert.sh | 35 +++++++++++ certbot/renew-cert.sh | 27 +++++++++ frpc-client/frpc.toml | 66 ++++++--------------- nginx/conf.d/fastgpt.conf | 39 ++++++++++--- nginx/conf.d/gitea.conf | 16 ++++- nginx/conf.d/image.conf | 20 +++++-- nginx/conf.d/registry.conf | 15 ++++- nginx/conf.d/www.conf | 14 ++++- nginx/docker-compose.yml | 10 +++- 13 files changed, 341 insertions(+), 69 deletions(-) create mode 100644 certbot/Dockerfile create mode 100644 certbot/README.md create mode 100644 certbot/crontab.example create mode 100644 certbot/docker-compose.yml create mode 100755 certbot/init-cert.sh create mode 100755 certbot/renew-cert.sh diff --git a/certbot/Dockerfile b/certbot/Dockerfile new file mode 100644 index 0000000..5ebe1a3 --- /dev/null +++ b/certbot/Dockerfile @@ -0,0 +1,20 @@ +# Certbot 镜像 - Let's Encrypt 证书管理 +FROM certbot/certbot:latest + +LABEL maintainer="awei" +LABEL description="Let's Encrypt certificate management service" + +# 安装额外工具 +RUN apk add --no-cache curl bash + +# 创建必要目录 +RUN mkdir -p /var/www/certbot /etc/letsencrypt /var/log/letsencrypt + +# 设置工作目录 +WORKDIR /var/www/certbot + +# 暴露端口(用于 ACME 验证) +EXPOSE 80 + +# 默认命令:启动 web 服务器用于 ACME 验证 +CMD ["sh", "-c", "while true; do echo 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 2\r\n\r\nOK' | nc -l -p 80; done"] diff --git a/certbot/README.md b/certbot/README.md new file mode 100644 index 0000000..5309197 --- /dev/null +++ b/certbot/README.md @@ -0,0 +1,117 @@ +# Let's Encrypt 证书管理服务 + +独立的 certbot 服务,用于管理 Let's Encrypt SSL证书的申请和自动续签。 + +## 架构说明 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ 用户请求 │───▶│ frps (远程) │───▶│ frpc (本地) │ +│ *.stonelan.cn │ │ 80/443端口 │ │ 80/8443端口 │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ nginx-proxy 容器 │ +│ - 80端口: 处理 ACME 验证请求,转发到 certbot 容器 │ +│ - 8443端口: HTTPS 反向代理,使用 Let's Encrypt 证书 │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ certbot 容器 │ +│ - 处理 ACME HTTP-01 验证 │ +│ - 定期续签证书 │ +│ - 续签后自动重载 nginx (通过 docker.sock) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## 文件说明 + +- `Dockerfile` - certbot 镜像定义 +- `docker-compose.yml` - certbot 容器配置 +- `init-cert.sh` - 初始化申请证书脚本 +- `renew-cert.sh` - 自动续签脚本 +- `crontab.example` - cron 配置示例 + +## 使用方法 + +### 1. 首次申请证书 + +```bash +cd /home/awei/workspace/common-server-public/certbot + +# 为每个域名申请证书 +./init-cert.sh fastgpt.stonelan.cn admin@example.com +./init-cert.sh gitea.stonelan.cn admin@example.com +./init-cert.sh image.stonelan.cn admin@example.com +./init-cert.sh registry.stonelan.cn admin@example.com +./init-cert.sh www.stonelan.cn admin@example.com +``` + +### 2. 配置自动续签 + +```bash +# 编辑 crontab +crontab -e + +# 添加以下行(每天凌晨 2 点检查续签) +0 2 * * * cd /home/awei/workspace/common-server-public/certbot && ./renew-cert.sh >> /var/log/certbot-renew.log 2>&1 +``` + +### 3. 手动续签测试 + +```bash +cd /home/awei/workspace/common-server-public/certbot +./renew-cert.sh +``` + +## 证书路径 + +证书存储在 Docker volume `certbot-certs` 中,nginx 容器通过共享 volume 访问: + +- 证书: `/etc/letsencrypt/live/<域名>/fullchain.pem` +- 私钥: `/etc/letsencrypt/live/<域名>/privkey.pem` + +## 端口映射 + +| 服务 | 容器端口 | 宿主机端口 | 说明 | +|------|----------|------------|------| +| nginx | 80 | 8081 | HTTP (ACME验证 + 重定向) | +| nginx | 8443 | 8443 | HTTPS (SSL反向代理) | +| frpc | 80 | 80 (远程) | HTTP 映射 | +| frpc | 8443 | 443 (远程) | HTTPS 映射 | + +## 重启 nginx + +续签后会自动通过 `docker kill -s HUP nginx-proxy` 重载 nginx。如需手动重启: + +```bash +docker kill -s HUP nginx-proxy +# 或 +docker restart nginx-proxy +``` + +## 故障排查 + +### 查看证书状态 +```bash +docker compose exec certbot certbot certificates +``` + +### 查看续签日志 +```bash +tail -f /var/log/certbot-renew.log +``` + +### 检查 ACME 验证是否可达 +```bash +curl http://fastgpt.stonelan.cn/.well-known/acme-challenge/test +``` + +## 注意事项 + +1. 确保域名 DNS 已正确解析到 frps 服务器 IP +2. 确保 frpc 和 nginx 容器在同一个 Docker 网络 (`trim-default`) +3. 证书有效期 90 天,建议每 60 天续签一次(cron 已配置每天检查) +4. 首次申请证书前,确保 nginx 容器已启动并能处理 ACME 验证请求 diff --git a/certbot/crontab.example b/certbot/crontab.example new file mode 100644 index 0000000..9302d39 --- /dev/null +++ b/certbot/crontab.example @@ -0,0 +1,7 @@ +# Let's Encrypt 自动续签 cron 配置 +# 每天凌晨 2 点执行续签检查 + +# 编辑 crontab: crontab -e +# 添加以下行: + +0 2 * * * cd /home/awei/workspace/common-server-public/certbot && ./renew-cert.sh >> /var/log/certbot-renew.log 2>&1 diff --git a/certbot/docker-compose.yml b/certbot/docker-compose.yml new file mode 100644 index 0000000..1baf02f --- /dev/null +++ b/certbot/docker-compose.yml @@ -0,0 +1,24 @@ +services: + certbot: + build: . + container_name: certbot + restart: unless-stopped + volumes: + - certbot-webroot:/var/www/certbot + - certbot-certs:/etc/letsencrypt + - certbot-logs:/var/log/letsencrypt + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - trim-default + +volumes: + certbot-webroot: + driver: local + certbot-certs: + driver: local + certbot-logs: + driver: local + +networks: + trim-default: + external: true diff --git a/certbot/init-cert.sh b/certbot/init-cert.sh new file mode 100755 index 0000000..fc79880 --- /dev/null +++ b/certbot/init-cert.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# Let's Encrypt 证书初始化脚本 +# 用法: ./init-cert.sh <域名> [邮箱] + +set -e + +DOMAIN=$1 +EMAIL=${2:-"admin@stonelan.cn"} + +if [ -z "$DOMAIN" ]; then + echo "用法: $0 <域名> [邮箱]" + echo "示例: $0 fastgpt.stonelan.cn admin@example.com" + exit 1 +fi + +echo "开始为域名 $DOMAIN 申请证书..." + +# 确保 certbot 容器运行 +docker compose up -d + +# 等待容器启动 +sleep 5 + +# 申请证书(使用 webroot 验证方式) +docker compose exec certbot certbot certonly \ + --webroot \ + --webroot-path=/var/www/certbot \ + --email "$EMAIL" \ + --agree-tos \ + --no-eff-email \ + -d "$DOMAIN" \ + --force-renewal + +echo "证书申请完成!" +echo "证书路径: /etc/letsencrypt/live/$DOMAIN/" diff --git a/certbot/renew-cert.sh b/certbot/renew-cert.sh new file mode 100755 index 0000000..5025375 --- /dev/null +++ b/certbot/renew-cert.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Let's Encrypt 证书自动续签脚本 +# 续签后自动重载 nginx + +set -e + +echo "开始检查证书续签..." + +# 执行续签 +docker compose exec certbot certbot renew \ + --webroot \ + --webroot-path=/var/www/certbot \ + --quiet + +# 检查是否有证书被续签 +if [ $? -eq 0 ]; then + echo "证书续签检查完成" + + # 重载 nginx(发送 HUP 信号) + echo "重载 nginx 配置..." + docker kill -s HUP nginx-proxy 2>/dev/null || echo "nginx-proxy 容器未运行或不存在" + + echo "续签流程完成" +else + echo "证书续签失败" + exit 1 +fi diff --git a/frpc-client/frpc.toml b/frpc-client/frpc.toml index 2ae5032..4f471b3 100644 --- a/frpc-client/frpc.toml +++ b/frpc-client/frpc.toml @@ -1,47 +1,19 @@ -# frpc 基础配置 -serverAddr = "47.113.230.48" -serverPort = 7000 -auth.method = "token" -auth.token = "8f3c9b7d5e2a4f6h1i0j" - -[[proxies]] -name = "http" -type = "tcp" -localIP = "nginx-proxy" -localPort = 80 -remotePort = 80 - -[[proxies]] -name = "https" -type = "tcp" -localIP = "10.0.3.173" -localPort = 4430 -remotePort = 443 - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# frpc 基础配置 +serverAddr = "47.113.230.48" +serverPort = 7000 +auth.method = "token" +auth.token = "8f3c9b7d5e2a4f6h1i0j" + +[[proxies]] +name = "http" +type = "tcp" +localIP = "nginx-proxy" +localPort = 80 +remotePort = 80 + +[[proxies]] +name = "https" +type = "tcp" +localIP = "nginx-proxy" +localPort = 8443 +remotePort = 443 diff --git a/nginx/conf.d/fastgpt.conf b/nginx/conf.d/fastgpt.conf index c44aefc..cbba6dd 100644 --- a/nginx/conf.d/fastgpt.conf +++ b/nginx/conf.d/fastgpt.conf @@ -1,24 +1,47 @@ +# HTTP -> HTTPS 重定向(ACME 验证由独立 certbot 服务处理) server { listen 80; + server_name fastgpt.stonelan.cn gitea.stonelan.cn image.stonelan.cn registry.stonelan.cn www.stonelan.cn; + + location /.well-known/acme-challenge/ { + proxy_pass http://certbot:80; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# HTTPS 配置 +server { + listen 8443 ssl; server_name fastgpt.stonelan.cn; - + + ssl_certificate /etc/letsencrypt/live/fastgpt.stonelan.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/fastgpt.stonelan.cn/privkey.pem; + + # SSL 优化配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + location / { proxy_pass http://10.0.3.173:3000; 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_connect_timeout 60s; proxy_read_timeout 600s; proxy_send_timeout 600s; - - # 支持大文件上传和API请求 + client_max_body_size 100M; proxy_buffering off; - - # 启用gzip压缩 + gzip on; gzip_vary on; gzip_min_length 1024; @@ -35,4 +58,4 @@ server { application/atom+xml image/svg+xml; } -} \ No newline at end of file +} diff --git a/nginx/conf.d/gitea.conf b/nginx/conf.d/gitea.conf index ffca386..d11693f 100644 --- a/nginx/conf.d/gitea.conf +++ b/nginx/conf.d/gitea.conf @@ -1,7 +1,19 @@ +# HTTPS 配置 server { - listen 80; + listen 8443 ssl; server_name gitea.stonelan.cn; client_max_body_size 200m; + + ssl_certificate /etc/letsencrypt/live/gitea.stonelan.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/gitea.stonelan.cn/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + location / { proxy_pass http://gitea:3000; proxy_connect_timeout 60s; @@ -13,4 +25,4 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } -} \ No newline at end of file +} diff --git a/nginx/conf.d/image.conf b/nginx/conf.d/image.conf index 68bad90..7e95585 100644 --- a/nginx/conf.d/image.conf +++ b/nginx/conf.d/image.conf @@ -1,17 +1,27 @@ +# HTTPS 配置 server { - listen 80; + listen 8443 ssl; server_name image.stonelan.cn; - + + ssl_certificate /etc/letsencrypt/live/image.stonelan.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/image.stonelan.cn/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + location / { proxy_pass http://10.0.3.173:3004; 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_connect_timeout 60s; proxy_read_timeout 600s; proxy_send_timeout 600s; } -} \ No newline at end of file +} diff --git a/nginx/conf.d/registry.conf b/nginx/conf.d/registry.conf index 95544f3..93e5cf7 100644 --- a/nginx/conf.d/registry.conf +++ b/nginx/conf.d/registry.conf @@ -1,8 +1,19 @@ +# HTTPS 配置 server { - listen 80; + listen 8443 ssl; server_name registry.stonelan.cn; client_max_body_size 500m; + ssl_certificate /etc/letsencrypt/live/registry.stonelan.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/registry.stonelan.cn/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + location / { proxy_pass http://127.0.0.1:3005; proxy_set_header Host $host; @@ -14,4 +25,4 @@ server { proxy_read_timeout 600s; proxy_send_timeout 600s; } -} \ No newline at end of file +} diff --git a/nginx/conf.d/www.conf b/nginx/conf.d/www.conf index e3d3613..2d06e35 100644 --- a/nginx/conf.d/www.conf +++ b/nginx/conf.d/www.conf @@ -1,7 +1,18 @@ +# HTTPS 配置 server { - listen 80; + listen 8443 ssl; server_name www.stonelan.cn; + ssl_certificate /etc/letsencrypt/live/www.stonelan.cn/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/www.stonelan.cn/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + location / { proxy_pass http://10.0.3.173:8080; proxy_set_header Host $host; @@ -9,7 +20,6 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # 添加代理超时配置 proxy_connect_timeout 60s; proxy_read_timeout 600s; proxy_send_timeout 600s; diff --git a/nginx/docker-compose.yml b/nginx/docker-compose.yml index 1843c27..d42e19e 100644 --- a/nginx/docker-compose.yml +++ b/nginx/docker-compose.yml @@ -4,13 +4,17 @@ services: container_name: nginx-proxy ports: - "8081:80" - - "4430:443" - # 配置已内嵌于镜像(构建时 COPY 进 /etc/nginx),故不挂载 conf.d + - "8443:8443" + volumes: + - certbot-certs:/etc/letsencrypt:ro restart: unless-stopped networks: - trim-default +volumes: + certbot-certs: + external: true + networks: trim-default: external: true -