Maitu Cloud Official Website

Baota Panel Deployment Manual (Non-Docker)

Last updated: 2026-08-16·25 min read

适用版本:v1.1.0(2026-08-15)· 部署包:mai-2-dingxin-v1.1.0.tar.gz

适用:你有一台装了宝塔面板的 Linux 服务器,想用宝塔的「Node.js 版本管理器」直接跑本系统(不使用 Docker)。
底层 = 起一个 Next.js 进程(单副本)+ 宝塔 Nginx 反代 + Let's Encrypt SSL。
数据库 = PostgreSQL(集中式,应用通过 DATABASE_URL 连接)。本手册第 0 步先让你准备好一个 Postgres 实例。
⚠️ 单副本硬约束:应用的「读缓存 + 登录限流 + 写锁」在进程内存,多实例并发会读旧数据/丢更新,故实例数保持 1(禁多开 / 负载均衡)。


0. 先准备两样东西

  • 部署包mai-2-dingxin-v1.1.0.tar.gz(见第 1 步;不含 node_modules/.next/.env

  • 一个 PostgreSQL 实例(三选一):

  • 宝塔「数据库」里建一个 PostgreSQL 库(填库名、用户名、强口令);

  • 服务器用 Docker 起一个 postgres:18-alpine 并挂卷(数据基线由 PG 18 生成,低于 18 无法恢复);

  • 云厂商托管 RDS(推荐限制来源 IP,只允许本机访问 5432)。 拿到连接串,形如:postgresql://用户名:强口令@地址:5432/库名

    应用首次启动会自动建表,并用 .envADMIN_USER/ADMIN_PASS 种子化初始管理员(建号后即失效)。站点内容可在后台自行搭建;若部署包含 _db_backup/ 数据基线,也可用 pg_restore -Fc --clean --if-exists -d 库名 _db_backup/mai_2_dingxin_20260815.dump 直接恢复已有内容。

1. 准备部署包(推荐用现成包,也可自行打包)

推荐:直接使用官方部署包 mai-2-dingxin-v1.1.0.tar.gz(已含源码 / 文档 / data/uploads / _db_backup 数据基线),跳到第 2 步上传即可。

自行打包(仅当你改了源码、需要从源码重打时):

tar --exclude=node_modules --exclude=.next --exclude=.git \
    --exclude=.env --exclude=backups --exclude=.workbuddy \
    -czf mai-2-dingxin-v1.1.0.tar.gz -C "项目根目录" .

2. 上传解压到服务器

  1. 宝塔 → 文件 → 进 /www/wwwroot/ → 新建 mai-2-dingxin

  2. 上传 mai-2-dingxin-v1.1.0.tar.gz → 右键解压到当前目录

目录应含:src/ public/ .env.example package.json package-lock.json(不含 node_modules/.next,下面会装会编)

运行时会自动生成 data/uploads(图片)与 data/backups(备份),需保证该目录可写(见第 7 步)。

3. 生成 .env(安全关键,Next.js 会自动读取)

  1. 复制 .env.example → 重命名 .env

  2. 编辑 .env,改成真实值(注意加了 HOSTNAME=127.0.0.1,让 3000 只监听本机):

ADMIN_JWT_SECRET=在此填 openssl rand -base64 48 的输出
SECRET_MASTER_KEY=再生成一个随机串填这里(库内 LLM API Key 信封加密主密钥,与备份分开保管)
ADMIN_PASS=在此填强密码
ADMIN_USER=admin
PORT=3000
NODE_ENV=production
HOSTNAME=127.0.0.1

# ===== PostgreSQL 连接串(必填,指向第 0 步准备的实例)=====
DATABASE_URL=postgresql://用户名:强口令@地址:5432/库名

# 可选:站点正式域名(canonical/sitemap/og 绝对地址用)
# 留空则改用后台「基础信息 → 网站域名」字段(推荐,实时生效);两者都不设会回退 localhost
NEXT_PUBLIC_SITE_URL=
# 可选:前台边缘缓存调优(详见 .env.example「前台边缘缓存」段)
# REVALIDATE_SECONDS=3600
# REVALIDATE_STALE=86400
# 想让「后台保存即整站刷新」:DNS 走 Cloudflare 并设 Cache Rule Cache Everything,
#   再填下面三项(最省事);或在宝塔 Nginx 里加 proxy_cache(参考 deploy/nginx-cache.conf)。
# CDN_PROVIDER=cloudflare
# CLOUDFLARE_ZONE_ID=
# CLOUDFLARE_API_TOKEN=

不强填 ADMIN_JWT_SECRET 会用代码弱默认值,后台 token 可被伪造 —— 生产必须填。
不强填 SECRET_MASTER_KEY 会退化为复用 ADMIN_JWT_SECRET 或开发兜底值,库内 API Key 等同弱加密并告警 —— 生产必须填。
.env 是唯一必要来源next start 和备份脚本 backup-db.mjs 都只读这个 .env 文件。宝塔「项目环境变量」只是可选的双保险——但备份脚本不读宝塔 UI 的环境变量,所以 DATABASE_URL 必须写进 .env,否则备份会静默失效(详见「备份」一节)。
🌐 站点域名(SEO 关键):本系统 canonical / sitemap.xml / og:image 的绝对地址都依赖站点域名。两种方式二选一:

  • 推荐(实时、零停机):部署后登录后台 → 「网站管理 → 基础信息」填**「网站域名」**并保存,立即生效,无需重启。

  • 备选(环境变量):在下方 .envNEXT_PUBLIC_SITE_URL=https://你的域名.com,然后重启 Node 项目即可(仅服务端读取,不需重新 build);此变量优先级高于后台字段。

  • 两者都不设会回退 http://localhost:3000,生产环境 SEO 链接全部错误,务必避免。

4. 装 Node 22(宝塔软件商店)

  1. 宝塔 → 软件商店 → 搜索安装 Node.js 版本管理器(新版同时内含 PM2)

  2. 打开它 → 版本管理 → 安装/确认 Node v22(与 package.jsonengines.node>=22 对应)。

    若服务器已自带(常见路径 /www/server/nodejs/v22.23.1/),直接用它、第 6 步选此版本即可,无需重复安装。

  3. 安装后记得在「版本」里确认 22 已就绪。

5. 装依赖 + 构建(宝塔「终端」或 SSH,进入项目目录)

# 宝塔终端默认 PATH 无 Node 22,先加上(否则 npm: command not found)
export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
node -v          # 确认 v22.23.1
cd /www/wwwroot/mai-2-dingxin
npm ci           # ⚠️ 必须完整安装(不要加 --omit=dev)
npm run build    # 看到 Compiled successfully 即成功

宝塔不会自动安装/编译,这两步必须手动跑。

6. 添加 Node 项目并启动

  1. 宝塔 → Node.js 版本管理器 → 项目 → 添加项目:

  2. 项目名称:mai-2-dingxin

  3. 项目目录:选 /www/wwwroot/mai-2-dingxin

  4. 启动命令:npm run start(即 next start

  5. 端口:3000

  6. 运行 Node 版本:选 v22

  7. 运行用户:默认 www(与 Nginx 一致即可)

  8. 实例数保持 1(不要开多开 / 负载均衡,原因见顶部「单副本硬约束」)

  9. 域名(可选但推荐):填入你的域名,宝塔会自动建好反代站点,第 8 步可直接在它上面挂 SSL

  10. 环境变量(关键!)ADMIN_JWT_SECRET=强随机串、SECRET_MASTER_KEY=另一串强随机串、ADMIN_USER=admin、ADMIN_PASS=强密码、NODE_ENV=production、PORT=3000、DATABASE_URL=第 0 步的 PostgreSQL 连接串

  11. 点「提交」后点「启动」,看日志确认输出 Ready、无报错(全新库会自动建表 + 种子化管理员)。

    若日志卡在「端口占用」:lsof -i:3000 查占用进程,kill 掉或改 PORT 与反代端口。
    若日志报连不上数据库:检查 DATABASE_URL 是否正确、Postgres 实例是否可达、5432 是否对应用服务器开放。

备选启动方式:launcher 托管模式

默认 npm run start(直跑 next start)最稳。若要用项目自带的 scripts/launcher.mjs 启动器(内置看门狗 + 受控重启),按下面改两处:

  • 启动命令改为:node scripts/launcher.mjs

  • 环境变量额外加SUPERVISED=1

SUPERVISED=1 让 launcher 进入托管模式——放弃自身二级监管、直跑 next,把重启/存活完全交给宝塔,停止项目时端口干净释放(不留孤儿进程占 3000)。务必加上,否则 launcher 走裸机模式(detached + 3001 控制端口),被宝塔停服时 next 可能逃逸成孤儿占端口。

7. 赋权 data 目录(必做,否则上传写不进)

Node 进程(默认 www 用户)要对 data/ 可写(存上传图片与备份):

chown -R www:www /www/wwwroot/mai-2-dingxin/data
chmod -R 755 /www/wwwroot/mai-2-dingxin/data

若启动/访问仍报 EACCES 写库错误:在进程日志里看实际运行用户,把 data 改为该用户即可。
赋权后重启 Node 项目(宝塔里点「重启」)。

8. 域名 + HTTPS 反代

若第 6 步已填域名(推荐):宝塔已自动生成反代站点。

  1. 宝塔 → 网站 → 找到该域名站点 → SSL → Let's Encrypt → 勾选域名 → 申请并部署 → 开启「强制 HTTPS」。

若第 6 步没填域名(手动反代)

  1. 宝塔 → 网站 → 添加站点(填域名,FTP/数据库不创建)

  2. 该站点 → 反向代理 → 目标 URL:http://127.0.0.1:3000 → 提交

  3. 同上步骤开 SSL + 强制 HTTPS。

因为第 3 步设了 HOSTNAME=127.0.0.1,3000 只在本机,公网只会走 80/443。宝塔「安全」里只放行 22/80/443,不要对外暴露 3000,也不要对外暴露 PostgreSQL 的 5432。

9. 验证与收尾

  • 前台 https://域名/、健康检查 https://域名/api/health(返回 200)

  • SEO 产出检查https://域名/robots.txt 返回 200 且含 Sitemap:https://域名/sitemap.xml 返回 200 且 <loc> 里的域名正确(不是 localhost)

  • 设置站点域名(canonical/sitemap/og 才能用正式域名):登录后台 → 「网站管理 → 基础信息」填**「网站域名」**并保存,立即生效、无需重启(详见第 3 步说明)

  • 后台 https://域名/admin 用第 3 步 .envADMIN_USER/ADMIN_PASS 登录(全新库直接生效,不是"原实例密码"),立即改强密码

  • 上线前确认:单实例、3000 仅本地、5432 不暴露、已赋权、已备份、站点域名已设

9.5 重置管理员密码(忘了密码 / 想改用 .envADMIN_PASS

库里已有用户时,.envADMIN_PASS 不生效。要强制用 .env 新密码,须清空库里的用户、让服务重启时重新种子:

# 1) 宝塔 Node.js 管理器里「停止」项目
# 2) 服务器执行(需 psql 客户端,连接第 0 步的库):
psql "$DATABASE_URL" -c "DELETE FROM data WHERE key='users.json';"
# 3) 回到宝塔「启动」项目 → 日志会打印全新库首次启动... 用 .env 的 ADMIN_USER/ADMIN_PASS 登录

登录后立刻在后台改强密码.envADMIN_PASS 只是建号用,之后不再参与校验)。
若没有 psql 客户端:在宝塔「数据库」管理界面直接执行上面那条 DELETE FROM data WHERE key='users.json'; 亦可。


排错(只对真会踩的)

现象

原因 / 解决

首页 500 / 改内容刷新没了

全新 PG 库只种子化管理员、不自动生成站点内容;先登录后台搭建内容。另确认 DATABASE_URL 正确、库已建

启动报连不上数据库 / ECONNREFUSED

DATABASE_URL 错或 Postgres 实例不可达;检查地址/口令/5432 是否对应用开放

启动报 EACCES / 写不进上传

data/ 运行用户无写权(第 7 步);看日志确认实际用户

启动报 Cannot find module

没跑 npm ci 完整安装(第 5 步)

访问 502

Node 项目没启动或端口不对(第 6 步 ReadyPORT 与反代一致?)

改了源码没生效

必须重新 npm run build 再重启 Node 项目,仅重启无效

登录被限流 429

登录 15 分钟内最多 10 次/IP,超限等 15 分钟或重启清空(单实例有效)


备份(必做,否则数据无保障)

cd /www/wwwroot/mai-2-dingxin
node scripts/backup-db.mjs      # pg_dump 导出库 + 打包 uploads → data/backups/

两个前提(缺一都会导致备份静默失效,务必核对):

  1. DATABASE_URL 必须写在项目根 .env 文件里——备份脚本只读 .env / .env.local 文件和 shell 环境变量,不读宝塔面板 UI 里填的环境变量。若只在宝塔「Node 项目」环境变量里填了 DATABASE_URL、没写进 .env,脚本会输出 SKIP: .../data/site.db not found (亦未配置 DATABASE_URL)等于没备份

  2. pg_dump 必须能被找到——安装 postgresql-client,或设 PG_BIN_DIR=/usr/lib/postgresql/<版本>/bin。找不到时脚本输出 WARN: L1 pg_dump 不可用

定时备份(crontab,每天凌晨 4 点):

crontab -e
# 0 4 * * * cd /www/wwwroot/mai-2-dingxin && /www/server/nodejs/v22.23.1/bin/node scripts/backup-db.mjs >> /var/log/site-backup.log 2>&1

cron 的环境常不含 node,建议用宝塔 Node 的完整路径(/www/server/nodejs/<版本>/bin/node,第 4 步确认过的版本);若你已把 node 放进系统 PATH 也可直接用 node

验证备份成功:手动跑一次,日志应出现 L1 OKL2 OKBACKUP DONE;出现 ERROR: 数据库备份失败...WARN: L1 pg_dump 不可用 就是上面两个前提没满足(脚本会以非 0 退出),按上文修正后重跑。


10. 生产环境代码 / 模板更新流程(手动)

适用场景:你改了代码/模板src/templates/**src/lib/theme.tsprimitives.tsx、新增模板、或其它 src/ 改动)后推上生产。
不适用:后台改内容(产品/新闻/案例/设置)实时生效,不需要走这里,也不用 build。只有"代码/模板改动"才需要 build + 重启。

更新前必读(3 条红线)

  1. 模板是编译进 .next/ 的 TSX 组件:改源码必须重新 npm run build;光重启不 build 无效(见排错表)。

  2. 绝对不碰 data/uploadsdata/backups:生产图片与备份在这两处,覆盖 = 丢数据。上传/解压时务必排除 data/

  3. 单副本(重启约 2–5 秒中断):挑低峰期操作;若担心 build 期间有用户正在后台改内容,可先在宝塔「停止」项目再操作。原因见顶部「单副本硬约束」。

详细步骤

步骤 0 — 备份(最先做,防翻车)

export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
cd /www/wwwroot/mai-2-dingxin
node scripts/backup-db.mjs && echo "✓ 数据库备份成功"            # pg_dump 导出 → data/backups(失败会打印 ERROR 并中断)
cp -r .next .next.bak.$(date +%F-%H%M) && echo "✓ 产物备份完成"   # 备份当前 .next(回滚用)

务必看到「✓ 数据库备份成功」再往下走backup-db.mjs 失败时会打印 ERROR: ... 并以非 0 退出,此时 && echo 不会执行(看不到"成功"字样)。若没看到成功回显,说明数据库没备上——先按「备份」一节修好前提(.env 里配 DATABASE_URL + pg_dump 可用)再继续,否则"防翻车"无从谈起。

步骤 1 — 把新代码传上服务器

  • 方式 A(整体同步,推荐):本地按第 1 步重新打包(已排除 node_modules/.next/.git/.env/data),上传到服务器临时目录解压,再用 rsync/宝塔文件管理器把其中 src/public/package*.json*.config.*.env.example移进项目目录跳过 data/.envbash # 例:若解压在 /tmp/new,仅同步代码、排除数据与环境 rsync -a --exclude='data' --exclude='.env' --exclude='node_modules' --exclude='.next' \ /tmp/new/ /www/wwwroot/mai-2-dingxin/

  • 方式 B(只改少量文件):宝塔「文件」管理器直接覆盖 src/ 下对应文件即可,更快。

步骤 2 — 让 Node 22 进 PATH(宝塔终端每次新开都要)

export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
node -v        # 确认 v22.23.1
cd /www/wwwroot/mai-2-dingxin

步骤 3 — 依赖变化才重装(否则跳过)
仅当改了 package.json / package-lock.json(如新模板加了依赖)才需要:

npm ci         # 完整安装,不要加 --omit=dev
复制

没改依赖直接下一步,省时间。

步骤 4 — 重新构建(核心,不可省)

npm run build  # 看到 Compiled successfully 即成功

构建失败常见原因:Cannot find module → 回到步骤 3 装依赖;Node 版本不对 → 确认 v22;磁盘满 → df -h 查。

步骤 5 — 数据迁移(仅 v1.0.x 升级到 v1.1.0 需要)

本版有两处数据变化:messages 表新增 type/meta 字段(重启后自动补全,无需手动);团队成员从 about.json 拆为独立 team.json,需跑一次迁移脚本(否则前台「核心团队」区块为空):

node scripts/migrate-team.mjs   # 幂等:team.json 已有成员则跳过,可反复跑

步骤 6 — 重启 Node 进程加载新产物

  • 宝塔 → Node.js 版本管理器 → 项目 mai-2-dingxin「重启」

  • 或命令行(看你起的方式):pm2 restart mai-2-dingxin
    看日志出现 Ready、无报错即成功。

步骤 7 — 验证

  • 前台 https://域名/ 正常、/api/health 返回 200

  • 后台 https://域名/admin 登录正常(HTTPS!否则 Secure cookie 被弃,见第 8 步)

  • 确认你改过的模板/页面确实生效(清浏览器缓存再看)

失败回滚

  • 代码/构建翻车(新代码有 bug、build 失败):恢复旧产物即可,数据不动。 bash cd /www/wwwroot/mai-2-dingxin rm -rf .next mv .next.bak.<步骤0的时间戳> .next # 宝塔里「重启」项目

  • 数据库需要回滚(误删/误改内容): bash # 备份脚本产出的是纯文本 SQL(pgdump.sql),用 psql 恢复: psql "$DATABASE_URL" -f data/backups/volume/data-<ts>/pgdump.sql # 或 L1 快照:psql "$DATABASE_URL" -f data/backups/snapshots/pgdump.sql.<ts> # 恢复后重启项目,让进程内缓存重新加载

  • 误覆盖 data(极端):从备份恢复 data/uploads 即可(库不在 data/ 里)。

一句话总结

备份 → 传代码(跳过 data/)→ PATH → [npm ci]npm run build[node scripts/migrate-team.mjs 数据迁移] → 重启 → 验证;翻车用 .next.bak 回滚代码,数据用 backup-db.mjs 的 pg_dump 备份回滚。


升级流程(极简版,老手回顾用)

  1. 宝塔「终端」进项目目录:[npm ci] && npm run build

  2. (仅 v1.0.x → v1.1.0)跑一次数据迁移:node scripts/migrate-team.mjs

  3. 宝塔 Node.js 管理器里重启 mai-2-dingxin 项目

  4. 后台改内容实时生效,不用重建;改代码/模板才需上面两步。

  5. 升级前务必node scripts/backup-db.mjs 打快照,并确认输出 L1 OK / BACKUP DONE(若 SKIP 说明 .envDATABASE_URL,先补上,见「备份」一节)。