Skip to content

Instantly share code, notes, and snippets.

@ZebTheWizard
Last active September 25, 2021 14:30
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 ZebTheWizard/278b65fb852322515dbdc00a18f340ba to your computer and use it in GitHub Desktop.
Save ZebTheWizard/278b65fb852322515dbdc00a18f340ba to your computer and use it in GitHub Desktop.
Cpanel git bare
<?php
// 1. place this install script in your public_html/ folder
// 2. go to example.com/install.php to generate all the files
// 3. add basic authentication users by clicking "manager users"
// 4. add the new remote "example.com/repos/git.cgi/<repo>.git"
// 5. remove the install.php script if it still exists.
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
?>
<?php if (isset($_POST["manage_users"])) : ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Git HTTP Installer</title>
<style>
section {
margin-bottom: 1rem;
}
</style>
</head>
<body>
<h1>
Git HTTP Installer for cPanel sites
</h1>
<div>
<a href="">Go back</a>
</div>
<p>You may need to contact your cPanel administrator to have htpasswd enabled.</p>
<div>
<strong style="color: red;"> This will wipe all current .htpasswd users</strong>
</div>
<form action="" method="POST">
<button type="submit" name="users_submitted" value="true">Save</button>
<section>
<label for="usernames">
<div>Usernames</div>
<div><small>Line separated list of usernames with access to repo.</small></div>
<textarea type="text" name="usernames" id="usernames" placeholder="bob
alice" value=""></textarea>
</label>
</section>
<section>
<label for="passwords">
<div>Passwords</div>
<div><small>Line separated list of passwords for corresponding users in field above.</small></div>
<textarea type="text" name="passwords" id="passwords" placeholder="suP3rs3cr3T
t0t@llyS@fe" value=""></textarea>
</label>
</section>
<input type="hidden" name="submitted" value="true">
<button type="submit" name="users_submitted" value="true">Save</button>
</form>
</body>
</html>
<?php elseif (isset($_POST['submitted'])) : ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installing</title>
</head>
<body>
<h1>Installing...</h1>
<div>
<a href="">Go back</a>
</div>
</body>
</html>
<?php else : ?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Git HTTP Installer</title>
<style>
section {
margin-bottom: 1rem;
}
</style>
</head>
<body>
<h1>
Git HTTP Installer for cPanel sites
</h1>
<p>You may need to contact your cPanel administrator to have git enabled.</p>
<form action="" method="POST">
<button type="submit" name="clean" value="true">Clean</button>
<button type="submit" name="manage_users" value="true">Manage Users</button>
<button type="submit" name="settings_submitted" value="true">Install</button>
<section>
<label for="domain">
<div>Domain Name</div>
<div><small>Do not include www.</small></div>
<input type="text" name="domain" id="domain" placeholder="example.com" value="<?php echo $_SERVER['SERVER_NAME'] ?>">
</label>
</section>
<section>
<label for="repo">
<div>Repo Name</div>
<div><small>Legal file system characters.</small></div>
<input type="text" name="repo" id="repo" placeholder="production" value="production">
</label>
</section>
<section>
<label for="basedir">
<div>Base Directory</div>
<div><small>Directory relative to repo where index.php is located.</small></div>
<input type="text" name="basedir" id="basedir" placeholder="/public" value="/">
</label>
</section>
<section>
<label for="branch">
<div>Branch</div>
<div><small>The branch to deploy</small></div>
<input type="text" name="branch" id="branch" placeholder="master" value="master">
</label>
</section>
<input type="hidden" name="submitted" value="true">
<button type="submit" name="settings_submitted" value="true">Install</button>
</form>
</body>
</html>
<?php endif ?>
<?php
class Shell
{
private static $commands = [];
public static function exec_shell($cmd)
{
$handle = popen("$cmd 2>&1", "r");
$output = "";
while (!feof($handle)) {
$buffer = fgets($handle, 4096);
$output .= $buffer;
}
pclose($handle);
return $output;
}
private static function print_shell($cmd)
{
$output = static::exec_shell($cmd);
echo "<div style='margin-bottom: 1rem; border-bottom: 1px solid black; padding-bottom: 1rem;'>
<pre>&gt; $cmd</pre>
<div style='padding-left: 1rem'><pre>$output</pre></div></div>";
}
public static function run($cmd)
{
static::print_shell($cmd);
}
public static function start()
{
ob_start();
}
public static function end()
{
$commands = explode(PHP_EOL, ob_get_clean());
$sub = "(";
$inSub = false;
foreach ($commands as $command) {
$command = trim($command);
if ($command === "(") {
$inSub = true;
} else if ($inSub) {
if ($command === ")") {
$sub .= ")";
static::exec_shell($sub);
$sub = "(";
$inSub = false;
} else {
$sub .= $command . ";";
}
} else if (!empty($command)) {
static::exec_shell($command);
}
}
}
}
class IO
{
private static $writeLocation = "";
public static function write($location, $permissions, $contents = null)
{
Shell::run("touch $location && chmod $permissions $location");
static::$writeLocation = $location;
if (empty($contents)) {
ob_start();
} else {
file_put_contents($location, $contents);
}
}
public static function writeEnd()
{
$buffer = ob_get_clean();
file_put_contents(static::$writeLocation, $buffer);
static::$writeLocation = "";
}
public static function die($message)
{
echo "<div><strong>ERROR: </strong> $message<br></div>";
die();
}
public static function guard_post($field)
{
if (!IO::has_post($field)) IO::die("missing $field field");
return IO::post($field);
}
public static function has_post($key)
{
return isset($_POST[$key]) && strlen(trim($_POST[$key]));
}
public static function post($key)
{
return static::has_post($key) ? trim($_POST[$key]) : null;
}
public static function redirect($url)
{
$string = '<script type="text/javascript">';
$string .= 'window.location = "' . $url . '"';
$string .= '</script>';
echo $string;
}
public static function removeDirectory($dir)
{
if (!file_exists($dir)) {
return true;
}
if (!is_dir($dir)) {
return unlink($dir);
}
foreach (scandir($dir) as $item) {
if ($item == '.' || $item == '..') {
continue;
}
if (!static::removeDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
return false;
}
}
return rmdir($dir);
}
}
?>
<?php
if (isset($_POST['settings_submitted'])) {
$DOMAIN = IO::guard_post("domain");
$REPO = IO::guard_post("repo");
$BASEDIR = IO::guard_post("basedir");
$BRANCH = IO::guard_post("branch");
$pwd = getcwd();
$repoLocation = $REPO . $BASEDIR;
Shell::run("mkdir -p code/$REPO");
Shell::run("mkdir -p repos");
Shell::run("mkdir -p repo_data/$REPO.git");
Shell::run("git init --bare --shared=all repo_data/$REPO.git");
Shell::run("chmod -R g+rw repo_data/$REPO.git");
Shell::run("chgrp -R theplace repo_data/$REPO.git");
// htpasswd -db /usr/web/.htpasswd-all jones Pwd4Steve
IO::write(".htaccess", "644", "# GENERATED HTACCESS CODE
RewriteEngine on
RewriteCond %{HTTP_HOST} ^(www.)?$DOMAIN$
RewriteCond %{REQUEST_URI} !^/code/$repoLocation
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /code/$repoLocation$1
RewriteCond %{HTTP_HOST} ^(www.)?$DOMAIN$
RewriteRule ^(/)?$ code/$repoLocation" . "index.php [L]
# END OF GENERATED HTACCESS CODE");
IO::write("repo_data/.htaccess", "644", "deny from all");
IO::write("repos/.htaccess", "644", "Options +ExecCGI
AddHandler cgi-script cgi
SetEnv GIT_PROJECT_ROOT $pwd/repo_data/
SetEnv GIT_HTTP_EXPORT_ALL
SetEnv BACKDOOR_INSTALL_LOCATION $pwd
#ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
# AUTHENTICATION
AuthType Basic
AuthName \"Protected '$pwd/repos'\"
AuthUserFile \"$pwd/.htpasswd\"
Require valid-user");
IO::write("repos/git.cgi", "755", "#! /bin/sh
logfile=$pwd/repos/git.log
date >> \"\$logfile\"
git http-backend \"\$@\" 2>> \"\$logfile\" || echo failed >> \"\$logfile\"
echo >> \"\$logfile\"");
IO::write("repos/git.log", "622", "");
IO::write("checkout_latest_version.php", "755", "<?php
function run(\$cmd)
{
\$handle = popen(\"\$cmd 2>&1\", 'r');
\$output = fread(\$handle, 4096);
pclose(\$handle);
return \$output;
}
\$user = get_current_user();
\$REPO = '$REPO';
\$BRANCH = '$BRANCH';
\$pwd = '$pwd';
echo ('Checking out remote as :' . \"\$user\\n\");
echo run(\"git --work-tree=\$pwd/code/\$REPO --git-dir=\$pwd/repo_data/\$REPO.git checkout -f \$BRANCH\");
");
IO::write("repo_data/$REPO.git/hooks/post-receive", "755", "#!/bin/sh
#Always works
curl $DOMAIN/checkout_latest_version.php
#Does not work when cgi scripts execute as nobody.
#php $pwd/repos/checkout.php");
Shell::run("chmod -R 777 repo_data/$REPO.git");
Shell::run("chmod -R 777 code");
Shell::run("ls -al repo_data/$REPO.git");
echo "<div><code><pre>git remote add $REPO https://$DOMAIN/repos/git.cgi/$REPO.git</pre></code></div>";
}
if (isset($_POST['users_submitted'])) {
$usernamesText = IO::guard_post("usernames");
$passwordsText = IO::guard_post("passwords");
$usernames = array_filter(explode(PHP_EOL, $usernamesText), "strlen");
$passwords = array_filter(explode(PHP_EOL, $passwordsText), "strlen");
if (count($usernames) != count($passwords)) IO::die("number of usernames and passwords do not match.");
for ($i = 0; $i < count($usernames); $i++) {
$username = $usernames[$i];
$password = $passwords[$i];
Shell::run("htpasswd -c -m -b .htpasswd $username $password");
}
}
if (isset($_POST['clean'])) {
$REPO = IO::guard_post("repo");
$pwd = getcwd();
IO::removeDirectory("code/$REPO");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment