2026 DeepSeek Harness 怎么用 launchd 开机自启?
本文面向需要长期托管 DeepSeek Harness 的开发者、平台工程师和交付负责人,重点解决普通账户运行、路径固定、凭据隔离、日志维护和异常恢复问题。结论是优先采用 LaunchAgent,并通过进程、端口、模型调用和重启恢复四个闭环完成签收。
macOS 启动阶段由 launchd 负责建立系统和用户环境,并通过 launchctl 管理代理与后台进程。官方终端说明明确指出,用户级任务应放在 ~/Library/LaunchAgents 这一类目录中。Apple 对后台服务的分类也区分了用户上下文中的 Agent 与系统上下文中的 Daemon,具体可参考官方后台服务总览。
本周建议动作:先用普通运行账户创建 LaunchAgent,固定 DeepSeek Harness 的可执行文件、Profile、工作目录和凭据来源,再依次验收进程、端口、模型调用与重启恢复。不要一开始就创建 root 级 LaunchDaemon,也不要把 KeepAlive 当成任务续跑机制。
这篇文章适合 3 类人:
- 远程 Mac 用户:希望机器重启或重新登录后自动恢复 DeepSeek Harness。
- 平台工程师:需要统一进程身份、日志位置和停止方式。
- 交付负责人:需要将“能启动”转化为可重复的持续运行验收。
01 第 1 步:先固定托管对象和恢复边界
DeepSeek Harness 的 Web UI、Headless 单次任务和长期自动化入口,不应直接共用一份 plist。它们的生命周期不同:Web UI 通常需要持续监听端口,Headless 任务则可能正常执行后退出,批处理入口还可能需要独立的队列、锁文件和产物目录。
先在远程 Mac 上写一份部署记录,至少包括:
- 当前使用的运行模式:Web、Headless,或其他自动化入口;
- 实际可执行文件的绝对路径;
- 使用的 Profile 名称或配置目录;
- 工作目录和状态目录;
- 运行账户;
- 监听地址与端口,如确实存在 Web 服务;
- 手动启动命令;
- 正常停止方法;
- 日志目录和产物目录。
DeepSeek Harness 的公开运行路径可能随版本、安装方式和源码分支变化,因此不要把 npx、全局命令或当前终端里的 which dsh 结果直接当作长期路径。先在目标账户下执行:
whoami
pwd
command -v node
command -v dsh
which npx
如果命令由版本管理工具提供,还要继续确认真实文件位置:
realpath "$(command -v node)"
realpath "$(command -v dsh)"
某些环境下,终端启动脚本会自动加载额外的 PATH、Node 版本和配置文件;而 launchd 启动任务时不会完整复刻交互式 Shell。这个差异是“终端能跑、开机后失败”的主要来源之一。
| 托管对象 | 适合的进程模型 | 首要验收项 | 不应直接承诺的恢复结果 |
|---|---|---|---|
| Web UI | 持续运行的 LaunchAgent | 账户、监听地址、端口、工作区、低风险任务 | 进程恢复不等于会话恢复 |
| Headless 单次任务 | 单次启动、明确退出 | 退出状态、产物、错误日志 | 异常退出不等于从断点继续 |
| 定时自动化入口 | 独立任务或调度器 | 触发条件、锁、重复执行保护 | 重启后可能只恢复进程,不恢复队列 |
| 需要无用户登录运行的系统服务 | 评估 LaunchDaemon | 权限、凭据、目录、审计 | 不应为了省事运行整个 Web UI |
官方进程管理说明将 LaunchAgent 定义为与已登录用户会话相关的代理,而 LaunchDaemon 运行在系统上下文中。LaunchAgent 与 LaunchDaemon 的角色说明是选择托管方式的依据。
02 第 2 步:普通账户优先使用 LaunchAgent
对于需要用户会话、用户目录、工作区权限或 Web UI 的 DeepSeek Harness,优先使用普通账户下的 LaunchAgent。它的配置位置通常是:
/Users/<运行账户>/Library/LaunchAgents/com.example.deepseek-harness.plist
这里的 <运行账户> 必须替换成真正运行 Harness 的账户,不能写成管理员账户、登录账户或远程连接工具使用的账户名称。Apple 的终端文档也列出了 ~/Library/LaunchAgents 仅作用于当前登录用户这一边界。LaunchAgent 目录与 launchctl 操作说明
下面只是结构模板,不是可以直接复制的成品:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.deepseek-harness</string>
<key>ProgramArguments</key>
<array>
<string>/ABSOLUTE/PATH/TO/EXECUTABLE</string>
<string>--profile</string>
<string>PROFILE_NAME</string>
</array>
<key>WorkingDirectory</key>
<string>/ABSOLUTE/PATH/TO/WORKSPACE</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/ABSOLUTE/PATH/TO/NODE/BIN:/usr/bin:/bin</string>
</dict>
<key>StandardOutPath</key>
<string>/ABSOLUTE/PATH/TO/LOGS/stdout.log</string>
<key>StandardErrorPath</key>
<string>/ABSOLUTE/PATH/TO/LOGS/stderr.log</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
需要替换的不是只有第一行程序路径,还包括工作目录、Profile、日志目录和 PATH。如果 DeepSeek Harness 依赖 npx,更稳妥的做法是先把实际入口解析成固定绝对路径,或者使用一个由普通账户拥有、可审计的包装脚本;不要让 plist 依赖交互式 Shell 的别名和初始化文件。
Apple 的创建 Launch Daemon 与 Agent 官方指南还说明,plist 中的 ProgramArguments、Label 和 KeepAlive 分别承担入口、标识和生命周期策略,不应把这些字段混同为应用自身的任务队列控制。
⚠️ 注意:不要在 plist 中写入真实 API Key。plist 通常会被备份、复制、提交到代码仓库或被拥有本机权限的其他进程读取,把密钥写进环境变量只是在配置文件里换了一种明文形式。
凭据应放在运行账户可读取、其他账户不可读取的位置,例如权限受控的凭据文件、系统凭据存储或由启动包装脚本安全读取的配置来源。无论采用哪种方式,都要验证 launchd 环境下能够读取凭据,但不要把环境变量完整打印到日志。
创建文件后先做权限检查:
chmod 600 "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
plutil -lint "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
plutil -lint 只能说明 XML 和 plist 结构可解析,不能证明路径、账户、Profile 或模型凭据正确。我们要把“配置文件有效”和“服务真正可用”分成两个阶段。
03 第 3 步:LaunchAgent 找不到 npx 或 Node.js 时逐项修复
出现“找不到 npx”或“找不到 Node.js”,通常不是 Harness 本身损坏,而是启动环境与终端环境不一致。不要先加无限重启,按以下顺序排查:
- 在目标运行账户下确认真实路径:
bash
command -v node
command -v npx
node --version
npx --version
-
将 plist 中的
PATH写成明确目录,不要写$PATH、~或依赖 Shell 展开。 -
如果 Node 由版本管理器安装,确认登录后该版本是否仍然存在,以及升级后绝对路径是否变化。
-
如果 DeepSeek Harness 通过
npx启动,确认网络、缓存目录和包版本策略;生产环境不要无条件使用会自动拉取最新版本的参数。 -
用与 LaunchAgent 相同的账户、工作目录和环境执行一次包装脚本,再读取标准错误日志。
一个简单的包装脚本可以只负责设置环境和调用固定入口:
#!/bin/zsh
set -u
export PATH="/ABSOLUTE/PATH/TO/NODE/BIN:/usr/bin:/bin"
cd "/ABSOLUTE/PATH/TO/WORKSPACE" || exit 20
exec "/ABSOLUTE/PATH/TO/DSH" \
--profile "PROFILE_NAME"
exec 的作用是让 launchd 直接管理目标进程,而不是长期驻留的 Shell 外壳。Apple 对 launchd 进程行为的说明明确提醒,受管理进程不应自行后台化,否则进程状态和重启判断会变得不可靠。
04 第 4 步:首次加载只验证身份、目录和日志
第一次加载时,不要马上测试复杂模型任务。先确认 4 件事:
- 进程确实由预期普通账户启动;
- 当前工作目录是目标工作区;
- Node、
dsh或包装脚本路径可解析; - 标准输出和标准错误都落到维护者知道的位置。
加载命令应使用当前用户会话:
launchctl bootout "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
launchctl kickstart -k "gui/$(id -u)/com.example.deepseek-harness"
随后检查状态:
launchctl print "gui/$(id -u)/com.example.deepseek-harness"
ps -axo user,pid,ppid,command | grep -i '[d]eepseek\|[d]sh'
tail -n 80 "/ABSOLUTE/PATH/TO/LOGS/stdout.log"
tail -n 80 "/ABSOLUTE/PATH/TO/LOGS/stderr.log"
如果进程立即退出,先看错误日志中的路径、权限、参数和凭据读取失败,不要通过设置 KeepAlive 无限重启。持续崩溃时,频繁拉起只会制造更多日志、端口竞争和状态锁,不能修复根因。
Apple 的后台服务生命周期说明可用于区分系统启动、用户登录和进程重新拉起这几个不同阶段,验收时不要把它们写成同一个事件。
05 第 5 步:把 Web UI 和 Headless 任务分别闭环
开机自启后 Web UI 能打开,但任务不能运行,是最容易被误判的情况。端口监听只能证明某个进程绑定了端口,不能证明工作区、Profile、模型凭据和工具权限都正确。
Web 模式至少完成以下闭环:
- 检查监听地址和端口:
bash
lsof -nP -iTCP:<端口> -sTCP:LISTEN
-
从远程 Mac 本机访问一次 Web UI,确认监听地址不是只绑定了错误的回环接口。
-
打开正确工作区,确认工作目录与手动启动时一致。
-
执行一条低风险、低权限、可重复的模型任务。
-
确认返回结果、日志和工作区产物均正常,同时检查日志没有泄露真实 API Key。
Headless 模式则不应把“进程保持运行”作为目标,而应验证退出状态和产物:
/ABSOLUTE/PATH/TO/DSH \
--profile "PROFILE_NAME" \
--input "/ABSOLUTE/PATH/TO/SAFE_INPUT"
status=$?
printf 'exit_status=%s\n' "$status"
exit "$status"
如果任务需要持续排队、断点续跑或跨重启恢复,应该另行设计队列、任务状态和幂等机制。KeepAlive 只负责在指定条件下重新启动进程,不会自动恢复 Harness 内部的对话上下文、未提交文件、未写完产物或远程 API 请求。
经验上,验收记录至少要把“进程启动成功”“Web UI 可访问”“模型调用成功”和“产物可核对”分成 4 行。只写“服务已启动”,交付后很难判断故障发生在哪一层。
06 第 6 步:重启、异常退出和重新登录分开测试
“重启后能自动启动”至少包含 4 种不同事件,不能只执行一次 kill 就宣布完成:
- 主动停止进程;
- 模拟异常退出;
- 当前用户退出并重新登录;
- 整机重启后重新登录。
主动停止用于验证停止方式是否清晰;异常退出用于验证 KeepAlive 或退出策略;重新登录用于验证 LaunchAgent 是否按预期依赖用户会话;整机重启用于验证路径、磁盘挂载、网络和凭据初始化顺序。
每次测试都记录:
- 触发动作;
- 进程何时消失;
- 进程何时重新出现;
- Web UI 是否重新可达;
- 模型任务是否需要重新发起;
- 是否需要人工确认;
- 是否出现重复实例、端口冲突或状态目录锁。
不要写“通常几秒恢复”这类没有实测依据的承诺。恢复时间受网络、磁盘、登录流程、包缓存、凭据服务和 Harness 初始化过程影响,应以目标远程 Mac 的记录为准。
07 第 7 步:长期维护采用可回退的版本流程
升级前先保存 3 类信息:
- 当前正在使用的启动命令;
- 当前可执行文件和依赖版本;
- 最近一次通过端到端验收的 plist、包装脚本和日志位置。
升级时按这个顺序执行:
launchctl bootout "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
# 替换版本、路径或包装脚本
plutil -lint "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
launchctl bootstrap "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
launchctl kickstart -k "gui/$(id -u)/com.example.deepseek-harness"
升级候选版本时,先停止旧实例,再切换新路径,最后检查端口和状态目录。不要同时启动两个使用同一个端口、Profile 或工作区锁文件的实例。
停用流程也应写进交付文档:
launchctl bootout "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist"
mv "$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist" \
"$HOME/Library/LaunchAgents/com.example.deepseek-harness.plist.disabled"
日志不要随 plist 一起删除。至少保留本次停用前的标准输出、标准错误、版本信息、启动命令和最后一次验收结果,方便回退和定位“升级后不能运行”的差异。
如果远程 Mac 还承担数据保留职责,可同时参考 DeepSeek Harness 数据备份与恢复方案;若主要问题是外部访问链路,则应把 DeepSeek Harness 远程 Web UI 访问 的端口、安全组和反向代理检查纳入同一份交付记录。
08 用 3 张表完成最终签收
| 检查层级 | 必须记录的结果 | 通过标准 | 失败后的回退 |
|---|---|---|---|
| 进程身份 | 用户、父进程、启动命令 | 由预期普通账户运行,路径为绝对路径 | 修复账户、PATH 和包装脚本 |
| 运行目录 | 工作目录、Profile、状态目录 | 与手动验证环境一致 | 回到固定工作目录后重新加载 |
| 日志出口 | 标准输出、标准错误 | 可写、可读、无敏感值 | 修复目录权限和日志路径 |
| Web 可用性 | 监听地址、端口、工作区 | 页面可达且能完成低风险任务 | 检查绑定地址、凭据和 Profile |
| Headless 可用性 | 退出状态、产物、错误信息 | 成功退出并产生可核对产物 | 移除自动重启,先修复任务本身 |
| 重启恢复 | 重新登录、整机重启 | 进程按预期恢复,人工边界明确 | 降级为手动启动并保留日志 |
| 配置项 | 推荐做法 | 常见错误 | 验证命令或动作 |
|---|---|---|---|
| 运行账户 | 普通用户 | 用 root 运行整个 Web UI | whoami、ps |
| 可执行文件 | 固定绝对路径 | 依赖 npx 或别名自动解析 |
realpath、包装脚本 |
PATH |
在 plist 或脚本中明确声明 | 依赖终端初始化文件 | 打印非敏感环境信息 |
| API Key | 使用受控凭据来源 | 明文写入 plist 或日志 | 执行低风险模型任务 |
| 工作目录 | 固定绝对路径 | 使用相对路径或临时目录 | 检查产物和状态文件 |
| 日志 | 标准输出与错误分离 | 只看终端窗口 | tail、日志轮转策略 |
KeepAlive |
只用于进程恢复 | 用于掩盖持续崩溃 | 主动停止与异常退出测试 |
| 场景 | LaunchAgent 适配度 | 交付结论 |
|---|---|---|
| 用户登录后恢复 Web UI | 高 | 优先采用,完成端口和任务闭环 |
| 用户登录后运行 Headless 单次任务 | 中 | 重点验证退出状态和产物 |
| 无用户登录仍必须运行 | 低至中 | 重新评估 LaunchDaemon 和凭据权限 |
| 需要恢复未完成任务 | 不能单独解决 | 增加任务队列、状态和幂等设计 |
| 需要多个版本并行 | 有风险 | 分离端口、Profile、状态目录和 Label |
| 需要临时测试候选版本 | 合适 | 使用独立工作区,保留可回退启动命令 |
最后执行一次可勾选验收:
- [ ] 已明确托管的是 Web、Headless 还是自动化入口。
- [ ] 已记录运行账户、绝对路径、Profile、工作目录和停止方法。
- [ ] 已使用普通账户下的 LaunchAgent,而不是默认使用 root。
- [ ] plist 已通过
plutil -lint,文件权限没有对其他用户开放。 - [ ] LaunchAgent 找不到 Node 或
npx的问题已通过固定路径解决。 - [ ] 进程身份、工作目录、标准输出和标准错误已核对。
- [ ] Web UI 已完成监听、访问、工作区和低风险模型任务验证。
- [ ] Headless 任务已验证退出状态和产物,而不是只看进程是否存在。
- [ ] 已分别测试主动停止、异常退出、重新登录和整机重启。
- [ ] 已记录哪些状态不会自动恢复,以及哪些情况需要人工介入。
- [ ] 已准备停用、升级和版本回退命令。
- [ ] 已用重启后的端到端任务作为最终签收证据。
如果当前方案是在个人 Mac 上长期挂着终端、依赖登录用户手动恢复,真实缺点通常是运行窗口不稳定、重启后环境不一致、日志分散在个人目录,以及多人交付时难以复现;如果改用临时云主机,又可能遇到 macOS 用户会话、Web UI 访问和本地工作区权限不匹配的问题。完成这套验收后,若本地 Mac 无法提供稳定的持续占用窗口,可以进一步查看 DeepSeek Harness 云端 Mac 交付验收,再根据任务周期规划 JEXCLOUD 的远程 Mac 环境;但若任务长期高负载、必须连接特定物理设备,或需要永久保留本地状态,自购并自行维护 Mac 仍可能更合适。
用 JEXCLOUD 远程 Mac,稳定托管长期运行任务
按需租用独立 Mac 环境,快速获得适合开发、测试与自动化任务的远程设备。
支持稳定的远程连接与持续运行,减少本地设备长期开机和维护成本。
立即租用