# 下载中心「静默死亡」根因定位与 systemd 托管修复

- 日期：2026-09-12
- 对象：本机下载中心 8899（`~/Downloads/download-server.js`，DL_ROOT=dl-hub）
- 症状：**平台突然无法访问**（进程消失、端口无监听、局域网连接被拒），页面无法打开；本会话内共发生 3 次（18:41 前一次、21:56、22:03）

---

## 一、取证过程与结论

| 检查项 | 结果 | 推论 |
|---|---|---|
| 进程 | 无 `download-server.js` 进程 | 进程已退出 |
| 端口 | 8899 无监听 | 服务确实停摆，非仅路由/防火墙问题 |
| `~/Downloads/dl-server.log` | **只有启动 3 行，无任何崩溃堆栈**，mtime 停在启动那一刻 | **不是 JS 异常**（未捕获异常/未处理 rejection 都会打印堆栈）→ 是被**信号**杀掉的 |
| `systemd-oomd` | inactive / not-found | 排除 systemd-oomd 杀 cgroup |
| dmesg/kern.log/syslog | 无 OOM、无 killed process 记录 | 排除内核 OOM Killer |
| 进程父子关系 | `PPid=1`（nohup 已脱离父进程）**但 `PGID=2482827`、`SID=2482827` 仍属于那个瞬态工具调用 shell**（该 shell 早已退出） | 进程组被整体回收时，服务一起被杀 |

**根因**：`restart-dl-server.sh` 用 `nohup ... &` 启动，只能让进程脱离父进程、忽略 `SIGHUP`，**并没有脱离进程组/会话**。该进程组属于一次性的工具调用 shell，进程组被清理（组级 SIGTERM/SIGKILL）时服务被静默杀死，因此日志永远停在启动行、没有任何异常信息。这也解释了为什么「重启后过一阵又打不开」反复出现。

> 注：原脚本注释里已记录过 `bwrap --die-with-parent` 会随脚本退出被杀，当时的对策是 nohup —— 实测不足以覆盖「进程组被回收」这一情形。

## 二、修复：交给 systemd 托管

新增系统单元 `/etc/systemd/system/dl-server.service`（副本存于 `~/Downloads/dl-server.service`）：

```ini
[Unit]
Description=DSH 下载中心 (dl-hub 文件服务, 端口 8899)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=zyw
Group=zyw
WorkingDirectory=/home/zyw/Downloads
Environment=DL_ROOT=/home/zyw/Downloads/dl-hub
Environment=DL_PORT=8899
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/usr/bin/node /home/zyw/Downloads/download-server.js
Restart=always
RestartSec=2
TimeoutStopSec=5
LimitNOFILE=65535
StandardOutput=append:/home/zyw/Downloads/dl-server.log
StandardError=append:/home/zyw/Downloads/dl-server.log

[Install]
WantedBy=multi-user.target
```

要点：
- 进程 cgroup 为 **`/system.slice/dl-server.service`**，与任何工具调用/会话彻底隔离，不可能再被会话级进程组清理误杀。
- `Restart=always` + `RestartSec=2`：**崩溃后 2 秒自动拉起**。
- `enabled` + `WantedBy=multi-user.target`：**开机自启**（此前重启机器后平台不会自己回来）。
- 用系统自带 `/usr/bin/node`（v20.15.1），不依赖 nvm 版本路径；PATH 内含 `/usr/bin`，保证 WMV 转码所需的 ffmpeg 可用。

## 三、验证

1. **自愈**：`kill -9 $(systemctl show -p MainPID --value dl-server)` → 5 秒内新进程 `2487208 → 2487322`，`is-active=active`，HTTP 200 ✅
2. **隔离**：`cat /proc/<pid>/cgroup` = `/system.slice/dl-server.service` ✅
3. **跨工具调用存活**：多次独立命令调用后仍 `active` / HTTP 200 ✅
4. **功能回归**：全套 23 项上传功能测试在托管实例（v20）上复跑 **23/23 通过** ✅
5. **局域网可访问**：`http://192.168.31.76:8899/` HTTP 200 ✅

## 四、踩坑记录：heredoc + `sudo -S` 抢 stdin

首次安装时用 `echo 密码 | sudo -S tee 单元文件 <<EOF ... EOF` 写入 → **写成 0 字节空文件**，systemd 报 `Unit file is masked`。
原因：函数内 `echo 密码 |` 已占用 stdin，heredoc 内容进不了 `tee`。
正确做法：**本地生成单元文件 → `sudo cp` 到 `/etc/systemd/system/`**（安装脚本 `~/Downloads/install-dl-service.sh` 已按此修正）。

## 五、日常运维命令

```bash
bash ~/Downloads/restart-dl-server.sh      # 重启（已改为 systemctl restart，文件名保持不变）
systemctl status dl-server                 # 状态
systemctl stop dl-server                   # 停止（注意：会中断正在进行的分块上传）
journalctl -u dl-server -n 50              # 日志（文件日志见 ~/Downloads/dl-server.log）
systemctl is-enabled dl-server             # 应输出 enabled
```

`restart-dl-server.sh` 已重写为 systemd 版本，旧文档中的调用方式仍然有效。

## 六、对当时在传上传的影响

- 21:56、22:03 两次静默死亡都打断了正在进行的分块上传，浏览器中对应文件行会显示「❌ 网络中断」，需重新选择文件。
- 好消息：**分块会话全部保留在磁盘上**，重新选择同一批文件会从断点续传。
  - 45 个会话、已收块折合 **64.4GB** 无需重传；目标合计 139.7GB。
  - 已完整落盘的 13 个文件在新的「跳过」逻辑下会**瞬间跳过**。
- 22:04 实测：近 120 秒无 `.part` 写入 → 浏览器侧上传已全部停止，需刷新页面重新发起。

## 七、相关文件

- 单元文件：`/etc/systemd/system/dl-server.service`（副本 `~/Downloads/dl-server.service`）
- 安装脚本：`~/Downloads/install-dl-service.sh`
- 重启脚本（已改为 systemd）：`~/Downloads/restart-dl-server.sh`
- 服务端：`~/Downloads/download-server.js`
