Полный production-фикс fix30/fix30.1: auth, smoke, bootstrap, preflight, rollback и валидация PATCH

This commit is contained in:
2026-05-09 02:46:48 +05:00
parent 1139c428b6
commit 78da898ba5
13 changed files with 177 additions and 20 deletions
+14 -2
View File
@@ -5,6 +5,10 @@ import { fail, info } from "../lib/log";
import { run } from "../lib/process";
import { assertPlatform } from "../platform/assert";
type PreflightOptions = {
requireCapabilities?: boolean;
};
function isNoDnsRecords(error: unknown): boolean {
return (
typeof error === "object" &&
@@ -41,8 +45,13 @@ async function isUnitActive(unit: string): Promise<boolean> {
}
}
export async function preflight(context: RuntimeContext): Promise<void> {
export async function preflight(context: RuntimeContext, options?: PreflightOptions): Promise<void> {
const isReconfigure = context.mode === "reconfigure";
const requireCapabilities = options?.requireCapabilities ?? true;
const needsFirewallCapabilities = !context.options.skipFirewall &&
context.config.firewallMode !== "off" &&
context.config.firewallMode !== "external";
if (process.getuid?.() !== 0) {
fail("installer must run as root");
@@ -51,7 +60,10 @@ export async function preflight(context: RuntimeContext): Promise<void> {
await assertPlatform({
distro: "debian",
supportedVersions: [13],
architectures: ["amd64"]
architectures: ["amd64"],
requireSystemdRun: requireCapabilities,
requireNftables: requireCapabilities && needsFirewallCapabilities,
requireOpenSsl3: requireCapabilities
});
if (!(await fileExists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
+8 -4
View File
@@ -144,23 +144,27 @@ export async function smoke(context: RuntimeContext): Promise<void> {
}
await runVisible`! ss -H -ltn | grep -q '\[::\]:${context.config.uiPort} '`;
await runVisible`! ss -H -lun | grep -q '\[::\]:${context.config.hysteriaPort} '`;
const missingTokenAuthCode = 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 (missingTokenAuthCode.trim() !== "403") {
throw new Error(`unexpected auth status without machine token: ${missingTokenAuthCode}`);
}
const invalidAuthResponse = await retry(
"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?access_token=${context.config.hysteriaTrafficStatsSecret}'`,
(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?access_token=${context.config.hysteriaTrafficStatsSecret}'`;
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`;
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?access_token=${context.config.hysteriaTrafficStatsSecret}'`;
if (invalidTypeAuthCode.trim() !== "400") {
throw new Error(`unexpected auth status for tx as string: ${invalidTypeAuthCode}`);
}
@@ -175,7 +179,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?access_token=${context.config.hysteriaTrafficStatsSecret}'`,
(response) => /"ok"\s*:\s*true/.test(response),
(response, error) => new Error(`unexpected auth response for valid credentials: ${response ?? String(error)}`),
);