├── LICENSE ├── README.md ├── deploy-config.example.php └── deploy.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 Marko Markovic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple PHP Git deploy script 2 | _Automatically deploy the code using PHP and Git._ 3 | 4 | ## Requirements 5 | 6 | * `git` and `rsync` are required on the server that's running the script 7 | (_server machine_). 8 | - Optionally, `tar` is required for backup functionality (`BACKUP_DIR` option). 9 | - Optionally, `composer` is required for composer functionality (`USE_COMPOSER` 10 | option). 11 | * The system user running PHP (e.g. `www-data`) needs to have the necessary 12 | access permissions for the `TMP_DIR` and `TARGET_DIR` locations on 13 | the _server machine_. 14 | * If the Git repository you wish to deploy is private, the system user running PHP 15 | also needs to have the right SSH keys to access the remote repository. 16 | 17 | ## Usage 18 | 19 | * Configure the script and put it somewhere that's accessible from the 20 | Internet. The preferred way to configure it is to use `deploy-config.php` file. 21 | Rename `deploy-config.example.php` to `deploy-config.php` and edit the 22 | configuration options there. That way, you won't have to edit the configuration 23 | again if you download the new version of `deploy.php`. 24 | * Configure your git repository to call this script when the code is updated. 25 | The instructions for GitHub and Bitbucket are below. 26 | 27 | ### GitHub 28 | 29 | 1. _(This step is only needed for private repositories)_ Go to 30 | `https://github.com/USERNAME/REPOSITORY/settings/keys` and add your server 31 | SSH key. 32 | 1. Go to `https://github.com/USERNAME/REPOSITORY/settings/hooks`. 33 | 1. Click **Add webhook** in the **Webhooks** panel. 34 | 1. Enter the **Payload URL** for your deployment script e.g. `http://example.com/deploy.php?sat=YourSecretAccessTokenFromDeployFile`. 35 | 1. _Optional_ Choose which events should trigger the deployment. 36 | 1. Make sure that the **Active** checkbox is checked. 37 | 1. Click **Add webhook**. 38 | 39 | ### Bitbucket 40 | 41 | 1. _(This step is only needed for private repositories)_ Go to 42 | `https://bitbucket.org/USERNAME/REPOSITORY/admin/deploy-keys` and add your 43 | server SSH key. 44 | 1. Go to `https://bitbucket.org/USERNAME/REPOSITORY/admin/services`. 45 | 1. Add **POST** service. 46 | 1. Enter the URL to your deployment script e.g. `http://example.com/deploy.php?sat=YourSecretAccessTokenFromDeployFile`. 47 | 1. Click **Save**. 48 | 49 | ### Generic Git 50 | 51 | 1. Configure the SSH keys. 52 | 1. Add a executable `.git/hooks/post_receive` script that calls the script e.g. 53 | 54 | ```sh 55 | #!/bin/sh 56 | echo "Triggering the code deployment ..." 57 | wget -q -O /dev/null http://example.com/deploy.php?sat=YourSecretAccessTokenFromDeployFile 58 | ``` 59 | 60 | ## Done! 61 | 62 | Next time you push the code to the repository that has a hook enabled, it's 63 | going to trigger the `deploy.php` script which is going to pull the changes and 64 | update the code on the _server machine_. 65 | 66 | For more info, read the source of `deploy.php`. 67 | 68 | ## Tips'n'Tricks 69 | 70 | * Because `rsync` is used for deployment, the `TARGET_DIR` doesn't have to be 71 | on the same server that the script is running e.g. `define('TARGET_DIR', 72 | 'username@example.com:/full/path/to/target_dir/');` is going to work as long 73 | as the user has the right SSH keys and access permissions. 74 | * You can have multiple scripts with different configurations. Simply rename 75 | the `deploy.php` to something else, for example `deploy_master.php` and 76 | `deploy_develop.php` and configure them separately. In that case, the 77 | configuration files need to be named `deploy_master-config.php` and 78 | `deploy_develop-config.php` respectively. 79 | 80 | --- 81 | 82 | If you find this script useful, consider donating BTC to `1fLnPZkMYw1TFNEsJZCciwDAmUhDw2wit`. 83 | -------------------------------------------------------------------------------- /deploy-config.example.php: -------------------------------------------------------------------------------- 1 | 172 | 173 | 174 | 175 | 176 | 177 | Simple PHP Git deploy script 178 | 185 | 186 | 187 | ACCESS DENIED!'); 191 | } 192 | if (SECRET_ACCESS_TOKEN === 'BetterChangeMeNowOrSufferTheConsequences') { 193 | header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden', true, 403); 194 | die("

You're suffering the consequences!
Change the SECRET_ACCESS_TOKEN from it's default value!

