2023-02-09 19:18:06 +00:00
#!/bin/bash
red = '\033[0;31m'
green = '\033[0;32m'
2024-12-20 18:04:34 +00:00
blue = '\033[0;34m'
2023-02-09 19:18:06 +00:00
yellow = '\033[0;33m'
plain = '\033[0m'
2026-04-02 01:49:12 +00:00
#添加一些基础函数
2023-02-09 19:18:06 +00:00
function LOGD( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } [调试] $* ${ plain } "
2023-02-09 19:18:06 +00:00
}
function LOGE( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ red } [错误] $* ${ plain } "
2023-02-09 19:18:06 +00:00
}
function LOGI( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ green } [信息] $* ${ plain } "
2023-02-09 19:18:06 +00:00
}
2023-04-18 05:51:21 +00:00
2026-04-02 01:49:12 +00:00
# 端口辅助函数:检测端口监听及所属进程(尽力而为)
2026-01-11 14:28:43 +00:00
is_port_in_use( ) {
local port = " $1 "
if command -v ss >/dev/null 2>& 1; then
2026-04-15 08:58:49 +00:00
ss -ltn 2>/dev/null | awk -v p = " : ${ port } $" '$4 ~ p {found=1} END {exit(found ? 0 : 1)}'
2026-01-11 14:28:43 +00:00
return
fi
if command -v netstat >/dev/null 2>& 1; then
2026-04-15 08:58:49 +00:00
netstat -lnt 2>/dev/null | awk -v p = " : ${ port } " '$4 ~ p {found=1} END {exit(found ? 0 : 1)}'
2026-01-11 14:28:43 +00:00
return
fi
if command -v lsof >/dev/null 2>& 1; then
lsof -nP -iTCP:${ port } -sTCP:LISTEN >/dev/null 2>& 1 && return 0
fi
return 1
}
2026-04-02 01:49:12 +00:00
# 域名/IP 验证简单辅助函数
2025-12-28 15:38:26 +00:00
is_ipv4( ) {
[ [ " $1 " = ~ ^( [ 0-9] { 1,3} \. ) { 3} [ 0-9] { 1,3} $ ] ] && return 0 || return 1
}
is_ipv6( ) {
[ [ " $1 " = ~ : ] ] && return 0 || return 1
}
is_ip( ) {
is_ipv4 " $1 " || is_ipv6 " $1 "
}
is_domain( ) {
2026-01-12 01:53:43 +00:00
[ [ " $1 " = ~ ^( [ A-Za-z0-9] ( -*[ A-Za-z0-9] ) *\. ) +( xn--[ a-z0-9] { 2,} | [ A-Za-z] { 2,} ) $ ] ] && return 0 || return 1
2025-12-28 15:38:26 +00:00
}
2026-04-02 01:49:12 +00:00
# 检查 root 权限
[ [ $EUID -ne 0 ] ] && LOGE "错误:必须使用 root 权限运行此脚本!\n" && exit 1
2023-02-09 19:18:06 +00:00
2026-04-02 01:49:12 +00:00
# 检查操作系统并设置发行版变量
2023-03-11 15:05:35 +00:00
if [ [ -f /etc/os-release ] ] ; then
source /etc/os-release
release = $ID
elif [ [ -f /usr/lib/os-release ] ] ; then
source /usr/lib/os-release
release = $ID
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
echo "无法识别操作系统,请联系作者!" >& 2
2023-03-07 22:34:07 +00:00
exit 1
2023-02-09 19:18:06 +00:00
fi
2026-04-02 01:49:12 +00:00
echo " 操作系统版本: $release "
2023-03-07 22:34:07 +00:00
2023-02-09 19:18:06 +00:00
os_version = ""
2024-09-24 14:50:00 +00:00
os_version = $( grep "^VERSION_ID" /etc/os-release | cut -d '=' -f2 | tr -d '"' | tr -d '.' )
2023-02-09 19:18:06 +00:00
2026-04-02 01:49:12 +00:00
# 声明变量
2026-01-03 02:57:19 +00:00
xui_folder = " ${ XUI_MAIN_FOLDER : =/usr/local/x-ui } "
xui_service = " ${ XUI_SERVICE : =/etc/systemd/system } "
2026-01-02 15:11:32 +00:00
log_folder = " ${ XUI_LOG_FOLDER : =/var/log/x-ui } "
mkdir -p " ${ log_folder } "
2023-07-18 10:24:28 +00:00
iplimit_log_path = " ${ log_folder } /3xipl.log "
iplimit_banned_log_path = " ${ log_folder } /3xipl-banned.log "
2023-02-09 19:18:06 +00:00
confirm( ) {
if [ [ $# > 1 ] ] ; then
2026-04-02 01:49:12 +00:00
echo && read -rp " $1 [默认 $2 ]: " temp
2023-04-29 21:27:15 +00:00
if [ [ " ${ temp } " = = "" ] ] ; then
2023-02-09 19:18:06 +00:00
temp = $2
fi
else
2026-04-02 01:49:12 +00:00
read -rp " $1 [y/n]: " temp
2023-02-09 19:18:06 +00:00
fi
2023-04-29 21:27:15 +00:00
if [ [ " ${ temp } " = = "y" || " ${ temp } " = = "Y" ] ] ; then
2023-02-09 19:18:06 +00:00
return 0
else
return 1
fi
}
confirm_restart( ) {
2026-04-02 01:49:12 +00:00
confirm "重启面板,注意:重启面板也会重启 xray" "y"
2023-02-09 19:18:06 +00:00
if [ [ $? = = 0 ] ] ; then
restart
else
show_menu
fi
}
before_show_menu( ) {
2026-04-02 01:49:12 +00:00
echo && echo -n -e " ${ yellow } 按回车键返回主菜单: ${ plain } " && read -r temp
2023-02-09 19:18:06 +00:00
show_menu
}
install( ) {
2026-04-02 01:49:12 +00:00
bash <( curl -Ls https://raw.githubusercontent.com/Sora39831/3x-ui/main/install.sh)
2023-02-09 19:18:06 +00:00
if [ [ $? = = 0 ] ] ; then
if [ [ $# = = 0 ] ] ; then
start
else
start 0
fi
fi
}
update( ) {
2026-04-02 01:49:12 +00:00
confirm "此功能将更新所有 x-ui 组件到最新版本,数据不会丢失。是否继续?" "y"
2023-04-24 13:25:29 +00:00
if [ [ $? != 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE "已取消"
2023-04-24 13:25:29 +00:00
if [ [ $# = = 0 ] ] ; then
before_show_menu
2023-04-21 15:30:14 +00:00
fi
2023-04-24 13:25:29 +00:00
return 0
fi
2026-04-02 01:49:12 +00:00
bash <( curl -Ls https://raw.githubusercontent.com/Sora39831/3x-ui/main/update.sh)
2023-04-24 13:25:29 +00:00
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "更新完成,面板已自动重启"
2024-11-04 09:33:07 +00:00
before_show_menu
2023-02-09 19:18:06 +00:00
fi
}
2024-06-24 13:22:05 +00:00
update_menu( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 正在更新菜单 ${ plain } "
confirm "此功能将更新菜单到最新版本。" "y"
2024-06-24 13:22:05 +00:00
if [ [ $? != 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE "已取消"
2024-06-24 13:22:05 +00:00
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
return 0
fi
2024-10-07 15:21:32 +00:00
2026-04-02 01:49:12 +00:00
curl -fLRo /usr/bin/x-ui https://raw.githubusercontent.com/Sora39831/3x-ui/main/x-ui.sh
2026-01-03 02:57:19 +00:00
chmod +x ${ xui_folder } /x-ui.sh
2024-06-24 13:22:05 +00:00
chmod +x /usr/bin/x-ui
2024-10-07 15:21:32 +00:00
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 更新成功。面板已自动重启。 ${ plain } "
2025-04-09 05:40:38 +00:00
exit 0
2024-06-24 13:22:05 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 更新菜单失败。 ${ plain } "
2024-06-24 13:22:05 +00:00
return 1
fi
}
2024-10-31 00:18:37 +00:00
legacy_version( ) {
2026-04-02 01:49:12 +00:00
echo -n "输入面板版本(例如 2.4.0) : "
2025-05-16 18:23:57 +00:00
read -r tag_version
2023-12-23 14:28:11 +00:00
2024-10-04 12:31:15 +00:00
if [ -z " $tag_version " ] ; then
2026-04-02 01:49:12 +00:00
echo "面板版本不能为空,退出。"
2024-01-20 13:58:44 +00:00
exit 1
2023-12-23 14:28:11 +00:00
fi
2026-04-02 01:49:12 +00:00
# 使用输入的面板版本号下载
install_command = "bash <(curl -Ls " https://raw.githubusercontent.com/Sora39831/3x-ui/v$tag_version /install.sh" ) v $tag_version "
2023-12-23 14:28:11 +00:00
2026-04-02 01:49:12 +00:00
echo " 正在下载并安装面板版本 $tag_version ... "
2023-12-23 14:28:11 +00:00
eval $install_command
}
2026-04-02 01:49:12 +00:00
# 处理脚本文件删除的函数
2024-02-17 16:23:02 +00:00
delete_script( ) {
2026-04-02 01:49:12 +00:00
rm " $0 " # 删除脚本自身
2024-02-17 16:23:02 +00:00
exit 1
}
2023-02-09 19:18:06 +00:00
uninstall( ) {
2026-04-02 01:49:12 +00:00
confirm "确定要卸载面板吗? xray 也会被卸载!" "n"
2023-02-09 19:18:06 +00:00
if [ [ $? != 0 ] ] ; then
if [ [ $# = = 0 ] ] ; then
show_menu
fi
return 0
fi
2025-09-22 19:56:43 +00:00
2026-04-02 08:15:07 +00:00
# 询问是否吊销证书
local domains = $( find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename { } \; 2>/dev/null)
if [ [ -n " $domains " ] ] ; then
echo ""
echo "检测到以下证书:"
echo " $domains "
confirm "是否要吊销所有证书?" "n"
if [ [ $? = = 0 ] ] ; then
for domain in $domains ; do
~/.acme.sh/acme.sh --revoke -d " ${ domain } " 2>/dev/null
LOGI " 域名 $domain 的证书已吊销 "
done
rm -rf /root/cert/
fi
fi
2026-04-15 09:48:18 +00:00
local current_db_type = ""
local db_host = ""
local db_port = ""
local db_name = ""
local db_user = ""
local delete_db_confirmed = 1
current_db_type = $( read_json_dbtype)
if [ [ " $current_db_type " = = "mariadb" ] ] ; then
local json_path = "/etc/x-ui/x-ui.json"
if command -v jq >/dev/null 2>& 1; then
db_host = $( jq -r '.databaseConnection.dbHost // .other.dbHost // "127.0.0.1"' " $json_path " 2>/dev/null)
db_port = $( jq -r '.databaseConnection.dbPort // .other.dbPort // "3306"' " $json_path " 2>/dev/null)
db_name = $( jq -r '.databaseConnection.dbName // .other.dbName // "3xui"' " $json_path " 2>/dev/null)
db_user = $( jq -r '.databaseConnection.dbUser // .other.dbUser // ""' " $json_path " 2>/dev/null)
else
db_host = $( grep -o '"dbHost"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' )
db_port = $( grep -o '"dbPort"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' )
db_name = $( grep -o '"dbName"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' )
db_user = $( grep -o '"dbUser"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' )
fi
db_host = " ${ db_host :- 127 .0.0.1 } "
db_port = " ${ db_port :- 3306 } "
db_name = " ${ db_name :- 3xui } "
echo -e " ${ yellow } 检测到当前数据库类型为 MariaDB ( ${ db_host } : ${ db_port } / ${ db_name } ) ${ plain } "
confirm "是否删除数据库并卸载本机 MariaDB? " "n"
delete_db_confirmed = $?
if [ [ $delete_db_confirmed = = 0 ] ] ; then
if [ [ " $db_host " = = "127.0.0.1" || " $db_host " = = "localhost" || " $db_host " = = "::1" ] ] ; then
remove_local_mariadb_data " $db_port " " $db_name " " $db_user "
uninstall_local_mariadb_packages
else
echo -e " ${ yellow } 当前 MariaDB 为远程地址 ( ${ db_host } ),跳过数据库删除与本机 MariaDB 卸载 ${ plain } "
fi
fi
fi
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service x-ui stop
rc-update del x-ui
rm /etc/init.d/x-ui -f
else
systemctl stop x-ui
systemctl disable x-ui
2026-01-03 02:57:19 +00:00
rm ${ xui_service } /x-ui.service -f
2025-09-22 19:56:43 +00:00
systemctl daemon-reload
systemctl reset-failed
fi
2023-02-09 19:18:06 +00:00
rm /etc/x-ui/ -rf
2026-01-03 02:57:19 +00:00
rm ${ xui_folder } / -rf
2023-02-09 19:18:06 +00:00
echo ""
2026-04-02 01:49:12 +00:00
echo -e "卸载成功。\n"
echo "如需再次安装面板,可使用以下命令:"
echo -e " ${ green } bash <(curl -Ls https://raw.githubusercontent.com/Sora39831/3x-ui/master/install.sh) ${ plain } "
2023-02-09 19:18:06 +00:00
echo ""
2026-04-02 01:49:12 +00:00
# 捕获 SIGTERM 信号
2024-02-17 16:23:02 +00:00
trap delete_script SIGTERM
delete_script
2023-02-09 19:18:06 +00:00
}
2026-04-15 09:48:18 +00:00
remove_local_mariadb_data( ) {
local db_port = " $1 "
local db_name = " $2 "
local db_user = " $3 "
local sql = ""
local account_host = ""
if ! has_local_mariadb_service && ! has_mariadb_cli; then
echo -e " ${ yellow } 未检测到本机 MariaDB 服务或客户端,跳过数据库删除 ${ plain } "
return 0
fi
if ! ensure_mariadb_client_ready; then
echo -e " ${ yellow } MariaDB 客户端未就绪,跳过数据库删除 ${ plain } "
return 0
fi
if ! ensure_local_mariadb_admin_access " ${ db_port } " ; then
echo -e " ${ yellow } 无法获取本机 MariaDB 管理权限,跳过数据库删除 ${ plain } "
return 0
fi
if [ [ " $db_name " = ~ ^[ A-Za-z0-9_.-] +$ ] ] ; then
sql = " ${ sql } DROP DATABASE IF EXISTS \` ${ db_name } \`; "
else
echo -e " ${ yellow } 数据库名不符合安全规则,跳过删除业务库: ${ db_name } ${ plain } "
fi
if [ [ -n " $db_user " ] ] ; then
if [ [ " $db_user " = ~ ^[ A-Za-z0-9_.-] +$ ] ] ; then
for account_host in "localhost" "127.0.0.1" "::1" ; do
sql = " ${ sql } DROP USER IF EXISTS ' ${ db_user } '@' ${ account_host } '; "
done
else
echo -e " ${ yellow } 业务用户名不符合安全规则,跳过删除业务账号: ${ db_user } ${ plain } "
fi
fi
if [ [ -z " $sql " ] ] ; then
echo -e " ${ yellow } 无可执行的数据库删除语句,跳过数据库删除 ${ plain } "
return 0
fi
if run_local_mariadb_admin_sql " $sql " ; then
echo -e " ${ green } 本机 MariaDB 业务库/业务账号删除完成 ${ plain } "
else
echo -e " ${ yellow } 本机 MariaDB 业务库/业务账号删除失败,继续执行卸载流程 ${ plain } "
fi
}
uninstall_local_mariadb_packages( ) {
echo -e " ${ green } 正在卸载本机 MariaDB... ${ plain } "
if command -v systemctl >/dev/null 2>& 1; then
systemctl stop mariadb 2>/dev/null || true
systemctl disable mariadb 2>/dev/null || true
systemctl stop mysql 2>/dev/null || true
systemctl disable mysql 2>/dev/null || true
elif [ [ $release = = "alpine" ] ] ; then
rc-service mariadb stop 2>/dev/null || true
rc-update del mariadb 2>/dev/null || true
fi
case " ${ release } " in
ubuntu | debian | linuxmint | armbian)
apt-get remove -y mariadb-server mariadb-client mariadb-common >/dev/null 2>& 1 || true
apt-get autoremove -y >/dev/null 2>& 1 || true
; ;
centos | rhel | almalinux | rocky | ol | alinux | amzn | fedora)
if command -v dnf >/dev/null 2>& 1; then
dnf remove -y mariadb-server mariadb mariadb-client >/dev/null 2>& 1 || true
else
yum remove -y mariadb-server mariadb mariadb-client >/dev/null 2>& 1 || true
fi
; ;
arch | manjaro | parch)
pacman -Rns --noconfirm mariadb mariadb-clients >/dev/null 2>& 1 || pacman -Rns --noconfirm mariadb >/dev/null 2>& 1 || true
; ;
opensuse* | sles | opensuse-tumbleweed | opensuse-leap)
zypper rm -y mariadb mariadb-client mariadb-server >/dev/null 2>& 1 || true
; ;
alpine)
apk del mariadb mariadb-client >/dev/null 2>& 1 || true
; ;
*)
echo -e " ${ yellow } 当前发行版未内置 MariaDB 卸载命令,请手动检查数据库包 ${ plain } "
; ;
esac
}
2023-02-09 19:18:06 +00:00
reset_user( ) {
2026-04-02 01:49:12 +00:00
confirm "确定要重置面板的用户名和密码吗?" "n"
2023-02-09 19:18:06 +00:00
if [ [ $? != 0 ] ] ; then
if [ [ $# = = 0 ] ] ; then
show_menu
fi
return 0
fi
2026-04-02 01:49:12 +00:00
read -rp "请设置登录用户名 [默认随机生成]: " config_account
2025-10-01 16:37:31 +00:00
[ [ -z $config_account ] ] && config_account = $( gen_random_string 10)
2026-04-02 01:49:12 +00:00
read -rp "请设置登录密码 [默认随机生成]: " config_password
2025-10-01 16:37:31 +00:00
[ [ -z $config_password ] ] && config_password = $( gen_random_string 18)
2025-07-02 09:25:25 +00:00
2026-04-02 01:49:12 +00:00
read -rp "是否要禁用当前配置的双因素认证?(y/n): " twoFactorConfirm
2025-07-02 09:25:25 +00:00
if [ [ $twoFactorConfirm != "y" && $twoFactorConfirm != "Y" ] ] ; then
2026-01-18 14:44:49 +00:00
${ xui_folder } /x-ui setting -username " ${ config_account } " -password " ${ config_password } " -resetTwoFactor false >/dev/null 2>& 1
2025-07-02 09:25:25 +00:00
else
2026-01-18 14:44:49 +00:00
${ xui_folder } /x-ui setting -username " ${ config_account } " -password " ${ config_password } " -resetTwoFactor true >/dev/null 2>& 1
2026-04-02 01:49:12 +00:00
echo -e "双因素认证已禁用。"
2025-07-02 09:25:25 +00:00
fi
2026-04-02 01:49:12 +00:00
echo -e " 面板登录用户名已重置为: ${ green } ${ config_account } ${ plain } "
echo -e " 面板登录密码已重置为: ${ green } ${ config_password } ${ plain } "
echo -e " ${ green } 请使用新的登录用户名和密码访问 X-UI 面板。请牢记! ${ plain } "
2023-02-09 19:18:06 +00:00
confirm_restart
}
2024-06-24 12:43:39 +00:00
gen_random_string( ) {
local length = " $1 "
2026-04-01 11:59:48 +00:00
openssl rand -base64 $(( length * 2 )) \
| tr -dc 'a-zA-Z0-9' \
| head -c " $length "
2024-06-24 12:43:39 +00:00
}
reset_webbasepath( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 正在重置 Web 路径 ${ plain } "
2024-10-07 15:21:32 +00:00
2026-04-02 01:49:12 +00:00
read -rp "确定要重置 Web 路径吗?(y/n): " confirm
2024-10-04 09:17:59 +00:00
if [ [ $confirm != "y" && $confirm != "Y" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 操作已取消。 ${ plain } "
2024-10-04 09:17:59 +00:00
return
2024-06-24 12:43:39 +00:00
fi
2024-10-04 09:17:59 +00:00
2025-07-22 10:53:12 +00:00
config_webBasePath = $( gen_random_string 18)
2024-10-07 15:21:32 +00:00
2026-04-02 01:49:12 +00:00
# 应用新的 Web 路径设置
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui setting -webBasePath " ${ config_webBasePath } " >/dev/null 2>& 1
2025-03-04 08:50:24 +00:00
2026-04-02 01:49:12 +00:00
echo -e " Web 路径已重置为: ${ green } ${ config_webBasePath } ${ plain } "
echo -e " ${ green } 请使用新的 Web 路径访问面板。 ${ plain } "
2024-10-07 15:38:23 +00:00
restart
2024-06-24 12:43:39 +00:00
}
2023-02-09 19:18:06 +00:00
reset_config( ) {
2026-04-02 08:15:07 +00:00
confirm "确定要重置所有面板设置吗?这将清除面板的端口、路径、证书等配置,但不会删除账户数据和流量数据。" "n"
2023-02-09 19:18:06 +00:00
if [ [ $? != 0 ] ] ; then
if [ [ $# = = 0 ] ] ; then
show_menu
fi
return 0
fi
2026-04-02 08:15:07 +00:00
# 重置面板证书配置
${ xui_folder } /x-ui cert -reset 2>/dev/null
# 重置面板设置(端口、路径等)
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui setting -reset
2026-04-02 08:15:07 +00:00
2026-04-02 01:49:12 +00:00
echo -e "所有面板设置已重置为默认值。"
2026-04-02 08:15:07 +00:00
echo -e " ${ yellow } 面板将使用默认端口 2053 和随机用户名/密码重新启动。 ${ plain } "
2024-11-04 09:33:07 +00:00
restart
2023-02-09 19:18:06 +00:00
}
check_config( ) {
2026-01-03 02:57:19 +00:00
local info = $( ${ xui_folder } /x-ui setting -show true )
2023-02-09 19:18:06 +00:00
if [ [ $? != 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE "获取当前设置出错,请查看日志"
2023-02-09 19:18:06 +00:00
show_menu
2024-10-29 14:07:56 +00:00
return
2023-02-09 19:18:06 +00:00
fi
LOGI " ${ info } "
2024-10-29 14:07:56 +00:00
local existing_webBasePath = $( echo " $info " | grep -Eo 'webBasePath: .+' | awk '{print $2}' )
local existing_port = $( echo " $info " | grep -Eo 'port: .+' | awk '{print $2}' )
2026-01-03 02:57:19 +00:00
local existing_cert = $( ${ xui_folder } /x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]' )
2025-07-06 09:22:36 +00:00
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)
fi
2024-10-29 14:07:56 +00:00
2024-11-12 11:25:16 +00:00
if [ [ -n " $existing_cert " ] ] ; then
local domain = $( basename " $( dirname " $existing_cert " ) " )
if [ [ " $domain " = ~ ^[ a-zA-Z0-9.-] +\. [ a-zA-Z] { 2,} $ ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 访问地址: https:// ${ domain } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
2024-11-12 11:25:16 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 访问地址: https:// ${ server_ip } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
2024-11-12 11:25:16 +00:00
fi
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } ⚠ 警告:未配置 SSL 证书! ${ plain } "
echo -e " ${ yellow } 您可以为 IP 地址获取 Let's Encrypt 证书(有效期约 6 天,自动续期)。 ${ plain } "
read -rp "现在为 IP 生成 SSL 证书?[y/N]: " gen_ssl
2026-01-05 04:28:02 +00:00
if [ [ " $gen_ssl " = = "y" || " $gen_ssl " = = "Y" ] ] ; then
2025-12-27 23:03:33 +00:00
stop >/dev/null 2>& 1
2026-01-05 04:28:02 +00:00
ssl_cert_issue_for_ip
2025-12-27 23:03:33 +00:00
if [ [ $? -eq 0 ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 访问地址: https:// ${ server_ip } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
# ssl_cert_issue_for_ip 已经重启面板,但确保其正在运行
2026-01-05 04:28:02 +00:00
start >/dev/null 2>& 1
2025-12-27 23:03:33 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "IP 证书配置失败。"
echo -e " ${ yellow } 您可以通过选项 19( SSL 证书管理)重试。 ${ plain } "
2026-01-05 04:28:02 +00:00
start >/dev/null 2>& 1
2025-12-27 23:03:33 +00:00
fi
else
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 访问地址: http:// ${ server_ip } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
echo -e " ${ yellow } 出于安全考虑,请使用选项 19( SSL 证书管理)配置 SSL 证书 ${ plain } "
2025-12-27 23:03:33 +00:00
fi
2024-11-12 11:25:16 +00:00
fi
2023-02-09 19:18:06 +00:00
}
set_port( ) {
2026-04-02 01:49:12 +00:00
echo -n "输入端口号[1-65535]: "
2025-05-16 18:23:57 +00:00
read -r port
2023-02-09 19:18:06 +00:00
if [ [ -z " ${ port } " ] ] ; then
2026-04-02 01:49:12 +00:00
LOGD "已取消"
2023-02-09 19:18:06 +00:00
before_show_menu
else
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui setting -port ${ port }
2026-04-02 01:49:12 +00:00
echo -e " 端口已设置,请立即重启面板,并使用新端口 ${ green } ${ port } ${ plain } 访问 Web 面板 "
2023-02-09 19:18:06 +00:00
confirm_restart
fi
}
start( ) {
check_status
if [ [ $? = = 0 ] ] ; then
echo ""
2026-04-02 01:49:12 +00:00
LOGI "面板正在运行,无需重复启动,如需重启请选择重启"
2023-02-09 19:18:06 +00:00
else
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service x-ui start
else
systemctl start x-ui
fi
2023-02-09 19:18:06 +00:00
sleep 2
check_status
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "x-ui 启动成功"
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "面板启动失败,可能是因为启动时间超过两秒,请稍后查看日志信息"
2023-02-09 19:18:06 +00:00
fi
fi
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
stop( ) {
check_status
if [ [ $? = = 1 ] ] ; then
echo ""
2026-04-02 01:49:12 +00:00
LOGI "面板已停止,无需重复停止!"
2023-02-09 19:18:06 +00:00
else
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service x-ui stop
else
systemctl stop x-ui
fi
2023-02-09 19:18:06 +00:00
sleep 2
check_status
if [ [ $? = = 1 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "x-ui 和 xray 已停止"
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "面板停止失败,可能是因为停止时间超过两秒,请稍后查看日志信息"
2023-02-09 19:18:06 +00:00
fi
fi
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
restart( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service x-ui restart
else
systemctl restart x-ui
fi
2023-02-09 19:18:06 +00:00
sleep 2
check_status
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "x-ui 和 xray 重启成功"
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "面板重启失败,可能是因为启动时间超过两秒,请稍后查看日志信息"
2023-02-09 19:18:06 +00:00
fi
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
2026-02-19 23:03:16 +00:00
restart_xray( ) {
systemctl reload x-ui
2026-04-02 01:49:12 +00:00
LOGI "xray-core 重启信号已发送,请查看日志信息确认 xray 是否重启成功"
2026-02-19 23:03:16 +00:00
sleep 2
show_xray_status
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
2023-02-09 19:18:06 +00:00
status( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service x-ui status
else
systemctl status x-ui -l
fi
2023-02-09 19:18:06 +00:00
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
enable( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
2026-03-04 11:32:01 +00:00
rc-update add x-ui default
2025-09-22 19:56:43 +00:00
else
systemctl enable x-ui
fi
2023-02-09 19:18:06 +00:00
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "x-ui 设置开机自启成功"
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "x-ui 设置开机自启失败"
2023-02-09 19:18:06 +00:00
fi
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
disable( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-update del x-ui
else
systemctl disable x-ui
fi
2023-02-09 19:18:06 +00:00
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI "x-ui 已取消开机自启"
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE "x-ui 取消开机自启失败"
2023-02-09 19:18:06 +00:00
fi
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
}
show_log( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } 调试日志 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2024-10-15 19:33:41 +00:00
2025-09-22 19:56:43 +00:00
case " $choice " in
0)
show_menu
; ;
1)
grep -F 'x-ui[' /var/log/messages
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
; ;
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2025-09-22 19:56:43 +00:00
show_log
; ;
esac
else
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } 调试日志 "
echo -e " ${ green } \t2. ${ plain } 清除所有日志 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2025-09-22 19:56:43 +00:00
case " $choice " in
0)
show_menu
; ;
1)
journalctl -u x-ui -e --no-pager -f -p debug
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
; ;
2)
sudo journalctl --rotate
sudo journalctl --vacuum-time= 1s
2026-04-02 01:49:12 +00:00
echo "所有日志已清除。"
2025-09-22 19:56:43 +00:00
restart
; ;
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2025-09-22 19:56:43 +00:00
show_log
; ;
esac
fi
2023-02-09 19:18:06 +00:00
}
2024-02-21 12:46:45 +00:00
bbr_menu( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } 启用 BBR "
echo -e " ${ green } \t2. ${ plain } 禁用 BBR "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2024-02-21 12:46:45 +00:00
case " $choice " in
0)
show_menu
; ;
1)
enable_bbr
2024-11-04 09:33:07 +00:00
bbr_menu
2024-02-21 12:46:45 +00:00
; ;
2)
disable_bbr
2024-11-04 09:33:07 +00:00
bbr_menu
; ;
2025-03-04 08:50:24 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
bbr_menu
2024-02-21 12:46:45 +00:00
; ;
esac
}
disable_bbr( ) {
2026-01-18 14:47:02 +00:00
if [ [ $( sysctl -n net.ipv4.tcp_congestion_control) != "bbr" ] ] || [ [ ! $( sysctl -n net.core.default_qdisc) = ~ ^( fq| cake) $ ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } BBR 当前未启用。 ${ plain } "
2024-11-04 09:33:07 +00:00
before_show_menu
2024-02-21 12:46:45 +00:00
fi
2026-01-18 14:47:02 +00:00
if [ -f "/etc/sysctl.d/99-bbr-x-ui.conf" ] ; then
old_settings = $( head -1 /etc/sysctl.d/99-bbr-x-ui.conf | tr -d '#' )
sysctl -w net.core.default_qdisc= " ${ old_settings % : * } "
sysctl -w net.ipv4.tcp_congestion_control= " ${ old_settings #* : } "
rm /etc/sysctl.d/99-bbr-x-ui.conf
sysctl --system
else
2026-04-02 01:49:12 +00:00
# 用 CUBIC 配置替换 BBR
2026-01-18 14:47:02 +00:00
if [ -f "/etc/sysctl.conf" ] ; then
sed -i 's/net.core.default_qdisc=fq/net.core.default_qdisc=pfifo_fast/' /etc/sysctl.conf
sed -i 's/net.ipv4.tcp_congestion_control=bbr/net.ipv4.tcp_congestion_control=cubic/' /etc/sysctl.conf
sysctl -p
fi
fi
2024-02-21 12:46:45 +00:00
2026-01-18 14:47:02 +00:00
if [ [ $( sysctl -n net.ipv4.tcp_congestion_control) != "bbr" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } BBR 已成功替换为 CUBIC。 ${ plain } "
2024-02-21 12:46:45 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 替换 BBR 为 CUBIC 失败。请检查系统配置。 ${ plain } "
2024-02-21 12:46:45 +00:00
fi
}
2023-03-07 22:34:07 +00:00
enable_bbr( ) {
2026-01-18 14:47:02 +00:00
if [ [ $( sysctl -n net.ipv4.tcp_congestion_control) = = "bbr" ] ] && [ [ $( sysctl -n net.core.default_qdisc) = ~ ^( fq| cake) $ ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } BBR 已启用! ${ plain } "
2024-11-04 09:33:07 +00:00
before_show_menu
2023-04-18 05:51:21 +00:00
fi
2023-02-09 19:18:06 +00:00
2026-04-02 01:49:12 +00:00
# 启用 BBR
2026-01-18 14:47:02 +00:00
if [ -d "/etc/sysctl.d/" ] ; then
{
echo " # $( sysctl -n net.core.default_qdisc) : $( sysctl -n net.ipv4.tcp_congestion_control) "
echo "net.core.default_qdisc = fq"
echo "net.ipv4.tcp_congestion_control = bbr"
} > "/etc/sysctl.d/99-bbr-x-ui.conf"
if [ -f "/etc/sysctl.conf" ] ; then
2026-04-02 01:49:12 +00:00
# 备份 sysctl.conf 中的旧设置(如果有)
2026-01-18 14:47:02 +00:00
sed -i 's/^net.core.default_qdisc/# &/' /etc/sysctl.conf
sed -i 's/^net.ipv4.tcp_congestion_control/# &/' /etc/sysctl.conf
fi
sysctl --system
else
sed -i '/net.core.default_qdisc/d' /etc/sysctl.conf
sed -i '/net.ipv4.tcp_congestion_control/d' /etc/sysctl.conf
echo "net.core.default_qdisc=fq" | tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
sysctl -p
fi
2023-02-09 19:18:06 +00:00
2026-04-02 01:49:12 +00:00
# 验证 BBR 是否已启用
2026-01-18 14:47:02 +00:00
if [ [ $( sysctl -n net.ipv4.tcp_congestion_control) = = "bbr" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } BBR 已成功启用。 ${ plain } "
2023-04-18 05:51:21 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 启用 BBR 失败。请检查系统配置。 ${ plain } "
2023-04-18 05:51:21 +00:00
fi
2023-02-09 19:18:06 +00:00
}
update_shell( ) {
2026-04-02 01:49:12 +00:00
curl -fLRo /usr/bin/x-ui -z /usr/bin/x-ui https://github.com/Sora39831/3x-ui/raw/main/x-ui.sh
2023-02-09 19:18:06 +00:00
if [ [ $? != 0 ] ] ; then
echo ""
2026-04-02 01:49:12 +00:00
LOGE "下载脚本失败,请检查机器是否能连接 GitHub"
2023-02-09 19:18:06 +00:00
before_show_menu
else
chmod +x /usr/bin/x-ui
2026-04-02 01:49:12 +00:00
LOGI "升级脚本成功,请重新运行脚本"
2024-11-04 09:33:07 +00:00
before_show_menu
2023-02-09 19:18:06 +00:00
fi
}
2026-04-02 01:49:12 +00:00
# 0: 运中, 1: 未运行, 2: 未安装
2023-02-09 19:18:06 +00:00
check_status( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
if [ [ ! -f /etc/init.d/x-ui ] ] ; then
2026-04-22 01:52:10 +00:00
if [ [ -x " ${ xui_folder } /x-ui " || -d /etc/x-ui || -d " ${ xui_folder } " ] ] ; then
return 1
fi
2025-09-22 19:56:43 +00:00
return 2
fi
if [ [ $( rc-service x-ui status | grep -F 'status: started' -c) = = 1 ] ] ; then
return 0
else
return 1
fi
2023-02-09 19:18:06 +00:00
else
2026-01-03 02:57:19 +00:00
if [ [ ! -f ${ xui_service } /x-ui.service ] ] ; then
2026-04-22 01:52:10 +00:00
if [ [ -x " ${ xui_folder } /x-ui " || -d /etc/x-ui || -d " ${ xui_folder } " ] ] ; then
return 1
fi
2025-09-22 19:56:43 +00:00
return 2
fi
temp = $( systemctl status x-ui | grep Active | awk '{print $3}' | cut -d "(" -f2 | cut -d ")" -f1)
if [ [ " ${ temp } " = = "running" ] ] ; then
return 0
else
return 1
fi
2023-02-09 19:18:06 +00:00
fi
}
check_enabled( ) {
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
if [ [ $( rc-update show | grep -F 'x-ui' | grep default -c) = = 1 ] ] ; then
return 0
else
return 1
fi
2023-02-09 19:18:06 +00:00
else
2025-09-22 19:56:43 +00:00
temp = $( systemctl is-enabled x-ui)
if [ [ " ${ temp } " = = "enabled" ] ] ; then
return 0
else
return 1
fi
2023-02-09 19:18:06 +00:00
fi
}
check_uninstall( ) {
check_status
if [ [ $? != 2 ] ] ; then
echo ""
2026-04-02 01:49:12 +00:00
LOGE "面板已安装,请勿重复安装"
2023-02-09 19:18:06 +00:00
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
return 1
else
return 0
fi
}
check_install( ) {
check_status
if [ [ $? = = 2 ] ] ; then
echo ""
2026-04-02 01:49:12 +00:00
LOGE "请先安装面板"
2023-02-09 19:18:06 +00:00
if [ [ $# = = 0 ] ] ; then
before_show_menu
fi
return 1
else
return 0
fi
}
show_status( ) {
check_status
case $? in
0)
2026-04-02 01:49:12 +00:00
echo -e " 面板状态: ${ green } 运行中 ${ plain } "
2023-02-09 19:18:06 +00:00
show_enable_status
; ;
1)
2026-04-02 01:49:12 +00:00
echo -e " 面板状态: ${ yellow } 未运行 ${ plain } "
2023-02-09 19:18:06 +00:00
show_enable_status
; ;
2)
2026-04-02 01:49:12 +00:00
echo -e " 面板状态: ${ red } 未安装 ${ plain } "
2023-02-09 19:18:06 +00:00
; ;
esac
show_xray_status
}
show_enable_status( ) {
check_enabled
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " 开机自启: ${ green } 是 ${ plain } "
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " 开机自启: ${ red } 否 ${ plain } "
2023-02-09 19:18:06 +00:00
fi
}
check_xray_status( ) {
count = $( ps -ef | grep "xray-linux" | grep -v "grep" | wc -l)
if [ [ count -ne 0 ] ] ; then
return 0
else
return 1
fi
}
show_xray_status( ) {
check_xray_status
if [ [ $? = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " xray 状态: ${ green } 运行中 ${ plain } "
2023-02-09 19:18:06 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " xray 状态: ${ red } 未运行 ${ plain } "
2023-02-09 19:18:06 +00:00
fi
}
2024-02-07 17:53:11 +00:00
firewall_menu( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } ${ green } 安装 ${ plain } 防火墙 "
echo -e " ${ green } \t2. ${ plain } 端口列表 [带编号] "
echo -e " ${ green } \t3. ${ plain } ${ green } 开放 ${ plain } 端口 "
echo -e " ${ green } \t4. ${ plain } ${ red } 删除 ${ plain } 列表中的端口 "
echo -e " ${ green } \t5. ${ plain } ${ green } 启用 ${ plain } 防火墙 "
echo -e " ${ green } \t6. ${ plain } ${ red } 禁用 ${ plain } 防火墙 "
echo -e " ${ green } \t7. ${ plain } 防火墙状态 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2024-02-07 17:53:11 +00:00
case " $choice " in
0)
show_menu
; ;
1)
2024-12-20 17:43:47 +00:00
install_firewall
2024-11-04 09:33:07 +00:00
firewall_menu
2024-02-07 17:53:11 +00:00
; ;
2)
2024-12-20 22:48:10 +00:00
ufw status numbered
2024-11-04 09:33:07 +00:00
firewall_menu
2024-02-07 17:53:11 +00:00
; ;
3)
2024-12-20 22:48:10 +00:00
open_ports
2024-11-04 09:33:07 +00:00
firewall_menu
2024-02-07 17:53:11 +00:00
; ;
4)
2024-12-20 22:48:10 +00:00
delete_ports
2026-04-02 01:49:12 +00:00
firewall_wall_menu
2024-12-20 17:43:47 +00:00
; ;
5)
2025-01-05 14:53:53 +00:00
ufw enable
2024-11-04 09:33:07 +00:00
firewall_menu
; ;
2024-12-20 17:43:47 +00:00
6)
2025-01-05 14:53:53 +00:00
ufw disable
firewall_menu
; ;
7)
2024-12-20 22:48:10 +00:00
ufw status verbose
2024-12-20 17:43:47 +00:00
firewall_menu
; ;
2025-03-04 08:50:24 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
firewall_menu
2024-02-07 17:53:11 +00:00
; ;
esac
}
2024-12-20 17:43:47 +00:00
install_firewall( ) {
2023-05-13 15:36:16 +00:00
if ! command -v ufw & >/dev/null; then
2026-04-02 01:49:12 +00:00
echo "ufw 防火墙未安装,正在安装..."
2023-07-01 12:26:43 +00:00
apt-get update
apt-get install -y ufw
2023-04-02 14:42:00 +00:00
else
2026-04-02 01:49:12 +00:00
echo "ufw 防火墙已安装"
2023-04-02 14:42:00 +00:00
fi
2026-04-02 01:49:12 +00:00
# 检查防火墙是否处于非活动状态
2023-07-01 12:26:43 +00:00
if ufw status | grep -q "Status: active" ; then
2026-04-02 01:49:12 +00:00
echo "防火墙已激活"
2023-04-18 05:51:21 +00:00
else
2026-04-02 01:49:12 +00:00
echo "正在激活防火墙..."
# 开放必要端口
2023-07-01 12:26:43 +00:00
ufw allow ssh
ufw allow http
ufw allow https
2024-12-20 17:43:47 +00:00
ufw allow 2053/tcp #webPort
ufw allow 2096/tcp #subport
2023-04-18 05:51:21 +00:00
2026-04-02 01:49:12 +00:00
# 启用防火墙
2023-07-01 12:26:43 +00:00
ufw --force enable
2024-12-20 18:04:34 +00:00
fi
2024-12-20 17:43:47 +00:00
}
2023-04-18 05:51:21 +00:00
2024-12-20 17:43:47 +00:00
open_ports( ) {
2026-04-02 01:49:12 +00:00
# 提示用户输入要开放的端口
read -rp "输入要开放的端口(例如 80,443,2053 或范围 400-500) : " ports
2023-04-18 05:51:21 +00:00
2026-04-02 01:49:12 +00:00
# 检查输入是否有效
2023-04-18 05:51:21 +00:00
if ! [ [ $ports = ~ ^( [ 0-9] +| [ 0-9] +-[ 0-9] +) ( ,( [ 0-9] +| [ 0-9] +-[ 0-9] +) ) *$ ] ] ; then
2026-04-02 01:49:12 +00:00
echo "错误:无效输入。请输入逗号分隔的端口列表或端口范围(例如 80,443,2053 或 400-500) 。" >& 2
2023-05-13 15:36:16 +00:00
exit 1
2023-04-18 05:51:21 +00:00
fi
2026-04-02 01:49:12 +00:00
# 使用 ufw 开放指定端口
2023-05-13 15:36:16 +00:00
IFS = ',' read -ra PORT_LIST <<< " $ports "
2023-04-18 05:51:21 +00:00
for port in " ${ PORT_LIST [@] } " ; do
if [ [ $port = = *-* ] ] ; then
2026-04-02 01:49:12 +00:00
# 将范围拆分为起始和结束端口
2023-05-13 15:36:16 +00:00
start_port = $( echo $port | cut -d'-' -f1)
end_port = $( echo $port | cut -d'-' -f2)
2026-04-02 01:49:12 +00:00
# 开放端口范围
2024-04-22 21:05:05 +00:00
ufw allow $start_port :$end_port /tcp
ufw allow $start_port :$end_port /udp
2023-04-18 05:51:21 +00:00
else
2026-04-02 01:49:12 +00:00
# 开放单个端口
2023-07-01 12:26:43 +00:00
ufw allow " $port "
2023-04-18 05:51:21 +00:00
fi
done
2023-04-02 14:42:00 +00:00
2026-04-02 01:49:12 +00:00
# 确认端口已开放
echo "已开放指定端口:"
2024-12-20 17:43:47 +00:00
for port in " ${ PORT_LIST [@] } " ; do
if [ [ $port = = *-* ] ] ; then
start_port = $( echo $port | cut -d'-' -f1)
end_port = $( echo $port | cut -d'-' -f2)
2026-04-02 01:49:12 +00:00
# 检查端口范围是否已成功开放
2024-12-20 17:43:47 +00:00
( ufw status | grep -q " $start_port : $end_port " ) && echo " $start_port - $end_port "
else
2026-04-02 01:49:12 +00:00
# 检查单个端口是否已成功开放
2024-12-20 17:43:47 +00:00
( ufw status | grep -q " $port " ) && echo " $port "
fi
done
2023-04-18 05:51:21 +00:00
}
2023-04-02 14:42:00 +00:00
2024-02-07 17:53:11 +00:00
delete_ports( ) {
2026-04-02 01:49:12 +00:00
# 显示当前带编号的规则
echo "当前 UFW 规则:"
2025-01-05 14:53:53 +00:00
ufw status numbered
2026-04-02 01:49:12 +00:00
# 询问用户删除方式
echo "您想通过以下哪种方式删除规则:"
echo "1) 规则编号"
echo "2) 端口号"
read -rp "请输入选择( 1 或 2) : " choice
2025-01-05 14:53:53 +00:00
if [ [ $choice -eq 1 ] ] ; then
2026-04-02 01:49:12 +00:00
# 按规则编号删除
read -rp "输入要删除的规则编号( 1, 2 等):" rule_numbers
2025-01-05 14:53:53 +00:00
2026-04-02 01:49:12 +00:00
# 验证输入
2025-01-05 14:53:53 +00:00
if ! [ [ $rule_numbers = ~ ^( [ 0-9] +) ( ,[ 0-9] +) *$ ] ] ; then
2026-04-02 01:49:12 +00:00
echo "错误:无效输入。请输入逗号分隔的规则编号列表。" >& 2
2025-01-05 14:53:53 +00:00
exit 1
fi
2024-02-07 17:53:11 +00:00
2026-04-02 01:49:12 +00:00
# 将编号拆分为数组
2025-01-05 14:53:53 +00:00
IFS = ',' read -ra RULE_NUMBERS <<< " $rule_numbers "
for rule_number in " ${ RULE_NUMBERS [@] } " ; do
2026-04-02 01:49:12 +00:00
# 按编号删除规则
ufw delete " $rule_number " || echo " 删除规则编号 $rule_number 失败 "
2025-01-05 14:53:53 +00:00
done
2024-02-07 17:53:11 +00:00
2026-04-02 01:49:12 +00:00
echo "已删除所选规则。"
2024-02-07 17:53:11 +00:00
2025-01-05 14:53:53 +00:00
elif [ [ $choice -eq 2 ] ] ; then
2026-04-02 01:49:12 +00:00
# 按端口删除
read -rp "输入要删除的端口(例如 80,443,2053 或范围 400-500) : " ports
2024-10-07 15:21:32 +00:00
2026-04-02 01:49:12 +00:00
# 验证输入
2025-01-05 14:53:53 +00:00
if ! [ [ $ports = ~ ^( [ 0-9] +| [ 0-9] +-[ 0-9] +) ( ,( [ 0-9] +| [ 0-9] +-[ 0-9] +) ) *$ ] ] ; then
2026-04-02 01:49:12 +00:00
echo "错误:无效输入。请输入逗号分隔的端口列表或端口范围(例如 80,443,2053 或 400-500) 。" >& 2
2025-01-05 14:53:53 +00:00
exit 1
2024-04-22 21:05:05 +00:00
fi
2025-01-05 14:53:53 +00:00
2026-04-02 01:49:12 +00:00
# 将端口拆分为数组
2025-01-05 14:53:53 +00:00
IFS = ',' read -ra PORT_LIST <<< " $ports "
for port in " ${ PORT_LIST [@] } " ; do
if [ [ $port = = *-* ] ] ; then
2026-04-02 01:49:12 +00:00
# 拆分端口范围
2025-01-05 14:53:53 +00:00
start_port = $( echo $port | cut -d'-' -f1)
end_port = $( echo $port | cut -d'-' -f2)
2026-04-02 01:49:12 +00:00
# 删除端口范围
2025-01-05 14:53:53 +00:00
ufw delete allow $start_port :$end_port /tcp
ufw delete allow $start_port :$end_port /udp
else
2026-04-02 01:49:12 +00:00
# 删除单个端口
2025-01-05 14:53:53 +00:00
ufw delete allow " $port "
fi
done
2026-04-02 01:49:12 +00:00
# 确认删除
echo "已删除指定端口:"
2025-01-05 14:53:53 +00:00
for port in " ${ PORT_LIST [@] } " ; do
if [ [ $port = = *-* ] ] ; then
start_port = $( echo $port | cut -d'-' -f1)
end_port = $( echo $port | cut -d'-' -f2)
2026-04-02 01:49:12 +00:00
# 检查端口范围是否已删除
2025-01-05 14:53:53 +00:00
( ufw status | grep -q " $start_port : $end_port " ) || echo " $start_port - $end_port "
else
2026-04-02 01:49:12 +00:00
# 检查单个端口是否已删除
2025-01-05 14:53:53 +00:00
( ufw status | grep -q " $port " ) || echo " $port "
fi
done
else
2026-04-02 01:49:12 +00:00
echo " ${ red } 错误: ${ plain } 无效选择。请输入 1 或 2。 " >& 2
2025-01-05 14:53:53 +00:00
exit 1
fi
2024-02-07 17:53:11 +00:00
}
2025-11-07 18:26:43 +00:00
update_all_geofiles( ) {
2026-01-09 16:03:53 +00:00
update_geofiles "main"
update_geofiles "IR"
update_geofiles "RU"
2025-11-07 18:26:43 +00:00
}
2026-01-09 16:03:53 +00:00
update_geofiles( ) {
case " ${ 1 } " in
"main" ) dat_files = ( geoip geosite) ; dat_source = "Loyalsoldier/v2ray-rules-dat" ; ;
"IR" ) dat_files = ( geoip_IR geosite_IR) ; dat_source = "chocolate4u/Iran-v2ray-rules" ; ;
"RU" ) dat_files = ( geoip_RU geosite_RU) ; dat_source = "runetfreedom/russia-v2ray-rules-dat" ; ;
esac
for dat in " ${ dat_files [@] } " ; do
2026-04-02 01:49:12 +00:00
# 移除后缀获取远程文件名(例如 geoip_IR -> geoip)
2026-01-11 14:28:43 +00:00
remote_file = " ${ dat %%_* } "
2026-01-09 16:03:53 +00:00
curl -fLRo ${ xui_folder } /bin/${ dat } .dat -z ${ xui_folder } /bin/${ dat } .dat \
2026-01-11 14:28:43 +00:00
https://github.com/${ dat_source } /releases/latest/download/${ remote_file } .dat
2026-01-09 16:03:53 +00:00
done
2025-11-07 18:26:43 +00:00
}
2023-04-18 05:51:21 +00:00
update_geo( ) {
2024-10-07 13:37:53 +00:00
echo -e " ${ green } \t1. ${ plain } Loyalsoldier (geoip.dat, geosite.dat) "
echo -e " ${ green } \t2. ${ plain } chocolate4u (geoip_IR.dat, geosite_IR.dat) "
2025-01-05 13:45:25 +00:00
echo -e " ${ green } \t3. ${ plain } runetfreedom (geoip_RU.dat, geosite_RU.dat) "
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t4. ${ plain } 全部更新 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2024-10-07 15:21:32 +00:00
2024-10-07 13:37:53 +00:00
case " $choice " in
0)
show_menu
; ;
1)
2026-01-09 16:03:53 +00:00
update_geofiles "main"
2026-04-02 01:49:12 +00:00
echo -e " ${ green } Loyalsoldier 数据集更新成功! ${ plain } "
2024-11-04 09:33:07 +00:00
restart
2024-10-07 13:37:53 +00:00
; ;
2)
2026-01-09 16:03:53 +00:00
update_geofiles "IR"
2026-04-02 01:49:12 +00:00
echo -e " ${ green } chocolate4u 数据集更新成功! ${ plain } "
2024-11-04 09:33:07 +00:00
restart
2024-10-07 13:37:53 +00:00
; ;
3)
2026-01-09 16:03:53 +00:00
update_geofiles "RU"
2026-04-02 01:49:12 +00:00
echo -e " ${ green } runetfreedom 数据集更新成功! ${ plain } "
2024-11-04 09:33:07 +00:00
restart
2024-10-07 13:37:53 +00:00
; ;
2025-11-07 18:26:43 +00:00
4)
update_all_geofiles
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 所有 geo 文件更新成功! ${ plain } "
2025-11-07 18:26:43 +00:00
restart
; ;
2024-10-07 13:37:53 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
update_geo
2024-10-07 13:37:53 +00:00
; ;
esac
2024-10-07 15:21:32 +00:00
2023-04-18 05:51:21 +00:00
before_show_menu
2023-04-02 14:42:00 +00:00
}
2023-02-15 18:57:42 +00:00
install_acme( ) {
2026-04-02 01:49:12 +00:00
# 检查 acme.sh 是否已安装
2024-10-07 13:28:00 +00:00
if command -v ~/.acme.sh/acme.sh & >/dev/null; then
2026-04-02 01:49:12 +00:00
LOGI "acme.sh 已安装。"
2024-10-07 13:28:00 +00:00
return 0
fi
2026-04-02 01:49:12 +00:00
LOGI "正在安装 acme.sh..."
cd ~ || return 1
2024-10-07 13:28:00 +00:00
curl -s https://get.acme.sh | sh
2023-02-15 18:57:42 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装 acme.sh 失败。"
2023-02-15 18:57:42 +00:00
return 1
else
2026-04-02 01:49:12 +00:00
LOGI "安装 acme.sh 成功。"
2023-02-15 18:57:42 +00:00
fi
2024-10-07 13:28:00 +00:00
2023-02-15 18:57:42 +00:00
return 0
}
2023-05-22 23:04:36 +00:00
ssl_cert_issue_main( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } 获取 SSL( 域名) "
echo -e " ${ green } \t2. ${ plain } 吊销证书 "
echo -e " ${ green } \t3. ${ plain } 强制续期 "
echo -e " ${ green } \t4. ${ plain } 查看已有域名 "
echo -e " ${ green } \t5. ${ plain } 为面板设置证书路径 "
echo -e " ${ green } \t6. ${ plain } 为 IP 地址获取 SSL( 6 天证书,自动续期) "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2023-05-22 23:04:36 +00:00
case " $choice " in
2024-01-20 13:58:44 +00:00
0)
show_menu
; ;
1)
ssl_cert_issue
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-01-20 13:58:44 +00:00
; ;
2)
2024-10-07 12:50:59 +00:00
local domains = $( find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename { } \; )
if [ -z " $domains " ] ; then
2026-04-02 01:49:12 +00:00
echo "未找到可吊销的证书。"
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo "已有域名:"
2024-10-07 12:50:59 +00:00
echo " $domains "
2026-04-02 01:49:12 +00:00
read -rp "请输入要吊销证书的域名:" domain
2024-10-07 15:21:32 +00:00
if echo " $domains " | grep -qw " $domain " ; then
2024-10-07 12:50:59 +00:00
~/.acme.sh/acme.sh --revoke -d ${ domain }
2026-04-02 01:49:12 +00:00
LOGI " 域名 $domain 的证书已吊销 "
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo "输入的域名无效。"
2024-10-07 12:50:59 +00:00
fi
fi
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-01-20 13:58:44 +00:00
; ;
3)
2024-10-07 12:50:59 +00:00
local domains = $( find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename { } \; )
if [ -z " $domains " ] ; then
2026-04-02 01:49:12 +00:00
echo "未找到可续期的证书。"
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo "已有域名:"
2024-10-07 12:50:59 +00:00
echo " $domains "
2026-04-02 01:49:12 +00:00
read -rp "请输入要续期 SSL 证书的域名:" domain
2024-10-07 15:21:32 +00:00
if echo " $domains " | grep -qw " $domain " ; then
2024-10-07 12:50:59 +00:00
~/.acme.sh/acme.sh --renew -d ${ domain } --force
2026-04-02 01:49:12 +00:00
LOGI " 域名 $domain 的证书已强制续期 "
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo "输入的域名无效。"
2024-10-07 12:50:59 +00:00
fi
fi
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-10-07 12:50:59 +00:00
; ;
4)
local domains = $( find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename { } \; )
if [ -z " $domains " ] ; then
2026-04-02 01:49:12 +00:00
echo "未找到证书。"
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo "已有域名及其路径:"
2024-10-07 12:50:59 +00:00
for domain in $domains ; do
local cert_path = " /root/cert/ ${ domain } /fullchain.pem "
local key_path = " /root/cert/ ${ domain } /privkey.pem "
if [ [ -f " ${ cert_path } " && -f " ${ key_path } " ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " 域名: ${ domain } "
echo -e " \t证书路径: ${ cert_path } "
echo -e " \t私钥路径: ${ key_path } "
2024-10-07 12:50:59 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " 域名: ${ domain } - 证书或密钥缺失。 "
2024-10-07 12:50:59 +00:00
fi
done
fi
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-10-07 12:50:59 +00:00
; ;
2024-10-07 13:13:38 +00:00
5)
local domains = $( find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename { } \; )
if [ -z " $domains " ] ; then
2026-04-02 01:49:12 +00:00
echo "未找到证书。"
2024-10-07 13:13:38 +00:00
else
2026-04-02 01:49:12 +00:00
echo "可选域名:"
2024-10-07 13:13:38 +00:00
echo " $domains "
2026-04-02 01:49:12 +00:00
read -rp "请选择一个域名设置面板路径:" domain
2024-10-07 15:21:32 +00:00
if echo " $domains " | grep -qw " $domain " ; then
2024-10-07 13:13:38 +00:00
local webCertFile = " /root/cert/ ${ domain } /fullchain.pem "
local webKeyFile = " /root/cert/ ${ domain } /privkey.pem "
2024-10-07 15:21:32 +00:00
2024-10-07 13:13:38 +00:00
if [ [ -f " ${ webCertFile } " && -f " ${ webKeyFile } " ] ] ; then
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui cert -webCert " $webCertFile " -webCertKey " $webKeyFile "
2026-04-02 01:49:12 +00:00
echo " 域名 $domain 的面板路径已设置 "
echo " - 证书文件: $webCertFile "
echo " - 私钥文件: $webKeyFile "
2024-10-07 15:38:23 +00:00
restart
2024-10-07 13:13:38 +00:00
else
2026-04-02 01:49:12 +00:00
echo " 未找到域名 $domain 的证书或私钥。 "
2024-10-07 13:13:38 +00:00
fi
else
2026-04-02 01:49:12 +00:00
echo "输入的域名无效。"
2024-10-07 13:13:38 +00:00
fi
fi
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-10-07 13:13:38 +00:00
; ;
2025-12-27 23:03:33 +00:00
6)
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } Let's Encrypt IP 地址 SSL 证书 ${ plain } "
echo -e "将使用短期配置文件为服务器 IP 获取证书。"
echo -e " ${ yellow } 证书有效期约 6 天,通过 acme.sh cron 自动续期。 ${ plain } "
echo -e " ${ yellow } 80 端口必须开放且可从外网访问。 ${ plain } "
confirm "是否继续?" "y"
2025-12-27 23:03:33 +00:00
if [ [ $? = = 0 ] ] ; then
ssl_cert_issue_for_ip
fi
ssl_cert_issue_main
; ;
2024-10-07 15:21:32 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
ssl_cert_issue_main
2024-01-20 13:58:44 +00:00
; ;
2023-05-22 23:04:36 +00:00
esac
}
2025-12-27 23:03:33 +00:00
ssl_cert_issue_for_ip( ) {
2026-04-02 01:49:12 +00:00
LOGI "开始为服务器 IP 自动生成 SSL 证书..."
LOGI "使用 Let's Encrypt 短期配置文件(约 6 天有效期,自动续期)"
2026-01-03 02:57:19 +00:00
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}' )
2026-04-02 01:49:12 +00:00
# 获取服务器 IP
2025-12-27 23:03:33 +00:00
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)
fi
2026-04-02 01:49:12 +00:00
2025-12-27 23:03:33 +00:00
if [ -z " $server_ip " ] ; then
2026-04-02 01:49:12 +00:00
LOGE "获取服务器 IP 地址失败"
2025-12-27 23:03:33 +00:00
return 1
fi
2026-04-02 01:49:12 +00:00
LOGI " 检测到服务器 IP: ${ server_ip } "
# 询问可选的 IPv6
2026-01-05 04:28:02 +00:00
local ipv6_addr = ""
2026-04-02 01:49:12 +00:00
read -rp "是否包含 IPv6 地址?(留空跳过):" ipv6_addr
ipv6_addr = " ${ ipv6_addr // / } " # 去除空格
# 先检查 acme.sh
2025-12-27 23:03:33 +00:00
if ! command -v ~/.acme.sh/acme.sh & >/dev/null; then
2026-04-02 01:49:12 +00:00
LOGI "未找到 acme.sh, 正在安装..."
2025-12-27 23:03:33 +00:00
install_acme
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装 acme.sh 失败"
2025-12-27 23:03:33 +00:00
return 1
fi
fi
2026-04-02 01:49:12 +00:00
# 安装 socat
2025-12-27 23:03:33 +00:00
case " ${ release } " in
ubuntu | debian | armbian)
apt-get update >/dev/null 2>& 1 && apt-get install socat -y >/dev/null 2>& 1
; ;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update >/dev/null 2>& 1 && dnf -y install socat >/dev/null 2>& 1
; ;
centos)
if [ [ " ${ VERSION_ID } " = ~ ^7 ] ] ; then
yum -y update >/dev/null 2>& 1 && yum -y install socat >/dev/null 2>& 1
else
dnf -y update >/dev/null 2>& 1 && dnf -y install socat >/dev/null 2>& 1
fi
; ;
arch | manjaro | parch)
pacman -Sy --noconfirm socat >/dev/null 2>& 1
; ;
opensuse-tumbleweed | opensuse-leap)
zypper refresh >/dev/null 2>& 1 && zypper -q install -y socat >/dev/null 2>& 1
; ;
alpine)
apk add socat curl openssl >/dev/null 2>& 1
; ;
*)
2026-04-02 01:49:12 +00:00
LOGW "不支持的系统,无法自动安装 socat"
2025-12-27 23:03:33 +00:00
; ;
esac
2026-04-02 01:49:12 +00:00
# 创建证书目录
2026-01-05 04:28:02 +00:00
certPath = "/root/cert/ip"
mkdir -p " $certPath "
2026-04-02 01:49:12 +00:00
# 构建域名参数
2026-01-05 04:28:02 +00:00
local domain_args = " -d ${ server_ip } "
if [ [ -n " $ipv6_addr " ] ] && is_ipv6 " $ipv6_addr " ; then
domain_args = " ${ domain_args } -d ${ ipv6_addr } "
2026-04-02 01:49:12 +00:00
LOGI " 包含 IPv6 地址: ${ ipv6_addr } "
2026-01-05 04:28:02 +00:00
fi
2026-04-02 01:49:12 +00:00
# 选择 HTTP-01 监听端口(默认 80, 允许自定义)
2026-01-11 14:28:43 +00:00
local WebPort = ""
2026-04-02 01:49:12 +00:00
read -rp "用于 ACME HTTP-01 监听的端口(默认 80) : " WebPort
2026-01-11 14:28:43 +00:00
WebPort = " ${ WebPort :- 80 } "
if ! [ [ " ${ WebPort } " = ~ ^[ 0-9] +$ ] ] || ( ( WebPort < 1 || WebPort > 65535) ) ; then
2026-04-02 01:49:12 +00:00
LOGE "无效端口,回退到 80。"
2026-01-11 14:28:43 +00:00
WebPort = 80
fi
2026-04-02 01:49:12 +00:00
LOGI " 使用端口 ${ WebPort } 为 IP 签发证书: ${ server_ip } "
2026-01-11 14:28:43 +00:00
if [ [ " ${ WebPort } " -ne 80 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGI " 提醒: Let's Encrypt 仍然连接 80 端口;请将外部 80 端口转发到 ${ WebPort } 。 "
2026-01-11 14:28:43 +00:00
fi
while true; do
if is_port_in_use " ${ WebPort } " ; then
2026-04-02 01:49:12 +00:00
LOGI " 端口 ${ WebPort } 当前被占用。 "
2026-01-11 14:28:43 +00:00
local alt_port = ""
2026-04-02 01:49:12 +00:00
read -rp "请输入另一个端口供 acme.sh 独立监听(留空取消):" alt_port
2026-01-11 14:28:43 +00:00
alt_port = " ${ alt_port // / } "
if [ [ -z " ${ alt_port } " ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE " 端口 ${ WebPort } 被占用,无法继续。 "
2026-01-11 14:28:43 +00:00
return 1
fi
if ! [ [ " ${ alt_port } " = ~ ^[ 0-9] +$ ] ] || ( ( alt_port < 1 || alt_port > 65535) ) ; then
2026-04-02 01:49:12 +00:00
LOGE "无效端口。"
2026-01-11 14:28:43 +00:00
return 1
fi
WebPort = " ${ alt_port } "
continue
else
2026-04-02 01:49:12 +00:00
LOGI " 端口 ${ WebPort } 空闲,可以进行独立验证。 "
2026-01-11 14:28:43 +00:00
break
fi
done
2026-04-02 01:49:12 +00:00
# 重载命令 - 续期后重启面板
2026-01-05 04:28:02 +00:00
local reloadCmd = "systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null"
2026-04-02 01:49:12 +00:00
# 使用短期配置文件为 IP 签发证书
2026-01-30 15:35:24 +00:00
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
2026-01-05 04:28:02 +00:00
~/.acme.sh/acme.sh --issue \
${ domain_args } \
--standalone \
--server letsencrypt \
--certificate-profile shortlived \
--days 6 \
--httpport ${ WebPort } \
--force
2026-04-02 01:49:12 +00:00
2026-01-05 04:28:02 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE " 为 IP 签发证书失败: ${ server_ip } "
LOGE " 请确保端口 ${ WebPort } 已开放且服务器可从外网访问 "
# 清理 acme.sh 数据( IPv4 和 IPv6)
2026-01-05 04:28:02 +00:00
rm -rf ~/.acme.sh/${ server_ip } 2>/dev/null
[ [ -n " $ipv6_addr " ] ] && rm -rf ~/.acme.sh/${ ipv6_addr } 2>/dev/null
rm -rf ${ certPath } 2>/dev/null
return 1
2025-12-27 23:03:33 +00:00
else
2026-04-02 01:49:12 +00:00
LOGI " IP 证书签发成功: ${ server_ip } "
2025-12-27 23:03:33 +00:00
fi
2026-04-02 01:49:12 +00:00
# 安装证书
# 注意: acme.sh 可能在 reloadcmd 失败时报告 "Reload error" 并返回非零退出码,
# 但证书文件仍然已安装。我们通过检查文件而非退出码来判断。
2026-01-05 04:28:02 +00:00
~/.acme.sh/acme.sh --installcert -d ${ server_ip } \
--key-file " ${ certPath } /privkey.pem " \
--fullchain-file " ${ certPath } /fullchain.pem " \
--reloadcmd " ${ reloadCmd } " 2>& 1 || true
2026-04-02 01:49:12 +00:00
# 验证证书文件存在(不依赖退出码 - reloadcmd 失败会导致非零)
2026-01-05 04:28:02 +00:00
if [ [ ! -f " ${ certPath } /fullchain.pem " || ! -f " ${ certPath } /privkey.pem " ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装后未找到证书文件"
# 清理 acme.sh 数据( IPv4 和 IPv6)
2026-01-05 04:28:02 +00:00
rm -rf ~/.acme.sh/${ server_ip } 2>/dev/null
[ [ -n " $ipv6_addr " ] ] && rm -rf ~/.acme.sh/${ ipv6_addr } 2>/dev/null
rm -rf ${ certPath } 2>/dev/null
return 1
fi
2026-04-02 01:49:12 +00:00
LOGI "证书文件安装成功"
# 启用自动续期
2026-01-05 04:28:02 +00:00
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>& 1
chmod 600 $certPath /privkey.pem 2>/dev/null
chmod 644 $certPath /fullchain.pem 2>/dev/null
2026-04-02 01:49:12 +00:00
# 为面板设置证书路径
2026-01-05 04:28:02 +00:00
local webCertFile = " ${ certPath } /fullchain.pem "
local webKeyFile = " ${ certPath } /privkey.pem "
2026-04-02 01:49:12 +00:00
2025-12-27 23:03:33 +00:00
if [ [ -f " $webCertFile " && -f " $webKeyFile " ] ] ; then
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui cert -webCert " $webCertFile " -webCertKey " $webKeyFile "
2026-04-02 01:49:12 +00:00
LOGI "面板证书已配置"
LOGI " - 证书文件: $webCertFile "
LOGI " - 私钥文件: $webKeyFile "
LOGI " - 有效期:约 6 天(通过 acme.sh cron 自动续期)"
echo -e " ${ green } 访问地址: https:// ${ server_ip } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
LOGI "面板将重启以应用 SSL 证书..."
2025-12-27 23:03:33 +00:00
restart
return 0
else
2026-04-02 01:49:12 +00:00
LOGE "安装后未找到证书文件"
2025-12-27 23:03:33 +00:00
return 1
fi
}
2023-04-29 21:27:15 +00:00
ssl_cert_issue( ) {
2026-01-03 02:57:19 +00:00
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}' )
2026-04-02 01:49:12 +00:00
# 先检查 acme.sh
2023-02-20 17:29:55 +00:00
if ! command -v ~/.acme.sh/acme.sh & >/dev/null; then
2026-04-02 01:49:12 +00:00
echo "未找到 acme.sh, 将安装它"
2023-02-20 17:29:55 +00:00
install_acme
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装 acme.sh 失败,请查看日志"
2023-02-20 17:29:55 +00:00
exit 1
fi
2023-02-15 18:57:42 +00:00
fi
2024-10-07 13:18:56 +00:00
2026-04-02 01:49:12 +00:00
# 安装 socat
2023-07-01 12:26:43 +00:00
case " ${ release } " in
2024-01-20 13:58:44 +00:00
ubuntu | debian | armbian)
2025-12-27 23:03:33 +00:00
apt-get update >/dev/null 2>& 1 && apt-get install socat -y >/dev/null 2>& 1
2024-01-20 13:58:44 +00:00
; ;
2025-12-03 20:40:49 +00:00
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
2025-12-27 23:03:33 +00:00
dnf -y update >/dev/null 2>& 1 && dnf -y install socat >/dev/null 2>& 1
2024-01-20 13:58:44 +00:00
; ;
2025-12-03 20:40:49 +00:00
centos)
2025-12-27 23:03:33 +00:00
if [ [ " ${ VERSION_ID } " = ~ ^7 ] ] ; then
yum -y update >/dev/null 2>& 1 && yum -y install socat >/dev/null 2>& 1
else
dnf -y update >/dev/null 2>& 1 && dnf -y install socat >/dev/null 2>& 1
fi
2025-12-03 23:05:21 +00:00
; ;
2024-04-17 06:26:02 +00:00
arch | manjaro | parch)
2025-12-27 23:03:33 +00:00
pacman -Sy --noconfirm socat >/dev/null 2>& 1
2024-04-01 08:42:28 +00:00
; ;
2025-12-27 23:03:33 +00:00
opensuse-tumbleweed | opensuse-leap)
zypper refresh >/dev/null 2>& 1 && zypper -q install -y socat >/dev/null 2>& 1
2025-10-01 21:11:37 +00:00
; ;
2025-09-22 19:56:43 +00:00
alpine)
2025-12-27 23:03:33 +00:00
apk add socat curl openssl >/dev/null 2>& 1
2025-09-22 19:56:43 +00:00
; ;
2024-01-20 13:58:44 +00:00
*)
2026-04-02 01:49:12 +00:00
LOGW "不支持的系统,无法自动安装 socat"
2024-01-20 13:58:44 +00:00
; ;
2023-07-01 12:26:43 +00:00
esac
2023-02-15 18:57:42 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装 socat 失败,请查看日志"
2023-02-15 18:57:42 +00:00
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "安装 socat 成功..."
2023-02-15 18:57:42 +00:00
fi
2023-04-02 17:31:08 +00:00
2026-04-02 01:49:12 +00:00
# 获取域名并验证
2023-02-15 18:57:42 +00:00
local domain = ""
2025-12-27 23:03:33 +00:00
while true; do
2026-04-02 01:49:12 +00:00
read -rp "请输入您的域名:" domain
domain = " ${ domain // / } " # 去除空格
2025-12-27 23:03:33 +00:00
if [ [ -z " $domain " ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE "域名不能为空,请重试。"
2025-12-27 23:03:33 +00:00
continue
fi
2026-04-02 01:49:12 +00:00
2025-12-27 23:03:33 +00:00
if ! is_domain " $domain " ; then
2026-04-02 01:49:12 +00:00
LOGE " 无效的域名格式: ${ domain } ,请输入有效的域名。 "
2025-12-27 23:03:33 +00:00
continue
fi
2026-04-02 01:49:12 +00:00
2025-12-27 23:03:33 +00:00
break
done
2026-04-02 01:49:12 +00:00
LOGD " 您的域名是: ${ domain } ,正在检查... "
2023-05-22 23:04:36 +00:00
2026-04-02 01:49:12 +00:00
# 检查是否已存在证书
2024-10-07 13:18:56 +00:00
local currentCert = $( ~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}' )
if [ " ${ currentCert } " = = " ${ domain } " ] ; then
2023-02-15 18:57:42 +00:00
local certInfo = $( ~/.acme.sh/acme.sh --list)
2026-04-02 01:49:12 +00:00
LOGE "系统已有该域名的证书,无法重复签发。当前证书详情:"
2023-02-15 18:57:42 +00:00
LOGI " $certInfo "
2023-05-22 23:45:34 +00:00
exit 1
2023-02-15 18:57:42 +00:00
else
2026-04-02 01:49:12 +00:00
LOGI "您的域名已准备好签发证书..."
2023-02-15 18:57:42 +00:00
fi
2023-05-13 15:36:16 +00:00
2026-04-02 01:49:12 +00:00
# 创建证书目录
2023-05-13 15:36:16 +00:00
certPath = " /root/cert/ ${ domain } "
if [ ! -d " $certPath " ] ; then
mkdir -p " $certPath "
else
rm -rf " $certPath "
mkdir -p " $certPath "
fi
2026-04-02 01:49:12 +00:00
# 获取独立服务器端口号
2023-02-15 18:57:42 +00:00
local WebPort = 80
2026-04-02 01:49:12 +00:00
read -rp "请选择要使用的端口(默认 80) : " WebPort
2023-02-15 18:57:42 +00:00
if [ [ ${ WebPort } -gt 65535 || ${ WebPort } -lt 1 ] ] ; then
2026-04-02 01:49:12 +00:00
LOGE " 输入 ${ WebPort } 无效,将使用默认端口 80。 "
2024-10-07 13:18:56 +00:00
WebPort = 80
2023-02-15 18:57:42 +00:00
fi
2026-04-02 01:49:12 +00:00
LOGI " 将使用端口: ${ WebPort } 签发证书。请确保此端口已开放。 "
2024-10-07 13:18:56 +00:00
2026-04-02 01:49:12 +00:00
# 签发证书
2026-01-30 15:35:24 +00:00
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
2025-03-17 08:12:52 +00:00
~/.acme.sh/acme.sh --issue -d ${ domain } --listen-v6 --standalone --httpport ${ WebPort } --force
2023-02-15 18:57:42 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "签发证书失败,请查看日志。"
2023-02-15 18:57:42 +00:00
rm -rf ~/.acme.sh/${ domain }
exit 1
else
2026-04-02 01:49:12 +00:00
LOGE "证书签发成功,正在安装证书..."
2023-02-15 18:57:42 +00:00
fi
2024-10-07 13:18:56 +00:00
2025-03-18 12:47:58 +00:00
reloadCmd = "x-ui restart"
2026-04-02 01:49:12 +00:00
LOGI " ACME 默认 --reloadcmd 为: ${ yellow } x-ui restart "
LOGI "此命令将在每次签发和续期证书时运行。"
read -rp "是否要修改 ACME 的 --reloadcmd? (y/n): " setReloadcmd
2025-03-18 12:47:58 +00:00
if [ [ " $setReloadcmd " = = "y" || " $setReloadcmd " = = "Y" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " \n ${ green } \t1. ${ plain } 预设: systemctl reload nginx ; x-ui restart "
echo -e " ${ green } \t2. ${ plain } 输入自定义命令 "
echo -e " ${ green } \t0. ${ plain } 保持默认 reloadcmd "
read -rp "请选择:" choice
2025-03-18 12:47:58 +00:00
case " $choice " in
1)
2026-04-02 01:49:12 +00:00
LOGI "Reloadcmd 设为: systemctl reload nginx ; x-ui restart"
2025-03-18 12:47:58 +00:00
reloadCmd = "systemctl reload nginx ; x-ui restart"
; ;
2026-04-02 01:49:12 +00:00
2)
LOGD "建议将 x-ui restart 放在最后,这样即使其他服务失败也不会报错"
read -rp "请输入自定义的 reloadcmd( 例如: systemctl reload nginx ; x-ui restart) : " reloadCmd
LOGI " 您的 reloadcmd 为: ${ reloadCmd } "
2025-03-18 12:47:58 +00:00
; ;
*)
2026-04-02 01:49:12 +00:00
LOGI "保持默认 reloadcmd"
2025-03-18 12:47:58 +00:00
; ;
esac
fi
2026-04-02 01:49:12 +00:00
# 安装证书
2023-04-02 17:31:08 +00:00
~/.acme.sh/acme.sh --installcert -d ${ domain } \
--key-file /root/cert/${ domain } /privkey.pem \
2025-03-18 12:47:58 +00:00
--fullchain-file /root/cert/${ domain } /fullchain.pem --reloadcmd " ${ reloadCmd } "
2023-02-15 18:57:42 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装证书失败,退出。"
2023-02-15 18:57:42 +00:00
rm -rf ~/.acme.sh/${ domain }
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "安装证书成功,正在启用自动续期..."
2023-02-15 18:57:42 +00:00
fi
2026-04-02 01:49:12 +00:00
# 启用自动续期
2023-05-13 15:36:16 +00:00
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "自动续期设置失败,证书详情:"
2023-05-13 15:36:16 +00:00
ls -lah cert/*
2026-01-05 04:28:02 +00:00
chmod 600 $certPath /privkey.pem
chmod 644 $certPath /fullchain.pem
2023-05-13 15:36:16 +00:00
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "自动续期设置成功,证书详情:"
2023-05-13 15:36:16 +00:00
ls -lah cert/*
2026-01-05 04:28:02 +00:00
chmod 600 $certPath /privkey.pem
chmod 644 $certPath /fullchain.pem
2023-05-13 15:36:16 +00:00
fi
2024-10-07 13:18:56 +00:00
2026-04-02 01:49:12 +00:00
# 证书安装成功后提示用户为面板设置证书路径
read -rp "是否要为面板设置此证书?(y/n): " setPanel
2024-10-07 13:18:56 +00:00
if [ [ " $setPanel " = = "y" || " $setPanel " = = "Y" ] ] ; then
local webCertFile = " /root/cert/ ${ domain } /fullchain.pem "
local webKeyFile = " /root/cert/ ${ domain } /privkey.pem "
2024-10-07 15:21:32 +00:00
2024-10-07 13:18:56 +00:00
if [ [ -f " $webCertFile " && -f " $webKeyFile " ] ] ; then
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui cert -webCert " $webCertFile " -webCertKey " $webKeyFile "
2026-04-02 01:49:12 +00:00
LOGI " 域名 $domain 的面板路径已设置 "
LOGI " - 证书文件: $webCertFile "
LOGI " - 私钥文件: $webKeyFile "
echo -e " ${ green } 访问地址: https:// ${ domain } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
2024-10-07 15:38:23 +00:00
restart
2024-10-07 13:18:56 +00:00
else
2026-04-02 01:49:12 +00:00
LOGE " 错误:未找到域名 $domain 的证书或私钥文件。 "
2024-10-07 13:18:56 +00:00
fi
else
2026-04-02 01:49:12 +00:00
LOGI "跳过面板路径设置。"
2024-10-07 13:18:56 +00:00
fi
2023-05-13 15:36:16 +00:00
}
2023-04-18 05:51:21 +00:00
2023-08-08 21:22:40 +00:00
ssl_cert_issue_CF( ) {
2026-01-03 02:57:19 +00:00
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}' )
2026-04-02 01:49:12 +00:00
LOGI "****** 使用说明 ******"
LOGI "请按照以下步骤完成操作:"
LOGI "1. Cloudflare 注册邮箱。"
LOGI "2. Cloudflare 全局 API 密钥。"
LOGI "3. 域名。"
LOGI "4. 证书签发后,将提示您为面板设置证书(可选)。"
LOGI "5. 脚本还支持安装后自动续期 SSL 证书。"
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
confirm "请确认信息并继续?[y/n]" "y"
2024-11-13 16:27:55 +00:00
2023-08-08 21:22:40 +00:00
if [ $? -eq 0 ] ; then
2026-04-02 01:49:12 +00:00
# 检查 acme.sh
2023-08-08 21:22:40 +00:00
if ! command -v ~/.acme.sh/acme.sh & >/dev/null; then
2026-04-02 01:49:12 +00:00
echo "未找到 acme.sh, 将安装它。"
2023-08-08 21:22:40 +00:00
install_acme
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "安装 acme.sh 失败,请查看日志。"
2023-08-08 21:22:40 +00:00
exit 1
fi
fi
2024-11-13 16:27:55 +00:00
2023-08-08 21:22:40 +00:00
CF_Domain = ""
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
LOGD "请设置域名:"
read -rp "在此输入您的域名:" CF_Domain
LOGD " 您的域名设置为: ${ CF_Domain } "
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
# 设置 Cloudflare API 信息
2024-11-13 16:27:55 +00:00
CF_GlobalKey = ""
CF_AccountEmail = ""
2026-04-02 01:49:12 +00:00
LOGD "请设置 API 密钥:"
read -rp "在此输入您的密钥:" CF_GlobalKey
LOGD " 您的 API 密钥为: ${ CF_GlobalKey } "
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
LOGD "请设置注册邮箱:"
read -rp "在此输入您的邮箱:" CF_AccountEmail
LOGD " 您的注册邮箱为: ${ CF_AccountEmail } "
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
# 设置默认 CA 为 Let's Encrypt
2026-01-30 15:35:24 +00:00
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
2023-08-08 21:22:40 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "设置默认 CA( Let's Encrypt) 失败, 脚本退出..."
2023-08-08 21:22:40 +00:00
exit 1
fi
2024-11-13 16:27:55 +00:00
2023-08-08 21:22:40 +00:00
export CF_Key = " ${ CF_GlobalKey } "
2024-11-13 16:27:55 +00:00
export CF_Email = " ${ CF_AccountEmail } "
2026-04-02 01:49:12 +00:00
# 使用 Cloudflare DNS 签发证书
2025-03-17 08:12:52 +00:00
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${ CF_Domain } -d *.${ CF_Domain } --log --force
2023-08-08 21:22:40 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "证书签发失败,脚本退出..."
2023-08-08 21:22:40 +00:00
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "证书签发成功,正在安装..."
2023-08-08 21:22:40 +00:00
fi
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
# 安装证书
2025-03-17 08:12:52 +00:00
certPath = " /root/cert/ ${ CF_Domain } "
if [ -d " $certPath " ] ; then
rm -rf ${ certPath }
fi
mkdir -p ${ certPath }
2024-11-16 11:32:16 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE " 创建目录失败: ${ certPath } "
2024-11-16 11:32:16 +00:00
exit 1
fi
2025-03-17 08:12:52 +00:00
reloadCmd = "x-ui restart"
2026-04-02 01:49:12 +00:00
LOGI " ACME 默认 --reloadcmd 为: ${ yellow } x-ui restart "
LOGI "此命令将在每次签发和续期证书时运行。"
read -rp "是否要修改 ACME 的 --reloadcmd? (y/n): " setReloadcmd
2025-03-17 08:12:52 +00:00
if [ [ " $setReloadcmd " = = "y" || " $setReloadcmd " = = "Y" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " \n ${ green } \t1. ${ plain } 预设: systemctl reload nginx ; x-ui restart "
echo -e " ${ green } \t2. ${ plain } 输入自定义命令 "
echo -e " ${ green } \t0. ${ plain } 保持默认 reloadcmd "
read -rp "请选择:" choice
2025-03-17 08:12:52 +00:00
case " $choice " in
1)
2026-04-02 01:49:12 +00:00
LOGI "Reloadcmd 设为: systemctl reload nginx ; x-ui restart"
2025-03-18 12:47:58 +00:00
reloadCmd = "systemctl reload nginx ; x-ui restart"
2025-03-17 08:12:52 +00:00
; ;
2026-04-02 01:49:12 +00:00
2)
LOGD "建议将 x-ui restart 放在最后,这样即使其他服务失败也不会报错"
read -rp "请输入自定义的 reloadcmd( 例如: systemctl reload nginx ; x-ui restart) : " reloadCmd
LOGI " 您的 reloadcmd 为: ${ reloadCmd } "
2025-03-17 08:12:52 +00:00
; ;
*)
2026-04-02 01:49:12 +00:00
LOGI "保持默认 reloadcmd"
2025-03-17 08:12:52 +00:00
; ;
esac
fi
2024-11-13 16:27:55 +00:00
~/.acme.sh/acme.sh --installcert -d ${ CF_Domain } -d *.${ CF_Domain } \
2025-03-17 08:12:52 +00:00
--key-file ${ certPath } /privkey.pem \
--fullchain-file ${ certPath } /fullchain.pem --reloadcmd " ${ reloadCmd } "
2026-04-02 01:49:12 +00:00
2023-08-08 21:22:40 +00:00
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "证书安装失败,脚本退出..."
2023-08-08 21:22:40 +00:00
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "证书安装成功,正在启用自动更新..."
2023-08-08 21:22:40 +00:00
fi
2024-11-13 16:27:55 +00:00
2026-04-02 01:49:12 +00:00
# 启用自动更新
2023-08-08 21:22:40 +00:00
~/.acme.sh/acme.sh --upgrade --auto-upgrade
if [ $? -ne 0 ] ; then
2026-04-02 01:49:12 +00:00
LOGE "自动更新设置失败,脚本退出..."
2023-08-08 21:22:40 +00:00
exit 1
else
2026-04-02 01:49:12 +00:00
LOGI "证书已安装并开启自动续期。详情如下:"
2025-03-17 08:12:52 +00:00
ls -lah ${ certPath } /*
2026-01-05 04:28:02 +00:00
chmod 600 ${ certPath } /privkey.pem
chmod 644 ${ certPath } /fullchain.pem
2024-11-13 16:27:55 +00:00
fi
2026-04-02 01:49:12 +00:00
# 证书安装成功后提示用户为面板设置证书路径
read -rp "是否要为面板设置此证书?(y/n): " setPanel
2024-11-13 16:27:55 +00:00
if [ [ " $setPanel " = = "y" || " $setPanel " = = "Y" ] ] ; then
2025-03-17 08:12:52 +00:00
local webCertFile = " ${ certPath } /fullchain.pem "
local webKeyFile = " ${ certPath } /privkey.pem "
2024-11-13 16:27:55 +00:00
if [ [ -f " $webCertFile " && -f " $webKeyFile " ] ] ; then
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui cert -webCert " $webCertFile " -webCertKey " $webKeyFile "
2026-04-02 01:49:12 +00:00
LOGI " 域名 $CF_Domain 的面板路径已设置 "
LOGI " - 证书文件: $webCertFile "
LOGI " - 私钥文件: $webKeyFile "
echo -e " ${ green } 访问地址: https:// ${ CF_Domain } : ${ existing_port } ${ existing_webBasePath } ${ plain } "
2024-11-13 16:27:55 +00:00
restart
else
2026-04-02 01:49:12 +00:00
LOGE " 错误:未找到域名 $CF_Domain 的证书或私钥文件。 "
2024-11-13 16:27:55 +00:00
fi
else
2026-04-02 01:49:12 +00:00
LOGI "跳过面板路径设置。"
2023-08-08 21:22:40 +00:00
fi
else
show_menu
fi
}
2023-04-03 15:52:23 +00:00
run_speedtest( ) {
2026-04-02 01:49:12 +00:00
# 检查 Speedtest 是否已安装
2024-01-20 13:58:44 +00:00
if ! command -v speedtest & >/dev/null; then
2026-04-02 01:49:12 +00:00
# 如未安装,确定安装方式
2024-11-20 21:48:32 +00:00
if command -v snap & >/dev/null; then
2026-04-02 01:49:12 +00:00
# 使用 snap 安装 Speedtest
echo "正在使用 snap 安装 Speedtest..."
2024-11-20 21:48:32 +00:00
snap install speedtest
2023-05-22 23:13:15 +00:00
else
2026-04-02 01:49:12 +00:00
# 回退到使用包管理器
2024-11-20 21:48:32 +00:00
local pkg_manager = ""
local speedtest_install_script = ""
if command -v dnf & >/dev/null; then
pkg_manager = "dnf"
speedtest_install_script = "https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v yum & >/dev/null; then
pkg_manager = "yum"
speedtest_install_script = "https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.rpm.sh"
elif command -v apt-get & >/dev/null; then
pkg_manager = "apt-get"
speedtest_install_script = "https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
elif command -v apt & >/dev/null; then
pkg_manager = "apt"
speedtest_install_script = "https://packagecloud.io/install/repositories/ookla/speedtest-cli/script.deb.sh"
fi
if [ [ -z $pkg_manager ] ] ; then
2026-04-02 01:49:12 +00:00
echo "错误:未找到包管理器。您可能需要手动安装 Speedtest。"
2024-11-20 21:48:32 +00:00
return 1
else
2026-04-02 01:49:12 +00:00
echo " 正在使用 $pkg_manager 安装 Speedtest... "
2024-11-20 21:48:32 +00:00
curl -s $speedtest_install_script | bash
$pkg_manager install -y speedtest
fi
2023-04-03 20:30:29 +00:00
fi
2023-04-03 15:52:23 +00:00
fi
speedtest
}
2024-10-28 18:24:44 +00:00
2023-07-18 10:24:28 +00:00
2024-12-20 17:33:27 +00:00
ip_validation( ) {
ipv6_regex = " ^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])) $"
ipv4_regex = " ^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0) $"
}
2023-06-24 20:36:18 +00:00
iplimit_main( ) {
2026-04-02 01:49:12 +00:00
echo -e " \n ${ green } \t1. ${ plain } 安装 Fail2ban 并配置 IP 限制 "
echo -e " ${ green } \t2. ${ plain } 修改封禁时长 "
echo -e " ${ green } \t3. ${ plain } 解封所有人 "
echo -e " ${ green } \t4. ${ plain } 封禁日志 "
echo -e " ${ green } \t5. ${ plain } 封禁指定 IP 地址 "
echo -e " ${ green } \t6. ${ plain } 解封指定 IP 地址 "
echo -e " ${ green } \t7. ${ plain } 实时日志 "
echo -e " ${ green } \t8. ${ plain } 服务状态 "
echo -e " ${ green } \t9. ${ plain } 重启服务 "
echo -e " ${ green } \t10. ${ plain } 卸载 Fail2ban 和 IP 限制 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" choice
2023-06-24 20:36:18 +00:00
case " $choice " in
2024-01-20 13:58:44 +00:00
0)
show_menu
; ;
1)
2026-04-02 01:49:12 +00:00
confirm "是否继续安装 Fail2ban 和 IP 限制?" "y"
2024-01-20 13:58:44 +00:00
if [ [ $? = = 0 ] ] ; then
install_iplimit
else
iplimit_main
fi
; ;
2)
2026-04-02 01:49:12 +00:00
read -rp "请输入新的封禁时长(分钟)[默认 30]: " NUM
2024-01-20 13:58:44 +00:00
if [ [ $NUM = ~ ^[ 0-9] +$ ] ] ; then
create_iplimit_jails ${ NUM }
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service fail2ban restart
else
systemctl restart fail2ban
fi
2024-01-20 13:58:44 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } ${ NUM } 不是数字!请重试。 ${ plain } "
2024-01-20 13:58:44 +00:00
fi
iplimit_main
; ;
3)
2026-04-02 01:49:12 +00:00
confirm "是否继续解封 IP 限制中的所有人?" "y"
2024-01-20 13:58:44 +00:00
if [ [ $? = = 0 ] ] ; then
2024-12-19 22:24:10 +00:00
fail2ban-client reload --restart --unban 3x-ipl
2024-01-21 01:15:17 +00:00
truncate -s 0 " ${ iplimit_banned_log_path } "
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 所有用户已成功解封。 ${ plain } "
2024-01-20 13:58:44 +00:00
iplimit_main
else
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 已取消。 ${ plain } "
2024-01-20 13:58:44 +00:00
fi
iplimit_main
; ;
4)
show_banlog
2024-11-04 09:33:07 +00:00
iplimit_main
2024-01-20 13:58:44 +00:00
; ;
5)
2026-04-02 01:49:12 +00:00
read -rp "输入要封禁的 IP 地址:" ban_ip
2024-12-20 17:33:27 +00:00
ip_validation
if [ [ $ban_ip = ~ $ipv4_regex || $ban_ip = ~ $ipv6_regex ] ] ; then
2024-12-17 16:59:04 +00:00
fail2ban-client set 3x-ipl banip " $ban_ip "
2026-04-02 01:49:12 +00:00
echo -e " ${ green } IP 地址 ${ ban_ip } 已成功封禁。 ${ plain } "
2024-12-17 16:59:04 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } IP 地址格式无效!请重试。 ${ plain } "
2024-12-17 16:59:04 +00:00
fi
2024-11-04 09:33:07 +00:00
iplimit_main
2024-01-20 13:58:44 +00:00
; ;
6)
2026-04-02 01:49:12 +00:00
read -rp "输入要解封的 IP 地址:" unban_ip
2024-12-20 17:33:27 +00:00
ip_validation
if [ [ $unban_ip = ~ $ipv4_regex || $unban_ip = ~ $ipv6_regex ] ] ; then
2024-12-17 16:59:04 +00:00
fail2ban-client set 3x-ipl unbanip " $unban_ip "
2026-04-02 01:49:12 +00:00
echo -e " ${ green } IP 地址 ${ unban_ip } 已成功解封。 ${ plain } "
2024-12-17 16:59:04 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } IP 地址格式无效!请重试。 ${ plain } "
2024-12-17 16:59:04 +00:00
fi
2024-11-04 09:33:07 +00:00
iplimit_main
2024-06-04 10:52:21 +00:00
; ;
7)
2024-12-17 16:59:04 +00:00
tail -f /var/log/fail2ban.log
2024-11-04 09:33:07 +00:00
iplimit_main
2024-10-28 18:24:44 +00:00
; ;
8)
2024-12-17 16:59:04 +00:00
service fail2ban status
iplimit_main
; ;
9)
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service fail2ban restart
else
systemctl restart fail2ban
fi
2024-12-17 16:59:04 +00:00
iplimit_main
; ;
10)
2024-01-20 13:58:44 +00:00
remove_iplimit
2024-11-04 09:33:07 +00:00
iplimit_main
; ;
2024-12-18 11:31:05 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
iplimit_main
2024-01-20 13:58:44 +00:00
; ;
2023-06-24 20:36:18 +00:00
esac
}
install_iplimit( ) {
if ! command -v fail2ban-client & >/dev/null; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } Fail2ban 未安装,正在安装...! ${ plain } \n "
2024-01-20 13:58:44 +00:00
2026-04-02 01:49:12 +00:00
# 检查操作系统并安装必要的软件包
2023-06-24 20:36:18 +00:00
case " ${ release } " in
2024-06-04 10:27:59 +00:00
ubuntu)
2025-08-15 11:33:31 +00:00
apt-get update
2024-06-04 10:27:59 +00:00
if [ [ " ${ os_version } " -ge 24 ] ] ; then
2025-08-15 11:33:31 +00:00
apt-get install python3-pip -y
2024-06-04 10:27:59 +00:00
python3 -m pip install pyasynchat --break-system-packages
fi
2025-08-15 11:33:31 +00:00
apt-get install fail2ban -y
; ;
debian)
apt-get update
if [ " $os_version " -ge 12 ] ; then
apt-get install -y python3-systemd
fi
apt-get install -y fail2ban
2024-06-04 10:27:59 +00:00
; ;
2025-08-15 11:33:31 +00:00
armbian)
apt-get update && apt-get install fail2ban -y
2024-01-20 13:58:44 +00:00
; ;
2025-12-03 20:40:49 +00:00
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
2024-01-20 13:58:44 +00:00
dnf -y update && dnf -y install fail2ban
; ;
2025-12-03 20:40:49 +00:00
centos)
if [ [ " ${ VERSION_ID } " = ~ ^7 ] ] ; then
yum update -y && yum install epel-release -y
yum -y install fail2ban
else
dnf -y update && dnf -y install fail2ban
fi
; ;
2024-04-17 06:26:02 +00:00
arch | manjaro | parch)
2024-06-04 10:27:59 +00:00
pacman -Syu --noconfirm fail2ban
; ;
2025-09-22 19:56:43 +00:00
alpine)
apk add fail2ban
; ;
2024-01-20 13:58:44 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 不支持的操作系统。请检查脚本并手动安装必要的软件包。 ${ plain } \n "
2024-01-20 13:58:44 +00:00
exit 1
; ;
2023-06-24 20:36:18 +00:00
esac
2024-01-19 14:58:09 +00:00
if ! command -v fail2ban-client & >/dev/null; then
2026-04-02 01:49:12 +00:00
echo -e " ${ red } Fail2ban 安装失败。 ${ plain } \n "
2024-01-19 14:58:09 +00:00
exit 1
fi
2026-04-02 01:49:12 +00:00
echo -e " ${ green } Fail2ban 安装成功! ${ plain } \n "
2023-06-24 20:36:18 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } Fail2ban 已安装。 ${ plain } \n "
2023-06-24 20:36:18 +00:00
fi
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 正在配置 IP 限制... ${ plain } \n "
2023-06-24 20:36:18 +00:00
2026-04-02 01:49:12 +00:00
# 确保 jail 文件没有冲突
2023-07-18 10:24:28 +00:00
iplimit_remove_conflicts
2023-06-24 20:36:18 +00:00
2026-04-02 01:49:12 +00:00
# 检查日志文件是否存在
2023-07-18 10:24:28 +00:00
if ! test -f " ${ iplimit_banned_log_path } " ; then
touch ${ iplimit_banned_log_path }
2023-06-24 20:36:18 +00:00
fi
2026-04-02 01:49:12 +00:00
# 检查服务日志文件是否存在,以免 fail2ban 报错
2023-07-18 10:24:28 +00:00
if ! test -f " ${ iplimit_log_path } " ; then
touch ${ iplimit_log_path }
2023-06-24 20:36:18 +00:00
fi
2026-04-02 01:49:12 +00:00
# 创建 iplimit jail 文件
# 此处不传递 bantime 以使用默认值
2023-07-18 10:24:28 +00:00
create_iplimit_jails
2023-06-24 20:36:18 +00:00
2026-04-02 01:49:12 +00:00
# 启动 fail2ban
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
if [ [ $( rc-service fail2ban status | grep -F 'status: started' -c) = = 0 ] ] ; then
rc-service fail2ban start
else
rc-service fail2ban restart
fi
rc-update add fail2ban
2023-06-24 20:36:18 +00:00
else
2025-09-22 19:56:43 +00:00
if ! systemctl is-active --quiet fail2ban; then
systemctl start fail2ban
else
systemctl restart fail2ban
fi
systemctl enable fail2ban
2023-06-24 20:36:18 +00:00
fi
2026-04-02 01:49:12 +00:00
echo -e " ${ green } IP 限制安装并配置成功! ${ plain } \n "
2023-06-24 20:36:18 +00:00
before_show_menu
}
2024-01-20 13:58:44 +00:00
remove_iplimit( ) {
2026-04-02 01:49:12 +00:00
echo -e " ${ green } \t1. ${ plain } 仅移除 IP 限制配置 "
echo -e " ${ green } \t2. ${ plain } 卸载 Fail2ban 和 IP 限制 "
echo -e " ${ green } \t0. ${ plain } 返回主菜单 "
read -rp "请选择:" num
2023-06-24 20:36:18 +00:00
case " $num " in
2024-01-20 13:58:44 +00:00
1)
rm -f /etc/fail2ban/filter.d/3x-ipl.conf
rm -f /etc/fail2ban/action.d/3x-ipl.conf
rm -f /etc/fail2ban/jail.d/3x-ipl.conf
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service fail2ban restart
else
systemctl restart fail2ban
fi
2026-04-02 01:49:12 +00:00
echo -e " ${ green } IP 限制已成功移除! ${ plain } \n "
2024-01-20 13:58:44 +00:00
before_show_menu
; ;
2)
rm -rf /etc/fail2ban
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
rc-service fail2ban stop
else
systemctl stop fail2ban
fi
2024-01-20 13:58:44 +00:00
case " ${ release } " in
2024-04-01 08:42:28 +00:00
ubuntu | debian | armbian)
2024-01-20 13:58:44 +00:00
apt-get remove -y fail2ban
apt-get purge -y fail2ban -y
apt-get autoremove -y
; ;
2025-12-03 20:40:49 +00:00
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
2024-01-20 13:58:44 +00:00
dnf remove fail2ban -y
dnf autoremove -y
; ;
2025-12-03 20:40:49 +00:00
centos)
2026-04-02 01:49:12 +00:00
if [ [ " ${ VERSION_ID } " = ~ ^7 ] ] ; then
2025-12-03 20:40:49 +00:00
yum remove fail2ban -y
yum autoremove -y
else
dnf remove fail2ban -y
dnf autoremove -y
fi
; ;
2024-04-17 06:26:02 +00:00
arch | manjaro | parch)
2024-04-01 08:42:28 +00:00
pacman -Rns --noconfirm fail2ban
; ;
2025-09-22 19:56:43 +00:00
alpine)
apk del fail2ban
; ;
2024-01-20 13:58:44 +00:00
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 不支持的操作系统。请手动卸载 Fail2ban。 ${ plain } \n "
2024-01-20 13:58:44 +00:00
exit 1
; ;
esac
2026-04-02 01:49:12 +00:00
echo -e " ${ green } Fail2ban 和 IP 限制已成功移除! ${ plain } \n "
2024-01-20 13:58:44 +00:00
before_show_menu
; ;
0)
2024-11-04 09:33:07 +00:00
show_menu
2024-01-20 13:58:44 +00:00
; ;
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-01-20 13:58:44 +00:00
remove_iplimit
; ;
2023-06-24 20:36:18 +00:00
esac
}
2023-06-08 12:38:08 +00:00
2025-08-15 11:33:31 +00:00
show_banlog( ) {
local system_log = "/var/log/fail2ban.log"
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 正在检查封禁日志... ${ plain } \n "
2025-08-15 11:33:31 +00:00
2025-09-22 19:56:43 +00:00
if [ [ $release = = "alpine" ] ] ; then
if [ [ $( rc-service fail2ban status | grep -F 'status: started' -c) = = 0 ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ red } Fail2ban 服务未运行! ${ plain } \n "
2025-09-22 19:56:43 +00:00
return 1
fi
else
if ! systemctl is-active --quiet fail2ban; then
2026-04-02 01:49:12 +00:00
echo -e " ${ red } Fail2ban 服务未运行! ${ plain } \n "
2025-09-22 19:56:43 +00:00
return 1
fi
2025-08-15 11:33:31 +00:00
fi
if [ [ -f " $system_log " ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 来自 fail2ban.log 的最近系统封禁活动: ${ plain } "
grep "3x-ipl" " $system_log " | grep -E "Ban|Unban" | tail -n 10 || echo -e " ${ yellow } 未找到最近的系统封禁活动 ${ plain } "
2025-08-15 11:33:31 +00:00
echo ""
fi
if [ [ -f " ${ iplimit_banned_log_path } " ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 3X-IPL 封禁日志条目: ${ plain } "
2025-08-15 11:33:31 +00:00
if [ [ -s " ${ iplimit_banned_log_path } " ] ] ; then
2026-04-02 01:49:12 +00:00
grep -v "INIT" " ${ iplimit_banned_log_path } " | tail -n 10 || echo -e " ${ yellow } 未找到封禁记录 ${ plain } "
2025-08-15 11:33:31 +00:00
else
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 封禁日志文件为空 ${ plain } "
2025-08-15 11:33:31 +00:00
fi
else
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 未找到封禁日志文件: ${ iplimit_banned_log_path } ${ plain } "
2025-08-15 11:33:31 +00:00
fi
2026-04-02 01:49:12 +00:00
echo -e " \n ${ green } 当前 jail 状态: ${ plain } "
fail2ban-client status 3x-ipl || echo -e " ${ yellow } 无法获取 jail 状态 ${ plain } "
2025-08-15 11:33:31 +00:00
}
create_iplimit_jails( ) {
2026-04-02 01:49:12 +00:00
# 如未传递则使用默认封禁时长 => 30 分钟
2025-08-15 11:33:31 +00:00
local bantime = " ${ 1 :- 30 } "
2026-04-02 01:49:12 +00:00
# 取消 fail2ban.conf 中 'allowipv6 = auto' 的注释
2025-08-15 11:33:31 +00:00
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
2026-04-02 01:49:12 +00:00
# 在 Debian 12+ 上, fail2ban 的默认后端应更改为 systemd
2025-08-15 11:33:31 +00:00
if [ [ " ${ release } " = = "debian" && ${ os_version } -ge 12 ] ] ; then
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
fi
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
[ 3x-ipl]
enabled = true
backend = auto
filter = 3x-ipl
action = 3x-ipl
logpath = ${ iplimit_log_path }
maxretry = 2
findtime = 32
bantime = ${ bantime } m
EOF
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
[ Definition]
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
2026-03-08 10:53:34 +00:00
failregex = \[ LIMIT_IP\] \s *Email\s *= \s *<F-USER>.+</F-USER>\s *\| \| \s *Disconnecting OLD IP\s *= \s *<ADDR>\s *\| \| \s *Timestamp\s *= \s *\d +
2025-08-15 11:33:31 +00:00
ignoreregex =
EOF
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
[ INCLUDES]
before = iptables-allports.conf
[ Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j <returntype>
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<actionflush>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
echo "\$(date +" %%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${ iplimit_banned_log_path }
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
echo "\$(date +" %%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${ iplimit_banned_log_path }
[ Init]
name = default
protocol = tcp
chain = INPUT
EOF
2026-04-02 01:49:12 +00:00
echo -e " ${ green } IP 限制 jail 文件已创建,封禁时长为 ${ bantime } 分钟。 ${ plain } "
2025-08-15 11:33:31 +00:00
}
iplimit_remove_conflicts( ) {
local jail_files = (
/etc/fail2ban/jail.conf
/etc/fail2ban/jail.local
)
for file in " ${ jail_files [@] } " ; do
2026-04-02 01:49:12 +00:00
# 检查 jail 文件中是否存在 [3x-ipl] 配置,如存在则移除
2025-08-15 11:33:31 +00:00
if test -f " ${ file } " && grep -qw '3x-ipl' ${ file } ; then
sed -i " /\[3x-ipl\]/,/^ $/d " ${ file }
2026-04-02 01:49:12 +00:00
echo -e " ${ yellow } 正在移除 jail ( ${ file } ) 中 [3x-ipl] 的冲突配置! ${ plain } \n "
2025-08-15 11:33:31 +00:00
fi
done
}
2024-10-31 08:53:47 +00:00
SSH_port_forwarding( ) {
2025-08-21 12:24:25 +00:00
local URL_lists = (
"https://api4.ipify.org"
"https://ipv4.icanhazip.com"
"https://v4.api.ipinfo.io/ip"
"https://ipv4.myexternalip.com/raw"
"https://4.ident.me"
"https://check-host.net/ip"
)
local server_ip = ""
for ip_address in " ${ URL_lists [@] } " ; do
2026-02-11 20:32:23 +00:00
local response = $( curl -s -w "\n%{http_code}" --max-time 3 " ${ ip_address } " 2>/dev/null)
local http_code = $( echo " $response " | tail -n1)
local ip_result = $( echo " $response " | head -n-1 | tr -d '[:space:]' )
if [ [ " ${ http_code } " = = "200" && -n " ${ ip_result } " ] ] ; then
server_ip = " ${ ip_result } "
2025-08-21 12:24:25 +00:00
break
fi
done
2026-02-11 20:32:23 +00:00
2026-01-03 02:57:19 +00:00
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}' )
2024-10-31 08:53:47 +00:00
local config_listenIP = ""
local listen_choice = ""
if [ [ -n " $existing_cert " && -n " $existing_key " ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 面板已配置 SSL, 安全。 ${ plain } "
2024-11-04 09:33:07 +00:00
before_show_menu
2024-10-31 08:53:47 +00:00
fi
2024-10-31 13:43:30 +00:00
if [ [ -z " $existing_cert " && -z " $existing_key " && ( -z " $existing_listenIP " || " $existing_listenIP " = = "0.0.0.0" ) ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " \n ${ red } 警告:未找到证书和密钥!面板不安全。 ${ plain } "
echo "请获取证书或设置 SSH 端口转发。"
2024-10-31 08:53:47 +00:00
fi
2024-10-31 13:43:30 +00:00
if [ [ -n " $existing_listenIP " && " $existing_listenIP " != "0.0.0.0" && ( -z " $existing_cert " && -z " $existing_key " ) ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e " \n ${ green } 当前 SSH 端口转发配置: ${ plain } "
echo -e "标准 SSH 命令:"
2024-10-31 08:53:47 +00:00
echo -e " ${ yellow } ssh -L 2222: ${ existing_listenIP } : ${ existing_port } root@ ${ server_ip } ${ plain } "
2026-04-02 01:49:12 +00:00
echo -e "\n如果使用 SSH 密钥:"
2024-10-31 08:53:47 +00:00
echo -e " ${ yellow } ssh -i <sshkeypath> -L 2222: ${ existing_listenIP } : ${ existing_port } root@ ${ server_ip } ${ plain } "
2026-04-02 01:49:12 +00:00
echo -e "\n连接后, 通过以下地址访问面板: "
2024-10-31 08:53:47 +00:00
echo -e " ${ yellow } http://localhost:2222 ${ existing_webBasePath } ${ plain } "
fi
2024-10-31 13:43:30 +00:00
2026-04-02 01:49:12 +00:00
echo -e "\n请选择: "
echo -e " ${ green } 1. ${ plain } 设置监听 IP "
echo -e " ${ green } 2. ${ plain } 清除监听 IP "
echo -e " ${ green } 0. ${ plain } 返回主菜单 "
read -rp "请选择:" num
2024-10-31 08:53:47 +00:00
case " $num " in
2024-10-31 13:43:30 +00:00
1)
if [ [ -z " $existing_listenIP " || " $existing_listenIP " = = "0.0.0.0" ] ] ; then
2026-04-02 01:49:12 +00:00
echo -e "\n未配置 listenIP。请选择: "
echo -e "1. 使用默认 IP (127.0.0.1)"
echo -e "2. 设置自定义 IP"
read -rp "请选择( 1 或 2) : " listen_choice
2024-10-31 13:43:30 +00:00
config_listenIP = "127.0.0.1"
2026-04-02 01:49:12 +00:00
[ [ " $listen_choice " = = "2" ] ] && read -rp "输入自定义监听 IP: " config_listenIP
2024-10-31 13:43:30 +00:00
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui setting -listenIP " ${ config_listenIP } " >/dev/null 2>& 1
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 监听 IP 已设置为 ${ config_listenIP } 。 ${ plain } "
echo -e " \n ${ green } SSH 端口转发配置: ${ plain } "
echo -e "标准 SSH 命令:"
2024-10-31 13:43:30 +00:00
echo -e " ${ yellow } ssh -L 2222: ${ config_listenIP } : ${ existing_port } root@ ${ server_ip } ${ plain } "
2026-04-02 01:49:12 +00:00
echo -e "\n如果使用 SSH 密钥:"
2024-10-31 13:43:30 +00:00
echo -e " ${ yellow } ssh -i <sshkeypath> -L 2222: ${ config_listenIP } : ${ existing_port } root@ ${ server_ip } ${ plain } "
2026-04-02 01:49:12 +00:00
echo -e "\n连接后, 通过以下地址访问面板: "
2024-10-31 13:43:30 +00:00
echo -e " ${ yellow } http://localhost:2222 ${ existing_webBasePath } ${ plain } "
2024-10-31 08:53:47 +00:00
restart
2024-10-31 13:43:30 +00:00
else
config_listenIP = " ${ existing_listenIP } "
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 当前监听 IP 已设置为 ${ config_listenIP } 。 ${ plain } "
2024-10-31 13:43:30 +00:00
fi
; ;
2)
2026-01-03 02:57:19 +00:00
${ xui_folder } /x-ui setting -listenIP 0.0.0.0 >/dev/null 2>& 1
2026-04-02 01:49:12 +00:00
echo -e " ${ green } 监听 IP 已清除。 ${ plain } "
2024-10-31 13:43:30 +00:00
restart
; ;
0)
2024-11-04 09:33:07 +00:00
show_menu
2024-10-31 13:43:30 +00:00
; ;
*)
2026-04-02 01:49:12 +00:00
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
2024-11-04 09:33:07 +00:00
SSH_port_forwarding
2024-10-31 13:43:30 +00:00
; ;
2024-10-31 08:53:47 +00:00
esac
}
2023-02-09 19:18:06 +00:00
show_usage( ) {
2025-11-07 18:26:43 +00:00
echo -e " ┌────────────────────────────────────────────────────────────────┐
2026-04-02 01:49:12 +00:00
│ ${ blue } x-ui 管理菜单用法(子命令):${ plain } │
2025-11-07 18:26:43 +00:00
│ │
2026-04-02 01:49:12 +00:00
│ ${ blue } x-ui${ plain } - 管理脚本 │
│ ${ blue } x-ui start${ plain } - 启动 │
│ ${ blue } x-ui stop${ plain } - 停止 │
│ ${ blue } x-ui restart${ plain } - 重启 │
| ${ blue } x-ui restart-xray${ plain } - 重启 Xray │
│ ${ blue } x-ui status${ plain } - 查看当前状态 │
│ ${ blue } x-ui settings${ plain } - 查看当前设置 │
│ ${ blue } x-ui enable${ plain } - 设置开机自启 │
│ ${ blue } x-ui disable${ plain } - 取消开机自启 │
│ ${ blue } x-ui log${ plain } - 查看日志 │
│ ${ blue } x-ui banlog${ plain } - 查看 Fail2ban 封禁日志 │
│ ${ blue } x-ui update${ plain } - 更新 │
│ ${ blue } x-ui update-all-geofiles${ plain } - 更新所有 geo 文件 │
│ ${ blue } x-ui legacy${ plain } - 安装旧版本 │
│ ${ blue } x-ui install${ plain } - 安装 │
│ ${ blue } x-ui uninstall${ plain } - 卸载 │
2025-11-07 18:26:43 +00:00
└────────────────────────────────────────────────────────────────┘"
2023-02-09 19:18:06 +00:00
}
2026-04-03 01:53:20 +00:00
# Read dbType from /etc/x-ui/x-ui.json using the Go binary
2026-04-03 01:31:56 +00:00
read_json_dbtype( ) {
2026-04-03 01:53:20 +00:00
local db_type
db_type = $( ${ xui_folder } /x-ui setting -showDbType 2>/dev/null)
2026-04-03 01:31:56 +00:00
if [ -z " $db_type " ] ; then
echo "sqlite"
else
echo " $db_type "
fi
}
2026-04-22 02:33:24 +00:00
get_database_setting( ) {
local key = " $1 "
local default_value = " $2 "
local json_path = "/etc/x-ui/x-ui.json"
local jq_expr = ""
if [ ! -f " $json_path " ] ; then
echo " $default_value "
return
fi
if command -v jq >/dev/null 2>& 1; then
case " $key " in
".dbType" )
jq_expr = '.databaseConnection.dbType // .other.dbType // .dbType // "sqlite"'
; ;
".dbHost" )
jq_expr = '.databaseConnection.dbHost // .other.dbHost // .dbHost // "127.0.0.1"'
; ;
".dbPort" )
jq_expr = '.databaseConnection.dbPort // .other.dbPort // .dbPort // "3306"'
; ;
".dbUser" )
jq_expr = '.databaseConnection.dbUser // .other.dbUser // .dbUser // ""'
; ;
".dbPassword" )
jq_expr = '.databaseConnection.dbPassword // .other.dbPassword // .dbPassword // ""'
; ;
".dbName" )
jq_expr = '.databaseConnection.dbName // .other.dbName // .dbName // "3xui"'
; ;
*)
jq_expr = " $key // $default_value "
; ;
esac
jq -r " $jq_expr " " $json_path " 2>/dev/null
return
fi
case " $key " in
".dbType" )
grep -o '"dbType"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".dbHost" )
grep -o '"dbHost"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".dbPort" )
grep -o '"dbPort"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".dbUser" )
grep -o '"dbUser"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".dbPassword" )
grep -o '"dbPassword"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".dbName" )
grep -o '"dbName"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
*)
echo " $default_value "
; ;
esac
}
2026-04-03 01:31:56 +00:00
# Show current database status
db_show_status( ) {
local current_type = $( read_json_dbtype)
echo -e " ${ green } 当前数据库类型: ${ current_type } ${ plain } "
if [ " $current_type " = "mariadb" ] ; then
2026-04-22 02:33:24 +00:00
local host = $( get_database_setting '.dbHost' '127.0.0.1' )
local port = $( get_database_setting '.dbPort' '3306' )
local dbname = $( get_database_setting '.dbName' '3xui' )
2026-04-03 01:31:56 +00:00
echo -e " ${ green } MariaDB 主机: ${ host :- 127 .0.0.1 } : ${ port :- 3306 } ${ plain } "
echo -e " ${ green } 数据库名: ${ dbname :- 3xui } ${ plain } "
fi
2026-04-10 07:31:35 +00:00
show_node_status
}
get_node_setting( ) {
local key = " $1 "
local default_value = " $2 "
local json_path = "/etc/x-ui/x-ui.json"
2026-04-15 08:58:49 +00:00
local jq_expr = ""
2026-04-10 07:31:35 +00:00
if [ ! -f " $json_path " ] ; then
echo " $default_value "
return
fi
if command -v jq >/dev/null 2>& 1; then
2026-04-15 08:58:49 +00:00
case " $key " in
".nodeRole" )
jq_expr = '.other.nodeRole // .nodeRole // "master"'
; ;
".nodeId" )
jq_expr = '.other.nodeId // .nodeId // ""'
; ;
".syncInterval" )
jq_expr = '.other.syncInterval // .syncInterval // "30"'
; ;
".trafficFlushInterval" )
jq_expr = '.other.trafficFlushInterval // .trafficFlushInterval // "10"'
; ;
*)
jq_expr = " $key // $default_value "
; ;
esac
jq -r " $jq_expr " " $json_path " 2>/dev/null
2026-04-10 07:31:35 +00:00
return
fi
case " $key " in
".nodeRole" )
grep -o '"nodeRole"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".nodeId" )
grep -o '"nodeId"[[:space:]]*:[[:space:]]*"[^"]*"' " $json_path " 2>/dev/null | tail -1 | sed 's/.*"\([^"]*\)"$/\1/' || echo " $default_value "
; ;
".syncInterval" )
grep -o '"syncInterval"[[:space:]]*:[[:space:]]*[^,}]*' " $json_path " 2>/dev/null | tail -1 | awk -F': ' '{print $2}' | tr -d '[:space:]' || echo " $default_value "
; ;
".trafficFlushInterval" )
grep -o '"trafficFlushInterval"[[:space:]]*:[[:space:]]*[^,}]*' " $json_path " 2>/dev/null | tail -1 | awk -F': ' '{print $2}' | tr -d '[:space:]' || echo " $default_value "
; ;
*)
echo " $default_value "
; ;
esac
}
show_node_status( ) {
local node_role
local node_id
local sync_interval
local flush_interval
node_role = $( get_node_setting '.nodeRole' '"master"' | tr -d '"' )
node_id = $( get_node_setting '.nodeId' '""' | tr -d '"' )
sync_interval = $( get_node_setting '.syncInterval' '30' | tr -d '"' )
flush_interval = $( get_node_setting '.trafficFlushInterval' '10' | tr -d '"' )
echo -e " ${ green } 节点角色: ${ node_role :- master } ${ plain } "
echo -e " ${ green } 节点 ID: ${ node_id :- <empty> } ${ plain } "
echo -e " ${ green } 同步间隔: ${ sync_interval :- 30 } s ${ plain } "
echo -e " ${ green } 流量回刷间隔: ${ flush_interval :- 10 } s ${ plain } "
}
set_node_role( ) {
local node_role = ""
local node_id = ""
read -rp "输入节点角色( master/worker) : " node_role
node_role = $( echo " ${ node_role } " | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]' )
if [ " $node_role " != "master" ] && [ " $node_role " != "worker" ] ; then
echo -e " ${ red } 无效的节点角色 ${ plain } "
return 1
fi
if [ " $node_role " = "worker" ] ; then
read -rp "输入节点 ID: " node_id
node_id = " ${ node_id // / } "
if [ -z " $node_id " ] ; then
echo -e " ${ red } worker 节点必须提供 nodeId ${ plain } "
return 1
fi
if ! ${ xui_folder } /x-ui setting -nodeRole worker -nodeId " $node_id " ; then
echo -e " ${ red } 节点角色更新失败 ${ plain } "
return 1
fi
else
if ! ${ xui_folder } /x-ui setting -nodeRole master; then
echo -e " ${ red } 节点角色更新失败 ${ plain } "
return 1
fi
fi
echo -e " ${ yellow } 节点设置已更新,建议重启面板使其完全生效。 ${ plain } "
}
set_node_id( ) {
local node_id = ""
local current_role = ""
read -rp "输入节点 ID: " node_id
node_id = " ${ node_id // / } "
current_role = $( get_node_setting '.nodeRole' '"master"' | tr -d '"' )
if [ " ${ current_role } " = "worker" ] && [ -z " ${ node_id } " ] ; then
echo -e " ${ red } worker 节点必须提供 nodeId ${ plain } "
return 1
fi
if ! ${ xui_folder } /x-ui setting -nodeId " $node_id " ; then
echo -e " ${ red } 节点 ID 更新失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 节点 ID 已更新,建议重启面板使其完全生效。 ${ plain } "
2026-04-03 01:31:56 +00:00
}
2026-04-15 08:58:49 +00:00
set_sync_interval( ) {
local sync_interval = ""
read -rp "输入同步间隔(秒): " sync_interval
sync_interval = " ${ sync_interval // / } "
if ! [ [ " ${ sync_interval } " = ~ ^[ 1-9] [ 0-9] *$ ] ] ; then
echo -e " ${ red } 同步间隔必须为正整数 ${ plain } "
return 1
fi
if ! ${ xui_folder } /x-ui setting -syncInterval " ${ sync_interval } " ; then
echo -e " ${ red } 同步间隔更新失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 同步间隔已更新,建议重启面板使其完全生效。 ${ plain } "
}
set_traffic_flush_interval( ) {
local flush_interval = ""
read -rp "输入流量回刷间隔(秒): " flush_interval
flush_interval = " ${ flush_interval // / } "
if ! [ [ " ${ flush_interval } " = ~ ^[ 1-9] [ 0-9] *$ ] ] ; then
echo -e " ${ red } 流量回刷间隔必须为正整数 ${ plain } "
return 1
fi
if ! ${ xui_folder } /x-ui setting -trafficFlushInterval " ${ flush_interval } " ; then
echo -e " ${ red } 流量回刷间隔更新失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 流量回刷间隔已更新,建议重启面板使其完全生效。 ${ plain } "
}
2026-04-22 02:33:24 +00:00
set_remote_database_connection( ) {
local current_type current_host current_port current_user current_name current_pass
local db_host db_port db_user db_name db_pass effective_pass
current_type = $( read_json_dbtype)
current_host = $( get_database_setting '.dbHost' '127.0.0.1' )
current_port = $( get_database_setting '.dbPort' '3306' )
current_user = $( get_database_setting '.dbUser' '' )
current_name = $( get_database_setting '.dbName' '3xui' )
current_pass = $( get_database_setting '.dbPassword' '' )
echo -e " ${ green } 当前远程数据库连接配置: ${ plain } "
echo -e " ${ green } Host: ${ current_host :- 127 .0.0.1 } ${ plain } "
echo -e " ${ green } Port: ${ current_port :- 3306 } ${ plain } "
echo -e " ${ green } User: ${ current_user :- <empty> } ${ plain } "
echo -e " ${ green } Database: ${ current_name :- 3xui } ${ plain } "
if [ -n " $current_pass " ] ; then
echo -e " ${ green } Password: <stored> ${ plain } "
else
echo -e " ${ green } Password: <empty> ${ plain } "
fi
if [ " $current_type " != "mariadb" ] ; then
echo -e " ${ yellow } 当前数据库类型为 ${ current_type } 。本操作只更新 MariaDB 连接配置,不会自动切换数据库类型。 ${ plain } "
fi
ensure_mariadb_client_ready || {
echo -e " ${ yellow } 已取消安装 MariaDB 客户端,返回数据库菜单 ${ plain } "
return 1
}
echo -e " ${ green } 请输入新的远程数据库连接信息,直接回车保留当前值。 ${ plain } "
read -rp " 远程 MariaDB host [ ${ current_host :- 127 .0.0.1 } ]: " db_host
read -rp " 远程 MariaDB port [ ${ current_port :- 3306 } ]: " db_port
read -rp " 业务数据库名 [ ${ current_name :- 3xui } ]: " db_name
read -rp " 业务用户名 [ ${ current_user } ]: " db_user
read -rsp "业务密码(留空则保持当前密码): " db_pass
echo
db_host = ${ db_host :- $current_host }
db_port = ${ db_port :- $current_port }
db_name = ${ db_name :- $current_name }
db_user = ${ db_user :- $current_user }
if [ -z " $db_pass " ] ; then
effective_pass = " $current_pass "
else
effective_pass = " $db_pass "
fi
if [ -z " $db_host " ] ; then
echo -e " ${ red } 远程 MariaDB host 不能为空 ${ plain } "
return 1
fi
if ! [ [ " ${ db_port } " = ~ ^[ 0-9] +$ ] ] || ( ( db_port < 1 || db_port > 65535) ) ; then
echo -e " ${ red } 远程 MariaDB 端口无效,请输入 1-65535 之间的数字 ${ plain } "
return 1
fi
if [ -z " $db_user " ] ; then
echo -e " ${ red } 业务用户名不能为空 ${ plain } "
return 1
fi
if [ -z " $db_name " ] ; then
echo -e " ${ red } 业务数据库名不能为空 ${ plain } "
return 1
fi
echo -e " ${ green } 正在验证远程 MariaDB 业务连接... ${ plain } "
if ! test_mariadb_database_connection " $db_host " " $db_port " " $db_name " " $db_user " " $effective_pass " ; then
echo -e " ${ red } 无法使用输入的远程 MariaDB 信息连接到业务数据库,配置未保存 ${ plain } "
return 1
fi
echo -e " ${ green } 正在保存远程 MariaDB 连接配置... ${ plain } "
if [ -n " $db_pass " ] ; then
XUI_DB_PASSWORD = " $db_pass " ${ xui_folder } /x-ui setting -dbHost " $db_host " -dbPort " $db_port " -dbUser " $db_user " -dbName " $db_name " >/dev/null 2>& 1
else
${ xui_folder } /x-ui setting -dbHost " $db_host " -dbPort " $db_port " -dbUser " $db_user " -dbName " $db_name " >/dev/null 2>& 1
fi
if [ $? -ne 0 ] ; then
echo -e " ${ red } 远程 MariaDB 连接配置保存失败 ${ plain } "
return 1
fi
if [ " $current_type " = "mariadb" ] ; then
echo -e " ${ yellow } 远程 MariaDB 连接配置已更新,建议重启面板使其完全生效。 ${ plain } "
else
echo -e " ${ yellow } 远程 MariaDB 连接配置已更新,当前数据库类型仍为 ${ current_type } 。 ${ plain } "
fi
}
2026-04-22 03:20:55 +00:00
validate_tcp_port( ) {
local port = " $1 "
[ [ " $port " = ~ ^[ 0-9] +$ ] ] && ( ( port >= 1 && port <= 65535) )
}
is_local_mariadb_host( ) {
case " $1 " in
"" | "127.0.0.1" | "localhost" | "::1" )
return 0
; ;
*)
return 1
; ;
esac
}
mariadb_server_override_path( ) {
local dir = ""
for dir in /etc/mysql/mariadb.conf.d /etc/mysql/conf.d /etc/my.cnf.d /etc/mariadb.conf.d; do
if [ -d " $dir " ] ; then
echo " ${ dir } /60-x-ui.cnf "
return 0
fi
done
echo "/etc/my.cnf"
}
mariadb_server_config_candidates( ) {
local override_path
override_path = $( mariadb_server_override_path)
local path = ""
for path in \
/etc/mysql/mariadb.conf.d/50-server.cnf \
/etc/mysql/mariadb.cnf \
/etc/mysql/my.cnf \
/etc/mysql/conf.d/mysql.cnf \
/etc/my.cnf.d/mariadb-server.cnf \
/etc/my.cnf.d/server.cnf \
/etc/mariadb.conf.d/50-server.cnf \
/etc/my.cnf; do
if [ -f " $path " ] && [ " $path " != " $override_path " ] ; then
echo " $path "
fi
done
2026-04-23 18:29:02 +00:00
echo " $override_path "
2026-04-22 03:20:55 +00:00
}
ensure_mariadb_override_file( ) {
local path
path = $( mariadb_server_override_path)
mkdir -p " $( dirname " $path " ) "
if [ ! -f " $path " ] ; then
printf "[mysqld]\n" >" $path "
elif ! grep -q '^\[mysqld\]' " $path " 2>/dev/null; then
printf "\n[mysqld]\n" >>" $path "
fi
echo " $path "
}
upsert_mariadb_mysqld_option( ) {
local file = " $1 "
local key = " $2 "
local value = " $3 "
local tmp_file
tmp_file = $( mktemp)
awk -v key = " $key " -v value = " $value " '
BEGIN {
in_section = 0
section_seen = 0
key_written = 0
}
/^\[ .*\] [ [ :space:] ] *$/ {
if ( in_section && !key_written) {
print key " = " value
key_written = 1
}
if ( $0 = = "[mysqld]" ) {
in_section = 1
section_seen = 1
} else {
in_section = 0
}
print
next
}
{
if ( in_section && $0 ~ "^[[:space:]]*[#;]?[[:space:]]*" key "[[:space:]]*=" ) {
if ( !key_written) {
print key " = " value
key_written = 1
}
next
}
print
}
END {
if ( !section_seen) {
print "[mysqld]"
}
if ( !key_written) {
print key " = " value
}
} ' " $file " >" $tmp_file "
cat " $tmp_file " >" $file "
rm -f " $tmp_file "
}
disable_mariadb_skip_networking( ) {
local file = ""
local tmp_file = ""
while IFS = read -r file; do
[ -f " $file " ] || continue
tmp_file = $( mktemp)
awk '
{
if ( $0 ~ /^[ [ :space:] ] *skip-networking( [ [ :space:] ] *= [ [ :space:] ] *.*) ?[ [ :space:] ] *$/) {
print "# x-ui disabled skip-networking to allow managed remote access"
next
}
print
} ' " $file " >" $tmp_file "
cat " $tmp_file " >" $file "
rm -f " $tmp_file "
done < <( mariadb_server_config_candidates)
}
read_mariadb_server_option( ) {
local key = " $1 "
local default_value = " $2 "
local file = ""
local value = ""
local result = ""
while IFS = read -r file; do
[ -f " $file " ] || continue
value = $( awk -v key = " $key " '
BEGIN {
in_section = 0
value = ""
}
/^\[ .*\] [ [ :space:] ] *$/ {
in_section = ( $0 = = "[mysqld]" )
next
}
{
if ( in_section && $0 !~ /^[ [ :space:] ] *[ #;]/ && $0 ~ "^[[:space:]]*" key "[[:space:]]*=") {
sub( /^[ [ :space:] ] *[ ^= ] += [ [ :space:] ] */, "" , $0 )
gsub( /[ [ :space:] ] +$/, "" , $0 )
value = $0
}
}
END {
print value
} ' " $file " )
if [ -n " $value " ] ; then
result = " $value "
fi
done < <( mariadb_server_config_candidates)
echo " ${ result :- $default_value } "
}
restart_mariadb_service( ) {
local svc_name = ""
2026-04-23 07:05:05 +00:00
local output = ""
2026-04-22 03:20:55 +00:00
if command -v systemctl >/dev/null 2>& 1; then
if systemctl list-unit-files 2>/dev/null | grep -q "^mariadb.service" ; then
svc_name = "mariadb"
elif systemctl list-unit-files 2>/dev/null | grep -q "^mysql.service" ; then
svc_name = "mysql"
fi
fi
if [ -n " $svc_name " ] ; then
2026-04-23 07:05:05 +00:00
output = $( systemctl restart " $svc_name " 2>& 1) || {
echo -e " ${ red } systemctl restart $svc_name 失败: ${ plain } " >& 2
echo " $output " >& 2
echo -e " ${ yellow } systemctl status $svc_name : ${ plain } " >& 2
systemctl status " $svc_name " --no-pager -l 2>& 1 | head -20 >& 2
return 1
}
return 0
2026-04-22 03:20:55 +00:00
fi
if [ [ $release = = "alpine" ] ] ; then
2026-04-23 07:05:05 +00:00
output = $( rc-service mariadb restart 2>& 1) || {
echo -e " ${ red } rc-service mariadb restart 失败: ${ plain } " >& 2
echo " $output " >& 2
return 1
}
2026-04-22 03:20:55 +00:00
return $?
fi
start_mariadb_service
}
configure_local_mariadb_server_network( ) {
local db_port = " $1 "
local bind_address = " $2 "
local override_file = ""
if ! validate_tcp_port " $db_port " ; then
echo -e " ${ red } MariaDB 端口无效,请输入 1-65535 之间的数字 ${ plain } "
return 1
fi
override_file = $( ensure_mariadb_override_file) || return 1
upsert_mariadb_mysqld_option " $override_file " "port" " $db_port "
upsert_mariadb_mysqld_option " $override_file " "bind-address" " $bind_address "
disable_mariadb_skip_networking
if ! restart_mariadb_service; then
echo -e " ${ red } 重启 MariaDB 失败,请检查配置文件 ${ plain } "
return 1
fi
return 0
}
2026-04-15 08:58:49 +00:00
has_mariadb_cli( ) {
command -v mariadb >/dev/null 2>& 1 || command -v mysql >/dev/null 2>& 1
}
mariadb_cli_bin( ) {
if command -v mariadb >/dev/null 2>& 1; then
command -v mariadb
2026-04-03 02:28:25 +00:00
return 0
fi
2026-04-15 08:58:49 +00:00
if command -v mysql >/dev/null 2>& 1; then
command -v mysql
2026-04-03 02:28:25 +00:00
return 0
fi
return 1
}
2026-04-15 08:58:49 +00:00
has_local_mariadb_service( ) {
if command -v systemctl >/dev/null 2>& 1; then
2026-04-23 07:20:34 +00:00
# Also verify the server package is actually installed (not just a stale service file)
if systemctl list-unit-files 2>/dev/null | grep -qE '^(mariadb|mysql)\.service$' ; then
case " ${ release } " in
ubuntu | debian | armbian | linuxmint)
dpkg -s mariadb-server >/dev/null 2>& 1 && return 0
# Package missing but service file exists — stale state
return 1
; ;
centos | rhel | almalinux | rocky | ol | alinux | amzn | fedora)
rpm -q mariadb-server >/dev/null 2>& 1 && return 0
return 1
; ;
*)
return 0
; ;
esac
fi
2026-04-15 08:58:49 +00:00
fi
[ [ -f /etc/init.d/mariadb ] ]
}
check_mariadb_installed( ) {
has_mariadb_cli || has_local_mariadb_service
}
install_mariadb_client( ) {
echo -e " ${ green } 正在安装 MariaDB 客户端... ${ plain } "
case " ${ release } " in
ubuntu | debian | linuxmint)
apt-get update -y && apt-get install -y mariadb-client
; ;
centos | rhel | almalinux | rocky | ol | alinux | amzn)
if command -v dnf >/dev/null 2>& 1; then
dnf install -y mariadb
else
yum install -y mariadb
fi
; ;
fedora)
dnf install -y mariadb
; ;
arch | manjaro)
pacman -Sy --noconfirm mariadb-clients >/dev/null 2>& 1 || pacman -Sy --noconfirm mariadb
; ;
opensuse* | sles)
zypper install -y mariadb-client
; ;
alpine)
apk add mariadb-client
; ;
*)
echo -e " ${ red } 不支持的发行版: ${ release } ,请手动安装 MariaDB 客户端 ${ plain } "
return 1
; ;
esac
}
install_local_mariadb_server( ) {
echo -e " ${ green } 正在安装本地 MariaDB... ${ plain } "
2026-04-03 02:28:25 +00:00
case " ${ release } " in
ubuntu | debian | linuxmint)
apt-get update -y && apt-get install -y mariadb-server mariadb-client
; ;
centos | rhel | almalinux | rocky | ol | alinux | amzn)
if command -v dnf >/dev/null 2>& 1; then
dnf install -y mariadb-server mariadb
else
yum install -y mariadb-server mariadb
fi
; ;
fedora)
dnf install -y mariadb-server mariadb
; ;
arch | manjaro)
pacman -Sy --noconfirm mariadb
2026-04-15 08:58:49 +00:00
mariadb-install-db --user= mysql --basedir= /usr --datadir= /var/lib/mysql >/dev/null 2>& 1 || true
2026-04-03 02:28:25 +00:00
; ;
opensuse* | sles)
zypper install -y mariadb-server mariadb-client
; ;
alpine)
apk add mariadb mariadb-client
2026-04-15 08:58:49 +00:00
mariadb-install-db --user= mysql --basedir= /usr --datadir= /var/lib/mysql >/dev/null 2>& 1 || true
2026-04-03 02:28:25 +00:00
; ;
*)
echo -e " ${ red } 不支持的发行版: ${ release } ,请手动安装 MariaDB ${ plain } "
return 1
; ;
esac
local ret = $?
if [ $ret -eq 0 ] ; then
echo -e " ${ green } MariaDB 安装成功 ${ plain } "
else
echo -e " ${ red } MariaDB 安装失败 ${ plain } "
fi
return $ret
}
start_mariadb_service( ) {
local svc_name = ""
2026-04-15 08:58:49 +00:00
if command -v systemctl >/dev/null 2>& 1; then
if systemctl list-unit-files 2>/dev/null | grep -q "^mariadb.service" ; then
svc_name = "mariadb"
elif systemctl list-unit-files 2>/dev/null | grep -q "^mysql.service" ; then
svc_name = "mysql"
fi
2026-04-03 02:28:25 +00:00
fi
if [ -n " $svc_name " ] ; then
2026-04-23 07:05:05 +00:00
systemctl start " $svc_name " 2>/dev/null || true
systemctl enable " $svc_name " 2>/dev/null || true
2026-04-15 08:58:49 +00:00
return 0
fi
if [ [ $release = = "alpine" ] ] ; then
2026-04-03 02:28:25 +00:00
rc-service mariadb start 2>/dev/null
rc-update add mariadb 2>/dev/null
2026-04-15 08:58:49 +00:00
return $?
2026-04-03 02:28:25 +00:00
fi
2026-04-15 08:58:49 +00:00
return 1
2026-04-03 02:28:25 +00:00
}
2026-04-15 08:58:49 +00:00
ensure_mariadb_client_ready( ) {
if has_mariadb_cli; then
2026-04-03 02:28:25 +00:00
return 0
fi
2026-04-15 08:58:49 +00:00
echo -e " ${ yellow } 未检测到 MariaDB 客户端 ${ plain } "
confirm "是否安装 MariaDB 客户端?" "y" || return 1
install_mariadb_client || return 1
has_mariadb_cli
}
ensure_local_mariadb_ready( ) {
if ! has_local_mariadb_service; then
echo -e " ${ yellow } 未检测到本地 MariaDB 服务 ${ plain } "
confirm "是否安装本地 MariaDB? " "y" || return 1
install_local_mariadb_server || return 1
2026-04-22 02:10:04 +00:00
LOCAL_MARIADB_JUST_INSTALLED = "1"
2026-04-15 08:58:49 +00:00
fi
ensure_mariadb_client_ready || return 1
start_mariadb_service || true
return 0
}
test_mariadb_server_connection( ) {
local host = " $1 " port = " $2 " user = " $3 " pass = " $4 "
local bin
2026-04-22 02:10:04 +00:00
local -a cmd
2026-04-23 18:29:02 +00:00
local err_output
2026-04-15 08:58:49 +00:00
bin = $( mariadb_cli_bin) || return 1
2026-04-22 02:10:04 +00:00
cmd = ( " $bin " -h " $host " -P " $port " -u " $user " )
if [ [ -n " $pass " ] ] ; then
cmd += ( " -p $pass " )
fi
cmd += ( -e "SELECT 1;" )
2026-04-23 18:29:02 +00:00
err_output = $( " ${ cmd [@] } " 2>& 1)
local rc = $?
if [ [ $rc -ne 0 ] ] ; then
echo -e " ${ red } MariaDB 连接失败: ${ err_output } ${ plain } " >& 2
return 1
fi
2026-04-15 08:58:49 +00:00
}
test_mariadb_database_connection( ) {
local host = " $1 " port = " $2 " dbname = " $3 " user = " $4 " pass = " $5 "
local bin
2026-04-22 02:10:04 +00:00
local -a cmd
2026-04-23 18:29:02 +00:00
local err_output
2026-04-15 08:58:49 +00:00
bin = $( mariadb_cli_bin) || return 1
2026-04-22 02:10:04 +00:00
cmd = ( " $bin " -h " $host " -P " $port " -u " $user " -D " $dbname " )
if [ [ -n " $pass " ] ] ; then
cmd += ( " -p $pass " )
fi
cmd += ( -e "SELECT 1;" )
2026-04-23 18:29:02 +00:00
err_output = $( " ${ cmd [@] } " 2>& 1)
local rc = $?
if [ [ $rc -ne 0 ] ] ; then
echo -e " ${ red } MariaDB 连接失败: ${ err_output } ${ plain } " >& 2
return 1
fi
2026-04-15 08:58:49 +00:00
}
is_safe_mariadb_identifier( ) {
[ [ " $1 " = ~ ^[ A-Za-z0-9_.-] +$ ] ]
}
escape_sql_string( ) {
printf "%s" " $1 " | sed "s/'/''/g"
}
LOCAL_MARIADB_ADMIN_MODE = ""
LOCAL_MARIADB_ADMIN_USER = ""
LOCAL_MARIADB_ADMIN_PASS = ""
LOCAL_MARIADB_ADMIN_PORT = "3306"
2026-04-22 02:10:04 +00:00
LOCAL_MARIADB_JUST_INSTALLED = "0"
2026-04-15 08:58:49 +00:00
try_local_mariadb_socket_admin( ) {
local bin
bin = $( mariadb_cli_bin) || return 1
" $bin " -e "SELECT 1;" >/dev/null 2>& 1 || " $bin " -uroot -e "SELECT 1;" >/dev/null 2>& 1
}
ensure_local_mariadb_admin_access( ) {
local port = " ${ 1 :- 3306 } "
2026-04-22 02:10:04 +00:00
local i
2026-04-15 08:58:49 +00:00
LOCAL_MARIADB_ADMIN_PORT = " $port "
2026-04-22 02:10:04 +00:00
for ( ( i = 0; i < 10; i++) ) ; do
if try_local_mariadb_socket_admin; then
LOCAL_MARIADB_ADMIN_MODE = "socket"
return 0
fi
sleep 1
done
if test_mariadb_server_connection "127.0.0.1" " $port " "root" "" ; then
LOCAL_MARIADB_ADMIN_MODE = "password"
LOCAL_MARIADB_ADMIN_USER = "root"
LOCAL_MARIADB_ADMIN_PASS = ""
if [ [ " $LOCAL_MARIADB_JUST_INSTALLED " = = "1" ] ] ; then
echo -e " ${ green } 检测到新安装 MariaDB, 已自动使用 root 免密权限初始化数据库。 ${ plain } "
fi
2026-04-03 02:28:25 +00:00
return 0
2026-04-15 08:58:49 +00:00
fi
local admin_user admin_pass
echo -e " ${ yellow } 无法通过 root socket 直接连接本地 MariaDB, 请输入管理员账号信息。 ${ plain } "
read -rp "MariaDB 管理员用户名 [root]: " admin_user
admin_user = " ${ admin_user :- root } "
2026-04-22 02:10:04 +00:00
read -rsp "MariaDB 管理员密码(可留空): " admin_pass
2026-04-15 08:58:49 +00:00
echo
if ! test_mariadb_server_connection "127.0.0.1" " $port " " $admin_user " " $admin_pass " ; then
echo -e " ${ red } 管理员账号连接失败 ${ plain } "
return 1
fi
LOCAL_MARIADB_ADMIN_MODE = "password"
LOCAL_MARIADB_ADMIN_USER = " $admin_user "
LOCAL_MARIADB_ADMIN_PASS = " $admin_pass "
}
run_local_mariadb_admin_sql( ) {
local sql = " $1 "
local bin
2026-04-22 02:10:04 +00:00
local -a cmd
2026-04-15 08:58:49 +00:00
bin = $( mariadb_cli_bin) || return 1
case " $LOCAL_MARIADB_ADMIN_MODE " in
socket)
" $bin " -e " $sql " >/dev/null 2>& 1 || " $bin " -uroot -e " $sql " >/dev/null 2>& 1
; ;
password)
2026-04-22 02:10:04 +00:00
cmd = ( " $bin " -h "127.0.0.1" -P " $LOCAL_MARIADB_ADMIN_PORT " -u " $LOCAL_MARIADB_ADMIN_USER " )
if [ [ -n " $LOCAL_MARIADB_ADMIN_PASS " ] ] ; then
cmd += ( " -p $LOCAL_MARIADB_ADMIN_PASS " )
fi
cmd += ( -e " $sql " )
" ${ cmd [@] } " >/dev/null 2>& 1
2026-04-15 08:58:49 +00:00
; ;
*)
return 1
; ;
esac
}
2026-04-22 03:20:55 +00:00
run_local_mariadb_admin_query( ) {
local sql = " $1 "
local bin
local -a cmd
bin = $( mariadb_cli_bin) || return 1
case " $LOCAL_MARIADB_ADMIN_MODE " in
socket)
" $bin " -N -B -e " $sql " 2>/dev/null || " $bin " -uroot -N -B -e " $sql " 2>/dev/null
; ;
password)
cmd = ( " $bin " -h "127.0.0.1" -P " $LOCAL_MARIADB_ADMIN_PORT " -u " $LOCAL_MARIADB_ADMIN_USER " )
if [ [ -n " $LOCAL_MARIADB_ADMIN_PASS " ] ] ; then
cmd += ( " -p $LOCAL_MARIADB_ADMIN_PASS " )
fi
cmd += ( -N -B -e " $sql " )
" ${ cmd [@] } " 2>/dev/null
; ;
*)
return 1
; ;
esac
}
2026-04-15 08:58:49 +00:00
ensure_mariadb_database_and_user( ) {
local dbname = " $1 " dbuser = " $2 " dbpass = " $3 "
local escaped_pass
local sql = ""
local account_host = ""
if ! is_safe_mariadb_identifier " $dbname " ; then
echo -e " ${ red } 业务数据库名仅支持字母、数字、点、下划线和连字符 ${ plain } "
return 1
fi
if ! is_safe_mariadb_identifier " $dbuser " ; then
echo -e " ${ red } 业务用户名仅支持字母、数字、点、下划线和连字符 ${ plain } "
2026-04-03 02:28:25 +00:00
return 1
fi
2026-04-15 08:58:49 +00:00
escaped_pass = $( escape_sql_string " $dbpass " )
sql = " CREATE DATABASE IF NOT EXISTS \` ${ dbname } \` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; "
for account_host in "localhost" "127.0.0.1" "::1" ; do
sql = " ${ sql } CREATE USER IF NOT EXISTS ' ${ dbuser } '@' ${ account_host } ' IDENTIFIED BY ' ${ escaped_pass } '; "
sql = " ${ sql } ALTER USER ' ${ dbuser } '@' ${ account_host } ' IDENTIFIED BY ' ${ escaped_pass } '; "
sql = " ${ sql } GRANT ALL PRIVILEGES ON \` ${ dbname } \`.* TO ' ${ dbuser } '@' ${ account_host } '; "
done
sql = " ${ sql } FLUSH PRIVILEGES; "
echo -e " ${ green } 正在确保本地 MariaDB 的业务库和业务账号存在... ${ plain } "
run_local_mariadb_admin_sql " $sql "
2026-04-03 02:28:25 +00:00
}
2026-04-22 03:20:55 +00:00
CURRENT_LOCAL_DB_PORT = ""
CURRENT_LOCAL_DB_USER = ""
CURRENT_LOCAL_DB_PASS = ""
CURRENT_LOCAL_DB_NAME = ""
load_local_mariadb_business_context( ) {
local db_type = ""
local db_host = ""
db_type = $( read_json_dbtype)
if [ " $db_type " != "mariadb" ] ; then
echo -e " ${ red } 当前数据库类型不是 MariaDB ${ plain } "
return 1
fi
db_host = $( get_database_setting '.dbHost' '127.0.0.1' )
if ! is_local_mariadb_host " $db_host " ; then
echo -e " ${ red } 当前面板使用的是远程 MariaDB, 无法管理本机 MariaDB 远程访问 ${ plain } "
return 1
fi
CURRENT_LOCAL_DB_PORT = $( get_database_setting '.dbPort' '3306' )
CURRENT_LOCAL_DB_USER = $( get_database_setting '.dbUser' '' )
CURRENT_LOCAL_DB_PASS = $( get_database_setting '.dbPassword' '' )
CURRENT_LOCAL_DB_NAME = $( get_database_setting '.dbName' '3xui' )
if ! validate_tcp_port " $CURRENT_LOCAL_DB_PORT " ; then
echo -e " ${ red } 当前配置中的本地 MariaDB 端口无效 ${ plain } "
return 1
fi
if [ -z " $CURRENT_LOCAL_DB_USER " ] ; then
echo -e " ${ red } 当前配置缺少 MariaDB 业务用户名 ${ plain } "
return 1
fi
if [ -z " $CURRENT_LOCAL_DB_NAME " ] ; then
echo -e " ${ red } 当前配置缺少 MariaDB 业务数据库名 ${ plain } "
return 1
fi
if ! is_safe_mariadb_identifier " $CURRENT_LOCAL_DB_USER " ; then
echo -e " ${ red } 当前配置中的 MariaDB 业务用户名不支持自动远程授权 ${ plain } "
return 1
fi
if ! is_safe_mariadb_identifier " $CURRENT_LOCAL_DB_NAME " ; then
echo -e " ${ red } 当前配置中的 MariaDB 业务数据库名不支持自动远程授权 ${ plain } "
return 1
fi
ensure_local_mariadb_ready || return 1
ensure_local_mariadb_admin_access " $CURRENT_LOCAL_DB_PORT " || return 1
return 0
}
list_remote_mariadb_hosts( ) {
local escaped_user = ""
escaped_user = $( escape_sql_string " $CURRENT_LOCAL_DB_USER " )
run_local_mariadb_admin_query " SELECT Host FROM mysql.user WHERE User = ' ${ escaped_user } ' AND Host NOT IN ('localhost', '127.0.0.1', '::1') ORDER BY Host; "
}
show_mariadb_remote_access_status( ) {
local bind_address = ""
local server_port = ""
local hosts = ""
load_local_mariadb_business_context || return 1
bind_address = $( read_mariadb_server_option "bind-address" "127.0.0.1" )
server_port = $( read_mariadb_server_option "port" " $CURRENT_LOCAL_DB_PORT " )
hosts = $( list_remote_mariadb_hosts | sed '/^$/d' )
echo -e " ${ green } MariaDB 服务端口: ${ server_port } ${ plain } "
echo -e " ${ green } MariaDB 监听地址: ${ bind_address } ${ plain } "
if [ " $bind_address " = "127.0.0.1" ] ; then
echo -e " ${ yellow } 远程访问状态: 已关闭 ${ plain } "
else
echo -e " ${ yellow } 远程访问状态: 已开启 ${ plain } "
fi
if [ -n " $hosts " ] ; then
echo -e " ${ green } 允许的远程 IP: ${ plain } "
printf '%s\n' " $hosts "
else
echo -e " ${ yellow } 允许的远程 IP: <empty> ${ plain } "
fi
}
add_mariadb_remote_ip_grant( ) {
local remote_ip = " $1 "
local escaped_user = ""
local escaped_pass = ""
local escaped_ip = ""
if ! is_ip " $remote_ip " ; then
echo -e " ${ red } 仅支持输入单个 IP 地址 ${ plain } "
return 1
fi
load_local_mariadb_business_context || return 1
escaped_user = $( escape_sql_string " $CURRENT_LOCAL_DB_USER " )
escaped_pass = $( escape_sql_string " $CURRENT_LOCAL_DB_PASS " )
escaped_ip = $( escape_sql_string " $remote_ip " )
run_local_mariadb_admin_sql " CREATE USER IF NOT EXISTS ' ${ escaped_user } '@' ${ escaped_ip } ' IDENTIFIED BY ' ${ escaped_pass } '; ALTER USER ' ${ escaped_user } '@' ${ escaped_ip } ' IDENTIFIED BY ' ${ escaped_pass } '; GRANT ALL PRIVILEGES ON \` ${ CURRENT_LOCAL_DB_NAME } \`.* TO ' ${ escaped_user } '@' ${ escaped_ip } '; FLUSH PRIVILEGES; "
}
remove_mariadb_remote_ip_grant( ) {
local remote_ip = " $1 "
local escaped_user = ""
local escaped_ip = ""
if ! is_ip " $remote_ip " ; then
echo -e " ${ red } 仅支持输入单个 IP 地址 ${ plain } "
return 1
fi
load_local_mariadb_business_context || return 1
escaped_user = $( escape_sql_string " $CURRENT_LOCAL_DB_USER " )
escaped_ip = $( escape_sql_string " $remote_ip " )
run_local_mariadb_admin_sql " DROP USER IF EXISTS ' ${ escaped_user } '@' ${ escaped_ip } '; FLUSH PRIVILEGES; "
}
set_local_mariadb_port( ) {
local current_port = ""
local current_bind = ""
local new_port = ""
load_local_mariadb_business_context || return 1
current_port = $( read_mariadb_server_option "port" " $CURRENT_LOCAL_DB_PORT " )
current_bind = $( read_mariadb_server_option "bind-address" "127.0.0.1" )
read -rp " 本地 MariaDB port [ ${ current_port :- 3306 } ]: " new_port
new_port = " ${ new_port // / } "
new_port = " ${ new_port :- $current_port } "
if ! validate_tcp_port " $new_port " ; then
echo -e " ${ red } 本地 MariaDB 端口无效,请输入 1-65535 之间的数字 ${ plain } "
return 1
fi
if ! configure_local_mariadb_server_network " $new_port " " $current_bind " ; then
return 1
fi
if ! ${ xui_folder } /x-ui setting -dbPort " $new_port " >/dev/null 2>& 1; then
echo -e " ${ red } 写入面板数据库端口配置失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 本地 MariaDB 端口已更新为 ${ new_port } ,建议重启面板使其完全生效。 ${ plain } "
}
enable_mariadb_remote_access( ) {
local hosts = ""
load_local_mariadb_business_context || return 1
hosts = $( list_remote_mariadb_hosts | sed '/^$/d' )
if [ -z " $hosts " ] ; then
echo -e " ${ red } 请先添加至少一个允许的远程 IP ${ plain } "
return 1
fi
if ! configure_local_mariadb_server_network " $CURRENT_LOCAL_DB_PORT " "0.0.0.0" ; then
return 1
fi
echo -e " ${ yellow } MariaDB 远程访问已开启,仅已授权的远程 IP 可连接。 ${ plain } "
}
disable_mariadb_remote_access( ) {
local hosts = ""
local host = ""
load_local_mariadb_business_context || return 1
hosts = $( list_remote_mariadb_hosts | sed '/^$/d' )
if [ -n " $hosts " ] ; then
while IFS = read -r host; do
[ -n " $host " ] || continue
remove_mariadb_remote_ip_grant " $host " >/dev/null 2>& 1 || true
done <<< " $hosts "
fi
if ! configure_local_mariadb_server_network " $CURRENT_LOCAL_DB_PORT " "127.0.0.1" ; then
return 1
fi
echo -e " ${ yellow } MariaDB 远程访问已关闭,并已清理远程 IP 授权。 ${ plain } "
}
show_mariadb_remote_ips( ) {
local hosts = ""
load_local_mariadb_business_context || return 1
hosts = $( list_remote_mariadb_hosts | sed '/^$/d' )
if [ -z " $hosts " ] ; then
echo -e " ${ yellow } 当前没有已授权的远程 IP ${ plain } "
return 0
fi
echo -e " ${ green } 当前已授权的远程 IP: ${ plain } "
printf '%s\n' " $hosts "
}
add_mariadb_remote_ip( ) {
local remote_ip = ""
read -rp "输入允许连接 MariaDB 的远程 IP: " remote_ip
remote_ip = " ${ remote_ip // / } "
if ! add_mariadb_remote_ip_grant " $remote_ip " ; then
echo -e " ${ red } 添加 MariaDB 允许 IP 失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 已添加 MariaDB 允许 IP: ${ remote_ip } ${ plain } "
}
remove_mariadb_remote_ip( ) {
local remote_ip = ""
read -rp "输入要移除的远程 IP: " remote_ip
remote_ip = " ${ remote_ip // / } "
if ! remove_mariadb_remote_ip_grant " $remote_ip " ; then
echo -e " ${ red } 移除 MariaDB 允许 IP 失败 ${ plain } "
return 1
fi
echo -e " ${ yellow } 已移除 MariaDB 允许 IP: ${ remote_ip } ${ plain } "
}
2026-04-03 01:31:56 +00:00
# Switch to MariaDB
db_switch_to_mariadb( ) {
local current_type = $( read_json_dbtype)
if [ " $current_type " = "mariadb" ] ; then
echo -e " ${ yellow } 当前已经是 MariaDB ${ plain } "
db_menu
return
fi
2026-04-15 08:58:49 +00:00
local mariadb_mode_choice mariadb_mode
local db_host db_port db_user db_pass db_name
read -rp "MariaDB 部署位置 [1=本地 MariaDB, 2=远程 MariaDB, 默认 1]: " mariadb_mode_choice
case " ${ mariadb_mode_choice :- 1 } " in
2)
mariadb_mode = "remote"
; ;
*)
mariadb_mode = "local"
; ;
esac
if [ [ " ${ mariadb_mode } " = = "remote" ] ] ; then
ensure_mariadb_client_ready || {
echo -e " ${ yellow } 已取消安装 MariaDB 客户端,返回数据库菜单 ${ plain } "
2026-04-03 02:28:25 +00:00
db_menu
return
2026-04-15 08:58:49 +00:00
}
echo -e " ${ green } 请输入远程 MariaDB 业务连接信息(直接回车使用默认值): ${ plain } "
read -rp "远程 MariaDB host [127.0.0.1]: " db_host
read -rp "远程 MariaDB port [3306]: " db_port
read -rp "业务数据库名 [3xui]: " db_name
read -rp "业务用户名: " db_user
read -rsp "业务密码: " db_pass
echo
db_host = ${ db_host :- 127 .0.0.1 }
db_port = ${ db_port :- 3306 }
db_name = ${ db_name :- 3xui }
2026-04-15 09:48:18 +00:00
while ! [ [ " ${ db_port } " = ~ ^[ 0-9] +$ ] ] || ( ( db_port < 1 || db_port > 65535) ) ; do
echo -e " ${ red } 远程 MariaDB 端口无效,请输入 1-65535 之间的数字 ${ plain } "
read -rp "远程 MariaDB port [3306]: " db_port
db_port = ${ db_port :- 3306 }
done
2026-04-15 08:58:49 +00:00
if [ [ -z " $db_user " || -z " $db_pass " ] ] ; then
echo -e " ${ red } 业务用户名和业务密码不能为空 ${ plain } "
2026-04-03 02:28:25 +00:00
db_menu
return
fi
2026-04-15 08:58:49 +00:00
echo -e " ${ green } 正在验证远程 MariaDB 业务连接... ${ plain } "
if ! test_mariadb_database_connection " $db_host " " $db_port " " $db_name " " $db_user " " $db_pass " ; then
echo -e " ${ red } 无法使用输入的远程 MariaDB 信息连接到业务数据库 ${ plain } "
2026-04-03 02:28:25 +00:00
db_menu
return
fi
else
2026-04-15 08:58:49 +00:00
db_host = "127.0.0.1"
2026-04-22 03:20:55 +00:00
read -rp "本地 MariaDB port [3306]: " db_port
2026-04-15 08:58:49 +00:00
read -rp "业务数据库名 [3xui]: " db_name
read -rp "业务用户名: " db_user
read -rsp "业务密码: " db_pass
echo
2026-04-22 03:20:55 +00:00
db_port = ${ db_port :- 3306 }
2026-04-15 08:58:49 +00:00
db_name = ${ db_name :- 3xui }
2026-04-22 03:20:55 +00:00
if ! validate_tcp_port " $db_port " ; then
echo -e " ${ red } 本地 MariaDB 端口无效,请输入 1-65535 之间的数字 ${ plain } "
db_menu
return
fi
2026-04-15 08:58:49 +00:00
if [ [ -z " $db_user " || -z " $db_pass " ] ] ; then
echo -e " ${ red } 业务用户名和业务密码不能为空 ${ plain } "
db_menu
return
fi
2026-04-03 01:31:56 +00:00
2026-04-15 08:58:49 +00:00
ensure_local_mariadb_ready || {
echo -e " ${ yellow } 本地 MariaDB 未准备完成,返回数据库菜单 ${ plain } "
db_menu
return
}
2026-04-22 03:20:55 +00:00
configure_local_mariadb_server_network " $db_port " "127.0.0.1" || {
db_menu
return
}
2026-04-15 08:58:49 +00:00
ensure_local_mariadb_admin_access " $db_port " || {
db_menu
return
}
ensure_mariadb_database_and_user " $db_name " " $db_user " " $db_pass " || {
db_menu
return
}
2026-04-03 02:28:25 +00:00
2026-04-15 08:58:49 +00:00
echo -e " ${ green } 正在验证本地 MariaDB 业务连接... ${ plain } "
if ! test_mariadb_database_connection " $db_host " " $db_port " " $db_name " " $db_user " " $db_pass " ; then
echo -e " ${ red } 无法使用创建后的本地 MariaDB 业务账号连接数据库 ${ plain } "
db_menu
return
fi
2026-04-03 02:28:25 +00:00
fi
2026-04-03 01:31:56 +00:00
echo -e " ${ green } 正在配置 MariaDB 连接... ${ plain } "
2026-04-03 01:53:20 +00:00
XUI_DB_PASSWORD = " $db_pass " ${ xui_folder } /x-ui setting -dbHost " $db_host " -dbPort " $db_port " -dbUser " $db_user " -dbName " $db_name " >/dev/null 2>& 1
2026-04-03 01:31:56 +00:00
echo -e " ${ green } 正在迁移数据从 SQLite 到 MariaDB... ${ plain } "
2026-04-03 01:53:20 +00:00
${ xui_folder } /x-ui migrate-db -direction sqlite-to-mariadb
2026-04-03 01:31:56 +00:00
if [ $? -eq 0 ] ; then
echo -e " ${ green } 数据库切换成功,正在重启面板... ${ plain } "
2026-04-03 01:53:20 +00:00
${ xui_folder } /x-ui setting -dbType mariadb >/dev/null 2>& 1
2026-04-03 01:31:56 +00:00
restart
else
2026-04-03 01:53:20 +00:00
echo -e " ${ red } 数据迁移失败,保持 SQLite 不变 ${ plain } "
2026-04-03 01:31:56 +00:00
restart
fi
}
# Switch to SQLite
db_switch_to_sqlite( ) {
local current_type = $( read_json_dbtype)
if [ " $current_type " = "sqlite" ] ; then
echo -e " ${ yellow } 当前已经是 SQLite ${ plain } "
db_menu
return
fi
echo -e " ${ green } 正在迁移数据从 MariaDB 到 SQLite... ${ plain } "
2026-04-03 01:53:20 +00:00
${ xui_folder } /x-ui migrate-db -direction mariadb-to-sqlite
2026-04-03 01:31:56 +00:00
if [ $? -eq 0 ] ; then
echo -e " ${ green } 数据库切换成功,正在重启面板... ${ plain } "
2026-04-03 01:53:20 +00:00
${ xui_folder } /x-ui setting -dbType sqlite >/dev/null 2>& 1
2026-04-03 01:31:56 +00:00
restart
else
2026-04-03 01:53:20 +00:00
echo -e " ${ red } 数据迁移失败,保持 MariaDB 不变 ${ plain } "
db_menu
2026-04-03 01:31:56 +00:00
fi
}
# Database management menu
db_menu( ) {
local current_type = $( read_json_dbtype)
echo -e "
╔────────────────────────────────────────────────╗
│ ${ green } 数据库管理${ plain } │
│────────────────────────────────────────────────│
│ ${ green } 0.${ plain } 返回主菜单 │
│ ${ green } 1.${ plain } 查看当前数据库类型(当前: ${ current_type } ) │
│ ${ green } 2.${ plain } 切换到 MariaDB │
│ ${ green } 3.${ plain } 切换到 SQLite │
2026-04-10 07:31:35 +00:00
│ ${ green } 4.${ plain } 查看当前节点设置 │
│ ${ green } 5.${ plain } 设置节点角色 │
│ ${ green } 6.${ plain } 设置节点 ID │
2026-04-15 08:58:49 +00:00
│ ${ green } 7.${ plain } 设置同步间隔 │
│ ${ green } 8.${ plain } 设置流量回刷间隔 │
2026-04-22 02:33:24 +00:00
│ ${ green } 9.${ plain } 设置远程数据库连接 │
2026-04-22 03:20:55 +00:00
│ ${ green } 10.${ plain } 设置本地 MariaDB 端口 │
│ ${ green } 11.${ plain } 查看 MariaDB 远程访问状态 │
│ ${ green } 12.${ plain } 开启 MariaDB 远程访问 │
│ ${ green } 13.${ plain } 关闭 MariaDB 远程访问 │
│ ${ green } 14.${ plain } 查看 MariaDB 允许 IP │
│ ${ green } 15.${ plain } 添加 MariaDB 允许 IP │
│ ${ green } 16.${ plain } 移除 MariaDB 允许 IP │
2026-04-03 01:31:56 +00:00
╚════════════════════════════════════════════════╝
"
2026-04-22 03:20:55 +00:00
read -rp "请输入选择 [0-16]: " num
2026-04-03 01:31:56 +00:00
case " ${ num } " in
0)
show_menu
; ;
1)
db_show_status
db_menu
; ;
2)
db_switch_to_mariadb
; ;
3)
db_switch_to_sqlite
; ;
2026-04-10 07:31:35 +00:00
4)
show_node_status
db_menu
; ;
5)
set_node_role
db_menu
; ;
6)
set_node_id
db_menu
; ;
2026-04-15 08:58:49 +00:00
7)
set_sync_interval
db_menu
; ;
8)
set_traffic_flush_interval
db_menu
; ;
2026-04-22 02:33:24 +00:00
9)
set_remote_database_connection
db_menu
; ;
2026-04-22 03:20:55 +00:00
10)
set_local_mariadb_port
db_menu
; ;
11)
show_mariadb_remote_access_status
db_menu
; ;
12)
enable_mariadb_remote_access
db_menu
; ;
13)
disable_mariadb_remote_access
db_menu
; ;
14)
show_mariadb_remote_ips
db_menu
; ;
15)
add_mariadb_remote_ip
db_menu
; ;
16)
remove_mariadb_remote_ip
db_menu
; ;
2026-04-03 01:31:56 +00:00
*)
echo -e " ${ red } 无效选项,请选择有效数字。 ${ plain } \n "
db_menu
; ;
esac
}
2023-02-09 19:18:06 +00:00
show_menu( ) {
echo -e "
2024-12-20 17:33:27 +00:00
╔────────────────────────────────────────────────╗
2026-04-02 01:49:12 +00:00
│ ${ green } 3X-UI 面板管理脚本${ plain } │
│ ${ green } 0.${ plain } 退出脚本 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 1.${ plain } 安装 │
│ ${ green } 2.${ plain } 更新 │
│ ${ green } 3.${ plain } 更新菜单 │
│ ${ green } 4.${ plain } 安装旧版本 │
│ ${ green } 5.${ plain } 卸载 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 6.${ plain } 重置用户名和密码 │
│ ${ green } 7.${ plain } 重置 Web 路径 │
│ ${ green } 8.${ plain } 重置设置 │
│ ${ green } 9.${ plain } 修改端口 │
│ ${ green } 10.${ plain } 查看当前设置 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 11.${ plain } 启动 │
│ ${ green } 12.${ plain } 停止 │
│ ${ green } 13.${ plain } 重启 │
| ${ green } 14.${ plain } 重启 Xray │
│ ${ green } 15.${ plain } 查看状态 │
│ ${ green } 16.${ plain } 日志管理 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 17.${ plain } 设置开机自启 │
│ ${ green } 18.${ plain } 取消开机自启 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 19.${ plain } SSL 证书管理 │
│ ${ green } 20.${ plain } Cloudflare SSL 证书 │
│ ${ green } 21.${ plain } IP 限制管理 │
│ ${ green } 22.${ plain } 防火墙管理 │
│ ${ green } 23.${ plain } SSH 端口转发管理 │
2024-12-20 17:33:27 +00:00
│────────────────────────────────────────────────│
2026-04-02 01:49:12 +00:00
│ ${ green } 24.${ plain } BBR 管理 │
│ ${ green } 25.${ plain } 更新 Geo 文件 │
│ ${ green } 26.${ plain } 网速测试 ( Speedtest) │
2026-04-03 01:31:56 +00:00
│────────────────────────────────────────────────│
│ ${ green } 27.${ plain } 数据库管理 │
2024-12-20 17:33:27 +00:00
╚────────────────────────────────────────────────╝
2023-07-01 12:26:43 +00:00
"
2023-02-09 19:18:06 +00:00
show_status
2026-04-03 01:31:56 +00:00
echo && read -rp "请输入选择 [0-27]: " num
2023-02-09 19:18:06 +00:00
case " ${ num } " in
0)
exit 0
; ;
1)
check_uninstall && install
; ;
2)
check_install && update
; ;
3)
2024-06-24 13:22:05 +00:00
check_install && update_menu
2023-02-09 19:18:06 +00:00
; ;
4)
2024-10-31 00:18:37 +00:00
check_install && legacy_version
2023-02-09 19:18:06 +00:00
; ;
5)
2024-06-24 13:22:05 +00:00
check_install && uninstall
2023-02-09 19:18:06 +00:00
; ;
6)
2024-06-24 13:22:05 +00:00
check_install && reset_user
2023-02-09 19:18:06 +00:00
; ;
7)
2024-06-24 13:22:05 +00:00
check_install && reset_webbasepath
2023-02-09 19:18:06 +00:00
; ;
8)
2024-06-24 13:22:05 +00:00
check_install && reset_config
2023-02-09 19:18:06 +00:00
; ;
9)
2024-06-24 13:22:05 +00:00
check_install && set_port
2023-02-09 19:18:06 +00:00
; ;
10)
2024-06-24 13:22:05 +00:00
check_install && check_config
2023-02-09 19:18:06 +00:00
; ;
11)
2024-06-24 13:22:05 +00:00
check_install && start
2023-02-09 19:18:06 +00:00
; ;
12)
2024-06-24 13:22:05 +00:00
check_install && stop
2023-02-09 19:18:06 +00:00
; ;
13)
2024-06-24 13:22:05 +00:00
check_install && restart
2023-02-09 19:18:06 +00:00
; ;
14)
2026-02-19 23:03:16 +00:00
check_install && restart_xray
2023-02-09 19:18:06 +00:00
; ;
15)
2026-02-19 23:03:16 +00:00
check_install && status
2023-02-09 19:18:06 +00:00
; ;
16)
2026-02-19 23:03:16 +00:00
check_install && show_log
2023-02-09 19:18:06 +00:00
; ;
2023-04-02 14:42:00 +00:00
17)
2026-02-19 23:03:16 +00:00
check_install && enable
2023-04-02 14:42:00 +00:00
; ;
18)
2026-02-19 23:03:16 +00:00
check_install && disable
2023-04-02 14:42:00 +00:00
; ;
2023-04-03 15:52:23 +00:00
19)
2026-02-19 23:03:16 +00:00
ssl_cert_issue_main
2023-04-03 15:52:23 +00:00
; ;
2023-05-13 15:36:16 +00:00
20)
2026-02-19 23:03:16 +00:00
ssl_cert_issue_CF
2023-08-08 21:22:40 +00:00
; ;
2024-01-27 09:26:10 +00:00
21)
2026-02-19 23:03:16 +00:00
iplimit_main
2024-01-01 20:09:21 +00:00
; ;
2024-09-09 07:48:48 +00:00
22)
2026-02-19 23:03:16 +00:00
firewall_menu
2024-06-24 12:43:39 +00:00
; ;
2024-09-09 07:48:48 +00:00
23)
2026-02-19 23:03:16 +00:00
SSH_port_forwarding
2024-06-24 13:22:05 +00:00
; ;
2024-09-09 07:48:48 +00:00
24)
2026-02-19 23:03:16 +00:00
bbr_menu
2024-10-31 08:53:47 +00:00
; ;
25)
2026-02-19 23:03:16 +00:00
update_geo
; ;
26)
2023-06-24 21:37:34 +00:00
run_speedtest
2024-01-20 13:58:44 +00:00
; ;
2026-04-03 01:31:56 +00:00
27)
check_install && db_menu
; ;
2023-02-09 19:18:06 +00:00
*)
2026-04-03 01:31:56 +00:00
LOGE "请输入正确的数字 [0-27]"
2023-02-09 19:18:06 +00:00
; ;
esac
}
if [ [ $# > 0 ] ] ; then
case $1 in
"start" )
check_install 0 && start 0
; ;
"stop" )
check_install 0 && stop 0
; ;
"restart" )
check_install 0 && restart 0
; ;
2026-02-19 23:03:16 +00:00
"restart-xray" )
check_install 0 && restart_xray 0
; ;
2023-02-09 19:18:06 +00:00
"status" )
check_install 0 && status 0
; ;
2024-06-24 11:47:13 +00:00
"settings" )
check_install 0 && check_config 0
; ;
2023-02-09 19:18:06 +00:00
"enable" )
check_install 0 && enable 0
; ;
"disable" )
check_install 0 && disable 0
; ;
"log" )
check_install 0 && show_log 0
; ;
2023-09-04 23:50:09 +00:00
"banlog" )
check_install 0 && show_banlog 0
; ;
2023-02-09 19:18:06 +00:00
"update" )
check_install 0 && update 0
; ;
2024-10-31 00:18:37 +00:00
"legacy" )
check_install 0 && legacy_version 0
2024-06-24 11:47:13 +00:00
; ;
2023-02-09 19:18:06 +00:00
"install" )
check_uninstall 0 && install 0
; ;
"uninstall" )
check_install 0 && uninstall 0
; ;
2025-11-07 18:26:43 +00:00
"update-all-geofiles" )
check_install 0 && update_all_geofiles 0 && restart 0
; ;
2023-02-09 19:18:06 +00:00
*) show_usage ; ;
esac
else
show_menu
2025-02-04 10:27:58 +00:00
fi