From 9a2c1c6b43471316f5cdd15b995f1e47b2f07d52 Mon Sep 17 00:00:00 2001 From: Ilya Kryuchkov <42733472+kr-ilya@users.noreply.github.com> Date: Sat, 3 Jan 2026 05:05:10 +0300 Subject: [PATCH 1/3] Fix: panel redirecting to old port after restart (#3594) * Fix panel redirect logic * Fix panel redirect logic * remove duplicate code * Cr fixes --- web/html/settings.html | 76 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/web/html/settings.html b/web/html/settings.html index e664e6ec..21294da7 100644 --- a/web/html/settings.html +++ b/web/html/settings.html @@ -120,6 +120,10 @@ oldAllSetting: new AllSetting(), allSetting: new AllSetting(), saveBtnDisable: true, + entryHost: null, + entryPort: null, + entryProtocol: null, + entryIsIP: false, user: {}, lang: LanguageManager.getLanguage(), inboundOptions: [], @@ -233,6 +237,31 @@ loading(spinning = true) { this.loadingStates.spinning = spinning; }, + _isIp(h) { + if (typeof h !== "string") return false; + + // IPv4: four dot-separated octets 0-255 + const v4 = h.split("."); + if ( + v4.length === 4 && + v4.every(p => /^\d{1,3}$/.test(p) && Number(p) <= 255) + ) return true; + + // IPv6: hex groups, optional single :: compression + if (!h.includes(":") || h.includes(":::")) return false; + const parts = h.split("::"); + if (parts.length > 2) return false; + + const splitGroups = s => (s ? s.split(":").filter(Boolean) : []); + const head = splitGroups(parts[0]); + const tail = splitGroups(parts[1]); + const validGroup = seg => /^[0-9a-fA-F]{1,4}$/.test(seg); + + if (![...head, ...tail].every(validGroup)) return false; + const groups = head.length + tail.length; + + return parts.length === 2 ? groups < 8 : groups === 8; + }, async getAllSetting() { const msg = await HttpUtil.post("/panel/setting/all"); @@ -307,16 +336,41 @@ this.loading(true); const msg = await HttpUtil.post("/panel/setting/restartPanel"); this.loading(false); - if (msg.success) { - this.loading(true); - await PromiseUtil.sleep(5000); - var { webCertFile, webKeyFile, webDomain: host, webPort: port, webBasePath: base } = this.allSetting; - if (host == this.oldAllSetting.webDomain) host = null; - if (port == this.oldAllSetting.webPort) port = null; - const isTLS = webCertFile !== "" || webKeyFile !== ""; - const url = URLBuilder.buildURL({ host, port, isTLS, base, path: "panel/settings" }); - window.location.replace(url); + if (!msg.success) return; + + this.loading(true); + await PromiseUtil.sleep(5000); + + const { webDomain, webPort, webBasePath, webCertFile, webKeyFile } = this.allSetting; + const newProtocol = (webCertFile || webKeyFile) ? "https:" : "http:"; + + let base = webBasePath ? webBasePath.replace(/^\//, "") : ""; + if (base && !base.endsWith("/")) base += "/"; + + if (!this.entryIsIP) { + const url = new URL(window.location.href); + url.pathname = `/${base}panel/settings`; + url.protocol = newProtocol; + window.location.replace(url.toString()); + return; } + + let finalHost = this.entryHost; + let finalPort = this.entryPort || ""; + + if (webDomain && this._isIp(webDomain)) { + finalHost = webDomain; + } + + if (webPort && Number(webPort) !== Number(this.entryPort)) { + finalPort = String(webPort); + } + + const url = new URL(`${newProtocol}//${finalHost}`); + if (finalPort) url.port = finalPort; + url.pathname = `/${base}panel/settings`; + + window.location.replace(url.toString()); }, toggleTwoFactor(newValue) { if (newValue) { @@ -568,6 +622,10 @@ } }, async mounted() { + this.entryHost = window.location.hostname; + this.entryPort = window.location.port; + this.entryProtocol = window.location.protocol; + this.entryIsIP = this._isIp(this.entryHost); await this.getAllSetting(); await this.loadInboundTags(); while (true) { From 1393f981bc313118b3ffde0b469cc07abdeb24c8 Mon Sep 17 00:00:00 2001 From: weekend sorrow Date: Sat, 3 Jan 2026 09:13:00 +0700 Subject: [PATCH 2/3] feat: Add etckeeper compatibility (#3602) --- install.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/install.sh b/install.sh index 480522ed..1009f047 100644 --- a/install.sh +++ b/install.sh @@ -643,6 +643,20 @@ install_x-ui() { chmod +x /usr/bin/x-ui mkdir -p /var/log/x-ui config_after_install + + # Etckeeper compatibility + if [ -d "/etc/.git" ]; then + if [ -f "/etc/.gitignore" ]; then + if ! grep -q "x-ui/x-ui.db" "/etc/.gitignore"; then + echo "" >> "/etc/.gitignore" + echo "x-ui/x-ui.db" >> "/etc/.gitignore" + echo -e "${green}Added x-ui.db to /etc/.gitignore for etckeeper${plain}" + fi + else + echo "x-ui/x-ui.db" > "/etc/.gitignore" + echo -e "${green}Created /etc/.gitignore and added x-ui.db for etckeeper${plain}" + fi + fi if [[ $release == "alpine" ]]; then wget --inet4-only -O /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc From 3287fa4d80672f9f5fcf90e6059a190939445297 Mon Sep 17 00:00:00 2001 From: Mikhail Grigorev Date: Sat, 3 Jan 2026 07:37:48 +0500 Subject: [PATCH 3/3] Added EnvironmentFile to systemd unit (#3606) * Added EnvironmentFile to systemd unit * Added support for older releases * Remove ARGS * Fixed copy unit * Fixed unit filename * Update update.sh --- .github/workflows/release.yml | 6 ++++-- install.sh | 13 ++++++++++++- update.sh | 19 +++++++++++++++++-- x-ui.service => x-ui.service.debian | 1 + x-ui.service.rhel | 16 ++++++++++++++++ 5 files changed, 50 insertions(+), 5 deletions(-) rename x-ui.service => x-ui.service.debian (88%) create mode 100644 x-ui.service.rhel diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c2a6989..563e1113 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,8 @@ on: - '**.go' - 'go.mod' - 'go.sum' - - 'x-ui.service' + - 'x-ui.service.debian' + - 'x-ui.service.rhel' jobs: build: @@ -78,7 +79,8 @@ jobs: mkdir x-ui cp xui-release x-ui/ - cp x-ui.service x-ui/ + cp x-ui.service.debian x-ui/ + cp x-ui.service.rhel x-ui/ cp x-ui.sh x-ui/ mv x-ui/xui-release x-ui/x-ui mkdir x-ui/bin diff --git a/install.sh b/install.sh index 1009f047..d3f15409 100644 --- a/install.sh +++ b/install.sh @@ -668,7 +668,18 @@ install_x-ui() { rc-update add x-ui rc-service x-ui start else - cp -f x-ui.service /etc/systemd/system/ + if [ -f "x-ui.service" ]; then + cp -f x-ui.service /etc/systemd/system/ + else + case "${release}" in + ubuntu | debian | armbian) + cp -f x-ui.service.debian /etc/systemd/system/x-ui.service + ;; + *) + cp -f x-ui.service.rhel /etc/systemd/system/x-ui.service + ;; + esac + fi systemctl daemon-reload systemctl enable x-ui systemctl start x-ui diff --git a/update.sh b/update.sh index 6da238b1..021167c1 100755 --- a/update.sh +++ b/update.sh @@ -615,6 +615,8 @@ update_x-ui() { echo -e "${green}Removing old x-ui version...${plain}" rm /usr/bin/x-ui -f >/dev/null 2>&1 rm /usr/local/x-ui/x-ui.service -f >/dev/null 2>&1 + rm /usr/local/x-ui/x-ui.service.debian -f >/dev/null 2>&1 + rm /usr/local/x-ui/x-ui.service.rhel -f >/dev/null 2>&1 rm /usr/local/x-ui/x-ui -f >/dev/null 2>&1 rm /usr/local/x-ui/x-ui.sh -f >/dev/null 2>&1 echo -e "${green}Removing old xray version...${plain}" @@ -677,8 +679,21 @@ update_x-ui() { rc-update add x-ui >/dev/null 2>&1 rc-service x-ui start >/dev/null 2>&1 else - echo -e "${green}Installing systemd unit...${plain}" - cp -f x-ui.service /etc/systemd/system/ >/dev/null 2>&1 + if [ -f "x-ui.service" ]; then + echo -e "${green}Installing systemd unit...${plain}" + cp -f x-ui.service /etc/systemd/system/ >/dev/null 2>&1 + else + case "${release}" in + ubuntu | debian | armbian) + echo -e "${green}Installing debian-like systemd unit...${plain}" + cp -f x-ui.service.debian /etc/systemd/system/x-ui.service >/dev/null 2>&1 + ;; + *) + echo -e "${green}Installing rhel-like systemd unit...${plain}" + cp -f x-ui.service.rhel /etc/systemd/system/x-ui.service >/dev/null 2>&1 + ;; + esac + fi chown root:root /etc/systemd/system/x-ui.service >/dev/null 2>&1 systemctl daemon-reload >/dev/null 2>&1 systemctl enable x-ui >/dev/null 2>&1 diff --git a/x-ui.service b/x-ui.service.debian similarity index 88% rename from x-ui.service rename to x-ui.service.debian index e911ef56..037f88bb 100644 --- a/x-ui.service +++ b/x-ui.service.debian @@ -4,6 +4,7 @@ After=network.target Wants=network.target [Service] +EnvironmentFile=-/etc/default/x-ui Environment="XRAY_VMESS_AEAD_FORCED=false" Type=simple WorkingDirectory=/usr/local/x-ui/ diff --git a/x-ui.service.rhel b/x-ui.service.rhel new file mode 100644 index 00000000..30652d52 --- /dev/null +++ b/x-ui.service.rhel @@ -0,0 +1,16 @@ +[Unit] +Description=x-ui Service +After=network.target +Wants=network.target + +[Service] +EnvironmentFile=-/etc/sysconfig/x-ui +Environment="XRAY_VMESS_AEAD_FORCED=false" +Type=simple +WorkingDirectory=/usr/local/x-ui/ +ExecStart=/usr/local/x-ui/x-ui +Restart=on-failure +RestartSec=5s + +[Install] +WantedBy=multi-user.target