Полный продовый фикс HY2XS/Hysteria2: auth DTO, валидация bind, smoke, URI, preflight и env-контракт
This commit is contained in:
@@ -127,6 +127,22 @@ function normalizeAcmeType(value: string): "http" | "tls" | "dns" {
|
||||
throw new Error(`invalid HY2XS_ACME_TYPE: ${value}`);
|
||||
}
|
||||
|
||||
function normalizeFixedHysteriaAuthMode(value: string | undefined): "http" {
|
||||
const mode = value || "http";
|
||||
if (mode !== "http") {
|
||||
throw new Error("HY2XS_HYSTERIA_AUTH_MODE is fixed in HY2XS production profile: http");
|
||||
}
|
||||
return "http";
|
||||
}
|
||||
|
||||
function normalizeFixedHysteriaObfsType(value: string | undefined): "salamander" {
|
||||
const obfsType = value || "salamander";
|
||||
if (obfsType !== "salamander") {
|
||||
throw new Error("HY2XS_HYSTERIA_OBFS_TYPE is fixed in HY2XS production profile: salamander");
|
||||
}
|
||||
return "salamander";
|
||||
}
|
||||
|
||||
function normalizeSafeAbsolutePath(name: string, value: string, options?: { disallowTmp?: boolean }): string {
|
||||
const v = value.trim();
|
||||
if (!v.startsWith("/")) {
|
||||
@@ -157,6 +173,8 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
const tlsMode = normalizeTlsMode(env.HY2XS_TLS_MODE || "acme");
|
||||
const acmeType = normalizeAcmeType(env.HY2XS_ACME_TYPE || "http");
|
||||
const firewallMode = normalizeFirewallMode(env);
|
||||
const hysteriaAuthMode = normalizeFixedHysteriaAuthMode(env.HY2XS_HYSTERIA_AUTH_MODE);
|
||||
const hysteriaObfsType = normalizeFixedHysteriaObfsType(env.HY2XS_HYSTERIA_OBFS_TYPE);
|
||||
|
||||
const config: RuntimeConfig = {
|
||||
domain: env.HY2XS_DOMAIN || "",
|
||||
@@ -181,14 +199,14 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
tlsKeyPath: normalizeSafeAbsolutePath("HY2XS_TLS_KEY_PATH", env.HY2XS_TLS_KEY_PATH || "/etc/hysteria/server.key"),
|
||||
hysteriaBindHost: normalizeIpv4Host("HY2XS_HYSTERIA_BIND_HOST", env.HY2XS_HYSTERIA_BIND_HOST || "0.0.0.0"),
|
||||
hysteriaPort,
|
||||
hysteriaAuthMode: "http",
|
||||
hysteriaAuthMode,
|
||||
hysteriaTrafficStatsHost: normalizeIpv4Host(
|
||||
"HY2XS_HYSTERIA_TRAFFIC_STATS_HOST",
|
||||
env.HY2XS_HYSTERIA_TRAFFIC_STATS_HOST || "127.0.0.1"
|
||||
),
|
||||
hysteriaTrafficStatsPort: trafficStatsPort,
|
||||
hysteriaTrafficStatsSecret: valueOrGenerate(env.HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET),
|
||||
hysteriaObfsType: "salamander",
|
||||
hysteriaObfsType,
|
||||
hysteriaObfsPassword: requireValue("HY2XS_HYSTERIA_OBFS_PASSWORD", valueOrGenerate(env.HY2XS_HYSTERIA_OBFS_PASSWORD)),
|
||||
hysteriaBandwidthUp: env.HY2XS_HYSTERIA_BANDWIDTH_UP || "50 mbps",
|
||||
hysteriaBandwidthDown: env.HY2XS_HYSTERIA_BANDWIDTH_DOWN || "50 mbps",
|
||||
|
||||
@@ -20,6 +20,9 @@ export async function writePostInstallEnv(context: RuntimeContext): Promise<void
|
||||
HYSTERIA_BIND_HOST: context.config.hysteriaBindHost,
|
||||
HYSTERIA_PORT: context.config.hysteriaPort,
|
||||
HYSTERIA_OBFS_PASSWORD: context.config.hysteriaObfsPassword,
|
||||
BANDWIDTH_UP: context.config.hysteriaBandwidthUp,
|
||||
BANDWIDTH_DOWN: context.config.hysteriaBandwidthDown,
|
||||
IGNORE_CLIENT_BANDWIDTH: context.config.hysteriaIgnoreClientBandwidth ? "true" : "false",
|
||||
HYSTERIA_API_HOST: context.config.hysteriaTrafficStatsHost,
|
||||
HYSTERIA_API_PORT: context.config.hysteriaTrafficStatsPort,
|
||||
UI_BIND_HOST: context.config.uiBindHost,
|
||||
|
||||
@@ -126,7 +126,7 @@ export async function preflight(context: RuntimeContext): Promise<void> {
|
||||
const aaaa = await run`getent ahostsv6 ${context.config.domain}`;
|
||||
if (aaaa.trim()) {
|
||||
fail(
|
||||
`domain ${context.config.domain} has AAAA record while HY2XS is IPv4-only; remove AAAA or set HY2XS_ALLOW_AAAA_WITH_IPV4_ONLY=true`
|
||||
`domain ${context.config.domain} has AAAA record while HY2XS profile is IPv4-only; remove AAAA record before install`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -148,18 +148,23 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
"auth invalid credentials",
|
||||
5,
|
||||
1000,
|
||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
||||
(response) => /"ok"\s*:\s*false/.test(response),
|
||||
(response, error) => new Error(`unexpected auth response for invalid credentials: ${response ?? String(error)}`),
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
const response = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||
const response = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||
if (!/"ok"\s*:\s*false/.test(response)) {
|
||||
throw new Error(`unexpected auth response during rate-limit smoke: ${response}`);
|
||||
}
|
||||
}
|
||||
|
||||
const invalidTypeAuthCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||
if (invalidTypeAuthCode.trim() !== "400") {
|
||||
throw new Error(`unexpected auth status for tx as string: ${invalidTypeAuthCode}`);
|
||||
}
|
||||
|
||||
if (context.mode === "install") {
|
||||
const adminConPass = (await runSecret`grep '^ADMIN_CON_PASS=' ${context.config.bootstrapAdminSecretPath} | head -n1 | cut -d= -f2-`).trim();
|
||||
if (!adminConPass) {
|
||||
@@ -170,7 +175,7 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
"auth valid credentials",
|
||||
10,
|
||||
1000,
|
||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${adminConPass}","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
||||
async () => runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${adminConPass}","tx":0}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`,
|
||||
(response) => /"ok"\s*:\s*true/.test(response),
|
||||
(response, error) => new Error(`unexpected auth response for valid credentials: ${response ?? String(error)}`),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user