Skip to content

Instantly share code, notes, and snippets.

@rdbox
Last active June 1, 2016 10:02
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 rdbox/69cf17ef8e62d7f6f9e4248e1b4dc2c7 to your computer and use it in GitHub Desktop.
Save rdbox/69cf17ef8e62d7f6f9e4248e1b4dc2c7 to your computer and use it in GitHub Desktop.
git: deployment to site
<?php
/**
* Simple PHP Git deploy script
*
* Automatically deploy the code using PHP and Git.
*
* @version 1.3.1
* @link https://github.com/markomarkovic/simple-php-git-deploy/
*/
// =========================================[ Configuration start ]===
/**
* It's preferable to configure the script using `deploy-config.php` file.
*
* Rename `deploy-config.example.php` to `deploy-config.php` and edit the
* configuration options there instead of here. That way, you won't have to edit
* the configuration again if you download the new version of `deploy.php`.
*/
if (file_exists(basename(__FILE__, '.php').'-config.php')) {
define('CONFIG_FILE', basename(__FILE__, '.php').'-config.php');
require_once CONFIG_FILE;
} else {
define('CONFIG_FILE', __FILE__);
}
/**
* Protect the script from unauthorized access by using a secret access token.
* If it's not present in the access URL as a GET variable named `sat`
* e.g. deploy.php?sat=Bett...s the script is not going to deploy.
* В строке SECRET_ACCESS_TOKEN надо указать любую пароль-фразу или набор символов
* SECRET_ACCESS_TOKEN на странице в стречается не сколько раз нужно поменять во всех местах.
* Важное замечание, SECRET_ACCESS_TOKEN должен отличается от файла deploy-config.php
* @var string
*/
if (!defined('SECRET_ACCESS_TOKEN')) define('SECRET_ACCESS_TOKEN', 'BetterChangeMeNowOrSufferTheConsequences');
/**
* The address of the remote Git repository that contains the code that's being
* deployed.
* If the repository is private, you'll need to use the SSH address.
*
* @var string
*/
if (!defined('REMOTE_REPOSITORY')) define('REMOTE_REPOSITORY', 'git@github.com:user/repo.git');
/**
* The branch that's being deployed.
* Must be present in the remote repository.
*
* @var string
*/
if (!defined('BRANCH')) define('BRANCH', 'master');
/**
* The location that the code is going to be deployed to.
* Don't forget the trailing slash!
*
* @var string Full path including the trailing slash
* указать полный путь к рабочей директории
*/
if (!defined('TARGET_DIR')) define('TARGET_DIR', '/full/path/public_html/');
/**
* Whether to delete the files that are not in the repository but are on the
* local (server) machine.
*
* !!! WARNING !!! This can lead to a serious loss of data if you're not
* careful. All files that are not in the repository are going to be deleted,
* except the ones defined in EXCLUDE section.
* BE CAREFUL!
*
* @var boolean
*/
if (!defined('DELETE_FILES')) define('DELETE_FILES', false);
/**
* The directories and files that are to be excluded when updating the code.
* Normally, these are the directories containing files that are not part of
* code base, for example user uploads or server-specific configuration files.
* Use rsync exclude pattern syntax for each element.
*
* @var serialized array of strings
*/
if (!defined('EXCLUDE')) define('EXCLUDE', serialize(array(
'.git',
)));
/**
* Temporary directory we'll use to stage the code before the update. If it
* already exists, script assumes that it contains an already cloned copy of the
* repository with the correct remote origin and only fetches changes instead of
* cloning the entire thing.
*
* @var string Full path including the trailing slash
*/
if (!defined('TMP_DIR')) define('TMP_DIR', '/full/path/public_html/tmp/spgd-'.md5(REMOTE_REPOSITORY).'/');
/**
* Whether to remove the TMP_DIR after the deployment.
* It's useful NOT to clean up in order to only fetch changes on the next
* deployment.
*/
if (!defined('CLEAN_UP')) define('CLEAN_UP', false);
/**
* Output the version of the deployed code.
*
* @var string Full path to the file name
*/
if (!defined('VERSION_FILE')) define('VERSION_FILE', TMP_DIR.'VERSION');
/**
* Time limit for each command.
*
* @var int Time in seconds
*/
if (!defined('TIME_LIMIT')) define('TIME_LIMIT', 30);
/**
* OPTIONAL
* Backup the TARGET_DIR into BACKUP_DIR before deployment.
*
* @var string Full backup directory path e.g. `/tmp/`
* полный путь к папке /tmp/
*/
if (!defined('BACKUP_DIR')) define('BACKUP_DIR', false);
/**
* OPTIONAL
* Whether to invoke composer after the repository is cloned or changes are
* fetched. Composer needs to be available on the server machine, installed
* globaly (as `composer`). See http://getcomposer.org/doc/00-intro.md#globally
*
* @var boolean Whether to use composer or not
* @link http://getcomposer.org/
*/
if (!defined('USE_COMPOSER')) define('USE_COMPOSER', false);
/**
* OPTIONAL
* The options that the composer is going to use.
*
* @var string Composer options
* @link http://getcomposer.org/doc/03-cli.md#install
*/
if (!defined('COMPOSER_OPTIONS')) define('COMPOSER_OPTIONS', '--no-dev');
/**
* OPTIONAL
* The COMPOSER_HOME environment variable is needed only if the script is
* executed by a system user that has no HOME defined, e.g. `www-data`.
*
* @var string Path to the COMPOSER_HOME e.g. `/tmp/composer`
* @link https://getcomposer.org/doc/03-cli.md#composer-home
*/
if (!defined('COMPOSER_HOME')) define('COMPOSER_HOME', false);
/**
* OPTIONAL
* Email address to be notified on deployment failure.
*
* @var string A single email address, or comma separated list of email addresses
* e.g. 'someone@example.com' or 'someone@example.com, someone-else@example.com, ...'
*/
if (!defined('EMAIL_ON_ERROR')) define('EMAIL_ON_ERROR', false);
// ===========================================[ Configuration end ]===
// If there's authorization error, set the correct HTTP header.
if (!isset($_GET['sat']) || $_GET['sat'] !== SECRET_ACCESS_TOKEN || SECRET_ACCESS_TOKEN === 'BetterChangeMeNowOrSufferTheConsequences') {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden', true, 403);
}
ob_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="robots" content="noindex">
<title>Simple PHP Git deploy script</title>
<style>
body { padding: 0 1em; background: #222; color: #fff; }
h2, .error { color: #c33; }
.prompt { color: #6be234; }
.command { color: #729fcf; }
.output { color: #999; }
</style>
</head>
<body>
<?php
if (!isset($_GET['sat']) || $_GET['sat'] !== SECRET_ACCESS_TOKEN) {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden', true, 403);
die('<h2>ACCESS DENIED!</h2>');
}
if (SECRET_ACCESS_TOKEN === 'BetterChangeMeNowOrSufferTheConsequences') {
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden', true, 403);
die("<h2>You're suffering the consequences!<br>Change the SECRET_ACCESS_TOKEN from it's default value!</h2>");
}
?>
<pre>
Checking the environment ...
Running as <b><?php echo trim(shell_exec('whoami')); ?></b>.
<?php
// Check if the required programs are available
$requiredBinaries = array('git', 'rsync');
if (defined('BACKUP_DIR') && BACKUP_DIR !== false) {
$requiredBinaries[] = 'tar';
if (!is_dir(BACKUP_DIR) || !is_writable(BACKUP_DIR)) {
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
die(sprintf('<div class="error">BACKUP_DIR `%s` does not exists or is not writeable.</div>', BACKUP_DIR));
}
}
if (defined('USE_COMPOSER') && USE_COMPOSER === true) {
$requiredBinaries[] = 'composer --no-ansi';
}
foreach ($requiredBinaries as $command) {
$path = trim(shell_exec('export PATH=$PATH; which '.$command));
if ($path == '') {
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
die(sprintf('<div class="error"><b>%s</b> not available. It needs to be installed on the server for this script to work.</div>', $command));
} else {
$version = explode("\n", shell_exec($command.' --version'));
printf('<b>%s</b> : %s'."\n"
, $path
, $version[0]
);
}
}
?>
Environment OK.
Using configuration defined in <?php echo CONFIG_FILE."\n"; ?>
Deploying <?php echo REMOTE_REPOSITORY; ?> <?php echo BRANCH."\n"; ?>
to <?php echo TARGET_DIR; ?> ...
<?php
// The commands
$commands = array();
// ========================================[ Pre-Deployment steps ]===
if (!is_dir(TMP_DIR)) {
// Clone the repository into the TMP_DIR
$commands[] = sprintf(
'git clone --depth=1 --branch %s %s %s'
, BRANCH
, REMOTE_REPOSITORY
, TMP_DIR
);
} else {
// TMP_DIR exists and hopefully already contains the correct remote origin
// so we'll fetch the changes and reset the contents.
$commands[] = sprintf(
'git --git-dir="%s.git" --work-tree="%s" fetch --tags origin %s'
, TMP_DIR
, TMP_DIR
, BRANCH
);
$commands[] = sprintf(
'git --git-dir="%s.git" --work-tree="%s" reset --hard FETCH_HEAD'
, TMP_DIR
, TMP_DIR
);
}
// Update the submodules
$commands[] = sprintf(
'git submodule update --init --recursive'
);
// Describe the deployed version
if (defined('VERSION_FILE') && VERSION_FILE !== '') {
$commands[] = sprintf(
'git --git-dir="%s.git" --work-tree="%s" describe --always > %s'
, TMP_DIR
, TMP_DIR
, VERSION_FILE
);
}
// Backup the TARGET_DIR
// without the BACKUP_DIR for the case when it's inside the TARGET_DIR
if (defined('BACKUP_DIR') && BACKUP_DIR !== false) {
$commands[] = sprintf(
"tar --exclude='%s*' -czf %s/%s-%s-%s.tar.gz %s*"
, BACKUP_DIR
, BACKUP_DIR
, basename(TARGET_DIR)
, md5(TARGET_DIR)
, date('YmdHis')
, TARGET_DIR // We're backing up this directory into BACKUP_DIR
);
}
// Invoke composer
if (defined('USE_COMPOSER') && USE_COMPOSER === true) {
$commands[] = sprintf(
'composer --no-ansi --no-interaction --no-progress --working-dir=%s install %s'
, TMP_DIR
, (defined('COMPOSER_OPTIONS')) ? COMPOSER_OPTIONS : ''
);
if (defined('COMPOSER_HOME') && is_dir(COMPOSER_HOME)) {
putenv('COMPOSER_HOME='.COMPOSER_HOME);
}
}
// ==================================================[ Deployment ]===
// Compile exclude parameters
$exclude = '';
foreach (unserialize(EXCLUDE) as $exc) {
$exclude .= ' --exclude='.$exc;
}
// Deployment command
$commands[] = sprintf(
'rsync -rltgoDzvO %s %s %s %s'
, TMP_DIR
, TARGET_DIR
, (DELETE_FILES) ? '--delete-after' : ''
, $exclude
);
// =======================================[ Post-Deployment steps ]===
// Remove the TMP_DIR (depends on CLEAN_UP)
if (CLEAN_UP) {
$commands['cleanup'] = sprintf(
'rm -rf %s'
, TMP_DIR
);
}
// =======================================[ Run the command steps ]===
$output = '';
foreach ($commands as $command) {
set_time_limit(TIME_LIMIT); // Reset the time limit for each command
if (file_exists(TMP_DIR) && is_dir(TMP_DIR)) {
chdir(TMP_DIR); // Ensure that we're in the right directory
}
$tmp = array();
exec($command.' 2>&1', $tmp, $return_code); // Execute the command
// Output the result
printf('
<span class="prompt">$</span> <span class="command">%s</span>
<div class="output">%s</div>
'
, htmlentities(trim($command))
, htmlentities(trim(implode("\n", $tmp)))
);
$output .= ob_get_contents();
ob_flush(); // Try to output everything as it happens
// Error handling and cleanup
if ($return_code !== 0) {
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
printf('
<div class="error">
Error encountered!
Stopping the script to prevent possible data loss.
CHECK THE DATA IN YOUR TARGET DIR!
</div>
'
);
if (CLEAN_UP) {
$tmp = shell_exec($commands['cleanup']);
printf('
Cleaning up temporary files ...
<span class="prompt">$</span> <span class="command">%s</span>
<div class="output">%s</div>
'
, htmlentities(trim($commands['cleanup']))
, htmlentities(trim($tmp))
);
}
$error = sprintf(
'Deployment error on %s using %s!'
, $_SERVER['HTTP_HOST']
, __FILE__
);
error_log($error);
if (EMAIL_ON_ERROR) {
$output .= ob_get_contents();
$headers = array();
$headers[] = sprintf('From: Simple PHP Git deploy script <simple-php-git-deploy@%s>', $_SERVER['HTTP_HOST']);
$headers[] = sprintf('X-Mailer: PHP/%s', phpversion());
mail(EMAIL_ON_ERROR, $error, strip_tags(trim($output)), implode("\r\n", $headers));
}
break;
}
}
?>
Done.
</pre>
</body>
</html>
<?php
/**
* Deployment configuration
*
* It's preferable to configure the script using this file instead of directly.
*
* Rename this file to `deploy-config.php` and edit the
* configuration options here instead of directly in `deploy.php`.
* That way, you won't have to edit the configuration again if you download the
* new version of `deploy.php`.
*
* @version 1.3.1
*/
/**
* Protect the script from unauthorized access by using a secret access token.
* If it's not present in the access URL as a GET variable named `sat`
* e.g. deploy.php?sat=Bett...s the script is not going to deploy.
*
* @var string
*/
define('SECRET_ACCESS_TOKEN', 'BetterChangeMeNowOrSufferTheConsequences');
/**
* The address of the remote Git repository that contains the code that's being
* deployed.
* If the repository is private, you'll need to use the SSH address.
*
* @var string
* Example: define('REMOTE_REPOSITORY', 'https://git@github.com/user/repo.git');
*/
define('REMOTE_REPOSITORY', 'git@github.com:user/repo.git');
/**
* The branch that's being deployed.
* Must be present in the remote repository.
*
* @var string
*/
define('BRANCH', 'master');
/**
* The location that the code is going to be deployed to.
* Don't forget the trailing slash!
*
* @var string Full path including the trailing slash
*/
define('TARGET_DIR', '/full/path/public_html/');
/**
* Whether to delete the files that are not in the repository but are on the
* local (server) machine.
*
* !!! WARNING !!! This can lead to a serious loss of data if you're not
* careful. All files that are not in the repository are going to be deleted,
* except the ones defined in EXCLUDE section.
* BE CAREFUL!
*
* @var boolean
*/
define('DELETE_FILES', false);
/**
* The directories and files that are to be excluded when updating the code.
* Normally, these are the directories containing files that are not part of
* code base, for example user uploads or server-specific configuration files.
* Use rsync exclude pattern syntax for each element.
*
* @var serialized array of strings
*/
define('EXCLUDE', serialize(array(
'.git',
)));
/**
* Temporary directory we'll use to stage the code before the update. If it
* already exists, script assumes that it contains an already cloned copy of the
* repository with the correct remote origin and only fetches changes instead of
* cloning the entire thing.
*
* @var string Full path including the trailing slash
*/
define('TMP_DIR', '/full/path/public_html/tmp/spgd-'.md5(REMOTE_REPOSITORY).'/');
/**
* Whether to remove the TMP_DIR after the deployment.
* It's useful NOT to clean up in order to only fetch changes on the next
* deployment.
*/
define('CLEAN_UP', false);
/**
* Output the version of the deployed code.
*
* @var string Full path to the file name
*/
define('VERSION_FILE', TMP_DIR.'VERSION');
/**
* Time limit for each command.
*
* @var int Time in seconds
*/
define('TIME_LIMIT', 30);
/**
* OPTIONAL
* Backup the TARGET_DIR into BACKUP_DIR before deployment.
*
* @var string Full backup directory path e.g. `/tmp/`
*/
define('BACKUP_DIR', false);
/**
* OPTIONAL
* Whether to invoke composer after the repository is cloned or changes are
* fetched. Composer needs to be available on the server machine, installed
* globaly (as `composer`). See http://getcomposer.org/doc/00-intro.md#globally
*
* @var boolean Whether to use composer or not
* @link http://getcomposer.org/
*/
define('USE_COMPOSER', false);
/**
* OPTIONAL
* The options that the composer is going to use.
*
* @var string Composer options
* @link http://getcomposer.org/doc/03-cli.md#install
*/
define('COMPOSER_OPTIONS', '--no-dev');
/**
* OPTIONAL
* The COMPOSER_HOME environment variable is needed only if the script is
* executed by a system user that has no HOME defined, e.g. `www-data`.
*
* @var string Path to the COMPOSER_HOME e.g. `/tmp/composer`
* @link https://getcomposer.org/doc/03-cli.md#composer-home
*/
define('COMPOSER_HOME', false);
/**
* OPTIONAL
* Email address to be notified on deployment failure.
*
* @var string A single email address, or comma separated list of email addresses
* e.g. 'someone@example.com' or 'someone@example.com, someone-else@example.com, ...'
*/
define('EMAIL_ON_ERROR', false);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment