├── Installer.php
├── LICENSE
├── README.md
├── composer.json
├── config.inc.php
├── deployer
├── img
├── cicd-gitlab-webhook.png
└── sequence-diagram.plantuml
├── src
├── App.php
├── Deployer.php
├── GetOpt.php
├── ShellConsole.php
├── default-config.inc.php
└── views
│ └── help.php
├── test
├── config.inc.php
└── deployer
├── tools
├── README.md
├── deployer
└── mirror
└── webhook
├── bitbucket
└── index.php
├── gitlab
└── index.php
└── index.html
/Installer.php:
--------------------------------------------------------------------------------
1 | Alternatively, you could call the original bootstrap: `$ ./deployer`, `$ php ./deployer`
78 |
79 | The interactive result could like be:
80 | ```
81 | $ deployer
82 |
83 | Your available projects in configuration:
84 | [0] your.project.com
85 | [1] second.project.com
86 | [2] other.site.com
87 |
88 | Please select a project [number or project, Ctrl+C to quit]:0
89 |
90 | Selected Project: your.project.com
91 | Successful Excuted Task: Git
92 | Successful Excuted Task: Composer
93 | Successful Excuted Task: Composer
94 | Successful Excuted Task: Test UnitTest
95 | Successful Excuted Task: Commands before: Minify assets
96 | Successful Excuted Task: Deploy to 127.0.0.11
97 | Successful Excuted Task: Deploy to 127.0.0.12
98 | Successful Excuted Task: Deploy
99 | Successful Excuted Task: Commands after: Email notification
100 | ```
101 |
102 | Or you could run by non-interactive mode with the same purpose:
103 |
104 | ```
105 | $ deployer --project="your.project.com"
106 | ```
107 |
108 | ---
109 |
110 | REQUIREMENTS
111 | ------------
112 |
113 | This library requires the following:
114 |
115 | - PHP(CLI) 5.4.0+
116 | - RSYNC
117 |
118 | ---
119 |
120 | INSTALLATION
121 | ------------
122 |
123 | ### Composer Installation
124 |
125 | Using Composer by `sudoer` or `root` to install is the easiest way with auto-installer:
126 |
127 | ```
128 | composer create-project --prefer-dist yidas/deployer-php-cli
129 | ```
130 |
131 | ### Wget Installation
132 |
133 | You could see [Release](https://github.com/yidas/deployer-php-cli/releases) for picking up the package with version, for example:
134 |
135 | ```
136 | $ wget https://github.com/yidas/deployer-php-cli/archive/master.tar.gz -O deployer-php-cli.tar.gz
137 | ```
138 |
139 | After download, uncompress the package:
140 |
141 | ```
142 | $ tar -zxvf deployer-php-cli.tar.gz
143 | ```
144 |
145 | > In addition, you can rename the unzipped folder by `mkdir deployer-php-cli && tar -zxvf deployer-php-cli.tar.gz --strip-components 1 -C deployer-php-cli`
146 |
147 | #### Make Command
148 |
149 | To make a command for deployer, if the package folder is `deployer-php-cli` then create a symbol by following command:
150 |
151 | ```
152 | $ sudo chmod +x $(pwd -L)/deployer-php-cli/deployer
153 | $ sudo ln -s $(pwd -L)/deployer-php-cli/deployer /usr/bin/deployer
154 | ```
155 |
156 | ### Startup
157 |
158 | After installation, you could start to set up the `config.inc.php` for deployer, and enjoy to use:
159 |
160 | ```
161 | $ deployer
162 | ```
163 |
164 | ### Upgrade
165 |
166 | To upgrade, you could re-install the deployer and copy the old `config.inc.php` to the new one, for example:
167 |
168 | ```
169 | $ cp ./deployer-php-cli/config.inc.php ./
170 | $ rm -r deployer-php-cli
171 | $ composer create-project --prefer-dist yidas/deployer-php-cli
172 | $ mv ./config.inc.php ./deployer-php-cli
173 | ```
174 |
175 | ---
176 |
177 | CONFIGURATION
178 | -------------
179 |
180 | ### Project Setting:
181 |
182 | You need to set up the projects configuration such as servers, source and destination in `config.inc.php` file:
183 |
184 | ```php
185 | [
190 | 'servers' => [
191 | '127.0.0.1',
192 | ],
193 | 'source' => '/home/user/project',
194 | 'destination' => '/var/www/html/prod/',
195 | ],
196 | ];
197 | ```
198 |
199 | > You could refer [config.inc.php](https://github.com/yidas/deployer-php-cli/blob/master/config.inc.php) file as an example..
200 |
201 | ### Config Options:
202 |
203 | Configuration provides many features' setting, you could customize and pick up the setting you need.
204 |
205 | |Key|Type|Description|
206 | |:-|:-|:-|
207 | |**servers**|array|Distant server host list|
208 | |**user**|array\|string|Local/Remote server user, auto detect current user if empty|
209 | |**source**|string|Local directory for deploy, use `/` as end means `*` |
210 | |**destination**|string|Remote path for synchronism|
211 | |**exclude**|array|Excluded files based on sourceFile path|
212 | |verbose|bool|Enable verbose with more infomation or not|
213 |
214 | #### Git
215 |
216 | To use Git into deploy task, you need to init or clone Git to the source directory at the first time:
217 |
218 | ```
219 | $ git clone git@gitlab.com:username/project-to-deploy.git sourceDir
220 | ```
221 |
222 | |Key|Type|Description|
223 | |:-|:-|:-|
224 | |enabled|bool|Enable git or not|
225 | |checkout|bool|Execute git checkout -- . before git pull |
226 | |branch|string|Branch name for git pull, pull default branch if empty |
227 | |submodule|bool|Git submodule enabled |
228 |
229 | #### Composer
230 |
231 | To use Composer into deploy task, make sure that there are composer files in the source directory.
232 |
233 | |Key|Type|Description|
234 | |:-|:-|:-|
235 | |enabled|bool|Enable Composer or not|
236 | |path|string|Composer executing relative path which supports multiple array paths|
237 | |command|string|Update command likes `composer update`|
238 |
239 | #### Test
240 |
241 | To use Test into deploy task, make sure that there are test configuration in the source directory.
242 |
243 | |Key|Type|Description|
244 | |:-|:-|:-|
245 | |enabled|bool|Enable Test or not|
246 | |name|string|The test name for display|
247 | |type|string|Test type, support `phpunit`.|
248 | |command|string|The test bootstrap command supported relative filepath such as `./vendor/bin/phpunit`|
249 | |configuration|string|The test configuration file supported relative filepath such as `./phpunit.xml`|
250 |
251 | #### Tests
252 |
253 | For multiple test tasks, using array to declare each [test options](#test):
254 |
255 | ```php
256 | return [
257 | 'default' => [
258 | 'tests' => [
259 | [
260 | 'name' => 'Test Task 1',
261 | // ...
262 | ],
263 | [
264 | 'name' => 'Test Task 2',
265 | // ...
266 | ],
267 | ],
268 | // ...
269 | ```
270 |
271 | #### Rsync
272 |
273 | |Key|Type|Description|
274 | |:-|:-|:-|
275 | |enabled|bool|Enable rsync or not|
276 | |params|string|Addition params of rsync command|
277 | |timeout|int|Timeout seconds of each rsync connections|
278 | |sleepSeconds|int|Seconds waiting of each rsync connections|
279 | |identityFile|string|Identity file path for appling rsync|
280 |
281 | #### Commands
282 |
283 | Commands provides you to customize deploy tasks with many trigger hooks.
284 |
285 | |Key|Type|Description|
286 | |:-|:-|:-|
287 | |init|array|Addition commands triggered at initialization|
288 | |before|array|Addition commands triggered before deploying|
289 | |after|array|Addition commands triggered after deploying|
290 |
291 | ### Example
292 |
293 | * Copy `project` directory form `/var/www/html/` to destination under `/var/www/html/test/`:
294 |
295 | ```php
296 | 'source' => '/var/www/html/project',
297 | 'destination' => '/var/www/html/test/',
298 | ```
299 |
300 | * Copy all files (`*`) form `/var/www/html/project/` to destination under `/var/www/html/test/`:
301 |
302 | ```php
303 | 'source' => '/var/www/html/project/',
304 | 'destination' => '/var/www/html/test/',
305 | ```
306 |
307 | ---
308 |
309 | USAGE
310 | -----
311 |
312 | ```
313 | Usage:
314 | deployer [options] [arguments]
315 | ./deployer [options] [arguments]
316 |
317 | Options:
318 | -h, --help Display this help message
319 | --version Show the current version of the application
320 | -p, --project Project key by configuration for deployment
321 | --config Show the seleted project configuration
322 | --configuration
323 | --skip-git Force to skip Git process
324 | --skip-composer Force to skip Composer process
325 | --git-reset Git reset to given commit with --hard option
326 | -v, --verbose Increase the verbosity of messages
327 | ```
328 |
329 | ### Interactive Project Select
330 |
331 | ```
332 | $ deployer
333 |
334 | Your available projects in configuration:
335 | [0] default
336 | [1] your.project.com
337 |
338 | Please select a project [number or project, Ctrl+C to quit]:your.project.com
339 |
340 | Selected Project: your.project.com
341 | Successful Excuted Task: Git
342 | Successful Excuted Task: Composer
343 | Successful Excuted Task: Deploy to 127.0.0.11
344 | Successful Excuted Task: Deploy
345 | ```
346 |
347 | ### Non-Interactive Project Select
348 |
349 | ```
350 | $ deployer --project="your.project.com"
351 | ```
352 |
353 | ### Skip Flows
354 |
355 | You could force to skip flows such as Git and Composer even when you enable then in config.
356 |
357 | ```
358 | $ deployer --project="default" --skip-git --skip-composer
359 | ```
360 |
361 | ### Revert & Reset back
362 |
363 | You could reset git to specified commit by using `--git-reset` option when you get trouble after newest release.
364 |
365 | ```
366 | $ deployer --project="default" --git-reset="79616d"
367 | ```
368 |
369 | > This option is same as executing `git reset --hard 79616d` in source project.
370 |
371 | ---
372 |
373 | IMPLEMENTATION
374 | --------------
375 |
376 | Assuming `project1` is the developing project which you want to deploy.
377 |
378 | Developers must has their own site to develop, for example:
379 |
380 | ```
381 | # Dev host
382 | /var/www/html/dev/nick/project1
383 | /var/www/html/dev/eric/project1
384 | ```
385 |
386 | In general, you would has stage `project1` which the files are same as production:
387 |
388 | ```
389 | # Dev/Stage host
390 | /var/www/html/project1
391 | ```
392 |
393 | The purpose is that production files need to be synchronous from stage:
394 |
395 | ```
396 | # Production host
397 | /var/www/html/project1
398 | ```
399 |
400 | This tool regard stage project as `source`, which means production refers to `destination`, so the config file could like:
401 |
402 | ```php
403 | return [
404 | 'project1' => [
405 | ...
406 | 'source' => '/var/www/html/project1',
407 | 'destination' => '/var/www/html/',
408 | ...
409 | ```
410 |
411 | After running this tool to deploy `project1`, the stage project's files would execute processes likes `git pull` then synchronise to production.
412 |
413 |
414 | ### Permissions Handling
415 |
416 | ##### 1. Local and Remote Users
417 |
418 | You could create a user on local for runing Deployer with `umask 002`. It will run process by the local user you set even you run Deployer by root:
419 |
420 | ```php
421 | return [
422 | 'project1' => [
423 | 'user' => [
424 | 'local' => 'deployer',
425 | 'remote' => 'deployer',
426 | ],
427 | ...
428 | ```
429 |
430 | ##### 2. Application File Permissions
431 |
432 | Deployer uses `rsync` to deploy local source project to remote ***without*** `--no-perms`, which means that the source files' permission would keep on remote, but the files' owner would re-generate by remote user including `root` with `--no-owner --no-group`.
433 |
434 | On the remote user, you could set the user's default groud ID to `www-data` in `/etc/passwd`, which the ***local user*** generates `664/775` mod files to deploy for ***remote*** `www-data` access.
435 |
436 | > For local user, `umask 002` could be set in `~/.bashrc` or global. Note that the permission need to apply for source files such as init from Git clone.
437 |
438 | ---
439 |
440 | CI/CD
441 | -----
442 |
443 | ### Webhook
444 |
445 | Deployer provides webhook feature for triggering project deployment by any webhook service such as Gitlab.
446 |
447 | To use webhook, you need add webhook setting into the projects you needed in `config.inc.php`:
448 |
449 | ```php
450 | return [
451 | 'project' => [
452 | // ...
453 | 'webhook' => [
454 | 'enabled' => true,
455 | 'provider' => 'gitlab',
456 | 'project' => 'yidas/deployer-php-cli',
457 | 'token' => 'da39a3ee5e6b4b0d3255bfef95601890afd80709',
458 | 'branch' => 'release',
459 | 'log' => '/tmp/deployer-webhook-project.log'
460 | ],
461 | ],
462 | ];
463 | ```
464 |
465 | |Key|Type|Description|
466 | |:-|:-|:-|
467 | |enabled|bool|Enable Webhook or not|
468 | |provider|string|Webhook provider such as `gitlab`|
469 | |project|string|Provider's project name likes `username/project`|
470 | |token|string|Webhook secret token|
471 | |branch|string|Listening branch for push event|
472 | |log|bool\|string|Enabled log and specify the log file|
473 |
474 | #### PHP Web Setting
475 |
476 | Deployer need a user to excute deployment, and the user is usually not the PHP web user.
477 |
478 | For PHP-FPM, you could add a new PHP pool socket with the current user setting for the webhook site, for example `/etc/php/fpm/pool.d/deployer.conf`:
479 |
480 | ```php
481 | [deployer]
482 |
483 | user = deployer
484 | group = www-data
485 |
486 | listen = /run/php/php7.0-fpm_deployer.sock
487 | ```
488 |
489 | Then give the new socket to the webhook server setting, for Nginx eaxmple `/etc/nginx/site-enabled/webhook`:
490 |
491 | ```nginx
492 | server_name webhook.your.com;
493 | root /srv/deployer/deployer-php-cli/webhook;
494 |
495 | location ~ \.php$ {
496 | include snippets/fastcgi-php.conf;
497 | fastcgi_param SCRIPT_FILENAME $request_filename;
498 | fastcgi_pass unix:/run/php/php7.0-fpm_deployer.sock;
499 | }
500 | ```
501 |
502 | After a successful webhook, Deployer would prepare to process while responding the status and the result url for checking the deployment result.
503 |
504 | > Note: The `PATH` environment variable between Shell and PHP should be set to the same to prevent any unexpected problems.
505 |
506 | #### Gitlab
507 |
508 | - Prividor key: `gitlab`
509 |
510 | According to above Nginx website setting, the webhook URL could be `https://webhook.your.com/gitlab`. After setting `config.inc.php` and setting up scecret token, you could give a push event to go!
511 |
512 |
513 |
514 | > Note: Default setting is listen `release` branch's push event to trigger.
515 |
516 | To browse the web page for result log report, enter the same webhook URL with `log` and `token` parameters to access.
517 | For example: `https://webhook.your.com/gitlab?log={project-name}&token={project-token}`
518 |
519 | ---
520 |
521 | ADDITIONS
522 | ---------
523 |
524 | ### Rsync without Password:
525 |
526 | You can put your local user's SSH public key to destination server user for authorization.
527 | ```
528 | .ssh/id_rsa.pub >> .ssh/authorized_keys
529 | ```
530 |
531 | ### Save Binary Encode File:
532 |
533 |
534 | While excuting script, if you get the error like `Exception: Zend Extension ./deployer does not exist`, you may save the script file with binary encode, which could done by using `vim`:
535 |
536 | ```
537 | :set ff=unix
538 | ```
539 |
540 | ### Yii2 Deployment
541 |
542 | For `yii2-app-advanced`, you need to enable Composer and set yii2 init command in `config.inc.php`:
543 |
544 | ```php
545 | 'composer' => [
546 | 'enabled' => true,
547 | ],
548 | 'commands' => [
549 | 'before' => [
550 | 'yii2 init prod' => './init --env=Production --overwrite=All',
551 | ],
552 | ],
553 | ```
554 |
555 | ### Minify/Uglify by Gulp
556 |
557 | #### 1. Install NPM, for Debian/Ubuntu:
558 |
559 | ```
560 | apt-get install npm
561 | ```
562 |
563 | #### 2. Install Gulp by NPM
564 |
565 | ```
566 | npm install -g gulp
567 | ```
568 |
569 | #### 3. Create Gulp Project
570 |
571 | ```
572 | cd /srv/tools/minify-project
573 | npm init
574 | npm install gulp --save-dev
575 | touch gulpfile.js
576 | ```
577 |
578 | #### 4. Set Gulp with packages
579 |
580 | Package: [gulp-uglify](https://www.npmjs.com/package/gulp-uglify)
581 |
582 | ```
583 | $ npm install gulp-uglify --save-dev
584 | $ npm install pump --save-dev
585 | ```
586 |
587 | `gulpfile.js`:
588 |
589 | ```javascript
590 | var gulp = require('gulp');
591 | var uglify = require('gulp-uglify');
592 | var pump = require('pump');
593 | var assetPath = '/srv/your.project.com/assets/js';
594 |
595 | gulp.task('compress', function (callback) {
596 | pump([
597 | gulp.src(assetPath+'/**/*.js'),
598 | uglify(),
599 | gulp.dest(assetPath)
600 | ],
601 | callback
602 | );
603 | });
604 | ```
605 |
606 | #### 5. Set Gulp Process into Deployer
607 |
608 | ```
609 | 'source' => '/srv/project',
610 | 'commands' => [
611 | 'before' => [
612 | 'Minify inner JS' => [
613 | 'command' => 'cd /srv/tools/minify-project; gulp compress',
614 | ],
615 | ],
616 | ],
617 | ```
618 |
619 |
620 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yidas/deployer-php-cli",
3 | "description": "Code deployment tool based on RSYNC running by PHP-CLI script",
4 | "keywords": ["deployment", "continuous-integration", "rsync" ,"php-cli"],
5 | "homepage": "https://github.com/yidas/deployer-php-cli",
6 | "type": "project",
7 | "license": "MIT",
8 | "support": {
9 | "issues": "https://github.com/yidas/deployer-php-cli/issues",
10 | "source": "https://github.com/yidas/deployer-php-cli"
11 | },
12 | "minimum-stability": "stable",
13 | "require": {
14 | "php": ">=5.4.0"
15 | },
16 | "autoload": {
17 | "classmap": ["Installer.php"]
18 | },
19 | "scripts": {
20 | "post-create-project-cmd": [
21 | "Installer::postCreateProject"
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/config.inc.php:
--------------------------------------------------------------------------------
1 | [
10 | 'servers' => [
11 | '127.0.0.1',
12 | ],
13 | 'source' => '/home/user/project',
14 | 'destination' => '/var/www/html/prod/',
15 | ],
16 | // This project config processes Git and Composer before deployment
17 | 'advanced' => [
18 | 'servers' => [
19 | '127.0.0.1',
20 | ],
21 | 'user' => [
22 | 'local' => '',
23 | 'remote' => '',
24 | ],
25 | 'source' => '/home/user/project-advanced',
26 | 'destination' => '/var/www/html/prod/',
27 | 'exclude' => [
28 | '.git',
29 | 'tmp/*',
30 | ],
31 | 'git' => [
32 | 'enabled' => true,
33 | 'path' => './',
34 | 'checkout' => true,
35 | 'branch' => 'master',
36 | 'submodule' => false,
37 | ],
38 | 'composer' => [
39 | 'enabled' => true,
40 | 'path' => './',
41 | // 'path' => ['./', './application/'],
42 | // If You use Xdebug on php-fpm
43 | // 'command' => 'COMPOSER_ALLOW_XDEBUG=1 composer -n install',
44 | 'command' => 'composer -n install',
45 | ],
46 | 'test' => [
47 | 'enabled' => false,
48 | 'name' => 'PHPUnit',
49 | 'type' => 'phpunit',
50 | // CodeIgniter 3 for example (https://github.com/yidas/codeigniter-phpunit)
51 | 'command' => './application/vendor/bin/phpunit',
52 | 'configuration' => './application/phpunit.xml',
53 | ],
54 | 'rsync' => [
55 | 'enabled' => true,
56 | 'params' => '-av --delete',
57 | // 'sleepSeconds' => 0,
58 | // 'timeout' => 60,
59 | // 'identityFile' => '/home/deployer/.ssh/id_rsa',
60 | ],
61 | 'commands' => [
62 | 'before' => [
63 | '',
64 | ],
65 | ],
66 | 'webhook' => [
67 | 'enabled' => false,
68 | 'provider' => 'gitlab',
69 | 'project' => 'yidas/deployer-php-cli',
70 | 'token' => 'thisistoken',
71 | ],
72 | 'verbose' => false,
73 | ],
74 | ];
75 |
--------------------------------------------------------------------------------
/deployer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 |
3 |
11 | * @filesource PHP 5.4.0+
12 | * @filesource RSYNC commander
13 | * @filesource Git commander
14 | * @filesource Composer commander
15 | *
16 | * @param string $argv[1] Project
17 | * @example
18 | * $ ./deployer // Interactive Project Select
19 | * $ ./deployer --project="default" // Non-Interactive Project Select
20 | */
21 |
22 | // App loader
23 | require __DIR__. '/src/App.php';
24 |
25 |
26 | /* Bootstrap */
27 |
28 | // error_reporting(E_ALL);
29 | // ini_set("display_errors", 1);
30 |
31 | /* Config List Handler */
32 | $configList = require __DIR__. '/config.inc.php';
33 | // print_r($configList);
34 |
35 | $argv = isset($argv) ? $argv : [];
36 |
37 | $app = new App;
38 | $app->run($configList, $argv);
39 |
40 |
--------------------------------------------------------------------------------
/img/cicd-gitlab-webhook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yidas/deployer-php-cli/967258859b3ff4f166c46d5ff1c6158531aa7bf4/img/cicd-gitlab-webhook.png
--------------------------------------------------------------------------------
/img/sequence-diagram.plantuml:
--------------------------------------------------------------------------------
1 | @startuml
2 | actor "User" as user
3 | participant "Stage Server" as stage
4 | participant "Git Repository" as repo
5 | participant "Production Server Group" as real
6 |
7 |
8 | alt Automation
9 | user -> repo: Push released branch
10 | repo -> stage: Trigger webhook
11 | else Manual
12 | user -> stage: SSH tunnel
13 | stage -> stage: Run by command line
14 | end
15 |
16 | group Pipeline
17 | stage -> stage: Git, Composer, test, tasks before
18 | stage -> real: Rsync
19 | real --> stage: Result
20 | stage -> stage: Tasks after
21 | end group
22 |
23 | alt Log Mode enabled
24 | stage -> stage: Save log file
25 | user -> stage: Browse result web page \n(Secret token)
26 | stage --> user: Result report
27 | end
28 |
29 | @enduml
30 |
--------------------------------------------------------------------------------
/src/App.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 | class App
10 | {
11 | const VERSION = '1.12.0';
12 |
13 | function __construct()
14 | {
15 | // Loader
16 | require __DIR__. '/ShellConsole.php';
17 | require __DIR__. '/Deployer.php';
18 | require __DIR__. '/GetOpt.php';
19 | }
20 |
21 | /**
22 | * @param array $configList
23 | */
24 | public function run(Array $configList, Array $argv)
25 | {
26 | // $projectKey = (isset($argv[1])) ? $argv[1] : 'default';
27 |
28 | /**
29 | * Options definition
30 | */
31 | $shortopts = "";
32 | $shortopts .= "h";
33 | $shortopts .= "p:";
34 | $shortopts .= "v";
35 |
36 | $longopts = array(
37 | "project:",
38 | "skip-git",
39 | "skip-composer",
40 | "git-reset:",
41 | "verbose",
42 | "config",
43 | "configuration",
44 | "help",
45 | "version",
46 | );
47 |
48 | try {
49 |
50 | // GetOpt
51 | $getOpt = new GetOpt($shortopts, $longopts);
52 | // var_dump($getOpt->getOptions());
53 |
54 | $projectKey = $getOpt->get(['project', 'p']);
55 | $showConfig = $getOpt->has(['config', 'configuration']);
56 | $showHelp = $getOpt->has(['help', 'h']);
57 | $showVersion = $getOpt->has(['version']);
58 |
59 | /**
60 | * Exception before App
61 | */
62 | // Help
63 | if ($showHelp) {
64 | // Version first
65 | $this->_echoVersion();
66 | echo "\r\n";
67 | // Load view with CLI auto display
68 | require __DIR__. '/views/help.php';
69 | echo "\r\n";
70 | return;
71 | }
72 | // Version
73 | if ($showVersion) {
74 | // Get version
75 | $this->_echoVersion();
76 | return;
77 | }
78 |
79 | // Check project config
80 | if (!isset($configList[$projectKey])) {
81 |
82 | // Welcome information
83 | // Get app root path
84 | $fileLocate = dirname(__DIR__);
85 | $this->_echoVersion();
86 | echo " Bootstrap directory: {$fileLocate}. \r\n";
87 | echo " Usage manual: `deployer --help`\r\n";
88 | echo "\r\n";
89 |
90 | // First time flag
91 | $isFirstTime = ($projectKey===null) ? true : false;
92 |
93 | while (!isset($configList[$projectKey])) {
94 |
95 | // Not in the first round
96 | if (!$isFirstTime) {
97 | echo "ERROR: The `{$projectKey}` project doesn't exist in your configuration.\n\n";
98 | }
99 |
100 | // Available project list
101 | echo "Your available projects in configuration:\n";
102 | $projectKeyMap = [];
103 | foreach ($configList as $key => $project) {
104 |
105 | $projectKeyMap[] = $key;
106 | // Get map key
107 | end($projectKeyMap);
108 | $num = key($projectKeyMap);
109 |
110 | echo " [{$num}] {$key}\n";
111 | }
112 | echo "\r\n";
113 | // Get project input
114 | echo " Please select a project [number or project, Ctrl+C to quit]:";
115 | $projectKey = trim(fgets(STDIN));
116 | echo "\r\n";
117 |
118 | // Number input finding by $projectKeyMap
119 | if (is_numeric($projectKey)) {
120 | $projectKey = isset($projectKeyMap[$projectKey])
121 | ? $projectKeyMap[$projectKey]
122 | : $projectKey;
123 | }
124 |
125 | $isFirstTime = false;
126 | }
127 | }
128 |
129 | // Config initialized
130 | $defaultConfig = require __DIR__. '/default-config.inc.php';
131 | $config = array_replace_recursive($defaultConfig, $configList[$projectKey]);
132 | // Add `projectKey` key to the current config
133 | $config['projectKey'] = $projectKey;
134 |
135 | // Rewrite config
136 | $config['git']['enabled'] = ($getOpt->has('skip-git'))
137 | ? false : $this->_val($config, ['git', 'enabled']);
138 | $config['composer']['enabled'] = ($getOpt->has('skip-composer'))
139 | ? false : $this->_val($config, ['composer', 'enabled']);
140 | $config['verbose'] = ($getOpt->has(['verbose', 'v']))
141 | ? true : $this->_val($config, ['verbose']);
142 | // Other config
143 | $config['git']['reset'] = $getOpt->get('git-reset');
144 |
145 | // Initial Deployer
146 | $deployer = new Deployer($config);
147 |
148 | /**
149 | * Exception before Deployer run
150 | */
151 | if ($showConfig) {
152 | echo "The `{$projectKey}` project's configuration is below:\n";
153 | print_r($deployer->getConfig());
154 | return;
155 | }
156 |
157 | // Run Deployer
158 | $deployer->run();
159 |
160 | } catch (Exception $e) {
161 |
162 | die("ERROR:{$e->getMessage()}\n");
163 | }
164 | }
165 |
166 | /**
167 | * Echo a line of version info
168 | */
169 | protected function _echoVersion()
170 | {
171 | $version = self::VERSION;
172 | echo "Deployer-PHP-CLI version {$version} \r\n";
173 | }
174 |
175 | /**
176 | * Var checker
177 | *
178 | * @param mixed Variable
179 | * @param array Variable array level ['level1', 'key']
180 | * @return mixed value of specified variable
181 | */
182 | protected function _val($var, $arrayLevel=[])
183 | {
184 | if (!isset($var)) {
185 |
186 | return null;
187 | }
188 |
189 | foreach ($arrayLevel as $key => $level) {
190 |
191 | if (!isset($var[$level])) {
192 |
193 | return null;
194 | }
195 |
196 | $var = &$var[$level];
197 | }
198 |
199 | return $var;
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/Deployer.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 |
11 | /**
12 | * Deployer Core
13 | */
14 | class Deployer
15 | {
16 | use ShellConsole;
17 |
18 | private $_config;
19 |
20 | /**
21 | * Result response
22 | *
23 | * @var string Text
24 | */
25 | private $_response;
26 |
27 | function __construct($config)
28 | {
29 | $this->_setConfig($config);
30 | }
31 |
32 | /**
33 | * Run
34 | *
35 | * @return string Result response
36 | */
37 | public function run()
38 | {
39 | $config = &$this->_config;
40 |
41 | // Check config
42 | $this->_checkConfig();
43 |
44 | ob_implicit_flush();
45 |
46 | // Local user check
47 | /**
48 | * @todo Switch user
49 | */
50 | if ($config['user']['local'] && $config['user']['local']!=$this->_getUser()) {
51 | $this->_print("Access denied, please switch to local user: `{$config['user']['local']}` from config");
52 | exit;
53 | }
54 |
55 | // cd into source directory
56 | $this->_cmd("cd {$this->_config['source']};");
57 |
58 | // Project selected info
59 | $this->_result("Selected Project: {$config['projectKey']}");
60 |
61 | // Total cost time start
62 | $startSecond = microtime(true);
63 |
64 | $this->runCommands('init');
65 | $this->runGit();
66 | $this->runComposer();
67 | $this->runTest();
68 | $this->runTests();
69 | $this->runCommands('before');
70 | $this->runDeploy();
71 | $this->runCommands('after');
72 |
73 | // Total cost time end
74 | $costSecond = abs(microtime(true) - $startSecond);
75 | $costSecond = number_format($costSecond, 2, ".", "");
76 | $this->_result("Total Cost Time: {$costSecond}s");
77 |
78 | return $this->_response;
79 | }
80 |
81 | /**
82 | * Git Process
83 | */
84 | public function runGit()
85 | {
86 | if (!isset($this->_config['git'])) {
87 | return;
88 | }
89 |
90 | // Default config
91 | $defaultConfig = [
92 | 'enabled' => false,
93 | 'path' => './',
94 | 'checkout' => true,
95 | 'branch' => 'master',
96 | 'submodule' => false,
97 | ];
98 |
99 | // Config init
100 | $config = array_merge($defaultConfig, $this->_config['git']);
101 |
102 | // Check enabled
103 | if (!$config || empty($config['enabled']) ) {
104 | return;
105 | }
106 |
107 | // Git process
108 | $this->_verbose("");
109 | $this->_verbose("### Git Process Start");
110 |
111 | // Path
112 | $path = (isset($config['path'])) ? $config['path'] : './';
113 | $path = $this->_getAbsolutePath($path);
114 |
115 | // Git Checkout
116 | if ($config['checkout']) {
117 | $result = $this->_cmd("git checkout -- .", $output, $path);
118 | // Common error check
119 | $this->checkError($result, $output);
120 | }
121 | // Git pull
122 | $cmd = ($config['branch'])
123 | ? "git pull origin {$config['branch']}"
124 | : "git pull";
125 | $result = $this->_cmd($cmd, $output, $path);
126 | // Common error check
127 | $this->checkError($result, $output);
128 | $this->_verbose("### Git Process Pull");
129 | $this->_verbose($output);
130 |
131 | // Git Checkout
132 | if (isset($config['submodule']) && $config['submodule']) {
133 | $result = $this->_cmd("git submodule init", $output, $path);
134 | $result = $this->_cmd("git submodule update", $output, $path);
135 | // Common error check
136 | $this->checkError($result, $output);
137 | }
138 |
139 | // Git reset commit
140 | if (isset($config['reset']) && $config['reset']) {
141 | $result = $this->_cmd("git reset --hard {$config['reset']}", $output, $path);
142 | $this->_verbose("### Git Process Reset Commit");
143 | $this->_verbose($result);
144 | // Common error check
145 | $this->checkError($result, $output);
146 | }
147 |
148 | $this->_verbose("### /Git Process End\n");
149 |
150 | $this->_done("Git");
151 | }
152 |
153 | /**
154 | * Composer Process
155 | */
156 | public function runComposer()
157 | {
158 | if (!isset($this->_config['composer'])) {
159 | return;
160 | }
161 |
162 | // Composer Config
163 | $config = &$this->_config['composer'];
164 |
165 | // Check enabled
166 | if (!$config || empty($config['enabled']) ) {
167 | return;
168 | }
169 |
170 | // Composer process
171 | $this->_verbose("");
172 | $this->_verbose("### Composer Process Start");
173 |
174 | // Path
175 | $path = (isset($config['path'])) ? $config['path'] : './';
176 | // Alternative multiple composer option
177 | $paths = is_array($path) ? $path : [$path];
178 | $isSinglePath = (count($paths)<=1) ? true : false;
179 |
180 | // Each composer path with same setting
181 | foreach ($paths as $key => $path) {
182 |
183 | $path = $this->_getAbsolutePath($path);
184 |
185 | $cmd = $config['command'];
186 | // Shell execution
187 | $result = $this->_cmd($cmd, $output, $path);
188 |
189 | $this->_verbose("### Composer Process Result");
190 | $this->_verbose($output);
191 |
192 | /**
193 | * Check error
194 | */
195 | if (!$result) {
196 | // Error
197 | $this->_verbose($output);
198 | // Single or multiple
199 | if ($isSinglePath) {
200 | // Single path does not show the key
201 | $this->_error("Composer");
202 | } else {
203 | // Multiple paths shows current info
204 | $this->_error("Composer #{$key} with path: {$path}");
205 | }
206 | }
207 |
208 | }
209 |
210 | $this->_verbose("### /Composer Process End\n");
211 |
212 | $this->_done("Composer");
213 | }
214 |
215 | /**
216 | * Test Process
217 | */
218 | public function runTest($config=null)
219 | {
220 | if (!$config) {
221 | if (!isset($this->_config['test'])) {
222 | return;
223 | }
224 |
225 | // Test Config
226 | $config = &$this->_config['test'];
227 | }
228 |
229 | // Check enabled
230 | if (!$config || empty($config['enabled']) ) {
231 | return;
232 | }
233 |
234 | // Commend required
235 | if (!isset($config['command'])) {
236 | $this->_error("Test (Config `command` not found)");
237 | }
238 |
239 | $name = (isset($config['name'])) ? $config['name'] : $config['command'];
240 |
241 | // Start process
242 | $this->_verbose("");
243 | $this->_verbose("### Test `{$name}` Process Start");
244 |
245 | // command
246 | $cmd = $this->_getAbsolutePath($config['command']);
247 |
248 | $configuration = (isset($config['configuration'])) ? $this->_getAbsolutePath($config['configuration']) : null;
249 |
250 | switch ($type = isset($config['type']) ? $config['type'] : null) {
251 |
252 | case 'phpunit':
253 | default:
254 |
255 | $cmd = ($configuration) ? "{$cmd} -c {$configuration}" : $cmd;
256 | break;
257 | }
258 |
259 | // Shell execution
260 | $result = $this->_cmd($cmd, $output);
261 |
262 | $this->_verbose("### Test `{$name}` Process Result");
263 | $this->_verbose($output);
264 |
265 | // Failures check
266 | $this->checkError($result, $output);
267 |
268 | $this->_verbose("### /Test Process End\n");
269 |
270 | $this->_done("Test `{$name}`");
271 | }
272 |
273 | /**
274 | * Test Process
275 | */
276 | public function runTests()
277 | {
278 | if (!isset($this->_config['tests'])) {
279 | return;
280 | }
281 |
282 | // Tests Config
283 | $configs = &$this->_config['tests'];
284 |
285 | if (!is_array($configs)) {
286 | $this->_error("Tests (Config must be array)");
287 | }
288 |
289 | foreach ($configs as $key => $config) {
290 | $this->runTest($config);
291 | }
292 | }
293 |
294 | /**
295 | * Customized Commands Process
296 | *
297 | * @param string Trigger point
298 | */
299 | public function runCommands($trigger)
300 | {
301 | if (!isset($this->_config['commands'])) {
302 | return;
303 | }
304 |
305 | // Commands Config
306 | $config = &$this->_config['commands'];
307 |
308 | // Check enabled
309 | if (!isset($config[$trigger]) || !is_array($config[$trigger])) {
310 | return;
311 | }
312 |
313 | // process
314 | foreach ($config[$trigger] as $key => $cmd) {
315 |
316 | if (!$cmd) {
317 | continue;
318 | }
319 |
320 | // Format compatibility
321 | $cmd = is_array($cmd) ? $cmd : ['command' => $cmd];
322 |
323 | $this->_verbose("");
324 | $this->_verbose("### Command:{$key} Process Start");
325 |
326 | // Format command
327 | $command = "{$cmd['command']};";
328 | $result = $this->_cmd($command, $output, true);
329 |
330 | // Check
331 | if (!$result) {
332 | $this->_verbose($output);
333 | $this->_error("Command:{$key}");
334 | }
335 |
336 | $this->_verbose("### Command:{$key} Process Result");
337 | $this->_verbose($output);
338 | $this->_verbose("### Command:{$key} Process Start");
339 |
340 | $this->_done("Commands {$trigger}: {$key}");
341 | }
342 | }
343 |
344 | /**
345 | * Deploy Process
346 | */
347 | public function runDeploy()
348 | {
349 | // Config
350 | $config = isset( $this->_config['rsync']) ? $this->_config['rsync'] : [];
351 |
352 | // Default config
353 | $defaultConfig = [
354 | 'enabled' => true,
355 | 'params' => '-av --delete',
356 | 'timeout' => 15,
357 | ];
358 |
359 | // Config init
360 | $config = array_merge($defaultConfig, $this->_config['rsync']);
361 |
362 | // Check enabled
363 | if (!$config['enabled']) {
364 | return;
365 | }
366 |
367 | /**
368 | * Command builder
369 | */
370 | $rsyncCmd = 'rsync ' . $config['params'];
371 |
372 | // Add exclude
373 | $excludeFiles = $this->_config['exclude'];
374 | foreach ((array)$excludeFiles as $key => $file) {
375 | $rsyncCmd .= " --exclude \"{$file}\"";
376 | }
377 |
378 | // IdentityFile
379 | $identityFile = isset($config['identityFile'])
380 | ? $config['identityFile']
381 | : null;
382 | if ($identityFile && file_exists($identityFile)) {
383 | $rsyncCmd .= " -e \"ssh -i {$identityFile}\"";
384 | }
385 | elseif ($identityFile) {
386 | $this->_error("Deploy (IdentityFile not found: {$identityFile})");
387 | }
388 |
389 | // Common parameters
390 | $rsyncCmd = sprintf("%s --timeout=%d %s",
391 | $rsyncCmd,
392 | $config['timeout'],
393 | $this->_config['source']
394 | );
395 |
396 | /**
397 | * Process
398 | */
399 | foreach ($this->_config['servers'] as $key => $server) {
400 |
401 | // Info display
402 | $this->_verbose("");
403 | $this->_verbose("### Rsync Process Info");
404 | $this->_verbose('[Process]: '.($key+1));
405 | $this->_verbose('[Server ]: '.$server);
406 | $this->_verbose('[User ]: '.$this->_config['user']['remote']);
407 | $this->_verbose('[Source ]: '.$this->_config['source']);
408 | $this->_verbose('[Remote ]: '.$this->_config['destination']);
409 |
410 | // Rsync destination building for each server
411 | $cmd = sprintf("%s --no-owner --no-group %s@%s:%s",
412 | $rsyncCmd,
413 | $this->_config['user']['remote'],
414 | $server,
415 | $this->_config['destination']
416 | );
417 |
418 | $this->_verbose('[Command]: '.$cmd);
419 |
420 | // Shell execution
421 | $result = $this->_cmd($cmd, $output);
422 |
423 | $this->_verbose("### Rsync Process Result");
424 | $this->_verbose("--------------------------");
425 | $this->_verbose($output);
426 | $this->_verbose("----------------------------");
427 | $this->_verbose("");
428 |
429 | /**
430 | * Check error
431 | */
432 | // Success only: sending incremental file list
433 | if (!$result) {
434 | // Error
435 | $this->_error("Deploy to {$server}");
436 |
437 | } else {
438 |
439 | // Sleep option per each deployed server
440 | if (isset($config['sleepSeconds'])) {
441 |
442 | sleep((int)$config['sleepSeconds']);
443 | }
444 |
445 | $this->_done("Deploy to {$server}");
446 | }
447 | }
448 |
449 | $this->_done("Deploy");
450 | }
451 |
452 | /**
453 | * Get project config
454 | *
455 | * @return array Config
456 | */
457 | public function getConfig()
458 | {
459 | return $this->_config;
460 | }
461 |
462 | /**
463 | * Config setting
464 | *
465 | * @param array $config
466 | */
467 | private function _setConfig($config)
468 | {
469 | if (!isset($config['servers']) || !$config['servers'] || !is_array($config['servers'])) {
470 | throw new Exception('Config not set: servers', 400);
471 | }
472 |
473 | if (!isset($config['source']) || !$config['source']) {
474 | throw new Exception('Config not set: source', 400);
475 | }
476 |
477 | $config['user'] = (isset($config['user']))
478 | ? $config['user']
479 | : [];
480 |
481 | $config['user']['local'] = is_string($config['user']) ? $config['user'] : $config['user']['local'];
482 | $config['user']['local'] = (isset($config['user']['local']) && $config['user']['local'])
483 | ? $config['user']['local']
484 | : $this->_getUser();
485 |
486 | $config['user']['remote'] = (isset($config['user']['remote']) && $config['user']['remote'])
487 | ? $config['user']['remote']
488 | : $config['user']['local'];
489 |
490 | $config['destination'] = (isset($config['destination']))
491 | ? $config['destination']
492 | : $config['source'];
493 |
494 | return $this->_config = $config;
495 | }
496 |
497 | private function _checkConfig()
498 | {
499 | $config = &$this->_config;
500 |
501 | // Check for type of file / directory
502 | if (!is_dir($config['source']) ) {
503 |
504 | throw new Exception('Source file is not a directory (project)');
505 | }
506 |
507 | // Check for type of link
508 | if (is_link($config['source'])) {
509 |
510 | throw new Exception('File input is symblic link');
511 | }
512 | }
513 |
514 | /**
515 | * Response
516 | *
517 | * @param string $string
518 | */
519 | private function _done($string)
520 | {
521 | $this->_result("Successful Excuted Task: {$string}");
522 | }
523 |
524 | /**
525 | * Response for error
526 | *
527 | * @param string $string
528 | */
529 | private function _error($string)
530 | {
531 | $this->_result("Failing Excuted Task: {$string}");
532 | if (!isset($this->_config['verbose']) || !$this->_config['verbose']) {
533 | $this->_result("(Use -v --verbose parameter to display error message)");
534 | }
535 | exit;
536 | }
537 |
538 | /**
539 | * Combined path with config source path if is relatived path
540 | *
541 | * @param $path
542 | * @return string Path
543 | */
544 | private function _getAbsolutePath($path=null)
545 | {
546 | // Is absolute path
547 | if (strpos($path, '/')===0 && file_exists($path)) {
548 |
549 | return $path;
550 | }
551 |
552 | return ($path) ? $this->_config['source'] ."/{$path}" : $this->_config['source'];
553 | }
554 |
555 | /**
556 | * Command (Shell as default)
557 | *
558 | * @param string $cmd
559 | * @param string $resultText
560 | * @param bool|string cd into source directory first (CentOS issue), string for customization
561 | * @return mixed Response
562 | */
563 | private function _cmd($cmd, &$resultText='', $cdSource=false)
564 | {
565 | // Clear rtrim
566 | $cmd = rtrim($cmd, ';');
567 |
568 | if ($cdSource) {
569 | // Get path with the determination
570 | $path = ($cdSource===true) ? $this->_config['source'] : $cdSource;
571 | $cmd = "cd {$path};{$cmd}";
572 | }
573 |
574 | return $this->_exec($cmd, $resultText);
575 | }
576 |
577 | /**
578 | * Result response
579 | *
580 | * @param string $string
581 | */
582 | private function _result($string='')
583 | {
584 | $this->_response .= $string . "\n";
585 | $this->_print($string);
586 | }
587 |
588 | /**
589 | * Verbose response
590 | *
591 | * @param string $string
592 | */
593 | private function _verbose($string='')
594 | {
595 | if (isset($this->_config['verbose']) && $this->_config['verbose']) {
596 | $this->_result($string);
597 | }
598 | }
599 |
600 | /**
601 | * check error for Git
602 | *
603 | * @param boolean $result Command result
604 | * @param string $output Result text
605 | * @return void
606 | */
607 | private function checkError($result, $output)
608 | {
609 | if (!$result) {
610 |
611 | $this->_verbose($output);
612 | $this->_error("Git");
613 | }
614 | }
615 | }
616 |
--------------------------------------------------------------------------------
/src/GetOpt.php:
--------------------------------------------------------------------------------
1 |
9 | * @version 1.0.0
10 | * @see http://php.net/manual/en/function.getopt.php#refsect1-function.getopt-parameters
11 | * @param string options
12 | * @param array longopts
13 | * @param int optind
14 | * @example
15 | * $getOpt = new GetOpt('h:v', ['host:', 'verbose']);
16 | * $hostname = $getOpt->get(['project', 'p']); // String or null
17 | * $debugOn = $getOpt->has(['verbose', 'v']); // Bool
18 | */
19 | class GetOpt
20 | {
21 | /**
22 | * @var array Cached options
23 | */
24 | private $_options;
25 |
26 | function __construct($options, array $longopts=[], $optind=null)
27 | {
28 | // $optind for PHP 7.1.0
29 | $this->_options = ($optind)
30 | ? getopt($options, $longopts, $optind)
31 | : getopt($options, $longopts);
32 |
33 | return $this;
34 | }
35 |
36 | /**
37 | * Get Option Value
38 | *
39 | * @param string|array Option priority key(s) for same purpose
40 | * @return mixed Result of purpose option value by getopt(), return null while not set
41 | * @example
42 | * $verbose = $this->get(['verbose', 'v']);
43 | */
44 | public function get($options)
45 | {
46 | // String Key
47 | if (is_string($options)) {
48 |
49 | return (isset($this->_options[$options])) ? $this->_options[$options] : null;
50 | }
51 | // Array Keys
52 | if (is_array($options)) {
53 | // Maping loop
54 | foreach ($options as $key => $option) {
55 | // First match
56 | if (isset($this->_options[$option])) {
57 |
58 | return $this->_options[$option];
59 | }
60 | }
61 | }
62 |
63 | return null;
64 | }
65 |
66 | /**
67 | * Get Option Value
68 | *
69 | * @param string|array Option priority key(s) for same purpose
70 | * @return mixed Result of purpose option value by getopt(), return null while not set
71 | * @example
72 | * $verbose = $this->get(['verbose', 'v']);
73 | */
74 | public function has($options)
75 | {
76 | // String Key
77 | if (is_string($options)) {
78 |
79 | return (isset($this->_options[$options])) ? true : false;
80 | }
81 | // Array Keys
82 | if (is_array($options)) {
83 | // Maping loop
84 | foreach ($options as $key => $option) {
85 | // First match
86 | if (isset($this->_options[$option])) {
87 |
88 | return true;
89 | }
90 | }
91 | }
92 |
93 | return false;
94 | }
95 |
96 | /**
97 | * Get Options
98 | *
99 | * @return array $this->$_options
100 | */
101 | public function getOptions()
102 | {
103 | return $this->_options;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/ShellConsole.php:
--------------------------------------------------------------------------------
1 |
7 | */
8 | trait ShellConsole
9 | {
10 | /**
11 | * Command
12 | *
13 | * @param string $cmd
14 | * @return mixed Response
15 | */
16 | // private function _exec($cmd)
17 | // {
18 | // return shell_exec($cmd);
19 | // }
20 |
21 | /**
22 | * Execute command line with status returning
23 | *
24 | * @param string $cmd
25 | * @param string $resultText
26 | * @param array $output
27 | * @param integer $errorCode
28 | * @return boolean Last command success or not
29 | */
30 | private function _exec($cmd, &$resultText='', &$output='', &$errorCode='')
31 | {
32 | $cmd = trim($cmd);
33 | $cmd = rtrim($cmd, ';');
34 |
35 | // stdout
36 | $cmd = "{$cmd} 2>&1;";
37 | exec($cmd, $output, $errorCode);
38 |
39 | // Build result text
40 | foreach ($output as $key => $string) {
41 | $resultText .= "{$string}\r\n";
42 | }
43 |
44 | return (!$errorCode) ? true : false;
45 | }
46 |
47 | /**
48 | * Get username
49 | *
50 | * @return string User
51 | */
52 | private function _getUser()
53 | {
54 | $this->_exec('echo $USER;', $user);
55 |
56 | return trim($user);
57 | }
58 |
59 | /**
60 | * Response
61 | *
62 | * @param string $string
63 | */
64 | private function _print($string)
65 | {
66 | echo "{$string}\n";
67 | }
68 | }
--------------------------------------------------------------------------------
/src/default-config.inc.php:
--------------------------------------------------------------------------------
1 | [
7 | '127.0.0.1',
8 | ],
9 | 'user' => [
10 | 'local' => '',
11 | 'remote' => '',
12 | ],
13 | 'source' => '',
14 | 'destination' => '',
15 | 'exclude' => [
16 | '.git',
17 | ],
18 | 'git' => [
19 | 'enabled' => false,
20 | 'path' => './',
21 | 'checkout' => true,
22 | 'branch' => 'master',
23 | 'submodule' => false,
24 | ],
25 | 'composer' => [
26 | 'enabled' => false,
27 | 'path' => './',
28 | 'command' => 'composer -n install',
29 | ],
30 | 'rsync' => [
31 | 'enabled' => true,
32 | 'params' => '-av --delete',
33 | 'sleepSeconds' => 0,
34 | 'timeout' => 60,
35 | 'identityFile' => null,
36 | ],
37 | 'commands' => [
38 | 'before' => [
39 | '',
40 | ],
41 | ],
42 | 'webhook' => [
43 | 'enabled' => false,
44 | 'provider' => 'gitlab',
45 | 'project' => '',
46 | 'token' => '',
47 | ],
48 | 'verbose' => false,
49 | ];
--------------------------------------------------------------------------------
/src/views/help.php:
--------------------------------------------------------------------------------
1 | Usage:
2 | deployer [options] [arguments]
3 | ./deployer [options] [arguments]
4 |
5 | Options:
6 | -h --help Display this help message
7 | --version Show the current version of the application
8 | -p, --project Project key by configuration for deployment
9 | --config Show the seleted project configuration
10 | --configuration
11 | --skip-git Force to skip Git process
12 | --skip-composer Force to skip Composer process
13 | --git-reset Git reset to given commit with --hard option
14 | -v, --verbose Increase the verbosity of messages
--------------------------------------------------------------------------------
/test/config.inc.php:
--------------------------------------------------------------------------------
1 | [
5 | 'servers' => [
6 | '127.0.0.1',
7 | ],
8 | 'user' => [
9 | 'local' => '',
10 | 'remote' => '',
11 | ],
12 | 'source' => __DIR__,
13 | 'destination' => __DIR__,
14 | 'exclude' => [
15 | '.git',
16 | ],
17 | 'git' => [
18 | 'enabled' => true,
19 | 'path' => './',
20 | 'checkout' => true,
21 | 'branch' => 'master',
22 | ],
23 | 'composer' => [
24 | 'enabled' => false,
25 | 'path' => './',
26 | 'command' => 'composer install',
27 | ],
28 | 'rsync' => [
29 | 'params' => '-av --delete',
30 | 'sleepSeconds' => 0,
31 | ],
32 | 'commands' => [
33 | 'before' => [
34 | '',
35 | ],
36 | ],
37 | 'verbose' => false,
38 | ],
39 | ];
40 |
--------------------------------------------------------------------------------
/test/deployer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 |
3 | run($configList, $argv);
27 |
28 |
--------------------------------------------------------------------------------
/tools/README.md:
--------------------------------------------------------------------------------
1 | Deployer *by PHP-CLI*
2 | =====================
3 |
4 | Code deployment tool based on RSYNC running by PHP-CLI script
5 |
6 | FEATURES
7 | --------
8 |
9 | ***1. Deploy to multiple servers by groups***
10 |
11 | ***2. Git supported for source project***
12 |
13 | ***3. Composer supported for source project***
14 |
15 | ***4. Filter for excluding specified files supported***
16 |
17 | These rsync php scripts are helping developers to deploy codes from local instance to remote instances.
18 |
19 | ---
20 |
21 | DEMONSTRATION
22 | -------------
23 |
24 | Deploy local project to remote servers by just executing the deployer in command:
25 |
26 | ```
27 | $ ./deployer
28 | ```
29 | Or you can call it by PHP-CLI:
30 | ```
31 | $ php ./deployer
32 | ```
33 |
34 | The result could like be:
35 | ```
36 | /* --- Git Process Start --- */
37 | Already up-to-date.
38 | /* --- Git Process End --- */
39 |
40 | /* --- Rsync Process Start --- */
41 | [Process]: 1
42 | [Group ]: default
43 | [Server ]: 127.0.0.1
44 | [User ]: nick_tsai
45 | [Source ]: /home/www/projects/deployer-php-cli
46 | [Remote ]: /var/www/html/projects/
47 | [Command]: rsync -av --delete --exclude "web/upload" --exclude "runtime/log" /home/www/projects/deployer-php-cli nick_tsai@127.0.0.1:/var/www/html/projects/
48 | [Message]:
49 | sending incremental file list
50 | deployer-php-cli/index.php
51 |
52 | sent 149,506 bytes received 814 bytes 60,128.00 bytes/sec
53 | total size is 45,912,740 speedup is 305.43
54 | /* --- Rsync Process End --- */
55 | ```
56 |
57 | ---
58 |
59 | INSTALLATION
60 | ------------
61 |
62 | - **[deployer](#deployer)**
63 |
64 | ```
65 | wget https://raw.githubusercontent.com/yidas/deployer-php-cli/master/src/deployer
66 | ```
67 |
68 | - **[mirror](#mirror)**
69 |
70 | ```
71 | wget https://raw.githubusercontent.com/yidas/deployer-php-cli/master/src/mirror
72 | ```
73 |
74 | After download, you could add excute property to that file by `chmod +x`.
75 |
76 | The scripts including shell script for running php at the first line:
77 | ```
78 | #!/usr/bin/php -q
79 | ```
80 | You can customize it for correct php bin path in your environment, saving the file with [binary encode](#save-bin-file).
81 |
82 | ---
83 |
84 | CONFIGURATION
85 | -------------
86 |
87 | ### Servers Setting:
88 |
89 | You need to set up the target servers' hostname or IP into the script file:
90 |
91 | ```
92 | $config['remoteServers'] = [
93 | 'default' => [
94 | '110.1.1.1',
95 | '110.1.2.1',
96 | ],
97 | 'stage' => [
98 | '110.1.1.1',
99 | ],
100 | 'prod' => [
101 | '110.1.2.1',
102 | ],
103 | ];
104 | ```
105 |
106 | Also, the remote server user need to be assigned:
107 |
108 | ```
109 | $config['remoteUser'] = 'www-data';
110 | ```
111 |
112 | ### Config Options
113 |
114 | |Key|Description|
115 | |:-|:-|
116 | |**remoteServers**|Distant server host list|
117 | |**remoteUser**|Remote server user|
118 | |**sourceFile**|Local directory for deploy |
119 | |**remotePath**|Remote path for synchronism|
120 | |rsyncParams|Addition params of rsync command|
121 | |**excludeFiles**|Excluded files based on sourceFile path|
122 | |sleepSeconds|Seconds waiting of each rsync connections|
123 | |gitEnabled|Enabled git or not|
124 | |gitCheckoutEnabled|Execute git checkout -- . before git pull |
125 | |gitBranch|Branch name for git pull, pull default branch if empty |
126 | |composerEnabled|Enabled Composer or not|
127 | |composerCommand|Composer command line for update or install|
128 | |commandsBeforeDeploy|Array of commands executing before deployment|
129 |
130 | ---
131 |
132 | SCRIPT FILES
133 | ------------
134 |
135 | - **[deployer](#deployer)**
136 | Rsync a specified source folder to remote servers under the folder by setting path, supporting filtering files from excludeFiles.
137 |
138 | You need to do more setting for p2p directories in `rsyncStatic.php`:
139 | ```
140 | $config['sourceFile'] = '/home/www/www.project.com/webroot';
141 | $config['remotePath'] = '/home/www/www.project.com/';
142 | ```
143 |
144 | - **[mirror](#mirror)**
145 | Rsync a file or a folder from current local path to destination servers with the same path automatically, the current path is base on Linux's "pwd -P" command.
146 |
147 | ---
148 |
149 | USAGE
150 | -----
151 |
152 | ### deployer
153 |
154 | For `deployer`, you need to set project folder path into the file with source & destination directory, then you can run it:
155 | ```
156 | $ ./deployer // Rsync to servers in default group
157 | $ ./deployer stage // Rsync to servers in stage group
158 | $ ./deployer prod // Rsync to servers in prod group
159 | ```
160 |
161 |
162 | ### mirror
163 |
164 | For `mirror`, you can put scripts in your home directory, and cd into the pre-sync file directory:
165 |
166 | ```
167 | $ ~/mirror file.php // Rsync file.php to servers with same path
168 | $ ~/mirror folderA // Rsync whole folderA to servers
169 | $ ~/mirror ./ // Rsync current whole folder
170 | $ ~/mirror ./ stage // Rsync to servers in stage group
171 | $ ~/mirror ./ prod // Rsync to servers in prod group
172 | ```
173 |
174 | ---
175 |
176 | ADDITION
177 | --------
178 |
179 | ### Rsync without Password:
180 |
181 | You can put your local user's SSH public key to destination server user for authorization.
182 | ```
183 | .ssh/id_rsa.pub >> .ssh/authorized_keys
184 | ```
185 |
186 | ### Save Binary Encode File:
187 |
188 | While excuting script, if you get the error like `Exception: Zend Extension ./deployer does not exist`, you may save the script file with binary encode, which could done by using `vim`:
189 |
190 | ```
191 | :set ff=unix
192 | ```
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/tools/deployer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 |
3 |
11 | * @filesource PHP >= 5.4 (Support 5.0 if removing Short-Array-Syntax)
12 | *
13 | * @param string $argv[1] Target servers group key of remoteServers
14 | * @example
15 | * $ ./deployer // Rsync to servers in default group
16 | * $ ./deployer stage // Rsync to servers in stage group
17 | * $ ./deployer prod // Rsync to servers in prod group
18 | */
19 |
20 |
21 | /* Configuration */
22 |
23 | /**
24 | * @var array Distant server host list
25 | */
26 | $config['remoteServers'] = [
27 | 'default' => [
28 | '110.1.1.1',
29 | '110.1.2.1',
30 | ],
31 | 'stage' => [
32 | '110.1.1.1',
33 | ],
34 | 'prod' => [
35 | '110.1.2.1',
36 | ],
37 | ];
38 |
39 | /**
40 | * @var string Remote server user
41 | */
42 | $config['remoteUser'] = 'www-data';
43 |
44 | /**
45 | * @var string Local directory for deploy
46 | */
47 | $config['sourceFile'] = '/home/www/www.project.com/webroot';
48 |
49 | /**
50 | * @var string Remote path for synchronism
51 | */
52 | $config['remotePath'] = '/home/www/www.project.com/';
53 |
54 | /**
55 | * @var string Addition params of rsync command
56 | */
57 | $config['rsyncParams'] = '-av --delete';
58 |
59 | /**
60 | * @var array Excluded files based on sourceFile path
61 | */
62 | $config['excludeFiles'] = [
63 | 'web/upload',
64 | 'runtime/log',
65 | ];
66 |
67 | /**
68 | * @var int Seconds waiting of each rsync connections
69 | */
70 | $config['sleepSeconds'] = 0;
71 |
72 |
73 | /**
74 | * @var bool Enabled git or not
75 | */
76 | $config['gitEnabled'] = false;
77 |
78 | /**
79 | * @var string Execute git checkout -- . before git pull
80 | */
81 | $config['gitCheckoutEnabled'] = false;
82 |
83 | /**
84 | * @var string Branch name for git pull, pull default branch if empty
85 | */
86 | $config['gitBranch'] = '';
87 |
88 | /**
89 | * @var bool Enabled Composer or not
90 | */
91 | $config['composerEnabled'] = false;
92 |
93 | /**
94 | * @var string Composer command line for update or install
95 | */
96 | $config['composerCommand'] = 'composer update';
97 |
98 | /**
99 | * @var string Array of commands executing before deployment
100 | */
101 | $config['commandsBeforeDeploy'] = [
102 | // 'cd /var/www/html/your-project',
103 | // 'gulp minify-all',
104 | // 'Minify' => 'cd /var/www/html/your-project; gulp minify-all',
105 | ];
106 |
107 | /* /Configuration */
108 |
109 |
110 | ob_implicit_flush();
111 |
112 | // Target server group list for rsync
113 | $serverEnv = (isset($argv[1])) ? $argv[1] : 'default';
114 |
115 | try {
116 |
117 | // Check for server list
118 | if (!isset($config['remoteServers'][$serverEnv])
119 | || !$config['remoteServers'][$serverEnv]) {
120 |
121 | throw new Exception("No server host in group: {$serverEnv}");
122 | }
123 |
124 | $sourceFile = $config['sourceFile'];
125 | $remotePath = $config['remotePath'];
126 |
127 | // File existence check
128 | if (strlen(trim($sourceFile))==0) {
129 |
130 | throw new Exception('None of file input');
131 | }
132 |
133 | // Check for type of file / directory
134 | if (!is_file($sourceFile) && !is_dir($sourceFile) ) {
135 |
136 | throw new Exception('Source file is not a file or directory');
137 | }
138 |
139 | // Check for type of link
140 | if (is_link($sourceFile)) {
141 |
142 | throw new Exception('File input is symblic link');
143 | }
144 |
145 | // Directory locate
146 | $result = shell_exec("cd {$config['sourceFile']};");
147 |
148 | // Git process
149 | if ($config['gitEnabled']) {
150 |
151 | echo "Processing Git...\n";
152 | $cmd = ($config['gitCheckoutEnabled'])
153 | ? "git checkout - .;"
154 | : "";
155 | $cmd .= ($config['gitBranch'])
156 | ? "git pull origin {$config['gitBranch']}"
157 | : "git pull";
158 |
159 | // Shell execution
160 | $result = shell_exec($cmd);
161 |
162 | echo "/* --- Git Process Result --- */\n";
163 | echo $result;
164 | echo "/* -------------------------- */\n";
165 | echo "\r\n";
166 | }
167 |
168 | // Composer process
169 | if ($config['composerEnabled']) {
170 |
171 | echo "/* --- Composer Process Start --- */\n";
172 | $cmd = $config['composerCommand'];
173 |
174 | // Shell execution
175 | $result = shell_exec($cmd);
176 | echo $result;
177 |
178 | echo "/* --- Composer Process End --- */\n";
179 | echo "\r\n";
180 | }
181 |
182 | // Commands process
183 | if ($config['commandsBeforeDeploy']) {
184 |
185 | foreach ((array)$config['commandsBeforeDeploy'] as $key => $cmd) {
186 |
187 | echo "/* --- Command:{$key} Process Start --- */\n";
188 |
189 | // Format command
190 | $cmd = "{$cmd};";
191 | // Shell execution
192 | $result = shell_exec($cmd);
193 | echo $result;
194 |
195 | echo "/* --- Command:{$key} Process End --- */\n";
196 | echo "\r\n";
197 | }
198 | }
199 |
200 | // Rsync each servers
201 | foreach ($config['remoteServers'][$serverEnv] as $key => $server) {
202 |
203 | // Info display
204 | echo "/* --- Rsync Process Info --- */\n";
205 | echo '[Process]: '.($key+1)."\n";
206 | echo '[Group ]: '.$serverEnv."\n";
207 | echo '[Server ]: '.$server."\n";
208 | echo '[User ]: '.$config['remoteUser']."\n";
209 | echo '[Source ]: '.$sourceFile."\n";
210 | echo '[Remote ]: '.$remotePath."\n";
211 | echo "/* -------------------------- */\n";
212 | echo "Processing Rsync...\n";
213 |
214 |
215 | /* Command builder */
216 |
217 | $cmd = 'rsync ' . $config['rsyncParams'];
218 |
219 | // Add exclude
220 | $excludeFiles = $config['excludeFiles'];
221 | foreach ((array)$excludeFiles as $key => $file) {
222 | $cmd .= " --exclude \"{$file}\"";
223 | }
224 |
225 | // Rsync shell command
226 | $cmd = sprintf("%s %s %s@%s:%s",
227 | $cmd,
228 | $sourceFile,
229 | $config['remoteUser'],
230 | $server,
231 | $remotePath
232 | );
233 |
234 | echo '[Command]: '.$cmd."\n";
235 |
236 | // Shell execution
237 | $result = shell_exec($cmd);
238 |
239 | echo "/* --- Rsync Process Result --- */\n";
240 | echo $result;
241 | echo "/* ---------------------------- */\n";
242 | echo "\r\n";
243 |
244 | sleep($config['sleepSeconds']);
245 | }
246 |
247 | echo "\r\n";
248 |
249 | } catch (Exception $e) {
250 |
251 | die('ERROR:'.$e->getMessage()."\n");
252 | }
253 |
254 |
255 |
--------------------------------------------------------------------------------
/tools/mirror:
--------------------------------------------------------------------------------
1 | #!/usr/bin/php -q
2 |
3 |
13 | * @filesource PHP >= 5.4 (Support >= 5.0 if removing Short-Array-Syntax)
14 | * @param string $argv[1] File/directory in current path for rsync
15 | * @param string $argv[2] (Optional) Target servers group key of remoteServers
16 | * @example
17 | * $ ~/mirror file.php // Rsync file.php to servers with same path
18 | * $ ~/mirror folderA // Rsync whole folderA to servers
19 | * $ ~/mirror ./ // Rsync current whole folder
20 | * $ ~/mirror ./ stage // Rsync to servers in stage group
21 | * $ ~/mirror ./ prod // Rsync to servers in prod group
22 | */
23 |
24 |
25 | /* Configuration */
26 |
27 | /**
28 | * @var array Distant server host list
29 | */
30 | $config['remoteServers'] = [
31 | 'default' => [
32 | '110.1.1.1',
33 | '110.1.2.1',
34 | ],
35 | 'stage' => [
36 | '110.1.1.1',
37 | ],
38 | 'prod' => [
39 | '110.1.2.1',
40 | ],
41 | ];
42 |
43 | /**
44 | * @var string Remote server user
45 | */
46 | $config['remoteUser'] = 'www-data';
47 |
48 | /**
49 | * @var string Addition params of rsync command
50 | */
51 | $config['rsyncParams'] = '-av --delete';
52 |
53 | /**
54 | * @var int Seconds waiting of each rsync connections
55 | */
56 | $config['sleepSeconds'] = 0;
57 |
58 | /* /Configuration */
59 |
60 |
61 | ob_implicit_flush();
62 |
63 | // File input
64 | $file = (isset($argv[1])) ? $argv[1] : NULL;
65 |
66 | // Target server group list for rsync
67 | $serverEnv = (isset($argv[2])) ? $argv[2] : 'default';
68 |
69 | // Directory of destination same as source
70 | $dir = trim(shell_exec("pwd -P"));
71 |
72 | try {
73 |
74 | // File existence check
75 | if (strlen(trim($file))==0)
76 | throw new Exception('None of file input');
77 |
78 | // Check $argv likes asterisk
79 | if (isset($argv[3]))
80 | throw new Exception('Invalid arguments input');
81 |
82 | /**
83 | * Validating file name input
84 | *
85 | * @var sstring $reg Regular patterns
86 | * @example
87 | * \w\/ // folderA/
88 | * \* // * or *.*
89 | * ^\/ // / or /etc
90 | *
91 | */
92 | $reg = '/(\w\/|\*|^\/)/';
93 |
94 | preg_match($reg,$file,$matches);
95 |
96 | if ($matches) {
97 |
98 | //print_r($matches);
99 |
100 | throw new Exception('Invalid file name input');
101 | }
102 |
103 | // Check for server list
104 | if (!isset($config['remoteServers'][$serverEnv])
105 | || !$config['remoteServers'][$serverEnv]) {
106 |
107 | throw new Exception("No server host in group: {$serverEnv}");
108 | }
109 |
110 | // File or directory of source definition
111 | $this_file = $dir.'/'.$file;
112 |
113 | // Check for type of link
114 | if (is_link($this_file))
115 | throw new Exception('File input is symblic link');
116 |
117 | // Check for type of file / directory
118 | if (!is_file($this_file) && !is_dir($this_file) )
119 | throw new Exception('File input is not a file or directory');
120 |
121 | // Check for syntax if is PHP
122 | if ( preg_match("/\.php$/i",$file)
123 | && !preg_match("/No syntax errors detected/i", shell_exec("php -l ".$this_file)) ) {
124 |
125 | throw new Exception('PHP syntax error!');
126 | }
127 |
128 | // Rsync each servers
129 | foreach ($config['remoteServers'][$serverEnv] as $key => $server) {
130 |
131 | // Info display
132 | echo '/* --- Process Start --- */'."\n";
133 | echo '[Process]: '.($key+1)."\n";
134 | echo '[Group ]: '.$serverEnv."\n";
135 | echo '[Server ]: '.$server."\n";
136 | echo '[User ]: '.$config['remoteUser']."\n";
137 |
138 |
139 | /* Command builder */
140 |
141 | $cmd = 'rsync ' . $config['rsyncParams'];
142 |
143 | // Rsync shell command
144 | $cmd = sprintf("%s %s %s@%s:%s",
145 | $cmd,
146 | $file,
147 | $config['remoteUser'],
148 | $server,
149 | $dir
150 | );
151 |
152 | echo '[Command]: '.$cmd."\n";
153 |
154 | // Shell execution
155 | $result = shell_exec($cmd);
156 |
157 | echo '[Message]: '."\n".$result;
158 |
159 | echo '/* --- /Process End --- */'."\n";
160 | echo "\r\n";
161 |
162 | sleep($config['sleepSeconds']);
163 | }
164 |
165 | echo "\r\n";
166 |
167 | } catch (Exception $e) {
168 |
169 | die('ERROR:'.$e->getMessage()."\n");
170 | }
171 |
--------------------------------------------------------------------------------
/webhook/bitbucket/index.php:
--------------------------------------------------------------------------------
1 | isset($_GET['token']) ? $_GET['token'] : null,
13 | 'project' => isset($_GET['log']) ? $_GET['log'] : null,
14 | 'branch' => isset($_GET['branch']) ? $_GET['branch'] : 'master',
15 | ];
16 |
17 | if (!$logMode) {
18 | $body = file_get_contents('php://input');
19 | $data = json_decode($body, true);
20 |
21 | // Push info
22 | $info = [
23 | 'token' => $token,
24 | 'project' => $data['repository']['full_name'],
25 | 'projectUrl' => $data['repository']['links']['html']['href'],
26 | 'branch' => $data['push']['changes'][0]['new']['name'],
27 | ];
28 | }
29 |
30 | try {
31 | // Check
32 | $configList = require __DIR__.'/../../config.inc.php';
33 |
34 | $matchedConfig = [];
35 | $errorInfo = null;
36 |
37 | foreach ($configList as $key => $config) {
38 | // Webhook setting check
39 | if (!isset($config['webhook']['enabled']) || !$config['webhook']['enabled']) {
40 | continue;
41 | }
42 | // provider check
43 | elseif (!isset($config['webhook']['provider']) || $config['webhook']['provider'] != 'bitbucket') {
44 | continue;
45 | }
46 | // Last mapping for project name
47 | elseif (!isset($config['webhook']['project']) || $config['webhook']['project'] != $info['project']) {
48 | continue;
49 | }
50 | // Webhook branch check
51 | elseif (isset($config['webhook']['branch']) && $config['webhook']['branch'] != $info['branch']) {
52 | $errorInfo[] = "Branch `{$info['branch']}` could not be matched from webhook config";
53 | continue;
54 | }
55 | // Use Git branch setting while no branch setting in Webhook
56 | elseif (!isset($config['webhook']['branch']) && isset($config['git']['branch']) && $config['git']['branch'] != $info['branch']) {
57 | $errorInfo[] = "Branch `{$info['branch']}` could not be matched from Git config";
58 | continue;
59 | }
60 |
61 | // match config
62 | $matchedConfig = $config;
63 | // For Deployer config
64 | $matchedConfig['projectKey'] = $key;
65 |
66 | break;
67 | }
68 | } catch (\Exception $e) {
69 | responseWithPack(null, $e->getCode(), $e->getMessage());
70 | exit;
71 | }
72 |
73 | // Matched config check
74 | if (empty($matchedConfig)) {
75 | responseWithPack($errorInfo, 404, 'No matched config found');
76 | exit;
77 | }
78 | // Authorization while setting token
79 | elseif (isset($matchedConfig['webhook']['token']) && $info['token'] != $matchedConfig['webhook']['token']) {
80 | responseWithPack(['inputToken' => $info['token']], 403, 'Token is invalid');
81 | exit;
82 | }
83 |
84 | // Log mode
85 | if ($logMode) {
86 | if (!isset($matchedConfig['webhook']['log'])) {
87 | die('Log setting is disabled');
88 | }
89 |
90 | $logFile = is_string($matchedConfig['webhook']['log'])
91 | ? $matchedConfig['webhook']['log']
92 | : $defaultLogFile;
93 |
94 | if (!file_exists($logFile)) {
95 | die('Log file not found');
96 | }
97 |
98 | // Read log
99 | $oldList = json_decode(file_get_contents($logFile), true);
100 | $logList = is_array($oldList) ? $oldList : [];
101 |
102 | // Output
103 | if ($logList) {
104 | foreach ($logList as $key => $row) {
105 | echo "{$row['datetime']}
".$row['response'].''; 106 | } 107 | } else { 108 | echo 'No record yet'; 109 | } 110 | 111 | exit; 112 | } 113 | 114 | /** 115 | * Fast response for webhook. 116 | */ 117 | $data = []; 118 | // Provide resultUrl when webhook log is enabled 119 | if (isset($matchedConfig['webhook']['log'])) { 120 | $data['resultUrl'] = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http') 121 | ."://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]" 122 | ."?log={$info['project']}&branch={$info['branch']}&token={$info['token']}"; 123 | } 124 | responseWithPack($data, 200, 'Deployer will start processing! Check log for result information.'); 125 | ignore_user_abort(true); 126 | header('Connection: close'); 127 | flush(); 128 | fastcgi_finish_request(); 129 | 130 | /** 131 | * Bootstrap. 132 | */ 133 | // Loader 134 | require __DIR__.'/../../src/ShellConsole.php'; 135 | require __DIR__.'/../../src/Deployer.php'; 136 | // Config initialized 137 | 138 | $defaultConfig = require __DIR__.'/../../src/default-config.inc.php'; 139 | $matchedConfig = array_replace_recursive($defaultConfig, $matchedConfig); 140 | // Initial Deployer 141 | $deployer = new Deployer($matchedConfig); 142 | // Run Deployer 143 | $res = $deployer->run(); 144 | file_put_contents('/tmp/debug2.log', json_encode($res)); 145 | 146 | if ($res && isset($matchedConfig['webhook']['log'])) { 147 | // Max rows per each log file 148 | $limit = 100; 149 | // Log file 150 | $logFile = is_string($matchedConfig['webhook']['log']) 151 | ? $matchedConfig['webhook']['log'] 152 | : $defaultLogFile; 153 | // Format 154 | $row = [ 155 | 'provider' => $config['webhook']['provider'], 156 | 'info' => $info, 157 | 'datetime' => date('Y-m-d H:i:s'), 158 | 'response' => $res, 159 | ]; 160 | // Log text 161 | $logList = []; 162 | 163 | if (file_exists($logFile)) { 164 | // Read log 165 | $oldList = json_decode(file_get_contents($logFile), true); 166 | $logList = is_array($oldList) ? $oldList : []; 167 | // Limit handling 168 | if (count($logList) >= $limit) { 169 | array_pop($logList); 170 | } 171 | } 172 | array_unshift($logList, $row); 173 | // Write back to log 174 | file_put_contents($logFile, json_encode($logList)); 175 | } 176 | 177 | /** 178 | * writeLog. 179 | * 180 | * @param string $text 181 | * 182 | * @return void 183 | */ 184 | function writeLog($text = 'no message', $writeLogFile = '/tmp/deployer-php-cli.log') 185 | { 186 | $text = is_array($text) ? print_r($text, true) : $text; 187 | 188 | file_put_contents($writeLogFile, $text); 189 | } 190 | 191 | /** 192 | * Response. 193 | * 194 | * @param int $status 195 | * @param array $body 196 | * 197 | * @return void 198 | */ 199 | function response($status = 200, $body = []) 200 | { 201 | http_response_code($status); 202 | header('Content-Type: application/json; charset=utf-8'); 203 | echo json_encode($body, JSON_UNESCAPED_SLASHES); 204 | } 205 | 206 | /** 207 | * Responese with pack. 208 | * 209 | * @param [type] $data 210 | * @param int $status 211 | * @param [type] $message 212 | * 213 | * @return void 214 | */ 215 | function responseWithPack($data = null, $status = 200, $message = null) 216 | { 217 | $body = [ 218 | 'code' => $status, 219 | ]; 220 | // Message field 221 | if ($message) { 222 | $body['message'] = $message; 223 | } 224 | // Data field 225 | if ($data) { 226 | $body['data'] = $data; 227 | } 228 | 229 | return response($status, $body); 230 | } 231 | -------------------------------------------------------------------------------- /webhook/gitlab/index.php: -------------------------------------------------------------------------------- 1 | $inputToken, 20 | 'project' => $data['project']['path_with_namespace'], 21 | 'projectUrl' => $data['project']['url'], 22 | 'branch' => str_replace('refs/heads/', '', $data['ref']), 23 | ]; 24 | // writeLog($info);exit; 25 | 26 | // Log mode info rewrite 27 | if ($logMode) { 28 | $info = [ 29 | 'token' => isset($_GET['token']) ? $_GET['token'] : null, 30 | 'project' => $_GET['log'], 31 | 'branch' => isset($_GET['branch']) ? $_GET['branch'] : 'master', 32 | ]; 33 | } 34 | 35 | try { 36 | 37 | // Check 38 | $configList = require __DIR__. '/../../config.inc.php'; 39 | 40 | $matchedConfig = []; 41 | $errorInfo = null; 42 | 43 | foreach ($configList as $key => $config) { 44 | 45 | // Webhook setting check 46 | if (!isset($config['webhook']['enabled']) || !$config['webhook']['enabled']) { 47 | continue; 48 | } 49 | // Gitlab provider check 50 | elseif (!isset($config['webhook']['provider']) || $config['webhook']['provider']!='gitlab') { 51 | continue; 52 | } 53 | // Last mapping for project name 54 | elseif (!isset($config['webhook']['project']) || $config['webhook']['project']!=$info['project']) { 55 | continue; 56 | } 57 | // Webhook branch check 58 | elseif (isset($config['webhook']['branch']) && $config['webhook']['branch']!=$info['branch']) { 59 | $errorInfo[] = "Branch `{$info['branch']}` could not be matched from webhook config"; 60 | continue; 61 | } 62 | // Use Git branch setting while no branch setting in Webhook 63 | elseif (!isset($config['webhook']['branch']) && isset($config['git']['branch']) && $config['git']['branch']!=$info['branch']) { 64 | $errorInfo[] = "Branch `{$info['branch']}` could not be matched from Git config"; 65 | continue; 66 | } 67 | 68 | // match config 69 | $matchedConfig = $config; 70 | // For Deployer config 71 | $matchedConfig['projectKey'] = $key; 72 | 73 | break; 74 | } 75 | } catch (\Exception $e) { 76 | responeseWithPack(null, $e->getCode(), $e->getMessage()); 77 | exit; 78 | } 79 | 80 | // Matched config check 81 | if (empty($matchedConfig)) { 82 | responeseWithPack($errorInfo, 404, 'No matched config found'); 83 | exit; 84 | } 85 | // Authorization while setting token 86 | elseif (isset($matchedConfig['webhook']['token']) && $info['token'] != $matchedConfig['webhook']['token']) { 87 | responeseWithPack(['inputToken' => $info['token']], 403, 'Token is invalid'); 88 | exit; 89 | } 90 | 91 | // Log mode 92 | if ($logMode) { 93 | 94 | if (!isset($matchedConfig['webhook']['log'])) { 95 | die('Log setting is disabled'); 96 | } 97 | 98 | $logFile = is_string($matchedConfig['webhook']['log']) 99 | ? $matchedConfig['webhook']['log'] 100 | : $defaultLogFile; 101 | 102 | if (!file_exists($logFile)) { 103 | die('Log file not found'); 104 | } 105 | 106 | // Read log 107 | $oldList = json_decode(file_get_contents($logFile), true); 108 | $logList = is_array($oldList) ? $oldList : []; 109 | 110 | // Output 111 | if ($logList) { 112 | foreach ($logList as $key => $row) { 113 | echo "{$row['datetime']}
". $row['response'] .""; 114 | } 115 | } else { 116 | echo 'No record yet'; 117 | } 118 | 119 | exit; 120 | } 121 | 122 | /** 123 | * Fast response for webhook 124 | */ 125 | $data = []; 126 | // Provide resultUrl when webhook log is enabled 127 | if (isset($matchedConfig['webhook']['log'])) { 128 | $data['resultUrl'] = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") 129 | . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]" 130 | . "?log={$info['project']}&branch={$info['branch']}&token={$info['token']}"; 131 | } 132 | responeseWithPack($data, 200, 'Deployer will start processing! Check log for result information.'); 133 | ignore_user_abort(true); 134 | header('Connection: close'); 135 | flush(); 136 | fastcgi_finish_request(); 137 | 138 | /** 139 | * Bootstrap 140 | */ 141 | // Loader 142 | require __DIR__. '/../../src/ShellConsole.php'; 143 | require __DIR__. '/../../src/Deployer.php'; 144 | // Config initialized 145 | $defaultConfig = require __DIR__. '/../../src/default-config.inc.php'; 146 | $matchedConfig = array_replace_recursive($defaultConfig, $matchedConfig); 147 | // Initial Deployer 148 | $deployer = new Deployer($matchedConfig); 149 | // Run Deployer 150 | $res = $deployer->run(); 151 | 152 | if ($res && isset($matchedConfig['webhook']['log'])) { 153 | 154 | // Max rows per each log file 155 | $limit = 100; 156 | // Log file 157 | $logFile = is_string($matchedConfig['webhook']['log']) 158 | ? $matchedConfig['webhook']['log'] 159 | : $defaultLogFile; 160 | // Format 161 | $row = [ 162 | 'provider' => $config['webhook']['provider'], 163 | 'info' => $info, 164 | 'datetime' => date("Y-m-d H:i:s"), 165 | 'response' => $res, 166 | ]; 167 | // Log text 168 | $logList = []; 169 | 170 | if (file_exists($logFile)) { 171 | 172 | // Read log 173 | $oldList = json_decode(file_get_contents($logFile), true); 174 | $logList = is_array($oldList) ? $oldList : []; 175 | // Limit handling 176 | if (count($logList) >= $limit) { 177 | array_pop($logList); 178 | } 179 | } 180 | array_unshift($logList, $row); 181 | // Write back to log 182 | file_put_contents($logFile, json_encode($logList)); 183 | } 184 | 185 | /** 186 | * writeLog 187 | * 188 | * @param string $text 189 | * @return void 190 | */ 191 | function writeLog($text='no message', $writeLogFile='/tmp/deployer-php-cli.log') 192 | { 193 | $text = is_array($text) ? print_r($text, true) : $text; 194 | 195 | file_put_contents($writeLogFile, $text); 196 | } 197 | 198 | /** 199 | * Response 200 | * 201 | * @param integer $status 202 | * @param array $body 203 | * @return void 204 | */ 205 | function response($status=200, $body=[]) 206 | { 207 | http_response_code($status); 208 | header('Content-Type: application/json; charset=utf-8'); 209 | echo json_encode($body, JSON_UNESCAPED_SLASHES); 210 | } 211 | 212 | /** 213 | * Responese with pack 214 | * 215 | * @param [type] $data 216 | * @param integer $status 217 | * @param [type] $message 218 | * @return void 219 | */ 220 | function responeseWithPack($data=null, $status=200, $message=null) 221 | { 222 | $body = [ 223 | 'code' => $status, 224 | ]; 225 | // Message field 226 | if ($message) { 227 | $body['message'] = $message; 228 | } 229 | // Data field 230 | if ($data) { 231 | $body['data'] = $data; 232 | } 233 | 234 | return response($status, $body); 235 | } -------------------------------------------------------------------------------- /webhook/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Webhook Interface Home Page
16 | 17 |Documentation:
18 | https://github.com/yidas/deployer-php-cli.
19 |
20 |
By YIDAS
21 | 22 | --------------------------------------------------------------------------------