loadDeployerCommon(); $this->setDefaultConfiguration(); $this->loadHostConfiguration(); $this->registerTasks(); } protected function loadDeployerCommon(): void { foreach (['/../../', '/../../../', '/../../../../'] as $path) { $file = realpath(__DIR__ . $path . 'vendor/deployer/deployer/recipe/common.php'); if ($file && file_exists($file)) { require $file; break; } } } protected function setDefaultConfiguration(): void { $this->set('CI_PROJECT_DIR', getEnv('CI_PROJECT_DIR')); $this->set('CI_HOST', getEnv('CI_HOST')); $this->set('ENVIRONMENT_NAME', getEnv('ENVIRONMENT_NAME')); $this->set('INSTANCE_ID', getEnv('INSTANCE_ID')); $this->set('app_container_path', ''); $this->set('prepare_dirs', [ '.dep', 'releases', 'shared', ]); $this->set('ignore_update_code_paths', [ 'var/cache', 'var/log/*', 'Build', 'resources', '.idea', '.editorconfig', '.git', '.gitignore', '.gitlab-ci.yml', ]); $this->set('writable_dirs', [ 'var', ]); $this->set('clear_paths', [ 'composer.json', 'composer.lock', ]); $this->set('bin/git', function () { return $this->which('git'); }); $this->set('bin/composer', function () { if (testLocally('[ -f {{deploy_path}}/.dep/composer.phar ]')) { return '{{bin/php}} {{deploy_path}}/.dep/composer.phar'; } if ($this->commandExist('composer')) { return $this->which('composer'); } warning("Composer binary wasn't found. Installing latest composer to \"{{deploy_path}}/.dep/composer.phar\"."); runLocally("cd {{deploy_path}} && curl -sS https://getcomposer.org/installer | {{bin/php}}"); runLocally('mv {{deploy_path}}/composer.phar {{deploy_path}}/.dep/composer.phar'); return '{{bin/php}} {{deploy_path}}/.dep/composer.phar'; }); $this->set('use_relative_symlink', function () { return $this->commandSupportsOption('ln', '--relative'); }); $this->set('use_atomic_symlink', function () { return $this->commandSupportsOption('mv', '--no-target-directory'); }); $this->set('release_name', function () { $latest = runLocally('cat {{deploy_path}}/.dep/latest_release || echo 0'); return strval(intval($latest) + 1); }); $this->set('releases_log', function () { if (!testLocally('[ -f {{deploy_path}}/.dep/releases_log ]')) { return []; } $releaseLogs = array_map(function ($line) { return json_decode($line, true); }, explode("\n", runLocally('tail -n 300 {{deploy_path}}/.dep/releases_log'))); return array_filter($releaseLogs); // Return all non-empty lines. }); $this->set('releases_list', function () { // If there is no releases return empty list. if (!testLocally('[ -d {{deploy_path}}/releases ] && [ "$(ls -A {{deploy_path}}/releases)" ]')) { return []; } // Will list only dirs in releases. $ll = explode("\n", runLocally('cd {{deploy_path}}/releases && ls -t -1 -d */')); $ll = array_map(function ($release) { return basename(rtrim(trim($release), '/')); }, $ll); // Return releases from newest to oldest. $releasesLog = array_reverse(get('releases_log')); $releases = []; foreach ($releasesLog as $release) { if (in_array($release['release_name'], $ll, true)) { $releases[] = $release['release_name']; } } return $releases; }); $this->set('release_path', function () { $releaseExists = testLocally('[ -h {{deploy_path}}/release ]'); if ($releaseExists) { $link = runLocally("readlink {{deploy_path}}/release"); return str_starts_with($link, '/') ? $link : get('deploy_path') . '/' . $link; } else { throw new Exception(parse('The "release_path" ({{deploy_path}}/release) does not exist.')); } }); $this->set('release_or_current_path', function () { $releaseExists = testLocally('[ -h {{deploy_path}}/release ]'); return $releaseExists ? get('release_path') : get('current_path'); }); $this->set('http_user', function () { $candidates = explode("\n", runLocally("ps axo comm,user | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq")); $httpUser = array_shift($candidates); if (empty($httpUser)) { throw new RuntimeException( "Can't detect http user name.\n" . "Please setup `http_user` config parameter." ); } return $httpUser; }); $this->set('http_group', function () { $candidates = explode("\n", runLocally("ps axo comm,group | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | sort | awk '{print \$NF}' | uniq")); $httpGroup = array_shift($candidates); if (empty($httpGroup)) { throw new RuntimeException( "Can't detect http user name.\n" . "Please setup `http_group` config parameter." ); } return $httpGroup; }); } protected function loadHostConfiguration(): void { // read configuration foreach (['/../../../../', '/../../../../../'] as $path) { $file = realpath(__DIR__ . $path . $_ENV['ENVIRONMENT_NAME'] . '.yaml'); if (file_exists($file)) { import($file); break; } } } protected function registerTasks(): void { new Rsync(); new Local(); new Remote(); new Deploy(); } protected function commandSupportsOption(string $command, string $option): bool { $man = runLocally("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true"); if (empty($man)) { return false; } return str_contains($man, $option); } protected function commandExist(string $command): bool { return testLocally("hash $command 2>/dev/null"); } protected function which(string $name): string { $nameEscaped = escapeshellarg($name); // Try `command`, should cover all Bourne-like shells // Try `which`, should cover most other cases // Fallback to `type` command, if the rest fails $path = runLocally("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped"); if (empty($path)) { throw new RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available"); } // Deal with issue when `type -p` outputs something like `type -ap` in some implementations return trim(str_replace("$name is", "", $path)); } protected function set(string $name, $value): void { set($name, $value); } }