fix(cms): run publish deploy as direct child — detached spawn died under systemd
CI Pipeline with Test Management / 🧪 Run Tests & Generate Reports (push) Waiting to run
CI Pipeline with Test Management / 🐳 Docker Integration Tests (push) Blocked by required conditions
CI Pipeline with Test Management / 🏗️ Build Docker Image (push) Blocked by required conditions
CI Pipeline with Test Management / 📊 Generate Test Summary (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🧪 Run Tests & Generate Reports (push) Waiting to run
Test Reporting & Gherkin Analysis / 📊 Analyze Test Coverage (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / 🔄 Sync with Linear (push) Blocked by required conditions
Test Reporting & Gherkin Analysis / ⚡ Performance Monitoring (push) Blocked by required conditions

Root-caused with systemd-run repros: under the unit's hardening flags the
backgrounded grandchild ('cmd &' / setsid) died silently, so a CMS publish
committed+pushed but never rebuilt the site (stale content, no deploy.log,
no trace). A direct (non-detached) exec child provably survives the same
flags; the response is sent first, output goes to deploy.log, and the
callback's audit entry now reports real deploy completion (ok/error).

Closes MITHOME-72
This commit is contained in:
Do Siki
2026-08-19 15:27:51 +02:00
parent 09228b6a6d
commit 068e148da1
+9 -7
View File
@@ -305,18 +305,20 @@ const server = http.createServer(async (req, res) => {
// trigger a rebuild. Deploy only the explicitly configured environment;
// never default to production. Overridable for tests.
if (outcome.hadChanges) {
// WHY setsid+nohup+stdin-null: under systemd the naive `cmd &` child died
// together with the spawning shell (observed: the deploy never ran after a
// CMS publish, leaving the site on stale content). Full detachment makes it
// survive; the audit entries make the spawn observable instead of silent.
// WHY direct child instead of a detached `cmd &`: under the systemd unit's
// hardening (NoNewPrivileges/PrivateTmp) the backgrounded grandchild died
// silently (observed twice: stale site after a publish). A direct child is
// not detached, runs to completion, and the callback turns the audit entry
// into a real "deploy finished/failed" signal. The HTTP response is already
// sent; deploy output goes to deploy.log so the pipes stay quiet.
const deployCmd = process.env.CONTENT_EDITOR_DEPLOY_CMD
|| `cd ../../../ && setsid nohup ./deploy.sh ${CMS_DEPLOY_ENV} > deploy.log 2>&1 < /dev/null &`;
|| `cd ../../../ && ./deploy.sh ${CMS_DEPLOY_ENV} > deploy.log 2>&1`;
writeAudit('deploy_spawned', { clientAddress, user: CMS_USER, env: CMS_DEPLOY_ENV });
exec(deployCmd, deployError => {
exec(deployCmd, { maxBuffer: 8 * 1024 * 1024 }, deployError => {
writeAudit('deploy_exec_exit', {
clientAddress,
user: CMS_USER,
result: deployError ? 'error' : 'shell_exited',
result: deployError ? 'error' : 'ok',
error: deployError ? String(deployError.message).slice(0, 300) : undefined,
});
});