diff --git a/README.md b/README.md index 5c4b5aa..f1bebb5 100644 --- a/README.md +++ b/README.md @@ -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:` для Hysteria, `127.0.0.1:` для UI по умолчанию); +- IPv6 явно out of scope; - UI запускается не от root (`hy2xs-admin`); - секреты и чувствительные конфиги: `0600`; - snapshot deploy-фактов: `/etc/hysteria/post-install.env`; diff --git a/docs/01-architecture-baseline.md b/docs/01-architecture-baseline.md index 5793678..f886b33 100644 --- a/docs/01-architecture-baseline.md +++ b/docs/01-architecture-baseline.md @@ -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 diff --git a/docs/02-build-layer-and-package.md b/docs/02-build-layer-and-package.md index f649749..c377075 100644 --- a/docs/02-build-layer-and-package.md +++ b/docs/02-build-layer-and-package.md @@ -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. + ## Инварианты Система считается правильной, если: diff --git a/docs/03-server-hysteria2.md b/docs/03-server-hysteria2.md index c3dfa11..7ccc93d 100644 --- a/docs/03-server-hysteria2.md +++ b/docs/03-server-hysteria2.md @@ -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 не используется diff --git a/docs/04-admin-panel-h-ui-fork.md b/docs/04-admin-panel-h-ui-fork.md index fcff131..b4b3327 100644 --- a/docs/04-admin-panel-h-ui-fork.md +++ b/docs/04-admin-panel-h-ui-fork.md @@ -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` diff --git a/docs/05-client-and-access-scope.md b/docs/05-client-and-access-scope.md index 77802d5..3bc9d41 100644 --- a/docs/05-client-and-access-scope.md +++ b/docs/05-client-and-access-scope.md @@ -39,6 +39,7 @@ - `listen` и `public endpoint` разделены; - в клиентских URL не используется `0.0.0.0`; - проект остаётся IPv4-only. +- если у домена есть AAAA, HY2XS его не обслуживает (IPv6 out of scope). ## Почему это важно diff --git a/docs/06-speed-limits-and-congestion.md b/docs/06-speed-limits-and-congestion.md index 28cdbf3..e1afc3c 100644 --- a/docs/06-speed-limits-and-congestion.md +++ b/docs/06-speed-limits-and-congestion.md @@ -44,6 +44,8 @@ - `HY2_BANDWIDTH_DOWN_Mbps` - `HY2_IGNORE_CLIENT_BANDWIDTH` +Дополнительно фиксируется `HY2_VERSION` как фактически установленная версия Hysteria2. + ## Что нельзя писать в проектных доках Не писать: diff --git a/docs/08-orchestrator-spec.md b/docs/08-orchestrator-spec.md index 7676030..b7e4c2f 100644 --- a/docs/08-orchestrator-spec.md +++ b/docs/08-orchestrator-spec.md @@ -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. ## Что не реализовывать diff --git a/docs/09-post-install-env.md b/docs/09-post-install-env.md index 1eb2a1b..fd96c39 100644 --- a/docs/09-post-install-env.md +++ b/docs/09-post-install-env.md @@ -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` ## Как работать с файлами diff --git a/docs/10-access-layer-out-of-scope.md b/docs/10-access-layer-out-of-scope.md index a6385a2..b303e46 100644 --- a/docs/10-access-layer-out-of-scope.md +++ b/docs/10-access-layer-out-of-scope.md @@ -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. diff --git a/docs/11-testing-and-acceptance.md b/docs/11-testing-and-acceptance.md index 2cd87a6..9b83f39 100644 --- a/docs/11-testing-and-acceptance.md +++ b/docs/11-testing-and-acceptance.md @@ -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 diff --git a/docs/12-operations-and-troubleshooting.md b/docs/12-operations-and-troubleshooting.md index 30080d4..8e59637 100644 --- a/docs/12-operations-and-troubleshooting.md +++ b/docs/12-operations-and-troubleshooting.md @@ -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). diff --git a/orchestrator/src/steps/hysteria.ts b/orchestrator/src/steps/hysteria.ts index b52b00c..6379376 100644 --- a/orchestrator/src/steps/hysteria.ts +++ b/orchestrator/src/steps/hysteria.ts @@ -1,8 +1,49 @@ import type { InstallContext } from "../types/context"; import { run, runVisible } from "../lib/process"; -export async function installHysteria(context: InstallContext): Promise { - 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 { + 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}`; } diff --git a/orchestrator/src/steps/smoke.ts b/orchestrator/src/steps/smoke.ts index 1035657..e389bde 100644 --- a/orchestrator/src/steps/smoke.ts +++ b/orchestrator/src/steps/smoke.ts @@ -18,6 +18,7 @@ export async function smoke(context: InstallContext): Promise { 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 { } 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`; + } }