CT8 上安装 pnpm 并部署 Sub-Store 后端教程

CT8 上安装 pnpm 并部署 Sub-Store 后端教程

本文介绍如何在 CT8 Hosting 的 Node.js 环境中,通过 SSH 使用 pnpm 部署 Sub-Store 后端。

本文以以下环境为例:

系统用户:your_username
Node.js:v22.22.2
pnpm:11.0.9
Sub-Store:v2.36.31
Node.js 端口:39439
后端访问前缀:/my_path

最终效果:

https://你的域名/my_path
        ↓
CT8 Node.js
        ↓
127.0.0.1:39439
        ↓
Sub-Store Backend

一、准备 CT8 Node.js 环境

首先通过 SSH 登录 CT8。

检查 Node.js:

node -v

例如:

v22.22.2

检查 npm:

npm -v

再检查 Corepack:

corepack --version

Corepack 可以管理项目指定的 pnpm 版本。Node.js 官方生态中的 Corepack 会根据项目 package.json 中的 packageManager 字段选择对应的包管理器版本。


二、在 CT8 安装 pnpm

方法一:使用 Corepack

推荐优先尝试:

corepack enable

然后:

pnpm -v

如果能够显示版本号,说明 pnpm 已经可以使用。


方法二:安装到用户目录

如果 CT8 不允许 corepack enable 修改系统目录,可以安装到自己的 Home 目录:

npm install --prefix "$HOME/.local" pnpm

安装完成后检查:

find "$HOME" -type f -name pnpm 2>/dev/null | head -20

例如:

/home/your_username/.local/bin/pnpm

将用户目录加入 PATH:

export PATH="$HOME/.local/bin:$PATH"

然后:

pnpm -v

三、解决 CT8 上 pnpm: command not found

CT8 上可能出现这样的情况:

pnpm start

可以运行,但是:

./start.sh

却提示:

pnpm: command not found

这是因为脚本运行时的 PATH 与 SSH 交互式 Shell 不一定完全相同。

可以检查:

which pnpm

如果没有输出,再执行:

find "$HOME" -type f -name pnpm 2>/dev/null | head -20

假设得到:

/home/your_username/.local/bin/pnpm

那么启动脚本中建议直接使用绝对路径:

/home/your_username/.local/bin/pnpm

同时加入:

export PATH="/home/your_username/.local/bin:$PATH"

这样最稳定。


四、下载 Sub-Store 后端源码

进入 Home 目录:

cd ~

克隆官方仓库:

git clone https://github.com/sub-store-org/Sub-Store.git

进入 backend:

cd ~/Sub-Store/backend

Sub-Store 官方仓库目前将后端源码放在 backend 目录,并使用 pnpm 管理依赖。

如果已经下载过:

cd ~/Sub-Store/backend
git pull

五、检查 package.json

执行:

cat package.json

你应该能看到类似:

{
  "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"
  }
}

这里有两个重要信息:

packageManager: pnpm@11.0.9

说明项目期望使用 pnpm 11.0.9。

以及:

start

实际执行:

nodemon
    ↓
babel-node
    ↓
src/main.js

因此不要直接:

node src/main.js

否则可能遇到:

Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@/core'

因为源码启动依赖项目的 Babel/路径别名配置。


六、安装项目依赖

进入:

cd ~/Sub-Store/backend

安装:

pnpm install

如果项目已经存在 lockfile,并且希望严格按照 lockfile 安装:

pnpm install --frozen-lockfile

安装完成后会生成:

node_modules/

七、确认 pnpm 版本

执行:

pnpm -v

如果项目要求:

11.0.9

可以通过 Corepack 管理指定版本。

例如:

corepack prepare pnpm@11.0.9 --activate

然后:

pnpm -v

确认版本。

项目在 package.json 中指定 packageManager 后,可以让 Corepack 根据项目配置使用相应的包管理器版本。


八、确认 CT8 分配的 Node.js 端口

CT8 Node.js 应用通常需要使用平台分配的端口。

本文使用:

39439

因此 Sub-Store 需要监听:

0.0.0.0:39439

