#!/bin/bash # LXS - Common library # Sourced by lxs.sh and all sub-scripts. Provides colors, UI helpers, loggers, # spinner, OS guards, and shared utilities (public IP, password generation, # apt lock wait, root re-exec, apt non-interactive setup). # Repo: https://git.hyko.cx/hykocx/lxs # ═══════════════════════════════════════════════════════════════════════════ # Colors — Cyberpunk 2077 neon palette # ═══════════════════════════════════════════════════════════════════════════ RED='\033[1;91m' # ICE alert GREEN='\033[1;92m' # phosphor green YELLOW='\033[1;93m' # signature electric yellow MAGENTA='\033[1;95m' # hot pink neon CYAN='\033[1;96m' # neon cyan WHITE='\033[1;97m' # bold white GRAY='\033[0;90m' # dark gray PURPLE='\033[1;95m' # alias on MAGENTA for legacy sub-scripts NC='\033[0m' BOLD='\033[1m' DIM='\033[2m' BLINK='\033[5m' REV='\033[7m' # ═══════════════════════════════════════════════════════════════════════════ # UI helpers # ═══════════════════════════════════════════════════════════════════════════ show_separator() { local cols cols=$(tput cols 2>/dev/null || echo 64) printf "${YELLOW}" printf '▀%.0s' $(seq 1 "$cols") printf "${NC}\n" } show_title() { local title="$1" local subtitle="${2:-SYS_MODULE}" echo "" echo -e "${YELLOW}${REV} ${title} ${NC} ${MAGENTA}// ${subtitle}${NC}" show_separator } info() { echo -e "${MAGENTA}[··]${NC} $*"; } ok() { echo -e "${GREEN}[OK]${NC} $*"; } warn() { echo -e "${YELLOW}[!!]${NC} $*"; } err() { echo -e "${RED}[KO]${NC} $*" >&2; } # ═══════════════════════════════════════════════════════════════════════════ # Spinner — runs a shell command, redirects output to a log file, shows # a spinner with a label, prints ✓/✗ on completion. Returns the command's # exit code. # # Usage: # run_spinner "Installing foo..." "apt-get install -y foo" # # Log file: ${LXS_LOG_FILE:-/tmp/lxs.log} # ═══════════════════════════════════════════════════════════════════════════ run_spinner() { local message=$1 shift local command="$*" local spinstr='|/-\' local log="${LXS_LOG_FILE:-/tmp/lxs.log}" echo -e "${MAGENTA}[··] ${message}${NC}" eval "$command" > "$log" 2>&1 & local pid=$! while kill -0 "$pid" 2>/dev/null; do local temp=${spinstr#?} printf "\r${MAGENTA}[%c·]${NC} ${message}" "$spinstr" spinstr=$temp${spinstr%"$temp"} sleep 0.15 done wait "$pid" local exit_code=$? if [ $exit_code -eq 0 ]; then printf "\r${GREEN}[OK]${NC} ${message}\n" else printf "\r${RED}[KO]${NC} ${message}\n" fi return $exit_code } # ═══════════════════════════════════════════════════════════════════════════ # Guards # ═══════════════════════════════════════════════════════════════════════════ require_debian_ubuntu() { if ! grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then err "This script supports Debian/Ubuntu only." return 1 fi } # Re-exec the current script via sudo if not already root. Preserves env and # arguments. Call near the top of a sub-script: # require_root "$0" "$@" require_root() { [ "$EUID" -eq 0 ] && return 0 local self=$1 shift exec sudo -E "$self" "$@" } # ═══════════════════════════════════════════════════════════════════════════ # Network / random / apt helpers # ═══════════════════════════════════════════════════════════════════════════ get_public_ip() { local ip="" ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null) || \ ip=$(curl -s --max-time 5 icanhazip.com 2>/dev/null) || \ ip=$(curl -s --max-time 5 ipinfo.io/ip 2>/dev/null) || \ ip=$(hostname -I 2>/dev/null | awk '{print $1}') echo "$ip" } # Generate a random alphanumeric string. Default length: 32. generate_password() { local length=${1:-32} LC_ALL=C tr -dc 'a-zA-Z0-9' /dev/null 2>&1 || return 0 export DEBIAN_FRONTEND=noninteractive export NEEDRESTART_MODE=a export NEEDRESTART_SUSPEND=1 echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 2>/dev/null || true } # Add a UFW allow rule, but only if UFW is installed AND active. No-op # otherwise — so app installers can declare the ports they need without # forcing UFW on hosts that don't use it. # # Usage: # ufw_allow 8000/tcp "Coolify dashboard" # ufw_allow 80/tcp ufw_allow() { command -v ufw >/dev/null 2>&1 || return 0 ufw status 2>/dev/null | grep -q "Status: active" || return 0 local rule=$1 comment=${2:-} if [ -n "$comment" ]; then ufw allow "$rule" comment "$comment" >/dev/null else ufw allow "$rule" >/dev/null fi ok "UFW :: allowed ${rule}${comment:+ (${comment})}" } # Wait for other apt/dpkg processes to release their locks. Up to 120s. wait_for_apt() { command -v apt >/dev/null 2>&1 || return 0 local max_wait=120 local wait_count=0 local lock_detected=false while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || \ fuser /var/lib/dpkg/lock >/dev/null 2>&1 || \ fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do [ "$lock_detected" = false ] && lock_detected=true && \ echo -e "${YELLOW}[!!] Waiting for other package manager to finish...${NC}" wait_count=$((wait_count + 1)) [ $wait_count -ge $max_wait ] && \ echo -e "${RED}[KO] Timeout waiting for package manager${NC}" && return 1 printf "\r${GRAY}[··] Waiting... (%ds/%ds)${NC}" $wait_count $max_wait sleep 1 done [ "$lock_detected" = true ] && \ echo -e "\n${GREEN}[OK] Package manager is now available${NC}" return 0 }