From 7d3b0b2cf87ee1949e29f5cca2f5409a2182f6e4 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 26 Jun 2026 10:49:40 +0000 Subject: [PATCH] fix: support shared egress proxy source refs --- scripts/src/hwlab-node/entry.ts | 5 ++++- scripts/src/hwlab-node/web-probe.ts | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/scripts/src/hwlab-node/entry.ts b/scripts/src/hwlab-node/entry.ts index 3a43ecb9..7564bc66 100644 --- a/scripts/src/hwlab-node/entry.ts +++ b/scripts/src/hwlab-node/entry.ts @@ -374,9 +374,12 @@ export type NodeRuntimeGitMirrorEgressProxySpec = namespace: string; serviceName: string; port: number; + sourceConfigRef: string | null; sourceRef: string; sourceKey: string; - sourceType: "subscription-url"; + sourceType: "subscription-url" | "master-shadowsocks"; + preferredOutbound: "vless-reality" | "hysteria2" | null; + sourceFingerprint: string | null; noProxy: string[]; }; diff --git a/scripts/src/hwlab-node/web-probe.ts b/scripts/src/hwlab-node/web-probe.ts index 0a644649..2e163b46 100644 --- a/scripts/src/hwlab-node/web-probe.ts +++ b/scripts/src/hwlab-node/web-probe.ts @@ -38,6 +38,7 @@ import { assertLane, assertNodeId, keyValueLinesFromText, numericField, optionVa import { discoverWebObserveIndexEntry, readWebObserveIndexEntry } from "./web-observe-render"; import { assertKnownOptions, nodeWebProbeAutoCommandTimeoutSeconds, nodeWebProbeDefaultUrl, normalizeNodeWebProbeObserveArgs, parseNodeWebProbeObserveOptions, parseNodeWebProbeSentinelOptions, parseWebProbeBrowserProxyMode } from "./web-probe-observe"; import { hwlabRuntimeActiveExternalPostgres } from "../hwlab-node-lanes"; +import { resolveEgressProxySourceRef } from "../egress-proxy-sources"; export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record { const gitSshProxy = httpProxyEndpoint(spec.networkProfile.proxy.http); @@ -1200,8 +1201,22 @@ export function nodeRuntimeGitMirrorEgressProxySpec(raw: Record if (mode !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be k8s-service-cluster-ip`); const port = Number(raw.port); if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`${path}.port must be a TCP port`); - const sourceType = stringValue(raw.sourceType, `${path}.sourceType`); - if (sourceType !== "subscription-url") throw new Error(`${path}.sourceType must be subscription-url`); + const sourceConfigRef = optionalStringValue(raw.sourceConfigRef, `${path}.sourceConfigRef`) ?? null; + const source = sourceConfigRef === null ? null : resolveEgressProxySourceRef(sourceConfigRef, `${HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH}.${path}.sourceConfigRef`); + const sourceType = source?.sourceType ?? stringValue(raw.sourceType, `${path}.sourceType`); + if (sourceType !== "subscription-url" && sourceType !== "master-shadowsocks") throw new Error(`${path}.sourceType must be subscription-url or master-shadowsocks`); + if (raw.sourceType !== undefined && raw.sourceType !== sourceType) throw new Error(`${path}.sourceType must match sourceConfigRef value ${sourceType}`); + const sourceRef = source?.sourceRef ?? stringValue(raw.sourceRef, `${path}.sourceRef`); + const sourceKey = source?.sourceKey ?? stringValue(raw.sourceKey, `${path}.sourceKey`); + if (source !== null) { + if (raw.sourceRef !== undefined && raw.sourceRef !== source.sourceRef) throw new Error(`${path}.sourceRef must match sourceConfigRef value ${source.sourceRef}`); + if (raw.sourceKey !== undefined && raw.sourceKey !== source.sourceKey) throw new Error(`${path}.sourceKey must match sourceConfigRef value ${source.sourceKey}`); + } + const preferredOutbound = source?.preferredOutbound ?? (raw.preferredOutbound === undefined ? null : stringValue(raw.preferredOutbound, `${path}.preferredOutbound`)); + if (preferredOutbound !== null && preferredOutbound !== "vless-reality" && preferredOutbound !== "hysteria2") throw new Error(`${path}.preferredOutbound must be vless-reality or hysteria2`); + if (source !== null && source.preferredOutbound !== null && raw.preferredOutbound !== undefined && raw.preferredOutbound !== source.preferredOutbound) { + throw new Error(`${path}.preferredOutbound must match sourceConfigRef value ${source.preferredOutbound}`); + } const noProxyRaw = raw.noProxy; if (!Array.isArray(noProxyRaw)) throw new Error(`${path}.noProxy must be an array`); const noProxy = noProxyRaw.map((item, index) => stringValue(item, `${path}.noProxy[${index}]`)); @@ -1211,9 +1226,12 @@ export function nodeRuntimeGitMirrorEgressProxySpec(raw: Record namespace: stringValue(raw.namespace, `${path}.namespace`), serviceName: stringValue(raw.serviceName, `${path}.serviceName`), port, - sourceRef: stringValue(raw.sourceRef, `${path}.sourceRef`), - sourceKey: stringValue(raw.sourceKey, `${path}.sourceKey`), + sourceConfigRef, + sourceRef, + sourceKey, sourceType, + preferredOutbound, + sourceFingerprint: source?.fingerprint ?? null, noProxy, }; }