Доведены пункты fix1 14/15/16: hardening install flow Hysteria, расширен smoke, синхронизированы docs

This commit is contained in:
2026-04-27 19:47:15 +05:00
parent 3fccd5c442
commit 140f512750
14 changed files with 114 additions and 10 deletions
+2 -1
View File
@@ -57,7 +57,7 @@ https://git.ext.flamy.studio/flamy_dev/HY2XS_flamy.git
На чистом Debian 12 target нужно распаковать архив и запустить от root:
```sh
./install.sh --package-dir . --config /etc/hy2xs/hy2xs.env --non-interactive
./install.sh --config /etc/hy2xs/hy2xs.env --non-interactive
```
После установки применяются команды оркестратора:
@@ -70,6 +70,7 @@ hy2xs-orchestrator reconfigure --package-dir /opt/hy2xs-package --config /etc/hy
Ключевые инварианты:
- только IPv4 (`0.0.0.0:<port>` для Hysteria, `127.0.0.1:<ui_port>` для UI по умолчанию);
- IPv6 явно out of scope;
- UI запускается не от root (`hy2xs-admin`);
- секреты и чувствительные конфиги: `0600`;
- snapshot deploy-фактов: `/etc/hysteria/post-install.env`;
+9 -1
View File
@@ -74,9 +74,10 @@ Target layer **не содержит сборщика** и **не выполня
Установка — на сервере.
На сервере не должно быть логики «собери мне UI» или «собери мне TypeScript оркестратор».
### 3. Оркестратор install-only
### 3. Оркестратор install/reconfigure-only
Оркестратор умеет только:
- установить
- применить явную реконфигурацию из runtime env
- разложить файлы
- создать базовую конфигурацию
- подготовить сервер к работе
@@ -125,3 +126,10 @@ Telegram-бот, backend выдачи ключей, remote profile publishing, b
6. Сервер разворачивает bundled UI из пакета.
7. Создаются systemd unit-файлы, firewall baseline и `post-install.env`.
8. Сервер готов как базовое рабочее окружение HY2XS.
## Runtime policy
- editable слой: `/etc/hy2xs/hy2xs.env` (0600)
- snapshot слой: `/etc/hysteria/post-install.env` (0600)
- изменения runtime применяются только через явный `reconfigure --dry-run/--apply`
- IPv6 out of scope: все bind/listen только IPv4
+5
View File
@@ -122,6 +122,11 @@ project/
- ядро Hysteria рассматривается как stable upstream component
- целевая установка должна брать его с official upstream на момент развёртывания
Дополнительно:
- `HY2XS_HYSTERIA_VERSION=latest|vX.Y.Z` задаётся через runtime env;
- при `latest` оркестратор записывает **фактически установленную** версию в `post-install.env`;
- install flow использует download-to-temp + explicit execute + post-install verification binary/version.
## Инварианты
Система считается правильной, если:
+4 -2
View File
@@ -22,8 +22,8 @@ Hysteria2 — основной транспортный компонент се
- по умолчанию install layer тянет **свежий upstream release / install source**
- фактически установленная версия обязательно записывается в `post-install.env`
- документация не обещает жёсткий pin как baseline
- если оператору нужна строгая фиксация версии, это отдельный режим, а не базовая модель
- поддерживаются политики `latest | vX.Y.Z` через `HY2XS_HYSTERIA_VERSION`
- при `vX.Y.Z` install обязан валидировать соответствие фактически установленной версии
## Платформа
@@ -109,3 +109,5 @@ Hysteria2 — основной транспортный компонент се
5. нужный UDP-порт реально слушается
6. тестовый совместимый клиент может подключиться
7. bundled UI работает поверх актуального состояния сервера
8. `trafficStats.secret` отдельный от `JWT_SECRET`
9. IPv6 listen не используется
+3
View File
@@ -80,6 +80,7 @@ Bundled H UI должна:
- HY2XS admin не запускается от root
- смена версии Hysteria2 через UI отключена в baseline
- список upstream releases не является частью operator UI baseline
- port hopping не является частью production path
### Что нельзя делать
- скачивать H UI с upstream прямо на target как baseline
@@ -109,3 +110,5 @@ Bundled H UI должна:
3. UI работает отдельным сервисом
4. UI не меняет install-only scope оркестратора
5. Hysteria остаётся внешним vanilla upstream-компонентом
6. UI не выступает updater-менеджером Hysteria2
7. `trafficStats.secret` не связан с `JWT_SECRET`
+1
View File
@@ -39,6 +39,7 @@
- `listen` и `public endpoint` разделены;
- в клиентских URL не используется `0.0.0.0`;
- проект остаётся IPv4-only.
- если у домена есть AAAA, HY2XS его не обслуживает (IPv6 out of scope).
## Почему это важно
+2
View File
@@ -44,6 +44,8 @@
- `HY2_BANDWIDTH_DOWN_Mbps`
- `HY2_IGNORE_CLIENT_BANDWIDTH`
Дополнительно фиксируется `HY2_VERSION` как фактически установленная версия Hysteria2.
## Что нельзя писать в проектных доках
Не писать:
+2 -1
View File
@@ -78,7 +78,7 @@
2. Проверяет базовые зависимости и install context.
3. Создаёт каталоги установки.
4. Разворачивает bundled HY2XS admin.
5. Скачивает Hysteria2 из official upstream.
5. Скачивает installer Hysteria2 в temp-файл и выполняет install с policy `latest|vX.Y.Z`.
6. Генерирует Hysteria config.
7. Создаёт systemd unit для Hysteria.
8. Создаёт systemd unit для HY2XS admin.
@@ -122,6 +122,7 @@
- только IPv4 bind/listen;
- TLS modes: `acme | file | self_signed_dev`;
- `trafficStats.secret` отдельный от `JWT_SECRET`;
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
## Что не реализовывать
+9
View File
@@ -58,11 +58,19 @@
### Общие
- `DEPLOY_DOMAIN`
- `PUBLIC_HOST`
- `PUBLIC_PORT`
- `SSH_PORT`
- `HY2XS_FIREWALL_ENABLED`
- `HY2XS_FIREWALL_STAGED_APPLY`
### Hysteria
- `HY2_SOURCE=official-upstream`
- `HY2_VERSION`
- `HY2_TLS_MODE`
- `HY2_ACME_EMAIL`
- `HY2_TLS_CERT_PATH`
- `HY2_TLS_KEY_PATH`
- `HY2_LISTEN_HOST`
- `HY2_PORT`
- `HY2_AUTH_MODE`
@@ -83,6 +91,7 @@
- `HUI_PORT`
- `HUI_INSTALL_DIR`
- `HUI_DATA_DIR`
- `HUI_LOG_DIR`
## Как работать с файлами
+3
View File
@@ -25,6 +25,7 @@ Baseline делает только следующее:
- создаёт systemd units
- применяет firewall baseline
- фиксирует deploy facts в `post-install.env`
- применяет runtime изменения только через `reconfigure --dry-run/--apply`
## Что может существовать рядом, но отдельно
@@ -39,3 +40,5 @@ Baseline делает только следующее:
## Итоговая формулировка
HY2XS baseline в этих документах — это **оркестратор установки и базовой серверной конфигурации**, а не пользовательский delivery platform.
Дополнение: baseline не включает port hopping и не включает updater-логику в HY2XS admin.
+3
View File
@@ -45,6 +45,9 @@
10. нет IPv6 listen (`[::]`) для Hysteria/HY2XS admin
11. `trafficStats.secret` не равен `JWT_SECRET`
12. bootstrap admin secret существует и имеет `0600`
13. `trafficStats` API: корректный secret принимает запрос, неверный secret отклоняется
14. TLS mode в `config.yaml` соответствует runtime env (`acme|file|self_signed_dev`)
15. `nft -c -f /etc/nftables.conf` проходит после apply
## D. Negative tests
@@ -20,6 +20,7 @@
- какую фактическую версию оркестратор установил
- что записано в `HY2_VERSION`
- не связано ли поведение со свежим upstream release
- какая policy была в `HY2XS_HYSTERIA_VERSION` (`latest|vX.Y.Z`)
### 4. Оркестратор — Bun/TypeScript, но target не билдит его
Если проблема в install flow, сначала смотреть:
@@ -85,6 +86,8 @@ cat /etc/hysteria/post-install.env
`post-install.env` — reference file, а не autoreconcile engine.
Редактировать нужно `/etc/hy2xs/hy2xs.env` и затем запускать `reconfigure --dry-run/--apply`.
## Правила эксплуатации
1. Не править сервер как будто на нём есть builder.
@@ -92,3 +95,4 @@ cat /etc/hysteria/post-install.env
3. Не считать `post-install.env` автоматическим механизмом применения изменений.
4. Не расширять install-only baseline до lifecycle-manager без отдельного проектного решения.
5. Не смешивать install baseline и access/bot platform в одной документации.
6. Не включать IPv6 в runtime-политике HY2XS (проект IPv4-only).
+45 -4
View File
@@ -1,8 +1,49 @@
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`;
function normalizeInstalledVersion(raw: string): string {
const match = raw.match(/v\d+\.\d+\.\d+/);
if (match) {
return match[0];
}
return raw.trim();
}
function validateVersionPolicy(value: string): void {
if (value === "latest") {
return;
}
if (/^v\d+\.\d+\.\d+$/.test(value)) {
return;
}
throw new Error(`invalid HY2XS_HYSTERIA_VERSION policy: ${value}`);
}
export async function installHysteria(context: InstallContext): Promise<void> {
const policy = context.config.hysteriaVersionPolicy;
validateVersionPolicy(policy);
const scriptPath = "/tmp/hy2xs-install-hysteria.sh";
await runVisible`curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location https://get.hy2.sh/ -o ${scriptPath}`;
await runVisible`test -s ${scriptPath}`;
await runVisible`chmod 700 ${scriptPath}`;
if (policy === "latest") {
await runVisible`bash ${scriptPath}`;
} else {
await runVisible`HYSTERIA_VERSION=${policy} bash ${scriptPath}`;
}
await runVisible`test -x /usr/local/bin/hysteria`;
const versionOutput = await run`/usr/local/bin/hysteria version`;
const installedVersion = normalizeInstalledVersion(versionOutput);
context.hysteriaVersion = installedVersion;
if (policy !== "latest" && installedVersion !== policy) {
throw new Error(
`installed Hysteria version mismatch: expected ${policy}, got ${installedVersion}. Review upstream installer env contract.`
);
}
await runVisible`rm -f ${scriptPath}`;
}
+22 -1
View File
@@ -18,6 +18,7 @@ export async function smoke(context: InstallContext): Promise<void> {
await runVisible`test -s ${context.config.bootstrapAdminSecretPath}`;
await runVisible`test "$(stat -c '%a' /etc/hysteria/config.yaml)" = '600'`;
await runVisible`test "$(stat -c '%a' /etc/hy2xs/hy2xs.env)" = '600'`;
await runVisible`test "$(stat -c '%a' /etc/hysteria/post-install.env)" = '600'`;
await runVisible`test "$(stat -c '%a' ${context.config.bootstrapAdminSecretPath})" = '600'`;
await runVisible`ss -H -ltn | grep -q '${context.config.uiBindHost}:${context.config.uiPort} '`;
if (context.config.uiBindHost === "127.0.0.1") {
@@ -25,5 +26,25 @@ export async function smoke(context: InstallContext): Promise<void> {
}
await runVisible`ss -H -lun | grep -q '0.0.0.0:${context.config.hysteriaPort} '`;
await runVisible`! ss -H -ltnu | grep -q '\[::\]'`;
await runVisible`curl -fsS --max-time 5 http://127.0.0.1:${context.config.uiPort}/ >/dev/null`;
await runVisible`curl -fsS --max-time 5 http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth >/dev/null`;
await runVisible`curl -fsS --max-time 5 -H 'Authorization: ${context.config.hysteriaTrafficStatsSecret}' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online >/dev/null`;
await runVisible`curl -fsS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online | grep -Eq '401|403'`;
await runVisible`nft -c -f /etc/nftables.conf`;
if (context.config.tlsMode === "acme") {
await runVisible`grep -q '^acme:' /etc/hysteria/config.yaml`;
await runVisible`! grep -q '^tls:' /etc/hysteria/config.yaml`;
}
if (context.config.tlsMode === "file") {
await runVisible`grep -q '^tls:' /etc/hysteria/config.yaml`;
await runVisible`! grep -q '^acme:' /etc/hysteria/config.yaml`;
await runVisible`grep -q 'insecure: false' /etc/hysteria/config.yaml`;
}
if (context.config.tlsMode === "self_signed_dev") {
await runVisible`grep -q '^tls:' /etc/hysteria/config.yaml`;
await runVisible`! grep -q '^acme:' /etc/hysteria/config.yaml`;
await runVisible`grep -q 'insecure: true' /etc/hysteria/config.yaml`;
}
}