From 0230f1ca9977d6e74de8996ef52a5438524f1640 Mon Sep 17 00:00:00 2001 From: Crimson Date: Mon, 31 Aug 2026 01:32:35 +0500 Subject: [PATCH] =?UTF-8?q?fix(orchestrator):=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=B0=D0=B3=D0=B0=D1=82=D1=8C=D1=81=D1=8F=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BC=D0=B0=D0=BD=D0=B3=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=B8=D0=BC=D0=B5=D0=BD=D0=B8=20=D1=82=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B7=D0=B8=D0=B5=D0=BD=D1=82=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=8E?= =?UTF-8?q?=D0=BD=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit systemd-run пропускает голое имя через unit_name_mangle_with_suffix, который сначала проверяет, не заканчивается ли оно уже известным типом юнита. Ключ операции — санитизированный ISO-timestamp вида `...T12-34-56.789Z`, то есть содержит точку, и корректность имени зависела бы от того, что `.789Z` случайно не совпало ни с одним типом systemd. Имя передаётся с явным суффиксом `.service`: systemd-run берёт его как есть и создаёт рядом одноимённый `.timer`, на который и рассчитывают снятие guard и status. --- orchestrator/src/steps/firewall.ts | 10 +++++++++- orchestrator/test/firewall-guard.test.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/orchestrator/src/steps/firewall.ts b/orchestrator/src/steps/firewall.ts index f892581..65573d3 100644 --- a/orchestrator/src/steps/firewall.ts +++ b/orchestrator/src/steps/firewall.ts @@ -447,7 +447,15 @@ async function armRollbackGuard(opId: string): Promise { await writeText(scriptPath, buildAutoRollbackScript(opId), 0o700); const unit = rollbackUnit(opId); - await runMutatingVisible`systemd-run --unit ${unit} --on-active=${FIREWALL_ROLLBACK_DEADLINE} /bin/sh ${scriptPath}`; + // Имя передаётся С суффиксом `.service`, а не голым. + // + // Голое имя systemd-run пропускает через unit_name_mangle_with_suffix, и тот + // сначала смотрит, не заканчивается ли оно уже известным типом юнита. Ключ + // операции — санитизированный ISO-timestamp вида `...T12-34-56.789Z`, то есть + // содержит точку, и корректность имени зависела бы от того, что `.789Z` + // случайно не совпало ни с одним типом. Явный суффикс убирает эту зависимость: + // systemd-run берёт имя как есть и создаёт рядом одноимённый `.timer`. + await runMutatingVisible`systemd-run --unit ${`${unit}.service`} --on-active=${FIREWALL_ROLLBACK_DEADLINE} /bin/sh ${scriptPath}`; info( `firewall rollback guard armed: ${unit} fires in ${FIREWALL_ROLLBACK_DEADLINE} unless the operation disarms it` ); diff --git a/orchestrator/test/firewall-guard.test.ts b/orchestrator/test/firewall-guard.test.ts index 0cd4e9a..ad1707d 100644 --- a/orchestrator/test/firewall-guard.test.ts +++ b/orchestrator/test/firewall-guard.test.ts @@ -271,6 +271,16 @@ describe("disarm доказывает снятие guard'а, а не сообщ ); }); + /** + * Имя транзиентного юнита передаётся с явным суффиксом. Голое имя systemd-run + * пропускает через мангling, который смотрит, не заканчивается ли оно уже + * известным типом юнита; ключ операции содержит точку (`...T12-34-56.789Z`), + * и корректность зависела бы от того, что `.789Z` ни с чем не совпало. + */ + test("имя юнита guard'а не зависит от мангления systemd", () => { + expect(firewallSource).toContain("systemd-run --unit ${`${unit}.service`}"); + }); + test("остановка guard'а стала стадией отката с отчётом", () => { const rollback = firewallSource.slice(firewallSource.indexOf("export async function rollbackFirewallNow")); expect(rollback).toContain('name: "stop firewall rollback guard"');