Compare commits

...

13 commits

Author SHA1 Message Date
Sanaei
d759adbeee
Merge branch 'main' into main 2026-01-03 04:41:13 +01:00
Igor Kamyshnikov
b747730211
vless: use Inbound Listen address in Subscription service (#3610)
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (push) Waiting to run
* vless: use Inbound Listen address in Subscription service

vless manual connection link and subscription produced connection link are aligned.
subscription service now returns an IP address configured on Inbound, instead of subscription service IP,
which is consistent when the address, returned by QR code for manual vless link distribution.
2026-01-03 04:39:30 +01:00
Sanaei
49e366014a
Merge branch 'main' into main 2026-01-03 03:58:12 +01:00
Nebulosa
692a73788a
Set variables for packaging purposes (#3600)
* Set Variables for settings
2026-01-03 03:57:19 +01:00
Mikhail Grigorev
3287fa4d80
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
2026-01-03 03:37:48 +01:00
lolka1333
6f5d9e3916
Merge branch 'main' into main 2026-01-03 03:17:46 +01:00
weekend sorrow
1393f981bc
feat: Add etckeeper compatibility (#3602) 2026-01-03 03:13:00 +01:00
Ilya Kryuchkov
9a2c1c6b43
Fix: panel redirecting to old port after restart (#3594)
* Fix panel redirect logic

* Fix panel redirect logic

* remove duplicate code

* Cr fixes
2026-01-03 03:05:10 +01:00
lolka1333
d32edc272e
Merge branch 'main' into main 2026-01-03 02:22:08 +01:00
Vlad Yaroslavlev
278aa1c85c
Fix telegram bot issue (#3608)
Some checks are pending
Release 3X-UI / build (386) (push) Waiting to run
Release 3X-UI / build (amd64) (push) Waiting to run
Release 3X-UI / build (arm64) (push) Waiting to run
Release 3X-UI / build (armv5) (push) Waiting to run
Release 3X-UI / build (armv6) (push) Waiting to run
Release 3X-UI / build (armv7) (push) Waiting to run
Release 3X-UI / build (s390x) (push) Waiting to run
Release 3X-UI / Build for Windows (push) Waiting to run
* fix: improve Telegram bot handling for concurrent starts and graceful shutdown

- Added logic to stop any existing long-polling loop when Start is called again.
- Introduced a mutex to manage access to shared state variables, ensuring thread safety.
- Updated the OnReceive method to prevent multiple concurrent executions.
- Enhanced Stop method to ensure proper cleanup of resources and state management.

* fix: enhance Telegram bot's long-polling management

- Improved handling of concurrent starts by stopping existing long-polling loops.
- Implemented mutex for thread-safe access to shared state variables.
- Updated OnReceive method to prevent multiple executions.
- Enhanced Stop method for better resource cleanup and state management.

* .
2026-01-02 16:13:32 +01:00
Anton Petrov
8fe297ef9d
Fix QR codes colors inversion (#3607) 2026-01-02 16:12:30 +01:00
Zhenyu Qi
c881d1015a
fix: handle GitHub API error responses in GetXrayVersions (#3609)
GitHub API returns JSON object instead of array when encountering errors
(e.g., rate limit exceeded). This causes JSON unmarshal error:
'cannot unmarshal object into Go value of type []service.Release'

Add HTTP status code check to handle error responses gracefully and
return user-friendly error messages instead of JSON parsing errors.

Fixes issue where getXrayVersion fails with unmarshal error when
GitHub API rate limit is exceeded.
2026-01-02 16:12:13 +01:00
Nebulosa
c061337ce7
Set log folder variable to /var/log/3x-ui (#3599)
* Set log folder variable to /var/log/3x-ui

* Set log folder as x-ui and create the log folder

* Create the log folder in install and update scripts
2026-01-02 16:11:32 +01:00
12 changed files with 318 additions and 142 deletions

View file

@ -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

View file

@ -109,7 +109,7 @@ func GetLogFolder() string {
if runtime.GOOS == "windows" {
return filepath.Join(".", "log")
}
return "/var/log"
return "/var/log/x-ui"
}
func copyFile(src, dst string) error {

View file

@ -8,6 +8,9 @@ plain='\033[0m'
cur_dir=$(pwd)
xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
xui_service="${XUI_SERVICE:=/etc/systemd/system}"
# check root
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
@ -158,7 +161,7 @@ setup_ssl_certificate() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
return 0
else
@ -215,15 +218,15 @@ EOF
fi
chmod 755 ${certDir}/* 2>/dev/null
/usr/local/x-ui/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
echo -e "${yellow}Self-signed certificate configured. Browsers will show a warning.${plain}"
return 0
}
# Comprehensive manual SSL certificate issuance via acme.sh
ssl_cert_issue() {
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
@ -366,7 +369,7 @@ ssl_cert_issue() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
echo -e "${green}Certificate paths set for the panel${plain}"
echo -e "${green}Certificate File: $webCertFile${plain}"
echo -e "${green}Private Key File: $webKeyFile${plain}"
@ -451,11 +454,11 @@ prompt_and_setup_ssl() {
}
config_after_install() {
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_hasDefaultCredential=$(${xui_folder}/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
# Properly detect empty cert by checking if cert: line exists and has content after it
local existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local URL_lists=(
"https://api4.ipify.org"
"https://ipv4.icanhazip.com"
@ -487,7 +490,7 @@ config_after_install() {
echo -e "${yellow}Generated random port: ${config_port}${plain}"
fi
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
echo ""
echo -e "${green}═══════════════════════════════════════════${plain}"
@ -515,7 +518,7 @@ config_after_install() {
else
local config_webBasePath=$(gen_random_string 18)
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
# If the panel is already installed but no certificate is configured, prompt for SSL now
@ -539,7 +542,7 @@ config_after_install() {
local config_password=$(gen_random_string 10)
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}"
echo -e "Generated new random login credentials:"
echo -e "###############################################"
echo -e "${green}Username: ${config_username}${plain}"
@ -551,7 +554,7 @@ config_after_install() {
# Existing install: if no cert configured, prompt user to set domain or self-signed
# Properly detect empty cert by checking if cert: line exists and has content after it
existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ -z "$existing_cert" ]]; then
echo ""
echo -e "${green}═══════════════════════════════════════════${plain}"
@ -566,11 +569,11 @@ config_after_install() {
fi
fi
/usr/local/x-ui/x-ui migrate
${xui_folder}/x-ui migrate
}
install_x-ui() {
cd /usr/local/
cd ${xui_folder%/x-ui}/
# Download resources
if [ $# == 0 ]; then
@ -584,7 +587,7 @@ install_x-ui() {
fi
fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
wget --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
wget --inet4-only -N -O ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1
@ -601,7 +604,7 @@ install_x-ui() {
url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui $1"
wget --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
wget --inet4-only -N -O ${xui_folder}-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1
@ -614,13 +617,13 @@ install_x-ui() {
fi
# Stop x-ui service and remove old resources
if [[ -e /usr/local/x-ui/ ]]; then
if [[ -e ${xui_folder}/ ]]; then
if [[ $release == "alpine" ]]; then
rc-service x-ui stop
else
systemctl stop x-ui
fi
rm /usr/local/x-ui/ -rf
rm ${xui_folder}/ -rf
fi
# Extract resources and set permissions
@ -641,7 +644,22 @@ install_x-ui() {
# Update x-ui cli and se set permission
mv -f /usr/bin/x-ui-temp /usr/bin/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
@ -653,7 +671,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 ${xui_service}/
else
case "${release}" in
ubuntu | debian | armbian)
cp -f x-ui.service.debian ${xui_service}/x-ui.service
;;
*)
cp -f x-ui.service.rhel ${xui_service}/x-ui.service
;;
esac
fi
systemctl daemon-reload
systemctl enable x-ui
systemctl start x-ui

View file

@ -179,9 +179,15 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
if inbound.Protocol != model.VMESS {
return ""
}
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
obj := map[string]any{
"v": "2",
"add": s.address,
"add": address,
"port": inbound.Port,
"type": "none",
}
@ -317,7 +323,13 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
}
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
address := s.address
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.VLESS {
return ""
}
@ -523,7 +535,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
}
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
address := s.address
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.Trojan {
return ""
}
@ -719,7 +736,12 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
}
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
address := s.address
var address string
if inbound.Listen == "" || inbound.Listen == "0.0.0.0" || inbound.Listen == "::" || inbound.Listen == "::0" {
address = s.address
} else {
address = inbound.Listen
}
if inbound.Protocol != model.Shadowsocks {
return ""
}

View file

@ -6,6 +6,9 @@ blue='\033[0;34m'
yellow='\033[0;33m'
plain='\033[0m'
xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
xui_service="${XUI_SERVICE:=/etc/systemd/system}"
# Don't edit this config
b_source="${BASH_SOURCE[0]}"
while [ -h "$b_source" ]; do
@ -190,7 +193,7 @@ setup_ssl_certificate() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" >/dev/null 2>&1
echo -e "${green}SSL certificate installed and configured successfully!${plain}"
return 0
else
@ -246,14 +249,14 @@ EOF
fi
chmod 755 ${certDir}/* 2>/dev/null
/usr/local/x-ui/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
echo -e "${yellow}Self-signed certificate configured. Browsers will show a warning.${plain}"
return 0
}
# Comprehensive manual SSL certificate issuance via acme.sh
ssl_cert_issue() {
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
@ -396,7 +399,7 @@ ssl_cert_issue() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
echo -e "${green}Certificate paths set for the panel${plain}"
echo -e "${green}Certificate File: $webCertFile${plain}"
echo -e "${green}Private Key File: $webKeyFile${plain}"
@ -485,13 +488,13 @@ prompt_and_setup_ssl() {
config_after_update() {
echo -e "${yellow}x-ui settings:${plain}"
/usr/local/x-ui/x-ui setting -show true
/usr/local/x-ui/x-ui migrate
${xui_folder}/x-ui setting -show true
${xui_folder}/x-ui migrate
# Properly detect empty cert by checking if cert: line exists and has content after it
local existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true 2>/dev/null | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
local existing_cert=$(${xui_folder}/x-ui setting -getCert true 2>/dev/null | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
# Get server IP
local URL_lists=(
@ -514,7 +517,7 @@ config_after_update() {
if [[ ${#existing_webBasePath} -lt 4 ]]; then
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
local config_webBasePath=$(gen_random_string 18)
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}"
existing_webBasePath="${config_webBasePath}"
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
fi
@ -559,10 +562,10 @@ config_after_update() {
}
update_x-ui() {
cd /usr/local/
cd ${xui_folder%/x-ui}/
if [ -f "/usr/local/x-ui/x-ui" ]; then
current_xui_version=$(/usr/local/x-ui/x-ui -v)
if [ -f "${xui_folder}/x-ui" ]; then
current_xui_version=$(${xui_folder}/x-ui -v)
echo -e "${green}Current x-ui version: ${current_xui_version}${plain}"
else
_fail "ERROR: Current x-ui version: unknown"
@ -579,16 +582,16 @@ update_x-ui() {
fi
fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
${wget_bin} -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
${wget_bin} -N -O ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
if [[ $? -ne 0 ]]; then
echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
${wget_bin} --inet4-only -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
${wget_bin} --inet4-only -N -O ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz 2>/dev/null
if [[ $? -ne 0 ]]; then
_fail "ERROR: Failed to download x-ui, please be sure that your server can access GitHub"
fi
fi
if [[ -e /usr/local/x-ui/ ]]; then
if [[ -e ${xui_folder}/ ]]; then
echo -e "${green}Stopping x-ui...${plain}"
if [[ $release == "alpine" ]]; then
if [ -f "/etc/init.d/x-ui" ]; then
@ -601,11 +604,11 @@ update_x-ui() {
_fail "ERROR: x-ui service unit not installed."
fi
else
if [ -f "/etc/systemd/system/x-ui.service" ]; then
if [ -f "${xui_service}/x-ui.service" ]; then
systemctl stop x-ui >/dev/null 2>&1
systemctl disable x-ui >/dev/null 2>&1
echo -e "${green}Removing old systemd unit version...${plain}"
rm /etc/systemd/system/x-ui.service -f >/dev/null 2>&1
rm ${xui_service}/x-ui.service -f >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
else
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
@ -613,15 +616,17 @@ update_x-ui() {
fi
fi
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 -f >/dev/null 2>&1
rm /usr/local/x-ui/x-ui.sh -f >/dev/null 2>&1
rm ${xui_folder} -f >/dev/null 2>&1
rm ${xui_folder}/x-ui.service -f >/dev/null 2>&1
rm ${xui_folder}/x-ui.service.debian -f >/dev/null 2>&1
rm ${xui_folder}/x-ui.service.rhel -f >/dev/null 2>&1
rm ${xui_folder}/x-ui -f >/dev/null 2>&1
rm ${xui_folder}/x-ui.sh -f >/dev/null 2>&1
echo -e "${green}Removing old xray version...${plain}"
rm /usr/local/x-ui/bin/xray-linux-amd64 -f >/dev/null 2>&1
rm ${xui_folder}/bin/xray-linux-amd64 -f >/dev/null 2>&1
echo -e "${green}Removing old README and LICENSE file...${plain}"
rm /usr/local/x-ui/bin/README.md -f >/dev/null 2>&1
rm /usr/local/x-ui/bin/LICENSE -f >/dev/null 2>&1
rm ${xui_folder}/bin/README.md -f >/dev/null 2>&1
rm ${xui_folder}/bin/LICENSE -f >/dev/null 2>&1
else
rm x-ui-linux-$(arch).tar.gz -f >/dev/null 2>&1
_fail "ERROR: x-ui not installed."
@ -651,15 +656,16 @@ update_x-ui() {
fi
fi
chmod +x /usr/local/x-ui/x-ui.sh >/dev/null 2>&1
chmod +x ${xui_folder}/x-ui.sh >/dev/null 2>&1
chmod +x /usr/bin/x-ui >/dev/null 2>&1
mkdir -p /var/log/x-ui >/dev/null 2>&1
echo -e "${green}Changing owner...${plain}"
chown -R root:root /usr/local/x-ui >/dev/null 2>&1
chown -R root:root ${xui_folder} >/dev/null 2>&1
if [ -f "/usr/local/x-ui/bin/config.json" ]; then
if [ -f "${xui_folder}/bin/config.json" ]; then
echo -e "${green}Changing on config file permissions...${plain}"
chmod 640 /usr/local/x-ui/bin/config.json >/dev/null 2>&1
chmod 640 ${xui_folder}/bin/config.json >/dev/null 2>&1
fi
if [[ $release == "alpine" ]]; then
@ -676,9 +682,22 @@ 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
chown root:root /etc/systemd/system/x-ui.service >/dev/null 2>&1
if [ -f "x-ui.service" ]; then
echo -e "${green}Installing systemd unit...${plain}"
cp -f x-ui.service ${xui_service}/ >/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 ${xui_service}/x-ui.service >/dev/null 2>&1
;;
*)
echo -e "${green}Installing rhel-like systemd unit...${plain}"
cp -f x-ui.service.rhel ${xui_service}/x-ui.service >/dev/null 2>&1
;;
esac
fi
chown root:root ${xui_service}/x-ui.service >/dev/null 2>&1
systemctl daemon-reload >/dev/null 2>&1
systemctl enable x-ui >/dev/null 2>&1
systemctl start x-ui >/dev/null 2>&1

File diff suppressed because one or more lines are too long

View file

@ -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) {

View file

@ -529,6 +529,18 @@ func (s *ServerService) GetXrayVersions() ([]string, error) {
}
defer resp.Body.Close()
// Check HTTP status code - GitHub API returns object instead of array on error
if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
var errorResponse struct {
Message string `json:"message"`
}
if json.Unmarshal(bodyBytes, &errorResponse) == nil && errorResponse.Message != "" {
return nil, fmt.Errorf("GitHub API error: %s", errorResponse.Message)
}
return nil, fmt.Errorf("GitHub API returned status %d: %s", resp.StatusCode, resp.Status)
}
buffer := bytes.NewBuffer(make([]byte, bufferSize))
buffer.Reset()
if _, err := buffer.ReadFrom(resp.Body); err != nil {

View file

@ -174,6 +174,10 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return err
}
// If Start is called again (e.g. during reload), ensure any previous long-polling
// loop is stopped before creating a new bot / receiver.
StopBot()
// Initialize hash storage to store callback queries
hashStorage = global.NewHashStorage(20 * time.Minute)
@ -207,6 +211,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
return err
}
parsedAdminIds := make([]int64, 0)
// Parse admin IDs from comma-separated string
if tgBotID != "" {
for _, adminID := range strings.Split(tgBotID, ",") {
@ -215,9 +220,12 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
return err
}
adminIds = append(adminIds, int64(id))
parsedAdminIds = append(parsedAdminIds, int64(id))
}
}
tgBotMutex.Lock()
adminIds = parsedAdminIds
tgBotMutex.Unlock()
// Get Telegram bot proxy URL
tgBotProxy, err := t.settingService.GetTgBotProxy()
@ -252,10 +260,12 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
}
// Start receiving Telegram bot messages
if !isRunning {
tgBotMutex.Lock()
alreadyRunning := isRunning || botCancel != nil
tgBotMutex.Unlock()
if !alreadyRunning {
logger.Info("Telegram bot receiver started")
go t.OnReceive()
isRunning = true
}
return nil
@ -300,6 +310,8 @@ func (t *Tgbot) NewBot(token string, proxyUrl string, apiServerUrl string) (*tel
// IsRunning checks if the Telegram bot is currently running.
func (t *Tgbot) IsRunning() bool {
tgBotMutex.Lock()
defer tgBotMutex.Unlock()
return isRunning
}
@ -317,34 +329,34 @@ func (t *Tgbot) SetHostname() {
// Stop safely stops the Telegram bot's Long Polling operation.
// This method now calls the global StopBot function and cleans up other resources.
func (t *Tgbot) Stop() {
// Call the global StopBot function to gracefully shut down Long Polling
StopBot()
// Stop the bot handler (in case the goroutine hasn't exited yet)
if botHandler != nil {
botHandler.Stop()
}
logger.Info("Stop Telegram receiver ...")
isRunning = false
tgBotMutex.Lock()
adminIds = nil
tgBotMutex.Unlock()
}
// StopBot safely stops the Telegram bot's Long Polling operation by cancelling its context.
// This is the global function called from main.go's signal handler and t.Stop().
func StopBot() {
// Don't hold the mutex while cancelling/waiting.
tgBotMutex.Lock()
defer tgBotMutex.Unlock()
cancel := botCancel
botCancel = nil
handler := botHandler
botHandler = nil
isRunning = false
tgBotMutex.Unlock()
if botCancel != nil {
if handler != nil {
handler.Stop()
}
if cancel != nil {
logger.Info("Sending cancellation signal to Telegram bot...")
// Calling botCancel() cancels the context passed to UpdatesViaLongPolling,
// which stops the Long Polling operation and closes the updates channel,
// allowing the th.Start() goroutine to exit cleanly.
botCancel()
botCancel = nil
// Giving the goroutine a small delay to exit cleanly.
// Cancels the context passed to UpdatesViaLongPolling; this closes updates channel
// and lets botHandler.Start() exit cleanly.
cancel()
botWG.Wait()
logger.Info("Telegram bot successfully stopped.")
}
@ -379,36 +391,38 @@ func (t *Tgbot) OnReceive() {
params := telego.GetUpdatesParams{
Timeout: 30, // Increased timeout to reduce API calls
}
// --- GRACEFUL SHUTDOWN FIX: Context creation ---
// Strict singleton: never start a second long-polling loop.
tgBotMutex.Lock()
// Create a context with cancellation and store the cancel function.
var ctx context.Context
// Check if botCancel is already set (to prevent race condition overwrite and goroutine leak)
if botCancel == nil {
ctx, botCancel = context.WithCancel(context.Background())
} else {
// If botCancel is already set, use a non-cancellable context for this redundant call.
// This prevents overwriting the active botCancel and causing a goroutine leak from the previous call.
logger.Warning("TgBot OnReceive called concurrently. Using background context for redundant call.")
ctx = context.Background() // <<< ИЗМЕНЕНИЕ
if botCancel != nil || isRunning {
tgBotMutex.Unlock()
logger.Warning("TgBot OnReceive called while already running; ignoring.")
return
}
ctx, cancel := context.WithCancel(context.Background())
botCancel = cancel
isRunning = true
// Add to WaitGroup before releasing the lock so StopBot() can't return
// before this receiver goroutine is accounted for.
botWG.Add(1)
tgBotMutex.Unlock()
// Get updates channel using the context.
updates, _ := bot.UpdatesViaLongPolling(ctx, &params)
botWG.Go(func() {
go func() {
defer botWG.Done()
h, _ := th.NewBotHandler(bot, updates)
tgBotMutex.Lock()
botHandler = h
tgBotMutex.Unlock()
botHandler, _ = th.NewBotHandler(bot, updates)
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
delete(userStates, message.Chat.ID)
t.SendMsgToTgbot(message.Chat.ID, t.I18nBot("tgbot.keyboardClosed"), tu.ReplyKeyboardRemove())
return nil
}, th.TextEqual(t.I18nBot("tgbot.buttons.closeKeyboard")))
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
// Use goroutine with worker pool for concurrent command processing
go func() {
messageWorkerPool <- struct{}{} // Acquire worker
@ -420,7 +434,7 @@ func (t *Tgbot) OnReceive() {
return nil
}, th.AnyCommand())
botHandler.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
h.HandleCallbackQuery(func(ctx *th.Context, query telego.CallbackQuery) error {
// Use goroutine with worker pool for concurrent callback processing
go func() {
messageWorkerPool <- struct{}{} // Acquire worker
@ -432,7 +446,7 @@ func (t *Tgbot) OnReceive() {
return nil
}, th.AnyCallbackQueryWithMessage())
botHandler.HandleMessage(func(ctx *th.Context, message telego.Message) error {
h.HandleMessage(func(ctx *th.Context, message telego.Message) error {
if userState, exists := userStates[message.Chat.ID]; exists {
switch userState {
case "awaiting_id":
@ -578,8 +592,8 @@ func (t *Tgbot) OnReceive() {
return nil
}, th.AnyMessage())
botHandler.Start()
})
h.Start()
}()
}
// answerCommand processes incoming command messages from Telegram users.

View file

@ -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/

16
x-ui.service.rhel Normal file
View file

@ -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

65
x-ui.sh
View file

@ -53,7 +53,10 @@ os_version=""
os_version=$(grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.')
# Declare Variables
log_folder="${XUI_LOG_FOLDER:=/var/log}"
xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
xui_service="${XUI_SERVICE:=/etc/systemd/system}"
log_folder="${XUI_LOG_FOLDER:=/var/log/x-ui}"
mkdir -p "${log_folder}"
iplimit_log_path="${log_folder}/3xipl.log"
iplimit_banned_log_path="${log_folder}/3xipl-banned.log"
@ -126,7 +129,7 @@ update_menu() {
fi
wget -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
chmod +x /usr/local/x-ui/x-ui.sh
chmod +x ${xui_folder}/x-ui.sh
chmod +x /usr/bin/x-ui
if [[ $? == 0 ]]; then
@ -175,13 +178,13 @@ uninstall() {
else
systemctl stop x-ui
systemctl disable x-ui
rm /etc/systemd/system/x-ui.service -f
rm ${xui_service}/x-ui.service -f
systemctl daemon-reload
systemctl reset-failed
fi
rm /etc/x-ui/ -rf
rm /usr/local/x-ui/ -rf
rm ${xui_folder}/ -rf
echo ""
echo -e "Uninstalled Successfully.\n"
@ -209,9 +212,9 @@ reset_user() {
read -rp "Do you want to disable currently configured two-factor authentication? (y/n): " twoFactorConfirm
if [[ $twoFactorConfirm != "y" && $twoFactorConfirm != "Y" ]]; then
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} -resetTwoFactor false >/dev/null 2>&1
${xui_folder}/x-ui setting -username ${config_account} -password ${config_password} -resetTwoFactor false >/dev/null 2>&1
else
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password} -resetTwoFactor true >/dev/null 2>&1
${xui_folder}/x-ui setting -username ${config_account} -password ${config_password} -resetTwoFactor true >/dev/null 2>&1
echo -e "Two factor authentication has been disabled."
fi
@ -273,7 +276,7 @@ EOF
fi
chmod 755 ${certDir}/* >/dev/null 2>&1
/usr/local/x-ui/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
LOGI "Self-signed certificate configured. Browsers will show a warning."
return 0
}
@ -290,7 +293,7 @@ reset_webbasepath() {
config_webBasePath=$(gen_random_string 18)
# Apply the new web base path setting
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}" >/dev/null 2>&1
echo -e "Web base path has been reset to: ${green}${config_webBasePath}${plain}"
echo -e "${green}Please use the new web base path to access the panel.${plain}"
@ -305,13 +308,13 @@ reset_config() {
fi
return 0
fi
/usr/local/x-ui/x-ui setting -reset
${xui_folder}/x-ui setting -reset
echo -e "All panel settings have been reset to default."
restart
}
check_config() {
local info=$(/usr/local/x-ui/x-ui setting -show true)
local info=$(${xui_folder}/x-ui setting -show true)
if [[ $? != 0 ]]; then
LOGE "get current settings error, please check logs"
show_menu
@ -321,7 +324,7 @@ check_config() {
local existing_webBasePath=$(echo "$info" | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(echo "$info" | grep -Eo 'port: .+' | awk '{print $2}')
local existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
if [ -z "$server_ip" ]; then
server_ip=$(curl -s --max-time 3 https://4.ident.me)
@ -362,7 +365,7 @@ set_port() {
LOGD "Cancelled"
before_show_menu
else
/usr/local/x-ui/x-ui setting -port ${port}
${xui_folder}/x-ui setting -port ${port}
echo -e "The port is set, Please restart the panel now, and use the new port ${green}${port}${plain} to access web panel"
confirm_restart
fi
@ -654,7 +657,7 @@ check_status() {
return 1
fi
else
if [[ ! -f /etc/systemd/system/x-ui.service ]]; then
if [[ ! -f ${xui_service}/x-ui.service ]]; then
return 2
fi
temp=$(systemctl status x-ui | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
@ -976,7 +979,7 @@ update_geo() {
echo -e "${green}\t0.${plain} Back to Main Menu"
read -rp "Choose an option: " choice
cd /usr/local/x-ui/bin
cd ${xui_folder}/bin
case "$choice" in
0)
@ -1118,7 +1121,7 @@ ssl_cert_issue_main() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "${webCertFile}" && -f "${webKeyFile}" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
echo "Panel paths set for domain: $domain"
echo " - Certificate File: $webCertFile"
echo " - Private Key File: $webKeyFile"
@ -1153,8 +1156,8 @@ ssl_cert_issue_main() {
ssl_cert_issue_for_ip() {
LOGI "Starting automatic SSL certificate generation for server IP..."
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
# Get server IP
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
@ -1264,7 +1267,7 @@ ssl_cert_issue_for_ip() {
local webKeyFile="/root/cert/${server_ip}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
LOGI "Certificate configured for panel"
LOGI " - Certificate File: $webCertFile"
LOGI " - Private Key File: $webKeyFile"
@ -1279,8 +1282,8 @@ ssl_cert_issue_for_ip() {
}
ssl_cert_issue() {
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
# check for acme.sh first
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
echo "acme.sh could not be found. we will install it"
@ -1445,7 +1448,7 @@ ssl_cert_issue() {
local webKeyFile="/root/cert/${domain}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
LOGI "Panel paths set for domain: $domain"
LOGI " - Certificate File: $webCertFile"
LOGI " - Private Key File: $webKeyFile"
@ -1460,8 +1463,8 @@ ssl_cert_issue() {
}
ssl_cert_issue_CF() {
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
LOGI "****** Instructions for Use ******"
LOGI "Follow the steps below to complete the process:"
LOGI "1. Cloudflare Registered E-mail."
@ -1585,7 +1588,7 @@ ssl_cert_issue_CF() {
local webKeyFile="${certPath}/privkey.pem"
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
/usr/local/x-ui/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
LOGI "Panel paths set for domain: $CF_Domain"
LOGI " - Certificate File: $webCertFile"
LOGI " - Private Key File: $webKeyFile"
@ -2049,11 +2052,11 @@ SSH_port_forwarding() {
break
fi
done
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
local existing_cert=$(/usr/local/x-ui/x-ui setting -getCert true | grep -Eo 'cert: .+' | awk '{print $2}')
local existing_key=$(/usr/local/x-ui/x-ui setting -getCert true | grep -Eo 'key: .+' | awk '{print $2}')
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
local existing_listenIP=$(${xui_folder}/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep -Eo 'cert: .+' | awk '{print $2}')
local existing_key=$(${xui_folder}/x-ui setting -getCert true | grep -Eo 'key: .+' | awk '{print $2}')
local config_listenIP=""
local listen_choice=""
@ -2094,7 +2097,7 @@ SSH_port_forwarding() {
config_listenIP="127.0.0.1"
[[ "$listen_choice" == "2" ]] && read -rp "Enter custom IP to listen on: " config_listenIP
/usr/local/x-ui/x-ui setting -listenIP "${config_listenIP}" >/dev/null 2>&1
${xui_folder}/x-ui setting -listenIP "${config_listenIP}" >/dev/null 2>&1
echo -e "${green}listen IP has been set to ${config_listenIP}.${plain}"
echo -e "\n${green}SSH Port Forwarding Configuration:${plain}"
echo -e "Standard SSH command:"
@ -2110,7 +2113,7 @@ SSH_port_forwarding() {
fi
;;
2)
/usr/local/x-ui/x-ui setting -listenIP 0.0.0.0 >/dev/null 2>&1
${xui_folder}/x-ui setting -listenIP 0.0.0.0 >/dev/null 2>&1
echo -e "${green}Listen IP has been cleared.${plain}"
restart
;;