feat(windows): add windows compatible

This commit is contained in:
2026-09-09 15:31:57 -04:00
parent db3f5d6652
commit 3fda22d3d5
16 changed files with 0 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
#!/bin/bash
# LXS - Server Hardening
# Description: Apply baseline security hardening (UFW + fail2ban + unattended-upgrades)
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_harden.log"
require_root "$0" "$@"
set -u
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
DO_UFW=1
DO_FAIL2BAN=1
DO_UNATTENDED=1
ASSUME_YES=0
for arg in "$@"; do
case "$arg" in
--no-ufw) DO_UFW=0 ;;
--no-fail2ban) DO_FAIL2BAN=0 ;;
--no-unattended) DO_UNATTENDED=0 ;;
-y|--yes) ASSUME_YES=1 ;;
-h|--help)
cat <<EOF
Usage: harden.sh [options]
Options:
--no-ufw Skip UFW firewall setup
--no-fail2ban Skip fail2ban setup
--no-unattended Skip unattended-upgrades setup
-y, --yes Skip confirmation prompt
-h, --help Show this help
EOF
exit 0
;;
*)
echo -e "${RED}Unknown option: $arg${NC}" >&2
exit 1
;;
esac
done
# ═══════════════════════════════════════════════════════════════════════════
# Pre-checks
# ═══════════════════════════════════════════════════════════════════════════
require_debian_ubuntu || exit 1
require_disk_space 500 || exit 1
# Read the effective Port from sshd_config + any drop-in under sshd_config.d/
sshd_effective_port() {
local files=(/etc/ssh/sshd_config)
if compgen -G "/etc/ssh/sshd_config.d/*.conf" >/dev/null; then
files+=(/etc/ssh/sshd_config.d/*.conf)
fi
awk '/^[[:space:]]*Port[[:space:]]+/ {print $2}' "${files[@]}" 2>/dev/null | tail -1
}
SSH_PORT=$(sshd_effective_port)
SSH_PORT=${SSH_PORT:-22}
echo -e "${WHITE}${BOLD}LXS Server Hardening${NC}"
show_separator
echo "The following actions will be applied to this server:"
[ $DO_UFW -eq 1 ] && echo " • UFW firewall: default deny incoming, allow SSH on port ${SSH_PORT}"
[ $DO_FAIL2BAN -eq 1 ] && echo " • fail2ban: enable sshd jail (bantime 1h, maxretry 5)"
[ $DO_UNATTENDED -eq 1 ] && echo " • unattended-upgrades: enable automatic security updates"
show_separator
if [ $ASSUME_YES -ne 1 ]; then
read -r -p "Proceed? [y/N] " reply
case "$reply" in
[yY]|[yY][eE][sS]) ;;
*) info "Aborted."; exit 0 ;;
esac
fi
apt_noninteractive
wait_for_apt || exit 1
# ═══════════════════════════════════════════════════════════════════════════
# UFW
# ═══════════════════════════════════════════════════════════════════════════
setup_ufw() {
info "Installing and configuring UFW..."
apt-get update -qq
apt-get install -y -qq ufw
ufw --force reset >/dev/null
ufw default deny incoming
ufw default allow outgoing
ufw allow "${SSH_PORT}/tcp" comment 'SSH'
ufw --force enable
ok "UFW enabled (SSH on ${SSH_PORT}/tcp allowed)"
}
# ═══════════════════════════════════════════════════════════════════════════
# fail2ban
# ═══════════════════════════════════════════════════════════════════════════
setup_fail2ban() {
info "Installing and configuring fail2ban..."
apt-get install -y -qq fail2ban
cat > /etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
[sshd]
enabled = true
port = ${SSH_PORT}
EOF
systemctl enable --now fail2ban
systemctl restart fail2ban
ok "fail2ban enabled (sshd jail active)"
}
# ═══════════════════════════════════════════════════════════════════════════
# unattended-upgrades
# ═══════════════════════════════════════════════════════════════════════════
setup_unattended() {
info "Installing and enabling unattended-upgrades..."
apt-get install -y -qq unattended-upgrades apt-listchanges
cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF
systemctl enable --now unattended-upgrades
ok "unattended-upgrades enabled (security updates only by default)"
}
# ═══════════════════════════════════════════════════════════════════════════
# Run
# ═══════════════════════════════════════════════════════════════════════════
[ $DO_UFW -eq 1 ] && setup_ufw
[ $DO_FAIL2BAN -eq 1 ] && setup_fail2ban
[ $DO_UNATTENDED -eq 1 ] && setup_unattended
# ═══════════════════════════════════════════════════════════════════════════
# Summary
# ═══════════════════════════════════════════════════════════════════════════
echo ""
echo -e "${WHITE}${BOLD}Summary${NC}"
show_separator
command -v ufw >/dev/null && echo "UFW: $(ufw status | head -1)"
systemctl is-active fail2ban >/dev/null 2>&1 && echo "fail2ban: active" || echo "fail2ban: inactive"
systemctl is-active unattended-upgrades >/dev/null 2>&1 && echo "unattended-upgrades: active" || echo "unattended-upgrades: inactive"
echo ""
ok "Hardening complete."
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# LXS - Tools index
# Description: Interactive menu listing the scripts in tools/
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
# Run a sibling tool script. Prefers a file next to this script (installed
# layout); falls back to downloading from LXS_RAW_BASE.
run_sibling() {
local script_path=$1
shift
local script_name self_dir resolved src="${BASH_SOURCE[0]}"
script_name=$(basename "$script_path")
if [ -n "$src" ]; then
resolved=$(readlink -f "$src" 2>/dev/null) \
|| resolved=$(realpath "$src" 2>/dev/null) \
|| resolved="$src"
self_dir=$(dirname "$resolved")
fi
# self_dir points at tools/; the install layout puts siblings here too.
if [ -n "$self_dir" ] && [ -f "${self_dir}/${script_name}" ]; then
chmod +x "${self_dir}/${script_name}" 2>/dev/null || true
"${self_dir}/${script_name}" "$@"
return $?
fi
local temp_file exit_code
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
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}"
"${temp_file}" "$@"
exit_code=$?
rm -f "${temp_file}"
return $exit_code
else
echo -e "${RED}[KO] Failed to download ${script_path}${NC}"
rm -f "${temp_file}"
return 1
fi
}
menu_tools() {
while true; do
clear
show_box_top "TOOLS" "SYS_DAEMONS"
echo ""
show_menu_item "01" "System Infos"
show_menu_item "02" "Server Benchmark"
show_menu_item "03" "Harden Server"
show_menu_item "04" "Change Root Password"
show_menu_item "05" "Update Server"
show_menu_item "06" "Root SSH Password Login"
show_menu_item "07" "Welcome Message (MOTD)"
show_menu_item "00" "BACK" "" exit
echo ""
show_box_bottom
echo ""
show_prompt
read -r choice
child_rc=0
case $choice in
1|01) run_sibling "tools/system-infos.sh"; child_rc=$? ;;
2|02) run_sibling "tools/server-benchmark.sh"; child_rc=$? ;;
3|03) run_sibling "tools/harden.sh"; child_rc=$? ;;
4|04) run_sibling "tools/root-password.sh"; child_rc=$? ;;
5|05) run_sibling "tools/update-server.sh"; child_rc=$? ;;
6|06) run_sibling "tools/root-ssh-login.sh"; child_rc=$? ;;
7|07) run_sibling "tools/welcome-message.sh"; child_rc=$? ;;
0|00) return ;;
*) echo -e "${RED}[KO] Invalid protocol. Select 0-7.${NC}"; sleep 1; continue ;;
esac
# Exit code 75 from a child means it already paused on its own
# (its own "Back" or end-of-run prompt) — skip the redundant prompt.
if [ "$child_rc" -ne 75 ]; then
echo ""
read -r -p "Press Enter to continue..."
fi
done
}
menu_tools
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
# LXS - Change root password
# Description: Change the root account password (interactive or generated)
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_root_password.log"
require_root "$0" "$@"
set -u
# ═══════════════════════════════════════════════════════════════════════════
# Arguments
# ═══════════════════════════════════════════════════════════════════════════
MODE=""
PASSWORD_LENGTH=24
for arg in "$@"; do
case "$arg" in
-g|--generate) MODE="generate" ;;
-i|--interactive) MODE="interactive" ;;
--length=*) PASSWORD_LENGTH="${arg#*=}" ;;
-h|--help)
cat <<EOF
Usage: root-password.sh [options]
Options:
-i, --interactive Prompt for the new password (passwd root)
-g, --generate Generate a strong random password and apply it
--length=N Length of the generated password (default: 24)
-h, --help Show this help
With no option, an interactive menu is shown.
EOF
exit 0
;;
*)
echo -e "${RED}Unknown option: $arg${NC}" >&2
exit 1
;;
esac
done
# ═══════════════════════════════════════════════════════════════════════════
# Actions
# ═══════════════════════════════════════════════════════════════════════════
change_interactive() {
info "Setting root password interactively..."
show_separator
if passwd root; then
show_separator
ok "Root password updated"
return 0
fi
show_separator
err "Failed to update root password"
return 1
}
change_generated() {
if ! [[ "$PASSWORD_LENGTH" =~ ^[0-9]+$ ]] || [ "$PASSWORD_LENGTH" -lt 12 ]; then
err "--length must be a number ≥ 12 (got: ${PASSWORD_LENGTH})"
return 1
fi
local new_password
new_password=$(generate_password "$PASSWORD_LENGTH")
if [ -z "$new_password" ]; then
err "Failed to generate a password"
return 1
fi
if ! echo "root:${new_password}" | chpasswd; then
err "Failed to apply the generated password"
return 1
fi
show_separator
ok "Root password updated"
echo ""
echo -e "${WHITE}${BOLD}New root password:${NC} ${YELLOW}${new_password}${NC}"
echo ""
warn "Store this password in a secure place. It will not be shown again."
show_separator
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Menu (when no mode is given on the CLI)
# ═══════════════════════════════════════════════════════════════════════════
if [ -z "$MODE" ]; then
echo -e "${WHITE}${BOLD}LXS - Change root password${NC}"
show_separator
echo -e " ${CYAN}[1]${NC} Set a new password interactively"
echo -e " ${CYAN}[2]${NC} Generate a strong random password"
echo -e " ${RED}[0]${NC} Cancel"
echo ""
echo -e -n "${BOLD}Choice [0-2]: ${NC}"
read -r choice
case "$choice" in
1) MODE="interactive" ;;
2) MODE="generate" ;;
0|"") info "Cancelled."; exit 0 ;;
*) err "Invalid option."; exit 1 ;;
esac
fi
case "$MODE" in
interactive) change_interactive ;;
generate) change_generated ;;
esac
+189
View File
@@ -0,0 +1,189 @@
#!/bin/bash
# LXS - Root SSH password login
# Description: Enable or disable root login over SSH with a password
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_root_ssh_login.log"
require_root "$0" "$@"
set -u
# Drop-in path. Numeric prefix `00-` makes it win over distro defaults — sshd
# applies the first match per option across the included files.
DROPIN_FILE="/etc/ssh/sshd_config.d/00-lxs-root-login.conf"
SSH_SERVICE="ssh"
command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^sshd\.service' && SSH_SERVICE="sshd"
# ═══════════════════════════════════════════════════════════════════════════
# Arguments
# ═══════════════════════════════════════════════════════════════════════════
ACTION=""
for arg in "$@"; do
case "$arg" in
--enable|enable) ACTION="enable" ;;
--disable|disable) ACTION="disable" ;;
--status|status) ACTION="status" ;;
-h|--help)
cat <<EOF
Usage: root-ssh-login.sh [action]
Actions:
--enable Allow root to log in over SSH with a password
--disable Disallow root password login (restore SSH defaults)
--status Show the current effective settings
-h, --help Show this help
With no action, an interactive menu is shown.
Note: the change is written to ${DROPIN_FILE}
and applied by reloading the SSH service after a successful 'sshd -t'.
EOF
exit 0
;;
*)
echo -e "${RED}Unknown option: $arg${NC}" >&2
exit 1
;;
esac
done
# ═══════════════════════════════════════════════════════════════════════════
# Pre-checks
# ═══════════════════════════════════════════════════════════════════════════
if [ ! -d /etc/ssh ] || ! command -v sshd >/dev/null 2>&1; then
err "OpenSSH server is not installed."
exit 1
fi
# ═══════════════════════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════════════════════
current_setting() {
local key=$1
sshd -T 2>/dev/null | awk -v k="${key,,}" 'tolower($1)==k {print $2; exit}'
}
show_status() {
local permit pwauth
permit=$(current_setting PermitRootLogin)
pwauth=$(current_setting PasswordAuthentication)
echo -e "${WHITE}${BOLD}Current SSH login settings${NC}"
show_separator
echo -e " PermitRootLogin: ${BOLD}${permit:-unknown}${NC}"
echo -e " PasswordAuthentication: ${BOLD}${pwauth:-unknown}${NC}"
if [ -f "$DROPIN_FILE" ]; then
echo -e " Drop-in: ${GRAY}${DROPIN_FILE}${NC}"
else
echo -e " Drop-in: ${GRAY}(none — distro defaults)${NC}"
fi
show_separator
if [ "${permit:-}" = "yes" ] && [ "${pwauth:-}" = "yes" ]; then
warn "Root password login is currently ENABLED."
else
ok "Root password login is currently DISABLED."
fi
}
reload_sshd() {
if ! sshd -t 2>>"$LXS_LOG_FILE"; then
err "sshd config test failed — see ${LXS_LOG_FILE}. Reverting."
return 1
fi
if systemctl reload "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then
ok "${SSH_SERVICE} reloaded"
return 0
fi
# Fall back to restart (some minimal images ship without reload support)
if systemctl restart "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then
ok "${SSH_SERVICE} restarted"
return 0
fi
err "Failed to reload/restart ${SSH_SERVICE} — see ${LXS_LOG_FILE}"
return 1
}
enable_root_login() {
# Warn if root has no password — enabling password auth would be useless.
local pw_status
pw_status=$(passwd -S root 2>/dev/null | awk '{print $2}')
case "$pw_status" in
L|NP)
warn "Root account has no usable password (status: ${pw_status})."
warn "Run 'lxs tool root-password' first, otherwise login will still fail."
;;
esac
cat > "${DROPIN_FILE}.tmp" <<'EOF'
# Managed by LXS (lxs tool root-ssh-login). Remove this file to revert.
PermitRootLogin yes
PasswordAuthentication yes
EOF
chmod 644 "${DROPIN_FILE}.tmp"
mv "${DROPIN_FILE}.tmp" "$DROPIN_FILE"
if ! reload_sshd; then
rm -f "$DROPIN_FILE"
reload_sshd >/dev/null 2>&1 || true
return 1
fi
ok "Root password login over SSH is now ENABLED"
warn "This weakens server security. Disable it again when no longer needed:"
echo -e " ${GRAY}lxs tool root-ssh-login --disable${NC}"
}
disable_root_login() {
if [ ! -f "$DROPIN_FILE" ]; then
info "Drop-in not present — root password login already follows SSH defaults."
else
local backup="${DROPIN_FILE}.bak.$$"
mv "$DROPIN_FILE" "$backup"
if ! reload_sshd; then
mv "$backup" "$DROPIN_FILE"
reload_sshd >/dev/null 2>&1 || true
return 1
fi
rm -f "$backup"
fi
ok "Root password login over SSH is now DISABLED"
echo ""
show_status
}
# ═══════════════════════════════════════════════════════════════════════════
# Menu (when no action is given on the CLI)
# ═══════════════════════════════════════════════════════════════════════════
if [ -z "$ACTION" ]; then
show_status
echo ""
echo -e " ${GREEN}[1]${NC} Enable root SSH password login"
echo -e " ${YELLOW}[2]${NC} Disable root SSH password login"
echo -e " ${RED}[0]${NC} Cancel"
echo ""
echo -e -n "${BOLD}Choice [0-2]: ${NC}"
read -r choice
case "$choice" in
1) ACTION="enable" ;;
2) ACTION="disable" ;;
0|"") info "Cancelled."; exit 0 ;;
*) err "Invalid option."; exit 1 ;;
esac
fi
case "$ACTION" in
enable) enable_root_login ;;
disable) disable_root_login ;;
status) show_status ;;
esac
+815
View File
@@ -0,0 +1,815 @@
#!/bin/bash
# LXS - Server Benchmark Tool
# Description: Performance testing and benchmarking tool
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_benchmark.log"
# Always clean up benchmark artifacts on exit, even on error or Ctrl+C. Without
# this, a partial 1-2GB dd file in /tmp can stay behind and fill the disk on
# small servers if the script is re-run.
cleanup_benchmark_artifacts() {
rm -f /tmp/lxs_test_write_* \
/tmp/lxs_test_read_* \
/tmp/lxs_write_result_*.tmp \
/tmp/lxs_read_result_*.tmp \
/tmp/lxs_cpu_result.tmp \
/tmp/lxs_ram_result.tmp \
/tmp/lxs_ping_result.tmp \
/tmp/lxs_ping_*.tmp 2>/dev/null || true
}
trap cleanup_benchmark_artifacts EXIT INT TERM
# Minimum free space (MB) to keep available during disk tests. If /tmp drops
# below this threshold mid-run we abort the remaining passes.
DISK_SAFETY_MARGIN_MB=200
# ═══════════════════════════════════════════════════════════════════════════
# Score Variables
# ═══════════════════════════════════════════════════════════════════════════
SCORE_CPU=0
SCORE_RAM=0
SCORE_DISK_WRITE=0
SCORE_DISK_READ=0
SCORE_NETWORK=0
SCORE_NETWORK_LATENCY=0
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
show_header() {
show_box_top "SERVER PERFORMANCE BENCHMARK"
}
install_dependencies() {
local deps_installed=true
# Check for sysbench
if ! command -v sysbench &> /dev/null; then
echo -e "${YELLOW}[!] sysbench not found, installing...${NC}"
echo ""
require_disk_space 300 || return 1
if command -v apt-get &> /dev/null; then
run_spinner "Updating package list..." "apt-get update -qq"
run_spinner "Installing sysbench..." "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq sysbench"
elif command -v yum &> /dev/null; then
run_spinner "Installing sysbench..." "yum install -y -q sysbench"
elif command -v dnf &> /dev/null; then
run_spinner "Installing sysbench..." "dnf install -y -q sysbench"
else
echo -e "${RED}[✗] Unable to install sysbench automatically${NC}"
deps_installed=false
fi
# Verify installation
if ! command -v sysbench &> /dev/null; then
deps_installed=false
fi
else
echo -e "${GREEN}[✓] All dependencies ready${NC}"
fi
if [ "$deps_installed" = true ]; then
echo ""
return 0
else
echo -e "${RED}[✗] Failed to install required dependencies${NC}"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# System Information
# ═══════════════════════════════════════════════════════════════════════════
show_system_info() {
echo -e "${WHITE}${BOLD}SYSTEM INFORMATION${NC}"
show_separator
local hostname=$(hostname)
local cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)
local cpu_cores=$(nproc)
local total_mem=$(free -h | awk '/^Mem:/ {print $2}')
local os_version=$(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)
local kernel=$(uname -r)
local disk_info=$(df -h / | awk 'NR==2 {print $2}')
echo -e "${GRAY}Hostname:${NC} $hostname"
echo -e "${GRAY}OS:${NC} $os_version"
echo -e "${GRAY}Kernel:${NC} $kernel"
echo -e "${GRAY}CPU:${NC} $cpu_model"
echo -e "${GRAY}vCPU Cores:${NC} $cpu_cores"
echo -e "${GRAY}RAM:${NC} $total_mem"
echo -e "${GRAY}Disk Size:${NC} $disk_info"
echo -e "${GRAY}Date:${NC} $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
}
# ═══════════════════════════════════════════════════════════════════════════
# Benchmark Tests
# ═══════════════════════════════════════════════════════════════════════════
test_cpu() {
echo -e "${WHITE}${BOLD}CPU PERFORMANCE TEST${NC}"
show_separator
local cpu_cores=$(nproc)
# Run sysbench CPU test with spinner
local temp_file="/tmp/lxs_cpu_result.tmp"
echo -e "${PURPLE}[*] Testing CPU performance (prime number calculation)...${NC}"
sysbench cpu --cpu-max-prime=20000 --threads=$cpu_cores run 2>/dev/null > "$temp_file" &
local pid=$!
local spinstr='|/-\'
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing CPU performance (prime number calculation)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
CPU_RESULT=$(grep "events per second" "$temp_file" | awk '{print $4}')
rm -f "$temp_file"
if [ -z "$CPU_RESULT" ]; then
printf "\r${RED}[✗]${NC} CPU test failed \n"
SCORE_CPU=0
else
printf "\r${GREEN}[✓]${NC} CPU test completed \n"
SCORE_CPU=$(echo "$CPU_RESULT" | awk '{printf "%.0f", $1}')
echo -e "${GREEN} Events per second:${NC} $CPU_RESULT"
echo -e "${CYAN} CPU Score:${NC} ${BOLD}$SCORE_CPU points${NC}"
fi
echo ""
}
test_memory() {
echo -e "${WHITE}${BOLD}MEMORY PERFORMANCE TEST${NC}"
show_separator
local cpu_cores=$(nproc)
# Run sysbench memory test with spinner
local temp_file="/tmp/lxs_ram_result.tmp"
echo -e "${PURPLE}[*] Testing memory performance (transfer speed)...${NC}"
sysbench memory --memory-total-size=5G --threads=$cpu_cores run 2>/dev/null > "$temp_file" &
local pid=$!
local spinstr='|/-\'
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing memory performance (transfer speed)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
RAM_RESULT=$(grep "transferred" "$temp_file" | awk '{print $(NF-1)}' | sed 's/[^0-9.]//g')
rm -f "$temp_file"
if [ -z "$RAM_RESULT" ]; then
printf "\r${RED}[✗]${NC} Memory test failed \n"
SCORE_RAM=0
else
printf "\r${GREEN}[✓]${NC} Memory test completed \n"
SCORE_RAM=$(echo "$RAM_RESULT" | awk '{printf "%.0f", $1}')
echo -e "${GREEN} Transfer speed:${NC} $RAM_RESULT MiB/sec"
echo -e "${CYAN} RAM Score:${NC} ${BOLD}$SCORE_RAM points${NC}"
fi
echo ""
}
test_disk_write() {
echo -e "${WHITE}${BOLD}DISK WRITE PERFORMANCE TEST${NC}"
show_separator
# Check available disk space in /tmp
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
# Build test configurations dynamically based on free space. Each test needs
# room for its file PLUS the safety margin, otherwise dd can fill the disk
# on small servers (1-2GB VPS). Tests are added from smallest to largest.
local test_configs=()
if [ "$available_space" -gt $((100 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("100:1M:100MB Sequential")
fi
if [ "$available_space" -gt $((1024 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("1024:1M:1GB Sequential")
fi
if [ "$available_space" -gt $((2048 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("2048:1M:2GB Sequential")
fi
if [ ${#test_configs[@]} -eq 0 ]; then
echo -e "${RED}[✗] Not enough free space on /tmp (${available_space}MB) to run any write test${NC}"
SCORE_DISK_WRITE=0
echo ""
return 0
fi
local all_speeds=()
local all_speeds_mb=()
local test_count=0
local spinstr='|/-\'
local test_count_display=$((${#test_configs[@]} * 3))
echo -e "${PURPLE}[*] Running advanced write tests (${#test_configs[@]} sizes x 3 passes = ${test_count_display} tests)...${NC}"
if [ "$available_space" -le 3072 ]; then
echo -e "${YELLOW}[!] Limited disk space (${available_space}MB free), running reduced test set${NC}"
fi
echo ""
# Run tests for each configuration
for config in "${test_configs[@]}"; do
local size_mb=$(echo "$config" | cut -d':' -f1)
local block_size=$(echo "$config" | cut -d':' -f2)
local description=$(echo "$config" | cut -d':' -f3)
local pass_speeds=()
local pass_count=0
# Re-check free space before each config; the previous test may have
# left the filesystem tighter than expected (cache, logs, etc.).
local current_free=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
if [ "$current_free" -lt $((size_mb + DISK_SAFETY_MARGIN_MB)) ]; then
printf "\r${YELLOW}[!]${NC} ${description}: Skipped (only ${current_free}MB free)\n"
continue
fi
# Run 3 passes for each configuration
for pass in 1 2 3; do
# Clean up before test
rm -f /tmp/lxs_test_write_${size_mb}
sync
# Clear cache
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null
sleep 0.5
local temp_file="/tmp/lxs_write_result_${size_mb}_${pass}.tmp"
# Run dd test with direct I/O (suppress error messages)
(dd if=/dev/zero of=/tmp/lxs_test_write_${size_mb} bs=${block_size} count=${size_mb} oflag=direct 2>"$temp_file" || true) &
local pid=$!
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing ${description} (Pass ${pass}/3)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
local exit_code=$?
# Parse result
local speed_value=""
local speed_unit=""
if [ $exit_code -eq 0 ]; then
local dd_output=$(cat "$temp_file")
speed_value=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $1}')
speed_unit=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $2}')
fi
# If direct I/O failed, try with sync
if [ -z "$speed_value" ] || [ "$speed_value" = "0" ] || [ $exit_code -ne 0 ]; then
rm -f /tmp/lxs_test_write_${size_mb}
(dd if=/dev/zero of=/tmp/lxs_test_write_${size_mb} bs=${block_size} count=${size_mb} conv=fdatasync 2>"$temp_file" || true) &
pid=$!
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing ${description} (Pass ${pass}/3)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
sync
local dd_output=$(cat "$temp_file")
speed_value=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $1}')
speed_unit=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $2}')
fi
rm -f "$temp_file"
# Convert to MB/s
local speed_mb=0
if [ ! -z "$speed_value" ] && [ "$speed_value" != "0" ]; then
if [ "$speed_unit" = "GB/s" ]; then
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1 * 1024}')
elif [ "$speed_unit" = "KB/s" ]; then
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1 / 1024}')
else
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1}')
fi
pass_speeds+=($speed_mb)
((pass_count++))
fi
# Clean up test file
rm -f /tmp/lxs_test_write_${size_mb}
done
# Calculate average for this configuration
if [ $pass_count -gt 0 ]; then
local sum=0
for speed in "${pass_speeds[@]}"; do
sum=$(echo "$sum + $speed" | bc -l)
done
local avg=$(echo "scale=2; $sum / $pass_count" | bc -l)
printf "\r${GREEN}[✓]${NC} ${description}: ${GREEN}${avg} MB/s${NC} (avg of ${pass_count} passes)\n"
all_speeds_mb+=($avg)
((test_count++))
else
printf "\r${YELLOW}[!]${NC} ${description}: Test failed\n"
fi
done
echo ""
# Calculate final score
if [ $test_count -gt 0 ]; then
local total_speed=0
for speed in "${all_speeds_mb[@]}"; do
total_speed=$(echo "$total_speed + $speed" | bc -l)
done
local avg_speed=$(echo "scale=2; $total_speed / $test_count" | bc -l)
SCORE_DISK_WRITE=$(echo "$avg_speed" | awk '{printf "%.0f", $1}')
echo -e "${CYAN} Average Write Speed:${NC} ${BOLD}${avg_speed} MB/s${NC}"
echo -e "${CYAN} Write Score:${NC} ${BOLD}$SCORE_DISK_WRITE points${NC}"
# Quality assessment
if (( $(echo "$avg_speed >= 1000" | bc -l) )); then
echo -e "${GREEN} Quality:${NC} Excellent (NVMe SSD)"
elif (( $(echo "$avg_speed >= 400" | bc -l) )); then
echo -e "${GREEN} Quality:${NC} Very Good (SATA SSD)"
elif (( $(echo "$avg_speed >= 100" | bc -l) )); then
echo -e "${CYAN} Quality:${NC} Good (Fast HDD/Basic SSD)"
else
echo -e "${YELLOW} Quality:${NC} Standard (HDD)"
fi
else
echo -e "${RED}[✗] All write tests failed${NC}"
SCORE_DISK_WRITE=0
fi
echo ""
}
test_disk_read() {
echo -e "${WHITE}${BOLD}DISK READ PERFORMANCE TEST${NC}"
show_separator
# Check available disk space in /tmp
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
# Build test configurations dynamically based on free space (see write test
# for rationale). The read test also writes a source file first, so the
# space requirement is the same as the write test.
local test_configs=()
if [ "$available_space" -gt $((100 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("100:1M:100MB Sequential")
fi
if [ "$available_space" -gt $((1024 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("1024:1M:1GB Sequential")
fi
if [ "$available_space" -gt $((2048 + DISK_SAFETY_MARGIN_MB)) ]; then
test_configs+=("2048:1M:2GB Sequential")
fi
if [ ${#test_configs[@]} -eq 0 ]; then
echo -e "${RED}[✗] Not enough free space on /tmp (${available_space}MB) to run any read test${NC}"
SCORE_DISK_READ=0
echo ""
return 0
fi
local all_speeds_mb=()
local test_count=0
local spinstr='|/-\'
local test_count_display=$((${#test_configs[@]} * 3))
echo -e "${PURPLE}[*] Running advanced read tests (${#test_configs[@]} sizes x 3 passes = ${test_count_display} tests)...${NC}"
if [ "$available_space" -le 3072 ]; then
echo -e "${YELLOW}[!] Limited disk space (${available_space}MB free), running reduced test set${NC}"
fi
echo ""
# Run tests for each configuration
for config in "${test_configs[@]}"; do
local size_mb=$(echo "$config" | cut -d':' -f1)
local block_size=$(echo "$config" | cut -d':' -f2)
local description=$(echo "$config" | cut -d':' -f3)
# Re-check free space before each config (same rationale as write test).
local current_free=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
if [ "$current_free" -lt $((size_mb + DISK_SAFETY_MARGIN_MB)) ]; then
printf "\r${YELLOW}[!]${NC} ${description}: Skipped (only ${current_free}MB free)\n"
continue
fi
# Create test file if it doesn't exist
if [ ! -f /tmp/lxs_test_read_${size_mb} ]; then
dd if=/dev/zero of=/tmp/lxs_test_read_${size_mb} bs=${block_size} count=${size_mb} 2>/dev/null || {
# If file creation fails, skip this test and remove any partial file
rm -f /tmp/lxs_test_read_${size_mb}
continue
}
sync
fi
local pass_speeds=()
local pass_count=0
# Run 3 passes for each configuration
for pass in 1 2 3; do
# Clear cache before each test
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null
sleep 0.5
local temp_file="/tmp/lxs_read_result_${size_mb}_${pass}.tmp"
# Run dd read test with direct I/O (suppress error messages)
(dd if=/tmp/lxs_test_read_${size_mb} of=/dev/null bs=${block_size} iflag=direct 2>"$temp_file" || true) &
local pid=$!
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing ${description} (Pass ${pass}/3)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
local exit_code=$?
# Parse result
local speed_value=""
local speed_unit=""
if [ $exit_code -eq 0 ]; then
local dd_output=$(cat "$temp_file")
speed_value=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $1}')
speed_unit=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $2}')
fi
# If direct I/O failed, try without it
if [ -z "$speed_value" ] || [ "$speed_value" = "0" ] || [ $exit_code -ne 0 ]; then
# Clear cache again
sync
echo 3 > /proc/sys/vm/drop_caches 2>/dev/null
(dd if=/tmp/lxs_test_read_${size_mb} of=/dev/null bs=${block_size} 2>"$temp_file" || true) &
pid=$!
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing ${description} (Pass ${pass}/3)..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
local dd_output=$(cat "$temp_file")
speed_value=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $1}')
speed_unit=$(echo "$dd_output" | tail -1 | grep -Eo '[0-9]+\.?[0-9]* [MGK]B/s' | awk '{print $2}')
fi
rm -f "$temp_file"
# Convert to MB/s
local speed_mb=0
if [ ! -z "$speed_value" ] && [ "$speed_value" != "0" ]; then
if [ "$speed_unit" = "GB/s" ]; then
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1 * 1024}')
elif [ "$speed_unit" = "KB/s" ]; then
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1 / 1024}')
else
speed_mb=$(echo "$speed_value" | awk '{printf "%.2f", $1}')
fi
pass_speeds+=($speed_mb)
((pass_count++))
fi
done
# Calculate average for this configuration
if [ $pass_count -gt 0 ]; then
local sum=0
for speed in "${pass_speeds[@]}"; do
sum=$(echo "$sum + $speed" | bc -l)
done
local avg=$(echo "scale=2; $sum / $pass_count" | bc -l)
printf "\r${GREEN}[✓]${NC} ${description}: ${GREEN}${avg} MB/s${NC} (avg of ${pass_count} passes)\n"
all_speeds_mb+=($avg)
((test_count++))
else
printf "\r${YELLOW}[!]${NC} ${description}: Test failed\n"
fi
# Clean up test file
rm -f /tmp/lxs_test_read_${size_mb}
done
echo ""
# Calculate final score
if [ $test_count -gt 0 ]; then
local total_speed=0
for speed in "${all_speeds_mb[@]}"; do
total_speed=$(echo "$total_speed + $speed" | bc -l)
done
local avg_speed=$(echo "scale=2; $total_speed / $test_count" | bc -l)
SCORE_DISK_READ=$(echo "$avg_speed" | awk '{printf "%.0f", $1}')
echo -e "${CYAN} Average Read Speed:${NC} ${BOLD}${avg_speed} MB/s${NC}"
echo -e "${CYAN} Read Score:${NC} ${BOLD}$SCORE_DISK_READ points${NC}"
# Quality assessment
if (( $(echo "$avg_speed >= 2000" | bc -l) )); then
echo -e "${GREEN} Quality:${NC} Excellent (High-end NVMe SSD)"
elif (( $(echo "$avg_speed >= 500" | bc -l) )); then
echo -e "${GREEN} Quality:${NC} Very Good (NVMe/Fast SATA SSD)"
elif (( $(echo "$avg_speed >= 150" | bc -l) )); then
echo -e "${CYAN} Quality:${NC} Good (SATA SSD)"
else
echo -e "${YELLOW} Quality:${NC} Standard (HDD)"
fi
else
echo -e "${RED}[✗] All read tests failed${NC}"
SCORE_DISK_READ=0
fi
echo ""
}
test_network() {
echo -e "${WHITE}${BOLD}NETWORK PERFORMANCE TEST${NC}"
show_separator
# Test ping to common servers with spinner
local temp_file="/tmp/lxs_ping_result.tmp"
echo -e "${PURPLE}[*] Testing network latency and speed...${NC}"
ping -c 5 8.8.8.8 2>/dev/null > "$temp_file" &
local pid=$!
local spinstr='|/-\'
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing network latency and speed..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait $pid
local ping_test=$(tail -1 "$temp_file" | awk -F '/' '{print $5}')
rm -f "$temp_file"
if [ -z "$ping_test" ]; then
printf "\r${YELLOW}[!]${NC} Network test skipped (no connectivity) \n"
SCORE_NETWORK=0
else
printf "\r${GREEN}[✓]${NC} Network test completed \n"
# Convert latency to score (lower is better, so invert)
# Score = 1000 / latency (max 100 points for <10ms)
SCORE_NETWORK=$(echo "$ping_test" | awk '{score = 1000 / $1; if (score > 100) score = 100; printf "%.0f", score}')
echo -e "${GREEN} Average latency to 8.8.8.8:${NC} $ping_test ms"
echo -e "${CYAN} Network Score:${NC} ${BOLD}$SCORE_NETWORK points${NC}"
fi
echo ""
}
test_network_latency() {
echo -e "${WHITE}${BOLD}GEOGRAPHIC NETWORK LATENCY TEST${NC}"
show_separator
# Define test locations with multiple servers for redundancy
# Format: "City|Server1,Server2,Server3"
local test_locations=(
"Montreal|ec2.ca-central-1.amazonaws.com,objectstorage.ca-montreal-1.oraclecloud.com"
"Toronto|objectstorage.ca-toronto-1.oraclecloud.com,tor-ca-ping.vultr.com,speedtest-tor1.digitalocean.com,speedtest.toronto1.linode.com"
"Vancouver|ec2.us-west-2.amazonaws.com"
"New York|ec2.us-east-1.amazonaws.com,speedtest-nyc1.digitalocean.com"
"Texas|tx-us-ping.vultr.com"
)
local total_latency=0
local successful_tests=0
local failed_count=0
echo -e "${PURPLE}[*] Testing latency to multiple geographic locations...${NC}"
echo ""
# Test each location
for location in "${test_locations[@]}"; do
local city=$(echo "$location" | cut -d'|' -f1)
local servers=$(echo "$location" | cut -d'|' -f2)
local city_latency=""
local city_success=false
# Try each server for the city until one succeeds
IFS=',' read -ra SERVER_ARRAY <<< "$servers"
for server in "${SERVER_ARRAY[@]}"; do
local temp_file="/tmp/lxs_ping_${city}_${server}.tmp"
# Ping with timeout (3 pings for faster results)
timeout 8 ping -c 3 -W 2 "$server" 2>/dev/null > "$temp_file" &
local pid=$!
local spinstr='|/-\'
while kill -0 $pid 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} Testing ${city}..." "$spinstr"
local spinstr=$temp${spinstr%"$temp"}
sleep 0.1
done
wait $pid
local exit_code=$?
# Parse result
local avg_latency=$(tail -1 "$temp_file" 2>/dev/null | awk -F '/' '{print $5}')
rm -f "$temp_file"
# Check if we got a valid result
if [ ! -z "$avg_latency" ] && [ "$avg_latency" != "0" ] && [ $exit_code -eq 0 ]; then
city_latency="$avg_latency"
city_success=true
break
fi
done
# Display result for this city
if [ "$city_success" = true ]; then
printf "\r${GREEN}[✓]${NC} %-12s ${GREEN}%6s ms${NC}\n" "$city:" "$city_latency"
total_latency=$(echo "$total_latency + $city_latency" | bc -l 2>/dev/null || echo "$total_latency")
((successful_tests++))
else
printf "\r${YELLOW}[!]${NC} %-12s ${YELLOW}Unreachable${NC}\n" "$city:"
((failed_count++))
fi
done
echo ""
# Calculate score
if [ $successful_tests -gt 0 ]; then
local avg_total=$(echo "scale=2; $total_latency / $successful_tests" | bc -l)
# Score calculation: Lower latency = higher score
# Perfect score (100) for avg latency <= 20ms
# Score decreases as latency increases
# Formula: max(0, 100 - ((avg_latency - 20) * 0.5))
SCORE_NETWORK_LATENCY=$(echo "$avg_total" | awk '{
if ($1 <= 20) score = 100;
else if ($1 >= 220) score = 0;
else score = 100 - (($1 - 20) * 0.5);
printf "%.0f", score
}')
local total_locations=$((successful_tests + failed_count))
echo -e "${CYAN} Average Latency:${NC} ${avg_total} ms (${successful_tests}/${total_locations} locations)"
echo -e "${CYAN} Latency Score:${NC} ${BOLD}$SCORE_NETWORK_LATENCY points${NC}"
# Provide context on latency quality
if (( $(echo "$avg_total <= 50" | bc -l) )); then
echo -e "${GREEN} Quality:${NC} Excellent latency across regions"
elif (( $(echo "$avg_total <= 100" | bc -l) )); then
echo -e "${CYAN} Quality:${NC} Good latency across regions"
elif (( $(echo "$avg_total <= 150" | bc -l) )); then
echo -e "${YELLOW} Quality:${NC} Average latency across regions"
else
echo -e "${RED} Quality:${NC} High latency detected"
fi
else
echo -e "${RED}[✗] All latency tests failed${NC}"
SCORE_NETWORK_LATENCY=0
fi
echo ""
}
# ═══════════════════════════════════════════════════════════════════════════
# Results Display
# ═══════════════════════════════════════════════════════════════════════════
show_results() {
show_separator
echo -e "${WHITE}${BOLD}BENCHMARK RESULTS${NC}"
show_separator
echo ""
echo -e "${GRAY}CPU Score:${NC} ${BOLD}$SCORE_CPU${NC} points"
echo -e "${GRAY}RAM Score:${NC} ${BOLD}$SCORE_RAM${NC} points"
echo -e "${GRAY}Disk Write Score:${NC} ${BOLD}$SCORE_DISK_WRITE${NC} points"
echo -e "${GRAY}Disk Read Score:${NC} ${BOLD}$SCORE_DISK_READ${NC} points"
echo -e "${GRAY}Network Score:${NC} ${BOLD}$SCORE_NETWORK${NC} points"
echo -e "${GRAY}Network Latency Score:${NC} ${BOLD}$SCORE_NETWORK_LATENCY${NC} points"
echo ""
show_separator
# Calculate final score with weighted average
# CPU = 30%, RAM = 20%, Disk Write = 15%, Disk Read = 15%, Network = 10%, Latency = 10%
SCORE_FINAL=$(awk "BEGIN {printf \"%.0f\", ($SCORE_CPU * 0.30) + ($SCORE_RAM * 0.20) + ($SCORE_DISK_WRITE * 0.15) + ($SCORE_DISK_READ * 0.15) + ($SCORE_NETWORK * 0.10) + ($SCORE_NETWORK_LATENCY * 0.10)}")
echo -e "${WHITE}${BOLD}FINAL BENCHMARK SCORE${NC}"
show_separator
echo ""
echo -e "${GREEN}${BOLD}$SCORE_FINAL points${NC}"
echo ""
show_separator
echo ""
# Performance rating
if [ "$SCORE_FINAL" -ge 5000 ]; then
echo -e "${GREEN}${BOLD}Performance Rating: [★★★★★] Exceptional${NC}"
elif [ "$SCORE_FINAL" -ge 3000 ]; then
echo -e "${GREEN}${BOLD}Performance Rating: [★★★★☆] Excellent${NC}"
elif [ "$SCORE_FINAL" -ge 2000 ]; then
echo -e "${CYAN}${BOLD}Performance Rating: [★★★☆☆] Good${NC}"
elif [ "$SCORE_FINAL" -ge 1000 ]; then
echo -e "${YELLOW}${BOLD}Performance Rating: [★★☆☆☆] Average${NC}"
else
echo -e "${RED}${BOLD}Performance Rating: [★☆☆☆☆] Below Average${NC}"
fi
echo ""
echo -e "${GRAY}Higher scores indicate better performance.${NC}"
echo ""
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Execution
# ═══════════════════════════════════════════════════════════════════════════
main() {
clear
show_header
echo ""
# Show system information
show_system_info
# Install dependencies
if ! install_dependencies; then
exit 1
fi
echo -e "${YELLOW}${BOLD}[!] Starting benchmark tests...${NC}"
echo -e "${YELLOW} This may take a few minutes.${NC}"
echo ""
# Run all tests
test_cpu
test_memory
test_disk_write
test_disk_read
test_network
test_network_latency
# Display results
show_results
# Wait for user input before exiting
echo ""
read -p "Press Enter to return to menu..."
}
# Check if running as root
if [ "$EUID" -ne 0 ]; then
echo ""
echo -e "${RED}${BOLD}[✗] ERROR: This script must be run as root${NC}"
echo ""
echo -e "${YELLOW}Please run with: sudo $0${NC}"
echo ""
exit 1
fi
# Execute main function
main
exit 75
+285
View File
@@ -0,0 +1,285 @@
#!/bin/bash
# LXS - System Infos
# Description: Essential system monitoring and diagnostic tools
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_system_infos.log"
# Menu: System Infos
menu_system_infos() {
while true; do
clear
show_box_top "SYSTEM INFOS"
echo ""
show_menu_item "1" "View system informations"
show_menu_item "2" "Check disk space"
show_menu_item "3" "Check memory usage"
show_menu_item "4" "Check CPU load"
show_menu_item "5" "Check network"
show_menu_item "6" "View system logs (last 50 lines)"
show_menu_item "7" "Show top resource-consuming processes"
show_menu_item "8" "Check disk health (SMART)"
show_menu_item "0" "Exit" "" exit
echo ""
show_box_bottom
echo ""
show_prompt
read -r choice
echo ""
case $choice in
1)
# View system informations
clear
show_box_top "SYSTEM INFORMATION"
echo ""
echo -e "${CYAN}${BOLD}Operating System:${NC}"
echo -e " $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
echo ""
echo -e "${CYAN}${BOLD}Kernel Version:${NC}"
echo -e " $(uname -r)"
echo ""
echo -e "${CYAN}${BOLD}System Uptime:${NC}"
echo -e " $(uptime -p 2>/dev/null || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')"
echo ""
echo -e "${CYAN}${BOLD}Load Average:${NC}"
echo -e " $(uptime | awk -F'load average:' '{print $2}')"
echo ""
echo -e "${CYAN}${BOLD}CPU Information:${NC}"
echo -e " Model: $(lscpu | grep "Model name" | cut -d':' -f2 | xargs)"
echo -e " Cores: $(nproc)"
echo -e " Architecture: $(uname -m)"
echo ""
echo -e "${CYAN}${BOLD}Memory:${NC}"
local total_mem=$(free -h | awk '/^Mem:/ {print $2}')
local used_mem=$(free -h | awk '/^Mem:/ {print $3}')
local available_mem=$(free -h | awk '/^Mem:/ {print $7}')
echo -e " Total: $total_mem"
echo -e " Used: $used_mem"
echo -e " Available: $available_mem"
echo ""
echo -e "${CYAN}${BOLD}Disk Space:${NC}"
echo -e " Total: $(df -h / | awk 'NR==2 {print $2}')"
echo -e " Used: $(df -h / | awk 'NR==2 {print $3}')"
echo -e " Available: $(df -h / | awk 'NR==2 {print $4}')"
echo -e " Usage: $(df -h / | awk 'NR==2 {print $5}')"
echo ""
echo -e "${CYAN}${BOLD}Network:${NC}"
echo -e " Hostname: $(hostname)"
echo -e " Local IP: $(hostname -I | awk '{print $1}')"
echo -e " Public IP: $(get_public_ip)"
echo ""
echo -e "${CYAN}${BOLD}Installed Software:${NC}"
if command -v git &> /dev/null; then
echo -e " ${GREEN}[✓]${NC} Git: $(git --version 2>/dev/null | cut -d' ' -f3)"
else
echo -e " ${GRAY}[ ]${NC} Git: Not installed"
fi
if command -v docker &> /dev/null; then
echo -e " ${GREEN}[✓]${NC} Docker: $(docker --version 2>/dev/null | cut -d' ' -f3 | tr -d ',')"
else
echo -e " ${GRAY}[ ]${NC} Docker: Not installed"
fi
if command -v node &> /dev/null; then
echo -e " ${GREEN}[✓]${NC} Node.js: $(node --version 2>/dev/null)"
else
echo -e " ${GRAY}[ ]${NC} Node.js: Not installed"
fi
if command -v python3 &> /dev/null; then
echo -e " ${GREEN}[✓]${NC} Python3: $(python3 --version 2>/dev/null | cut -d' ' -f2)"
else
echo -e " ${GRAY}[ ]${NC} Python3: Not installed"
fi
;;
2)
# Check disk space
echo -e "${CYAN}${BOLD}Disk Space Usage:${NC}"
echo ""
df -h
echo ""
show_separator
echo -e "${CYAN}${BOLD}Inode Usage:${NC}"
echo ""
df -i
;;
3)
# Check memory usage
echo -e "${CYAN}${BOLD}Memory Usage:${NC}"
echo ""
free -h
echo ""
show_separator
echo -e "${CYAN}${BOLD}Detailed Memory Info:${NC}"
echo ""
cat /proc/meminfo | head -10
;;
4)
# Check CPU load
echo -e "${CYAN}${BOLD}CPU Load and Top Processes:${NC}"
echo ""
uptime
echo ""
show_separator
echo ""
top -bn1 | head -20
;;
5)
# Check network
echo -e "${CYAN}${BOLD}Network Check:${NC}"
echo ""
echo -e "${CYAN}[1/5]${NC} Network Interfaces:"
echo ""
ip -brief addr show 2>/dev/null || ifconfig -a
echo ""
echo -e "${CYAN}[2/5]${NC} Public IP Address:"
PUBLIC_IP=$(get_public_ip)
echo -e " ${WHITE}$PUBLIC_IP${NC}"
echo ""
echo -e "${CYAN}[3/5]${NC} DNS Resolution Test:"
echo -e -n " Testing google.com... "
if nslookup google.com >/dev/null 2>&1; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
fi
echo ""
echo -e "${CYAN}[4/5]${NC} Internet Connectivity Test:"
echo -e -n " Pinging google.com... "
if ping -c 1 google.com >/dev/null 2>&1; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}FAILED${NC}"
fi
echo ""
echo -e "${CYAN}[5/5]${NC} Open Ports:"
echo ""
if command -v ss &> /dev/null; then
ss -tulpn 2>/dev/null | grep LISTEN || echo " No listening ports or insufficient permissions"
elif command -v netstat &> /dev/null; then
netstat -tulpn 2>/dev/null | grep LISTEN || echo " No listening ports or insufficient permissions"
else
echo " ${YELLOW}Neither ss nor netstat available${NC}"
fi
;;
6)
# View system logs
echo -e "${CYAN}${BOLD}System Logs (last 50 lines):${NC}"
echo ""
sudo journalctl -n 50 --no-pager
;;
7)
# Show top resource-consuming processes
echo -e "${CYAN}${BOLD}Top Memory-Consuming Processes:${NC}"
echo ""
ps aux --sort=-%mem | head -15
echo ""
show_separator
echo ""
echo -e "${CYAN}${BOLD}Top CPU-Consuming Processes:${NC}"
echo ""
ps aux --sort=-%cpu | head -15
;;
8)
# Check disk health
echo -e "${CYAN}${BOLD}Disk Health Check (SMART):${NC}"
echo ""
if command -v smartctl &> /dev/null; then
disks=$(lsblk -d -n -p -o NAME,TYPE | grep "disk" | awk '{print $1}')
if [ -z "$disks" ]; then
echo -e "${RED}[✗] No disks found${NC}"
else
for disk in $disks; do
disk_name=$(basename "$disk")
show_separator
echo -e "${WHITE}Disk: $disk${NC}"
# Get disk info
disk_size=$(lsblk -d -n -o SIZE "$disk" 2>/dev/null)
disk_model=$(lsblk -d -n -o MODEL "$disk" 2>/dev/null | xargs)
disk_rota=$(cat /sys/block/$disk_name/queue/rotational 2>/dev/null)
echo -e " Size: ${CYAN}$disk_size${NC}"
[ -n "$disk_model" ] && echo -e " Model: ${CYAN}$disk_model${NC}"
[ "$disk_rota" == "0" ] && echo -e " Type: ${CYAN}SSD/Virtual${NC}" || echo -e " Type: ${CYAN}HDD${NC}"
echo ""
# Check if it's a virtual disk
if [[ $disk == *"/dev/vd"* ]] || [[ $disk == *"/dev/xvd"* ]]; then
echo -e " ${YELLOW}[!] Virtual disk detected - SMART not available${NC}"
echo -e " ${GRAY}[i] Virtual disks don't support SMART monitoring${NC}"
else
# Try SMART check for physical disks
smart_output=$(sudo smartctl -H "$disk" 2>&1)
if echo "$smart_output" | grep -q "PASSED"; then
echo -e " ${GREEN}[✓] SMART Status: PASSED${NC}"
elif echo "$smart_output" | grep -q "FAILED"; then
echo -e " ${RED}[✗] SMART Status: FAILED${NC}"
echo -e " ${RED}[!] WARNING: Disk may be failing!${NC}"
else
echo -e " ${YELLOW}[!] SMART not available for this disk${NC}"
fi
fi
echo ""
done
show_separator
fi
else
echo -e "${RED}[✗] smartmontools is not installed${NC}"
echo ""
read -p "Would you like to install it? (y/n): " install_smart
if [[ $install_smart =~ ^[Yy]$ ]]; then
echo ""
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
run_spinner "Updating package list..." "sudo apt update -qq"
run_spinner "Installing smartmontools..." "sudo apt install -y -qq smartmontools -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
echo ""
echo -e "${GRAY}[i] Run this option again to check disk health${NC}"
fi
fi
;;
0)
exit 75
;;
*)
echo -e "${RED}[✗] Invalid option. Please select 0-8.${NC}"
sleep 2
continue
;;
esac
echo ""
show_separator
echo ""
read -p "Press Enter to continue..."
done
}
menu_system_infos
+150
View File
@@ -0,0 +1,150 @@
#!/bin/bash
# LXS - Update server
# Description: Refresh package lists and upgrade installed packages
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_update_server.log"
require_root "$0" "$@"
set -u
# ═══════════════════════════════════════════════════════════════════════════
# Arguments
# ═══════════════════════════════════════════════════════════════════════════
DO_FULL_UPGRADE=0
DO_AUTOREMOVE=1
DO_AUTOCLEAN=1
ASSUME_YES=0
for arg in "$@"; do
case "$arg" in
--full|--dist-upgrade) DO_FULL_UPGRADE=1 ;;
--no-autoremove) DO_AUTOREMOVE=0 ;;
--no-autoclean) DO_AUTOCLEAN=0 ;;
-y|--yes) ASSUME_YES=1 ;;
-h|--help)
cat <<EOF
Usage: update-server.sh [options]
Options:
--full, --dist-upgrade Use 'apt-get full-upgrade' (may add/remove packages)
--no-autoremove Skip 'apt-get autoremove'
--no-autoclean Skip 'apt-get autoclean'
-y, --yes Skip confirmation prompt
-h, --help Show this help
EOF
exit 0
;;
*)
echo -e "${RED}Unknown option: $arg${NC}" >&2
exit 1
;;
esac
done
# ═══════════════════════════════════════════════════════════════════════════
# Pre-checks
# ═══════════════════════════════════════════════════════════════════════════
require_debian_ubuntu || exit 1
if ! command -v apt-get >/dev/null 2>&1; then
err "apt-get is not available on this system"
exit 1
fi
require_disk_space 1024 || exit 1
UPGRADE_CMD="upgrade"
UPGRADE_LABEL="Upgrade installed packages"
if [ $DO_FULL_UPGRADE -eq 1 ]; then
UPGRADE_CMD="full-upgrade"
UPGRADE_LABEL="Full-upgrade (may add/remove packages)"
fi
echo -e "${WHITE}${BOLD}LXS Server Update${NC}"
show_separator
echo "The following actions will be performed:"
echo " • Refresh package lists (apt-get update)"
echo "${UPGRADE_LABEL}"
[ $DO_AUTOREMOVE -eq 1 ] && echo " • Remove unused packages (apt-get autoremove)"
[ $DO_AUTOCLEAN -eq 1 ] && echo " • Clean old package archives (apt-get autoclean)"
show_separator
if [ $ASSUME_YES -ne 1 ]; then
echo -e -n "${BOLD}Proceed? [y/N]: ${NC}"
read -r reply
case "$reply" in
[yY]|[yY][eE][sS]) ;;
*) info "Cancelled."; exit 0 ;;
esac
fi
# ═══════════════════════════════════════════════════════════════════════════
# Run
# ═══════════════════════════════════════════════════════════════════════════
apt_noninteractive
wait_for_apt || exit 1
APT_OPTS='-y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"'
run_spinner "Refreshing package lists..." "apt-get update" || {
err "apt-get update failed — see ${LXS_LOG_FILE}"
exit 1
}
run_spinner "${UPGRADE_LABEL}..." "apt-get ${APT_OPTS} ${UPGRADE_CMD}" || {
err "apt-get ${UPGRADE_CMD} failed — see ${LXS_LOG_FILE}"
exit 1
}
if [ $DO_AUTOREMOVE -eq 1 ]; then
run_spinner "Removing unused packages..." "apt-get ${APT_OPTS} autoremove --purge" \
|| warn "autoremove failed — see ${LXS_LOG_FILE}"
fi
if [ $DO_AUTOCLEAN -eq 1 ]; then
run_spinner "Cleaning old archives..." "apt-get ${APT_OPTS} autoclean" \
|| warn "autoclean failed — see ${LXS_LOG_FILE}"
fi
show_separator
ok "Server updated"
# ═══════════════════════════════════════════════════════════════════════════
# Reboot hint
# ═══════════════════════════════════════════════════════════════════════════
if [ -f /var/run/reboot-required ]; then
echo ""
warn "A reboot is required to complete the update."
if [ -f /var/run/reboot-required.pkgs ]; then
echo -e "${GRAY}Packages requiring reboot:${NC}"
sed 's/^/ • /' /var/run/reboot-required.pkgs
fi
if [ -t 0 ]; then
echo ""
echo -e -n "${BOLD}Reboot now? [y/N]: ${NC}"
read -r reboot_reply
case "$reboot_reply" in
[yY]|[yY][eE][sS])
info "Rebooting..."
systemctl reboot
;;
*)
info "Reboot skipped. Remember to reboot later."
;;
esac
fi
fi
+203
View File
@@ -0,0 +1,203 @@
#!/bin/bash
# LXS - Welcome message (MOTD)
# Description: View, edit, or reset the SSH login welcome message (/etc/motd)
# Author: LXS
# Date: 2025
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
eval "$_lib"
unset _lib
export LXS_LOG_FILE="/tmp/lxs_welcome_message.log"
require_root "$0" "$@"
set -u
MOTD_FILE="/etc/motd"
BACKUP_FILE="/etc/motd.lxs.bak"
DYNAMIC_DIR="/etc/update-motd.d"
# ═══════════════════════════════════════════════════════════════════════════
# Arguments
# ═══════════════════════════════════════════════════════════════════════════
ACTION=""
TEXT=""
FROM_FILE=""
while [ $# -gt 0 ]; do
case "$1" in
view|--view|show|--show) ACTION="view" ;;
set|--set|edit|--edit) ACTION="set" ;;
reset|--reset) ACTION="reset" ;;
--text) shift; TEXT=${1:-} ;;
--text=*) TEXT="${1#*=}" ;;
--from-file) shift; FROM_FILE=${1:-} ;;
--from-file=*) FROM_FILE="${1#*=}" ;;
-h|--help)
cat <<EOF
Usage: welcome-message.sh [action] [options]
Actions:
view Show the current welcome message
set Set a new welcome message (see source options below)
reset Clear the welcome message (a backup is kept)
Source options for 'set' (mutually exclusive):
--text "..." Use the given string as the new message
--from-file PATH Read the new message from PATH
(none) Open an interactive editor (\$EDITOR or nano/vi)
-h, --help Show this help
With no action, an interactive menu is shown.
The welcome message lives in ${MOTD_FILE}.
On Ubuntu, dynamic MOTD scripts in ${DYNAMIC_DIR} also contribute to the
banner shown at login — those are not modified by this tool.
EOF
exit 0
;;
*)
echo -e "${RED}Unknown option: $1${NC}" >&2
exit 1
;;
esac
shift
done
if [ -n "$TEXT" ] && [ -n "$FROM_FILE" ]; then
err "--text and --from-file are mutually exclusive"
exit 1
fi
# ═══════════════════════════════════════════════════════════════════════════
# Helpers
# ═══════════════════════════════════════════════════════════════════════════
view_motd() {
echo -e "${WHITE}${BOLD}Current welcome message${NC} ${GRAY}(${MOTD_FILE})${NC}"
show_separator
if [ ! -s "$MOTD_FILE" ]; then
echo -e "${GRAY}(empty)${NC}"
else
cat "$MOTD_FILE"
fi
show_separator
if [ -d "$DYNAMIC_DIR" ] && compgen -G "${DYNAMIC_DIR}/*" >/dev/null; then
echo ""
warn "Dynamic MOTD scripts are present in ${DYNAMIC_DIR}:"
find "$DYNAMIC_DIR" -maxdepth 1 -type f -executable -printf ' • %f\n' | sort
echo -e "${GRAY}These run at login and add to the banner.${NC}"
fi
}
backup_motd() {
[ -f "$MOTD_FILE" ] || return 0
cp -a "$MOTD_FILE" "$BACKUP_FILE"
info "Previous message backed up to ${BACKUP_FILE}"
}
write_motd() {
local src=$1
backup_motd
install -m 644 "$src" "$MOTD_FILE"
ok "Welcome message updated"
echo ""
view_motd
}
set_from_text() {
local tmp
tmp=$(mktemp /tmp/lxs.motd.XXXXXX) || { err "mktemp failed"; return 1; }
# Preserve embedded newlines; ensure a trailing newline.
printf '%s\n' "$1" > "$tmp"
write_motd "$tmp"
rm -f "$tmp"
}
set_from_file() {
local src=$1
if [ ! -r "$src" ]; then
err "Cannot read file: ${src}"
return 1
fi
write_motd "$src"
}
set_interactive() {
local editor=${EDITOR:-}
if [ -z "$editor" ]; then
if command -v nano >/dev/null 2>&1; then editor="nano"
elif command -v vi >/dev/null 2>&1; then editor="vi"
else
err "No editor found (\$EDITOR unset; nano/vi missing). Use --text or --from-file."
return 1
fi
fi
local tmp
tmp=$(mktemp /tmp/lxs.motd.XXXXXX) || { err "mktemp failed"; return 1; }
[ -s "$MOTD_FILE" ] && cat "$MOTD_FILE" > "$tmp"
info "Opening ${editor}... save and exit to apply, leave empty to cancel."
"$editor" "$tmp"
if [ ! -s "$tmp" ]; then
warn "Empty content — change cancelled."
rm -f "$tmp"
return 0
fi
write_motd "$tmp"
rm -f "$tmp"
}
reset_motd() {
if [ ! -s "$MOTD_FILE" ]; then
info "Welcome message is already empty."
return 0
fi
backup_motd
: > "$MOTD_FILE"
chmod 644 "$MOTD_FILE"
ok "Welcome message cleared"
}
# ═══════════════════════════════════════════════════════════════════════════
# Menu (when no action is given on the CLI)
# ═══════════════════════════════════════════════════════════════════════════
if [ -z "$ACTION" ]; then
echo -e "${WHITE}${BOLD}LXS - Welcome message${NC}"
show_separator
echo -e " ${CYAN}[1]${NC} View current message"
echo -e " ${GREEN}[2]${NC} Set a new message"
echo -e " ${YELLOW}[3]${NC} Reset (clear) the message"
echo -e " ${RED}[0]${NC} Cancel"
echo ""
echo -e -n "${BOLD}Choice [0-3]: ${NC}"
read -r choice
case "$choice" in
1) ACTION="view" ;;
2) ACTION="set" ;;
3) ACTION="reset" ;;
0|"") info "Cancelled."; exit 0 ;;
*) err "Invalid option."; exit 1 ;;
esac
fi
case "$ACTION" in
view) view_motd ;;
set)
if [ -n "$TEXT" ]; then set_from_text "$TEXT"
elif [ -n "$FROM_FILE" ]; then set_from_file "$FROM_FILE"
else set_interactive
fi
;;
reset) reset_motd ;;
esac