fix(install): 刚装完的服务会失败,单元要说清楚而不是每 5 秒重启一次

我在真内核上把安装序列完整走了一遍(容器里补了 useradd/systemctl 的替身,其余全是真的),
暴露出一个只有真跑才看得见的缺陷:

装完 → `install` 启动服务 → 但那时**还没有配对的手机**,而守护进程**故意**拒绝空的设备列表
(一个什么都不能授权的 agent 不该坐在那里显得健康)。于是 `Restart=always` + `RestartSec=5`
变成每 5 秒重启一次,永远,journal 里刷同一行。实测:exit 1,"devices is empty, so no phone
could ever be authorised"。

配对发生在安装**之后**(运维在节点上跑 `--pair`,然后在手机上配对),所以这不是异常状态,
而是流程的必经一步。修法两条:

- 单元加 `StartLimitIntervalSec=300` / `StartLimitBurst=10`:十次之后 systemd 放弃,单元进入
  `failed`——一个运维看得见、能处理的状态,而不是无限重启。
- `install` 的结束语改成说清这一步:服务会退出并停住,下一步是 `--pair`,用
  `systemctl status` 看状态。此前它说"installed and running",而那是假的。

同一轮演练确认了此前所有修复在真内核上成立:服务用户建了、配置与日志目录交给它了、配置对
该用户可读、单元来自二进制且内容一致、卸载按序拆干净(用户/目录/单元全无残留)。

Go 全包通过(新增一项守着"单元必须有启动上限,且 install 必须说出下一步")。
This commit is contained in:
chick
2026-09-18 01:09:15 +08:00
parent fc7a77c500
commit 9949cfe5b0
5 changed files with 92 additions and 3 deletions
+15
View File
@@ -39,6 +39,20 @@ relay's `agents.json` by hand. Without it, `enroll` prints the entry to add.
Removing it again is `sudo msa-agent uninstall`; `--dry-run` shows the plan first, and `--keep-logs`
keeps the audit trail.
### After installing, pair
The daemon refuses to start with no paired devices, on purpose: an agent that could authorise nothing
must not sit there looking healthy. Pairing happens after install, so a freshly installed node's service
will fail and stop — the unit's start limit makes systemd give up rather than restart every five seconds
forever, and `systemctl status msa-agent` shows it as failed rather than flapping.
```sh
sudo /opt/msa-agent/msa-agent --pair # opens the LAN window; then pair from the app
```
Then `systemctl start msa-agent` (or just wait — it is enabled) and check `journalctl -u msa-agent -n 20`
for the policy digest and the device count.
## Subcommands
| Command | What it does |
@@ -49,6 +63,7 @@ keeps the audit trail.
| `uninstall` | Tells the relay first, then removes the service, unit, config, logs and user |
| `version` | Prints the version stamped into the binary |
| `--pair` | Opens the temporary LAN window a phone uses to pair; closes itself |
| `--pair-bind` | The address that window binds to (an IP literal; the default is all interfaces) |
| `--check` | Validates the config and policy, then exits |
| `--once` | Runs a single poll cycle and exits |