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:
2026-05-12 17:29:21 -04:00
parent aad89c1778
commit eadae693a5
13 changed files with 3820 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 HYKO
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+85 -1
View File
@@ -1,2 +1,86 @@
# lxs
# LXS
Linux multi-tool — a Bash menu and CLI for installing common server apps and running system tools on Debian/Ubuntu.
## Quick install
```bash
sudo bash <(curl -fsSL https://git.hyko.cx/hykocx/lxs/raw/branch/main/lxs.sh) setup
```
This downloads the repo tarball, installs every file to `/usr/local/share/lxs/`, and creates the symlink `/usr/local/bin/lxs`. After install, sub-scripts run from disk — no network calls per command.
Or run once without installing:
```bash
bash <(curl -fsSL https://git.hyko.cx/hykocx/lxs/raw/branch/main/lxs.sh)
```
When a new version is published in this repo, the interactive menu shows a *"Nouvelle version disponible"* banner; run `lxs update` to pull all updated files.
## Usage
```bash
lxs # Interactive menu (checks for updates, cached 24h)
lxs setup # First-time install of all files to /usr/local/share/lxs
lxs update # Update all installed files to latest
lxs install <app> # Install an application
lxs tool <name> [args] # Run a system tool
lxs info # Show system info
lxs version # Show version
lxs help # Show help
```
### Applications
| Command | Description |
|---|---|
| `lxs install coolify` | Self-hosted PaaS |
| `lxs install pterodactyl` | Game server management panel |
| `lxs install uptime-kuma` | Monitoring tool |
| `lxs install cloudpanel` | Web hosting control panel |
| `lxs install proxmox` | Proxmox VE management tools |
### Tools
| Command | Description |
|---|---|
| `lxs tool system` | System monitoring and diagnostics |
| `lxs tool benchmark` | Server benchmark (CPU / RAM / disk / network) |
| `lxs tool harden` | Baseline hardening: UFW + fail2ban + SSH key-only + unattended-upgrades |
## Project structure
```
lxs/
├── lxs.sh # Main entrypoint (menu + CLI dispatcher)
├── VERSION # Single source of truth for the version (bump on every release)
├── lib/
│ └── common.sh # Shared helpers (colors, loggers, spinner)
├── apps/ # Application installers
│ ├── coolify.sh
│ ├── pterodactyl.sh
│ ├── uptime-kuma.sh
│ ├── cloudpanel.sh
│ └── proxmox.sh
└── tools/ # System tools
├── system-tools.sh
├── server-benchmark.sh
└── harden.sh
```
After `lxs setup`, the full tree lives in `/usr/local/share/lxs/` and sub-scripts execute from disk. If `lxs.sh` is run without being installed (the one-liner mode), it falls back to downloading sub-scripts on demand.
### Releasing a new version
Bump only the [VERSION](VERSION) file — `lxs.sh` reads it at startup. Installed clients also fetch this file (cached 24 h in `~/.cache/lxs/`) to detect updates. In one-shot mode (`bash <(curl …lxs.sh)`) the file isn't readable from the process substitution, so `lxs version` reports `dev`.
## Requirements
- Debian or Ubuntu (other distros may work but are not tested)
- `curl`
- Some sub-scripts require root; they will auto-elevate via `sudo` when run.
## License
MIT
+1
View File
@@ -0,0 +1 @@
0.1.0
+637
View File
@@ -0,0 +1,637 @@
#!/bin/bash
# LXS - CloudPanel Installation Script
# Description: Install and manage CloudPanel hosting platform
# 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_cloudpanel.log"
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
INSTALL_DIR="/usr/local/cloudpanel"
PANEL_PORT=8443
INSTALLER_URL="https://installer.cloudpanel.io/ce/v2/install.sh"
INSTALLER_SHA256="19cfa702e7936a79e47812ff57d9859175ea902c62a68b2c15ccd1ebaf36caeb"
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
is_installed() {
[ -d "$INSTALL_DIR" ] || command -v clpctl &> /dev/null || [ -f "/usr/local/bin/clpctl" ]
}
check_requirements() {
echo -e "${WHITE}Checking System Requirements...${NC}"
# Check root access
if [ "$EUID" -ne 0 ]; then
echo -e "${RED}[✗] This script must be run as root${NC}"
return 1
fi
echo -e "${GREEN}[✓] Running as root${NC}"
local cpu_cores=$(nproc)
[ $cpu_cores -lt 1 ] && echo -e "${RED}[✗] Insufficient CPU cores (minimum: 1)${NC}" && return 1
echo -e "${GREEN}[✓] CPU cores: $cpu_cores${NC}"
local total_mem_gb=$(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024 / 1024))
if [ $total_mem_gb -lt 1 ]; then
echo -e "${RED}[✗] Insufficient RAM: ${total_mem_gb}GB (minimum: 1GB)${NC}"
return 1
elif [ $total_mem_gb -lt 2 ]; then
echo -e "${YELLOW}[!] Warning: 2GB RAM is recommended (found: ${total_mem_gb}GB)${NC}"
else
echo -e "${GREEN}[✓] Memory: ${total_mem_gb}GB${NC}"
fi
local available_gb=$(($(df / | awk 'NR==2 {print $4}') / 1024 / 1024))
if [ $available_gb -lt 10 ]; then
echo -e "${RED}[✗] Insufficient disk space: ${available_gb}GB (minimum: 10GB)${NC}"
return 1
elif [ $available_gb -lt 20 ]; then
echo -e "${YELLOW}[!] Warning: 20GB free space is recommended (found: ${available_gb}GB)${NC}"
else
echo -e "${GREEN}[✓] Disk space: ${available_gb}GB available${NC}"
fi
local arch=$(uname -m)
if [[ "$arch" != "x86_64" && "$arch" != "aarch64" ]]; then
echo -e "${RED}[✗] Unsupported architecture: $arch${NC}"
return 1
fi
echo -e "${GREEN}[✓] Architecture: $arch${NC}"
# Check OS - CloudPanel supports Ubuntu 24.04, 22.04, Debian 12, 11
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu)
if [[ "$VERSION_ID" == "24.04" || "$VERSION_ID" == "22.04" ]]; then
echo -e "${GREEN}[✓] OS: $PRETTY_NAME${NC}"
else
echo -e "${YELLOW}[!] Warning: Ubuntu version $VERSION_ID may not be officially supported${NC}"
echo -e "${GRAY} Supported: Ubuntu 24.04 LTS, Ubuntu 22.04 LTS${NC}"
fi
;;
debian)
if [[ "$VERSION_ID" == "12" || "$VERSION_ID" == "11" ]]; then
echo -e "${GREEN}[✓] OS: $PRETTY_NAME${NC}"
else
echo -e "${YELLOW}[!] Warning: Debian version $VERSION_ID may not be officially supported${NC}"
echo -e "${GRAY} Supported: Debian 12 LTS, Debian 11 LTS${NC}"
fi
;;
*)
echo -e "${RED}[✗] Unsupported OS: $PRETTY_NAME${NC}"
echo -e "${GRAY} Supported: Ubuntu 24.04/22.04, Debian 12/11${NC}"
return 1
;;
esac
else
echo -e "${YELLOW}[!] Warning: Cannot detect OS version${NC}"
fi
return 0
}
get_clpctl_path() {
local max_attempts=30
local attempt=0
local clpctl_path=""
while [ $attempt -lt $max_attempts ]; do
if command -v clpctl &> /dev/null; then
clpctl_path="clpctl"
break
elif [ -f "/usr/local/bin/clpctl" ]; then
clpctl_path="/usr/local/bin/clpctl"
break
fi
attempt=$((attempt + 1))
sleep 2
done
echo "$clpctl_path"
}
configure_cloudpanel_basic_auth() {
echo ""
echo -e "${WHITE}${BOLD}Configuring CloudPanel Basic Auth...${NC}\n"
# Wait for clpctl to be available
echo -e "${PURPLE}[*] Waiting for CloudPanel CLI to be ready...${NC}"
local clpctl_path=$(get_clpctl_path)
if [ -z "$clpctl_path" ]; then
echo -e "${YELLOW}[!] Warning: CloudPanel CLI not found, skipping Basic Auth configuration${NC}"
return 1
fi
echo -e "${GREEN}[✓] CloudPanel CLI is ready${NC}"
echo ""
# Generate random credentials (global variables for display later)
BASIC_AUTH_USERNAME=$(generate_password 8)
BASIC_AUTH_PASSWORD=$(generate_password 20)
echo -e "${PURPLE}[*] Enabling Basic Auth...${NC}"
# Enable Basic Auth
if $clpctl_path cloudpanel:enable:basic-auth --userName="$BASIC_AUTH_USERNAME" --password="$BASIC_AUTH_PASSWORD" >> /tmp/lxs_cloudpanel.log 2>&1; then
echo -e "${GREEN}[✓] Basic Auth enabled successfully${NC}"
echo ""
return 0
else
echo -e "${YELLOW}[!] Warning: Failed to enable Basic Auth${NC}"
echo -e "${GRAY} You can enable it manually later with:${NC}"
echo -e "${GRAY} clpctl cloudpanel:enable:basic-auth --userName=USERNAME --password='PASSWORD'${NC}"
return 1
fi
}
create_cloudpanel_admin() {
echo ""
echo -e "${WHITE}${BOLD}Creating CloudPanel Admin Account...${NC}\n"
# Wait for clpctl to be available
echo -e "${PURPLE}[*] Waiting for CloudPanel CLI to be ready...${NC}"
local clpctl_path=$(get_clpctl_path)
if [ -z "$clpctl_path" ]; then
echo -e "${YELLOW}[!] Warning: CloudPanel CLI not found, skipping admin account creation${NC}"
return 1
fi
echo -e "${GREEN}[✓] CloudPanel CLI is ready${NC}"
echo ""
# Ask for admin email
echo -e "${CYAN}Admin Email (ex. admin@example.com):${NC}"
read -r ADMIN_EMAIL
while [ -z "$ADMIN_EMAIL" ]; do
echo -e "${RED}Email is required!${NC}"
read -r ADMIN_EMAIL
done
echo ""
# Generate random credentials (global variables for display later)
ADMIN_USERNAME=$(generate_password 8 | tr '[:upper:]' '[:lower:]')
ADMIN_PASSWORD=$(generate_password 20)
echo -e "${PURPLE}[*] Creating admin account...${NC}"
# Create admin user
if $clpctl_path user:add --userName="$ADMIN_USERNAME" --email="$ADMIN_EMAIL" --firstName="Admin" --lastName="User" --password="$ADMIN_PASSWORD" --role="admin" --timezone="UTC" --status="1" >> /tmp/lxs_cloudpanel.log 2>&1; then
echo -e "${GREEN}[✓] Admin account created successfully${NC}"
echo ""
return 0
else
echo -e "${YELLOW}[!] Warning: Failed to create admin account${NC}"
echo -e "${GRAY} You can create it manually later with:${NC}"
echo -e "${GRAY} clpctl user:add --userName=$ADMIN_USERNAME --email=$ADMIN_EMAIL --firstName='Admin' --lastName='User' --password='$ADMIN_PASSWORD' --role='admin' --timezone='UTC' --status='1'${NC}"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# Installation Functions
# ═══════════════════════════════════════════════════════════════════════════
install_cloudpanel() {
local start_time=$(date +%s)
apt_noninteractive
echo -e "${WHITE}${BOLD}CLOUDPANEL INSTALLATION${NC}\n"
if is_installed; then
echo -e "${YELLOW}CloudPanel is already installed!${NC}"
return 0
fi
check_requirements || return 1
echo ""
wait_for_apt || return 1
echo ""
# Update system and install required packages
echo -e "${PURPLE}[*] Updating system packages...${NC}"
if ! run_spinner "Updating system" apt update && apt -y upgrade && apt -y install curl wget sudo; then
echo -e "${RED}[✗] Failed to update system packages${NC}"
return 1
fi
echo ""
# Set database engine to MySQL 8.4
DB_ENGINE="MYSQL_8.4"
echo -e "${GREEN}[✓] Database engine: MySQL 8.4${NC}"
echo ""
# Download installer
echo -e "${PURPLE}[*] Downloading CloudPanel installer...${NC}"
if ! curl -sS "$INSTALLER_URL" -o /tmp/cloudpanel_install.sh; then
echo -e "${RED}[✗] Failed to download installer${NC}"
return 1
fi
# Verify SHA256 checksum
echo -e "${PURPLE}[*] Verifying installer checksum...${NC}"
local calculated_sha=$(sha256sum /tmp/cloudpanel_install.sh | awk '{print $1}')
if [ "$calculated_sha" != "$INSTALLER_SHA256" ]; then
echo -e "${RED}[✗] Installer checksum verification failed!${NC}"
echo -e "${GRAY} Expected: $INSTALLER_SHA256${NC}"
echo -e "${GRAY} Got: $calculated_sha${NC}"
rm -f /tmp/cloudpanel_install.sh
return 1
fi
echo -e "${GREEN}[✓] Installer checksum verified${NC}"
echo ""
# Get server IP
local server_ip=$(get_public_ip)
echo -e "${WHITE}Starting Installation...${NC}"
echo -e "${GRAY}[i] This may take 10-20 minutes...${NC}"
echo ""
# Execute installation
show_separator
DB_ENGINE=$DB_ENGINE bash /tmp/cloudpanel_install.sh
local install_exit_code=$?
show_separator
# Clean up installer
rm -f /tmp/cloudpanel_install.sh
if [ $install_exit_code -ne 0 ]; then
echo ""
echo -e "${RED}[✗] Installation failed!${NC}"
echo -e "${GRAY}Check /tmp/lxs_cloudpanel.log for details${NC}"
return 1
fi
echo -e "${GREEN}[✓] Installation completed${NC}"
# Configure Basic Auth
configure_cloudpanel_basic_auth
# Create admin account
create_cloudpanel_admin
local duration=$(( $(date +%s) - start_time ))
local minutes=$((duration / 60))
local seconds=$((duration % 60))
echo ""
echo -e "${GREEN}${BOLD}Installation Completed Successfully!${NC}"
echo -e "${GRAY}Installation time: ${minutes}m ${seconds}s${NC}"
echo ""
show_separator
echo -e "${WHITE}${BOLD}CLOUDPANEL ACCESS INFORMATION${NC}"
show_separator
echo -e "${WHITE}Panel URL: ${GREEN}${BOLD}https://$server_ip:$PANEL_PORT${NC}"
# Display Basic Auth credentials if configured
if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then
echo ""
echo -e "${WHITE}${BOLD}BASIC AUTH CREDENTIALS${NC}"
echo -e "${WHITE}Username: ${GREEN}${BOLD}$BASIC_AUTH_USERNAME${NC}"
echo -e "${WHITE}Password: ${GREEN}${BOLD}$BASIC_AUTH_PASSWORD${NC}"
echo ""
echo -e "${YELLOW}[!] Basic Auth is enabled. You will need these credentials to access CloudPanel.${NC}"
fi
# Display Admin account credentials if created
if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then
echo ""
echo -e "${WHITE}${BOLD}ADMIN ACCOUNT CREDENTIALS${NC}"
echo -e "${WHITE}Email: ${GREEN}${BOLD}$ADMIN_EMAIL${NC}"
echo -e "${WHITE}Username: ${GREEN}${BOLD}$ADMIN_USERNAME${NC}"
echo -e "${WHITE}Password: ${GREEN}${BOLD}$ADMIN_PASSWORD${NC}"
fi
echo ""
echo -e "${RED}${BOLD}IMPORTANT SECURITY STEPS:${NC}"
echo -e "${YELLOW}1. Access CloudPanel immediately via the URL above${NC}"
echo -e "${YELLOW}2. Ignore the self-signed certificate warning${NC}"
echo -e "${YELLOW}3. Click 'Advanced' and 'Proceed' to continue${NC}"
if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then
echo -e "${YELLOW}4. Enter Basic Auth credentials when prompted${NC}"
fi
if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then
echo -e "${YELLOW}$([ -n "$BASIC_AUTH_USERNAME" ] && echo "5" || echo "4"). Login with admin credentials above${NC}"
fi
echo ""
echo -e "${RED}${BOLD}REQUIRED FIREWALL PORTS:${NC}"
echo -e "${GRAY}20, 21, 22, 25, 53, 80, 443, 465, 587, 993, 995, $PANEL_PORT${NC}"
show_separator
if [ -n "$BASIC_AUTH_USERNAME" ] && [ -n "$BASIC_AUTH_PASSWORD" ]; then
echo ""
echo -e "${RED}${BOLD}IMPORTANT:${NC} ${YELLOW}Save Basic Auth credentials securely!${NC}"
fi
if [ -n "$ADMIN_USERNAME" ] && [ -n "$ADMIN_PASSWORD" ]; then
echo ""
echo -e "${RED}${BOLD}IMPORTANT:${NC} ${YELLOW}Save admin credentials securely!${NC}"
fi
}
update_cloudpanel() {
echo -e "${WHITE}${BOLD}CLOUDPANEL UPDATE${NC}\n"
apt_noninteractive
if ! is_installed; then
echo -e "${RED}CloudPanel is not installed!${NC}"
return 1
fi
echo -e "${PURPLE}[*] Updating CloudPanel...${NC}"
# Use clpctl if available
if command -v clpctl &> /dev/null; then
clpctl update
elif [ -f "/usr/local/bin/clpctl" ]; then
/usr/local/bin/clpctl update
else
echo -e "${YELLOW}[!] CloudPanel CLI not found, downloading latest installer...${NC}"
curl -sS "$INSTALLER_URL" -o /tmp/cloudpanel_update.sh
if echo "$INSTALLER_SHA256 /tmp/cloudpanel_update.sh" | sha256sum -c --quiet; then
bash /tmp/cloudpanel_update.sh
rm -f /tmp/cloudpanel_update.sh
else
echo -e "${RED}[✗] Failed to verify update installer${NC}"
rm -f /tmp/cloudpanel_update.sh
return 1
fi
fi
[ $? -eq 0 ] && echo -e "${GREEN}[✓] Update completed${NC}" || echo -e "${RED}[✗] Update failed${NC}"
}
show_status() {
echo -e "${WHITE}${BOLD}CLOUDPANEL STATUS${NC}\n"
if ! is_installed; then
echo -e "${RED}CloudPanel is not installed!${NC}"
return 1
fi
# Check CloudPanel service
if systemctl is-active --quiet cloudpanel; then
echo -e "${GREEN}[✓] CloudPanel service is running${NC}"
else
echo -e "${YELLOW}[!] CloudPanel service status unknown${NC}"
fi
# Check web server
if systemctl is-active --quiet nginx || systemctl is-active --quiet apache2; then
echo -e "${GREEN}[✓] Web server is running${NC}"
else
echo -e "${YELLOW}[!] Web server status unknown${NC}"
fi
# Check database
if systemctl is-active --quiet mysql || systemctl is-active --quiet mariadb; then
echo -e "${GREEN}[✓] Database service is running${NC}"
else
echo -e "${YELLOW}[!] Database service status unknown${NC}"
fi
echo ""
local server_ip=$(get_public_ip)
echo -e "${CYAN}Panel URL: ${BOLD}https://$server_ip:$PANEL_PORT${NC}"
# Show clpctl status if available
if command -v clpctl &> /dev/null || [ -f "/usr/local/bin/clpctl" ]; then
echo ""
echo -e "${WHITE}CloudPanel CLI available${NC}"
echo -e "${GRAY}Run 'clpctl' for available commands${NC}"
fi
return 0
}
install_rclone() {
echo -e "${PURPLE}[*] Checking rclone installation...${NC}"
if command -v rclone &> /dev/null; then
local rclone_version=$(rclone version | head -n 1 | awk '{print $2}')
echo -e "${GREEN}[✓] rclone is already installed (version: $rclone_version)${NC}"
return 0
fi
echo -e "${PURPLE}[*] Installing rclone...${NC}"
wait_for_apt || return 1
# Install rclone using official method
if ! run_spinner "Installing rclone" curl https://rclone.org/install.sh | bash; then
echo -e "${RED}[✗] Failed to install rclone${NC}"
return 1
fi
if command -v rclone &> /dev/null; then
local rclone_version=$(rclone version | head -n 1 | awk '{print $2}')
echo -e "${GREEN}[✓] rclone installed successfully (version: $rclone_version)${NC}"
return 0
else
echo -e "${RED}[✗] rclone installation failed${NC}"
return 1
fi
}
configure_rclone_backblaze() {
echo ""
echo -e "${WHITE}${BOLD}Configure rclone for Backblaze B2${NC}\n"
# Check if CloudPanel is installed
if ! is_installed; then
echo -e "${YELLOW}[!] CloudPanel is not installed. Please install CloudPanel first.${NC}"
return 1
fi
# Install rclone if needed
if ! install_rclone; then
return 1
fi
echo ""
echo -e "${CYAN}Backblaze B2 Configuration${NC}"
echo -e "${GRAY}You need to provide your Backblaze B2 credentials.${NC}"
echo -e "${GRAY}To get your credentials:${NC}"
echo -e "${GRAY} 1. Log in to your Backblaze account${NC}"
echo -e "${GRAY} 2. Go to 'App Keys' section${NC}"
echo -e "${GRAY} 3. Create a new Application Key (or use an existing one)${NC}"
echo -e "${GRAY} 4. Copy the 'Key ID' (Account ID) and 'Application Key'${NC}"
echo ""
# Ask for Account ID (Key ID)
echo -e "${CYAN}Account ID (Key ID):${NC}"
read -r B2_ACCOUNT_ID
while [ -z "$B2_ACCOUNT_ID" ]; do
echo -e "${RED}Account ID is required!${NC}"
read -r B2_ACCOUNT_ID
done
# Ask for Application Key
echo ""
echo -e "${CYAN}Application Key:${NC}"
read -rs B2_APPLICATION_KEY
while [ -z "$B2_APPLICATION_KEY" ]; do
echo -e "${RED}Application Key is required!${NC}"
read -rs B2_APPLICATION_KEY
done
echo ""
# Ask for bucket name (optional, for information)
echo ""
echo -e "${CYAN}Bucket Name (optional, for reference):${NC}"
read -r B2_BUCKET_NAME
echo ""
echo -e "${PURPLE}[*] Configuring rclone...${NC}"
# Create rclone config directory if it doesn't exist
# Use root's home directory since script runs as root
local rclone_config_dir="/root/.config/rclone"
mkdir -p "$rclone_config_dir"
# Configure rclone non-interactively
# The remote name must be "remote" for CloudPanel to recognize it
local rclone_config_file="$rclone_config_dir/rclone.conf"
# Check if remote already exists
if [ -f "$rclone_config_file" ] && grep -q "^\[remote\]" "$rclone_config_file" 2>/dev/null; then
echo -e "${YELLOW}[!] A remote named 'remote' already exists.${NC}"
echo -e "${CYAN}Do you want to overwrite it? (y/n):${NC}"
read -r overwrite_choice
if [[ ! "$overwrite_choice" =~ ^[Yy]$ ]]; then
echo -e "${YELLOW}[!] Configuration cancelled${NC}"
return 1
fi
# Create a temporary file without the [remote] section
local temp_config=$(mktemp)
local in_remote_section=false
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^\[remote\] ]]; then
in_remote_section=true
continue
elif [[ "$line" =~ ^\[ ]] && [ "$in_remote_section" = true ]; then
in_remote_section=false
echo "$line" >> "$temp_config"
elif [ "$in_remote_section" = false ]; then
echo "$line" >> "$temp_config"
fi
done < "$rclone_config_file"
mv "$temp_config" "$rclone_config_file"
fi
# Add new remote configuration
cat >> "$rclone_config_file" << EOF
[remote]
type = b2
account = $B2_ACCOUNT_ID
key = $B2_APPLICATION_KEY
hard_delete = false
EOF
if [ $? -eq 0 ]; then
echo -e "${GREEN}[✓] rclone configured successfully${NC}"
else
echo -e "${RED}[✗] Failed to configure rclone${NC}"
return 1
fi
# Test the configuration
echo ""
echo -e "${PURPLE}[*] Testing rclone configuration...${NC}"
if rclone lsd remote: > /dev/null 2>&1; then
echo -e "${GREEN}[✓] rclone connection test successful${NC}"
# List buckets if available
echo ""
echo -e "${CYAN}Available buckets:${NC}"
rclone lsd remote: 2>/dev/null | awk '{print " - " $5}' || echo -e "${GRAY} (Unable to list buckets)${NC}"
else
echo -e "${YELLOW}[!] Warning: Could not test connection to Backblaze B2${NC}"
echo -e "${GRAY} Please verify your credentials are correct${NC}"
fi
echo ""
show_separator
echo -e "${WHITE}${BOLD}Configuration Complete!${NC}"
show_separator
echo ""
echo -e "${GREEN}[✓] rclone has been configured with Backblaze B2${NC}"
echo ""
echo -e "${WHITE}${BOLD}Next Steps:${NC}"
echo -e "${CYAN}1. Access CloudPanel at: ${BOLD}https://$(get_public_ip):$PANEL_PORT${NC}"
echo -e "${CYAN}2. Go to Settings > Backups${NC}"
echo -e "${CYAN}3. Select 'Custom Rclone Config' as the storage provider${NC}"
if [ -n "$B2_BUCKET_NAME" ]; then
echo -e "${CYAN}4. In 'Storage Directory', enter: ${BOLD}$B2_BUCKET_NAME${NC}"
else
echo -e "${CYAN}4. In 'Storage Directory', enter your Backblaze B2 bucket name${NC}"
fi
echo -e "${CYAN}5. Configure backup frequency and retention as needed${NC}"
echo -e "${CYAN}6. Save the configuration${NC}"
echo ""
echo -e "${GRAY}[i] Note: The rclone remote is named 'remote' as required by CloudPanel${NC}"
show_separator
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Menu
# ═══════════════════════════════════════════════════════════════════════════
show_menu() {
clear
echo -e "${WHITE}${BOLD}CLOUDPANEL MANAGEMENT${NC}\n"
echo -e " ${GREEN}[1]${NC} Install CloudPanel"
echo -e " ${YELLOW}[2]${NC} Update CloudPanel"
echo -e " ${CYAN}[3]${NC} View Status"
echo -e " ${PURPLE}[4]${NC} Configure rclone for Backblaze B2"
echo -e " ${RED}[0]${NC} Back to main menu"
echo ""
echo -n "Choice [0-4]: "
}
main() {
while true; do
show_menu
read -r choice
echo ""
case $choice in
1) install_cloudpanel ;;
2) update_cloudpanel ;;
3) show_status ;;
4) configure_rclone_backblaze ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
main
+260
View File
@@ -0,0 +1,260 @@
#!/bin/bash
# LXS - Coolify Installation Script
# Description: Install and manage Coolify deployment platform
# 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_coolify.log"
require_root "$0" "$@"
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
INSTALL_DIR="/data/coolify"
PORT=8000
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
is_installed() {
[ -d "$INSTALL_DIR" ] && docker ps | grep -q "coolify"
}
check_requirements() {
echo -e "${WHITE}Checking System Requirements...${NC}"
local cpu_cores=$(nproc)
[ $cpu_cores -lt 2 ] && echo -e "${YELLOW}[!] Warning: Recommended minimum is 2 CPU cores (found: $cpu_cores)${NC}" || echo -e "${GREEN}[✓] CPU cores: $cpu_cores${NC}"
local total_mem_gb=$(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024 / 1024))
[ $total_mem_gb -lt 2 ] && echo -e "${YELLOW}[!] Warning: Recommended minimum is 2GB RAM (found: ${total_mem_gb}GB)${NC}" || echo -e "${GREEN}[✓] Memory: ${total_mem_gb}GB${NC}"
local available_gb=$(($(df / | awk 'NR==2 {print $4}') / 1024 / 1024))
[ $available_gb -lt 30 ] && echo -e "${YELLOW}[!] Warning: Recommended minimum is 30GB free space (found: ${available_gb}GB)${NC}" || echo -e "${GREEN}[✓] Disk space: ${available_gb}GB available${NC}"
local arch=$(uname -m)
if [[ "$arch" != "x86_64" && "$arch" != "aarch64" ]]; then
echo -e "${RED}[✗] Unsupported architecture: $arch${NC}"
return 1
fi
echo -e "${GREEN}[✓] Architecture: $arch${NC}"
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Installation Functions
# ═══════════════════════════════════════════════════════════════════════════
install_coolify() {
local start_time=$(date +%s)
apt_noninteractive
echo -e "${WHITE}${BOLD}COOLIFY INSTALLATION${NC}\n"
if is_installed; then
echo -e "${YELLOW}Coolify is already installed!${NC}"
return 0
fi
check_requirements || return 1
echo ""
wait_for_apt || return 1
echo ""
echo -e "${WHITE}Running Official Coolify Installer${NC}"
echo -e "${GRAY}[i] Installing Docker and all dependencies...${NC}"
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
if [ $? -ne 0 ]; then
echo -e "${RED}[✗] Installation failed!${NC}"
return 1
fi
echo -e "${PURPLE}[*] Waiting for Coolify to start...${NC}"
sleep 10
local max_retries=30
local retry=0
while [ $retry -lt $max_retries ]; do
docker ps | grep -q "coolify" && break
retry=$((retry + 1))
sleep 2
done
local duration=$(( $(date +%s) - start_time ))
local minutes=$((duration / 60))
local seconds=$((duration % 60))
echo ""
echo -e "${GREEN}${BOLD}Installation Completed Successfully!${NC}"
echo -e "${GRAY}Installation time: ${minutes}m ${seconds}s${NC}"
echo ""
echo -e "${CYAN}Access URL: ${BOLD}http://$(get_public_ip):$PORT${NC}"
echo ""
echo -e "${RED}${BOLD}IMPORTANT:${NC} ${RED}Create your admin account immediately!${NC}"
echo -e "${RED}Visit the URL now to secure your installation.${NC}"
}
update_coolify() {
echo -e "${WHITE}${BOLD}COOLIFY UPDATE${NC}\n"
apt_noninteractive
if ! is_installed; then
echo -e "${RED}Coolify is not installed!${NC}"
return 1
fi
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
[ $? -eq 0 ] && echo -e "${GREEN}[✓] Update completed${NC}" || echo -e "${RED}[✗] Update failed${NC}"
}
show_status() {
echo -e "${WHITE}${BOLD}COOLIFY STATUS${NC}\n"
if ! is_installed; then
echo -e "${RED}Coolify is not installed!${NC}"
return 1
fi
if docker ps | grep -q "coolify"; then
echo -e "${GREEN}Coolify is running${NC}\n"
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "(NAMES|coolify)"
echo ""
echo -e "${CYAN}Access URL: ${BOLD}http://$(get_public_ip):$PORT${NC}"
else
echo -e "${RED}Coolify is not running${NC}"
fi
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Menu
# ═══════════════════════════════════════════════════════════════════════════
show_post_installation_guide() {
clear
echo -e "${WHITE}${BOLD}COOLIFY POST-INSTALLATION GUIDE${NC}\n"
show_separator
echo -e "${CYAN}${BOLD}1. Domain Name${NC}"
echo -e "${GRAY} Settings -> General${NC}"
echo -e "${WHITE} Name${NC}"
echo ""
show_separator
echo -e "${CYAN}${BOLD}2. Timezone${NC}"
echo -e "${GRAY} Settings -> General${NC}"
echo -e "${WHITE} Timezone: Toronto${NC}"
echo ""
show_separator
echo -e "${CYAN}${BOLD}3. Do Not Track Activation${NC}"
echo -e "${GRAY} Settings -> Advanced${NC}"
echo ""
show_separator
echo -e "${CYAN}${BOLD}4. Discord Notifications${NC}"
echo -e "${GRAY} Notifications -> Discord${NC}"
echo ""
echo -e "${WHITE} Configuration:${NC}"
echo -e " ${GREEN}[x]${NC} Deployment Success"
echo -e " ${GREEN}[x]${NC} Deployment Failure"
echo -e " ${GRAY}[ ]${NC} Container Status Changes"
echo -e " ${GREEN}[x]${NC} Backup Success"
echo -e " ${GREEN}[x]${NC} Backup Failure"
echo -e " ${GRAY}[ ]${NC} Scheduled Task Success"
echo -e " ${GREEN}[x]${NC} Scheduled Task Failure"
echo -e " ${GRAY}[ ]${NC} Docker Cleanup Success"
echo -e " ${GREEN}[x]${NC} Docker Cleanup Failure"
echo -e " ${GREEN}[x]${NC} Server Disk Usage"
echo -e " ${GREEN}[x]${NC} Server Reachable"
echo -e " ${GREEN}[x]${NC} Server Unreachable"
echo ""
show_separator
echo -e "${CYAN}${BOLD}5. S3 Storage for Backups${NC}"
echo -e "${GRAY} S3 Storages${NC}"
echo ""
echo -e "${WHITE} Configuration:${NC}"
echo -e " ${GRAY}Name:${NC} Provider name (ex. Backblaze)"
echo -e " ${GRAY}Endpoint:${NC} "
echo -e " ${GRAY}Bucket:${NC} "
echo -e " ${GRAY}Access Key:${NC} "
echo -e " ${GRAY}Secret Key:${NC} "
echo ""
show_separator
echo -e "${CYAN}${BOLD}6. Coolify Backup Configuration${NC}"
echo -e "${GRAY} Settings -> Backup${NC}"
echo ""
echo -e "${WHITE} Configuration:${NC}"
echo -e " ${GREEN}[x]${NC} S3 Enabled"
echo -e " ${GRAY}Number of backups to keep:${NC} 7"
echo ""
show_separator
echo -e "${CYAN}${BOLD}7. Server Timezone${NC}"
echo -e "${GRAY} Servers -> localhost -> General${NC}"
echo -e "${WHITE} Timezone: Toronto${NC}"
echo ""
show_separator
echo -e "${CYAN}${BOLD}8. Docker Cleanup Schedule${NC}"
echo -e "${GRAY} Servers -> localhost -> Docker Cleanup${NC}"
echo ""
echo -e "${WHITE} Schedule (every hour):${NC}"
echo -e " ${GRAY}0 * * * *${NC}"
echo ""
show_separator
echo ""
}
show_menu() {
clear
echo -e "${WHITE}${BOLD}COOLIFY MANAGEMENT${NC}\n"
echo -e " ${GREEN}[1]${NC} Install Coolify"
echo -e " ${YELLOW}[2]${NC} Update Coolify"
echo -e " ${CYAN}[3]${NC} View Status"
echo -e " ${PURPLE}[4]${NC} Post-Installation Guide"
echo -e " ${RED}[0]${NC} Back to main menu"
echo ""
echo -n "Choice [0-4]: "
}
main() {
while true; do
show_menu
read -r choice
echo ""
case $choice in
1) install_coolify ;;
2) update_coolify ;;
3) show_status ;;
4) show_post_installation_guide ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
main
+354
View File
@@ -0,0 +1,354 @@
#!/bin/bash
# LXS - Proxmox VE Management Script
# Description: Tools for managing and troubleshooting Proxmox VE
# 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_proxmox.log"
require_root "$0" "$@"
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
LOCKFILE="/var/lib/pve-cluster/.pmxcfs.lockfile"
SERVICE_NAME="pve-cluster"
PANEL_PORT=8006
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
is_proxmox() {
[ -f /etc/pve/.version ] || command -v pveversion &> /dev/null
}
get_service_status() {
if systemctl is-active --quiet $SERVICE_NAME; then
echo -e "${GREEN}running${NC}"
else
echo -e "${RED}stopped${NC}"
fi
}
check_proxmox() {
if ! is_proxmox; then
echo -e "${RED}[!] This is not a Proxmox VE system!${NC}"
echo -e "${YELLOW} This script is designed for Proxmox VE only.${NC}"
return 1
fi
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Login Fix Functions
# ═══════════════════════════════════════════════════════════════════════════
fix_login_issue() {
local start_time=$(date +%s)
echo -e "${WHITE}${BOLD}PROXMOX LOGIN FIX${NC}\n"
check_proxmox || return 1
echo -e "${CYAN}This will reset the pve-cluster lockfile to fix login issues.${NC}"
echo ""
echo "[1/3] Stopping pve-cluster service..."
run_spinner "Stopping $SERVICE_NAME..." "systemctl stop $SERVICE_NAME"
if [ $? -ne 0 ]; then
echo -e "${RED}[!] Failed to stop pve-cluster service${NC}"
return 1
fi
echo ""
echo "[2/3] Removing lockfile..."
if [ -f "$LOCKFILE" ]; then
run_spinner "Removing lockfile..." "rm -f '$LOCKFILE'"
echo -e "${GRAY} Lockfile was present and has been removed${NC}"
else
echo -e "${YELLOW}[!] Lockfile not found (may already be removed)${NC}"
fi
echo ""
echo "[3/3] Starting pve-cluster service..."
run_spinner "Starting $SERVICE_NAME..." "systemctl start $SERVICE_NAME"
if [ $? -ne 0 ]; then
echo -e "${RED}[!] Failed to start pve-cluster service${NC}"
echo -e "${YELLOW} Check logs with: journalctl -xe${NC}"
return 1
fi
sleep 2
local duration=$(( $(date +%s) - start_time ))
local server_ip=$(hostname -I | awk '{print $1}')
echo ""
show_separator
echo -e "${GREEN}${BOLD}Fix Completed!${NC}"
echo -e "${GRAY}Time: ${duration}s${NC}"
echo ""
echo -e "${WHITE}Service Status:${NC} $(get_service_status)"
echo ""
echo -e "${CYAN}You should now be able to login to the Proxmox web interface.${NC}"
echo -e "${WHITE}Panel URL: ${GREEN}${BOLD}https://$server_ip:$PANEL_PORT${NC}"
show_separator
}
# ═══════════════════════════════════════════════════════════════════════════
# Service Management Functions
# ═══════════════════════════════════════════════════════════════════════════
restart_pve_cluster() {
echo -e "${WHITE}${BOLD}RESTART PVE-CLUSTER${NC}\n"
check_proxmox || return 1
run_spinner "Restarting $SERVICE_NAME..." "systemctl restart $SERVICE_NAME"
sleep 2
echo ""
echo -e "${WHITE}Service Status:${NC} $(get_service_status)"
}
restart_all_services() {
echo -e "${WHITE}${BOLD}RESTART ALL PROXMOX SERVICES${NC}\n"
check_proxmox || return 1
local services=("pve-cluster" "pvedaemon" "pveproxy" "pvestatd")
for service in "${services[@]}"; do
if systemctl list-unit-files | grep -q "^$service.service"; then
run_spinner "Restarting $service..." "systemctl restart $service"
fi
done
sleep 2
echo ""
echo -e "${GREEN}[✓] All services restarted${NC}"
echo ""
echo -e "${WHITE}Panel URL: ${GREEN}${BOLD}https://$(hostname -I | awk '{print $1}'):$PANEL_PORT${NC}"
}
# ═══════════════════════════════════════════════════════════════════════════
# Status Functions
# ═══════════════════════════════════════════════════════════════════════════
show_status() {
echo -e "${WHITE}${BOLD}PROXMOX VE STATUS${NC}\n"
check_proxmox || return 1
# Proxmox version
echo -e "${WHITE}Proxmox Version:${NC}"
pveversion 2>/dev/null || echo -e "${GRAY}Unable to determine version${NC}"
echo ""
# Service statuses
echo -e "${WHITE}Service Status:${NC}"
local services=("pve-cluster" "pvedaemon" "pveproxy" "pvestatd")
for service in "${services[@]}"; do
if systemctl list-unit-files | grep -q "^$service.service"; then
if systemctl is-active --quiet $service; then
echo -e " ${GREEN}[✓]${NC} $service"
else
echo -e " ${RED}[✗]${NC} $service"
fi
fi
done
echo ""
# Lockfile status
if [ -f "$LOCKFILE" ]; then
echo -e "${WHITE}Lockfile:${NC} ${YELLOW}Present${NC} ($LOCKFILE)"
else
echo -e "${WHITE}Lockfile:${NC} ${GREEN}Not present${NC}"
fi
echo ""
# Access URL
local server_ip=$(hostname -I | awk '{print $1}')
echo -e "${WHITE}Panel URL: ${GREEN}${BOLD}https://$server_ip:$PANEL_PORT${NC}"
echo ""
# Cluster info
if command -v pvecm &> /dev/null; then
echo -e "${WHITE}Cluster Status:${NC}"
pvecm status 2>/dev/null | head -5 || echo -e "${GRAY}Not in a cluster or unable to get status${NC}"
fi
return 0
}
show_storage_status() {
echo -e "${WHITE}${BOLD}PROXMOX STORAGE STATUS${NC}\n"
check_proxmox || return 1
# Storage list
echo -e "${WHITE}Storage Configuration:${NC}"
if command -v pvesm &> /dev/null; then
pvesm status 2>/dev/null || echo -e "${GRAY}Unable to get storage status${NC}"
else
echo -e "${GRAY}pvesm command not available${NC}"
fi
echo ""
# Disk usage
echo -e "${WHITE}Disk Usage:${NC}"
df -h / /var /tmp 2>/dev/null | head -5
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Maintenance Functions
# ═══════════════════════════════════════════════════════════════════════════
clear_cache() {
echo -e "${WHITE}${BOLD}CLEAR PROXMOX CACHE${NC}\n"
check_proxmox || return 1
echo -e "${CYAN}This will clear various Proxmox caches and temporary files.${NC}"
echo ""
run_spinner "Clearing apt cache..." "apt clean"
run_spinner "Clearing journal logs (older than 7 days)..." "journalctl --vacuum-time=7d"
# Clear task logs older than 30 days
if [ -d "/var/log/pve/tasks" ]; then
run_spinner "Clearing old task logs..." "find /var/log/pve/tasks -type f -mtime +30 -delete"
fi
echo ""
echo -e "${GREEN}[✓] Cache cleared${NC}"
}
update_proxmox() {
echo -e "${WHITE}${BOLD}UPDATE PROXMOX VE${NC}\n"
check_proxmox || return 1
# Configure non-interactive mode
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
local start_time=$(date +%s)
run_spinner "Updating package lists..." "apt update"
echo ""
echo -e "${PURPLE}[*] Upgrading packages...${NC}"
show_separator
apt dist-upgrade -y
local exit_code=$?
show_separator
if [ $exit_code -eq 0 ]; then
local duration=$(( $(date +%s) - start_time ))
echo ""
echo -e "${GREEN}${BOLD}Update Completed!${NC}"
echo -e "${GRAY}Time: $((duration / 60))m $((duration % 60))s${NC}"
echo ""
echo -e "${YELLOW}[!] A reboot may be required if the kernel was updated.${NC}"
else
echo ""
echo -e "${RED}[✗] Update failed${NC}"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# Network Functions
# ═══════════════════════════════════════════════════════════════════════════
show_network_info() {
echo -e "${WHITE}${BOLD}PROXMOX NETWORK INFO${NC}\n"
check_proxmox || return 1
local server_ip=$(hostname -I | awk '{print $1}')
local public_ip=$(get_public_ip)
echo -e "${WHITE}Hostname:${NC} $(hostname)"
echo -e "${WHITE}Local IP:${NC} $server_ip"
echo -e "${WHITE}Public IP:${NC} $public_ip"
echo ""
echo -e "${WHITE}Network Interfaces:${NC}"
ip -br addr show 2>/dev/null || ifconfig 2>/dev/null | grep -E "^[a-z]|inet "
echo ""
echo -e "${WHITE}Bridges:${NC}"
if command -v brctl &> /dev/null; then
brctl show 2>/dev/null || echo -e "${GRAY}No bridges found${NC}"
else
ip link show type bridge 2>/dev/null || echo -e "${GRAY}No bridges found${NC}"
fi
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Menu
# ═══════════════════════════════════════════════════════════════════════════
show_menu() {
clear
echo -e "${WHITE}${BOLD}PROXMOX VE MANAGEMENT${NC}\n"
echo -e " ${GREEN}[1]${NC} Fix Login Issue (reset lockfile)"
echo -e " ${YELLOW}[2]${NC} Restart pve-cluster service"
echo -e " ${YELLOW}[3]${NC} Restart all Proxmox services"
echo -e " ${CYAN}[4]${NC} View System Status"
echo -e " ${CYAN}[5]${NC} View Storage Status"
echo -e " ${CYAN}[6]${NC} View Network Info"
echo -e " ${PURPLE}[7]${NC} Update Proxmox VE"
echo -e " ${PURPLE}[8]${NC} Clear Cache"
echo -e " ${RED}[0]${NC} Back to main menu"
echo ""
echo -n "Choice [0-8]: "
}
main() {
while true; do
show_menu
read -r choice
echo ""
case $choice in
1) fix_login_issue ;;
2) restart_pve_cluster ;;
3) restart_all_services ;;
4) show_status ;;
5) show_storage_status ;;
6) show_network_info ;;
7) update_proxmox ;;
8) clear_cache ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
main
+330
View File
@@ -0,0 +1,330 @@
#!/bin/bash
# LXS - Pterodactyl Installation Script
# Description: Install Pterodactyl Panel + Wings
# 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_pterodactyl.log"
require_root "$0" "$@"
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
PANEL_DIR="/var/www/pterodactyl"
WINGS_DIR="/etc/pterodactyl"
DB_NAME="panel"
DB_USER="pterodactyl"
DB_HOST="127.0.0.1"
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
is_installed() {
[ -d "$PANEL_DIR" ] && systemctl list-unit-files | grep -q "pteroq.service"
}
# ═══════════════════════════════════════════════════════════════════════════
# Installation Functions
# ═══════════════════════════════════════════════════════════════════════════
install_dependencies() {
apt_noninteractive
run_spinner "Updating system..." "apt update -qq"
run_spinner "Installing dependencies..." "apt install -y -qq software-properties-common curl -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
run_spinner "Adding PHP repository..." "LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php && apt update -qq"
run_spinner "Installing PHP and services..." "apt install -y -qq php8.3 php8.3-{cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} mariadb-server nginx tar unzip git redis-server -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
}
install_composer() {
run_spinner "Installing Composer..." "curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer --quiet"
}
setup_database() {
DB_PASS=$(generate_password)
echo -e "${PURPLE}[*] Configuring database...${NC}"
systemctl start mariadb && systemctl enable mariadb
mysql -u root <<MYSQL_SCRIPT
CREATE DATABASE IF NOT EXISTS ${DB_NAME};
CREATE USER IF NOT EXISTS '${DB_USER}'@'${DB_HOST}' IDENTIFIED BY '${DB_PASS}';
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'${DB_HOST}';
FLUSH PRIVILEGES;
MYSQL_SCRIPT
[ $? -eq 0 ] && echo -e "${GREEN}[✓] Database configured${NC}" || { echo -e "${RED}[✗] Database failed${NC}"; return 1; }
}
install_panel() {
mkdir -p $PANEL_DIR && cd $PANEL_DIR
run_spinner "Downloading Panel..." "curl -Lo panel.tar.gz https://github.com/pterodactyl/panel/releases/latest/download/panel.tar.gz"
run_spinner "Extracting..." "tar -xzf panel.tar.gz"
run_spinner "Setting permissions..." "chmod -R 755 storage/* bootstrap/cache/"
}
configure_panel() {
cd $PANEL_DIR
echo -e "${PURPLE}[*] Installing dependencies (this may take a few minutes)...${NC}"
export COMPOSER_ALLOW_SUPERUSER=1
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --optimize-autoloader --no-interaction > /tmp/lxs_composer.log 2>&1
cp .env.example .env
php artisan key:generate --force
sed -i "s/DB_DATABASE=.*/DB_DATABASE=${DB_NAME}/" .env
sed -i "s/DB_USERNAME=.*/DB_USERNAME=${DB_USER}/" .env
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/" .env
run_spinner "Running migrations..." "php artisan migrate --seed --force"
echo ""
echo -e "${WHITE}${BOLD}Admin Account Setup${NC}"
read -p "Email: " ADMIN_EMAIL
read -p "Username: " ADMIN_USERNAME
read -p "First Name: " ADMIN_FIRSTNAME
read -p "Last Name: " ADMIN_LASTNAME
read -sp "Password: " ADMIN_PASSWORD
echo ""
php artisan p:user:make --email="$ADMIN_EMAIL" --username="$ADMIN_USERNAME" --name-first="$ADMIN_FIRSTNAME" --name-last="$ADMIN_LASTNAME" --password="$ADMIN_PASSWORD" --admin=1 --no-interaction
chown -R www-data:www-data $PANEL_DIR
}
configure_nginx() {
local domain=$1
cat > /etc/nginx/sites-available/pterodactyl.conf <<'EOF'
server {
listen 80;
server_name DOMAIN_PLACEHOLDER;
root /var/www/pterodactyl/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
EOF
sed -i "s/DOMAIN_PLACEHOLDER/$domain/g" /etc/nginx/sites-available/pterodactyl.conf
ln -sf /etc/nginx/sites-available/pterodactyl.conf /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl restart nginx php8.3-fpm
}
configure_ssl() {
local domain=$1
local email=$2
run_spinner "Installing Certbot..." "apt install -y -qq certbot python3-certbot-nginx -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
certbot --nginx -d $domain --non-interactive --agree-tos --email $email --redirect 2>/dev/null
[ $? -eq 0 ] && cd $PANEL_DIR && sed -i "s|APP_URL=.*|APP_URL=https://${domain}|" .env
}
setup_services() {
cat > /etc/systemd/system/pteroq.service <<'EOF'
[Unit]
Description=Pterodactyl Queue Worker
After=redis-server.service
[Service]
User=www-data
Group=www-data
Restart=always
ExecStart=/usr/bin/php /var/www/pterodactyl/artisan queue:work --queue=high,standard,low --sleep=3 --tries=3
[Install]
WantedBy=multi-user.target
EOF
systemctl enable --now redis-server pteroq.service
(crontab -l 2>/dev/null; echo "* * * * * php /var/www/pterodactyl/artisan schedule:run >> /dev/null 2>&1") | crontab -
}
install_docker() {
run_spinner "Installing Docker..." "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \
echo \"deb [arch=\$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null && \
apt update -qq && apt install -y -qq docker-ce docker-ce-cli containerd.io -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
systemctl enable --now docker
}
install_wings() {
mkdir -p $WINGS_DIR
run_spinner "Installing Wings..." "curl -L -o /usr/local/bin/wings https://github.com/pterodactyl/wings/releases/latest/download/wings_linux_amd64 && chmod +x /usr/local/bin/wings"
cat > /etc/systemd/system/wings.service <<'EOF'
[Unit]
Description=Pterodactyl Wings
After=docker.service
Requires=docker.service
[Service]
User=root
WorkingDirectory=/etc/pterodactyl
ExecStart=/usr/local/bin/wings
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Installation
# ═══════════════════════════════════════════════════════════════════════════
install_pterodactyl() {
local start_time=$(date +%s)
# Configure non-interactive mode globally
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
echo -e "${WHITE}${BOLD}PTERODACTYL INSTALLATION${NC}\n"
if is_installed; then
echo -e "${YELLOW}Pterodactyl is already installed!${NC}"
return 0
fi
echo "[1/10] Installing dependencies..."
install_dependencies && install_composer
echo ""
echo "[2/10] Setting up database..."
setup_database
echo ""
echo "[3/10] Installing Panel..."
install_panel
echo ""
echo "[4/10] Configuring Panel..."
configure_panel
echo ""
echo "[5/10] Domain configuration..."
read -p "Panel domain (e.g., panel.example.com): " PANEL_DOMAIN
read -p "Wings domain (e.g., wings.example.com): " WINGS_DOMAIN
read -p "Email for SSL: " SSL_EMAIL
echo ""
echo "[6/10] Configuring Nginx..."
configure_nginx "$PANEL_DOMAIN"
echo ""
echo "[7/10] Configuring SSL..."
configure_ssl "$PANEL_DOMAIN" "$SSL_EMAIL"
echo ""
echo "[8/10] Setting up services..."
setup_services
echo ""
echo "[9/10] Installing Wings..."
install_docker && install_wings
echo ""
echo "[10/10] Wings configuration..."
echo -e "${YELLOW}Configure Wings from Panel: Admin -> Nodes -> Create Node${NC}"
echo -e "${GRAY}Node settings:${NC}"
echo -e " FQDN: ${CYAN}$WINGS_DOMAIN${NC}"
echo -e " SSL: ${CYAN}Yes${NC}"
echo -e " Port: ${CYAN}8080${NC}"
echo ""
read -p "Press Enter when ready to paste Wings config..."
echo -e "${CYAN}Paste config and press Ctrl+D:${NC}"
cat > /etc/pterodactyl/config.yml
[ -s /etc/pterodactyl/config.yml ] && systemctl enable --now wings
local duration=$(( $(date +%s) - start_time ))
echo ""
echo -e "${GREEN}${BOLD}Installation Completed!${NC}"
echo -e "${GRAY}Time: $((duration / 60))m $((duration % 60))s${NC}"
echo ""
echo -e "${CYAN}Panel: ${BOLD}https://$PANEL_DOMAIN${NC}"
echo -e "${CYAN}Wings: ${BOLD}$WINGS_DOMAIN:8080${NC}"
echo ""
echo -e "${WHITE}Database Credentials:${NC}"
echo -e " Host: $DB_HOST"
echo -e " Database: $DB_NAME"
echo -e " User: $DB_USER"
echo -e " Password: $DB_PASS"
}
show_status() {
echo -e "${WHITE}${BOLD}PTERODACTYL STATUS${NC}\n"
if ! is_installed; then
echo -e "${RED}Pterodactyl is not installed!${NC}"
return 1
fi
systemctl is-active --quiet pteroq && echo -e "${GREEN}[✓] Panel Queue${NC}" || echo -e "${RED}[✗] Panel Queue${NC}"
systemctl is-active --quiet nginx && echo -e "${GREEN}[✓] Web Server${NC}" || echo -e "${RED}[✗] Web Server${NC}"
systemctl is-active --quiet wings && echo -e "${GREEN}[✓] Wings${NC}" || echo -e "${YELLOW}[!] Wings (needs config)${NC}"
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Menu
# ═══════════════════════════════════════════════════════════════════════════
show_menu() {
clear
echo -e "${WHITE}${BOLD}PTERODACTYL MANAGEMENT${NC}\n"
echo -e " ${GREEN}[1]${NC} Install Pterodactyl"
echo -e " ${CYAN}[2]${NC} View Status"
echo -e " ${RED}[0]${NC} Back to main menu"
echo ""
echo -n "Choice [0-2]: "
}
main() {
while true; do
show_menu
read -r choice
echo ""
case $choice in
1) install_pterodactyl ;;
2) show_status ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
main
+248
View File
@@ -0,0 +1,248 @@
#!/bin/bash
# LXS - Uptime Kuma Installation Script
# Description: Install and manage Uptime Kuma monitoring tool
# 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_uptime_kuma.log"
require_root "$0" "$@"
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
INSTALL_DIR="/opt/uptime-kuma"
SERVICE_NAME="uptime-kuma"
PORT=3001
# ═══════════════════════════════════════════════════════════════════════════
# Helper Functions
# ═══════════════════════════════════════════════════════════════════════════
is_installed() {
[ -d "$INSTALL_DIR" ] && systemctl list-unit-files | grep -q "$SERVICE_NAME.service"
}
# ═══════════════════════════════════════════════════════════════════════════
# Installation Functions
# ═══════════════════════════════════════════════════════════════════════════
install_dependencies() {
apt_noninteractive
run_spinner "Updating system..." "apt update -qq && apt upgrade -y -qq -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
run_spinner "Installing dependencies..." "apt install -y -qq curl git wget -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
run_spinner "Adding Node.js repository..." "curl -fsSL https://deb.nodesource.com/setup_20.x | bash -"
run_spinner "Installing Node.js..." "apt install -y -qq nodejs -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
}
configure_domain_ssl() {
local domain=$1
local email=$2
run_spinner "Installing Nginx..." "apt install -y -qq nginx -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
run_spinner "Installing Certbot..." "apt install -y -qq certbot python3-certbot-nginx -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
cat > /etc/nginx/sites-available/$domain <<EOF
server {
listen 80;
server_name $domain;
location / {
proxy_pass http://localhost:$PORT;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
EOF
ln -sf /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && systemctl restart nginx
certbot --nginx -d $domain --non-interactive --agree-tos --email $email --redirect 2>/dev/null
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Installation
# ═══════════════════════════════════════════════════════════════════════════
install_uptime_kuma() {
local start_time=$(date +%s)
# Configure non-interactive mode globally
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
echo -e "${WHITE}${BOLD}UPTIME KUMA INSTALLATION${NC}\n"
if is_installed; then
echo -e "${YELLOW}Uptime Kuma is already installed!${NC}"
return 0
fi
echo "[1/4] Installing dependencies..."
install_dependencies
echo ""
echo "[2/4] Cloning repository..."
run_spinner "Downloading from GitHub..." "git clone https://github.com/louislam/uptime-kuma.git '$INSTALL_DIR'"
echo ""
echo "[3/4] Installing Uptime Kuma..."
cd "$INSTALL_DIR"
run_spinner "Running npm setup..." "npm run setup"
echo ""
echo "[4/4] Creating service..."
cat > /etc/systemd/system/$SERVICE_NAME.service <<EOF
[Unit]
Description=Uptime Kuma
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=$INSTALL_DIR
ExecStart=/usr/bin/npm run start-server
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
run_spinner "Starting service..." "systemctl daemon-reload && systemctl enable $SERVICE_NAME && systemctl start $SERVICE_NAME"
sleep 5
local duration=$(( $(date +%s) - start_time ))
local server_ip=$(get_public_ip)
echo ""
echo -e "${GREEN}${BOLD}Installation Completed!${NC}"
echo -e "${GRAY}Time: $((duration / 60))m $((duration % 60))s${NC}"
echo ""
echo -e "${CYAN}Access URL: ${BOLD}http://$server_ip:$PORT${NC}"
echo ""
# Optional domain configuration
echo -e "${WHITE}Configure domain with SSL? (y/n)${NC}"
read -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
read -p "Domain name: " DOMAIN_NAME
read -p "Email for SSL: " EMAIL
echo ""
configure_domain_ssl "$DOMAIN_NAME" "$EMAIL"
[ $? -eq 0 ] && echo -e "${GREEN}[✓] Domain configured: ${BOLD}https://$DOMAIN_NAME${NC}"
fi
echo ""
echo -e "${WHITE}Next steps:${NC}"
echo -e "1. Visit the URL above"
echo -e "2. Choose SQLite database"
echo -e "3. Create admin account"
}
update_uptime_kuma() {
echo -e "${WHITE}${BOLD}UPTIME KUMA UPDATE${NC}\n"
# Configure non-interactive mode
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
if ! is_installed; then
echo -e "${RED}Uptime Kuma is not installed!${NC}"
return 1
fi
local start_time=$(date +%s)
run_spinner "Stopping service..." "systemctl stop $SERVICE_NAME"
BACKUP_DIR="${INSTALL_DIR}_backup_$(date +%Y%m%d_%H%M%S)"
[ -d "$INSTALL_DIR/data" ] && run_spinner "Backing up..." "cp -r '$INSTALL_DIR/data' '$BACKUP_DIR'"
cd "$INSTALL_DIR"
run_spinner "Pulling updates..." "git fetch --all && git checkout master && git pull"
run_spinner "Updating dependencies..." "npm run setup"
run_spinner "Starting service..." "systemctl start $SERVICE_NAME"
local duration=$(( $(date +%s) - start_time ))
echo ""
echo -e "${GREEN}${BOLD}Update Completed!${NC}"
echo -e "${GRAY}Time: $((duration / 60))m $((duration % 60))s${NC}"
echo -e "${CYAN}Access: ${BOLD}http://$(get_public_ip):$PORT${NC}"
}
show_status() {
echo -e "${WHITE}${BOLD}UPTIME KUMA STATUS${NC}\n"
if ! is_installed; then
echo -e "${RED}Uptime Kuma is not installed!${NC}"
return 1
fi
if systemctl is-active --quiet $SERVICE_NAME; then
echo -e "${GREEN}Service is running${NC}\n"
systemctl status $SERVICE_NAME --no-pager | head -10
echo ""
echo -e "${CYAN}Access: ${BOLD}http://$(get_public_ip):$PORT${NC}"
else
echo -e "${RED}Service is not running${NC}"
fi
return 0
}
# ═══════════════════════════════════════════════════════════════════════════
# Main Menu
# ═══════════════════════════════════════════════════════════════════════════
show_menu() {
clear
echo -e "${WHITE}${BOLD}UPTIME KUMA MANAGEMENT${NC}\n"
echo -e " ${GREEN}[1]${NC} Install Uptime Kuma"
echo -e " ${YELLOW}[2]${NC} Update Uptime Kuma"
echo -e " ${CYAN}[3]${NC} View Status"
echo -e " ${RED}[0]${NC} Back to main menu"
echo ""
echo -n "Choice [0-3]: "
}
main() {
while true; do
show_menu
read -r choice
echo ""
case $choice in
1) install_uptime_kuma ;;
2) update_uptime_kuma ;;
3) show_status ;;
0) return 0 ;;
*) echo -e "${RED}Invalid option${NC}" ;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
main
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash
# LXS - Common library
# Sourced by lxs.sh and all sub-scripts. Provides colors, UI helpers, loggers,
# spinner, OS guards, and shared utilities (public IP, password generation,
# apt lock wait, root re-exec, apt non-interactive setup).
# Repo: https://git.hyko.cx/hykocx/lxs
# ═══════════════════════════════════════════════════════════════════════════
# Colors
# ═══════════════════════════════════════════════════════════════════════════
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
PURPLE='\033[1;94m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
GRAY='\033[0;37m'
NC='\033[0m'
BOLD='\033[1m'
# ═══════════════════════════════════════════════════════════════════════════
# UI helpers
# ═══════════════════════════════════════════════════════════════════════════
show_separator() {
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
info() { echo -e "${PURPLE}[*]${NC} $*"; }
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
err() { echo -e "${RED}[✗]${NC} $*" >&2; }
# ═══════════════════════════════════════════════════════════════════════════
# Spinner — runs a shell command, redirects output to a log file, shows
# a spinner with a label, prints ✓/✗ on completion. Returns the command's
# exit code.
#
# Usage:
# run_spinner "Installing foo..." "apt-get install -y foo"
#
# Log file: ${LXS_LOG_FILE:-/tmp/lxs.log}
# ═══════════════════════════════════════════════════════════════════════════
run_spinner() {
local message=$1
shift
local command="$*"
local spinstr='|/-\'
local log="${LXS_LOG_FILE:-/tmp/lxs.log}"
echo -e "${PURPLE}[*] ${message}${NC}"
eval "$command" > "$log" 2>&1 &
local pid=$!
while kill -0 "$pid" 2>/dev/null; do
local temp=${spinstr#?}
printf "\r${PURPLE}[%c]${NC} ${message}" "$spinstr"
spinstr=$temp${spinstr%"$temp"}
sleep 0.15
done
wait "$pid"
local exit_code=$?
if [ $exit_code -eq 0 ]; then
printf "\r${GREEN}[✓]${NC} ${message}\n"
else
printf "\r${RED}[✗]${NC} ${message}\n"
fi
return $exit_code
}
# ═══════════════════════════════════════════════════════════════════════════
# Guards
# ═══════════════════════════════════════════════════════════════════════════
require_debian_ubuntu() {
if ! grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then
err "This script supports Debian/Ubuntu only."
return 1
fi
}
# Re-exec the current script via sudo if not already root. Preserves env and
# arguments. Call near the top of a sub-script:
# require_root "$0" "$@"
require_root() {
[ "$EUID" -eq 0 ] && return 0
local self=$1
shift
exec sudo -E "$self" "$@"
}
# ═══════════════════════════════════════════════════════════════════════════
# Network / random / apt helpers
# ═══════════════════════════════════════════════════════════════════════════
get_public_ip() {
local ip=""
ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null) || \
ip=$(curl -s --max-time 5 icanhazip.com 2>/dev/null) || \
ip=$(curl -s --max-time 5 ipinfo.io/ip 2>/dev/null) || \
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
echo "$ip"
}
# Generate a random alphanumeric string. Default length: 32.
generate_password() {
local length=${1:-32}
LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | head -c "$length"
}
# Configure apt/debconf for fully non-interactive runs. Safe to call multiple
# times. No-op when apt is absent.
apt_noninteractive() {
command -v apt >/dev/null 2>&1 || return 0
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
export NEEDRESTART_SUSPEND=1
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 2>/dev/null || true
}
# Wait for other apt/dpkg processes to release their locks. Up to 120s.
wait_for_apt() {
command -v apt >/dev/null 2>&1 || return 0
local max_wait=120
local wait_count=0
local lock_detected=false
while fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || \
fuser /var/lib/dpkg/lock >/dev/null 2>&1 || \
fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
[ "$lock_detected" = false ] && lock_detected=true && \
echo -e "${YELLOW}[!] Waiting for other package manager to finish...${NC}"
wait_count=$((wait_count + 1))
[ $wait_count -ge $max_wait ] && \
echo -e "${RED}[✗] Timeout waiting for package manager${NC}" && return 1
printf "\r${GRAY}[i] Waiting... (%ds/%ds)${NC}" $wait_count $max_wait
sleep 1
done
[ "$lock_detected" = true ] && \
echo -e "\n${GREEN}[✓] Package manager is now available${NC}"
return 0
}
Executable
+472
View File
@@ -0,0 +1,472 @@
#!/bin/bash
# LXS - Linux multi-tool
# Description: Centralized server management and deployment toolkit
# Repo: https://git.hyko.cx/hykocx/lxs
# License: MIT
# ═══════════════════════════════════════════════════════════════════════════
# Configuration
# ═══════════════════════════════════════════════════════════════════════════
LXS_SCRIPT_DIR=""
if [ -n "${BASH_SOURCE[0]:-}" ]; then
_lxs_resolved=$(readlink -f "${BASH_SOURCE[0]}" 2>/dev/null) \
|| _lxs_resolved=$(realpath "${BASH_SOURCE[0]}" 2>/dev/null) \
|| _lxs_resolved="${BASH_SOURCE[0]}"
LXS_SCRIPT_DIR=$(dirname "$_lxs_resolved")
unset _lxs_resolved
fi
if [ -n "$LXS_SCRIPT_DIR" ] && [ -r "${LXS_SCRIPT_DIR}/VERSION" ]; then
LXS_VERSION=$(head -n1 "${LXS_SCRIPT_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]')
fi
LXS_VERSION="${LXS_VERSION:-dev}"
LXS_REPO_URL="https://git.hyko.cx/hykocx/lxs"
LXS_BRANCH="${LXS_BRANCH:-main}"
export LXS_RAW_BASE="${LXS_RAW_BASE:-${LXS_REPO_URL}/raw/branch/${LXS_BRANCH}}"
LXS_TARBALL_URL="${LXS_TARBALL_URL:-${LXS_REPO_URL}/archive/${LXS_BRANCH}.tar.gz}"
LXS_INSTALL_DIR="${LXS_INSTALL_DIR:-/usr/local/share/lxs}"
LXS_BIN_PATH="${LXS_BIN_PATH:-/usr/local/bin/lxs}"
LXS_CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/lxs"
LXS_VERSION_CACHE="${LXS_CACHE_DIR}/remote_version"
LXS_VERSION_TTL=86400
# ═══════════════════════════════════════════════════════════════════════════
# Load common library
# Prefers a sibling lib/common.sh (installed or repo checkout); falls back to
# fetching it from the remote when run via `curl | bash`.
# ═══════════════════════════════════════════════════════════════════════════
if [ -n "$LXS_SCRIPT_DIR" ] && [ -r "${LXS_SCRIPT_DIR}/lib/common.sh" ]; then
# shellcheck disable=SC1091
. "${LXS_SCRIPT_DIR}/lib/common.sh"
else
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || {
echo "Failed to fetch lib/common.sh" >&2
exit 1
}
eval "$_lib"
unset _lib
fi
# ═══════════════════════════════════════════════════════════════════════════
# Core helpers
# ═══════════════════════════════════════════════════════════════════════════
lxs_self_path() {
local src="${BASH_SOURCE[0]}"
[ -z "$src" ] && return 1
if command -v readlink >/dev/null 2>&1; then
readlink -f "$src" 2>/dev/null && return 0
fi
if command -v realpath >/dev/null 2>&1; then
realpath "$src" 2>/dev/null && return 0
fi
echo "$src"
}
lxs_is_installed() {
local self
self=$(lxs_self_path) || return 1
[ "$(dirname "$self")" = "$LXS_INSTALL_DIR" ]
}
lxs_need_root_for() {
local path=$1
local parent
parent=$(dirname "$path")
[ -w "$parent" ] && return 1
[ "$EUID" -eq 0 ] && return 1
return 0
}
check_remote_version() {
LXS_UPDATE_AVAILABLE=0
LXS_REMOTE_VERSION=""
mkdir -p "$LXS_CACHE_DIR" 2>/dev/null || return 0
local now mtime age=999999
now=$(date +%s 2>/dev/null) || return 0
if [ -f "$LXS_VERSION_CACHE" ]; then
mtime=$(stat -c %Y "$LXS_VERSION_CACHE" 2>/dev/null || stat -f %m "$LXS_VERSION_CACHE" 2>/dev/null || echo 0)
age=$((now - mtime))
fi
if [ "$age" -ge "$LXS_VERSION_TTL" ]; then
(
curl -fsSL --max-time 3 -H "Cache-Control: no-cache" \
"${LXS_RAW_BASE}/VERSION" -o "${LXS_VERSION_CACHE}.tmp" 2>/dev/null \
&& mv "${LXS_VERSION_CACHE}.tmp" "$LXS_VERSION_CACHE" \
|| rm -f "${LXS_VERSION_CACHE}.tmp"
) >/dev/null 2>&1 &
disown 2>/dev/null || true
fi
[ -f "$LXS_VERSION_CACHE" ] || return 0
local cached
cached=$(head -n1 "$LXS_VERSION_CACHE" 2>/dev/null | tr -d '[:space:]')
[ -z "$cached" ] && return 0
local highest
highest=$(printf '%s\n%s\n' "$LXS_VERSION" "$cached" | sort -V | tail -n1)
if [ "$highest" = "$cached" ] && [ "$cached" != "$LXS_VERSION" ]; then
LXS_UPDATE_AVAILABLE=1
LXS_REMOTE_VERSION="$cached"
fi
}
show_lxs_logo() {
echo -e "${WHITE}${BOLD}"
echo -e "╔═══════════════════════════════════════════════════════╗"
echo -e "║ ║"
echo -e "║ ██╗ ██╗ ██╗███████╗ ║"
echo -e "║ ██║ ╚██╗██╔╝██╔════╝ ║"
echo -e "║ ██║ ╚███╔╝ ███████╗ ║"
echo -e "║ ██║ ██╔██╗ ╚════██║ ║"
echo -e "║ ███████╗██╔╝ ██╗███████║ ║"
echo -e "║ ╚══════╝╚═╝ ╚═╝╚══════╝ v${LXS_VERSION}"
echo -e "║ ║"
echo -e "╚═══════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
show_header() {
clear
show_lxs_logo
if [ "${LXS_UPDATE_AVAILABLE:-0}" = "1" ]; then
echo -e "${YELLOW}[!] Nouvelle version disponible: ${LXS_REMOTE_VERSION} (actuelle: ${LXS_VERSION})${NC}"
echo -e "${GRAY} Lance: lxs update${NC}"
echo ""
fi
local hostname os_version kernel uptime_info ip_address cpu_model cpu_cores total_mem used_mem disk_usage
hostname=$(hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "${HOSTNAME:-unknown}")
os_version=$(lsb_release -ds 2>/dev/null || grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2)
kernel=$(uname -r)
uptime_info=$(uptime -p 2>/dev/null || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')
ip_address=$(get_public_ip)
cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)
cpu_cores=$(nproc)
total_mem=$(free -h | awk '/^Mem:/ {print $2}')
used_mem=$(free -h | awk '/^Mem:/ {print $3}')
disk_usage=$(df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}')
echo -e "${GRAY}Hostname:${NC} $hostname"
echo -e "${GRAY}IP Address:${NC} $ip_address"
echo -e "${GRAY}OS:${NC} $os_version"
echo -e "${GRAY}Kernel:${NC} $kernel"
echo -e "${GRAY}Uptime:${NC} $uptime_info"
echo -e "${GRAY}CPU:${NC} $cpu_model ($cpu_cores cores)"
echo -e "${GRAY}Memory:${NC} $used_mem / $total_mem"
echo -e "${GRAY}Disk:${NC} $disk_usage"
echo ""
}
check_os() {
if ! grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then
echo -e "${YELLOW}[!] LXS is tested on Debian/Ubuntu. Other distros may not work.${NC}"
read -r -p "Continue anyway? [y/N] " reply
case "$reply" in
[yY]|[yY][eE][sS]) ;;
*) exit 0 ;;
esac
fi
}
download_and_run() {
local script_path=$1
shift
local script_name local_path
script_name=$(basename "$script_path")
local_path="${LXS_INSTALL_DIR}/${script_path}"
if lxs_is_installed && [ -f "$local_path" ]; then
chmod +x "$local_path" 2>/dev/null || true
echo ""
show_separator
echo ""
"$local_path" "$@"
local exit_code=$?
echo ""
show_separator
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}[✓] Script completed successfully${NC}"
else
echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}"
fi
return $exit_code
fi
local temp_file
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
echo ""
echo -e "${PURPLE}[*] Downloading ${BOLD}${script_name}${NC}${PURPLE}...${NC}"
if curl -fsSL -H "Cache-Control: no-cache" \
-o "${temp_file}" \
"${LXS_RAW_BASE}/${script_path}"; then
echo -e "${GREEN}[✓] Downloaded${NC}"
chmod +x "${temp_file}"
echo ""
show_separator
echo ""
"${temp_file}" "$@"
local exit_code=$?
rm -f "${temp_file}"
echo ""
show_separator
if [ $exit_code -eq 0 ]; then
echo -e "${GREEN}[✓] Script completed successfully${NC}"
else
echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}"
fi
return $exit_code
else
echo -e "${RED}[✗] Failed to download ${script_path}${NC}"
echo -e "${RED} URL: ${LXS_RAW_BASE}/${script_path}${NC}"
rm -f "${temp_file}"
return 1
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# CLI commands
# ═══════════════════════════════════════════════════════════════════════════
cmd_version() {
echo "lxs ${LXS_VERSION}"
}
cmd_help() {
cat <<EOF
LXS - Linux multi-tool (v${LXS_VERSION})
Usage:
lxs Interactive menu
lxs setup Install lxs and all sub-scripts to ${LXS_INSTALL_DIR}
lxs update Update all installed files to latest
lxs install <app> Install an application
lxs tool <name> [args] Run a system tool
lxs info Show system info
lxs version Show version
lxs help Show this help
Applications (lxs install ...):
coolify Self-hosted PaaS
pterodactyl Game server management panel
uptime-kuma Monitoring tool
cloudpanel Web hosting control panel
proxmox Proxmox VE management tools
Tools (lxs tool ...):
system System monitoring and diagnostics
benchmark Server benchmark (CPU/RAM/disk/network)
harden Baseline hardening (UFW + fail2ban + SSH key-only + auto-updates)
Source: ${LXS_REPO_URL}
EOF
}
cmd_install() {
local app="${1:-}"
case "$app" in
coolify) download_and_run "apps/coolify.sh" ;;
pterodactyl) download_and_run "apps/pterodactyl.sh" ;;
uptime-kuma) download_and_run "apps/uptime-kuma.sh" ;;
cloudpanel) download_and_run "apps/cloudpanel.sh" ;;
proxmox) download_and_run "apps/proxmox.sh" ;;
"") echo -e "${RED}[✗] Missing app name. Try: lxs help${NC}"; return 1 ;;
*) echo -e "${RED}[✗] Unknown app: $app. Try: lxs help${NC}"; return 1 ;;
esac
}
cmd_tool() {
local tool="${1:-}"
shift || true
case "$tool" in
system) download_and_run "tools/system-tools.sh" "$@" ;;
benchmark) download_and_run "tools/server-benchmark.sh" "$@" ;;
harden) download_and_run "tools/harden.sh" "$@" ;;
"") echo -e "${RED}[✗] Missing tool name. Try: lxs help${NC}"; return 1 ;;
*) echo -e "${RED}[✗] Unknown tool: $tool. Try: lxs help${NC}"; return 1 ;;
esac
}
lxs_sync_install() {
local action="${1:-update}"
if lxs_need_root_for "$LXS_INSTALL_DIR" || lxs_need_root_for "$LXS_BIN_PATH"; then
echo -e "${YELLOW}[!] Need sudo to write ${LXS_INSTALL_DIR}; re-running with sudo...${NC}"
exec sudo -E LXS_INSTALL_DIR="$LXS_INSTALL_DIR" LXS_BIN_PATH="$LXS_BIN_PATH" \
LXS_BRANCH="$LXS_BRANCH" LXS_REPO_URL="$LXS_REPO_URL" \
bash "$(lxs_self_path)" "$action"
fi
local work
work=$(mktemp -d /tmp/lxs.install.XXXXXX) || {
echo -e "${RED}[✗] Failed to create temp dir${NC}"
return 1
}
trap 'rm -rf "$work"' RETURN
echo -e "${PURPLE}[*] Downloading ${LXS_TARBALL_URL}...${NC}"
if ! curl -fsSL -H "Cache-Control: no-cache" -o "${work}/lxs.tgz" "$LXS_TARBALL_URL"; then
echo -e "${RED}[✗] Download failed${NC}"
return 1
fi
mkdir -p "${work}/extracted"
if ! tar -xzf "${work}/lxs.tgz" --strip-components=1 -C "${work}/extracted"; then
echo -e "${RED}[✗] Extraction failed${NC}"
return 1
fi
if [ ! -f "${work}/extracted/lxs.sh" ] || [ ! -f "${work}/extracted/VERSION" ]; then
echo -e "${RED}[✗] Tarball is missing lxs.sh or VERSION${NC}"
return 1
fi
mkdir -p "$LXS_INSTALL_DIR"
if command -v rsync >/dev/null 2>&1; then
rsync -a --delete "${work}/extracted/" "${LXS_INSTALL_DIR}/" || {
echo -e "${RED}[✗] rsync failed${NC}"
return 1
}
else
rm -rf "${LXS_INSTALL_DIR}.old" 2>/dev/null
[ -d "$LXS_INSTALL_DIR" ] && mv "$LXS_INSTALL_DIR" "${LXS_INSTALL_DIR}.old"
mkdir -p "$LXS_INSTALL_DIR"
if ! cp -a "${work}/extracted/." "${LXS_INSTALL_DIR}/"; then
echo -e "${RED}[✗] Copy failed; restoring previous install${NC}"
rm -rf "$LXS_INSTALL_DIR"
[ -d "${LXS_INSTALL_DIR}.old" ] && mv "${LXS_INSTALL_DIR}.old" "$LXS_INSTALL_DIR"
return 1
fi
rm -rf "${LXS_INSTALL_DIR}.old"
fi
chmod +x "${LXS_INSTALL_DIR}/lxs.sh" 2>/dev/null || true
find "${LXS_INSTALL_DIR}/apps" "${LXS_INSTALL_DIR}/tools" -name '*.sh' -exec chmod +x {} + 2>/dev/null || true
if [ -e "$LXS_BIN_PATH" ] || [ -L "$LXS_BIN_PATH" ]; then
rm -f "$LXS_BIN_PATH"
fi
ln -sfn "${LXS_INSTALL_DIR}/lxs.sh" "$LXS_BIN_PATH"
rm -f "$LXS_VERSION_CACHE"
local new_version
new_version=$(head -n1 "${LXS_INSTALL_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]')
echo -e "${GREEN}[✓] lxs ${new_version} installed in ${LXS_INSTALL_DIR}${NC}"
echo -e "${GREEN}[✓] Symlink: ${LXS_BIN_PATH}${LXS_INSTALL_DIR}/lxs.sh${NC}"
}
cmd_update() {
lxs_sync_install update
}
cmd_install_self() {
lxs_sync_install setup
}
# ═══════════════════════════════════════════════════════════════════════════
# Interactive menus
# ═══════════════════════════════════════════════════════════════════════════
menu_install_apps() {
while true; do
show_header
echo -e "${WHITE}${BOLD}APPLICATIONS${NC}"
echo ""
echo -e " ${GREEN}[1]${NC} Coolify"
echo -e " ${GREEN}[2]${NC} Pterodactyl Panel"
echo -e " ${GREEN}[3]${NC} Uptime Kuma"
echo -e " ${GREEN}[4]${NC} CloudPanel"
echo -e " ${GREEN}[5]${NC} Proxmox VE Tools"
echo -e " ${RED}[0]${NC} Back"
echo ""
echo -e -n "${BOLD}Choice [0-5]: ${NC}"
read -r choice
case $choice in
1) download_and_run "apps/coolify.sh" ;;
2) download_and_run "apps/pterodactyl.sh" ;;
3) download_and_run "apps/uptime-kuma.sh" ;;
4) download_and_run "apps/cloudpanel.sh" ;;
5) download_and_run "apps/proxmox.sh" ;;
0) return ;;
*) echo -e "${RED}[✗] Invalid option. Please select 0-5.${NC}"; sleep 1 ;;
esac
if [ "$choice" != "0" ]; then
echo ""
read -r -p "Press Enter to continue..."
fi
done
}
main_menu() {
check_os
check_remote_version
while true; do
show_header
echo -e "${WHITE}${BOLD}MAIN MENU${NC}"
echo ""
echo -e " ${GREEN}[1]${NC} Applications"
echo -e " ${CYAN}[2]${NC} System Tools"
echo -e " ${PURPLE}[3]${NC} Server Benchmark"
echo -e " ${YELLOW}[4]${NC} Harden Server"
echo -e " ${GRAY}[u]${NC} Update lxs"
echo -e " ${RED}[0]${NC} Exit"
echo ""
echo -e -n "${BOLD}Choice: ${NC}"
read -r choice
case $choice in
1) menu_install_apps ;;
2) download_and_run "tools/system-tools.sh" ;;
3) download_and_run "tools/server-benchmark.sh" ;;
4) download_and_run "tools/harden.sh" ;;
u|U) cmd_update ;;
0)
clear
show_lxs_logo
echo -e "${GREEN}Bye.${NC}\n"
exit 0
;;
*) echo -e "${RED}[✗] Invalid option.${NC}"; sleep 1 ;;
esac
if [[ "$choice" != "0" && "$choice" != "1" ]]; then
echo ""
read -r -p "Press Enter to continue..."
fi
done
}
# ═══════════════════════════════════════════════════════════════════════════
# Entrypoint
# ═══════════════════════════════════════════════════════════════════════════
case "${1:-}" in
install) shift; cmd_install "$@" ;;
tool) shift; cmd_tool "$@" ;;
info) show_header ;;
update) cmd_update ;;
setup|install-self) cmd_install_self ;;
version|-v|--version) cmd_version ;;
help|-h|--help) cmd_help ;;
"") main_menu ;;
*) echo -e "${RED}[✗] Unknown command: $1${NC}"; cmd_help; exit 1 ;;
esac
+211
View File
@@ -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."
+763
View File
@@ -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
+287
View File
@@ -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