Skip to content
Merged

ip cert #3631

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 125 additions & 69 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,29 @@ is_domain() {
install_base() {
case "${release}" in
ubuntu | debian | armbian)
apt-get update && apt-get install -y -q curl tar tzdata openssl socat
apt-get update && apt-get install -y -q curl tar tzdata socat
;;
fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
dnf -y update && dnf install -y -q curl tar tzdata openssl socat
dnf -y update && dnf install -y -q curl tar tzdata socat
;;
centos)
if [[ "${VERSION_ID}" =~ ^7 ]]; then
yum -y update && yum install -y curl tar tzdata openssl socat
yum -y update && yum install -y curl tar tzdata socat
else
dnf -y update && dnf install -y -q curl tar tzdata openssl socat
dnf -y update && dnf install -y -q curl tar tzdata socat
fi
;;
arch | manjaro | parch)
pacman -Syu && pacman -Syu --noconfirm curl tar tzdata openssl socat
pacman -Syu && pacman -Syu --noconfirm curl tar tzdata socat
;;
opensuse-tumbleweed | opensuse-leap)
zypper refresh && zypper -q install -y curl tar timezone openssl socat
zypper refresh && zypper -q install -y curl tar timezone socat
;;
alpine)
apk update && apk add curl tar tzdata openssl socat
apk update && apk add curl tar tzdata socat
;;
*)
apt-get update && apt-get install -y -q curl tar tzdata openssl socat
apt-get update && apt-get install -y -q curl tar tzdata socat
;;
esac
}
Expand Down Expand Up @@ -170,56 +170,109 @@ setup_ssl_certificate() {
fi
}

