mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-02-28 05:02:59 +00:00
629 lines
23 KiB
Bash
629 lines
23 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# lib/ssl.sh - SSL certificate management (acme.sh, Let's Encrypt, Cloudflare)
|
||
|
|
|
||
|
|
# Include guard
|
||
|
|
[[ -n "${__X_UI_SSL_INCLUDED:-}" ]] && return 0
|
||
|
|
__X_UI_SSL_INCLUDED=1
|
||
|
|
|
||
|
|
# Source dependencies
|
||
|
|
source "${LIB_DIR}/common.sh"
|
||
|
|
source "${LIB_DIR}/service.sh"
|
||
|
|
|
||
|
|
install_acme() {
|
||
|
|
# Check if acme.sh is already installed
|
||
|
|
if command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||
|
|
LOGI "acme.sh is already installed."
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
|
||
|
|
LOGI "Installing acme.sh..."
|
||
|
|
cd ~ || return 1 # Ensure you can change to the home directory
|
||
|
|
|
||
|
|
curl -s https://get.acme.sh | sh
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Installation of acme.sh failed."
|
||
|
|
return 1
|
||
|
|
else
|
||
|
|
LOGI "Installation of acme.sh succeeded."
|
||
|
|
fi
|
||
|
|
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
|
||
|
|
ssl_cert_issue_main() {
|
||
|
|
echo -e "${green}\t1.${plain} Get SSL (Domain)"
|
||
|
|
echo -e "${green}\t2.${plain} Revoke"
|
||
|
|
echo -e "${green}\t3.${plain} Force Renew"
|
||
|
|
echo -e "${green}\t4.${plain} Show Existing Domains"
|
||
|
|
echo -e "${green}\t5.${plain} Set Cert paths for the panel"
|
||
|
|
echo -e "${green}\t6.${plain} Get SSL for IP Address (6-day cert, auto-renews)"
|
||
|
|
echo -e "${green}\t0.${plain} Back to Main Menu"
|
||
|
|
|
||
|
|
read -rp "Choose an option: " choice
|
||
|
|
case "$choice" in
|
||
|
|
0)
|
||
|
|
show_menu
|
||
|
|
;;
|
||
|
|
1)
|
||
|
|
ssl_cert_issue
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
2)
|
||
|
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||
|
|
if [ -z "$domains" ]; then
|
||
|
|
echo "No certificates found to revoke."
|
||
|
|
else
|
||
|
|
echo "Existing domains:"
|
||
|
|
echo "$domains"
|
||
|
|
read -rp "Please enter a domain from the list to revoke the certificate: " domain
|
||
|
|
if echo "$domains" | grep -qw "$domain"; then
|
||
|
|
~/.acme.sh/acme.sh --revoke -d ${domain}
|
||
|
|
LOGI "Certificate revoked for domain: $domain"
|
||
|
|
else
|
||
|
|
echo "Invalid domain entered."
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
3)
|
||
|
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||
|
|
if [ -z "$domains" ]; then
|
||
|
|
echo "No certificates found to renew."
|
||
|
|
else
|
||
|
|
echo "Existing domains:"
|
||
|
|
echo "$domains"
|
||
|
|
read -rp "Please enter a domain from the list to renew the SSL certificate: " domain
|
||
|
|
if echo "$domains" | grep -qw "$domain"; then
|
||
|
|
~/.acme.sh/acme.sh --renew -d ${domain} --force
|
||
|
|
LOGI "Certificate forcefully renewed for domain: $domain"
|
||
|
|
else
|
||
|
|
echo "Invalid domain entered."
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
4)
|
||
|
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||
|
|
if [ -z "$domains" ]; then
|
||
|
|
echo "No certificates found."
|
||
|
|
else
|
||
|
|
echo "Existing domains and their paths:"
|
||
|
|
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
|
||
|
|
echo -e "Domain: ${domain}"
|
||
|
|
echo -e "\tCertificate Path: ${cert_path}"
|
||
|
|
echo -e "\tPrivate Key Path: ${key_path}"
|
||
|
|
else
|
||
|
|
echo -e "Domain: ${domain} - Certificate or Key missing."
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
fi
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
5)
|
||
|
|
local domains=$(find /root/cert/ -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
|
||
|
|
if [ -z "$domains" ]; then
|
||
|
|
echo "No certificates found."
|
||
|
|
else
|
||
|
|
echo "Available domains:"
|
||
|
|
echo "$domains"
|
||
|
|
read -rp "Please choose a domain to set the panel paths: " domain
|
||
|
|
|
||
|
|
if echo "$domains" | grep -qw "$domain"; then
|
||
|
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||
|
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||
|
|
|
||
|
|
if [[ -f "${webCertFile}" && -f "${webKeyFile}" ]]; then
|
||
|
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||
|
|
echo "Panel paths set for domain: $domain"
|
||
|
|
echo " - Certificate File: $webCertFile"
|
||
|
|
echo " - Private Key File: $webKeyFile"
|
||
|
|
restart
|
||
|
|
else
|
||
|
|
echo "Certificate or private key not found for domain: $domain."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
echo "Invalid domain entered."
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
6)
|
||
|
|
echo -e "${yellow}Let's Encrypt SSL Certificate for IP Address${plain}"
|
||
|
|
echo -e "This will obtain a certificate for your server's IP using the shortlived profile."
|
||
|
|
echo -e "${yellow}Certificate valid for ~6 days, auto-renews via acme.sh cron job.${plain}"
|
||
|
|
echo -e "${yellow}Port 80 must be open and accessible from the internet.${plain}"
|
||
|
|
confirm "Do you want to proceed?" "y"
|
||
|
|
if [[ $? == 0 ]]; then
|
||
|
|
ssl_cert_issue_for_ip
|
||
|
|
fi
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
|
||
|
|
*)
|
||
|
|
echo -e "${red}Invalid option. Please select a valid number.${plain}\n"
|
||
|
|
ssl_cert_issue_main
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
ssl_cert_issue_for_ip() {
|
||
|
|
LOGI "Starting automatic SSL certificate generation for server IP..."
|
||
|
|
LOGI "Using Let's Encrypt shortlived profile (~6 days validity, auto-renews)"
|
||
|
|
|
||
|
|
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||
|
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||
|
|
|
||
|
|
# Get server IP
|
||
|
|
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
|
||
|
|
if [ -z "$server_ip" ]; then
|
||
|
|
server_ip=$(curl -s --max-time 3 https://4.ident.me)
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -z "$server_ip" ]; then
|
||
|
|
LOGE "Failed to get server IP address"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
LOGI "Server IP detected: ${server_ip}"
|
||
|
|
|
||
|
|
# 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
|
||
|
|
|
||
|
|
# check for acme.sh first
|
||
|
|
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||
|
|
LOGI "acme.sh not found, installing..."
|
||
|
|
install_acme
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Failed to install acme.sh"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# install socat
|
||
|
|
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
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
LOGW "Unsupported OS for automatic socat installation"
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
# Create certificate directory
|
||
|
|
certPath="/root/cert/ip"
|
||
|
|
mkdir -p "$certPath"
|
||
|
|
|
||
|
|
# Build domain arguments
|
||
|
|
local domain_args="-d ${server_ip}"
|
||
|
|
if [[ -n "$ipv6_addr" ]] && is_ipv6 "$ipv6_addr"; then
|
||
|
|
domain_args="${domain_args} -d ${ipv6_addr}"
|
||
|
|
LOGI "Including IPv6 address: ${ipv6_addr}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Use port 80 for certificate issuance
|
||
|
|
local WebPort=80
|
||
|
|
LOGI "Using port ${WebPort} to issue certificate for IP: ${server_ip}"
|
||
|
|
LOGI "Make sure port ${WebPort} is open and not in use..."
|
||
|
|
|
||
|
|
# Reload command - restarts panel after renewal
|
||
|
|
local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null"
|
||
|
|
|
||
|
|
# issue the certificate for IP with shortlived profile
|
||
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||
|
|
~/.acme.sh/acme.sh --issue \
|
||
|
|
${domain_args} \
|
||
|
|
--standalone \
|
||
|
|
--server letsencrypt \
|
||
|
|
--certificate-profile shortlived \
|
||
|
|
--days 6 \
|
||
|
|
--httpport ${WebPort} \
|
||
|
|
--force
|
||
|
|
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Failed to issue certificate for IP: ${server_ip}"
|
||
|
|
LOGE "Make sure port ${WebPort} is open and the server is accessible from the internet"
|
||
|
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||
|
|
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
|
||
|
|
else
|
||
|
|
LOGI "Certificate issued successfully for IP: ${server_ip}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Install the 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 ${server_ip} \
|
||
|
|
--key-file "${certPath}/privkey.pem" \
|
||
|
|
--fullchain-file "${certPath}/fullchain.pem" \
|
||
|
|
--reloadcmd "${reloadCmd}" 2>&1 || true
|
||
|
|
|
||
|
|
# Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero)
|
||
|
|
if [[ ! -f "${certPath}/fullchain.pem" || ! -f "${certPath}/privkey.pem" ]]; then
|
||
|
|
LOGE "Certificate files not found after installation"
|
||
|
|
# Cleanup acme.sh data for both IPv4 and IPv6 if specified
|
||
|
|
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
|
||
|
|
|
||
|
|
LOGI "Certificate files installed successfully"
|
||
|
|
|
||
|
|
# enable auto-renew
|
||
|
|
~/.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
|
||
|
|
|
||
|
|
# Set certificate paths for the panel
|
||
|
|
local webCertFile="${certPath}/fullchain.pem"
|
||
|
|
local webKeyFile="${certPath}/privkey.pem"
|
||
|
|
|
||
|
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||
|
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||
|
|
LOGI "Certificate configured for panel"
|
||
|
|
LOGI " - Certificate File: $webCertFile"
|
||
|
|
LOGI " - Private Key File: $webKeyFile"
|
||
|
|
LOGI " - Validity: ~6 days (auto-renews via acme.sh cron)"
|
||
|
|
echo -e "${green}Access URL: https://${server_ip}:${existing_port}${existing_webBasePath}${plain}"
|
||
|
|
LOGI "Panel will restart to apply SSL certificate..."
|
||
|
|
restart
|
||
|
|
return 0
|
||
|
|
else
|
||
|
|
LOGE "Certificate files not found after installation"
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
ssl_cert_issue() {
|
||
|
|
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||
|
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||
|
|
# check for acme.sh first
|
||
|
|
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||
|
|
echo "acme.sh could not be found. we will install it"
|
||
|
|
install_acme
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "install acme failed, please check logs"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# install socat
|
||
|
|
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
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
LOGW "Unsupported OS for automatic socat installation"
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "install socat failed, please check logs"
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "install socat succeed..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# get the domain here, and we need to verify it
|
||
|
|
local domain=""
|
||
|
|
while true; do
|
||
|
|
read -rp "Please enter your domain name: " domain
|
||
|
|
domain="${domain// /}" # Trim whitespace
|
||
|
|
|
||
|
|
if [[ -z "$domain" ]]; then
|
||
|
|
LOGE "Domain name cannot be empty. Please try again."
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
if ! is_domain "$domain"; then
|
||
|
|
LOGE "Invalid domain format: ${domain}. Please enter a valid domain name."
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
|
||
|
|
break
|
||
|
|
done
|
||
|
|
LOGD "Your domain is: ${domain}, checking it..."
|
||
|
|
|
||
|
|
# check if there already exists a certificate
|
||
|
|
local currentCert=$(~/.acme.sh/acme.sh --list | tail -1 | awk '{print $1}')
|
||
|
|
if [ "${currentCert}" == "${domain}" ]; then
|
||
|
|
local certInfo=$(~/.acme.sh/acme.sh --list)
|
||
|
|
LOGE "System already has certificates for this domain. Cannot issue again. Current certificate details:"
|
||
|
|
LOGI "$certInfo"
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "Your domain is ready for issuing certificates now..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# create a directory for the certificate
|
||
|
|
certPath="/root/cert/${domain}"
|
||
|
|
if [ ! -d "$certPath" ]; then
|
||
|
|
mkdir -p "$certPath"
|
||
|
|
else
|
||
|
|
rm -rf "$certPath"
|
||
|
|
mkdir -p "$certPath"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# get the port number for the standalone server
|
||
|
|
local WebPort=80
|
||
|
|
read -rp "Please choose which port to use (default is 80): " WebPort
|
||
|
|
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
||
|
|
LOGE "Your input ${WebPort} is invalid, will use default port 80."
|
||
|
|
WebPort=80
|
||
|
|
fi
|
||
|
|
LOGI "Will use port: ${WebPort} to issue certificates. Please make sure this port is open."
|
||
|
|
|
||
|
|
# issue the certificate
|
||
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||
|
|
~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Issuing certificate failed, please check logs."
|
||
|
|
rm -rf ~/.acme.sh/${domain}
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGE "Issuing certificate succeeded, installing certificates..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
reloadCmd="x-ui restart"
|
||
|
|
|
||
|
|
LOGI "Default --reloadcmd for ACME is: ${yellow}x-ui restart"
|
||
|
|
LOGI "This command will run on every certificate issue and renew."
|
||
|
|
read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
||
|
|
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
||
|
|
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; x-ui restart"
|
||
|
|
echo -e "${green}\t2.${plain} Input your own command"
|
||
|
|
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||
|
|
read -rp "Choose an option: " choice
|
||
|
|
case "$choice" in
|
||
|
|
1)
|
||
|
|
LOGI "Reloadcmd is: systemctl reload nginx ; x-ui restart"
|
||
|
|
reloadCmd="systemctl reload nginx ; x-ui restart"
|
||
|
|
;;
|
||
|
|
2)
|
||
|
|
LOGD "It's recommended to put x-ui restart at the end, so it won't raise an error if other services fails"
|
||
|
|
read -rp "Please enter your reloadcmd (example: systemctl reload nginx ; x-ui restart): " reloadCmd
|
||
|
|
LOGI "Your reloadcmd is: ${reloadCmd}"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
LOGI "Keep default reloadcmd"
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
fi
|
||
|
|
|
||
|
|
# install the certificate
|
||
|
|
~/.acme.sh/acme.sh --installcert -d ${domain} \
|
||
|
|
--key-file /root/cert/${domain}/privkey.pem \
|
||
|
|
--fullchain-file /root/cert/${domain}/fullchain.pem --reloadcmd "${reloadCmd}"
|
||
|
|
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Installing certificate failed, exiting."
|
||
|
|
rm -rf ~/.acme.sh/${domain}
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "Installing certificate succeeded, enabling auto renew..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# enable auto-renew
|
||
|
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Auto renew failed, certificate details:"
|
||
|
|
ls -lah cert/*
|
||
|
|
chmod 600 $certPath/privkey.pem
|
||
|
|
chmod 644 $certPath/fullchain.pem
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "Auto renew succeeded, certificate details:"
|
||
|
|
ls -lah cert/*
|
||
|
|
chmod 600 $certPath/privkey.pem
|
||
|
|
chmod 644 $certPath/fullchain.pem
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Prompt user to set panel paths after successful certificate installation
|
||
|
|
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||
|
|
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
||
|
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
||
|
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
||
|
|
|
||
|
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||
|
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||
|
|
LOGI "Panel paths set for domain: $domain"
|
||
|
|
LOGI " - Certificate File: $webCertFile"
|
||
|
|
LOGI " - Private Key File: $webKeyFile"
|
||
|
|
echo -e "${green}Access URL: https://${domain}:${existing_port}${existing_webBasePath}${plain}"
|
||
|
|
restart
|
||
|
|
else
|
||
|
|
LOGE "Error: Certificate or private key file not found for domain: $domain."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
LOGI "Skipping panel path setting."
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
ssl_cert_issue_CF() {
|
||
|
|
local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||
|
|
local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||
|
|
LOGI "****** Instructions for Use ******"
|
||
|
|
LOGI "Follow the steps below to complete the process:"
|
||
|
|
LOGI "1. Cloudflare Registered E-mail."
|
||
|
|
LOGI "2. Cloudflare Global API Key."
|
||
|
|
LOGI "3. The Domain Name."
|
||
|
|
LOGI "4. Once the certificate is issued, you will be prompted to set the certificate for the panel (optional)."
|
||
|
|
LOGI "5. The script also supports automatic renewal of the SSL certificate after installation."
|
||
|
|
|
||
|
|
confirm "Do you confirm the information and wish to proceed? [y/n]" "y"
|
||
|
|
|
||
|
|
if [ $? -eq 0 ]; then
|
||
|
|
# Check for acme.sh first
|
||
|
|
if ! command -v ~/.acme.sh/acme.sh &>/dev/null; then
|
||
|
|
echo "acme.sh could not be found. We will install it."
|
||
|
|
install_acme
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Install acme failed, please check logs."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
CF_Domain=""
|
||
|
|
|
||
|
|
LOGD "Please set a domain name:"
|
||
|
|
read -rp "Input your domain here: " CF_Domain
|
||
|
|
LOGD "Your domain name is set to: ${CF_Domain}"
|
||
|
|
|
||
|
|
# Set up Cloudflare API details
|
||
|
|
CF_GlobalKey=""
|
||
|
|
CF_AccountEmail=""
|
||
|
|
LOGD "Please set the API key:"
|
||
|
|
read -rp "Input your key here: " CF_GlobalKey
|
||
|
|
LOGD "Your API key is: ${CF_GlobalKey}"
|
||
|
|
|
||
|
|
LOGD "Please set up registered email:"
|
||
|
|
read -rp "Input your email here: " CF_AccountEmail
|
||
|
|
LOGD "Your registered email address is: ${CF_AccountEmail}"
|
||
|
|
|
||
|
|
# Set the default CA to Let's Encrypt
|
||
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Default CA, Let'sEncrypt fail, script exiting..."
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
export CF_Key="${CF_GlobalKey}"
|
||
|
|
export CF_Email="${CF_AccountEmail}"
|
||
|
|
|
||
|
|
# Issue the certificate using Cloudflare DNS
|
||
|
|
~/.acme.sh/acme.sh --issue --dns dns_cf -d ${CF_Domain} -d *.${CF_Domain} --log --force
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Certificate issuance failed, script exiting..."
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "Certificate issued successfully, Installing..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Install the certificate
|
||
|
|
certPath="/root/cert/${CF_Domain}"
|
||
|
|
if [ -d "$certPath" ]; then
|
||
|
|
rm -rf ${certPath}
|
||
|
|
fi
|
||
|
|
|
||
|
|
mkdir -p ${certPath}
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Failed to create directory: ${certPath}"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
reloadCmd="x-ui restart"
|
||
|
|
|
||
|
|
LOGI "Default --reloadcmd for ACME is: ${yellow}x-ui restart"
|
||
|
|
LOGI "This command will run on every certificate issue and renew."
|
||
|
|
read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
||
|
|
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
||
|
|
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; x-ui restart"
|
||
|
|
echo -e "${green}\t2.${plain} Input your own command"
|
||
|
|
echo -e "${green}\t0.${plain} Keep default reloadcmd"
|
||
|
|
read -rp "Choose an option: " choice
|
||
|
|
case "$choice" in
|
||
|
|
1)
|
||
|
|
LOGI "Reloadcmd is: systemctl reload nginx ; x-ui restart"
|
||
|
|
reloadCmd="systemctl reload nginx ; x-ui restart"
|
||
|
|
;;
|
||
|
|
2)
|
||
|
|
LOGD "It's recommended to put x-ui restart at the end, so it won't raise an error if other services fails"
|
||
|
|
read -rp "Please enter your reloadcmd (example: systemctl reload nginx ; x-ui restart): " reloadCmd
|
||
|
|
LOGI "Your reloadcmd is: ${reloadCmd}"
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
LOGI "Keep default reloadcmd"
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
fi
|
||
|
|
~/.acme.sh/acme.sh --installcert -d ${CF_Domain} -d *.${CF_Domain} \
|
||
|
|
--key-file ${certPath}/privkey.pem \
|
||
|
|
--fullchain-file ${certPath}/fullchain.pem --reloadcmd "${reloadCmd}"
|
||
|
|
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Certificate installation failed, script exiting..."
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "Certificate installed successfully, Turning on automatic updates..."
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Enable auto-update
|
||
|
|
~/.acme.sh/acme.sh --upgrade --auto-upgrade
|
||
|
|
if [ $? -ne 0 ]; then
|
||
|
|
LOGE "Auto update setup failed, script exiting..."
|
||
|
|
exit 1
|
||
|
|
else
|
||
|
|
LOGI "The certificate is installed and auto-renewal is turned on. Specific information is as follows:"
|
||
|
|
ls -lah ${certPath}/*
|
||
|
|
chmod 600 ${certPath}/privkey.pem
|
||
|
|
chmod 644 ${certPath}/fullchain.pem
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Prompt user to set panel paths after successful certificate installation
|
||
|
|
read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
||
|
|
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
||
|
|
local webCertFile="${certPath}/fullchain.pem"
|
||
|
|
local webKeyFile="${certPath}/privkey.pem"
|
||
|
|
|
||
|
|
if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
|
||
|
|
${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
|
||
|
|
LOGI "Panel paths set for domain: $CF_Domain"
|
||
|
|
LOGI " - Certificate File: $webCertFile"
|
||
|
|
LOGI " - Private Key File: $webKeyFile"
|
||
|
|
echo -e "${green}Access URL: https://${CF_Domain}:${existing_port}${existing_webBasePath}${plain}"
|
||
|
|
restart
|
||
|
|
else
|
||
|
|
LOGE "Error: Certificate or private key file not found for domain: $CF_Domain."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
LOGI "Skipping panel path setting."
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
show_menu
|
||
|
|
fi
|
||
|
|
}
|