Merge branch 'main' into node-mode

This commit is contained in:
konstpic 2026-01-12 11:13:12 +03:00 committed by GitHub
commit 7d863ff469
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 201 additions and 74 deletions

View file

@ -29,7 +29,8 @@ RUN apk add --no-cache --update \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
fail2ban \ fail2ban \
bash bash \
curl
COPY --from=builder /app/build/ /app/ COPY --from=builder /app/build/ /app/
COPY --from=builder /app/DockerEntrypoint.sh /app/ COPY --from=builder /app/DockerEntrypoint.sh /app/

View file

@ -82,9 +82,12 @@ type HistoryOfSeeders struct {
// GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model. // GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model.
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig { func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
listen := i.Listen listen := i.Listen
if listen != "" { // Default to 0.0.0.0 (all interfaces) when listen is empty
listen = fmt.Sprintf("\"%v\"", listen) // This ensures proper dual-stack IPv4/IPv6 binding in systems where bindv6only=0
if listen == "" {
listen = "0.0.0.0"
} }
listen = fmt.Sprintf("\"%v\"", listen)
return &xray.InboundConfig{ return &xray.InboundConfig{
Listen: json_util.RawMessage(listen), Listen: json_util.RawMessage(listen),
Port: i.Port, Port: i.Port,

View file

@ -53,35 +53,52 @@ is_ip() {
is_ipv4 "$1" || is_ipv6 "$1" is_ipv4 "$1" || is_ipv6 "$1"
} }
is_domain() { is_domain() {
[[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+[A-Za-z]{2,}$ ]] && return 0 || return 1 [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1
}
# Port helpers
is_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
return
fi
if command -v netstat >/dev/null 2>&1; then
netstat -lnt 2>/dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
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
} }
install_base() { install_base() {
case "${release}" in case "${release}" in
ubuntu | debian | armbian) ubuntu | debian | armbian)
apt-get update && apt-get install -y -q curl tar tzdata socat apt-get update && apt-get install -y -q curl tar tzdata socat ca-certificates
;; ;;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol) fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update && dnf install -y -q curl tar tzdata socat dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates
;; ;;
centos) centos)
if [[ "${VERSION_ID}" =~ ^7 ]]; then if [[ "${VERSION_ID}" =~ ^7 ]]; then
yum -y update && yum install -y curl tar tzdata socat yum -y update && yum install -y curl tar tzdata socat ca-certificates
else else
dnf -y update && dnf install -y -q curl tar tzdata socat dnf -y update && dnf install -y -q curl tar tzdata socat ca-certificates
fi fi
;; ;;
arch | manjaro | parch) arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm curl tar tzdata socat pacman -Syu && pacman -Syu --noconfirm curl tar tzdata socat ca-certificates
;; ;;
opensuse-tumbleweed | opensuse-leap) opensuse-tumbleweed | opensuse-leap)
zypper refresh && zypper -q install -y curl tar timezone socat zypper refresh && zypper -q install -y curl tar timezone socat ca-certificates
;; ;;
alpine) alpine)
apk update && apk add curl tar tzdata socat apk update && apk add curl tar tzdata socat ca-certificates
;; ;;
*) *)
apt-get update && apt-get install -y -q curl tar tzdata socat apt-get update && apt-get install -y -q curl tar tzdata socat ca-certificates
;; ;;
esac esac
} }
@ -180,7 +197,7 @@ setup_ip_certificate() {
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}" echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}" echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
echo -e "${yellow}Port 80 must be open and accessible from the internet.${plain}" echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
# Check for acme.sh # Check for acme.sh
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
@ -216,6 +233,43 @@ setup_ip_certificate() {
# Set reload command for auto-renewal (add || true so it doesn't fail during first install) # Set reload command for auto-renewal (add || true so it doesn't fail during first install)
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true" local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true"
# Choose port for HTTP-01 listener (default 80, prompt override)
local WebPort=""
read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
WebPort="${WebPort:-80}"
if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
echo -e "${red}Invalid port provided. Falling back to 80.${plain}"
WebPort=80
fi
echo -e "${green}Using port ${WebPort} for standalone validation.${plain}"
if [[ "${WebPort}" -ne 80 ]]; then
echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}"
fi
# Ensure chosen port is available
while true; do
if is_port_in_use "${WebPort}"; then
echo -e "${yellow}Port ${WebPort} is in use.${plain}"
local alt_port=""
read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
alt_port="${alt_port// /}"
if [[ -z "${alt_port}" ]]; then
echo -e "${red}Port ${WebPort} is busy; cannot proceed.${plain}"
return 1
fi
if ! [[ "${alt_port}" =~ ^[0-9]+$ ]] || ((alt_port < 1 || alt_port > 65535)); then
echo -e "${red}Invalid port provided.${plain}"
return 1
fi
WebPort="${alt_port}"
continue
else
echo -e "${green}Port ${WebPort} is free and ready for standalone validation.${plain}"
break
fi
done
# Issue certificate with shortlived profile # Issue certificate with shortlived profile
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
@ -226,12 +280,12 @@ setup_ip_certificate() {
--server letsencrypt \ --server letsencrypt \
--certificate-profile shortlived \ --certificate-profile shortlived \
--days 6 \ --days 6 \
--httpport 80 \ --httpport ${WebPort} \
--force --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${red}Failed to issue IP certificate${plain}" echo -e "${red}Failed to issue IP certificate${plain}"
echo -e "${yellow}Please ensure port 80 is open and accessible from the internet${plain}" echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
# Cleanup acme.sh data for both IPv4 and IPv6 if specified # Cleanup acme.sh data for both IPv4 and IPv6 if specified
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null

View file

@ -78,7 +78,24 @@ is_ip() {
is_ipv4 "$1" || is_ipv6 "$1" is_ipv4 "$1" || is_ipv6 "$1"
} }
is_domain() { is_domain() {
[[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+[A-Za-z]{2,}$ ]] && return 0 || return 1 [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1
}
# Port helpers
is_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
return
fi
if command -v netstat >/dev/null 2>&1; then
netstat -lnt 2>/dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
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
} }
gen_random_string() { gen_random_string() {
@ -205,7 +222,7 @@ setup_ip_certificate() {
echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}" echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}" echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
echo -e "${yellow}Port 80 must be open and accessible from the internet.${plain}" echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
# Check for acme.sh # Check for acme.sh
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
@ -241,6 +258,43 @@ setup_ip_certificate() {
# Set reload command for auto-renewal (add || true so it doesn't fail if service stopped) # Set reload command for auto-renewal (add || true so it doesn't fail if service stopped)
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true" local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true"
# Choose port for HTTP-01 listener (default 80, prompt override)
local WebPort=""
read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
WebPort="${WebPort:-80}"
if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
echo -e "${red}Invalid port provided. Falling back to 80.${plain}"
WebPort=80
fi
echo -e "${green}Using port ${WebPort} for standalone validation.${plain}"
if [[ "${WebPort}" -ne 80 ]]; then
echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}"
fi
# Ensure chosen port is available
while true; do
if is_port_in_use "${WebPort}"; then
echo -e "${yellow}Port ${WebPort} is currently in use.${plain}"
local alt_port=""
read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
alt_port="${alt_port// /}"
if [[ -z "${alt_port}" ]]; then
echo -e "${red}Port ${WebPort} is busy; cannot proceed.${plain}"
return 1
fi
if ! [[ "${alt_port}" =~ ^[0-9]+$ ]] || ((alt_port < 1 || alt_port > 65535)); then
echo -e "${red}Invalid port provided.${plain}"
return 1
fi
WebPort="${alt_port}"
continue
else
echo -e "${green}Port ${WebPort} is free and ready for standalone validation.${plain}"
break
fi
done
# Issue certificate with shortlived profile # Issue certificate with shortlived profile
echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}" echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1 ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt >/dev/null 2>&1
@ -251,12 +305,12 @@ setup_ip_certificate() {
--server letsencrypt \ --server letsencrypt \
--certificate-profile shortlived \ --certificate-profile shortlived \
--days 6 \ --days 6 \
--httpport 80 \ --httpport ${WebPort} \
--force --force
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo -e "${red}Failed to issue IP certificate${plain}" echo -e "${red}Failed to issue IP certificate${plain}"
echo -e "${yellow}Please ensure port 80 is open and accessible from the internet${plain}" echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
# Cleanup acme.sh data for both IPv4 and IPv6 if specified # Cleanup acme.sh data for both IPv4 and IPv6 if specified
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
[[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2>/dev/null

125
x-ui.sh
View file

@ -19,6 +19,23 @@ function LOGI() {
echo -e "${green}[INF] $* ${plain}" echo -e "${green}[INF] $* ${plain}"
} }
# Port helpers: detect listener and owning process (best effort)
is_port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn 2>/dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
return
fi
if command -v netstat >/dev/null 2>&1; then
netstat -lnt 2>/dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
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
}
# Simple helpers for domain/IP validation # Simple helpers for domain/IP validation
is_ipv4() { is_ipv4() {
[[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1 [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1
@ -30,7 +47,7 @@ is_ip() {
is_ipv4 "$1" || is_ipv6 "$1" is_ipv4 "$1" || is_ipv6 "$1"
} }
is_domain() { is_domain() {
[[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+[A-Za-z]{2,}$ ]] && return 0 || return 1 [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1
} }
# check root # check root
@ -539,36 +556,6 @@ enable_bbr() {
before_show_menu before_show_menu
fi fi
# Check the OS and install necessary packages
case "${release}" in
ubuntu | debian | armbian)
apt-get update && apt-get install -yqq --no-install-recommends ca-certificates
;;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update && dnf -y install ca-certificates
;;
centos)
if [[ "${VERSION_ID}" =~ ^7 ]]; then
yum -y update && yum -y install ca-certificates
else
dnf -y update && dnf -y install ca-certificates
fi
;;
arch | manjaro | parch)
pacman -Sy --noconfirm ca-certificates
;;
opensuse-tumbleweed | opensuse-leap)
zypper refresh && zypper -q install -y ca-certificates
;;
alpine)
apk add ca-certificates
;;
*)
echo -e "${red}Unsupported operating system. Please check the script and install the necessary packages manually.${plain}\n"
exit 1
;;
esac
# Enable BBR # Enable BBR
echo "net.core.default_qdisc=fq" | tee -a /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 echo "net.ipv4.tcp_congestion_control=bbr" | tee -a /etc/sysctl.conf
@ -903,24 +890,23 @@ delete_ports() {
} }
update_all_geofiles() { update_all_geofiles() {
update_main_geofiles update_geofiles "main"
update_ir_geofiles update_geofiles "IR"
update_ru_geofiles update_geofiles "RU"
} }
update_main_geofiles() { update_geofiles() {
curl -fLRo geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat case "${1}" in
curl -fLRo geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat "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";;
update_ir_geofiles() { esac
curl -fLRo geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat for dat in "${dat_files[@]}"; do
curl -fLRo geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat # Remove suffix for remote filename (e.g., geoip_IR -> geoip)
} remote_file="${dat%%_*}"
curl -fLRo ${xui_folder}/bin/${dat}.dat -z ${xui_folder}/bin/${dat}.dat \
update_ru_geofiles() { https://github.com/${dat_source}/releases/latest/download/${remote_file}.dat
curl -fLRo geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat done
curl -fLRo geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
} }
update_geo() { update_geo() {
@ -931,24 +917,22 @@ update_geo() {
echo -e "${green}\t0.${plain} Back to Main Menu" echo -e "${green}\t0.${plain} Back to Main Menu"
read -rp "Choose an option: " choice read -rp "Choose an option: " choice
cd ${xui_folder}/bin
case "$choice" in case "$choice" in
0) 0)
show_menu show_menu
;; ;;
1) 1)
update_main_geofiles update_geofiles "main"
echo -e "${green}Loyalsoldier datasets have been updated successfully!${plain}" echo -e "${green}Loyalsoldier datasets have been updated successfully!${plain}"
restart restart
;; ;;
2) 2)
update_ir_geofiles update_geofiles "IR"
echo -e "${green}chocolate4u datasets have been updated successfully!${plain}" echo -e "${green}chocolate4u datasets have been updated successfully!${plain}"
restart restart
;; ;;
3) 3)
update_ru_geofiles update_geofiles "RU"
echo -e "${green}runetfreedom datasets have been updated successfully!${plain}" echo -e "${green}runetfreedom datasets have been updated successfully!${plain}"
restart restart
;; ;;
@ -1181,10 +1165,41 @@ ssl_cert_issue_for_ip() {
LOGI "Including IPv6 address: ${ipv6_addr}" LOGI "Including IPv6 address: ${ipv6_addr}"
fi fi
# Use port 80 for certificate issuance # Choose port for HTTP-01 listener (default 80, allow override)
local WebPort=80 local WebPort=""
read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
WebPort="${WebPort:-80}"
if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
LOGE "Invalid port provided. Falling back to 80."
WebPort=80
fi
LOGI "Using port ${WebPort} to issue certificate for IP: ${server_ip}" LOGI "Using port ${WebPort} to issue certificate for IP: ${server_ip}"
LOGI "Make sure port ${WebPort} is open and not in use..." if [[ "${WebPort}" -ne 80 ]]; then
LOGI "Reminder: Let's Encrypt still reaches port 80; forward external port 80 to ${WebPort} for validation."
fi
while true; do
if is_port_in_use "${WebPort}"; then
LOGI "Port ${WebPort} is currently in use."
local alt_port=""
read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
alt_port="${alt_port// /}"
if [[ -z "${alt_port}" ]]; then
LOGE "Port ${WebPort} is busy; cannot proceed with issuance."
return 1
fi
if ! [[ "${alt_port}" =~ ^[0-9]+$ ]] || ((alt_port < 1 || alt_port > 65535)); then
LOGE "Invalid port provided."
return 1
fi
WebPort="${alt_port}"
continue
else
LOGI "Port ${WebPort} is free and ready for standalone validation."
break
fi
done
# Reload command - restarts panel after renewal # Reload command - restarts panel after renewal
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null" local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null"