'config/sites/' . basename(dirname($config)) . '/settings.yaml', glob(__DIR__ . '/config/sites/*/config.yaml') )); set('shared_files', [ 'config/system/secrets.php', 'config/system/settings.php', ...get('site_settings_files'), ]); set('writable_dirs', [ '{{typo3/public_dir}}/typo3temp/assets', 'var/cache', 'var/lock', 'var/log', 'var/transient', ]); set('writable_mode', 'chmod'); // The release is built from the committed state, so nothing uncommitted and no dev dependencies go live set('build_dir', __DIR__ . '/var/deploy-build'); set('use_rsync', true); set('rsync_src', '{{build_dir}}'); // contrib/rsync.php replaces the TYPO3 recipe's rsync settings, so its exclude list (which has var/) // does not apply: the language packs in var/labels are deployed with the release $rsync = get('rsync'); $rsync['exclude'] = array_merge($rsync['exclude'], [ '/{{typo3/public_dir}}/fileadmin', 'node_modules', '/packages/*/Resources/Private/Assets', '/.ddev', '/deploy.php', '/rector.php', '/fractor.php', '/webpack.config.js', '/tailwind.config.js', '/package.json', '/package-lock.json', '/.nvmrc', ]); // No .gitignore filter: it would drop the built assets $rsync['filter'] = []; // -t preserves mtimes: without it every file lands with a fresh timestamp, so unchanged // files get a new Last-Modified on every deploy and every conditional request revalidates // into a full response instead of a 304. $rsync['flags'] = 'rzt'; $rsync['options'] = ['delete', 'links', 'chmod=D755,F664']; set('rsync', $rsync); host('prod') ->setHostname('dedi10193.your-server.de') ->setRemoteUser('eptypo3') ->setForwardAgent(true) ->setSshMultiplexing(true) ->setDeployPath('/usr/home/eptypo3/public_html/{{application}}') ->set('bin/php', '/usr/bin/php85') ->set('http_user', 'eptypo3') // The temp script goes into the shared typo3temp/assets, which resolves to the same directory from // every release: PHP workers with a stale realpath cache still map "current" to the previous release // and would 404 on a script placed inside the new one. ->set('cachetool_args', '--web=SymfonyHttpClient --web-path={{deploy_path}}/shared/{{typo3/public_dir}}/typo3temp/assets/ --web-url=https://www.ep-reisen.de/typo3temp/assets') ; task('deploy:build', function () { runLocally('rm -rf {{build_dir}} && mkdir -p {{build_dir}}'); runLocally('git archive HEAD | tar -x -C {{build_dir}}'); // Assets first: Composer only publishes the theme's Resources/Public to public/_assets // if the directory exists when it runs, and the asset build creates it runLocally('ddev exec bash -c "cd var/deploy-build && npm ci --no-audit --no-fund && npm run build"'); runLocally('ddev exec bash -c "cd var/deploy-build && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader"'); })->once(); // deploy:shared would create a missing shared dir empty, and the sites would lose every file task('deploy:check:fileadmin', function () { if (!test('[ -d {{deploy_path}}/shared/{{typo3/public_dir}}/fileadmin ]')) { throw new \RuntimeException(parse('{{deploy_path}}/shared/{{typo3/public_dir}}/fileadmin is missing')); } }); // The same for a site's settings.yaml: an empty one silently resets the site to the default settings // (navigation, storage folders, ...). A new site's file is put in place by hand first. task('deploy:check:site-settings', function () { foreach (get('site_settings_files') as $file) { if (!test("[ -s {{deploy_path}}/shared/$file ]")) { throw new \RuntimeException(parse("{{deploy_path}}/shared/$file is missing or empty")); } } }); // current_path instead of the recipe's release_path, so it also works after a rollback task('typo3:cache:flush', function () { run('{{bin/php}} {{current_path}}/{{bin/typo3}} cache:flush'); }); task('deploy', [ 'deploy:info', 'deploy:check:fileadmin', 'deploy:check:site-settings', // Build locally first, so a failing build aborts before the server is touched at all. 'deploy:build', 'deploy:setup', 'deploy:lock', 'deploy:release', 'rsync', 'deploy:shared', 'deploy:writable', // The schema has to exist before the new code starts serving traffic. '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. // OPcache first: until it is reset the vhosts keep running the previous release, and the pages it // renders (with its hashed CSS/JS) would outlive the flush. after('deploy:symlink', 'cachetool:clear:opcache'); after('deploy:symlink', 'typo3:cache:flush'); // Pages cached by the other release must not survive a rollback after('rollback', 'cachetool:clear:opcache'); after('rollback', 'typo3:cache:flush');