#!/bin/bash # LXS - Linux multi-tool # Description: Centralized server management and deployment toolkit # Repo: https://git.hyko.cx/hykocx/lxs # License: MIT # ═══════════════════════════════════════════════════════════════════════════ # Configuration # ═══════════════════════════════════════════════════════════════════════════ LXS_SCRIPT_DIR="" if [ -n "${BASH_SOURCE[0]:-}" ]; then _lxs_resolved=$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null) \ || _lxs_resolved=$(realpath "${BASH_SOURCE[0]}" 2>/dev/null) \ || _lxs_resolved="${BASH_SOURCE[0]}" LXS_SCRIPT_DIR=$(dirname "$_lxs_resolved") unset _lxs_resolved fi if [ -n "$LXS_SCRIPT_DIR" ] && [ -r "${LXS_SCRIPT_DIR}/VERSION" ]; then LXS_VERSION=$(head -n1 "${LXS_SCRIPT_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]') fi LXS_VERSION="${LXS_VERSION:-dev}" LXS_REPO_URL="https://git.hyko.cx/hykocx/lxs" LXS_BRANCH="${LXS_BRANCH:-main}" export LXS_RAW_BASE="${LXS_RAW_BASE:-${LXS_REPO_URL}/raw/branch/${LXS_BRANCH}}" LXS_TARBALL_URL="${LXS_TARBALL_URL:-${LXS_REPO_URL}/archive/${LXS_BRANCH}.tar.gz}" LXS_INSTALL_DIR="${LXS_INSTALL_DIR:-/usr/local/share/lxs}" LXS_BIN_PATH="${LXS_BIN_PATH:-/usr/local/bin/lxs}" LXS_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/lxs" LXS_VERSION_CACHE="${LXS_CACHE_DIR}/remote_version" LXS_VERSION_TTL=86400 # ═══════════════════════════════════════════════════════════════════════════ # Load common library # Prefers a sibling lib/common.sh (installed or repo checkout); falls back to # fetching it from the remote when run via `curl | bash`. # ═══════════════════════════════════════════════════════════════════════════ if [ -n "$LXS_SCRIPT_DIR" ] && [ -r "${LXS_SCRIPT_DIR}/lib/common.sh" ]; then # shellcheck disable=SC1091 . "${LXS_SCRIPT_DIR}/lib/common.sh" else _lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2 exit 1 } eval "$_lib" unset _lib fi # ═══════════════════════════════════════════════════════════════════════════ # Core helpers # ═══════════════════════════════════════════════════════════════════════════ lxs_self_path() { local src="${BASH_SOURCE[0]}" [ -z "$src" ] && return 1 if command -v readlink >/dev/null 2>&1; then readlink -f "$src" 2>/dev/null && return 0 fi if command -v realpath >/dev/null 2>&1; then realpath "$src" 2>/dev/null && return 0 fi echo "$src" } lxs_is_installed() { local self self=$(lxs_self_path) || return 1 [ "$(dirname "$self")" = "$LXS_INSTALL_DIR" ] } lxs_need_root_for() { local path=$1 local parent parent=$(dirname "$path") [ -w "$parent" ] && return 1 [ "$EUID" -eq 0 ] && return 1 return 0 } check_remote_version() { LXS_UPDATE_AVAILABLE=0 LXS_REMOTE_VERSION="" mkdir -p "$LXS_CACHE_DIR" 2>/dev/null || return 0 local now mtime age=999999 now=$(date +%s 2>/dev/null) || return 0 if [ -f "$LXS_VERSION_CACHE" ]; then mtime=$(stat -c %Y "$LXS_VERSION_CACHE" 2>/dev/null || stat -f %m "$LXS_VERSION_CACHE" 2>/dev/null || echo 0) age=$((now - mtime)) fi if [ "$age" -ge "$LXS_VERSION_TTL" ]; then ( curl -fsSL --max-time 3 -H "Cache-Control: no-cache" \ "${LXS_RAW_BASE}/VERSION" -o "${LXS_VERSION_CACHE}.tmp" 2>/dev/null \ && mv "${LXS_VERSION_CACHE}.tmp" "$LXS_VERSION_CACHE" \ || rm -f "${LXS_VERSION_CACHE}.tmp" ) >/dev/null 2>&1 & disown 2>/dev/null || true fi [ -f "$LXS_VERSION_CACHE" ] || return 0 local cached cached=$(head -n1 "$LXS_VERSION_CACHE" 2>/dev/null | tr -d '[:space:]') [ -z "$cached" ] && return 0 local highest highest=$(printf '%s\n%s\n' "$LXS_VERSION" "$cached" | sort -V | tail -n1) if [ "$highest" = "$cached" ] && [ "$cached" != "$LXS_VERSION" ]; then LXS_UPDATE_AVAILABLE=1 LXS_REMOTE_VERSION="$cached" fi } show_lxs_logo() { echo -e "${WHITE}${BOLD}" echo -e "╔═══════════════════════════════════════════════════════╗" echo -e "║ ║" echo -e "║ ██╗ ██╗ ██╗███████╗ ║" echo -e "║ ██║ ╚██╗██╔╝██╔════╝ ║" echo -e "║ ██║ ╚███╔╝ ███████╗ ║" echo -e "║ ██║ ██╔██╗ ╚════██║ ║" echo -e "║ ███████╗██╔╝ ██╗███████║ ║" echo -e "║ ╚══════╝╚═╝ ╚═╝╚══════╝ v${LXS_VERSION} ║" echo -e "║ ║" echo -e "╚═══════════════════════════════════════════════════════╝" echo -e "${NC}" } show_header() { clear show_lxs_logo if [ "${LXS_UPDATE_AVAILABLE:-0}" = "1" ]; then echo -e "${YELLOW}[!] Nouvelle version disponible: ${LXS_REMOTE_VERSION} (actuelle: ${LXS_VERSION})${NC}" echo -e "${GRAY} Lance: lxs update${NC}" echo "" fi local hostname os_version kernel uptime_info ip_address cpu_model cpu_cores total_mem used_mem disk_usage hostname=$(hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "${HOSTNAME:-unknown}") os_version=$(lsb_release -ds 2>/dev/null || grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2) kernel=$(uname -r) uptime_info=$(uptime -p 2>/dev/null || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}') ip_address=$(get_public_ip) cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs) cpu_cores=$(nproc) total_mem=$(free -h | awk '/^Mem:/ {print $2}') used_mem=$(free -h | awk '/^Mem:/ {print $3}') disk_usage=$(df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}') echo -e "${GRAY}Hostname:${NC} $hostname" echo -e "${GRAY}IP Address:${NC} $ip_address" echo -e "${GRAY}OS:${NC} $os_version" echo -e "${GRAY}Kernel:${NC} $kernel" echo -e "${GRAY}Uptime:${NC} $uptime_info" echo -e "${GRAY}CPU:${NC} $cpu_model ($cpu_cores cores)" echo -e "${GRAY}Memory:${NC} $used_mem / $total_mem" echo -e "${GRAY}Disk:${NC} $disk_usage" echo "" } check_os() { if ! grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then echo -e "${YELLOW}[!] LXS is tested on Debian/Ubuntu. Other distros may not work.${NC}" read -r -p "Continue anyway? [y/N] " reply case "$reply" in [yY]|[yY][eE][sS]) ;; *) exit 0 ;; esac fi } download_and_run() { local script_path=$1 shift local script_name local_path script_name=$(basename "$script_path") local_path="${LXS_INSTALL_DIR}/${script_path}" if lxs_is_installed && [ -f "$local_path" ]; then chmod +x "$local_path" 2>/dev/null || true echo "" show_separator echo "" "$local_path" "$@" local exit_code=$? echo "" show_separator if [ $exit_code -eq 0 ]; then echo -e "${GREEN}[✓] Script completed successfully${NC}" else echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}" fi return $exit_code fi local temp_file temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh") echo "" echo -e "${PURPLE}[*] Downloading ${BOLD}${script_name}${NC}${PURPLE}...${NC}" if curl -fsSL -H "Cache-Control: no-cache" \ -o "${temp_file}" \ "${LXS_RAW_BASE}/${script_path}"; then echo -e "${GREEN}[✓] Downloaded${NC}" chmod +x "${temp_file}" echo "" show_separator echo "" "${temp_file}" "$@" local exit_code=$? rm -f "${temp_file}" echo "" show_separator if [ $exit_code -eq 0 ]; then echo -e "${GREEN}[✓] Script completed successfully${NC}" else echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}" fi return $exit_code else echo -e "${RED}[✗] Failed to download ${script_path}${NC}" echo -e "${RED} URL: ${LXS_RAW_BASE}/${script_path}${NC}" rm -f "${temp_file}" return 1 fi } # ═══════════════════════════════════════════════════════════════════════════ # CLI commands # ═══════════════════════════════════════════════════════════════════════════ cmd_version() { echo "lxs ${LXS_VERSION}" } cmd_help() { cat < Install an application lxs tool [args] Run a system tool lxs info Show system info lxs version Show version lxs help Show this help Applications (lxs install ...): coolify Self-hosted PaaS pterodactyl Game server management panel uptime-kuma Monitoring tool cloudpanel Web hosting control panel proxmox Proxmox VE management tools Tools (lxs tool ...): system System monitoring and diagnostics benchmark Server benchmark (CPU/RAM/disk/network) harden Baseline hardening (UFW + fail2ban + SSH key-only + auto-updates) Source: ${LXS_REPO_URL} EOF } cmd_install() { local app="${1:-}" case "$app" in coolify) download_and_run "apps/coolify.sh" ;; pterodactyl) download_and_run "apps/pterodactyl.sh" ;; uptime-kuma) download_and_run "apps/uptime-kuma.sh" ;; cloudpanel) download_and_run "apps/cloudpanel.sh" ;; proxmox) download_and_run "apps/proxmox.sh" ;; "") echo -e "${RED}[✗] Missing app name. Try: lxs help${NC}"; return 1 ;; *) echo -e "${RED}[✗] Unknown app: $app. Try: lxs help${NC}"; return 1 ;; esac } cmd_tool() { local tool="${1:-}" shift || true case "$tool" in system) download_and_run "tools/system-tools.sh" "$@" ;; benchmark) download_and_run "tools/server-benchmark.sh" "$@" ;; harden) download_and_run "tools/harden.sh" "$@" ;; "") echo -e "${RED}[✗] Missing tool name. Try: lxs help${NC}"; return 1 ;; *) echo -e "${RED}[✗] Unknown tool: $tool. Try: lxs help${NC}"; return 1 ;; esac } lxs_sync_install() { local action="${1:-update}" if lxs_need_root_for "$LXS_INSTALL_DIR" || lxs_need_root_for "$LXS_BIN_PATH"; then echo -e "${YELLOW}[!] Need sudo to write ${LXS_INSTALL_DIR}; re-running with sudo...${NC}" exec sudo -E LXS_INSTALL_DIR="$LXS_INSTALL_DIR" LXS_BIN_PATH="$LXS_BIN_PATH" \ LXS_BRANCH="$LXS_BRANCH" LXS_REPO_URL="$LXS_REPO_URL" \ bash "$(lxs_self_path)" "$action" fi local work work=$(mktemp -d /tmp/lxs.install.XXXXXX) || { echo -e "${RED}[✗] Failed to create temp dir${NC}" return 1 } trap 'rm -rf "$work"' RETURN echo -e "${PURPLE}[*] Downloading ${LXS_TARBALL_URL}...${NC}" if ! curl -fsSL -H "Cache-Control: no-cache" -o "${work}/lxs.tgz" "$LXS_TARBALL_URL"; then echo -e "${RED}[✗] Download failed${NC}" return 1 fi mkdir -p "${work}/extracted" if ! tar -xzf "${work}/lxs.tgz" --strip-components=1 -C "${work}/extracted"; then echo -e "${RED}[✗] Extraction failed${NC}" return 1 fi if [ ! -f "${work}/extracted/lxs.sh" ] || [ ! -f "${work}/extracted/VERSION" ]; then echo -e "${RED}[✗] Tarball is missing lxs.sh or VERSION${NC}" return 1 fi mkdir -p "$LXS_INSTALL_DIR" if command -v rsync >/dev/null 2>&1; then rsync -a --delete "${work}/extracted/" "${LXS_INSTALL_DIR}/" || { echo -e "${RED}[✗] rsync failed${NC}" return 1 } else rm -rf "${LXS_INSTALL_DIR}.old" 2>/dev/null [ -d "$LXS_INSTALL_DIR" ] && mv "$LXS_INSTALL_DIR" "${LXS_INSTALL_DIR}.old" mkdir -p "$LXS_INSTALL_DIR" if ! cp -a "${work}/extracted/." "${LXS_INSTALL_DIR}/"; then echo -e "${RED}[✗] Copy failed; restoring previous install${NC}" rm -rf "$LXS_INSTALL_DIR" [ -d "${LXS_INSTALL_DIR}.old" ] && mv "${LXS_INSTALL_DIR}.old" "$LXS_INSTALL_DIR" return 1 fi rm -rf "${LXS_INSTALL_DIR}.old" fi chmod +x "${LXS_INSTALL_DIR}/lxs.sh" 2>/dev/null || true find "${LXS_INSTALL_DIR}/apps" "${LXS_INSTALL_DIR}/tools" -name '*.sh' -exec chmod +x {} + 2>/dev/null || true if [ -e "$LXS_BIN_PATH" ] || [ -L "$LXS_BIN_PATH" ]; then rm -f "$LXS_BIN_PATH" fi ln -sfn "${LXS_INSTALL_DIR}/lxs.sh" "$LXS_BIN_PATH" rm -f "$LXS_VERSION_CACHE" local new_version new_version=$(head -n1 "${LXS_INSTALL_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]') echo -e "${GREEN}[✓] lxs ${new_version} installed in ${LXS_INSTALL_DIR}${NC}" echo -e "${GREEN}[✓] Symlink: ${LXS_BIN_PATH} → ${LXS_INSTALL_DIR}/lxs.sh${NC}" } cmd_update() { lxs_sync_install update } cmd_install_self() { lxs_sync_install setup } # ═══════════════════════════════════════════════════════════════════════════ # Interactive menus # ═══════════════════════════════════════════════════════════════════════════ menu_install_apps() { while true; do show_header echo -e "${WHITE}${BOLD}APPLICATIONS${NC}" echo "" echo -e " ${GREEN}[1]${NC} Coolify" echo -e " ${GREEN}[2]${NC} Pterodactyl Panel" echo -e " ${GREEN}[3]${NC} Uptime Kuma" echo -e " ${GREEN}[4]${NC} CloudPanel" echo -e " ${GREEN}[5]${NC} Proxmox VE Tools" echo -e " ${RED}[0]${NC} Back" echo "" echo -e -n "${BOLD}Choice [0-5]: ${NC}" read -r choice case $choice in 1) download_and_run "apps/coolify.sh" ;; 2) download_and_run "apps/pterodactyl.sh" ;; 3) download_and_run "apps/uptime-kuma.sh" ;; 4) download_and_run "apps/cloudpanel.sh" ;; 5) download_and_run "apps/proxmox.sh" ;; 0) return ;; *) echo -e "${RED}[✗] Invalid option. Please select 0-5.${NC}"; sleep 1 ;; esac if [ "$choice" != "0" ]; then echo "" read -r -p "Press Enter to continue..." fi done } main_menu() { check_os check_remote_version while true; do show_header echo -e "${WHITE}${BOLD}MAIN MENU${NC}" echo "" echo -e " ${GREEN}[1]${NC} Applications" echo -e " ${CYAN}[2]${NC} System Tools" echo -e " ${PURPLE}[3]${NC} Server Benchmark" echo -e " ${YELLOW}[4]${NC} Harden Server" echo -e " ${GRAY}[u]${NC} Update lxs" echo -e " ${RED}[0]${NC} Exit" echo "" echo -e -n "${BOLD}Choice: ${NC}" read -r choice case $choice in 1) menu_install_apps ;; 2) download_and_run "tools/system-tools.sh" ;; 3) download_and_run "tools/server-benchmark.sh" ;; 4) download_and_run "tools/harden.sh" ;; u|U) cmd_update ;; 0) clear show_lxs_logo echo -e "${GREEN}Bye.${NC}\n" exit 0 ;; *) echo -e "${RED}[✗] Invalid option.${NC}"; sleep 1 ;; esac if [[ "$choice" != "0" && "$choice" != "1" ]]; then echo "" read -r -p "Press Enter to continue..." fi done } # ═══════════════════════════════════════════════════════════════════════════ # Entrypoint # ═══════════════════════════════════════════════════════════════════════════ case "${1:-}" in install) shift; cmd_install "$@" ;; tool) shift; cmd_tool "$@" ;; info) show_header ;; update) cmd_update ;; setup|install-self) cmd_install_self ;; version|-v|--version) cmd_version ;; help|-h|--help) cmd_help ;; "") main_menu ;; *) echo -e "${RED}[✗] Unknown command: $1${NC}"; cmd_help; exit 1 ;; esac