Compare commits
12 Commits
dc1475c64a
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| db3f5d6652 | |||
| 2a50724328 | |||
| 15c42e1f24 | |||
| 173b3bd581 | |||
| c7dcaed0bf | |||
| ade8e76a68 | |||
| e0bd8e0a97 | |||
| db6b349bc4 | |||
| cdcd89b5b2 | |||
| c3002ef274 | |||
| fc50a71763 | |||
| 8383612fe8 |
@@ -33,52 +33,6 @@ lxs version # Show version
|
|||||||
lxs help # Show help
|
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
|
|
||||||
│ ├── index.sh # Interactive menu listing the apps below
|
|
||||||
│ ├── coolify.sh
|
|
||||||
│ ├── pterodactyl.sh
|
|
||||||
│ ├── uptime-kuma.sh
|
|
||||||
│ ├── cloudpanel.sh
|
|
||||||
│ └── proxmox.sh
|
|
||||||
└── tools/ # System tools
|
|
||||||
├── index.sh # Interactive menu listing the tools below
|
|
||||||
├── system-infos.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 (`curl … | bash`) the file isn't on disk next to the script, so `lxs version` reports `dev`.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Debian or Ubuntu (other distros may work but are not tested)
|
- Debian or Ubuntu (other distros may work but are not tested)
|
||||||
|
|||||||
+2
-1
@@ -220,6 +220,7 @@ install_cloudpanel() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
require_disk_space 2048 || return 1
|
||||||
check_requirements || return 1
|
check_requirements || return 1
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -637,7 +638,7 @@ main() {
|
|||||||
2) update_cloudpanel ;;
|
2) update_cloudpanel ;;
|
||||||
3) show_status ;;
|
3) show_status ;;
|
||||||
4) configure_rclone_backblaze ;;
|
4) configure_rclone_backblaze ;;
|
||||||
0) return 0 ;;
|
0) return 75 ;;
|
||||||
*) echo -e "${RED}Invalid option${NC}" ;;
|
*) echo -e "${RED}Invalid option${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -67,6 +67,7 @@ install_coolify() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
require_disk_space 3072 || return 1
|
||||||
check_requirements || return 1
|
check_requirements || return 1
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -252,7 +253,7 @@ main() {
|
|||||||
2) update_coolify ;;
|
2) update_coolify ;;
|
||||||
3) show_status ;;
|
3) show_status ;;
|
||||||
4) show_post_installation_guide ;;
|
4) show_post_installation_guide ;;
|
||||||
0) return 0 ;;
|
0) return 75 ;;
|
||||||
*) echo -e "${RED}Invalid option${NC}" ;;
|
*) echo -e "${RED}Invalid option${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+24
-21
@@ -34,16 +34,16 @@ run_sibling() {
|
|||||||
|
|
||||||
local temp_file exit_code
|
local temp_file exit_code
|
||||||
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
||||||
echo -e "${PURPLE}[*] Downloading ${BOLD}${script_name}${NC}${PURPLE}...${NC}"
|
echo -e "${CYAN}[..] Fetching ${BOLD}${script_name}${NC}${CYAN}...${NC}"
|
||||||
if curl -fsSL -H "Cache-Control: no-cache" -o "${temp_file}" "${LXS_RAW_BASE}/${script_path}"; then
|
if curl -fsSL -H "Cache-Control: no-cache" -o "${temp_file}" "${LXS_RAW_BASE}/${script_path}"; then
|
||||||
echo -e "${GREEN}[✓] Downloaded${NC}"
|
echo -e "${GREEN}[OK] Payload acquired${NC}"
|
||||||
chmod +x "${temp_file}"
|
chmod +x "${temp_file}"
|
||||||
"${temp_file}" "$@"
|
"${temp_file}" "$@"
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
rm -f "${temp_file}"
|
rm -f "${temp_file}"
|
||||||
return $exit_code
|
return $exit_code
|
||||||
else
|
else
|
||||||
echo -e "${RED}[✗] Failed to download ${script_path}${NC}"
|
echo -e "${RED}[KO] Failed to download ${script_path}${NC}"
|
||||||
rm -f "${temp_file}"
|
rm -f "${temp_file}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -52,31 +52,34 @@ run_sibling() {
|
|||||||
menu_apps() {
|
menu_apps() {
|
||||||
while true; do
|
while true; do
|
||||||
clear
|
clear
|
||||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
show_box_top "APPLICATIONS" "APP_REPOSITORY"
|
||||||
echo -e "${WHITE}║ APPLICATIONS ║${NC}"
|
|
||||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}[1]${NC} Coolify"
|
show_menu_item "01" "Coolify"
|
||||||
echo -e " ${GREEN}[2]${NC} Pterodactyl Panel"
|
show_menu_item "02" "Pterodactyl Panel"
|
||||||
echo -e " ${GREEN}[3]${NC} Uptime Kuma"
|
show_menu_item "03" "Uptime Kuma"
|
||||||
echo -e " ${GREEN}[4]${NC} CloudPanel"
|
show_menu_item "04" "CloudPanel"
|
||||||
echo -e " ${GREEN}[5]${NC} Proxmox VE Tools"
|
show_menu_item "05" "Proxmox VE Tools"
|
||||||
echo -e " ${RED}[0]${NC} Back"
|
show_menu_item "00" "BACK" "" exit
|
||||||
echo ""
|
echo ""
|
||||||
echo -e -n "${BOLD}Choice [0-5]: ${NC}"
|
show_box_bottom
|
||||||
|
echo ""
|
||||||
|
show_prompt
|
||||||
read -r choice
|
read -r choice
|
||||||
|
|
||||||
|
child_rc=0
|
||||||
case $choice in
|
case $choice in
|
||||||
1) run_sibling "apps/coolify.sh" ;;
|
1|01) run_sibling "apps/coolify.sh"; child_rc=$? ;;
|
||||||
2) run_sibling "apps/pterodactyl.sh" ;;
|
2|02) run_sibling "apps/pterodactyl.sh"; child_rc=$? ;;
|
||||||
3) run_sibling "apps/uptime-kuma.sh" ;;
|
3|03) run_sibling "apps/uptime-kuma.sh"; child_rc=$? ;;
|
||||||
4) run_sibling "apps/cloudpanel.sh" ;;
|
4|04) run_sibling "apps/cloudpanel.sh"; child_rc=$? ;;
|
||||||
5) run_sibling "apps/proxmox.sh" ;;
|
5|05) run_sibling "apps/proxmox.sh"; child_rc=$? ;;
|
||||||
0) return ;;
|
0|00) return ;;
|
||||||
*) echo -e "${RED}[✗] Invalid option. Please select 0-5.${NC}"; sleep 1; continue ;;
|
*) echo -e "${RED}[KO] Invalid protocol. Select 0-5.${NC}"; sleep 1; continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "$choice" != "0" ]; then
|
# Exit code 75 from a child means it already paused on its own
|
||||||
|
# (its own "Back" or end-of-run prompt) — skip the redundant prompt.
|
||||||
|
if [ "$child_rc" -ne 75 ]; then
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p "Press Enter to continue..."
|
read -r -p "Press Enter to continue..."
|
||||||
fi
|
fi
|
||||||
|
|||||||
+3
-1
@@ -245,6 +245,8 @@ update_proxmox() {
|
|||||||
|
|
||||||
check_proxmox || return 1
|
check_proxmox || return 1
|
||||||
|
|
||||||
|
require_disk_space 1024 || return 1
|
||||||
|
|
||||||
# Configure non-interactive mode
|
# Configure non-interactive mode
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
export NEEDRESTART_MODE=a
|
export NEEDRESTART_MODE=a
|
||||||
@@ -371,7 +373,7 @@ main() {
|
|||||||
7) update_proxmox ;;
|
7) update_proxmox ;;
|
||||||
8) clear_cache ;;
|
8) clear_cache ;;
|
||||||
9) open_firewall_ports ;;
|
9) open_firewall_ports ;;
|
||||||
0) return 0 ;;
|
0) return 75 ;;
|
||||||
*) echo -e "${RED}Invalid option${NC}" ;;
|
*) echo -e "${RED}Invalid option${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -212,6 +212,8 @@ install_pterodactyl() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
require_disk_space 3072 || return 1
|
||||||
|
|
||||||
echo "[1/10] Installing dependencies..."
|
echo "[1/10] Installing dependencies..."
|
||||||
install_dependencies && install_composer
|
install_dependencies && install_composer
|
||||||
echo ""
|
echo ""
|
||||||
@@ -323,7 +325,7 @@ main() {
|
|||||||
case $choice in
|
case $choice in
|
||||||
1) install_pterodactyl ;;
|
1) install_pterodactyl ;;
|
||||||
2) show_status ;;
|
2) show_status ;;
|
||||||
0) return 0 ;;
|
0) return 75 ;;
|
||||||
*) echo -e "${RED}Invalid option${NC}" ;;
|
*) echo -e "${RED}Invalid option${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -94,6 +94,8 @@ install_uptime_kuma() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
require_disk_space 1024 || return 1
|
||||||
|
|
||||||
echo "[1/4] Installing dependencies..."
|
echo "[1/4] Installing dependencies..."
|
||||||
install_dependencies
|
install_dependencies
|
||||||
echo ""
|
echo ""
|
||||||
@@ -242,7 +244,7 @@ main() {
|
|||||||
1) install_uptime_kuma ;;
|
1) install_uptime_kuma ;;
|
||||||
2) update_uptime_kuma ;;
|
2) update_uptime_kuma ;;
|
||||||
3) show_status ;;
|
3) show_status ;;
|
||||||
0) return 0 ;;
|
0) return 75 ;;
|
||||||
*) echo -e "${RED}Invalid option${NC}" ;;
|
*) echo -e "${RED}Invalid option${NC}" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
+148
-24
@@ -7,31 +7,119 @@
|
|||||||
# Repo: https://git.hyko.cx/hykocx/lxs
|
# Repo: https://git.hyko.cx/hykocx/lxs
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# Colors
|
# Colors — minimal palette: red, cyan, white (+ gray as a white shade)
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
RED='\033[0;31m'
|
RED='\033[38;2;255;64;64m' # errors, destructive actions
|
||||||
GREEN='\033[0;32m'
|
CYAN='\033[38;2;0;229;255m' # accents, titles, OK, info
|
||||||
YELLOW='\033[1;33m'
|
WHITE='\033[38;2;240;240;240m' # primary text
|
||||||
PURPLE='\033[1;94m'
|
GRAY='\033[38;2;140;140;140m' # secondary text, comments, separators
|
||||||
CYAN='\033[0;36m'
|
|
||||||
WHITE='\033[1;37m'
|
|
||||||
GRAY='\033[0;37m'
|
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
BOLD='\033[1m'
|
BOLD='\033[1m'
|
||||||
|
DIM='\033[2m'
|
||||||
|
|
||||||
|
# Legacy aliases — older sub-scripts still reference these. Map onto CYAN
|
||||||
|
# so the palette stays at red/cyan/white without breaking them.
|
||||||
|
YELLOW="$CYAN"
|
||||||
|
GREEN="$CYAN"
|
||||||
|
MAGENTA="$CYAN"
|
||||||
|
PURPLE="$CYAN"
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# UI helpers
|
# UI helpers — title + horizontal rule, no closed box.
|
||||||
|
#
|
||||||
|
# Layout:
|
||||||
|
# show_box_top "TITLE" ["RIGHT_TAG"] → TITLE [ RIGHT ]
|
||||||
|
# ─────────────────────────────────
|
||||||
|
# show_box_mid "SECTION" → ─ SECTION ─────────────────────
|
||||||
|
# show_box_bottom → ───────────────────────────────
|
||||||
|
# show_separator → ─── (light gray divider)
|
||||||
|
# show_menu_item "01" "LABEL" "desc" → [01] LABEL // desc
|
||||||
|
# show_prompt → >
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
show_separator() {
|
_lxs_term_cols() {
|
||||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
local cols
|
||||||
|
cols=$(tput cols 2>/dev/null || echo 80)
|
||||||
|
[ "$cols" -gt 100 ] && cols=100
|
||||||
|
[ "$cols" -lt 60 ] && cols=60
|
||||||
|
echo "$cols"
|
||||||
}
|
}
|
||||||
|
|
||||||
info() { echo -e "${PURPLE}[*]${NC} $*"; }
|
show_box_top() {
|
||||||
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
|
local title="$1" right="${2:-}"
|
||||||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
local cols pad_len pad
|
||||||
err() { echo -e "${RED}[✗]${NC} $*" >&2; }
|
cols=$(_lxs_term_cols)
|
||||||
|
if [ -n "$right" ]; then
|
||||||
|
pad_len=$(( cols - 2 - ${#title} - ${#right} - 4 ))
|
||||||
|
[ "$pad_len" -lt 1 ] && pad_len=1
|
||||||
|
pad=$(printf ' %.0s' $(seq 1 "$pad_len"))
|
||||||
|
printf " ${CYAN}${BOLD}%s${NC}%s${GRAY}[ ${CYAN}%s${GRAY} ]${NC}\n" \
|
||||||
|
"$title" "$pad" "$right"
|
||||||
|
else
|
||||||
|
printf " ${CYAN}${BOLD}%s${NC}\n" "$title"
|
||||||
|
fi
|
||||||
|
_lxs_hr "$cols"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_box_mid() {
|
||||||
|
local title="$1"
|
||||||
|
local cols fill_len fill
|
||||||
|
cols=$(_lxs_term_cols)
|
||||||
|
fill_len=$(( cols - ${#title} - 4 ))
|
||||||
|
[ "$fill_len" -lt 2 ] && fill_len=2
|
||||||
|
fill=$(printf '─%.0s' $(seq 1 "$fill_len"))
|
||||||
|
printf "${GRAY}─ ${CYAN}${BOLD}%s${NC} ${GRAY}%s${NC}\n" "$title" "$fill"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_box_bottom() {
|
||||||
|
_lxs_hr "$(_lxs_term_cols)"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_separator() {
|
||||||
|
_lxs_hr "$(_lxs_term_cols)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_lxs_hr() {
|
||||||
|
local cols=$1 fill
|
||||||
|
fill=$(printf '─%.0s' $(seq 1 "$cols"))
|
||||||
|
printf "${GRAY}%s${NC}\n" "$fill"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_title() {
|
||||||
|
local title="$1"
|
||||||
|
local subtitle="${2:-}"
|
||||||
|
echo ""
|
||||||
|
if [ -n "$subtitle" ]; then
|
||||||
|
show_box_top "$title" "$subtitle"
|
||||||
|
else
|
||||||
|
show_box_top "$title"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Render a menu line. Pass "exit" as 4th arg to color the key red.
|
||||||
|
# show_menu_item "01" "APPLICATIONS" "deploy stacks"
|
||||||
|
# show_menu_item "00" "EXIT" "quit shell" exit
|
||||||
|
show_menu_item() {
|
||||||
|
local key="$1" label="$2" desc="${3:-}" kind="${4:-}"
|
||||||
|
local key_color="${CYAN}"
|
||||||
|
[ "$kind" = "exit" ] && key_color="${RED}"
|
||||||
|
if [ -n "$desc" ]; then
|
||||||
|
printf " ${key_color}[%s]${NC} ${WHITE}${BOLD}%-18s${NC} ${GRAY}// %s${NC}\n" \
|
||||||
|
"$key" "$label" "$desc"
|
||||||
|
else
|
||||||
|
printf " ${key_color}[%s]${NC} ${WHITE}${BOLD}%s${NC}\n" "$key" "$label"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_prompt() {
|
||||||
|
echo -e -n " ${CYAN}>${NC} "
|
||||||
|
}
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}[..]${NC} $*"; }
|
||||||
|
ok() { echo -e "${CYAN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${CYAN}[!!]${NC} $*"; }
|
||||||
|
err() { echo -e "${RED}[KO]${NC} $*" >&2; }
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# Spinner — runs a shell command, redirects output to a log file, shows
|
# Spinner — runs a shell command, redirects output to a log file, shows
|
||||||
@@ -51,13 +139,13 @@ run_spinner() {
|
|||||||
local spinstr='|/-\'
|
local spinstr='|/-\'
|
||||||
local log="${LXS_LOG_FILE:-/tmp/lxs.log}"
|
local log="${LXS_LOG_FILE:-/tmp/lxs.log}"
|
||||||
|
|
||||||
echo -e "${PURPLE}[*] ${message}${NC}"
|
echo -e "${CYAN}[..] ${message}${NC}"
|
||||||
eval "$command" > "$log" 2>&1 &
|
eval "$command" > "$log" 2>&1 &
|
||||||
local pid=$!
|
local pid=$!
|
||||||
|
|
||||||
while kill -0 "$pid" 2>/dev/null; do
|
while kill -0 "$pid" 2>/dev/null; do
|
||||||
local temp=${spinstr#?}
|
local temp=${spinstr#?}
|
||||||
printf "\r${PURPLE}[%c]${NC} ${message}" "$spinstr"
|
printf "\r${CYAN}[%c.]${NC} ${message}" "$spinstr"
|
||||||
spinstr=$temp${spinstr%"$temp"}
|
spinstr=$temp${spinstr%"$temp"}
|
||||||
sleep 0.15
|
sleep 0.15
|
||||||
done
|
done
|
||||||
@@ -65,9 +153,9 @@ run_spinner() {
|
|||||||
wait "$pid"
|
wait "$pid"
|
||||||
local exit_code=$?
|
local exit_code=$?
|
||||||
if [ $exit_code -eq 0 ]; then
|
if [ $exit_code -eq 0 ]; then
|
||||||
printf "\r${GREEN}[✓]${NC} ${message}\n"
|
printf "\r${CYAN}[OK]${NC} ${message}\n"
|
||||||
else
|
else
|
||||||
printf "\r${RED}[✗]${NC} ${message}\n"
|
printf "\r${RED}[KO]${NC} ${message}\n"
|
||||||
fi
|
fi
|
||||||
return $exit_code
|
return $exit_code
|
||||||
}
|
}
|
||||||
@@ -83,6 +171,42 @@ require_debian_ubuntu() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Verify enough free disk space before doing apt installs or writing config.
|
||||||
|
# Fails early with a clear message so we don't get half-installed packages or
|
||||||
|
# files truncated mid-write when the disk is full.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# require_disk_space # 500MB on /var (apt cache + lists)
|
||||||
|
# require_disk_space 1024 # 1024MB on /var
|
||||||
|
# require_disk_space 1024 /var /opt # 1024MB on each listed path
|
||||||
|
#
|
||||||
|
# Duplicate filesystems (e.g. /var on the same fs as /) are checked once.
|
||||||
|
require_disk_space() {
|
||||||
|
local min_mb=${1:-500}
|
||||||
|
shift 2>/dev/null || true
|
||||||
|
local paths=("$@")
|
||||||
|
[ ${#paths[@]} -eq 0 ] && paths=(/var)
|
||||||
|
|
||||||
|
local path avail fs seen=" " rc=0
|
||||||
|
for path in "${paths[@]}"; do
|
||||||
|
fs=$(df -P "$path" 2>/dev/null | awk 'NR==2 {print $1}')
|
||||||
|
if [ -z "$fs" ]; then
|
||||||
|
err "Cannot read disk usage for ${path}"
|
||||||
|
rc=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
[[ "$seen" == *" $fs "* ]] && continue
|
||||||
|
seen="$seen$fs "
|
||||||
|
|
||||||
|
avail=$(df -Pm "$path" 2>/dev/null | awk 'NR==2 {print $4}')
|
||||||
|
if [ "${avail:-0}" -lt "$min_mb" ]; then
|
||||||
|
err "Not enough disk space on ${path} (${fs}): ${avail}MB free, ${min_mb}MB required."
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return $rc
|
||||||
|
}
|
||||||
|
|
||||||
# Re-exec the current script via sudo if not already root. Preserves env and
|
# Re-exec the current script via sudo if not already root. Preserves env and
|
||||||
# arguments. Call near the top of a sub-script:
|
# arguments. Call near the top of a sub-script:
|
||||||
# require_root "$0" "$@"
|
# require_root "$0" "$@"
|
||||||
@@ -138,7 +262,7 @@ ufw_allow() {
|
|||||||
else
|
else
|
||||||
ufw allow "$rule" >/dev/null
|
ufw allow "$rule" >/dev/null
|
||||||
fi
|
fi
|
||||||
ok "UFW: allowed ${rule}${comment:+ (${comment})}"
|
ok "UFW :: allowed ${rule}${comment:+ (${comment})}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Wait for other apt/dpkg processes to release their locks. Up to 120s.
|
# Wait for other apt/dpkg processes to release their locks. Up to 120s.
|
||||||
@@ -154,17 +278,17 @@ wait_for_apt() {
|
|||||||
fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
|
fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do
|
||||||
|
|
||||||
[ "$lock_detected" = false ] && lock_detected=true && \
|
[ "$lock_detected" = false ] && lock_detected=true && \
|
||||||
echo -e "${YELLOW}[!] Waiting for other package manager to finish...${NC}"
|
echo -e "${YELLOW}[!!] Waiting for other package manager to finish...${NC}"
|
||||||
|
|
||||||
wait_count=$((wait_count + 1))
|
wait_count=$((wait_count + 1))
|
||||||
[ $wait_count -ge $max_wait ] && \
|
[ $wait_count -ge $max_wait ] && \
|
||||||
echo -e "${RED}[✗] Timeout waiting for package manager${NC}" && return 1
|
echo -e "${RED}[KO] Timeout waiting for package manager${NC}" && return 1
|
||||||
|
|
||||||
printf "\r${GRAY}[i] Waiting... (%ds/%ds)${NC}" $wait_count $max_wait
|
printf "\r${GRAY}[..] Waiting... (%ds/%ds)${NC}" $wait_count $max_wait
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
[ "$lock_detected" = true ] && \
|
[ "$lock_detected" = true ] && \
|
||||||
echo -e "\n${GREEN}[✓] Package manager is now available${NC}"
|
echo -e "\n${GREEN}[OK] Package manager is now available${NC}"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,18 +120,7 @@ check_remote_version() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show_lxs_logo() {
|
show_lxs_logo() {
|
||||||
echo -e "${WHITE}${BOLD}"
|
show_box_top "LXS // v${LXS_VERSION}" "ONLINE"
|
||||||
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() {
|
show_header() {
|
||||||
@@ -139,37 +128,32 @@ show_header() {
|
|||||||
show_lxs_logo
|
show_lxs_logo
|
||||||
|
|
||||||
if [ "${LXS_UPDATE_AVAILABLE:-0}" = "1" ]; then
|
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 ""
|
echo ""
|
||||||
|
echo -e " ${CYAN}[!!] UPDATE_AVAILABLE${NC} ${WHITE}v${LXS_REMOTE_VERSION}${NC} ${GRAY}// run \`lxs update\`${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local hostname os_version kernel uptime_info ip_address cpu_model cpu_cores total_mem used_mem disk_usage
|
local hostname os_version kernel uptime_info ip_address cpu_cores total_mem used_mem disk_usage
|
||||||
hostname=$(hostname 2>/dev/null || cat /etc/hostname 2>/dev/null || echo "${HOSTNAME:-unknown}")
|
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)
|
os_version=$(lsb_release -ds 2>/dev/null || grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2)
|
||||||
kernel=$(uname -r)
|
kernel=$(uname -r)
|
||||||
uptime_info=$(uptime -p 2>/dev/null || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')
|
uptime_info=$(uptime -p 2>/dev/null | sed 's/^up //' || uptime | awk -F'up ' '{print $2}' | awk -F',' '{print $1}')
|
||||||
ip_address=$(get_public_ip)
|
ip_address=$(get_public_ip)
|
||||||
cpu_model=$(grep "model name" /proc/cpuinfo | head -1 | cut -d':' -f2 | xargs)
|
|
||||||
cpu_cores=$(nproc)
|
cpu_cores=$(nproc)
|
||||||
total_mem=$(free -h | awk '/^Mem:/ {print $2}')
|
total_mem=$(free -h | awk '/^Mem:/ {print $2}')
|
||||||
used_mem=$(free -h | awk '/^Mem:/ {print $3}')
|
used_mem=$(free -h | awk '/^Mem:/ {print $3}')
|
||||||
disk_usage=$(df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}')
|
disk_usage=$(df -h / | awk 'NR==2 {print $3 "/" $2 " (" $5 ")"}')
|
||||||
|
|
||||||
echo -e "${GRAY}Hostname:${NC} $hostname"
|
echo ""
|
||||||
echo -e "${GRAY}IP Address:${NC} $ip_address"
|
printf " ${CYAN}NODE${NC} ${WHITE}%-26s${NC} ${CYAN}KERN${NC} ${WHITE}%s${NC}\n" "$hostname" "$kernel"
|
||||||
echo -e "${GRAY}OS:${NC} $os_version"
|
printf " ${CYAN}ADDR${NC} ${WHITE}%-26s${NC} ${CYAN}UP${NC} ${WHITE}%s${NC}\n" "$ip_address" "$uptime_info"
|
||||||
echo -e "${GRAY}Kernel:${NC} $kernel"
|
printf " ${CYAN}OS${NC} ${WHITE}%-26s${NC} ${CYAN}CPU${NC} ${WHITE}%s cores${NC}\n" "$os_version" "$cpu_cores"
|
||||||
echo -e "${GRAY}Uptime:${NC} $uptime_info"
|
printf " ${CYAN}DISK${NC} ${WHITE}%-26s${NC} ${CYAN}MEM${NC} ${WHITE}%s / %s${NC}\n" "$disk_usage" "$used_mem" "$total_mem"
|
||||||
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 ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
check_os() {
|
check_os() {
|
||||||
if ! grep -qiE 'debian|ubuntu' /etc/os-release 2>/dev/null; then
|
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}"
|
echo -e "${YELLOW}[!!] LXS is tested on Debian/Ubuntu. Other distros may not work.${NC}"
|
||||||
read -r -p "Continue anyway? [y/N] " reply
|
read -r -p "Continue anyway? [y/N] " reply
|
||||||
case "$reply" in
|
case "$reply" in
|
||||||
[yY]|[yY][eE][sS]) ;;
|
[yY]|[yY][eE][sS]) ;;
|
||||||
@@ -195,9 +179,9 @@ download_and_run() {
|
|||||||
echo ""
|
echo ""
|
||||||
show_separator
|
show_separator
|
||||||
if [ $exit_code -eq 0 ]; then
|
if [ $exit_code -eq 0 ]; then
|
||||||
echo -e "${GREEN}[✓] Script completed successfully${NC}"
|
echo -e "${GREEN}[OK] Script completed successfully${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}"
|
echo -e "${YELLOW}[!!] Script exited with code: $exit_code${NC}"
|
||||||
fi
|
fi
|
||||||
return $exit_code
|
return $exit_code
|
||||||
fi
|
fi
|
||||||
@@ -206,12 +190,12 @@ download_and_run() {
|
|||||||
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${PURPLE}[*] Downloading ${BOLD}${script_name}${NC}${PURPLE}...${NC}"
|
echo -e "${CYAN}[..] Fetching ${BOLD}${script_name}${NC}${CYAN}...${NC}"
|
||||||
|
|
||||||
if curl -fsSL -H "Cache-Control: no-cache" \
|
if curl -fsSL -H "Cache-Control: no-cache" \
|
||||||
-o "${temp_file}" \
|
-o "${temp_file}" \
|
||||||
"${LXS_RAW_BASE}/${script_path}"; then
|
"${LXS_RAW_BASE}/${script_path}"; then
|
||||||
echo -e "${GREEN}[✓] Downloaded${NC}"
|
echo -e "${GREEN}[OK] Payload acquired${NC}"
|
||||||
chmod +x "${temp_file}"
|
chmod +x "${temp_file}"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -226,14 +210,14 @@ download_and_run() {
|
|||||||
echo ""
|
echo ""
|
||||||
show_separator
|
show_separator
|
||||||
if [ $exit_code -eq 0 ]; then
|
if [ $exit_code -eq 0 ]; then
|
||||||
echo -e "${GREEN}[✓] Script completed successfully${NC}"
|
echo -e "${GREEN}[OK] Script completed successfully${NC}"
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}[!] Script exited with code: $exit_code${NC}"
|
echo -e "${YELLOW}[!!] Script exited with code: $exit_code${NC}"
|
||||||
fi
|
fi
|
||||||
return $exit_code
|
return $exit_code
|
||||||
else
|
else
|
||||||
echo -e "${RED}[✗] Failed to download ${script_path}${NC}"
|
echo -e "${RED}[KO] Failed to download ${script_path}${NC}"
|
||||||
echo -e "${RED} URL: ${LXS_RAW_BASE}/${script_path}${NC}"
|
echo -e "${RED} URL: ${LXS_RAW_BASE}/${script_path}${NC}"
|
||||||
rm -f "${temp_file}"
|
rm -f "${temp_file}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -275,8 +259,8 @@ cmd_install() {
|
|||||||
uptime-kuma) download_and_run "apps/uptime-kuma.sh" ;;
|
uptime-kuma) download_and_run "apps/uptime-kuma.sh" ;;
|
||||||
cloudpanel) download_and_run "apps/cloudpanel.sh" ;;
|
cloudpanel) download_and_run "apps/cloudpanel.sh" ;;
|
||||||
proxmox) download_and_run "apps/proxmox.sh" ;;
|
proxmox) download_and_run "apps/proxmox.sh" ;;
|
||||||
"") echo -e "${RED}[✗] Missing app name. Try: lxs help${NC}"; return 1 ;;
|
"") echo -e "${RED}[KO] Missing app name. Try: lxs help${NC}"; return 1 ;;
|
||||||
*) echo -e "${RED}[✗] Unknown app: $app. Try: lxs help${NC}"; return 1 ;;
|
*) echo -e "${RED}[KO] Unknown app: $app. Try: lxs help${NC}"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,8 +271,12 @@ cmd_tool() {
|
|||||||
system) download_and_run "tools/system-infos.sh" "$@" ;;
|
system) download_and_run "tools/system-infos.sh" "$@" ;;
|
||||||
benchmark) download_and_run "tools/server-benchmark.sh" "$@" ;;
|
benchmark) download_and_run "tools/server-benchmark.sh" "$@" ;;
|
||||||
harden) download_and_run "tools/harden.sh" "$@" ;;
|
harden) download_and_run "tools/harden.sh" "$@" ;;
|
||||||
"") echo -e "${RED}[✗] Missing tool name. Try: lxs help${NC}"; return 1 ;;
|
root-password) download_and_run "tools/root-password.sh" "$@" ;;
|
||||||
*) echo -e "${RED}[✗] Unknown tool: $tool. Try: lxs help${NC}"; return 1 ;;
|
update) download_and_run "tools/update-server.sh" "$@" ;;
|
||||||
|
root-ssh-login) download_and_run "tools/root-ssh-login.sh" "$@" ;;
|
||||||
|
welcome|motd) download_and_run "tools/welcome-message.sh" "$@" ;;
|
||||||
|
"") echo -e "${RED}[KO] Missing tool name. Try: lxs help${NC}"; return 1 ;;
|
||||||
|
*) echo -e "${RED}[KO] Unknown tool: $tool. Try: lxs help${NC}"; return 1 ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +284,7 @@ lxs_sync_install() {
|
|||||||
local action="${1:-update}"
|
local action="${1:-update}"
|
||||||
|
|
||||||
if lxs_need_root_for "$LXS_INSTALL_DIR" || lxs_need_root_for "$LXS_BIN_PATH"; then
|
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}"
|
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" \
|
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" \
|
LXS_BRANCH="$LXS_BRANCH" LXS_REPO_URL="$LXS_REPO_URL" \
|
||||||
bash "$(lxs_self_path)" "$action"
|
bash "$(lxs_self_path)" "$action"
|
||||||
@@ -304,25 +292,25 @@ lxs_sync_install() {
|
|||||||
|
|
||||||
local work
|
local work
|
||||||
work=$(mktemp -d /tmp/lxs.install.XXXXXX) || {
|
work=$(mktemp -d /tmp/lxs.install.XXXXXX) || {
|
||||||
echo -e "${RED}[✗] Failed to create temp dir${NC}"
|
echo -e "${RED}[KO] Failed to create temp dir${NC}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
trap 'rm -rf "$work"' RETURN
|
trap 'rm -rf "$work"' RETURN
|
||||||
|
|
||||||
echo -e "${PURPLE}[*] Downloading ${LXS_TARBALL_URL}...${NC}"
|
echo -e "${CYAN}[..] Fetching ${LXS_TARBALL_URL}...${NC}"
|
||||||
if ! curl -fsSL -H "Cache-Control: no-cache" -o "${work}/lxs.tgz" "$LXS_TARBALL_URL"; then
|
if ! curl -fsSL -H "Cache-Control: no-cache" -o "${work}/lxs.tgz" "$LXS_TARBALL_URL"; then
|
||||||
echo -e "${RED}[✗] Download failed${NC}"
|
echo -e "${RED}[KO] Download failed${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "${work}/extracted"
|
mkdir -p "${work}/extracted"
|
||||||
if ! tar -xzf "${work}/lxs.tgz" --strip-components=1 -C "${work}/extracted"; then
|
if ! tar -xzf "${work}/lxs.tgz" --strip-components=1 -C "${work}/extracted"; then
|
||||||
echo -e "${RED}[✗] Extraction failed${NC}"
|
echo -e "${RED}[KO] Extraction failed${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "${work}/extracted/lxs.sh" ] || [ ! -f "${work}/extracted/VERSION" ]; then
|
if [ ! -f "${work}/extracted/lxs.sh" ] || [ ! -f "${work}/extracted/VERSION" ]; then
|
||||||
echo -e "${RED}[✗] Tarball is missing lxs.sh or VERSION${NC}"
|
echo -e "${RED}[KO] Tarball is missing lxs.sh or VERSION${NC}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -330,7 +318,7 @@ lxs_sync_install() {
|
|||||||
|
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
rsync -a --delete "${work}/extracted/" "${LXS_INSTALL_DIR}/" || {
|
rsync -a --delete "${work}/extracted/" "${LXS_INSTALL_DIR}/" || {
|
||||||
echo -e "${RED}[✗] rsync failed${NC}"
|
echo -e "${RED}[KO] rsync failed${NC}"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -338,7 +326,7 @@ lxs_sync_install() {
|
|||||||
[ -d "$LXS_INSTALL_DIR" ] && mv "$LXS_INSTALL_DIR" "${LXS_INSTALL_DIR}.old"
|
[ -d "$LXS_INSTALL_DIR" ] && mv "$LXS_INSTALL_DIR" "${LXS_INSTALL_DIR}.old"
|
||||||
mkdir -p "$LXS_INSTALL_DIR"
|
mkdir -p "$LXS_INSTALL_DIR"
|
||||||
if ! cp -a "${work}/extracted/." "${LXS_INSTALL_DIR}/"; then
|
if ! cp -a "${work}/extracted/." "${LXS_INSTALL_DIR}/"; then
|
||||||
echo -e "${RED}[✗] Copy failed; restoring previous install${NC}"
|
echo -e "${RED}[KO] Copy failed; restoring previous install${NC}"
|
||||||
rm -rf "$LXS_INSTALL_DIR"
|
rm -rf "$LXS_INSTALL_DIR"
|
||||||
[ -d "${LXS_INSTALL_DIR}.old" ] && mv "${LXS_INSTALL_DIR}.old" "$LXS_INSTALL_DIR"
|
[ -d "${LXS_INSTALL_DIR}.old" ] && mv "${LXS_INSTALL_DIR}.old" "$LXS_INSTALL_DIR"
|
||||||
return 1
|
return 1
|
||||||
@@ -358,8 +346,8 @@ lxs_sync_install() {
|
|||||||
|
|
||||||
local new_version
|
local new_version
|
||||||
new_version=$(head -n1 "${LXS_INSTALL_DIR}/VERSION" 2>/dev/null | tr -d '[:space:]')
|
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}[OK] lxs ${new_version} installed in ${LXS_INSTALL_DIR}${NC}"
|
||||||
echo -e "${GREEN}[✓] Symlink: ${LXS_BIN_PATH} → ${LXS_INSTALL_DIR}/lxs.sh${NC}"
|
echo -e "${GREEN}[OK] Symlink: ${LXS_BIN_PATH} → ${LXS_INSTALL_DIR}/lxs.sh${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_update() {
|
cmd_update() {
|
||||||
@@ -379,30 +367,38 @@ main_menu() {
|
|||||||
check_remote_version
|
check_remote_version
|
||||||
while true; do
|
while true; do
|
||||||
show_header
|
show_header
|
||||||
echo -e "${WHITE}${BOLD}MAIN MENU${NC}"
|
show_box_mid "SELECT"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${GREEN}[1]${NC} Applications"
|
show_menu_item "01" "APPLICATIONS" "deploy stacks"
|
||||||
echo -e " ${CYAN}[2]${NC} Tools"
|
show_menu_item "02" "TOOLS" "sysadmin daemons"
|
||||||
echo -e " ${GRAY}[u]${NC} Update lxs"
|
show_menu_item "UU" "FETCH_UPDATE" "sync remote"
|
||||||
echo -e " ${RED}[0]${NC} Exit"
|
show_menu_item "00" "JACK_OUT" "exit shell" exit
|
||||||
echo ""
|
echo ""
|
||||||
echo -e -n "${BOLD}Choice: ${NC}"
|
show_box_bottom
|
||||||
|
echo ""
|
||||||
|
show_prompt
|
||||||
read -r choice
|
read -r choice
|
||||||
|
|
||||||
case $choice in
|
case $choice in
|
||||||
1) download_and_run "apps/index.sh" ;;
|
1) download_and_run "apps/index.sh" ;;
|
||||||
2) download_and_run "tools/index.sh" ;;
|
2) download_and_run "tools/index.sh" ;;
|
||||||
u|U) cmd_update ;;
|
u|U|uu|UU)
|
||||||
0)
|
if cmd_update && [ -x "${LXS_INSTALL_DIR}/lxs.sh" ]; then
|
||||||
|
echo ""
|
||||||
|
read -r -p "Press Enter to reload with the new version..."
|
||||||
|
exec bash "${LXS_INSTALL_DIR}/lxs.sh"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
0|00)
|
||||||
clear
|
clear
|
||||||
show_lxs_logo
|
show_lxs_logo
|
||||||
echo -e "${GREEN}Bye.${NC}\n"
|
echo -e "${CYAN}JACK_OUT${NC} ${GRAY}// session terminated${NC}\n"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
*) echo -e "${RED}[✗] Invalid option.${NC}"; sleep 1 ;;
|
*) echo -e "${RED}[KO] Invalid protocol.${NC}"; sleep 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [[ "$choice" != "0" && "$choice" != "1" && "$choice" != "2" ]]; then
|
if [[ "$choice" != "0" && "$choice" != "00" && "$choice" != "1" && "$choice" != "2" ]]; then
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p "Press Enter to continue..."
|
read -r -p "Press Enter to continue..."
|
||||||
fi
|
fi
|
||||||
@@ -422,5 +418,5 @@ case "${1:-}" in
|
|||||||
version|-v|--version) cmd_version ;;
|
version|-v|--version) cmd_version ;;
|
||||||
help|-h|--help) cmd_help ;;
|
help|-h|--help) cmd_help ;;
|
||||||
"") main_menu ;;
|
"") main_menu ;;
|
||||||
*) echo -e "${RED}[✗] Unknown command: $1${NC}"; cmd_help; exit 1 ;;
|
*) echo -e "${RED}[KO] Unknown command: $1${NC}"; cmd_help; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
+17
-62
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# LXS - Server Hardening
|
# LXS - Server Hardening
|
||||||
# Description: Apply baseline security hardening (UFW + fail2ban + SSH key-only + unattended-upgrades)
|
# Description: Apply baseline security hardening (UFW + fail2ban + unattended-upgrades)
|
||||||
# Author: LXS
|
# Author: LXS
|
||||||
# Date: 2025
|
# Date: 2025
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@ set -u
|
|||||||
|
|
||||||
DO_UFW=1
|
DO_UFW=1
|
||||||
DO_FAIL2BAN=1
|
DO_FAIL2BAN=1
|
||||||
DO_SSH=1
|
|
||||||
DO_UNATTENDED=1
|
DO_UNATTENDED=1
|
||||||
ASSUME_YES=0
|
ASSUME_YES=0
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ for arg in "$@"; do
|
|||||||
case "$arg" in
|
case "$arg" in
|
||||||
--no-ufw) DO_UFW=0 ;;
|
--no-ufw) DO_UFW=0 ;;
|
||||||
--no-fail2ban) DO_FAIL2BAN=0 ;;
|
--no-fail2ban) DO_FAIL2BAN=0 ;;
|
||||||
--no-ssh) DO_SSH=0 ;;
|
|
||||||
--no-unattended) DO_UNATTENDED=0 ;;
|
--no-unattended) DO_UNATTENDED=0 ;;
|
||||||
-y|--yes) ASSUME_YES=1 ;;
|
-y|--yes) ASSUME_YES=1 ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
@@ -40,7 +38,6 @@ Usage: harden.sh [options]
|
|||||||
Options:
|
Options:
|
||||||
--no-ufw Skip UFW firewall setup
|
--no-ufw Skip UFW firewall setup
|
||||||
--no-fail2ban Skip fail2ban setup
|
--no-fail2ban Skip fail2ban setup
|
||||||
--no-ssh Skip SSH key-only enforcement
|
|
||||||
--no-unattended Skip unattended-upgrades setup
|
--no-unattended Skip unattended-upgrades setup
|
||||||
-y, --yes Skip confirmation prompt
|
-y, --yes Skip confirmation prompt
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
@@ -59,18 +56,27 @@ done
|
|||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
require_debian_ubuntu || exit 1
|
require_debian_ubuntu || exit 1
|
||||||
|
require_disk_space 500 || exit 1
|
||||||
|
|
||||||
SSH_PORT=$(awk '/^[[:space:]]*Port[[:space:]]+/ {print $2; exit}' /etc/ssh/sshd_config 2>/dev/null)
|
# Read the effective Port from sshd_config + any drop-in under sshd_config.d/
|
||||||
|
sshd_effective_port() {
|
||||||
|
local files=(/etc/ssh/sshd_config)
|
||||||
|
if compgen -G "/etc/ssh/sshd_config.d/*.conf" >/dev/null; then
|
||||||
|
files+=(/etc/ssh/sshd_config.d/*.conf)
|
||||||
|
fi
|
||||||
|
awk '/^[[:space:]]*Port[[:space:]]+/ {print $2}' "${files[@]}" 2>/dev/null | tail -1
|
||||||
|
}
|
||||||
|
|
||||||
|
SSH_PORT=$(sshd_effective_port)
|
||||||
SSH_PORT=${SSH_PORT:-22}
|
SSH_PORT=${SSH_PORT:-22}
|
||||||
|
|
||||||
echo -e "${WHITE}${BOLD}LXS Server Hardening${NC}"
|
echo -e "${WHITE}${BOLD}LXS Server Hardening${NC}"
|
||||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
show_separator
|
||||||
echo "The following actions will be applied to this server:"
|
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_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_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"
|
[ $DO_UNATTENDED -eq 1 ] && echo " • unattended-upgrades: enable automatic security updates"
|
||||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
show_separator
|
||||||
|
|
||||||
if [ $ASSUME_YES -ne 1 ]; then
|
if [ $ASSUME_YES -ne 1 ]; then
|
||||||
read -r -p "Proceed? [y/N] " reply
|
read -r -p "Proceed? [y/N] " reply
|
||||||
@@ -80,7 +86,8 @@ if [ $ASSUME_YES -ne 1 ]; then
|
|||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
apt_noninteractive
|
||||||
|
wait_for_apt || exit 1
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# UFW
|
# UFW
|
||||||
@@ -121,55 +128,6 @@ EOF
|
|||||||
ok "fail2ban enabled (sshd jail active)"
|
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
|
# unattended-upgrades
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -192,7 +150,6 @@ EOF
|
|||||||
|
|
||||||
[ $DO_UFW -eq 1 ] && setup_ufw
|
[ $DO_UFW -eq 1 ] && setup_ufw
|
||||||
[ $DO_FAIL2BAN -eq 1 ] && setup_fail2ban
|
[ $DO_FAIL2BAN -eq 1 ] && setup_fail2ban
|
||||||
[ $DO_SSH -eq 1 ] && setup_ssh || true
|
|
||||||
[ $DO_UNATTENDED -eq 1 ] && setup_unattended
|
[ $DO_UNATTENDED -eq 1 ] && setup_unattended
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -201,11 +158,9 @@ EOF
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${WHITE}${BOLD}Summary${NC}"
|
echo -e "${WHITE}${BOLD}Summary${NC}"
|
||||||
echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
show_separator
|
||||||
command -v ufw >/dev/null && echo "UFW: $(ufw status | head -1)"
|
command -v ufw >/dev/null && echo "UFW: $(ufw status | head -1)"
|
||||||
systemctl is-active fail2ban >/dev/null 2>&1 && echo "fail2ban: active" || echo "fail2ban: inactive"
|
systemctl is-active 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"
|
systemctl is-active unattended-upgrades >/dev/null 2>&1 && echo "unattended-upgrades: active" || echo "unattended-upgrades: inactive"
|
||||||
echo ""
|
echo ""
|
||||||
ok "Hardening complete."
|
ok "Hardening complete."
|
||||||
|
|||||||
+28
-17
@@ -35,16 +35,16 @@ run_sibling() {
|
|||||||
|
|
||||||
local temp_file exit_code
|
local temp_file exit_code
|
||||||
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
temp_file=$(mktemp "/tmp/lxs.${script_name%.*}.XXXXXX.sh")
|
||||||
echo -e "${PURPLE}[*] Downloading ${BOLD}${script_name}${NC}${PURPLE}...${NC}"
|
echo -e "${CYAN}[..] Fetching ${BOLD}${script_name}${NC}${CYAN}...${NC}"
|
||||||
if curl -fsSL -H "Cache-Control: no-cache" -o "${temp_file}" "${LXS_RAW_BASE}/${script_path}"; then
|
if curl -fsSL -H "Cache-Control: no-cache" -o "${temp_file}" "${LXS_RAW_BASE}/${script_path}"; then
|
||||||
echo -e "${GREEN}[✓] Downloaded${NC}"
|
echo -e "${GREEN}[OK] Payload acquired${NC}"
|
||||||
chmod +x "${temp_file}"
|
chmod +x "${temp_file}"
|
||||||
"${temp_file}" "$@"
|
"${temp_file}" "$@"
|
||||||
exit_code=$?
|
exit_code=$?
|
||||||
rm -f "${temp_file}"
|
rm -f "${temp_file}"
|
||||||
return $exit_code
|
return $exit_code
|
||||||
else
|
else
|
||||||
echo -e "${RED}[✗] Failed to download ${script_path}${NC}"
|
echo -e "${RED}[KO] Failed to download ${script_path}${NC}"
|
||||||
rm -f "${temp_file}"
|
rm -f "${temp_file}"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -53,27 +53,38 @@ run_sibling() {
|
|||||||
menu_tools() {
|
menu_tools() {
|
||||||
while true; do
|
while true; do
|
||||||
clear
|
clear
|
||||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
show_box_top "TOOLS" "SYS_DAEMONS"
|
||||||
echo -e "${WHITE}║ TOOLS ║${NC}"
|
|
||||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${CYAN}[1]${NC} System Infos"
|
show_menu_item "01" "System Infos"
|
||||||
echo -e " ${PURPLE}[2]${NC} Server Benchmark"
|
show_menu_item "02" "Server Benchmark"
|
||||||
echo -e " ${YELLOW}[3]${NC} Harden Server"
|
show_menu_item "03" "Harden Server"
|
||||||
echo -e " ${RED}[0]${NC} Back"
|
show_menu_item "04" "Change Root Password"
|
||||||
|
show_menu_item "05" "Update Server"
|
||||||
|
show_menu_item "06" "Root SSH Password Login"
|
||||||
|
show_menu_item "07" "Welcome Message (MOTD)"
|
||||||
|
show_menu_item "00" "BACK" "" exit
|
||||||
echo ""
|
echo ""
|
||||||
echo -e -n "${BOLD}Choice [0-3]: ${NC}"
|
show_box_bottom
|
||||||
|
echo ""
|
||||||
|
show_prompt
|
||||||
read -r choice
|
read -r choice
|
||||||
|
|
||||||
|
child_rc=0
|
||||||
case $choice in
|
case $choice in
|
||||||
1) run_sibling "tools/system-infos.sh" ;;
|
1|01) run_sibling "tools/system-infos.sh"; child_rc=$? ;;
|
||||||
2) run_sibling "tools/server-benchmark.sh" ;;
|
2|02) run_sibling "tools/server-benchmark.sh"; child_rc=$? ;;
|
||||||
3) run_sibling "tools/harden.sh" ;;
|
3|03) run_sibling "tools/harden.sh"; child_rc=$? ;;
|
||||||
0) return ;;
|
4|04) run_sibling "tools/root-password.sh"; child_rc=$? ;;
|
||||||
*) echo -e "${RED}[✗] Invalid option. Please select 0-3.${NC}"; sleep 1; continue ;;
|
5|05) run_sibling "tools/update-server.sh"; child_rc=$? ;;
|
||||||
|
6|06) run_sibling "tools/root-ssh-login.sh"; child_rc=$? ;;
|
||||||
|
7|07) run_sibling "tools/welcome-message.sh"; child_rc=$? ;;
|
||||||
|
0|00) return ;;
|
||||||
|
*) echo -e "${RED}[KO] Invalid protocol. Select 0-7.${NC}"; sleep 1; continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "$choice" != "0" ]; then
|
# Exit code 75 from a child means it already paused on its own
|
||||||
|
# (its own "Back" or end-of-run prompt) — skip the redundant prompt.
|
||||||
|
if [ "$child_rc" -ne 75 ]; then
|
||||||
echo ""
|
echo ""
|
||||||
read -r -p "Press Enter to continue..."
|
read -r -p "Press Enter to continue..."
|
||||||
fi
|
fi
|
||||||
|
|||||||
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LXS - Change root password
|
||||||
|
# Description: Change the root account password (interactive or generated)
|
||||||
|
# Author: LXS
|
||||||
|
# Date: 2025
|
||||||
|
|
||||||
|
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
|
||||||
|
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
|
||||||
|
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
|
||||||
|
eval "$_lib"
|
||||||
|
unset _lib
|
||||||
|
export LXS_LOG_FILE="/tmp/lxs_root_password.log"
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Arguments
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
MODE=""
|
||||||
|
PASSWORD_LENGTH=24
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-g|--generate) MODE="generate" ;;
|
||||||
|
-i|--interactive) MODE="interactive" ;;
|
||||||
|
--length=*) PASSWORD_LENGTH="${arg#*=}" ;;
|
||||||
|
-h|--help)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: root-password.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-i, --interactive Prompt for the new password (passwd root)
|
||||||
|
-g, --generate Generate a strong random password and apply it
|
||||||
|
--length=N Length of the generated password (default: 24)
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
With no option, an interactive menu is shown.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $arg${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Actions
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
change_interactive() {
|
||||||
|
info "Setting root password interactively..."
|
||||||
|
show_separator
|
||||||
|
if passwd root; then
|
||||||
|
show_separator
|
||||||
|
ok "Root password updated"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
show_separator
|
||||||
|
err "Failed to update root password"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
change_generated() {
|
||||||
|
if ! [[ "$PASSWORD_LENGTH" =~ ^[0-9]+$ ]] || [ "$PASSWORD_LENGTH" -lt 12 ]; then
|
||||||
|
err "--length must be a number ≥ 12 (got: ${PASSWORD_LENGTH})"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local new_password
|
||||||
|
new_password=$(generate_password "$PASSWORD_LENGTH")
|
||||||
|
if [ -z "$new_password" ]; then
|
||||||
|
err "Failed to generate a password"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! echo "root:${new_password}" | chpasswd; then
|
||||||
|
err "Failed to apply the generated password"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_separator
|
||||||
|
ok "Root password updated"
|
||||||
|
echo ""
|
||||||
|
echo -e "${WHITE}${BOLD}New root password:${NC} ${YELLOW}${new_password}${NC}"
|
||||||
|
echo ""
|
||||||
|
warn "Store this password in a secure place. It will not be shown again."
|
||||||
|
show_separator
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Menu (when no mode is given on the CLI)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if [ -z "$MODE" ]; then
|
||||||
|
echo -e "${WHITE}${BOLD}LXS - Change root password${NC}"
|
||||||
|
show_separator
|
||||||
|
echo -e " ${CYAN}[1]${NC} Set a new password interactively"
|
||||||
|
echo -e " ${CYAN}[2]${NC} Generate a strong random password"
|
||||||
|
echo -e " ${RED}[0]${NC} Cancel"
|
||||||
|
echo ""
|
||||||
|
echo -e -n "${BOLD}Choice [0-2]: ${NC}"
|
||||||
|
read -r choice
|
||||||
|
case "$choice" in
|
||||||
|
1) MODE="interactive" ;;
|
||||||
|
2) MODE="generate" ;;
|
||||||
|
0|"") info "Cancelled."; exit 0 ;;
|
||||||
|
*) err "Invalid option."; exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
interactive) change_interactive ;;
|
||||||
|
generate) change_generated ;;
|
||||||
|
esac
|
||||||
Executable
+189
@@ -0,0 +1,189 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LXS - Root SSH password login
|
||||||
|
# Description: Enable or disable root login over SSH with a password
|
||||||
|
# Author: LXS
|
||||||
|
# Date: 2025
|
||||||
|
|
||||||
|
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
|
||||||
|
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
|
||||||
|
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
|
||||||
|
eval "$_lib"
|
||||||
|
unset _lib
|
||||||
|
export LXS_LOG_FILE="/tmp/lxs_root_ssh_login.log"
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# Drop-in path. Numeric prefix `00-` makes it win over distro defaults — sshd
|
||||||
|
# applies the first match per option across the included files.
|
||||||
|
DROPIN_FILE="/etc/ssh/sshd_config.d/00-lxs-root-login.conf"
|
||||||
|
SSH_SERVICE="ssh"
|
||||||
|
command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files 2>/dev/null | grep -q '^sshd\.service' && SSH_SERVICE="sshd"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Arguments
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
ACTION=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--enable|enable) ACTION="enable" ;;
|
||||||
|
--disable|disable) ACTION="disable" ;;
|
||||||
|
--status|status) ACTION="status" ;;
|
||||||
|
-h|--help)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: root-ssh-login.sh [action]
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
--enable Allow root to log in over SSH with a password
|
||||||
|
--disable Disallow root password login (restore SSH defaults)
|
||||||
|
--status Show the current effective settings
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
With no action, an interactive menu is shown.
|
||||||
|
|
||||||
|
Note: the change is written to ${DROPIN_FILE}
|
||||||
|
and applied by reloading the SSH service after a successful 'sshd -t'.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $arg${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Pre-checks
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if [ ! -d /etc/ssh ] || ! command -v sshd >/dev/null 2>&1; then
|
||||||
|
err "OpenSSH server is not installed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Helpers
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
current_setting() {
|
||||||
|
local key=$1
|
||||||
|
sshd -T 2>/dev/null | awk -v k="${key,,}" 'tolower($1)==k {print $2; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
local permit pwauth
|
||||||
|
permit=$(current_setting PermitRootLogin)
|
||||||
|
pwauth=$(current_setting PasswordAuthentication)
|
||||||
|
echo -e "${WHITE}${BOLD}Current SSH login settings${NC}"
|
||||||
|
show_separator
|
||||||
|
echo -e " PermitRootLogin: ${BOLD}${permit:-unknown}${NC}"
|
||||||
|
echo -e " PasswordAuthentication: ${BOLD}${pwauth:-unknown}${NC}"
|
||||||
|
if [ -f "$DROPIN_FILE" ]; then
|
||||||
|
echo -e " Drop-in: ${GRAY}${DROPIN_FILE}${NC}"
|
||||||
|
else
|
||||||
|
echo -e " Drop-in: ${GRAY}(none — distro defaults)${NC}"
|
||||||
|
fi
|
||||||
|
show_separator
|
||||||
|
if [ "${permit:-}" = "yes" ] && [ "${pwauth:-}" = "yes" ]; then
|
||||||
|
warn "Root password login is currently ENABLED."
|
||||||
|
else
|
||||||
|
ok "Root password login is currently DISABLED."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
reload_sshd() {
|
||||||
|
if ! sshd -t 2>>"$LXS_LOG_FILE"; then
|
||||||
|
err "sshd config test failed — see ${LXS_LOG_FILE}. Reverting."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if systemctl reload "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then
|
||||||
|
ok "${SSH_SERVICE} reloaded"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
# Fall back to restart (some minimal images ship without reload support)
|
||||||
|
if systemctl restart "$SSH_SERVICE" 2>>"$LXS_LOG_FILE"; then
|
||||||
|
ok "${SSH_SERVICE} restarted"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
err "Failed to reload/restart ${SSH_SERVICE} — see ${LXS_LOG_FILE}"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_root_login() {
|
||||||
|
# Warn if root has no password — enabling password auth would be useless.
|
||||||
|
local pw_status
|
||||||
|
pw_status=$(passwd -S root 2>/dev/null | awk '{print $2}')
|
||||||
|
case "$pw_status" in
|
||||||
|
L|NP)
|
||||||
|
warn "Root account has no usable password (status: ${pw_status})."
|
||||||
|
warn "Run 'lxs tool root-password' first, otherwise login will still fail."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cat > "${DROPIN_FILE}.tmp" <<'EOF'
|
||||||
|
# Managed by LXS (lxs tool root-ssh-login). Remove this file to revert.
|
||||||
|
PermitRootLogin yes
|
||||||
|
PasswordAuthentication yes
|
||||||
|
EOF
|
||||||
|
chmod 644 "${DROPIN_FILE}.tmp"
|
||||||
|
mv "${DROPIN_FILE}.tmp" "$DROPIN_FILE"
|
||||||
|
|
||||||
|
if ! reload_sshd; then
|
||||||
|
rm -f "$DROPIN_FILE"
|
||||||
|
reload_sshd >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
ok "Root password login over SSH is now ENABLED"
|
||||||
|
warn "This weakens server security. Disable it again when no longer needed:"
|
||||||
|
echo -e " ${GRAY}lxs tool root-ssh-login --disable${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_root_login() {
|
||||||
|
if [ ! -f "$DROPIN_FILE" ]; then
|
||||||
|
info "Drop-in not present — root password login already follows SSH defaults."
|
||||||
|
else
|
||||||
|
local backup="${DROPIN_FILE}.bak.$$"
|
||||||
|
mv "$DROPIN_FILE" "$backup"
|
||||||
|
if ! reload_sshd; then
|
||||||
|
mv "$backup" "$DROPIN_FILE"
|
||||||
|
reload_sshd >/dev/null 2>&1 || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rm -f "$backup"
|
||||||
|
fi
|
||||||
|
ok "Root password login over SSH is now DISABLED"
|
||||||
|
echo ""
|
||||||
|
show_status
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Menu (when no action is given on the CLI)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if [ -z "$ACTION" ]; then
|
||||||
|
show_status
|
||||||
|
echo ""
|
||||||
|
echo -e " ${GREEN}[1]${NC} Enable root SSH password login"
|
||||||
|
echo -e " ${YELLOW}[2]${NC} Disable root SSH password login"
|
||||||
|
echo -e " ${RED}[0]${NC} Cancel"
|
||||||
|
echo ""
|
||||||
|
echo -e -n "${BOLD}Choice [0-2]: ${NC}"
|
||||||
|
read -r choice
|
||||||
|
case "$choice" in
|
||||||
|
1) ACTION="enable" ;;
|
||||||
|
2) ACTION="disable" ;;
|
||||||
|
0|"") info "Cancelled."; exit 0 ;;
|
||||||
|
*) err "Invalid option."; exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
enable) enable_root_login ;;
|
||||||
|
disable) disable_root_login ;;
|
||||||
|
status) show_status ;;
|
||||||
|
esac
|
||||||
+76
-24
@@ -12,6 +12,25 @@ eval "$_lib"
|
|||||||
unset _lib
|
unset _lib
|
||||||
export LXS_LOG_FILE="/tmp/lxs_benchmark.log"
|
export LXS_LOG_FILE="/tmp/lxs_benchmark.log"
|
||||||
|
|
||||||
|
# Always clean up benchmark artifacts on exit, even on error or Ctrl+C. Without
|
||||||
|
# this, a partial 1-2GB dd file in /tmp can stay behind and fill the disk on
|
||||||
|
# small servers if the script is re-run.
|
||||||
|
cleanup_benchmark_artifacts() {
|
||||||
|
rm -f /tmp/lxs_test_write_* \
|
||||||
|
/tmp/lxs_test_read_* \
|
||||||
|
/tmp/lxs_write_result_*.tmp \
|
||||||
|
/tmp/lxs_read_result_*.tmp \
|
||||||
|
/tmp/lxs_cpu_result.tmp \
|
||||||
|
/tmp/lxs_ram_result.tmp \
|
||||||
|
/tmp/lxs_ping_result.tmp \
|
||||||
|
/tmp/lxs_ping_*.tmp 2>/dev/null || true
|
||||||
|
}
|
||||||
|
trap cleanup_benchmark_artifacts EXIT INT TERM
|
||||||
|
|
||||||
|
# Minimum free space (MB) to keep available during disk tests. If /tmp drops
|
||||||
|
# below this threshold mid-run we abort the remaining passes.
|
||||||
|
DISK_SAFETY_MARGIN_MB=200
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# Score Variables
|
# Score Variables
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -28,13 +47,7 @@ SCORE_NETWORK_LATENCY=0
|
|||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
show_header() {
|
show_header() {
|
||||||
echo -e "${WHITE}${BOLD}"
|
show_box_top "SERVER PERFORMANCE BENCHMARK"
|
||||||
echo "╔═══════════════════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "║ SERVER PERFORMANCE BENCHMARK ║"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════════════════╝"
|
|
||||||
echo -e "${NC}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
install_dependencies() {
|
install_dependencies() {
|
||||||
@@ -45,6 +58,8 @@ install_dependencies() {
|
|||||||
echo -e "${YELLOW}[!] sysbench not found, installing...${NC}"
|
echo -e "${YELLOW}[!] sysbench not found, installing...${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
require_disk_space 300 || return 1
|
||||||
|
|
||||||
if command -v apt-get &> /dev/null; then
|
if command -v apt-get &> /dev/null; then
|
||||||
run_spinner "Updating package list..." "apt-get update -qq"
|
run_spinner "Updating package list..." "apt-get update -qq"
|
||||||
run_spinner "Installing sysbench..." "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq sysbench"
|
run_spinner "Installing sysbench..." "DEBIAN_FRONTEND=noninteractive apt-get install -y -qq sysbench"
|
||||||
@@ -184,17 +199,27 @@ test_disk_write() {
|
|||||||
# Check available disk space in /tmp
|
# Check available disk space in /tmp
|
||||||
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
||||||
|
|
||||||
# Test configurations: size_mb, block_size, description
|
# Build test configurations dynamically based on free space. Each test needs
|
||||||
local test_configs=(
|
# room for its file PLUS the safety margin, otherwise dd can fill the disk
|
||||||
"100:1M:100MB Sequential"
|
# on small servers (1-2GB VPS). Tests are added from smallest to largest.
|
||||||
"1024:1M:1GB Sequential"
|
local test_configs=()
|
||||||
)
|
if [ "$available_space" -gt $((100 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
test_configs+=("100:1M:100MB Sequential")
|
||||||
# Add 2GB test only if we have enough space (at least 3GB free)
|
fi
|
||||||
if [ "$available_space" -gt 3072 ]; then
|
if [ "$available_space" -gt $((1024 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
test_configs+=("1024:1M:1GB Sequential")
|
||||||
|
fi
|
||||||
|
if [ "$available_space" -gt $((2048 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
test_configs+=("2048:1M:2GB Sequential")
|
test_configs+=("2048:1M:2GB Sequential")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ${#test_configs[@]} -eq 0 ]; then
|
||||||
|
echo -e "${RED}[✗] Not enough free space on /tmp (${available_space}MB) to run any write test${NC}"
|
||||||
|
SCORE_DISK_WRITE=0
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
local all_speeds=()
|
local all_speeds=()
|
||||||
local all_speeds_mb=()
|
local all_speeds_mb=()
|
||||||
local test_count=0
|
local test_count=0
|
||||||
@@ -216,6 +241,14 @@ test_disk_write() {
|
|||||||
local pass_speeds=()
|
local pass_speeds=()
|
||||||
local pass_count=0
|
local pass_count=0
|
||||||
|
|
||||||
|
# Re-check free space before each config; the previous test may have
|
||||||
|
# left the filesystem tighter than expected (cache, logs, etc.).
|
||||||
|
local current_free=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
||||||
|
if [ "$current_free" -lt $((size_mb + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
printf "\r${YELLOW}[!]${NC} ${description}: Skipped (only ${current_free}MB free)\n"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
# Run 3 passes for each configuration
|
# Run 3 passes for each configuration
|
||||||
for pass in 1 2 3; do
|
for pass in 1 2 3; do
|
||||||
# Clean up before test
|
# Clean up before test
|
||||||
@@ -350,17 +383,27 @@ test_disk_read() {
|
|||||||
# Check available disk space in /tmp
|
# Check available disk space in /tmp
|
||||||
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
local available_space=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
||||||
|
|
||||||
# Test configurations: size_mb, block_size, description
|
# Build test configurations dynamically based on free space (see write test
|
||||||
local test_configs=(
|
# for rationale). The read test also writes a source file first, so the
|
||||||
"100:1M:100MB Sequential"
|
# space requirement is the same as the write test.
|
||||||
"1024:1M:1GB Sequential"
|
local test_configs=()
|
||||||
)
|
if [ "$available_space" -gt $((100 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
test_configs+=("100:1M:100MB Sequential")
|
||||||
# Add 2GB test only if we have enough space (at least 3GB free)
|
fi
|
||||||
if [ "$available_space" -gt 3072 ]; then
|
if [ "$available_space" -gt $((1024 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
test_configs+=("1024:1M:1GB Sequential")
|
||||||
|
fi
|
||||||
|
if [ "$available_space" -gt $((2048 + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
test_configs+=("2048:1M:2GB Sequential")
|
test_configs+=("2048:1M:2GB Sequential")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ ${#test_configs[@]} -eq 0 ]; then
|
||||||
|
echo -e "${RED}[✗] Not enough free space on /tmp (${available_space}MB) to run any read test${NC}"
|
||||||
|
SCORE_DISK_READ=0
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
local all_speeds_mb=()
|
local all_speeds_mb=()
|
||||||
local test_count=0
|
local test_count=0
|
||||||
local spinstr='|/-\'
|
local spinstr='|/-\'
|
||||||
@@ -378,10 +421,18 @@ test_disk_read() {
|
|||||||
local block_size=$(echo "$config" | cut -d':' -f2)
|
local block_size=$(echo "$config" | cut -d':' -f2)
|
||||||
local description=$(echo "$config" | cut -d':' -f3)
|
local description=$(echo "$config" | cut -d':' -f3)
|
||||||
|
|
||||||
|
# Re-check free space before each config (same rationale as write test).
|
||||||
|
local current_free=$(df -BM /tmp | awk 'NR==2 {print $4}' | sed 's/M//')
|
||||||
|
if [ "$current_free" -lt $((size_mb + DISK_SAFETY_MARGIN_MB)) ]; then
|
||||||
|
printf "\r${YELLOW}[!]${NC} ${description}: Skipped (only ${current_free}MB free)\n"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
# Create test file if it doesn't exist
|
# Create test file if it doesn't exist
|
||||||
if [ ! -f /tmp/lxs_test_read_${size_mb} ]; then
|
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 || {
|
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
|
# If file creation fails, skip this test and remove any partial file
|
||||||
|
rm -f /tmp/lxs_test_read_${size_mb}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sync
|
sync
|
||||||
@@ -760,4 +811,5 @@ fi
|
|||||||
|
|
||||||
# Execute main function
|
# Execute main function
|
||||||
main
|
main
|
||||||
|
exit 75
|
||||||
|
|
||||||
|
|||||||
+15
-17
@@ -16,21 +16,21 @@ export LXS_LOG_FILE="/tmp/lxs_system_infos.log"
|
|||||||
menu_system_infos() {
|
menu_system_infos() {
|
||||||
while true; do
|
while true; do
|
||||||
clear
|
clear
|
||||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
show_box_top "SYSTEM INFOS"
|
||||||
echo -e "${WHITE}║ SYSTEM INFOS ║${NC}"
|
|
||||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${CYAN}[1]${NC} View system informations"
|
show_menu_item "1" "View system informations"
|
||||||
echo -e " ${CYAN}[2]${NC} Check disk space"
|
show_menu_item "2" "Check disk space"
|
||||||
echo -e " ${CYAN}[3]${NC} Check memory usage"
|
show_menu_item "3" "Check memory usage"
|
||||||
echo -e " ${CYAN}[4]${NC} Check CPU load"
|
show_menu_item "4" "Check CPU load"
|
||||||
echo -e " ${CYAN}[5]${NC} Check network"
|
show_menu_item "5" "Check network"
|
||||||
echo -e " ${CYAN}[6]${NC} View system logs (last 50 lines)"
|
show_menu_item "6" "View system logs (last 50 lines)"
|
||||||
echo -e " ${CYAN}[7]${NC} Show top resource-consuming processes"
|
show_menu_item "7" "Show top resource-consuming processes"
|
||||||
echo -e " ${CYAN}[8]${NC} Check disk health (SMART)"
|
show_menu_item "8" "Check disk health (SMART)"
|
||||||
echo -e " ${RED}[0]${NC} Exit"
|
show_menu_item "0" "Exit" "" exit
|
||||||
echo ""
|
echo ""
|
||||||
echo -e -n "${BOLD}Choice [0-8]: ${NC}"
|
show_box_bottom
|
||||||
|
echo ""
|
||||||
|
show_prompt
|
||||||
read -r choice
|
read -r choice
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -39,9 +39,7 @@ menu_system_infos() {
|
|||||||
1)
|
1)
|
||||||
# View system informations
|
# View system informations
|
||||||
clear
|
clear
|
||||||
echo -e "${WHITE}╔═══════════════════════════════════════════════════════╗${NC}"
|
show_box_top "SYSTEM INFORMATION"
|
||||||
echo -e "${WHITE}║ SYSTEM INFORMATION ║${NC}"
|
|
||||||
echo -e "${WHITE}╚═══════════════════════════════════════════════════════╝${NC}"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo -e "${CYAN}${BOLD}Operating System:${NC}"
|
echo -e "${CYAN}${BOLD}Operating System:${NC}"
|
||||||
@@ -267,7 +265,7 @@ menu_system_infos() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
0)
|
0)
|
||||||
exit 0
|
exit 75
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}[✗] Invalid option. Please select 0-8.${NC}"
|
echo -e "${RED}[✗] Invalid option. Please select 0-8.${NC}"
|
||||||
|
|||||||
Executable
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LXS - Update server
|
||||||
|
# Description: Refresh package lists and upgrade installed packages
|
||||||
|
# Author: LXS
|
||||||
|
# Date: 2025
|
||||||
|
|
||||||
|
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
|
||||||
|
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
|
||||||
|
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
|
||||||
|
eval "$_lib"
|
||||||
|
unset _lib
|
||||||
|
export LXS_LOG_FILE="/tmp/lxs_update_server.log"
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Arguments
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
DO_FULL_UPGRADE=0
|
||||||
|
DO_AUTOREMOVE=1
|
||||||
|
DO_AUTOCLEAN=1
|
||||||
|
ASSUME_YES=0
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--full|--dist-upgrade) DO_FULL_UPGRADE=1 ;;
|
||||||
|
--no-autoremove) DO_AUTOREMOVE=0 ;;
|
||||||
|
--no-autoclean) DO_AUTOCLEAN=0 ;;
|
||||||
|
-y|--yes) ASSUME_YES=1 ;;
|
||||||
|
-h|--help)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: update-server.sh [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--full, --dist-upgrade Use 'apt-get full-upgrade' (may add/remove packages)
|
||||||
|
--no-autoremove Skip 'apt-get autoremove'
|
||||||
|
--no-autoclean Skip 'apt-get autoclean'
|
||||||
|
-y, --yes Skip confirmation prompt
|
||||||
|
-h, --help Show this help
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $arg${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Pre-checks
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
require_debian_ubuntu || exit 1
|
||||||
|
|
||||||
|
if ! command -v apt-get >/dev/null 2>&1; then
|
||||||
|
err "apt-get is not available on this system"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
require_disk_space 1024 || exit 1
|
||||||
|
|
||||||
|
UPGRADE_CMD="upgrade"
|
||||||
|
UPGRADE_LABEL="Upgrade installed packages"
|
||||||
|
if [ $DO_FULL_UPGRADE -eq 1 ]; then
|
||||||
|
UPGRADE_CMD="full-upgrade"
|
||||||
|
UPGRADE_LABEL="Full-upgrade (may add/remove packages)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${WHITE}${BOLD}LXS Server Update${NC}"
|
||||||
|
show_separator
|
||||||
|
echo "The following actions will be performed:"
|
||||||
|
echo " • Refresh package lists (apt-get update)"
|
||||||
|
echo " • ${UPGRADE_LABEL}"
|
||||||
|
[ $DO_AUTOREMOVE -eq 1 ] && echo " • Remove unused packages (apt-get autoremove)"
|
||||||
|
[ $DO_AUTOCLEAN -eq 1 ] && echo " • Clean old package archives (apt-get autoclean)"
|
||||||
|
show_separator
|
||||||
|
|
||||||
|
if [ $ASSUME_YES -ne 1 ]; then
|
||||||
|
echo -e -n "${BOLD}Proceed? [y/N]: ${NC}"
|
||||||
|
read -r reply
|
||||||
|
case "$reply" in
|
||||||
|
[yY]|[yY][eE][sS]) ;;
|
||||||
|
*) info "Cancelled."; exit 0 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Run
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
apt_noninteractive
|
||||||
|
wait_for_apt || exit 1
|
||||||
|
|
||||||
|
APT_OPTS='-y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold"'
|
||||||
|
|
||||||
|
run_spinner "Refreshing package lists..." "apt-get update" || {
|
||||||
|
err "apt-get update failed — see ${LXS_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
run_spinner "${UPGRADE_LABEL}..." "apt-get ${APT_OPTS} ${UPGRADE_CMD}" || {
|
||||||
|
err "apt-get ${UPGRADE_CMD} failed — see ${LXS_LOG_FILE}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $DO_AUTOREMOVE -eq 1 ]; then
|
||||||
|
run_spinner "Removing unused packages..." "apt-get ${APT_OPTS} autoremove --purge" \
|
||||||
|
|| warn "autoremove failed — see ${LXS_LOG_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $DO_AUTOCLEAN -eq 1 ]; then
|
||||||
|
run_spinner "Cleaning old archives..." "apt-get ${APT_OPTS} autoclean" \
|
||||||
|
|| warn "autoclean failed — see ${LXS_LOG_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_separator
|
||||||
|
ok "Server updated"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Reboot hint
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if [ -f /var/run/reboot-required ]; then
|
||||||
|
echo ""
|
||||||
|
warn "A reboot is required to complete the update."
|
||||||
|
if [ -f /var/run/reboot-required.pkgs ]; then
|
||||||
|
echo -e "${GRAY}Packages requiring reboot:${NC}"
|
||||||
|
sed 's/^/ • /' /var/run/reboot-required.pkgs
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -t 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo -e -n "${BOLD}Reboot now? [y/N]: ${NC}"
|
||||||
|
read -r reboot_reply
|
||||||
|
case "$reboot_reply" in
|
||||||
|
[yY]|[yY][eE][sS])
|
||||||
|
info "Rebooting..."
|
||||||
|
systemctl reboot
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
info "Reboot skipped. Remember to reboot later."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
Executable
+203
@@ -0,0 +1,203 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# LXS - Welcome message (MOTD)
|
||||||
|
# Description: View, edit, or reset the SSH login welcome message (/etc/motd)
|
||||||
|
# Author: LXS
|
||||||
|
# Date: 2025
|
||||||
|
|
||||||
|
# Load LXS common library (colors, separator, run_spinner, loggers, helpers)
|
||||||
|
LXS_RAW_BASE="${LXS_RAW_BASE:-https://git.hyko.cx/hykocx/lxs/raw/branch/main}"
|
||||||
|
_lib=$(curl -fsSL "${LXS_RAW_BASE}/lib/common.sh") || { echo "Failed to fetch lib/common.sh" >&2; exit 1; }
|
||||||
|
eval "$_lib"
|
||||||
|
unset _lib
|
||||||
|
export LXS_LOG_FILE="/tmp/lxs_welcome_message.log"
|
||||||
|
|
||||||
|
require_root "$0" "$@"
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
MOTD_FILE="/etc/motd"
|
||||||
|
BACKUP_FILE="/etc/motd.lxs.bak"
|
||||||
|
DYNAMIC_DIR="/etc/update-motd.d"
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Arguments
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
ACTION=""
|
||||||
|
TEXT=""
|
||||||
|
FROM_FILE=""
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
view|--view|show|--show) ACTION="view" ;;
|
||||||
|
set|--set|edit|--edit) ACTION="set" ;;
|
||||||
|
reset|--reset) ACTION="reset" ;;
|
||||||
|
--text) shift; TEXT=${1:-} ;;
|
||||||
|
--text=*) TEXT="${1#*=}" ;;
|
||||||
|
--from-file) shift; FROM_FILE=${1:-} ;;
|
||||||
|
--from-file=*) FROM_FILE="${1#*=}" ;;
|
||||||
|
-h|--help)
|
||||||
|
cat <<EOF
|
||||||
|
Usage: welcome-message.sh [action] [options]
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
view Show the current welcome message
|
||||||
|
set Set a new welcome message (see source options below)
|
||||||
|
reset Clear the welcome message (a backup is kept)
|
||||||
|
|
||||||
|
Source options for 'set' (mutually exclusive):
|
||||||
|
--text "..." Use the given string as the new message
|
||||||
|
--from-file PATH Read the new message from PATH
|
||||||
|
(none) Open an interactive editor (\$EDITOR or nano/vi)
|
||||||
|
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
With no action, an interactive menu is shown.
|
||||||
|
|
||||||
|
The welcome message lives in ${MOTD_FILE}.
|
||||||
|
On Ubuntu, dynamic MOTD scripts in ${DYNAMIC_DIR} also contribute to the
|
||||||
|
banner shown at login — those are not modified by this tool.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo -e "${RED}Unknown option: $1${NC}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$TEXT" ] && [ -n "$FROM_FILE" ]; then
|
||||||
|
err "--text and --from-file are mutually exclusive"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Helpers
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
view_motd() {
|
||||||
|
echo -e "${WHITE}${BOLD}Current welcome message${NC} ${GRAY}(${MOTD_FILE})${NC}"
|
||||||
|
show_separator
|
||||||
|
if [ ! -s "$MOTD_FILE" ]; then
|
||||||
|
echo -e "${GRAY}(empty)${NC}"
|
||||||
|
else
|
||||||
|
cat "$MOTD_FILE"
|
||||||
|
fi
|
||||||
|
show_separator
|
||||||
|
|
||||||
|
if [ -d "$DYNAMIC_DIR" ] && compgen -G "${DYNAMIC_DIR}/*" >/dev/null; then
|
||||||
|
echo ""
|
||||||
|
warn "Dynamic MOTD scripts are present in ${DYNAMIC_DIR}:"
|
||||||
|
find "$DYNAMIC_DIR" -maxdepth 1 -type f -executable -printf ' • %f\n' | sort
|
||||||
|
echo -e "${GRAY}These run at login and add to the banner.${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_motd() {
|
||||||
|
[ -f "$MOTD_FILE" ] || return 0
|
||||||
|
cp -a "$MOTD_FILE" "$BACKUP_FILE"
|
||||||
|
info "Previous message backed up to ${BACKUP_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_motd() {
|
||||||
|
local src=$1
|
||||||
|
backup_motd
|
||||||
|
install -m 644 "$src" "$MOTD_FILE"
|
||||||
|
ok "Welcome message updated"
|
||||||
|
echo ""
|
||||||
|
view_motd
|
||||||
|
}
|
||||||
|
|
||||||
|
set_from_text() {
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp /tmp/lxs.motd.XXXXXX) || { err "mktemp failed"; return 1; }
|
||||||
|
# Preserve embedded newlines; ensure a trailing newline.
|
||||||
|
printf '%s\n' "$1" > "$tmp"
|
||||||
|
write_motd "$tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_from_file() {
|
||||||
|
local src=$1
|
||||||
|
if [ ! -r "$src" ]; then
|
||||||
|
err "Cannot read file: ${src}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
write_motd "$src"
|
||||||
|
}
|
||||||
|
|
||||||
|
set_interactive() {
|
||||||
|
local editor=${EDITOR:-}
|
||||||
|
if [ -z "$editor" ]; then
|
||||||
|
if command -v nano >/dev/null 2>&1; then editor="nano"
|
||||||
|
elif command -v vi >/dev/null 2>&1; then editor="vi"
|
||||||
|
else
|
||||||
|
err "No editor found (\$EDITOR unset; nano/vi missing). Use --text or --from-file."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp /tmp/lxs.motd.XXXXXX) || { err "mktemp failed"; return 1; }
|
||||||
|
[ -s "$MOTD_FILE" ] && cat "$MOTD_FILE" > "$tmp"
|
||||||
|
|
||||||
|
info "Opening ${editor}... save and exit to apply, leave empty to cancel."
|
||||||
|
"$editor" "$tmp"
|
||||||
|
|
||||||
|
if [ ! -s "$tmp" ]; then
|
||||||
|
warn "Empty content — change cancelled."
|
||||||
|
rm -f "$tmp"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_motd "$tmp"
|
||||||
|
rm -f "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_motd() {
|
||||||
|
if [ ! -s "$MOTD_FILE" ]; then
|
||||||
|
info "Welcome message is already empty."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
backup_motd
|
||||||
|
: > "$MOTD_FILE"
|
||||||
|
chmod 644 "$MOTD_FILE"
|
||||||
|
ok "Welcome message cleared"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# Menu (when no action is given on the CLI)
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
if [ -z "$ACTION" ]; then
|
||||||
|
echo -e "${WHITE}${BOLD}LXS - Welcome message${NC}"
|
||||||
|
show_separator
|
||||||
|
echo -e " ${CYAN}[1]${NC} View current message"
|
||||||
|
echo -e " ${GREEN}[2]${NC} Set a new message"
|
||||||
|
echo -e " ${YELLOW}[3]${NC} Reset (clear) the message"
|
||||||
|
echo -e " ${RED}[0]${NC} Cancel"
|
||||||
|
echo ""
|
||||||
|
echo -e -n "${BOLD}Choice [0-3]: ${NC}"
|
||||||
|
read -r choice
|
||||||
|
case "$choice" in
|
||||||
|
1) ACTION="view" ;;
|
||||||
|
2) ACTION="set" ;;
|
||||||
|
3) ACTION="reset" ;;
|
||||||
|
0|"") info "Cancelled."; exit 0 ;;
|
||||||
|
*) err "Invalid option."; exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
view) view_motd ;;
|
||||||
|
set)
|
||||||
|
if [ -n "$TEXT" ]; then set_from_text "$TEXT"
|
||||||
|
elif [ -n "$FROM_FILE" ]; then set_from_file "$FROM_FILE"
|
||||||
|
else set_interactive
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
reset) reset_motd ;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user