fix(runtime): закрыть fix2 P0 runtime pass без legacy
This commit is contained in:
+9
-6
@@ -14,6 +14,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func resolveUiBindHost() (string, error) {
|
||||
@@ -32,7 +33,6 @@ func runServer(port string) error {
|
||||
defer releaseResource()
|
||||
|
||||
middleware.InitLog()
|
||||
service.InitForward()
|
||||
if err := initFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -77,16 +77,19 @@ func releaseResource() {
|
||||
if err := service.ReleaseHysteria2(); err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
if err := service.RemoveByComment(); err != nil {
|
||||
logrus.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func initFile() error {
|
||||
var dirs = []string{constant.LogDir, constant.SqliteDBDir, constant.BinDir, constant.ExportPathDir}
|
||||
var dirs = []string{
|
||||
constant.LogDir,
|
||||
constant.SqliteDBDir,
|
||||
constant.BinDir,
|
||||
constant.ExportPathDir,
|
||||
filepath.Dir(constant.SqliteDBPath),
|
||||
}
|
||||
for _, item := range dirs {
|
||||
if !util.Exists(item) {
|
||||
if err := os.Mkdir(item, os.ModePerm); err != nil {
|
||||
if err := os.MkdirAll(item, 0o750); err != nil {
|
||||
logrus.Errorf("%s create err: %v", item, err)
|
||||
return errors.New(fmt.Sprintf("%s create err", item))
|
||||
}
|
||||
|
||||
@@ -78,11 +78,6 @@ func UpdateConfigs(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if key == constant.Hysteria2ConfigPortHopping {
|
||||
vo.Fail("port hopping out of scope in HY2XS production", c)
|
||||
return
|
||||
}
|
||||
|
||||
if key == constant.ResetTrafficCron {
|
||||
resetTrafficCron, err := service.GetConfig(constant.ResetTrafficCron)
|
||||
if err != nil {
|
||||
|
||||
+106
-26
@@ -13,17 +13,18 @@ import (
|
||||
"hy2xs-admin/util"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var sqlInitStr = "CREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT NOT NULL UNIQUE DEFAULT '',\n pass TEXT NOT NULL DEFAULT '',\n con_pass TEXT NOT NULL DEFAULT '',\n quota INTEGER NOT NULL DEFAULT 0,\n download INTEGER NOT NULL DEFAULT 0,\n upload INTEGER NOT NULL DEFAULT 0,\n expire_time INTEGER NOT NULL DEFAULT 0,\n kick_util_time INTEGER NOT NULL DEFAULT 0,\n device_no INTEGER NOT NULL DEFAULT 3,\n role TEXT NOT NULL DEFAULT 'user',\n deleted INTEGER NOT NULL DEFAULT 0,\n create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nALTER TABLE account\n ADD COLUMN login_at INTEGER NOT NULL DEFAULT 0;\nALTER TABLE account\n ADD COLUMN con_at INTEGER NOT NULL DEFAULT 0;\nALTER TABLE account\n ADD COLUMN remark INTEGER NOT NULL DEFAULT '';\nCREATE INDEX IF NOT EXISTS account_deleted_index ON account (deleted);\nCREATE INDEX IF NOT EXISTS account_username_index ON account (username);\nCREATE INDEX IF NOT EXISTS account_con_pass_index ON account (con_pass);\nCREATE INDEX IF NOT EXISTS account_pass_index ON account (pass);\nINSERT INTO account (id, username, pass, con_pass, quota, download, upload, expire_time, device_no, role)\nSELECT 1 ,'sysadmin', '02f382b76ca1ab7aa06ab03345c7712fd5b971fb0c0f2aef98bac9cd', 'sysadmin.sysadmin', -1, 0, 0, 253370736000000, 6, 'admin'\n WHERE NOT EXISTS (SELECT 1 FROM account WHERE id = 1);\nCREATE TABLE IF NOT EXISTS config\n(\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n key TEXT NOT NULL UNIQUE DEFAULT '',\n value TEXT NOT NULL DEFAULT '',\n remark TEXT NOT NULL DEFAULT '',\n create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nCREATE INDEX IF NOT EXISTS config_key_index ON config (key);\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_WEB_PORT', '8081', 'HY2XS admin Web Port'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_WEB_PORT');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_WEB_CONTEXT', '/', 'HY2XS admin Web Context'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_WEB_CONTEXT');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_CRT_PATH', '', 'HY2XS admin CRT File Path'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_CRT_PATH');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_KEY_PATH', '', 'HY2XS admin KEY File Path'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_KEY_PATH');\nINSERT INTO config (key, value, remark)\nSELECT 'JWT_SECRET', hex(randomblob(10)), 'JWT Secret'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'JWT_SECRET');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_ENABLE', '0', 'Hysteria2 Switch'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_ENABLE');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_CONFIG', '', 'Hysteria2 Config'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_TRAFFIC_TIME', '1', 'Hysteria2 Traffic Time'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_TRAFFIC_TIME');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_CONFIG_REMARK', '', 'Hysteria2 Config Remark'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG_REMARK');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_CONFIG_PORT_HOPPING', '', 'Hysteria2 Config Port Hopping'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG_PORT_HOPPING');\nINSERT INTO config (key, value, remark)\nSELECT 'RESET_TRAFFIC_CRON', '', 'Reset Traffic Cron'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'RESET_TRAFFIC_CRON');\nINSERT INTO config (key, value, remark)\nSELECT 'CLASH_EXTENSION', '', 'Clash Subscription Extension'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'CLASH_EXTENSION');"
|
||||
var sqlInitStr = "CREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT NOT NULL UNIQUE DEFAULT '',\n pass TEXT NOT NULL DEFAULT '',\n con_pass TEXT NOT NULL DEFAULT '',\n quota INTEGER NOT NULL DEFAULT 0,\n download INTEGER NOT NULL DEFAULT 0,\n upload INTEGER NOT NULL DEFAULT 0,\n expire_time INTEGER NOT NULL DEFAULT 0,\n kick_util_time INTEGER NOT NULL DEFAULT 0,\n device_no INTEGER NOT NULL DEFAULT 3,\n role TEXT NOT NULL DEFAULT 'user',\n deleted INTEGER NOT NULL DEFAULT 0,\n create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nALTER TABLE account\n ADD COLUMN login_at INTEGER NOT NULL DEFAULT 0;\nALTER TABLE account\n ADD COLUMN con_at INTEGER NOT NULL DEFAULT 0;\nALTER TABLE account\n ADD COLUMN remark INTEGER NOT NULL DEFAULT '';\nALTER TABLE account\n ADD COLUMN force_password_change INTEGER NOT NULL DEFAULT 0;\nCREATE INDEX IF NOT EXISTS account_deleted_index ON account (deleted);\nCREATE INDEX IF NOT EXISTS account_username_index ON account (username);\nCREATE INDEX IF NOT EXISTS account_con_pass_index ON account (con_pass);\nCREATE INDEX IF NOT EXISTS account_pass_index ON account (pass);\nCREATE TABLE IF NOT EXISTS config\n(\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n key TEXT NOT NULL UNIQUE DEFAULT '',\n value TEXT NOT NULL DEFAULT '',\n remark TEXT NOT NULL DEFAULT '',\n create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\nCREATE INDEX IF NOT EXISTS config_key_index ON config (key);\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_WEB_PORT', '8081', 'HY2XS admin Web Port'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_WEB_PORT');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_WEB_CONTEXT', '/', 'HY2XS admin Web Context'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_WEB_CONTEXT');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_CRT_PATH', '', 'HY2XS admin CRT File Path'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_CRT_PATH');\nINSERT INTO config (key, value, remark)\nSELECT 'H_UI_KEY_PATH', '', 'HY2XS admin KEY File Path'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'H_UI_KEY_PATH');\nINSERT INTO config (key, value, remark)\nSELECT 'JWT_SECRET', hex(randomblob(10)), 'JWT Secret'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'JWT_SECRET');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_ENABLE', '0', 'Hysteria2 Switch'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_ENABLE');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_CONFIG', '', 'Hysteria2 Config'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_TRAFFIC_TIME', '1', 'Hysteria2 Traffic Time'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_TRAFFIC_TIME');\nINSERT INTO config (key, value, remark)\nSELECT 'HYSTERIA2_CONFIG_REMARK', '', 'Hysteria2 Config Remark'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG_REMARK');\nINSERT INTO config (key, value, remark)\nSELECT 'RESET_TRAFFIC_CRON', '', 'Reset Traffic Cron'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'RESET_TRAFFIC_CRON');\nINSERT INTO config (key, value, remark)\nSELECT 'CLASH_EXTENSION', '', 'Clash Subscription Extension'\n WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'CLASH_EXTENSION');"
|
||||
|
||||
var sqliteDB *gorm.DB
|
||||
|
||||
func InitSqliteDB() error {
|
||||
var err error
|
||||
sqliteDB, err = gorm.Open(sqlite.Open(fmt.Sprintf("%s%s", os.Getenv("HUI_DATA"), constant.SqliteDBPath)), &gorm.Config{
|
||||
sqliteDB, err = gorm.Open(sqlite.Open(constant.SqliteDBPath), &gorm.Config{
|
||||
TranslateError: true,
|
||||
Logger: logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags),
|
||||
@@ -72,53 +73,132 @@ func InitSql(port string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := ensureAccountSchema(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureSecureBootstrapAdmin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureTrafficStatsSecret(); err != nil {
|
||||
return err
|
||||
}
|
||||
if tx := sqliteDB.Exec("ALTER TABLE account ADD COLUMN force_password_change INTEGER NOT NULL DEFAULT 0"); tx.Error != nil && !strings.Contains(tx.Error.Error(), "duplicate column name") {
|
||||
logrus.Errorf("sqlite exec err: %v", tx.Error)
|
||||
return errors.New("sqlite exec err")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func envInt(name string, fallback int) int {
|
||||
if raw := strings.TrimSpace(os.Getenv(name)); raw != "" {
|
||||
if value, err := strconv.Atoi(raw); err == nil {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func envBoolAsInt(name string, fallback int) int {
|
||||
raw := strings.TrimSpace(strings.ToLower(os.Getenv(name)))
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
if raw == "true" || raw == "1" {
|
||||
return 1
|
||||
}
|
||||
if raw == "false" || raw == "0" {
|
||||
return 0
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func ensureAccountSchema() error {
|
||||
if tx := sqliteDB.Exec("ALTER TABLE account ADD COLUMN force_password_change INTEGER NOT NULL DEFAULT 0"); tx.Error != nil && !strings.Contains(tx.Error.Error(), "duplicate column name") {
|
||||
logrus.Errorf("sqlite exec err: %v", tx.Error)
|
||||
return errors.New("sqlite exec err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureSecureBootstrapAdmin() error {
|
||||
admin, err := GetAccount("role = 'admin' and deleted = 0")
|
||||
if err != nil {
|
||||
return nil
|
||||
adminUser := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_USER"))
|
||||
if adminUser == "" {
|
||||
adminUser = "admin"
|
||||
}
|
||||
if admin.Pass == nil {
|
||||
return nil
|
||||
}
|
||||
if !util.IsBcryptHash(*admin.Pass) {
|
||||
adminPassword := strings.TrimSpace(os.Getenv("HY2XS_ADMIN_INITIAL_PASSWORD"))
|
||||
if adminPassword == "" {
|
||||
password, pwdErr := util.RandomString(18)
|
||||
if pwdErr != nil {
|
||||
return pwdErr
|
||||
}
|
||||
hash, hashErr := util.HashPassword(password)
|
||||
if hashErr != nil {
|
||||
return hashErr
|
||||
adminPassword = password
|
||||
}
|
||||
forcePasswordChange := envBoolAsInt("HY2XS_FORCE_PASSWORD_CHANGE", 1)
|
||||
quota := int64(-1)
|
||||
expireTime := int64(253370736000000)
|
||||
deviceNo := int64(envInt("HY2XS_ADMIN_DEVICE_NO", 6))
|
||||
role := "admin"
|
||||
deleted := int64(0)
|
||||
conPassSecret, conErr := util.RandomString(28)
|
||||
if conErr != nil {
|
||||
return conErr
|
||||
}
|
||||
conPass := fmt.Sprintf("%s.%s", adminUser, conPassSecret)
|
||||
hash, hashErr := util.HashPassword(adminPassword)
|
||||
if hashErr != nil {
|
||||
return hashErr
|
||||
}
|
||||
|
||||
admin, err := GetAccount("role = 'admin' and deleted = 0")
|
||||
if err != nil {
|
||||
username := adminUser
|
||||
account := entity.Account{
|
||||
Username: &username,
|
||||
Pass: &hash,
|
||||
ConPass: &conPass,
|
||||
Quota: "a,
|
||||
ExpireTime: &expireTime,
|
||||
DeviceNo: &deviceNo,
|
||||
Role: &role,
|
||||
Deleted: &deleted,
|
||||
ForcePasswordChange: func() *int64 { v := int64(forcePasswordChange); return &v }(),
|
||||
}
|
||||
conPass, conErr := util.RandomString(28)
|
||||
if conErr != nil {
|
||||
return conErr
|
||||
}
|
||||
if updateErr := UpdateAccount([]int64{*admin.Id}, map[string]interface{}{
|
||||
"pass": hash,
|
||||
"force_password_change": 1,
|
||||
"con_pass": fmt.Sprintf("%s.%s", *admin.Username, conPass),
|
||||
}); updateErr != nil {
|
||||
return updateErr
|
||||
if _, saveErr := SaveAccount(account); saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if admin.Pass == 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
|
||||
}
|
||||
|
||||
func ensureTrafficStatsSecret() error {
|
||||
envSecret := strings.TrimSpace(os.Getenv("HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET"))
|
||||
if envSecret != "" {
|
||||
if existing, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
||||
if existing.Value != nil && *existing.Value != envSecret {
|
||||
return UpdateConfig([]string{constant.Hysteria2TrafficStatsSecret}, map[string]interface{}{"value": envSecret})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
key := constant.Hysteria2TrafficStatsSecret
|
||||
remark := "Hysteria2 trafficStats API secret"
|
||||
if _, saveErr := SaveConfig(entity.Config{Key: &key, Value: &envSecret, Remark: &remark}); saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := GetConfig("key = ?", constant.Hysteria2TrafficStatsSecret); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@ CREATE INDEX IF NOT EXISTS account_deleted_index ON account (deleted);
|
||||
CREATE INDEX IF NOT EXISTS account_username_index ON account (username);
|
||||
CREATE INDEX IF NOT EXISTS account_con_pass_index ON account (con_pass);
|
||||
CREATE INDEX IF NOT EXISTS account_pass_index ON account (pass);
|
||||
INSERT INTO account (id, username, pass, con_pass, quota, download, upload, expire_time, device_no, role)
|
||||
SELECT 1 ,'sysadmin', '02f382b76ca1ab7aa06ab03345c7712fd5b971fb0c0f2aef98bac9cd', 'sysadmin.sysadmin', -1, 0, 0, 253370736000000, 6, 'admin'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM account WHERE id = 1);
|
||||
CREATE TABLE IF NOT EXISTS config
|
||||
(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -66,9 +63,6 @@ INSERT INTO config (key, value, remark)
|
||||
SELECT 'HYSTERIA2_CONFIG_REMARK', '', 'Hysteria2 Config Remark'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG_REMARK');
|
||||
INSERT INTO config (key, value, remark)
|
||||
SELECT 'HYSTERIA2_CONFIG_PORT_HOPPING', '', 'Hysteria2 Config Port Hopping'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'HYSTERIA2_CONFIG_PORT_HOPPING');
|
||||
INSERT INTO config (key, value, remark)
|
||||
SELECT 'RESET_TRAFFIC_CRON', '', 'Reset Traffic Cron'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM config WHERE key = 'RESET_TRAFFIC_CRON');
|
||||
INSERT INTO config (key, value, remark)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
## Development environment
|
||||
|
||||
# Variables must use the VITE_ prefix to be exposed externally
|
||||
NODE_ENV='development'
|
||||
|
||||
VITE_APP_TITLE = 'h-ui'
|
||||
VITE_APP_PORT = 3000
|
||||
VITE_APP_BASE_API = '/hui'
|
||||
@@ -1,5 +0,0 @@
|
||||
## Production environment
|
||||
|
||||
VITE_APP_TITLE = 'h-ui'
|
||||
VITE_APP_PORT = 80
|
||||
VITE_APP_BASE_API = '/hui'
|
||||
@@ -37,7 +37,6 @@
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"fast-glob": "^3.2.11",
|
||||
"postcss": "^8.4.21",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-scss": "^4.0.6",
|
||||
@@ -57,8 +56,7 @@
|
||||
"unplugin-vue-components": "^0.23.0",
|
||||
"vite": "^4.3.1",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^0.35.0",
|
||||
"vite-plugin-dynamic-base": "1.0.2"
|
||||
"vue-tsc": "^0.35.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
|
||||
Generated
+14
-151
@@ -78,9 +78,6 @@ importers:
|
||||
eslint-plugin-vue:
|
||||
specifier: ^9.9.0
|
||||
version: 9.9.0(eslint@8.34.0)
|
||||
fast-glob:
|
||||
specifier: ^3.2.11
|
||||
version: 3.3.1
|
||||
postcss:
|
||||
specifier: ^8.4.21
|
||||
version: 8.4.21
|
||||
@@ -135,9 +132,6 @@ importers:
|
||||
vite:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1(@types/node@20.7.0)(sass@1.58.3)
|
||||
vite-plugin-dynamic-base:
|
||||
specifier: 1.0.2
|
||||
version: 1.0.2
|
||||
vite-plugin-svg-icons:
|
||||
specifier: ^2.0.1
|
||||
version: 2.0.1(vite@4.3.1(@types/node@20.7.0)(sass@1.58.3))
|
||||
@@ -437,6 +431,7 @@ packages:
|
||||
'@humanwhocodes/config-array@0.11.11':
|
||||
resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
deprecated: Use @eslint/config-array instead
|
||||
|
||||
'@humanwhocodes/module-importer@1.0.1':
|
||||
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||
@@ -444,6 +439,7 @@ packages:
|
||||
|
||||
'@humanwhocodes/object-schema@1.2.1':
|
||||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||
deprecated: Use @eslint/object-schema instead
|
||||
|
||||
'@iconify-json/ep@1.1.8':
|
||||
resolution: {integrity: sha512-pHCrsWU1R9/pTDU+Fps4+mjqOQFLtpGdXWegkhQ1P1DlgQAlCPyICtl6E1s8b7VwJMeZXaK84HA02UF6WD0o/Q==}
|
||||
@@ -527,81 +523,6 @@ packages:
|
||||
rollup:
|
||||
optional: true
|
||||
|
||||
'@swc/core-darwin-arm64@1.11.24':
|
||||
resolution: {integrity: sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@swc/core-darwin-x64@1.11.24':
|
||||
resolution: {integrity: sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@swc/core-linux-arm-gnueabihf@1.11.24':
|
||||
resolution: {integrity: sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@swc/core-linux-arm64-gnu@1.11.24':
|
||||
resolution: {integrity: sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.11.24':
|
||||
resolution: {integrity: sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.11.24':
|
||||
resolution: {integrity: sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.11.24':
|
||||
resolution: {integrity: sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.11.24':
|
||||
resolution: {integrity: sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@swc/core-win32-ia32-msvc@1.11.24':
|
||||
resolution: {integrity: sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@swc/core-win32-x64-msvc@1.11.24':
|
||||
resolution: {integrity: sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@swc/core@1.11.24':
|
||||
resolution: {integrity: sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
'@swc/helpers': '>=0.5.17'
|
||||
peerDependenciesMeta:
|
||||
'@swc/helpers':
|
||||
optional: true
|
||||
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
'@swc/types@0.1.21':
|
||||
resolution: {integrity: sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.7':
|
||||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
||||
|
||||
@@ -783,9 +704,11 @@ packages:
|
||||
|
||||
'@volar/vue-code-gen@0.35.0':
|
||||
resolution: {integrity: sha512-IqIkzs3eRlJjABVXXBf5ik4/a8lQ3wqKemWMR33+KzOmj5xtXS+lgMMLNXJpj0Mw2nOLVl0I2KuIaTHFy485DQ==}
|
||||
deprecated: 'WARNING: This project has been renamed to @vue/language-core. Install using @vue/language-core instead.'
|
||||
|
||||
'@volar/vue-typescript@0.35.0':
|
||||
resolution: {integrity: sha512-Ojn98P888wWbk1qeR5f6spgtJZMwUos+OkJfZpzyafN5S21LxF58o7DhhNG8rMm1zMNUQHb+evlp5AXI5zHOmA==}
|
||||
deprecated: 'WARNING: This project has been renamed to @vue/typescript. Install using @vue/typescript instead.'
|
||||
|
||||
'@vue/compiler-core@3.2.45':
|
||||
resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==}
|
||||
@@ -1368,6 +1291,7 @@ packages:
|
||||
eslint@8.34.0:
|
||||
resolution: {integrity: sha512-1Z8iFsucw+7kSqXNZVslXS8Ioa4u2KM7GPwuKtkTFAqZ/cHMcEaR+1+Br0wLlot49cNxIiZk5wp8EAbPcYZxTg==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
|
||||
hasBin: true
|
||||
|
||||
espree@9.6.1:
|
||||
@@ -1547,6 +1471,7 @@ packages:
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
global-modules@2.0.0:
|
||||
resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
|
||||
@@ -1690,6 +1615,7 @@ packages:
|
||||
|
||||
inflight@1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
|
||||
|
||||
inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
@@ -1700,10 +1626,12 @@ packages:
|
||||
is-accessor-descriptor@0.1.6:
|
||||
resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
deprecated: Please upgrade to v0.1.7
|
||||
|
||||
is-accessor-descriptor@1.0.0:
|
||||
resolution: {integrity: sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
deprecated: Please upgrade to v1.0.1
|
||||
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
@@ -1721,10 +1649,12 @@ packages:
|
||||
is-data-descriptor@0.1.4:
|
||||
resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
deprecated: Please upgrade to v0.1.5
|
||||
|
||||
is-data-descriptor@1.0.0:
|
||||
resolution: {integrity: sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
deprecated: Please upgrade to v1.0.1
|
||||
|
||||
is-descriptor@0.1.6:
|
||||
resolution: {integrity: sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==}
|
||||
@@ -2084,9 +2014,6 @@ packages:
|
||||
node-fetch-native@1.4.0:
|
||||
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
|
||||
|
||||
node-html-parser@5.4.2:
|
||||
resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==}
|
||||
|
||||
node-releases@2.0.13:
|
||||
resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
|
||||
|
||||
@@ -2419,6 +2346,7 @@ packages:
|
||||
|
||||
rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rollup@3.29.3:
|
||||
@@ -2728,6 +2656,7 @@ packages:
|
||||
tar@6.2.0:
|
||||
resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
|
||||
text-table@0.2.0:
|
||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||
@@ -2909,9 +2838,6 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vite-plugin-dynamic-base@1.0.2:
|
||||
resolution: {integrity: sha512-0M1+W+EAFdZ2Aom5j2ToDkhZtHk1pS1vYesT+5sSEq7t+WARF1nl+yWsjOcyNdZZDt0fT80bNXPLJGoJuKSOjg==}
|
||||
|
||||
vite-plugin-svg-icons@2.0.1:
|
||||
resolution: {integrity: sha512-6ktD+DhV6Rz3VtedYvBKKVA2eXF+sAQVaKkKLDSqGUfnhqXl3bj5PPkVTl3VexfTuZy66PmINi8Q6eFnVfRUmA==}
|
||||
peerDependencies:
|
||||
@@ -2962,6 +2888,7 @@ packages:
|
||||
vue-i18n@9.0.0:
|
||||
resolution: {integrity: sha512-iks0eJDv/4cK/7tl/ooMUroNVVIGOK4kKS1PIHmPQk7QjT/sDfFM84vjPKgpARbw0GjJsOiADL43jufNfs9e9A==}
|
||||
engines: {node: '>= 10'}
|
||||
deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
@@ -3418,58 +3345,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
rollup: 3.29.3
|
||||
|
||||
'@swc/core-darwin-arm64@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-darwin-x64@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-linux-arm-gnueabihf@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-linux-arm64-gnu@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-linux-x64-musl@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-win32-ia32-msvc@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core-win32-x64-msvc@1.11.24':
|
||||
optional: true
|
||||
|
||||
'@swc/core@1.11.24':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
'@swc/types': 0.1.21
|
||||
optionalDependencies:
|
||||
'@swc/core-darwin-arm64': 1.11.24
|
||||
'@swc/core-darwin-x64': 1.11.24
|
||||
'@swc/core-linux-arm-gnueabihf': 1.11.24
|
||||
'@swc/core-linux-arm64-gnu': 1.11.24
|
||||
'@swc/core-linux-arm64-musl': 1.11.24
|
||||
'@swc/core-linux-x64-gnu': 1.11.24
|
||||
'@swc/core-linux-x64-musl': 1.11.24
|
||||
'@swc/core-win32-arm64-msvc': 1.11.24
|
||||
'@swc/core-win32-ia32-msvc': 1.11.24
|
||||
'@swc/core-win32-x64-msvc': 1.11.24
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
||||
'@swc/types@0.1.21':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
|
||||
'@sxzz/popperjs-es@2.11.7': {}
|
||||
|
||||
'@trysound/sax@0.2.0': {}
|
||||
@@ -5147,11 +5022,6 @@ snapshots:
|
||||
|
||||
node-fetch-native@1.4.0: {}
|
||||
|
||||
node-html-parser@5.4.2:
|
||||
dependencies:
|
||||
css-select: 4.3.0
|
||||
he: 1.2.0
|
||||
|
||||
node-releases@2.0.13: {}
|
||||
|
||||
normalize-package-data@2.5.0:
|
||||
@@ -6068,13 +5938,6 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vite-plugin-dynamic-base@1.0.2:
|
||||
dependencies:
|
||||
'@swc/core': 1.11.24
|
||||
node-html-parser: 5.4.2
|
||||
transitivePeerDependencies:
|
||||
- '@swc/helpers'
|
||||
|
||||
vite-plugin-svg-icons@2.0.1(vite@4.3.1(@types/node@20.7.0)(sass@1.58.3)):
|
||||
dependencies:
|
||||
'@types/svgo': 2.6.4
|
||||
|
||||
@@ -157,8 +157,6 @@ export default {
|
||||
config: {
|
||||
enable: "Enable/Disable",
|
||||
remark: "Remark",
|
||||
portHopping:
|
||||
"Port Hopping, Multiple individual ports: 1234,5678,9012; A range of ports: 20000-50000; A combination of both: 1234,5000-6000,7044,8000-9000",
|
||||
clashExtension: "Clash subscription extension",
|
||||
listen:
|
||||
"When the IP address is omitted, the server will listen on all interfaces, both IPv4 and IPv6. To listen on IPv4 only, you can use 0.0.0.0:443. To listen on IPv6 only, you can use [::]:443.",
|
||||
|
||||
@@ -152,7 +152,6 @@ export default {
|
||||
config: {
|
||||
enable: "Включить/отключить",
|
||||
remark: "Комментарий",
|
||||
portHopping: "Port hopping: отдельные порты, диапазоны или их комбинации",
|
||||
clashExtension: "Расширение подписки Clash",
|
||||
listen: "Адрес и порт прослушивания. Для IPv4 используйте 0.0.0.0:443.",
|
||||
tlsType: "Тип TLS",
|
||||
|
||||
Vendored
-1
@@ -9,7 +9,6 @@ declare module "*.vue" {
|
||||
|
||||
// TypeScript-подсказки для переменных окружения
|
||||
interface ImportMetaEnv {
|
||||
VITE_APP_TITLE: string;
|
||||
VITE_APP_PORT: string;
|
||||
VITE_APP_BASE_API: string;
|
||||
}
|
||||
|
||||
@@ -121,14 +121,6 @@
|
||||
<el-input v-model="configForm.remark" clearable />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="$t('hysteria.config.portHopping')"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-form-item label="portHopping" prop="portHopping">
|
||||
<el-input v-model="configForm.portHopping" clearable />
|
||||
</el-form-item>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
:content="$t('hysteria.config.clashExtension')"
|
||||
placement="bottom"
|
||||
@@ -1089,7 +1081,6 @@ const { t } = useI18n();
|
||||
|
||||
const hysteria2EnableKey = "HYSTERIA2_ENABLE";
|
||||
const hysteria2Remark = "HYSTERIA2_CONFIG_REMARK";
|
||||
const hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING";
|
||||
const clashExtension = "CLASH_EXTENSION";
|
||||
const dataFormRef = ref(ElForm);
|
||||
|
||||
@@ -1133,7 +1124,6 @@ const state = reactive({
|
||||
},
|
||||
configForm: {
|
||||
remark: "",
|
||||
portHopping: "",
|
||||
clashExtension: "",
|
||||
},
|
||||
dataForm: { ...defaultHysteria2ServerConfig } as Hysteria2ServerConfig,
|
||||
@@ -1292,7 +1282,6 @@ const handleExport = async () => {
|
||||
const handleReset = () => {
|
||||
state.configForm = {
|
||||
remark: "",
|
||||
portHopping: "",
|
||||
clashExtension: "",
|
||||
};
|
||||
state.dataForm = deepCopy(defaultHysteria2ServerConfig);
|
||||
@@ -1339,10 +1328,6 @@ const submitForm = () => {
|
||||
key: hysteria2Remark,
|
||||
value: state.configForm.remark,
|
||||
},
|
||||
{
|
||||
key: hysteria2ConfigPortHopping,
|
||||
value: state.configForm.portHopping,
|
||||
},
|
||||
{
|
||||
key: clashExtension,
|
||||
value: state.configForm.clashExtension,
|
||||
@@ -1479,7 +1464,6 @@ const setConfig = () => {
|
||||
keys: [
|
||||
hysteria2EnableKey,
|
||||
hysteria2Remark,
|
||||
hysteria2ConfigPortHopping,
|
||||
clashExtension,
|
||||
],
|
||||
}).then((response) => {
|
||||
@@ -1489,8 +1473,6 @@ const setConfig = () => {
|
||||
state.enableForm.enable = configVo.value;
|
||||
} else if (configVo.key === hysteria2Remark) {
|
||||
state.configForm.remark = configVo.value;
|
||||
} else if (configVo.key === hysteria2ConfigPortHopping) {
|
||||
state.configForm.portHopping = configVo.value;
|
||||
} else if (configVo.key === clashExtension) {
|
||||
state.configForm.clashExtension = configVo.value;
|
||||
}
|
||||
@@ -1624,5 +1606,3 @@ onMounted(() => {
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -21,12 +21,10 @@ const pathSrc = path.resolve(__dirname, "src");
|
||||
const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g;
|
||||
const DRIVE_LETTER_REGEX = /^[a-z]:/i;
|
||||
|
||||
import { dynamicBase } from "vite-plugin-dynamic-base";
|
||||
|
||||
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
return {
|
||||
base: mode === "production" ? "/__dynamic_base__/" : "/",
|
||||
base: "/",
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": pathSrc,
|
||||
@@ -57,7 +55,6 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dynamicBase({}),
|
||||
vue(),
|
||||
UnoCSS({
|
||||
/* options */
|
||||
|
||||
@@ -11,7 +11,6 @@ const (
|
||||
Hysteria2Config = "HYSTERIA2_CONFIG"
|
||||
Hysteria2TrafficTime = "HYSTERIA2_TRAFFIC_TIME"
|
||||
Hysteria2ConfigRemark = "HYSTERIA2_CONFIG_REMARK"
|
||||
Hysteria2ConfigPortHopping = "HYSTERIA2_CONFIG_PORT_HOPPING"
|
||||
ResetTrafficCron = "RESET_TRAFFIC_CRON"
|
||||
ClashExtension = "CLASH_EXTENSION"
|
||||
)
|
||||
|
||||
@@ -1,20 +1,35 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
LogDir = "logs/"
|
||||
SqliteDBDir = "data/"
|
||||
BinDir = "bin/"
|
||||
ExportPathDir = "export/"
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
SqliteDBPath = "data/h_ui.db"
|
||||
func getenv(name string, fallback string) string {
|
||||
value := os.Getenv(name)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var (
|
||||
DataRootDir = getenv("HUI_DATA", "/var/lib/hy2xs-admin")
|
||||
LogDir = getenv("HUI_LOG", "/var/log/hy2xs")
|
||||
SqliteDBDir = DataRootDir
|
||||
BinDir = filepath.Join(DataRootDir, "bin")
|
||||
ExportPathDir = filepath.Join(DataRootDir, "export")
|
||||
|
||||
SqliteDBPath = filepath.Join(DataRootDir, "h_ui.db")
|
||||
|
||||
Hysteria2ConfigPath = "/etc/hysteria/config.yaml"
|
||||
Hysteria2BinPath = "/usr/local/bin/hysteria"
|
||||
|
||||
SystemLogPath = "logs/hy2xs-admin.log"
|
||||
Hysteria2LogPath = "logs/hysteria2.log"
|
||||
|
||||
TokenType = "Bearer"
|
||||
|
||||
Version = "v0.0.22"
|
||||
SystemLogPath = filepath.Join(LogDir, "hy2xs-admin.log")
|
||||
Hysteria2LogPath = filepath.Join(LogDir, "hysteria2.log")
|
||||
)
|
||||
|
||||
const (
|
||||
TokenType = "Bearer"
|
||||
Version = "v0.0.22"
|
||||
)
|
||||
|
||||
@@ -43,6 +43,9 @@ func Login(username string, plainPassword string) (string, bool, error) {
|
||||
}
|
||||
|
||||
requirePasswordChange := legacy
|
||||
if account.ForcePasswordChange != nil {
|
||||
requirePasswordChange = *account.ForcePasswordChange != 0
|
||||
}
|
||||
return token, requirePasswordChange, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,234 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hy2xs-admin/dao"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/util"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
netManager string
|
||||
ingressInterface string
|
||||
Add = "add"
|
||||
Delete = "delete"
|
||||
Table = "hui_porthopping"
|
||||
Comment = "hui_hysteria_porthopping"
|
||||
)
|
||||
|
||||
func InitForward() {
|
||||
if nft, err := util.Exec("command -v nft"); err == nil && strings.TrimSpace(nft) != "" {
|
||||
netManager = "nft"
|
||||
} else if iptables, err := util.Exec("command -v iptables"); err == nil && strings.TrimSpace(iptables) != "" {
|
||||
netManager = "iptables"
|
||||
}
|
||||
|
||||
if ii, err := util.Exec("ls /sys/class/net | grep -E '^en|^eth'"); err == nil && strings.TrimSpace(ii) != "" {
|
||||
iiList := strings.Split(ii, "\n")
|
||||
ingressInterface = strings.TrimSpace(iiList[0])
|
||||
}
|
||||
}
|
||||
|
||||
func InitTableAndChain() error {
|
||||
if netManager == "nft" {
|
||||
_, err := util.Exec(fmt.Sprintf("nft add table inet %s", Table))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = util.Exec(fmt.Sprintf("nft add chain inet %s prerouting { type nat hook prerouting priority dstnat\\; policy accept\\; }", Table))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitPortHopping() error {
|
||||
if err := RemoveByComment(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hysteria2Config, err := GetHysteria2Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// set port forward
|
||||
hysteria2ConfigPortHopping, err := dao.GetConfig("key = ?", constant.Hysteria2ConfigPortHopping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *hysteria2ConfigPortHopping.Value != "" {
|
||||
listen := strings.Split(*hysteria2Config.Listen, ":")
|
||||
if len(listen) == 2 {
|
||||
portHoppings := strings.Split(*hysteria2ConfigPortHopping.Value, ",")
|
||||
for _, item := range portHoppings {
|
||||
if err := portForward(item, listen[1], Add); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func portForward(rules string, target string, option string) error {
|
||||
switch netManager {
|
||||
case "nft":
|
||||
switch option {
|
||||
case Add, Delete:
|
||||
return nftForward(rules, target, option)
|
||||
default:
|
||||
return errors.New("unsupported command option")
|
||||
}
|
||||
case "iptables":
|
||||
switch option {
|
||||
case Add:
|
||||
return iptablesForward(rules, target, "-A")
|
||||
case Delete:
|
||||
return iptablesForward(rules, target, "-D")
|
||||
default:
|
||||
return errors.New("unsupported command option")
|
||||
}
|
||||
default:
|
||||
return errors.New("port hopping not supported on this system")
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveByComment() error {
|
||||
switch netManager {
|
||||
case "nft":
|
||||
return ntfRemoveByComment(Comment)
|
||||
case "iptables":
|
||||
return iptablesRemoveByComment(Comment)
|
||||
default:
|
||||
return errors.New("port hopping not supported on this system")
|
||||
}
|
||||
}
|
||||
|
||||
func nftForward(rules string, target string, option string) error {
|
||||
if ingressInterface == "" {
|
||||
return fmt.Errorf("no network interface detected")
|
||||
}
|
||||
// nft list ruleset
|
||||
// Создать таблицу: nft add table inet hui_hysteria_porthopping
|
||||
// Создать chain: nft add chain inet hui_hysteria_porthopping prerouting { type nat hook prerouting priority dstnat\; policy accept\; }
|
||||
// Добавить rule: nft add rule inet hui_hysteria_porthopping prerouting iifname enp1s0 udp dport {30000-40000} counter redirect to :444 comment hui_hysteria_porthopping
|
||||
_, err := util.Exec(fmt.Sprintf("nft %s rule inet %s prerouting iifname %s udp dport {%s} counter redirect to :%s comment %s", option, Table, ingressInterface, rules, target, Comment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ntfRemoveByComment(comment string) error {
|
||||
rules, err := nftRules()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if strings.Contains(rule, comment) {
|
||||
parts := strings.Fields(rule)
|
||||
handle := parts[len(parts)-1]
|
||||
_, err := util.Exec(fmt.Sprintf("nft delete rule inet %s prerouting handle %s", Table, strings.TrimSpace(handle)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nftRules() ([]string, error) {
|
||||
listOutput, err := util.Exec(fmt.Sprintf("nft list ruleset | grep -q %s && echo 'found' || echo 'not found'", Comment))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strings.TrimSpace(listOutput) == "not found" {
|
||||
return []string{}, nil
|
||||
}
|
||||
output, err := util.Exec(fmt.Sprintf("nft --handle list chain inet %s prerouting", Table))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func iptablesForward(rules string, target string, option string) error {
|
||||
if ingressInterface == "" {
|
||||
return fmt.Errorf("no network interface detected")
|
||||
}
|
||||
|
||||
rulePairs := strings.Split(rules, ",")
|
||||
for _, pair := range rulePairs {
|
||||
ports := ""
|
||||
portRange := strings.Split(pair, "-")
|
||||
if len(portRange) == 1 {
|
||||
ports = strings.TrimSpace(portRange[0])
|
||||
} else if len(portRange) == 2 {
|
||||
startPort := strings.TrimSpace(portRange[0])
|
||||
endPort := strings.TrimSpace(portRange[1])
|
||||
ports = startPort + ":" + endPort
|
||||
} else {
|
||||
return fmt.Errorf("invalid port range format: %s", pair)
|
||||
}
|
||||
|
||||
if len(ports) != 0 {
|
||||
if err := iptablesAddRule(option, ports, target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesAddRule(option, ports, target string) error {
|
||||
protocols := [2]string{"iptables", "ip6tables"}
|
||||
for _, protocol := range protocols {
|
||||
// iptables -t nat -A PREROUTING -i enp1s0 -p udp --dport 30000:40000 -j REDIRECT --to-port 444 -m comment --comment hui_hysteria_porthopping
|
||||
_, err := util.Exec(fmt.Sprintf("%s -t nat %s PREROUTING -i %s -p udp --dport %s -j REDIRECT --to-port %s -m comment --comment %s", protocol, option, ingressInterface, ports, target, Comment))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesRemoveByComment(comment string) error {
|
||||
protocols := [2]string{"iptables", "ip6tables"}
|
||||
for _, protocol := range protocols {
|
||||
rules, err := iptablesRules(protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if strings.Contains(rule, comment) {
|
||||
parts := strings.Fields(rule)
|
||||
handle := parts[0]
|
||||
_, err := util.Exec(fmt.Sprintf("%s -t nat -D PREROUTING %s", protocol, strings.TrimSpace(handle)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func iptablesRules(protocol string) ([]string, error) {
|
||||
// iptables -t nat -L PREROUTING -v --line-numbers
|
||||
output, err := util.Exec(fmt.Sprintf("%s -t nat -L PREROUTING -v --line-numbers", protocol))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rules := strings.Split(output, "\n")
|
||||
return rules, nil
|
||||
}
|
||||
@@ -109,4 +109,5 @@
|
||||
|
||||
## Пример
|
||||
|
||||
См. [examples/post-install.env.example](examples/post-install.env.example).
|
||||
Используйте canonical runtime-файл `package/config/hy2xs.env` как базовый шаблон значений
|
||||
и переносите его параметры в `/etc/hy2xs/hy2xs.env`.
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
10. [10-access-layer-out-of-scope.md](10-access-layer-out-of-scope.md)
|
||||
11. [11-testing-and-acceptance.md](11-testing-and-acceptance.md)
|
||||
12. [12-operations-and-troubleshooting.md](12-operations-and-troubleshooting.md)
|
||||
13. [examples/post-install.env.example](examples/post-install.env.example)
|
||||
|
||||
## Жёсткие рамки baseline
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# post-install.env example
|
||||
# Baseline reference file for a clean Debian 12 deployment.
|
||||
|
||||
DEPLOY_TARGET_OS=debian12
|
||||
DEPLOY_TIMESTAMP=2026-04-13T10:00:00Z
|
||||
PACKAGE_NAME=hy2xs-install-package
|
||||
PACKAGE_BUILD_ID=build-20260413-001
|
||||
PACKAGE_VERSION=0.1.0
|
||||
|
||||
ORCH_SOURCE_STACK=bun-typescript
|
||||
ORCH_BUILD_MODE=bun-compile
|
||||
ORCH_BUILD_ID=build-20260413-001
|
||||
ORCH_ENTRYPOINT=/usr/local/lib/hy2xs/hy2xs-orchestrator
|
||||
|
||||
DEPLOY_DOMAIN=example.com
|
||||
PUBLIC_HOST=example.com
|
||||
PUBLIC_PORT=443
|
||||
SSH_PORT=22
|
||||
HY2XS_FIREWALL_ENABLED=true
|
||||
HY2XS_FIREWALL_STAGED_APPLY=true
|
||||
|
||||
HY2_SOURCE=official-upstream
|
||||
HY2_VERSION=v2.8.1
|
||||
HY2_TLS_MODE=acme
|
||||
HY2_ACME_EMAIL=admin@example.com
|
||||
HY2_TLS_CERT_PATH=
|
||||
HY2_TLS_KEY_PATH=
|
||||
HY2_LISTEN_HOST=0.0.0.0
|
||||
HY2_PORT=443
|
||||
HY2_AUTH_MODE=http
|
||||
HY2_AUTH_URL=http://127.0.0.1:8081/hui/hysteria2/auth
|
||||
HY2_TRAFFIC_STATS_LISTEN=127.0.0.1:25413
|
||||
HY2_OBFS_TYPE=salamander
|
||||
HY2_OBFS_PASSWORD=CHANGE_ME
|
||||
HY2_BANDWIDTH_UP_Mbps=50
|
||||
HY2_BANDWIDTH_DOWN_Mbps=50
|
||||
HY2_IGNORE_CLIENT_BANDWIDTH=false
|
||||
HY2_CONFIG_PATH=/etc/hysteria/config.yaml
|
||||
|
||||
HUI_ENABLED=true
|
||||
HUI_FORK_REF=packaged
|
||||
HUI_BUILD_ID=build-20260413-001
|
||||
HUI_BIND_HOST=127.0.0.1
|
||||
HUI_PORT=8081
|
||||
HUI_INSTALL_DIR=/opt/hy2xs-admin
|
||||
HUI_DATA_DIR=/var/lib/hy2xs-admin
|
||||
HUI_LOG_DIR=/var/log/hy2xs-admin
|
||||
@@ -3,7 +3,7 @@ import type { InstallContext, InstallOptions } from "../types/context";
|
||||
import { exists, readText, writeText } from "../lib/fs";
|
||||
import { runVisible } from "../lib/process";
|
||||
import { step } from "../lib/log";
|
||||
import { defaultRuntimeConfig, parseRuntimeEnv, renderRuntimeEnv } from "../config/env";
|
||||
import { parseRuntimeEnv, renderRuntimeEnv } from "../config/env";
|
||||
import { preflight } from "../steps/preflight";
|
||||
import { installDeps } from "../steps/deps";
|
||||
import { prepareFilesystem } from "../steps/filesystem";
|
||||
@@ -29,7 +29,9 @@ function secret(): string {
|
||||
|
||||
export async function install(options: InstallOptions): Promise<void> {
|
||||
const hasConfig = await exists(options.configPath);
|
||||
const config = hasConfig ? parseRuntimeEnv(await readText(options.configPath)) : defaultRuntimeConfig();
|
||||
const sourceConfigPath = hasConfig ? options.configPath : `${options.packageDir}/config/hy2xs.env`;
|
||||
const sourceConfigRaw = await readText(sourceConfigPath);
|
||||
const config = parseRuntimeEnv(sourceConfigRaw);
|
||||
|
||||
const context: InstallContext = {
|
||||
options,
|
||||
|
||||
@@ -83,6 +83,13 @@ function normalizeTlsMode(value: string): TlsMode {
|
||||
throw new Error(`invalid HY2XS_TLS_MODE: ${value}`);
|
||||
}
|
||||
|
||||
function normalizeAcmeType(value: string): "http" | "tls" | "dns" {
|
||||
if (value === "http" || value === "tls" || value === "dns") {
|
||||
return value;
|
||||
}
|
||||
throw new Error(`invalid HY2XS_ACME_TYPE: ${value}`);
|
||||
}
|
||||
|
||||
export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
const env = parseEnv(content);
|
||||
|
||||
@@ -91,6 +98,7 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
const hysteriaPort = parsePort("HY2XS_HYSTERIA_PORT", env.HY2XS_HYSTERIA_PORT, 443);
|
||||
const trafficStatsPort = parsePort("HY2XS_HYSTERIA_TRAFFIC_STATS_PORT", env.HY2XS_HYSTERIA_TRAFFIC_STATS_PORT, 36712);
|
||||
const tlsMode = normalizeTlsMode(env.HY2XS_TLS_MODE || "acme");
|
||||
const acmeType = normalizeAcmeType(env.HY2XS_ACME_TYPE || "http");
|
||||
|
||||
const config: RuntimeConfig = {
|
||||
domain: env.HY2XS_DOMAIN || "",
|
||||
@@ -106,6 +114,7 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
adminInitialPassword: env.HY2XS_ADMIN_INITIAL_PASSWORD || randomSecret(),
|
||||
forcePasswordChange: parseBool("HY2XS_FORCE_PASSWORD_CHANGE", env.HY2XS_FORCE_PASSWORD_CHANGE, true),
|
||||
tlsMode,
|
||||
acmeType,
|
||||
acmeEmail: env.HY2XS_ACME_EMAIL || "",
|
||||
tlsCertPath: env.HY2XS_TLS_CERT_PATH || "/etc/hysteria/server.crt",
|
||||
tlsKeyPath: env.HY2XS_TLS_KEY_PATH || "/etc/hysteria/server.key",
|
||||
@@ -140,46 +149,6 @@ export function parseRuntimeEnv(content: string): RuntimeConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function defaultRuntimeConfig(): RuntimeConfig {
|
||||
return parseRuntimeEnv(
|
||||
[
|
||||
"HY2XS_IPV6_ENABLED=false",
|
||||
"HY2XS_DOMAIN=",
|
||||
"HY2XS_PUBLIC_HOST=127.0.0.1",
|
||||
"HY2XS_PUBLIC_PORT=443",
|
||||
"HY2XS_SSH_PORT=22",
|
||||
"HY2XS_FIREWALL_ENABLED=true",
|
||||
"HY2XS_FIREWALL_STAGED_APPLY=true",
|
||||
"HY2XS_UI_BIND_HOST=127.0.0.1",
|
||||
"HY2XS_UI_PORT=8080",
|
||||
"HY2XS_ADMIN_USER=admin",
|
||||
`HY2XS_ADMIN_INITIAL_PASSWORD=${randomSecret()}`,
|
||||
"HY2XS_FORCE_PASSWORD_CHANGE=true",
|
||||
"HY2XS_TLS_MODE=self_signed_dev",
|
||||
"HY2XS_ACME_EMAIL=",
|
||||
"HY2XS_TLS_CERT_PATH=/etc/hysteria/server.crt",
|
||||
"HY2XS_TLS_KEY_PATH=/etc/hysteria/server.key",
|
||||
"HY2XS_HYSTERIA_BIND_HOST=0.0.0.0",
|
||||
"HY2XS_HYSTERIA_PORT=443",
|
||||
"HY2XS_HYSTERIA_AUTH_MODE=http",
|
||||
"HY2XS_HYSTERIA_AUTH_URL=http://127.0.0.1:8080/hui/hysteria2/auth",
|
||||
"HY2XS_HYSTERIA_TRAFFIC_STATS_HOST=127.0.0.1",
|
||||
"HY2XS_HYSTERIA_TRAFFIC_STATS_PORT=36712",
|
||||
`HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET=${randomSecret()}`,
|
||||
"HY2XS_HYSTERIA_OBFS_TYPE=salamander",
|
||||
`HY2XS_HYSTERIA_OBFS_PASSWORD=${randomSecret()}`,
|
||||
"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_HYSTERIA_VERSION=latest",
|
||||
"HY2XS_INSTALL_DIR=/opt/hy2xs-admin",
|
||||
"HY2XS_DATA_DIR=/var/lib/hy2xs-admin",
|
||||
"HY2XS_LOG_DIR=/var/log/hy2xs"
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
export function validateRuntimeConfig(config: RuntimeConfig): void {
|
||||
if (config.ipv6Enabled) {
|
||||
throw new Error("HY2XS is IPv4-only: HY2XS_IPV6_ENABLED must be false");
|
||||
@@ -228,6 +197,7 @@ export function renderRuntimeEnv(config: RuntimeConfig): string {
|
||||
`HY2XS_ADMIN_INITIAL_PASSWORD=${config.adminInitialPassword}`,
|
||||
`HY2XS_FORCE_PASSWORD_CHANGE=${config.forcePasswordChange}`,
|
||||
`HY2XS_TLS_MODE=${config.tlsMode}`,
|
||||
`HY2XS_ACME_TYPE=${config.acmeType}`,
|
||||
`HY2XS_ACME_EMAIL=${config.acmeEmail}`,
|
||||
`HY2XS_TLS_CERT_PATH=${config.tlsCertPath}`,
|
||||
`HY2XS_TLS_KEY_PATH=${config.tlsKeyPath}`,
|
||||
|
||||
@@ -48,3 +48,32 @@ export async function runVisible(command: TemplateStringsArray, ...args: unknown
|
||||
throw new Error(`command failed (${exitCode}): ${rendered}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHidden(command: TemplateStringsArray, ...args: unknown[]): Promise<void> {
|
||||
const rendered = renderCommand(command, args);
|
||||
const process = Bun.spawn(["sh", "-c", rendered], {
|
||||
stdout: "inherit",
|
||||
stderr: "inherit"
|
||||
});
|
||||
const exitCode = await process.exited;
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(`command failed (${exitCode})`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function runSecret(command: TemplateStringsArray, ...args: unknown[]): Promise<string> {
|
||||
const rendered = renderCommand(command, args);
|
||||
const process = Bun.spawn(["sh", "-c", rendered], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe"
|
||||
});
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(process.stdout).text(),
|
||||
new Response(process.stderr).text(),
|
||||
process.exited
|
||||
]);
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(`command failed (${exitCode}): ${stderr.trim()}`);
|
||||
}
|
||||
return stdout.trim();
|
||||
}
|
||||
|
||||
@@ -9,11 +9,17 @@ export async function applyFirewall(context: InstallContext): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const acmeChallengePort = context.config.acmeType === "tls" ? 443 : 80;
|
||||
const acmeRule = context.config.tlsMode === "acme"
|
||||
? `tcp dport ${acmeChallengePort} accept`
|
||||
: "# acme challenge port disabled";
|
||||
|
||||
const rendered = renderTemplate(await readText(`${context.options.packageDir}/templates/nftables/hy2xs.nft.tpl`), {
|
||||
SSH_PORT: context.config.sshPort,
|
||||
HYSTERIA_PORT: context.config.hysteriaPort,
|
||||
UI_PORT: context.config.uiPort,
|
||||
UI_BIND_HOST: context.config.uiBindHost
|
||||
UI_BIND_HOST: context.config.uiBindHost,
|
||||
ACME_RULE: acmeRule
|
||||
});
|
||||
|
||||
await runVisible`cp -a /etc/nftables.d/hy2xs.nft /etc/nftables.d/hy2xs.nft.bak 2>/dev/null || true`;
|
||||
@@ -30,7 +36,6 @@ export async function applyFirewall(context: InstallContext): Promise<void> {
|
||||
await runVisible`systemctl enable --now nftables`;
|
||||
|
||||
await runVisible`ss -H -ltn | grep -q ':${context.config.sshPort} ' || (echo 'ssh port check failed' >&2; exit 1)`;
|
||||
await runVisible`ss -H -lun | grep -q ':${context.config.hysteriaPort} ' || (echo 'hysteria udp port check failed' >&2; exit 1)`;
|
||||
|
||||
if (context.config.firewallStagedApply) {
|
||||
await runVisible`systemctl stop hy2xs-fw-rollback || true`;
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function installHysteria(context: InstallContext): Promise<void> {
|
||||
if (policy === "latest") {
|
||||
await runVisible`bash ${scriptPath}`;
|
||||
} else {
|
||||
await runVisible`HYSTERIA_VERSION=${policy} bash ${scriptPath}`;
|
||||
await runVisible`bash ${scriptPath} --version ${policy}`;
|
||||
}
|
||||
|
||||
await runVisible`test -x /usr/local/bin/hysteria`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { InstallContext } from "../types/context";
|
||||
import { info } from "../lib/log";
|
||||
import { runVisible } from "../lib/process";
|
||||
import { runHidden, runSecret, runVisible } from "../lib/process";
|
||||
|
||||
export async function smoke(context: InstallContext): Promise<void> {
|
||||
if (context.options.skipStart) {
|
||||
@@ -26,10 +26,13 @@ export async function smoke(context: InstallContext): Promise<void> {
|
||||
}
|
||||
await runVisible`ss -H -lun | grep -q '0.0.0.0:${context.config.hysteriaPort} '`;
|
||||
await runVisible`! ss -H -ltnu | grep -q '\[::\]'`;
|
||||
await runVisible`curl -fsS --max-time 5 http://127.0.0.1:${context.config.uiPort}/hui/hysteria2/auth >/dev/null`;
|
||||
await runHidden`curl -fsS --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 >/dev/null`;
|
||||
|
||||
await runVisible`curl -fsS --max-time 5 -H 'Authorization: ${context.config.hysteriaTrafficStatsSecret}' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online >/dev/null`;
|
||||
await runVisible`curl -fsS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online | grep -Eq '401|403'`;
|
||||
await runHidden`curl -fsS --max-time 5 -H 'Authorization: ${context.config.hysteriaTrafficStatsSecret}' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online >/dev/null`;
|
||||
const deniedCode = await runSecret`curl -fsS --max-time 5 -o /dev/null -w '%{http_code}' -H 'Authorization: invalid-hy2xs-secret' http://127.0.0.1:${context.config.hysteriaTrafficStatsPort}/online`;
|
||||
if (!/(401|403)/.test(deniedCode)) {
|
||||
throw new Error(`unexpected trafficStats status for invalid secret: ${deniedCode}`);
|
||||
}
|
||||
|
||||
await runVisible`nft -c -f /etc/nftables.conf`;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ export type RuntimeConfig = {
|
||||
adminInitialPassword: string;
|
||||
forcePasswordChange: boolean;
|
||||
tlsMode: TlsMode;
|
||||
acmeType: "http" | "tls" | "dns";
|
||||
acmeEmail: string;
|
||||
tlsCertPath: string;
|
||||
tlsKeyPath: string;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# HY2XS canonical production runtime config (packaged baseline)
|
||||
HY2XS_IPV6_ENABLED=false
|
||||
HY2XS_DOMAIN=
|
||||
HY2XS_PUBLIC_HOST=127.0.0.1
|
||||
HY2XS_PUBLIC_PORT=443
|
||||
HY2XS_SSH_PORT=22
|
||||
HY2XS_FIREWALL_ENABLED=true
|
||||
HY2XS_FIREWALL_STAGED_APPLY=true
|
||||
HY2XS_UI_BIND_HOST=127.0.0.1
|
||||
HY2XS_UI_PORT=8080
|
||||
HY2XS_ADMIN_USER=admin
|
||||
HY2XS_ADMIN_INITIAL_PASSWORD=
|
||||
HY2XS_FORCE_PASSWORD_CHANGE=true
|
||||
HY2XS_TLS_MODE=self_signed_dev
|
||||
HY2XS_ACME_EMAIL=
|
||||
HY2XS_TLS_CERT_PATH=/etc/hysteria/server.crt
|
||||
HY2XS_TLS_KEY_PATH=/etc/hysteria/server.key
|
||||
HY2XS_HYSTERIA_BIND_HOST=0.0.0.0
|
||||
HY2XS_HYSTERIA_PORT=443
|
||||
HY2XS_HYSTERIA_AUTH_MODE=http
|
||||
HY2XS_HYSTERIA_AUTH_URL=http://127.0.0.1:8080/hui/hysteria2/auth
|
||||
HY2XS_HYSTERIA_TRAFFIC_STATS_HOST=127.0.0.1
|
||||
HY2XS_HYSTERIA_TRAFFIC_STATS_PORT=36712
|
||||
HY2XS_HYSTERIA_TRAFFIC_STATS_SECRET=
|
||||
HY2XS_HYSTERIA_OBFS_TYPE=salamander
|
||||
HY2XS_HYSTERIA_OBFS_PASSWORD=
|
||||
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_HYSTERIA_VERSION=latest
|
||||
HY2XS_INSTALL_DIR=/opt/hy2xs-admin
|
||||
HY2XS_DATA_DIR=/var/lib/hy2xs-admin
|
||||
HY2XS_LOG_DIR=/var/log/hy2xs
|
||||
@@ -1,47 +0,0 @@
|
||||
# HY2XS post-install reference file.
|
||||
# Файл создаётся оркестратором после первичной установки и не является runtime-конфигом.
|
||||
|
||||
DEPLOY_TARGET_OS=debian12
|
||||
DEPLOY_TIMESTAMP=2026-04-13T10:00:00Z
|
||||
PACKAGE_NAME=hy2xs-install-package
|
||||
PACKAGE_BUILD_ID=build-20260413-001
|
||||
PACKAGE_VERSION=0.1.0
|
||||
|
||||
ORCH_SOURCE_STACK=bun-typescript
|
||||
ORCH_BUILD_MODE=bun-compile
|
||||
ORCH_BUILD_ID=build-20260413-001
|
||||
ORCH_ENTRYPOINT=/usr/local/lib/hy2xs/hy2xs-orchestrator
|
||||
|
||||
DEPLOY_DOMAIN=example.com
|
||||
PUBLIC_HOST=example.com
|
||||
PUBLIC_PORT=443
|
||||
SSH_PORT=22
|
||||
HY2XS_FIREWALL_ENABLED=true
|
||||
HY2XS_FIREWALL_STAGED_APPLY=true
|
||||
|
||||
HY2_SOURCE=official-upstream
|
||||
HY2_VERSION=v2.8.1
|
||||
HY2_TLS_MODE=acme
|
||||
HY2_ACME_EMAIL=admin@example.com
|
||||
HY2_TLS_CERT_PATH=
|
||||
HY2_TLS_KEY_PATH=
|
||||
HY2_LISTEN_HOST=0.0.0.0
|
||||
HY2_PORT=443
|
||||
HY2_AUTH_MODE=http
|
||||
HY2_AUTH_URL=http://127.0.0.1:8081/hui/hysteria2/auth
|
||||
HY2_TRAFFIC_STATS_LISTEN=127.0.0.1:25413
|
||||
HY2_OBFS_TYPE=salamander
|
||||
HY2_OBFS_PASSWORD=CHANGE_ME
|
||||
HY2_BANDWIDTH_UP_Mbps=50
|
||||
HY2_BANDWIDTH_DOWN_Mbps=50
|
||||
HY2_IGNORE_CLIENT_BANDWIDTH=false
|
||||
HY2_CONFIG_PATH=/etc/hysteria/config.yaml
|
||||
|
||||
HUI_ENABLED=true
|
||||
HUI_FORK_REF=packaged
|
||||
HUI_BUILD_ID=build-20260413-001
|
||||
HUI_BIND_HOST=127.0.0.1
|
||||
HUI_PORT=8081
|
||||
HUI_INSTALL_DIR=/opt/hy2xs-admin
|
||||
HUI_DATA_DIR=/var/lib/hy2xs-admin
|
||||
HUI_LOG_DIR=/var/log/hy2xs-admin
|
||||
+7
-1
@@ -3,6 +3,8 @@ set -eu
|
||||
|
||||
PACKAGE_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
ORCHESTRATOR="$PACKAGE_DIR/orchestrator/hy2xs-orchestrator"
|
||||
ORCHESTRATOR_INSTALL_PATH="/usr/local/lib/hy2xs/hy2xs-orchestrator"
|
||||
ORCHESTRATOR_SYMLINK="/usr/local/bin/hy2xs-orchestrator"
|
||||
|
||||
log() {
|
||||
printf '[hy2xs-install] %s\n' "$*"
|
||||
@@ -21,6 +23,10 @@ if [ ! -x "$ORCHESTRATOR" ]; then
|
||||
fail "Missing executable orchestrator artifact: $ORCHESTRATOR"
|
||||
fi
|
||||
|
||||
install -d -m 0755 /usr/local/lib/hy2xs
|
||||
install -m 0755 "$ORCHESTRATOR" "$ORCHESTRATOR_INSTALL_PATH"
|
||||
ln -sf "$ORCHESTRATOR_INSTALL_PATH" "$ORCHESTRATOR_SYMLINK"
|
||||
|
||||
log "package directory: $PACKAGE_DIR"
|
||||
log "starting install-only orchestrator"
|
||||
exec "$ORCHESTRATOR" install --package-dir "$PACKAGE_DIR" "$@"
|
||||
exec "$ORCHESTRATOR_INSTALL_PATH" install --package-dir "$PACKAGE_DIR" "$@"
|
||||
|
||||
@@ -5,6 +5,7 @@ table ip hy2xs {
|
||||
iif lo accept
|
||||
ct state established,related accept
|
||||
tcp dport {{SSH_PORT}} accept
|
||||
{{ACME_RULE}}
|
||||
udp dport {{HYSTERIA_PORT}} accept
|
||||
tcp dport {{UI_PORT}} ip saddr {{UI_BIND_HOST}} accept
|
||||
icmp type echo-request accept
|
||||
|
||||
Reference in New Issue
Block a user