chore: slim deploy.php down to what every deploy needs
Removed the one-off tasks of the first deploys, all done now: host:check
(host checks before the first deploy, including the static vhosts),
typo3:secrets:init and typo3:secrets:check (secrets.php is filled in), and
typo3:settings:init (the shared settings.php exists since the deploy after
3f88229). Also removed what the Deployer 8 recipe already provides with the
same result: typo3/public_dir and bin/typo3 (read from composer.json),
typo3:extension:setup and the unlock after a failed deploy. The build always
runs through ddev exec, as deploys run on the host.
The resolved configuration (rsync excludes, flags and filter, shared and
writable directories, build path) and the task order are unchanged.
contrib/rsync.php replaces the recipe's rsync settings, so the recipe's
var/ exclude never applied: var/labels is deployed with the release, as
before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
73fe876c78
commit
f4e024a9f8
1 file changed
+23
-124
+23
-124
@@ -2,29 +2,13 @@
|
||||
|
||||
namespace Deployer;
|
||||
|
||||
// Run on the host (Deployer 8, PHP >= 8.3): dep deploy prod. The SSH keys stay on the host; the
|
||||
// build runs inside DDEV, so Composer and Node are the project's versions.
|
||||
// Prerequisites on the host: see .ddev/plans/TYPO3-13-GO-LIVE.md (PHP 8.5.12, database, secrets.php)
|
||||
|
||||
require 'recipe/typo3.php';
|
||||
require 'contrib/rsync.php';
|
||||
|
||||
set('application', 'ep-reisen-typo3-13');
|
||||
|
||||
// Defined here rather than taken from recipe/typo3.php, whose task and setting names differ between
|
||||
// Deployer versions (a globally installed "dep" may not be the project's vendor/bin/dep 8)
|
||||
set('typo3/public_dir', 'public');
|
||||
set('bin/typo3', 'vendor/bin/typo3');
|
||||
set('keep_releases', 3);
|
||||
|
||||
// The release is built locally from the committed state, so nothing uncommitted is ever deployed
|
||||
// and the host needs neither Composer nor Node
|
||||
set('build_dir', __DIR__ . '/var/deploy-build');
|
||||
set('use_rsync', true);
|
||||
set('rsync_src', '{{build_dir}}');
|
||||
|
||||
// fileadmin is the production fileadmin of the TYPO3 10 release: it is the CDN origin's fileadmin
|
||||
// (static/ep-reisen/fileadmin links there too), and TYPO3 13 writes its processed images into it
|
||||
// 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', [
|
||||
@@ -36,10 +20,7 @@ set('shared_dirs', [
|
||||
// Baseline of the MyE&P import: without it every deploy would trigger a full re-import
|
||||
'var/transient/ep_products_import',
|
||||
]);
|
||||
// settings.php is shared as LocalConfiguration.php was on TYPO3 10: production is leading, so what
|
||||
// is changed in the backend (extension configuration, install tool) survives deploys and rollbacks.
|
||||
// It is not in git (encryptionKey, install tool password): configuration changes for production are
|
||||
// made there, in the backend or by hand on the shared file.
|
||||
// Not in git, production is leading (encryptionKey, install tool, backend changes)
|
||||
set('shared_files', [
|
||||
'config/system/secrets.php',
|
||||
'config/system/settings.php',
|
||||
@@ -53,6 +34,13 @@ set('writable_dirs', [
|
||||
]);
|
||||
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',
|
||||
@@ -68,7 +56,7 @@ $rsync['exclude'] = array_merge($rsync['exclude'], [
|
||||
'/package-lock.json',
|
||||
'/.nvmrc',
|
||||
]);
|
||||
// The build directory has no .gitignore filtering to do, and the filter would drop the built assets
|
||||
// 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
|
||||
@@ -83,124 +71,41 @@ host('prod')
|
||||
->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 () {
|
||||
// Inside DDEV directly, on the host through "ddev exec" (paths relative to the project root)
|
||||
$inDdev = getenv('IS_DDEV_PROJECT') === 'true';
|
||||
$exec = fn(string $command) => runLocally($inDdev
|
||||
? $command
|
||||
: 'ddev exec bash -c ' . escapeshellarg($command));
|
||||
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 an extension's Resources/Public as public/_assets/<hash>
|
||||
// if the directory exists when it runs, and the theme's is created by the build
|
||||
$exec('cd var/deploy-build && npm ci --no-audit --no-fund && npm run build');
|
||||
$exec('cd var/deploy-build && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader');
|
||||
runLocally('test -L {{build_dir}}/public/_assets/' . md5('/vendor/ep/ep-theme/') . ' && test -f {{build_dir}}/packages/ep_theme/Resources/Public/entrypoints.json');
|
||||
// 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();
|
||||
|
||||
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('<info>✔</info> %s: %s', $label, trim(run($command))));
|
||||
} catch (\Throwable $e) {
|
||||
writeln(sprintf('<error>✘</error> %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');
|
||||
}
|
||||
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');
|
||||
});
|
||||
|
||||
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('<comment>Created ' . parse($target) . ' from the template: fill it in, then deploy again.</comment>');
|
||||
});
|
||||
|
||||
desc('Seed the shared settings.php from the live release unless it is there already (never overwrites)');
|
||||
task('typo3:settings:init', function () {
|
||||
$target = '{{deploy_path}}/shared/config/system/settings.php';
|
||||
if (test("[ -s $target ]")) {
|
||||
return;
|
||||
}
|
||||
// deploy:shared would seed it from the new release, i.e. from git, and drop production's settings
|
||||
if (!test('[ -f {{deploy_path}}/current/config/system/settings.php ]')) {
|
||||
return;
|
||||
}
|
||||
run('mkdir -p {{deploy_path}}/shared/config/system');
|
||||
run("cp -L {{deploy_path}}/current/config/system/settings.php $target");
|
||||
writeln('<comment>Created ' . parse($target) . ' from the live release.</comment>');
|
||||
});
|
||||
|
||||
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');
|
||||
// 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');
|
||||
});
|
||||
|
||||
desc('Link the production fileadmin into the release');
|
||||
task('typo3:fileadmin', function () {
|
||||
run('test -d {{fileadmin_path}}');
|
||||
// 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');
|
||||
run('test -d {{release_path}}/{{typo3/public_dir}}/fileadmin/user_upload');
|
||||
});
|
||||
|
||||
desc('Deploys TYPO3 13');
|
||||
task('deploy', [
|
||||
'deploy:info',
|
||||
// Build first, so a failing build aborts before the server is touched at all.
|
||||
'build',
|
||||
// Build locally first, so a failing build aborts before the server is touched at all.
|
||||
'deploy:build',
|
||||
'deploy:setup',
|
||||
'deploy:lock',
|
||||
'deploy:release',
|
||||
'rsync',
|
||||
'typo3:secrets:init',
|
||||
'typo3:settings: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).
|
||||
// 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',
|
||||
@@ -209,12 +114,6 @@ task('deploy', [
|
||||
// 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 a host's typo3temp. Vhosts that
|
||||
// point at current/public keep running the previous release until PHP's path cache expires (~2 min);
|
||||
// pages it renders in that window stay in the page cache until the next flush.
|
||||
after('deploy:symlink', 'typo3:cache:flush');
|
||||
// A rollback switches the code back; pages cached by the other release must not survive it
|
||||
// Pages cached by the other release must not survive a rollback
|
||||
after('rollback', 'typo3:cache:flush');
|
||||
|
||||
|
||||
after('deploy:failed', 'deploy:unlock');
|
||||
Reference in new issue
Block a user