fix12: разделение env-артефактов и стабилизация smoke
This commit is contained in:
@@ -12,10 +12,6 @@ func initHysteria2MachineAuthRouter(hysteria2Api *gin.RouterGroup) {
|
||||
}
|
||||
}
|
||||
|
||||
func initHysteria2SubscribeRouter(hysteria2Api *gin.RouterGroup) {
|
||||
hysteria2Api.GET("/:conPass", controller.Hysteria2Subscribe)
|
||||
}
|
||||
|
||||
func initHysteria2Router(hysteria2Api *gin.RouterGroup) {
|
||||
hysteria2 := hysteria2Api.Group("/hysteria2")
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
13. `trafficStats` API: корректный secret принимает запрос, неверный secret отклоняется
|
||||
14. TLS mode в `config.yaml` соответствует runtime env (`acme|file|self_signed_dev`)
|
||||
15. при `HY2XS_TLS_MODE=acme` в `config.yaml` выставлен `acme.type` из `HY2XS_ACME_TYPE`
|
||||
16. subscribe/node URL в API/QR формируются по `HY2XS_PUBLIC_HOST` + `HY2XS_PUBLIC_PORT`
|
||||
16. direct `hysteria2://` node URL в API/QR формируется по `HY2XS_PUBLIC_HOST` + `HY2XS_PUBLIC_PORT`; subscription delivery endpoint отключён в baseline и не входит в acceptance
|
||||
17. `nft -c -f /etc/nftables.conf` проходит после apply
|
||||
18. пароль admin и `con_pass` не перезаписываются при рестарте `hy2xs-admin`
|
||||
19. остановка/рестарт UI не останавливает `hysteria-server`
|
||||
|
||||
@@ -12,7 +12,7 @@ import { installHysteria } from "../steps/hysteria";
|
||||
import { generateConfig } from "../steps/config";
|
||||
import { deploySystemd } from "../steps/systemd";
|
||||
import { applyFirewall } from "../steps/firewall";
|
||||
import { writePostInstallEnv } from "../steps/env";
|
||||
import { writeBootstrapAdminSecret, writePostInstallEnv } from "../steps/env";
|
||||
import { smoke } from "../steps/smoke";
|
||||
|
||||
export async function install(options: InstallOptions): Promise<void> {
|
||||
@@ -64,6 +64,8 @@ export async function install(options: InstallOptions): Promise<void> {
|
||||
await applyFirewall(context);
|
||||
step("post-install env");
|
||||
await writePostInstallEnv(context);
|
||||
step("bootstrap admin secret");
|
||||
await writeBootstrapAdminSecret(context);
|
||||
step("smoke checks");
|
||||
await smoke(context);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ export async function writePostInstallEnv(context: RuntimeContext): Promise<void
|
||||
await writeText("/etc/hysteria/post-install.env", rendered, 0o600);
|
||||
await runVisible`chown root:root /etc/hysteria/post-install.env`;
|
||||
await runVisible`chmod 0600 /etc/hysteria/post-install.env`;
|
||||
}
|
||||
|
||||
export async function writeBootstrapAdminSecret(context: RuntimeContext): Promise<void> {
|
||||
await writeText(
|
||||
context.config.bootstrapAdminSecretPath,
|
||||
[
|
||||
|
||||
@@ -3,24 +3,31 @@ import { info } from "../lib/log";
|
||||
import { runHidden, runSecret, runVisible } from "../lib/process";
|
||||
|
||||
async function retry<T>(
|
||||
label: string,
|
||||
attempts: number,
|
||||
delayMs: number,
|
||||
action: () => Promise<T>,
|
||||
validate: (value: T) => boolean,
|
||||
errorFactory: (value: T) => Error,
|
||||
errorFactory: (value: T | undefined, error: unknown) => Error,
|
||||
): Promise<T> {
|
||||
let lastValue: T | undefined;
|
||||
let lastError: unknown;
|
||||
for (let i = 0; i < attempts; i += 1) {
|
||||
try {
|
||||
const value = await action();
|
||||
lastValue = value;
|
||||
if (validate(value)) {
|
||||
return value;
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
if (i < attempts - 1) {
|
||||
info(`${label}: retry ${i + 1}/${attempts}`);
|
||||
await runHidden`sleep ${Math.max(1, Math.ceil(delayMs / 1000))}`;
|
||||
}
|
||||
}
|
||||
throw errorFactory(lastValue as T);
|
||||
throw errorFactory(lastValue, lastError);
|
||||
}
|
||||
|
||||
export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
@@ -62,11 +69,12 @@ 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 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`,
|
||||
(response) => /"ok"\s*:\s*false/.test(response),
|
||||
(response) => new Error(`unexpected auth response for invalid credentials: ${response}`),
|
||||
(response, error) => new Error(`unexpected auth response for invalid credentials: ${response ?? String(error)}`),
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
@@ -76,25 +84,29 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mode === "install") {
|
||||
const adminConPass = (await runSecret`grep '^ADMIN_CON_PASS=' ${context.config.bootstrapAdminSecretPath} | head -n1 | cut -d= -f2-`).trim();
|
||||
if (!adminConPass) {
|
||||
throw new Error("admin connection password is empty in bootstrap secret file");
|
||||
}
|
||||
|
||||
const validAuthResponse = await retry(
|
||||
await retry(
|
||||
"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`,
|
||||
(response) => /"ok"\s*:\s*true/.test(response),
|
||||
() => new Error("unexpected auth response for valid credentials"),
|
||||
(response, error) => new Error(`unexpected auth response for valid credentials: ${response ?? String(error)}`),
|
||||
);
|
||||
}
|
||||
|
||||
await retry(
|
||||
"trafficStats valid secret",
|
||||
10,
|
||||
1000,
|
||||
async () => runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: ${context.config.hysteriaTrafficStatsSecret}' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online`,
|
||||
(code) => /^2\d\d$/.test(code.trim()),
|
||||
(code) => new Error(`unexpected trafficStats status for valid secret: ${code}`),
|
||||
(code, error) => new Error(`unexpected trafficStats status for valid secret: ${code ?? String(error)}`),
|
||||
);
|
||||
const deniedCode = await runSecret`curl -sS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online`;
|
||||
if (!/(401|403)/.test(deniedCode)) {
|
||||
|
||||
Reference in New Issue
Block a user