55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
import { Router } from 'express';
|
|
import express from 'express';
|
|
import { spawn } from 'child_process';
|
|
import { createHmac, timingSafeEqual } from 'crypto';
|
|
|
|
const router = Router();
|
|
|
|
const DEPLOY_SECRET = process.env.DEPLOY_WEBHOOK_SECRET ?? '';
|
|
const APP_DIR = process.env.APP_DIR ?? '/opt/roject';
|
|
const SERVICE_NAME = process.env.SERVICE_NAME ?? 'roject';
|
|
|
|
function verifySignature( rawBody: Buffer, signature: string ): boolean {
|
|
if ( !DEPLOY_SECRET ) return false;
|
|
const expected = createHmac( 'sha256', DEPLOY_SECRET ).update( rawBody ).digest( 'hex' );
|
|
try {
|
|
return timingSafeEqual( Buffer.from( signature ), Buffer.from( expected ) );
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function triggerDeploy() {
|
|
const cmd = `git -C ${APP_DIR} pull && npm --prefix ${APP_DIR} run build && systemctl restart ${SERVICE_NAME}`;
|
|
const child = spawn( 'bash', [ '-c', cmd ], { detached: true, stdio: 'ignore' } );
|
|
child.unref();
|
|
}
|
|
|
|
router.post( '/', express.raw( { type: 'application/json' } ), ( req, res ) => {
|
|
const signature = req.headers[ 'x-gitea-signature' ] as string;
|
|
|
|
if ( !DEPLOY_SECRET ) {
|
|
console.error( '[deploy] DEPLOY_WEBHOOK_SECRET is not set' );
|
|
res.status( 500 ).json( { error: 'Deploy not configured' } );
|
|
return;
|
|
}
|
|
|
|
if ( !signature || !verifySignature( req.body as Buffer, signature ) ) {
|
|
res.status( 401 ).json( { error: 'Invalid signature' } );
|
|
return;
|
|
}
|
|
|
|
const payload = JSON.parse( ( req.body as Buffer ).toString() );
|
|
|
|
if ( payload.ref !== 'refs/heads/main' ) {
|
|
res.json( { message: 'Not main branch, skipping' } );
|
|
return;
|
|
}
|
|
|
|
console.log( `[deploy] triggered by push from ${payload.pusher?.login ?? 'unknown'}` );
|
|
res.json( { message: 'Deploy triggered' } );
|
|
triggerDeploy();
|
|
} );
|
|
|
|
export default router;
|