Add shell exec endpoint — POST /exec runs SSH command on any known container host
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
20a8d50084
commit
caf81d5f39
1 changed files with 38 additions and 1 deletions
|
|
@ -121,6 +121,27 @@ async function sshGet(host, cmdArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
async function sshExec(host, command) {
|
||||
return new Promise(resolve => {
|
||||
const child = spawn('ssh', [
|
||||
'-i', SSH_KEY,
|
||||
'-o', 'StrictHostKeyChecking=no',
|
||||
'-o', 'ConnectTimeout=5',
|
||||
`root@${host}`,
|
||||
'sh', '-c', command,
|
||||
], { shell: false })
|
||||
let stdout = '', stderr = ''
|
||||
child.stdout?.on('data', d => { stdout += d })
|
||||
child.stderr?.on('data', d => { stderr += d })
|
||||
const timer = setTimeout(() => {
|
||||
child.kill()
|
||||
resolve({ stdout, stderr: stderr + '\nTimed out after 30s', exitCode: -1 })
|
||||
}, 30_000)
|
||||
child.on('error', err => { clearTimeout(timer); resolve({ stdout: '', stderr: err.message, exitCode: -1 }) })
|
||||
child.on('close', code => { clearTimeout(timer); resolve({ stdout, stderr, exitCode: code ?? -1 }) })
|
||||
})
|
||||
}
|
||||
|
||||
async function getForgejoCommit(repo) {
|
||||
try {
|
||||
const headers = { Accept: 'application/json' }
|
||||
|
|
@ -225,7 +246,7 @@ app.get('/health-status', async () => {
|
|||
if (row.length >= 3) disk = { total: +row[1] * 1024, used: +row[2] * 1024 }
|
||||
}
|
||||
|
||||
return { name, ...httpResult, ram, disk }
|
||||
return { name, host, ...httpResult, ram, disk }
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
@ -257,6 +278,22 @@ app.post('/deploy/:repo', async (request, reply) => {
|
|||
return reply.status(202).send({ ok: true, deploying: repo })
|
||||
})
|
||||
|
||||
app.post('/exec', async (request, reply) => {
|
||||
const { host, command } = request.body || {}
|
||||
if (!host || !command || typeof command !== 'string' || command.length > 2000)
|
||||
return reply.status(400).send({ error: 'host and command required' })
|
||||
|
||||
const knownHosts = new Set([
|
||||
...Object.values(INFRA_HEALTH).map(v => v.host),
|
||||
...Object.values(INFRA_DEPLOY).map(v => v.host),
|
||||
...Object.values(appRegistry).map(v => v.host),
|
||||
])
|
||||
if (!knownHosts.has(host)) return reply.status(403).send({ error: 'unknown host' })
|
||||
|
||||
const result = await sshExec(host, command)
|
||||
return result
|
||||
})
|
||||
|
||||
app.post('/webhook', { config: { rawBody: true } }, async (request, reply) => {
|
||||
const sig = request.headers['x-hub-signature-256']
|
||||
|| request.headers['x-forgejo-signature']
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue