Appearance
部署管理端 jy-admin
管理端是 Next.js 15,不能当静态站丢给 Nginx。生产形态:Next.js Standalone 预构建包 + systemd 常驻进程 + Nginx 反向代理。
- 应用目录:
/opt/web/jy-admin - 进程监听:
127.0.0.1:5174(不直接暴露公网) - 对外入口:https://admin.jianyi.my(Nginx 终止 TLS 后反代)
这是什么部署方式?
一句话:在虚拟机/裸机上跑一个预构建好的 Node 服务,由 systemd 保活,由 Nginx 做 HTTPS 和反代。
也叫:
- Standalone 部署(Next.js 官方
output: 'standalone'产物) - 传统 VPS 部署(相对 Vercel / Serverless 而言)
- 反代式 Node 部署(Nginx 在前,Node 只监听本机)
和 jy-site 的本质区别
| jy-site(公开博客) | jy-admin(管理端) | |
|---|---|---|
| 框架 | SvelteKit + adapter-static | Next.js 15 App Router |
| 运行时 | 无(纯 HTML/CSS/JS) | 必须有 Node |
| 数据 | 构建时从 MySQL 烘焙进静态文件 | 运行时连 MySQL 读写 |
| 部署物 | build/ 目录 | standalone/ 目录(含 server.js) |
| 对外服务 | Nginx 直接 root 静态目录 | Nginx proxy_pass 到 Node |
jy-admin 用了 Server Actions、API Routes(登录、触发构建)、iron-session(服务端 session),这些都需要请求到达 Node 进程才能执行,所以不能像 jy-site 那样 pnpm build 后丢一堆 HTML 就完事。
完整链路(从改代码到用户访问)
text
【本机 / CI 构建】
jy-admin 源码
↓ pnpm build(output: 'standalone')
.next/standalone/ ← 只含运行所需的最小 node_modules
↓ 复制 static、public;排除 .env
jy-admin-standalone.tgz
↓ scp → 服务器 /tmp
【服务器替换】
systemctl stop jy-admin
备份 /opt/web/jy-admin → jy-admin.bak.<时间戳>
解压新包到 /opt/web/jy-admin
保留原 /opt/web/jy-admin/.env(数据库、Token 不进包)
systemctl start jy-admin
【用户访问】
浏览器 → https://admin.jianyi.my
↓ Nginx(证书、HTTP/2、反代)
↓ 127.0.0.1:5174
node server.js(systemd 托管)
↓ 读 .env,连 MySQL,执行 Server Action / API
返回 HTML / JSONStandalone 是什么?
next.config.ts 里设置了 output: 'standalone'。pnpm build 后 Next.js 会:
- 分析服务端代码实际
import了哪些依赖(文件追踪 / file tracing) - 把
server.js、.next/server、以及用到的node_modules子集打进.next/standalone/ - 产物体积远小于把整个项目的
node_modules拷上服务器
启动时执行的是 standalone 根目录的 server.js,它相当于精简版 next start,不需要在服务器上再跑 pnpm install 或 pnpm build。
bash
# 本机构建后还需手动补齐(standalone 默认不包含这两块)
cp -R .next/static .next/standalone/.next/static
cp -R public .next/standalone/public三层各自干什么
| 层 | 组件 | 职责 |
|---|---|---|
| 边缘 | Nginx + Let's Encrypt | 域名、HTTPS、把 443 转到本机 5174 |
| 应用 | node server.js | SSR、Server Actions、Session、调 GitHub API |
| 进程管理 | systemd | 开机自启、崩溃重启、日志、环境变量注入 |
Node 只绑定 127.0.0.1,公网不能直接访问 5174,减少暴露面;证书和域名统一由 Nginx 处理。
环境变量为什么不打进包?
.env 含数据库密码、SESSION_SECRET、GITHUB_TOKEN,属于环境机密,应:
- 本机构建时从 standalone 包里排除
- 服务器上单独维护
/opt/web/jy-admin/.env - 通过 systemd 的
EnvironmentFile=注入进程
改 .env 只需 systemctl restart jy-admin,不必重新构建前端包。
与其他部署方式对比
下面把 当前 jy-admin 方案 和常见替代方案放在一起对比,便于选型或迁移。
总览对比表
| 方式 | 是否需要 Node 常驻 | 服务器上要装什么 | 构建在哪做 | 典型复杂度 | 适合场景 |
|---|---|---|---|---|---|
| Standalone + systemd + Nginx(当前) | 是 | Node + systemd + Nginx | 本机或 CI 预构建 | 中 | 自有 VPS、要完全掌控、已有 Nginx |
静态导出 output: 'export' | 否 | 仅 Nginx | 构建机 | 低 | 纯静态站(jy-admin 不适用) |
服务器上 git pull + pnpm build + next start | 是 | Node + 完整 dev 依赖 + 源码 | 服务器 | 中高 | 小项目、服务器配置够、不想传包 |
PM2 托管 next start | 是 | Node + PM2 + 完整或 standalone 包 | 本机或服务器 | 中 | 习惯 PM2、要多进程/集群 |
| Docker 单容器 | 是 | Docker + 镜像 | 镜像构建时 | 中高 | 要环境隔离、可复现镜像 |
| Docker Compose(admin + db + nginx) | 是 | Docker Compose | 镜像构建时 | 高 | 多服务编排、一键起全套 |
| Vercel / 同类 Serverless | 按需 | 无(平台托管) | 平台构建 | 低(接入快) | Next 官方推荐、不想管服务器 |
| 平台即服务(Railway / Render 等) | 是 | 平台托管容器/进程 | 连 Git 自动构建 | 低~中 | 不想自管 VPS 但仍要 Node |
分项对比表
| 维度 | Standalone + systemd(当前) | 服务器现场构建 | PM2 | Docker | Vercel |
|---|---|---|---|---|---|
| 部署速度 | 快(只传 ~20MB 包) | 慢(服务器装依赖+编译) | 取决于是否 standalone | 镜像拉取/构建 | 平台自动 |
| 服务器内存占用 | 较低(最小 node_modules) | 高(完整 node_modules + 构建缓存) | 同左或 standalone | 镜像层额外占用 | 无自管成本 |
| 环境一致性 | 构建机决定运行时依赖 | 易受服务器 Node/OS 影响 | 同左 | 镜像锁定,最好 | 平台锁定 |
| 回滚 | 备份目录切换 + restart | git checkout + 重构建 | 同 standalone | 换镜像 tag | 平台版本回退 |
| 日志 | journalctl -u jy-admin | 自行配置 | pm2 logs | docker logs | 平台控制台 |
| 开机自启 | systemctl enable | 需自己写 unit 或 PM2 | pm2 startup | restart: always | 平台负责 |
| HTTPS | Nginx + certbot(自管) | 同左 | 同左 | 可 Nginx 或 Traefik | 平台自带 |
| Secrets | 服务器 .env + systemd | 服务器 .env | 同左 | env_file / secrets | 平台环境变量 |
| 与 jy-site 同机部署 | 很自然(共用 Nginx/MySQL) | 同左 | 同左 | 需 Compose 设计网络 | 管理端和静态站分离托管 |
| CentOS 7 / 老 glibc | 已验证(非官方 Node 22) | 同左 | 同左 | 基础镜像可选旧 glibc | 不涉及 |
为什么当前没选其他方案?
| 方案 | 未采用的原因(本项目) |
|---|---|
| 静态导出 | jy-admin 有 Server Actions、登录 Session、触发 GitHub API,无法静态化 |
服务器现场 pnpm build | CentOS 7 上编译慢、占内存;构建依赖和运行时混在同一台机,不利于回滚 |
| PM2 | 功能与 systemd 重叠;Linux 服务器已有 systemd,不必多一层 |
| Docker | 测试机资源有限,当前 Nginx + MySQL + 两个静态站 + admin 已够用;后续可迁 |
| Vercel | 管理端需连自建 MySQL、触发内网/私有部署链,和现有 154.89.148.95 架构不一致 |
| 把 admin 也塞进 GitHub Actions 自动部署 | 尚未做;目前是手动 scp 包,公开站才有 Actions workflow |
什么时候值得换方案?
| 若出现… | 可考虑… |
|---|---|
| 频繁发版、多人协作 | 为 jy-admin 增加 GitHub Actions,自动 build + scp + restart |
| 要 dev/staging/prod 环境一致 | Docker 镜像 + Compose |
| 不想维护 Nginx/证书 | Vercel 托管 admin(需解决 MySQL 连通,如 PlanetScale / 隧道) |
| 单机多 Node 应用、要进程监控面板 | PM2 或 Docker + 监控 |
| 公开站和管理端都要容器化 | 参考 handbook 里的 Docker 方案章节(模板性质) |
和本仓库其他站点的部署对照
| 站点 | 部署模式 | 目录 | 有无 Node 进程 |
|---|---|---|---|
| jy-site | 静态预渲染 + Nginx root | /opt/web/jysite | 无 |
| jy-admin | Standalone + systemd + Nginx 反代 | /opt/web/jy-admin | 有(:5174) |
| jy-docs | VitePress 静态 + Nginx root | /opt/web/doc.jysite | 无 |
三者在同一台测试机上共用 Nginx 和 MySQL,只有管理端多一个 systemd 管理的 Node 进程。
为什么不能直接装官网 Node 22
CentOS 7 的 glibc 是 2.17。官网 node-v22-linux-x64 会报:
text
GLIBC_2.27 not found
GLIBCXX_3.4.21 not found使用 unofficial-builds 的 glibc-217 包:
text
https://unofficial-builds.nodejs.org/download/release/v22.22.1/node-v22.22.1-linux-x64-glibc-217.tar.gz装到 /usr/local/node-v22,并把 node / npm 链到 /usr/local/bin。
构建
jy-admin/next.config.ts 已设 output: 'standalone'。
bash
cd jy-admin
NODE_ENV=production pnpm build
cp -R .next/static .next/standalone/.next/static
cp -R public .next/standalone/public不要把本机 .env 打进包。服务器上单独写生产环境变量:同机 MySQL 用 127.0.0.1,用户 blog。
systemd
管理端进程由 systemd 托管,不是手动 nohup node 或 PM2。
| 项目 | 值 |
|---|---|
| 应用目录 | /opt/web/jy-admin |
| unit 文件 | /etc/systemd/system/jy-admin.service |
| 环境变量 | /opt/web/jy-admin/.env |
| 启动命令 | /usr/local/bin/node server.js |
| 监听 | 127.0.0.1:5174 |
| 对外域名 | https://admin.jianyi.my(Nginx 反代) |
unit 文件
/etc/systemd/system/jy-admin.service:
ini
[Unit]
Description=jy-admin Next.js
After=network.target mysqld.service
[Service]
Type=simple
WorkingDirectory=/opt/web/jy-admin
Environment=NODE_ENV=production
Environment=PORT=5174
Environment=HOSTNAME=127.0.0.1
EnvironmentFile=/opt/web/jy-admin/.env
ExecStart=/usr/local/bin/node server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target各段含义:
| 段 / 字段 | 作用 |
|---|---|
[Unit] After=... | 等网络和 MySQL 就绪后再启动 |
WorkingDirectory | 在 /opt/web/jy-admin 下运行,server.js 才能找到 .next 等文件 |
Environment | 写死在 unit 里的变量(生产模式、端口) |
EnvironmentFile | 从 .env 注入 DATABASE_*、SESSION_SECRET、GITHUB_* 等 |
ExecStart | systemd 实际执行的启动命令 |
Restart=on-failure | 进程异常退出时,5 秒后自动重试 |
WantedBy=multi-user.target | enable 后随正常开机启动 |
首次启用:
bash
systemctl daemon-reload # 新建或修改 unit 文件后执行
systemctl enable --now jy-admin # 立刻启动 + 设为开机自启为什么 systemctl 能查看和控制?
text
你输入 systemctl / journalctl
↓
与 systemd(PID 1)通过 D-Bus / 本地 socket 通信
↓
systemd 读取 unit 文件:/etc/systemd/system/jy-admin.service
↓
按 [Service] 里的 ExecStart 启动进程,并记录 stdout/stderrsystemctl status jy-admin:读 unit 定义 + 当前进程状态(active/inactive/failed、PID、最近日志)。systemctl restart jy-admin:先停旧进程,再按ExecStart拉起新进程。systemctl enable jy-admin:在/etc/systemd/system/multi-user.target.wants/下创建符号链接,开机进入多用户模式时自动启动。journalctl -u jy-admin:按 unit 名过滤 journald 日志;Node 的 stdout/stderr 默认被 systemd 接管。
常用命令
查看状态
bash
systemctl status jy-admin # 状态、PID、最近几行日志
systemctl is-active jy-admin # 只输出 active / inactive / failed
systemctl is-enabled jy-admin # 是否开机自启:enabled / disabled
systemctl show jy-admin # 全部属性(WorkingDirectory、MainPID 等)启停与重启
bash
systemctl start jy-admin # 启动
systemctl stop jy-admin # 停止(部署新包前常用)
systemctl restart jy-admin # 重启(改 .env 后常用)开机自启
bash
systemctl enable jy-admin # 设为开机自启
systemctl disable jy-admin # 取消开机自启日志
bash
journalctl -u jy-admin # 全部日志
journalctl -u jy-admin -n 80 --no-pager # 最近 80 行
journalctl -u jy-admin -f # 实时跟踪
journalctl -u jy-admin --since today # 今天的日志修改配置后的区别
| 改了什么 | 需要执行的命令 |
|---|---|
/opt/web/jy-admin/.env | systemctl restart jy-admin |
/etc/systemd/system/jy-admin.service | systemctl daemon-reload && systemctl restart jy-admin |
| 应用代码(重新部署 standalone 包) | systemctl stop jy-admin → 替换目录 → systemctl start jy-admin |
只改 .env 不需要 daemon-reload。
与 Nginx 的分工
text
用户 → https://admin.jianyi.my
↓ Nginx(/etc/nginx/conf.d/admin.jianyi.my.conf)
↓ 反代到 127.0.0.1:5174
↓ jy-admin(systemd 管理的 node server.js)- systemd:管 Node 进程生死、环境变量、日志、开机自启。
- Nginx:管 HTTPS、域名、把外网流量转到本机 5174。
查「进程在不在」用 systemctl;查「外网能不能访问」还要看 Nginx 和证书。
日常维护速查
| 目的 | 命令 |
|---|---|
| 看是否在跑 | systemctl status jy-admin |
改完 .env 生效 | systemctl restart jy-admin |
| 看报错 | journalctl -u jy-admin -n 80 --no-pager |
| 部署新包前停服务 | systemctl stop jy-admin |
| 部署后启动 | systemctl start jy-admin |
| 改 unit 文件后 | systemctl daemon-reload && systemctl restart jy-admin |
远程示例:
bash
ssh jy-test 'systemctl status jy-admin'
ssh jy-test 'journalctl -u jy-admin -n 50 --no-pager'
ssh jy-test 'systemctl restart jy-admin'旧版本备份目录示例:/opt/web/jy-admin.bak.20260814-100906(每次部署前 cp -a 整目录)。
Nginx + 证书
配置放在 /etc/nginx/conf.d/admin.jianyi.my.conf,先只听 80,再申请证书:
bash
certbot --nginx -d admin.jianyi.my --redirect证书目录:/etc/letsencrypt/live/admin.jianyi.my/。续期:certbot renew(机器上已有 certbot)。
DNS 必须先把 admin.jianyi.my A 记录指到 154.89.148.95。本机如果开了 Clash Fake-IP,dig 可能看到 198.18.0.x,以服务器上的 getent hosts 为准。
登录
SQL dump 的密码哈希是假的。部署时已为 admin 写入真实 bcrypt。账号密码见这次对话,不要写进公开文档或 Git。
生产环境 NODE_ENV=production 时 session cookie 为 secure,必须走 HTTPS,否则登录态种不上。
线上触发构建
管理端顶栏「构建并部署」会用 GitHub API 触发 jy-site 仓库的 deploy-jy-site.yml。本地 .env 有 GITHUB_* 还不够,服务器 /opt/web/jy-admin/.env 也必须有这两行,然后重启:
bash
# 追加到 /opt/web/jy-admin/.env(不要提交到 Git)
GITHUB_REPOSITORY=jianyi241/jy-site
GITHUB_TOKEN=<与本地管理端相同的 PAT>
systemctl restart jy-adminToken 需要能对 jianyi241/jy-site 发 repository_dispatch:fine-grained 选 Contents: Read and write,或 classic 选 repo。
改完后打开 https://admin.jianyi.my/settings ,「GitHub Token」应为「已配置」。再点顶栏「构建并部署」即可。只改 .env 不用重新构建管理端,执行 systemctl restart jy-admin 即可。