This commit is contained in:
张威33321
2026-09-08 20:36:17 +08:00
parent 58d65188a1
commit fb87be680d
19 changed files with 398 additions and 212 deletions

View File

@@ -1,27 +1,27 @@
# Nginx Docker image
# 包含自定义配置的反向代理
# 使用官方 Nginx 镜像
FROM nginx:09051708
FROM swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/nginx:alpine
# 维护者信息
LABEL maintainer="awei"
LABEL description="HTTP server with SSL support"
LABEL maintainer="StoneLan"
LABEL description="Nginx reverse proxy container"
# 复制 nginx 配置
# 复制配置文件
# conf.d 只放不依赖证书的常驻配置80 端口ACME 验证 + HTTPS 跳转)
COPY nginx.conf /etc/nginx/nginx.conf
COPY conf.d /etc/nginx/conf.d
COPY verification.html /usr/share/nginx/html/verification.html
# 复制静态文件
COPY html /usr/share/nginx/html
# 创建日志目录与 HTTPS 配置目录
# ssl.d 必须存在nginx.conf 里的 wildcard include 在目录不存在时会报 emerg
RUN mkdir -p /var/log/nginx /etc/nginx/ssl.d
# 创建必要的目录
RUN mkdir -p /var/log/nginx && \
mkdir -p /etc/nginx/ssl && \
chown -R nginx:nginx /var/log/nginx && \
chown -R nginx:nginx /etc/nginx
# 暴露端口
EXPOSE 80
EXPOSE 8443
EXPOSE 80 443
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
# 健康检查:/healthz 固定返回 200不跟随 301冷启动阶段也不会误判
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 -O /dev/null http://127.0.0.1/healthz || exit 1
# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

30
nginx/conf.d/00-http.conf Normal file
View File

@@ -0,0 +1,30 @@
# HTTP 入口ACME 验证 + HTTPS 跳转
#
# 【关键】这个文件必须常驻,且不能引用任何证书文件。
# 首次部署时证书尚不存在,如果 nginx 加载了带 ssl_certificate 的 server 块,
# 会直接 [emerg] cannot load certificate 退出;而申请证书又必须要有 80 端口可用
# —— 形成死锁。因此所有 HTTPS server 块统一放到 /etc/nginx/ssl.d/
# 由 nginx/enable-ssl.sh 在证书就绪后再启用。
server {
listen 80 default_server;
server_name _;
# 健康检查探针(容器 HEALTHCHECK 使用),不跟随 301
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# ACME HTTP-01 验证
# certbot 通过 webroot 写入 /var/www/certbot/.well-known/acme-challenge/<token>
# 该目录是 certbot-webroot 卷nginx 以只读方式挂载
location /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
}
location / {
return 301 https://$host$request_uri;
}
}

View File

@@ -6,14 +6,22 @@ services:
- "8081:80"
- "8443:8443"
volumes:
# 证书(只读)
- certbot-certs:/etc/letsencrypt:ro
# ACME 验证目录只读certbot 写入挑战文件nginx 直接对外提供
- certbot-webroot:/var/www/certbot:ro
# HTTPS server 块:默认为空,证书就绪后由 enable-ssl.sh 写入
- ./ssl.d:/etc/nginx/ssl.d:ro
restart: unless-stopped
networks:
- trim-default
volumes:
# 与 certbot/docker-compose.yml 共用,物理卷名由 certbot 侧固定为同名
certbot-certs:
external: true
certbot-webroot:
external: true
networks:
trim-default:

59
nginx/enable-ssl.sh Normal file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
#
# 启用 HTTPS 配置:只把"已经拿到证书"的域名对应的 server 块
# 从 ssl.d.available/ 复制到 ssl.d/nginx 挂载并 include 的目录),
# 然后校验配置并优雅重载 nginx。
#
# 冷启动时 ssl.d/ 为空nginx 只加载 conf.d/00-http.conf不引用证书
# 因此可以在没有证书的情况下正常启动并提供 80 端口 —— 这是打破
# "先有证书还是先有 nginx" 死锁的关键。
#
set -euo pipefail
# cron / 非登录 shell 下 PATH 极简,显式补齐
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin${PATH:+:$PATH}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
AVAILABLE="$SCRIPT_DIR/ssl.d.available"
ENABLED="$SCRIPT_DIR/ssl.d"
DOMAINS="${DOMAINS:-fastgpt.stonelan.cn gitea.stonelan.cn image.stonelan.cn registry.stonelan.cn www.stonelan.cn}"
mkdir -p "$ENABLED"
cd "$SCRIPT_DIR"
copied=()
for d in $DOMAINS; do
conf="$AVAILABLE/$d.conf"
if [ ! -f "$conf" ]; then
echo "跳过 $d:缺少 $conf"
continue
fi
# nginx 容器以只读方式挂载了 certbot-certs 卷,用它判断证书是否就绪
if ! docker compose exec -T nginx-proxy test -f "/etc/letsencrypt/live/$d/fullchain.pem" 2>/dev/null; then
echo "跳过 $d:证书不存在(/etc/letsencrypt/live/$d/fullchain.pem"
continue
fi
if ! cmp -s "$conf" "$ENABLED/$d.conf" 2>/dev/null; then
cp "$conf" "$ENABLED/$d.conf"
copied+=("$ENABLED/$d.conf")
echo "已启用 $d"
fi
done
if [ "${#copied[@]}" -eq 0 ]; then
echo "没有需要变更的 HTTPS 配置"
exit 0
fi
if out=$(docker compose exec -T nginx-proxy nginx -t 2>&1); then
echo "$out" | sed 's/^/ /'
docker kill -s HUP nginx-proxy
echo "nginx 已重载HTTPS 配置生效"
else
echo "$out" | sed 's/^/ /'
echo "nginx 配置校验失败,回滚本次变更" >&2
rm -f "${copied[@]}"
exit 1
fi

