setHostname('dedi10193.your-server.de') ->setRemoteUser('eptypo3') ->setForwardAgent(true) ->setSshMultiplexing(true) ->setDeployPath('/usr/home/eptypo3/public_html/{{application}}') // Verify on the host once Hetzner has installed it ->set('bin/php', '/usr/bin/php85') ->set('http_user', 'eptypo3') ; desc('Build the release locally: committed state, production dependencies, theme assets'); task('build', function () { runLocally('rm -rf {{build_dir}} && mkdir -p {{build_dir}}'); runLocally('git archive HEAD | tar -x -C {{build_dir}}'); runLocally('cd {{build_dir}} && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader'); runLocally('cd {{build_dir}} && npm ci --no-audit --no-fund && npm run build'); })->once(); desc('Read-only checks of the host before the first deploy (see .ddev/plans/TYPO3-13-GO-LIVE.md)'); task('host:check', function () { $checks = [ 'PHP CLI 8.5.12+' => '{{bin/php}} -r \'exit(version_compare(PHP_VERSION, "8.5.12", ">=") ? 0 : 1);\' && {{bin/php}} -v | head -1', 'PHP extensions' => '{{bin/php}} -r \'$m = array_diff(["mysqli", "pdo_mysql", "intl", "mbstring", "xml", "zip", "gd", "fileinfo", "openssl", "curl", "zlib"], get_loaded_extensions()); echo $m ? "missing: " . implode(", ", $m) : "ok"; exit($m ? 1 : 0);\'', 'image processor (GraphicsMagick)' => 'test -x /usr/bin/gm && /usr/bin/gm version | head -1', 'production fileadmin' => 'test -d {{fileadmin_path}} && du -sh {{fileadmin_path}} 2>/dev/null | cut -f1', 'static/ep-reisen/fileadmin links there' => 'test "$(readlink -f /usr/home/eptypo3/public_html/static/ep-reisen/fileadmin)" = "$(readlink -f {{fileadmin_path}})" && echo yes', 'free disk space' => 'df -h /usr/home/eptypo3 | tail -1', 'secrets.php in place' => 'test -s {{deploy_path}}/shared/config/system/secrets.php && {{bin/php}} -r \'exit(is_array(require "{{deploy_path}}/shared/config/system/secrets.php") ? 0 : 1);\' && echo yes', 'MyE&P reachable' => 'curl -s -o /dev/null -w "%{http_code}" --max-time 10 https://my.ep-reisen.de/api/last-update', ]; $failed = false; foreach ($checks as $label => $command) { try { writeln(sprintf('✔ %s: %s', $label, trim(run($command)))); } catch (\Throwable $e) { writeln(sprintf('✘ %s', $label)); $failed = true; } } writeln('Not checkable from the CLI: open_basedir and PHP 8.5.12 (FPM) of the static vhosts, see the go-live plan.'); if ($failed) { throw new \RuntimeException('Host checks failed'); } }); desc('Put the secrets.php template into shared/ unless a secrets.php is there already (never overwrites)'); task('typo3:secrets:init', function () { $target = '{{deploy_path}}/shared/config/system/secrets.php'; if (test("[ -s $target ]")) { return; } run('mkdir -p {{deploy_path}}/shared/config/system'); upload(__DIR__ . '/config/system/secrets.example.php', $target); run("chmod 600 $target"); writeln('Created ' . parse($target) . ' from the template: fill it in, then deploy again.'); }); desc('Stop before the database is touched while secrets.php still lacks the database connection'); task('typo3:secrets:check', function () { $secrets = '{{deploy_path}}/shared/config/system/secrets.php'; $filled = run("{{bin/php}} -r '\$s = require \"$secrets\"; echo is_array(\$s) && (\$s[\"DB\"][\"Connections\"][\"Default\"][\"dbname\"] ?? \"\") !== \"\" ? \"yes\" : \"no\";'"); if ($filled !== 'yes') { throw new \RuntimeException(parse("$secrets has no database connection yet: fill it in, then deploy again. Nothing went live.")); } }); desc('TYPO3 - Set up all extensions (database schema: adds and changes only)'); task('typo3:extension:setup', function () { run('{{bin/php}} {{release_path}}/{{bin/typo3}} extension:setup'); }); desc('TYPO3 - Clear all caches'); task('typo3:cache:flush', function () { run('{{bin/php}} {{release_path}}/{{bin/typo3}} cache:flush'); }); desc('Link the production fileadmin into the release'); task('typo3:fileadmin', function () { run('test -d {{fileadmin_path}}'); run('ln -sfn {{fileadmin_path}} {{release_path}}/{{typo3/public_dir}}/fileadmin'); }); desc('Deploys TYPO3 13'); task('deploy', [ 'deploy:info', // Build first, so a failing build aborts before the server is touched at all. 'build', 'deploy:setup', 'deploy:lock', 'deploy:release', 'rsync', 'typo3:secrets:init', 'deploy:shared', 'typo3:fileadmin', 'deploy:writable', 'typo3:secrets:check', // The schema has to exist before the new code starts serving traffic (adds and changes only). 'typo3:extension:setup', // deploy:publish == deploy:symlink, deploy:unlock, deploy:cleanup, deploy:success 'deploy:publish', ]); // Caches belong to the release that goes live, so they are cleared after the symlink flip. Hooking // into deploy:symlink rather than appending to the list above keeps them inside deploy:publish, ahead // of deploy:unlock and deploy:success - a failing cache task must not follow a declared success. // No OPcache reset (cachetool): its web adapter would need PHP under www.ep-reisen.de/typo3temp, which // is the static docroot. Not needed either: the front controller stubs in the static docroots resolve // "current" with readlink() on every request, so a new release is always loaded under new paths. after('deploy:symlink', 'typo3:cache:flush'); after('deploy:failed', 'deploy:unlock');