feat: 添加 Let's Encrypt SSL 证书管理
- 新增独立 certbot 服务项目 - nginx 配置添加 HTTPS 8443 端口监听 - frpc.toml HTTPS 代理指向 nginx:8443 - 支持自动续签并重载 nginx - 共享 Docker volume 传递证书
This commit is contained in:
20
certbot/Dockerfile
Normal file
20
certbot/Dockerfile
Normal file
@@ -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"]
|
||||
117
certbot/README.md
Normal file
117
certbot/README.md
Normal file
@@ -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 验证请求
|
||||
7
certbot/crontab.example
Normal file
7
certbot/crontab.example
Normal file
@@ -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
|
||||
24
certbot/docker-compose.yml
Normal file
24
certbot/docker-compose.yml
Normal file
@@ -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
|
||||
35
certbot/init-cert.sh
Executable file
35
certbot/init-cert.sh
Executable file
@@ -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/"
|
||||
27
certbot/renew-cert.sh
Executable file
27
certbot/renew-cert.sh
Executable file
@@ -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
|
||||
@@ -14,34 +14,6 @@ remotePort = 80
|
||||
[[proxies]]
|
||||
name = "https"
|
||||
type = "tcp"
|
||||
localIP = "10.0.3.173"
|
||||
localPort = 4430
|
||||
localIP = "nginx-proxy"
|
||||
localPort = 8443
|
||||
remotePort = 443
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,33 @@
|
||||
# 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;
|
||||
@@ -9,16 +35,13 @@ 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;
|
||||
|
||||
# 支持大文件上传和API请求
|
||||
client_max_body_size 100M;
|
||||
proxy_buffering off;
|
||||
|
||||
# 启用gzip压缩
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
# 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user