feat(terminal): proxy /terminal (+WebSocket) through the Void app to ttyd
Makes the embedded Terminal work via the raw LAN IP too (bypasses Traefik's /terminal route). ttyd base-path preserved; firewall on CT300 opened to the app host. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
server.js
25
server.js
@@ -11,11 +11,29 @@ import { startCron } from './lib/cron/index.js';
|
||||
import { seedFromConfig } from './lib/health/registry.js';
|
||||
import { mcpAuth } from './lib/api/middleware/mcp_auth.js';
|
||||
import { handleMcp } from './lib/mcp/http.js';
|
||||
import httpProxy from 'http-proxy';
|
||||
|
||||
const VERSION = '2.0.0-alpha.16';
|
||||
|
||||
// Proxy /terminal (+ its WebSocket) to ttyd on CT 300, so the embedded terminal
|
||||
// works whether the Void is reached via Traefik (void2-app.hynesy.com) OR the
|
||||
// raw LAN IP (which bypasses Traefik's /terminal route). ttyd's base-path is
|
||||
// /terminal, so the path is preserved as-is.
|
||||
const TTYD_URL = process.env.TTYD_URL || 'http://192.168.1.212:7681';
|
||||
const terminalProxy = httpProxy.createProxyServer({ target: TTYD_URL, ws: true, changeOrigin: true });
|
||||
terminalProxy.on('error', (err) => log.error({ err }, 'terminal proxy error'));
|
||||
const isTerminalPath = (url = '') => url === '/terminal' || url.startsWith('/terminal/') || url.startsWith('/terminal?');
|
||||
|
||||
export function createApp() {
|
||||
const app = express();
|
||||
|
||||
// Terminal proxy first — before body parsing/static — so the raw request
|
||||
// stream is forwarded untouched to ttyd.
|
||||
app.use((req, res, next) => {
|
||||
if (isTerminalPath(req.url)) return terminalProxy.web(req, res, {}, () => { res.status(502).end('terminal unavailable'); });
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(express.json({
|
||||
limit: '10mb',
|
||||
verify: (req, _res, buf) => { req.rawBody = buf; }
|
||||
@@ -71,7 +89,12 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
// One-time bootstrap of the service registry from config/services.json if empty.
|
||||
seedFromConfig().then(n => { if (n) log.info({ seeded: n }, 'monitored_services seeded from config'); })
|
||||
.catch(err => log.error({ err }, 'service registry seed failed'));
|
||||
app.listen(port, () => log.info({ port }, 'void-server listening'));
|
||||
const server = app.listen(port, () => log.info({ port }, 'void-server listening'));
|
||||
// Proxy the terminal's WebSocket upgrade to ttyd as well.
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
if (isTerminalPath(req.url)) terminalProxy.ws(req, socket, head);
|
||||
else socket.destroy();
|
||||
});
|
||||
for (const sig of ['SIGTERM', 'SIGINT']) {
|
||||
process.on(sig, async () => {
|
||||
log.info({ sig }, 'shutting down');
|
||||
|
||||
Reference in New Issue
Block a user