This commit is contained in:
Heidi
2026-05-14 21:09:18 +01:00
parent 3c08023cb9
commit 7026eacb12
2 changed files with 99 additions and 18 deletions
+2 -2
View File
@@ -101,8 +101,8 @@ loads the updated listener:
pm2 reload tssbot-webhook --update-env pm2 reload tssbot-webhook --update-env
``` ```
The webhook listener reads `.env` on startup. To send a Discord notification The webhook listener reads `.env` on startup. To send Discord notifications for
whenever the listener starts or restarts, set: listener restarts and GitHub push deploy start/success/failure events, set:
```sh ```sh
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/... DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
+90 -9
View File
@@ -52,6 +52,28 @@ function json(res, status, body) {
res.end(JSON.stringify(body)) res.end(JSON.stringify(body))
} }
function safeJsonParse(rawBody) {
try {
return JSON.parse(rawBody.toString('utf8'))
} catch {
return null
}
}
function shortSha(value) {
return value ? String(value).slice(0, 7) : 'unknown'
}
function branchName(ref) {
return String(ref || '').replace(/^refs\/heads\//, '') || 'unknown'
}
function truncate(value, maxLength = 900) {
const text = String(value || '')
if (text.length <= maxLength) return text
return `${text.slice(0, maxLength - 3)}...`
}
function verifySignature(rawBody, signature) { function verifySignature(rawBody, signature) {
if (!SECRET) return true if (!SECRET) return true
if (!signature || !signature.startsWith('sha256=')) return false if (!signature || !signature.startsWith('sha256=')) return false
@@ -169,10 +191,7 @@ function postDiscordWebhook(payload) {
function notifyDiscordRestart() { function notifyDiscordRestart() {
const startedAt = new Date() const startedAt = new Date()
postDiscordWebhook({ sendDiscordEmbed({
username: 'tssbot webhook',
embeds: [
{
title: 'Webhook listener restarted', title: 'Webhook listener restarted',
color: 0x00f2ff, color: 0x00f2ff,
fields: [ fields: [
@@ -181,12 +200,68 @@ function notifyDiscordRestart() {
{ name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false }, { name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false },
], ],
timestamp: startedAt.toISOString(), timestamp: startedAt.toISOString(),
},
],
}) })
} }
async function deploy() { function sendDiscordEmbed(embed) {
return postDiscordWebhook({
username: 'tssbot webhook',
embeds: [embed],
})
}
function deployFields(push) {
const headCommit = push?.head_commit
const fields = [
{ name: 'Host', value: os.hostname(), inline: true },
{ name: 'Branch', value: branchName(push?.ref), inline: true },
{ name: 'Commit', value: shortSha(push?.after || headCommit?.id), inline: true },
{ name: 'Restart targets', value: RESTART_TARGETS.join(', ') || 'none', inline: false },
]
if (push?.sender?.login) {
fields.push({ name: 'Sender', value: push.sender.login, inline: true })
}
if (headCommit?.message) {
fields.push({ name: 'Message', value: truncate(headCommit.message), inline: false })
}
return fields
}
async function notifyDeployStarted(push) {
await sendDiscordEmbed({
title: 'GitHub push deploy started',
color: 0xf4ee3e,
fields: deployFields(push),
timestamp: new Date().toISOString(),
})
}
async function notifyDeployCompleted(push) {
await sendDiscordEmbed({
title: 'GitHub push deploy completed',
color: 0x00f2ff,
fields: deployFields(push),
timestamp: new Date().toISOString(),
})
}
async function notifyDeployFailed(push, error) {
await sendDiscordEmbed({
title: 'GitHub push deploy failed',
color: 0xe82517,
fields: [
...deployFields(push),
{ name: 'Error', value: truncate(error?.message || error), inline: false },
],
timestamp: new Date().toISOString(),
})
}
async function deploy(push) {
await notifyDeployStarted(push)
await run('git', ['pull', '--ff-only']) await run('git', ['pull', '--ff-only'])
await ensureBuildDependencies() await ensureBuildDependencies()
await run('npm', ['run', 'build']) await run('npm', ['run', 'build'])
@@ -194,6 +269,8 @@ async function deploy() {
for (const target of RESTART_TARGETS) { for (const target of RESTART_TARGETS) {
await run('pm2', ['reload', target, '--update-env']) await run('pm2', ['reload', target, '--update-env'])
} }
await notifyDeployCompleted(push)
} }
http http
@@ -212,6 +289,7 @@ http
req.on('data', (chunk) => chunks.push(chunk)) req.on('data', (chunk) => chunks.push(chunk))
req.on('end', () => { req.on('end', () => {
const rawBody = Buffer.concat(chunks) const rawBody = Buffer.concat(chunks)
const push = safeJsonParse(rawBody)
if (!verifySignature(rawBody, req.headers['x-hub-signature-256'])) { if (!verifySignature(rawBody, req.headers['x-hub-signature-256'])) {
json(res, 401, { error: 'Invalid signature' }) json(res, 401, { error: 'Invalid signature' })
@@ -231,9 +309,12 @@ http
deploying = true deploying = true
json(res, 202, { accepted: true, restart_targets: RESTART_TARGETS }) json(res, 202, { accepted: true, restart_targets: RESTART_TARGETS })
deploy() deploy(push)
.then(() => console.log('GitHub push deploy completed')) .then(() => console.log('GitHub push deploy completed'))
.catch((error) => console.error('GitHub push deploy failed:', error)) .catch((error) => {
console.error('GitHub push deploy failed:', error)
notifyDeployFailed(push, error)
})
.finally(() => { .finally(() => {
deploying = false deploying = false
}) })