fix(sub2api): add JD01 account local proxy

This commit is contained in:
Codex
2026-06-30 10:38:03 +00:00
parent b1fda2a479
commit b073a1c52c
9 changed files with 395 additions and 29 deletions
+114 -18
View File
@@ -14,7 +14,7 @@ import { capture, compactCapture, parseJsonOutput, prepareFrpcSecret, shQuote }
import { yamlBooleanField, yamlFieldLabel, yamlIntegerField } from "../platform-infra-ops-library";
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
import type { EgressProxySecretMaterial, ExternalActiveSecretMaterial, PublicExposureSecretMaterial, Sub2ApiConfig, Sub2ApiPublicExposureConfig, Sub2ApiTargetConfig } from "./entry";
import type { AccountLocalProxySecretMaterial, EgressProxySecretMaterial, ExternalActiveSecretMaterial, PublicExposureSecretMaterial, Sub2ApiConfig, Sub2ApiPublicExposureConfig, Sub2ApiTargetConfig } from "./entry";
import { fieldManager, requiredSecretKeys, sub2apiCaddyManagedMarker } from "./entry";
import { managedResourceCleanupPlan } from "./manifest";
@@ -99,8 +99,30 @@ export function quoteEnv(value: string): string {
return `'${value.replaceAll("'", "'\"'\"'")}'`;
}
export function dryRunScript(yaml: string, target: Sub2ApiTargetConfig): string {
export function dryRunScript(
yaml: string,
target: Sub2ApiTargetConfig,
accountLocalProxySecretMaterial: AccountLocalProxySecretMaterial | null,
): string {
const encoded = Buffer.from(yaml, "utf8").toString("base64");
const accountLocalProxySecretName = accountLocalProxySecretMaterial?.secretName ?? "sub2api-account-local-proxy-config";
const accountLocalProxySecretKey = accountLocalProxySecretMaterial?.secretKey ?? "config.json";
const accountLocalProxySecretFile = accountLocalProxySecretMaterial === null
? ""
: `printf '%s' '${Buffer.from(accountLocalProxySecretMaterial.configJson, "utf8").toString("base64")}' | base64 -d >"$tmp/account-local-proxy-config.json"`;
const accountLocalProxySecretSummary = accountLocalProxySecretMaterial === null
? "None"
: `json.loads(${JSON.stringify(JSON.stringify({
sourceRef: accountLocalProxySecretMaterial.sourceRef,
sourcePath: accountLocalProxySecretMaterial.sourcePath,
secretName: accountLocalProxySecretMaterial.secretName,
secretKey: accountLocalProxySecretMaterial.secretKey,
fingerprint: accountLocalProxySecretMaterial.fingerprint,
sourceBytes: accountLocalProxySecretMaterial.sourceBytes,
selectedOutbound: accountLocalProxySecretMaterial.selectedOutbound,
proxyUrl: accountLocalProxySecretMaterial.proxyUrl,
valuesPrinted: false,
}))})`;
return `
set -u
tmp="$(mktemp -d)"
@@ -111,6 +133,11 @@ client_out="$tmp/client.out"
client_err="$tmp/client.err"
server_out="$tmp/server.out"
server_err="$tmp/server.err"
account_local_proxy_secret_yaml="$tmp/account-local-proxy-secret.yaml"
account_local_proxy_secret_out="$tmp/account-local-proxy-secret.out"
account_local_proxy_secret_err="$tmp/account-local-proxy-secret.err"
account_local_proxy_secret_action="not-enabled"
account_local_proxy_secret_rc=0
kubectl apply --dry-run=client -f "$manifest" >"$client_out" 2>"$client_err"
client_rc=$?
if kubectl get namespace ${target.namespace} >/dev/null 2>&1; then
@@ -123,20 +150,44 @@ else
printf '%s\\n' 'server dry-run skipped because namespace does not exist yet; first apply creates it before namespaced resources' >"$server_out"
: >"$server_err"
fi
python3 - "$client_rc" "$server_rc" "$namespace_exists" "$client_out" "$client_err" "$server_out" "$server_err" <<'PY'
if [ "${accountLocalProxySecretMaterial === null ? "false" : "true"}" = "true" ]; then
${accountLocalProxySecretFile}
kubectl -n ${target.namespace} create secret generic ${accountLocalProxySecretName} \\
--from-file=${accountLocalProxySecretKey}="$tmp/account-local-proxy-config.json" \\
--dry-run=client -o yaml >"$account_local_proxy_secret_yaml" 2>"$account_local_proxy_secret_err"
account_local_proxy_secret_rc=$?
account_local_proxy_secret_action="client-dry-run"
if [ "$account_local_proxy_secret_rc" -eq 0 ]; then
if [ "$namespace_exists" = "true" ]; then
kubectl apply --server-side --dry-run=server --field-manager=${fieldManager} -f "$account_local_proxy_secret_yaml" >"$account_local_proxy_secret_out" 2>>"$account_local_proxy_secret_err"
account_local_proxy_secret_rc=$?
account_local_proxy_secret_action="server-dry-run"
else
printf '%s\\n' 'server dry-run skipped because namespace does not exist yet; client dry-run succeeded' >"$account_local_proxy_secret_out"
fi
else
: >"$account_local_proxy_secret_out"
fi
else
: >"$account_local_proxy_secret_out"
: >"$account_local_proxy_secret_err"
fi
python3 - "$client_rc" "$server_rc" "$account_local_proxy_secret_rc" "$namespace_exists" "$account_local_proxy_secret_action" "$client_out" "$client_err" "$server_out" "$server_err" "$account_local_proxy_secret_out" "$account_local_proxy_secret_err" <<'PY'
import json
import sys
client_rc = int(sys.argv[1])
server_rc = int(sys.argv[2])
namespace_exists = sys.argv[3] == "true"
paths = sys.argv[4:]
account_local_proxy_secret_rc = int(sys.argv[3])
namespace_exists = sys.argv[4] == "true"
account_local_proxy_secret_action = sys.argv[5]
paths = sys.argv[6:]
def text(path):
try:
return open(path, encoding="utf-8").read()
except FileNotFoundError:
return ""
payload = {
"ok": client_rc == 0 and server_rc == 0,
"ok": client_rc == 0 and server_rc == 0 and account_local_proxy_secret_rc == 0,
"target": "${target.id}",
"namespace": "${target.namespace}",
"namespaceExistsBeforeDryRun": namespace_exists,
@@ -151,6 +202,13 @@ payload = {
"stdout": text(paths[2])[-4000:],
"stderr": text(paths[3])[-4000:],
},
"accountLocalProxy": ${accountLocalProxySecretSummary},
"accountLocalProxySecretDryRun": {
"exitCode": account_local_proxy_secret_rc,
"action": account_local_proxy_secret_action,
"stdout": text(paths[4])[-4000:],
"stderr": text(paths[5])[-4000:],
},
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
sys.exit(0 if payload["ok"] else 1)
@@ -165,6 +223,7 @@ export function applyScript(
secretMaterial: ExternalActiveSecretMaterial | null,
publicExposureSecretMaterial: PublicExposureSecretMaterial | null,
egressProxySecretMaterial: EgressProxySecretMaterial | null,
accountLocalProxySecretMaterial: AccountLocalProxySecretMaterial | null,
): string {
const encoded = Buffer.from(yaml, "utf8").toString("base64");
const cleanupPlan = managedResourceCleanupPlan(sub2api, target);
@@ -173,6 +232,8 @@ export function applyScript(
const publicExposureSecretKey = publicExposureSecretMaterial?.secretKey ?? "frpc.toml";
const egressProxySecretName = egressProxySecretMaterial?.secretName ?? sub2api.defaults.cleanup.egressProxy.secretName;
const egressProxySecretKey = egressProxySecretMaterial?.secretKey ?? "config.json";
const accountLocalProxySecretName = accountLocalProxySecretMaterial?.secretName ?? "sub2api-account-local-proxy-config";
const accountLocalProxySecretKey = accountLocalProxySecretMaterial?.secretKey ?? "config.json";
if (target.databaseMode === "external-active" && secretMaterial === null) throw new Error("external-active apply requires secret material");
const externalSecretFiles = secretMaterial === null
? ""
@@ -222,6 +283,22 @@ export function applyScript(
noProxy: egressProxySecretMaterial.noProxy,
valuesPrinted: false,
}))})`;
const accountLocalProxySecretFile = accountLocalProxySecretMaterial === null
? ""
: ` printf '%s' '${Buffer.from(accountLocalProxySecretMaterial.configJson, "utf8").toString("base64")}' | base64 -d >"$tmp/account-local-proxy-config.json"`;
const accountLocalProxySecretSummary = accountLocalProxySecretMaterial === null
? "None"
: `json.loads(${JSON.stringify(JSON.stringify({
sourceRef: accountLocalProxySecretMaterial.sourceRef,
sourcePath: accountLocalProxySecretMaterial.sourcePath,
secretName: accountLocalProxySecretMaterial.secretName,
secretKey: accountLocalProxySecretMaterial.secretKey,
fingerprint: accountLocalProxySecretMaterial.fingerprint,
sourceBytes: accountLocalProxySecretMaterial.sourceBytes,
selectedOutbound: accountLocalProxySecretMaterial.selectedOutbound,
proxyUrl: accountLocalProxySecretMaterial.proxyUrl,
valuesPrinted: false,
}))})`;
return `
set -u
tmp="$(mktemp -d)"
@@ -236,6 +313,8 @@ exposure_secret_out="$tmp/exposure-secret.out"
exposure_secret_err="$tmp/exposure-secret.err"
egress_secret_out="$tmp/egress-secret.out"
egress_secret_err="$tmp/egress-secret.err"
account_local_proxy_secret_out="$tmp/account-local-proxy-secret.out"
account_local_proxy_secret_err="$tmp/account-local-proxy-secret.err"
apply_out="$tmp/apply.out"
apply_err="$tmp/apply.err"
cleanup_out="$tmp/cleanup.out"
@@ -248,6 +327,8 @@ exposure_secret_action="not-enabled"
exposure_secret_rc=0
egress_secret_action="not-enabled"
egress_secret_rc=0
account_local_proxy_secret_action="not-enabled"
account_local_proxy_secret_rc=0
if [ "$ns_rc" -eq 0 ]; then
if [ "${target.databaseMode}" = "external-pending" ]; then
secret_action="external-pending-not-managed"
@@ -307,14 +388,25 @@ ${egressProxySecretFile}
: >"$egress_secret_out"
: >"$egress_secret_err"
fi
if [ "${accountLocalProxySecretMaterial === null ? "false" : "true"}" = "true" ]; then
${accountLocalProxySecretFile}
kubectl -n ${target.namespace} create secret generic ${accountLocalProxySecretName} \\
--from-file=${accountLocalProxySecretKey}="$tmp/account-local-proxy-config.json" \\
--dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f - >"$account_local_proxy_secret_out" 2>"$account_local_proxy_secret_err"
account_local_proxy_secret_rc=$?
account_local_proxy_secret_action="synced"
else
: >"$account_local_proxy_secret_out"
: >"$account_local_proxy_secret_err"
fi
fi
apply_rc=1
if [ "$ns_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ] && [ "$exposure_secret_rc" -eq 0 ] && [ "$egress_secret_rc" -eq 0 ]; then
if [ "$ns_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ] && [ "$exposure_secret_rc" -eq 0 ] && [ "$egress_secret_rc" -eq 0 ] && [ "$account_local_proxy_secret_rc" -eq 0 ]; then
kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >"$apply_out" 2>"$apply_err"
apply_rc=$?
else
: >"$apply_out"
printf '%s\\n' 'skipped because namespace, app secret, public exposure secret, or egress proxy secret step failed' >"$apply_err"
printf '%s\\n' 'skipped because namespace, app secret, public exposure secret, egress proxy secret, or account-local proxy secret step failed' >"$apply_err"
fi
cleanup_rc=0
if [ "$apply_rc" -eq 0 ]; then
@@ -414,19 +506,21 @@ else
printf '%s\\n' 'skipped because apply failed' >"$cleanup_err"
cleanup_rc=1
fi
python3 - "$ns_rc" "$secret_rc" "$exposure_secret_rc" "$egress_secret_rc" "$apply_rc" "$cleanup_rc" "$secret_action" "$exposure_secret_action" "$egress_secret_action" "$ns_out" "$ns_err" "$secret_out" "$secret_err" "$exposure_secret_out" "$exposure_secret_err" "$egress_secret_out" "$egress_secret_err" "$apply_out" "$apply_err" "$cleanup_out" "$cleanup_err" <<'PY'
python3 - "$ns_rc" "$secret_rc" "$exposure_secret_rc" "$egress_secret_rc" "$account_local_proxy_secret_rc" "$apply_rc" "$cleanup_rc" "$secret_action" "$exposure_secret_action" "$egress_secret_action" "$account_local_proxy_secret_action" "$ns_out" "$ns_err" "$secret_out" "$secret_err" "$exposure_secret_out" "$exposure_secret_err" "$egress_secret_out" "$egress_secret_err" "$account_local_proxy_secret_out" "$account_local_proxy_secret_err" "$apply_out" "$apply_err" "$cleanup_out" "$cleanup_err" <<'PY'
import json
import sys
ns_rc = int(sys.argv[1])
secret_rc = int(sys.argv[2])
exposure_secret_rc = int(sys.argv[3])
egress_secret_rc = int(sys.argv[4])
apply_rc = int(sys.argv[5])
cleanup_rc = int(sys.argv[6])
secret_action = sys.argv[7]
exposure_secret_action = sys.argv[8]
egress_secret_action = sys.argv[9]
paths = sys.argv[10:]
account_local_proxy_secret_rc = int(sys.argv[5])
apply_rc = int(sys.argv[6])
cleanup_rc = int(sys.argv[7])
secret_action = sys.argv[8]
exposure_secret_action = sys.argv[9]
egress_secret_action = sys.argv[10]
account_local_proxy_secret_action = sys.argv[11]
paths = sys.argv[12:]
def text(path):
try:
return open(path, encoding="utf-8").read()
@@ -438,7 +532,7 @@ def parsed(path):
except Exception:
return None
payload = {
"ok": ns_rc == 0 and secret_rc == 0 and exposure_secret_rc == 0 and egress_secret_rc == 0 and apply_rc == 0 and cleanup_rc == 0,
"ok": ns_rc == 0 and secret_rc == 0 and exposure_secret_rc == 0 and egress_secret_rc == 0 and account_local_proxy_secret_rc == 0 and apply_rc == 0 and cleanup_rc == 0,
"target": "${target.id}",
"namespace": "${target.namespace}",
"databaseMode": "${target.databaseMode}",
@@ -454,13 +548,15 @@ payload = {
},
"publicExposure": ${exposureSecretSummary},
"egressProxy": ${egressProxySecretSummary},
"accountLocalProxy": ${accountLocalProxySecretSummary},
"steps": {
"namespace": {"exitCode": ns_rc, "stdout": text(paths[0])[-4000:], "stderr": text(paths[1])[-4000:]},
"secret": {"exitCode": secret_rc, "stdout": text(paths[2])[-4000:], "stderr": text(paths[3])[-4000:]},
"publicExposureSecret": {"exitCode": exposure_secret_rc, "action": exposure_secret_action, "stdout": text(paths[4])[-4000:], "stderr": text(paths[5])[-4000:]},
"egressProxySecret": {"exitCode": egress_secret_rc, "action": egress_secret_action, "stdout": text(paths[6])[-4000:], "stderr": text(paths[7])[-4000:]},
"apply": {"exitCode": apply_rc, "stdout": text(paths[8])[-8000:], "stderr": text(paths[9])[-4000:]},
"cleanup": {"exitCode": cleanup_rc, "summary": parsed(paths[10]), "stdout": text(paths[10])[-8000:], "stderr": text(paths[11])[-4000:]},
"accountLocalProxySecret": {"exitCode": account_local_proxy_secret_rc, "action": account_local_proxy_secret_action, "stdout": text(paths[8])[-4000:], "stderr": text(paths[9])[-4000:]},
"apply": {"exitCode": apply_rc, "stdout": text(paths[10])[-8000:], "stderr": text(paths[11])[-4000:]},
"cleanup": {"exitCode": cleanup_rc, "summary": parsed(paths[12]), "stdout": text(paths[12])[-8000:], "stderr": text(paths[13])[-4000:]},
},
}
print(json.dumps(payload, ensure_ascii=False, indent=2))