feat: initial project scaffold for lxs multi-tool
- add main entrypoint with interactive menu and CLI dispatcher (lxs.sh) - add shared helpers library with colors, loggers, and spinner (lib/common.sh) - add app installers for coolify, pterodactyl, uptime-kuma, cloudpanel, and proxmox (apps/) - add system tools for monitoring, benchmarking, and hardening (tools/) - add VERSION file (0.1.0) as single source of truth for releases - add MIT LICENSE - expand README with usage, project structure, and release workflow
This commit is contained in:
Executable
+211
@@ -0,0 +1,211 @@
|
||||
#!/bin/bash
|
||||
|
||||
# LXS - Server Hardening
|
||||
# Description: Apply baseline security hardening (UFW + fail2ban + SSH key-only + 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_SSH=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-ssh) DO_SSH=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-ssh Skip SSH key-only enforcement
|
||||
--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
|
||||
|
||||
SSH_PORT=$(awk '/^[[:space:]]*Port[[:space:]]+/ {print $2; exit}' /etc/ssh/sshd_config 2>/dev/null)
|
||||
SSH_PORT=${SSH_PORT:-22}
|
||||
|
||||
echo -e "${WHITE}${BOLD}LXS Server Hardening${NC}"
|
||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
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_SSH -eq 1 ] && echo " • SSH: disable password auth (key-only), prohibit-password root login"
|
||||
[ $DO_UNATTENDED -eq 1 ] && echo " • unattended-upgrades: enable automatic security updates"
|
||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
|
||||
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
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# 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)"
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# SSH key-only
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
has_authorized_key() {
|
||||
local user_home
|
||||
for user_home in /root "/home/${SUDO_USER:-}"; do
|
||||
[ -z "$user_home" ] && continue
|
||||
[ "$user_home" = "/home/" ] && continue
|
||||
if [ -s "${user_home}/.ssh/authorized_keys" ]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
setup_ssh() {
|
||||
info "Enforcing SSH key-only authentication..."
|
||||
if ! has_authorized_key; then
|
||||
err "No authorized_keys found for root or ${SUDO_USER:-current user}."
|
||||
err "Refusing to disable password auth — you would lock yourself out."
|
||||
err "Add a public key first, then re-run: harden.sh --no-ufw --no-fail2ban --no-unattended"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local cfg=/etc/ssh/sshd_config
|
||||
cp -n "$cfg" "${cfg}.lxs-backup"
|
||||
|
||||
sed -i -E \
|
||||
-e 's/^[#[:space:]]*PasswordAuthentication[[:space:]]+.*/PasswordAuthentication no/' \
|
||||
-e 's/^[#[:space:]]*ChallengeResponseAuthentication[[:space:]]+.*/ChallengeResponseAuthentication no/' \
|
||||
-e 's/^[#[:space:]]*KbdInteractiveAuthentication[[:space:]]+.*/KbdInteractiveAuthentication no/' \
|
||||
-e 's/^[#[:space:]]*PermitRootLogin[[:space:]]+.*/PermitRootLogin prohibit-password/' \
|
||||
"$cfg"
|
||||
|
||||
grep -q '^PasswordAuthentication ' "$cfg" || echo 'PasswordAuthentication no' >> "$cfg"
|
||||
grep -q '^PermitRootLogin ' "$cfg" || echo 'PermitRootLogin prohibit-password' >> "$cfg"
|
||||
|
||||
if sshd -t 2>/tmp/lxs_sshd_test; then
|
||||
systemctl reload ssh 2>/dev/null || systemctl reload sshd
|
||||
ok "SSH configured for key-only auth (backup: ${cfg}.lxs-backup)"
|
||||
else
|
||||
err "sshd config test failed; restoring backup."
|
||||
cp "${cfg}.lxs-backup" "$cfg"
|
||||
cat /tmp/lxs_sshd_test >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# 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_SSH -eq 1 ] && setup_ssh || true
|
||||
[ $DO_UNATTENDED -eq 1 ] && setup_unattended
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Summary
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
echo ""
|
||||
echo -e "${WHITE}${BOLD}Summary${NC}"
|
||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
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"
|
||||
echo "SSH password auth: $(grep -E '^PasswordAuthentication' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo unknown)"
|
||||
echo "SSH root login: $(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo unknown)"
|
||||
systemctl is-active unattended-upgrades >/dev/null 2>&1 && echo "unattended-upgrades: active" || echo "unattended-upgrades: inactive"
|
||||
echo ""
|
||||
ok "Hardening complete."
|
||||
Executable
+763
@@ -0,0 +1,763 @@
|
||||
#!/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"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# 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() {
|
||||
echo -e "${WHITE}${BOLD}"
|
||||
echo "╔═══════════════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ ║"
|
||||
echo "║ SERVER PERFORMANCE BENCHMARK ║"
|
||||
echo "║ ║"
|
||||
echo "╚═══════════════════════════════════════════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
}
|
||||
|
||||
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 ""
|
||||
|
||||
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//')
|
||||
|
||||
# Test configurations: size_mb, block_size, description
|
||||
local test_configs=(
|
||||
"100:1M:100MB Sequential"
|
||||
"1024:1M:1GB Sequential"
|
||||
)
|
||||
|
||||
# Add 2GB test only if we have enough space (at least 3GB free)
|
||||
if [ "$available_space" -gt 3072 ]; then
|
||||
test_configs+=("2048:1M:2GB Sequential")
|
||||
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
|
||||
|
||||
# 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//')
|
||||
|
||||
# Test configurations: size_mb, block_size, description
|
||||
local test_configs=(
|
||||
"100:1M:100MB Sequential"
|
||||
"1024:1M:1GB Sequential"
|
||||
)
|
||||
|
||||
# Add 2GB test only if we have enough space (at least 3GB free)
|
||||
if [ "$available_space" -gt 3072 ]; then
|
||||
test_configs+=("2048:1M:2GB Sequential")
|
||||
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)
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
Executable
+287
@@ -0,0 +1,287 @@
|
||||
#!/bin/bash
|
||||
|
||||
# LXS - System Tools
|
||||
# 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_tools.log"
|
||||
|
||||
# Menu: System Tools
|
||||
menu_system_tools() {
|
||||
while true; do
|
||||
clear
|
||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${WHITE}║ SYSTEM TOOLS ║${NC}"
|
||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[1]${NC} View system informations"
|
||||
echo -e " ${CYAN}[2]${NC} Check disk space"
|
||||
echo -e " ${CYAN}[3]${NC} Check memory usage"
|
||||
echo -e " ${CYAN}[4]${NC} Check CPU load"
|
||||
echo -e " ${CYAN}[5]${NC} Check network"
|
||||
echo -e " ${CYAN}[6]${NC} View system logs (last 50 lines)"
|
||||
echo -e " ${CYAN}[7]${NC} Show top resource-consuming processes"
|
||||
echo -e " ${CYAN}[8]${NC} Check disk health (SMART)"
|
||||
echo -e " ${RED}[0]${NC} Exit"
|
||||
echo ""
|
||||
echo -e -n "${BOLD}Choice [0-8]: ${NC}"
|
||||
read -r choice
|
||||
|
||||
echo ""
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
# View system informations
|
||||
clear
|
||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${WHITE}║ SYSTEM INFORMATION ║${NC}"
|
||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
||||
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 0
|
||||
;;
|
||||
*)
|
||||
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_tools
|
||||
|
||||
Reference in New Issue
Block a user