Доведены пункты fix1 14/15/16: hardening install flow Hysteria, расширен smoke, синхронизированы docs
This commit is contained in:
@@ -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`;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
## Инварианты
|
||||
|
||||
Система считается правильной, если:
|
||||
|
||||
@@ -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 не используется
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
- `listen` и `public endpoint` разделены;
|
||||
- в клиентских URL не используется `0.0.0.0`;
|
||||
- проект остаётся IPv4-only.
|
||||
- если у домена есть AAAA, HY2XS его не обслуживает (IPv6 out of scope).
|
||||
|
||||
## Почему это важно
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
- `HY2_BANDWIDTH_DOWN_Mbps`
|
||||
- `HY2_IGNORE_CLIENT_BANDWIDTH`
|
||||
|
||||
Дополнительно фиксируется `HY2_VERSION` как фактически установленная версия Hysteria2.
|
||||
|
||||
## Что нельзя писать в проектных доках
|
||||
|
||||
Не писать:
|
||||
|
||||
@@ -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.
|
||||
|
||||
## Что не реализовывать
|
||||
|
||||
@@ -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`
|
||||
|
||||
## Как работать с файлами
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user