Initial commit: management
This commit is contained in:
commit
ab329d497b
8 changed files with 243 additions and 0 deletions
72
updater/src/index.js
Normal file
72
updater/src/index.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import Fastify from 'fastify'
|
||||
import crypto from 'crypto'
|
||||
import { execSync } from 'child_process'
|
||||
import { deployMap } from '../deploy-map.js'
|
||||
|
||||
const app = Fastify({ logger: true })
|
||||
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET || ''
|
||||
const SSH_KEY = process.env.SSH_KEY_PATH || '/root/.ssh/id_ed25519'
|
||||
const deployLog = []
|
||||
|
||||
function verifySignature(body, signature) {
|
||||
if (!WEBHOOK_SECRET) return true // disabled in dev
|
||||
const expected = `sha256=${crypto.createHmac('sha256', WEBHOOK_SECRET).update(body).digest('hex')}`
|
||||
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ''))
|
||||
}
|
||||
|
||||
async function deploy(target, repoName) {
|
||||
const { host, path } = target
|
||||
const cmd = `ssh -i ${SSH_KEY} -o StrictHostKeyChecking=no root@${host} "cd ${path} && git pull && docker compose up -d --build"`
|
||||
|
||||
const entry = { repo: repoName, host, started: new Date().toISOString(), status: 'running' }
|
||||
deployLog.unshift(entry)
|
||||
if (deployLog.length > 50) deployLog.pop()
|
||||
|
||||
try {
|
||||
const out = execSync(cmd, { timeout: 300_000, encoding: 'utf8' })
|
||||
entry.status = 'success'
|
||||
entry.output = out.slice(-500)
|
||||
app.log.info(`Deploy ${repoName} → success`)
|
||||
} catch (err) {
|
||||
entry.status = 'failed'
|
||||
entry.error = err.message.slice(-500)
|
||||
app.log.error(`Deploy ${repoName} → failed: ${err.message}`)
|
||||
}
|
||||
entry.finished = new Date().toISOString()
|
||||
}
|
||||
|
||||
app.post('/webhook', {
|
||||
config: { rawBody: true }
|
||||
}, async (request, reply) => {
|
||||
const sig = request.headers['x-hub-signature-256']
|
||||
const rawBody = request.rawBody || JSON.stringify(request.body)
|
||||
|
||||
if (!verifySignature(rawBody, sig)) {
|
||||
return reply.status(401).send({ error: 'Invalid signature' })
|
||||
}
|
||||
|
||||
const event = request.headers['x-gitea-event'] || request.headers['x-github-event']
|
||||
if (event !== 'push') return reply.status(200).send({ ok: true, skipped: 'not a push event' })
|
||||
|
||||
const repoName = request.body?.repository?.name
|
||||
const ref = request.body?.ref || ''
|
||||
if (!ref.endsWith('/main') && !ref.endsWith('/master')) {
|
||||
return reply.status(200).send({ ok: true, skipped: 'not main branch' })
|
||||
}
|
||||
|
||||
const target = deployMap[repoName]
|
||||
if (!target) {
|
||||
app.log.warn(`No deploy target for repo: ${repoName}`)
|
||||
return reply.status(200).send({ ok: true, skipped: 'no deploy target' })
|
||||
}
|
||||
|
||||
// Kick off deploy async — don't block the webhook response
|
||||
deploy(target, repoName).catch(() => {})
|
||||
return reply.status(202).send({ ok: true, deploying: repoName })
|
||||
})
|
||||
|
||||
app.get('/health', async () => ({ status: 'healthy' }))
|
||||
|
||||
app.get('/deploys', async () => deployLog)
|
||||
|
||||
await app.listen({ port: 9000, host: '0.0.0.0' })
|
||||
Loading…
Add table
Add a link
Reference in a new issue