View File

@@ -1,29 +1,34 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
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;
#tcp_nopush on;
keepalive_timeout 65;
}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
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;
#tcp_nopush on;
keepalive_timeout 65;
# HTTPS server 块按需加载:
# 该目录默认为空nginx 的 wildcard include 匹配 0 个文件时不报错),
# 证书申请成功后由 enable-ssl.sh 写入配置,从而避免冷启动时
# 因证书文件不存在导致 nginx 拒绝启动。
include /etc/nginx/ssl.d/*.conf;
}

View File

@@ -1,18 +1,6 @@
# 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 配置
# HTTPS 配置 —— fastgpt.stonelan.cn
# 仅在 /etc/letsencrypt/live/fastgpt.stonelan.cn/fullchain.pem 存在时,
# 由 nginx/enable-ssl.sh 复制到 ssl.d/ 才会被 nginx 加载。
server {
listen 8443 ssl;
server_name fastgpt.stonelan.cn;

View File

@@ -1,4 +1,5 @@
# HTTPS 配置
# HTTPS 配置 —— gitea.stonelan.cn
# 仅在证书存在时由 nginx/enable-ssl.sh 复制到 ssl.d/ 才会生效。
server {
listen 8443 ssl;
server_name gitea.stonelan.cn;

View File

@@ -1,4 +1,5 @@
# HTTPS 配置
# HTTPS 配置 —— image.stonelan.cn
# 仅在证书存在时由 nginx/enable-ssl.sh 复制到 ssl.d/ 才会生效。
server {
listen 8443 ssl;
server_name image.stonelan.cn;

View File

@@ -1,4 +1,5 @@
# HTTPS 配置
# HTTPS 配置 —— registry.stonelan.cn
# 仅在证书存在时由 nginx/enable-ssl.sh 复制到 ssl.d/ 才会生效。
server {
listen 8443 ssl;
server_name registry.stonelan.cn;

View File

@@ -1,4 +1,5 @@
# HTTPS 配置
# HTTPS 配置 —— www.stonelan.cn
# 仅在证书存在时由 nginx/enable-ssl.sh 复制到 ssl.d/ 才会生效。
server {
listen 8443 ssl;
server_name www.stonelan.cn;

2
nginx/ssl.d/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# nginx 从这里加载 HTTPS 配置wildcard include目录为空时 nginx 正常启动)。
# 首次申请证书前本目录为空,由 ../enable-ssl.sh 按需写入。

View File

@@ -1,4 +1,8 @@
#!/bin/bash
#!/usr/bin/env bash
#
# 把仓库里的 nginx 配置同步到宿主机 nginx非容器部署路径
#
set -euo pipefail
# 检查并创建nginx用户
if ! id -u nginx >/dev/null 2>&1; then
@@ -6,31 +10,49 @@ if ! id -u nginx >/dev/null 2>&1; then
sudo useradd -r -s /sbin/nologin nginx
fi
# 创建 conf.d 目录(如果不存在
sudo mkdir -p /etc/nginx/conf.d
# 创建配置目录ssl.d 必须存在nginx.conf 里的 wildcard include 在目录缺失时会报 emerg
sudo mkdir -p /etc/nginx/conf.d /etc/nginx/ssl.d
# 备份并覆盖 nginx.conf 文件
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
sudo cp nginx.conf /etc/nginx/
# 备份并覆盖 conf.d 目录下的所有 .conf 文件
# 备份并覆盖 conf.d 目录下的所有 .conf 文件(只放不依赖证书的配置)
for conf in conf.d/*.conf; do
filename=$(basename "$conf")
sudo cp "/etc/nginx/conf.d/$filename" "/etc/nginx/conf.d/${filename}.bak" 2>/dev/null || true
sudo cp "$conf" /etc/nginx/conf.d/
done
# HTTPS 配置:只有证书已就绪的域名才启用,避免 nginx 因证书缺失拒绝启动
DOMAINS="${DOMAINS:-fastgpt.stonelan.cn gitea.stonelan.cn image.stonelan.cn registry.stonelan.cn www.stonelan.cn}"
for d in $DOMAINS; do
src="ssl.d.available/$d.conf"
[ -f "$src" ] || { echo "跳过 $d:缺少 $src"; continue; }
if [ -f "/etc/letsencrypt/live/$d/fullchain.pem" ]; then
sudo cp "$src" "/etc/nginx/ssl.d/$d.conf"
echo "已启用 $d"
else
echo "跳过 $d:证书尚未生成"
fi
done
# 测试并重载配置
if sudo nginx -t; then
sudo systemctl reload nginx
echo "配置已生效HTTPS已启用"
if sudo systemctl is-active --quiet nginx; then
sudo systemctl reload nginx
echo "配置已重载"
else
sudo systemctl start nginx
echo "nginx 已启动"
fi
else
echo "配置测试失败,已恢复备份"
# 恢复 nginx.conf 备份
sudo cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf
# 恢复 conf.d 目录下的所有 .conf 文件备份
for conf in conf.d/*.conf; do
filename=$(basename "$conf")
sudo mv -f "/etc/nginx/conf.d/${filename}.bak" "/etc/nginx/conf.d/$filename" 2>/dev/null || true
done
fi
exit 1
fi