chore: retire private certificate release path

This commit is contained in:
WuYongJie 2026-09-03 17:53:09 +08:00
parent 6a4868b196
commit 798a178927
10 changed files with 34 additions and 235 deletions

View File

@ -67,7 +67,7 @@
- 只有用户明确要求“部署、上线”时才执行生产发布;正式发布只使用已合并的最新 `main`。
- Windows 开发机统一使用 `scripts/deploy-from-workstation.ps1`。服务器只运行开发机已构建的 `caiwu_akaunting:<commit>` 镜像,禁止在服务器执行 Composer、npm 或 Docker 构建。
- 当前生产入口为 `https://218.93.190.75:8442`;未完成正式域名或公网 `80/443` 验收前,不改成无端口地址,也不在日常发布使用 `-ConfigureHttps`。
- 当前生产入口为 `https://caiwu.jiansuntuzhi.cn:8442`;公网仍只使用 `8442`,日常发布不得改成无端口地址。
- 发布脚本检测到新增数据库迁移时必须停止,取得负责人明确授权并完成备份后才能使用迁移开关。
- 容器内 Laravel 命令使用 `www-data`。发布后只需确认部署 SHA、应用容器健康、受影响的真实页面/接口、迁移是否执行,以及当前和上一版镜像可回滚。
- 程序静态资源随应用镜像同源发布;七牛只存用户上传的业务图片、票据和附件。

View File

@ -19,8 +19,8 @@ fi
test -s "$SOURCE_CONFIG"
test -s "$TARGET_CONFIG"
test -s /etc/nginx/caiwuxitong-backend.crt
test -s /etc/nginx/caiwuxitong-backend.key
test -s /etc/nginx/caiwu-jiansuntuzhi-fullchain.pem
test -s /etc/nginx/caiwu-jiansuntuzhi.key
install -d -m 700 "$BACKUP_DIR"
install -m 600 "$TARGET_CONFIG" "$BACKUP_DIR/caiwuxitong-backend-8442.conf"

View File

