dsh 0.2.0-rc.2: in-container update (experimental channel), path-agnostic compose, upstream patches adapted to 0.1.x/0.2.x

This commit is contained in:
hermes committed 2026-10-01 19:21:19 +00:00
commit e6dcef9bf5
302 files changed
+75178

No files matched your search

@@ -0,0 +1,248 @@
import assert from 'node:assert/strict'
import { request as httpRequest } from 'node:http'
import { mkdir, mkdtemp } from 'node:fs/promises'
import { tmpdir, userInfo } from 'node:os'
import { join } from 'node:path'
import test from 'node:test'
import WebSocket from '../../control-plane/services/management/node_modules/ws/wrapper.mjs'
import { createManagementServer } from '../../control-plane/services/management/server.mjs'
import { TerminalSessionManager, terminalSessionInternals } from '../../control-plane/services/management/terminal/sessions.mjs'
const API = '/_dsh_platform/api/v1/'
function request(port, method, path, body) {
return new Promise((resolve, reject) => {
const encoded = body === undefined ? undefined : Buffer.from(JSON.stringify(body))
const outgoing = httpRequest({
host: '127.0.0.1', port, method, path,
headers: encoded === undefined ? undefined : { 'content-type': 'application/json', 'content-length': encoded.byteLength },
}, response => {
const chunks = []
response.on('data', chunk => chunks.push(chunk))
response.once('end', () => {
const text = Buffer.concat(chunks).toString('utf8')
resolve({ status: response.statusCode, body: text === '' ? undefined : JSON.parse(text) })
})
})
outgoing.once('error', reject)
outgoing.end(encoded)
})
}
function collector(url) {
const socket = new WebSocket(url)
let output = ''
let exit
const waiters = new Set()
socket.on('message', bytes => {
const value = JSON.parse(bytes.toString())
if (value.type === 'output') output += value.data
if (value.type === 'exit') exit = value
for (const waiter of waiters) waiter()
})
const waitFor = async predicate => {
for (let attempt = 0; attempt < 400; attempt += 1) {
if (predicate({ output, exit })) return { output, exit }
await new Promise(resolve => {
const timer = setTimeout(() => { waiters.delete(done); resolve() }, 25)
const done = () => { clearTimeout(timer); waiters.delete(done); resolve() }
waiters.add(done)
})
}
throw new Error(`terminal output did not reach expected state: ${output}`)
}
return { socket, waitFor, output: () => output }
}
async function fixture({ reconnectMs = 30_000 } = {}) {
const root = await mkdtemp(join(tmpdir(), 'dsh-terminal-'))
const workspace = join(root, 'workspace')
const dshHome = join(root, 'dsh')
await mkdir(workspace)
await mkdir(dshHome)
const reports = []
const terminalSessions = new TerminalSessionManager({
cwd: workspace,
dshHome,
reconnectMs,
report: async (message, fields) => { reports.push({ message, fields }) },
})
const logs = {
diagnostic: async () => {}, query: async () => [], on: () => {}, off: () => {},
}
const state = { read: async () => ({}), on: () => {}, off: () => {} }
const coordinator = { publicStatus: async () => ({}), hasActiveTask: () => false, state, on: () => {}, off: () => {} }
const server = createManagementServer({ coordinator, logs, terminalSessions })
await new Promise((resolve, reject) => {
server.once('error', reject)
server.listen(0, '127.0.0.1', resolve)
})
const port = server.address().port
return {
root, workspace, dshHome, reports, terminalSessions, server, port,
close: async () => {
await terminalSessions.shutdown()
await new Promise(resolve => server.close(resolve))
},
}
}
test('terminal HTTP and WebSocket lifecycle provides a real bounded PTY', async () => {
const value = await fixture()
try {
const created = await request(value.port, 'POST', `${API}terminal/sessions`, { cols: 92, rows: 31 })
assert.equal(created.status, 201)
assert.match(created.body.sessionId, terminalSessionInternals.SESSION_PATTERN)
const path = `${API}terminal/sessions/${created.body.sessionId}`
assert.equal((await request(value.port, 'GET', path)).body.status, 'running')
const first = collector(`ws://127.0.0.1:${value.port}${path}/stream`)
await new Promise((resolve, reject) => {
first.socket.once('open', resolve)
first.socket.once('error', reject)
})
first.socket.send(JSON.stringify({ type: 'resize', cols: 101, rows: 37 }))
first.socket.send(JSON.stringify({
type: 'input',
data: "printf '\\033[31mred\\033[0m\\n'; printf '中文✓\\n'; printf 'cwd=%s\\n' \"$PWD\"; printf 'home=%s\\n' \"$HOME\"; printf 'dsh=%s\\n' \"$DSH_HOME\"; printf 'uid=%s\\n' \"$(id -u)\"; printf 'gids=%s\\n' \"$(id -G)\"; printf 'path=%s\\n' \"$PATH\"; printf 'umask=%s\\n' \"$(umask)\"; stty size; printf 'command-complete\\n'\n",
}))
let initial
try {
initial = await first.waitFor(({ output }) => output.includes('\x1b[31mred\x1b[0m') && output.match(/command-complete/g)?.length >= 3)
} catch (error) {
error.message += `; status=${JSON.stringify((await request(value.port, 'GET', path)).body)} reports=${JSON.stringify(value.reports)}`
throw error
}
assert.match(initial.output, /\x1b\[31mred\x1b\[0m/)
assert.match(initial.output, /中文✓/)
assert.match(initial.output, new RegExp(`cwd=${value.workspace.replaceAll('/', '\\/')}`))
assert.match(initial.output, new RegExp(`home=${userInfo().homedir.replaceAll('/', '\\/')}`))
assert.match(initial.output, new RegExp(`dsh=${value.dshHome.replaceAll('/', '\\/')}`))
assert.match(initial.output, new RegExp(`uid=${String(process.getuid())}`))
for (const group of process.getgroups()) assert.match(initial.output, new RegExp(`gids=[^\\r\\n]*\\b${String(group)}\\b`))
assert.match(initial.output, /path=\S+/)
assert.match(initial.output, /umask=0002/)
assert.match(initial.output, /37 101/)
first.socket.send(JSON.stringify({ type: 'input', data: "read -p 'answer: ' value; printf 'interactive=%s\\n' \"$value\"\n" }))
await first.waitFor(({ output }) => output.includes('answer:'))
first.socket.send(JSON.stringify({ type: 'input', data: 'works\n' }))
await first.waitFor(({ output }) => output.includes('interactive=works'))
first.socket.send(JSON.stringify({ type: 'input', data: "printf 'long-start\\n'; head -c 300000 /dev/zero | tr '\\0' x; printf '\\nlong-tail\\n'\n" }))
await first.waitFor(({ output }) => output.length > 300_000 && output.match(/long-tail/g)?.length >= 2)
first.socket.send(JSON.stringify({ type: 'input', data: "printf 'cached-marker\\n'\n" }))
await first.waitFor(({ output }) => output.includes('cached-marker'))
first.socket.close()
await new Promise(resolve => first.socket.once('close', resolve))
const second = collector(`ws://127.0.0.1:${value.port}${path}/stream`)
await new Promise((resolve, reject) => {
second.socket.once('open', resolve)
second.socket.once('error', reject)
})
const replay = (await second.waitFor(({ output }) => output.includes('cached-marker'))).output
assert.match(replay, /long-tail/)
assert.match(replay, /cached-marker/)
assert.doesNotMatch(replay, /long-start/)
assert.ok(Buffer.byteLength(replay) <= 256 * 1024 + 1024)
second.socket.send(JSON.stringify({ type: 'input', data: 'exit 7\n' }))
const ended = await second.waitFor(result => result.exit !== undefined)
assert.deepEqual({ code: ended.exit.code, signal: ended.exit.signal }, { code: 7, signal: null })
assert.equal((await request(value.port, 'GET', path)).body.status, 'exited')
assert.equal((await request(value.port, 'DELETE', path)).body.closed, true)
second.socket.close()
const messages = value.reports.map(entry => entry.message)
for (const event of [
'terminal.session.created', 'terminal.session.connected', 'terminal.session.disconnected',
'terminal.session.reconnected', 'terminal.session.exited', 'terminal.session.closed',
]) assert.ok(messages.includes(event), `${event} was not reported`)
assert.doesNotMatch(JSON.stringify(value.reports), /cached-marker|interactive=works|command-complete/)
} finally {
await value.close()
}
})
test('terminal sessions accept a trusted per-session network environment', async () => {
const value = await fixture()
try {
const created = value.terminalSessions.create({}, {
HTTP_PROXY: 'http://127.0.0.1:17896',
NO_PROXY: 'localhost,.example.com',
})
const path = `${API}terminal/sessions/${created.sessionId}`
const connection = collector(`ws://127.0.0.1:${value.port}${path}/stream`)
await new Promise((resolve, reject) => {
connection.socket.once('open', resolve)
connection.socket.once('error', reject)
})
connection.socket.send(JSON.stringify({
type: 'input',
data: "printf 'proxy=%s\\nno_proxy=%s\\n' \"$HTTP_PROXY\" \"$NO_PROXY\"\n",
}))
const result = await connection.waitFor(({ output }) => output.includes('no_proxy=localhost,.example.com'))
assert.match(result.output, /proxy=http:\/\/127\.0\.0\.1:17896/)
connection.socket.close()
} finally {
await value.close()
}
})
test('terminal sessions enforce dimensions, message size, expiry, and exact IDs', async () => {
const value = await fixture({ reconnectMs: 80 })
try {
for (const body of [{ cols: 1, rows: 24 }, { cols: 501, rows: 24 }, { cols: 80, rows: 0 }, { cols: 80, rows: 201 }]) {
assert.equal((await request(value.port, 'POST', `${API}terminal/sessions`, body)).status, 400)
}
const created = (await request(value.port, 'POST', `${API}terminal/sessions`, {})).body
const path = `${API}terminal/sessions/${created.sessionId}`
const connection = collector(`ws://127.0.0.1:${value.port}${path}/stream`)
await new Promise((resolve, reject) => {
connection.socket.once('open', resolve)
connection.socket.once('error', reject)
})
connection.socket.send(JSON.stringify({ type: 'resize', cols: 1, rows: 20 }))
const closeCode = await new Promise(resolve => connection.socket.once('close', resolve))
assert.equal(closeCode, 1008)
let expiredStatus
for (let attempt = 0; attempt < 100; attempt += 1) {
expiredStatus = (await request(value.port, 'GET', path)).status
if (expiredStatus === 404) break
await new Promise(resolve => setTimeout(resolve, 20))
}
assert.equal(expiredStatus, 404)
const oversized = (await request(value.port, 'POST', `${API}terminal/sessions`, {})).body
const oversizedConnection = collector(`ws://127.0.0.1:${value.port}${API}terminal/sessions/${oversized.sessionId}/stream`)
await new Promise((resolve, reject) => {
oversizedConnection.socket.once('open', resolve)
oversizedConnection.socket.once('error', reject)
})
oversizedConnection.socket.send(JSON.stringify({ type: 'input', data: 'x'.repeat(64 * 1024 + 1) }))
const oversizedClose = await new Promise(resolve => oversizedConnection.socket.once('close', resolve))
assert.ok([1008, 1009].includes(oversizedClose))
assert.equal((await request(value.port, 'GET', `${API}terminal/sessions/not-a-uuid`)).status, 404)
} finally {
await value.close()
}
})
test('terminal sessions default to the Root maintenance home', () => {
const terminalSessions = new TerminalSessionManager()
assert.equal(terminalSessions.cwd, '/root')
})
test('terminal shutdown waits for every helper process to close', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-terminal-shutdown-'))
const workspace = join(root, 'workspace')
const dshHome = join(root, 'dsh')
await mkdir(workspace)
await mkdir(dshHome)
const terminalSessions = new TerminalSessionManager({ cwd: workspace, dshHome })
const created = terminalSessions.create()
const session = terminalSessions.sessions.get(created.sessionId)
assert.equal(session.child.exitCode, null)
await terminalSessions.shutdown()
assert.equal(terminalSessions.sessions.size, 0)
assert.equal(session.child.exitCode !== null || session.child.signalCode !== null, true)
})