订单配置

This commit is contained in:
tianmo committed 2026-09-30 11:02:58 +08:00
1 parent e81481623c
commit c7f43e30a9
11 files changed
+492 -19

No files matched your search

+20 -1
View File
@@ -4,13 +4,32 @@
服务器上**不需要安装任何东西**,它复用的就是你已经能用的 SSH。
## 用哪个脚本
| 你的系统 | 脚本 | 配置文件 |
|---|---|---|
| Windows | `deploy.ps1` | `deploy.config.ps1`(照抄 `deploy.config.example.ps1`) |
| macOS / Linux | `deploy.sh` | `deploy.config.sh`(照抄 `deploy.config.example.sh`) |
Mac 上先执行一次 `chmod +x deploy/deploy.sh`,之后用法和 Windows 版完全一样:
```bash
./deploy/deploy.sh api
./deploy/deploy.sh admin
./deploy/deploy.sh
./deploy/deploy.sh api --dry-run # 只打包、只显示会上传哪些文件,不真传
./deploy/deploy.sh api --with-install # 顺带装依赖(只有改了 package.json 才需要)
```
> 第一次用先跑 `--dry-run`,它会打印打包大小和文件清单,核对无误再去掉这个参数。
---
## 一次性配置
### 1. 填配置文件
编辑 `deploy/deploy.config.ps1`,需要改这几项:
编辑 `deploy/deploy.config.ps1`(Mac 上是 `deploy/deploy.config.sh`,字段名多一层下划线,含义完全一样),需要改这几项:
| 配置项 | 填什么 | 怎么查 |
|---|---|---|
+52
View File
@@ -0,0 +1,52 @@
# 复制本文件为 deploy.config.sh 并填写自己的服务器信息。
# deploy.config.sh 已 gitignore,服务器地址不会进仓库。
# ---- SSH ----
DEPLOY_HOST='1.2.3.4' # 服务器 IP 或域名
DEPLOY_PORT=22
DEPLOY_USER='root'
# 服务器上 Node 的 bin 目录(远端构建时会加进 PATH)。
# 宝塔 → 软件商店 → Node 版本管理器里看,例如 /www/server/nodejs/v22.14.0/bin
NODE_BIN='/www/server/nodejs/v22/bin'
# ---- Nginx 反代缓存 ----
# 宝塔在 /www/server/nginx/conf/proxy.conf 里全局开了 proxy_cache cache_one,
# 而 Next.js 给页面的响应头是 Cache-Control: s-maxage=31536000(允许缓存一年)。
# 两者一叠加,新构建传上去也访问不到,而旧 HTML 引用的分包早被删掉,页面会整块消失。
# 部署脚本每次清一次这个目录并 reload。留空则跳过。
NGINX_CACHE_DIR='/www/server/nginx/proxy_cache_dir'
NGINX_BIN='/www/server/nginx/sbin/nginx'
# ---- yelaymaths_api ----
API_LABEL='yelaymaths_api (server API)'
API_LOCAL='yelaymaths_api'
API_REMOTE='/www/wwwroot/yelaymaths/yelaymaths_api'
API_PATHS='src prisma tools package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json tsconfig.build.json nest-cli.json .env.example'
API_EXCLUDES=''
# 部署前先在服务器上删掉的旧目录(留空表示不删)
API_CLEAN=''
API_LOCAL_BUILD='' # 想在本机构建就填 'pnpm build',一般留空
API_INSTALL='pnpm install --frozen-lockfile'
API_BUILD='pnpm build'
# 重启命令:杀掉旧进程。服务器上执行 pm2 list 看真实进程名。
API_RESTART='pm2 restart yelaymaths-api'
# 拉起命令:宝塔不会自动把进程拉回来,所以自己 setsid + nohup 起。留空则只杀不拉。
API_START='setsid nohup node dist/main.js >> /www/wwwlogs/yelaymaths-api.out.log 2>&1 < /dev/null &'
API_HEALTH='http://127.0.0.1:3002/healthz'
# ---- yelaymaths_admin ----
# 注意:NEXT_PUBLIC_* 是构建时写死的,.env.local 必须在服务器上先准备好(脚本不会上传它)。
ADMIN_LABEL='yelaymaths_admin (admin console)'
ADMIN_LOCAL='yelaymaths_admin'
ADMIN_REMOTE='/www/wwwroot/yelaymaths/yelaymaths_admin'
ADMIN_PATHS='src package.json pnpm-lock.yaml tsconfig.json next.config.ts postcss.config.mjs'
ADMIN_EXCLUDES=''
# Next 的预渲染缓存在 .next/cache 里,命中就不读磁盘新文件,所以每次先删掉重建
ADMIN_CLEAN='.next/cache'
ADMIN_LOCAL_BUILD=''
ADMIN_INSTALL='pnpm install --frozen-lockfile'
ADMIN_BUILD='pnpm build'
ADMIN_RESTART='pm2 restart yelaymaths-admin'
ADMIN_START='setsid nohup node node_modules/next/dist/bin/next start -p 3001 >> /www/wwwlogs/yelaymaths-admin.out.log 2>&1 < /dev/null &'
ADMIN_HEALTH='http://127.0.0.1:3001/'
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env bash
#
# Mac / Linux 版一键部署,行为与 deploy.ps1 一致。
#
# ./deploy/deploy.sh api 只部署服务端
# ./deploy/deploy.sh admin 只部署管理端
# ./deploy/deploy.sh 两个都部署
# ./deploy/deploy.sh api --dry-run 只打包、只显示会上传什么,不真传
# ./deploy/deploy.sh api --with-install 顺带装依赖(只有改了 package.json 才需要)
#
# 用白名单打包,.env / node_modules / dist / .next 结构上不可能被传上去。
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(dirname "$script_dir")"
config_file="$script_dir/deploy.config.sh"
target='all'
dry_run=0
with_install=0
# bash 3.2(macOS 自带)在 set -u 下对无参数的 "$@" 会报 unbound variable,所以要加这层保护
for arg in ${@+"$@"}; do
case "$arg" in
api|admin|all) target="$arg" ;;
-n|--dry-run) dry_run=1 ;;
--with-install) with_install=1 ;;
-h|--help) sed -n '2,13p' "$0"; exit 0 ;;
# ${arg} 必须带花括号:紧跟中文时 bash 会把多字节字符合进变量名
*) echo "未知参数:${arg}(可用:api | admin | all | --dry-run | --with-install)" >&2; exit 2 ;;
esac
done
if [ ! -f "$config_file" ]; then
echo 'ERROR: 找不到 deploy.config.sh' >&2
echo '把 deploy/deploy.config.example.sh 复制成 deploy/deploy.config.sh,填好服务器信息再跑。' >&2
exit 1
fi
# shellcheck disable=SC1090
. "$config_file"
for cmd in ssh scp tar; do
command -v "$cmd" >/dev/null 2>&1 || { echo "缺少命令:$cmd" >&2; exit 1; }
done
ssh_target="$DEPLOY_USER@$DEPLOY_HOST"
# 读配置里的某一项,如 val API_LABEL
val() {
local out
eval "out=\${$1:-}"
printf '%s' "$out"
}
deploy_app() {
local key="$1"
local up label local_path remote_path paths excludes clean local_build
local install build restart start health nginx_cache nginx_bin
up="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
label="$(val "${up}_LABEL")"
local_path="$repo_root/$(val "${up}_LOCAL")"
remote_path="$(val "${up}_REMOTE")"
paths="$(val "${up}_PATHS")"
excludes="$(val "${up}_EXCLUDES")"
clean="$(val "${up}_CLEAN")"
local_build="$(val "${up}_LOCAL_BUILD")"
install="$(val "${up}_INSTALL")"
build="$(val "${up}_BUILD")"
restart="$(val "${up}_RESTART")"
start="$(val "${up}_START")"
health="$(val "${up}_HEALTH")"
[ -d "$local_path" ] || { echo "本地目录不存在:$local_path" >&2; exit 1; }
echo ''
echo "=== $label ==="
# ---- 本地构建(默认关,配了 $X_LOCAL_BUILD 才跑)----
if [ -n "$local_build" ]; then
echo "[1/5] Building locally: $local_build"
# 国内拉 Prisma 引擎很慢,指向镜像
( cd "$local_path" && PRISMA_ENGINES_MIRROR='https://registry.npmmirror.com/-/binary/prisma' bash -c "$local_build" )
else
echo '[1/5] Skipping local build'
fi
# ---- 打包:只收白名单里存在的路径 ----
echo "[2/5] Packing $(val "${up}_LOCAL")"
local stamp tar_file remote_tar items
stamp="$(date +%Y%m%d-%H%M%S)"
tar_file="${TMPDIR:-/tmp}/yelay-${key}-${stamp}.tar.gz"
remote_tar="/tmp/$(basename "$tar_file")"
local -a tar_args=()
for p in $excludes; do tar_args+=("--exclude=$p"); done
local -a file_args=()
for p in $paths; do
[ -e "$local_path/$p" ] && file_args+=("$p")
done
[ ${#file_args[@]} -gt 0 ] || { echo "白名单里没有任何存在的路径:$paths" >&2; exit 1; }
( cd "$local_path" && tar -czf "$tar_file" ${tar_args[@]+"${tar_args[@]}"} "${file_args[@]}" )
local size_kb
size_kb=$(( $(wc -c < "$tar_file") / 1024 ))
echo " ${size_kb} KB ($(printf '%s, ' "${file_args[@]}" | sed 's/, $//'))"
if [ "$dry_run" = 1 ]; then
echo ' DryRun: nothing uploaded.'
rm -f "$tar_file"
return 0
fi
echo "[3/5] Uploading to $remote_path"
scp -P "$DEPLOY_PORT" -q "$tar_file" "$ssh_target:$remote_tar"
# ---- 拼远端脚本 ----
local clean_cmd=''
for p in $clean; do clean_cmd="$clean_cmd
rm -rf '$remote_path/$p'"; done
local install_cmd=''
[ "$with_install" = 1 ] && install_cmd="$install"
local start_cmd=''
# 先 sleep 2 让刚被杀的进程彻底放开端口,否则新进程会 EADDRINUSE
[ -n "$start" ] && start_cmd="sleep 2
$start"
# 探活:30 秒还没起来就让部署直接失败,免得「显示成功、线上 502」
local health_cmd=''
if [ -n "$health" ]; then
health_cmd="for i in {1..30}; do curl -sf -o /dev/null '$health' && break; sleep 1; done
curl -sf -o /dev/null '$health' || { echo 'ERROR: service did not come up: $health'; exit 1; }"
fi
nginx_cache="$(val NGINX_CACHE_DIR)"
nginx_bin="$(val NGINX_BIN)"
local nginx_cmd=''
if [ -n "$nginx_cache" ]; then
nginx_cmd="find '$nginx_cache' -mindepth 1 -delete 2>/dev/null || true"
[ -n "$nginx_bin" ] && nginx_cmd="$nginx_cmd
'$nginx_bin' -s reload 2>/dev/null || true"
fi
# 远端脚本保持 ASCII 注释,避免 SSH 传输编码问题
local remote_script
remote_script="$(cat <<REMOTE
set -e
export PATH=${NODE_BIN}:\$PATH
mkdir -p '$remote_path'
cd '$remote_path'
$clean_cmd
# macOS 打的包带 com.apple.provenance 之类的 xattr 头,GNU tar 每条警告一行,刷屏且无用
tar --warning=no-unknown-keyword -xzf '$remote_tar'
rm -f '$remote_tar'
$install_cmd
$build
$restart
$start_cmd
$health_cmd
$nginx_cmd
echo '__DEPLOY_DONE__'
REMOTE
)"
echo '[4/5] Running on server'
ssh -p "$DEPLOY_PORT" "$ssh_target" 'bash -s' <<< "$remote_script"
rm -f "$tar_file"
if [ -n "$restart" ]; then
echo '[5/5] Restarted'
else
echo '[5/5] Done'
echo " NOTE: 去宝塔面板手动重启 $label"
fi
[ -n "$start" ] && echo " Started, health check passed: $health"
[ -n "$nginx_cmd" ] && echo ' Nginx proxy cache cleared and reloaded'
}
started_at=$(date +%s)
case "$target" in
api) deploy_app api ;;
admin) deploy_app admin ;;
all) deploy_app api; deploy_app admin ;;
esac
[ "$dry_run" = 1 ] || echo "
All done in $(( $(date +%s) - started_at ))s"