feat: deploy sub2api api2 on jd01

This commit is contained in:
Codex
2026-06-30 09:25:59 +00:00
parent b60cd70752
commit e0c70a2de7
5 changed files with 217 additions and 19 deletions
@@ -972,14 +972,30 @@ def generate_api_key():
alphabet = string.ascii_letters + string.digits
return "sk-unidesk-codex-" + "".join(secrets.choice(alphabet) for _ in range(48))
def ensure_api_key_secret(group_id):
def existing_sub2api_pool_api_key(token):
try:
existing = next((item for item in list_user_keys(token) if item.get("name") == POOL_API_KEY_NAME), None)
except Exception:
return None
if not isinstance(existing, dict):
return None
key = existing.get("key")
return key if isinstance(key, str) and key else None
def ensure_api_key_secret(group_id, token):
existing = None
try:
existing = decode_secret_value(POOL_API_KEY_SECRET_NAME, POOL_API_KEY_SECRET_KEY)
except Exception:
existing = None
api_key = existing if existing else generate_api_key()
secret_action = "kept-existing" if existing else "created"
reused = None if existing else existing_sub2api_pool_api_key(token)
api_key = existing or reused or generate_api_key()
if existing:
secret_action = "kept-existing"
elif reused:
secret_action = "reused-existing-sub2api-key"
else:
secret_action = "created"
manifest = {
"apiVersion": "v1",
"kind": "Secret",
@@ -2419,7 +2435,7 @@ def run_sync():
load_factor_status = account_load_factor_status(token)
ws_v2_status = account_ws_v2_status(token)
temp_unschedulable_status = account_temp_unschedulable_status(token)
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id)
api_key, secret_action, secret_apply_stdout = ensure_api_key_secret(group_id, token)
api_key_result = ensure_sub2api_api_key(token, api_key, group_id)
owner_balance = ensure_pool_owner_balance(token, api_key_result["userId"])
owner_concurrency = ensure_pool_owner_concurrency(token, api_key_result["userId"])
+1 -1
View File
@@ -290,7 +290,7 @@ export async function apply(config: UniDeskConfig, options: ApplyOptions): Promi
: applyScript(sub2api, yaml, target, secretMaterial, publicExposureSecretMaterial, egressProxySecretMaterial),
);
const parsed = parseJsonOutput(result.stdout);
const pk01Exposure = target.publicExposure?.enabled === true ? await applyPk01PublicExposure(config, target) : null;
const pk01Exposure = target.publicExposure === null ? null : await applyPk01PublicExposure(config, target);
return {
ok: result.exitCode === 0 && boolField(parsed, "ok", false) && (pk01Exposure === null || pk01Exposure.ok === true),
action: "platform-infra-sub2api-apply",
@@ -14,6 +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 { sub2apiCaddyManagedMarker } from "./entry";
import type { EgressProxySubscriptionCandidateSummary, EgressProxySubscriptionDiagnostics, Sub2ApiEgressProxyConfig, Sub2ApiPublicExposureConfig, Sub2ApiTargetConfig } from "./entry";
import { status } from "./actions";
import { publicExposureUpstream, renderPk01CaddyService, renderPk01Caddyfile } from "./apply-script";
@@ -217,7 +218,8 @@ export function redactSubscriptionUri(uri: string): string {
export async function applyPk01PublicExposure(config: UniDeskConfig, target: Sub2ApiTargetConfig): Promise<Record<string, unknown>> {
const exposure = target.publicExposure;
if (exposure === null || !exposure.enabled) return { ok: true, action: "not-enabled" };
if (exposure === null) return { ok: true, action: "not-configured" };
if (!exposure.enabled) return await removePk01PublicExposure(config, target, exposure);
const start = await startPk01PublicExposureJob(config, target, exposure);
if (!start.ok || typeof start.remoteJobId !== "string") {
return {
@@ -244,6 +246,139 @@ export async function applyPk01PublicExposure(config: UniDeskConfig, target: Sub
};
}
export async function removePk01PublicExposure(config: UniDeskConfig, target: Sub2ApiTargetConfig, exposure: Sub2ApiPublicExposureConfig): Promise<Record<string, unknown>> {
const marker = sub2apiCaddyManagedMarker(target);
const script = `
set -u
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
config_path=${shQuote(exposure.pk01.caddyConfigPath)}
service_name=${shQuote(exposure.pk01.caddyServiceName)}
marker=${shQuote(marker)}
hostname=${shQuote(exposure.dns.hostname)}
next="$tmp/Caddyfile.next"
update_out="$tmp/update.out"
update_err="$tmp/update.err"
fmt_out="$tmp/fmt.out"
fmt_err="$tmp/fmt.err"
validate_out="$tmp/validate.out"
validate_err="$tmp/validate.err"
install_out="$tmp/install.out"
install_err="$tmp/install.err"
reload_out="$tmp/reload.out"
reload_err="$tmp/reload.err"
if ! [ -f "$config_path" ]; then
python3 - "$config_path" "$marker" "$hostname" <<'PY'
import json
import sys
print(json.dumps({
"ok": True,
"action": "caddy-config-missing-noop",
"configPath": sys.argv[1],
"marker": sys.argv[2],
"hostname": sys.argv[3],
"valuesPrinted": False,
}, ensure_ascii=False, indent=2))
PY
exit 0
fi
sudo python3 - "$config_path" "$next" "$marker" >"$update_out" 2>"$update_err" <<'PY'
import pathlib
import re
import sys
config_path = pathlib.Path(sys.argv[1])
next_path = pathlib.Path(sys.argv[2])
marker = sys.argv[3]
text = config_path.read_text(encoding="utf-8")
pattern = re.compile(r"(?ms)^# BEGIN unidesk managed (?P<name>[^\\n]+)\\n(?P<body>.*?)\\n# END unidesk managed (?P=name)\\n*")
removed = False
def replace(match):
global removed
if match.group("name") != marker:
return match.group(0)
removed = True
return ""
next_text = pattern.sub(replace, text)
next_text = re.sub(r"\\n{3,}", "\\n\\n", next_text).rstrip() + "\\n"
next_path.write_text(next_text, encoding="utf-8")
print("removed" if removed else "absent")
PY
update_rc=$?
if [ "$update_rc" -eq 0 ]; then
sudo caddy fmt --overwrite "$next" >"$fmt_out" 2>"$fmt_err"
fmt_rc=$?
else
: >"$fmt_out"; : >"$fmt_err"; fmt_rc=1
fi
if [ "$fmt_rc" -eq 0 ]; then
sudo caddy validate --config "$next" >"$validate_out" 2>"$validate_err"
validate_rc=$?
else
: >"$validate_out"; : >"$validate_err"; validate_rc=1
fi
if [ "$validate_rc" -eq 0 ]; then
sudo install -m 0644 "$next" "$config_path" >"$install_out" 2>"$install_err"
install_rc=$?
else
: >"$install_out"; : >"$install_err"; install_rc=1
fi
if [ "$install_rc" -eq 0 ]; then
sudo systemctl reload "$service_name" >"$reload_out" 2>"$reload_err" || sudo systemctl restart "$service_name" >>"$reload_out" 2>>"$reload_err"
reload_rc=$?
else
: >"$reload_out"; : >"$reload_err"; reload_rc=1
fi
python3 - "$update_rc" "$fmt_rc" "$validate_rc" "$install_rc" "$reload_rc" "$config_path" "$marker" "$hostname" "$update_out" "$update_err" "$fmt_out" "$fmt_err" "$validate_out" "$validate_err" "$install_out" "$install_err" "$reload_out" "$reload_err" <<'PY'
import json
import sys
update_rc, fmt_rc, validate_rc, install_rc, reload_rc = [int(value) for value in sys.argv[1:6]]
config_path, marker, hostname = sys.argv[6:9]
paths = sys.argv[9:]
def text(path, limit=3000):
try:
return open(path, encoding="utf-8", errors="replace").read()[-limit:]
except FileNotFoundError:
return ""
update_stdout = text(paths[0], 1000).strip()
payload = {
"ok": update_rc == 0 and fmt_rc == 0 and validate_rc == 0 and install_rc == 0 and reload_rc == 0,
"action": "removed-managed-block" if update_stdout == "removed" else "managed-block-absent",
"target": "${target.id}",
"publicBaseUrl": "${exposure.publicBaseUrl}",
"hostname": hostname,
"configPath": config_path,
"managedBlock": {"marker": marker},
"steps": {
"update": {"exitCode": update_rc, "stdout": update_stdout, "stderr": text(paths[1])},
"fmt": {"exitCode": fmt_rc, "stdout": text(paths[2]), "stderr": text(paths[3])},
"validate": {"exitCode": validate_rc, "stdout": text(paths[4]), "stderr": text(paths[5])},
"install": {"exitCode": install_rc, "stdout": text(paths[6]), "stderr": text(paths[7])},
"reload": {"exitCode": reload_rc, "stdout": text(paths[8]), "stderr": text(paths[9])},
},
"valuesPrinted": False,
}
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, ["sh"], script);
const parsed = parseJsonOutput(result.stdout);
return {
ok: result.exitCode === 0 && boolField(parsed, "ok", false),
action: "platform-infra-sub2api-pk01-public-exposure",
route: exposure.pk01.route,
mode: "remove-disabled-managed-block",
summary: parsed ?? null,
capture: compactCapture(result, { full: result.exitCode !== 0 }),
};
}
export async function startPk01PublicExposureJob(config: UniDeskConfig, target: Sub2ApiTargetConfig, exposure: Sub2ApiPublicExposureConfig): Promise<Record<string, unknown>> {
const jobId = `sub2api-pk01-exposure-${new Date().toISOString().replace(/[^0-9A-Za-z]/gu, "")}-${randomBytes(3).toString("hex")}`;
const payload = pk01PublicExposureScript(target, exposure);