第三方应用托管 · 阿里云单节点 · 深圳
一套 ToB 售前方案引擎,代码不在我手上,服务器和运维在。这页讲的是它怎么上线、怎么在没人盯着的时候继续活着,以及一次例行暴露面体检查出了什么。
724027 秒 ≈ 8 天 9 小时 —— 与 ps 报的进程存活时长对得上,说明这段时间里 systemd 没有重启过它。
托管对象
PresaleOS,一个面向 ToB 销售与售前团队的方案引擎。以下描述取自它自己的 docs/,不是我的评价。
完整链路是:客户自助填诊断表 → 线索入库 → AI 生成客户背调 → 生成调研问题、回收结果 → 方案工厂出方案 → 素材库里改 → 演示页预览 → 导出 PDF。方案有 4 类模板(标准售前 10 页 / 管理层汇报 8 页 / POC 验证 9 页 / 行业解决方案 12 页),系统会按客户行业、规模、预算推荐一个,销售也能手动换。
| 页面 | 作用 | 体量 |
|---|---|---|
| sales-workspace.html | 售前后台:线索、背调、调研、方案、素材库、统计 | 21,964 行 |
| knowledge-hub.html | 行业知识库与企业模块库 | 3,513 行 |
| proposal-presentation.html | 方案演示页,支持导出 PDF | 3,392 行 |
| client-diagnosis.html | 客户自助诊断,提交后入库 | 2,759 行 |
| ai-assistant.html | AI 助手 | 1,811 行 |
| index.html | 入口,跳转到后台 | 89 行 |
六个页面合计 33,528 行 / 1.5 MB。wc -l 实测,不含 assets。
归属说明:产品由作者团队设计和开发,我负责的是它跑起来之后的那一段 —— 上线、在线、出事时能找到原因。
这页收在我的项目索引里,是因为「让别人的东西在我的机器上稳定跑着」本身就是一份可交付的活 —— 下面写的都是这部分。
托管
纯静态页面谁都能挂。麻烦的是它需要一个真实的大模型出口 —— 而模型 Key 绝对不能进前端 JS。所以架构被劈成两半。
nginx 直接吐 HTML;/api/ 和 /health 反代到 127.0.0.1:3000。
公网直连 :3000 打不通(实测超时)—— 外面唯一能碰到它的路径,是 nginx 明确放行的那两条。
presales-ai-proxy.service,Type=simple / Restart=always / RestartSec=5,以非 root 的 ecs-user 运行。
工作目录不在网站根目录里,是 /www/server/ 下的独立路径 —— 服务端代码和它读的环境变量,天然不在任何 URL 能到达的范围内。
acme.sh 签 Let's Encrypt ECDSA P-256,有效期 2026-07-06 → 2026-10-04,下一次自动续期排在 2026-09-03。
cron 一天跑四次,到点自己去续;装证书时挂了 --reloadcmd,续完 nginx 自己重载。少了这个钩子,新证书会躺在磁盘上,而 nginx 还在用内存里那张旧的 —— 一直用到它真的过期。
2026-08-03 · 例行体检
上线之后我对这台机器上的每个站点做了一遍暴露面扫描 —— 就是老老实实拿 curl 去试那些不该被访问、但经常忘记挡的路径。
这个站命中了 6 个:项目文档、开发时留下的环境变量文件、打包产物、以及编辑器和备份留下的 .tmp / .bak 残留。其中一个文件里带着一把当时仍然有效的模型 Key,而另一个压缩包里又装了同一份。
这些文件属于作者,哪个还有用不归我判断。
所以全部在 nginx 配置层拦截 —— 文件原样留在磁盘上,只是从公网消失。要恢复,删三行配置就行。
逐个确认这些路径被页面零引用,环境变量文件只被服务端进程读取,不经过 HTTP。
还有一条:这台机器上另有一个无扩展名的长串路径在跑别的服务,拦截规则写成带扩展名的正则,才不会误伤它。这条是当时最容易翻车的地方。
同一轮体检里另一处泄露的成因是:凭据文件被 .gitignore 正确排除了,但打包分发时是整目录压缩 —— gitignore 完全不参与。
.gitignore 保护的是 git,不是压缩包。
| 规则形态 | 意图 | 2026-08-04 公网复测 |
|---|---|---|
| location = /README.md | 项目文档(内含演示账号)不外发 | 404 ✓ |
| ~ /\.(env|git) | 环境变量与版本库元数据 | 404 ✓ |
| ~* \.(zip|tar\.gz|sql|bak|tmp…)$ | 打包产物与备份 | 404 ✓ |
| ~* \.(bak|tmp|old|orig)\. | 带时间戳的中间残留 | 404 ✓ |
| / · 六个应用页面 | 正常访问,不能被误伤 | 200 ✓ |
| /health | 存活探针,保持可探测 | 200 ✓ |
改配置那天做过一次全量复测,今天写这页时又跑了一遍 —— 上表是今天的结果。
合规
大陆节点对外提供网页服务,每个页面页脚都要有备案号并链到工信部。这不是风格问题,是法定要求。
这个站没有构建脚本,HTML 就是源文件,所以备案号是逐页手工插在最后一个 </body> 前的。其中 sales-workspace.html 有 4 处 </body> —— 前 3 处在 JS 模板字符串里(用来生成可下载的报价单),只有最后一处是文档真正的结尾。按第一个匹配插入,就会把备案号塞进一段 JS 字符串里,页面上什么都不会显示,而你以为做完了。
还有一个文件被我跳过没动:它的正文停在一行 JS 注释中间,没有 </body> 也没有 </html>,是个残缺文件。往截断的 <script> 后面追加 HTML 只会变成脚本里的垃圾字符 —— 这种情况正确的做法是停下来问作者,不是硬补。
改动前先整包备份了这 6 个 HTML,再动手。
入口
站点是活的,下面两个链接现在就能打开。
后台账号不放在这页。它不是我可以代作者对全网开放的东西 —— 何况我刚把这个站上暴露的凭据收干净,自己转头再开一个口子说不过去。
需要看完整链路(登录 → 背调 → 方案 → 导出 PDF),找我,我来安排一次演示。