import express from 'express' import { WebSocketServer } from 'ws' import { createContainerPty } from './docker.js' import { getContainerPath, runCommand } from './utils.js' import path from 'path' import fs from 'fs/promises' const app = express() app.use(express.json({ strict: false })) // TODO: Convert to spawn + websocket app.get('/api/container/:uuid([a-zA-Z0-9-]+)/exec/shell', async (req, res) => { const uuid = req.params.uuid /** @type {string} */ const command = JSON.parse(req.query.command) try { const output = await runCommand('docker', ['exec', uuid, '/bin/sh', '-c', command]) res.json(output) } catch (e) { res.json({ error: e.toString() }) } }) // app.get('/api/container/:uuid([a-zA-Z0-9-]+)/exec/ls/*', async (req, res) => { // const uuid = req.params.uuid // const p = path.normalize(req.params[0]) // try { // const files = await runCommand('/bin/ls', [path.join(getContainerPath('phc', uuid), p)]) // res.json(files.trim().split('\n')) // } catch (e) { // res.json({ error: e.toString() }) // } // }) app.get('/api/container/:uuid([a-zA-Z0-9-]+)/exec/ls/*', async (req, res) => { const uuid = req.params.uuid const p = path.normalize(req.params[0]) try { const dirPath = path.join(getContainerPath('phc', uuid), p) const fileList = await fs.readdir(dirPath) const result = await Promise.all( fileList.map(async name => { const fullPath = path.join(dirPath, name) const stats = await fs.lstat(fullPath) return { type: stats.isDirectory() ? 'folder' : 'file', name, } }) ) result.sort((e1, e2) => e2.type.localeCompare(e1.type)) res.json(result) } catch (e) { res.json({ error: e.toString() }) } }) app.get('/api/container/:uuid([a-zA-Z0-9-]+)/fs/*', async (req, res) => { const uuid = req.params.uuid const p = path.normalize(req.params[0]) console.log('Getting', p) try { const content = await fs.readFile(path.join(getContainerPath('phc', uuid), p), 'utf8') res.json(content) } catch (e) { res.json({ error: e.toString() }) } }) app.put('/api/container/:uuid([a-zA-Z0-9-]+)/fs/*', async (req, res) => { const uuid = req.params.uuid const p = path.normalize(req.params[0]) const content = req.body console.log(content) try { // await fs.writeFile(path.join(getContainerPath('phc', uuid), p), content) await runCommand('docker', ['exec', '-i', uuid, '/usr/bin/tee', `/project/${p}`], { stdin: content, }) res.json('ok') } catch (e) { res.json({ error: e.toString() }) } }) const server = app.listen(5432, () => { console.log('Starting API server on :5432...') }) const wss = new WebSocketServer({ server }) wss.on('connection', async (ws, req) => { const tag = req.url.split('/').at(-2) console.log(`Client connected to "${tag}"`) const container = await createContainerPty(tag, { onData(data) { ws.send( JSON.stringify({ type: 'pty', data, }) ) }, onExit(e) { ws.send( JSON.stringify({ type: 'destroyed', ...e, }) ) ws.close() }, }) ws.send( JSON.stringify({ type: 'created', id: container.id, }) ) ws.on('message', data => { container.pty.write(data) }) ws.on('close', () => { container.pty.kill() console.log(`Client disconnected`) }) })