Files
typo3-13/deploy.php
T
frommeandClaude Opus 5.5 88fac7313e fix: deploys reset OPcache before flushing the TYPO3 caches, as on TYPO3 10
After a deploy, pages showed unstyled content until the caches were
flushed by hand in the backend. Every vhost points at current/public
since 2026-10-24 and keeps running the previous release after the
switch; pages it renders after the deploy's flush reference that
release's hashed CSS/JS, which the new release does not have, and stay
in the page cache and as staticfilecache files.

As in TYPO3 10's deploy.php, cachetool resets OPcache right after the
switch (and after a rollback), before the flush. Its web adapter puts
the temp script into the shared typo3temp/assets, which every release
resolves to the same directory, and requests it through
www.ep-reisen.de. cachetool is pinned to 10.0.0 for PHP 8.5.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 08:20:21 +01:00

131 lines
5.2 KiB
PHP

<?php
namespace Deployer;
require 'recipe/typo3.php';
require 'contrib/rsync.php';
require 'contrib/cachetool.php';
set('cachetool_url', 'https://github.com/gordalina/cachetool/releases/download/10.0.0/cachetool.phar');
set('application', 'ep-reisen-typo3-13');
set('keep_releases', 3);
// fileadmin of the TYPO3 10 release until it is moved into shared/ (plan 4a, tidy up step 2)
set('fileadmin_path', '/usr/home/eptypo3/public_html/ep-reisen-typo3/shared/public/fileadmin');
set('shared_dirs', [
'{{typo3/public_dir}}/typo3temp/assets',
'var/lock',
'var/log',
'var/session',
'var/spool',
// Baseline of the MyE&P import: without it every deploy would trigger a full re-import
'var/transient/ep_products_import',
]);
// Not in git, production is leading (encryptionKey, install tool, backend changes)
set('shared_files', [
'config/system/secrets.php',
'config/system/settings.php',
]);
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();
task('typo3:fileadmin', function () {
// Relative (-r), like Deployer's shared dirs: the vhosts answer 403 for an absolute symlink here
run('ln -sfnr {{fileadmin_path}} {{release_path}}/{{typo3/public_dir}}/fileadmin');
});
// 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',
// 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',
'typo3:fileadmin',
'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');