Skip to content

Instantly share code, notes, and snippets.

@yus-ham
Last active October 21, 2023 13:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yus-ham/625bca92e4a5e3dd236dedae6b2df362 to your computer and use it in GitHub Desktop.
Save yus-ham/625bca92e4a5e3dd236dedae6b2df362 to your computer and use it in GitHub Desktop.
Auto pull by webhooks
<?php
$CI_SECRET = 'xxxxx';
$BASE_DIR = '/var/www/html/';
$_SERVER['REQUEST_METHOD'] === 'POST' OR die(header('HTTP/1.1 405').'#METHOD_NOT_ALLOWED');
$data = file_get_contents('php://input');
$sign = substr($_SERVER['HTTP_X_HUB_SIGNATURE_256'] ??null, 7) OR die(header('HTTP/1.1 400').'#SIGNATURE');
$host_sign = hash_hmac('sha256', $data, $CI_SECRET);
hash_equals($host_sign, $sign) OR die(header('HTTP/1.1 401 #INVALID_SIGNATURE'));
$data = json_decode($data);
header('Content-Type: text/plain');
$branch = trim(explode('/', $data->ref)[2]);
$project = preg_replace('/\'"|\.\./', '', $branch ?: $data->repository->name);
chdir($BASE_DIR.$project);
$host_branch = trim(explode('/', file_get_contents("$BASE_DIR$project/.git/HEAD"))[2]);
$remote = parse_ini_file("$BASE_DIR$project/.git/config", $section=true)["branch $host_branch"]["remote"];
if ($host_branch !== $branch) {
echo 'Branch not matched, Skip!';
} else {
foreach ([
"git fetch $remote $branch",
"git merge $remote/$branch --no-edit",
"composer --no-interaction --prefer-dist -o i",
"php yii migrate --interactive=0",
] as $cmd) {
$result = null;
print "\n$cmd\n";
passthru($cmd, $result);
$result and print "Exit code: $result\n";
}
}
echo "\n\ngit status\n";
system("git status");
echo "git log\n";
exec("git log -n 5", $output);
$commit = null;
foreach ((array)$output as $line) {
if (trim($line)) {
if (strpos($line, 'commit ') === 0) {
$commit and print_r($commit);
$commit = [$line];
}
else $commit[] = $line;
}
}
$commit and print_r($commit);
import { appendFileSync } from 'fs'
import { basename } from 'path'
const VALIDATE = true
const TEST_PROJECT = 'press'
const BASE_DIR = '/var/www/html/'
const TOKEN = '!Xxx123'
const REMOTE = 'origin'
const projects = {
'press': {
branch: 'main',
logFile: `/var/www/html/press-git/frontpage/ci/status.html`,
directory: 'press-git',
env: {
//BUILD_OUTDIR: 'dist/portal',
API_URL: '/press-dev/app-api/web',
BASE_URL: '/press-dev/adminpanel',
},
extra_commands: [
`bun install`,
(p) => {
const out = `[${date()}] Build started ...\n\n`
appendFileSync(p.logFileTxt, out)
appendFileSync(p.logFile, `${nl2br(out)}\n`)
},
{ cmd: `bun vite build -l error`, isSuccess: o => !o.match(/Found \d+ errors? in/) },
(p) => {
const out = `[${date()}] CI finished with status: Success\n`
appendFileSync(p.logFileTxt, out)
appendFileSync(p.logFile, out)
console.info(out)
},
]
},
/*'portal-be': {
env: {},
branch: 'develop',
pm2_proc_name: 'portal-be.3001',
logFile: `/var/www/html/portal/ci-status-be.html`,
extra_commands: [
(p, out) => {
spawnProcess(`pm2 restart 12 --update-env`, p)
const proc = spawnProcess(`pm2 jlist`, p)
for (const pm2_proc of JSON.parse(proc.stdout.toString())) {
if (pm2_proc.name === p.pm2_proc_name && pm2_proc.pid) {
out = `[${date()}] CI finished with status: Success\n`
appendFileSync(p.logFileTxt, out)
appendFileSync(p.logFile, out)
console.info(out)
return
}
}
out = `[${date()}] CI finished with status: Failed\n`
appendFileSync(p.logFileTxt, out)
appendFileSync(p.logFile, out)
console.error(out)
},
]
},*/
}
// start ci-gitlab with pm2 id for portal-be
// pm2 start <ci-gitlab> -f --interpreter-args -- pm2-id <portal-be>=<0>
let isPm2Id
for (let item of (process.argv || [])) {
if (isPm2Id) {
item = item.split('=').map(x => x.trim())
if (item[0] && projects[item[0]]) {
projects[item[0]].pm2_id = item[1]
}
isPm2Id = false
continue
}
if (item.startsWith('pm2-id')) {
isPm2Id = true
}
}
const server = Bun.serve({
port: 9000,
async fetch(req, p) {
//console.info({req})
try {
p = await getProject(req)
Bun.sleep(1).then(_ => startCI(p))
} catch (e) {
console.error(e)
return new Response(e.message, e)
}
return new Response('OK\n\nCheck CI logs at: https://localhost/ci/' + basename(p.logFile))
}
})
console.info(`Webhook server started on http://${server.hostname}:${server.port}`)
async function getProject(req) {
const config = new URLSearchParams(req.headers.get('x-gitlab-token'))
if (VALIDATE && !config.get('token'))
throw { message: 'No Token', status: 401 }
if (VALIDATE && config.get('token') !== TOKEN)
throw { message: 'Invalid Token', status: 422 }
const params = VALIDATE ? await req.json() : {}
const push_branch = params.ref?.split('/')[2]
const project_name = params.project?.path_with_namespace.split('/')[1] || TEST_PROJECT
const project = projects[project_name] || {}
if (VALIDATE && project.is_running)
throw {message: 'Already running', status: 419 }
if (VALIDATE && push_branch !== project.branch)
throw { message: 'Invalid branch', status: 422 }
project.name = project_name
project.is_running = true
project.logFileTxt = project.logFile.replace(/\.html$/, '.txt')
return project
}
async function startCI(project, log) {
await Bun.write(project.logFileTxt, (log = `[${date()}] CI started ...`) + `\n\n`)
await Bun.write(project.logFile, `<!doctype html><body style="font-family:monospace">\n${log}<br><br>\n`)
const opts = {
stdout: 'pipe',
stderr: 'pipe',
env: { ...Bun.env, ...project.env },
cwd: BASE_DIR + (project.directory || project.name)
}
//console.info({opts})
new Array(
(p) => console.info(`Project: ${p.name}`),
`git status`,
`git fetch ${REMOTE} ${project.branch}`,
`git merge ${REMOTE}/${project.branch} --no-edit`,
`git log -5`,
...project.extra_commands,
(p) => {
console.info(`---- END ------------------------------`)
p.is_running = false
},
)
.reduceRight((_onExit, cmd) => {
const onExit = () => console.info(`---------------------------------------`) || _onExit()
return async () => {
if (typeof cmd === 'function')
return Promise.resolve().then(_ => cmd(project)).then(onExit)
if (!cmd.cmd) {
cmd = { cmd }
}
cmd.isSuccess = () => true
console.info(`CWD: ${opts.cwd}\nCMD: ${cmd.cmd}`)
const proc = spawnProcess(cmd.cmd, opts)
const err = proc.stderr.toString()
if (err) {
const _err = `[${date()}] ${err}`
appendFileSync(project.logFileTxt, _err + '\n\n')
appendFileSync(project.logFile, `<font color="#dc3545">${nl2br(_err)}</font><br><br>\n`)
}
let out = proc.stdout.toString()
if (proc.success && cmd.isSuccess(out)) {
out = `[${date()}] ${cmd.cmd}\n${out}\n\n`
appendFileSync(project.logFileTxt, out)
appendFileSync(project.logFile, nl2br(out) + '\n')
return onExit()
}
console.error(out)
appendFileSync(project.logFileTxt, out + '\n\n\n')
appendFileSync(project.logFile, `<font color="#dc3545">${nl2br(out)}</font><br><br>\n`)
out = `[${date()}] CI finished with status: Failed`
console.error(out)
appendFileSync(project.logFileTxt, out + '\n')
appendFileSync(project.logFile, `<font color="#dc3545">${out}</font>\n`)
project.is_running = false
}
})()
}
const nl2br = (o) => o.replace(/\n/g, '<br>\n')
const date = (d) => { d = new Date(); d.setHours(d.getHours() - 1); return d.toString().slice(0, 29) + '0700' }
const spawnProcess = (cmd, opts) => { opts.cwd || (opts.cwd = BASE_DIR + (opts.directory || opts.name)); return Bun.spawnSync(cmd.split(' '), { ...opts }) }
<?php
// usage token=Secret123&project_dir=project_name
$CI_SECRET = 'xxx';
$BASE_DIR = '/var/www/html/';
$_SERVER['REQUEST_METHOD'] === 'POST' OR die(header('HTTP/1.1 405').'#METHOD_NOT_ALLOWED');
$token = $_SERVER['HTTP_X_GITLAB_TOKEN'] ??null;
$token OR die(header('HTTP/1.1 400').'#NO_TOKEN');
$opts = [];
parse_str($token, $opts);
$opts['token'] === $CI_SECRET OR die(header('HTTP/1.1 400').'#INVALID_TOKEN');
header('Content-Type: text/plain');
$data = json_decode(file_get_contents('php://input'));
$project = preg_replace('/\'"|\.\./', '', ($opts['project_dir'] ??null) ?: explode('/',$data->project->path_with_namespace)[1]);
chdir($BASE_DIR.$project);
$branch = explode('/', $data->ref)[2];
$host_branch = trim(explode('/', file_get_contents('.git/HEAD'))[2]);
$remote = parse_ini_file('.git/config', $section=true)["branch $host_branch"]["remote"];
if ($host_branch != $branch) {
echo 'Branch not matched, Skip!';
} else {
foreach ([
"git fetch $remote $branch",
"git merge $remote/$branch --no-edit",
"composer --no-interaction --prefer-dist -o i",
"php yii migrate --interactive=0",
] as $cmd) {
$result = null;
print "\n$cmd\n";
passthru($cmd, $result);
$result and print "Exit code: $result\n";
}
}
echo "\n\ngit status\n";
system("git status");
echo "git log\n";
exec("git log -n 5", $output);
$commit = null;
foreach ((array)$output as $line) {
if (trim($line)) {
if (strpos($line, 'commit ') === 0) {
$commit and print_r($commit);
$commit = [$line];
}
else $commit[] = $line;
}
}
$commit and print_r($commit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment