c7dcaed0bf
- reduce color palette to red/cyan/white/gray; alias legacy vars onto CYAN - add show_box_top/mid/bottom, show_separator, show_menu_item, show_prompt helpers to lib/common.sh - update apps/index.sh and tools/index.sh to use new UI helpers instead of inline echo/ANSI - drop BLINK, REV escape codes and remove hardcoded prompt strings from callers
423 lines
16 KiB
Bash
Executable File
423 lines
16 KiB
Bash
Executable File
#!/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() {
|
|
show_box_top "LXS // v${LXS_VERSION}" "ONLINE"
|
|
}
|
|
|
|
show_header() {
|
|
clear
|
|
show_lxs_logo
|
|
|
|
if [ "${LXS_UPDATE_AVAILABLE:-0}" = "1" ]; then
|
|
echo ""
|
|
echo -e " ${CYAN}[!!] UPDATE_AVAILABLE${NC} ${WHITE}v${LXS_REMOTE_VERSION}${NC} ${GRAY}// run \`lxs update\`${NC}"
|
|
fi
|
|
|
|
local hostname os_version kernel uptime_info ip_address 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 | sed 's/^up //' || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')
|
|
ip_address=$(get_public_ip)
|
|
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 ""
|
|
printf " ${CYAN}NODE${NC} ${WHITE}%-26s${NC} ${CYAN}KERN${NC} ${WHITE}%s${NC}\n" "$hostname" "$kernel"
|
|
printf " ${CYAN}ADDR${NC} ${WHITE}%-26s${NC} ${CYAN}UP${NC} ${WHITE}%s${NC}\n" "$ip_address" "$uptime_info"
|
|
printf " ${CYAN}OS${NC} ${WHITE}%-26s${NC} ${CYAN}CPU${NC} ${WHITE}%s cores${NC}\n" "$os_version" "$cpu_cores"
|
|
printf " ${CYAN}DISK${NC} ${WHITE}%-26s${NC} ${CYAN}MEM${NC} ${WHITE}%s / %s${NC}\n" "$disk_usage" "$used_mem" "$total_mem"
|
|
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}[OK] 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 "${CYAN}[..] Fetching ${BOLD}${script_name}${NC}${CYAN}...${NC}"
|
|
|
|
if curl -fsSL -H "Cache-Control: no-cache" \
|
|
-o "${temp_file}" \
|
|
"${LXS_RAW_BASE}/${script_path}"; then
|
|
echo -e "${GREEN}[OK] Payload acquired${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}[OK] Script completed successfully${NC}"
|
|
else
|
|
echo -e "${YELLOW}[!!] Script exited with code: $exit_code${NC}"
|
|
fi
|
|
return $exit_code
|
|
else
|
|
echo -e "${RED}[KO] 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 <<EOF
|
|
LXS - Linux multi-tool (v${LXS_VERSION})
|
|
|
|
Usage:
|
|
lxs Interactive menu (browse apps and tools)
|
|
lxs setup Install lxs and all sub-scripts to ${LXS_INSTALL_DIR}
|
|
lxs update Update all installed files to latest
|
|
lxs install <app> Install an application
|
|
lxs tool <name> [args] Run a system tool
|
|
lxs info Show system info
|
|
lxs version Show version
|
|
lxs help Show this help
|
|
|
|
Run \`lxs\` for the interactive menu to browse available applications and tools.
|
|
|
|
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}[KO] Missing app name. Try: lxs help${NC}"; return 1 ;;
|
|
*) echo -e "${RED}[KO] 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-infos.sh" "$@" ;;
|
|
benchmark) download_and_run "tools/server-benchmark.sh" "$@" ;;
|
|
harden) download_and_run "tools/harden.sh" "$@" ;;
|
|
root-password) download_and_run "tools/root-password.sh" "$@" ;;
|
|
update) download_and_run "tools/update-server.sh" "$@" ;;
|
|
root-ssh-login) download_and_run "tools/root-ssh-login.sh" "$@" ;;
|
|
welcome|motd) download_and_run "tools/welcome-message.sh" "$@" ;;
|
|
"") echo -e "${RED}[KO] Missing tool name. Try: lxs help${NC}"; return 1 ;;
|
|
*) echo -e "${RED}[KO] 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}[KO] Failed to create temp dir${NC}"
|
|
return 1
|
|
}
|
|
trap 'rm -rf "$work"' RETURN
|
|
|
|
echo -e "${CYAN}[..] Fetching ${LXS_TARBALL_URL}...${NC}"
|
|
if ! curl -fsSL -H "Cache-Control: no-cache" -o "${work}/lxs.tgz" "$LXS_TARBALL_URL"; then
|
|
echo -e "${RED}[KO] 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}[KO] Extraction failed${NC}"
|
|
return 1
|
|
fi
|
|
|
|
if [ ! -f "${work}/extracted/lxs.sh" ] || [ ! -f "${work}/extracted/VERSION" ]; then
|
|
echo -e "${RED}[KO] 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}[KO] 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}[KO] 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}[OK] lxs ${new_version} installed in ${LXS_INSTALL_DIR}${NC}"
|
|
echo -e "${GREEN}[OK] Symlink: ${LXS_BIN_PATH} → ${LXS_INSTALL_DIR}/lxs.sh${NC}"
|
|
}
|
|
|
|
cmd_update() {
|
|
lxs_sync_install update
|
|
}
|
|
|
|
cmd_install_self() {
|
|
lxs_sync_install setup
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
# Interactive menu
|
|
# ═══════════════════════════════════════════════════════════════════════════
|
|
|
|
main_menu() {
|
|
check_os
|
|
check_remote_version
|
|
while true; do
|
|
show_header
|
|
show_box_mid "SELECT"
|
|
echo ""
|
|
show_menu_item "01" "APPLICATIONS" "deploy stacks"
|
|
show_menu_item "02" "TOOLS" "sysadmin daemons"
|
|
show_menu_item "UU" "FETCH_UPDATE" "sync remote"
|
|
show_menu_item "00" "JACK_OUT" "exit shell" exit
|
|
echo ""
|
|
show_box_bottom
|
|
echo ""
|
|
show_prompt
|
|
read -r choice
|
|
|
|
case $choice in
|
|
1) download_and_run "apps/index.sh" ;;
|
|
2) download_and_run "tools/index.sh" ;;
|
|
u|U|uu|UU)
|
|
if cmd_update && [ -x "${LXS_INSTALL_DIR}/lxs.sh" ]; then
|
|
echo ""
|
|
read -r -p "Press Enter to reload with the new version..."
|
|
exec bash "${LXS_INSTALL_DIR}/lxs.sh"
|
|
fi
|
|
;;
|
|
0|00)
|
|
clear
|
|
show_lxs_logo
|
|
echo -e "${CYAN}JACK_OUT${NC} ${GRAY}// session terminated${NC}\n"
|
|
exit 0
|
|
;;
|
|
*) echo -e "${RED}[KO] Invalid protocol.${NC}"; sleep 1 ;;
|
|
esac
|
|
|
|
if [[ "$choice" != "0" && "$choice" != "00" && "$choice" != "1" && "$choice" != "2" ]]; 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}[KO] Unknown command: $1${NC}"; cmd_help; exit 1 ;;
|
|
esac
|