The constants of the sites become editable in Site Management >
Settings (admins only in TYPO3 13.4). settings.definitions.yaml of the
set ep/theme defines all 116 constants plus googleTagManagerId, with the
labels and categories of their constant editor annotations and today's
constants.typoscript values as defaults. Every setting is a string,
yes/no ones with a select of exactly '0' and '1': a bool setting would
hand '' instead of '0' to TypoScript.
config/sites/*/settings.yaml become shared files on the server and leave
git, like settings.php: production is leading, so backend edits survive
deploys, and each environment keeps its own values. Nine settings nobody
reads (twitterUrl, vimeoUrl, cookiefirstKey, ...) were dropped locally;
on production they stay in the seeded files until removed there.
deploy:check:site-settings aborts before anything else touches the
server if one of the shared files is missing or empty: deploy:shared
would create it empty, silently resetting that site to the defaults.
Rollout, after the site set is live: seed the shared files from the
live release once, then deploy:
cd <deploy_path> && for f in current/config/sites/*/settings.yaml; do
mkdir -p "shared/${f#current/}" && rmdir "shared/${f#current/}" &&
cp -p "$f" "shared/${f#current/}"; done
Verified locally: the resolved setup of 43 pages is unchanged and only
the nine unused constants are gone (switched state); the root template
state is unchanged; all 1912 frontend URLs are identical.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
148 lines
5.9 KiB
PHP
148 lines
5.9 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);
|
|
|
|
set('shared_dirs', [
|
|
'{{typo3/public_dir}}/fileadmin',
|
|
'{{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; the site
|
|
// settings are edited in the backend's Settings module, one settings.yaml per site)
|
|
set('site_settings_files', array_map(
|
|
static fn(string $config): string => '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');
|