@ -1,10 +1,10 @@
server {
listen 8442 ssl default_server;
listen [::]:8442 ssl default_server;
server_name 218.93.190.75;
ssl_certificate /etc/nginx/caiwuxitong-backend.crt;
ssl_certificate_key /etc/nginx/caiwuxitong-backend.key;
ssl_session_cache shared:CAIWU_BACKEND_SSL:2m;
server_name _;
ssl_certificate /etc/nginx/caiwu-jiansuntuzhi-fullchain.pem;
ssl_certificate_key /etc/nginx/caiwu-jiansuntuzhi.key;
ssl_session_cache shared:CAIWU_DOMAIN_SSL:2m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 12m;
@ -16,13 +16,13 @@ server {
location @plain_http_request {
if ($request_uri !~ "^/auth/dingtalk/callback(?:[?]|$)") {
return 308 https://218.93.190.75:8442$request_uri;
return 308 https://caiwu.jiansuntuzhi.cn:8442$request_uri;
}
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host caiwu.jiansuntuzhi.cn;
proxy_set_header X-Forwarded-Host caiwu.jiansuntuzhi.cn;
proxy_set_header X-Forwarded-Port 8442;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@ -30,6 +30,21 @@ server {
proxy_read_timeout 30s;
}
location / {
return 308 https://caiwu.jiansuntuzhi.cn:8442$request_uri;
}
}
server {
listen 8442 ssl;
listen [::]:8442 ssl;
server_name caiwu.jiansuntuzhi.cn;
ssl_certificate /etc/nginx/caiwu-jiansuntuzhi-fullchain.pem;
ssl_certificate_key /etc/nginx/caiwu-jiansuntuzhi.key;
ssl_session_cache shared:CAIWU_DOMAIN_SSL:2m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 12m;
location / {
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
@ -44,33 +59,3 @@ server {
proxy_read_timeout 120s;
}
}
server {
listen 8442 ssl;
listen [::]:8442 ssl;
server_name 118.196.84.79;
ssl_certificate /etc/nginx/caiwuxitong-backend.crt;
ssl_certificate_key /etc/nginx/caiwuxitong-backend.key;
ssl_session_cache shared:CAIWU_BACKEND_SSL:2m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 12m;
allow 127.0.0.1;
allow ::1;
allow 118.196.84.79;
deny all;
location / {
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
proxy_set_header Host 218.93.190.75;
proxy_set_header X-Forwarded-Host 218.93.190.75;
proxy_set_header X-Forwarded-Port 443;
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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}

View File

@ -1,24 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name 218.93.190.75;
client_max_body_size 12m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type text/plain;
}
location / {
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
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 http;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}

View File

@ -1,44 +0,0 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name 218.93.190.75;
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type text/plain;
}
location / {
return 308 https://$host$request_uri;
}
}
server {
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
server_name 218.93.190.75;
ssl_certificate /etc/letsencrypt/live/caiwuxitong-ip/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/caiwuxitong-ip/privkey.pem;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 12m;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
location / {
proxy_pass http://127.0.0.1:18088;
proxy_http_version 1.1;
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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}

View File

@ -1,102 +0,0 @@
#!/bin/sh
set -eu
SERVER_IP="${1:?SERVER_IP is required}"
REMOTE_DIR="${2:-/opt/caiwuxitong}"
CERTBOT_DIR="/opt/caiwuxitong-certbot"
CERT_NAME="caiwuxitong-ip"
NGINX_SITE="/etc/nginx/sites-available/caiwuxitong"
WEBROOT="/var/www/letsencrypt"
if [ "$(id -u)" -ne 0 ]; then
echo "setup-https.sh must run as root" >&2
exit 1
fi
if [ "$SERVER_IP" != "218.93.190.75" ]; then
echo "Refusing to configure an unexpected server IP: $SERVER_IP" >&2
exit 1
fi
test -f "$REMOTE_DIR/host-nginx-http.conf"
test -f "$REMOTE_DIR/host-nginx.conf"
curl -fsS -o /dev/null http://127.0.0.1:18088/
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y --no-install-recommends nginx python3-venv ca-certificates
install -d -m 755 "$WEBROOT/.well-known/acme-challenge"
install -m 644 "$REMOTE_DIR/host-nginx-http.conf" "$NGINX_SITE"
ln -sfn "$NGINX_SITE" /etc/nginx/sites-enabled/caiwuxitong
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl enable --now nginx
systemctl reload nginx
if [ ! -x "$CERTBOT_DIR/bin/certbot" ]; then
python3 -m venv "$CERTBOT_DIR"
"$CERTBOT_DIR/bin/pip" install \
--disable-pip-version-check \
--no-cache-dir \
--timeout 180 \
--index-url https://pypi.org/simple \
'certbot==5.4.0'
fi
"$CERTBOT_DIR/bin/certbot" --version
if [ ! -s "/etc/letsencrypt/live/$CERT_NAME/fullchain.pem" ]; then
"$CERTBOT_DIR/bin/certbot" certonly \
--non-interactive \
--agree-tos \
--register-unsafely-without-email \
--preferred-profile shortlived \
--webroot \
--webroot-path "$WEBROOT" \
--ip-address "$SERVER_IP" \
--cert-name "$CERT_NAME"
fi
install -m 644 "$REMOTE_DIR/host-nginx.conf" "$NGINX_SITE"
nginx -t
systemctl reload nginx
cat > /usr/local/sbin/caiwuxitong-certbot-deploy-hook <<'EOF'
#!/bin/sh
set -eu
nginx -t
systemctl reload nginx
EOF
chmod 700 /usr/local/sbin/caiwuxitong-certbot-deploy-hook
cat > /etc/systemd/system/caiwuxitong-certbot-renew.service <<EOF
[Unit]
Description=Renew CaiWuXiTong short-lived IP certificate
After=network-online.target nginx.service
[Service]
Type=oneshot
ExecStart=$CERTBOT_DIR/bin/certbot renew --quiet --deploy-hook /usr/local/sbin/caiwuxitong-certbot-deploy-hook
EOF
cat > /etc/systemd/system/caiwuxitong-certbot-renew.timer <<'EOF'
[Unit]
Description=Check CaiWuXiTong IP certificate renewal every 8 hours
[Timer]
OnCalendar=*-*-* 00,08,16:15:00
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now caiwuxitong-certbot-renew.timer
curl -fsS -o /dev/null "https://$SERVER_IP/"
openssl x509 -in "/etc/letsencrypt/live/$CERT_NAME/fullchain.pem" -noout -subject -issuer -dates
systemctl list-timers caiwuxitong-certbot-renew.timer --no-pager
echo "HTTPS_READY=https://$SERVER_IP"

View File

@ -32,6 +32,7 @@ services:
- ./modules:/var/www/html/modules
- ./resources:/var/www/html/resources
- ./routes:/var/www/html/routes
- ./scripts:/var/www/html/scripts
- ./tests:/var/www/html/tests
networks:
- jiangling_net

View File

@ -82,20 +82,11 @@
- 创建持久化数据库卷和运行文件卷;运行文件卷继续保存缓存、临时文件和可回滚的旧上传原件。
- 上传应用与 MariaDB 首次全量包,并初始化仅限本机访问的增量 Registry。
当前云平台仅提供可用的 `TCP 8442`,生产直连地址为 `https://218.93.190.75:8442`。应用容器只绑定服务器本机 `127.0.0.1:18088`,公网由轻量 Nginx 在 `8442` 提供 HTTPS;Registry 的 `5000` 端口仍禁止对公网开放。
当前云平台仅提供可用的 `TCP 8442`,生产地址为 `https://caiwu.jiansuntuzhi.cn:8442`。应用容器只绑定服务器本机 `127.0.0.1:18088`,公网由轻量 Nginx 在 `8442` 提供 HTTPS;Registry 的 `5000` 端口仍禁止对公网开放。
只有云平台已经真正放行 `TCP 80/443`(外网不仅能握手,还能收到服务器响应)时,才执行一次受信任 HTTPS 配置:
生产域名使用公共 CA 签发的 `*.jiansuntuzhi.cn` 证书。证书和私钥只保存在服务器 Nginx 私有目录,禁止进入 Git、镜像或发布日志;更换证书后必须执行 `nginx -t`、重载并从外网验证域名和到期时间。
```powershell
.\scripts\deploy-from-workstation.ps1 `
-Server 218.93.190.75 `
-KeyPath "C:\Users\你的用户名\.ssh\caiwu.pem" `
-ConfigureHttps
```
该步骤在服务器安装轻量 Nginx 和独立 Certbot 环境,为公网 IP 申请 Let’s Encrypt 短期证书,并启用每 8 小时检查一次的自动续期。服务器仍不构建任何应用源码或镜像。当前 `80/443` 尚未真正可用时不得执行;继续使用 `8442` 内部证书入口。
首次打开 `https://218.93.190.75:8442` 后,由负责人或指定人员在网页安装向导中设置管理员账号和密码。当前内部证书可能触发浏览器警告;正式域名或受信任 IP 证书完成后再取消端口和警告。
首次打开 `https://caiwu.jiansuntuzhi.cn:8442` 后,由负责人或指定人员在网页安装向导中设置管理员账号和密码。
## 5. 日常正式发布
@ -406,7 +397,7 @@ php artisan system-database:reencrypt-upstream-passwords --apply
- 发布分支和完整 Git SHA;
- 镜像标签;
- 服务器 `caiwu_app`、`caiwu_db` 状态;
- `https://218.93.190.75:8442` 的实际返回结果和证书状态;
- `https://caiwu.jiansuntuzhi.cn:8442` 的实际返回结果和证书状态;
- 是否执行数据库迁移;
- 本地临时包是否删除;
- 本地/服务器保留了几个本项目镜像;

View File

@ -19,7 +19,6 @@ param(
[ValidateRange(24, 8760)]
[int]$BuildCacheMaxAgeHours = 720,
[switch]$BootstrapServer,
[switch]$ConfigureHttps,
[switch]$ConfigureDingTalkCallback,
[switch]$AllowNonMain,
[switch]$AllowMigrations
@ -309,7 +308,7 @@ try {
if (-not $AppUrl) {
$AppUrl = if ($Server -eq '218.93.190.75') {
"https://${Server}:8442"
'https://caiwu.jiansuntuzhi.cn:8442'
}
else {
"https://${Server}"
@ -403,11 +402,8 @@ try {
deploy/activate-incremental-release.sh `
deploy/config-db-init.sh `
deploy/update-runtime-url.sh `
deploy/setup-https.sh `
deploy/configure-dingtalk-callback.sh `
deploy/host-nginx-8442-dingtalk.conf `
deploy/host-nginx-http.conf `
deploy/host-nginx.conf `
deploy/setup-incremental-registry.sh `
deploy/registry-config.yml `
"${sshTarget}:$RemotePath/"
@ -517,12 +513,6 @@ try {
} '服务器增量激活或健康检查失败。'
}
if ($ConfigureHttps) {
Invoke-Native {
ssh -i $keyFullPath -o BatchMode=yes $sshTarget "chmod 700 '$RemotePath/setup-https.sh' && '$RemotePath/setup-https.sh' '$Server' '$RemotePath'"
} '服务器 HTTPS 配置或证书申请失败。'
}
if ($ConfigureDingTalkCallback) {
Invoke-Native {
ssh -i $keyFullPath -o BatchMode=yes $sshTarget "chmod 700 '$RemotePath/configure-dingtalk-callback.sh' && '$RemotePath/configure-dingtalk-callback.sh' '$Server' '$RemotePath'"

View File

@ -13,7 +13,9 @@ class DingTalkCallbackNginxTest extends TestCase
$this->assertStringContainsString('error_page 497 = @plain_http_request;', $config);
$this->assertStringContainsString('if ($request_uri !~ "^/auth/dingtalk/callback(?:[?]|$)")', $config);
$this->assertStringContainsString('proxy_set_header X-Forwarded-Proto http;', $config);
$this->assertStringContainsString('return 308 https://218.93.190.75:8442$request_uri;', $config);
$this->assertStringContainsString('return 308 https://caiwu.jiansuntuzhi.cn:8442$request_uri;', $config);
$this->assertStringContainsString('ssl_certificate /etc/nginx/caiwu-jiansuntuzhi-fullchain.pem;', $config);
$this->assertStringNotContainsString('caiwuxitong-backend.crt', $config);
}
public function test_configuration_script_validates_and_restores_on_failure(): void