或者:

:::39439

九、设置 Sub-Store 环境变量

本教程使用:

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 让后端自身处理该路径前缀

这里最容易遗漏的是:

SUB_STORE_BACKEND_PREFIX=/my_path

如果只设置:

SUB_STORE_FRONTEND_BACKEND_PATH=/my_path

前端访问:

https://你的域名/my_path

可能得到:

404 Not Found

而同时设置:

SUB_STORE_BACKEND_PREFIX=/my_path

后端也会使用这个前缀。


十、测试启动 Sub-Store

进入:

cd ~/Sub-Store/backend

执行:

export PORT=39439
export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

pnpm start

如果正常,日志应该类似:

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

看到:

listening on :::39439

就说明 Node.js 已经成功监听 CT8 分配的端口。


十一、检查环境变量是否生效

可以通过 Sub-Store 启动后的信息确认:

SUB_STORE_FRONTEND_BACKEND_PATH=/my_path

以及:

SUB_STORE_BACKEND_PREFIX=/my_path

确保没有拼写错误。


十二、测试本地后端

另外打开一个 SSH 窗口。

执行:

curl -i http://127.0.0.1:39439/

如果返回 HTTP 响应,说明 Node.js 后端正常。

也可以检查端口:

ss -lnt | grep 39439

应该看到类似:

LISTEN ... 0.0.0.0:39439

或者:

LISTEN ... [::]:39439

十三、配置 CT8 网站

假设你的域名是:

https://yourdomain.ct8.pl

Node.js 后端实际监听:

127.0.0.1:39439

那么最终访问:

https://yourdomain.ct8.pl/my_path

应该由 CT8 的 Node.js 网站映射到:

127.0.0.1:39439

如果 CT8 Node.js 网站本身已经负责端口转发,就不需要再额外使用 Nginx。

最终结构:

浏览器
  │
  │ HTTPS
  ▼
https://yourdomain.ct8.pl/my_path
  │
  ▼
CT8 Node.js
  │
  ▼
39439
  │
  ▼
Sub-Store

十四、创建启动脚本

为了避免每次输入环境变量,可以创建:

cd ~/Sub-Store/backend
vim start.sh

写入:

#!/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

保存后:

chmod +x start.sh

以后:

./start.sh

即可启动。


十五、为什么推荐 pnpm 绝对路径

不要只写:

pnpm start

而推荐:

/home/your_username/.local/bin/pnpm start

because CT8 的 cron、后台脚本 and SSH Shell 的 PATH 可能不同。

这样即使:

which pnpm

没有结果,脚本仍然可以找到 pnpm。


十六、让 Sub-Store 后台运行

测试确认正常后,可以使用:

nohup ./start.sh > sub-store.log 2>&1 &

查看日志:

tail -f sub-store.log

查看进程:

ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep

查看端口:

ss -lnt | grep 39439

十七、增加自动保活

CT8 上可以使用 cron 定时检查。

编辑:

crontab -e

例如每 5 分钟检查一次:

*/5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1

但是这里有一个重要问题:

如果 start.sh 每次都直接:

pnpm start

就会产生多个 Sub-Store 实例。

所以必须增加防重复启动。


十八、推荐的最终保活脚本

把 start.sh 修改为:

#!/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 判断

有些情况下:

curl http://127.0.0.1:39439/

不一定能准确判断 Node.js 是否已经运行。

例如:

  • / 返回非 2xx
  • 后端要求 /my_path
  • 服务刚启动还没有完成初始化
  • Node.js 已监听端口但 HTTP 尚未完全准备好

这时:

curl -sf

可能返回失败。

如果 cron 频繁调用脚本,就可能导致:

pnpm start
pnpm start
pnpm start
pnpm start

最终产生多个 Node.js 实例。

因此保活脚本同时检查:

进程
+
nodemon
+
端口

更加适合这个场景。


二十、清理重复的 Sub-Store 进程

如果之前误启动了多个实例,可以先查看:

ps -aux | grep "Sub-Store/backend" | grep -v grep

