mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
105 lines
3.8 KiB
TypeScript
105 lines
3.8 KiB
TypeScript
import type { BindMode, DeploymentExposure, DeploymentMode } from "./constants.js";
|
|
|
|
export const LOOPBACK_BIND_HOST = "127.0.0.1";
|
|
export const ALL_INTERFACES_BIND_HOST = "0.0.0.0";
|
|
|
|
function normalizeHost(host: string | null | undefined): string | undefined {
|
|
const trimmed = host?.trim();
|
|
return trimmed ? trimmed : undefined;
|
|
}
|
|
|
|
export function isLoopbackHost(host: string | null | undefined): boolean {
|
|
const normalized = normalizeHost(host)?.toLowerCase();
|
|
return normalized === "127.0.0.1" || normalized === "localhost" || normalized === "::1";
|
|
}
|
|
|
|
export function isAllInterfacesHost(host: string | null | undefined): boolean {
|
|
const normalized = normalizeHost(host)?.toLowerCase();
|
|
return normalized === "0.0.0.0" || normalized === "::";
|
|
}
|
|
|
|
export function inferBindModeFromHost(
|
|
host: string | null | undefined,
|
|
opts?: { tailnetBindHost?: string | null | undefined },
|
|
): BindMode {
|
|
const normalized = normalizeHost(host);
|
|
const tailnetBindHost = normalizeHost(opts?.tailnetBindHost);
|
|
|
|
if (!normalized || isLoopbackHost(normalized)) return "loopback";
|
|
if (isAllInterfacesHost(normalized)) return "lan";
|
|
if (tailnetBindHost && normalized === tailnetBindHost) return "tailnet";
|
|
return "custom";
|
|
}
|
|
|
|
export function validateConfiguredBindMode(input: {
|
|
deploymentMode: DeploymentMode;
|
|
deploymentExposure: DeploymentExposure;
|
|
bind?: BindMode | null | undefined;
|
|
host?: string | null | undefined;
|
|
customBindHost?: string | null | undefined;
|
|
}): string[] {
|
|
const bind = input.bind ?? inferBindModeFromHost(input.host);
|
|
const customBindHost = normalizeHost(input.customBindHost);
|
|
const errors: string[] = [];
|
|
|
|
if (input.deploymentMode === "local_trusted" && bind !== "loopback") {
|
|
errors.push("local_trusted requires server.bind=loopback");
|
|
}
|
|
|
|
if (bind === "custom" && !customBindHost) {
|
|
const legacyHost = normalizeHost(input.host);
|
|
if (!legacyHost || isLoopbackHost(legacyHost) || isAllInterfacesHost(legacyHost)) {
|
|
errors.push("server.customBindHost is required when server.bind=custom");
|
|
}
|
|
}
|
|
|
|
if (input.deploymentMode === "authenticated" && input.deploymentExposure === "public" && bind === "tailnet") {
|
|
errors.push("server.bind=tailnet is only supported for authenticated/private deployments");
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
export function resolveRuntimeBind(input: {
|
|
bind?: BindMode | null | undefined;
|
|
host?: string | null | undefined;
|
|
customBindHost?: string | null | undefined;
|
|
tailnetBindHost?: string | null | undefined;
|
|
}): {
|
|
bind: BindMode;
|
|
host: string;
|
|
customBindHost?: string;
|
|
errors: string[];
|
|
} {
|
|
const bind = input.bind ?? inferBindModeFromHost(input.host, { tailnetBindHost: input.tailnetBindHost });
|
|
const legacyHost = normalizeHost(input.host);
|
|
const customBindHost =
|
|
normalizeHost(input.customBindHost) ??
|
|
(bind === "custom" && legacyHost && !isLoopbackHost(legacyHost) && !isAllInterfacesHost(legacyHost)
|
|
? legacyHost
|
|
: undefined);
|
|
|
|
switch (bind) {
|
|
case "loopback":
|
|
return { bind, host: LOOPBACK_BIND_HOST, customBindHost, errors: [] };
|
|
case "lan":
|
|
return { bind, host: ALL_INTERFACES_BIND_HOST, customBindHost, errors: [] };
|
|
case "custom":
|
|
return customBindHost
|
|
? { bind, host: customBindHost, customBindHost, errors: [] }
|
|
: { bind, host: legacyHost ?? LOOPBACK_BIND_HOST, errors: ["server.customBindHost is required when server.bind=custom"] };
|
|
case "tailnet": {
|
|
const tailnetBindHost = normalizeHost(input.tailnetBindHost);
|
|
return tailnetBindHost
|
|
? { bind, host: tailnetBindHost, customBindHost, errors: [] }
|
|
: {
|
|
bind,
|
|
host: legacyHost ?? LOOPBACK_BIND_HOST,
|
|
customBindHost,
|
|
errors: [
|
|
"server.bind=tailnet requires a detected Tailscale address or PAPERCLIP_TAILNET_BIND_HOST",
|
|
],
|
|
};
|
|
}
|
|
}
|
|
}
|