Продакшн рефактор без легаси
This commit is contained in:
@@ -7,7 +7,7 @@ HY2XS — production installer/runtime-manager для развёртывания
|
|||||||
- production builder на Debian 12 amd64 создаёт один переносимый архив;
|
- production builder на Debian 12 amd64 создаёт один переносимый архив;
|
||||||
- install-only оркестратор на Bun + TypeScript поставляется на target как готовый compiled artifact;
|
- install-only оркестратор на Bun + TypeScript поставляется на target как готовый compiled artifact;
|
||||||
- HY2XS admin поставляется в составе пакета как bundled fork;
|
- HY2XS admin поставляется в составе пакета как bundled fork;
|
||||||
- vanilla Hysteria2 скачивается с official upstream во время установки;
|
- vanilla Hysteria2 устанавливается как pinned binary (version/url/sha256 из metadata install package);
|
||||||
- runtime-конфиг управляется через `/etc/hy2xs/hy2xs.env` и команду `reconfigure`.
|
- runtime-конфиг управляется через `/etc/hy2xs/hy2xs.env` и команду `reconfigure`.
|
||||||
|
|
||||||
В baseline намеренно не входят target-side build, rollback/uninstall orchestration, Telegram bot delivery, port hopping и generic server-manager функции.
|
В 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.
|
- [`package`](package) — skeleton итогового install package: entrypoint, templates, systemd units, package docs.
|
||||||
- [`apps`](apps) — исходники HY2XS admin fork.
|
- [`apps`](apps) — исходники HY2XS admin fork.
|
||||||
- [`docs`](docs) — архитектурная документация и acceptance.
|
- [`docs`](docs) — архитектурная документация и acceptance.
|
||||||
- [`hy2xs_implementation_plan-no_git`](hy2xs_implementation_plan-no_git) — roadmap и task breakdown.
|
|
||||||
- [`dist`](dist) — итоговые install archives, создаются builder'ом и не хранятся в git.
|
- [`dist`](dist) — итоговые install archives, создаются builder'ом и не хранятся в git.
|
||||||
|
|
||||||
## Сборка
|
## Сборка
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"hy2xs-admin/service"
|
"hy2xs-admin/service"
|
||||||
"hy2xs-admin/util"
|
"hy2xs-admin/util"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -299,7 +300,7 @@ func ExportAccount(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileName := fmt.Sprintf("AccountExport-%s.json", time.Now().Format("20060102150405"))
|
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 {
|
if err = util.ExportFile(filePath, accountExports, 0); err != nil {
|
||||||
vo.Fail(err.Error(), c)
|
vo.Fail(err.Error(), c)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"hy2xs-admin/service"
|
"hy2xs-admin/service"
|
||||||
"hy2xs-admin/util"
|
"hy2xs-admin/util"
|
||||||
"io"
|
"io"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -230,7 +231,7 @@ func ExportHysteria2Config(c *gin.Context) {
|
|||||||
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
|
hysteria2ServerConfig.TrafficStats.Secret = &trafficStatsSecret
|
||||||
|
|
||||||
fileName := fmt.Sprintf("Hysteria2Config-%s.yaml", time.Now().Format("20060102150405"))
|
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 {
|
if err = util.ExportFile(filePath, hysteria2ServerConfig, 1); err != nil {
|
||||||
vo.Fail(err.Error(), c)
|
vo.Fail(err.Error(), c)
|
||||||
@@ -258,7 +259,7 @@ func ExportConfig(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileName := fmt.Sprintf("SystemConfig-%s.json", time.Now().Format("20060102150405"))
|
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 {
|
if err = util.ExportFile(filePath, configs, 0); err != nil {
|
||||||
vo.Fail(err.Error(), c)
|
vo.Fail(err.Error(), c)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,12 +5,14 @@ import (
|
|||||||
"hy2xs-admin/controller"
|
"hy2xs-admin/controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
func initHysteria2AuthRouter(hysteria2Api *gin.RouterGroup) {
|
func initHysteria2MachineAuthRouter(hysteria2Api *gin.RouterGroup) {
|
||||||
hysteria2 := hysteria2Api.Group("/hysteria2")
|
hysteria2 := hysteria2Api.Group("/hysteria2")
|
||||||
{
|
{
|
||||||
hysteria2.POST("/auth", controller.Hysteria2Auth)
|
hysteria2.POST("/auth", controller.Hysteria2Auth)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initHysteria2SubscribeRouter(hysteria2Api *gin.RouterGroup) {
|
||||||
hysteria2Api.GET("/:conPass", controller.Hysteria2Subscribe)
|
hysteria2Api.GET("/:conPass", controller.Hysteria2Subscribe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-14
@@ -8,34 +8,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Router(router *gin.Engine, huiWebContext *string) {
|
func Router(router *gin.Engine, huiWebContext *string) {
|
||||||
// global context
|
|
||||||
relativePath := "/"
|
relativePath := "/"
|
||||||
if huiWebContext != nil && strings.HasPrefix(*huiWebContext, "/") {
|
if huiWebContext != nil && strings.HasPrefix(*huiWebContext, "/") {
|
||||||
relativePath = *huiWebContext
|
relativePath = *huiWebContext
|
||||||
}
|
}
|
||||||
globalGroup := router.Group(relativePath)
|
|
||||||
{
|
|
||||||
globalGroup.Use(middleware.FilterHandler(), middleware.LogHandler(), middleware.RateLimiterHandler())
|
|
||||||
|
|
||||||
frontend.InitFrontend(router, relativePath)
|
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 := globalGroup.Group("/hui")
|
||||||
{
|
authApi.Use(middleware.FilterHandler(), middleware.LogHandler(), middleware.RateLimiterHandler())
|
||||||
initAuthRouter(authApi)
|
initAuthRouter(authApi)
|
||||||
initHysteria2AuthRouter(authApi)
|
initHysteria2SubscribeRouter(authApi)
|
||||||
}
|
|
||||||
|
|
||||||
globalGroup.Use(middleware.JWTHandler())
|
|
||||||
|
|
||||||
globalGroup.Use(middleware.AdminHandler())
|
|
||||||
|
|
||||||
huiAdminApi := globalGroup.Group("/hui")
|
huiAdminApi := globalGroup.Group("/hui")
|
||||||
{
|
huiAdminApi.Use(
|
||||||
|
middleware.FilterHandler(),
|
||||||
|
middleware.LogHandler(),
|
||||||
|
middleware.RateLimiterHandler(),
|
||||||
|
middleware.JWTHandler(),
|
||||||
|
middleware.AdminHandler(),
|
||||||
|
)
|
||||||
initAccountAdminRouter(huiAdminApi)
|
initAccountAdminRouter(huiAdminApi)
|
||||||
initConfigRouter(huiAdminApi)
|
initConfigRouter(huiAdminApi)
|
||||||
initHysteria2Router(huiAdminApi)
|
initHysteria2Router(huiAdminApi)
|
||||||
initLogRouter(huiAdminApi)
|
initLogRouter(huiAdminApi)
|
||||||
initMonitorRouter(huiAdminApi)
|
initMonitorRouter(huiAdminApi)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -123,9 +123,10 @@ project/
|
|||||||
- целевая установка должна брать его с official upstream на момент развёртывания
|
- целевая установка должна брать его с official upstream на момент развёртывания
|
||||||
|
|
||||||
Дополнительно:
|
Дополнительно:
|
||||||
- `HY2XS_HYSTERIA_VERSION=latest|vX.Y.Z` задаётся через runtime env;
|
- версия Hysteria2 фиксируется в metadata install package (`metadata/hysteria.version`);
|
||||||
- при `latest` оркестратор записывает **фактически установленную** версию в `post-install.env`;
|
- URL артефакта и SHA256 фиксируются в metadata install package (`metadata/hysteria.url`, `metadata/hysteria.sha256`);
|
||||||
- install flow использует download-to-temp + explicit execute + post-install verification binary/version.
|
- runtime `reconfigure` не обновляет и не откатывает бинарник Hysteria2;
|
||||||
|
- install flow валидирует SHA256 и фактическую версию установленного бинарника.
|
||||||
|
|
||||||
## Инварианты
|
## Инварианты
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ Hysteria2 — основной транспортный компонент се
|
|||||||
|
|
||||||
- по умолчанию install layer тянет **свежий upstream release / install source**
|
- по умолчанию install layer тянет **свежий upstream release / install source**
|
||||||
- фактически установленная версия обязательно записывается в `post-install.env`
|
- фактически установленная версия обязательно записывается в `post-install.env`
|
||||||
- поддерживаются политики `latest | vX.Y.Z` через `HY2XS_HYSTERIA_VERSION`
|
- бинарник Hysteria2 устанавливается только на этапе `install`
|
||||||
- при `vX.Y.Z` install обязан валидировать соответствие фактически установленной версии
|
- версия, URL и SHA256 фиксируются в metadata install package
|
||||||
|
- `reconfigure` не обновляет и не откатывает бинарник Hysteria2
|
||||||
|
|
||||||
## Платформа
|
## Платформа
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
2. Проверяет базовые зависимости и install context.
|
2. Проверяет базовые зависимости и install context.
|
||||||
3. Создаёт каталоги установки.
|
3. Создаёт каталоги установки.
|
||||||
4. Разворачивает bundled HY2XS admin.
|
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.
|
6. Генерирует Hysteria config.
|
||||||
7. Создаёт systemd unit для Hysteria.
|
7. Создаёт systemd unit для Hysteria.
|
||||||
8. Создаёт systemd unit для HY2XS admin.
|
8. Создаёт systemd unit для HY2XS admin.
|
||||||
@@ -123,6 +123,8 @@
|
|||||||
- 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;
|
- install flow фиксирует фактически установленную версию Hysteria в snapshot;
|
||||||
|
- версия/URL/SHA256 Hysteria берутся из metadata install package;
|
||||||
|
- `reconfigure` не обновляет бинарник Hysteria, только runtime-слой.
|
||||||
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
|
- при `reconfigure --apply`: backup -> staged apply -> smoke -> rollback on fail.
|
||||||
|
|
||||||
## Что не реализовывать
|
## Что не реализовывать
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
- какую фактическую версию оркестратор установил
|
- какую фактическую версию оркестратор установил
|
||||||
- что записано в `HY2_VERSION`
|
- что записано в `HY2_VERSION`
|
||||||
- не связано ли поведение со свежим upstream release
|
- не связано ли поведение со свежим upstream release
|
||||||
- какая policy была в `HY2XS_HYSTERIA_VERSION` (`latest|vX.Y.Z`)
|
- какая версия Hysteria зафиксирована в metadata установленного пакета
|
||||||
|
|
||||||
|
Для обновления бинарника Hysteria2 используйте новый release install package.
|
||||||
|
Изменение runtime env не обновляет бинарник Hysteria2.
|
||||||
|
|
||||||
### 4. Оркестратор — Bun/TypeScript, но target не билдит его
|
### 4. Оркестратор — Bun/TypeScript, но target не билдит его
|
||||||
Если проблема в install flow, сначала смотреть:
|
Если проблема в 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`.
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { randomBytes } from "node:crypto";
|
|
||||||
import type { InstallContext, InstallOptions } from "../types/context";
|
import type { InstallContext, InstallOptions } from "../types/context";
|
||||||
import { exists, readText, writeText } from "../lib/fs";
|
import { exists, readText, writeText } from "../lib/fs";
|
||||||
import { runVisible } from "../lib/process";
|
import { runVisible } from "../lib/process";
|
||||||
@@ -16,10 +15,6 @@ import { applyFirewall } from "../steps/firewall";
|
|||||||
import { writePostInstallEnv } from "../steps/env";
|
import { writePostInstallEnv } from "../steps/env";
|
||||||
import { smoke } from "../steps/smoke";
|
import { smoke } from "../steps/smoke";
|
||||||
|
|
||||||
function secret(): string {
|
|
||||||
return randomBytes(24).toString("base64url");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function install(options: InstallOptions): Promise<void> {
|
export async function install(options: InstallOptions): Promise<void> {
|
||||||
const hasSourceConfig = options.sourceConfigPath ? await exists(options.sourceConfigPath) : false;
|
const hasSourceConfig = options.sourceConfigPath ? await exists(options.sourceConfigPath) : false;
|
||||||
if (options.sourceConfigPath && !hasSourceConfig) {
|
if (options.sourceConfigPath && !hasSourceConfig) {
|
||||||
@@ -30,15 +25,22 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
const config = parseRuntimeEnv(sourceConfigRaw);
|
const config = parseRuntimeEnv(sourceConfigRaw);
|
||||||
|
|
||||||
const context: InstallContext = {
|
const context: InstallContext = {
|
||||||
|
mode: "install",
|
||||||
options,
|
options,
|
||||||
config,
|
config,
|
||||||
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
|
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
|
||||||
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
|
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
|
||||||
installDate: new Date().toISOString(),
|
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");
|
step("preflight");
|
||||||
await preflight(context);
|
await preflight(context);
|
||||||
step("system dependencies");
|
step("system dependencies");
|
||||||
@@ -48,6 +50,8 @@ export async function install(options: InstallOptions): Promise<void> {
|
|||||||
step("write runtime env");
|
step("write runtime env");
|
||||||
await runVisible`mkdir -p /etc/hy2xs`;
|
await runVisible`mkdir -p /etc/hy2xs`;
|
||||||
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
|
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
|
||||||
|
await runVisible`chown root:root ${options.runtimeConfigPath}`;
|
||||||
|
await runVisible`chmod 0600 ${options.runtimeConfigPath}`;
|
||||||
step("bundled UI");
|
step("bundled UI");
|
||||||
await deployUi(context);
|
await deployUi(context);
|
||||||
step("Hysteria2 upstream install");
|
step("Hysteria2 upstream install");
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
|
|||||||
const configRaw = await readText(options.sourceConfigPath);
|
const configRaw = await readText(options.sourceConfigPath);
|
||||||
const config = parseRuntimeEnv(configRaw);
|
const config = parseRuntimeEnv(configRaw);
|
||||||
|
|
||||||
const context: ReconfigureContext & { packageVersion: string; packageBuildId: string; installDate: string; hysteriaAuthPassword: string; hysteriaVersion: string } = {
|
const context: ReconfigureContext = {
|
||||||
|
mode: "reconfigure",
|
||||||
options,
|
options,
|
||||||
config,
|
config,
|
||||||
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
|
packageVersion: await readPackageValue(options.packageDir, "package.version", "unknown"),
|
||||||
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
|
packageBuildId: await readPackageValue(options.packageDir, "package.build_id", "unknown"),
|
||||||
installDate: new Date().toISOString(),
|
installDate: new Date().toISOString(),
|
||||||
hysteriaAuthPassword: "managed-by-ui-auth",
|
|
||||||
hysteriaVersion: await readInstalledHysteriaVersion()
|
hysteriaVersion: await readInstalledHysteriaVersion()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,6 +81,8 @@ export async function reconfigure(options: ReconfigureOptions): Promise<void> {
|
|||||||
await applyFirewall(context);
|
await applyFirewall(context);
|
||||||
step("write env artifacts");
|
step("write env artifacts");
|
||||||
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
|
await writeText(options.runtimeConfigPath, renderRuntimeEnv(config), 0o600);
|
||||||
|
await runVisible`chown root:root ${options.runtimeConfigPath}`;
|
||||||
|
await runVisible`chmod 0600 ${options.runtimeConfigPath}`;
|
||||||
await writePostInstallEnv(context);
|
await writePostInstallEnv(context);
|
||||||
step("smoke checks");
|
step("smoke checks");
|
||||||
await smoke(context);
|
await smoke(context);
|
||||||
|
|||||||
@@ -148,8 +148,6 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
|||||||
false
|
false
|
||||||
),
|
),
|
||||||
hysteriaConfigPath: env.HY2XS_HYSTERIA_CONFIG_PATH || "/etc/hysteria/config.yaml",
|
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",
|
installDir: env.HY2XS_INSTALL_DIR || "/opt/hy2xs-admin",
|
||||||
dataDir: env.HY2XS_DATA_DIR || "/var/lib/hy2xs-admin",
|
dataDir: env.HY2XS_DATA_DIR || "/var/lib/hy2xs-admin",
|
||||||
logDir: env.HY2XS_LOG_DIR || "/var/log/hy2xs",
|
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_BANDWIDTH_DOWN=${config.hysteriaBandwidthDown}`,
|
||||||
`HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=${config.hysteriaIgnoreClientBandwidth}`,
|
`HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=${config.hysteriaIgnoreClientBandwidth}`,
|
||||||
`HY2XS_HYSTERIA_CONFIG_PATH=${config.hysteriaConfigPath}`,
|
`HY2XS_HYSTERIA_CONFIG_PATH=${config.hysteriaConfigPath}`,
|
||||||
`HY2XS_ALLOW_LATEST_HYSTERIA=${config.allowLatestHysteria}`,
|
|
||||||
`HY2XS_HYSTERIA_VERSION=${config.hysteriaVersionPolicy}`,
|
|
||||||
`HY2XS_INSTALL_DIR=${config.installDir}`,
|
`HY2XS_INSTALL_DIR=${config.installDir}`,
|
||||||
`HY2XS_DATA_DIR=${config.dataDir}`,
|
`HY2XS_DATA_DIR=${config.dataDir}`,
|
||||||
`HY2XS_LOG_DIR=${config.logDir}`
|
`HY2XS_LOG_DIR=${config.logDir}`
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { InstallContext } from "../types/context";
|
import type { RuntimeContext } from "../types/context";
|
||||||
import { readText, renderTemplate, writeText } from "../lib/fs";
|
import { readText, renderTemplate, writeText } from "../lib/fs";
|
||||||
import { runVisible } from "../lib/process";
|
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"
|
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}`
|
? `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, {
|
const rendered = renderTemplate(template, {
|
||||||
HYSTERIA_BIND_HOST: context.config.hysteriaBindHost,
|
HYSTERIA_BIND_HOST: context.config.hysteriaBindHost,
|
||||||
HYSTERIA_PORT: context.config.hysteriaPort,
|
HYSTERIA_PORT: context.config.hysteriaPort,
|
||||||
HYSTERIA_AUTH_PASSWORD: context.hysteriaAuthPassword,
|
|
||||||
HYSTERIA_OBFS_PASSWORD: context.config.hysteriaObfsPassword,
|
HYSTERIA_OBFS_PASSWORD: context.config.hysteriaObfsPassword,
|
||||||
HYSTERIA_API_HOST: context.config.hysteriaTrafficStatsHost,
|
HYSTERIA_API_HOST: context.config.hysteriaTrafficStatsHost,
|
||||||
HYSTERIA_API_PORT: context.config.hysteriaTrafficStatsPort,
|
HYSTERIA_API_PORT: context.config.hysteriaTrafficStatsPort,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import type { InstallContext } from "../types/context";
|
import type { RuntimeContext } from "../types/context";
|
||||||
import { readText, renderTemplate, writeText } from "../lib/fs";
|
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`), {
|
const rendered = renderTemplate(await readText(`${context.options.packageDir}/templates/env/post-install.env.tpl`), {
|
||||||
PACKAGE_VERSION: context.packageVersion,
|
PACKAGE_VERSION: context.packageVersion,
|
||||||
PACKAGE_BUILD_ID: context.packageBuildId,
|
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 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(
|
await writeText(
|
||||||
context.config.bootstrapAdminSecretPath,
|
context.config.bootstrapAdminSecretPath,
|
||||||
[
|
[
|
||||||
@@ -39,4 +42,6 @@ export async function writePostInstallEnv(context: InstallContext): Promise<void
|
|||||||
].join("\n") + "\n",
|
].join("\n") + "\n",
|
||||||
0o600
|
0o600
|
||||||
);
|
);
|
||||||
|
await runVisible`chown root:root ${context.config.bootstrapAdminSecretPath}`;
|
||||||
|
await runVisible`chmod 0600 ${context.config.bootstrapAdminSecretPath}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import { runVisible } from "../lib/process";
|
|||||||
export async function prepareFilesystem(context: InstallContext): Promise<void> {
|
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 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`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`install -d -m 0700 -o root -g root /etc/hy2xs`;
|
||||||
await runVisible`chown -R hysteria:hysteria /etc/hysteria /var/lib/hysteria`;
|
await runVisible`install -d -m 0755 -o root -g root /etc/hysteria`;
|
||||||
await runVisible`chown -R root:root ${context.config.installDir}`;
|
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`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`;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { exists, readText, renderTemplate, writeText } from "../lib/fs";
|
||||||
import { fail, info } from "../lib/log";
|
import { fail, info } from "../lib/log";
|
||||||
import { runVisible } from "../lib/process";
|
import { runVisible } from "../lib/process";
|
||||||
@@ -27,7 +27,7 @@ function isSafeNftablesEntrypoint(content: string): boolean {
|
|||||||
return effective === "flush ruleset";
|
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) {
|
if (context.options.skipFirewall || !context.config.firewallEnabled) {
|
||||||
info("firewall skipped by flag");
|
info("firewall skipped by flag");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,41 +9,32 @@ function normalizeInstalledVersion(raw: string): string {
|
|||||||
return raw.trim();
|
return raw.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateVersionPolicy(value: string): void {
|
function validatePinnedVersion(value: string): void {
|
||||||
if (/^v\d+\.\d+\.\d+$/.test(value)) {
|
if (/^v\d+\.\d+\.\d+$/.test(value)) {
|
||||||
return;
|
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> {
|
export async function installHysteria(context: InstallContext): Promise<void> {
|
||||||
const policy = context.config.hysteriaVersionPolicy;
|
validatePinnedVersion(context.hysteriaTargetVersion);
|
||||||
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);
|
|
||||||
|
|
||||||
const scriptPath = "/tmp/hy2xs-install-hysteria.sh";
|
const tmp = "/tmp/hy2xs-hysteria-linux-amd64";
|
||||||
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`curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location ${context.hysteriaArtifactUrl} -o ${tmp}`;
|
||||||
await runVisible`bash ${scriptPath}`;
|
await runVisible`test -s ${tmp}`;
|
||||||
} else {
|
await runVisible`printf '%s %s\n' ${context.hysteriaArtifactSha256} ${tmp} | sha256sum -c -`;
|
||||||
await runVisible`bash ${scriptPath} --version ${policy}`;
|
await runVisible`install -m 0755 ${tmp} /usr/local/bin/hysteria`;
|
||||||
}
|
await runVisible`rm -f ${tmp}`;
|
||||||
|
|
||||||
await runVisible`test -x /usr/local/bin/hysteria`;
|
await runVisible`test -x /usr/local/bin/hysteria`;
|
||||||
const versionOutput = await run`/usr/local/bin/hysteria version`;
|
const versionOutput = await run`/usr/local/bin/hysteria version`;
|
||||||
const installedVersion = normalizeInstalledVersion(versionOutput);
|
const installedVersion = normalizeInstalledVersion(versionOutput);
|
||||||
context.hysteriaVersion = installedVersion;
|
context.hysteriaVersion = installedVersion;
|
||||||
|
|
||||||
if (policy !== "latest" && installedVersion !== policy) {
|
if (installedVersion !== context.hysteriaTargetVersion) {
|
||||||
throw new Error(
|
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}`;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { InstallContext } from "../types/context";
|
import type { RuntimeContext } from "../types/context";
|
||||||
import { exists, readText } from "../lib/fs";
|
import { exists, readText } from "../lib/fs";
|
||||||
import { fail, info } from "../lib/log";
|
import { fail, info } from "../lib/log";
|
||||||
import { run } from "../lib/process";
|
import { run } from "../lib/process";
|
||||||
@@ -21,8 +21,8 @@ async function isUnitActive(unit: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function preflight(context: InstallContext): Promise<void> {
|
export async function preflight(context: RuntimeContext): Promise<void> {
|
||||||
const isReconfigure = context.packageVersion === "reconfigure";
|
const isReconfigure = context.mode === "reconfigure";
|
||||||
|
|
||||||
if (process.getuid?.() !== 0) {
|
if (process.getuid?.() !== 0) {
|
||||||
fail("installer must run as root");
|
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");
|
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");
|
fail("bundled HY2XS admin is missing from install package");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { InstallContext } from "../types/context";
|
import type { RuntimeContext } from "../types/context";
|
||||||
import { info } from "../lib/log";
|
import { info } from "../lib/log";
|
||||||
import { runHidden, runSecret, runVisible } from "../lib/process";
|
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) {
|
if (context.options.skipStart) {
|
||||||
info("service start and smoke checks skipped by flag");
|
info("service start and smoke checks skipped by flag");
|
||||||
return;
|
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 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 -r /etc/hysteria/config.yaml`;
|
||||||
await runVisible`sudo -u hy2xs-admin test ! -w /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} '`;
|
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") {
|
||||||
await runVisible`! ss -H -ltn | grep -q '0.0.0.0:${context.config.uiPort} '`;
|
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}`);
|
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();
|
const adminConPass = (await runSecret`grep '^ADMIN_CON_PASS=' ${context.config.bootstrapAdminSecretPath} | head -n1 | cut -d= -f2-`).trim();
|
||||||
if (!adminConPass) {
|
if (!adminConPass) {
|
||||||
throw new Error("admin connection password is empty in bootstrap secret file");
|
throw new Error("admin connection password is empty in bootstrap secret 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 { readText, renderTemplate, writeText } from "../lib/fs";
|
||||||
import { runVisible } from "../lib/process";
|
import { runVisible } from "../lib/process";
|
||||||
|
|
||||||
export async function deploySystemd(context: InstallContext): Promise<void> {
|
export async function deploySystemd(context: RuntimeContext): Promise<void> {
|
||||||
const values = {
|
const values = {
|
||||||
UI_BIND_HOST: context.config.uiBindHost,
|
UI_BIND_HOST: context.config.uiBindHost,
|
||||||
UI_PORT: context.config.uiPort,
|
UI_PORT: context.config.uiPort,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export type InstallOptions = {
|
export type CommonOptions = {
|
||||||
packageDir: string;
|
packageDir: string;
|
||||||
sourceConfigPath: string;
|
sourceConfigPath: string;
|
||||||
runtimeConfigPath: string;
|
runtimeConfigPath: string;
|
||||||
@@ -7,17 +7,15 @@ export type InstallOptions = {
|
|||||||
skipStart: boolean;
|
skipStart: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReconfigureOptions = {
|
export type InstallOptions = CommonOptions;
|
||||||
packageDir: string;
|
|
||||||
sourceConfigPath: string;
|
export type ReconfigureOptions = CommonOptions & {
|
||||||
runtimeConfigPath: string;
|
|
||||||
nonInteractive: boolean;
|
|
||||||
dryRun: boolean;
|
dryRun: boolean;
|
||||||
apply: boolean;
|
apply: boolean;
|
||||||
skipFirewall: boolean;
|
|
||||||
skipStart: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type RunMode = "install" | "reconfigure";
|
||||||
|
|
||||||
export type TlsMode = "acme" | "file" | "self_signed_dev";
|
export type TlsMode = "acme" | "file" | "self_signed_dev";
|
||||||
|
|
||||||
export type RuntimeConfig = {
|
export type RuntimeConfig = {
|
||||||
@@ -54,25 +52,31 @@ export type RuntimeConfig = {
|
|||||||
hysteriaBandwidthDown: string;
|
hysteriaBandwidthDown: string;
|
||||||
hysteriaIgnoreClientBandwidth: boolean;
|
hysteriaIgnoreClientBandwidth: boolean;
|
||||||
hysteriaConfigPath: string;
|
hysteriaConfigPath: string;
|
||||||
allowLatestHysteria: boolean;
|
|
||||||
hysteriaVersionPolicy: string;
|
|
||||||
installDir: string;
|
installDir: string;
|
||||||
dataDir: string;
|
dataDir: string;
|
||||||
logDir: string;
|
logDir: string;
|
||||||
bootstrapAdminSecretPath: string;
|
bootstrapAdminSecretPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InstallContext = {
|
export type RuntimeContext = {
|
||||||
options: InstallOptions;
|
mode: RunMode;
|
||||||
|
options: CommonOptions;
|
||||||
config: RuntimeConfig;
|
config: RuntimeConfig;
|
||||||
packageVersion: string;
|
packageVersion: string;
|
||||||
packageBuildId: string;
|
packageBuildId: string;
|
||||||
installDate: string;
|
installDate: string;
|
||||||
hysteriaAuthPassword: string;
|
|
||||||
hysteriaVersion: string;
|
hysteriaVersion: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ReconfigureContext = {
|
export type InstallContext = RuntimeContext & {
|
||||||
options: ReconfigureOptions;
|
mode: "install";
|
||||||
config: RuntimeConfig;
|
options: InstallOptions;
|
||||||
|
hysteriaTargetVersion: string;
|
||||||
|
hysteriaArtifactUrl: string;
|
||||||
|
hysteriaArtifactSha256: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReconfigureContext = RuntimeContext & {
|
||||||
|
mode: "reconfigure";
|
||||||
|
options: ReconfigureOptions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ HY2XS_HYSTERIA_BANDWIDTH_UP=50 mbps
|
|||||||
HY2XS_HYSTERIA_BANDWIDTH_DOWN=50 mbps
|
HY2XS_HYSTERIA_BANDWIDTH_DOWN=50 mbps
|
||||||
HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=false
|
HY2XS_HYSTERIA_IGNORE_CLIENT_BANDWIDTH=false
|
||||||
HY2XS_HYSTERIA_CONFIG_PATH=/etc/hysteria/config.yaml
|
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_INSTALL_DIR=/opt/hy2xs-admin
|
||||||
HY2XS_DATA_DIR=/var/lib/hy2xs-admin
|
HY2XS_DATA_DIR=/var/lib/hy2xs-admin
|
||||||
HY2XS_LOG_DIR=/var/log/hy2xs
|
HY2XS_LOG_DIR=/var/log/hy2xs
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ if [ ! -x "$ORCHESTRATOR" ]; then
|
|||||||
fail "Missing executable orchestrator artifact: $ORCHESTRATOR"
|
fail "Missing executable orchestrator artifact: $ORCHESTRATOR"
|
||||||
fi
|
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 -d -m 0755 /usr/local/lib/hy2xs
|
||||||
install -m 0755 "$ORCHESTRATOR" "$ORCHESTRATOR_INSTALL_PATH"
|
install -m 0755 "$ORCHESTRATOR" "$ORCHESTRATOR_INSTALL_PATH"
|
||||||
ln -sf "$ORCHESTRATOR_INSTALL_PATH" "$ORCHESTRATOR_SYMLINK"
|
ln -sf "$ORCHESTRATOR_INSTALL_PATH" "$ORCHESTRATOR_SYMLINK"
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -3,6 +3,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
STAGE_DIR="tools/build/output/hy2xs-install"
|
STAGE_DIR="tools/build/output/hy2xs-install"
|
||||||
ADMIN_BUILD_DIR="tools/build/output/hy2xs-admin-build"
|
ADMIN_BUILD_DIR="tools/build/output/hy2xs-admin-build"
|
||||||
|
HYSTERIA_LOCK_FILE="tools/build/hysteria-lock.env"
|
||||||
|
|
||||||
prepare_stage() {
|
prepare_stage() {
|
||||||
local version="$1"
|
local version="$1"
|
||||||
@@ -62,6 +63,13 @@ write_metadata() {
|
|||||||
local version="$1"
|
local version="$1"
|
||||||
local build_id="$2"
|
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 'name=HY2XS\n'
|
||||||
printf 'version=%s\n' "$version"
|
printf 'version=%s\n' "$version"
|
||||||
@@ -77,8 +85,15 @@ write_metadata() {
|
|||||||
printf 'pnpm_version=%s\n' "$($PNPM_BIN --version)"
|
printf 'pnpm_version=%s\n' "$($PNPM_BIN --version)"
|
||||||
printf 'hysteria_source=official-upstream\n'
|
printf 'hysteria_source=official-upstream\n'
|
||||||
printf 'hysteria_target=linux-amd64\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"
|
} >"$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"
|
cd "$STAGE_DIR"
|
||||||
find . -type f ! -path './metadata/checksums.txt' -print0 \
|
find . -type f ! -path './metadata/checksums.txt' -print0 \
|
||||||
|
|||||||
Reference in New Issue
Block a user