Доведены пункты 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: На чистом Debian 12 target нужно распаковать архив и запустить от root:
```sh ```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 по умолчанию); - только IPv4 (`0.0.0.0:<port>` для Hysteria, `127.0.0.1:<ui_port>` для UI по умолчанию);
- IPv6 явно out of scope;
- UI запускается не от root (`hy2xs-admin`); - UI запускается не от root (`hy2xs-admin`);
- секреты и чувствительные конфиги: `0600`; - секреты и чувствительные конфиги: `0600`;
- snapshot deploy-фактов: `/etc/hysteria/post-install.env`; - snapshot deploy-фактов: `/etc/hysteria/post-install.env`;
+9 -1
View File
@@ -74,9 +74,10 @@ Target layer **не содержит сборщика** и **не выполня
Установка — на сервере. Установка — на сервере.
На сервере не должно быть логики «собери мне UI» или «собери мне TypeScript оркестратор». На сервере не должно быть логики «собери мне UI» или «собери мне TypeScript оркестратор».
### 3. Оркестратор install-only ### 3. Оркестратор install/reconfigure-only
Оркестратор умеет только: Оркестратор умеет только:
- установить - установить
- применить явную реконфигурацию из runtime env
- разложить файлы - разложить файлы
- создать базовую конфигурацию - создать базовую конфигурацию
- подготовить сервер к работе - подготовить сервер к работе
@@ -125,3 +126,10 @@ Telegram-бот, backend выдачи ключей, remote profile publishing, b
6. Сервер разворачивает bundled UI из пакета. 6. Сервер разворачивает bundled UI из пакета.
7. Создаются systemd unit-файлы, firewall baseline и `post-install.env`. 7. Создаются systemd unit-файлы, firewall baseline и `post-install.env`.
8. Сервер готов как базовое рабочее окружение HY2XS. 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 - ядро Hysteria рассматривается как stable upstream component
- целевая установка должна брать его с official upstream на момент развёртывания - целевая установка должна брать его с 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** - по умолчанию install layer тянет **свежий upstream release / install source**
- фактически установленная версия обязательно записывается в `post-install.env` - фактически установленная версия обязательно записывается в `post-install.env`
- документация не обещает жёсткий pin как baseline - поддерживаются политики `latest | vX.Y.Z` через `HY2XS_HYSTERIA_VERSION`
- если оператору нужна строгая фиксация версии, это отдельный режим, а не базовая модель - при `vX.Y.Z` install обязан валидировать соответствие фактически установленной версии
## Платформа ## Платформа
@@ -109,3 +109,5 @@ Hysteria2 — основной транспортный компонент се
5. нужный UDP-порт реально слушается 5. нужный UDP-порт реально слушается
6. тестовый совместимый клиент может подключиться 6. тестовый совместимый клиент может подключиться
7. bundled UI работает поверх актуального состояния сервера 7. bundled UI работает поверх актуального состояния сервера
8. `trafficStats.secret` отдельный от `JWT_SECRET`
9. IPv6 listen не используется
+3
View File
@@ -80,6 +80,7 @@ Bundled H UI должна:
- HY2XS admin не запускается от root - HY2XS admin не запускается от root
- смена версии Hysteria2 через UI отключена в baseline - смена версии Hysteria2 через UI отключена в baseline
- список upstream releases не является частью operator UI baseline - список upstream releases не является частью operator UI baseline
- port hopping не является частью production path
### Что нельзя делать ### Что нельзя делать
- скачивать H UI с upstream прямо на target как baseline - скачивать H UI с upstream прямо на target как baseline
@@ -109,3 +110,5 @@ Bundled H UI должна:
3. UI работает отдельным сервисом 3. UI работает отдельным сервисом
4. UI не меняет install-only scope оркестратора 4. UI не меняет install-only scope оркестратора
5. Hysteria остаётся внешним vanilla upstream-компонентом 5. Hysteria остаётся внешним vanilla upstream-компонентом
6. UI не выступает updater-менеджером Hysteria2
7. `trafficStats.secret` не связан с `JWT_SECRET`
+1
View File
@@ -39,6 +39,7 @@
- `listen` и `public endpoint` разделены; - `listen` и `public endpoint` разделены;
- в клиентских URL не используется `0.0.0.0`; - в клиентских URL не используется `0.0.0.0`;
- проект остаётся IPv4-only. - проект остаётся IPv4-only.
- если у домена есть AAAA, HY2XS его не обслуживает (IPv6 out of scope).
## Почему это важно ## Почему это важно
+2
View File
@@ -44,6 +44,8 @@
- `HY2_BANDWIDTH_DOWN_Mbps` - `HY2_BANDWIDTH_DOWN_Mbps`
- `HY2_IGNORE_CLIENT_BANDWIDTH` - `HY2_IGNORE_CLIENT_BANDWIDTH`
Дополнительно фиксируется `HY2_VERSION` как фактически установленная версия Hysteria2.
## Что нельзя писать в проектных доках ## Что нельзя писать в проектных доках
Не писать: Не писать:
+2 -1
View File
@@ -78,7 +78,7 @@
2. Проверяет базовые зависимости и install context. 2. Проверяет базовые зависимости и install context.
3. Создаёт каталоги установки. 3. Создаёт каталоги установки.
4. Разворачивает bundled HY2XS admin. 4. Разворачивает bundled HY2XS admin.
5. Скачивает Hysteria2 из official upstream. 5. Скачивает installer Hysteria2 в temp-файл и выполняет install с policy `latest|vX.Y.Z`.
6. Генерирует Hysteria config. 6. Генерирует Hysteria config.
7. Создаёт systemd unit для Hysteria. 7. Создаёт systemd unit для Hysteria.
8. Создаёт systemd unit для HY2XS admin. 8. Создаёт systemd unit для HY2XS admin.
@@ -122,6 +122,7 @@
- только IPv4 bind/listen; - только IPv4 bind/listen;
- TLS modes: `acme | file | self_signed_dev`; - TLS modes: `acme | file | self_signed_dev`;
- `trafficStats.secret` отдельный от `JWT_SECRET`; - `trafficStats.secret` отдельный от `JWT_SECRET`;
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail. - при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
## Что не реализовывать ## Что не реализовывать
+9
View File
@@ -58,11 +58,19 @@
### Общие ### Общие
- `DEPLOY_DOMAIN` - `DEPLOY_DOMAIN`
- `PUBLIC_HOST`
- `PUBLIC_PORT`
- `SSH_PORT` - `SSH_PORT`
- `HY2XS_FIREWALL_ENABLED`
- `HY2XS_FIREWALL_STAGED_APPLY`
### Hysteria ### Hysteria
- `HY2_SOURCE=official-upstream` - `HY2_SOURCE=official-upstream`
- `HY2_VERSION` - `HY2_VERSION`
- `HY2_TLS_MODE`
- `HY2_ACME_EMAIL`
- `HY2_TLS_CERT_PATH`
- `HY2_TLS_KEY_PATH`
- `HY2_LISTEN_HOST` - `HY2_LISTEN_HOST`
- `HY2_PORT` - `HY2_PORT`
- `HY2_AUTH_MODE` - `HY2_AUTH_MODE`
@@ -83,6 +91,7 @@
- `HUI_PORT` - `HUI_PORT`
- `HUI_INSTALL_DIR` - `HUI_INSTALL_DIR`
- `HUI_DATA_DIR` - `HUI_DATA_DIR`
- `HUI_LOG_DIR`
## Как работать с файлами ## Как работать с файлами
+3
View File
@@ -25,6 +25,7 @@ Baseline делает только следующее:
- создаёт systemd units - создаёт systemd units
- применяет firewall baseline - применяет firewall baseline
- фиксирует deploy facts в `post-install.env` - фиксирует deploy facts в `post-install.env`
- применяет runtime изменения только через `reconfigure --dry-run/--apply`
## Что может существовать рядом, но отдельно ## Что может существовать рядом, но отдельно
@@ -39,3 +40,5 @@ Baseline делает только следующее:
## Итоговая формулировка ## Итоговая формулировка
HY2XS baseline в этих документах — это **оркестратор установки и базовой серверной конфигурации**, а не пользовательский delivery platform. HY2XS baseline в этих документах — это **оркестратор установки и базовой серверной конфигурации**, а не пользовательский delivery platform.
Дополнение: baseline не включает port hopping и не включает updater-логику в HY2XS admin.
+3
View File
@@ -45,6 +45,9 @@
10. нет IPv6 listen (`[::]`) для Hysteria/HY2XS admin 10. нет IPv6 listen (`[::]`) для Hysteria/HY2XS admin
11. `trafficStats.secret` не равен `JWT_SECRET` 11. `trafficStats.secret` не равен `JWT_SECRET`
12. bootstrap admin secret существует и имеет `0600` 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 ## D. Negative tests
@@ -20,6 +20,7 @@
- какую фактическую версию оркестратор установил - какую фактическую версию оркестратор установил
- что записано в `HY2_VERSION` - что записано в `HY2_VERSION`
- не связано ли поведение со свежим upstream release - не связано ли поведение со свежим upstream release
- какая policy была в `HY2XS_HYSTERIA_VERSION` (`latest|vX.Y.Z`)
### 4. Оркестратор — Bun/TypeScript, но target не билдит его ### 4. Оркестратор — Bun/TypeScript, но target не билдит его
Если проблема в install flow, сначала смотреть: Если проблема в install flow, сначала смотреть:
@@ -85,6 +86,8 @@ cat /etc/hysteria/post-install.env
`post-install.env` — reference file, а не autoreconcile engine. `post-install.env` — reference file, а не autoreconcile engine.
Редактировать нужно `/etc/hy2xs/hy2xs.env` и затем запускать `reconfigure --dry-run/--apply`.
## Правила эксплуатации ## Правила эксплуатации
1. Не править сервер как будто на нём есть builder. 1. Не править сервер как будто на нём есть builder.
@@ -92,3 +95,4 @@ cat /etc/hysteria/post-install.env
3. Не считать `post-install.env` автоматическим механизмом применения изменений. 3. Не считать `post-install.env` автоматическим механизмом применения изменений.
4. Не расширять install-only baseline до lifecycle-manager без отдельного проектного решения. 4. Не расширять install-only baseline до lifecycle-manager без отдельного проектного решения.
5. Не смешивать install baseline и access/bot platform в одной документации. 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 type { InstallContext } from "../types/context";
import { run, runVisible } from "../lib/process"; import { run, runVisible } from "../lib/process";
export async function installHysteria(context: InstallContext): Promise<void> { function normalizeInstalledVersion(raw: string): string {
await runVisible`curl -fsSL https://get.hy2.sh/ -o /tmp/hy2xs-install-hysteria.sh`; const match = raw.match(/v\d+\.\d+\.\d+/);
await runVisible`sh /tmp/hy2xs-install-hysteria.sh`; if (match) {
context.hysteriaVersion = await run`/usr/local/bin/hysteria version`; 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 -s ${context.config.bootstrapAdminSecretPath}`;
await runVisible`test "$(stat -c '%a' /etc/hysteria/config.yaml)" = '600'`; 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/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`test "$(stat -c '%a' ${context.config.bootstrapAdminSecretPath})" = '600'`;
await runVisible`ss -H -ltn | grep -q '${context.config.uiBindHost}:${context.config.uiPort} '`; await runVisible`ss -H -ltn | grep -q '${context.config.uiBindHost}:${context.config.uiPort} '`;
if (context.config.uiBindHost === "127.0.0.1") { 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 -lun | grep -q '0.0.0.0:${context.config.hysteriaPort} '`;
await runVisible`! ss -H -ltnu | grep -q '\[::\]'`; 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`;
}
} }