diff --git a/AGENTS.md b/AGENTS.md index a20aa6aa..66d62242 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,7 +67,7 @@ - 只有用户明确要求“部署、上线”时才执行生产发布;正式发布只使用已合并的最新 `main`。 - Windows 开发机统一使用 `scripts/deploy-from-workstation.ps1`。服务器只运行开发机已构建的 `caiwu_akaunting:` 镜像,禁止在服务器执行 Composer、npm 或 Docker 构建。 -- 当前生产入口为 `https://218.93.190.75:8442`;未完成正式域名或公网 `80/443` 验收前,不改成无端口地址,也不在日常发布使用 `-ConfigureHttps`。 +- 当前生产入口为 `https://caiwu.jiansuntuzhi.cn:8442`;公网仍只使用 `8442`,日常发布不得改成无端口地址。 - 发布脚本检测到新增数据库迁移时必须停止,取得负责人明确授权并完成备份后才能使用迁移开关。 - 容器内 Laravel 命令使用 `www-data`。发布后只需确认部署 SHA、应用容器健康、受影响的真实页面/接口、迁移是否执行,以及当前和上一版镜像可回滚。 - 程序静态资源随应用镜像同源发布;七牛只存用户上传的业务图片、票据和附件。 diff --git a/deploy/configure-dingtalk-callback.sh b/deploy/configure-dingtalk-callback.sh index b8627dca..a9aa08fa 100644 --- a/deploy/configure-dingtalk-callback.sh +++ b/deploy/configure-dingtalk-callback.sh @@ -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" diff --git a/deploy/host-nginx-8442-dingtalk.conf b/deploy/host-nginx-8442-dingtalk.conf index eaf73ef4..65a8172e 100644 --- a/deploy/host-nginx-8442-dingtalk.conf +++ b/deploy/host-nginx-8442-dingtalk.conf @@ -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; - } -} diff --git a/deploy/host-nginx-http.conf b/deploy/host-nginx-http.conf deleted file mode 100644 index 9da4b4ce..00000000 --- a/deploy/host-nginx-http.conf +++ /dev/null @@ -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; - } -} diff --git a/deploy/host-nginx.conf b/deploy/host-nginx.conf deleted file mode 100644 index dc2be2e3..00000000 --- a/deploy/host-nginx.conf +++ /dev/null @@ -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; - } -} diff --git a/deploy/setup-https.sh b/deploy/setup-https.sh deleted file mode 100644 index f7a41f40..00000000 --- a/deploy/setup-https.sh +++ /dev/null @@ -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 < /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" diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 121a9284..1f9168d4 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -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 diff --git a/docs/开发与部署SOP.md b/docs/开发与部署SOP.md index b6765614..cb0b8d15 100644 --- a/docs/开发与部署SOP.md +++ b/docs/开发与部署SOP.md @@ -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` 的实际返回结果和证书状态; - 是否执行数据库迁移; - 本地临时包是否删除; - 本地/服务器保留了几个本项目镜像; diff --git a/scripts/deploy-from-workstation.ps1 b/scripts/deploy-from-workstation.ps1 index d05e0829..fcf9f2eb 100644 --- a/scripts/deploy-from-workstation.ps1 +++ b/scripts/deploy-from-workstation.ps1 @@ -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'" diff --git a/tests/Unit/Deployment/DingTalkCallbackNginxTest.php b/tests/Unit/Deployment/DingTalkCallbackNginxTest.php index ac24c976..f445edb5 100644 --- a/tests/Unit/Deployment/DingTalkCallbackNginxTest.php +++ b/tests/Unit/Deployment/DingTalkCallbackNginxTest.php @@ -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