#!/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 # ═══════════════════════════════════════════════════════════════════════════ RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' PURPLE='\033[1;94m' CYAN='\033[0;36m' WHITE='\033[1;37m' GRAY='\033[0;37m' NC='\033[0m' BOLD='\033[1m' # ═══════════════════════════════════════════════════════════════════════════ # UI helpers # ═══════════════════════════════════════════════════════════════════════════ show_separator() { echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } info() { echo -e "${PURPLE}[*]${NC} $*"; } ok() { echo -e "${GREEN}[✓]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } err() { echo -e "${RED}[✗]${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 "${PURPLE}[*] ${message}${NC}" eval "$command" > "$log" 2>&1 & local pid=$! while kill -0 "$pid" 2>/dev/null; do local temp=${spinstr#?} printf "\r${PURPLE}[%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}[✓]${NC} ${message}\n" else printf "\r${RED}[✗]${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}[✗] Timeout waiting for package manager${NC}" && return 1 printf "\r${GRAY}[i] Waiting... (%ds/%ds)${NC}" $wait_count $max_wait sleep 1 done [ "$lock_detected" = true ] && \ echo -e "\n${GREEN}[✓] Package manager is now available${NC}" return 0 }