# Fallback: generate a self-signed certificate (not publicly trusted)
setup_self_signed_certificate() {
local name="$1" # domain or IP to place in SAN
local certDir="/root/cert/selfsigned"
# Issue Let's Encrypt IP certificate with shortlived profile (~6 days validity)
# Requires acme.sh and port 80 open for HTTP-01 challenge
setup_ip_certificate() {
local ipv4="$1"
local ipv6="$2" # optional

echo -e "${yellow}Generating a self-signed certificate (not publicly trusted)...${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}Port 80 must be open and accessible from the internet.${plain}"

# Check for acme.sh
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
install_acme
if [ $? -ne 0 ]; then
echo -e "${red}Failed to install acme.sh${plain}"
return 1
fi
fi

# Validate IP address
if [[ -z "$ipv4" ]]; then
echo -e "${red}IPv4 address is required${plain}"
return 1
fi

if ! is_ipv4 "$ipv4"; then
echo -e "${red}Invalid IPv4 address: $ipv4${plain}"
return 1
fi

# Create certificate directory
local certDir="/root/cert/ip"
mkdir -p "$certDir"

local sanExt=""
if is_ip "$name"; then
sanExt="IP:${name}"
else
sanExt="DNS:${name}"
# Build domain arguments
local domain_args="-d ${ipv4}"
if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then
domain_args="${domain_args} -d ${ipv6}"
echo -e "${green}Including IPv6 address: ${ipv6}${plain}"
fi

# Use -addext if supported; fallback to config file if needed
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout "${certDir}/privkey.pem" \
-out "${certDir}/fullchain.pem" \
-subj "/CN=${name}" \
-addext "subjectAltName=${sanExt}" >/dev/null 2>&1
# 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"

if [[ $? -ne 0 ]]; then
# Fallback via temporary config file (for older OpenSSL versions)
local tmpCfg="${certDir}/openssl.cnf"
cat > "$tmpCfg" <<EOF
[req]
distinguished_name=req_distinguished_name
req_extensions=v3_req
[req_distinguished_name]
[v3_req]
subjectAltName=${sanExt}
EOF
openssl req -x509 -nodes -newkey rsa:2048 -days 365 \
-keyout "${certDir}/privkey.pem" \
-out "${certDir}/fullchain.pem" \
-subj "/CN=${name}" \
-config "$tmpCfg" -extensions v3_req >/dev/null 2>&1
rm -f "$tmpCfg"
# Issue certificate with shortlived profile
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 --issue \
${domain_args} \
--standalone \
--server letsencrypt \
--certificate-profile shortlived \
--days 6 \
--httpport 80 \
--force

if [ $? -ne 0 ]; then
echo -e "${red}Failed to issue IP certificate${plain}"
echo -e "${yellow}Please ensure port 80 is open and accessible from the internet${plain}"
rm -rf ~/.acme.sh/${ipv4} 2>/dev/null
return 1
fi

echo -e "${green}Certificate issued successfully, installing...${plain}"

# Install certificate
# Note: acme.sh may report "Reload error" and exit non-zero if reloadcmd fails,
# but the cert files are still installed. We check for files instead of exit code.
~/.acme.sh/acme.sh --installcert -d ${ipv4} \
--key-file "${certDir}/privkey.pem" \
--fullchain-file "${certDir}/fullchain.pem" \
--reloadcmd "${reloadCmd}" 2>&1 || true

# Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero)
if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
echo -e "${red}Failed to generate self-signed certificate${plain}"
echo -e "${red}Certificate files not found after installation${plain}"
return 1
fi

echo -e "${green}Certificate files installed successfully${plain}"

# Enable auto-upgrade for acme.sh (ensures cron job runs)
~/.acme.sh/acme.sh --upgrade --auto-upgrade >/dev/null 2>&1

chmod 755 ${certDir}/* 2>/dev/null
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem" >/dev/null 2>&1
echo -e "${yellow}Self-signed certificate configured. Browsers will show a warning.${plain}"

# Configure panel to use the certificate
echo -e "${green}Setting certificate paths for the panel...${plain}"
${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem"

if [ $? -ne 0 ]; then
echo -e "${yellow}Warning: Could not set certificate paths automatically${plain}"
echo -e "${yellow}Certificate files are at:${plain}"
echo -e " Cert: ${certDir}/fullchain.pem"
echo -e " Key: ${certDir}/privkey.pem"
else
echo -e "${green}Certificate paths configured successfully${plain}"
fi

echo -e "${green}IP certificate installed and configured successfully!${plain}"
echo -e "${green}Certificate valid for ~6 days, auto-renews via acme.sh cron job.${plain}"
echo -e "${yellow}acme.sh will automatically renew and reload x-ui before expiry.${plain}"
return 0
}

Expand Down Expand Up @@ -359,7 +412,7 @@ ssl_cert_issue() {
chmod 755 $certPath/*
fi

# Restart panel
# start panel
systemctl start x-ui 2>/dev/null || rc-service x-ui start 2>/dev/null

# Prompt user to set panel paths after successful certificate installation
Expand Down Expand Up @@ -387,7 +440,7 @@ ssl_cert_issue() {
return 0
}

# Reusable interactive SSL setup (domain or self-signed)
# Reusable interactive SSL setup (domain or IP)
# Sets global `SSL_HOST` to the chosen domain/IP for Access URL usage
prompt_and_setup_ssl() {
local panel_port="$1"
Expand All @@ -397,20 +450,21 @@ prompt_and_setup_ssl() {
local ssl_choice=""

echo -e "${yellow}Choose SSL certificate setup method:${plain}"
echo -e "${green}1.${plain} Let's Encrypt (domain required, recommended)"
echo -e "${green}2.${plain} Self-signed certificate (not publicly trusted)"
read -rp "Choose an option (default 2): " ssl_choice
echo -e "${green}1.${plain} Let's Encrypt for Domain (90-day validity, auto-renews)"
echo -e "${green}2.${plain} Let's Encrypt for IP Address (6-day validity, auto-renews)"
echo -e "${blue}Note:${plain} Both options require port 80 open. IP certs use shortlived profile."
read -rp "Choose an option (default 2 for IP): " ssl_choice
ssl_choice="${ssl_choice// /}" # Trim whitespace

# Default to 2 (self-signed) if not 1
# Default to 2 (IP cert) if not 1
if [[ "$ssl_choice" != "1" ]]; then
ssl_choice="2"
fi

case "$ssl_choice" in
1)
# User chose Let's Encrypt domain option
echo -e "${green}Using ssl_cert_issue() for comprehensive domain setup...${plain}"
echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
ssl_cert_issue
# Extract the domain that was used from the certificate
local cert_domain=$(~/.acme.sh/acme.sh --list 2>/dev/null | tail -1 | awk '{print $1}')
Expand All @@ -423,28 +477,30 @@ prompt_and_setup_ssl() {
fi
;;
2)
# User chose self-signed option
# Stop panel if running
# User chose Let's Encrypt IP certificate option
echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"

# Ask for optional IPv6
local ipv6_addr=""
read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
ipv6_addr="${ipv6_addr// /}" # Trim whitespace

# Stop panel if running (port 80 needed)
if [[ $release == "alpine" ]]; then
rc-service x-ui stop >/dev/null 2>&1
else
systemctl stop x-ui >/dev/null 2>&1
fi
echo -e "${yellow}Using server IP for self-signed certificate: ${server_ip}${plain}"
setup_self_signed_certificate "${server_ip}"

setup_ip_certificate "${server_ip}" "${ipv6_addr}"
if [ $? -eq 0 ]; then
SSL_HOST="${server_ip}"
echo -e "${green}✓ Self-signed SSL configured successfully${plain}"
echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
else
echo -e "${red}✗ Self-signed SSL setup failed${plain}"
echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
SSL_HOST="${server_ip}"
fi
# Start panel after SSL is configured
if [[ $release == "alpine" ]]; then
rc-service x-ui start >/dev/null 2>&1
else
systemctl start x-ui >/dev/null 2>&1
fi

;;
*)
echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
Expand Down Expand Up @@ -497,7 +553,7 @@ config_after_install() {
echo -e "${green} SSL Certificate Setup (MANDATORY) ${plain}"
echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${yellow}For security, SSL certificate is required for all panels.${plain}"
echo -e "${yellow}Let's Encrypt requires a domain name (IP certificates are not issued).${plain}"
echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
echo ""

prompt_and_setup_ssl "${config_port}" "${config_webBasePath}" "${server_ip}"
Expand Down Expand Up @@ -527,7 +583,7 @@ config_after_install() {
echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${green} SSL Certificate Setup (RECOMMENDED) ${plain}"
echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${yellow}Let's Encrypt requires a domain name (IP certificates are not issued).${plain}"
echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
echo ""
prompt_and_setup_ssl "${existing_port}" "${config_webBasePath}" "${server_ip}"
echo -e "${green}Access URL: https://${SSL_HOST}:${existing_port}/${config_webBasePath}${plain}"
Expand All @@ -552,15 +608,15 @@ config_after_install() {
echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}"
fi

# Existing install: if no cert configured, prompt user to set domain or self-signed
# Existing install: if no cert configured, prompt user for SSL setup
# Properly detect empty cert by checking if cert: line exists and has content after it
existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
if [[ -z "$existing_cert" ]]; then
echo ""
echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${green} SSL Certificate Setup (RECOMMENDED) ${plain}"
echo -e "${green}═══════════════════════════════════════════${plain}"
echo -e "${yellow}Let's Encrypt requires a domain name (IP certificates are not issued).${plain}"
echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
echo ""
prompt_and_setup_ssl "${existing_port}" "${existing_webBasePath}" "${server_ip}"
echo -e "${green}Access URL: https://${SSL_HOST}:${existing_port}/${existing_webBasePath}${plain}"
Expand All @@ -587,7 +643,7 @@ install_x-ui() {
fi
fi
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz -z ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
if [[ $? -ne 0 ]]; then
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
exit 1
Expand All @@ -604,7 +660,7 @@ install_x-ui() {

url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
echo -e "Beginning to install x-ui $1"
curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz -z ${xui_folder}-linux-$(arch).tar.gz ${url}
curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url}
if [[ $? -ne 0 ]]; then
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
exit 1
Expand Down
Loading