[Unit] Description=pi mail-bridge (AgentMail ↔ @earendil-works/pi-coding-agent) After=network-online.target agentmail-gateway.service Wants=network-online.target [Service] Type=simple # 桥自己不需要工作目录 —— 每条会话的 cwd 来自邮件寻址的 path 位。 # 但 systemd 要求一个存在的目录,且 pi 的 SettingsManager 会在这里找 # 项目级配置,因此指向仓库而不是 /。 WorkingDirectory=/opt/agentmail/plugins/pi-mail-bridge/current ExecStart=/usr/bin/node /opt/agentmail/plugins/pi-mail-bridge/current/src/index.mjs # pi 靠 HOME 定位 ~/.pi/agent(settings.json、auth.json、models.json、sessions/)。 # systemd 不会自动注入 HOME,不显式给就: # - 读不到 provider 凭证 → 每封邮件都"没有可用模型" # - 会话落到 /.pi 或直接失败 → C-8 的会话快照永远是空的 Environment=HOME=/root # pi 的全局扩展 pi-a2a / pi-acp 绑死 127.0.0.1:12010 / 12011。 # 桥用 noExtensions:true 起会话,本进程不会去 bind 那两个端口; # 这两个变量是给「同机还跑着 pi CLI」的情况留的隔离位, # 万一将来放开扩展加载,端口也不会和交互式 pi 撞。 Environment=PI_A2A_PORT=13010 Environment=PI_ACP_PORT=13011 EnvironmentFile=/etc/agentmail/pi.env Restart=always RestartSec=10 # 桥用 O_EXCL 建 /pi-bridge.lock 保证单实例 # (两个实例各自订阅 SSE 会让同一封邮件各起一条会话,发件人收到两封回信, # 而去重集合在各自内存里、彼此看不到)。 # # 被 SIGKILL 或主机掉电时锁文件留在盘上 —— 桥自己会检查锁里的 pid 是否还活着, # 但那要求 /proc 可读且 pid 未被复用。systemd 重启前清一次更直接: # ExecStartPre 里的 pid 必然已经死了(systemd 确认进程退出后才重启)。 # # 用 `-` 前缀:文件不存在时 rm 失败不该拦住启动。 ExecStartPre=-/bin/rm -f /root/.agentmail-pi/pi-bridge.lock # 桥是长驻守护进程,启动即注册 + 立刻打一次心跳 + 订阅 SSE(B-1), # 不像 opencode 那样惰加载,因此不需要 ExecStartPost 预热。 # pi 会话在内存里持有整条对话,长跑之后常驻几百 MB。给一个上限让它被 # OOM killer 挑中而不是拖垮整机;Restart=always 会把它拉回来。 MemoryMax=4G # 优雅停机:桥收到 SIGTERM 后要注销 SSE、清锁文件。 # 默认 90 秒太长(停机时人在等),10 秒足够那两件事。 TimeoutStopSec=10 [Install] WantedBy=multi-user.target