整理发布脚本

This commit is contained in:
2026-08-23 14:37:36 +08:00
parent d98b0aefb0
commit 608e3e5ec5
16 changed files with 8 additions and 1211 deletions
-77
View File
@@ -1,77 +0,0 @@
#!/usr/bin/env node
const fs = require('fs');
const crypto = require('crypto');
const path = process.argv[2] || '/opt/dukang/server/dukang-api/.env.production';
function stripQuotes(s) {
const t = s.trim();
if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
return t.slice(1, -1);
}
return t;
}
function parseEnvValue(text, key) {
const re = new RegExp(`^${key}=(.*)$`, 'm');
const m = text.match(re);
if (!m) return null;
return stripQuotes(m[1]);
}
function tryKey(name, key) {
const literalN = (key.match(/\\n/g) || []).length;
const realN = (key.match(/\n/g) || []).length;
const hasBegin = /BEGIN (RSA )?PRIVATE KEY/.test(key);
try {
crypto.createPrivateKey(key);
console.log(`${name}: OK begin=${hasBegin} literalN=${literalN} realN=${realN} len=${key.length}`);
return true;
} catch (e) {
console.log(
`${name}: FAIL ${e.message} begin=${hasBegin} literalN=${literalN} realN=${realN} len=${key.length} head=${JSON.stringify(key.slice(0, 48))}`,
);
return false;
}
}
const text = fs.readFileSync(path, 'utf8');
const raw = parseEnvValue(text, 'WX_MCH_PRIVATE_KEY');
if (!raw) {
console.log('MISSING WX_MCH_PRIVATE_KEY in', path);
process.exit(1);
}
console.log('file=', path);
console.log('MOCK_PAY=', parseEnvValue(text, 'MOCK_PAY'));
console.log('WX_MINI_APP_ID=', parseEnvValue(text, 'WX_MINI_APP_ID'));
console.log('WX_APP_ID=', parseEnvValue(text, 'WX_APP_ID'));
console.log('WX_MCH_ID=', parseEnvValue(text, 'WX_MCH_ID'));
console.log('WX_PAY_NOTIFY_URL=', parseEnvValue(text, 'WX_PAY_NOTIFY_URL'));
const variants = {
as_is: raw,
unescape_n: raw.replace(/\\n/g, '\n'),
unescape_twice: raw.replace(/\\\\n/g, '\\n').replace(/\\n/g, '\n'),
strip_cr_unesc: raw.replace(/\\n/g, '\n').replace(/\r/g, ''),
// dotenv style sometimes leaves surrounding quotes in process.env
quoted_unesc: stripQuotes(raw).replace(/\\n/g, '\n'),
};
let ok = false;
for (const [name, key] of Object.entries(variants)) {
if (tryKey(name, key)) ok = true;
}
// also simulate dotenv load
try {
const dotenv = require('dotenv');
const parsed = dotenv.parse(text);
const fromDotenv = parsed.WX_MCH_PRIVATE_KEY || '';
console.log('dotenv_raw_literalN=', (fromDotenv.match(/\\n/g) || []).length, 'realN=', (fromDotenv.match(/\n/g) || []).length);
tryKey('dotenv_as_is', fromDotenv);
tryKey('dotenv_unescape', fromDotenv.replace(/\\n/g, '\n'));
} catch (e) {
console.log('dotenv skip', e.message);
}
process.exit(ok ? 0 : 2);
-65
View File
@@ -1,65 +0,0 @@
#!/usr/bin/env bash
# 为 api.lingshivip.cn 申请 DNS 证书并启用 HTTPS
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SSL_DIR="/etc/nginx/ssl/api.lingshivip.cn"
NGINX_CONF="/etc/nginx/conf.d/dukang-lingshivip.conf"
mkdir -p "$SSL_DIR" /var/www/certbot/.well-known/acme-challenge
echo "==> 1. 发起 DNS 验证(需先在 DNSPod 添加 TXT 记录)"
~/.acme.sh/acme.sh --issue -d api.lingshivip.cn --dns \
--yes-I-know-dns-manual-mode-enough-go-ahead-please \
--server letsencrypt || true
echo ""
echo "请在 DNSPod 添加上述 TXT 记录后,执行:"
echo " $0 --renew"
echo ""
if [[ "${1:-}" != "--renew" ]]; then
exit 0
fi
echo "==> 2. 继续 DNS 验证并签发证书"
~/.acme.sh/acme.sh --renew -d api.lingshivip.cn --yes-I-know-dns-manual-mode-enough-go-ahead-please
echo "==> 3. 安装证书到 nginx 目录"
install -m 644 ~/.acme.sh/api.lingshivip.cn_ecc/fullchain.cer "$SSL_DIR/fullchain.cer"
install -m 600 ~/.acme.sh/api.lingshivip.cn_ecc/api.lingshivip.cn.key "$SSL_DIR/api.lingshivip.cn.key"
echo "==> 4. 更新 nginx 并重载"
cp "$SCRIPT_DIR/nginx-lingshivip.conf" "$NGINX_CONF"
python3 - <<'PY'
from pathlib import Path
p = Path("/etc/nginx/conf.d/dukang-lingshivip.conf")
text = p.read_text()
old = """ location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
# API 独立域名 HTTPS"""
new = """ location / {
return 301 https://$host$request_uri;
}
}
# API 独立域名 HTTPS"""
if old not in text:
raise SystemExit("nginx api http redirect patch failed")
p.write_text(text.replace(old, new, 1))
PY
nginx -t
systemctl reload nginx
echo "==> 5. 健康检查"
curl -sf -o /dev/null -w "api-https:%{http_code}\n" https://api.lingshivip.cn/api/v1/health
echo "==> api.lingshivip.cn HTTPS 已启用"
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env bash
# runxian.top 裸域名申请 Let's Encrypt 并启用 HTTPS
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DOMAIN=runxian.top
EMAIL="${CERTBOT_EMAIL:-admin@runxian.top}"
echo "==> 检查 DNS: $DOMAIN"
ip="$(dig +short "$DOMAIN" A @223.5.5.5 | tail -1)"
if [[ -z "$ip" ]]; then
echo "ERROR: $DOMAIN 无 A 记录,请先在 DNS 添加指向本机公网 IP(当前服务器: $(curl -sf ifconfig.me || echo unknown)"
exit 1
fi
echo " $DOMAIN -> $ip"
mkdir -p /var/www/certbot /var/log/nginx/dukang
# 若尚未有证书,先部署仅 HTTP 的配置以便 ACME 校验
if [[ ! -f /etc/letsencrypt/live/runxian.top/fullchain.pem ]]; then
echo "==> 临时 HTTP 配置(用于 ACME..."
cat > /etc/nginx/conf.d/dukang-runxian-apex.conf <<'EOF'
server {
listen 80;
server_name runxian.top;
root /opt/dukang-haoke/public;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location ~ ^/MP_verify_.*\.txt$ {
default_type text/plain;
access_log off;
}
location / {
try_files $uri $uri/ =404;
}
}
EOF
nginx -t
systemctl reload nginx
fi
echo "==> 申请证书..."
certbot certonly --webroot -w /var/www/certbot \
--cert-name runxian.top \
-d runxian.top \
--non-interactive --agree-tos -m "$EMAIL"
echo "==> 切换 HTTPS 配置..."
install -m 644 "$SCRIPT_DIR/nginx-runxian-apex.conf" /etc/nginx/conf.d/dukang-runxian-apex.conf
nginx -t
systemctl reload nginx
echo "==> 验证..."
code="$(curl -sf -o /dev/null -w '%{http_code}' "https://$DOMAIN/MP_verify_ayPJ4CQqbUcec3jX.txt" || echo fail)"
echo " https://$DOMAIN/MP_verify_ayPJ4CQqbUcec3jX.txt -> $code"
echo "==> runxian.top HTTPS 已启用"
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
# 为杜康 runxian.top 申请 Let's Encrypt 并切换 HTTPS 配置
# 三端 H5 统一入口 user.runxian.top/{user,shop,partner}/
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DOMAINS=(
user.runxian.top
shop.runxian.top
partner.runxian.top
webadmin.runxian.top
dkapi.runxian.top
)
echo "==> 检查 DNS 解析到本机..."
for d in "${DOMAINS[@]}"; do
ip="$(dig +short "$d" @223.5.5.5 | tail -1)"
if [[ -z "$ip" ]]; then
echo "ERROR: $d 无 A 记录,请先在 DNS 添加指向本机公网 IP"
exit 1
fi
echo " $d -> $ip"
done
mkdir -p /var/www/certbot /var/log/nginx/dukang
echo "==> 申请证书..."
certbot certonly --webroot -w /var/www/certbot \
--cert-name user.runxian.top \
-d user.runxian.top \
-d shop.runxian.top \
-d partner.runxian.top \
-d webadmin.runxian.top \
-d dkapi.runxian.top \
--non-interactive --agree-tos -m admin@runxian.top || {
echo "certbot 失败,请确认 DNS 已生效且 80 端口可从公网访问"
exit 1
}
echo "==> 切换 nginx HTTPS 配置..."
install -m 644 "$SCRIPT_DIR/nginx-runxian-dukang-ssl.conf" /etc/nginx/conf.d/dukang-runxian.conf
nginx -t
systemctl reload nginx
echo "==> 验证..."
for url in \
https://user.runxian.top/user/ \
https://user.runxian.top/shop/ \
https://user.runxian.top/partner/ \
https://shop.runxian.top/ \
https://webadmin.runxian.top/ \
https://dkapi.runxian.top/api/v1/health; do
code="$(curl -sf -o /dev/null -w '%{http_code}' "$url" || echo fail)"
echo " $url -> $code"
done
echo "==> runxian.top 杜康域名 HTTPS 已启用(H5 统一入口 user.runxian.top"
-33
View File
@@ -1,33 +0,0 @@
map $host $dukang_h5_port {
user.ai-mirror.xyz 8091;
shop.ai-mirror.xyz 8092;
partner.ai-mirror.xyz 8093;
}
# HTTP — 证书申请期间先提供 HTTP 服务
server {
listen 80;
server_name user.ai-mirror.xyz shop.ai-mirror.xyz partner.ai-mirror.xyz;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
}
location / {
proxy_pass http://127.0.0.1:$dukang_h5_port;
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 $scheme;
}
}
-54
View File
@@ -1,54 +0,0 @@
# 杜康好客 H5 三端 — ai-mirror.xyz
# API 内部端口 8090H5 分别 8091/8092/8093
map $host $dukang_h5_port {
user.ai-mirror.xyz 8091;
shop.ai-mirror.xyz 8092;
partner.ai-mirror.xyz 8093;
}
# HTTP → HTTPS
server {
listen 80;
server_name user.ai-mirror.xyz shop.ai-mirror.xyz partner.ai-mirror.xyz;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name user.ai-mirror.xyz shop.ai-mirror.xyz partner.ai-mirror.xyz;
access_log /var/log/nginx/dukang/access.log main;
error_log /var/log/nginx/dukang/error.log warn;
ssl_certificate /etc/letsencrypt/live/user.ai-mirror.xyz/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.ai-mirror.xyz/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:$dukang_h5_port;
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 $scheme;
}
}
-105
View File
@@ -1,105 +0,0 @@
# 证书申请期间 — lingshivip.cn HTTPapi HTTPS 证书就绪后换 nginx-lingshivip.conf
map $host $dukang_lingshi_port {
user.lingshivip.cn 8091;
shop.lingshivip.cn 8092;
partner.lingshivip.cn 8093;
admin.lingshivip.cn 8094;
}
server {
listen 80;
server_name user.lingshivip.cn shop.lingshivip.cn partner.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 80;
server_name api.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
server {
listen 80;
server_name admin.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:8094;
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 $scheme;
}
}
server {
listen 443 ssl;
server_name user.lingshivip.cn shop.lingshivip.cn partner.lingshivip.cn;
access_log /var/log/nginx/dukang/lingshi.access.log main;
error_log /var/log/nginx/dukang/lingshi.error.log warn;
ssl_certificate /etc/letsencrypt/live/user.lingshivip.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.lingshivip.cn/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:$dukang_lingshi_port;
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 $scheme;
}
}
-135
View File
@@ -1,135 +0,0 @@
# 杜康好客 — lingshivip.cn 五端 + API
# API 8090H5 8091/8092/8093admin-web 8094
map $host $dukang_lingshi_port {
user.lingshivip.cn 8091;
shop.lingshivip.cn 8092;
partner.lingshivip.cn 8093;
admin.lingshivip.cn 8094;
}
# HTTP → HTTPSH5 三端)
server {
listen 80;
server_name user.lingshivip.cn shop.lingshivip.cn partner.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
# API:证书就绪前 HTTP 直连;就绪后改走 HTTPS server
server {
listen 80;
server_name api.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
# admin 待 DNS + 证书就绪前先走 HTTP
server {
listen 80;
server_name admin.lingshivip.cn;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:8094;
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 $scheme;
}
}
# API 独立域名 HTTPS(证书:/etc/nginx/ssl/api.lingshivip.cn/
server {
listen 443 ssl;
server_name api.lingshivip.cn;
access_log /var/log/nginx/dukang/lingshi-api.access.log main;
error_log /var/log/nginx/dukang/lingshi-api.error.log warn;
ssl_certificate /etc/nginx/ssl/api.lingshivip.cn/fullchain.cer;
ssl_certificate_key /etc/nginx/ssl/api.lingshivip.cn/api.lingshivip.cn.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
server {
listen 443 ssl;
server_name user.lingshivip.cn shop.lingshivip.cn partner.lingshivip.cn;
access_log /var/log/nginx/dukang/lingshi.access.log main;
error_log /var/log/nginx/dukang/lingshi.error.log warn;
ssl_certificate /etc/letsencrypt/live/user.lingshivip.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.lingshivip.cn/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:$dukang_lingshi_port;
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 $scheme;
}
}
-53
View File
@@ -1,53 +0,0 @@
# runxian.top 裸域名 — 静态公共资源(微信域名校验等)
# HTTP: 保留 MP_verify + ACME;其余跳转 HTTPS
# HTTPS: deploy/enable-runxian-apex-ssl.sh 申请证书后启用
server {
listen 80;
server_name runxian.top;
access_log /var/log/nginx/dukang/runxian-apex.access.log main;
error_log /var/log/nginx/dukang/runxian-apex.error.log warn;
root /opt/dukang-haoke/public;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location ~ ^/MP_verify_.*\.txt$ {
default_type text/plain;
access_log off;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name runxian.top;
access_log /var/log/nginx/dukang/runxian-apex.access.log main;
error_log /var/log/nginx/dukang/runxian-apex.error.log warn;
ssl_certificate /etc/letsencrypt/live/runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
root /opt/dukang-haoke/public;
index index.html;
location ~ ^/MP_verify_.*\.txt$ {
default_type text/plain;
access_log off;
}
location / {
try_files $uri $uri/ =404;
}
}
-218
View File
@@ -1,218 +0,0 @@
# 杜康好客 — runxian.top HTTPS
# 执行: deploy/enable-runxian-dukang-ssl.sh
# 三端 H5 统一入口 user.runxian.top/{user,shop,partner}/
server {
listen 80;
server_name user.runxian.top shop.runxian.top partner.runxian.top m.runxian.top;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
return 301 https://user.runxian.top$request_uri;
}
}
server {
listen 443 ssl;
server_name m.runxian.top;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://user.runxian.top$request_uri;
}
server {
listen 443 ssl;
server_name user.runxian.top;
access_log /var/log/nginx/dukang/runxian-h5.access.log main;
error_log /var/log/nginx/dukang/runxian-h5.error.log warn;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location ~ ^/MP_verify_.*\.txt$ {
root /opt/dukang-haoke/apps/mini-user/dist;
default_type text/plain;
access_log off;
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location = / {
return 302 /user/;
}
# Taro 路由页是 /pages/xxx/index;补全短路径,避免 UniversalRouter 404 白屏
location ~ ^/user/pages/([^/]+)/?$ {
return 302 /user/pages/$1/index$is_args$args;
}
location /user/ {
rewrite ^/user/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8091;
proxy_http_version 1.1;
# serve 若仍发出绝对 Location:/pages/...,补回 /user 前缀
proxy_redirect / /user/;
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;
}
location /shop/ {
rewrite ^/shop/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8092;
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 $scheme;
}
location /partner/ {
rewrite ^/partner/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8093;
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 $scheme;
}
# 旧 C 端根路径书签(无 /user 前缀)→ /user/...
location / {
return 302 /user$request_uri;
}
}
server {
listen 443 ssl;
server_name shop.runxian.top;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://user.runxian.top/shop$request_uri;
}
server {
listen 443 ssl;
server_name partner.runxian.top;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://user.runxian.top/partner$request_uri;
}
server {
listen 80;
server_name dkapi.runxian.top;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
include /opt/dukang-haoke/deploy/nginx-deploy-webhook.conf;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name dkapi.runxian.top;
access_log /var/log/nginx/dukang/runxian-api.access.log main;
error_log /var/log/nginx/dukang/runxian-api.error.log warn;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
include /opt/dukang-haoke/deploy/nginx-deploy-webhook.conf;
location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
server {
listen 80;
server_name webadmin.runxian.top;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name webadmin.runxian.top;
access_log /var/log/nginx/dukang/runxian-admin.access.log main;
error_log /var/log/nginx/dukang/runxian-admin.error.log warn;
ssl_certificate /etc/letsencrypt/live/user.runxian.top/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/user.runxian.top/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
client_max_body_size 20m;
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:8094;
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 $scheme;
}
}
-154
View File
@@ -1,154 +0,0 @@
# 杜康好客 — runxian.topHTTP
# 三端 H5 统一入口 user.runxian.top/{user,shop,partner}/(微信网页授权单域名)
# shop/partner 子域名 301 到 user.runxian.top 路径;webadmin → 8094dkapi → 8090
server {
listen 80;
server_name user.runxian.top;
access_log /var/log/nginx/dukang/runxian-h5.access.log main;
error_log /var/log/nginx/dukang/runxian-h5.error.log warn;
client_max_body_size 20m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location ~ ^/MP_verify_.*\.txt$ {
root /opt/dukang-haoke/apps/mini-user/dist;
default_type text/plain;
access_log off;
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location = / {
return 302 /user/;
}
location ~ ^/user/pages/([^/]+)/?$ {
return 302 /user/pages/$1/index$is_args$args;
}
location /user/ {
rewrite ^/user/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8091;
proxy_http_version 1.1;
proxy_redirect / /user/;
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;
}
location /shop/ {
rewrite ^/shop/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8092;
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 $scheme;
}
location /partner/ {
rewrite ^/partner/(.*)$ /$1 break;
proxy_pass http://127.0.0.1:8093;
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 $scheme;
}
location / {
return 302 /user$request_uri;
}
}
server {
listen 80;
server_name shop.runxian.top;
return 301 http://user.runxian.top/shop$request_uri;
}
server {
listen 80;
server_name partner.runxian.top;
return 301 http://user.runxian.top/partner$request_uri;
}
server {
listen 80;
server_name m.runxian.top;
return 301 http://user.runxian.top$request_uri;
}
server {
listen 80;
server_name dkapi.runxian.top;
access_log /var/log/nginx/dukang/runxian-api.access.log main;
error_log /var/log/nginx/dukang/runxian-api.error.log warn;
client_max_body_size 20m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location / {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
}
server {
listen 80;
server_name webadmin.runxian.top;
access_log /var/log/nginx/dukang/runxian-admin.access.log main;
error_log /var/log/nginx/dukang/runxian-admin.error.log warn;
client_max_body_size 20m;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type "text/plain";
}
location /api/ {
proxy_pass http://127.0.0.1:8090;
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 $scheme;
proxy_set_header Connection "";
}
location / {
proxy_pass http://127.0.0.1:8094;
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 $scheme;
}
}
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
# 续部署:user.runxian.top 统一路径入口 + nginx 切换
set -euo pipefail
APP_ROOT="${APP_ROOT:-/opt/dukang-haoke}"
H5_BASE="https://user.runxian.top"
cd "$APP_ROOT"
echo "==> 1. PM2 重启(H5 dist 已含 /user|shop|partner/ 路径)"
pm2 restart dukang-api dukang-h5-user dukang-h5-shop dukang-h5-partner dukang-admin-web
pm2 save
echo "==> 2. 安装 nginx HTTPS 配置"
install -m 644 "$APP_ROOT/deploy/nginx-runxian-dukang-ssl.conf" /etc/nginx/conf.d/dukang-runxian.conf
nginx -t
systemctl reload nginx
echo "==> 3. 更新 USER_H5_URL"
ENV_FILE="$APP_ROOT/server/dukang-api/.env.production"
if grep -q '^USER_H5_URL=' "$ENV_FILE"; then
sed -i "s|^USER_H5_URL=.*|USER_H5_URL=${H5_BASE}/user|" "$ENV_FILE"
else
echo "USER_H5_URL=${H5_BASE}/user" >> "$ENV_FILE"
fi
pm2 restart dukang-api
echo "==> 4. 验证"
for url in \
"${H5_BASE}/user/" \
"${H5_BASE}/shop/" \
"${H5_BASE}/partner/" \
"https://shop.runxian.top/" \
"https://dkapi.runxian.top/api/v1/health"; do
code="$(curl -sk -o /dev/null -w '%{http_code}' "$url" || echo fail)"
echo " $url -> $code"
done
echo "==> user.runxian.top 统一部署完成"
-57
View File
@@ -1,57 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
APP_ROOT="/opt/dukang-haoke"
DEPLOY_DIR="$APP_ROOT/deploy"
echo "==> install api deps only"
cd "$APP_ROOT"
export NODE_OPTIONS="--max-old-space-size=1024"
pnpm install --filter @dukang/api... --filter @dukang/domain --filter @dukang/shared-types
echo "==> env"
cat > "$APP_ROOT/server/dukang-api/.env.production" <<'ENV'
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="gky4CNZgwdrYoDIDy4AWBiJRa4WX2ax8STe3TEG93Ht"
JWT_EXPIRES_IN="7d"
PORT=8090
MOCK_SMS=true
MOCK_SMS_CODE=123456
MOCK_PAY=true
MOCK_DELIVERY_AUTO=true
AUTO_APPROVE_STORE=true
ENV
echo "==> prisma"
cd "$APP_ROOT/server/dukang-api"
pnpm prisma:generate
npx prisma db push --accept-data-loss
pnpm prisma:seed
echo "==> serve"
npm install -g serve
echo "==> pm2"
mkdir -p /var/log/nginx/dukang /var/www/certbot
pm2 delete dukang-api dukang-h5-user dukang-h5-shop dukang-h5-partner 2>/dev/null || true
pm2 start "$DEPLOY_DIR/ecosystem.config.cjs"
pm2 save
echo "==> ssl"
if [ ! -f /etc/letsencrypt/live/user.ai-mirror.xyz/fullchain.pem ]; then
certbot certonly --webroot -w /var/www/certbot \
-d user.ai-mirror.xyz -d shop.ai-mirror.xyz -d partner.ai-mirror.xyz \
--non-interactive --agree-tos -m admin@ai-mirror.xyz
fi
echo "==> nginx"
cp "$DEPLOY_DIR/nginx-ai-mirror.conf" /etc/nginx/conf.d/dukang-ai-mirror.conf
nginx -t
systemctl reload nginx
echo "==> DONE"
pm2 list
ss -tlnp | grep -E '809[0-3]' || true
curl -s -o /dev/null -w "user:%{http_code}\n" http://127.0.0.1:8091/
curl -s -o /dev/null -w "api:%{http_code}\n" http://127.0.0.1:8090/api/v1/health 2>/dev/null || curl -s -o /dev/null -w "api:%{http_code}\n" http://127.0.0.1:8090/
-79
View File
@@ -1,79 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
APP_ROOT="/opt/dukang-haoke"
DEPLOY_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "==> 1. 安装 MySQL(如未安装)"
if ! command -v mysqld &>/dev/null; then
dnf install -y mysql-server
systemctl enable --now mysqld
sleep 3
fi
echo "==> 2. 配置 MySQL 数据库"
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS dukang_haoke CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" 2>/dev/null || true
mysql -uroot -e "CREATE USER IF NOT EXISTS 'dukang'@'localhost' IDENTIFIED BY 'Dukang2026!';" 2>/dev/null || \
mysql -uroot -e "ALTER USER 'dukang'@'localhost' IDENTIFIED BY 'Dukang2026!';" 2>/dev/null || true
mysql -uroot -e "GRANT ALL PRIVILEGES ON dukang_haoke.* TO 'dukang'@'localhost'; FLUSH PRIVILEGES;" 2>/dev/null || true
echo "==> 3. 启用 pnpm"
corepack enable
corepack prepare pnpm@11.2.2 --activate
echo "==> 4. 安装依赖并构建"
cd "$APP_ROOT"
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
pnpm build
echo "==> 5. 写入 API 生产环境变量"
cat > "$APP_ROOT/server/dukang-api/.env.production" <<'ENV'
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="gky4CNZgwdrYoDIDy4AWBiJRa4WX2ax8STe3TEG93Ht"
JWT_EXPIRES_IN="7d"
PORT=8090
MOCK_SMS=true
MOCK_SMS_CODE=123456
MOCK_PAY=true
MOCK_DELIVERY_AUTO=true
AUTO_APPROVE_STORE=true
ENV
echo "==> 6. 初始化数据库"
cd "$APP_ROOT/server/dukang-api"
pnpm prisma:generate
npx prisma db push --accept-data-loss
pnpm prisma:seed
echo "==> 7. 安装 serve(静态服务)"
npm install -g serve
echo "==> 8. 启动 PM2 进程"
mkdir -p /var/log/nginx/dukang /var/www/certbot
pm2 delete dukang-api dukang-h5-user dukang-h5-shop dukang-h5-partner 2>/dev/null || true
pm2 start "$DEPLOY_DIR/ecosystem.config.cjs"
pm2 save
echo "==> 9. 申请 SSL 证书"
if [ ! -f /etc/letsencrypt/live/user.ai-mirror.xyz/fullchain.pem ]; then
certbot certonly --webroot -w /var/www/certbot \
-d user.ai-mirror.xyz \
-d shop.ai-mirror.xyz \
-d partner.ai-mirror.xyz \
--non-interactive --agree-tos -m admin@ai-mirror.xyz || \
certbot certonly --nginx \
-d user.ai-mirror.xyz \
-d shop.ai-mirror.xyz \
-d partner.ai-mirror.xyz \
--non-interactive --agree-tos -m admin@ai-mirror.xyz
fi
echo "==> 10. 配置 Nginx"
cp "$DEPLOY_DIR/nginx-ai-mirror.conf" /etc/nginx/conf.d/dukang-ai-mirror.conf
nginx -t
systemctl reload nginx
echo "==> 部署完成"
pm2 list
ss -tlnp | grep -E '809[0-3]' || true
-4
View File
@@ -1,4 +0,0 @@
# 服务器机器相关(可合并到 .env.production 的参考项)
PORT=8090
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
REDIS_URL="redis://localhost:6379"
+7 -21
View File
@@ -2,7 +2,7 @@
# 在服务器上执行:安装 Webhook 自动发版
set -euo pipefail
APP_ROOT="${APP_ROOT:-/opt/dukang-haoke}"
APP_ROOT="${APP_ROOT:-/opt/dukang}"
DEPLOY_DIR="$APP_ROOT/deploy"
ENV_FILE="$DEPLOY_DIR/auto-release.env"
LOG_DIR="/var/log/dukang"
@@ -30,28 +30,14 @@ HOOK_CONF="$DEPLOY_DIR/nginx-deploy-webhook.conf"
rm -f /etc/nginx/conf.d/dukang-deploy-webhook.conf
MARKER="include $HOOK_CONF;"
for conf in \
/etc/nginx/sites-available/dukang \
/etc/nginx/conf.d/dukang-runxian.conf \
/etc/nginx/conf.d/dukang-runxian-ssl.conf
do
if [[ ! -f "$conf" ]]; then
continue
fi
# 清理旧错误 include
sed -i '\|include /etc/nginx/conf.d/dukang-deploy-webhook.conf;|d' "$conf"
if grep -q 'server_name api.dukanghaoke.com' "$conf"; then
if ! grep -qF "$MARKER" "$conf"; then
sed -i "/server_name api.dukanghaoke.com;/a\\ $MARKER" "$conf"
echo " 已 patch $conf (api.dukanghaoke.com)"
fi
elif grep -q 'server_name dkapi.runxian.top' "$conf"; then
if ! grep -qF "$MARKER" "$conf"; then
sed -i "/server_name dkapi.runxian.top;/a\\ $MARKER" "$conf"
echo " 已 patch $conf (dkapi.runxian.top)"
NGINX_SITE="/etc/nginx/sites-available/dukang"
if [[ -f "$NGINX_SITE" ]]; then
sed -i '\|include /etc/nginx/conf.d/dukang-deploy-webhook.conf;|d' "$NGINX_SITE"
if grep -q 'server_name api.dukanghaoke.com' "$NGINX_SITE" && ! grep -qF "$MARKER" "$NGINX_SITE"; then
sed -i "/server_name api.dukanghaoke.com;/a\\ $MARKER" "$NGINX_SITE"
echo " 已 patch $NGINX_SITE (api.dukanghaoke.com)"
fi
fi
done
nginx -t
systemctl reload nginx