从零理清一台共用服务器:upload-server 配置化、防目录穿越与 PM2 收敛

今天回头收拾一台腾讯云 Ubuntu 服务器,这活儿比写新功能更考验工程素养。这台机器挂着我的博客、刷题站、面试站、提醒服务好几个项目,但长期没人系统整理:文件上传接口硬编码在源码里,想新增一个素材目录得改代码、重启、发版;root 和 ubuntu 两套 PM2 各跑各的;SSH 还得每次手敲密码。我花了一天把它理顺,核心做了三件事:配通 SSH 免密、把 upload-server 重构成配置驱动并加防目录穿越、把散落的服务收敛到 ubuntu 一个账号下。

先把登录做干净

腾讯云默认账号是 ubuntu,root 公钥直连被拒属正常。私钥 alleria.pem 我一开始权限 644,SSH 直接拒绝,改成 600 才通。然后在 ~/.ssh/config 里配了别名 drew-server,之后 ssh drew-server 直连免密。小事,但省下的每次登录摩擦是实打实的。

摸清 upload-server 的底

它是 Express + multer,监听 127.0.0.1:3100,nginx 把 getoffer.alleria.cn/api/ 反代过去,/images/ 指向静态根。上传接口是 POST /api/upload,multipart 字段名必须叫 video;?type= 决定落盘目录:ar 落到 images/AR、avatar 落到 images/avatar、默认视频落到 images/video/{level}.mp4。返回的就是可直接访问的外链,域名早就 HTTPS+备案了。我实测用 type=ar 传了一张图拿到外链,顺手把 AR 绘图桌的线稿库接了进来,现在已经 24 个素材。

真正的活是重构

我把目录映射从 index.js 里抽出来,变成一份 config.json 白名单(相对 STATIC_ROOT=/var/www/getoffer-static):

{"video":"images/video","avatar":"images/avatar","ar":"images/AR"}

URL 由 BASE_URL/<rel>/<文件名> 自动拼,不再写死前缀;未知 type 直接返回 400 并列出可用值;启动时自动建目录;还加了目录越界校验,防止 ../../ 一路穿出 STATIC_ROOT。现在想加一个新素材类型,配置文件加一行 + pm2 restart 就完事,不用碰代码。

重构前我先把 index.js 备份成 index.js.bak-20260906,实测临时加个 demo type 上传成功并自动建目录后,再把测试文件还原清理掉。没有测试环境的服务端改动,靠"备份 → 实测 → 还原"的闭环也能低成本试错。

进程收敛是收尾

这台机器上 root 和 ubuntu 各有一套 PM2 daemon,images/ 静态目录还被 book/crops/goods/icons/materials/pokemon/share 一堆项目共用。我把 interview-site(Nuxt,端口 3000)从 root PM2 迁到 ubuntu PM2:sudo 停掉 root 侧 → ubuntu 同参启动 → 本地 3000 返回 200 → 删掉 root 侧进程并 save。现在 ubuntu 一个 PM2 统一管四个:reminder-service(cluster)、upload-server、blog、interview-site。

方法论沉淀

  1. 配置驱动优于硬编码。 能力扩展从"改码发版"降为"配置 + 重启",上线风险小了一大截。
  2. 安全是重构的自然产出。 白名单 + 越界校验,把"能传哪"从隐式约定变成显式约束,目录穿越这种事从源头就堵了。
  3. 进程归属要单一。 两套 PM2 看着能用,实际是埋雷,收敛到一处后运维心智负担直接减半。
  4. 先备份再动手。 服务端改动没有测试环境时,备份 + 实测 + 还原是兜底的勇气来源。

这套经验后来固化成了一个 getoffer-upload skill,把接口约定、字段名、目录规则、pm2 路径坑都记下来,下次接新项目直接复用。对全栈岗来说,能收拾好一台共用服务器,比单独写一个功能更能说明工程成熟度。