Продакшн рефактор без легаси

This commit is contained in:
2026-04-30 20:54:38 +05:00
parent c10ab1fafd
commit 4a0d9569d1
41 changed files with 193 additions and 888 deletions
+1 -2
View File
@@ -7,7 +7,7 @@ HY2XS — production installer/runtime-manager для развёртывания
- production builder на Debian 12 amd64 создаёт один переносимый архив;
- install-only оркестратор на Bun + TypeScript поставляется на target как готовый compiled artifact;
- HY2XS admin поставляется в составе пакета как bundled fork;
- vanilla Hysteria2 скачивается с official upstream во время установки;
- vanilla Hysteria2 устанавливается как pinned binary (version/url/sha256 из metadata install package);
- runtime-конфиг управляется через `/etc/hy2xs/hy2xs.env` и команду `reconfigure`.
В baseline намеренно не входят target-side build, rollback/uninstall orchestration, Telegram bot delivery, port hopping и generic server-manager функции.
@@ -19,7 +19,6 @@ HY2XS — production installer/runtime-manager для развёртывания
- [`package`](package) — skeleton итогового install package: entrypoint, templates, systemd units, package docs.
- [`apps`](apps) — исходники HY2XS admin fork.
- [`docs`](docs) — архитектурная документация и acceptance.
- [`hy2xs_implementation_plan-no_git`](hy2xs_implementation_plan-no_git) — roadmap и task breakdown.
- [`dist`](dist) — итоговые install archives, создаются builder'ом и не хранятся в git.
## Сборка
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"hy2xs-admin/service"
"hy2xs-admin/util"
"io"
"path/filepath"
"strings"
"time"
)
@@ -299,7 +300,7 @@ func ExportAccount(c *gin.Context) {
}
fileName := fmt.Sprintf("AccountExport-%s.json", time.Now().Format("20060102150405"))
filePath := constant.ExportPathDir + fileName
filePath := filepath.Join(constant.ExportPathDir, fileName)
if err = util.ExportFile(filePath, accountExports, 0); err != nil {
vo.Fail(err.Error(), c)
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"hy2xs-admin/service"
"hy2xs-admin/util"
"io"
"path/filepath"
"strconv"
"strings"
"time"
@@ -230,7 +231,7 @@ func ExportHysteria2Config(c *gin.Context) {
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
fileName := fmt.Sprintf("Hysteria2Config-%s.yaml", time.Now().Format("20060102150405"))
filePath := constant.ExportPathDir + fileName
filePath := filepath.Join(constant.ExportPathDir, fileName)
if err = util.ExportFile(filePath, hysteria2ServerConfig, 1); err != nil {
vo.Fail(err.Error(), c)
@@ -258,7 +259,7 @@ func ExportConfig(c *gin.Context) {
return
}
fileName := fmt.Sprintf("SystemConfig-%s.json", time.Now().Format("20060102150405"))
filePath := constant.ExportPathDir + fileName
filePath := filepath.Join(constant.ExportPathDir, fileName)
if err = util.ExportFile(filePath, configs, 0); err != nil {
vo.Fail(err.Error(), c)
+26
View File
@@ -0,0 +1,26 @@
package middleware
import (
"net"
"net/http"
"github.com/gin-gonic/gin"
"hy2xs-admin/model/vo"
)
func LocalOnlyHandler() gin.HandlerFunc {
return func(c *gin.Context) {
host, _, err := net.SplitHostPort(c.Request.RemoteAddr)
if err != nil {
host = c.ClientIP()
}
if host != "127.0.0.1" && host != "::1" {
vo.Fail("local access only", c)
c.AbortWithStatus(http.StatusForbidden)
return
}
c.Next()
}
}
+4 -2
View File
@@ -5,12 +5,14 @@ import (
"hy2xs-admin/controller"
)
func initHysteria2AuthRouter(hysteria2Api *gin.RouterGroup) {
func initHysteria2MachineAuthRouter(hysteria2Api *gin.RouterGroup) {
hysteria2 := hysteria2Api.Group("/hysteria2")
{
hysteria2.POST("/auth", controller.Hysteria2Auth)
}
}
func initHysteria2SubscribeRouter(hysteria2Api *gin.RouterGroup) {
hysteria2Api.GET("/:conPass", controller.Hysteria2Subscribe)
}
+15 -14
View File
@@ -8,34 +8,35 @@ import (
)
func Router(router *gin.Engine, huiWebContext *string) {
// global context
relativePath := "/"
if huiWebContext != nil && strings.HasPrefix(*huiWebContext, "/") {
relativePath = *huiWebContext
}
globalGroup := router.Group(relativePath)
{
globalGroup.Use(middleware.FilterHandler(), middleware.LogHandler(), middleware.RateLimiterHandler())
frontend.InitFrontend(router, relativePath)
globalGroup := router.Group(relativePath)
machineApi := globalGroup.Group("/hui")
machineApi.Use(middleware.LocalOnlyHandler(), middleware.LogHandler())
initHysteria2MachineAuthRouter(machineApi)
authApi := globalGroup.Group("/hui")
{
authApi.Use(middleware.FilterHandler(), middleware.LogHandler(), middleware.RateLimiterHandler())
initAuthRouter(authApi)
initHysteria2AuthRouter(authApi)
}
globalGroup.Use(middleware.JWTHandler())
globalGroup.Use(middleware.AdminHandler())
initHysteria2SubscribeRouter(authApi)
huiAdminApi := globalGroup.Group("/hui")
{
huiAdminApi.Use(
middleware.FilterHandler(),
middleware.LogHandler(),
middleware.RateLimiterHandler(),
middleware.JWTHandler(),
middleware.AdminHandler(),
)
initAccountAdminRouter(huiAdminApi)
initConfigRouter(huiAdminApi)
initHysteria2Router(huiAdminApi)
initLogRouter(huiAdminApi)
initMonitorRouter(huiAdminApi)
}
}
}
+4 -3
View File
@@ -123,9 +123,10 @@ project/
- целевая установка должна брать его с 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.
- версия Hysteria2 фиксируется в metadata install package (`metadata/hysteria.version`);
- URL артефакта и SHA256 фиксируются в metadata install package (`metadata/hysteria.url`, `metadata/hysteria.sha256`);
- runtime `reconfigure` не обновляет и не откатывает бинарник Hysteria2;
- install flow валидирует SHA256 и фактическую версию установленного бинарника.
## Инварианты
+3 -2
View File
@@ -22,8 +22,9 @@ Hysteria2 — основной транспортный компонент се
- по умолчанию install layer тянет **свежий upstream release / install source**
- фактически установленная версия обязательно записывается в `post-install.env`
- поддерживаются политики `latest | vX.Y.Z` через `HY2XS_HYSTERIA_VERSION`
- при `vX.Y.Z` install обязан валидировать соответствие фактически установленной версии
- бинарник Hysteria2 устанавливается только на этапе `install`
- версия, URL и SHA256 фиксируются в metadata install package
- `reconfigure` не обновляет и не откатывает бинарник Hysteria2
## Платформа
+3 -1
View File
@@ -78,7 +78,7 @@
2. Проверяет базовые зависимости и install context.
3. Создаёт каталоги установки.
4. Разворачивает bundled HY2XS admin.
5. Скачивает installer Hysteria2 в temp-файл и выполняет install с policy `latest|vX.Y.Z`.
5. Скачивает pinned Hysteria2 binary из package metadata, проверяет SHA256 и выполняет install.
6. Генерирует Hysteria config.
7. Создаёт systemd unit для Hysteria.
8. Создаёт systemd unit для HY2XS admin.
@@ -123,6 +123,8 @@
- TLS modes: `acme | file | self_signed_dev`;
- `trafficStats.secret` отдельный от `JWT_SECRET`;
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
- версия/URL/SHA256 Hysteria берутся из metadata install package;
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой.
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
## Что не реализовывать
+4 -1
View File
@@ -20,7 +20,10 @@
- какую фактическую версию оркестратор установил
- что записано в `HY2_VERSION`
- не связано ли поведение со свежим upstream release
- какая policy была в `HY2XS_HYSTERIA_VERSION` (`latest|vX.Y.Z`)
- какая версия Hysteria зафиксирована в metadata установленного пакета
Для обновления бинарника Hysteria2 используйте новый release install package.
Изменение runtime env не обновляет бинарник Hysteria2.
### 4. Оркестратор — Bun/TypeScript, но target не билдит его
Если проблема в install flow, сначала смотреть:
@@ -1,41 +0,0 @@
HY2XS implementation plan
Назначение
Этот пакет фиксирует полный план реализации системы HY2XS на основе ранее согласованных docs, но уже в прикладной форме: что именно делать, в каком порядке, как разложить проект и какие границы у каждого слоя.
Зафиксированные решения
1. Название всей системы: HY2XS.
2. Название панели: HY2XS admin.
3. Серверный транспорт: ванильная Hysteria2 из official upstream во время установки.
4. UI: собственный fork H UI внутри проекта, поставляется вместе с пакетом.
5. Оркестратор: install-only, только под чистый Debian 12.
6. Стек оркестратора: Bun + TypeScript.
7. Builder layer: отдельно, локально, вне сервера.
8. Update / rollback / uninstall: вне scope.
9. UI переводим на русский или русский/английский, если двуязычность реализуется быстро и без раздувания scope.
10. Из UI вырезаем ссылки на оригинальный репозиторий, встроенный update-flow и любое поведение, завязанное на внешний upstream H UI.
11. Telegram-бот, backend выдачи доступа, remote profiles и похожий access layer — вне scope этого плана.
Состав плана
01-scope-and-boundaries.txt
02-target-repo-structure.txt
03-builder-layer.txt
04-runtime-package-layout.txt
05-orchestrator-install-flow.txt
06-hysteria-runtime-layer.txt
07-hy2xs-admin-fork-plan.txt
08-localization-and-rebranding.txt
09-access-layer-out-of-scope.txt
10-post-install-env-and-config-policy.txt
11-testing-acceptance-and-smoke.txt
12-phased-roadmap.txt
13-task-breakdown-checklist.txt
Что считать результатом
Результат этой работы — не абстрактные рассуждения, а один репозиторий/проект HY2XS, внутри которого:
- есть локальный builder;
- есть install-only orchestrator на Bun/TypeScript;
- есть встроенный fork HY2XS admin;
- есть шаблоны и unit-файлы;
- есть единый пакет для переноса на чистый Debian 12;
- сервер после установки готов к работе с Hysteria2, UI и базовым server environment.
@@ -1,46 +0,0 @@
HY2XS: scope and boundaries
1. Что входит в реализацию
- Локальный builder layer.
- Итоговый install package.
- Install-only orchestrator для чистого Debian 12.
- Оркестратор на Bun + TypeScript.
- Vanilla Hysteria2 runtime, скачиваемая с official upstream во время установки.
- Встроенный fork панели HY2XS admin.
- Systemd unit-файлы.
- Базовый nftables baseline.
- post-install.env.
- Документация и acceptance checks.
2. Что не входит в реализацию
- Telegram-бот.
- Backend/контур для remote profiles и выдачи ключей.
- Update manager.
- Rollback manager.
- Uninstall.
- Поддержка грязных или давно живущих серверов.
- Target-side build.
- Docker baseline.
- Multi-node и cluster-архитектура.
- Port hopping в первой версии.
- Автоматическая миграция старых H UI состояний.
- Полноценная собственная клиентская программа.
3. Что считаем правильной эксплуатационной моделью
- Сборка выполняется локально.
- На сервер переносится только готовый пакет.
- Сервер выполняет только первичную установку и базовую настройку.
- Если сервер сломан или состояние стало непрозрачным, штатный путь — переустановка ОС и повторный install.
4. Источники истины по слоям
- Runtime transport: Hysteria2 config + runtime state.
- UI layer: наш fork HY2XS admin.
- Install facts: /etc/hysteria/post-install.env.
- Install logic: исходники оркестратора на Bun/TypeScript + собранный install-артефакт.
5. Главные архитектурные запреты
- Не скачивать HY2XS admin с внешнего upstream на target.
- Не компилировать UI на target.
- Не выполнять `bun install` / transpile / compile оркестратора на target.
- Не пытаться сделать оркестратор инструментом полного жизненного цикла.
- Не смешивать install baseline и access platform в одном scope.
@@ -1,95 +0,0 @@
HY2XS: target repo structure
Цель
Структура должна жёстко разделять builder, runtime package, fork UI, документацию и server-side install logic.
Рекомендуемая структура
project/
├── README.md
├── docs/
│ ├── architecture/
│ ├── implementation/
│ └── operations/
├── builder/
│ ├── build.sh
│ ├── lib/
│ │ ├── common.sh
│ │ ├── package.sh
│ │ ├── ui.sh
│ │ └── verify.sh
│ ├── manifests/
│ │ └── package.manifest
│ └── output/
├── package/
│ ├── install.sh
│ ├── orchestrator/
│ │ └── hy2xs-orchestrator
│ ├── templates/
│ │ ├── hysteria/
│ │ ├── nftables/
│ │ └── env/
│ ├── systemd/
│ │ ├── hysteria-server.service
│ │ └── hy2xs-admin.service
│ ├── ui/
│ │ └── hy2xs-admin/
│ ├── docs/
│ └── metadata/
│ ├── package.version
│ ├── package.build_id
│ └── checksums.txt
├── orchestrator/
│ ├── package.json
│ ├── bun.lock
│ ├── tsconfig.json
│ ├── src/
│ │ ├── cli.ts
│ │ ├── commands/
│ │ │ └── install.ts
│ │ ├── steps/
│ │ │ ├── preflight.ts
│ │ │ ├── deps.ts
│ │ │ ├── filesystem.ts
│ │ │ ├── hysteria.ts
│ │ │ ├── ui.ts
│ │ │ ├── systemd.ts
│ │ │ ├── firewall.ts
│ │ │ ├── env.ts
│ │ │ └── smoke.ts
│ │ ├── lib/
│ │ └── types/
│ └── dist/
├── ui/
│ └── hy2xs-admin-fork/
│ ├── upstream-base/
│ ├── app/
│ ├── assets/
│ ├── locales/
│ │ ├── ru/
│ │ └── en/
│ ├── branding/
│ ├── patches/
│ └── BUILD_NOTES.md
├── config/
│ └── hy2xs.env
└── dist/
└── hy2xs-install-<version>.tar.gz
Что важно
1. builder/ и orchestrator/ разделены.
2. package/ — staging area и состав будущего install package.
3. orchestrator/ — исходники install-only оркестратора на Bun/TypeScript.
4. package/orchestrator/ — уже собранный install-артефакт, а не исходники для target-side build.
5. ui/hy2xs-admin-fork/ — постоянный исходник нашего форка.
6. dist/ — только финальные артефакты.
Минимальная допустимая упрощённая структура
Если хочешь не раздувать проект на старте, можно сократить до:
- builder/
- orchestrator/
- package/
- ui/hy2xs-admin-fork/
- docs/
- dist/
Но логическое разделение всё равно должно сохраниться.
@@ -1,64 +0,0 @@
HY2XS: builder layer plan
Цель builder layer
На Debian 12 amd64 build host собрать один чистый install package, который можно перенести на чистый Debian 12 без target-side build.
Технологический выбор
Baseline packaging: shell-first.
- Основной packaging pipeline: sh/bash.
- Оркестратор при этом пишется на Bun + TypeScript.
- Builder на Debian 12 amd64 собирает install-артефакт оркестратора.
- На target не должно быть обязательного JS/TS toolchain шага.
Задачи builder layer
1. Проверка структуры репозитория.
2. Проверка наличия обязательных файлов пакета.
3. Подготовка HY2XS admin fork к поставке.
4. Локальная сборка оркестратора из Bun/TypeScript.
5. Копирование UI-артефактов в package staging.
6. Копирование templates, systemd units, docs и examples.
7. Генерация metadata: version, build_id, checksums.
8. Упаковка итогового архива.
Порядок реализации builder
Этап 1.
- Создать tools/build/build.sh.
- Создать tools/build/lib/common.sh.
- Создать tools/build/lib/verify.sh.
- Создать tools/build/lib/package.sh.
- Создать tools/build/lib/deps.sh.
- Создать tools/build/README.md.
Production-дополнение.
- Проверять Debian 12 amd64.
- Самостоятельно доставлять apt build-зависимости.
- Проверять и фиксировать версии Go/Bun/Node.js/pnpm.
- Использовать локальный управляемый toolchain при несовпадении версий.
Этап 2.
- Реализовать очистку package staging directory.
- Реализовать копирование package/ skeleton.
- Реализовать сборку `orchestrator/` и копирование артефакта в `package/orchestrator/`.
- Реализовать копирование ui/hy2xs-admin-fork в package/ui/hy2xs-admin.
Этап 3.
- Реализовать запись package.version и package.build_id.
- Реализовать checksums.txt.
- Реализовать создание dist/hy2xs-install-<version>.tar.gz.
Этап 4.
- Добавить builder smoke-проверку: архив собрался, в нём есть install.sh, оркестратор, UI, unit-файлы, templates.
Что builder не делает
- Не качает Hysteria2.
- Не выполняет серверные действия.
- Не меняет production state.
- Не содержит uninstall/update логики.
- Не включает access/bot backend в baseline package.
Acceptance criteria
- Одна команда локально создаёт переносимый install package.
- В package нет builder scripts.
- В package есть встроенный HY2XS admin.
- В package есть собранный Bun/TypeScript orchestrator.
- package можно передать на сервер без дополнительной сборки.
@@ -1,50 +0,0 @@
HY2XS: runtime package layout
Цель
Зафиксировать состав итогового install package, который должен попасть на target machine.
Состав пакета
1. install entrypoint
- install.sh
2. orchestrator artifact
- hy2xs-orchestrator
3. templates
- Hysteria config template
- nftables template
- post-install.env template
4. systemd units
- hysteria-server.service
- hy2xs-admin.service
5. bundled UI
- HY2XS admin runtime files
- статические ассеты
- локали
- branding assets
6. metadata
- package.version
- package.build_id
- checksums.txt
7. docs/config
- короткий README по установке
- canonical runtime source config (hy2xs.env)
Что не должно быть в runtime package
- builder/
- git history
- upstream remote URLs H UI
- update scripts панели
- ссылки на оригинальный бренд/репозиторий H UI в интерфейсе и документации пакета
- target-side dependency installation для оркестратора
- Telegram/access backend как обязательная часть baseline package
Что должен делать install.sh
- быть единой точкой входа на target
- вызывать собранный артефакт оркестратора
- логировать запуск
- завершаться с понятным кодом ошибки
@@ -1,86 +0,0 @@
HY2XS: orchestrator install flow
Главная роль
Install-only orchestrator ставит систему на чистый Debian 12 и больше ничего не обещает.
Технологическая фиксация
- Исходники оркестратора: Bun + TypeScript.
- Сборка оркестратора: локально, builder layer'ом.
- Исполнение на target: готовый install-артефакт через thin wrapper.
Рекомендуемый порядок модулей
1. preflight
- проверить Debian 12
- проверить root/sudo context
- проверить, что порт свободен
- проверить, что не существует конфликтующей старой установки
2. deps
- установить системные зависимости
- проверить наличие systemd, nft, curl/wget, tar, openssl и прочего необходимого минимума
3. filesystem
- создать системного пользователя hysteria
- создать каталоги:
/etc/hysteria
/var/lib/hysteria
/opt/hy2xs-admin
/var/lib/hy2xs-admin
/var/log/hy2xs
/usr/local/lib/hy2xs
4. bundled UI deploy
- разложить HY2XS admin из пакета
- назначить владельца и права
5. Hysteria install
- скачать свежую Hysteria2 из official upstream
- установить бинарь в согласованный путь
- зафиксировать фактическую версию
6. config generation
- сгенерировать /etc/hysteria/config.yaml
- сгенерировать параметры obfs
- записать домен, порт, bandwidth policy
7. systemd
- разложить hysteria-server.service
- разложить hy2xs-admin.service
- daemon-reload
- enable services
8. firewall
- применить baseline nftables
- не терять SSH
9. post-install.env
- создать /etc/hysteria/post-install.env
- записать package version, build id, orchestrator stack/build, Hysteria version, UI build info, порты и пути
10. start and smoke
- стартовать сервисы
- проверить systemctl is-active
- проверить UDP listen
- проверить доступность UI
Политика ошибок
- Любой конфликт неизвестного старого состояния = stop with error.
- Никакой сложной автомиграции.
- Ошибки должны быть текстовыми и пригодными для диагностики.
CLI baseline
Допустимые флаги:
- --non-interactive
- --port
- --ssh-port
- --skip-firewall
- --skip-start
- --ui-port
- --ui-bind-host
Что не реализовывать
- update subcommands
- rollback subcommands
- uninstall subcommands
- reconcile logic
- bot/access-delivery subcommands
@@ -1,42 +0,0 @@
HY2XS: Hysteria runtime layer plan
Цель
Сделать серверный транспорт предсказуемым и полностью ванильным со стороны Hysteria2.
Решения
1. Hysteria2 не форкается.
2. Скачивается во время установки с official upstream.
3. Используется одна baseline-схема без port hopping.
Базовый runtime policy
- Listen: 0.0.0.0:<PORT>
- Transport: QUIC/UDP
- IPv4-only
- obfs.type: salamander
- obfs.password: генерируется при установке
- bandwidth.up: 50 Mbps
- bandwidth.down: 50 Mbps
- ignoreClientBandwidth: false
Что надо реализовать
1. Шаблон server config.
2. Генератор переменных для шаблона.
3. Проверку валидности конфига перед запуском.
4. Фиксацию фактической версии Hysteria в post-install.env.
Нужно зафиксировать в коде
- Единый путь к конфигу.
- Единый путь к бинарю.
- Единый путь к data dir.
- Единый набор сетевых переменных.
Что не смешивать
- Не смешивать UI-состояние и transport-конфиг в одном месте.
- Не полагаться на host TCP BBR как на главный механизм управления Hysteria.
- Не смешивать install runtime layer и access/delivery layer.
Acceptance criteria
- Сервис стартует через systemd.
- Конфиг читается без ошибки.
- Тестовый совместимый клиент подключается.
- Политика speed limit соответствует согласованной серверной и клиентской конфигурации.
@@ -1,60 +0,0 @@
HY2XS admin: fork implementation plan
Цель
Превратить H UI в наш поддерживаемый внутренний компонент HY2XS admin.
Главные задачи
1. Забрать исходный UI в собственный fork внутри проекта.
2. Отвязать интерфейс от оригинального бренда, ссылок и update-потока.
3. Подготовить UI к поставке внутри install package.
4. Сделать UI отдельным systemd-сервисом.
Обязательные изменения в форке
A. Ребрендинг
- Новое имя продукта: HY2XS.
- Новое имя панели: HY2XS admin.
- Заменить названия в заголовках, логотипах, footer, title, README панели, системных сообщениях.
B. Удаление upstream-зависимостей
- Удалить ссылки на оригинальный GitHub/Git repository.
- Удалить кнопки, меню и тексты, предлагающие update из внешнего upstream.
- Удалить или скрыть экран/логику самопроверки обновлений, если она встроена.
- Удалить любой текст вида "official repo", "check updates", "new version available" относительно оригинального H UI.
C. Локализация
- Минимум: полный русский.
- Предпочтительно: русский + английский, если это делается быстро через словари/locale files без переписывания UI-логики.
- Китайский как основная locale больше не нужен для поставки HY2XS.
D. Packaging readiness
- У UI должен быть предсказуемый build/runtime output.
- UI должен работать из локально поставленных файлов, без git clone и без target-side npm/yarn/pnpm build.
Слои задач по форку
Этап 1. Первичная инвентаризация
- Найти все brand strings.
- Найти все repo/update ссылки.
- Найти все locales.
- Найти все места, где UI показывает своё имя.
Этап 2. Ребрендинг
- Переименовать UI в HY2XS admin.
- Подменить logo/title/favicon, если есть.
- Обновить системные тексты.
Этап 3. Вырезание update-flow
- Удалить пункты меню обновлений.
- Удалить backend/frontend обработчики update-функций.
- Удалить внешние endpoints и тексты об обновлениях.
Этап 4. Локализация
- Вынести строки в locale-файлы, если это ещё не сделано.
- Создать ru locale.
- Опционально создать en locale.
- Проверить, что UI не содержит жёстко зашитых китайских строк.
Этап 5. Runtime packaging
- Подготовить результат, который builder просто копирует в package/ui/hy2xs-admin.
Техническое правило
Source-of-truth install lifecycle остаётся у оркестратора. HY2XS admin не должен расширять scope установки и не должен превращаться в update-manager.
@@ -1,44 +0,0 @@
HY2XS: localization and branding plan
Цель
Сделать систему цельной по названию, языку интерфейса и операторскому UX.
1. Названия
- Система: HY2XS
- Панель: HY2XS admin
- Service names:
- hysteria-server.service
- hy2xs-admin.service
- Пути и package naming должны использовать hy2xs как canonical slug
2. Где переименовывать
- UI titles
- navbar/header/footer
- login page
- browser tab title
- package metadata
- docs
- install output
- post-install.env package name
- systemd description lines
3. Политика языка
Минимум для первой версии:
- русский интерфейс панели
- русский install output/docs для оператора
Предпочтительная быстрая модель:
- RU по умолчанию
- EN как дополнительная locale, если её можно добавить быстро
- никакой сложной i18n-платформы, если в исходном UI уже есть простой словарный механизм
4. Что удалить
- китайские дефолтные тексты в видимых местах
- оригинальные названия продукта
- упоминания оригинальной панели как управляемого внешнего продукта
5. Acceptance criteria
- В UI нет китайского языка в обычном операторском пути.
- В UI нет оригинального названия H UI.
- Во всех ключевых местах виден бренд HY2XS / HY2XS admin.
- Если включён EN, переключение не ломает layout.
@@ -1,29 +0,0 @@
HY2XS: access layer out of scope
Цель
Явно убрать из плана всё, что не относится к install-only baseline.
Что вне scope
- Telegram-бот.
- Backend выдачи ключей.
- Remote profile publishing.
- Deep links.
- Billing/подписки.
- Self-service кабинет.
- Любой обязательный пользовательский delivery layer.
Что остаётся в scope
- Установка Hysteria2.
- Установка HY2XS admin.
- Настройка server config.
- Настройка systemd.
- Настройка firewall.
- Генерация post-install.env.
Почему это важно
Если оставить access layer внутри baseline-плана, документация начинает неверно описывать продукт как платформу выдачи доступа. По факту здесь нужен только оркестратор установки и базовой серверной конфигурации.
Минимальный deliverable
- Один install package.
- Один install-only orchestrator.
- Один reproducible install flow для чистого Debian 12.
@@ -1,53 +0,0 @@
HY2XS: post-install env and config policy
Цель
Оставить после установки один прозрачный reference file с фактами развёртывания.
Путь
/etc/hysteria/post-install.env
Что обязательно писать
Deploy/package:
- PACKAGE_NAME=HY2XS
- PACKAGE_VERSION
- PACKAGE_BUILD_ID
- DEPLOY_TIMESTAMP
- DEPLOY_TARGET_OS=debian-12
Orchestrator:
- ORCH_SOURCE_STACK=bun-typescript
- ORCH_BUILD_MODE
- ORCH_BUILD_ID
- ORCH_ENTRYPOINT
Network/common:
- DEPLOY_DOMAIN
- SSH_PORT
Hysteria:
- HY2_SOURCE=official-upstream
- HY2_VERSION
- HY2_LISTEN_HOST
- HY2_PORT
- HY2_OBFS_TYPE=salamander
- HY2_OBFS_PASSWORD
- HY2_BANDWIDTH_UP_Mbps=50
- HY2_BANDWIDTH_DOWN_Mbps=50
- HY2_IGNORE_CLIENT_BANDWIDTH=false
- HY2_CONFIG_PATH
HY2XS admin:
- HUI_ENABLED=true
- HUI_FORK_REF
- HUI_BUILD_ID
- HUI_BIND_HOST
- HUI_PORT
- HUI_INSTALL_DIR
- HUI_DATA_DIR
- HUI_BRAND=HY2XS admin
- HUI_DEFAULT_LOCALE=ru
Правило использования
- post-install.env — reference file.
- Изменение этого файла само по себе не должно считаться применением runtime-изменений.
- Любые ручные правки оператора должны потом осознанно переноситься в реальные рабочие конфиги и применяться документированным способом.
@@ -1,48 +0,0 @@
HY2XS: testing, acceptance and smoke plan
1. Builder checks
- package успешно собирается локально
- в архиве есть UI, install entrypoint, orchestrator artifact, templates, units, metadata
- нет builder мусора в финальном package
2. Target install checks
- чистый Debian 12
- install flow проходит без ручной сборки
- Hysteria скачана с upstream
- HY2XS admin разложен из bundled package
- созданы оба systemd unit
- создан post-install.env
- post-install.env фиксирует Bun/TypeScript orchestrator stack
3. Runtime checks
- systemctl is-active hysteria-server = active
- systemctl is-active hy2xs-admin = active
- UDP порт слушается
- SSH не потерян после firewall
- UI доступен на заданном bind host/port
- тестовый совместимый клиент подключается
4. Branding/localization checks
- в UI нет китайских строк на основных маршрутах
- в UI нет ссылок на оригинальный репозиторий
- в UI нет update-кнопок и update-текстов
- бренд везде HY2XS / HY2XS admin
5. Negative checks
- порт уже занят
- старое состояние найдено
- неверный домен
- ошибка скачивания Hysteria2
- конфликтующие файлы UI
- отсутствует root context
6. Acceptance definition
Система считается реализованной, когда:
- локальный builder собирает install package;
- target install-only flow разворачивает систему на чистом Debian 12;
- HY2XS admin работает как встроенный форк;
- Hysteria2 получена с official upstream;
- оркестратор зафиксирован как Bun/TypeScript stack;
- UI русифицирован и ребрендирован;
- upstream update/repo logic удалена из UI;
- install acceptance не зависит от bot/access layer.
@@ -1,59 +0,0 @@
HY2XS: phased roadmap
Фаза 1. Skeleton and structure
Результат:
- создана структура репозитория;
- созданы каталоги builder/, orchestrator/, package/, ui/hy2xs-admin-fork/, docs/, dist/.
Фаза 2. Builder baseline
Результат:
- build.sh собирает package staging;
- builder локально собирает Bun/TypeScript orchestrator artifact;
- генерируется итоговый архив;
- builder проверяет минимальную целостность пакета.
Фаза 3. Install-only orchestrator baseline
Результат:
- чистый Debian 12 проходит preflight;
- раскладываются каталоги;
- создаётся post-install.env;
- скачивается Hysteria2;
- создаются unit-файлы.
Фаза 4. Hysteria runtime baseline
Результат:
- Hysteria2 стартует через systemd;
- слушает нужный UDP-порт;
- firewall baseline применён;
- smoke checks проходят.
Фаза 5. HY2XS admin fork baseline
Результат:
- UI форк находится в проекте;
- поставляется в пакете;
- стартует отдельным сервисом;
- не содержит target-side build.
Фаза 6. Rebranding and de-upstreaming
Результат:
- UI переименован в HY2XS admin;
- удалены ссылки на оригинальный repo;
- удалены update-кнопки и update-flow;
- бренд HY2XS отражён в docs, install output и metadata.
Фаза 7. Localization
Результат:
- русский интерфейс готов полностью;
- опционально добавлен английский;
- китайские строки не видны оператору.
Фаза 8. Scope cleanup and docs alignment
Результат:
- Telegram/access layer убран из baseline docs;
- в документации зафиксирован стек оркестратора Bun + TypeScript;
- install scope отделён от user delivery scope.
Фаза 9. Final acceptance
Результат:
- весь install flow от builder до рабочего сервера проходит воспроизводимо;
- docs соответствуют фактической реализации.
@@ -1,56 +0,0 @@
HY2XS: task breakdown checklist
A. Repo and structure
[ ] Создать целевую структуру проекта.
[ ] Разнести builder, package, orchestrator, ui и docs.
[ ] Добавить dist/ и metadata policy.
B. Builder
[ ] Реализовать build.sh.
[ ] Реализовать staging сборку пакета.
[ ] Реализовать локальную сборку Bun/TypeScript orchestrator artifact.
[ ] Реализовать checksums и build_id.
[ ] Проверить содержимое архива.
C. Orchestrator
[ ] Реализовать preflight.
[ ] Реализовать deps.
[ ] Реализовать filesystem.
[ ] Реализовать Hysteria install from upstream.
[ ] Реализовать UI deploy from bundled package.
[ ] Реализовать config generation.
[ ] Реализовать systemd units deployment.
[ ] Реализовать nftables baseline.
[ ] Реализовать post-install.env generation.
[ ] Реализовать smoke checks.
D. Hysteria baseline
[ ] Сделать template config.
[ ] Сделать obfs password generation.
[ ] Зафиксировать bandwidth defaults.
[ ] Проверить launch через systemd.
E. HY2XS admin fork
[ ] Инвентаризировать brand strings.
[ ] Инвентаризировать repo/update references.
[ ] Переименовать UI в HY2XS admin.
[ ] Удалить update UI/actions.
[ ] Удалить ссылки на оригинальный repo.
[ ] Подготовить packaged runtime output.
F. Localization
[ ] Вынести строки в локали, если нужно.
[ ] Добавить ru locale.
[ ] Опционально добавить en locale.
[ ] Проверить отсутствие китайского в основных маршрутах.
G. Scope alignment
[ ] Убрать Telegram/access layer из baseline docs.
[ ] Убрать bot/profile assumptions из acceptance.
[ ] Зафиксировать Bun + TypeScript как стек оркестратора.
[ ] Зафиксировать deliverable как install-only server environment.
H. Docs and acceptance
[ ] Обновить docs по факту реализации.
[ ] Прогнать acceptance checklist.
[ ] Зафиксировать финальные инварианты.
@@ -1,8 +0,0 @@
### 💎
"Проблема" с типами в Bun вообще не ваша забота на этапе создания бинарника. При сборке они просто вырезаются. Ваш план:
1. Пишете логику оркестратора на TS, как мы обсуждали.
2. Перед финальной сборкой прогоняете `bunx tsc --noEmit` для проверки типов, если хотите перестраховаться.
3. Собираете командой `bun build ./src/main.ts --compile --outfile orchestrator`.
4. Запускаете на любом сервере: `./orchestrator`.
+11 -7
View File
@@ -1,4 +1,3 @@
import { randomBytes } from "node:crypto";
import type { InstallContext, InstallOptions } from "../types/context";
import { exists, readText, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
@@ -16,10 +15,6 @@ import { applyFirewall } from "../steps/firewall";
import { writePostInstallEnv } from "../steps/env";
import { smoke } from "../steps/smoke";
function secret(): string {
return randomBytes(24).toString("base64url");
}
export async function install(options: InstallOptions): Promise<void> {
const hasSourceConfig = options.sourceConfigPath ? await exists(options.sourceConfigPath) : false;
if (options.sourceConfigPath && !hasSourceConfig) {
@@ -30,15 +25,22 @@ export async function install(options: InstallOptions): Promise<void> {
const config = parseRuntimeEnv(sourceConfigRaw);
const context: InstallContext = {
mode: "install",
options,
config,
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
installDate: new Date().toISOString(),
hysteriaAuthPassword: secret(),
hysteriaVersion: "unknown"
hysteriaVersion: "unknown",
hysteriaTargetVersion: await readPackageValue(options.packageDir, "hysteria.version", ""),
hysteriaArtifactUrl: await readPackageValue(options.packageDir, "hysteria.url", ""),
hysteriaArtifactSha256: await readPackageValue(options.packageDir, "hysteria.sha256", "")
};
if (!context.hysteriaTargetVersion || !context.hysteriaArtifactUrl || !context.hysteriaArtifactSha256) {
throw new Error("missing Hysteria lock metadata in package: hysteria.version/hysteria.url/hysteria.sha256");
}
step("preflight");
await preflight(context);
step("system dependencies");
@@ -48,6 +50,8 @@ export async function install(options: InstallOptions): Promise<void> {
step("write runtime env");
await runVisible`mkdir -p /etc/hy2xs`;
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
await runVisible`chown root:root ${options.runtimeConfigPath}`;
await runVisible`chmod 0600 ${options.runtimeConfigPath}`;
step("bundled UI");
await deployUi(context);
step("Hysteria2 upstream install");
+4 -2
View File
@@ -46,13 +46,13 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
const configRaw = await readText(options.sourceConfigPath);
const config = parseRuntimeEnv(configRaw);
const context: ReconfigureContext & { packageVersion: string; packageBuildId: string; installDate: string; hysteriaAuthPassword: string; hysteriaVersion: string } = {
const context: ReconfigureContext = {
mode: "reconfigure",
options,
config,
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
installDate: new Date().toISOString(),
hysteriaAuthPassword: "managed-by-ui-auth",
hysteriaVersion: await readInstalledHysteriaVersion()
};
@@ -81,6 +81,8 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
await applyFirewall(context);
step("write env artifacts");
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
await runVisible`chown root:root ${options.runtimeConfigPath}`;
await runVisible`chmod 0600 ${options.runtimeConfigPath}`;
await writePostInstallEnv(context);
step("smoke checks");
await smoke(context);
-4
View File
@@ -148,8 +148,6 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
false
),
hysteriaConfigPath: env.HY2XS_HYSTERIA_CONFIG_PATH || "/etc/hysteria/config.yaml",
allowLatestHysteria: parseBool("HY2XS_ALLOW_LATEST_HYSTERIA", env.HY2XS_ALLOW_LATEST_HYSTERIA, false),
hysteriaVersionPolicy: env.HY2XS_HYSTERIA_VERSION || "v2.6.0",
installDir: env.HY2XS_INSTALL_DIR || "/opt/hy2xs-admin",
dataDir: env.HY2XS_DATA_DIR || "/var/lib/hy2xs-admin",
logDir: env.HY2XS_LOG_DIR || "/var/log/hy2xs",
@@ -240,8 +238,6 @@ export function renderRuntimeEnv(config: RuntimeConfig): string {
`HY2XS_HYSTERIA_BANDWIDTH_DOWN=${config.hysteriaBandwidthDown}`,
`HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=${config.hysteriaIgnoreClientBandwidth}`,
`HY2XS_HYSTERIA_CONFIG_PATH=${config.hysteriaConfigPath}`,
`HY2XS_ALLOW_LATEST_HYSTERIA=${config.allowLatestHysteria}`,
`HY2XS_HYSTERIA_VERSION=${config.hysteriaVersionPolicy}`,
`HY2XS_INSTALL_DIR=${config.installDir}`,
`HY2XS_DATA_DIR=${config.dataDir}`,
`HY2XS_LOG_DIR=${config.logDir}`
+2 -3
View File
@@ -1,8 +1,8 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
export async function generateConfig(context: InstallContext): Promise<void> {
export async function generateConfig(context: RuntimeContext): Promise<void> {
const tlsAcmeBlock = context.config.tlsMode === "acme"
? `acme:\n domains:\n - ${context.config.domain}\n email: ${context.config.acmeEmail}\n ca: letsencrypt\n dir: /var/lib/hysteria/acme\n listenHost: 0.0.0.0\n type: ${context.config.acmeType}`
: "";
@@ -14,7 +14,6 @@ export async function generateConfig(context: InstallContext): Promise<void> {
const rendered = renderTemplate(template, {
HYSTERIA_BIND_HOST: context.config.hysteriaBindHost,
HYSTERIA_PORT: context.config.hysteriaPort,
HYSTERIA_AUTH_PASSWORD: context.hysteriaAuthPassword,
HYSTERIA_OBFS_PASSWORD: context.config.hysteriaObfsPassword,
HYSTERIA_API_HOST: context.config.hysteriaTrafficStatsHost,
HYSTERIA_API_PORT: context.config.hysteriaTrafficStatsPort,
+7 -2
View File
@@ -1,7 +1,8 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
export async function writePostInstallEnv(context: InstallContext): Promise<void> {
export async function writePostInstallEnv(context: RuntimeContext): 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,
@@ -30,6 +31,8 @@ export async function writePostInstallEnv(context: InstallContext): Promise<void
});
await writeText("/etc/hysteria/post-install.env", rendered, 0o600);
await runVisible`chown root:root /etc/hysteria/post-install.env`;
await runVisible`chmod 0600 /etc/hysteria/post-install.env`;
await writeText(
context.config.bootstrapAdminSecretPath,
[
@@ -39,4 +42,6 @@ export async function writePostInstallEnv(context: InstallContext): Promise<void
].join("\n") + "\n",
0o600
);
await runVisible`chown root:root ${context.config.bootstrapAdminSecretPath}`;
await runVisible`chmod 0600 ${context.config.bootstrapAdminSecretPath}`;
}
+8 -5
View File
@@ -4,10 +4,13 @@ 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`id -u hy2xs-admin >/dev/null 2>&1 || useradd --system --home ${context.config.dataDir} --shell /usr/sbin/nologin hy2xs-admin`;
await runVisible`mkdir -p /etc/hy2xs /etc/hysteria /var/lib/hysteria ${context.config.installDir} ${context.config.dataDir} ${context.config.logDir} /usr/local/lib/hy2xs /etc/nftables.d`;
await runVisible`chown -R hysteria:hysteria /etc/hysteria /var/lib/hysteria`;
await runVisible`chown -R root:root ${context.config.installDir}`;
await runVisible`install -d -m 0700 -o root -g root /etc/hy2xs`;
await runVisible`install -d -m 0755 -o root -g root /etc/hysteria`;
await runVisible`install -d -m 0750 -o hysteria -g hysteria /var/lib/hysteria`;
await runVisible`install -d -m 0750 -o hy2xs-admin -g hy2xs-admin ${context.config.dataDir}`;
await runVisible`install -d -m 0750 -o hy2xs-admin -g hy2xs-admin ${context.config.logDir}`;
await runVisible`install -d -m 0755 -o root -g root ${context.config.installDir}`;
await runVisible`install -d -m 0755 -o root -g root /usr/local/lib/hy2xs`;
await runVisible`install -d -m 0755 -o root -g root /etc/nftables.d`;
await runVisible`chmod -R go-w ${context.config.installDir}`;
await runVisible`chown -R hy2xs-admin:hy2xs-admin ${context.config.dataDir} ${context.config.logDir}`;
await runVisible`chmod 700 /etc/hy2xs`;
}
+2 -2
View File
@@ -1,4 +1,4 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { exists, readText, renderTemplate, writeText } from "../lib/fs";
import { fail, info } from "../lib/log";
import { runVisible } from "../lib/process";
@@ -27,7 +27,7 @@ function isSafeNftablesEntrypoint(content: string): boolean {
return effective === "flush ruleset";
}
export async function applyFirewall(context: InstallContext): Promise<void> {
export async function applyFirewall(context: RuntimeContext): Promise<void> {
if (context.options.skipFirewall || !context.config.firewallEnabled) {
info("firewall skipped by flag");
return;
+11 -20
View File
@@ -9,41 +9,32 @@ function normalizeInstalledVersion(raw: string): string {
return raw.trim();
}
function validateVersionPolicy(value: string): void {
function validatePinnedVersion(value: string): void {
if (/^v\d+\.\d+\.\d+$/.test(value)) {
return;
}
throw new Error(`invalid HY2XS_HYSTERIA_VERSION policy: ${value}`);
throw new Error(`invalid pinned Hysteria version in package metadata: ${value}`);
}
export async function installHysteria(context: InstallContext): Promise<void> {
const policy = context.config.hysteriaVersionPolicy;
if (policy === "latest" && !context.config.allowLatestHysteria) {
throw new Error("HY2XS_HYSTERIA_VERSION=latest is not allowed in production; pin vX.Y.Z or set HY2XS_ALLOW_LATEST_HYSTERIA=true");
}
validateVersionPolicy(policy);
validatePinnedVersion(context.hysteriaTargetVersion);
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}`;
const tmp = "/tmp/hy2xs-hysteria-linux-amd64";
if (policy === "latest") {
await runVisible`bash ${scriptPath}`;
} else {
await runVisible`bash ${scriptPath} --version ${policy}`;
}
await runVisible`curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location ${context.hysteriaArtifactUrl} -o ${tmp}`;
await runVisible`test -s ${tmp}`;
await runVisible`printf '%s %s\n' ${context.hysteriaArtifactSha256} ${tmp} | sha256sum -c -`;
await runVisible`install -m 0755 ${tmp} /usr/local/bin/hysteria`;
await runVisible`rm -f ${tmp}`;
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) {
if (installedVersion !== context.hysteriaTargetVersion) {
throw new Error(
`installed Hysteria version mismatch: expected ${policy}, got ${installedVersion}. Review upstream installer env contract.`
`installed Hysteria version mismatch: expected ${context.hysteriaTargetVersion}, got ${installedVersion}`
);
}
await runVisible`rm -f ${scriptPath}`;
}
+13 -4
View File
@@ -1,4 +1,4 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { exists, readText } from "../lib/fs";
import { fail, info } from "../lib/log";
import { run } from "../lib/process";
@@ -21,8 +21,8 @@ async function isUnitActive(unit: string): Promise<boolean> {
}
}
export async function preflight(context: InstallContext): Promise<void> {
const isReconfigure = context.packageVersion === "reconfigure";
export async function preflight(context: RuntimeContext): Promise<void> {
const isReconfigure = context.mode === "reconfigure";
if (process.getuid?.() !== 0) {
fail("installer must run as root");
@@ -33,7 +33,16 @@ export async function preflight(context: InstallContext): Promise<void> {
fail("HY2XS baseline supports only clean Debian 12");
}
if (!(await exists(`${context.options.packageDir}/ui/hy2xs-admin`))) {
if (!(await exists(`${context.options.packageDir}/systemd/hy2xs-admin.service`))) {
fail("missing hy2xs-admin systemd unit in package");
}
if (!(await exists(`${context.options.packageDir}/systemd/hysteria-server.service`))) {
fail("missing hysteria-server systemd unit in package");
}
if (!(await exists(`${context.options.packageDir}/templates/hysteria/config.yaml.tpl`))) {
fail("missing Hysteria config template in package");
}
if (context.mode === "install" && !(await exists(`${context.options.packageDir}/ui/hy2xs-admin/hy2xs-admin`))) {
fail("bundled HY2XS admin is missing from install package");
}
+12 -2
View File
@@ -1,8 +1,8 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { info } from "../lib/log";
import { runHidden, runSecret, runVisible } from "../lib/process";
export async function smoke(context: InstallContext): Promise<void> {
export async function smoke(context: RuntimeContext): Promise<void> {
if (context.options.skipStart) {
info("service start and smoke checks skipped by flag");
return;
@@ -30,6 +30,9 @@ export async function smoke(context: InstallContext): Promise<void> {
await runVisible`sudo -u hysteria test -r /etc/hysteria/config.yaml`;
await runVisible`sudo -u hy2xs-admin test -r /etc/hysteria/config.yaml`;
await runVisible`sudo -u hy2xs-admin test ! -w /etc/hysteria/config.yaml`;
await runVisible`sudo -u hy2xs-admin test ! -r /etc/hy2xs/hy2xs.env`;
await runVisible`sudo -u hy2xs-admin test ! -r /etc/hy2xs/bootstrap-admin.secret`;
await runVisible`sudo -u hysteria test ! -r /etc/hy2xs/bootstrap-admin.secret`;
await runVisible`ss -H -ltn | grep -q '${context.config.uiBindHost}:${context.config.uiPort} '`;
if (context.config.uiBindHost === "127.0.0.1") {
await runVisible`! ss -H -ltn | grep -q '0.0.0.0:${context.config.uiPort} '`;
@@ -42,6 +45,13 @@ export async function smoke(context: InstallContext): Promise<void> {
throw new Error(`unexpected auth response for invalid credentials: ${invalidAuthResponse}`);
}
for (let i = 0; i < 10; i += 1) {
const response = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
if (!/"ok"\s*:\s*false/.test(response)) {
throw new Error(`unexpected auth response during rate-limit smoke: ${response}`);
}
}
const adminConPass = (await runSecret`grep '^ADMIN_CON_PASS=' ${context.config.bootstrapAdminSecretPath} | head -n1 | cut -d= -f2-`).trim();
if (!adminConPass) {
throw new Error("admin connection password is empty in bootstrap secret file");
+2 -2
View File
@@ -1,8 +1,8 @@
import type { InstallContext } from "../types/context";
import type { RuntimeContext } from "../types/context";
import { readText, renderTemplate, writeText } from "../lib/fs";
import { runVisible } from "../lib/process";
export async function deploySystemd(context: InstallContext): Promise<void> {
export async function deploySystemd(context: RuntimeContext): Promise<void> {
const values = {
UI_BIND_HOST: context.config.uiBindHost,
UI_PORT: context.config.uiPort,
+20 -16
View File
@@ -1,4 +1,4 @@
export type InstallOptions = {
export type CommonOptions = {
packageDir: string;
sourceConfigPath: string;
runtimeConfigPath: string;
@@ -7,17 +7,15 @@ export type InstallOptions = {
skipStart: boolean;
};
export type ReconfigureOptions = {
packageDir: string;
sourceConfigPath: string;
runtimeConfigPath: string;
nonInteractive: boolean;
export type InstallOptions = CommonOptions;
export type ReconfigureOptions = CommonOptions & {
dryRun: boolean;
apply: boolean;
skipFirewall: boolean;
skipStart: boolean;
};
export type RunMode = "install" | "reconfigure";
export type TlsMode = "acme" | "file" | "self_signed_dev";
export type RuntimeConfig = {
@@ -54,25 +52,31 @@ export type RuntimeConfig = {
hysteriaBandwidthDown: string;
hysteriaIgnoreClientBandwidth: boolean;
hysteriaConfigPath: string;
allowLatestHysteria: boolean;
hysteriaVersionPolicy: string;
installDir: string;
dataDir: string;
logDir: string;
bootstrapAdminSecretPath: string;
};
export type InstallContext = {
options: InstallOptions;
export type RuntimeContext = {
mode: RunMode;
options: CommonOptions;
config: RuntimeConfig;
packageVersion: string;
packageBuildId: string;
installDate: string;
hysteriaAuthPassword: string;
hysteriaVersion: string;
};
export type ReconfigureContext = {
options: ReconfigureOptions;
config: RuntimeConfig;
export type InstallContext = RuntimeContext & {
mode: "install";
options: InstallOptions;
hysteriaTargetVersion: string;
hysteriaArtifactUrl: string;
hysteriaArtifactSha256: string;
};
export type ReconfigureContext = RuntimeContext & {
mode: "reconfigure";
options: ReconfigureOptions;
};
-2
View File
@@ -32,8 +32,6 @@ HY2XS_HYSTERIA_BANDWIDTH_UP=50 mbps
HY2XS_HYSTERIA_BANDWIDTH_DOWN=50 mbps
HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=false
HY2XS_HYSTERIA_CONFIG_PATH=/etc/hysteria/config.yaml
HY2XS_ALLOW_LATEST_HYSTERIA=false
HY2XS_HYSTERIA_VERSION=v2.6.0
HY2XS_INSTALL_DIR=/opt/hy2xs-admin
HY2XS_DATA_DIR=/var/lib/hy2xs-admin
HY2XS_LOG_DIR=/var/log/hy2xs
+10
View File
@@ -24,6 +24,16 @@ if [ ! -x "$ORCHESTRATOR" ]; then
fail "Missing executable orchestrator artifact: $ORCHESTRATOR"
fi
if [ ! -f "$PACKAGE_DIR/metadata/checksums.txt" ]; then
fail "Missing package checksums: $PACKAGE_DIR/metadata/checksums.txt"
fi
log "verifying package checksums"
(
cd "$PACKAGE_DIR"
sha256sum -c metadata/checksums.txt
)
install -d -m 0755 /usr/local/lib/hy2xs
install -m 0755 "$ORCHESTRATOR" "$ORCHESTRATOR_INSTALL_PATH"
ln -sf "$ORCHESTRATOR_INSTALL_PATH" "$ORCHESTRATOR_SYMLINK"
+3
View File
@@ -0,0 +1,3 @@
HYSTERIA_VERSION=v2.6.0
HYSTERIA_ARTIFACT_URL=https://github.com/apernet/hysteria/releases/download/app%2Fv2.6.0/hysteria-linux-amd64
HYSTERIA_ARTIFACT_SHA256=replace-with-release-sha256
+15
View File
@@ -3,6 +3,7 @@ set -euo pipefail
STAGE_DIR="tools/build/output/hy2xs-install"
ADMIN_BUILD_DIR="tools/build/output/hy2xs-admin-build"
HYSTERIA_LOCK_FILE="tools/build/hysteria-lock.env"
prepare_stage() {
local version="$1"
@@ -62,6 +63,13 @@ write_metadata() {
local version="$1"
local build_id="$2"
[ -f "$HYSTERIA_LOCK_FILE" ] || fail "missing Hysteria lock file: $HYSTERIA_LOCK_FILE"
# shellcheck disable=SC1090
. "$HYSTERIA_LOCK_FILE"
[ -n "${HYSTERIA_VERSION:-}" ] || fail "HYSTERIA_VERSION is required in $HYSTERIA_LOCK_FILE"
[ -n "${HYSTERIA_ARTIFACT_URL:-}" ] || fail "HYSTERIA_ARTIFACT_URL is required in $HYSTERIA_LOCK_FILE"
[ -n "${HYSTERIA_ARTIFACT_SHA256:-}" ] || fail "HYSTERIA_ARTIFACT_SHA256 is required in $HYSTERIA_LOCK_FILE"
{
printf 'name=HY2XS\n'
printf 'version=%s\n' "$version"
@@ -77,8 +85,15 @@ write_metadata() {
printf 'pnpm_version=%s\n' "$($PNPM_BIN --version)"
printf 'hysteria_source=official-upstream\n'
printf 'hysteria_target=linux-amd64\n'
printf 'hysteria_version=%s\n' "$HYSTERIA_VERSION"
printf 'hysteria_artifact_url=%s\n' "$HYSTERIA_ARTIFACT_URL"
printf 'hysteria_artifact_sha256=%s\n' "$HYSTERIA_ARTIFACT_SHA256"
} >"$STAGE_DIR/metadata/package.env"
printf '%s\n' "$HYSTERIA_VERSION" >"$STAGE_DIR/metadata/hysteria.version"
printf '%s\n' "$HYSTERIA_ARTIFACT_URL" >"$STAGE_DIR/metadata/hysteria.url"
printf '%s\n' "$HYSTERIA_ARTIFACT_SHA256" >"$STAGE_DIR/metadata/hysteria.sha256"
(
cd "$STAGE_DIR"
find . -type f ! -path './metadata/checksums.txt' -print0 \