|
|
import { Webhooks, createNodeMiddleware } from '@octokit/webhooks' |
|
|
import { exec as execute, spawn } from 'node:child_process' |
|
|
import { promisify } from 'node:util' |
|
|
import morgan from 'morgan' |
|
|
import express from 'express' |
|
|
import ini from 'ini' |
|
|
|
|
|
const exec = promisify(execute) |
|
|
|
|
|
const { |
|
|
GIT_URL, |
|
|
WEBHOOK_SECRET, |
|
|
PORT = 7860, |
|
|
} = process.env |
|
|
|
|
|
const CONFIG_FILE = 'hf.conf' |
|
|
const REPO_NAME = extractRepoName(GIT_URL) |
|
|
|
|
|
let childProcess = null |
|
|
let config = null |
|
|
let env = {} |
|
|
|
|
|
const webhooks = new Webhooks({ secret: WEBHOOK_SECRET }) |
|
|
const logApp = createLogger('App') |
|
|
const logWebhook = createLogger('Webhook') |
|
|
|
|
|
if (!REPO_NAME) { |
|
|
logApp('error', 'Please provide $GIT_URL environment variable.') |
|
|
process.exit(1) |
|
|
} |
|
|
|
|
|
if (!WEBHOOK_SECRET) { |
|
|
logApp('error', 'Please provide $WEBHOOK_SECRET environment variable.') |
|
|
process.exit(1) |
|
|
} |
|
|
|
|
|
function extractRepoName(url) { |
|
|
if (!url) return null |
|
|
const name = url.split('/').pop() |
|
|
return name.endsWith('.git') ? name.slice(0, -4) : name |
|
|
} |
|
|
|
|
|
function formatDate(date) { |
|
|
const options = { |
|
|
year: 'numeric', |
|
|
month: '2-digit', |
|
|
day: '2-digit', |
|
|
hour: '2-digit', |
|
|
minute: '2-digit', |
|
|
hour12: true |
|
|
} |
|
|
return new Date(date).toLocaleString('en-US', options).replace(',', '') |
|
|
} |
|
|
|
|
|
function createLogger(context) { |
|
|
return (level, message) => { |
|
|
const timestamp = formatDate(new Date()) |
|
|
console.log(`[${timestamp}] [${level.toUpperCase()}] [${context}] ${message}`) |
|
|
} |
|
|
} |
|
|
|
|
|
async function executeCommand(command, cwd = REPO_NAME) { |
|
|
try { |
|
|
const stzdCmd = command.replace(new RegExp(GIT_URL, 'g'), '****') |
|
|
logApp('info', `Executing: ${stzdCmd}`) |
|
|
|
|
|
const { stdout, stderr } = await exec(command, { cwd }) |
|
|
|
|
|
if (stdout) console.log(stdout) |
|
|
if (stderr) console.error(stderr) |
|
|
|
|
|
return true |
|
|
} catch (error) { |
|
|
const sanitizedC = command.replace(new RegExp(GIT_URL, 'g'), '****') |
|
|
logApp('error', `Command failed: ${sanitizedC} - ${error.message}`) |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function cloneRepository() { |
|
|
logApp('info', 'Cloning repository...') |
|
|
return await executeCommand(`git clone ${GIT_URL}`, '.') |
|
|
} |
|
|
|
|
|
async function pullLatestChanges() { |
|
|
logApp('info', 'Pulling latest changes...') |
|
|
return await executeCommand('git pull') |
|
|
} |
|
|
|
|
|
async function runSetupScripts(scripts) { |
|
|
logApp('info', 'Running setup scripts...') |
|
|
for (const script of scripts) { |
|
|
const success = await executeCommand(script) |
|
|
if (!success) return false |
|
|
} |
|
|
return true |
|
|
} |
|
|
|
|
|
async function buildApplication() { |
|
|
logApp('info', 'Building application...') |
|
|
if (!config) { |
|
|
logApp('error', 'Configuration not loaded. Please clone the repository before building the application.') |
|
|
return false |
|
|
} |
|
|
if (!(await pullLatestChanges())) return false |
|
|
if (!(await runSetupScripts(config.script))) return false |
|
|
return true |
|
|
} |
|
|
|
|
|
function validateConfig(config) { |
|
|
if (!config) throw new Error("No config found in config file.") |
|
|
if (!config.command) throw new Error("No Command found in config file.") |
|
|
if (!config.script) throw new Error("No script for setup installation found in config file.") |
|
|
} |
|
|
|
|
|
async function loadConfiguration(filename) { |
|
|
try { |
|
|
const { stdout } = await exec(`cat ${filename}`, { cwd: REPO_NAME }) |
|
|
const obj = ini.parse(stdout) |
|
|
validateConfig(obj.config) |
|
|
config = obj.config |
|
|
env = obj.env || {} |
|
|
return true |
|
|
} catch (error) { |
|
|
logApp('error', `Failed to load configuration from ${filename}: ${error.message}`) |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
async function startApplication(build = false) { |
|
|
if (childProcess) { |
|
|
logApp('info', 'Restarting application...') |
|
|
childProcess.kill() |
|
|
childProcess = null |
|
|
} else { |
|
|
logApp('info', 'Starting application...') |
|
|
if (!(await cloneRepository())) return |
|
|
} |
|
|
|
|
|
if (build) { |
|
|
if (!(await loadConfiguration(CONFIG_FILE))) return |
|
|
if (!(await buildApplication())) return |
|
|
} |
|
|
|
|
|
const [command, ...args] = config.command.split(' ') |
|
|
logApp('info', `Executing command: ${config.command}`) |
|
|
|
|
|
childProcess = spawn(command, args, { |
|
|
env: { ...process.env, ...env }, |
|
|
cwd: REPO_NAME, |
|
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'], |
|
|
}) |
|
|
|
|
|
childProcess.on('message', async (msg) => { |
|
|
const action = msg.trim() |
|
|
if (action === 'reset') { |
|
|
await startApplication() |
|
|
} else if (action === 'build') { |
|
|
await startApplication(true) |
|
|
} else if (action === 'pull') { |
|
|
await pullLatestChanges() |
|
|
} else if (action === 'setup') { |
|
|
await runSetupScripts(config.script) |
|
|
} |
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
webhooks.onAny((event) => { |
|
|
logWebhook('info', `Received event: ${event.name} with ID: ${event.id}`) |
|
|
if (childProcess && event.name === 'push') { |
|
|
childProcess.send('push='+JSON.stringify(event)) |
|
|
childProcess.emit('message', 'build') |
|
|
} |
|
|
}) |
|
|
|
|
|
function initializeServer() { |
|
|
const app = express() |
|
|
const middleware = createNodeMiddleware(webhooks, { path: '/webhook' }) |
|
|
|
|
|
app.use(morgan('combined')) |
|
|
app.use(middleware) |
|
|
|
|
|
app.get('/', (req, res) => { |
|
|
res.json({ now: 'alive', message: "Hello_World" }) |
|
|
}) |
|
|
|
|
|
app.listen(PORT, () => { |
|
|
logApp('info', `Server listening to port [${PORT}]`) |
|
|
}) |
|
|
} |
|
|
|
|
|
initializeServer() |
|
|
startApplication(true) |