RemoteMac 2026.08.19

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 中的 ProgramArgumentsLabelKeepAlive 分别承担入口、标识和生命周期策略,不应把这些字段混同为应用自身的任务队列控制。

⚠️ 注意:不要在 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 本身损坏,而是启动环境与终端环境不一致。不要先加无限重启,按以下顺序排查:

  1. 在目标运行账户下确认真实路径:

bash command -v node command -v npx node --version npx --version

  1. 将 plist 中的 PATH 写成明确目录,不要写 $PATH~ 或依赖 Shell 展开。

  2. 如果 Node 由版本管理器安装,确认登录后该版本是否仍然存在,以及升级后绝对路径是否变化。

  3. 如果 DeepSeek Harness 通过 npx 启动,确认网络、缓存目录和包版本策略;生产环境不要无条件使用会自动拉取最新版本的参数。

  4. 用与 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 模式至少完成以下闭环:

  1. 检查监听地址和端口:

bash lsof -nP -iTCP:<端口> -sTCP:LISTEN

  1. 从远程 Mac 本机访问一次 Web UI,确认监听地址不是只绑定了错误的回环接口。

  2. 打开正确工作区,确认工作目录与手动启动时一致。

  3. 执行一条低风险、低权限、可重复的模型任务。

  4. 确认返回结果、日志和工作区产物均正常,同时检查日志没有泄露真实 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 类信息:

  1. 当前正在使用的启动命令;
  2. 当前可执行文件和依赖版本;
  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 whoamips
可执行文件 固定绝对路径 依赖 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

用 JEXCLOUD 远程 Mac,稳定托管长期运行任务

按需租用独立 Mac 环境,快速获得适合开发、测试与自动化任务的远程设备。

支持稳定的远程连接与持续运行,减少本地设备长期开机和维护成本。

立即租用