rojects/source/server/routes/deploy.ts

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;