Продакшн рефактор без легаси
This commit is contained in:
@@ -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.
|
||||
|
||||
## Сборка
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
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
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 и фактическую версию установленного бинарника.
|
||||
|
||||
## Инварианты
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
## Платформа
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
## Что не реализовывать
|
||||
|
||||
@@ -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`.
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -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`;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
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 \
|
||||
|
||||
Reference in New Issue
Block a user