이미지스튜디오를 ComfyUI 백엔드로 전환, 원본 UI 임베드, LTX 비디오 고화질 옵션 추가

diffusers venv 스크립트 방식 대신 상시구동 ComfyUI(systemd, GPU1)를 통해
SDXL/FLUX/PuLID 스타일변환/LTX 비디오를 생성하도록 전환. ComfyUI 자체 웹
UI를 리버스 프록시로 스튜디오 앱에 같은 도메인으로 임베드(routes-comfyui.ts)
하고, LTX-Video에 non-distilled "dev" 체크포인트 기반 고화질 옵션을 추가.
날씨 앱에는 Windy 제트기류/태풍 마커, 게이트웨이에는 wol-gate 웨이크 로그
조회 API도 함께 반영.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
kim
2026-08-05 17:21:36 +09:00
co-authored by Claude Sonnet 5
parent 675ee75de5
commit 6014e51ba3
7 changed files with 566 additions and 329 deletions
+51
View File
@@ -51,6 +51,7 @@ import {
} from './guards/prompt-gates';
import { registerArduinoRoutes } from './routes/routes-arduino';
import { registerAndroidRoutes, attachAndroidWsProxy } from './routes/routes-android';
import { registerComfyUIRoutes, attachComfyUIWsProxy } from './routes/routes-comfyui';
import { registerBgTasksRoutes, loadTaskHeartbeatConfig } from './routes/routes-bg-tasks';
import { registerKakaoRoutes } from './routes/routes-kakao';
import { registerScheduleRoutes } from './routes/routes-schedules';
@@ -3596,6 +3597,54 @@ app.get('/api/logs', (req, res) => {
res.json({ entries: logRing.slice(), totalCount: logTotalCount });
});
// ── wol-gate wake log (TrueNAS 컨테이너 로그를 SSH로 가져옴) ────────────────────
app.get('/api/wol-gate-log', async (req, res) => {
const authCfg = getAuthConfig();
if (authCfg.enabled) {
const sess = getSessionUser(req);
if (!sess) return res.status(401).json({ error: 'Unauthorized' });
}
const { execFile } = await import('child_process');
execFile(
'ssh',
['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', 'truenas', 'docker', 'logs', '-t', 'ix-wol-gate-wol-gate-1', '--tail', '40'],
{ timeout: 8000 },
(err, stdout, stderr) => {
if (err) {
res.status(502).json({ error: (stderr || err.message || 'ssh failed').trim() });
return;
}
const triggerRe = /triggered by (\S+) "(\S+) ([^"]*)" UA="([^"]*)"/;
const lines = stdout.split('\n').filter(l => l.trim().length > 0).map(line => {
// docker logs -t prefixes each line with an RFC3339 UTC timestamp, e.g.
// "2026-08-04T04:12:34.123456789Z [wol-gate] waking - triggered by ..."
const tsMatch = line.match(/^(\S+)\s(.*)$/);
const rawTs = tsMatch ? tsMatch[1] : null;
const text = tsMatch ? tsMatch[2] : line;
const time = rawTs
? new Date(rawTs).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', hour12: false })
: null;
let type: 'info' | 'success' | 'warn' = 'info';
let event: string | null = null;
if (text.includes('waking - triggered by')) { type = 'success'; event = '깨움'; }
else if (text.includes('wake suppressed')) { type = 'warn'; event = '억제(쿨다운)'; }
const m = text.match(triggerRe);
if (m) {
const [, ip, method, hostPath, ua] = m;
const slashIdx = hostPath.indexOf('/');
const host = slashIdx >= 0 ? hostPath.slice(0, slashIdx) : hostPath;
const path = slashIdx >= 0 ? hostPath.slice(slashIdx) : '';
return { type, time, event, ip, method, host, path, ua, text };
}
return { type, time, event, text };
});
res.json({ lines });
}
);
});
// ── Network speed tracker ──────────────────────────────────────────────────
let _netPrev: { rx: number; tx: number; ts: number } | null = null;
let _netSpeed = { rx_bps: 0, tx_bps: 0, iface: '' };
@@ -4281,6 +4330,7 @@ app.use((err: any, req: express.Request, res: express.Response, _next: express.N
registerArduinoRoutes(app, getSessionUser);
registerAndroidRoutes(app, getSessionUser);
registerComfyUIRoutes(app, getSessionUser);
registerBgTasksRoutes(app, {
getSessionUser,
handleChat,
@@ -4403,6 +4453,7 @@ server.on('upgrade', (req, socket, head) => {
}
});
attachAndroidWsProxy(server, getSessionUser);
attachComfyUIWsProxy(server, getSessionUserFromUpgradeReq);
attachVoiceRealtimeWsProxy(server, getSessionUserFromUpgradeReq);
wss.on('error', (err: any) => {
if (err?.code === 'EADDRINUSE') {