['body' => 'localInfo'], 'local:setup' => ['body' => 'localSetup'], 'remote:prepare' => ['body' => 'remotePrepare'], 'local:lock' => ['body' => 'localLock'], 'local:unlock' => ['body' => 'localUnlock'], 'local:release' => ['body' => 'localRelease'], 'local:update_code' => ['body' => 'localUpdateCode'], 'local:env' => ['body' => 'localEnv'], 'local:shared' => ['body' => 'localShared'], 'local:writable' => ['body' => 'localWritable'], 'local:write_release' => ['body' => 'localWriteRelease'], 'local:create_folder' => ['body' => 'localCreateFolder'], 'local:vendors' => ['body' => 'localVendors'], 'local:clear_paths' => ['body' => 'localClearPaths'], 'local:symlink' => ['body' => 'localSymlink'], 'local:cleanup' => ['body' => 'localCleanup'], 'local:echo_release_number' => ['body' => 'echoReleaseNumber'], 'remote:change_group' => ['body' => 'remoteChangeGroup'], 'remote:writable' => ['body' => 'remoteWritable'], 'remote:db_compare' => ['body' => 'remoteDbCompare'], 'remote:fix_folder_structure' => ['body' => 'remoteFixFolderStructure'], 'remote:clear_cache' => ['body' => 'remoteClearCache'], 'remote:clear_opcache' => ['body' => 'remoteClearOpcache'], 'deploy:prepare' => [ 'body' => [ 'local:info', 'local:setup', 'remote:prepare', 'rsync:warmup', 'local:lock', 'local:release', 'local:update_code', 'local:env', 'local:shared', 'local:writable', 'local:write_release', 'local:create_folder', 'local:vendors', ] ], 'deploy:publish' => [ 'body' => [ 'local:clear_paths', 'local:symlink', 'local:cleanup', 'rsync:remote', 'remote:change_group', 'remote:writable', 'remote:db_compare', 'remote:fix_folder_structure', 'rsync:switch_current', 'remote:clear_cache', 'remote:clear_opcache', 'local:echo_release_number', 'local:unlock', 'deploy:success' ] ], ]; public function __construct(array $tasks = []) { if ($tasks !== []) { $this->tasks = $tasks; } $this->loadDeployerCommon(); $this->setDefaultConfiguration(); $this->loadHostConfiguration(); $this->initializeTasks(); $this->registerAfterTask(); } protected function loadDeployerCommon(): void { foreach (['/../../', '/../../../', '/../../../../'] as $path) { $file = realpath(__DIR__ . $path . 'vendor/deployer/deployer/recipe/common.php'); if (file_exists($file)) { require $file; break; } } new Rsync(); } 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 substr($link, 0, 1) === '/' ? $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 registerAfterTask(): void { // [Optional] if deploy fails automatically unlock. after('deploy:failed', 'local:unlock'); } protected function getSudo(): string { return $this->get('clear_use_sudo') ? 'sudo ' : ''; } protected function processRemoteTask(callable $callback): void { $config = Context::get()->getConfig(); if ($config->get('local') && $config->get('remote_path')) { // backup $contextBackup = Context::pop(); $config->set('local', false); // create temporary host $hostname = $contextBackup->getHost()->get('hostname'); $host = new Host($hostname); // create new context with host $context = new Context($host); Context::push($context); // execute the task \Closure::bind($callback, $this)(); // restore Context::pop(); $config->set('local', true); Context::push($contextBackup); } } 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 getApplicationContext(): string { return $this->has('application_context') ? 'TYPO3_CONTEXT="' . $this->get('application_context') . '" ' : ''; } protected function getTYPO3Bin(string $path, string $testPath): string { if (test('[ -f ' . $testPath . '/releases/{{release_name}}/vendor/bin/typo3cms ]')) { $bin = $path . '/releases/{{release_name}}/vendor/bin/typo3cms'; } else { $bin = $path . '/releases/{{release_name}}/vendor/bin/typo3'; } return $bin; } /** * Displays info about deployment */ public function localInfo(): void { info("deploying {{what}} to {{where}}"); } /** * Prepares host for deploy */ public function localSetup(): void { runLocally( <<hasArray('prepare_dirs')) { return; } $prepareDirs = $this->get('prepare_dirs'); foreach ($prepareDirs as $dir) { if (test('[ ! -e "{{remote_path}}/' . $dir . '" ]')) { run($this->getSudo() . ' mkdir -p {{remote_path}}/' . $dir); } } } /** * Locks deploy */ public function localLock(): void { $user = escapeshellarg(get('user')); $locked = runLocally("[ -f {{deploy_path}}/.dep/deploy.lock ] && echo +locked || echo $user > {{deploy_path}}/.dep/deploy.lock"); if ($locked === '+locked') { $lockedUser = runLocally("cat {{deploy_path}}/.dep/deploy.lock"); throw new GracefulShutdownException( "Deploy locked by $lockedUser.\n" . "Execute \"deploy:unlock\" task to unlock." ); } } /** * Unlocks deploy */ public function localUnlock(): void { // always success runLocally("rm -f {{deploy_path}}/.dep/deploy.lock"); } /** * Prepares release */ public function localRelease(): void { // Clean up if there is unfinished release. if (testLocally('[ -h {{deploy_path}}/release ]')) { runLocally('rm {{deploy_path}}/release'); // Delete symlink. } // We need to get releases_list at same point as release_name, // as standard release_name's implementation depends on it and, // if user overrides it, we need to get releases_list manually. $releasesList = get('releases_list'); $releaseName = get('release_name'); $releasePath = "{{deploy_path}}/releases/$releaseName"; // Check what there is no such release path. if (testLocally("[ -d $releasePath ]")) { $freeReleaseName = '...'; // Check what $releaseName is integer. if (ctype_digit($releaseName)) { $freeReleaseName = intval($releaseName); // Find free release name. while (testLocally("[ -d {{deploy_path}}/releases/$freeReleaseName ]")) { $freeReleaseName++; } } throw new Exception("Release name \"$releaseName\" already exists.\nRelease name can be overridden via:\n dep deploy -o release_name=$freeReleaseName"); } // Save release_name. if (is_numeric($releaseName) && is_integer(intval($releaseName))) { runLocally("echo $releaseName > {{deploy_path}}/.dep/latest_release"); } // Metainfo. $timestamp = timestamp(); $metainfo = [ 'created_at' => $timestamp, 'release_name' => $releaseName, 'user' => get('user'), 'target' => get('target'), ]; // Save metainfo about release. $json = escape_shell_argument(json_encode($metainfo)); runLocally("echo $json >> {{deploy_path}}/.dep/releases_log"); // Make new release. runLocally("mkdir -p $releasePath"); runLocally("{{bin/symlink}} $releasePath {{deploy_path}}/release"); // Add to releases list. array_unshift($releasesList, $releaseName); set('releases_list', $releasesList); // Set previous_release. if (isset($releasesList[1])) { set('previous_release', "{{deploy_path}}/releases/{$releasesList[1]}"); } } /** * Updates code */ public function localUpdateCode(): void { $git = get('bin/git'); $repository = get('repository'); $target = get('target'); if (empty($repository)) { throw new ConfigurationException("Missing 'repository' configuration."); } $targetWithDir = $target; if (!empty(get('sub_directory'))) { $targetWithDir .= ':{{sub_directory}}'; } $bare = parse('{{deploy_path}}/.dep/repo'); $env = [ 'GIT_TERMINAL_PROMPT' => '0', 'GIT_SSH_COMMAND' => get('git_ssh_command'), ]; start: // Clone the repository to a bare repo. runLocally("[ -d $bare ] || mkdir -p $bare"); runLocally("[ -f $bare/HEAD ] || $git clone --mirror $repository $bare 2>&1", ['env' => $env]); // If remote url changed, drop `.dep/repo` and reinstall. if (runLocally("cd $bare; $git config --get remote.origin.url") !== $repository) { runLocally("rm -rf {{deploy_path}}$bare"); goto start; } runLocally("$git remote update 2>&1", ['env' => $env]); // Copy to release_path. if (get('update_code_strategy') === 'archive') { runLocally("cd $bare; $git archive $targetWithDir | tar -x -f - -C {{release_path}} 2>&1"); } elseif (get('update_code_strategy') === 'clone') { runLocally("cd {{release_path}}; $git clone -l $bare ."); runLocally("cd {{release_path}}; $git remote set-url origin $repository", ['env' => $env]); runLocally("cd {{release_path}}; $git checkout --force $target"); } else { throw new ConfigurationException(parse("Unknown `update_code_strategy` option: {{update_code_strategy}}.")); } // Save git revision in REVISION file. $rev = escapeshellarg(runLocally("cd $bare; $git rev-list $target -1")); runLocally("echo $rev > {{release_path}}/REVISION"); } /** * Configure .env file */ public function localEnv(): void { if (testLocally('[ ! -e {{release_or_current_path}}/.env ] && [ -f {{release_or_current_path}}/{{dotenv_example}} ]')) { runLocally('cp {{release_or_current_path}}/{{dotenv_example}} {{release_or_current_path}}/.env'); } } /** * Creates symlinks for shared files and dirs */ public function localShared(): void { $sharedPath = "{{deploy_path}}/shared"; // Validate shared_dir, find duplicates foreach (get('shared_dirs') as $a) { foreach (get('shared_dirs') as $b) { if ($a !== $b && strpos(rtrim($a, '/') . '/', rtrim($b, '/') . '/') === 0) { throw new Exception("Can not share same dirs `$a` and `$b`."); } } } $copyVerbosity = output()->getVerbosity() === OutputInterface::VERBOSITY_DEBUG ? 'v' : ''; foreach (get('shared_dirs') as $dir) { // Make sure all path without tailing slash. $dir = trim($dir, '/'); // Check if shared dir does not exist. if (!testLocally("[ -d $sharedPath/$dir ]")) { // Create shared dir if it does not exist. runLocally("mkdir -p $sharedPath/$dir"); // If release contains shared dir, copy that dir from release to shared. if (testLocally("[ -d $(echo {{release_path}}/$dir) ]")) { runLocally("cp -r$copyVerbosity {{release_path}}/$dir $sharedPath/" . dirname($dir)); } } // Remove from source. runLocally("rm -rf {{release_path}}/$dir"); // Create path to shared dir in release dir if it does not exist. // Symlink will not create the path and will fail otherwise. runLocally("mkdir -p `dirname {{release_path}}/$dir`"); // Symlink shared dir to release dir runLocally("{{bin/symlink}} $sharedPath/$dir {{release_path}}/$dir"); } foreach (get('shared_files') as $file) { $dirname = dirname(parse($file)); // Create dir of shared file if not existing if (!testLocally("[ -d $sharedPath/$dirname ]")) { runLocally("mkdir -p $sharedPath/$dirname"); } // Check if shared file does not exist in shared. // and file exist in release if (!testLocally("[ -f $sharedPath/$file ]") && testLocally("[ -f {{release_path}}/$file ]")) { // Copy file in shared dir if not present runLocally("cp -r$copyVerbosity {{release_path}}/$file $sharedPath/$file"); } // Remove from source. runLocally("if [ -f $(echo {{release_path}}/$file) ]; then rm -rf {{release_path}}/$file; fi"); // Ensure dir is available in release runLocally("if [ ! -d $(echo {{release_path}}/$dirname) ]; then mkdir -p {{release_path}}/$dirname;fi"); // Touch shared runLocally("[ -f $sharedPath/$file ] || touch $sharedPath/$file"); // Symlink shared dir to release dir runLocally("{{bin/symlink}} $sharedPath/$file {{release_path}}/$file"); } } /** * Makes writable dirs */ public function localWritable(): void { $dirs = join(' ', get('writable_dirs')); $mode = get('writable_mode'); $recursive = get('writable_recursive') ? '-R' : ''; $sudo = get('writable_use_sudo') ? 'sudo' : ''; if (empty($dirs)) { return; } // Check that we don't have absolute path if (strpos($dirs, ' /') !== false) { throw new \RuntimeException('Absolute path not allowed in config parameter `writable_dirs`.'); } // Create directories if they don't exist runLocally("cd {{release_or_current_path}}; mkdir -p $dirs"); if ($mode === 'chown') { $httpUser = get('http_user'); // Change owner. // -L traverse every symbolic link to a directory encountered runLocally("cd {{release_or_current_path}}; $sudo chown -L $recursive $httpUser $dirs"); } elseif ($mode === 'chgrp') { // Change group ownership. // -L traverse every symbolic link to a directory encountered runLocally("cd {{release_or_current_path}}; $sudo chgrp -L $recursive {{http_group}} $dirs"); runLocally("cd {{release_or_current_path}}; $sudo chmod $recursive g+rwx $dirs"); } elseif ($mode === 'chmod') { runLocally("cd {{release_or_current_path}}; $sudo chmod $recursive {{writable_chmod_mode}} $dirs"); } elseif ($mode === 'acl') { $remoteUser = get('remote_user', false); if (empty($remoteUser)) { $remoteUser = runLocally('whoami'); } $httpUser = get('http_user'); if (strlen(runLocally("chmod --help | grep ugoa; true")) > 0) { // Try OS-X specific setting of access-rights runLocally("cd {{release_or_current_path}}; $sudo chmod g+w $dirs"); } elseif ($this->commandExist('setfacl')) { $setFaclUsers = "-m u:\"$httpUser\":rwX"; // Check if remote user exists, before adding it to setfacl $remoteUserExists = testLocally("id -u $remoteUser &>/dev/null 2>&1 || exit 0"); if ($remoteUserExists === true) { $setFaclUsers .= " -m u:$remoteUser:rwX"; } if (empty($sudo)) { // When running without sudo, exception may be thrown // if executing setfacl on files created by http user (in directory that has been setfacl before). // These directories/files should be skipped. // Now, we will check each directory for ACL and only setfacl for which has not been set before. $writeableDirs = get('writable_dirs'); foreach ($writeableDirs as $dir) { // Check if ACL has been set or not $hasfacl = runLocally("cd {{release_or_current_path}}; getfacl -p $dir | grep \"^user:$httpUser:.*w\" | wc -l"); // Set ACL for directory if it has not been set before if (!$hasfacl) { runLocally("cd {{release_or_current_path}}; setfacl -L $recursive $setFaclUsers $dir"); runLocally("cd {{release_or_current_path}}; setfacl -dL $recursive $setFaclUsers $dir"); } } } else { runLocally("cd {{release_or_current_path}}; $sudo setfacl -L $recursive $setFaclUsers $dirs"); runLocally("cd {{release_or_current_path}}; $sudo setfacl -dL $recursive $setFaclUsers $dirs"); } } else { $alias = currentHost()->getAlias(); throw new \RuntimeException("Can't set writable dirs with ACL.\nInstall ACL with next command:\ndep run 'sudo apt-get install acl' -- $alias"); } } elseif ($mode === 'sticky') { // Changes the group of the files, sets sticky bit to the directories // and add the writable bit for all files runLocally("cd {{release_or_current_path}}; for dir in $dirs;" . 'do ' . 'chgrp -L -R {{http_group}} ${dir}; ' . 'find ${dir} -type d -exec chmod g+rwxs \{\} \;;' . 'find ${dir} -type f -exec chmod g+rw \{\} \;;' . 'done'); } elseif ($mode === 'skip') { // Does nothing, saves time if no changes are required for some environments return; } else { throw new \RuntimeException("Unknown writable_mode `$mode`."); } } public function localWriteRelease(): void { runLocally('echo ' . getEnv('CI_COMMIT_REF_NAME') . ' > {{release_path}}/release'); } /** * Creating necessary folders */ public function localCreateFolder(): void { $sudo = $this->getSudo(); if (testLocally('[ ! -d {{release_path}}/var/cache ]')) { runLocally('mkdir -p {{release_path}}/var/cache'); } runLocally($sudo . ' chmod -R 775 {{release_path}}/var/cache'); if (testLocally('[ ! -d {{release_path}}/var/log ]')) { runLocally('mkdir -p {{release_path}}/var/log'); } runLocally($sudo . ' chmod -R 775 {{release_path}}/var/log'); } /** * Installing vendors */ public function localVendors(): void { if (!$this->commandExist('unzip')) { info( 'To speed up composer installation setup "unzip" command' . ' with PHP zip extension https://goo.gl/sxzFcD' ); } // if composer.json exists if (testLocally('[ -d $(echo {{release_path}}) ] && [ -e "{{release_path}}/composer.json" ]')) { runLocally('{{bin/composer}} --working-dir={{release_path}} {{composer_options}};'); } } /** * Cleanup files and/or directories */ public function localClearPaths(): void { $paths = get('clear_paths'); $sudo = get('clear_use_sudo') ? 'sudo' : ''; $batch = 100; $commands = []; foreach ($paths as $path) { $commands[] = "$sudo rm -rf {{release_path}}/$path"; } $chunks = array_chunk($commands, $batch); foreach ($chunks as $chunk) { runLocally(implode('; ', $chunk)); } } /** * Creating symlink to release */ public function localSymlink(): void { if (get('use_atomic_symlink')) { runLocally("mv -T {{deploy_path}}/release {{current_path}}"); } else { // Atomic symlink does not supported. // Will use simple two steps switch. runLocally("cd {{deploy_path}} && {{bin/symlink}} {{release_path}} {{current_path}}"); // Atomic override symlink. runLocally("cd {{deploy_path}} && rm release"); // Remove release link. } } /** * Cleaning up old releases */ public function localCleanup(): void { $releases = get('releases_list'); $keep = get('keep_releases'); $sudo = get('cleanup_use_sudo') ? 'sudo' : ''; runLocally("cd {{deploy_path}} && if [ -e release ]; then rm release; fi"); if ($keep > 0) { foreach (array_slice($releases, $keep) as $release) { runLocally("$sudo rm -rf {{deploy_path}}/releases/$release"); } } } /** * Change usergroup for the hole project */ public function remoteChangeGroup(): void { if ($this->get('http_group')) { $sudo = get('writable_use_sudo') ? 'sudo' : ''; $runOpts = []; if ($sudo) { $runOpts['tty'] = $this->get('writable_tty', false); } $path = parse('{{remote_path}}/releases/{{release_name}}/'); run($sudo . ' chgrp -H -R ' . $this->get('http_group') . ' ' . $path, $runOpts); run($sudo . ' chmod -R 2755 ' . $path, $runOpts); } } /** * Set files and/or folders writable */ public function remoteWritable(): void { if ($this->hasArray('writable_dirs') || $this->hasArray('writable_files')) { $sudo = get('writable_use_sudo') ? 'sudo' : ''; $runOpts = []; if ($sudo) { $runOpts['tty'] = get('writable_tty', false); } $recursive = $this->get('writable_recursive') ? '-R' : ''; $httpGroup = $this->get('http_group', false); $path = parse('{{remote_path}}/releases/{{release_name}}/'); if ($this->hasArray('writable_dirs')) { foreach ($this->get('writable_dirs') as $dir) { if (test('[ ! -d "' . $path . $dir . '" ]')) { run('mkdir -p ' . $path . $dir); } run($sudo . ' chmod -R 2775 ' . $path . $dir); if ($httpGroup) { run( $sudo . ' chgrp -H ' . $recursive . ' ' . $httpGroup . ' ' . $path . $dir, $runOpts ); } } } if ($this->hasArray('writable_files')) { foreach ($this->get('writable_files') as $file) { if (test('[ -e "' . $path . $file . '" ]')) { run($sudo . ' chmod g+w ' . $path . $file); if ($httpGroup) { run($sudo . ' chgrp ' . $httpGroup . ' ' . $path . $file, $runOpts); } } } } } } /** * Database migration */ public function remoteDbCompare(): void { $path = parse('{{app_container_path}}') ?: parse('{{remote_path}}'); $php = $this->getSudo() . parse('{{bin/php}} '); if (!str_contains($php, 'TYPO3_CONTEXT')) { $php = $this->getApplicationContext() . $php; } $result = run( $php . $this->getTYPO3Bin($path, parse('{{remote_path}}')) . ' database:updateschema "*.add,*.change"' ); writeln($result); } /** * Fix folder structure */ public function remoteFixFolderStructure(): void { $path = parse('{{app_container_path}}') ?: parse('{{remote_path}}'); $php = $this->getSudo() . parse('{{bin/php}} '); if (!str_contains($php, 'TYPO3_CONTEXT')) { $php = $this->getApplicationContext() . $php; } $result = run( $php . $this->getTYPO3Bin($path, parse('{{remote_path}}')) . ' install:fixfolderstructure' ); writeln($result); } /** * Clear cache via typo3 cache:flush */ public function remoteClearCache(): void { $path = parse('{{app_container_path}}') ?: parse('{{remote_path}}'); $php = $this->getSudo() . parse('{{bin/php}} '); if (!str_contains($php, 'TYPO3_CONTEXT')) { $php = $this->getApplicationContext() . $php; } $result = run($php . $this->getTYPO3Bin($path, parse('{{remote_path}}')) . ' cache:flush'); writeln($result); } /** * Clear opcache via curl on {CI_HOST}/cache.php */ public function remoteClearOpcache(): void { $webDomain = rtrim($this->get('web_domain'), '/'); $htaccess = $this->has('htaccess') ? '--user ' . $this->get('htaccess') : ''; run('curl ' . $htaccess . ' -sk ' . $webDomain . '/cache.php'); } public function echoReleaseNumber(): void { info('Folder of this release is: {{release_path}}'); } }