Merge pull request #1045 from pikasTech/fix/1017-caddy-route-order

fix(web-probe): keep sentinel caddy path routes
This commit is contained in:
Lyon
2026-06-26 21:58:45 +08:00
committed by GitHub
+81 -32
View File
@@ -2389,19 +2389,12 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
" }",
"}",
];
const block = routePrefix === "/"
? [
`${hostname} {`,
...proxyLines.map((line) => ` ${line}`),
"}",
"",
].join("\n")
: [
`handle_path ${routePrefix}* {`,
...proxyLines.map((line) => ` ${line}`),
"}",
"",
].join("\n");
const block = [
routePrefix === "/" ? "handle {" : `handle_path ${routePrefix}* {`,
...proxyLines.map((line) => ` ${line}`),
"}",
"",
].join("\n");
const blockB64 = Buffer.from(block, "utf8").toString("base64");
const script = [
"set +e",
@@ -2429,6 +2422,28 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"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 collect_nested_managed(segment):",
" preserved = []",
" lines = segment.splitlines()",
" index = 0",
" while index < len(lines):",
" stripped = lines[index].strip()",
" if stripped.startswith('# BEGIN ') and stripped != begin:",
" owner_text = stripped[len('# BEGIN '):]",
" end_line = '# END ' + owner_text",
" block_lines = [lines[index]]",
" index += 1",
" while index < len(lines):",
" block_lines.append(lines[index])",
" if lines[index].strip() == end_line:",
" break",
" index += 1",
" preserved.append('\\n'.join(block_lines).rstrip() + '\\n')",
" index += 1",
" return preserved",
"preserved_blocks = []",
"for match in pattern.finditer(text):",
" preserved_blocks.extend(collect_nested_managed(match.group(0)))",
"text = pattern.sub('', text)",
"def site_span(src, host):",
" match = re.search(rf'(?m)^([ \\t]*){re.escape(host)}[ \\t]*\\{{[ \\t]*\\n', src)",
@@ -2449,21 +2464,42 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
" return match.start(), end_index, index, match.end()",
" index += 1",
" raise ValueError(f'unclosed Caddy site block for {host}')",
"if route_prefix == '/':",
" managed = f'{begin}\\n{block.rstrip()}\\n{end}\\n'",
" text = text.rstrip() + '\\n\\n' + managed",
"handler = '\\n'.join((' ' + line) if line else '' for line in block.rstrip().splitlines())",
"managed = f' {begin}\\n{handler}\\n {end}\\n'",
"def fallback_insert_pos(site, relative_open, close_rel):",
" for match in re.finditer(r'(?m)^[ \\t]*handle[ \\t]*\\{[ \\t]*\\n', site[relative_open:close_rel]):",
" pos = relative_open + match.start()",
" prev_end = pos - 1",
" if prev_end >= 0 and site[prev_end] == '\\n':",
" prev_end -= 1",
" if prev_end >= 0:",
" prev_start = site.rfind('\\n', 0, prev_end + 1) + 1",
" if site[prev_start:prev_end + 1].strip().startswith('# BEGIN '):",
" return prev_start",
" return pos",
" return relative_open",
"def append_before_close(site, close_rel, addition):",
" prefix = site[:close_rel]",
" suffix = site[close_rel:]",
" if prefix and not prefix.endswith('\\n'):",
" prefix += '\\n'",
" return prefix + addition + suffix",
"span = site_span(text, hostname)",
"if span is None:",
" body = ''.join(preserved_blocks) + managed",
" text = text.rstrip() + '\\n\\n' + f'{hostname} {{\\n{body}}}\\n'",
"else:",
" handler = '\\n'.join((' ' + line) if line else '' for line in block.rstrip().splitlines())",
" managed = f' {begin}\\n{handler}\\n {end}\\n'",
" span = site_span(text, hostname)",
" if span is None:",
" text = text.rstrip() + '\\n\\n' + f'{hostname} {{\\n{managed}}}\\n'",
" start, stop, close_index, open_end = span",
" site = text[start:stop]",
" relative_open = open_end - start",
" close_rel = close_index - start",
" additions = ''.join(preserved_blocks) + managed",
" if route_prefix == '/':",
" replacement = append_before_close(site, close_rel, additions)",
" else:",
" start, stop, close_index, open_end = span",
" site = text[start:stop]",
" relative_open = open_end - start",
" replacement = site[:relative_open] + managed + site[relative_open:]",
" text = text[:start] + replacement + text[stop:]",
" insert_at = fallback_insert_pos(site, relative_open, close_rel)",
" replacement = site[:insert_at] + additions + site[insert_at:]",
" text = text[:start] + replacement + text[stop:]",
"config.write_text(text, encoding='utf-8')",
"PY",
"python_rc=$?",
@@ -2471,13 +2507,21 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"reload_rc=",
"if [ \"$python_rc\" = 0 ]; then sudo caddy validate --config \"$next\" --adapter caddyfile >/tmp/web-probe-sentinel-caddy-validate.out 2>/tmp/web-probe-sentinel-caddy-validate.err; validate_rc=$?; fi",
"if [ \"$validate_rc\" = 0 ]; then sudo install -m 0644 \"$next\" \"$config_path\" >/tmp/web-probe-sentinel-caddy-install.out 2>/tmp/web-probe-sentinel-caddy-install.err && (sudo systemctl reload \"$service\" >/tmp/web-probe-sentinel-caddy-reload.out 2>/tmp/web-probe-sentinel-caddy-reload.err || sudo systemctl restart \"$service\" >>/tmp/web-probe-sentinel-caddy-reload.out 2>>/tmp/web-probe-sentinel-caddy-reload.err); reload_rc=$?; fi",
"probe_rc=1",
"probe_status=",
"if [ \"$reload_rc\" = 0 ]; then",
" probe_path=\"$route_prefix\"",
" if [ \"$probe_path\" = \"/\" ]; then probe_url=\"https://$hostname/\"; else probe_url=\"https://$hostname$probe_path/\"; fi",
" probe_status=$(curl -k -sS -o /tmp/web-probe-sentinel-caddy-probe.out -w '%{http_code}' --max-time 10 --resolve \"$hostname:443:127.0.0.1\" \"$probe_url\" 2>/tmp/web-probe-sentinel-caddy-probe.err)",
" probe_rc=$?",
"fi",
"after_present=no",
"grep -Fq \"# BEGIN $marker\" \"$config_path\" 2>/dev/null && after_present=yes",
"active=$(systemctl is-active \"$service\" 2>/dev/null || true)",
"err=$(cat /tmp/web-probe-sentinel-caddy-python.err /tmp/web-probe-sentinel-caddy-validate.err /tmp/web-probe-sentinel-caddy-install.err /tmp/web-probe-sentinel-caddy-reload.err 2>/dev/null | tr '\\n' ';' | cut -c1-1000 || true)",
"python3 - \"$python_rc\" \"$validate_rc\" \"$reload_rc\" \"$after_present\" \"$active\" \"$hostname\" \"$config_path\" \"$err\" <<'PY'",
"err=$(cat /tmp/web-probe-sentinel-caddy-python.err /tmp/web-probe-sentinel-caddy-validate.err /tmp/web-probe-sentinel-caddy-install.err /tmp/web-probe-sentinel-caddy-reload.err /tmp/web-probe-sentinel-caddy-probe.err 2>/dev/null | tr '\\n' ';' | cut -c1-1000 || true)",
"python3 - \"$python_rc\" \"$validate_rc\" \"$reload_rc\" \"$probe_rc\" \"$probe_status\" \"$after_present\" \"$active\" \"$hostname\" \"$config_path\" \"$err\" <<'PY'",
"import json, sys",
"python_rc, validate_rc, reload_rc, after_present, active, hostname, config_path, error_preview = sys.argv[1:9]",
"python_rc, validate_rc, reload_rc, probe_rc, probe_status, after_present, active, hostname, config_path, error_preview = sys.argv[1:11]",
"def num(value):",
" if value == '':",
" return None",
@@ -2485,13 +2529,18 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
" return int(value)",
" except ValueError:",
" return None",
"http_status = num(probe_status)",
"probe_ok = num(probe_rc) == 0 and http_status is not None and 200 <= http_status < 400",
"payload = {",
" 'ok': num(python_rc) == 0 and num(validate_rc) == 0 and num(reload_rc) == 0 and after_present == 'yes',",
" 'ok': num(python_rc) == 0 and num(validate_rc) == 0 and num(reload_rc) == 0 and probe_ok and after_present == 'yes',",
" 'hostname': hostname,",
" 'configPath': config_path,",
" 'pythonExitCode': num(python_rc),",
" 'validateExitCode': num(validate_rc),",
" 'reloadExitCode': num(reload_rc),",
" 'routeProbeExitCode': num(probe_rc),",
" 'routeProbeHttpStatus': http_status,",
" 'routeProbeOk': probe_ok,",
" 'afterBlockPresent': after_present == 'yes',",
" 'active': active,",
" 'errorPreview': error_preview,",
@@ -3294,11 +3343,11 @@ function renderControlPlaneResult(result: Record<string, unknown>): string {
"",
Object.keys(runtimeSecretsApply).length === 0 ? "RUNTIME_SECRETS\n-" : table(["OK", "SECRETS", "KEYS", "SKIPPED"], [[runtimeSecretsApply.ok, runtimeSecretsApply.secretCount ?? "-", runtimeSecretsApply.keyCount ?? "-", runtimeSecretsApply.skippedKeyCount ?? "-"]]),
"",
Object.keys(publicExposureApply).length === 0 ? "PUBLIC_EXPOSURE_APPLY\n-" : table(["OK", "SECRET", "CADDY", "HOST"], [[publicExposureApply.ok, record(publicExposureApply.secret).ok, record(publicExposureApply.caddy).ok, publicExposureApply.hostname]]),
Object.keys(publicExposureApply).length === 0 ? "PUBLIC_EXPOSURE_APPLY\n-" : table(["OK", "SECRET", "CADDY", "HOST", "ROUTE_HTTP"], [[publicExposureApply.ok, record(publicExposureApply.secret).ok, record(publicExposureApply.caddy).ok, publicExposureApply.hostname, record(publicExposureApply.caddy).routeProbeHttpStatus ?? "-"]]),
"",
Object.keys(publicExposureCaddy).length === 0 || publicExposureCaddy.ok === true
? "CADDY_APPLY_DETAIL\n-"
: table(["PY", "VALIDATE", "RELOAD", "BLOCK", "ACTIVE", "ERROR", "STDOUT", "STDERR"], [[publicExposureCaddy.pythonExitCode, publicExposureCaddy.validateExitCode, publicExposureCaddy.reloadExitCode, publicExposureCaddy.afterBlockPresent, publicExposureCaddy.active, short(publicExposureCaddy.errorPreview), short(record(publicExposureCaddy.result).stdoutPreview), short(record(publicExposureCaddy.result).stderrPreview)]]),
: table(["PY", "VALIDATE", "RELOAD", "PROBE", "HTTP", "BLOCK", "ACTIVE", "ERROR", "STDOUT", "STDERR"], [[publicExposureCaddy.pythonExitCode, publicExposureCaddy.validateExitCode, publicExposureCaddy.reloadExitCode, publicExposureCaddy.routeProbeExitCode, publicExposureCaddy.routeProbeHttpStatus, publicExposureCaddy.afterBlockPresent, publicExposureCaddy.active, short(publicExposureCaddy.errorPreview), short(record(publicExposureCaddy.result).stdoutPreview), short(record(publicExposureCaddy.result).stderrPreview)]]),
"",
Object.keys(argoApply).length === 0 ? "ARGO_APPLY\n-" : table(["OK", "EXIT", "PREVIEW"], [[argoApply.ok, record(argoApply.result).exitCode, record(argoApply.result).stdoutPreview]]),
"",