mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-28 05:02:59 +00:00
Merge branch 'main' into test-branch
# Conflicts: # lib/geo.sh
This commit is contained in:
commit
4abef0df5a
7 changed files with 178 additions and 15 deletions
|
|
@ -54,7 +54,8 @@ RUN apk add --no-cache \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
tzdata \
|
tzdata \
|
||||||
fail2ban \
|
fail2ban \
|
||||||
bash
|
bash \
|
||||||
|
curl
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,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,
|
||||||
|
|
|
||||||
62
install.sh
62
install.sh
|
|
@ -53,7 +53,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
|
||||||
}
|
}
|
||||||
|
|
||||||
install_base() {
|
install_base() {
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ is_ip() {
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
# Generate random string
|
# Generate random string
|
||||||
|
|
|
||||||
54
lib/ssl.sh
54
lib/ssl.sh
|
|
@ -30,6 +30,23 @@ install_acme() {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ssl_cert_issue_main() {
|
ssl_cert_issue_main() {
|
||||||
echo -e "${green}\t1.${plain} Get SSL (Domain)"
|
echo -e "${green}\t1.${plain} Get SSL (Domain)"
|
||||||
echo -e "${green}\t2.${plain} Revoke"
|
echo -e "${green}\t2.${plain} Revoke"
|
||||||
|
|
@ -224,10 +241,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"
|
||||||
|
|
|
||||||
62
update.sh
62
update.sh
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@ func (s *XrayService) GetXrayErr() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
err := p.GetErr()
|
err := p.GetErr()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
|
if runtime.GOOS == "windows" && err.Error() == "exit status 1" {
|
||||||
// exit status 1 on Windows means that Xray process was killed
|
// exit status 1 on Windows means that Xray process was killed
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue