'use strict'; const express = require('express'); const { WebSocketServer } = require('ws'); const pty = require('node-pty'); const http = require('http'); const https = require('https'); const fs = require('fs'); const path = require('path'); const os = require('os'); const app = express(); const PORT = process.env.PORT || 3333; app.use(express.static(path.join(__dirname))); // ホームディレクトリのTailscale証明書を自動検出 function findCert() { const home = os.homedir(); const crt = fs.readdirSync(home).find((f) => f.endsWith('.crt')); if (!crt) return null; const key = crt.replace('.crt', '.key'); if (!fs.existsSync(path.join(home, key))) return null; return { cert: fs.readFileSync(path.join(home, crt)), key: fs.readFileSync(path.join(home, key)) }; } const tlsOpts = findCert(); const server = tlsOpts ? https.createServer(tlsOpts, app) : http.createServer(app); const proto = tlsOpts ? 'https' : 'http'; const wss = new WebSocketServer({ server, path: '/terminal' }); wss.on('connection', (ws) => { const shell = process.env.SHELL || '/bin/bash'; const ptyProc = pty.spawn(shell, [], { name: 'xterm-256color', cols: 80, rows: 24, cwd: process.env.HOME || '/home/ubuntu-pc', env: process.env }); ptyProc.onData((data) => { if (ws.readyState === 1) { ws.send(JSON.stringify({ type: 'output', data })); } }); ws.on('message', (raw) => { try { const msg = JSON.parse(raw.toString()); if (msg.type === 'input') ptyProc.write(msg.data); if (msg.type === 'resize') ptyProc.resize(Number(msg.cols), Number(msg.rows)); } catch {} }); ws.on('close', () => { try { ptyProc.kill(); } catch {} }); ptyProc.onExit(() => { try { ws.close(); } catch {} }); }); server.listen(PORT, '0.0.0.0', () => { console.log(`posimai-dev running on ${proto}://0.0.0.0:${PORT}`); });