fix: preserve PK01 Caddy managed blocks

This commit is contained in:
Codex
2026-06-13 14:04:10 +00:00
parent 5574150c64
commit 153ffb4c47
6 changed files with 467 additions and 301 deletions
+2 -90
View File
@@ -1,6 +1,7 @@
import { createHash } from "node:crypto";
import { Buffer } from "node:buffer";
import type { UniDeskConfig } from "./config";
import { applyPk01CaddyManagedBlock } from "./pk01-caddy";
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
export interface PublicServiceExposure {
@@ -57,96 +58,7 @@ export async function applyPk01CaddyBlock(
exposure: PublicServiceExposure,
markers: { start: string; end: string },
): Promise<Record<string, unknown>> {
if (!exposure.enabled) return { ok: true, action: "not-enabled" };
const block = `${markers.start}
${exposure.dns.hostname} {
reverse_proxy 127.0.0.1:${exposure.frpc.remotePort} {
transport http {
response_header_timeout ${exposure.pk01.responseHeaderTimeoutSeconds}s
}
}
}
${markers.end}
`;
const blockB64 = Buffer.from(block, "utf8").toString("base64");
const script = `
set -u
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
block="$tmp/${serviceId}.caddy"
printf '%s' '${blockB64}' | base64 -d >"$block"
sudo python3 - ${shQuote(exposure.pk01.caddyConfigPath)} "$block" ${shQuote(markers.start)} ${shQuote(markers.end)} >"$tmp/update.out" 2>"$tmp/update.err" <<'PY'
import pathlib
import sys
config_path = pathlib.Path(sys.argv[1])
block_path = pathlib.Path(sys.argv[2])
start = sys.argv[3]
end = sys.argv[4]
text = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
block = block_path.read_text(encoding="utf-8").strip() + "\\n"
if start in text and end in text:
before = text.split(start, 1)[0].rstrip()
tail = text.split(end, 1)[1].lstrip()
next_text = before + "\\n\\n" + block + "\\n" + tail
action = "update"
else:
next_text = text.rstrip() + "\\n\\n" + block
action = "append"
tmp = config_path.with_suffix(config_path.suffix + ".unidesk-${serviceId}.tmp")
tmp.write_text(next_text, encoding="utf-8")
tmp.replace(config_path)
print(action)
PY
update_rc=$?
if [ "$update_rc" -eq 0 ]; then
sudo caddy fmt --overwrite ${shQuote(exposure.pk01.caddyConfigPath)} >"$tmp/fmt.out" 2>"$tmp/fmt.err"
fmt_rc=$?
else
: >"$tmp/fmt.out"; : >"$tmp/fmt.err"; fmt_rc=1
fi
if [ "$fmt_rc" -eq 0 ]; then
sudo caddy validate --config ${shQuote(exposure.pk01.caddyConfigPath)} >"$tmp/validate.out" 2>"$tmp/validate.err"
validate_rc=$?
else
: >"$tmp/validate.out"; : >"$tmp/validate.err"; validate_rc=1
fi
if [ "$validate_rc" -eq 0 ]; then
sudo systemctl reload ${shQuote(exposure.pk01.caddyServiceName)} >"$tmp/reload.out" 2>"$tmp/reload.err" || sudo systemctl restart ${shQuote(exposure.pk01.caddyServiceName)} >>"$tmp/reload.out" 2>>"$tmp/reload.err"
reload_rc=$?
else
: >"$tmp/reload.out"; : >"$tmp/reload.err"; reload_rc=1
fi
python3 - "$update_rc" "$fmt_rc" "$validate_rc" "$reload_rc" "$tmp/update.out" "$tmp/update.err" "$tmp/fmt.out" "$tmp/fmt.err" "$tmp/validate.out" "$tmp/validate.err" "$tmp/reload.out" "$tmp/reload.err" <<'PY'
import json, sys
rcs = [int(value) for value in sys.argv[1:5]]
def text(path):
try:
return open(path, encoding="utf-8", errors="replace").read()[-3000:]
except FileNotFoundError:
return ""
payload = {
"ok": all(rc == 0 for rc in rcs),
"serviceId": "${serviceId}",
"hostname": "${exposure.dns.hostname}",
"remotePort": ${exposure.frpc.remotePort},
"caddyConfigPath": "${exposure.pk01.caddyConfigPath}",
"service": "${exposure.pk01.caddyServiceName}",
"managedBlock": {"start": "${markers.start}", "end": "${markers.end}"},
"steps": {
"update": {"exitCode": rcs[0], "stdout": text(sys.argv[5]), "stderr": text(sys.argv[6])},
"fmt": {"exitCode": rcs[1], "stdout": text(sys.argv[7]), "stderr": text(sys.argv[8])},
"validate": {"exitCode": rcs[2], "stdout": text(sys.argv[9]), "stderr": text(sys.argv[10])},
"reload": {"exitCode": rcs[3], "stdout": text(sys.argv[11]), "stderr": text(sys.argv[12])},
},
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
sys.exit(0 if payload["ok"] else 1)
PY
`;
const result = await capture(config, exposure.pk01.route, ["script"], script);
const parsed = parseJsonOutput(result.stdout);
return parsed ?? { ok: false, capture: compactCapture(result, { full: true }) };
return await applyPk01CaddyManagedBlock(config, serviceId, exposure, markers);
}
export function prepareFrpcSecret(params: {