fix(fix4): enforce runtime contract and orchestrator guardrails
This commit is contained in:
@@ -57,9 +57,17 @@ https://git.ext.flamy.studio/flamy_dev/HY2XS_flamy.git
|
|||||||
На чистом Debian 12 target нужно распаковать архив и запустить от root:
|
На чистом Debian 12 target нужно распаковать архив и запустить от root:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./install.sh --config /etc/hy2xs/hy2xs.env --non-interactive
|
./install.sh --non-interactive
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Опционально можно передать внешний source-config (не путь runtime-файла на target):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./install.sh --config /root/custom-hy2xs.env --non-interactive
|
||||||
|
```
|
||||||
|
|
||||||
|
`/etc/hy2xs/hy2xs.env` создаётся install-слоем автоматически и далее редактируется оператором перед `reconfigure`.
|
||||||
|
|
||||||
После установки применяются команды оркестратора:
|
После установки применяются команды оркестратора:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isOrchestratorManagedConfigKey(key string) bool {
|
||||||
|
switch key {
|
||||||
|
case constant.HUIWebPort,
|
||||||
|
constant.HUIWebContext,
|
||||||
|
constant.HUICrtPath,
|
||||||
|
constant.HUIKeyPath,
|
||||||
|
constant.Hysteria2Enable,
|
||||||
|
constant.Hysteria2Config,
|
||||||
|
constant.Hysteria2TrafficStatsSecret:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateConfigs(c *gin.Context) {
|
func UpdateConfigs(c *gin.Context) {
|
||||||
configsUpdateDto, err := validateField(c, dto.ConfigsUpdateDto{})
|
configsUpdateDto, err := validateField(c, dto.ConfigsUpdateDto{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,6 +52,11 @@ func UpdateConfigs(c *gin.Context) {
|
|||||||
key := *item.Key
|
key := *item.Key
|
||||||
value := *item.Value
|
value := *item.Value
|
||||||
|
|
||||||
|
if isOrchestratorManagedConfigKey(key) {
|
||||||
|
vo.Fail(fmt.Sprintf("%s managed by orchestrator: use hy2xs-orchestrator reconfigure", key), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if key == constant.HUIWebPort && strconv.FormatInt(port, 10) != value {
|
if key == constant.HUIWebPort && strconv.FormatInt(port, 10) != value {
|
||||||
port, err := strconv.Atoi(value)
|
port, err := strconv.Atoi(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -117,17 +137,12 @@ func GetConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
running := service.Hysteria2IsRunning()
|
running := service.Hysteria2IsRunning()
|
||||||
|
if *config.Key == constant.Hysteria2Enable {
|
||||||
if (*config.Value == "1") != running {
|
|
||||||
enable := "0"
|
|
||||||
if running {
|
if running {
|
||||||
enable = "1"
|
configVo.Value = "1"
|
||||||
|
} else {
|
||||||
|
configVo.Value = "0"
|
||||||
}
|
}
|
||||||
if err := service.UpdateConfig(constant.Hysteria2Enable, enable); err != nil {
|
|
||||||
vo.Fail(err.Error(), c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
configVo.Value = enable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vo.Success(configVo, c)
|
vo.Success(configVo, c)
|
||||||
@@ -284,6 +299,12 @@ func ImportConfig(c *gin.Context) {
|
|||||||
vo.Fail("content Unmarshal err", c)
|
vo.Fail("content Unmarshal err", c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for _, cfg := range configs {
|
||||||
|
if cfg.Key != nil && isOrchestratorManagedConfigKey(*cfg.Key) {
|
||||||
|
vo.Fail(fmt.Sprintf("%s managed by orchestrator: use hy2xs-orchestrator reconfigure", *cfg.Key), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if err = service.UpsertConfig(configs); err != nil {
|
if err = service.UpsertConfig(configs); err != nil {
|
||||||
vo.Fail(err.Error(), c)
|
vo.Fail(err.Error(), c)
|
||||||
return
|
return
|
||||||
|
|||||||
+8
-16
@@ -2,7 +2,6 @@ package dao
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -120,7 +119,7 @@ func ensureAccountSchema() error {
|
|||||||
func ensureSecureBootstrapAdmin() error {
|
func ensureSecureBootstrapAdmin() error {
|
||||||
adminUser := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_USER"))
|
adminUser := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_USER"))
|
||||||
if adminUser == "" {
|
if adminUser == "" {
|
||||||
adminUser = "admin"
|
adminUser = "hy2xsadmin"
|
||||||
}
|
}
|
||||||
adminPassword := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_INITIAL_PASSWORD"))
|
adminPassword := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_INITIAL_PASSWORD"))
|
||||||
if adminPassword == "" {
|
if adminPassword == "" {
|
||||||
@@ -136,11 +135,14 @@ func ensureSecureBootstrapAdmin() error {
|
|||||||
deviceNo := int64(envInt("HY2XS_ADMIN_DEVICE_NO", 6))
|
deviceNo := int64(envInt("HY2XS_ADMIN_DEVICE_NO", 6))
|
||||||
role := "admin"
|
role := "admin"
|
||||||
deleted := int64(0)
|
deleted := int64(0)
|
||||||
conPassSecret, conErr := util.RandomString(28)
|
conPass := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_CON_PASS"))
|
||||||
if conErr != nil {
|
if conPass == "" {
|
||||||
return conErr
|
generated, genErr := util.RandomString(28)
|
||||||
|
if genErr != nil {
|
||||||
|
return genErr
|
||||||
|
}
|
||||||
|
conPass = generated
|
||||||
}
|
}
|
||||||
conPass := fmt.Sprintf("%s.%s", adminUser, conPassSecret)
|
|
||||||
hash, hashErr := util.HashPassword(adminPassword)
|
hash, hashErr := util.HashPassword(adminPassword)
|
||||||
if hashErr != nil {
|
if hashErr != nil {
|
||||||
return hashErr
|
return hashErr
|
||||||
@@ -169,16 +171,6 @@ func ensureSecureBootstrapAdmin() error {
|
|||||||
if admin.Pass == nil {
|
if admin.Pass == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
|
||||||
"username": adminUser,
|
|
||||||
"pass": hash,
|
|
||||||
"con_pass": conPass,
|
|
||||||
"force_password_change": forcePasswordChange,
|
|
||||||
}
|
|
||||||
if updateErr := UpdateAccount([]int64{*admin.Id}, updates); updateErr != nil {
|
|
||||||
return updateErr
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ func NewHysteria2Api(apiPort int64) *Hysteria2Api {
|
|||||||
// ListUsers Информация о трафике каждого пользователя
|
// ListUsers Информация о трафике каждого пользователя
|
||||||
func (h *Hysteria2Api) ListUsers(clear bool, secret string) (map[string]bo.Hysteria2UserTraffic, error) {
|
func (h *Hysteria2Api) ListUsers(clear bool, secret string) (map[string]bo.Hysteria2UserTraffic, error) {
|
||||||
var users map[string]bo.Hysteria2UserTraffic
|
var users map[string]bo.Hysteria2UserTraffic
|
||||||
if !NewHysteria2Instance().IsRunning() {
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
url := fmt.Sprintf("http://127.0.0.1:%d/traffic", h.apiPort)
|
url := fmt.Sprintf("http://127.0.0.1:%d/traffic", h.apiPort)
|
||||||
@@ -66,9 +63,6 @@ func (h *Hysteria2Api) ListUsers(clear bool, secret string) (map[string]bo.Hyste
|
|||||||
|
|
||||||
// KickUsers Принудительное отключение
|
// KickUsers Принудительное отключение
|
||||||
func (h *Hysteria2Api) KickUsers(keys []string, secret string) error {
|
func (h *Hysteria2Api) KickUsers(keys []string, secret string) error {
|
||||||
if !NewHysteria2Instance().IsRunning() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
usernamesByte, err := json.Marshal(keys)
|
usernamesByte, err := json.Marshal(keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Hysteria2 KickUsers Marshal err: %v", err)
|
logrus.Errorf("Hysteria2 KickUsers Marshal err: %v", err)
|
||||||
@@ -101,9 +95,6 @@ func (h *Hysteria2Api) KickUsers(keys []string, secret string) error {
|
|||||||
// OnlineUsers Пользователи онлайн
|
// OnlineUsers Пользователи онлайн
|
||||||
func (h *Hysteria2Api) OnlineUsers(secret string) (map[string]int64, error) {
|
func (h *Hysteria2Api) OnlineUsers(secret string) (map[string]int64, error) {
|
||||||
var onlineUsers map[string]int64
|
var onlineUsers map[string]int64
|
||||||
if !NewHysteria2Instance().IsRunning() {
|
|
||||||
return onlineUsers, nil
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
url := fmt.Sprintf("http://127.0.0.1:%d/online", h.apiPort)
|
url := fmt.Sprintf("http://127.0.0.1:%d/online", h.apiPort)
|
||||||
|
|||||||
@@ -15,26 +15,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func UpdateConfig(key string, value string) error {
|
func UpdateConfig(key string, value string) error {
|
||||||
if key == constant.Hysteria2Enable {
|
|
||||||
if value == "1" {
|
|
||||||
hysteria2Config, err := GetHysteria2Config()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hysteria2Config.Listen == nil || *hysteria2Config.Listen == "" {
|
|
||||||
logrus.Errorf("hysteria2 config is empty")
|
|
||||||
return errors.New("hysteria2 config is empty")
|
|
||||||
}
|
|
||||||
// Запуск Hysteria2
|
|
||||||
if err = StartHysteria2(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := StopHysteria2(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dao.UpdateConfig([]string{key}, map[string]interface{}{"value": value})
|
return dao.UpdateConfig([]string{key}, map[string]interface{}{"value": value})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ var kickMutex sync.Mutex
|
|||||||
|
|
||||||
func CronHandleAccount() {
|
func CronHandleAccount() {
|
||||||
go func() {
|
go func() {
|
||||||
hysteriaEnable, err := dao.GetConfig("key = ?", constant.Hysteria2Enable)
|
if !Hysteria2IsRunning() {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if hysteriaEnable.Value != nil && *hysteriaEnable.Value == "1" {
|
|
||||||
apiPort, err := GetHysteria2ApiPort()
|
apiPort, err := GetHysteria2ApiPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -37,7 +36,6 @@ func CronHandleAccount() {
|
|||||||
|
|
||||||
// Принудительное отключение
|
// Принудительное отключение
|
||||||
go kickAccount(apiPort, *trafficSecretConfig.Value)
|
go kickAccount(apiPort, *trafficSecretConfig.Value)
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ func StartServer(crtPath string, keyPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StopServer() error {
|
func StopServer() error {
|
||||||
if err := StopHysteria2(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
- `SSH_PORT`
|
- `SSH_PORT`
|
||||||
- `HY2XS_FIREWALL_ENABLED`
|
- `HY2XS_FIREWALL_ENABLED`
|
||||||
- `HY2XS_FIREWALL_STAGED_APPLY`
|
- `HY2XS_FIREWALL_STAGED_APPLY`
|
||||||
|
- `HY2XS_ADMIN_USER`
|
||||||
|
- `HY2XS_FORCE_PASSWORD_CHANGE`
|
||||||
|
|
||||||
### Hysteria
|
### Hysteria
|
||||||
- `HY2_SOURCE=official-upstream`
|
- `HY2_SOURCE=official-upstream`
|
||||||
@@ -101,6 +103,11 @@
|
|||||||
3. оператор запускает `reconfigure --dry-run`, затем `reconfigure --apply`;
|
3. оператор запускает `reconfigure --dry-run`, затем `reconfigure --apply`;
|
||||||
4. оркестратор обновляет runtime и перезаписывает snapshot.
|
4. оркестратор обновляет runtime и перезаписывает snapshot.
|
||||||
|
|
||||||
|
Важно:
|
||||||
|
- `HY2XS_ADMIN_INITIAL_PASSWORD` используется только для первичного bootstrap seed;
|
||||||
|
- `HY2XS_ADMIN_CON_PASS` — отдельная runtime-сущность для Hysteria auth/smoke;
|
||||||
|
- после первичного seed перезапуски `hy2xs-admin` не должны переопределять пароль admin и `con_pass`.
|
||||||
|
|
||||||
## Что нельзя делать
|
## Что нельзя делать
|
||||||
|
|
||||||
- сваливать туда временный мусор
|
- сваливать туда временный мусор
|
||||||
|
|||||||
@@ -49,7 +49,10 @@
|
|||||||
14. TLS mode в `config.yaml` соответствует runtime env (`acme|file|self_signed_dev`)
|
14. TLS mode в `config.yaml` соответствует runtime env (`acme|file|self_signed_dev`)
|
||||||
15. при `HY2XS_TLS_MODE=acme` в `config.yaml` выставлен `acme.type` из `HY2XS_ACME_TYPE`
|
15. при `HY2XS_TLS_MODE=acme` в `config.yaml` выставлен `acme.type` из `HY2XS_ACME_TYPE`
|
||||||
16. subscribe/node URL в API/QR формируются по `HY2XS_PUBLIC_HOST` + `HY2XS_PUBLIC_PORT`
|
16. subscribe/node URL в API/QR формируются по `HY2XS_PUBLIC_HOST` + `HY2XS_PUBLIC_PORT`
|
||||||
15. `nft -c -f /etc/nftables.conf` проходит после apply
|
17. `nft -c -f /etc/nftables.conf` проходит после apply
|
||||||
|
18. пароль admin и `con_pass` не перезаписываются при рестарте `hy2xs-admin`
|
||||||
|
19. остановка/рестарт UI не останавливает `hysteria-server`
|
||||||
|
20. traffic accounting/kick ориентируются на systemd status, а не на SQLite `HYSTERIA2_ENABLE`
|
||||||
|
|
||||||
## D. Negative tests
|
## D. Negative tests
|
||||||
|
|
||||||
@@ -79,3 +82,4 @@
|
|||||||
9. отсутствует production path для port hopping
|
9. отсутствует production path для port hopping
|
||||||
10. UI не запускается от root
|
10. UI не запускается от root
|
||||||
11. клиентские endpoint не зависят от request `Host`/`hostname`
|
11. клиентские endpoint не зависят от request `Host`/`hostname`
|
||||||
|
12. production build verify падает, если `config/hy2xs.env` содержит placeholder-значения
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
|||||||
uiPort,
|
uiPort,
|
||||||
adminUser: requireValue("HY2XS_ADMIN_USER", env.HY2XS_ADMIN_USER || "admin"),
|
adminUser: requireValue("HY2XS_ADMIN_USER", env.HY2XS_ADMIN_USER || "admin"),
|
||||||
adminInitialPassword: valueOrGenerate(env.HY2XS_ADMIN_INITIAL_PASSWORD),
|
adminInitialPassword: valueOrGenerate(env.HY2XS_ADMIN_INITIAL_PASSWORD),
|
||||||
|
adminConPass: requireValue("HY2XS_ADMIN_CON_PASS", valueOrGenerate(env.HY2XS_ADMIN_CON_PASS)),
|
||||||
forcePasswordChange: parseBool("HY2XS_FORCE_PASSWORD_CHANGE", env.HY2XS_FORCE_PASSWORD_CHANGE, true),
|
forcePasswordChange: parseBool("HY2XS_FORCE_PASSWORD_CHANGE", env.HY2XS_FORCE_PASSWORD_CHANGE, true),
|
||||||
tlsMode,
|
tlsMode,
|
||||||
acmeType,
|
acmeType,
|
||||||
@@ -202,6 +203,7 @@ export function renderRuntimeEnv(config: RuntimeConfig): string {
|
|||||||
`HY2XS_UI_PORT=${config.uiPort}`,
|
`HY2XS_UI_PORT=${config.uiPort}`,
|
||||||
`HY2XS_ADMIN_USER=${config.adminUser}`,
|
`HY2XS_ADMIN_USER=${config.adminUser}`,
|
||||||
`HY2XS_ADMIN_INITIAL_PASSWORD=${config.adminInitialPassword}`,
|
`HY2XS_ADMIN_INITIAL_PASSWORD=${config.adminInitialPassword}`,
|
||||||
|
`HY2XS_ADMIN_CON_PASS=${config.adminConPass}`,
|
||||||
`HY2XS_FORCE_PASSWORD_CHANGE=${config.forcePasswordChange}`,
|
`HY2XS_FORCE_PASSWORD_CHANGE=${config.forcePasswordChange}`,
|
||||||
`HY2XS_TLS_MODE=${config.tlsMode}`,
|
`HY2XS_TLS_MODE=${config.tlsMode}`,
|
||||||
`HY2XS_ACME_TYPE=${config.acmeType}`,
|
`HY2XS_ACME_TYPE=${config.acmeType}`,
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ 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 writeText(context.config.bootstrapAdminSecretPath, `${context.config.adminUser}:${context.config.adminInitialPassword}\n`, 0o600);
|
await writeText(context.config.bootstrapAdminSecretPath, `${context.config.adminUser}:${context.config.adminConPass}\n`, 0o600);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ 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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validAuthResponse = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${context.config.adminUser}.${context.config.adminInitialPassword}","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
const adminConPass = (await runSecret`grep '^${context.config.adminUser}:' ${context.config.bootstrapAdminSecretPath} | head -n1 | cut -d: -f2-`).trim();
|
||||||
|
if (!adminConPass) {
|
||||||
|
throw new Error("admin connection password is empty in bootstrap secret file");
|
||||||
|
}
|
||||||
|
|
||||||
|
const validAuthResponse = await runSecret`curl -sS --max-time 5 -X POST -H 'Content-Type: application/json' --data '{"addr":"127.0.0.1:12345","auth":"${adminConPass}","tx":"0"}' http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth`;
|
||||||
if (!/"ok"\s*:\s*true/.test(validAuthResponse)) {
|
if (!/"ok"\s*:\s*true/.test(validAuthResponse)) {
|
||||||
throw new Error(`unexpected auth response for valid credentials`);
|
throw new Error(`unexpected auth response for valid credentials`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type RuntimeConfig = {
|
|||||||
uiPort: number;
|
uiPort: number;
|
||||||
adminUser: string;
|
adminUser: string;
|
||||||
adminInitialPassword: string;
|
adminInitialPassword: string;
|
||||||
|
adminConPass: string;
|
||||||
forcePasswordChange: boolean;
|
forcePasswordChange: boolean;
|
||||||
tlsMode: TlsMode;
|
tlsMode: TlsMode;
|
||||||
acmeType: "http" | "tls" | "dns";
|
acmeType: "http" | "tls" | "dns";
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ HY2XS_FIREWALL_ENABLED=true
|
|||||||
HY2XS_FIREWALL_STAGED_APPLY=true
|
HY2XS_FIREWALL_STAGED_APPLY=true
|
||||||
HY2XS_UI_BIND_HOST=127.0.0.1
|
HY2XS_UI_BIND_HOST=127.0.0.1
|
||||||
HY2XS_UI_PORT=8080
|
HY2XS_UI_PORT=8080
|
||||||
HY2XS_ADMIN_USER=admin
|
HY2XS_ADMIN_USER=hy2xsadmin
|
||||||
HY2XS_ADMIN_INITIAL_PASSWORD=__GENERATE__
|
HY2XS_ADMIN_INITIAL_PASSWORD=__GENERATE__
|
||||||
|
HY2XS_ADMIN_CON_PASS=__GENERATE__
|
||||||
HY2XS_FORCE_PASSWORD_CHANGE=true
|
HY2XS_FORCE_PASSWORD_CHANGE=true
|
||||||
HY2XS_TLS_MODE=acme
|
HY2XS_TLS_MODE=acme
|
||||||
HY2XS_ACME_TYPE=http
|
HY2XS_ACME_TYPE=http
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ require_repo_layout() {
|
|||||||
[ -f "orchestrator/bun.lock" ] || fail "missing orchestrator/bun.lock"
|
[ -f "orchestrator/bun.lock" ] || fail "missing orchestrator/bun.lock"
|
||||||
[ -f "package/install.sh" ] || fail "missing package/install.sh"
|
[ -f "package/install.sh" ] || fail "missing package/install.sh"
|
||||||
[ -d "package/templates" ] || fail "missing package/templates"
|
[ -d "package/templates" ] || fail "missing package/templates"
|
||||||
|
[ -f "package/config/hy2xs.env" ] || fail "missing package/config/hy2xs.env"
|
||||||
[ -f "package/templates/env/post-install.env.tpl" ] || fail "missing package/templates/env/post-install.env.tpl"
|
[ -f "package/templates/env/post-install.env.tpl" ] || fail "missing package/templates/env/post-install.env.tpl"
|
||||||
[ -d "package/systemd" ] || fail "missing package/systemd"
|
[ -d "package/systemd" ] || fail "missing package/systemd"
|
||||||
[ -d "apps" ] || fail "missing apps HY2XS admin source"
|
[ -d "apps" ] || fail "missing apps HY2XS admin source"
|
||||||
@@ -31,6 +32,12 @@ verify_archive() {
|
|||||||
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hysteria-server.service$' || fail "archive missing hysteria systemd unit"
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hysteria-server.service$' || fail "archive missing hysteria systemd unit"
|
||||||
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hy2xs-admin.service$' || fail "archive missing admin systemd unit"
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/systemd/hy2xs-admin.service$' || fail "archive missing admin systemd unit"
|
||||||
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/hysteria/config.yaml.tpl$' || fail "archive missing Hysteria config template"
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/hysteria/config.yaml.tpl$' || fail "archive missing Hysteria config template"
|
||||||
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/config/hy2xs.env$' || fail "archive missing canonical runtime config"
|
||||||
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/env/post-install.env.tpl$' || fail "archive missing post-install env template"
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/templates/env/post-install.env.tpl$' || fail "archive missing post-install env template"
|
||||||
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/checksums.txt$' || fail "archive missing checksums"
|
printf '%s\n' "$listing" | grep -q '^hy2xs-install/metadata/checksums.txt$' || fail "archive missing checksums"
|
||||||
|
|
||||||
|
local env_content
|
||||||
|
env_content="$(tar -xOzf "$archive" hy2xs-install/config/hy2xs.env)"
|
||||||
|
printf '%s\n' "$env_content" | grep -q 'replace-with-your-domain.example' && fail "packaged hy2xs.env contains placeholder domain"
|
||||||
|
printf '%s\n' "$env_content" | grep -q 'replace-with-your-email@example.com' && fail "packaged hy2xs.env contains placeholder email"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user