fix: switch monitor root via sentinel yaml

This commit is contained in:
Codex
2026-06-28 03:28:10 +00:00
parent f371008af9
commit e4b9446ca9
8 changed files with 173 additions and 25 deletions
+22 -6
View File
@@ -12,7 +12,7 @@ import { repoRoot, rootPath } from "./config";
import { runCommand, type CommandResult } from "./command";
import { startJob } from "./jobs";
import { webProbeSentinelConfigPlan, withWebProbeSentinelConfigRendered } from "./hwlab-node-web-sentinel-config";
import { requireSentinelIdForRegistry, resolveWebProbeSentinel } from "./hwlab-node-web-sentinel-resolver";
import { effectiveWebProbeSentinelPublicExposure, requireSentinelIdForRegistry, resolveWebProbeSentinel } from "./hwlab-node-web-sentinel-resolver";
import type { HwlabRuntimeLaneSpec } from "./hwlab-node-lanes";
import type { RenderedCliResult } from "./output";
import { runWebProbeRemoteArtifactJob } from "./web-probe-remote-artifact";
@@ -310,7 +310,8 @@ function loadSentinelCicdState(spec: HwlabRuntimeLaneSpec, sentinelId: string |
const runtime = recordTarget(readConfigRefTarget(sentinel.configRefs.runtime), sentinel.configRefs.runtime);
const cicd = recordTarget(readConfigRefTarget(sentinel.configRefs.cicd), sentinel.configRefs.cicd);
const scenarios = readConfigRefTarget(sentinel.configRefs.scenarios);
const publicExposure = recordTarget(readConfigRefTarget(sentinel.configRefs.publicExposure), sentinel.configRefs.publicExposure);
const rawPublicExposure = recordTarget(readConfigRefTarget(sentinel.configRefs.publicExposure), sentinel.configRefs.publicExposure);
const publicExposure = effectiveWebProbeSentinelPublicExposure(spec, sentinel.id, rawPublicExposure);
const secrets = recordTarget(readConfigRefTarget(sentinel.configRefs.secrets), sentinel.configRefs.secrets);
const controlPlaneRef = stringField(cicd, "controlPlaneConfigRef");
const controlPlaneTarget = recordTarget(readConfigRefTarget(controlPlaneRef), controlPlaneRef);
@@ -3584,6 +3585,9 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
const responseHeaderTimeoutSeconds = numberAt(state.publicExposure, "caddy.responseHeaderTimeoutSeconds");
const remotePort = numberAt(state.publicExposure, "frpc.httpProxy.remotePort");
const routePrefix = normalizeRoutePrefix(stringAtNullable(state.publicExposure, "routePrefix"));
const rootOrder = stringAtNullable(state.publicExposure, "caddy.rootOrder") ?? "normal";
const monitorRoot = record(state.publicExposure.monitorRoot);
const cleanupOwner = monitorRoot.enabled === false ? stringAtNullable(monitorRoot, "caddyManagedBlockOwner") : null;
const proxyLines = [
`reverse_proxy 127.0.0.1:${remotePort} {`,
" transport http {",
@@ -3605,6 +3609,8 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
`config_path=${shellQuote(configPath)}`,
`service=${shellQuote(serviceName)}`,
`route_prefix=${shellQuote(routePrefix)}`,
`root_order=${shellQuote(rootOrder)}`,
`cleanup_owner=${shellQuote(cleanupOwner ?? "")}`,
`block_b64=${shellQuote(blockB64)}`,
"marker=\"unidesk managed $owner\"",
"tmp=$(mktemp -d)",
@@ -3613,17 +3619,21 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"next=\"$tmp/Caddyfile\"",
"printf '%s' \"$block_b64\" | base64 -d >\"$block\"",
"if [ -f \"$config_path\" ]; then cp \"$config_path\" \"$next\"; else : >\"$next\"; fi",
"python3 - \"$next\" \"$block\" \"$marker\" \"$hostname\" \"$route_prefix\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err",
"python3 - \"$next\" \"$block\" \"$marker\" \"$hostname\" \"$route_prefix\" \"$root_order\" \"$cleanup_owner\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err",
"import pathlib, re, sys",
"config = pathlib.Path(sys.argv[1])",
"block = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8')",
"marker = sys.argv[3]",
"hostname = sys.argv[4]",
"route_prefix = sys.argv[5]",
"root_order = sys.argv[6]",
"cleanup_owner = sys.argv[7]",
"text = config.read_text(encoding='utf-8') if config.exists() else ''",
"begin = f'# BEGIN {marker}'",
"end = f'# END {marker}'",
"pattern = re.compile(rf'(?ms)^[ \\t]*# BEGIN {re.escape(marker)}\\n.*?^[ \\t]*# END {re.escape(marker)}\\n*')",
"def managed_pattern(marker_text):",
" return re.compile(rf'(?ms)^[ \\t]*# BEGIN {re.escape(marker_text)}\\n.*?^[ \\t]*# END {re.escape(marker_text)}\\n*')",
"pattern = managed_pattern(marker)",
"def collect_nested_managed(segment):",
" preserved = []",
" lines = segment.splitlines()",
@@ -3647,6 +3657,10 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"for match in pattern.finditer(text):",
" preserved_blocks.extend(collect_nested_managed(match.group(0)))",
"text = pattern.sub('', text)",
"if cleanup_owner:",
" cleanup_marker = f'unidesk managed {cleanup_owner}'",
" if cleanup_marker != marker:",
" text = managed_pattern(cleanup_marker).sub('', text)",
"def site_span(src, host):",
" match = re.search(rf'(?m)^([ \\t]*){re.escape(host)}[ \\t]*\\{{[ \\t]*\\n', src)",
" if not match:",
@@ -3696,7 +3710,9 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
" relative_open = open_end - start",
" close_rel = close_index - start",
" additions = ''.join(preserved_blocks) + managed",
" if route_prefix == '/':",
" if route_prefix == '/' and root_order == 'active':",
" replacement = site[:relative_open] + additions + site[relative_open:]",
" elif route_prefix == '/':",
" replacement = append_before_close(site, close_rel, additions)",
" else:",
" insert_at = fallback_insert_pos(site, relative_open, close_rel)",
@@ -3753,7 +3769,7 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
].join("\n");
const result = runCommand(["trans", stringAt(state.publicExposure, "caddy.route"), "sh", "--", script], repoRoot, { timeoutMs: Math.min(timeoutSeconds, 60) * 1000 });
const parsed = parseJsonObject(result.stdout);
return { ok: result.exitCode === 0 && parsed?.ok === true, routePrefix, ...record(parsed), result: compactCommand(result), valuesRedacted: true };
return { ok: result.exitCode === 0 && parsed?.ok === true, routePrefix, rootOrder, ...record(parsed), result: compactCommand(result), valuesRedacted: true };
}
function readAnalysisSummaryFromWorkspace(state: SentinelCicdState, stateDir: string, timeoutSeconds: number): Record<string, unknown> {