feat: deploy sub2api api2 on jd01
This commit is contained in:
@@ -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"])
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user