feat: 技术支持工单/企微权限/开发版本管理/消息推送等迭代
This commit is contained in:
@@ -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);
|
||||
@@ -1,22 +0,0 @@
|
||||
# 复制为 auto-release.env(勿提交 Git)
|
||||
# 服务器路径: /opt/dukang/deploy/auto-release.env
|
||||
|
||||
APP_ROOT=/opt/dukang
|
||||
GIT_REPO_URL=git@git.yqidian.com:jacy/dukang.git
|
||||
GIT_REMOTE=origin
|
||||
GIT_BRANCH=dev
|
||||
DEPLOY_GIT_REF=refs/heads/dev
|
||||
|
||||
# Webhook 密钥(CodeUp 配置「Secret Token」时使用同一值)
|
||||
DEPLOY_WEBHOOK_SECRET=change-me-to-a-long-random-string
|
||||
|
||||
DEPLOY_WEBHOOK_HOST=127.0.0.1
|
||||
DEPLOY_WEBHOOK_PORT=8095
|
||||
DEPLOY_LOG_FILE=/var/log/dukang/deploy.log
|
||||
DEPLOY_LOCK_FILE=/var/run/dukang-deploy.lock
|
||||
|
||||
# Prisma db push 遇到 schema 变更警告时自动继续(开发/测试环境可开)
|
||||
PRISMA_ACCEPT_DATA_LOSS=true
|
||||
|
||||
# 注意:Admin「发布更新」读取的是 server/dukang-api/.env.production 中的
|
||||
# DEPLOY_WEBHOOK_URL / DEPLOY_WEBHOOK_SECRET,须与下方 DEPLOY_WEBHOOK_SECRET 一致
|
||||
@@ -1,113 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 自动发版:git pull + remote-release(由 webhook 或手动触发)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ENV_FILE="$SCRIPT_DIR/auto-release.env"
|
||||
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang}"
|
||||
GIT_REMOTE="${GIT_REMOTE:-origin}"
|
||||
GIT_BRANCH="${GIT_BRANCH:-dev}"
|
||||
LOCK_FILE="${DEPLOY_LOCK_FILE:-/var/run/dukang-deploy.lock}"
|
||||
PENDING_FILE="${DEPLOY_PENDING_FILE:-/var/run/dukang-deploy.pending}"
|
||||
LOG_FILE="${DEPLOY_LOG_FILE:-/var/log/dukang/deploy.log}"
|
||||
WEBHOOK_FETCH_RETRIES="${WEBHOOK_FETCH_RETRIES:-5}"
|
||||
WEBHOOK_FETCH_DELAY_SEC="${WEBHOOK_FETCH_DELAY_SEC:-3}"
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source <(sed 's/\r$//' "$ENV_FILE")
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
exec 9>"$LOCK_FILE"
|
||||
if ! flock -n 9; then
|
||||
if [[ -n "${DEPLOY_TRIGGER:-}" ]]; then
|
||||
date '+%s' >"$PENDING_FILE"
|
||||
log "QUEUED: another deploy is in progress (webhook will retry after current run)"
|
||||
else
|
||||
log "SKIP: another deploy is in progress"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "START auto-release (branch=$GIT_BRANCH)"
|
||||
|
||||
cd "$APP_ROOT"
|
||||
|
||||
if [[ ! -d .git ]]; then
|
||||
log "ERROR: $APP_ROOT is not a git repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 统一远程仓库为 SSH 地址
|
||||
GIT_REPO_URL="${GIT_REPO_URL:-git@git.yqidian.com:jacy/dukang.git}"
|
||||
CURRENT_URL="$(git remote get-url "$GIT_REMOTE" 2>/dev/null || true)"
|
||||
if [[ "$CURRENT_URL" != "$GIT_REPO_URL" ]]; then
|
||||
log "SET remote $GIT_REMOTE -> $GIT_REPO_URL (was: ${CURRENT_URL:-none})"
|
||||
git remote set-url "$GIT_REMOTE" "$GIT_REPO_URL" 2>/dev/null \
|
||||
|| git remote add "$GIT_REMOTE" "$GIT_REPO_URL"
|
||||
fi
|
||||
|
||||
git fetch "$GIT_REMOTE"
|
||||
git checkout "$GIT_BRANCH"
|
||||
|
||||
LOCAL_SHA="$(git rev-parse HEAD)"
|
||||
REMOTE_SHA="$(git rev-parse "$GIT_REMOTE/$GIT_BRANCH")"
|
||||
|
||||
# CodeUp 常在 push 完成前连发 webhook,首次 fetch 可能仍拿到旧 SHA
|
||||
if [[ "$LOCAL_SHA" == "$REMOTE_SHA" ]] && [[ -n "${DEPLOY_TRIGGER:-}" ]]; then
|
||||
for ((i = 1; i <= WEBHOOK_FETCH_RETRIES; i++)); do
|
||||
log "WEBHOOK retry fetch $i/$WEBHOOK_FETCH_RETRIES (waiting ${WEBHOOK_FETCH_DELAY_SEC}s for remote)"
|
||||
sleep "$WEBHOOK_FETCH_DELAY_SEC"
|
||||
git fetch "$GIT_REMOTE"
|
||||
REMOTE_SHA="$(git rev-parse "$GIT_REMOTE/$GIT_BRANCH")"
|
||||
if [[ "$LOCAL_SHA" != "$REMOTE_SHA" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ "$LOCAL_SHA" == "$REMOTE_SHA" ]]; then
|
||||
log "SKIP: already up to date ($LOCAL_SHA)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "PULL $LOCAL_SHA -> $REMOTE_SHA"
|
||||
|
||||
# 保留服务器生产密钥,避免 reset 覆盖
|
||||
ENV_BACKUP="$(mktemp)"
|
||||
API_ENV_PROD_BACKUP="$(mktemp)"
|
||||
cp "$ENV_FILE" "$ENV_BACKUP" 2>/dev/null || true
|
||||
cp "$APP_ROOT/server/dukang-api/.env.production" "$API_ENV_PROD_BACKUP" 2>/dev/null || true
|
||||
|
||||
git checkout "$GIT_BRANCH"
|
||||
git reset --hard "$GIT_REMOTE/$GIT_BRANCH"
|
||||
|
||||
cp "$ENV_BACKUP" "$ENV_FILE" 2>/dev/null || true
|
||||
cp "$API_ENV_PROD_BACKUP" "$APP_ROOT/server/dukang-api/.env.production" 2>/dev/null || true
|
||||
rm -f "$ENV_BACKUP" "$API_ENV_PROD_BACKUP"
|
||||
sed -i 's/\r$//' "$ENV_FILE" 2>/dev/null || true
|
||||
|
||||
# 修复 Windows 换行
|
||||
find "$APP_ROOT/deploy" -maxdepth 1 -name '*.sh' -exec sed -i 's/\r$//' {} + 2>/dev/null || true
|
||||
|
||||
log "RUN remote-release.sh"
|
||||
RELEASE_ARGS=()
|
||||
if [[ "${PRISMA_ACCEPT_DATA_LOSS:-false}" == "true" ]]; then
|
||||
RELEASE_ARGS+=(--accept-data-loss)
|
||||
fi
|
||||
bash "$APP_ROOT/deploy/remote-release.sh" "${RELEASE_ARGS[@]}"
|
||||
|
||||
log "DONE auto-release ($REMOTE_SHA)"
|
||||
|
||||
if [[ -f "$PENDING_FILE" ]]; then
|
||||
rm -f "$PENDING_FILE"
|
||||
log "RETRY: running queued webhook deploy"
|
||||
DEPLOY_TRIGGER="${DEPLOY_TRIGGER:-queued}" bash "$0"
|
||||
fi
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在生产机上初始化 Staging 目录(同机双栈,一次性)
|
||||
# 用法(SSH 到服务器后):
|
||||
# APP_ROOT=/opt/dukang bash /opt/dukang/deploy/bootstrap-staging.sh
|
||||
# 或本机:
|
||||
# ssh root@host 'bash -s' < deploy/bootstrap-staging.sh
|
||||
set -euo pipefail
|
||||
|
||||
PROD_ROOT="${PROD_ROOT:-/opt/dukang}"
|
||||
STAGING_ROOT="${STAGING_ROOT:-/opt/dukang-staging}"
|
||||
GIT_REMOTE_URL="${GIT_REMOTE_URL:-}"
|
||||
GIT_BRANCH="${GIT_BRANCH:-dev}"
|
||||
|
||||
if [[ -d "$STAGING_ROOT/.git" ]]; then
|
||||
echo "==> 已存在 $STAGING_ROOT,跳过 clone"
|
||||
else
|
||||
if [[ -z "$GIT_REMOTE_URL" ]]; then
|
||||
if [[ -d "$PROD_ROOT/.git" ]]; then
|
||||
GIT_REMOTE_URL="$(git -C "$PROD_ROOT" remote get-url origin)"
|
||||
else
|
||||
echo "错误: 请设置 GIT_REMOTE_URL,或确保 $PROD_ROOT 为 git 仓库" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
echo "==> clone $GIT_REMOTE_URL → $STAGING_ROOT (branch $GIT_BRANCH)"
|
||||
git clone --branch "$GIT_BRANCH" "$GIT_REMOTE_URL" "$STAGING_ROOT"
|
||||
fi
|
||||
|
||||
mkdir -p "$STAGING_ROOT/server/dukang-api"
|
||||
if [[ ! -f "$STAGING_ROOT/server/dukang-api/.env.staging" ]]; then
|
||||
if [[ -f "$STAGING_ROOT/server/dukang-api/.env.staging.example" ]]; then
|
||||
cp "$STAGING_ROOT/server/dukang-api/.env.staging.example" \
|
||||
"$STAGING_ROOT/server/dukang-api/.env.staging"
|
||||
echo "==> 已生成 .env.staging(请编辑 DATABASE_URL / Redis / 微信后 sync 或本机改)"
|
||||
else
|
||||
echo "WARN: 无 .env.staging.example,请手动创建 .env.staging"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> 下一步:"
|
||||
echo " 1. 创建 RDS 库 dukang_staging,填好 $STAGING_ROOT/server/dukang-api/.env.staging"
|
||||
echo " 2. DNS: api-test/user-test/shop-test/partner-test/admin-test.dukanghaoke.com → 本机"
|
||||
echo " 3. 启用 nginx: ln -sf $STAGING_ROOT/deploy/nginx-dukang-staging.conf /etc/nginx/sites-enabled/"
|
||||
echo " nginx -t && systemctl reload nginx(证书需含 *-test SAN)"
|
||||
echo " 4. 本机发版: bash deploy/deploy-staging.sh -- --seed --accept-data-loss"
|
||||
echo "==> bootstrap 完成"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 发生产环境:分支默认 main → /opt/dukang(8090–8094)
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
echo "==> 即将发往 PRODUCTION(main → /opt/dukang)"
|
||||
exec bash "$SCRIPT_DIR/deploy.sh" --env production "$@"
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 发测试环境:分支默认 dev → /opt/dukang-staging(8190–8194)
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
exec bash "$SCRIPT_DIR/deploy.sh" --env staging "$@"
|
||||
@@ -1,17 +0,0 @@
|
||||
# 复制为 deploy.env 后填写(deploy.env 勿提交)
|
||||
DEPLOY_HOST=your.server.ip.or.domain
|
||||
DEPLOY_USER=root
|
||||
DEPLOY_PORT=22
|
||||
# DEPLOY_SSH_KEY=~/.ssh/id_rsa
|
||||
|
||||
# 同机双栈目录
|
||||
PROD_APP_ROOT=/opt/dukang
|
||||
STAGING_APP_ROOT=/opt/dukang-staging
|
||||
# 兼容旧字段(未指定 --env 时可能用到)
|
||||
APP_ROOT=/opt/dukang
|
||||
|
||||
# 生产仓库(SSH)
|
||||
GIT_REPO_URL=git@git.yqidian.com:jacy/dukang.git
|
||||
GIT_REMOTE=origin
|
||||
# 默认分支已由 --env 决定:staging→dev,production→main
|
||||
# GIT_BRANCH=dev
|
||||
@@ -1,180 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地执行:SSH 到服务器拉代码并发布(支持 staging / production 同机双栈)
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ENV_FILE="$SCRIPT_DIR/deploy.env"
|
||||
|
||||
DEPLOY_HOST=""
|
||||
DEPLOY_USER="root"
|
||||
DEPLOY_PORT="22"
|
||||
DEPLOY_SSH_KEY=""
|
||||
APP_ROOT=""
|
||||
GIT_REMOTE="origin"
|
||||
GIT_BRANCH=""
|
||||
DUKANG_DEPLOY_ENV=""
|
||||
|
||||
CHECK_ONLY=false
|
||||
RELEASE_ARGS=()
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法: deploy/deploy.sh --env staging|production [选项] [-- 远程 release 参数]
|
||||
|
||||
环境配置: deploy/deploy.env(可复制 deploy.env.example)
|
||||
|
||||
选项:
|
||||
--env ENV staging(测试)| production(生产)【必填,或用 deploy-staging/prod.sh】
|
||||
--host HOST 服务器地址(覆盖 deploy.env)
|
||||
--user USER SSH 用户,默认 root
|
||||
--port PORT SSH 端口,默认 22
|
||||
--key PATH SSH 私钥路径
|
||||
--branch BRANCH 发布分支(staging 默认 dev,production 默认 main)
|
||||
--check 仅 SSH 连接并查看 PM2 / 端口状态
|
||||
-h, --help 显示帮助
|
||||
|
||||
示例:
|
||||
./deploy/deploy-staging.sh -- --skip-db
|
||||
./deploy/deploy-prod.sh -- --skip-db
|
||||
./deploy/deploy.sh --env staging --branch dev -- --skip-db
|
||||
./deploy/deploy.sh --env production --branch main -- --skip-db
|
||||
EOF
|
||||
}
|
||||
|
||||
load_env() {
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
load_env
|
||||
|
||||
# CLI 覆盖 deploy.env(须在 load_env 之后解析)
|
||||
CLI_ENV=""
|
||||
CLI_BRANCH=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--env) CLI_ENV="$2"; shift 2 ;;
|
||||
--host) DEPLOY_HOST="$2"; shift 2 ;;
|
||||
--user) DEPLOY_USER="$2"; shift 2 ;;
|
||||
--port) DEPLOY_PORT="$2"; shift 2 ;;
|
||||
--key) DEPLOY_SSH_KEY="$2"; shift 2 ;;
|
||||
--branch) CLI_BRANCH="$2"; shift 2 ;;
|
||||
--check) CHECK_ONLY=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--)
|
||||
shift
|
||||
RELEASE_ARGS=("$@")
|
||||
break
|
||||
;;
|
||||
*) RELEASE_ARGS+=("$1"); shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$CLI_ENV" ]]; then
|
||||
DUKANG_DEPLOY_ENV="$CLI_ENV"
|
||||
fi
|
||||
|
||||
# 兼容旧调用:未传 --env 时,若 GIT_BRANCH=main 视为生产,否则 staging
|
||||
if [[ -z "$DUKANG_DEPLOY_ENV" ]]; then
|
||||
if [[ "${CLI_BRANCH:-${GIT_BRANCH:-}}" == "main" || "${CLI_BRANCH:-${GIT_BRANCH:-}}" == "master" ]]; then
|
||||
DUKANG_DEPLOY_ENV=production
|
||||
else
|
||||
DUKANG_DEPLOY_ENV=staging
|
||||
echo "WARN: 未指定 --env,默认按 staging 发测试环境。生产请用 --env production 或 deploy-prod.sh" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$DUKANG_DEPLOY_ENV" in
|
||||
staging|stage|test) DUKANG_DEPLOY_ENV=staging ;;
|
||||
production|prod) DUKANG_DEPLOY_ENV=production ;;
|
||||
*)
|
||||
echo "错误: --env 须为 staging 或 production" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$DUKANG_DEPLOY_ENV" == "staging" ]]; then
|
||||
APP_ROOT="${STAGING_APP_ROOT:-${APP_ROOT:-/opt/dukang-staging}}"
|
||||
# 若 deploy.env 里 APP_ROOT 仍是生产路径,staging 强制用 STAGING_APP_ROOT 或默认
|
||||
if [[ -n "${STAGING_APP_ROOT:-}" ]]; then
|
||||
APP_ROOT="$STAGING_APP_ROOT"
|
||||
elif [[ "$APP_ROOT" == "/opt/dukang" ]]; then
|
||||
APP_ROOT="/opt/dukang-staging"
|
||||
fi
|
||||
# 测试固定默认 dev;CLI --branch 优先,忽略 deploy.env 里误配的 main
|
||||
GIT_BRANCH="${CLI_BRANCH:-dev}"
|
||||
PORT_GREP='819[0-4]'
|
||||
else
|
||||
APP_ROOT="${PROD_APP_ROOT:-${APP_ROOT:-/opt/dukang}}"
|
||||
if [[ -n "${PROD_APP_ROOT:-}" ]]; then
|
||||
APP_ROOT="$PROD_APP_ROOT"
|
||||
elif [[ "$APP_ROOT" == "/opt/dukang-staging" ]]; then
|
||||
APP_ROOT="/opt/dukang"
|
||||
fi
|
||||
# 生产固定默认 main;CLI --branch 优先,忽略 deploy.env 里误配的 dev
|
||||
GIT_BRANCH="${CLI_BRANCH:-main}"
|
||||
PORT_GREP='809[0-4]'
|
||||
fi
|
||||
|
||||
if [[ -z "$DEPLOY_HOST" ]]; then
|
||||
echo "错误: 未配置 DEPLOY_HOST。请创建 deploy/deploy.env 或使用 --host" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH_OPTS=(-o "StrictHostKeyChecking=accept-new" -p "$DEPLOY_PORT")
|
||||
if [[ -n "$DEPLOY_SSH_KEY" ]]; then
|
||||
SSH_OPTS+=(-i "$DEPLOY_SSH_KEY")
|
||||
fi
|
||||
TARGET="${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
|
||||
run_remote() {
|
||||
ssh "${SSH_OPTS[@]}" "$TARGET" "$@"
|
||||
}
|
||||
|
||||
if [[ "$CHECK_ONLY" == true ]]; then
|
||||
echo "==> 检查 $TARGET ($DUKANG_DEPLOY_ENV)"
|
||||
run_remote "pm2 list; ss -tlnp | grep -E '$PORT_GREP' || true"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REMOTE_RELEASE_ARGS=""
|
||||
if [[ ${#RELEASE_ARGS[@]} -gt 0 ]]; then
|
||||
REMOTE_RELEASE_ARGS="${RELEASE_ARGS[*]}"
|
||||
fi
|
||||
|
||||
echo "==> 发布到 $TARGET env=$DUKANG_DEPLOY_ENV branch=$GIT_BRANCH root=$APP_ROOT"
|
||||
|
||||
run_remote bash -s <<EOF
|
||||
set -euo pipefail
|
||||
APP_ROOT="$APP_ROOT"
|
||||
GIT_REMOTE="$GIT_REMOTE"
|
||||
GIT_BRANCH="$GIT_BRANCH"
|
||||
RELEASE_ARGS="$REMOTE_RELEASE_ARGS"
|
||||
DUKANG_DEPLOY_ENV="$DUKANG_DEPLOY_ENV"
|
||||
export DEPLOY_TRIGGER=manual
|
||||
export APP_ROOT
|
||||
export DUKANG_DEPLOY_ENV
|
||||
|
||||
cd "\$APP_ROOT"
|
||||
|
||||
if [[ ! -d .git ]]; then
|
||||
echo "错误: \$APP_ROOT 不是 git 仓库。请先 clone 到该目录(staging 建议 /opt/dukang-staging)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> 拉取代码"
|
||||
git fetch "\$GIT_REMOTE"
|
||||
git checkout "\$GIT_BRANCH"
|
||||
git pull --ff-only "\$GIT_REMOTE" "\$GIT_BRANCH"
|
||||
|
||||
# 无额外参数时执行 DB 同步;无 schema 变更请显式传 -- --skip-db
|
||||
if [[ -n "\$RELEASE_ARGS" ]]; then
|
||||
bash deploy/remote-release.sh \$RELEASE_ARGS
|
||||
else
|
||||
bash deploy/remote-release.sh
|
||||
fi
|
||||
EOF
|
||||
|
||||
echo "==> 本地发布命令已完成 ($DUKANG_DEPLOY_ENV)"
|
||||
@@ -1,31 +0,0 @@
|
||||
name: dukang-v1
|
||||
|
||||
# 与 dukang-v3(6014/6015)隔离;本栈使用 6016/6017
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: dukang-v1-mysql
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_DATABASE: dukang_haoke
|
||||
ports:
|
||||
- '6016:3306'
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: dukang-v1-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '6017:6379'
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
name: dukang-v1_mysql_data
|
||||
redis_data:
|
||||
name: dukang-v1_redis_data
|
||||
@@ -1,70 +0,0 @@
|
||||
/** PM2 ecosystem — 杜康好客生产部署 */
|
||||
const APP_ROOT = process.env.APP_ROOT || '/opt/dukang';
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'dukang-api',
|
||||
cwd: `${APP_ROOT}/server/dukang-api`,
|
||||
script: 'dist/main.js',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
APP_ENV: 'production',
|
||||
PORT: 8090,
|
||||
},
|
||||
},
|
||||
{
|
||||
// C 端改为 mini-user H5 产物(端口仍 8091,进程名保持兼容旧脚本)
|
||||
// cleanUrls:false 见 apps/mini-user/serve.json(发版时复制到 dist/)
|
||||
// 否则 /pages/x/index 会被 301 成 /pages/x,Location 缺 /user 前缀 → Taro 白屏
|
||||
name: 'dukang-h5-user',
|
||||
cwd: `${APP_ROOT}/apps/mini-user`,
|
||||
script: 'npx',
|
||||
args: 'serve dist -l 8091 -c serve.json',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-h5-shop',
|
||||
cwd: `${APP_ROOT}/apps/h5-shop`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8092',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-h5-partner',
|
||||
cwd: `${APP_ROOT}/apps/h5-partner`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8093',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-admin-web',
|
||||
cwd: `${APP_ROOT}/apps/admin-web`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8094',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-deploy-webhook',
|
||||
cwd: `${APP_ROOT}/deploy`,
|
||||
script: 'webhook-server.mjs',
|
||||
interpreter: 'node',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
APP_ENV: 'production',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,55 +0,0 @@
|
||||
/** PM2 ecosystem — 杜康好客 Staging(同机双栈,端口 8190–8194) */
|
||||
const APP_ROOT = process.env.APP_ROOT || '/opt/dukang-staging';
|
||||
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'dukang-stg-api',
|
||||
cwd: `${APP_ROOT}/server/dukang-api`,
|
||||
script: 'dist/main.js',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
APP_ENV: 'staging',
|
||||
PORT: 8190,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dukang-stg-h5-user',
|
||||
cwd: `${APP_ROOT}/apps/mini-user`,
|
||||
script: 'npx',
|
||||
args: 'serve dist -l 8191 -c serve.json',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-stg-h5-shop',
|
||||
cwd: `${APP_ROOT}/apps/h5-shop`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8192',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-stg-h5-partner',
|
||||
cwd: `${APP_ROOT}/apps/h5-partner`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8193',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
{
|
||||
name: 'dukang-stg-admin-web',
|
||||
cwd: `${APP_ROOT}/apps/admin-web`,
|
||||
script: 'npx',
|
||||
args: 'serve -s dist -l 8194',
|
||||
interpreter: 'none',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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 已启用"
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 启用 api.dukanghaoke.com:扩证书 + 装 nginx 配置
|
||||
set -euo pipefail
|
||||
|
||||
DOMAIN_API=api.dukanghaoke.com
|
||||
CERT_NAME=user.dukanghaoke.com
|
||||
EMAIL="${CERTBOT_EMAIL:-admin@dukanghaoke.com}"
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang}"
|
||||
NGINX_SRC="$APP_ROOT/deploy/nginx-dukanghaoke.conf"
|
||||
NGINX_DST="/etc/nginx/sites-available/dukang"
|
||||
|
||||
mkdir -p /var/www/certbot/.well-known/acme-challenge
|
||||
mkdir -p /var/log/nginx/dukang
|
||||
|
||||
echo "==> 1. 临时 HTTP 放行 api 域名(便于 http-01)"
|
||||
# 先写入含 api 的 80 server,若证书尚未含 api,443 块可暂用现有证书
|
||||
if [[ -f "$NGINX_SRC" ]]; then
|
||||
install -m 644 "$NGINX_SRC" "$NGINX_DST"
|
||||
ln -sfn "$NGINX_DST" /etc/nginx/sites-enabled/dukang
|
||||
fi
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
echo "==> 2. 扩展证书加入 $DOMAIN_API"
|
||||
certbot certonly --nginx \
|
||||
--cert-name "$CERT_NAME" \
|
||||
--expand \
|
||||
-d user.dukanghaoke.com \
|
||||
-d shop.dukanghaoke.com \
|
||||
-d partner.dukanghaoke.com \
|
||||
-d admin.dukanghaoke.com \
|
||||
-d api.dukanghaoke.com \
|
||||
--email "$EMAIL" \
|
||||
--agree-tos \
|
||||
--non-interactive \
|
||||
--keep-until-expiring
|
||||
|
||||
echo "==> 3. 重载 nginx"
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
echo "==> 4. 健康检查"
|
||||
sleep 1
|
||||
curl -sf -o /dev/null -w "api-https:%{http_code} content-type:%{content_type}\n" \
|
||||
"https://${DOMAIN_API}/api/v1/health"
|
||||
curl -sf "https://${DOMAIN_API}/api/v1/health"; echo
|
||||
echo "==> 完成"
|
||||
@@ -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 已启用"
|
||||
@@ -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)"
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# 杜康好客 H5 三端 — ai-mirror.xyz
|
||||
# API 内部端口 8090;H5 分别 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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# 杜康好客 — CodeUp Webhook 反代(挂到 api.dukanghaoke.com 443/80 server 块内)
|
||||
# setup-webhook.sh 会自动 include 此文件
|
||||
|
||||
location = /hooks/deploy {
|
||||
proxy_pass http://127.0.0.1:8095/deploy;
|
||||
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 X-Gitlab-Token $http_x_gitlab_token;
|
||||
proxy_set_header X-Codeup-Token $http_x_codeup_token;
|
||||
proxy_set_header X-Deploy-Token $http_x_deploy_token;
|
||||
client_max_body_size 1m;
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
# 杜康好客 — Staging(测试)*-test.dukanghaoke.com → 8190–8194
|
||||
# 证书:可与生产共用 user.dukanghaoke.com 证书(SAN 含 *-test),或单独申请
|
||||
# 启用:ln -sf /opt/dukang-staging/deploy/nginx-dukang-staging.conf /etc/nginx/sites-enabled/
|
||||
# nginx -t && systemctl reload nginx
|
||||
|
||||
# HTTP → HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name user-test.dukanghaoke.com shop-test.dukanghaoke.com partner-test.dukanghaoke.com admin-test.dukanghaoke.com api-test.dukanghaoke.com;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain";
|
||||
}
|
||||
|
||||
include /opt/dukang-staging/deploy/nginx-mp-verify-staging.conf;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# api-test.dukanghaoke.com → 8190
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name api-test.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8190;
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
# user-test.dukanghaoke.com → 8191
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name user-test.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang-staging/deploy/nginx-mp-verify-staging.conf;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8190;
|
||||
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 "";
|
||||
}
|
||||
|
||||
# 旧 /user 前缀书签 → 根路径(staging 独立域名,无 /user 子路径)
|
||||
location = /user {
|
||||
return 301 /;
|
||||
}
|
||||
location ^~ /user/ {
|
||||
rewrite ^/user/(.*)$ /$1 permanent;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8191;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# shop-test.dukanghaoke.com → 8192
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name shop-test.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang-staging/deploy/nginx-mp-verify-staging.conf;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8190;
|
||||
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:8192;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# partner-test.dukanghaoke.com → 8193
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name partner-test.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang-staging/deploy/nginx-mp-verify-staging.conf;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8190;
|
||||
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:8193;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# admin-test.dukanghaoke.com → 8194
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name admin-test.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8190;
|
||||
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:8194;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
# 杜康好客 — dukanghaoke.com
|
||||
# HTTP → HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
server_name user.dukanghaoke.com shop.dukanghaoke.com partner.dukanghaoke.com admin.dukanghaoke.com api.dukanghaoke.com;
|
||||
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
default_type "text/plain";
|
||||
}
|
||||
|
||||
include /opt/dukang/deploy/nginx-mp-verify.conf;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# api.dukanghaoke.com → 8090
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name api.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang/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 "";
|
||||
}
|
||||
}
|
||||
|
||||
# user.dukanghaoke.com → 8091
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name user.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang/deploy/nginx-mp-verify.conf;
|
||||
|
||||
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:8091;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# shop.dukanghaoke.com → 8092
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name shop.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang/deploy/nginx-mp-verify.conf;
|
||||
|
||||
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:8092;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# partner.dukanghaoke.com → 8093
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name partner.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
include /opt/dukang/deploy/nginx-mp-verify.conf;
|
||||
|
||||
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:8093;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
# admin.dukanghaoke.com → 8094
|
||||
server {
|
||||
listen 443 ssl;
|
||||
http2 on;
|
||||
server_name admin.dukanghaoke.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/user.dukanghaoke.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/user.dukanghaoke.com/privkey.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
|
||||
client_max_body_size 50m;
|
||||
|
||||
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 Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
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_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
# 证书申请期间 — lingshivip.cn HTTP(api 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;
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
# 杜康好客 — lingshivip.cn 五端 + API
|
||||
# API 8090;H5 8091/8092/8093;admin-web 8094
|
||||
|
||||
map $host $dukang_lingshi_port {
|
||||
user.lingshivip.cn 8091;
|
||||
shop.lingshivip.cn 8092;
|
||||
partner.lingshivip.cn 8093;
|
||||
admin.lingshivip.cn 8094;
|
||||
}
|
||||
|
||||
# HTTP → HTTPS(H5 三端)
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 微信 JS 安全域名校验文件(Staging)
|
||||
# 文件放在 /opt/dukang-staging/public/MP_verify_*.txt
|
||||
location ~ ^/MP_verify_.*\.txt$ {
|
||||
root /opt/dukang-staging/public;
|
||||
default_type text/plain;
|
||||
charset utf-8;
|
||||
access_log off;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# 微信 JS 安全域名校验文件(根目录直出,勿走 SPA)
|
||||
# 文件放在 /opt/dukang/public/MP_verify_*.txt
|
||||
location ~ ^/MP_verify_.*\.txt$ {
|
||||
root /opt/dukang/public;
|
||||
default_type text/plain;
|
||||
charset utf-8;
|
||||
access_log off;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
# 杜康好客 — runxian.top(HTTP)
|
||||
# 三端 H5 统一入口 user.runxian.top/{user,shop,partner}/(微信网页授权单域名)
|
||||
# shop/partner 子域名 301 到 user.runxian.top 路径;webadmin → 8094;dkapi → 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;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
ayPJ4CQqbUcec3jX
|
||||
@@ -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 统一部署完成"
|
||||
@@ -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/
|
||||
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在服务器上执行:拉取后的常规发版(不覆盖 env 文件、默认不 seed)
|
||||
# 通过 DUKANG_DEPLOY_ENV=staging|production 区分同机双栈
|
||||
set -euo pipefail
|
||||
|
||||
DUKANG_DEPLOY_ENV="${DUKANG_DEPLOY_ENV:-production}"
|
||||
case "$DUKANG_DEPLOY_ENV" in
|
||||
staging|stage|test) DUKANG_DEPLOY_ENV=staging ;;
|
||||
production|prod) DUKANG_DEPLOY_ENV=production ;;
|
||||
*)
|
||||
echo "错误: DUKANG_DEPLOY_ENV 须为 staging 或 production,当前=$DUKANG_DEPLOY_ENV" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$DUKANG_DEPLOY_ENV" == "staging" ]]; then
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang-staging}"
|
||||
ENV_FILE_NAME=".env.staging"
|
||||
ECOSYSTEM_FILE="ecosystem.staging.config.cjs"
|
||||
VITE_API_DEFAULT="https://api-test.dukanghaoke.com"
|
||||
API_PORT=8190
|
||||
USER_PORT=8191
|
||||
PORT_GREP='819[0-4]'
|
||||
PM2_API=dukang-stg-api
|
||||
PM2_USER=dukang-stg-h5-user
|
||||
PM2_SHOP=dukang-stg-h5-shop
|
||||
PM2_PARTNER=dukang-stg-h5-partner
|
||||
PM2_ADMIN=dukang-stg-admin-web
|
||||
else
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang}"
|
||||
ENV_FILE_NAME=".env.production"
|
||||
ECOSYSTEM_FILE="ecosystem.config.cjs"
|
||||
VITE_API_DEFAULT="https://api.dukanghaoke.com"
|
||||
API_PORT=8090
|
||||
USER_PORT=8091
|
||||
PORT_GREP='809[0-4]'
|
||||
PM2_API=dukang-api
|
||||
PM2_USER=dukang-h5-user
|
||||
PM2_SHOP=dukang-h5-shop
|
||||
PM2_PARTNER=dukang-h5-partner
|
||||
PM2_ADMIN=dukang-admin-web
|
||||
fi
|
||||
|
||||
DEPLOY_DIR="$APP_ROOT/deploy"
|
||||
export APP_ROOT
|
||||
export APP_ENV="$DUKANG_DEPLOY_ENV"
|
||||
export DUKANG_ENV_FILE="$ENV_FILE_NAME"
|
||||
|
||||
SKIP_BUILD=false
|
||||
SKIP_DB=false
|
||||
RUN_SEED=false
|
||||
DB_PUSH_ACCEPT_DATA_LOSS=false
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
用法: remote-release.sh [选项]
|
||||
|
||||
环境: DUKANG_DEPLOY_ENV=${DUKANG_DEPLOY_ENV} APP_ROOT=${APP_ROOT} env=${ENV_FILE_NAME}
|
||||
|
||||
--skip-build 跳过 pnpm build(仅重启 PM2)
|
||||
--skip-db 跳过 Prisma generate / db push
|
||||
--seed 执行 prisma:seed(默认不执行)
|
||||
--accept-data-loss prisma db push 时允许数据丢失(慎用)
|
||||
-h, --help 显示帮助
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=true ;;
|
||||
--skip-db) SKIP_DB=true ;;
|
||||
--seed) RUN_SEED=true ;;
|
||||
--accept-data-loss) DB_PUSH_ACCEPT_DATA_LOSS=true ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) echo "未知参数: $1" >&2; usage; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
cd "$APP_ROOT"
|
||||
|
||||
echo "==> 发版环境: $DUKANG_DEPLOY_ENV ($APP_ROOT)"
|
||||
|
||||
echo "==> 1. 启用 pnpm"
|
||||
corepack enable 2>/dev/null || true
|
||||
corepack prepare pnpm@11.2.2 --activate 2>/dev/null || true
|
||||
|
||||
echo "==> 2. 安装依赖"
|
||||
# mini-user H5(Taro)构建峰值内存较高
|
||||
export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=8192}"
|
||||
# C 端 H5 静态资源路径:staging 独立域名根路径;生产 nginx 入口为 /user/
|
||||
if [[ "$DUKANG_DEPLOY_ENV" == "staging" ]]; then
|
||||
export TARO_H5_PUBLIC_PATH="${TARO_H5_PUBLIC_PATH:-/}"
|
||||
unset TARO_H5_ROUTER_BASENAME
|
||||
else
|
||||
export TARO_H5_PUBLIC_PATH="${TARO_H5_PUBLIC_PATH:-/user/}"
|
||||
export TARO_H5_ROUTER_BASENAME="${TARO_H5_ROUTER_BASENAME:-/user}"
|
||||
fi
|
||||
# C 端 H5 编译期注入的 API origin(勿落到 localhost)
|
||||
export VITE_API_TARGET="${VITE_API_TARGET:-$VITE_API_DEFAULT}"
|
||||
export VITE_APP_ENV="$DUKANG_DEPLOY_ENV"
|
||||
pnpm approve-builds --all 2>/dev/null || true
|
||||
pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
||||
|
||||
if [[ "$SKIP_DB" == false ]]; then
|
||||
echo "==> 3. 数据库 schema 同步(构建前,读取 $ENV_FILE_NAME)"
|
||||
cd "$APP_ROOT/server/dukang-api"
|
||||
if [[ ! -f "$ENV_FILE_NAME" ]]; then
|
||||
echo "错误: 未找到 server/dukang-api/$ENV_FILE_NAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
node scripts/with-api-env.cjs pnpm prisma:generate
|
||||
if [[ "$DB_PUSH_ACCEPT_DATA_LOSS" == true ]]; then
|
||||
node scripts/with-api-env.cjs npx prisma db push --accept-data-loss
|
||||
else
|
||||
node scripts/with-api-env.cjs npx prisma db push
|
||||
fi
|
||||
if [[ "$RUN_SEED" == true ]]; then
|
||||
echo "==> 执行 seed"
|
||||
node scripts/with-api-env.cjs pnpm prisma:seed
|
||||
fi
|
||||
cd "$APP_ROOT"
|
||||
else
|
||||
echo "==> 3. 跳过数据库"
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_BUILD" == false ]]; then
|
||||
echo "==> 4. 构建(C 端使用 mini-user H5,跳过 h5-user)"
|
||||
pnpm --filter @dukang/shared-types \
|
||||
--filter @dukang/domain \
|
||||
--filter @dukang/api \
|
||||
--filter @dukang/mini-user \
|
||||
--filter @dukang/h5-shop \
|
||||
--filter @dukang/h5-partner \
|
||||
--filter @dukang/admin-web \
|
||||
build
|
||||
# serve 读取 dist/serve.json(相对静态根目录)
|
||||
if [[ -f "$APP_ROOT/apps/mini-user/serve.json" ]]; then
|
||||
cp -f "$APP_ROOT/apps/mini-user/serve.json" "$APP_ROOT/apps/mini-user/dist/serve.json"
|
||||
fi
|
||||
else
|
||||
echo "==> 4. 跳过构建"
|
||||
if [[ -f "$APP_ROOT/apps/mini-user/serve.json" && -d "$APP_ROOT/apps/mini-user/dist" ]]; then
|
||||
cp -f "$APP_ROOT/apps/mini-user/serve.json" "$APP_ROOT/apps/mini-user/dist/serve.json"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "==> 5. 重启 PM2 ($ECOSYSTEM_FILE)"
|
||||
if pm2 describe "$PM2_API" &>/dev/null; then
|
||||
pm2 delete "$PM2_USER" 2>/dev/null || true
|
||||
pm2 start "$DEPLOY_DIR/$ECOSYSTEM_FILE" --only "$PM2_USER"
|
||||
pm2 restart "$PM2_API" "$PM2_SHOP" "$PM2_PARTNER" "$PM2_ADMIN" 2>/dev/null \
|
||||
|| pm2 restart "$PM2_API" "$PM2_SHOP" "$PM2_PARTNER"
|
||||
else
|
||||
pm2 start "$DEPLOY_DIR/$ECOSYSTEM_FILE"
|
||||
fi
|
||||
pm2 save
|
||||
|
||||
echo "==> 6. 健康检查"
|
||||
sleep 2
|
||||
pm2 list
|
||||
ss -tlnp | grep -E "$PORT_GREP" || true
|
||||
curl -sf -o /dev/null -w "mini-user(h5): %{http_code}\n" "http://127.0.0.1:${USER_PORT}/" || echo "mini-user(h5): FAIL"
|
||||
curl -sf -o /dev/null -w "api: %{http_code}\n" "http://127.0.0.1:${API_PORT}/api/v1/health" 2>/dev/null \
|
||||
|| curl -sf -o /dev/null -w "api: %{http_code}\n" "http://127.0.0.1:${API_PORT}/" \
|
||||
|| echo "api: FAIL"
|
||||
|
||||
echo "==> 7. 记录系统版本"
|
||||
cd "$APP_ROOT/server/dukang-api"
|
||||
APP_ROOT="$APP_ROOT" DEPLOY_TRIGGER="${DEPLOY_TRIGGER:-manual}" DUKANG_ENV_FILE="$ENV_FILE_NAME" \
|
||||
node scripts/with-api-env.cjs node scripts/record-system-version.cjs \
|
||||
|| echo "WARN: system_version 写入失败"
|
||||
|
||||
echo "==> 发版完成 ($DUKANG_DEPLOY_ENV)"
|
||||
|
||||
if [[ "$DUKANG_DEPLOY_ENV" == "staging" ]]; then
|
||||
echo "==> 8. 重载 nginx(staging)"
|
||||
if command -v nginx >/dev/null 2>&1 && nginx -t 2>/dev/null; then
|
||||
systemctl reload nginx || echo "WARN: nginx reload failed"
|
||||
else
|
||||
echo "WARN: nginx 未安装或配置校验失败,跳过重载"
|
||||
fi
|
||||
fi
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
# 服务器机器相关(可合并到 .env.production 的参考项)
|
||||
PORT=8090
|
||||
DATABASE_URL="mysql://dukang:Dukang2026!@localhost:3306/dukang_haoke"
|
||||
REDIS_URL="redis://localhost:6379"
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 在服务器上执行:安装 Webhook 自动发版
|
||||
set -euo pipefail
|
||||
|
||||
APP_ROOT="${APP_ROOT:-/opt/dukang-haoke}"
|
||||
DEPLOY_DIR="$APP_ROOT/deploy"
|
||||
ENV_FILE="$DEPLOY_DIR/auto-release.env"
|
||||
LOG_DIR="/var/log/dukang"
|
||||
|
||||
echo "==> 1. 准备目录"
|
||||
mkdir -p "$LOG_DIR" /var/run
|
||||
chmod +x "$DEPLOY_DIR/auto-release.sh" "$DEPLOY_DIR/setup-webhook.sh" 2>/dev/null || true
|
||||
sed -i 's/\r$//' "$DEPLOY_DIR"/*.sh "$DEPLOY_DIR"/*.mjs 2>/dev/null || true
|
||||
|
||||
echo "==> 2. 生成 webhook 密钥(若不存在)"
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
SECRET="$(openssl rand -hex 24)"
|
||||
cp "$DEPLOY_DIR/auto-release.env.example" "$ENV_FILE"
|
||||
sed -i "s/change-me-to-a-long-random-string/$SECRET/" "$ENV_FILE"
|
||||
chmod 600 "$ENV_FILE"
|
||||
echo " 已创建 $ENV_FILE"
|
||||
else
|
||||
echo " 保留已有 $ENV_FILE"
|
||||
fi
|
||||
sed -i 's/\r$//' "$ENV_FILE" 2>/dev/null || true
|
||||
|
||||
echo "==> 3. Nginx webhook 路由"
|
||||
HOOK_CONF="$DEPLOY_DIR/nginx-deploy-webhook.conf"
|
||||
# 勿放在 /etc/nginx/conf.d/(会被 http 上下文直接加载)
|
||||
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)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
|
||||
echo "==> 4. 启动 PM2 webhook"
|
||||
cd "$APP_ROOT"
|
||||
if pm2 describe dukang-deploy-webhook &>/dev/null; then
|
||||
pm2 restart dukang-deploy-webhook
|
||||
else
|
||||
pm2 start "$DEPLOY_DIR/ecosystem.config.cjs" --only dukang-deploy-webhook
|
||||
fi
|
||||
pm2 save
|
||||
|
||||
echo "==> 5. 健康检查"
|
||||
sleep 1
|
||||
curl -sf http://127.0.0.1:8095/health | head -c 200
|
||||
echo
|
||||
|
||||
SECRET="$(grep DEPLOY_WEBHOOK_SECRET "$ENV_FILE" | cut -d= -f2- | tr -d '\"')"
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Webhook 已就绪"
|
||||
echo " URL: https://api.dukanghaoke.com/hooks/deploy"
|
||||
echo " Secret: $SECRET"
|
||||
echo " Branch: dev (refs/heads/dev)"
|
||||
echo " Log: $LOG_DIR/deploy.log"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "CodeUp 配置:仓库 → 设置 → Webhooks → 添加"
|
||||
echo " URL: https://api.dukanghaoke.com/hooks/deploy"
|
||||
echo " Secret Token: (与上方 Secret 相同)"
|
||||
echo " 触发事件: Push events"
|
||||
echo " 分支过滤: dev"
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 同步 API 环境变量到服务器
|
||||
# 用法: deploy/sync-api-env.sh staging|production
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
ENV_FILE="$REPO_ROOT/deploy/deploy.env"
|
||||
|
||||
DEPLOY_HOST=""
|
||||
DEPLOY_USER="root"
|
||||
DEPLOY_PORT="22"
|
||||
DEPLOY_SSH_KEY=""
|
||||
APP_ROOT="/opt/dukang"
|
||||
STAGING_APP_ROOT="/opt/dukang-staging"
|
||||
PROD_APP_ROOT="/opt/dukang"
|
||||
|
||||
TARGET="${1:-}"
|
||||
case "$TARGET" in
|
||||
staging|stage|test)
|
||||
TARGET=staging
|
||||
SRC_ENV="$REPO_ROOT/server/dukang-api/.env.staging"
|
||||
REMOTE_NAME=".env.staging"
|
||||
PM2_NAME=dukang-stg-api
|
||||
HEALTH_PORT=8190
|
||||
;;
|
||||
production|prod)
|
||||
TARGET=production
|
||||
SRC_ENV="$REPO_ROOT/server/dukang-api/.env.production"
|
||||
REMOTE_NAME=".env.production"
|
||||
PM2_NAME=dukang-api
|
||||
HEALTH_PORT=8090
|
||||
;;
|
||||
development|dev)
|
||||
echo "错误: development 仅用于本地开发,请使用 .env,不同步到服务器" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "用法: $0 staging|production" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -f "$ENV_FILE" ]]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
fi
|
||||
|
||||
if [[ "$TARGET" == "staging" ]]; then
|
||||
APP_ROOT="${STAGING_APP_ROOT:-/opt/dukang-staging}"
|
||||
else
|
||||
APP_ROOT="${PROD_APP_ROOT:-/opt/dukang}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SRC_ENV" ]]; then
|
||||
echo "错误: 未找到 $SRC_ENV(可从 ${SRC_ENV}.example 复制)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$DEPLOY_HOST" ]]; then
|
||||
echo "错误: 未配置 DEPLOY_HOST(deploy/deploy.env)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SSH_OPTS=(-o "StrictHostKeyChecking=accept-new" -p "$DEPLOY_PORT")
|
||||
SCP_OPTS=(-o "StrictHostKeyChecking=accept-new" -P "$DEPLOY_PORT")
|
||||
[[ -n "$DEPLOY_SSH_KEY" ]] && SSH_OPTS+=(-i "$DEPLOY_SSH_KEY") && SCP_OPTS+=(-i "$DEPLOY_SSH_KEY")
|
||||
REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}"
|
||||
|
||||
echo "==> 同步 $TARGET 环境到 $REMOTE:$APP_ROOT/server/dukang-api/$REMOTE_NAME"
|
||||
scp "${SCP_OPTS[@]}" "$SRC_ENV" "$REMOTE:$APP_ROOT/server/dukang-api/$REMOTE_NAME"
|
||||
ssh "${SSH_OPTS[@]}" "$REMOTE" \
|
||||
"pm2 restart $PM2_NAME && sleep 2 && curl -sf -o /dev/null -w 'api-health:%{http_code}\n' http://127.0.0.1:${HEALTH_PORT}/api/v1/health"
|
||||
echo "==> 完成"
|
||||
@@ -1,139 +0,0 @@
|
||||
/**
|
||||
* CodeUp / GitLab 兼容的部署 Webhook 接收器
|
||||
* 监听 127.0.0.1:8095,由 Nginx 反代 /hooks/deploy
|
||||
*/
|
||||
import http from 'http';
|
||||
import { spawn } from 'child_process';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function loadEnv() {
|
||||
const envPath = join(__dirname, 'auto-release.env');
|
||||
if (!existsSync(envPath)) return;
|
||||
for (const line of readFileSync(envPath, 'utf8').split('\n')) {
|
||||
const trimmed = line.replace(/\r$/, '').trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||
const eq = trimmed.indexOf('=');
|
||||
if (eq === -1) continue;
|
||||
const key = trimmed.slice(0, eq).trim();
|
||||
let val = trimmed.slice(eq + 1).trim();
|
||||
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
||||
val = val.slice(1, -1);
|
||||
}
|
||||
if (!(key in process.env)) process.env[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnv();
|
||||
|
||||
const PORT = Number(process.env.DEPLOY_WEBHOOK_PORT || 8095);
|
||||
const HOST = process.env.DEPLOY_WEBHOOK_HOST || '127.0.0.1';
|
||||
const SECRET = (process.env.DEPLOY_WEBHOOK_SECRET || '').replace(/\r$/, '').trim();
|
||||
const ALLOWED_REF = process.env.DEPLOY_GIT_REF || 'refs/heads/dev';
|
||||
const APP_ROOT = process.env.APP_ROOT || '/opt/dukang-haoke';
|
||||
const RELEASE_SCRIPT = join(APP_ROOT, 'deploy', 'auto-release.sh');
|
||||
const DEBOUNCE_MS = Number(process.env.DEPLOY_WEBHOOK_DEBOUNCE_MS || 15000);
|
||||
|
||||
let lastTriggerAt = 0;
|
||||
let lastTriggerRef = '';
|
||||
|
||||
function readBody(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks = [];
|
||||
req.on('data', (c) => chunks.push(c));
|
||||
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||||
req.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
function getToken(req) {
|
||||
return (
|
||||
req.headers['x-gitlab-token'] ||
|
||||
req.headers['x-codeup-token'] ||
|
||||
req.headers['x-deploy-token'] ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
function json(res, status, data) {
|
||||
const body = JSON.stringify(data);
|
||||
res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
|
||||
res.end(body);
|
||||
}
|
||||
|
||||
function triggerRelease(trigger) {
|
||||
const now = Date.now();
|
||||
if (now - lastTriggerAt < DEBOUNCE_MS && lastTriggerRef === trigger) {
|
||||
console.log(`[webhook] debounced duplicate trigger ref=${trigger}`);
|
||||
return false;
|
||||
}
|
||||
lastTriggerAt = now;
|
||||
lastTriggerRef = trigger;
|
||||
console.log(`[webhook] trigger deploy ref=${trigger}`);
|
||||
const child = spawn('bash', [RELEASE_SCRIPT], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
env: { ...process.env, DEPLOY_TRIGGER: trigger },
|
||||
});
|
||||
child.unref();
|
||||
return true;
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/health') {
|
||||
return json(res, 200, { ok: true, service: 'dukang-deploy-webhook' });
|
||||
}
|
||||
|
||||
if (req.method !== 'POST' || url.pathname !== '/deploy') {
|
||||
return json(res, 404, { ok: false, message: 'not found' });
|
||||
}
|
||||
|
||||
if (!SECRET) {
|
||||
return json(res, 503, { ok: false, message: 'webhook secret not configured' });
|
||||
}
|
||||
|
||||
const token = getToken(req);
|
||||
if (token !== SECRET) {
|
||||
console.log(`[webhook] rejected invalid token from ${req.headers['x-real-ip'] || req.socket.remoteAddress || 'unknown'}`);
|
||||
return json(res, 403, { ok: false, message: 'invalid token' });
|
||||
}
|
||||
|
||||
let payload = {};
|
||||
try {
|
||||
const raw = await readBody(req);
|
||||
if (raw) payload = JSON.parse(raw);
|
||||
} catch {
|
||||
return json(res, 400, { ok: false, message: 'invalid json body' });
|
||||
}
|
||||
|
||||
const ref = payload.ref || payload.object_attributes?.ref || '';
|
||||
if (ref && ref !== ALLOWED_REF) {
|
||||
console.log(`[webhook] ignored ref=${ref} (allowed=${ALLOWED_REF})`);
|
||||
return json(res, 200, {
|
||||
ok: true,
|
||||
skipped: true,
|
||||
message: `ignored ref: ${ref} (allowed: ${ALLOWED_REF})`,
|
||||
});
|
||||
}
|
||||
|
||||
const source = typeof payload.source === 'string' ? payload.source : '';
|
||||
const trigger = source === 'admin' ? 'admin' : ref || 'manual';
|
||||
const started = triggerRelease(trigger);
|
||||
return json(res, 202, {
|
||||
ok: true,
|
||||
accepted: true,
|
||||
started,
|
||||
message: started ? 'deploy started' : 'deploy debounced (duplicate webhook)',
|
||||
ref: ref || ALLOWED_REF,
|
||||
source: trigger,
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`dukang deploy webhook listening on http://${HOST}:${PORT}`);
|
||||
});
|
||||
Reference in New Issue
Block a user