From 89b1137b0027433e9e64fde4e9d7f366af873243 Mon Sep 17 00:00:00 2001 From: Vladimir Avtsenov Date: Thu, 11 Jun 2026 13:51:54 +0300 Subject: [PATCH] feat(env): allow setting the initial URI path for the web panel (#5149) * feat(env): allow setting the initial URI path for the web panel * fix(setting): normalize and guard XUI_INIT_WEB_BASE_PATH default Address Copilot review on PR #5149: an env value that is empty, whitespace, or lacks slashes (e.g. `panel`) could produce an invalid webBasePath such as `/ /` and reach the frontend un-normalized. getEnv now trims whitespace and falls back when the value is empty; the env-derived default is passed through the existing normalizeBasePath helper (reused from node.go) so it always carries a leading and trailing slash. GetBasePath reuses the same helper instead of duplicating the slash logic. --------- Co-authored-by: Sanaei --- .env.example | 3 ++- CONTRIBUTING.md | 2 ++ README.ar_EG.md | 1 + README.es_ES.md | 1 + README.fa_IR.md | 1 + README.md | 1 + README.ru_RU.md | 1 + README.tr_TR.md | 1 + README.zh_CN.md | 1 + docker-compose.yml | 1 + internal/web/service/setting.go | 23 +++++++++++++++-------- 11 files changed, 27 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 62aa8c0bd..ee262468b 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ XUI_DEBUG=true XUI_DB_FOLDER=x-ui XUI_LOG_FOLDER=x-ui -XUI_BIN_FOLDER=x-ui \ No newline at end of file +XUI_BIN_FOLDER=x-ui +XUI_INIT_WEB_BASE_PATH=/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c528615c9..3b71d4264 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,7 @@ XUI_DEBUG=true XUI_DB_FOLDER=x-ui XUI_LOG_FOLDER=x-ui XUI_BIN_FOLDER=x-ui +XUI_INIT_WEB_BASE_PATH=/ ``` Drop the xray binary (`xray-windows-amd64.exe` on Windows, `xray-linux-amd64` on Linux, etc.) plus the matching `geoip.dat` and `geosite.dat` files into `x-ui/`. The easiest source is a [released Xray-core build](https://github.com/XTLS/Xray-core/releases). On Windows, `wintun.dll` is also required for testing TUN inbounds. @@ -254,6 +255,7 @@ For deeper notes on the frontend toolchain see [`frontend/README.md`](frontend/R | `XUI_DB_FOLDER` | platform default | Where `x-ui.db` lives | | `XUI_LOG_FOLDER` | platform default | Where `3xui.log` lives | | `XUI_BIN_FOLDER` | `bin` | Where the xray binary, geo files, and xray `config.json` live | +| `XUI_INIT_WEB_BASE_PATH` | `/` | The initial URI path for the web panel | | `XUI_DB_TYPE` | `sqlite` | Set to `postgres` to use PostgreSQL via `XUI_DB_DSN` | | `XUI_DB_DSN` | — | PostgreSQL DSN when `XUI_DB_TYPE=postgres` | diff --git a/README.ar_EG.md b/README.ar_EG.md index 1615ad1c6..ce5ccc767 100644 --- a/README.ar_EG.md +++ b/README.ar_EG.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | مجلد ملف قاعدة بيانات SQLite | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | الحد الأقصى للاتصالات المفتوحة (تجمّع PostgreSQL) | — | | `XUI_DB_MAX_IDLE_CONNS` | الحد الأقصى للاتصالات الخاملة (تجمّع PostgreSQL) | — | +| `XUI_INIT_WEB_BASE_PATH` | مسار URI الأولي للوحة الويب | `/` | | `XUI_ENABLE_FAIL2BAN` | تفعيل فرض حدود IP المعتمد على Fail2ban | `true` | | `XUI_LOG_LEVEL` | مستوى السجل (`debug`، `info`، `warning`، `error`) | `info` | | `XUI_DEBUG` | تفعيل وضع التصحيح | `false` | diff --git a/README.es_ES.md b/README.es_ES.md index 8e9465b3c..0708ea0b0 100644 --- a/README.es_ES.md +++ b/README.es_ES.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | Directorio del archivo de base de datos SQLite | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | Máximo de conexiones abiertas (pool de PostgreSQL) | — | | `XUI_DB_MAX_IDLE_CONNS` | Máximo de conexiones inactivas (pool de PostgreSQL) | — | +| `XUI_INIT_WEB_BASE_PATH` | La ruta URI inicial para el panel web | `/` | | `XUI_ENABLE_FAIL2BAN` | Habilitar la aplicación de límites de IP basada en Fail2ban | `true` | | `XUI_LOG_LEVEL` | Nivel de registro (`debug`, `info`, `warning`, `error`) | `info` | | `XUI_DEBUG` | Habilitar el modo de depuración | `false` | diff --git a/README.fa_IR.md b/README.fa_IR.md index debaf8242..4f1de440a 100644 --- a/README.fa_IR.md +++ b/README.fa_IR.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | پوشه‌ی فایل پایگاه‌داده‌ی SQLite | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | حداکثر اتصالات باز (استخر PostgreSQL) | — | | `XUI_DB_MAX_IDLE_CONNS` | حداکثر اتصالات بی‌کار (استخر PostgreSQL) | — | +| `XUI_INIT_WEB_BASE_PATH` | مسیر URI اولیه برای پنل وب | `/` | | `XUI_ENABLE_FAIL2BAN` | فعال‌سازی اعمال محدودیت IP مبتنی بر Fail2ban | `true` | | `XUI_LOG_LEVEL` | سطح گزارش‌گیری (`debug`، `info`، `warning`، `error`) | `info` | | `XUI_DEBUG` | فعال‌سازی حالت دیباگ | `false` | diff --git a/README.md b/README.md index 396b762ba..5cdf51aac 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | Directory for the SQLite database file | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | Maximum open connections (PostgreSQL pool) | — | | `XUI_DB_MAX_IDLE_CONNS` | Maximum idle connections (PostgreSQL pool) | — | +| `XUI_INIT_WEB_BASE_PATH` | The initial URI path for the web panel | `/` | | `XUI_ENABLE_FAIL2BAN` | Enable Fail2ban-based IP-limit enforcement | `true` | | `XUI_LOG_LEVEL` | Log verbosity (`debug`, `info`, `warning`, `error`) | `info` | | `XUI_DEBUG` | Enable debug mode | `false` | diff --git a/README.ru_RU.md b/README.ru_RU.md index 0c74b1bb6..4bcb3c793 100644 --- a/README.ru_RU.md +++ b/README.ru_RU.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | Каталог для файла базы данных SQLite | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | Максимум открытых соединений (пул PostgreSQL) | — | | `XUI_DB_MAX_IDLE_CONNS` | Максимум простаивающих соединений (пул PostgreSQL) | — | +| `XUI_INIT_WEB_BASE_PATH` | Начальный URI-путь для веб-панели | `/` | | `XUI_ENABLE_FAIL2BAN` | Включить применение лимитов IP на основе Fail2ban | `true` | | `XUI_LOG_LEVEL` | Уровень логирования (`debug`, `info`, `warning`, `error`) | `info` | | `XUI_DEBUG` | Включить режим отладки | `false` | diff --git a/README.tr_TR.md b/README.tr_TR.md index ae7e717a2..30f3dfaac 100644 --- a/README.tr_TR.md +++ b/README.tr_TR.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | SQLite veritabanı dizini | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | Maksimum açık bağlantı sayısı (PostgreSQL havuzu) | — | | `XUI_DB_MAX_IDLE_CONNS` | Maksimum boşta bekleme bağlantısı (PostgreSQL havuzu) | — | +| `XUI_INIT_WEB_BASE_PATH` | Web paneli için başlangıç URI yolu | `/` | | `XUI_ENABLE_FAIL2BAN` | Fail2ban tabanlı IP limit uygulamasını etkinleştir | `true` | | `XUI_LOG_LEVEL` | Günlük (Log) ayrıntı seviyesi (`debug`, `info`, `warning`, `error`) | `info` | | `XUI_DEBUG` | Hata ayıklama (debug) modunu etkinleştir | `false` | diff --git a/README.zh_CN.md b/README.zh_CN.md index 8fbbeff6b..12b3d6f09 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -130,6 +130,7 @@ docker run -d --cap-add=NET_ADMIN --cap-add=NET_RAW ... ghcr.io/mhsanaei/3x-ui | `XUI_DB_FOLDER` | SQLite 数据库文件所在目录 | `/etc/x-ui` | | `XUI_DB_MAX_OPEN_CONNS` | 最大打开连接数(PostgreSQL 连接池) | — | | `XUI_DB_MAX_IDLE_CONNS` | 最大空闲连接数(PostgreSQL 连接池) | — | +| `XUI_INIT_WEB_BASE_PATH` | Web 面板的初始 URI 路径 | `/` | | `XUI_ENABLE_FAIL2BAN` | 启用基于 Fail2ban 的 IP 限制 | `true` | | `XUI_LOG_LEVEL` | 日志级别(`debug`、`info`、`warning`、`error`) | `info` | | `XUI_DEBUG` | 启用调试模式 | `false` | diff --git a/docker-compose.yml b/docker-compose.yml index 5593dfec7..9d75ba1cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: environment: XRAY_VMESS_AEAD_FORCED: "false" XUI_ENABLE_FAIL2BAN: "true" + # XUI_INIT_WEB_BASE_PATH: "/" # To use PostgreSQL instead of the default SQLite, run: # docker compose --profile postgres up -d # and uncomment the two lines below. diff --git a/internal/web/service/setting.go b/internal/web/service/setting.go index a9f9adc84..11b9f81d9 100644 --- a/internal/web/service/setting.go +++ b/internal/web/service/setting.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "net/http" + "os" "reflect" "strconv" "strings" @@ -37,7 +38,7 @@ var defaultValueMap = map[string]string{ "secret": random.Seq(32), "panelGuid": uuid.NewString(), "apiToken": "", - "webBasePath": "/", + "webBasePath": normalizeBasePath(getEnv("XUI_INIT_WEB_BASE_PATH", "/")), "sessionMaxAge": "360", "trustedProxyCIDRs": "127.0.0.1/32,::1/128", "pageSize": "25", @@ -237,6 +238,18 @@ func mustString(value string, _ error) string { return value } +func getEnv(key, fallback string) string { + val, ok := os.LookupEnv(key) + if !ok { + return fallback + } + val = strings.TrimSpace(val) + if val == "" { + return fallback + } + return val +} + func (s *SettingService) ResetSettings() error { db := database.GetDB() err := db.Where("1 = 1").Delete(model.Setting{}).Error @@ -587,13 +600,7 @@ func (s *SettingService) GetBasePath() (string, error) { if err != nil { return "", err } - if !strings.HasPrefix(basePath, "/") { - basePath = "/" + basePath - } - if !strings.HasSuffix(basePath, "/") { - basePath += "/" - } - return basePath, nil + return normalizeBasePath(basePath), nil } func (s *SettingService) GetTimeLocation() (*time.Location, error) {