"); 195 | } 196 | ?> 197 |
198 | 
199 | Checking the environment ...
200 | 
201 | Running as .
202 | 
203 | BACKUP_DIR `%s` does not exists or is not writeable.', BACKUP_DIR));
211 | 	}
212 | }
213 | if (defined('USE_COMPOSER') && USE_COMPOSER === true) {
214 | 	$requiredBinaries[] = 'composer --no-ansi';
215 | }
216 | foreach ($requiredBinaries as $command) {
217 | 	$path = trim(shell_exec('which '.$command));
218 | 	if ($path == '') {
219 | 		header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
220 | 		die(sprintf('
%s not available. It needs to be installed on the server for this script to work.
', $command)); 221 | } else { 222 | $version = explode("\n", shell_exec($command.' --version')); 223 | printf('%s : %s'."\n" 224 | , $path 225 | , $version[0] 226 | ); 227 | } 228 | } 229 | ?> 230 | 231 | Environment OK. 232 | 233 | Using configuration defined in 234 | 235 | Deploying 236 | to ... 237 | 238 | %s' 277 | , TMP_DIR 278 | , TMP_DIR 279 | , VERSION_FILE 280 | ); 281 | } 282 | 283 | // Backup the TARGET_DIR 284 | // without the BACKUP_DIR for the case when it's inside the TARGET_DIR 285 | if (defined('BACKUP_DIR') && BACKUP_DIR !== false) { 286 | $commands[] = sprintf( 287 | "tar --exclude='%s*' -czf %s/%s-%s-%s.tar.gz %s*" 288 | , BACKUP_DIR 289 | , BACKUP_DIR 290 | , basename(TARGET_DIR) 291 | , md5(TARGET_DIR) 292 | , date('YmdHis') 293 | , TARGET_DIR // We're backing up this directory into BACKUP_DIR 294 | ); 295 | } 296 | 297 | // Invoke composer 298 | if (defined('USE_COMPOSER') && USE_COMPOSER === true) { 299 | $commands[] = sprintf( 300 | 'composer --no-ansi --no-interaction --no-progress --working-dir=%s install %s' 301 | , TMP_DIR 302 | , (defined('COMPOSER_OPTIONS')) ? COMPOSER_OPTIONS : '' 303 | ); 304 | if (defined('COMPOSER_HOME') && is_dir(COMPOSER_HOME)) { 305 | putenv('COMPOSER_HOME='.COMPOSER_HOME); 306 | } 307 | } 308 | 309 | // ==================================================[ Deployment ]=== 310 | 311 | // Compile exclude parameters 312 | $exclude = ''; 313 | foreach (unserialize(EXCLUDE) as $exc) { 314 | $exclude .= ' --exclude='.$exc; 315 | } 316 | // Deployment command 317 | $commands[] = sprintf( 318 | 'rsync -rltgoDzvO %s %s %s %s' 319 | , TMP_DIR 320 | , TARGET_DIR 321 | , (DELETE_FILES) ? '--delete-after' : '' 322 | , $exclude 323 | ); 324 | 325 | // =======================================[ Post-Deployment steps ]=== 326 | 327 | // Remove the TMP_DIR (depends on CLEAN_UP) 328 | if (CLEAN_UP) { 329 | $commands['cleanup'] = sprintf( 330 | 'rm -rf %s' 331 | , TMP_DIR 332 | ); 333 | } 334 | 335 | // =======================================[ Run the command steps ]=== 336 | $output = ''; 337 | foreach ($commands as $command) { 338 | set_time_limit(TIME_LIMIT); // Reset the time limit for each command 339 | if (file_exists(TMP_DIR) && is_dir(TMP_DIR)) { 340 | chdir(TMP_DIR); // Ensure that we're in the right directory 341 | } 342 | $tmp = array(); 343 | exec($command.' 2>&1', $tmp, $return_code); // Execute the command 344 | // Output the result 345 | printf(' 346 | $ %s 347 |
%s
348 | ' 349 | , htmlentities(trim($command)) 350 | , htmlentities(trim(implode("\n", $tmp))) 351 | ); 352 | $output .= ob_get_contents(); 353 | ob_flush(); // Try to output everything as it happens 354 | 355 | // Error handling and cleanup 356 | if ($return_code !== 0) { 357 | header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500); 358 | printf(' 359 |
360 | Error encountered! 361 | Stopping the script to prevent possible data loss. 362 | CHECK THE DATA IN YOUR TARGET DIR! 363 |
364 | ' 365 | ); 366 | if (CLEAN_UP) { 367 | $tmp = shell_exec($commands['cleanup']); 368 | printf(' 369 | 370 | 371 | Cleaning up temporary files ... 372 | 373 | $ %s 374 |
%s
375 | ' 376 | , htmlentities(trim($commands['cleanup'])) 377 | , htmlentities(trim($tmp)) 378 | ); 379 | } 380 | $error = sprintf( 381 | 'Deployment error on %s using %s!' 382 | , $_SERVER['HTTP_HOST'] 383 | , __FILE__ 384 | ); 385 | error_log($error); 386 | if (EMAIL_ON_ERROR) { 387 | $output .= ob_get_contents(); 388 | $headers = array(); 389 | $headers[] = sprintf('From: Simple PHP Git deploy script ', $_SERVER['HTTP_HOST']); 390 | $headers[] = sprintf('X-Mailer: PHP/%s', phpversion()); 391 | mail(EMAIL_ON_ERROR, $error, strip_tags(trim($output)), implode("\r\n", $headers)); 392 | } 393 | break; 394 | } 395 | } 396 | ?> 397 | 398 | Done. 399 |
400 | 401 | 402 | --------------------------------------------------------------------------------