feat: add hwlab opencode public proxy config

This commit is contained in:
Codex
2026-06-29 16:28:59 +00:00
parent 9a84e2d258
commit 58eec6ec0d
4 changed files with 118 additions and 23 deletions
+46
View File
@@ -282,6 +282,13 @@ export interface HwlabRuntimePublicExposureFrpcProxySpec {
readonly remotePort: number;
}
export interface HwlabRuntimePublicExposureFrpcExtraProxySpec extends HwlabRuntimePublicExposureFrpcProxySpec {
readonly id: string;
readonly hostname?: string;
readonly publicBaseUrl?: string;
readonly cloudWebEnvName?: string;
}
export interface HwlabRuntimePublicExposureSpec {
readonly enabled: boolean;
readonly mode: "pk01-caddy-frp";
@@ -303,6 +310,7 @@ export interface HwlabRuntimePublicExposureSpec {
readonly responseHeaderTimeoutSeconds: number;
readonly webProxy: HwlabRuntimePublicExposureFrpcProxySpec;
readonly apiProxy: HwlabRuntimePublicExposureFrpcProxySpec;
readonly extraProxies: readonly HwlabRuntimePublicExposureFrpcExtraProxySpec[];
}
export interface HwlabRuntimeBootstrapAdminSpec {
@@ -1207,6 +1215,32 @@ function publicExposureProxyConfig(value: unknown, path: string): HwlabRuntimePu
};
}
function publicExposureExtraProxyConfig(value: unknown, path: string): HwlabRuntimePublicExposureFrpcExtraProxySpec {
const raw = asRecord(value, path);
const publicBaseUrl = optionalStringField(raw, "publicBaseUrl", path)?.replace(/\/+$/u, "");
const hostname = optionalStringField(raw, "hostname", path);
if (publicBaseUrl !== undefined) {
validatePublicExposureOrigin(publicBaseUrl, `${path}.publicBaseUrl`);
if (hostname !== undefined && new URL(publicBaseUrl).hostname !== hostname) throw new Error(`${path}.publicBaseUrl hostname must match ${path}.hostname`);
}
const cloudWebEnvName = optionalStringField(raw, "cloudWebEnvName", path);
if (cloudWebEnvName !== undefined && !/^HWLAB_CLOUD_WEB_[A-Z0-9_]+$/u.test(cloudWebEnvName)) throw new Error(`${path}.cloudWebEnvName must be a HWLAB_CLOUD_WEB_* env name`);
if (cloudWebEnvName !== undefined && publicBaseUrl === undefined) throw new Error(`${path}.cloudWebEnvName requires ${path}.publicBaseUrl`);
return {
id: stringField(raw, "id", path),
...publicExposureProxyConfig(value, path),
...(hostname === undefined ? {} : { hostname }),
...(publicBaseUrl === undefined ? {} : { publicBaseUrl }),
...(cloudWebEnvName === undefined ? {} : { cloudWebEnvName }),
};
}
function publicExposureExtraProxiesConfig(value: unknown, path: string): HwlabRuntimePublicExposureFrpcExtraProxySpec[] {
if (value === undefined) return [];
if (!Array.isArray(value)) throw new Error(`${path} must be an array`);
return value.map((item, index) => publicExposureExtraProxyConfig(item, `${path}[${index}]`));
}
function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicExposureSpec | null {
if (value === undefined) return null;
const raw = asRecord(value, path);
@@ -1237,9 +1271,21 @@ function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicE
responseHeaderTimeoutSeconds: numberField(caddy, "responseHeaderTimeoutSeconds", `${path}.caddy`),
webProxy: publicExposureProxyConfig(frpc.webProxy, `${path}.frpc.webProxy`),
apiProxy: publicExposureProxyConfig(frpc.apiProxy, `${path}.frpc.apiProxy`),
extraProxies: publicExposureExtraProxiesConfig(frpc.extraProxies, `${path}.frpc.extraProxies`),
};
}
function validatePublicExposureOrigin(publicBaseUrl: string, path: string): void {
try {
const parsed = new URL(publicBaseUrl);
if (parsed.protocol !== "https:") throw new Error("must use https");
if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) throw new Error("must point to the public origin root");
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`${path} must be an https origin root URL: ${message}`);
}
}
function observabilityConfig(value: unknown, path: string): HwlabRuntimeObservabilitySpec {
const raw = asRecord(value, path);
const recordingRules = observabilityRecordingRulesConfig(raw.recordingRules, `${path}.recordingRules`);