From 5e9928652e7bbaaa8eadfdc828c2573540688784 Mon Sep 17 00:00:00 2001 From: mzl92 Date: Thu, 27 Aug 2026 18:17:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=81=AF=E5=8F=8C=E9=87=8D?= =?UTF-8?q?=E6=A0=B8=E5=AF=B9:=20docker=E5=8F=AA=E8=AF=BB=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=9F=A5runner=E7=9C=9F=E5=AE=9E=E8=BF=9B=E7=A8=8B,?= =?UTF-8?q?=20=E5=BC=BA=E5=81=9C=E4=B8=8D=E5=86=8D=E5=81=87=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E8=BF=90=E8=A1=8C=E4=B8=AD(=E6=96=B0=E5=A2=9E'?= =?UTF-8?q?=E5=B7=B2=E5=81=9C=E6=AD=A2'=E6=80=81);=20=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E5=8D=95=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 ++ public/index.html | 5 +++-- server.js | 38 +++++++++++++++++++++++++++----------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1b12be6..64cbd74 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,8 @@ services: - /opt/hermes/data/recruitment-data:/src # 任务机状态文件(只读, 任务状态灯用) - /opt/hermes/data/logs/recruitment/state:/state:ro + # docker只读接口(任务状态灯核对runner进程是否真实存活) + - /var/run/docker.sock:/var/run/docker.sock:ro # 自身状态(users.db/secret/快照)持久化 - ./data:/data/admin environment: diff --git a/public/index.html b/public/index.html index 102899b..40791ba 100644 --- a/public/index.html +++ b/public/index.html @@ -292,8 +292,9 @@ async function refreshTaskStatus(){ const s=await api('/api/task-status'); $('taskStatus').innerHTML=['hr1','hr2'].map(a=>{ const t=s[a]||{state:'idle',text:'今日未运行'}; - const extra=t.chatLeft!=null?`·剩${t.chatLeft}次`:''; - return `${a} ${t.text}${extra}`; + const extra=t.chatLeft!=null&&t.state==='running'?`·剩${t.chatLeft}次`:''; + const tip=t.detail?`(${t.detail})`:''; + return `${a} ${t.text}${extra}`; }).join(''); }catch(_){} } diff --git a/server.js b/server.js index 6248001..6389d18 100644 --- a/server.js +++ b/server.js @@ -286,22 +286,38 @@ app.post('/api/users/:id/resetpwd', requireAuth, requireSuper, (req, res) => { res.json({ ok: true }); }); -// ---------- 筛选任务状态(所有登录用户, 读runner状态文件) ---------- -app.get('/api/task-status', requireAuth, (req, res) => { +// ---------- 筛选任务状态(所有登录用户) ---------- +// 状态文件 + 进程存活双重核对: runner被强停时不写状态文件,仅信文件会假"运行中" +const http = require('http'); +function hermesRunnerProcs() { + return new Promise((resolve) => { + const req = http.request( + { socketPath: '/var/run/docker.sock', path: '/containers/hermes/top?ps_args=-eo pid,args', method: 'GET', timeout: 3000 }, + (r) => { let d = ''; r.on('data', (c) => d += c); r.on('end', () => { try { resolve(JSON.parse(d).Processes || []); } catch (_) { resolve([]); } }); } + ); + req.on('error', () => resolve([])); + req.end(); + }); +} +app.get('/api/task-status', requireAuth, async (req, res) => { const stateDir = process.env.STATE_DIR || '/state'; const bjToday = new Date(Date.now() + 8 * 3600e3).toISOString().slice(0, 10); const doneSet = new Set(['time_cutoff', 'chat_exhausted', 'done']); - const errSet = new Set(['login_expired', 'alert', 'tunnel_failed', 'platform_blocked', 'qr_expired', 'error', 'stopped_by_user']); + const errSet = new Set(['login_expired', 'alert', 'tunnel_failed', 'platform_blocked', 'qr_expired', 'error']); + const procs = await hermesRunnerProcs(); + const alive = (acct) => procs.some((p) => /daily-runner(\.js)?\s/.test(String(p)) && String(p).includes(acct)); const out = {}; for (const acct of ['hr1', 'hr2']) { - try { - const s = JSON.parse(fs.readFileSync(path.join(stateDir, `state-${acct}.json`), 'utf8')); - if (s.date !== bjToday) { out[acct] = { state: 'idle', text: '今日未运行' }; continue; } - if (s.status === 'running') out[acct] = { state: 'running', text: '运行中', chatLeft: s.chatLeft }; - else if (doneSet.has(s.status)) out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft }; - else if (errSet.has(s.status)) out[acct] = { state: 'error', text: '异常停止', detail: s.status }; - else out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft }; - } catch (_) { out[acct] = { state: 'idle', text: '今日未运行' }; } + let s = null; + try { s = JSON.parse(fs.readFileSync(path.join(stateDir, `state-${acct}.json`), 'utf8')); } catch (_) {} + const isAlive = alive(acct); + if (isAlive) { out[acct] = { state: 'running', text: '运行中', chatLeft: s && s.chatLeft }; continue; } + if (!s || s.date !== bjToday) { out[acct] = { state: 'idle', text: '今日未运行' }; continue; } + if (s.status === 'running') out[acct] = { state: 'stopped', text: '已停止', detail: '状态未更新(进程已退出)' }; + else if (s.status === 'stopped_by_user') out[acct] = { state: 'stopped', text: '手动停止' }; + else if (doneSet.has(s.status)) out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft }; + else if (errSet.has(s.status)) out[acct] = { state: 'error', text: '异常停止', detail: s.status }; + else out[acct] = { state: 'done', text: '已收工', chatLeft: s.chatLeft }; } res.json(out); });