diff --git a/apps/service/hysteria2.go b/apps/service/hysteria2.go index 9fc54a1..1a09a77 100644 --- a/apps/service/hysteria2.go +++ b/apps/service/hysteria2.go @@ -4,12 +4,10 @@ import ( "errors" "fmt" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v3" "hy2xs-admin/dao" "hy2xs-admin/model/constant" "hy2xs-admin/model/vo" "hy2xs-admin/util" - "os" ) func InitHysteria2() error { @@ -28,97 +26,21 @@ func InitHysteria2() error { return nil } -func setHysteria2ConfigYAML() error { - serverConfig, err := GetHysteria2Config() - if err != nil { - return err - } - if serverConfig.Listen == nil || *serverConfig.Listen == "" { - return errors.New("hysteria2 config is empty") - } - - authHttpUrl, err := GetAuthHttpUrl() - if err != nil { - return err - } - - if serverConfig.Auth == nil || serverConfig.Auth.HTTP == nil || serverConfig.Auth.HTTP.URL == nil { - if err := UpdateHysteria2Config(serverConfig); err != nil { - return err - } - serverConfig, err = GetHysteria2Config() - if err != nil { - return err - } - } - - // update auth http url - if *serverConfig.Auth.HTTP.URL != authHttpUrl { - serverConfig.Auth.HTTP.URL = &authHttpUrl - if err := UpdateHysteria2Config(serverConfig); err != nil { - return err - } - } - - hysteria2Config, err := yaml.Marshal(&serverConfig) - if err != nil { - logrus.Errorf("marshal hysteria2 config err: %v", err) - return errors.New("marshal hysteria2 config err") - } - tmpPath := fmt.Sprintf("%s.tmp", constant.Hysteria2ConfigPath) - file, err := os.OpenFile(tmpPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) - if err != nil { - logrus.Errorf("create hysteria2 server config file err: %v", err) - return errors.New("create hysteria2 server config file err") - } - defer file.Close() - _, err = file.WriteString(string(hysteria2Config)) - if err != nil { - logrus.Errorf("write hysteria2 config.json file err: %v", err) - return errors.New("hysteria2 config.json file write err") - } - if syncErr := file.Sync(); syncErr != nil { - return syncErr - } - if closeErr := file.Close(); closeErr != nil { - return closeErr - } - if renameErr := os.Rename(tmpPath, constant.Hysteria2ConfigPath); renameErr != nil { - return renameErr - } - if chmodErr := os.Chmod(constant.Hysteria2ConfigPath, 0600); chmodErr != nil { - return chmodErr - } - if chownErr := os.Chown(constant.Hysteria2ConfigPath, 0, 0); chownErr != nil { - return chownErr - } - return nil -} - func Hysteria2IsRunning() bool { _, err := util.Exec("systemctl is-active --quiet hysteria-server") return err == nil } func StartHysteria2() error { - if err := setHysteria2ConfigYAML(); err != nil { - return err - } - return util.Systemctl("restart", "hysteria-server") + return errors.New("managed by orchestrator: use hy2xs-orchestrator reconfigure") } func StopHysteria2() error { - return util.Systemctl("stop", "hysteria-server") + return errors.New("managed by orchestrator: use hy2xs-orchestrator reconfigure") } func RestartHysteria2() error { - if err := StopHysteria2(); err != nil { - return err - } - if err := StartHysteria2(); err != nil { - return err - } - return nil + return errors.New("managed by orchestrator: use hy2xs-orchestrator reconfigure") } func ReleaseHysteria2() error { diff --git a/docs/03-server-hysteria2.md b/docs/03-server-hysteria2.md index d184e1a..768145e 100644 --- a/docs/03-server-hysteria2.md +++ b/docs/03-server-hysteria2.md @@ -61,14 +61,15 @@ Hysteria2 — основной транспортный компонент се Требования: - production default: `acme` - поддерживаемые режимы: `acme | file | self_signed_dev` -- `self_signed_dev` только для dev/lab +- `self_signed_dev` только для dev/lab и только при явном `HY2XS_ALLOW_SELF_SIGNED_DEV=true` - корректный `server_name` / SNI на клиентах - одна понятная TLS policy - без смешивания нескольких несовместимых схем по умолчанию Инварианты: - `acme` -> только `acme` block в конфиге; -- `acme` block обязан содержать `type: http|tls|dns` из runtime env (`HY2XS_ACME_TYPE`); +- `acme` block обязан содержать `type: http|tls` из runtime env (`HY2XS_ACME_TYPE`); +- `HY2XS_ACME_TYPE=dns` в production-профиле запрещён до отдельной реализации; - `file` -> только `tls.cert`/`tls.key` block; - `self_signed_dev` -> только dev сценарии. diff --git a/docs/09-post-install-env.md b/docs/09-post-install-env.md index d216f68..97906d7 100644 --- a/docs/09-post-install-env.md +++ b/docs/09-post-install-env.md @@ -65,6 +65,7 @@ - `HY2XS_FIREWALL_STAGED_APPLY` - `HY2XS_ADMIN_USER` - `HY2XS_FORCE_PASSWORD_CHANGE` +- `HY2XS_ALLOW_SELF_SIGNED_DEV` ### Hysteria - `HY2_SOURCE=official-upstream` @@ -106,6 +107,7 @@ Важно: - `HY2XS_ADMIN_INITIAL_PASSWORD` используется только для первичного bootstrap seed; - `HY2XS_ADMIN_CON_PASS` — отдельная runtime-сущность для Hysteria auth/smoke; +- bootstrap secret хранится в явном формате `KEY=VALUE` (`ADMIN_USER`, `ADMIN_INITIAL_PASSWORD`, `ADMIN_CON_PASS`), права `0600`; - после первичного seed перезапуски `hy2xs-admin` не должны переопределять пароль admin и `con_pass`. ## Что нельзя делать diff --git a/orchestrator/src/config/env.ts b/orchestrator/src/config/env.ts index 5617df1..3bcaa69 100644 --- a/orchestrator/src/config/env.ts +++ b/orchestrator/src/config/env.ts @@ -111,7 +111,7 @@ export function parseRuntimeEnv(content: string): RuntimeConfig { domain: env.HY2XS_DOMAIN || "", publicHost: normalizePublicHost(env.HY2XS_PUBLIC_HOST || env.HY2XS_DOMAIN || ""), publicPort: parsePort("HY2XS_PUBLIC_PORT", env.HY2XS_PUBLIC_PORT, hysteriaPort), - ipv6Enabled: false, + ipv6Enabled: parseBool("HY2XS_IPV6_ENABLED", env.HY2XS_IPV6_ENABLED, false), sshPort: parsePort("HY2XS_SSH_PORT", env.HY2XS_SSH_PORT, 22), firewallEnabled: parseBool("HY2XS_FIREWALL_ENABLED", env.HY2XS_FIREWALL_ENABLED, true), firewallStagedApply: parseBool("HY2XS_FIREWALL_STAGED_APPLY", env.HY2XS_FIREWALL_STAGED_APPLY, true), @@ -121,6 +121,7 @@ export function parseRuntimeEnv(content: string): RuntimeConfig { 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), + allowSelfSignedDev: parseBool("HY2XS_ALLOW_SELF_SIGNED_DEV", env.HY2XS_ALLOW_SELF_SIGNED_DEV, false), tlsMode, acmeType, acmeEmail: env.HY2XS_ACME_EMAIL || "", @@ -178,11 +179,17 @@ export function validateRuntimeConfig(config: RuntimeConfig): void { if (!config.acmeEmail) { throw new Error("HY2XS_ACME_EMAIL is required for TLS mode acme"); } + if (config.acmeType === "dns") { + throw new Error("HY2XS_ACME_TYPE=dns is not supported in production profile yet"); + } } if (config.tlsMode === "file") { requireValue("HY2XS_TLS_CERT_PATH", config.tlsCertPath); requireValue("HY2XS_TLS_KEY_PATH", config.tlsKeyPath); } + if (config.tlsMode === "self_signed_dev" && !config.allowSelfSignedDev) { + throw new Error("HY2XS_TLS_MODE=self_signed_dev requires HY2XS_ALLOW_SELF_SIGNED_DEV=true"); + } if (config.publicHost === "0.0.0.0") { throw new Error("HY2XS_PUBLIC_HOST must be a routable domain or IPv4, not 0.0.0.0"); @@ -205,6 +212,7 @@ export function renderRuntimeEnv(config: RuntimeConfig): string { `HY2XS_ADMIN_INITIAL_PASSWORD=${config.adminInitialPassword}`, `HY2XS_ADMIN_CON_PASS=${config.adminConPass}`, `HY2XS_FORCE_PASSWORD_CHANGE=${config.forcePasswordChange}`, + `HY2XS_ALLOW_SELF_SIGNED_DEV=${config.allowSelfSignedDev}`, `HY2XS_TLS_MODE=${config.tlsMode}`, `HY2XS_ACME_TYPE=${config.acmeType}`, `HY2XS_ACME_EMAIL=${config.acmeEmail}`, diff --git a/orchestrator/src/steps/config.ts b/orchestrator/src/steps/config.ts index 8092207..339c16a 100644 --- a/orchestrator/src/steps/config.ts +++ b/orchestrator/src/steps/config.ts @@ -22,6 +22,7 @@ export async function generateConfig(context: InstallContext): Promise { UI_PORT: context.config.uiPort, BANDWIDTH_UP: context.config.hysteriaBandwidthUp, BANDWIDTH_DOWN: context.config.hysteriaBandwidthDown, + IGNORE_CLIENT_BANDWIDTH: context.config.hysteriaIgnoreClientBandwidth ? "true" : "false", TLS_ACME_BLOCK: tlsAcmeBlock, TLS_FILE_BLOCK: tlsFileBlock, AUTH_INSECURE: context.config.tlsMode === "self_signed_dev" ? "true" : "false" diff --git a/orchestrator/src/steps/env.ts b/orchestrator/src/steps/env.ts index 58dce0a..cad3d5b 100644 --- a/orchestrator/src/steps/env.ts +++ b/orchestrator/src/steps/env.ts @@ -30,5 +30,13 @@ export async function writePostInstallEnv(context: InstallContext): Promise { return; } - await runVisible`systemctl start hysteria-server hy2xs-admin`; + await runVisible`systemctl restart hysteria-server hy2xs-admin`; await runVisible`systemctl is-active --quiet hysteria-server`; await runVisible`systemctl is-active --quiet hy2xs-admin`; await runVisible`/usr/local/bin/hysteria version`; @@ -16,7 +16,9 @@ export async function smoke(context: InstallContext): Promise { await runVisible`test -s /etc/hy2xs/hy2xs.env`; await runVisible`test -s /etc/hysteria/post-install.env`; await runVisible`test -s ${context.config.bootstrapAdminSecretPath}`; - await runVisible`grep -q '^${context.config.adminUser}:' ${context.config.bootstrapAdminSecretPath}`; + await runVisible`grep -q '^ADMIN_USER=' ${context.config.bootstrapAdminSecretPath}`; + await runVisible`grep -q '^ADMIN_INITIAL_PASSWORD=' ${context.config.bootstrapAdminSecretPath}`; + await runVisible`grep -q '^ADMIN_CON_PASS=' ${context.config.bootstrapAdminSecretPath}`; await runVisible`test "$(stat -c '%a' /etc/hysteria/config.yaml)" = '600'`; await runVisible`test "$(stat -c '%a' /etc/hy2xs/hy2xs.env)" = '600'`; await runVisible`test "$(stat -c '%a' /etc/hysteria/post-install.env)" = '600'`; @@ -32,7 +34,7 @@ export async function smoke(context: InstallContext): Promise { throw new Error(`unexpected auth response for invalid credentials: ${invalidAuthResponse}`); } - const adminConPass = (await runSecret`grep '^${context.config.adminUser}:' ${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) { throw new Error("admin connection password is empty in bootstrap secret file"); } diff --git a/orchestrator/src/types/context.ts b/orchestrator/src/types/context.ts index f4a0502..93ac4ac 100644 --- a/orchestrator/src/types/context.ts +++ b/orchestrator/src/types/context.ts @@ -24,7 +24,7 @@ export type RuntimeConfig = { domain: string; publicHost: string; publicPort: number; - ipv6Enabled: false; + ipv6Enabled: boolean; sshPort: number; firewallEnabled: boolean; firewallStagedApply: boolean; @@ -34,6 +34,7 @@ export type RuntimeConfig = { adminInitialPassword: string; adminConPass: string; forcePasswordChange: boolean; + allowSelfSignedDev: boolean; tlsMode: TlsMode; acmeType: "http" | "tls" | "dns"; acmeEmail: string; diff --git a/package/config/hy2xs.env b/package/config/hy2xs.env index 979eb38..ac3c1c4 100644 --- a/package/config/hy2xs.env +++ b/package/config/hy2xs.env @@ -11,7 +11,8 @@ HY2XS_UI_PORT=8080 HY2XS_ADMIN_USER=hy2xsadmin HY2XS_ADMIN_INITIAL_PASSWORD=__GENERATE__ HY2XS_ADMIN_CON_PASS=__GENERATE__ -HY2XS_FORCE_PASSWORD_CHANGE=true +HY2XS_FORCE_PASSWORD_CHANGE=false +HY2XS_ALLOW_SELF_SIGNED_DEV=false HY2XS_TLS_MODE=acme HY2XS_ACME_TYPE=http HY2XS_ACME_EMAIL=replace-with-your-email@example.com diff --git a/package/templates/hysteria/config.yaml.tpl b/package/templates/hysteria/config.yaml.tpl index 3990bbb..70a2290 100644 --- a/package/templates/hysteria/config.yaml.tpl +++ b/package/templates/hysteria/config.yaml.tpl @@ -18,6 +18,8 @@ bandwidth: up: "{{BANDWIDTH_UP}}" down: "{{BANDWIDTH_DOWN}}" +ignoreClientBandwidth: {{IGNORE_CLIENT_BANDWIDTH}} + trafficStats: listen: {{HYSTERIA_API_HOST}}:{{HYSTERIA_API_PORT}} secret: "{{HYSTERIA_API_SECRET}}"