第三方应用托管 · 阿里云单节点 · 深圳

不改一行代码,
把它稳住

一套 ToB 售前方案引擎,代码不在我手上,服务器和运维在。这页讲的是它怎么上线、怎么在没人盯着的时候继续活着,以及一次例行暴露面体检查出了什么。

公网探测 · 2026-08-04
curl -s https://yezi.alittleyd.com/health {"ok":true,"service":"presales-ai-proxy","version":"1.0.0", "model":"deepseek-v4-flash","hasApiKey":true, "uptime":724027.6} systemctl show presales-ai-proxy -p ActiveState,ExecMainStartTimestamp ActiveState=active 进程已连续运行 8-09:08:08 (ps -o etime)

724027 秒 ≈ 8 天 9 小时 —— 与 ps 报的进程存活时长对得上,说明这段时间里 systemd 没有重启过它。

连续在线
8天 9 小时配了 Restart=always 兜底,但这 8 天没用上
常驻内存
56MBNode 单进程 RSS,8 天无明显增长
证书
ECDSAP-256Let's Encrypt,到期 2026-10-04,自动续
公网复测
11/ 117 条该通的通,4 条该挡的挡住

托管对象

先说清楚这东西是干嘛的

PresaleOS,一个面向 ToB 销售与售前团队的方案引擎。以下描述取自它自己的 docs/,不是我的评价。

完整链路是:客户自助填诊断表 → 线索入库 → AI 生成客户背调 → 生成调研问题、回收结果 → 方案工厂出方案 → 素材库里改 → 演示页预览 → 导出 PDF。方案有 4 类模板(标准售前 10 页 / 管理层汇报 8 页 / POC 验证 9 页 / 行业解决方案 12 页),系统会按客户行业、规模、预算推荐一个,销售也能手动换。

页面作用体量
sales-workspace.html售前后台:线索、背调、调研、方案、素材库、统计21,964 行
knowledge-hub.html行业知识库与企业模块库3,513 行
proposal-presentation.html方案演示页,支持导出 PDF3,392 行
client-diagnosis.html客户自助诊断,提交后入库2,759 行
ai-assistant.htmlAI 助手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 明确放行的那两条。

后半:systemd 常驻

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 还在用内存里那张旧的 —— 一直用到它真的过期。

模型出口 · Key 只存在于服务端环境变量
浏览器 ──▶ nginx :443 (TLS 终止) └─ /api/ ──▶ 127.0.0.1:3000 presales-ai-proxy (systemd) └─ 读 AI_API_KEY 环境变量 ──▶ 大模型 API 前端拿得到的只有 /api/ 这个入口,拿不到 Key。 当前模型:deepseek-v4-flash /health 的 hasApiKey 只报 true/false,不回显值。

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 只会变成脚本里的垃圾字符 —— 这种情况正确的做法是停下来问作者,不是硬补。

逐页核对 · grep -c 备案号
index.html 1 client-diagnosis.html 1 sales-workspace.html 1 ← 4 个 </body>,用 rfind 定位 proposal-presentation.html 1 knowledge-hub.html 1 ai-assistant.html 1

改动前先整包备份了这 6 个 HTML,再动手。

入口

自己点进去看

站点是活的,下面两个链接现在就能打开。

后台账号不放在这页。它不是我可以代作者对全网开放的东西 —— 何况我刚把这个站上暴露的凭据收干净,自己转头再开一个口子说不过去。

需要看完整链路(登录 → 背调 → 方案 → 导出 PDF),找我,我来安排一次演示。