Довёл fix20: firewall-mode, staged state, diagnostics, readiness и build-gate
This commit is contained in:
@@ -10,7 +10,7 @@ export async function writePostInstallEnv(context: RuntimeContext): Promise<void
|
||||
PUBLIC_HOST: context.config.publicHost,
|
||||
PUBLIC_PORT: context.config.publicPort,
|
||||
SSH_PORT: context.config.sshPort,
|
||||
FIREWALL_ENABLED: context.config.firewallEnabled ? "true" : "false",
|
||||
FIREWALL_MODE: context.config.firewallMode,
|
||||
FIREWALL_STAGED_APPLY: context.config.firewallStagedApply ? "true" : "false",
|
||||
HYSTERIA_VERSION: context.hysteriaVersion,
|
||||
TLS_MODE: context.config.tlsMode,
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
import type { RuntimeContext } from "../types/context";
|
||||
import { exists, readText, renderTemplate, writeText } from "../lib/fs";
|
||||
import { fileExists, readText, renderTemplate, writeText } from "../lib/fs";
|
||||
import { fail, info } from "../lib/log";
|
||||
import { runVisible } from "../lib/process";
|
||||
|
||||
const FW_BACKUP_FILES = [
|
||||
"/etc/nftables.conf.hy2xs.bak",
|
||||
"/etc/nftables.conf.candidate",
|
||||
"/etc/nftables.d/hy2xs.nft.bak",
|
||||
"/etc/nftables.d/hy2xs.nft.candidate",
|
||||
"/etc/nftables.d/hy2xs.nft.existed",
|
||||
"/etc/nftables.d/hy2xs.nft.include.existed",
|
||||
"/etc/nftables.d/nftables.conf.existed",
|
||||
"/etc/nftables.d/hy2xs.rollback.prepared"
|
||||
];
|
||||
type NftEntrypointKind =
|
||||
| "missing"
|
||||
| "hy2xs-managed"
|
||||
| "empty"
|
||||
| "debian-empty-template"
|
||||
| "include-compatible"
|
||||
| "foreign";
|
||||
|
||||
async function cleanupFirewallBackupFiles(): Promise<void> {
|
||||
for (const file of FW_BACKUP_FILES) {
|
||||
await runVisible`rm -f ${file}`;
|
||||
}
|
||||
export type FirewallEntrypointKind = NftEntrypointKind;
|
||||
|
||||
function rollbackRoot(opId: string): string {
|
||||
return `/run/hy2xs/rollback/${opId}`;
|
||||
}
|
||||
|
||||
function rollbackUnit(opId: string): string {
|
||||
return `hy2xs-fw-rollback-${opId}`;
|
||||
}
|
||||
|
||||
function operationKey(context: RuntimeContext): string {
|
||||
return context.installDate.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
||||
}
|
||||
|
||||
function rollbackMarker(opId: string): string {
|
||||
return `${rollbackRoot(opId)}/prepared`;
|
||||
}
|
||||
|
||||
function rollbackBackup(path: string, opId: string): string {
|
||||
return `${rollbackRoot(opId)}/${path}`;
|
||||
}
|
||||
|
||||
async function ensureRollbackRoot(opId: string): Promise<void> {
|
||||
await runVisible`mkdir -p ${rollbackRoot(opId)}`;
|
||||
}
|
||||
|
||||
async function cleanupFirewallBackupFiles(opId: string): Promise<void> {
|
||||
await runVisible`rm -rf ${rollbackRoot(opId)}`;
|
||||
}
|
||||
|
||||
function stripNftComments(content: string): string {
|
||||
@@ -28,28 +49,55 @@ function stripNftComments(content: string): string {
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function isSafeNftablesEntrypoint(content: string): boolean {
|
||||
if (content.includes("HY2XS-MANAGED")) {
|
||||
return true;
|
||||
function classifyNftEntrypoint(content: string): NftEntrypointKind {
|
||||
if (!content.trim()) {
|
||||
return "missing";
|
||||
}
|
||||
|
||||
const effective = stripNftComments(content)
|
||||
if (content.includes("HY2XS-MANAGED")) {
|
||||
return "hy2xs-managed";
|
||||
}
|
||||
|
||||
const withoutComments = stripNftComments(content);
|
||||
const effective = withoutComments
|
||||
.replace(/^#!\/usr\/sbin\/nft\s+-f\s*/m, "")
|
||||
.trim();
|
||||
|
||||
if (!effective) {
|
||||
return true;
|
||||
return "empty";
|
||||
}
|
||||
|
||||
return effective === "flush ruleset";
|
||||
const normalized = effective.replace(/\s+/g, " ").trim();
|
||||
if (normalized === "flush ruleset") {
|
||||
return "debian-empty-template";
|
||||
}
|
||||
|
||||
if (/include\s+"\/etc\/nftables\.d\/hy2xs\.nft"/.test(effective)) {
|
||||
return "include-compatible";
|
||||
}
|
||||
|
||||
return "foreign";
|
||||
}
|
||||
|
||||
export async function detectFirewallEntrypointKind(): Promise<FirewallEntrypointKind> {
|
||||
if (!(await fileExists("/etc/nftables.conf"))) {
|
||||
return "missing";
|
||||
}
|
||||
return classifyNftEntrypoint(await readText("/etc/nftables.conf"));
|
||||
}
|
||||
|
||||
export async function applyFirewall(context: RuntimeContext): Promise<void> {
|
||||
if (context.options.skipFirewall || !context.config.firewallEnabled) {
|
||||
const opId = operationKey(context);
|
||||
if (context.options.skipFirewall || context.config.firewallMode === "off") {
|
||||
info("firewall skipped by flag");
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.config.firewallMode === "external") {
|
||||
info("firewall mode is external: nftables is not modified");
|
||||
return;
|
||||
}
|
||||
|
||||
const acmeChallengePort = context.config.acmeType === "tls" ? 443 : 80;
|
||||
const acmeRule = context.config.tlsMode === "acme"
|
||||
? `tcp dport ${acmeChallengePort} accept`
|
||||
@@ -61,20 +109,28 @@ export async function applyFirewall(context: RuntimeContext): Promise<void> {
|
||||
ACME_RULE: acmeRule
|
||||
});
|
||||
|
||||
const existing = await exists("/etc/nftables.conf")
|
||||
const existing = await fileExists("/etc/nftables.conf")
|
||||
? await readText("/etc/nftables.conf")
|
||||
: "";
|
||||
|
||||
if (existing && !isSafeNftablesEntrypoint(existing) && !context.config.firewallAllowTakeover) {
|
||||
fail("existing non-HY2XS nftables.conf found; set HY2XS_FIREWALL_ALLOW_TAKEOVER=true or HY2XS_FIREWALL_ENABLED=false");
|
||||
const entrypointKind = classifyNftEntrypoint(existing);
|
||||
const managedAllowed = new Set<NftEntrypointKind>([
|
||||
"missing",
|
||||
"hy2xs-managed",
|
||||
"empty",
|
||||
"debian-empty-template",
|
||||
"include-compatible"
|
||||
]);
|
||||
if (context.config.firewallMode === "managed" && !managedAllowed.has(entrypointKind)) {
|
||||
fail("foreign nftables.conf detected; use HY2XS_FIREWALL_MODE=takeover|external|off");
|
||||
}
|
||||
|
||||
await runVisible`touch /etc/nftables.d/hy2xs.rollback.prepared`;
|
||||
await runVisible`cp -a /etc/nftables.conf /etc/nftables.conf.hy2xs.bak 2>/dev/null || true`;
|
||||
await runVisible`cp -a /etc/nftables.d/hy2xs.nft /etc/nftables.d/hy2xs.nft.bak 2>/dev/null || true`;
|
||||
await runVisible`test -f /etc/nftables.conf && echo 1 > /etc/nftables.d/nftables.conf.existed || rm -f /etc/nftables.d/nftables.conf.existed`;
|
||||
await runVisible`test -f /etc/nftables.d/hy2xs.nft && echo 1 > /etc/nftables.d/hy2xs.nft.existed || rm -f /etc/nftables.d/hy2xs.nft.existed`;
|
||||
await runVisible`grep -q 'include "/etc/nftables.d/hy2xs.nft"' /etc/nftables.conf && echo 1 > /etc/nftables.d/hy2xs.nft.include.existed || rm -f /etc/nftables.d/hy2xs.nft.include.existed`;
|
||||
await ensureRollbackRoot(opId);
|
||||
await runVisible`touch ${rollbackMarker(opId)}`;
|
||||
await runVisible`cp -a /etc/nftables.conf ${rollbackBackup("nftables.conf.bak", opId)} 2>/dev/null || true`;
|
||||
await runVisible`cp -a /etc/nftables.d/hy2xs.nft ${rollbackBackup("hy2xs.nft.bak", opId)} 2>/dev/null || true`;
|
||||
await runVisible`test -f /etc/nftables.conf && echo 1 > ${rollbackBackup("nftables.conf.existed", opId)} || rm -f ${rollbackBackup("nftables.conf.existed", opId)}`;
|
||||
await runVisible`test -f /etc/nftables.d/hy2xs.nft && echo 1 > ${rollbackBackup("hy2xs.nft.existed", opId)} || rm -f ${rollbackBackup("hy2xs.nft.existed", opId)}`;
|
||||
await writeText("/etc/nftables.d/hy2xs.nft.candidate", rendered, 0o600);
|
||||
await runVisible`nft -c -f /etc/nftables.d/hy2xs.nft.candidate`;
|
||||
|
||||
@@ -103,7 +159,8 @@ include "/etc/nftables.d/hy2xs.nft"
|
||||
await runVisible`nft -c -f /etc/nftables.conf`;
|
||||
|
||||
if (context.config.firewallStagedApply) {
|
||||
await runVisible`systemd-run --unit hy2xs-fw-rollback --on-active=45s /bin/sh -c 'if [ -f /etc/nftables.d/hy2xs.rollback.prepared ]; then if [ -f /etc/nftables.d/nftables.conf.existed ]; then cp -a /etc/nftables.conf.hy2xs.bak /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi; if [ -f /etc/nftables.d/hy2xs.nft.existed ]; then cp -a /etc/nftables.d/hy2xs.nft.bak /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi; if [ -f /etc/nftables.d/nftables.conf.existed ]; then nft -f /etc/nftables.conf >/dev/null 2>&1 || true; else nft flush ruleset >/dev/null 2>&1 || true; fi; fi'`;
|
||||
const unit = rollbackUnit(opId);
|
||||
await runVisible`systemd-run --unit ${unit} --on-active=45s /bin/sh -c 'if [ -f ${rollbackMarker(opId)} ]; then if [ -f ${rollbackBackup("nftables.conf.existed", opId)} ]; then cp -a ${rollbackBackup("nftables.conf.bak", opId)} /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi; if [ -f ${rollbackBackup("hy2xs.nft.existed", opId)} ]; then cp -a ${rollbackBackup("hy2xs.nft.bak", opId)} /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi; if [ -f ${rollbackBackup("nftables.conf.existed", opId)} ]; then nft -f /etc/nftables.conf >/dev/null 2>&1 || true; else nft flush ruleset >/dev/null 2>&1 || true; fi; fi'`;
|
||||
}
|
||||
|
||||
await runVisible`nft -f /etc/nftables.conf`;
|
||||
@@ -115,35 +172,39 @@ include "/etc/nftables.d/hy2xs.nft"
|
||||
}
|
||||
|
||||
export async function cancelFirewallRollback(context: RuntimeContext): Promise<void> {
|
||||
if (!context.config.firewallEnabled || context.options.skipFirewall) {
|
||||
const opId = operationKey(context);
|
||||
if (context.options.skipFirewall || context.config.firewallMode === "off" || context.config.firewallMode === "external") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.config.firewallStagedApply) {
|
||||
await runVisible`systemctl stop hy2xs-fw-rollback || true`;
|
||||
await runVisible`systemctl reset-failed hy2xs-fw-rollback || true`;
|
||||
const unit = rollbackUnit(opId);
|
||||
await runVisible`systemctl stop ${unit}.timer ${unit}.service || true`;
|
||||
await runVisible`systemctl reset-failed ${unit}.timer ${unit}.service || true`;
|
||||
}
|
||||
|
||||
await cleanupFirewallBackupFiles();
|
||||
await cleanupFirewallBackupFiles(opId);
|
||||
}
|
||||
|
||||
export async function rollbackFirewallNow(context: RuntimeContext): Promise<void> {
|
||||
if (!context.config.firewallEnabled || context.options.skipFirewall) {
|
||||
const opId = operationKey(context);
|
||||
if (context.options.skipFirewall || context.config.firewallMode === "off" || context.config.firewallMode === "external") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await exists("/etc/nftables.d/hy2xs.rollback.prepared"))) {
|
||||
if (!(await fileExists(rollbackMarker(opId)))) {
|
||||
info("firewall rollback skipped: no HY2XS rollback markers found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.config.firewallStagedApply) {
|
||||
await runVisible`systemctl stop hy2xs-fw-rollback || true`;
|
||||
await runVisible`systemctl reset-failed hy2xs-fw-rollback || true`;
|
||||
const unit = rollbackUnit(opId);
|
||||
await runVisible`systemctl stop ${unit}.timer ${unit}.service || true`;
|
||||
await runVisible`systemctl reset-failed ${unit}.timer ${unit}.service || true`;
|
||||
}
|
||||
|
||||
await runVisible`if [ -f /etc/nftables.d/nftables.conf.existed ]; then cp -a /etc/nftables.conf.hy2xs.bak /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi`;
|
||||
await runVisible`if [ -f /etc/nftables.d/hy2xs.nft.existed ]; then cp -a /etc/nftables.d/hy2xs.nft.bak /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi`;
|
||||
await runVisible`if [ -f /etc/nftables.d/nftables.conf.existed ]; then nft -f /etc/nftables.conf >/dev/null 2>&1 || true; else nft flush ruleset >/dev/null 2>&1 || true; fi`;
|
||||
await cleanupFirewallBackupFiles();
|
||||
await runVisible`if [ -f ${rollbackBackup("nftables.conf.existed", opId)} ]; then cp -a ${rollbackBackup("nftables.conf.bak", opId)} /etc/nftables.conf 2>/dev/null || true; else rm -f /etc/nftables.conf; fi`;
|
||||
await runVisible`if [ -f ${rollbackBackup("hy2xs.nft.existed", opId)} ]; then cp -a ${rollbackBackup("hy2xs.nft.bak", opId)} /etc/nftables.d/hy2xs.nft 2>/dev/null || true; else rm -f /etc/nftables.d/hy2xs.nft; fi`;
|
||||
await runVisible`if [ -f ${rollbackBackup("nftables.conf.existed", opId)} ]; then nft -f /etc/nftables.conf >/dev/null 2>&1 || true; else nft flush ruleset >/dev/null 2>&1 || true; fi`;
|
||||
await cleanupFirewallBackupFiles(opId);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { RuntimeContext } from "../types/context";
|
||||
import { exists } from "../lib/fs";
|
||||
import { dirExists, fileExists } from "../lib/fs";
|
||||
import { fail, info } from "../lib/log";
|
||||
import { run } from "../lib/process";
|
||||
import { assertPlatform } from "../platform/assert";
|
||||
@@ -38,36 +38,46 @@ export async function preflight(context: RuntimeContext): Promise<void> {
|
||||
fail("installer must run as root");
|
||||
}
|
||||
|
||||
try {
|
||||
await run`command -v sudo >/dev/null 2>&1`;
|
||||
} catch {
|
||||
fail("sudo is required for installer smoke checks. Install it with: apt-get update && apt-get install -y sudo");
|
||||
}
|
||||
|
||||
await assertPlatform({
|
||||
distro: "debian",
|
||||
supportedVersions: [13],
|
||||
architectures: ["amd64"]
|
||||
});
|
||||
|
||||
if (!(await exists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
|
||||
if (!(await fileExists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
|
||||
fail("missing hy2xs-admin systemd unit in package");
|
||||
}
|
||||
if (!(await exists(`${context.options.packageDir}/systemd/hysteria-server.service`))) {
|
||||
if (!(await fileExists(`${context.options.packageDir}/systemd/hysteria-server.service`))) {
|
||||
fail("missing hysteria-server systemd unit in package");
|
||||
}
|
||||
if (!(await exists(`${context.options.packageDir}/templates/hysteria/config.yaml.tpl`))) {
|
||||
if (!(await fileExists(`${context.options.packageDir}/templates/hysteria/config.yaml.tpl`))) {
|
||||
fail("missing Hysteria config template in package");
|
||||
}
|
||||
if (context.mode === "install" && !(await exists(`${context.options.packageDir}/ui/hy2xs-admin/hy2xs-admin`))) {
|
||||
if (context.mode === "install" && !(await fileExists(`${context.options.packageDir}/ui/hy2xs-admin/hy2xs-admin`))) {
|
||||
fail("bundled HY2XS admin is missing from install package");
|
||||
}
|
||||
|
||||
if (!isReconfigure && (await exists("/etc/hysteria/post-install.env"))) {
|
||||
if (!(await fileExists("/usr/bin/apt-get")) && !(await fileExists("/bin/apt-get"))) {
|
||||
fail("apt-get is required on target host");
|
||||
}
|
||||
|
||||
if (!(await fileExists("/usr/bin/dpkg-query")) && !(await fileExists("/bin/dpkg-query"))) {
|
||||
fail("dpkg-query is required on target host");
|
||||
}
|
||||
|
||||
if (!isReconfigure) {
|
||||
try {
|
||||
await run`command -v sudo >/dev/null 2>&1`;
|
||||
} catch {
|
||||
info("sudo not found in preflight: installDeps step will install sudo before smoke checks");
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReconfigure && (await fileExists("/etc/hysteria/post-install.env"))) {
|
||||
fail("existing HY2XS post-install.env found; update/repair is out of scope");
|
||||
}
|
||||
|
||||
if (!isReconfigure && (await exists(context.config.installDir))) {
|
||||
if (!isReconfigure && (await dirExists(context.config.installDir))) {
|
||||
fail("existing /opt/hy2xs-admin found; conflicting old state");
|
||||
}
|
||||
|
||||
@@ -115,7 +125,9 @@ export async function preflight(context: RuntimeContext): Promise<void> {
|
||||
try {
|
||||
const aaaa = await run`getent ahostsv6 ${context.config.domain}`;
|
||||
if (aaaa.trim()) {
|
||||
info(`warning: domain ${context.config.domain} has AAAA record; HY2XS remains IPv4-only`);
|
||||
fail(
|
||||
`domain ${context.config.domain} has AAAA record while HY2XS is IPv4-only; remove AAAA or set HY2XS_ALLOW_AAAA_WITH_IPV4_ONLY=true`
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// no AAAA is acceptable
|
||||
|
||||
@@ -2,6 +2,31 @@ import type { RuntimeContext } from "../types/context";
|
||||
import { info } from "../lib/log";
|
||||
import { runHidden, runSecret, runVisible } from "../lib/process";
|
||||
|
||||
function parseLocalAddress(line: string): string {
|
||||
const cols = line.trim().split(/\s+/);
|
||||
return cols[3] ?? "";
|
||||
}
|
||||
|
||||
function hasTcpListener(lines: string, host: string, port: number): boolean {
|
||||
return lines
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.some((line) => {
|
||||
const local = parseLocalAddress(line);
|
||||
return local === `${host}:${port}`;
|
||||
});
|
||||
}
|
||||
|
||||
function hasUdpListener(lines: string, host: string, port: number): boolean {
|
||||
return lines
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.some((line) => {
|
||||
const local = parseLocalAddress(line);
|
||||
return local === `${host}:${port}`;
|
||||
});
|
||||
}
|
||||
|
||||
async function retry<T>(
|
||||
label: string,
|
||||
attempts: number,
|
||||
@@ -31,14 +56,64 @@ async function retry<T>(
|
||||
}
|
||||
|
||||
export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
if (context.options.skipStart) {
|
||||
info("service start and smoke checks skipped by flag");
|
||||
if (context.options.skipServiceStart && context.options.skipSmoke) {
|
||||
info("service start and smoke checks skipped by flags");
|
||||
return;
|
||||
}
|
||||
|
||||
await runVisible`systemctl restart hysteria-server hy2xs-admin`;
|
||||
await runVisible`systemctl is-active --quiet hysteria-server`;
|
||||
await runVisible`systemctl is-active --quiet hy2xs-admin`;
|
||||
if (!context.options.skipServiceStart) {
|
||||
await runVisible`systemctl restart hysteria-server hy2xs-admin`;
|
||||
} else {
|
||||
info("service restart skipped by flag");
|
||||
}
|
||||
|
||||
await retry(
|
||||
"systemd hysteria-server active",
|
||||
15,
|
||||
1000,
|
||||
async () => runSecret`systemctl is-active hysteria-server || true`,
|
||||
(state) => state.trim() === "active",
|
||||
(state, error) => new Error(`hysteria-server is not active: ${state ?? String(error)}`),
|
||||
);
|
||||
await retry(
|
||||
"systemd hy2xs-admin active",
|
||||
15,
|
||||
1000,
|
||||
async () => runSecret`systemctl is-active hy2xs-admin || true`,
|
||||
(state) => state.trim() === "active",
|
||||
(state, error) => new Error(`hy2xs-admin is not active: ${state ?? String(error)}`),
|
||||
);
|
||||
|
||||
if (context.options.skipSmoke) {
|
||||
info("smoke checks skipped by flag");
|
||||
return;
|
||||
}
|
||||
|
||||
await retry(
|
||||
"ui tcp listener readiness",
|
||||
15,
|
||||
1000,
|
||||
async () => runSecret`ss -H -ltn`,
|
||||
(lines) => hasTcpListener(lines, context.config.uiBindHost, context.config.uiPort),
|
||||
(lines, error) => new Error(`ui listener not ready on ${context.config.uiBindHost}:${context.config.uiPort}: ${lines ?? String(error)}`),
|
||||
);
|
||||
await retry(
|
||||
"hysteria udp listener readiness",
|
||||
15,
|
||||
1000,
|
||||
async () => runSecret`ss -H -lun`,
|
||||
(lines) => hasUdpListener(lines, context.config.hysteriaBindHost, context.config.hysteriaPort),
|
||||
(lines, error) => new Error(`hysteria udp listener not ready on 0.0.0.0:${context.config.hysteriaPort}: ${lines ?? String(error)}`),
|
||||
);
|
||||
await retry(
|
||||
"admin healthz readiness",
|
||||
15,
|
||||
1000,
|
||||
async () => runSecret`curl -sS --max-time 5 http://127.0.0.1:${context.config.uiPort}/healthz`,
|
||||
(response) => /"ok"\s*:\s*true/.test(response),
|
||||
(response, error) => new Error(`admin healthz is not ready: ${response ?? String(error)}`),
|
||||
);
|
||||
|
||||
await runVisible`/usr/local/bin/hysteria version`;
|
||||
await runVisible`test -s /etc/hysteria/config.yaml`;
|
||||
await runVisible`test -s /etc/hy2xs/hy2xs.env`;
|
||||
@@ -61,11 +136,12 @@ export async function smoke(context: RuntimeContext): Promise<void> {
|
||||
await runVisible`sudo -u hy2xs-admin test ! -r /etc/hy2xs/hy2xs.env`;
|
||||
await runVisible`sudo -u hy2xs-admin test ! -r /etc/hy2xs/bootstrap-admin.secret`;
|
||||
await runVisible`sudo -u hysteria test ! -r /etc/hy2xs/bootstrap-admin.secret`;
|
||||
await runVisible`ss -H -ltn | grep -q '${context.config.uiBindHost}:${context.config.uiPort} '`;
|
||||
if (context.config.uiBindHost === "127.0.0.1") {
|
||||
await runVisible`! ss -H -ltn | grep -q '0.0.0.0:${context.config.uiPort} '`;
|
||||
const tcp = await runSecret`ss -H -ltn`;
|
||||
if (hasTcpListener(tcp, "0.0.0.0", context.config.uiPort)) {
|
||||
throw new Error(`ui listener must not be public on 0.0.0.0:${context.config.uiPort}`);
|
||||
}
|
||||
}
|
||||
await runVisible`ss -H -lun | grep -q '0.0.0.0:${context.config.hysteriaPort} '`;
|
||||
await runVisible`! ss -H -ltn | grep -q '\[::\]:${context.config.uiPort} '`;
|
||||
await runVisible`! ss -H -lun | grep -q '\[::\]:${context.config.hysteriaPort} '`;
|
||||
const invalidAuthResponse = await retry(
|
||||
|
||||
Reference in New Issue
Block a user