CT8 上安装 pnpm 并部署 Sub-Store 后端教程
本文介绍如何在 CT8 Hosting 的 Node.js 环境中,通过 SSH 使用 pnpm 部署 Sub-Store 后端。
本文以以下环境为例:
1
2
3
4
5
6
| 系统用户:your_username
Node.js:v22.22.2
pnpm:11.0.9
Sub-Store:v2.36.31
Node.js 端口:39439
后端访问前缀:/my_path
|
最终效果:
1
2
3
4
5
6
7
| https://你的域名/my_path
↓
CT8 Node.js
↓
127.0.0.1:39439
↓
Sub-Store Backend
|
一、准备 CT8 Node.js 环境
首先通过 SSH 登录 CT8。
检查 Node.js:
例如:
检查 npm:
再检查 Corepack:
Corepack 可以管理项目指定的 pnpm 版本。Node.js 官方生态中的 Corepack 会根据项目 package.json 中的 packageManager 字段选择对应的包管理器版本。
二、在 CT8 安装 pnpm
方法一:使用 Corepack
推荐优先尝试:
然后:
如果能够显示版本号,说明 pnpm 已经可以使用。
方法二:安装到用户目录
如果 CT8 不允许 corepack enable 修改系统目录,可以安装到自己的 Home 目录:
1
| npm install --prefix "$HOME/.local" pnpm
|
安装完成后检查:
1
| find "$HOME" -type f -name pnpm 2>/dev/null | head -20
|
例如:
1
| /home/your_username/.local/bin/pnpm
|
将用户目录加入 PATH:
1
| export PATH="$HOME/.local/bin:$PATH"
|
然后:
三、解决 CT8 上 pnpm: command not found
CT8 上可能出现这样的情况:
可以运行,但是:
却提示:
1
| pnpm: command not found
|
这是因为脚本运行时的 PATH 与 SSH 交互式 Shell 不一定完全相同。
可以检查:
如果没有输出,再执行:
1
| find "$HOME" -type f -name pnpm 2>/dev/null | head -20
|
假设得到:
1
| /home/your_username/.local/bin/pnpm
|
那么启动脚本中建议直接使用绝对路径:
1
| /home/your_username/.local/bin/pnpm
|
同时加入:
1
| export PATH="/home/your_username/.local/bin:$PATH"
|
这样最稳定。
四、下载 Sub-Store 后端源码
进入 Home 目录:
克隆官方仓库:
1
| git clone https://github.com/sub-store-org/Sub-Store.git
|
进入 backend:
Sub-Store 官方仓库目前将后端源码放在 backend 目录,并使用 pnpm 管理依赖。
如果已经下载过:
1
2
| cd ~/Sub-Store/backend
git pull
|
五、检查 package.json
执行:
你应该能看到类似:
1
2
3
4
5
6
7
8
9
10
| {
"name": "sub-store",
"version": "2.36.31",
"main": "src/main.js",
"packageManager": "pnpm@11.0.9",
"scripts": {
"serve": "node sub-store.min.js",
"start": "nodemon -w src -w package.json --exec babel-node src/main.js"
}
}
|
这里有两个重要信息:
1
| packageManager: pnpm@11.0.9
|
说明项目期望使用 pnpm 11.0.9。
以及:
实际执行:
1
2
3
4
5
| nodemon
↓
babel-node
↓
src/main.js
|
因此不要直接:
否则可能遇到:
1
| Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/core'
|
因为源码启动依赖项目的 Babel/路径别名配置。
六、安装项目依赖
进入:
安装:
如果项目已经存在 lockfile,并且希望严格按照 lockfile 安装:
1
| pnpm install --frozen-lockfile
|
安装完成后会生成:
七、确认 pnpm 版本
执行:
如果项目要求:
可以通过 Corepack 管理指定版本。
例如:
1
| corepack prepare pnpm@11.0.9 --activate
|
然后:
确认版本。
项目在 package.json 中指定 packageManager 后,可以让 Corepack 根据项目配置使用相应的包管理器版本。
八、确认 CT8 分配的 Node.js 端口
CT8 Node.js 应用通常需要使用平台分配的端口。
本文使用:
因此 Sub-Store 需要监听:
或者:
九、设置 Sub-Store 环境变量
本教程使用:
1
2
3
| export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
|
三个变量的作用:
| 变量 | 作用 |
|---|
PORT | Node.js 服务监听端口 |
SUB_STORE_FRONTEND_BACKEND_PATH | 前端访问后端时使用的路径前缀 |
SUB_STORE_BACKEND_PREFIX | 让后端自身处理该路径前缀 |
这里最容易遗漏的是:
1
| SUB_STORE_BACKEND_PREFIX=/my_path
|
如果只设置:
1
| SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
|
前端访问:
可能得到:
而同时设置:
1
| SUB_STORE_BACKEND_PREFIX=/my_path
|
后端也会使用这个前缀。
十、测试启动 Sub-Store
进入:
执行:
1
2
3
4
5
| export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
pnpm start
|
如果正常,日志应该类似:
1
2
3
4
5
6
7
| Sub-Store -- v2.36.31
[sub-store] INFO: [CORS] allowed origins: * (default:node)
[sub-store] INFO: [BACKEND] body JSON limit: 1mb
[sub-store] INFO: [BACKEND] listening on :::39439
|
看到:
就说明 Node.js 已经成功监听 CT8 分配的端口。
十一、检查环境变量是否生效
可以通过 Sub-Store 启动后的信息确认:
1
| SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
|
以及:
1
| SUB_STORE_BACKEND_PREFIX=/my_path
|
确保没有拼写错误。
十二、测试本地后端
另外打开一个 SSH 窗口。
执行:
1
| curl -i http://127.0.0.1:39439/
|
如果返回 HTTP 响应,说明 Node.js 后端正常。
也可以检查端口:
应该看到类似:
1
| LISTEN ... 0.0.0.0:39439
|
或者:
十三、配置 CT8 网站
假设你的域名是:
1
| https://yourdomain.ct8.pl
|
Node.js 后端实际监听:
那么最终访问:
1
| https://yourdomain.ct8.pl/my_path
|
应该由 CT8 的 Node.js 网站映射到:
如果 CT8 Node.js 网站本身已经负责端口转发,就不需要再额外使用 Nginx。
最终结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 浏览器
│
│ HTTPS
▼
https://yourdomain.ct8.pl/my_path
│
▼
CT8 Node.js
│
▼
39439
│
▼
Sub-Store
|
十四、创建启动脚本
为了避免每次输入环境变量,可以创建:
1
2
| cd ~/Sub-Store/backend
vim start.sh
|
写入:
1
2
3
4
5
6
7
8
9
10
11
12
| #!/bin/bash
export PATH="/home/your_username/.local/bin:$PATH"
export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
cd "$(dirname "$0")" || exit 1
# 使用 pnpm start
exec /home/your_username/.local/bin/pnpm start
|
保存后:
以后:
即可启动。
十五、为什么推荐 pnpm 绝对路径
不要只写:
而推荐:
1
| /home/your_username/.local/bin/pnpm start
|
because CT8 的 cron、后台脚本 and SSH Shell 的 PATH 可能不同。
这样即使:
没有结果,脚本仍然可以找到 pnpm.
十六、让 Sub-Store 后台运行
测试确认正常后,可以使用:
1
| nohup ./start.sh > sub-store.log 2>&1 &
|
查看日志:
查看进程:
1
| ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep
|
查看端口:
十七、增加自动保活
CT8 上可以使用 cron 定时检查。
编辑:
例如每 5 分钟检查一次:
1
| */5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1
|
但是这里有一个重要问题:
如果 start.sh 每次都直接:
就会产生多个 Sub-Store 实例。
所以必须增加防重复启动。
十八、推荐的最终保活脚本
把 start.sh 修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| #!/bin/bash
export PATH="/home/your_username/.local/bin:$PATH"
export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
cd "$(dirname "$0")" || exit 1
# 检查 Sub-Store 主进程
if pgrep -f "/Sub-Store/backend.*babel-node.*src/main.js" >/dev/null 2>&1; then
echo "[Sub-Store] 已经在运行,无需启动。"
exit 0
fi
# 检查 nodemon
if pgrep -f "/Sub-Store/backend.*nodemon.*src" >/dev/null 2>&1; then
echo "[Sub-Store] nodemon 已经在运行,无需启动。"
exit 0
fi
# 检查 39439 端口
if ss -lnt 2>/dev/null | grep -q ':39439 '; then
echo "[Sub-Store] 39439 端口已经被占用,无需启动。"
exit 0
fi
echo "[Sub-Store] 服务未运行,正在启动..."
nohup /home/your_username/.local/bin/pnpm start >> sub-store.log 2>&1 &
echo "[Sub-Store] 启动命令已执行,PID: $!"
|
十九、为什么不要只用 curl 判断
有些情况下:
1
| curl http://127.0.0.1:39439/
|
不一定能准确判断 Node.js 是否已经运行。
例如:
/ 返回非 2xx- 后端要求
/my_path - 服务刚启动还没有完成初始化
- Node.js 已监听端口但 HTTP 尚未完全准备好
这时:
可能返回失败。
如果 cron 频繁调用脚本,就可能导致:
1
2
3
4
| pnpm start
pnpm start
pnpm start
pnpm start
|
最终产生多个 Node.js 实例。
二十、清理重复的 Sub-Store 进程
如果之前误启动了多个实例,可以先查看:
1
| ps -aux | grep "Sub-Store/backend" | grep -v grep
|
如果确认都是重复的 Sub-Store,可以停止:
1
| pkill -f "/Sub-Store/backend.*"
|
二十一、检查 Sub-Store 是否正常运行
查看进程
1
| ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep
|
查看端口
查看日志
1
| tail -100 sub-store.log
|
实时查看:
测试本地接口
1
| curl -i http://127.0.0.1:39439/
|
测试网站
浏览器打开:
二十二、常见问题
问题 1:pnpm: command not found
执行:
1
| find "$HOME" -type f -name pnpm 2>/dev/null | head
|
如果找到:
1
| /home/your_username/.local/bin/pnpm
|
使用:
1
| export PATH="$HOME/.local/bin:$PATH"
|
或者直接使用:
1
| /home/your_username/.local/bin/pnpm
|
问题 2:直接 node src/main.js 报错
例如:
1
| Cannot find package '@/core'
|
不要直接:
使用:
because pnpm start 会通过项目配置启动:
1
2
3
| nodemon
→ babel-node
→ src/main.js
|
问题 3:启动后监听 3000
如果看到:
1
| listen EPERM: operation not permitted :::3000
|
说明项目没有使用你设置的端口,或者 PORT 没有被正确读取。
检查项目源码中实际使用的端口变量:
1
| grep -RniE "3000|PORT|listen\(" src | head -100
|
对于当前 Sub-Store 版本,实际部署时应以项目自身支持的环境变量为准。
官方仓库的开发说明也使用 SUB_STORE_BACKEND_API_PORT 指定开发后端端口,例如:
1
| SUB_STORE_BACKEND_API_PORT=3000 pnpm esbuild:dev
|
因此不同启动脚本可能使用不同的端口变量,不能一概而论。
问题 4:/my_path 返回 404
检查:
1
2
| export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
|
然后重新启动:
同时确认 CT8 Node.js 应用确实将域名请求转发到了:
问题 5:重复运行 start.sh
如果出现多个:
或者多个:
不要继续运行脚本。
先:
1
| ps -aux | grep "Sub-Store/backend" | grep -v grep
|
确认后清理:
1
| pkill -f "/Sub-Store/backend.*"
|
然后只启动一个:
二十三、完整部署流程
以后重新部署 Sub-Store,可以直接按照下面流程操作。
1. 登录 CT8
2. 检查 Node.js
3. 检查 pnpm
如果找不到:
1
| export PATH="$HOME/.local/bin:$PATH"
|
4. 获取源码
1
2
| cd ~
git clone https://github.com/sub-store-org/Sub-Store.git
|
5. 进入 backend
6. 安装依赖
7. 创建启动脚本
内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| #!/bin/bash
export PATH="/home/your_username/.local/bin:$PATH"
export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path
cd "$(dirname "$0")" || exit 1
if pgrep -f "/Sub-Store/backend.*babel-node.*src/main.js" >/dev/null 2>&1; then
echo "[Sub-Store] 已经在运行,无需启动。"
exit 0
fi
if pgrep -f "/Sub-Store/backend.*nodemon.*src" >/dev/null 2>&1; then
echo "[Sub-Store] nodemon 已经在运行,无需启动。"
exit 0
fi
if ss -lnt 2>/dev/null | grep -q ':39439 '; then
echo "[Sub-Store] 39439 端口已经被占用,无需启动。"
exit 0
fi
echo "[Sub-Store] 服务未运行,正在启动..."
nohup /home/your_username/.local/bin/pnpm start >> sub-store.log 2>&1 &
echo "[Sub-Store] 启动命令已执行,PID: $!"
|
8. 添加执行权限
9. 启动
10. 检查
11. 设置 cron
加入:
1
| */5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1
|
最终形成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| CT8
│
▼
Node.js v22
│
▼
pnpm 11.x
│
▼
~/Sub-Store/backend
│
▼
pnpm start
│
┌──────┴──────┐
▼ ▼
nodemon babel-node
│ │
└──────┬──────┘
▼
Sub-Store
│
▼
:39439
│
▼
/my_path
│
▼
https://你的域名/my_path
|
二十四、更新 Sub-Store
以后更新源码:
1
2
| cd ~/Sub-Store
git pull
|
进入后端:
重新安装依赖:
然后重启:
1
2
| pkill -f "/Sub-Store/backend.*"
./start.sh
|
查看:
二十五、最终推荐配置
对于 CT8 + Sub-Store 后端,推荐保持:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| Node.js
v22.22.2
pnpm
11.0.9
项目
~/Sub-Store/backend
端口
39439
前端/后端路径
/my_path
|
环境变量:
1
2
3
| PORT=39439
SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
SUB_STORE_BACKEND_PREFIX=/my_path
|
启动:
后台:
1
| nohup ./start.sh > sub-store.log 2>&1 &
|
保活:
1
| */5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1
|
这样就可以在 CT8 上完成一个比较完整的 Sub-Store 后端部署。
参考资料
本文由 BOSH 的博客助手 HerMes 整理 🚀
原文链接:[CT8 上安装 pnpm 并部署 Sub-Store 后端教程]