fix(runtime): закрыть fix2 P0 runtime pass без legacy

This commit is contained in:
2026-04-28 03:28:19 +05:00
parent e2901db6cd
commit 96d9bbcece
31 changed files with 264 additions and 631 deletions
+9 -6
View File
@@ -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))
}
-5
View File
@@ -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
View File
@@ -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: &quota,
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
}
-6
View File
@@ -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)
-8
View File
@@ -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'
-5
View File
@@ -1,5 +0,0 @@
## Production environment
VITE_APP_TITLE = 'h-ui'
VITE_APP_PORT = 80
VITE_APP_BASE_API = '/hui'
+1 -3
View File
@@ -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"
+14 -151
View File
@@ -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
-2
View File
@@ -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.",
-1
View File
@@ -152,7 +152,6 @@ export default {
config: {
enable: "Включить/отключить",
remark: "Комментарий",
portHopping: "Port hopping: отдельные порты, диапазоны или их комбинации",
clashExtension: "Расширение подписки Clash",
listen: "Адрес и порт прослушивания. Для IPv4 используйте 0.0.0.0:443.",
tlsType: "Тип TLS",
-1
View File
@@ -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>
+1 -4
View File
@@ -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 */
-1
View File
@@ -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"
)
+27 -12
View File
@@ -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"
)
+3
View File
@@ -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
}
-234
View File
@@ -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
}