|
@@ -42,6 +42,16 @@ arch() {
|
|
|
|
|
|
|
|
echo "Arch: $(arch)"
|
|
echo "Arch: $(arch)"
|
|
|
|
|
|
|
|
|
|
+# Non-interactive mode: triggered explicitly via XUI_NONINTERACTIVE=1, or
|
|
|
|
|
+# implicitly when stdin is not a TTY (e.g. `curl ... | bash`, cloud-init).
|
|
|
|
|
+# In this mode every prompt below is replaced by an env var or a sane default.
|
|
|
|
|
+if [[ "${XUI_NONINTERACTIVE:-0}" == "1" ]] || [[ ! -t 0 ]]; then
|
|
|
|
|
+ NONINTERACTIVE=1
|
|
|
|
|
+else
|
|
|
|
|
+ NONINTERACTIVE=0
|
|
|
|
|
+fi
|
|
|
|
|
+export NONINTERACTIVE
|
|
|
|
|
+
|
|
|
# Simple helpers
|
|
# Simple helpers
|
|
|
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
|
|
@@ -122,6 +132,54 @@ gen_random_string() {
|
|
|
| head -c "$length"
|
|
| head -c "$length"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+# prompt_or_default VARNAME "prompt text" "default" [ENV_NAME]
|
|
|
|
|
+# Interactive: read into VARNAME. Non-interactive: VARNAME = ${ENV_NAME:-default}.
|
|
|
|
|
+# ENV_NAME defaults to VARNAME when omitted. Keeps every interactive prompt
|
|
|
|
|
+# string byte-for-byte identical to the original `read -rp`.
|
|
|
|
|
+prompt_or_default() {
|
|
|
|
|
+ local __var="$1" __prompt="$2" __default="$3" __env="${4:-$1}"
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ printf -v "$__var" '%s' "${!__env:-$__default}"
|
|
|
|
|
+ else
|
|
|
|
|
+ # shellcheck disable=SC2229
|
|
|
|
|
+ read -rp "$__prompt" "$__var"
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# write_install_result <user> <pass> <port> <webpath> <scheme> <host> <token> <dbtype>
|
|
|
|
|
+# Persists a parseable, root-only credentials file consumed by cloud-init/MOTD.
|
|
|
|
|
+# Values are written with printf '%q' so a pinned password/username containing
|
|
|
|
|
+# spaces, quotes, $(...) or backticks is shell-escaped and the file stays safely
|
|
|
|
|
+# source-able (consumers do '. install-result.env'). For the alphanumeric random
|
|
|
|
|
+# values gen_random_string emits, %q is a no-op. This is a DIFFERENT file from the
|
|
|
|
|
+# Postgres env file (/etc/default/x-ui).
|
|
|
|
|
+write_install_result() {
|
|
|
|
|
+ local u="$1" p="$2" port="$3" wbp="$4" scheme="$5" host="$6" token="$7" dbtype="$8"
|
|
|
|
|
+ local result_file="/etc/x-ui/install-result.env"
|
|
|
|
|
+ local url_host="${host:-SERVER_IP_UNKNOWN}"
|
|
|
|
|
+ install -d -m 755 /etc/x-ui 2> /dev/null
|
|
|
|
|
+ local prev_umask
|
|
|
|
|
+ prev_umask=$(umask)
|
|
|
|
|
+ umask 077
|
|
|
|
|
+ if ! {
|
|
|
|
|
+ printf 'XUI_USERNAME=%q\n' "$u"
|
|
|
|
|
+ printf 'XUI_PASSWORD=%q\n' "$p"
|
|
|
|
|
+ printf 'XUI_PANEL_PORT=%q\n' "$port"
|
|
|
|
|
+ printf 'XUI_WEB_BASE_PATH=%q\n' "$wbp"
|
|
|
|
|
+ printf 'XUI_ACCESS_URL=%q\n' "${scheme}://${url_host}:${port}/${wbp}"
|
|
|
|
|
+ printf 'XUI_API_TOKEN=%q\n' "$token"
|
|
|
|
|
+ printf 'XUI_DB_TYPE=%q\n' "$dbtype"
|
|
|
|
|
+ } > "$result_file"; then
|
|
|
|
|
+ umask "$prev_umask"
|
|
|
|
|
+ echo -e "${yellow}Warning: failed to write ${result_file}.${plain}" >&2
|
|
|
|
|
+ return 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ umask "$prev_umask"
|
|
|
|
|
+ chmod 600 "$result_file" 2> /dev/null
|
|
|
|
|
+ chown root:root "$result_file" 2> /dev/null || true
|
|
|
|
|
+ echo -e "${green}Install result written to ${result_file} (mode 600).${plain}"
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
install_postgres_local() {
|
|
install_postgres_local() {
|
|
|
local pg_user pg_pass
|
|
local pg_user pg_pass
|
|
|
pg_pass=$(gen_random_string 24)
|
|
pg_pass=$(gen_random_string 24)
|
|
@@ -391,7 +449,7 @@ setup_ip_certificate() {
|
|
|
|
|
|
|
|
# Choose port for HTTP-01 listener (default 80, prompt override)
|
|
# Choose port for HTTP-01 listener (default 80, prompt override)
|
|
|
local WebPort=""
|
|
local WebPort=""
|
|
|
- read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
|
|
|
|
|
|
|
+ prompt_or_default WebPort "Port to use for ACME HTTP-01 listener (default 80): " "80" XUI_ACME_HTTP_PORT
|
|
|
WebPort="${WebPort:-80}"
|
|
WebPort="${WebPort:-80}"
|
|
|
if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
|
|
if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
|
|
|
echo -e "${red}Invalid port provided. Falling back to 80.${plain}"
|
|
echo -e "${red}Invalid port provided. Falling back to 80.${plain}"
|
|
@@ -408,6 +466,10 @@ setup_ip_certificate() {
|
|
|
echo -e "${yellow}Port ${WebPort} is in use.${plain}"
|
|
echo -e "${yellow}Port ${WebPort} is in use.${plain}"
|
|
|
|
|
|
|
|
local alt_port=""
|
|
local alt_port=""
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ echo -e "${red}Port ${WebPort} is busy; cannot proceed in non-interactive mode.${plain}"
|
|
|
|
|
+ return 1
|
|
|
|
|
+ fi
|
|
|
read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
|
|
read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
|
|
|
alt_port="${alt_port// /}"
|
|
alt_port="${alt_port// /}"
|
|
|
if [[ -z "${alt_port}" ]]; then
|
|
if [[ -z "${alt_port}" ]]; then
|
|
@@ -429,6 +491,7 @@ setup_ip_certificate() {
|
|
|
# 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 --force > /dev/null 2>&1
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
|
|
|
|
|
+ [[ -n "${XUI_ACME_EMAIL:-}" ]] && ~/.acme.sh/acme.sh --register-account -m "${XUI_ACME_EMAIL}" > /dev/null 2>&1
|
|
|
|
|
|
|
|
~/.acme.sh/acme.sh --issue \
|
|
~/.acme.sh/acme.sh --issue \
|
|
|
${domain_args} \
|
|
${domain_args} \
|
|
@@ -517,22 +580,30 @@ ssl_cert_issue() {
|
|
|
|
|
|
|
|
# get the domain here, and we need to verify it
|
|
# get the domain here, and we need to verify it
|
|
|
local domain=""
|
|
local domain=""
|
|
|
- while true; do
|
|
|
|
|
- read -rp "Please enter your domain name: " domain
|
|
|
|
|
- domain="${domain// /}" # Trim whitespace
|
|
|
|
|
-
|
|
|
|
|
- if [[ -z "$domain" ]]; then
|
|
|
|
|
- echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ domain="${XUI_DOMAIN// /}"
|
|
|
|
|
+ if [[ -z "$domain" ]] || ! is_domain "$domain"; then
|
|
|
|
|
+ echo -e "${red}XUI_SSL_MODE=domain requires a valid XUI_DOMAIN (got: '${XUI_DOMAIN:-}').${plain}"
|
|
|
|
|
+ return 1
|
|
|
fi
|
|
fi
|
|
|
|
|
+ else
|
|
|
|
|
+ while true; do
|
|
|
|
|
+ read -rp "Please enter your domain name: " domain
|
|
|
|
|
+ domain="${domain// /}" # Trim whitespace
|
|
|
|
|
|
|
|
- if ! is_domain "$domain"; then
|
|
|
|
|
- echo -e "${red}Invalid domain format: ${domain}. Please enter a valid domain name.${plain}"
|
|
|
|
|
- continue
|
|
|
|
|
- fi
|
|
|
|
|
|
|
+ if [[ -z "$domain" ]]; then
|
|
|
|
|
+ echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
|
|
|
|
|
+ continue
|
|
|
|
|
+ fi
|
|
|
|
|
|
|
|
- break
|
|
|
|
|
- done
|
|
|
|
|
|
|
+ if ! is_domain "$domain"; then
|
|
|
|
|
+ echo -e "${red}Invalid domain format: ${domain}. Please enter a valid domain name.${plain}"
|
|
|
|
|
+ continue
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ break
|
|
|
|
|
+ done
|
|
|
|
|
+ fi
|
|
|
echo -e "${green}Your domain is: ${domain}, checking it...${plain}"
|
|
echo -e "${green}Your domain is: ${domain}, checking it...${plain}"
|
|
|
SSL_ISSUED_DOMAIN="${domain}"
|
|
SSL_ISSUED_DOMAIN="${domain}"
|
|
|
|
|
|
|
@@ -574,7 +645,7 @@ ssl_cert_issue() {
|
|
|
|
|
|
|
|
# get the port number for the standalone server
|
|
# get the port number for the standalone server
|
|
|
local WebPort=80
|
|
local WebPort=80
|
|
|
- read -rp "Please choose which port to use (default is 80): " WebPort
|
|
|
|
|
|
|
+ prompt_or_default WebPort "Please choose which port to use (default is 80): " "80" XUI_ACME_HTTP_PORT
|
|
|
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
|
if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
|
|
|
echo -e "${yellow}Your input ${WebPort} is invalid, will use default port 80.${plain}"
|
|
echo -e "${yellow}Your input ${WebPort} is invalid, will use default port 80.${plain}"
|
|
|
WebPort=80
|
|
WebPort=80
|
|
@@ -588,6 +659,7 @@ ssl_cert_issue() {
|
|
|
if [[ ${cert_exists} -eq 0 ]]; then
|
|
if [[ ${cert_exists} -eq 0 ]]; then
|
|
|
# issue the certificate
|
|
# issue the certificate
|
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
|
|
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
|
|
|
|
|
+ [[ -n "${XUI_ACME_EMAIL:-}" ]] && ~/.acme.sh/acme.sh --register-account -m "${XUI_ACME_EMAIL}" > /dev/null 2>&1
|
|
|
~/.acme.sh/acme.sh --issue -d ${domain} $(acme_listen_flag) --standalone --httpport ${WebPort} --force
|
|
~/.acme.sh/acme.sh --issue -d ${domain} $(acme_listen_flag) --standalone --httpport ${WebPort} --force
|
|
|
if [ $? -ne 0 ]; then
|
|
if [ $? -ne 0 ]; then
|
|
|
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
|
echo -e "${red}Issuing certificate failed, please check logs.${plain}"
|
|
@@ -605,7 +677,11 @@ ssl_cert_issue() {
|
|
|
reloadCmd="systemctl restart x-ui || rc-service x-ui restart"
|
|
reloadCmd="systemctl restart x-ui || rc-service x-ui restart"
|
|
|
echo -e "${green}Default --reloadcmd for ACME is: ${yellow}systemctl restart x-ui || rc-service x-ui restart${plain}"
|
|
echo -e "${green}Default --reloadcmd for ACME is: ${yellow}systemctl restart x-ui || rc-service x-ui restart${plain}"
|
|
|
echo -e "${green}This command will run on every certificate issue and renew.${plain}"
|
|
echo -e "${green}This command will run on every certificate issue and renew.${plain}"
|
|
|
- read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ setReloadcmd="n"
|
|
|
|
|
+ else
|
|
|
|
|
+ read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
|
|
|
|
|
+ fi
|
|
|
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
|
if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
|
|
|
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; systemctl restart x-ui"
|
|
echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; systemctl restart x-ui"
|
|
|
echo -e "${green}\t2.${plain} Input your own command"
|
|
echo -e "${green}\t2.${plain} Input your own command"
|
|
@@ -671,7 +747,11 @@ ssl_cert_issue() {
|
|
|
systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
|
|
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
|
|
# 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 [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ setPanel="y"
|
|
|
|
|
+ else
|
|
|
|
|
+ read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
|
|
|
|
|
+ fi
|
|
|
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
|
if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
|
|
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
|
local webCertFile="/root/cert/${domain}/fullchain.pem"
|
|
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
|
local webKeyFile="/root/cert/${domain}/privkey.pem"
|
|
@@ -712,12 +792,24 @@ prompt_and_setup_ssl() {
|
|
|
echo -e "${green}4.${plain} Skip SSL (advanced — behind reverse proxy / SSH tunnel only)"
|
|
echo -e "${green}4.${plain} Skip SSL (advanced — behind reverse proxy / SSH tunnel only)"
|
|
|
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
|
echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
|
|
|
echo -e "${blue}Note:${plain} Option 4 serves the panel over plain HTTP — only safe behind nginx/Caddy or an SSH tunnel."
|
|
echo -e "${blue}Note:${plain} Option 4 serves the panel over plain HTTP — only safe behind nginx/Caddy or an SSH tunnel."
|
|
|
- read -rp "Choose an option (default 2 for IP): " ssl_choice
|
|
|
|
|
- ssl_choice="${ssl_choice// /}" # Trim whitespace
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ case "${XUI_SSL_MODE:-none}" in
|
|
|
|
|
+ domain) ssl_choice="1" ;;
|
|
|
|
|
+ ip) ssl_choice="2" ;;
|
|
|
|
|
+ none | "") ssl_choice="4" ;;
|
|
|
|
|
+ *)
|
|
|
|
|
+ echo -e "${yellow}Unknown XUI_SSL_MODE='${XUI_SSL_MODE}', defaulting to none (HTTP).${plain}"
|
|
|
|
|
+ ssl_choice="4"
|
|
|
|
|
+ ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+ else
|
|
|
|
|
+ read -rp "Choose an option (default 2 for IP): " ssl_choice
|
|
|
|
|
+ ssl_choice="${ssl_choice// /}" # Trim whitespace
|
|
|
|
|
|
|
|
- # Default to 2 (IP cert) if input is empty or invalid (not 1, 3 or 4)
|
|
|
|
|
- if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" && "$ssl_choice" != "4" ]]; then
|
|
|
|
|
- ssl_choice="2"
|
|
|
|
|
|
|
+ # Default to 2 (IP cert) if input is empty or invalid (not 1, 3 or 4)
|
|
|
|
|
+ if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" && "$ssl_choice" != "4" ]]; then
|
|
|
|
|
+ ssl_choice="2"
|
|
|
|
|
+ fi
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
case "$ssl_choice" in
|
|
case "$ssl_choice" in
|
|
@@ -748,7 +840,7 @@ prompt_and_setup_ssl() {
|
|
|
|
|
|
|
|
# Ask for optional IPv6
|
|
# Ask for optional IPv6
|
|
|
local ipv6_addr=""
|
|
local ipv6_addr=""
|
|
|
- read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
|
|
|
|
|
|
|
+ prompt_or_default ipv6_addr "Do you have an IPv6 address to include? (leave empty to skip): " "" XUI_SSL_IPV6
|
|
|
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
|
ipv6_addr="${ipv6_addr// /}" # Trim whitespace
|
|
|
|
|
|
|
|
# Stop panel if running (port 80 needed)
|
|
# Stop panel if running (port 80 needed)
|
|
@@ -840,7 +932,12 @@ prompt_and_setup_ssl() {
|
|
|
SSL_HOST="${server_ip}"
|
|
SSL_HOST="${server_ip}"
|
|
|
|
|
|
|
|
local bind_local=""
|
|
local bind_local=""
|
|
|
- read -rp "Bind the panel to 127.0.0.1 only? (recommended — forces SSH tunnel / reverse-proxy access) [y/N]: " bind_local
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ # Cloud images must stay reachable on their public interface.
|
|
|
|
|
+ bind_local="n"
|
|
|
|
|
+ else
|
|
|
|
|
+ read -rp "Bind the panel to 127.0.0.1 only? (recommended — forces SSH tunnel / reverse-proxy access) [y/N]: " bind_local
|
|
|
|
|
+ fi
|
|
|
if [[ "$bind_local" == "y" || "$bind_local" == "Y" ]]; then
|
|
if [[ "$bind_local" == "y" || "$bind_local" == "Y" ]]; then
|
|
|
${xui_folder}/x-ui setting -listenIP "127.0.0.1" > /dev/null 2>&1
|
|
${xui_folder}/x-ui setting -listenIP "127.0.0.1" > /dev/null 2>&1
|
|
|
SSL_HOST="127.0.0.1"
|
|
SSL_HOST="127.0.0.1"
|
|
@@ -895,22 +992,29 @@ config_after_install() {
|
|
|
done
|
|
done
|
|
|
|
|
|
|
|
if [[ -z "$server_ip" ]]; then
|
|
if [[ -z "$server_ip" ]]; then
|
|
|
- echo -e "${yellow}Could not auto-detect server IP from any provider.${plain}"
|
|
|
|
|
- while [[ -z "$server_ip" ]]; do
|
|
|
|
|
- read -rp "Please enter your server's public IPv4 address: " server_ip
|
|
|
|
|
- server_ip="${server_ip// /}"
|
|
|
|
|
- if [[ ! "$server_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
|
|
|
- echo -e "${red}Invalid IPv4 address. Please try again.${plain}"
|
|
|
|
|
- server_ip=""
|
|
|
|
|
- fi
|
|
|
|
|
- done
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ # Panel binds 0.0.0.0 regardless; the IP is only used to compose the
|
|
|
|
|
+ # displayed access URL. Fall back to XUI_SERVER_IP or leave blank.
|
|
|
|
|
+ server_ip="${XUI_SERVER_IP:-}"
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e "${yellow}Could not auto-detect server IP from any provider.${plain}"
|
|
|
|
|
+ while [[ -z "$server_ip" ]]; do
|
|
|
|
|
+ read -rp "Please enter your server's public IPv4 address: " server_ip
|
|
|
|
|
+ server_ip="${server_ip// /}"
|
|
|
|
|
+ if [[ ! "$server_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
|
|
|
+ echo -e "${red}Invalid IPv4 address. Please try again.${plain}"
|
|
|
|
|
+ server_ip=""
|
|
|
|
|
+ fi
|
|
|
|
|
+ done
|
|
|
|
|
+ fi
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
|
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
|
|
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
|
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
|
|
- local config_webBasePath=$(gen_random_string 18)
|
|
|
|
|
- local config_username=$(gen_random_string 10)
|
|
|
|
|
- local config_password=$(gen_random_string 10)
|
|
|
|
|
|
|
+ local config_webBasePath="${XUI_WEB_BASE_PATH:-$(gen_random_string 18)}"
|
|
|
|
|
+ local config_username="${XUI_USERNAME:-$(gen_random_string 10)}"
|
|
|
|
|
+ local config_password="${XUI_PASSWORD:-$(gen_random_string 10)}"
|
|
|
|
|
+ local config_port=""
|
|
|
|
|
|
|
|
local db_label="SQLite (/etc/x-ui/x-ui.db)"
|
|
local db_label="SQLite (/etc/x-ui/x-ui.db)"
|
|
|
echo ""
|
|
echo ""
|
|
@@ -919,8 +1023,16 @@ config_after_install() {
|
|
|
echo -e "${green}═══════════════════════════════════════════${plain}"
|
|
echo -e "${green}═══════════════════════════════════════════${plain}"
|
|
|
echo -e " 1) SQLite (default — recommended for < 500 clients)"
|
|
echo -e " 1) SQLite (default — recommended for < 500 clients)"
|
|
|
echo -e " 2) PostgreSQL (recommended for high client counts / many nodes)"
|
|
echo -e " 2) PostgreSQL (recommended for high client counts / many nodes)"
|
|
|
- read -rp "Choose [1]: " db_choice
|
|
|
|
|
- db_choice="${db_choice:-1}"
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ if [[ "${XUI_DB_TYPE:-sqlite}" == "postgres" ]]; then
|
|
|
|
|
+ db_choice="2"
|
|
|
|
|
+ else
|
|
|
|
|
+ db_choice="1"
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ read -rp "Choose [1]: " db_choice
|
|
|
|
|
+ db_choice="${db_choice:-1}"
|
|
|
|
|
+ fi
|
|
|
if [[ "$db_choice" == "2" ]]; then
|
|
if [[ "$db_choice" == "2" ]]; then
|
|
|
local xui_env_file
|
|
local xui_env_file
|
|
|
case "${release}" in
|
|
case "${release}" in
|
|
@@ -939,6 +1051,30 @@ config_after_install() {
|
|
|
local pg_mode=""
|
|
local pg_mode=""
|
|
|
local pg_local_installed=0
|
|
local pg_local_installed=0
|
|
|
while [[ -z "$xui_dsn" ]]; do
|
|
while [[ -z "$xui_dsn" ]]; do
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ if [[ -n "${XUI_DB_DSN:-}" ]]; then
|
|
|
|
|
+ xui_dsn="${XUI_DB_DSN}"
|
|
|
|
|
+ db_label="PostgreSQL (external)"
|
|
|
|
|
+ break
|
|
|
|
|
+ fi
|
|
|
|
|
+ echo -e "${yellow}Installing PostgreSQL locally (non-interactive)...${plain}"
|
|
|
|
|
+ local pg_cred_file
|
|
|
|
|
+ pg_cred_file=$(mktemp 2> /dev/null) || pg_cred_file=$(mktemp -t x-ui-pg-creds.XXXXXXXX)
|
|
|
|
|
+ if [[ -n "${pg_cred_file}" ]] && xui_dsn=$(PG_CRED_FILE="${pg_cred_file}" install_postgres_local); then
|
|
|
|
|
+ pg_local_installed=1
|
|
|
|
|
+ if [[ -r "${pg_cred_file}" ]]; then
|
|
|
|
|
+ # shellcheck disable=SC1090
|
|
|
|
|
+ source "${pg_cred_file}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ rm -f "${pg_cred_file}"
|
|
|
|
|
+ db_label="PostgreSQL (${PG_USER}@${PG_HOST}:${PG_PORT}/${PG_DB})"
|
|
|
|
|
+ break
|
|
|
|
|
+ fi
|
|
|
|
|
+ rm -f "${pg_cred_file}"
|
|
|
|
|
+ echo -e "${red}PostgreSQL installation failed in non-interactive mode; aborting.${plain}"
|
|
|
|
|
+ echo -e "${yellow}Set XUI_DB_DSN to use an existing server, or XUI_DB_TYPE=sqlite.${plain}"
|
|
|
|
|
+ exit 1
|
|
|
|
|
+ fi
|
|
|
echo ""
|
|
echo ""
|
|
|
echo -e " 1) Install PostgreSQL locally and create a dedicated user/db (recommended)"
|
|
echo -e " 1) Install PostgreSQL locally and create a dedicated user/db (recommended)"
|
|
|
echo -e " 2) Use an existing PostgreSQL server (enter DSN)"
|
|
echo -e " 2) Use an existing PostgreSQL server (enter DSN)"
|
|
@@ -1008,13 +1144,23 @@ EOF
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
- read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
|
|
|
|
- if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
|
|
|
|
- read -rp "Please set up the panel port: " config_port
|
|
|
|
|
- echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
|
|
|
|
|
|
+ if [[ "$NONINTERACTIVE" == "1" ]]; then
|
|
|
|
|
+ if [[ -n "${XUI_PANEL_PORT:-}" ]]; then
|
|
|
|
|
+ config_port="${XUI_PANEL_PORT}"
|
|
|
|
|
+ echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
|
|
|
|
+ else
|
|
|
|
|
+ config_port=$(shuf -i 1024-62000 -n 1)
|
|
|
|
|
+ echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
|
|
|
|
+ fi
|
|
|
else
|
|
else
|
|
|
- local config_port=$(shuf -i 1024-62000 -n 1)
|
|
|
|
|
- echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
|
|
|
|
|
|
+ read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
|
|
|
|
+ if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
|
|
|
|
+ read -rp "Please set up the panel port: " config_port
|
|
|
|
|
+ echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
|
|
|
|
+ else
|
|
|
|
|
+ config_port=$(shuf -i 1024-62000 -n 1)
|
|
|
|
|
+ echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
|
|
|
|
+ fi
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
|
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
|
@@ -1081,6 +1227,14 @@ EOF
|
|
|
echo -e "${yellow}⚠ Save the password — it is not stored anywhere else in plain text.${plain}"
|
|
echo -e "${yellow}⚠ Save the password — it is not stored anywhere else in plain text.${plain}"
|
|
|
unset PG_USER PG_PASS PG_HOST PG_PORT PG_DB
|
|
unset PG_USER PG_PASS PG_HOST PG_PORT PG_DB
|
|
|
fi
|
|
fi
|
|
|
|
|
+
|
|
|
|
|
+ # Persist a machine-parseable credentials file for cloud-init / MOTD.
|
|
|
|
|
+ : "${SSL_SCHEME:=https}"
|
|
|
|
|
+ : "${SSL_HOST:=${server_ip}}"
|
|
|
|
|
+ local db_type_out="sqlite"
|
|
|
|
|
+ [[ "$db_choice" == "2" ]] && db_type_out="postgres"
|
|
|
|
|
+ write_install_result "${config_username}" "${config_password}" "${config_port}" \
|
|
|
|
|
+ "${config_webBasePath}" "${SSL_SCHEME}" "${SSL_HOST}" "${config_apiToken}" "${db_type_out}"
|
|
|
else
|
|
else
|
|
|
local config_webBasePath=$(gen_random_string 18)
|
|
local config_webBasePath=$(gen_random_string 18)
|
|
|
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
|
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
|
@@ -1104,8 +1258,8 @@ EOF
|
|
|
fi
|
|
fi
|
|
|
else
|
|
else
|
|
|
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
|
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
|
|
- local config_username=$(gen_random_string 10)
|
|
|
|
|
- local config_password=$(gen_random_string 10)
|
|
|
|
|
|
|
+ local config_username="${XUI_USERNAME:-$(gen_random_string 10)}"
|
|
|
|
|
+ local config_password="${XUI_PASSWORD:-$(gen_random_string 10)}"
|
|
|
|
|
|
|
|
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
|
|
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
|
|
|
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}"
|
|
${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}"
|
|
@@ -1114,6 +1268,14 @@ EOF
|
|
|
echo -e "${green}Username: ${config_username}${plain}"
|
|
echo -e "${green}Username: ${config_username}${plain}"
|
|
|
echo -e "${green}Password: ${config_password}${plain}"
|
|
echo -e "${green}Password: ${config_password}${plain}"
|
|
|
echo -e "###############################################"
|
|
echo -e "###############################################"
|
|
|
|
|
+
|
|
|
|
|
+ # Persist a machine-parseable credentials file for cloud-init / MOTD.
|
|
|
|
|
+ local config_apiToken
|
|
|
|
|
+ config_apiToken=$(${xui_folder}/x-ui setting -getApiToken true | grep -Eo 'apiToken: .+' | awk '{print $2}')
|
|
|
|
|
+ : "${SSL_SCHEME:=https}"
|
|
|
|
|
+ : "${SSL_HOST:=${server_ip}}"
|
|
|
|
|
+ write_install_result "${config_username}" "${config_password}" "${existing_port}" \
|
|
|
|
|
+ "${existing_webBasePath}" "${SSL_SCHEME}" "${SSL_HOST}" "${config_apiToken}" "${XUI_DB_TYPE:-sqlite}"
|
|
|
else
|
|
else
|
|
|
echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}"
|
|
echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}"
|
|
|
fi
|
|
fi
|