deploy: 标准目录部署 —— 运行时不再依赖源码目录
用户注意到:「当前 agentmail 是在源码目录部署的,应当改为标准目录部署」。 查证后有三处实证(都不是猜测): 1. ★ **失败通知钩子执行的是仓库里的脚本** (`/home/program/agentmail/deploy/service-failure-notify.mjs`,8 处引用: 4 个 drop-in + zcode/zcode-mail-bridge 单元 + agentmail-failure-flush)。 仓库一挪/一改名,故障通知就**静默失效** —— 而那条管线正是用来报告服务故障的。 2. **opencode-serve 的 cwd 就是源码目录**(`WorkingDirectory=/home/program/agentmail`)。 3. ★ **仓库里的 `deploy/*.service` 是旧的源码目录版本**(ExecStart 指向 `/home/program/agentmail/plugins/...`),而机器上的已被改过 —— 也就是说 **谁跑一次 install.sh 就会把部署退回源码目录**。drop-in 更是只存在于 /etc 里, 仓库完全没有它们。 ## 改动 - **唯一真相**:`deploy/systemd/` 镜像 systemd 目录结构,收进全部单元与 drop-in (8 个单元 + 12 个 drop-in),路径全部改到 `/opt`。 - 运行时脚本装到 **`/opt/agentmail/bin/service-failure-notify.mjs`**(自包含, 无相对导入);`install.sh` 与 `redeploy-gateway.sh` 都会幂等地装它。 - opencode 的 cwd 改为 `/opt/agentmail`(与网关一致),已重启生效 (`/proc/<pid>/cwd` 已核)。 - 删掉 `deploy/*.service` 的旧副本,避免两个真相。 - dsh 的 `cordis.patch.yml` 注释里的安装示例也改到标准位置(运行时用的是环境变量, 那条注释是唯一残留)。 ## 判据(`deploy/check-deploy-drift.mjs` 新增「标准目录部署」四条 + 自检) ① 任何 unit/drop-in 都不得引用源码目录;② 已安装单元与 `deploy/systemd/` 逐字节一致; ③ 通知脚本在标准位置且可执行;④ 各服务的 cwd/ExecStart 不在源码目录 (homeagent/dsh/zcode 是**别的产品**的标准位置,按白名单放行)。 自检两个样本:引用源码目录的必须红、干净样本必须绿(证明不是恒真)。 顺带修掉一处**真漂移**:仓库里 dsh 的 `dist/index.js` 落后于部署件(改了 src 没重建), 重建后 `check-deploy-drift` 报「四个宿主都在跑当前代码」。 ## 复核 - `/etc/systemd/system/` 引用仓库:**0** 个文件;`/opt/agentmail` 下只剩旧二进制/备份里 的构建路径(Go 嵌的源码路径,无害)与一条注释。 - 四个宿主都在跑当前代码;标准目录四项全绿。 - 全部服务 active,opencode/网关 cwd 均已在安装根下。
This commit is contained in:
@ -45,17 +45,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import {
|
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
||||||
readFileSync,
|
|
||||||
readdirSync,
|
|
||||||
lstatSync,
|
|
||||||
existsSync,
|
|
||||||
mkdtempSync,
|
|
||||||
writeFileSync,
|
|
||||||
mkdirSync,
|
|
||||||
rmSync,
|
|
||||||
realpathSync
|
|
||||||
} from 'node:fs';
|
|
||||||
import { join, relative, dirname } from 'node:path';
|
import { join, relative, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
@ -410,12 +400,145 @@ export function selfCheck() {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准目录部署检查(2026-09-14)。
|
||||||
|
*
|
||||||
|
* 用户注意到:「当前 agentmail 是在源码目录部署的,应当改为标准目录部署」。
|
||||||
|
* 当时有三处实证:① 失败通知钩子执行的是**仓库里**的脚本(仓库一挪,故障通知静默
|
||||||
|
* 失效);② opencode 服务的 cwd 就是源码目录;③ 仓库里的 `deploy/*.service` 是旧的
|
||||||
|
* 源码目录版本,而机器上的已被改过 —— 谁跑一次 install.sh 就把部署退回源码目录。
|
||||||
|
*
|
||||||
|
* 现在:单元与 drop-in 的唯一真相是 `deploy/systemd/`(镜像 systemd 目录结构),
|
||||||
|
* 运行时脚本装在 `/opt/agentmail/bin/`,服务不依赖仓库是否存在。
|
||||||
|
*
|
||||||
|
* @param {object} [inject] 注入点(判据自检时喂假文件系统)
|
||||||
|
*/
|
||||||
|
export function checkLayout(inject = {}) {
|
||||||
|
const readdir = inject.readdir ?? readdirSync;
|
||||||
|
const readFile = inject.readFile ?? readFileSync;
|
||||||
|
const exists = inject.exists ?? existsSync;
|
||||||
|
const stat = inject.stat ?? statSync;
|
||||||
|
const out = [];
|
||||||
|
const push = (name, ok, note = '') => out.push({ name, ok, note });
|
||||||
|
|
||||||
|
const SYS = '/etc/systemd/system';
|
||||||
|
const REPO = '/home/program/agentmail';
|
||||||
|
// 自有进程必须住在安装根下;其余宿主有它们自己的标准位置(不是本项目的源码目录)
|
||||||
|
const HOST_ALLOW = {
|
||||||
|
'homeagent.service': '/home/newqqagent',
|
||||||
|
'dsh.service': '/usr/bin/dsh',
|
||||||
|
'zcode.service': '/opt/ZCode'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ① 任何 unit/drop-in 都不得引用源码目录
|
||||||
|
const offenders = [];
|
||||||
|
const walk = dir => {
|
||||||
|
let entries = [];
|
||||||
|
try { entries = readdir(dir, { withFileTypes: true }); } catch { return; }
|
||||||
|
for (const e of entries) {
|
||||||
|
const full = `${dir}/${e.name}`;
|
||||||
|
if (e.isDirectory()) walk(full);
|
||||||
|
else if (/\.(conf|service|timer)$/.test(e.name) && !e.name.includes('.bak')) {
|
||||||
|
let text = '';
|
||||||
|
try { text = String(readFile(full, 'utf8')); } catch { continue; }
|
||||||
|
if (text.includes(REPO)) offenders.push(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
walk(SYS);
|
||||||
|
push('没有任何 unit/drop-in 引用源码目录', offenders.length === 0, offenders.join(' '));
|
||||||
|
|
||||||
|
// ② 已安装单元与仓库副本一致(仓库是唯一真相)
|
||||||
|
const drift = [];
|
||||||
|
const repoUnits = inject.repoUnits ?? new URL('../systemd', import.meta.url).pathname.replace(/\/$/, '');
|
||||||
|
const compare = dir => {
|
||||||
|
let entries = [];
|
||||||
|
try { entries = readdir(dir, { withFileTypes: true }); } catch { return; }
|
||||||
|
for (const e of entries) {
|
||||||
|
const full = `${dir}/${e.name}`;
|
||||||
|
if (e.isDirectory()) compare(full);
|
||||||
|
else {
|
||||||
|
const rel = full.slice(repoUnits.length + 1);
|
||||||
|
const live = `${SYS}/${rel}`;
|
||||||
|
if (!exists(live)) { drift.push(`${rel}(缺)`); continue; }
|
||||||
|
let a = '', b = '';
|
||||||
|
try { a = String(readFile(full, 'utf8')); b = String(readFile(live, 'utf8')); } catch { continue; }
|
||||||
|
if (a !== b) drift.push(rel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
compare(repoUnits);
|
||||||
|
push('已安装单元与 deploy/systemd/ 一致', drift.length === 0, drift.join(' '));
|
||||||
|
|
||||||
|
// ③ 通知脚本在标准位置且可执行
|
||||||
|
const script = '/opt/agentmail/bin/service-failure-notify.mjs';
|
||||||
|
let scriptOk = false;
|
||||||
|
let note = script;
|
||||||
|
try {
|
||||||
|
const st = stat(script);
|
||||||
|
scriptOk = st.isFile() && (st.mode & 0o111) !== 0;
|
||||||
|
if (!scriptOk) note = `${script} 缺执行位`;
|
||||||
|
} catch { note = `${script} 不存在`; }
|
||||||
|
push('故障通知脚本装在 /opt/agentmail/bin/ 且可执行', scriptOk, note);
|
||||||
|
|
||||||
|
// ④ 自有服务的 cwd / ExecStart 不得落在源码目录
|
||||||
|
const badHosts = [];
|
||||||
|
for (const unit of ['agentmail-gateway.service', 'pi-mail-bridge.service', 'opencode-serve.service',
|
||||||
|
'zcode-mail-bridge.service', 'homeagent.service', 'dsh.service', 'zcode.service']) {
|
||||||
|
let text = '';
|
||||||
|
try { text = String(readFile(`${SYS}/${unit}`, 'utf8')); } catch { continue; }
|
||||||
|
const cwd = (text.match(/WorkingDirectory=(.+)/) || [])[1]?.trim() ?? '';
|
||||||
|
const exec = (text.match(/ExecStart=(.+)/) || [])[1]?.trim() ?? '';
|
||||||
|
const allow = HOST_ALLOW[unit];
|
||||||
|
if (allow) {
|
||||||
|
// 用 includes 而不是 startsWith:zcode 的 ExecStart 是
|
||||||
|
// `dbus-run-session -- xvfb-run … /opt/ZCode/zcode …`,宿主路径在中间。
|
||||||
|
if (!text.includes(allow)) badHosts.push(`${unit}(不在 ${allow})`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (cwd.startsWith(REPO) || exec.startsWith(REPO)) badHosts.push(`${unit}(${cwd || exec})`);
|
||||||
|
}
|
||||||
|
push('各服务的工作目录/可执行文件不在源码目录', badHosts.length === 0, badHosts.join(' '));
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 标准目录那组自检:坏样本必须红、干净样本必须绿(证明它不是恒真)。 */
|
||||||
|
export function layoutSelfCheck() {
|
||||||
|
const fake = map => ({
|
||||||
|
readdir: dir => map[dir] ?? [],
|
||||||
|
readFile: p => {
|
||||||
|
if (!(p in map)) throw new Error('ENOENT');
|
||||||
|
return map[p];
|
||||||
|
},
|
||||||
|
exists: p => p in map,
|
||||||
|
stat: () => ({ isFile: () => true, mode: 0o755 }),
|
||||||
|
repoUnits: '/repo/systemd'
|
||||||
|
});
|
||||||
|
const bad = checkLayout(fake({
|
||||||
|
'/etc/systemd/system': [{ name: 'x.service', isDirectory: () => false }],
|
||||||
|
'/etc/systemd/system/x.service': 'ExecStart=/home/program/agentmail/bin/x',
|
||||||
|
'/repo/systemd': [],
|
||||||
|
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||||
|
}));
|
||||||
|
const good = checkLayout(fake({
|
||||||
|
'/etc/systemd/system': [{ name: 'y.service', isDirectory: () => false }],
|
||||||
|
'/etc/systemd/system/y.service': 'ExecStart=/opt/agentmail/agentmail-gateway',
|
||||||
|
'/repo/systemd': [],
|
||||||
|
'/opt/agentmail/bin/service-failure-notify.mjs': 'x'
|
||||||
|
}));
|
||||||
|
return [
|
||||||
|
{ name: '标准目录:引用源码目录的样本必须判红', ok: bad[0].ok === false },
|
||||||
|
{ name: '标准目录:干净样本必须判绿', ok: good[0].ok === true }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const json = process.argv.includes('--json');
|
const json = process.argv.includes('--json');
|
||||||
const wantSelfCheck = process.argv.includes('--self-check');
|
const wantSelfCheck = process.argv.includes('--self-check');
|
||||||
|
|
||||||
if (wantSelfCheck) {
|
if (wantSelfCheck) {
|
||||||
const checks = selfCheck();
|
const checks = selfCheck().concat(layoutSelfCheck());
|
||||||
if (json) console.log(JSON.stringify({ selfCheck: checks }, null, 2));
|
if (json) console.log(JSON.stringify({ selfCheck: checks }, null, 2));
|
||||||
else {
|
else {
|
||||||
console.log('判据自检(先证明检查器能发现差异):');
|
console.log('判据自检(先证明检查器能发现差异):');
|
||||||
@ -428,20 +551,25 @@ function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const layout = checkLayout();
|
||||||
|
const layoutBad = layout.filter(c => !c.ok);
|
||||||
|
|
||||||
const results = HOSTS.map(checkHost);
|
const results = HOSTS.map(checkHost);
|
||||||
if (json) {
|
if (json) {
|
||||||
console.log(JSON.stringify({ hosts: results }, null, 2));
|
console.log(JSON.stringify({ hosts: results, layout }, null, 2));
|
||||||
} else {
|
} else {
|
||||||
console.log('\n部署漂移检查(快照 vs 仓库 HEAD,以及进程到底在跑哪份代码):');
|
console.log('\n部署漂移检查(快照 vs 仓库 HEAD,以及进程到底在跑哪份代码):');
|
||||||
for (const r of results) {
|
for (const r of results) {
|
||||||
console.log(`\n ${r.host}(${r.plugin},加载方式 ${r.load})${r.stale ? ' ⚠️ 漂移' : ''}`);
|
console.log(`\n ${r.host}(${r.plugin},加载方式 ${r.load})${r.stale ? ' ⚠️ 漂移' : ''}`);
|
||||||
for (const c of r.checks) console.log(` ${c.ok ? '通过' : '失败'} ${c.name}${c.note ? ' — ' + c.note : ''}`);
|
for (const c of r.checks) console.log(` ${c.ok ? '通过' : '失败'} ${c.name}${c.note ? ' — ' + c.note : ''}`);
|
||||||
}
|
}
|
||||||
|
console.log('\n 标准目录部署:');
|
||||||
|
for (const c of layout) console.log(` ${c.ok ? '通过' : '失败'} ${c.name}${c.note ? ' — ' + c.note : ''}`);
|
||||||
const stale = results.filter(r => r.stale);
|
const stale = results.filter(r => r.stale);
|
||||||
console.log(`\n 结论:${stale.length === 0 ? '四个宿主都在跑当前代码' : `${stale.length} 个宿主需要重新部署/重启`}`);
|
console.log(`\n 结论:${stale.length === 0 ? '四个宿主都在跑当前代码' : `${stale.length} 个宿主需要重新部署/重启`}`);
|
||||||
for (const r of stale) console.log(` ✗ ${r.host}:node deploy/redeploy-plugin.sh ${r.host}`);
|
for (const r of stale) console.log(` ✗ ${r.host}:node deploy/redeploy-plugin.sh ${r.host}`);
|
||||||
}
|
}
|
||||||
process.exit(results.some(r => r.stale) ? 1 : 0);
|
process.exit(results.some(r => r.stale) || layoutBad.length ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 仅在被直接执行时跑 main(被 import 时只导出,供测试调用)
|
// 仅在被直接执行时跑 main(被 import 时只导出,供测试调用)
|
||||||
|
|||||||
@ -186,9 +186,17 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> 安装 systemd 单元"
|
echo "==> 安装 systemd 单元"
|
||||||
install -m 0644 "$REPO/deploy/agentmail-gateway.service" /etc/systemd/system/
|
# 单元与 drop-in 的**唯一真相**在 deploy/systemd/(镜像 systemd 的目录结构)。
|
||||||
install -m 0644 "$REPO/deploy/opencode-serve.service" /etc/systemd/system/
|
#
|
||||||
install -m 0644 "$REPO/deploy/pi-mail-bridge.service" /etc/systemd/system/
|
# 以前这里逐个 install 三个 .service,而 drop-in(失败通知/重启退避等)只存在于
|
||||||
|
# /etc 里 —— 于是"仓库里的是旧的、机器上的是新的",谁跑一次本脚本就把部署退回
|
||||||
|
# 源码目录(旧版 ExecStart 指向 /home/program/agentmail/plugins/...)。
|
||||||
|
install -d /opt/agentmail/bin
|
||||||
|
install -m 0755 "$REPO/deploy/service-failure-notify.mjs" /opt/agentmail/bin/service-failure-notify.mjs
|
||||||
|
while IFS= read -r f; do
|
||||||
|
rel="${f#"$REPO/deploy/systemd/"}"
|
||||||
|
install -D -m 0644 "$f" "/etc/systemd/system/$rel"
|
||||||
|
done < <(find "$REPO/deploy/systemd" -type f)
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
echo "==> 启用并启动"
|
echo "==> 启用并启动"
|
||||||
|
|||||||
@ -83,6 +83,12 @@ if [ "$SYNC_WEB" = 1 ] && [ -d "$REPO/client/electron/dist/assets" ]; then
|
|||||||
run "rm -f '$REPO/server/internal/static/static/index.html'"
|
run "rm -f '$REPO/server/internal/static/static/index.html'"
|
||||||
run "cp -r '$REPO/client/electron/dist/.' '$REPO/server/internal/static/static/'"
|
run "cp -r '$REPO/client/electron/dist/.' '$REPO/server/internal/static/static/'"
|
||||||
ok "前端产物已同步"
|
ok "前端产物已同步"
|
||||||
|
|
||||||
|
# 运行时脚本必须装在安装根下 —— 单元/drop-in 里引用的是
|
||||||
|
# /opt/agentmail/bin/service-failure-notify.mjs,不是仓库路径。
|
||||||
|
# 漏了这一步,故障通知会在"仓库被挪走/改名"时静默失效(2026-09-14 修的就是这个)。
|
||||||
|
install -d /opt/agentmail/bin
|
||||||
|
install -m 0755 "$REPO/deploy/service-failure-notify.mjs" /opt/agentmail/bin/service-failure-notify.mjs
|
||||||
else
|
else
|
||||||
say "1. 跳过前端同步"
|
say "1. 跳过前端同步"
|
||||||
[ "$SYNC_WEB" = 0 ] && ok "--skip-web" || warn "client/electron/dist 不存在,先跑 cd client/electron && npm run build"
|
[ "$SYNC_WEB" = 0 ] && ok "--skip-web" || warn "client/electron/dist 不存在,先跑 cd client/electron && npm run build"
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
# 按目录名从 /etc/agentmail/<agent>.env 解析各自的身份再投。
|
# 按目录名从 /etc/agentmail/<agent>.env 解析各自的身份再投。
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=AgentMail 故障通知补投(排空 spool)
|
Description=AgentMail 故障通知补投(排空 spool)
|
||||||
Documentation=file:///home/program/agentmail/docs/PLAN.md
|
Documentation=file:///opt/agentmail/docs/PLAN.md
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --flush-all
|
ExecStart=/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush-all
|
||||||
10
deploy/systemd/agentmail-failure-flush.timer
Normal file
10
deploy/systemd/agentmail-failure-flush.timer
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=每 10 分钟补投 AgentMail 故障通知
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=10min
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# 2026-09-05 systemd 审计添加的 drop-in(原单元文件未改动)。
|
||||||
|
#
|
||||||
|
# 为什么加:本机的重启限速器其实是死代码。
|
||||||
|
# StartLimitBurst=5 + StartLimitIntervalUSec=10s 的含义是「10 秒内启动 5 次
|
||||||
|
# 就放弃」,但这些单元的 RestartSec 都在 2~10s 之间 —— 5 次重启必然跨过
|
||||||
|
# 10 秒,计数器每次都在触发前清零。于是一个永久坏掉的服务会以固定频率
|
||||||
|
# 无限重试,systemd 从头到尾不会说一句话。
|
||||||
|
#
|
||||||
|
# 实测后果(2026-09-05 07:54–09:13):llmsproxy 因为 config.yaml 里 headers
|
||||||
|
# 键重复而无法解析、退出码 1,systemd 每 5 秒拉一次,连拉 79 分钟 ——
|
||||||
|
# 836 次重启,占掉本次启动 986 条 "Failed with result" 里的 901 条,
|
||||||
|
# 而 "start request repeated too quickly" 一次都没出现过。
|
||||||
|
#
|
||||||
|
# 怎么修:改成指数退避。重试间隔从该单元自己的 RestartSec 出发,经 RestartSteps
|
||||||
|
# 级递增到 RestartMaxDelaySec,崩溃循环会衰减到每 5 分钟一次。服务不会被放弃
|
||||||
|
# (病根修好后它仍会自己回来),日志也不再被刷爆。
|
||||||
|
#
|
||||||
|
# 故意不动的东西:StartLimitBurst / StartLimitIntervalUSec。收紧它们会让故障单元
|
||||||
|
# 进入终态 failed、必须人工 systemctl reset-failed 才能复活 —— 这正好和这些
|
||||||
|
# 长驻 agent 依赖的自愈行为相反。
|
||||||
|
[Service]
|
||||||
|
RestartSteps=6
|
||||||
|
RestartMaxDelaySec=5min
|
||||||
24
deploy/systemd/dsh.service
Normal file
24
deploy/systemd/dsh.service
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=DeepSeek Harness (dsh) Web UI on loopback
|
||||||
|
After=network-online.target llmsproxy.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Environment="NODE_ENV=production"
|
||||||
|
Environment="DEVECO_CLI_CLT_PATH=/opt/huawei/command-line-tools"
|
||||||
|
Environment="DEVECO_HOME=/opt/huawei/command-line-tools"
|
||||||
|
Environment="DSH_WEB_FETCH_PROVIDER=http"
|
||||||
|
Environment=AGENTMAIL_GATEWAY_URL=http://127.0.0.1:8180
|
||||||
|
Environment=AGENTMAIL_AGENT_NAME=dsh
|
||||||
|
Environment=AGENTMAIL_AGENT_KEY=ak_dsh_aae41c5a77592c185b29d4bdf2506b43c22e7db0209a63eb
|
||||||
|
Environment=AGENTMAIL_REPLY_PROVIDER=llmsproxy
|
||||||
|
Environment=AGENTMAIL_REPLY_MODEL=AUTO
|
||||||
|
Environment=DEEPSEEK_API_KEY=sk-gw-8100222100bccec24320c1d92d59d63d
|
||||||
|
ExecStart=/usr/bin/dsh web --host 127.0.0.1 --port 3080 --no-open --trusted-host harness.jianfgit.xyz --trusted-host 192.168.2.60 --trusted-host 192.168.2.60:3080
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
TimeoutStopSec=15
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
deploy/systemd/dsh.service.d/lan-forward.conf
Normal file
14
deploy/systemd/dsh.service.d/lan-forward.conf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# dsh 启动时把 LAN 转发一起带起来。
|
||||||
|
#
|
||||||
|
# 依赖关系分两个方向,两边各管一半:
|
||||||
|
# dsh-lan.service 里的 PartOf=dsh.service → dsh 停止/重启时 socat 跟着停/重启
|
||||||
|
# 本文件的 Wants=dsh-lan.service → dsh 启动时 socat 跟着启动
|
||||||
|
#
|
||||||
|
# 少了这一半的症状:`systemctl stop dsh && systemctl start dsh` 之后
|
||||||
|
# dsh 活着、本地 3080 通,但 LAN 与公网全不通 —— socat 没人拉起来。
|
||||||
|
# 单元自己的 WantedBy=multi-user.target 只在开机时生效,手工 start 不走那条路。
|
||||||
|
#
|
||||||
|
# 用 Wants 而不是 Requires:转发层起不来不该阻止 dsh 本身启动,
|
||||||
|
# 本地 127.0.0.1:3080 仍然可用。
|
||||||
|
[Unit]
|
||||||
|
Wants=dsh-lan.service
|
||||||
7
deploy/systemd/dsh.service.d/zz-failure-notify.conf
Normal file
7
deploy/systemd/dsh.service.d/zz-failure-notify.conf
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Service]
|
||||||
|
# 桥进程异常退出(SIGKILL / OOM / timeout / watchdog / 非零退出)时发一封邮件。
|
||||||
|
# 正常 stop/restart 不上报:脚本内 isAbnormalExit 会按 SERVICE_RESULT 提前返回。
|
||||||
|
# 进程内 uncaughtException 捕获不了 SIGKILL/OOM,所以由 systemd 统一覆盖。
|
||||||
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service dsh.service
|
||||||
|
# 下次启动补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
||||||
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service dsh.service
|
||||||
23
deploy/systemd/dsh.service.d/zz-restart-backoff.conf
Normal file
23
deploy/systemd/dsh.service.d/zz-restart-backoff.conf
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 2026-09-05 systemd 审计添加的 drop-in(原单元文件未改动)。
|
||||||
|
#
|
||||||
|
# 为什么加:本机的重启限速器其实是死代码。
|
||||||
|
# StartLimitBurst=5 + StartLimitIntervalUSec=10s 的含义是「10 秒内启动 5 次
|
||||||
|
# 就放弃」,但这些单元的 RestartSec 都在 2~10s 之间 —— 5 次重启必然跨过
|
||||||
|
# 10 秒,计数器每次都在触发前清零。于是一个永久坏掉的服务会以固定频率
|
||||||
|
# 无限重试,systemd 从头到尾不会说一句话。
|
||||||
|
#
|
||||||
|
# 实测后果(2026-09-05 07:54–09:13):llmsproxy 因为 config.yaml 里 headers
|
||||||
|
# 键重复而无法解析、退出码 1,systemd 每 5 秒拉一次,连拉 79 分钟 ——
|
||||||
|
# 836 次重启,占掉本次启动 986 条 "Failed with result" 里的 901 条,
|
||||||
|
# 而 "start request repeated too quickly" 一次都没出现过。
|
||||||
|
#
|
||||||
|
# 怎么修:改成指数退避。重试间隔从该单元自己的 RestartSec 出发,经 RestartSteps
|
||||||
|
# 级递增到 RestartMaxDelaySec,崩溃循环会衰减到每 5 分钟一次。服务不会被放弃
|
||||||
|
# (病根修好后它仍会自己回来),日志也不再被刷爆。
|
||||||
|
#
|
||||||
|
# 故意不动的东西:StartLimitBurst / StartLimitIntervalUSec。收紧它们会让故障单元
|
||||||
|
# 进入终态 failed、必须人工 systemctl reset-failed 才能复活 —— 这正好和这些
|
||||||
|
# 长驻 agent 依赖的自愈行为相反。
|
||||||
|
[Service]
|
||||||
|
RestartSteps=6
|
||||||
|
RestartMaxDelaySec=5min
|
||||||
16
deploy/systemd/homeagent.service
Normal file
16
deploy/systemd/homeagent.service
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=HomeAgent - 24/7 AI Butler
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/home/newqqagent
|
||||||
|
ExecStart=/usr/local/bin/homed -data /home/newqqagent
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
Environment=HOME=/root
|
||||||
|
Environment=GOMODCACHE=/root/go/pkg/mod
|
||||||
|
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
6
deploy/systemd/homeagent.service.d/agentmail.conf
Normal file
6
deploy/systemd/homeagent.service.d/agentmail.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[Service]
|
||||||
|
EnvironmentFile=/etc/agentmail/homeagent.env
|
||||||
|
Environment=AGENTMAIL_GATEWAY_URL=http://127.0.0.1:8180
|
||||||
|
# AgentMail 身份,与插件名(homeagent-mail-bridge)分开:
|
||||||
|
# 密钥绑定的是这个名字,用插件名注册会被 Gateway 拒(403 密钥已绑定到别的 Agent)。
|
||||||
|
Environment=AGENTMAIL_AGENT_NAME=homeagent
|
||||||
2
deploy/systemd/homeagent.service.d/onnx.conf
Normal file
2
deploy/systemd/homeagent.service.d/onnx.conf
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[Service]
|
||||||
|
Environment=ONNXRUNTIME_DIR=/opt/onnxruntime
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
[Service]
|
||||||
|
# 桥进程异常退出(SIGKILL / OOM / timeout / watchdog / 非零退出)时发一封邮件。
|
||||||
|
# 正常 stop/restart 不上报:脚本内 isAbnormalExit 会按 SERVICE_RESULT 提前返回。
|
||||||
|
# 进程内 uncaughtException 捕获不了 SIGKILL/OOM,所以由 systemd 统一覆盖。
|
||||||
|
Environment=AGENTMAIL_AGENT_NAME=homeagent
|
||||||
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service homeagent.service
|
||||||
|
# 下次启动补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
||||||
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service homeagent.service
|
||||||
23
deploy/systemd/homeagent.service.d/zz-restart-backoff.conf
Normal file
23
deploy/systemd/homeagent.service.d/zz-restart-backoff.conf
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 2026-09-05 systemd 审计添加的 drop-in(原单元文件未改动)。
|
||||||
|
#
|
||||||
|
# 为什么加:本机的重启限速器其实是死代码。
|
||||||
|
# StartLimitBurst=5 + StartLimitIntervalUSec=10s 的含义是「10 秒内启动 5 次
|
||||||
|
# 就放弃」,但这些单元的 RestartSec 都在 2~10s 之间 —— 5 次重启必然跨过
|
||||||
|
# 10 秒,计数器每次都在触发前清零。于是一个永久坏掉的服务会以固定频率
|
||||||
|
# 无限重试,systemd 从头到尾不会说一句话。
|
||||||
|
#
|
||||||
|
# 实测后果(2026-09-05 07:54–09:13):llmsproxy 因为 config.yaml 里 headers
|
||||||
|
# 键重复而无法解析、退出码 1,systemd 每 5 秒拉一次,连拉 79 分钟 ——
|
||||||
|
# 836 次重启,占掉本次启动 986 条 "Failed with result" 里的 901 条,
|
||||||
|
# 而 "start request repeated too quickly" 一次都没出现过。
|
||||||
|
#
|
||||||
|
# 怎么修:改成指数退避。重试间隔从该单元自己的 RestartSec 出发,经 RestartSteps
|
||||||
|
# 级递增到 RestartMaxDelaySec,崩溃循环会衰减到每 5 分钟一次。服务不会被放弃
|
||||||
|
# (病根修好后它仍会自己回来),日志也不再被刷爆。
|
||||||
|
#
|
||||||
|
# 故意不动的东西:StartLimitBurst / StartLimitIntervalUSec。收紧它们会让故障单元
|
||||||
|
# 进入终态 failed、必须人工 systemctl reset-failed 才能复活 —— 这正好和这些
|
||||||
|
# 长驻 agent 依赖的自愈行为相反。
|
||||||
|
[Service]
|
||||||
|
RestartSteps=6
|
||||||
|
RestartMaxDelaySec=5min
|
||||||
@ -5,7 +5,7 @@ Wants=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
WorkingDirectory=/home/program/agentmail
|
WorkingDirectory=/opt/agentmail
|
||||||
ExecStart=/usr/local/bin/opencode serve --port 4097 --hostname 127.0.0.1
|
ExecStart=/usr/local/bin/opencode serve --port 4097 --hostname 127.0.0.1
|
||||||
|
|
||||||
# opencode 靠 HOME 定位 ~/.config/opencode(插件列表、provider 配置、认证)。
|
# opencode 靠 HOME 定位 ~/.config/opencode(插件列表、provider 配置、认证)。
|
||||||
@ -26,11 +26,5 @@ ExecStartPost=/bin/sh -c 'for i in $(seq 1 30); do \
|
|||||||
sleep 1; \
|
sleep 1; \
|
||||||
done; true'
|
done; true'
|
||||||
|
|
||||||
# 异常退出邮件上报:进程内钩子捕获不了 SIGKILL/OOM,只能由 systemd 覆盖。
|
|
||||||
# 正常 stop/restart 不上报(脚本内 isAbnormalExit 提前返回)。
|
|
||||||
ExecStopPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --report --service opencode-serve.service
|
|
||||||
# 补发上次 Gateway 不可达时暂存的报告
|
|
||||||
ExecStartPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --flush --service opencode-serve.service
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
[Service]
|
||||||
|
# 桥进程异常退出(SIGKILL / OOM / timeout / watchdog / 非零退出)时发一封邮件。
|
||||||
|
# 正常 stop/restart 不上报:脚本内 isAbnormalExit 会按 SERVICE_RESULT 提前返回。
|
||||||
|
# 进程内 uncaughtException 捕获不了 SIGKILL/OOM,所以由 systemd 统一覆盖。
|
||||||
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service opencode-serve.service
|
||||||
|
# 下次启动补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
||||||
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service opencode-serve.service
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# 2026-09-05 systemd 审计添加的 drop-in(原单元文件未改动)。
|
||||||
|
#
|
||||||
|
# 为什么加:本机的重启限速器其实是死代码。
|
||||||
|
# StartLimitBurst=5 + StartLimitIntervalUSec=10s 的含义是「10 秒内启动 5 次
|
||||||
|
# 就放弃」,但这些单元的 RestartSec 都在 2~10s 之间 —— 5 次重启必然跨过
|
||||||
|
# 10 秒,计数器每次都在触发前清零。于是一个永久坏掉的服务会以固定频率
|
||||||
|
# 无限重试,systemd 从头到尾不会说一句话。
|
||||||
|
#
|
||||||
|
# 实测后果(2026-09-05 07:54–09:13):llmsproxy 因为 config.yaml 里 headers
|
||||||
|
# 键重复而无法解析、退出码 1,systemd 每 5 秒拉一次,连拉 79 分钟 ——
|
||||||
|
# 836 次重启,占掉本次启动 986 条 "Failed with result" 里的 901 条,
|
||||||
|
# 而 "start request repeated too quickly" 一次都没出现过。
|
||||||
|
#
|
||||||
|
# 怎么修:改成指数退避。重试间隔从该单元自己的 RestartSec 出发,经 RestartSteps
|
||||||
|
# 级递增到 RestartMaxDelaySec,崩溃循环会衰减到每 5 分钟一次。服务不会被放弃
|
||||||
|
# (病根修好后它仍会自己回来),日志也不再被刷爆。
|
||||||
|
#
|
||||||
|
# 故意不动的东西:StartLimitBurst / StartLimitIntervalUSec。收紧它们会让故障单元
|
||||||
|
# 进入终态 failed、必须人工 systemctl reset-failed 才能复活 —— 这正好和这些
|
||||||
|
# 长驻 agent 依赖的自愈行为相反。
|
||||||
|
[Service]
|
||||||
|
RestartSteps=6
|
||||||
|
RestartMaxDelaySec=5min
|
||||||
@ -9,8 +9,8 @@ Type=simple
|
|||||||
# 桥自己不需要工作目录 —— 每条会话的 cwd 来自邮件寻址的 path 位。
|
# 桥自己不需要工作目录 —— 每条会话的 cwd 来自邮件寻址的 path 位。
|
||||||
# 但 systemd 要求一个存在的目录,且 pi 的 SettingsManager 会在这里找
|
# 但 systemd 要求一个存在的目录,且 pi 的 SettingsManager 会在这里找
|
||||||
# 项目级配置,因此指向仓库而不是 /。
|
# 项目级配置,因此指向仓库而不是 /。
|
||||||
WorkingDirectory=/home/program/agentmail/plugins/pi-mail-bridge
|
WorkingDirectory=/opt/agentmail/plugins/pi-mail-bridge/current
|
||||||
ExecStart=/usr/bin/node /home/program/agentmail/plugins/pi-mail-bridge/src/index.mjs
|
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/)。
|
# pi 靠 HOME 定位 ~/.pi/agent(settings.json、auth.json、models.json、sessions/)。
|
||||||
# systemd 不会自动注入 HOME,不显式给就:
|
# systemd 不会自动注入 HOME,不显式给就:
|
||||||
@ -42,13 +42,7 @@ RestartSec=10
|
|||||||
ExecStartPre=-/bin/rm -f /root/.agentmail-pi/pi-bridge.lock
|
ExecStartPre=-/bin/rm -f /root/.agentmail-pi/pi-bridge.lock
|
||||||
|
|
||||||
# 桥是长驻守护进程,启动即注册 + 立刻打一次心跳 + 订阅 SSE(B-1),
|
# 桥是长驻守护进程,启动即注册 + 立刻打一次心跳 + 订阅 SSE(B-1),
|
||||||
# 不像 opencode 那样惰加载,因此不需要预热。
|
# 不像 opencode 那样惰加载,因此不需要 ExecStartPost 预热。
|
||||||
#
|
|
||||||
# 异常退出邮件上报:进程内 uncaughtException 捕获不了 SIGKILL/OOM,只能由 systemd 覆盖。
|
|
||||||
# 正常 stop/restart 不上报(脚本内 isAbnormalExit 提前返回)。
|
|
||||||
ExecStopPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --report --service pi-mail-bridge.service
|
|
||||||
# 补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
|
||||||
ExecStartPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --flush --service pi-mail-bridge.service
|
|
||||||
|
|
||||||
# pi 会话在内存里持有整条对话,长跑之后常驻几百 MB。给一个上限让它被
|
# pi 会话在内存里持有整条对话,长跑之后常驻几百 MB。给一个上限让它被
|
||||||
# OOM killer 挑中而不是拖垮整机;Restart=always 会把它拉回来。
|
# OOM killer 挑中而不是拖垮整机;Restart=always 会把它拉回来。
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
[Service]
|
||||||
|
# 桥进程异常退出(SIGKILL / OOM / timeout / watchdog / 非零退出)时发一封邮件。
|
||||||
|
# 正常 stop/restart 不上报:脚本内 isAbnormalExit 会按 SERVICE_RESULT 提前返回。
|
||||||
|
# 进程内 uncaughtException 捕获不了 SIGKILL/OOM,所以由 systemd 统一覆盖。
|
||||||
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service pi-mail-bridge.service
|
||||||
|
# 下次启动补发上次 Gateway 不可达时暂存的报告(per-agent spool,不跨 Agent 混发)
|
||||||
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service pi-mail-bridge.service
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# 2026-09-05 systemd 审计添加的 drop-in(原单元文件未改动)。
|
||||||
|
#
|
||||||
|
# 为什么加:本机的重启限速器其实是死代码。
|
||||||
|
# StartLimitBurst=5 + StartLimitIntervalUSec=10s 的含义是「10 秒内启动 5 次
|
||||||
|
# 就放弃」,但这些单元的 RestartSec 都在 2~10s 之间 —— 5 次重启必然跨过
|
||||||
|
# 10 秒,计数器每次都在触发前清零。于是一个永久坏掉的服务会以固定频率
|
||||||
|
# 无限重试,systemd 从头到尾不会说一句话。
|
||||||
|
#
|
||||||
|
# 实测后果(2026-09-05 07:54–09:13):llmsproxy 因为 config.yaml 里 headers
|
||||||
|
# 键重复而无法解析、退出码 1,systemd 每 5 秒拉一次,连拉 79 分钟 ——
|
||||||
|
# 836 次重启,占掉本次启动 986 条 "Failed with result" 里的 901 条,
|
||||||
|
# 而 "start request repeated too quickly" 一次都没出现过。
|
||||||
|
#
|
||||||
|
# 怎么修:改成指数退避。重试间隔从该单元自己的 RestartSec 出发,经 RestartSteps
|
||||||
|
# 级递增到 RestartMaxDelaySec,崩溃循环会衰减到每 5 分钟一次。服务不会被放弃
|
||||||
|
# (病根修好后它仍会自己回来),日志也不再被刷爆。
|
||||||
|
#
|
||||||
|
# 故意不动的东西:StartLimitBurst / StartLimitIntervalUSec。收紧它们会让故障单元
|
||||||
|
# 进入终态 failed、必须人工 systemctl reset-failed 才能复活 —— 这正好和这些
|
||||||
|
# 长驻 agent 依赖的自愈行为相反。
|
||||||
|
[Service]
|
||||||
|
RestartSteps=6
|
||||||
|
RestartMaxDelaySec=5min
|
||||||
@ -1,6 +1,6 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=zcode mail-bridge (AgentMail ↔ ZCode headless CLI)
|
Description=zcode mail-bridge (AgentMail ↔ ZCode headless CLI)
|
||||||
Documentation=file:/home/program/agentmail/plugins/zcode-mail-bridge/README.md
|
Documentation=file:/opt/agentmail/plugins/zcode-mail-bridge/README.md
|
||||||
After=network-online.target agentmail-gateway.service
|
After=network-online.target agentmail-gateway.service
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
|
|
||||||
@ -29,9 +29,9 @@ RestartSec=10
|
|||||||
|
|
||||||
# 异常退出邮件上报:进程内的钩子捕获不了 SIGKILL/OOM,只能由 systemd 覆盖。
|
# 异常退出邮件上报:进程内的钩子捕获不了 SIGKILL/OOM,只能由 systemd 覆盖。
|
||||||
# 正常 stop/restart 不上报(脚本内 isAbnormalExit 提前返回)。
|
# 正常 stop/restart 不上报(脚本内 isAbnormalExit 提前返回)。
|
||||||
ExecStopPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --report --service zcode-mail-bridge.service
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service zcode-mail-bridge.service
|
||||||
# 补发上次网关不可达时暂存的报告(per-agent spool)
|
# 补发上次网关不可达时暂存的报告(per-agent spool)
|
||||||
ExecStartPost=-/usr/bin/node /home/program/agentmail/deploy/service-failure-notify.mjs --flush --service zcode-mail-bridge.service
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service zcode-mail-bridge.service
|
||||||
|
|
||||||
# 一轮 ZCode 会派生一个 node 进程(模型 + 工具),内存占用比另三个桥高。
|
# 一轮 ZCode 会派生一个 node 进程(模型 + 工具),内存占用比另三个桥高。
|
||||||
# 给一个上限让它被 OOM killer 挑中而不是拖垮整机;Restart=always 会拉回来。
|
# 给一个上限让它被 OOM killer 挑中而不是拖垮整机;Restart=always 会拉回来。
|
||||||
48
deploy/systemd/zcode.service
Normal file
48
deploy/systemd/zcode.service
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=ZCode headless (Xvfb 虚拟屏) —— 供 Web 远控 / 官方中转访问
|
||||||
|
Documentation=https://zcode.z.ai
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
# 为什么用虚拟屏而不是 --headless:ZCode 的 Web 远控(web-remote-control)是
|
||||||
|
# **桌面端发起**的功能(界面上有 copyLink / 二维码,"Try again from desktop")。
|
||||||
|
# 它没有对应的命令行开关 —— 我一开始把 `--web-remote-navigation-height`(CSS 变量)
|
||||||
|
# 与 `--remote-debugging-port`(Chrome)误读成 ZCode 的开关了。
|
||||||
|
# 所以要让远控可用,就必须真的把桌面应用跑起来,而无头服务器上没有显示 →
|
||||||
|
# 用 Xvfb 提供一个。
|
||||||
|
#
|
||||||
|
# --no-sandbox:Electron 以 root 运行时拒绝启用沙箱(会 FATAL 退出)。
|
||||||
|
# 这是本机的既有取舍(其它服务也以 root 跑),但要知道它降低了渲染进程的隔离。
|
||||||
|
#
|
||||||
|
# --remote-debugging-port:暴露 CDP,用于程序化驱动界面(登录、开启远控、
|
||||||
|
# 截图核对状态),不必依赖 xdotool 之类的坐标点击。
|
||||||
|
#
|
||||||
|
# 为什么套一层 dbus-run-session:Electron 的**单实例 / 深链转发**依赖会话总线,
|
||||||
|
# 而下面把 DBUS_SESSION_BUS_ADDRESS 设成了 disabled:(无会话总线时的既有噪声)。
|
||||||
|
# 这里用 dbus-run-session 给本服务单独起一个会话总线 —— 只为该子进程提供,
|
||||||
|
# **不动系统总线配置**。
|
||||||
|
#
|
||||||
|
# 启动命令只在此处定义一次(唯一的 ExecStart)。需要改启动方式就改这里,
|
||||||
|
# 不要在 drop-in 里整条重写 —— drop-in 重写会**静默盖住**本行,
|
||||||
|
# 导致这里的改动不生效(历史上已因此出现过崩溃循环)。
|
||||||
|
ExecStart=/usr/bin/dbus-run-session -- /usr/bin/xvfb-run -a -s "-screen 0 1600x1000x24" /opt/ZCode/zcode --no-sandbox --disable-gpu --remote-debugging-port=9333
|
||||||
|
|
||||||
|
Environment=HOME=/root
|
||||||
|
# 把 xdg-open 包装放到 PATH 最前:用于抓取 shell.openExternal 的目标 URL
|
||||||
|
# (无头机上没有浏览器,否则这个信息会丢失,登录链接就拿不到)
|
||||||
|
Environment=PATH=/root/gotmp/zcodewrap:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
Environment=ELECTRON_DISABLE_SECURITY_WARNINGS=1
|
||||||
|
# 无会话总线时的既有噪声(dbus 报错),不影响功能,不因此改系统总线配置
|
||||||
|
Environment=DBUS_SESSION_BUS_ADDRESS=disabled:
|
||||||
|
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# 崩溃可见性:与 AgentMail 其它宿主同款约定(失败要有人知道,不能静默)
|
||||||
|
ExecStopPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --report --service zcode.service
|
||||||
|
ExecStartPost=-/usr/bin/node /opt/agentmail/bin/service-failure-notify.mjs --flush --service zcode.service
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
deploy/systemd/zcode.service.d/10-dbus.conf
Normal file
14
deploy/systemd/zcode.service.d/10-dbus.conf
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Service]
|
||||||
|
# 清掉主单元的 DBUS_SESSION_BUS_ADDRESS=disabled(Electron 单实例 / 深链转发依赖 DBus)。
|
||||||
|
#
|
||||||
|
# 本文件**只覆盖这一个环境变量**。启动命令(ExecStart,已含 dbus-run-session 包装)
|
||||||
|
# 由主单元 /etc/systemd/system/zcode.service 唯一定义。
|
||||||
|
#
|
||||||
|
# 历史教训:本文件曾经写成 `ExecStart=` 清空 + 整条重写来加 dbus-run-session 包装。
|
||||||
|
# 那样做的两个问题:
|
||||||
|
# 1. 它**静默盖住**主单元的 ExecStart —— 以后改主单元的启动参数不会生效,
|
||||||
|
# 且看 `systemctl cat` 才能发现(排查时极易误判)
|
||||||
|
# 2. 主单元那条 ExecStart 原本是「反斜杠续行」的两行,抄过来时续行被压平成空格,
|
||||||
|
# 一度出现过 `xvfb-run: 200: 0: not found` 的崩溃循环
|
||||||
|
# 所以包装归主单元,drop-in 只管它名字里那件事(dbus 环境变量)。
|
||||||
|
Environment=DBUS_SESSION_BUS_ADDRESS=
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# zcode 邮件驱动(deploy/zcode-mail-bridge.service)的 EnvironmentFile。
|
# zcode 邮件驱动(deploy/systemd/zcode-mail-bridge.service)的 EnvironmentFile。
|
||||||
#
|
#
|
||||||
# 与 pi / opencode / dsh 三桥**同一套变量名** —— 同名同义,排障时才不用
|
# 与 pi / opencode / dsh 三桥**同一套变量名** —— 同名同义,排障时才不用
|
||||||
# 每换一个平台就重新记一遍。
|
# 每换一个平台就重新记一遍。
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
# dsh-mail-bridge bundle patch.
|
# dsh-mail-bridge bundle patch.
|
||||||
# 安装方式: dsh plugin --profile web add link:/home/program/agentmail/plugins/dsh-mail-bridge
|
# 安装方式(标准位置,见 deploy/systemd/):
|
||||||
|
# dsh plugin --profile web add link:/opt/agentmail/plugins/dsh-mail-bridge/current
|
||||||
|
# 注意:不要指向源码目录 —— 生产不跑仓库工作区(快照 + 原子软链切换)。
|
||||||
- insert:
|
- insert:
|
||||||
- id: dsh-mail-bridge
|
- id: dsh-mail-bridge
|
||||||
name: dsh-mail-bridge
|
name: dsh-mail-bridge
|
||||||
|
|||||||
Reference in New Issue
Block a user