Подготовить HY2XS к production-сборке

This commit is contained in:
2026-04-25 23:13:12 +05:00
commit 84a4e94567
277 changed files with 26513 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
import type { InstallContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
export async function generateConfig(context: InstallContext): Promise<void> {
const template = await readText(`${context.options.packageDir}/templates/hysteria/config.yaml.tpl`);
const rendered = renderTemplate(template, {
HYSTERIA_PORT: context.options.port,
HYSTERIA_AUTH_PASSWORD: context.hysteriaAuthPassword,
HYSTERIA_OBFS_PASSWORD: context.hysteriaObfsPassword,
HYSTERIA_API_PORT: context.hysteriaApiPort,
HYSTERIA_API_SECRET: context.hysteriaApiSecret,
UI_PORT: context.options.uiPort,
BANDWIDTH_UP: "50 mbps",
BANDWIDTH_DOWN: "50 mbps"
});
await writeText("/etc/hysteria/config.yaml", rendered, 0o600);
await runVisible`openssl req -x509 -newkey rsa:2048 -nodes -days 3650 -subj /CN=${context.options.domain || "hy2xs.local"} -keyout /etc/hysteria/server.key -out /etc/hysteria/server.crt`;
await runVisible`chown hysteria:hysteria /etc/hysteria/config.yaml /etc/hysteria/server.key /etc/hysteria/server.crt`;
}
+7
View File
@@ -0,0 +1,7 @@
import type { InstallContext } from "../types/context";
import { runVisible } from "../lib/process";
export async function installDeps(_context: InstallContext): Promise<void> {
await runVisible`apt-get update`;
await runVisible`apt-get install -y ca-certificates curl iproute2 tar openssl nftables systemd`;
}
+20
View File
@@ -0,0 +1,20 @@
import type { InstallContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
export async function writePostInstallEnv(context: InstallContext): Promise<void> {
const rendered = renderTemplate(await readText(`${context.options.packageDir}/templates/env/post-install.env.tpl`), {
PACKAGE_VERSION: context.packageVersion,
PACKAGE_BUILD_ID: context.packageBuildId,
INSTALL_DATE: context.installDate,
DOMAIN: context.options.domain,
SSH_PORT: context.options.sshPort,
HYSTERIA_VERSION: context.hysteriaVersion,
HYSTERIA_PORT: context.options.port,
HYSTERIA_OBFS_PASSWORD: context.hysteriaObfsPassword,
HYSTERIA_API_PORT: context.hysteriaApiPort,
UI_BIND_HOST: context.options.uiBindHost,
UI_PORT: context.options.uiPort
});
await writeText("/etc/hysteria/post-install.env", rendered, 0o600);
}
+9
View File
@@ -0,0 +1,9 @@
import type { InstallContext } from "../types/context";
import { runVisible } from "../lib/process";
export async function prepareFilesystem(_context: InstallContext): Promise<void> {
await runVisible`id -u hysteria >/dev/null 2>&1 || useradd --system --home /var/lib/hysteria --shell /usr/sbin/nologin hysteria`;
await runVisible`mkdir -p /etc/hysteria /var/lib/hysteria /opt/hy2xs-admin /var/lib/hy2xs-admin /var/log/hy2xs /usr/local/lib/hy2xs`;
await runVisible`chown -R hysteria:hysteria /etc/hysteria /var/lib/hysteria`;
await runVisible`chown -R root:root /var/lib/hy2xs-admin`;
}
+22
View File
@@ -0,0 +1,22 @@
import type { InstallContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { info } from "../lib/log";
import { runVisible } from "../lib/process";
export async function applyFirewall(context: InstallContext): Promise<void> {
if (context.options.skipFirewall) {
info("firewall skipped by flag");
return;
}
const rendered = renderTemplate(await readText(`${context.options.packageDir}/templates/nftables/hy2xs.nft.tpl`), {
SSH_PORT: context.options.sshPort,
HYSTERIA_PORT: context.options.port,
UI_PORT: context.options.uiPort
});
await runVisible`cp -a /etc/nftables.conf /etc/nftables.conf.hy2xs.bak 2>/dev/null || true`;
await writeText("/etc/nftables.conf", rendered, 0o644);
await runVisible`nft -f /etc/nftables.conf`;
await runVisible`systemctl enable --now nftables`;
}
+8
View File
@@ -0,0 +1,8 @@
import type { InstallContext } from "../types/context";
import { run, runVisible } from "../lib/process";
export async function installHysteria(context: InstallContext): Promise<void> {
await runVisible`curl -fsSL https://get.hy2.sh/ -o /tmp/hy2xs-install-hysteria.sh`;
await runVisible`sh /tmp/hy2xs-install-hysteria.sh`;
context.hysteriaVersion = await run`/usr/local/bin/hysteria version`;
}
+53
View File
@@ -0,0 +1,53 @@
import type { InstallContext } from "../types/context";
import { exists, readText } from "../lib/fs";
import { fail } from "../lib/log";
import { run } from "../lib/process";
async function isPortBusy(port: number): Promise<boolean> {
try {
const output = await run`ss -H -lntu`;
return output.split("\n").some((line) => line.includes(`:${port} `) || line.endsWith(`:${port}`));
} catch {
return false;
}
}
export async function preflight(context: InstallContext): Promise<void> {
if (process.getuid?.() !== 0) {
fail("installer must run as root");
}
const osRelease = await readText("/etc/os-release");
if (!/^ID=debian$/m.test(osRelease) || !/^VERSION_ID="?12"?$/m.test(osRelease)) {
fail("HY2XS baseline supports only clean Debian 12");
}
if (!(await exists(`${context.options.packageDir}/ui/hy2xs-admin`))) {
fail("bundled HY2XS admin is missing from install package");
}
if (await exists("/etc/hysteria/post-install.env")) {
fail("existing HY2XS post-install.env found; update/repair is out of scope");
}
if (await exists("/opt/hy2xs-admin")) {
fail("existing /opt/hy2xs-admin found; conflicting old state");
}
const ports = new Set([context.options.port, context.options.uiPort]);
if (ports.size !== 2) {
fail("Hysteria port and UI port must be different");
}
if (context.options.domain && !/^[a-zA-Z0-9.-]+$/.test(context.options.domain)) {
fail("domain contains unsupported characters");
}
if (await isPortBusy(context.options.port)) {
fail(`Hysteria UDP/TCP port already appears to be in use: ${context.options.port}`);
}
if (await isPortBusy(context.options.uiPort)) {
fail(`HY2XS admin port already appears to be in use: ${context.options.uiPort}`);
}
}
+19
View File
@@ -0,0 +1,19 @@
import type { InstallContext } from "../types/context";
import { info } from "../lib/log";
import { runVisible } from "../lib/process";
export async function smoke(context: InstallContext): Promise<void> {
if (context.options.skipStart) {
info("service start and smoke checks skipped by flag");
return;
}
await runVisible`systemctl start hysteria-server hy2xs-admin`;
await runVisible`systemctl is-active --quiet hysteria-server`;
await runVisible`systemctl is-active --quiet hy2xs-admin`;
await runVisible`/usr/local/bin/hysteria version`;
await runVisible`test -s /etc/hysteria/config.yaml`;
await runVisible`test -s /etc/hysteria/post-install.env`;
await runVisible`ss -H -lntu | grep -q ':${context.options.uiPort} '`;
await runVisible`curl -fsS --max-time 5 http://127.0.0.1:${context.options.uiPort}/ >/dev/null`;
}
+18
View File
@@ -0,0 +1,18 @@
import type { InstallContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
export async function deploySystemd(context: InstallContext): Promise<void> {
const values = {
UI_BIND_HOST: context.options.uiBindHost,
UI_PORT: context.options.uiPort
};
const hysteriaUnit = await readText(`${context.options.packageDir}/systemd/hysteria-server.service`);
const adminUnit = renderTemplate(await readText(`${context.options.packageDir}/systemd/hy2xs-admin.service`), values);
await writeText("/etc/systemd/system/hysteria-server.service", hysteriaUnit, 0o644);
await writeText("/etc/systemd/system/hy2xs-admin.service", adminUnit, 0o644);
await runVisible`systemctl daemon-reload`;
await runVisible`systemctl enable hysteria-server hy2xs-admin`;
}
+8
View File
@@ -0,0 +1,8 @@
import type { InstallContext } from "../types/context";
import { runVisible } from "../lib/process";
export async function deployUi(context: InstallContext): Promise<void> {
await runVisible`cp -a ${context.options.packageDir}/ui/hy2xs-admin/. /opt/hy2xs-admin/`;
await runVisible`chown -R root:root /opt/hy2xs-admin`;
await runVisible`chmod -R go-w /opt/hy2xs-admin`;
}