如果确认都是重复的 Sub-Store,可以停止:

pkill -f "/Sub-Store/backend.*"

然后再次确认:

ps -aux | grep "Sub-Store/backend" | grep -v grep

没有输出后,再:

./start.sh

二十一、检查 Sub-Store 是否正常运行

查看进程

ps -aux | grep -E "pnpm|nodemon|babel-node" | grep -v grep

查看端口

ss -lnt | grep 39439

查看日志

tail -100 sub-store.log

实时查看:

tail -f sub-store.log

测试本地接口

curl -i http://127.0.0.1:39439/

测试网站

浏览器打开:

https://你的域名/my_path

二十二、常见问题

问题 1:pnpm: command not found

执行:

find "$HOME" -type f -name pnpm 2>/dev/null | head

如果找到:

/home/your_username/.local/bin/pnpm

使用:

export PATH="$HOME/.local/bin:$PATH"

或者直接使用:

/home/your_username/.local/bin/pnpm

问题 2:直接 node src/main.js 报错

例如:

Cannot find package '@/core'

不要直接:

node src/main.js

使用:

pnpm start

because pnpm start 会通过项目配置启动:

nodemon
→ babel-node
→ src/main.js

问题 3:启动后监听 3000

如果看到:

listen EPERM: operation not permitted :::3000

说明项目没有使用你设置的端口,或者 PORT 没有被正确读取。

检查项目源码中实际使用的端口变量:

grep -RniE "3000|PORT|listen\(" src | head -100

对于当前 Sub-Store 版本,实际部署时应以项目自身支持的环境变量为准。

官方仓库的开发说明也使用 SUB_STORE_BACKEND_API_PORT 指定开发后端端口,例如:

SUB_STORE_BACKEND_API_PORT=3000 pnpm esbuild:dev

因此不同启动脚本可能使用不同的端口变量,不能一概而论。


问题 4:/my_path 返回 404

检查:

export SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
export SUB_STORE_BACKEND_PREFIX=/my_path

然后重新启动:

./start.sh

同时确认 CT8 Node.js 应用确实将域名请求转发到了:

39439

问题 5:重复运行 start.sh

如果出现多个:

pnpm start

或者多个:

nodemon

不要继续运行脚本。

先:

ps -aux | grep "Sub-Store/backend" | grep -v grep

确认后清理:

pkill -f "/Sub-Store/backend.*"

然后只启动一个:

./start.sh

二十三、完整部署流程

以后重新部署 Sub-Store,可以直接按照下面流程操作。

1. 登录 CT8

ssh 用户名@服务器

2. 检查 Node.js

node -v

3. 检查 pnpm

pnpm -v

如果找不到:

export PATH="$HOME/.local/bin:$PATH"

4. 获取源码

cd ~
git clone https://github.com/sub-store-org/Sub-Store.git

5. 进入 backend

cd ~/Sub-Store/backend

6. 安装依赖

pnpm install

7. 创建启动脚本

vim start.sh

内容:

#!/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. 添加执行权限

chmod +x start.sh

9. 启动

./start.sh

10. 检查

ss -lnt | grep 39439

11. 设置 cron

crontab -e

加入:

*/5 * * * * /home/your_username/Sub-Store/backend/start.sh >> /home/your_username/Sub-Store/backend/watchdog.log 2>&1

最终形成:

                    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

以后更新源码:

cd ~/Sub-Store
git pull

进入后端:

cd backend

重新安装依赖:

pnpm install

然后重启:

pkill -f "/Sub-Store/backend.*"
./start.sh

查看:

tail -f sub-store.log

二十五、最终推荐配置

对于 CT8 + Sub-Store 后端,推荐保持:

Node.js
v22.22.2

pnpm
11.0.9

项目
~/Sub-Store/backend

端口
39439

前端/后端路径
/my_path

环境变量:

PORT=39439
SUB_STORE_FRONTEND_BACKEND_PATH=/my_path
SUB_STORE_BACKEND_PREFIX=/my_path

启动:

./start.sh

后台:

nohup ./start.sh > sub-store.log 2>&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 后端教程