@@ -105,33 +105,36 @@ info() { echo -e "${C_GRAY} $1${C_RESET}"; }
105105spin () {
106106 local label=" $1 " ; shift
107107 [[ " $1 " == " --" ]] && shift
108- local frames=' |/-\\ ' i=0 start now elapsed
108+ local frames=' |/-\' i=0 start now elapsed
109109 start=$( date +%s)
110110
111111 # Run command in background, redirect output to a log so spinner can paint
112112 local logfile; logfile=$( mktemp)
113113 ( " $@ " > " $logfile " 2>&1 ) &
114114 local pid=$!
115115
116+ # Real escape character (not the literal \033 backslash-zero-three-three)
117+ local ESC=$' \033 '
116118 # Hide cursor
117- printf ' \033 [?25l'
119+ printf ' %s [?25l' " $ESC "
118120 while kill -0 " $pid " 2> /dev/null; do
119121 now=$( date +%s) ; elapsed=$(( now - start ))
120122 local frame=" ${frames: i++% ${# frames} : 1} "
121- printf " \r ${C_CYAN}${frame}${C_RESET} %s ${C_GRAY} (%ds elapsed)${C_RESET} " " $label " " $elapsed "
123+ printf ' \r %s[1;36m%s%s[0m %s %s[0;90m(%ds elapsed)%s[0m ' \
124+ " $ESC " " $frame " " $ESC " " $label " " $ESC " " $elapsed " " $ESC "
122125 sleep 0.2
123126 done
124127 wait " $pid " ; local rc=$?
125128 now=$( date +%s) ; elapsed=$(( now - start ))
126- # Restore cursor and clear spinner line
127- printf ' \r\033[K\033 [?25h'
129+ # Clear spinner line and restore cursor
130+ printf ' \r%s[2K%s [?25h' " $ESC " " $ESC "
128131
129132 if [[ $rc -eq 0 ]]; then
130- ok " $ label ${C_GRAY} (${elapsed} s)${C_RESET} "
133+ echo -e " ${C_GREEN} ✔ ${C_RESET} ${ label} ${C_GRAY} (${elapsed} s)${C_RESET} "
131134 else
132- fail " $ label — exit ${rc} (${elapsed} s)"
133- echo -e " ${C_GRAY} ── last 10 lines of output ──${C_RESET} "
134- tail -10 " $logfile " 2> /dev/null | while IFS= read -r l; do echo -e " ${C_GRAY} $l ${C_RESET} " ; done
135+ echo -e " ${C_RED} ✘ ${C_RESET} ${ label} ${C_RED} — exit ${rc}${C_RESET} ${C_GRAY} (${elapsed} s)${C_RESET} "
136+ echo -e " ${C_GRAY} ── last 10 lines of output ──${C_RESET} "
137+ tail -10 " $logfile " 2> /dev/null | while IFS= read -r l; do echo -e " ${C_GRAY} $l ${C_RESET} " ; done
135138 fi
136139 rm -f " $logfile "
137140 return $rc
@@ -386,17 +389,23 @@ phase2_install_all() {
386389
387390 local netlify_ok=false
388391
389- # ── Attempt 1: standard npm global install ───────────
390- if spin " Installing Netlify CLI via npm (~1-2min, ~100MB)" -- \
391- bash -c ' npm install -g netlify-cli --prefer-online' ; then
392+ # Common npm flags to speed up installs:
393+ # --no-audit / --no-fund → skips post-install network calls
394+ # --no-progress → suppresses progress bar (much faster on slow terms)
395+ # --prefer-offline → use cache if available
396+ local NPM_FAST=" --no-audit --no-fund --no-progress --prefer-offline"
397+
398+ # ── Attempt 1: fast npm global install ───────────────
399+ if spin " Installing Netlify CLI via npm (~30-60s)" -- \
400+ bash -c " npm install -g netlify-cli ${NPM_FAST} " ; then
392401 command -v netlify & > /dev/null && netlify_ok=true
393402 fi
394403
395404 # ── Attempt 2: npm with lower max-old-space (low-RAM VPS) ─
396405 if [[ " $netlify_ok " != " true" ]]; then
397406 warn " Attempt 1 failed — retrying with 512MB memory limit..."
398407 if spin " Installing Netlify CLI (low-mem mode)" -- \
399- bash -c ' NODE_OPTIONS=" --max-old-space-size=512" npm install -g netlify-cli' ; then
408+ bash -c " NODE_OPTIONS=' --max-old-space-size=512' npm install -g netlify-cli ${NPM_FAST} " ; then
400409 command -v netlify & > /dev/null && netlify_ok=true
401410 fi
402411 fi
@@ -406,7 +415,7 @@ phase2_install_all() {
406415 warn " Attempt 2 failed — cleaning npm cache and retrying..."
407416 npm cache clean --force > /dev/null 2>&1 || true
408417 if spin " Installing Netlify CLI (after cache clean)" -- \
409- bash -c ' npm install -g netlify-cli' ; then
418+ bash -c " npm install -g netlify-cli ${NPM_FAST} " ; then
410419 command -v netlify & > /dev/null && netlify_ok=true
411420 fi
412421 fi
@@ -472,7 +481,8 @@ NPXWRAP
472481 if command -v vercel & > /dev/null; then
473482 ok " Vercel CLI already installed ($( vercel --version 2> /dev/null | head -1) )"
474483 else
475- spin " Installing Vercel CLI via npm (~30s, ~50MB)" -- bash -c ' npm install -g vercel'
484+ spin " Installing Vercel CLI via npm (~20-40s)" -- \
485+ bash -c ' npm install -g vercel --no-audit --no-fund --no-progress --prefer-offline'
476486 fi
477487
478488 # ── 2d. xray-knife ──────────────────────────────────────
@@ -673,26 +683,40 @@ phase4a_ssl() {
673683 grep -iE " register|already|account" | head -3 || true
674684
675685 # ── Helper: run acme.sh --issue and capture full output ────
686+ # $1: mode (standalone | webroot)
687+ # $2: extra flags (e.g. "--force")
676688 _run_acme_issue () {
677- local mode=" $1 " # "standalone" | "webroot "
678- info " Running: acme.sh --issue -d ${CFG_DOMAIN} --${mode} --keylength ec-256 --listen-v4"
689+ local mode=" $1 " extra= " ${2 :- } "
690+ info " Running: acme.sh --issue -d ${CFG_DOMAIN} --${mode} --keylength ec-256 --listen-v4 ${extra} "
679691 local out rc
680692 if [[ " $mode " == " webroot" ]]; then
681693 mkdir -p /var/www/html
682694 out=$( " $ACME_CMD " --issue -d " $CFG_DOMAIN " --webroot /var/www/html \
683- --keylength ec-256 --listen-v4 --server letsencrypt 2>&1 ) || rc=$?
695+ --keylength ec-256 --listen-v4 --server letsencrypt $extra 2>&1 ) || rc=$?
684696 else
685697 out=$( " $ACME_CMD " --issue -d " $CFG_DOMAIN " --standalone \
686- --keylength ec-256 --listen-v4 --server letsencrypt 2>&1 ) || rc=$?
698+ --keylength ec-256 --listen-v4 --server letsencrypt $extra 2>&1 ) || rc=$?
687699 fi
688700 rc=${rc:- 0}
701+ # Save full output for later inspection
702+ LAST_ACME_OUT=" $out "
689703 # Show last 25 lines so user can see the real error
690704 echo " $out " | tail -25 | while IFS= read -r l; do echo -e " ${C_GRAY}${l}${C_RESET} " ; done
691705 return $rc
692706 }
693707
708+ # ── Check if a valid cert already exists on disk (covers both ECC and RSA) ─
709+ local acme_cert_path=" $HOME /.acme.sh/${CFG_DOMAIN} _ecc/${CFG_DOMAIN} .cer"
710+ local acme_cert_path_rsa=" $HOME /.acme.sh/${CFG_DOMAIN} /${CFG_DOMAIN} .cer"
711+ if [[ -f " $acme_cert_path " ]]; then
712+ info " Found existing EC cert at ${acme_cert_path} — will reuse"
713+ elif [[ -f " $acme_cert_path_rsa " ]]; then
714+ info " Found existing RSA cert at ${acme_cert_path_rsa} — will reuse"
715+ acme_cert_path=" $acme_cert_path_rsa "
716+ fi
717+
694718 # ── Issue certificate ──────────────────────────────────────
695- local issue_rc=0
719+ local issue_rc=0 LAST_ACME_OUT= " "
696720 if [[ " $port80_used " == " true" ]]; then
697721 if command -v nginx & > /dev/null && [[ -d /var/www/html ]]; then
698722 _run_acme_issue webroot || issue_rc=$?
@@ -708,8 +732,30 @@ phase4a_ssl() {
708732 _run_acme_issue standalone || issue_rc=$?
709733 fi
710734
735+ # ── Handle "Skipping. Next renewal time" — cert already exists & valid ───
736+ # acme.sh exits 2 in this case; treat as success if the cer file is there.
737+ if [[ $issue_rc -ne 0 ]] && echo " $LAST_ACME_OUT " | grep -qiE " Domains not changed|Skipping.*Next renewal" ; then
738+ if [[ -f " $acme_cert_path " ]]; then
739+ info " acme.sh: existing cert still valid — using it as-is"
740+ issue_rc=0
741+ else
742+ # The 'skip' message lied (no cert on disk) — force re-issue
743+ warn " acme.sh says 'skip' but no cert file found — forcing re-issue with --force"
744+ issue_rc=0
745+ if [[ " $port80_used " == " true" ]] && ! { command -v nginx & > /dev/null && [[ -d /var/www/html ]]; }; then
746+ systemctl stop xray 2> /dev/null || true
747+ sleep 2
748+ _run_acme_issue standalone " --force" || issue_rc=$?
749+ systemctl start xray 2> /dev/null || true
750+ elif [[ " $port80_used " == " true" ]]; then
751+ _run_acme_issue webroot " --force" || issue_rc=$?
752+ else
753+ _run_acme_issue standalone " --force" || issue_rc=$?
754+ fi
755+ fi
756+ fi
757+
711758 # ── Verify the cert file actually exists before installcert ─
712- local acme_cert_path=" $HOME /.acme.sh/${CFG_DOMAIN} _ecc/${CFG_DOMAIN} .cer"
713759 if [[ $issue_rc -ne 0 ]] || [[ ! -f " $acme_cert_path " ]]; then
714760 fail " acme.sh --issue failed (exit ${issue_rc} ). No cert at ${acme_cert_path} "
715761 info " Common causes:"
@@ -722,12 +768,13 @@ phase4a_ssl() {
722768 return 1
723769 fi
724770
725- ok " acme.sh issued certificate (ec-256) "
771+ ok " acme.sh certificate ready "
726772
727773 # ── Install certificate to target dir ──────────────────────
728- # --ecc is REQUIRED when --installcert references an EC key (matches the
729- # ${domain}_ecc directory acme.sh created during --issue).
730- " $ACME_CMD " --installcert -d " $CFG_DOMAIN " --ecc \
774+ # --ecc selects the EC-key cert directory (${domain}_ecc/). Omit it for RSA.
775+ local ecc_flag=" --ecc"
776+ [[ " $acme_cert_path " == * " ${CFG_DOMAIN} /${CFG_DOMAIN} .cer" ]] && ecc_flag=" "
777+ " $ACME_CMD " --installcert -d " $CFG_DOMAIN " $ecc_flag \
731778 --cert-file " ${SSL_DIR} /cert.pem" \
732779 --key-file " ${SSL_KEY} " \
733780 --fullchain-file " ${SSL_CERT} " \
0 commit comments