install.sh 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. #!/bin/bash
  2. red='\033[0;31m'
  3. green='\033[0;32m'
  4. blue='\033[0;34m'
  5. yellow='\033[0;33m'
  6. plain='\033[0m'
  7. cur_dir=$(pwd)
  8. xui_folder="${XUI_MAIN_FOLDER:=/usr/local/x-ui}"
  9. xui_service="${XUI_SERVICE:=/etc/systemd/system}"
  10. # check root
  11. [[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
  12. # Check OS and set release variable
  13. if [[ -f /etc/os-release ]]; then
  14. source /etc/os-release
  15. release=$ID
  16. elif [[ -f /usr/lib/os-release ]]; then
  17. source /usr/lib/os-release
  18. release=$ID
  19. else
  20. echo "Failed to check the system OS, please contact the author!" >&2
  21. exit 1
  22. fi
  23. echo "The OS release is: $release"
  24. arch() {
  25. case "$(uname -m)" in
  26. x86_64 | x64 | amd64) echo 'amd64' ;;
  27. i*86 | x86) echo '386' ;;
  28. armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
  29. armv7* | armv7 | arm) echo 'armv7' ;;
  30. armv6* | armv6) echo 'armv6' ;;
  31. armv5* | armv5) echo 'armv5' ;;
  32. s390x) echo 's390x' ;;
  33. *) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
  34. esac
  35. }
  36. echo "Arch: $(arch)"
  37. # Simple helpers
  38. is_ipv4() {
  39. [[ "$1" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] && return 0 || return 1
  40. }
  41. is_ipv6() {
  42. [[ "$1" =~ : ]] && return 0 || return 1
  43. }
  44. is_ip() {
  45. is_ipv4 "$1" || is_ipv6 "$1"
  46. }
  47. is_domain() {
  48. [[ "$1" =~ ^([A-Za-z0-9](-*[A-Za-z0-9])*\.)+(xn--[a-z0-9]{2,}|[A-Za-z]{2,})$ ]] && return 0 || return 1
  49. }
  50. # Port helpers
  51. is_port_in_use() {
  52. local port="$1"
  53. if command -v ss > /dev/null 2>&1; then
  54. ss -ltn 2> /dev/null | awk -v p=":${port}$" '$4 ~ p {exit 0} END {exit 1}'
  55. return
  56. fi
  57. if command -v netstat > /dev/null 2>&1; then
  58. netstat -lnt 2> /dev/null | awk -v p=":${port} " '$4 ~ p {exit 0} END {exit 1}'
  59. return
  60. fi
  61. if command -v lsof > /dev/null 2>&1; then
  62. lsof -nP -iTCP:${port} -sTCP:LISTEN > /dev/null 2>&1 && return 0
  63. fi
  64. return 1
  65. }
  66. install_base() {
  67. case "${release}" in
  68. ubuntu | debian | armbian)
  69. apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
  70. ;;
  71. fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
  72. dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
  73. ;;
  74. centos)
  75. if [[ "${VERSION_ID}" =~ ^7 ]]; then
  76. yum -y update && yum install -y cronie curl tar tzdata socat ca-certificates openssl
  77. else
  78. dnf -y update && dnf install -y -q cronie curl tar tzdata socat ca-certificates openssl
  79. fi
  80. ;;
  81. arch | manjaro | parch)
  82. pacman -Syu && pacman -Syu --noconfirm cronie curl tar tzdata socat ca-certificates openssl
  83. ;;
  84. opensuse-tumbleweed | opensuse-leap)
  85. zypper refresh && zypper -q install -y cron curl tar timezone socat ca-certificates openssl
  86. ;;
  87. alpine)
  88. apk update && apk add dcron curl tar tzdata socat ca-certificates openssl
  89. ;;
  90. *)
  91. apt-get update && apt-get install -y -q cron curl tar tzdata socat ca-certificates openssl
  92. ;;
  93. esac
  94. }
  95. gen_random_string() {
  96. local length="$1"
  97. openssl rand -base64 $((length * 2)) \
  98. | tr -dc 'a-zA-Z0-9' \
  99. | head -c "$length"
  100. }
  101. install_postgres_local() {
  102. local pg_user="xui"
  103. local pg_db="xui"
  104. local pg_pass
  105. pg_pass=$(gen_random_string 24)
  106. case "${release}" in
  107. ubuntu | debian | armbian)
  108. apt-get update >&2 && apt-get install -y -q postgresql >&2 || return 1
  109. ;;
  110. fedora | amzn | virtuozzo | rhel | almalinux | rocky | ol)
  111. dnf install -y -q postgresql-server postgresql-contrib >&2 || return 1
  112. [[ -d /var/lib/pgsql/data && -f /var/lib/pgsql/data/PG_VERSION ]] || postgresql-setup --initdb >&2 || return 1
  113. ;;
  114. centos)
  115. if [[ "${VERSION_ID}" =~ ^7 ]]; then
  116. yum install -y postgresql-server postgresql-contrib >&2 || return 1
  117. else
  118. dnf install -y -q postgresql-server postgresql-contrib >&2 || return 1
  119. fi
  120. [[ -d /var/lib/pgsql/data && -f /var/lib/pgsql/data/PG_VERSION ]] || postgresql-setup --initdb >&2 || return 1
  121. ;;
  122. arch | manjaro | parch)
  123. pacman -Syu --noconfirm postgresql >&2 || return 1
  124. if [[ ! -f /var/lib/postgres/data/PG_VERSION ]]; then
  125. sudo -u postgres initdb -D /var/lib/postgres/data >&2 || return 1
  126. fi
  127. ;;
  128. opensuse-tumbleweed | opensuse-leap)
  129. zypper -q install -y postgresql-server postgresql-contrib >&2 || return 1
  130. ;;
  131. alpine)
  132. apk add --no-cache postgresql postgresql-contrib >&2 || return 1
  133. if [[ ! -f /var/lib/postgresql/data/PG_VERSION ]]; then
  134. /etc/init.d/postgresql setup >&2 || return 1
  135. fi
  136. rc-update add postgresql default >&2 2> /dev/null || true
  137. rc-service postgresql start >&2 || return 1
  138. ;;
  139. *)
  140. echo -e "${red}Unsupported distro for automatic PostgreSQL install: ${release}${plain}" >&2
  141. return 1
  142. ;;
  143. esac
  144. if [[ "${release}" != "alpine" ]]; then
  145. systemctl enable --now postgresql >&2 || return 1
  146. fi
  147. # Wait briefly for the server to accept connections.
  148. local i
  149. for i in 1 2 3 4 5; do
  150. sudo -u postgres psql -tAc 'SELECT 1' > /dev/null 2>&1 && break
  151. sleep 1
  152. done
  153. # Idempotent role/db creation.
  154. sudo -u postgres psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${pg_user}'" 2> /dev/null \
  155. | grep -q 1 \
  156. || sudo -u postgres psql -c "CREATE USER ${pg_user} WITH PASSWORD '${pg_pass}';" >&2 || return 1
  157. sudo -u postgres psql -tAc "SELECT 1 FROM pg_database WHERE datname='${pg_db}'" 2> /dev/null \
  158. | grep -q 1 \
  159. || sudo -u postgres psql -c "CREATE DATABASE ${pg_db} OWNER ${pg_user};" >&2 || return 1
  160. sudo -u postgres psql -c "ALTER USER ${pg_user} WITH PASSWORD '${pg_pass}';" >&2 || return 1
  161. local pg_pass_enc
  162. pg_pass_enc=$(printf '%s' "${pg_pass}" | sed -e 's/%/%25/g' -e 's/:/%3A/g' -e 's/@/%40/g' -e 's|/|%2F|g' -e 's/?/%3F/g' -e 's/#/%23/g')
  163. echo "postgres://${pg_user}:${pg_pass_enc}@127.0.0.1:5432/${pg_db}?sslmode=disable"
  164. return 0
  165. }
  166. install_acme() {
  167. echo -e "${green}Installing acme.sh for SSL certificate management...${plain}"
  168. cd ~ || return 1
  169. curl -s https://get.acme.sh | sh > /dev/null 2>&1
  170. if [ $? -ne 0 ]; then
  171. echo -e "${red}Failed to install acme.sh${plain}"
  172. return 1
  173. else
  174. echo -e "${green}acme.sh installed successfully${plain}"
  175. fi
  176. return 0
  177. }
  178. setup_ssl_certificate() {
  179. local domain="$1"
  180. local server_ip="$2"
  181. local existing_port="$3"
  182. local existing_webBasePath="$4"
  183. echo -e "${green}Setting up SSL certificate...${plain}"
  184. # Check if acme.sh is installed
  185. if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
  186. install_acme
  187. if [ $? -ne 0 ]; then
  188. echo -e "${yellow}Failed to install acme.sh, skipping SSL setup${plain}"
  189. return 1
  190. fi
  191. fi
  192. # Create certificate directory
  193. local certPath="/root/cert/${domain}"
  194. mkdir -p "$certPath"
  195. # Issue certificate
  196. echo -e "${green}Issuing SSL certificate for ${domain}...${plain}"
  197. echo -e "${yellow}Note: Port 80 must be open and accessible from the internet${plain}"
  198. ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
  199. ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport 80 --force
  200. if [ $? -ne 0 ]; then
  201. echo -e "${yellow}Failed to issue certificate for ${domain}${plain}"
  202. echo -e "${yellow}Please ensure port 80 is open and try again later with: x-ui${plain}"
  203. rm -rf ~/.acme.sh/${domain} 2> /dev/null
  204. rm -rf "$certPath" 2> /dev/null
  205. return 1
  206. fi
  207. # Install certificate
  208. ~/.acme.sh/acme.sh --installcert -d ${domain} \
  209. --key-file /root/cert/${domain}/privkey.pem \
  210. --fullchain-file /root/cert/${domain}/fullchain.pem \
  211. --reloadcmd "systemctl restart x-ui" > /dev/null 2>&1
  212. if [ $? -ne 0 ]; then
  213. echo -e "${yellow}Failed to install certificate${plain}"
  214. return 1
  215. fi
  216. # Enable auto-renew
  217. ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
  218. # Secure permissions: private key readable only by owner
  219. chmod 600 $certPath/privkey.pem 2> /dev/null
  220. chmod 644 $certPath/fullchain.pem 2> /dev/null
  221. # Set certificate for panel
  222. local webCertFile="/root/cert/${domain}/fullchain.pem"
  223. local webKeyFile="/root/cert/${domain}/privkey.pem"
  224. if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
  225. ${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile" > /dev/null 2>&1
  226. echo -e "${green}SSL certificate installed and configured successfully!${plain}"
  227. return 0
  228. else
  229. echo -e "${yellow}Certificate files not found${plain}"
  230. return 1
  231. fi
  232. }
  233. # Issue Let's Encrypt IP certificate with shortlived profile (~6 days validity)
  234. # Requires acme.sh and port 80 open for HTTP-01 challenge
  235. setup_ip_certificate() {
  236. local ipv4="$1"
  237. local ipv6="$2" # optional
  238. echo -e "${green}Setting up Let's Encrypt IP certificate (shortlived profile)...${plain}"
  239. echo -e "${yellow}Note: IP certificates are valid for ~6 days and will auto-renew.${plain}"
  240. echo -e "${yellow}Default listener is port 80. If you choose another port, ensure external port 80 forwards to it.${plain}"
  241. # Check for acme.sh
  242. if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
  243. install_acme
  244. if [ $? -ne 0 ]; then
  245. echo -e "${red}Failed to install acme.sh${plain}"
  246. return 1
  247. fi
  248. fi
  249. # Validate IP address
  250. if [[ -z "$ipv4" ]]; then
  251. echo -e "${red}IPv4 address is required${plain}"
  252. return 1
  253. fi
  254. if ! is_ipv4 "$ipv4"; then
  255. echo -e "${red}Invalid IPv4 address: $ipv4${plain}"
  256. return 1
  257. fi
  258. # Create certificate directory
  259. local certDir="/root/cert/ip"
  260. mkdir -p "$certDir"
  261. # Build domain arguments
  262. local domain_args="-d ${ipv4}"
  263. if [[ -n "$ipv6" ]] && is_ipv6 "$ipv6"; then
  264. domain_args="${domain_args} -d ${ipv6}"
  265. echo -e "${green}Including IPv6 address: ${ipv6}${plain}"
  266. fi
  267. # Set reload command for auto-renewal (add || true so it doesn't fail during first install)
  268. local reloadCmd="systemctl restart x-ui 2>/dev/null || rc-service x-ui restart 2>/dev/null || true"
  269. # Choose port for HTTP-01 listener (default 80, prompt override)
  270. local WebPort=""
  271. read -rp "Port to use for ACME HTTP-01 listener (default 80): " WebPort
  272. WebPort="${WebPort:-80}"
  273. if ! [[ "${WebPort}" =~ ^[0-9]+$ ]] || ((WebPort < 1 || WebPort > 65535)); then
  274. echo -e "${red}Invalid port provided. Falling back to 80.${plain}"
  275. WebPort=80
  276. fi
  277. echo -e "${green}Using port ${WebPort} for standalone validation.${plain}"
  278. if [[ "${WebPort}" -ne 80 ]]; then
  279. echo -e "${yellow}Reminder: Let's Encrypt still connects on port 80; forward external port 80 to ${WebPort}.${plain}"
  280. fi
  281. # Ensure chosen port is available
  282. while true; do
  283. if is_port_in_use "${WebPort}"; then
  284. echo -e "${yellow}Port ${WebPort} is in use.${plain}"
  285. local alt_port=""
  286. read -rp "Enter another port for acme.sh standalone listener (leave empty to abort): " alt_port
  287. alt_port="${alt_port// /}"
  288. if [[ -z "${alt_port}" ]]; then
  289. echo -e "${red}Port ${WebPort} is busy; cannot proceed.${plain}"
  290. return 1
  291. fi
  292. if ! [[ "${alt_port}" =~ ^[0-9]+$ ]] || ((alt_port < 1 || alt_port > 65535)); then
  293. echo -e "${red}Invalid port provided.${plain}"
  294. return 1
  295. fi
  296. WebPort="${alt_port}"
  297. continue
  298. else
  299. echo -e "${green}Port ${WebPort} is free and ready for standalone validation.${plain}"
  300. break
  301. fi
  302. done
  303. # Issue certificate with shortlived profile
  304. echo -e "${green}Issuing IP certificate for ${ipv4}...${plain}"
  305. ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force > /dev/null 2>&1
  306. ~/.acme.sh/acme.sh --issue \
  307. ${domain_args} \
  308. --standalone \
  309. --server letsencrypt \
  310. --certificate-profile shortlived \
  311. --days 6 \
  312. --httpport ${WebPort} \
  313. --force
  314. if [ $? -ne 0 ]; then
  315. echo -e "${red}Failed to issue IP certificate${plain}"
  316. echo -e "${yellow}Please ensure port ${WebPort} is reachable (or forwarded from external port 80)${plain}"
  317. # Cleanup acme.sh data for both IPv4 and IPv6 if specified
  318. rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
  319. [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
  320. rm -rf ${certDir} 2> /dev/null
  321. return 1
  322. fi
  323. echo -e "${green}Certificate issued successfully, installing...${plain}"
  324. # Install certificate
  325. # Note: acme.sh may report "Reload error" and exit non-zero if reloadcmd fails,
  326. # but the cert files are still installed. We check for files instead of exit code.
  327. ~/.acme.sh/acme.sh --installcert -d ${ipv4} \
  328. --key-file "${certDir}/privkey.pem" \
  329. --fullchain-file "${certDir}/fullchain.pem" \
  330. --reloadcmd "${reloadCmd}" 2>&1 || true
  331. # Verify certificate files exist (don't rely on exit code - reloadcmd failure causes non-zero)
  332. if [[ ! -f "${certDir}/fullchain.pem" || ! -f "${certDir}/privkey.pem" ]]; then
  333. echo -e "${red}Certificate files not found after installation${plain}"
  334. # Cleanup acme.sh data for both IPv4 and IPv6 if specified
  335. rm -rf ~/.acme.sh/${ipv4} 2> /dev/null
  336. [[ -n "$ipv6" ]] && rm -rf ~/.acme.sh/${ipv6} 2> /dev/null
  337. rm -rf ${certDir} 2> /dev/null
  338. return 1
  339. fi
  340. echo -e "${green}Certificate files installed successfully${plain}"
  341. # Enable auto-upgrade for acme.sh (ensures cron job runs)
  342. ~/.acme.sh/acme.sh --upgrade --auto-upgrade > /dev/null 2>&1
  343. # Secure permissions: private key readable only by owner
  344. chmod 600 ${certDir}/privkey.pem 2> /dev/null
  345. chmod 644 ${certDir}/fullchain.pem 2> /dev/null
  346. # Configure panel to use the certificate
  347. echo -e "${green}Setting certificate paths for the panel...${plain}"
  348. ${xui_folder}/x-ui cert -webCert "${certDir}/fullchain.pem" -webCertKey "${certDir}/privkey.pem"
  349. if [ $? -ne 0 ]; then
  350. echo -e "${yellow}Warning: Could not set certificate paths automatically${plain}"
  351. echo -e "${yellow}Certificate files are at:${plain}"
  352. echo -e " Cert: ${certDir}/fullchain.pem"
  353. echo -e " Key: ${certDir}/privkey.pem"
  354. else
  355. echo -e "${green}Certificate paths configured successfully${plain}"
  356. fi
  357. echo -e "${green}IP certificate installed and configured successfully!${plain}"
  358. echo -e "${green}Certificate valid for ~6 days, auto-renews via acme.sh cron job.${plain}"
  359. echo -e "${yellow}acme.sh will automatically renew and reload x-ui before expiry.${plain}"
  360. return 0
  361. }
  362. # Comprehensive manual SSL certificate issuance via acme.sh
  363. ssl_cert_issue() {
  364. local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep 'webBasePath:' | awk -F': ' '{print $2}' | tr -d '[:space:]' | sed 's#^/##')
  365. local existing_port=$(${xui_folder}/x-ui setting -show true | grep 'port:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
  366. # check for acme.sh first
  367. if ! command -v ~/.acme.sh/acme.sh &> /dev/null; then
  368. echo "acme.sh could not be found. Installing now..."
  369. cd ~ || return 1
  370. curl -s https://get.acme.sh | sh
  371. if [ $? -ne 0 ]; then
  372. echo -e "${red}Failed to install acme.sh${plain}"
  373. return 1
  374. else
  375. echo -e "${green}acme.sh installed successfully${plain}"
  376. fi
  377. fi
  378. # get the domain here, and we need to verify it
  379. local domain=""
  380. while true; do
  381. read -rp "Please enter your domain name: " domain
  382. domain="${domain// /}" # Trim whitespace
  383. if [[ -z "$domain" ]]; then
  384. echo -e "${red}Domain name cannot be empty. Please try again.${plain}"
  385. continue
  386. fi
  387. if ! is_domain "$domain"; then
  388. echo -e "${red}Invalid domain format: ${domain}. Please enter a valid domain name.${plain}"
  389. continue
  390. fi
  391. break
  392. done
  393. echo -e "${green}Your domain is: ${domain}, checking it...${plain}"
  394. SSL_ISSUED_DOMAIN="${domain}"
  395. # detect existing certificate and reuse it if present
  396. local cert_exists=0
  397. if ~/.acme.sh/acme.sh --list 2> /dev/null | awk '{print $1}' | grep -Fxq "${domain}"; then
  398. cert_exists=1
  399. local certInfo=$(~/.acme.sh/acme.sh --list 2> /dev/null | grep -F "${domain}")
  400. echo -e "${yellow}Existing certificate found for ${domain}, will reuse it.${plain}"
  401. [[ -n "${certInfo}" ]] && echo "$certInfo"
  402. else
  403. echo -e "${green}Your domain is ready for issuing certificates now...${plain}"
  404. fi
  405. # create a directory for the certificate
  406. certPath="/root/cert/${domain}"
  407. if [ ! -d "$certPath" ]; then
  408. mkdir -p "$certPath"
  409. else
  410. rm -rf "$certPath"
  411. mkdir -p "$certPath"
  412. fi
  413. # get the port number for the standalone server
  414. local WebPort=80
  415. read -rp "Please choose which port to use (default is 80): " WebPort
  416. if [[ ${WebPort} -gt 65535 || ${WebPort} -lt 1 ]]; then
  417. echo -e "${yellow}Your input ${WebPort} is invalid, will use default port 80.${plain}"
  418. WebPort=80
  419. fi
  420. echo -e "${green}Will use port: ${WebPort} to issue certificates. Please make sure this port is open.${plain}"
  421. # Stop panel temporarily
  422. echo -e "${yellow}Stopping panel temporarily...${plain}"
  423. systemctl stop x-ui 2> /dev/null || rc-service x-ui stop 2> /dev/null
  424. if [[ ${cert_exists} -eq 0 ]]; then
  425. # issue the certificate
  426. ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt --force
  427. ~/.acme.sh/acme.sh --issue -d ${domain} --listen-v6 --standalone --httpport ${WebPort} --force
  428. if [ $? -ne 0 ]; then
  429. echo -e "${red}Issuing certificate failed, please check logs.${plain}"
  430. rm -rf ~/.acme.sh/${domain}
  431. systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
  432. return 1
  433. else
  434. echo -e "${green}Issuing certificate succeeded, installing certificates...${plain}"
  435. fi
  436. else
  437. echo -e "${green}Using existing certificate, installing certificates...${plain}"
  438. fi
  439. # Setup reload command
  440. reloadCmd="systemctl restart x-ui || rc-service x-ui restart"
  441. echo -e "${green}Default --reloadcmd for ACME is: ${yellow}systemctl restart x-ui || rc-service x-ui restart${plain}"
  442. echo -e "${green}This command will run on every certificate issue and renew.${plain}"
  443. read -rp "Would you like to modify --reloadcmd for ACME? (y/n): " setReloadcmd
  444. if [[ "$setReloadcmd" == "y" || "$setReloadcmd" == "Y" ]]; then
  445. echo -e "\n${green}\t1.${plain} Preset: systemctl reload nginx ; systemctl restart x-ui"
  446. echo -e "${green}\t2.${plain} Input your own command"
  447. echo -e "${green}\t0.${plain} Keep default reloadcmd"
  448. read -rp "Choose an option: " choice
  449. case "$choice" in
  450. 1)
  451. echo -e "${green}Reloadcmd is: systemctl reload nginx ; systemctl restart x-ui${plain}"
  452. reloadCmd="systemctl reload nginx ; systemctl restart x-ui"
  453. ;;
  454. 2)
  455. echo -e "${yellow}It's recommended to put x-ui restart at the end${plain}"
  456. read -rp "Please enter your custom reloadcmd: " reloadCmd
  457. echo -e "${green}Reloadcmd is: ${reloadCmd}${plain}"
  458. ;;
  459. *)
  460. echo -e "${green}Keeping default reloadcmd${plain}"
  461. ;;
  462. esac
  463. fi
  464. # install the certificate
  465. local installOutput=""
  466. installOutput=$(~/.acme.sh/acme.sh --installcert -d ${domain} \
  467. --key-file /root/cert/${domain}/privkey.pem \
  468. --fullchain-file /root/cert/${domain}/fullchain.pem --reloadcmd "${reloadCmd}" 2>&1)
  469. local installRc=$?
  470. echo "${installOutput}"
  471. local installWroteFiles=0
  472. if echo "${installOutput}" | grep -q "Installing key to:" && echo "${installOutput}" | grep -q "Installing full chain to:"; then
  473. installWroteFiles=1
  474. fi
  475. if [[ -f "/root/cert/${domain}/privkey.pem" && -f "/root/cert/${domain}/fullchain.pem" && (${installRc} -eq 0 || ${installWroteFiles} -eq 1) ]]; then
  476. echo -e "${green}Installing certificate succeeded, enabling auto renew...${plain}"
  477. else
  478. echo -e "${red}Installing certificate failed, exiting.${plain}"
  479. if [[ ${cert_exists} -eq 0 ]]; then
  480. rm -rf ~/.acme.sh/${domain}
  481. fi
  482. systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
  483. return 1
  484. fi
  485. # enable auto-renew
  486. ~/.acme.sh/acme.sh --upgrade --auto-upgrade
  487. if [ $? -ne 0 ]; then
  488. echo -e "${yellow}Auto renew setup had issues, certificate details:${plain}"
  489. ls -lah /root/cert/${domain}/
  490. # Secure permissions: private key readable only by owner
  491. chmod 600 $certPath/privkey.pem 2> /dev/null
  492. chmod 644 $certPath/fullchain.pem 2> /dev/null
  493. else
  494. echo -e "${green}Auto renew succeeded, certificate details:${plain}"
  495. ls -lah /root/cert/${domain}/
  496. # Secure permissions: private key readable only by owner
  497. chmod 600 $certPath/privkey.pem 2> /dev/null
  498. chmod 644 $certPath/fullchain.pem 2> /dev/null
  499. fi
  500. # start panel
  501. systemctl start x-ui 2> /dev/null || rc-service x-ui start 2> /dev/null
  502. # Prompt user to set panel paths after successful certificate installation
  503. read -rp "Would you like to set this certificate for the panel? (y/n): " setPanel
  504. if [[ "$setPanel" == "y" || "$setPanel" == "Y" ]]; then
  505. local webCertFile="/root/cert/${domain}/fullchain.pem"
  506. local webKeyFile="/root/cert/${domain}/privkey.pem"
  507. if [[ -f "$webCertFile" && -f "$webKeyFile" ]]; then
  508. ${xui_folder}/x-ui cert -webCert "$webCertFile" -webCertKey "$webKeyFile"
  509. echo -e "${green}Certificate paths set for the panel${plain}"
  510. echo -e "${green}Certificate File: $webCertFile${plain}"
  511. echo -e "${green}Private Key File: $webKeyFile${plain}"
  512. echo ""
  513. echo -e "${green}Access URL: https://${domain}:${existing_port}/${existing_webBasePath}${plain}"
  514. echo -e "${yellow}Panel will restart to apply SSL certificate...${plain}"
  515. systemctl restart x-ui 2> /dev/null || rc-service x-ui restart 2> /dev/null
  516. else
  517. echo -e "${red}Error: Certificate or private key file not found for domain: $domain.${plain}"
  518. fi
  519. else
  520. echo -e "${yellow}Skipping panel path setting.${plain}"
  521. fi
  522. return 0
  523. }
  524. # Reusable interactive SSL setup (domain or IP)
  525. # Sets global `SSL_HOST` to the chosen domain/IP for Access URL usage
  526. prompt_and_setup_ssl() {
  527. local panel_port="$1"
  528. local web_base_path="$2"
  529. local server_ip="$3"
  530. local ssl_choice=""
  531. SSL_SCHEME="https"
  532. echo -e "${yellow}Choose SSL certificate setup method:${plain}"
  533. echo -e "${green}1.${plain} Let's Encrypt for Domain (90-day validity, auto-renews)"
  534. echo -e "${green}2.${plain} Let's Encrypt for IP Address (6-day validity, auto-renews)"
  535. echo -e "${green}3.${plain} Custom SSL Certificate (Path to existing files)"
  536. echo -e "${green}4.${plain} Skip SSL (advanced — behind reverse proxy / SSH tunnel only)"
  537. echo -e "${blue}Note:${plain} Options 1 & 2 require port 80 open. Option 3 requires manual paths."
  538. echo -e "${blue}Note:${plain} Option 4 serves the panel over plain HTTP — only safe behind nginx/Caddy or an SSH tunnel."
  539. read -rp "Choose an option (default 2 for IP): " ssl_choice
  540. ssl_choice="${ssl_choice// /}" # Trim whitespace
  541. # Default to 2 (IP cert) if input is empty or invalid (not 1, 3 or 4)
  542. if [[ "$ssl_choice" != "1" && "$ssl_choice" != "3" && "$ssl_choice" != "4" ]]; then
  543. ssl_choice="2"
  544. fi
  545. case "$ssl_choice" in
  546. 1)
  547. # User chose Let's Encrypt domain option
  548. echo -e "${green}Using Let's Encrypt for domain certificate...${plain}"
  549. if ssl_cert_issue; then
  550. local cert_domain="${SSL_ISSUED_DOMAIN}"
  551. if [[ -z "${cert_domain}" ]]; then
  552. cert_domain=$(~/.acme.sh/acme.sh --list 2> /dev/null | tail -1 | awk '{print $1}')
  553. fi
  554. if [[ -n "${cert_domain}" ]]; then
  555. SSL_HOST="${cert_domain}"
  556. echo -e "${green}✓ SSL certificate configured successfully with domain: ${cert_domain}${plain}"
  557. else
  558. echo -e "${yellow}SSL setup may have completed, but domain extraction failed${plain}"
  559. SSL_HOST="${server_ip}"
  560. fi
  561. else
  562. echo -e "${red}SSL certificate setup failed for domain mode.${plain}"
  563. SSL_HOST="${server_ip}"
  564. fi
  565. ;;
  566. 2)
  567. # User chose Let's Encrypt IP certificate option
  568. echo -e "${green}Using Let's Encrypt for IP certificate (shortlived profile)...${plain}"
  569. # Ask for optional IPv6
  570. local ipv6_addr=""
  571. read -rp "Do you have an IPv6 address to include? (leave empty to skip): " ipv6_addr
  572. ipv6_addr="${ipv6_addr// /}" # Trim whitespace
  573. # Stop panel if running (port 80 needed)
  574. if [[ $release == "alpine" ]]; then
  575. rc-service x-ui stop > /dev/null 2>&1
  576. else
  577. systemctl stop x-ui > /dev/null 2>&1
  578. fi
  579. setup_ip_certificate "${server_ip}" "${ipv6_addr}"
  580. if [ $? -eq 0 ]; then
  581. SSL_HOST="${server_ip}"
  582. echo -e "${green}✓ Let's Encrypt IP certificate configured successfully${plain}"
  583. else
  584. echo -e "${red}✗ IP certificate setup failed. Please check port 80 is open.${plain}"
  585. SSL_HOST="${server_ip}"
  586. fi
  587. ;;
  588. 3)
  589. # User chose Custom Paths (User Provided) option
  590. echo -e "${green}Using custom existing certificate...${plain}"
  591. local custom_cert=""
  592. local custom_key=""
  593. local custom_domain=""
  594. # 3.1 Request Domain to compose Panel URL later
  595. read -rp "Please enter domain name certificate issued for: " custom_domain
  596. custom_domain="${custom_domain// /}" # Remove spaces
  597. # 3.2 Loop for Certificate Path
  598. while true; do
  599. read -rp "Input certificate path (keywords: .crt / fullchain): " custom_cert
  600. # Strip quotes if present
  601. custom_cert=$(echo "$custom_cert" | tr -d '"' | tr -d "'")
  602. if [[ -f "$custom_cert" && -r "$custom_cert" && -s "$custom_cert" ]]; then
  603. break
  604. elif [[ ! -f "$custom_cert" ]]; then
  605. echo -e "${red}Error: File does not exist! Try again.${plain}"
  606. elif [[ ! -r "$custom_cert" ]]; then
  607. echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
  608. else
  609. echo -e "${red}Error: File is empty!${plain}"
  610. fi
  611. done
  612. # 3.3 Loop for Private Key Path
  613. while true; do
  614. read -rp "Input private key path (keywords: .key / privatekey): " custom_key
  615. # Strip quotes if present
  616. custom_key=$(echo "$custom_key" | tr -d '"' | tr -d "'")
  617. if [[ -f "$custom_key" && -r "$custom_key" && -s "$custom_key" ]]; then
  618. break
  619. elif [[ ! -f "$custom_key" ]]; then
  620. echo -e "${red}Error: File does not exist! Try again.${plain}"
  621. elif [[ ! -r "$custom_key" ]]; then
  622. echo -e "${red}Error: File exists but is not readable (check permissions)!${plain}"
  623. else
  624. echo -e "${red}Error: File is empty!${plain}"
  625. fi
  626. done
  627. # 3.4 Apply Settings via x-ui binary
  628. ${xui_folder}/x-ui cert -webCert "$custom_cert" -webCertKey "$custom_key" > /dev/null 2>&1
  629. # Set SSL_HOST for composing Panel URL
  630. if [[ -n "$custom_domain" ]]; then
  631. SSL_HOST="$custom_domain"
  632. else
  633. SSL_HOST="${server_ip}"
  634. fi
  635. echo -e "${green}✓ Custom certificate paths applied.${plain}"
  636. echo -e "${yellow}Note: You are responsible for renewing these files externally.${plain}"
  637. systemctl restart x-ui > /dev/null 2>&1 || rc-service x-ui restart > /dev/null 2>&1
  638. ;;
  639. 4)
  640. echo ""
  641. echo -e "${red}⚠ Panel will be installed WITHOUT SSL/TLS.${plain}"
  642. echo -e "${yellow}Login credentials and cookies will travel as plain HTTP.${plain}"
  643. echo -e "${yellow}Only safe when:${plain}"
  644. echo -e "${yellow} • A reverse proxy (nginx, Caddy, Traefik) terminates TLS for you, or${plain}"
  645. echo -e "${yellow} • You access the panel exclusively via SSH tunnel${plain}"
  646. echo ""
  647. SSL_SCHEME="http"
  648. SSL_HOST="${server_ip}"
  649. local bind_local=""
  650. read -rp "Bind the panel to 127.0.0.1 only? (recommended — forces SSH tunnel / reverse-proxy access) [y/N]: " bind_local
  651. if [[ "$bind_local" == "y" || "$bind_local" == "Y" ]]; then
  652. ${xui_folder}/x-ui setting -listenIP "127.0.0.1" > /dev/null 2>&1
  653. SSL_HOST="127.0.0.1"
  654. echo -e "${green}✓ Panel bound to 127.0.0.1 only. It is now unreachable from the public internet.${plain}"
  655. echo ""
  656. echo -e "${green}SSH Port Forwarding — open the panel from your local machine via:${plain}"
  657. echo -e " Standard SSH command:"
  658. echo -e " ${yellow}ssh -L 2222:127.0.0.1:${panel_port} root@${server_ip}${plain}"
  659. echo -e " If using an SSH key:"
  660. echo -e " ${yellow}ssh -i <sshkeypath> -L 2222:127.0.0.1:${panel_port} root@${server_ip}${plain}"
  661. echo -e " Then open in your browser:"
  662. echo -e " ${yellow}http://localhost:2222/${web_base_path}${plain}"
  663. echo ""
  664. echo -e "${yellow}Alternative: point a reverse proxy (nginx/Caddy) at 127.0.0.1:${panel_port} and let it terminate TLS.${plain}"
  665. else
  666. echo -e "${yellow}Panel will listen on all interfaces over plain HTTP. Make sure something else is terminating TLS in front of it.${plain}"
  667. fi
  668. systemctl restart x-ui > /dev/null 2>&1 || rc-service x-ui restart > /dev/null 2>&1
  669. echo -e "${green}✓ SSL setup skipped.${plain}"
  670. ;;
  671. *)
  672. echo -e "${red}Invalid option. Skipping SSL setup.${plain}"
  673. SSL_HOST="${server_ip}"
  674. ;;
  675. esac
  676. }
  677. config_after_install() {
  678. local existing_hasDefaultCredential=$(${xui_folder}/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
  679. local existing_webBasePath=$(${xui_folder}/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}' | sed 's#^/##')
  680. local existing_port=$(${xui_folder}/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
  681. # Properly detect empty cert by checking if cert: line exists and has content after it
  682. local existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
  683. local URL_lists=(
  684. "https://api4.ipify.org"
  685. "https://ipv4.icanhazip.com"
  686. "https://v4.api.ipinfo.io/ip"
  687. "https://ipv4.myexternalip.com/raw"
  688. "https://4.ident.me"
  689. "https://check-host.net/ip"
  690. )
  691. local server_ip=""
  692. for ip_address in "${URL_lists[@]}"; do
  693. local response=$(curl -s -w "\n%{http_code}" --max-time 3 "${ip_address}" 2> /dev/null)
  694. local http_code=$(echo "$response" | tail -n1)
  695. local ip_result=$(echo "$response" | head -n-1 | tr -d '[:space:]"')
  696. if [[ "${http_code}" == "200" && "${ip_result}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  697. server_ip="${ip_result}"
  698. break
  699. fi
  700. done
  701. if [[ -z "$server_ip" ]]; then
  702. echo -e "${yellow}Could not auto-detect server IP from any provider.${plain}"
  703. while [[ -z "$server_ip" ]]; do
  704. read -rp "Please enter your server's public IPv4 address: " server_ip
  705. server_ip="${server_ip// /}"
  706. if [[ ! "$server_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  707. echo -e "${red}Invalid IPv4 address. Please try again.${plain}"
  708. server_ip=""
  709. fi
  710. done
  711. fi
  712. if [[ ${#existing_webBasePath} -lt 4 ]]; then
  713. if [[ "$existing_hasDefaultCredential" == "true" ]]; then
  714. local config_webBasePath=$(gen_random_string 18)
  715. local config_username=$(gen_random_string 10)
  716. local config_password=$(gen_random_string 10)
  717. local db_label="SQLite (/etc/x-ui/x-ui.db)"
  718. echo ""
  719. echo -e "${green}═══════════════════════════════════════════${plain}"
  720. echo -e "${green} Database Selection ${plain}"
  721. echo -e "${green}═══════════════════════════════════════════${plain}"
  722. echo -e " 1) SQLite (default — recommended for < 1000 clients)"
  723. echo -e " 2) PostgreSQL (recommended for high client counts / many nodes)"
  724. read -rp "Choose [1]: " db_choice
  725. db_choice="${db_choice:-1}"
  726. if [[ "$db_choice" == "2" ]]; then
  727. echo ""
  728. echo -e " 1) Install PostgreSQL locally and create a dedicated user/db (recommended)"
  729. echo -e " 2) Use an existing PostgreSQL server (enter DSN)"
  730. read -rp "Choose [1]: " pg_mode
  731. pg_mode="${pg_mode:-1}"
  732. local xui_dsn=""
  733. if [[ "$pg_mode" == "2" ]]; then
  734. while [[ -z "$xui_dsn" ]]; do
  735. read -rp "Enter PostgreSQL DSN (postgres://user:pass@host:port/dbname?sslmode=disable): " xui_dsn
  736. xui_dsn="${xui_dsn// /}"
  737. done
  738. db_label="PostgreSQL (external)"
  739. else
  740. echo -e "${yellow}Installing PostgreSQL — this may take a moment...${plain}"
  741. if xui_dsn=$(install_postgres_local); then
  742. db_label="PostgreSQL ([email protected]:5432/xui)"
  743. else
  744. echo -e "${red}PostgreSQL installation failed; falling back to SQLite.${plain}"
  745. xui_dsn=""
  746. fi
  747. fi
  748. if [[ -n "$xui_dsn" ]]; then
  749. install -d -m 755 /etc/default
  750. umask 077
  751. cat > /etc/default/x-ui << EOF
  752. XUI_DB_TYPE=postgres
  753. XUI_DB_DSN=${xui_dsn}
  754. EOF
  755. chmod 600 /etc/default/x-ui
  756. umask 022
  757. export XUI_DB_TYPE=postgres
  758. export XUI_DB_DSN="${xui_dsn}"
  759. fi
  760. fi
  761. read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
  762. if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
  763. read -rp "Please set up the panel port: " config_port
  764. echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
  765. else
  766. local config_port=$(shuf -i 1024-62000 -n 1)
  767. echo -e "${yellow}Generated random port: ${config_port}${plain}"
  768. fi
  769. ${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
  770. echo ""
  771. echo -e "${green}═══════════════════════════════════════════${plain}"
  772. echo -e "${green} SSL Certificate Setup (RECOMMENDED) ${plain}"
  773. echo -e "${green}═══════════════════════════════════════════${plain}"
  774. echo -e "${yellow}SSL is strongly recommended. Skip only if a reverse proxy${plain}"
  775. echo -e "${yellow}or SSH tunnel handles TLS for you.${plain}"
  776. echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
  777. echo ""
  778. prompt_and_setup_ssl "${config_port}" "${config_webBasePath}" "${server_ip}"
  779. # Retrieve the API token for display
  780. local config_apiToken=$(${xui_folder}/x-ui setting -getApiToken true | grep -Eo 'apiToken: .+' | awk '{print $2}')
  781. # Display final credentials and access information
  782. echo ""
  783. echo -e "${green}═══════════════════════════════════════════${plain}"
  784. echo -e "${green} Panel Installation Complete! ${plain}"
  785. echo -e "${green}═══════════════════════════════════════════${plain}"
  786. echo -e "${green}Username: ${config_username}${plain}"
  787. echo -e "${green}Password: ${config_password}${plain}"
  788. echo -e "${green}Port: ${config_port}${plain}"
  789. echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
  790. echo -e "${green}Database: ${db_label}${plain}"
  791. echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${config_port}/${config_webBasePath}${plain}"
  792. echo -e "${green}API Token: ${config_apiToken}${plain}"
  793. echo -e "${green}═══════════════════════════════════════════${plain}"
  794. echo -e "${yellow}⚠ IMPORTANT: Save these credentials securely!${plain}"
  795. if [[ "$SSL_SCHEME" == "https" ]]; then
  796. echo -e "${yellow}⚠ SSL Certificate: Enabled and configured${plain}"
  797. else
  798. echo -e "${yellow}⚠ SSL Certificate: Skipped — panel is HTTP-only. Use a reverse proxy or SSH tunnel.${plain}"
  799. fi
  800. else
  801. local config_webBasePath=$(gen_random_string 18)
  802. echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
  803. ${xui_folder}/x-ui setting -webBasePath "${config_webBasePath}"
  804. echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
  805. # If the panel is already installed but no certificate is configured, prompt for SSL now
  806. if [[ -z "${existing_cert}" ]]; then
  807. echo ""
  808. echo -e "${green}═══════════════════════════════════════════${plain}"
  809. echo -e "${green} SSL Certificate Setup (RECOMMENDED) ${plain}"
  810. echo -e "${green}═══════════════════════════════════════════${plain}"
  811. echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
  812. echo ""
  813. prompt_and_setup_ssl "${existing_port}" "${config_webBasePath}" "${server_ip}"
  814. echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${existing_port}/${config_webBasePath}${plain}"
  815. else
  816. # If a cert already exists, just show the access URL
  817. echo -e "${green}Access URL: https://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
  818. fi
  819. fi
  820. else
  821. if [[ "$existing_hasDefaultCredential" == "true" ]]; then
  822. local config_username=$(gen_random_string 10)
  823. local config_password=$(gen_random_string 10)
  824. echo -e "${yellow}Default credentials detected. Security update required...${plain}"
  825. ${xui_folder}/x-ui setting -username "${config_username}" -password "${config_password}"
  826. echo -e "Generated new random login credentials:"
  827. echo -e "###############################################"
  828. echo -e "${green}Username: ${config_username}${plain}"
  829. echo -e "${green}Password: ${config_password}${plain}"
  830. echo -e "###############################################"
  831. else
  832. echo -e "${green}Username, Password, and WebBasePath are properly set.${plain}"
  833. fi
  834. # Existing install: if no cert configured, prompt user for SSL setup
  835. # Properly detect empty cert by checking if cert: line exists and has content after it
  836. existing_cert=$(${xui_folder}/x-ui setting -getCert true | grep 'cert:' | awk -F': ' '{print $2}' | tr -d '[:space:]')
  837. if [[ -z "$existing_cert" ]]; then
  838. echo ""
  839. echo -e "${green}═══════════════════════════════════════════${plain}"
  840. echo -e "${green} SSL Certificate Setup (RECOMMENDED) ${plain}"
  841. echo -e "${green}═══════════════════════════════════════════${plain}"
  842. echo -e "${yellow}Let's Encrypt now supports both domains and IP addresses!${plain}"
  843. echo ""
  844. prompt_and_setup_ssl "${existing_port}" "${existing_webBasePath}" "${server_ip}"
  845. echo -e "${green}Access URL: ${SSL_SCHEME}://${SSL_HOST}:${existing_port}/${existing_webBasePath}${plain}"
  846. else
  847. echo -e "${green}SSL certificate already configured. No action needed.${plain}"
  848. fi
  849. fi
  850. ${xui_folder}/x-ui migrate
  851. }
  852. install_x-ui() {
  853. cd ${xui_folder%/x-ui}/
  854. # Download resources
  855. if [ $# == 0 ]; then
  856. tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
  857. if [[ ! -n "$tag_version" ]]; then
  858. echo -e "${yellow}Trying to fetch version with IPv4...${plain}"
  859. tag_version=$(curl -4 -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
  860. if [[ ! -n "$tag_version" ]]; then
  861. echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}"
  862. exit 1
  863. fi
  864. fi
  865. echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
  866. curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
  867. if [[ $? -ne 0 ]]; then
  868. echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
  869. exit 1
  870. fi
  871. else
  872. tag_version=$1
  873. tag_version_numeric=${tag_version#v}
  874. min_version="2.3.5"
  875. if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then
  876. echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}"
  877. exit 1
  878. fi
  879. url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
  880. echo -e "Beginning to install x-ui $1"
  881. curl -4fLRo ${xui_folder}-linux-$(arch).tar.gz ${url}
  882. if [[ $? -ne 0 ]]; then
  883. echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
  884. exit 1
  885. fi
  886. fi
  887. curl -4fLRo /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
  888. if [[ $? -ne 0 ]]; then
  889. echo -e "${red}Failed to download x-ui.sh${plain}"
  890. exit 1
  891. fi
  892. # Stop x-ui service and remove old resources
  893. if [[ -e ${xui_folder}/ ]]; then
  894. if [[ $release == "alpine" ]]; then
  895. rc-service x-ui stop
  896. else
  897. systemctl stop x-ui
  898. fi
  899. rm ${xui_folder}/ -rf
  900. fi
  901. # Extract resources and set permissions
  902. tar zxvf x-ui-linux-$(arch).tar.gz
  903. rm x-ui-linux-$(arch).tar.gz -f
  904. cd x-ui
  905. chmod +x x-ui
  906. chmod +x x-ui.sh
  907. # Check the system's architecture and rename the file accordingly
  908. if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
  909. mv bin/xray-linux-$(arch) bin/xray-linux-arm
  910. chmod +x bin/xray-linux-arm
  911. fi
  912. chmod +x x-ui bin/xray-linux-$(arch)
  913. # Update x-ui cli and se set permission
  914. mv -f /usr/bin/x-ui-temp /usr/bin/x-ui
  915. chmod +x /usr/bin/x-ui
  916. mkdir -p /var/log/x-ui
  917. config_after_install
  918. # Etckeeper compatibility
  919. if [ -d "/etc/.git" ]; then
  920. if [ -f "/etc/.gitignore" ]; then
  921. if ! grep -q "x-ui/x-ui.db" "/etc/.gitignore"; then
  922. echo "" >> "/etc/.gitignore"
  923. echo "x-ui/x-ui.db" >> "/etc/.gitignore"
  924. echo -e "${green}Added x-ui.db to /etc/.gitignore for etckeeper${plain}"
  925. fi
  926. else
  927. echo "x-ui/x-ui.db" > "/etc/.gitignore"
  928. echo -e "${green}Created /etc/.gitignore and added x-ui.db for etckeeper${plain}"
  929. fi
  930. fi
  931. if [[ $release == "alpine" ]]; then
  932. curl -4fLRo /etc/init.d/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.rc
  933. if [[ $? -ne 0 ]]; then
  934. echo -e "${red}Failed to download x-ui.rc${plain}"
  935. exit 1
  936. fi
  937. chmod +x /etc/init.d/x-ui
  938. rc-update add x-ui
  939. rc-service x-ui start
  940. else
  941. # Install systemd service file
  942. service_installed=false
  943. if [ -f "x-ui.service" ]; then
  944. echo -e "${green}Found x-ui.service in extracted files, installing...${plain}"
  945. cp -f x-ui.service ${xui_service}/ > /dev/null 2>&1
  946. if [[ $? -eq 0 ]]; then
  947. service_installed=true
  948. fi
  949. fi
  950. if [ "$service_installed" = false ]; then
  951. case "${release}" in
  952. ubuntu | debian | armbian)
  953. if [ -f "x-ui.service.debian" ]; then
  954. echo -e "${green}Found x-ui.service.debian in extracted files, installing...${plain}"
  955. cp -f x-ui.service.debian ${xui_service}/x-ui.service > /dev/null 2>&1
  956. if [[ $? -eq 0 ]]; then
  957. service_installed=true
  958. fi
  959. fi
  960. ;;
  961. arch | manjaro | parch)
  962. if [ -f "x-ui.service.arch" ]; then
  963. echo -e "${green}Found x-ui.service.arch in extracted files, installing...${plain}"
  964. cp -f x-ui.service.arch ${xui_service}/x-ui.service > /dev/null 2>&1
  965. if [[ $? -eq 0 ]]; then
  966. service_installed=true
  967. fi
  968. fi
  969. ;;
  970. *)
  971. if [ -f "x-ui.service.rhel" ]; then
  972. echo -e "${green}Found x-ui.service.rhel in extracted files, installing...${plain}"
  973. cp -f x-ui.service.rhel ${xui_service}/x-ui.service > /dev/null 2>&1
  974. if [[ $? -eq 0 ]]; then
  975. service_installed=true
  976. fi
  977. fi
  978. ;;
  979. esac
  980. fi
  981. # If service file not found in tar.gz, download from GitHub
  982. if [ "$service_installed" = false ]; then
  983. echo -e "${yellow}Service files not found in tar.gz, downloading from GitHub...${plain}"
  984. case "${release}" in
  985. ubuntu | debian | armbian)
  986. curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.debian > /dev/null 2>&1
  987. ;;
  988. arch | manjaro | parch)
  989. curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.arch > /dev/null 2>&1
  990. ;;
  991. *)
  992. curl -4fLRo ${xui_service}/x-ui.service https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.service.rhel > /dev/null 2>&1
  993. ;;
  994. esac
  995. if [[ $? -ne 0 ]]; then
  996. echo -e "${red}Failed to install x-ui.service from GitHub${plain}"
  997. exit 1
  998. fi
  999. service_installed=true
  1000. fi
  1001. if [ "$service_installed" = true ]; then
  1002. echo -e "${green}Setting up systemd unit...${plain}"
  1003. chown root:root ${xui_service}/x-ui.service > /dev/null 2>&1
  1004. chmod 644 ${xui_service}/x-ui.service > /dev/null 2>&1
  1005. systemctl daemon-reload
  1006. systemctl enable x-ui
  1007. systemctl start x-ui
  1008. else
  1009. echo -e "${red}Failed to install x-ui.service file${plain}"
  1010. exit 1
  1011. fi
  1012. fi
  1013. echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
  1014. echo -e ""
  1015. echo -e "┌───────────────────────────────────────────────────────┐
  1016. │ ${blue}x-ui control menu usages (subcommands):${plain} │
  1017. │ │
  1018. │ ${blue}x-ui${plain} - Admin Management Script │
  1019. │ ${blue}x-ui start${plain} - Start │
  1020. │ ${blue}x-ui stop${plain} - Stop │
  1021. │ ${blue}x-ui restart${plain} - Restart │
  1022. │ ${blue}x-ui status${plain} - Current Status │
  1023. │ ${blue}x-ui settings${plain} - Current Settings │
  1024. │ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
  1025. │ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
  1026. │ ${blue}x-ui log${plain} - Check logs │
  1027. │ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
  1028. │ ${blue}x-ui update${plain} - Update │
  1029. │ ${blue}x-ui legacy${plain} - Legacy version │
  1030. │ ${blue}x-ui install${plain} - Install │
  1031. │ ${blue}x-ui uninstall${plain} - Uninstall │
  1032. └───────────────────────────────────────────────────────┘"
  1033. }
  1034. echo -e "${green}Running...${plain}"
  1035. install_base
  1036. install_x-ui $1