Compare commits

..

59 Commits

Author SHA1 Message Date
buildplan
6f1b2563df
remove dartnode logo
Project does not meet 1000 stars requirement
2025-12-10 11:13:18 +00:00
buildplan
b6a392dbfb
Add dartnode logo 2025-12-10 11:03:29 +00:00
buildplan
5857a1a45e
Merge pull request #87 from buildplan/improve_swap
Improve swap
2025-11-27 22:25:18 +00:00
buildplan
be54f03de3 version and checksum 2025-11-27 22:19:53 +00:00
buildplan
c7e66378ee checksum v0.78.4 2025-11-27 22:18:44 +00:00
buildplan
df67befa0c update message 2025-11-27 22:18:17 +00:00
buildplan
428f6fa9d5 version and checksum 2025-11-27 17:06:20 +00:00
buildplan
e9161387c7 checksum v0.78.4 2025-11-27 17:05:32 +00:00
buildplan
ff72d42b14 remove any unnecessary code 2025-11-27 16:25:48 +00:00
buildplan
d3bb2dca38 version and changelog 2025-11-27 15:14:06 +00:00
buildplan
1f0a20d998 Improved swap configuration with retries 2025-11-27 15:06:12 +00:00
buildplan
c5a1621ae6 improve configure_swap 2025-11-27 14:26:57 +00:00
buildplan
dfae3a2fb9 Refactor swap configuration 2025-11-26 21:33:25 +00:00
buildplan
37655c0736
Merge pull request #86 from buildplan/improve_summary
Improve enviorment detection in summary and run apt-upgrade at final step
2025-11-26 13:49:20 +00:00
buildplan
b7fdd5b456 version and checksum 2025-11-26 13:45:50 +00:00
buildplan
d86e9b1eb8 checksum v0.78.3 2025-11-26 13:45:05 +00:00
buildplan
87c241c208 remove unused veriables 2025-11-26 12:05:18 +00:00
buildplan
0733e58651 Improved environment information summary 2025-11-26 11:19:34 +00:00
buildplan
e7dbe538dc improve summay and final system update 2025-11-26 11:06:12 +00:00
buildplan
30a77d43be
Merge pull request #85 from buildplan/fix_hostname
Fix hostname
2025-11-25 21:52:35 +00:00
buildplan
9f9b158c12 update checksum 2025-11-25 21:35:33 +00:00
buildplan
38690ea41f checksum v0.78.2 2025-11-25 21:34:57 +00:00
buildplan
e59a56581f enhance hostname configuration to prevent cloud-init overwrites 2025-11-25 21:34:09 +00:00
buildplan
ca160b3a4a update checksum 2025-11-25 20:57:21 +00:00
buildplan
ffa1245386 checksum v0.78.2 2025-11-25 20:56:44 +00:00
buildplan
80d6b140d5 prevent hostname overwrites in cloud-init 2025-11-25 20:55:10 +00:00
buildplan
4a35b98ebc
Merge pull request #84 from buildplan/fix_config
Fix hostname and disable cloud-init for hostname setting on cloud VPS
2025-11-25 20:18:41 +00:00
buildplan
11bbc21057 checksum updated 2025-11-25 20:07:02 +00:00
buildplan
2b78321971 checksum v0.78.2 2025-11-25 20:06:19 +00:00
buildplan
a284a908d1 fix hostname in /etc/hosts 2025-11-25 20:05:25 +00:00
buildplan
d24ec53084
Merge pull request #83 from buildplan/fix_hostname
Fix hostname
2025-11-25 17:42:01 +00:00
buildplan
bb252fdc1b version and checksum 2025-11-25 17:37:51 +00:00
buildplan
54c5eb3d0f checksum v0.78.2 2025-11-25 17:37:11 +00:00
buildplan
91c9057203 version and changelog updated 2025-11-25 17:36:10 +00:00
buildplan
b40b1793e4 set new hostname in /etc/hosts 2025-11-25 17:33:43 +00:00
buildplan
5a917cb9e9
Merge pull request #82 from buildplan/fix_ipv6_only
Fix ipv6 only
2025-11-25 16:31:15 +00:00
buildplan
8f12b2f21a version and checksum 2025-11-25 16:23:51 +00:00
buildplan
4ef957bd85 checksum v0.78.1 2025-11-25 16:23:13 +00:00
buildplan
8b555fe771 fix collect_config on IPv6 only VPS 2025-11-25 16:22:21 +00:00
buildplan
b75c9b77ff
Merge pull request #81 from buildplan/handle_local_vm
handle local vm
2025-11-25 00:45:29 +00:00
buildplan
fe0af92e91 version and checksum 2025-11-25 00:39:43 +00:00
buildplan
21e95b0b6f checksum v0.78 2025-11-25 00:38:30 +00:00
buildplan
c9eea30d97 changelog update 2025-11-25 00:31:44 +00:00
buildplan
b6b04f96d2 in setup_user group exists but user doesn't 2025-11-25 00:27:51 +00:00
buildplan
cdff42881a ip address global veriables 2025-11-25 00:13:34 +00:00
buildplan
83e4420ee6 Summary improvement to show different IPs 2025-11-24 18:22:39 +00:00
buildplan
a306b9b675 improve ip detection and display in configure_ssh 2025-11-24 18:02:58 +00:00
buildplan
0f00ade070 handles different environments: Public IP, NAT,Local VM 2025-11-24 17:39:56 +00:00
buildplan
b31e0311b0
Merge pull request #80 from buildplan/check_dependencies
unbound variable fix for SSH when on local VM, check_dependencies before check_system to avoid failures on minimal servers
2025-11-24 17:08:10 +00:00
buildplan
ee9fc1b017 updated checksum in README 2025-11-24 15:38:38 +00:00
buildplan
aa452e5008 checksum v0.71.2 2025-11-24 15:38:02 +00:00
buildplan
b21f1e00d8 collect config improvements 2025-11-24 15:37:19 +00:00
buildplan
8b74a2c5c7 version and checksum updated 2025-11-24 15:08:27 +00:00
buildplan
853d351cab checksum v0.71.2 2025-11-24 15:07:50 +00:00
buildplan
cf8f5aa104 improve fail2ban setup 2025-11-24 15:06:56 +00:00
buildplan
ae94f7391c checksum v0.77.2 2025-11-24 14:52:56 +00:00
buildplan
2e18d747ab version and checksum 2025-11-24 13:15:24 +00:00
buildplan
5133306a0c checksum v0.77.2 2025-11-24 13:12:59 +00:00
buildplan
17dfe530fa unbound variable fix in SSH connection detection 2025-11-24 13:12:06 +00:00
3 changed files with 498 additions and 196 deletions

View File

@ -7,9 +7,9 @@
-----
**Version:** v0.77.1
**Version:** v0.78.4
**Last Updated:** 2025-11-19
**Last Updated:** 2025-11-27
**Compatible With:**
@ -87,12 +87,12 @@ sha256sum du_setup.sh
Compare the output hash to the one below. They must match exactly.
`3ea3416cf0916ea92c382e1c899de0ef2f29535c9c93d1fa7466b4a78e1bb26d`
`d7378f43082166cad26c7b232f3cd01e778a3377dea996c3f434e0f483317659`
Or echo the hash to check, it should output: `du_setup.sh: OK`
```bash
echo 3ea3416cf0916ea92c382e1c899de0ef2f29535c9c93d1fa7466b4a78e1bb26d du_setup.sh | sha256sum --check
echo d7378f43082166cad26c7b232f3cd01e778a3377dea996c3f434e0f483317659 du_setup.sh | sha256sum --check
```
### 3. Run the Script

View File

@ -1,8 +1,19 @@
#!/bin/bash
# Debian and Ubuntu Server Hardening Interactive Script
# Version: 0.77.1 | 2025-11-19
# Version: 0.78.4 | 2025-11-27
# Changelog:
# - v0.78.4: Improved configure_swap to detect swap partitions vs files.
# Prevents 'fallocate' crashes on physical partitions by offering to disable them or skip.
# - v0.78.3: Update the summary to try to show the right environment detection based on finding personal VMs and cloud VPS.
# Run update & upgrade in the final step to ensure system is fully updated after restart.
# - v0.78.2: In configure_system set choosen hostname from collect_config in the /etc/hosts
# - v0.78.1: Collect config failure fixed on IPv6 only VPS.
# - v0.78: Script tries to handles different environments: Direct Public IP, NAT/Router and Local VM only
# The configure_ssh function provides context-aware instructions based on different environments.
# In setup_user handle if group exists but user doesn't - attach user to existing group.
# - v0.77.2: Fixed an unbound variable for SSH when on a local virtual machine;
# check_dependencies should come before check_system to keep minimal servers from failing.
# - v0.77.1: Auto SSH connection whitelist feat & whitelist deduplication.
# - v0.77: User-configurable ignoreip functionality for configure_fail2ban function.
# Add a few more core packages in install_packages function.
@ -84,7 +95,7 @@
set -euo pipefail
# --- Update Configuration ---
CURRENT_VERSION="0.77.1"
CURRENT_VERSION="0.78.4"
SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh"
CHECKSUM_URL="${SCRIPT_URL}.sha256"
@ -128,8 +139,13 @@ SKIP_CLEANUP=false # If true, skip cleanup tasks
DETECTED_VIRT_TYPE=""
DETECTED_MANUFACTURER=""
DETECTED_PRODUCT=""
IS_CLOUD_PROVIDER=false
IS_CONTAINER=false
ENVIRONMENT_TYPE="unknown"
DETECTED_PROVIDER_NAME=""
SERVER_IP_V4="Unknown"
SERVER_IP_V6="Not available"
LOCAL_IP_V4=""
SSHD_BACKUP_FILE=""
LOCAL_KEY_ADDED=false
@ -235,7 +251,7 @@ print_header() {
printf '%s\n' "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}"
printf '%s\n' "${CYAN}║ ║${NC}"
printf '%s\n' "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}"
printf '%s\n' "${CYAN}v0.77.1 | 2025-11-19 ${NC}"
printf '%s\n' "${CYAN} v0.78.4 | 2025-11-27${NC}"
printf '%s\n' "${CYAN}║ ║${NC}"
printf '%s\n' "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}"
printf '\n'
@ -471,7 +487,6 @@ detect_environment() {
DETECTED_MANUFACTURER="$MANUFACTURER"
DETECTED_PRODUCT="$PRODUCT"
DETECTED_BIOS_VENDOR="${DETECTED_BIOS_VENDOR:-unknown}"
IS_CLOUD_PROVIDER="$IS_CLOUD_VPS"
log "Environment detection: VIRT=$VIRT_TYPE, MANUFACTURER=$MANUFACTURER, PRODUCT=$PRODUCT, IS_CLOUD=$IS_CLOUD_VPS, TYPE=$ENVIRONMENT_TYPE"
}
@ -2545,11 +2560,6 @@ validate_timezone() {
[[ -e "/usr/share/zoneinfo/$tz" ]]
}
validate_swap_size() {
local size_upper="${1^^}" # Convert to uppercase for case-insensitivity
[[ "$size_upper" =~ ^[0-9]+[MG]$ && "${size_upper%[MG]}" -ge 1 ]]
}
validate_ufw_port() {
local port="$1"
# Matches port (e.g., 8080) or port/protocol (e.g., 8080/tcp, 123/udp)
@ -2600,17 +2610,25 @@ validate_ip_or_cidr() {
return 1
}
convert_to_bytes() {
local size_upper="${1^^}" # Convert to uppercase for case-insensitivity
local unit="${size_upper: -1}"
local value="${size_upper%[MG]}"
if [[ "$unit" == "G" ]]; then
echo $((value * 1024 * 1024 * 1024))
elif [[ "$unit" == "M" ]]; then
echo $((value * 1024 * 1024))
else
echo 0
# Convert size (e.g., 2G) to MB for validation/dd
convert_to_mb() {
local input="${1:-}"
local size="${input^^}"
size="${size// /}"
local num="${size%[MG]}"
local unit="${size: -1}"
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
print_error "Invalid swap size format: '$input'. Expected format: 2G, 512M." >&2
return 1
fi
case "$unit" in
G) echo "$((num * 1024))" ;;
M) echo "$num" ;;
*)
print_error "Unknown or missing unit in swap size: '$input'. Use 'M' or 'G'." >&2
return 1
;;
esac
}
# --- script update check ---
@ -2731,8 +2749,9 @@ check_system() {
fi
if [[ -f /etc/os-release ]]; then
# shellcheck source=/dev/null
source /etc/os-release
ID=$ID # Populate global ID variable
ID=${ID:-unknown} # Populate global ID variable
if [[ $ID == "debian" && $VERSION_ID =~ ^(12|13)$ ]] || \
[[ $ID == "ubuntu" && $VERSION_ID =~ ^(20.04|22.04|24.04)$ ]]; then
print_success "Compatible OS detected: $PRETTY_NAME"
@ -2760,7 +2779,10 @@ check_system() {
fi
fi
if curl -s --head https://deb.debian.org >/dev/null || curl -s --head https://archive.ubuntu.com >/dev/null; then
if curl -s --head https://deb.debian.org >/dev/null || \
curl -s --head https://archive.ubuntu.com >/dev/null || \
wget -q --spider https://deb.debian.org || \
wget -q --spider https://archive.ubuntu.com; then
print_success "Internet connectivity confirmed."
else
print_error "No internet connectivity. Please check your network."
@ -2791,6 +2813,7 @@ check_system() {
collect_config() {
print_section "Configuration Setup"
# --- Input Collection ---
while true; do
read -rp "$(printf '%s' "${CYAN}Enter username for new admin user: ${NC}")" USERNAME
if validate_username "$USERNAME"; then
@ -2809,46 +2832,74 @@ collect_config() {
if validate_hostname "$SERVER_NAME"; then break; else print_error "Invalid hostname."; fi
done
read -rp "$(printf '%s' "${CYAN}Enter a 'pretty' hostname (optional): ${NC}")" PRETTY_NAME
[[ -z "$PRETTY_NAME" ]] && PRETTY_NAME="$SERVER_NAME"
# --- SSH Port Detection ---
PREVIOUS_SSH_PORT=$(ss -tlpn | grep sshd | grep -oP ':\K\d+' | head -n 1)
local PROMPT_DEFAULT_PORT=${PREVIOUS_SSH_PORT:-2222}
[[ -z "$PRETTY_NAME" ]] && PRETTY_NAME="$SERVER_NAME"
while true; do
read -rp "$(printf '%s' "${CYAN}Enter custom SSH port (1024-65535) [$PROMPT_DEFAULT_PORT]: ${NC}")" SSH_PORT
SSH_PORT=${SSH_PORT:-$PROMPT_DEFAULT_PORT}
if validate_port "$SSH_PORT" || [[ -n "$PREVIOUS_SSH_PORT" && "$SSH_PORT" == "$PREVIOUS_SSH_PORT" ]]; then
break; else print_error "Invalid port. Choose a port between 1024-65535."; fi
done
SERVER_IP_V4=$(curl -4 -s https://ifconfig.me 2>/dev/null || echo "unknown")
SERVER_IP_V6=$(curl -6 -s https://ifconfig.me 2>/dev/null || echo "not available")
if [[ "$SERVER_IP_V4" != "unknown" ]]; then
print_info "Detected server IPv4: $SERVER_IP_V4"
fi
if [[ "$SERVER_IP_V6" != "not available" ]]; then
print_info "Detected server IPv6: $SERVER_IP_V6"
fi
printf '\n%s\n' "${YELLOW}Configuration Summary:${NC}"
printf " %-15s %s\n" "Username:" "$USERNAME"
printf " %-15s %s\n" "Hostname:" "$SERVER_NAME"
if [[ -n "$PREVIOUS_SSH_PORT" && "$SSH_PORT" != "$PREVIOUS_SSH_PORT" ]]; then
printf " %-15s %s (change from current: %s)\n" "SSH Port:" "$SSH_PORT" "$PREVIOUS_SSH_PORT"
# --- IP Detection ---
print_info "Detecting network configuration..."
# 1. Get the Local LAN IP (Explicit Check)
# This prevents crashing on IPv6-only servers
if ip -4 route get 8.8.8.8 >/dev/null 2>&1; then
LOCAL_IP_V4=$(ip -4 route get 8.8.8.8 | head -1 | awk '{print $7}')
else
printf " %-15s %s\n" "SSH Port:" "$SSH_PORT"
LOCAL_IP_V4=""
fi
# 2. Get Public IPs with timeouts
SERVER_IP_V4=$(curl -4 -s --connect-timeout 4 --max-time 5 https://ifconfig.me 2>/dev/null || \
curl -4 -s --connect-timeout 4 --max-time 5 https://ip.me 2>/dev/null || \
curl -4 -s --connect-timeout 4 --max-time 5 https://icanhazip.com 2>/dev/null || \
echo "Unknown")
if [[ "$SERVER_IP_V4" != "unknown" ]]; then
printf " %-15s %s\n" "Server IPv4:" "$SERVER_IP_V4"
SERVER_IP_V6=$(curl -6 -s --connect-timeout 4 --max-time 5 https://ifconfig.me 2>/dev/null || \
curl -6 -s --connect-timeout 4 --max-time 5 https://ip.me 2>/dev/null || \
curl -6 -s --connect-timeout 4 --max-time 5 https://icanhazip.com 2>/dev/null || \
echo "Not available")
# --- Display Summary ---
printf '\n%s\n' "${YELLOW}Configuration Summary:${NC}"
printf " %-22s %s\n" "Username:" "$USERNAME"
printf " %-22s %s\n" "Hostname:" "$SERVER_NAME"
if [[ -n "$PREVIOUS_SSH_PORT" && "$SSH_PORT" != "$PREVIOUS_SSH_PORT" ]]; then
printf " %-22s %s (change from current: %s)\n" "SSH Port:" "$SSH_PORT" "$PREVIOUS_SSH_PORT"
else
printf " %-22s %s\n" "SSH Port:" "$SSH_PORT"
fi
if [[ "$SERVER_IP_V6" != "not available" ]]; then
printf " %-15s %s\n" "Server IPv6:" "$SERVER_IP_V6"
# --- IP Display Logic ---
if [[ "$SERVER_IP_V4" != "Unknown" ]]; then
if [[ "$SERVER_IP_V4" == "$LOCAL_IP_V4" ]]; then
# 1: Direct Public IP
printf " %-22s %s (Direct)\n" "Server IPv4:" "$SERVER_IP_V4"
else
# 2: NAT
printf " %-22s %s (Internet)\n" "Public IPv4:" "$SERVER_IP_V4"
if [[ -n "$LOCAL_IP_V4" ]]; then
printf " %-22s %s (Internal)\n" "Local IPv4:" "$LOCAL_IP_V4"
fi
fi
else
# Fallback if public check failed
if [[ -n "$LOCAL_IP_V4" ]]; then
printf " %-22s %s (Local)\n" "Server IPv4:" "$LOCAL_IP_V4"
fi
fi
if [[ "$SERVER_IP_V6" != "Not available" ]]; then
printf " %-22s %s\n" "Public IPv6:" "$SERVER_IP_V6"
fi
if ! confirm $'\nContinue with this configuration?' "y"; then print_info "Exiting."; exit 0; fi
log "Configuration collected: USER=$USERNAME, HOST=$SERVER_NAME, PORT=$SSH_PORT, IPV4=$SERVER_IP_V4, IPV6=$SERVER_IP_V6"
log "Configuration collected: USER=$USERNAME, HOST=$SERVER_NAME, PORT=$SSH_PORT, IPV4=$SERVER_IP_V4, IPV6=$SERVER_IP_V6, LOCAL=$LOCAL_IP_V4"
}
install_packages() {
print_section "Package Installation"
print_info "Updating package lists and upgrading system..."
print_info "This may take a moment. Please wait..."
if ! apt-get update -qq || ! DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq; then
print_error "Failed to update or upgrade system packages."
exit 1
@ -2878,7 +2929,13 @@ setup_user() {
if [[ $USER_EXISTS == false ]]; then
print_info "Creating user '$USERNAME'..."
if ! adduser --disabled-password --gecos "" "$USERNAME"; then
# Check if group exists but user doesn't (common with 'admin' on Ubuntu)
local -a ADDUSER_OPTS=("--disabled-password" "--gecos" "")
if getent group "$USERNAME" >/dev/null 2>&1; then
print_warning "Group '$USERNAME' already exists. Attaching new user to this existing group."
ADDUSER_OPTS+=("--ingroup" "$USERNAME")
fi
if ! adduser "${ADDUSER_OPTS[@]}" "$USERNAME"; then
print_error "Failed to create user '$USERNAME'."
exit 1
fi
@ -3121,18 +3178,59 @@ configure_system() {
fi
print_info "Configuring hostname..."
# System Hostname
if [[ $(hostnamectl --static) != "$SERVER_NAME" ]]; then
hostnamectl set-hostname "$SERVER_NAME"
hostnamectl set-hostname "$PRETTY_NAME" --pretty
print_success "Hostname updated to: $SERVER_NAME"
else
print_info "Hostname is already set to $SERVER_NAME."
fi
if [[ -d /etc/cloud/cloud.cfg.d ]]; then
print_info "Disabling cloud-init host management via override file..."
echo "manage_etc_hosts: false" > /etc/cloud/cloud.cfg.d/99-du-setup-hosts.cfg
echo "preserve_hostname: true" >> /etc/cloud/cloud.cfg.d/99-du-setup-hosts.cfg
log "Created /etc/cloud/cloud.cfg.d/99-du-setup-hosts.cfg"
elif [[ -f /etc/cloud/cloud.cfg ]]; then
if grep -q "manage_etc_hosts: true" /etc/cloud/cloud.cfg; then
print_info "Disabling cloud-init 'manage_etc_hosts' in main config..."
sed -i 's/manage_etc_hosts: true/manage_etc_hosts: false/g' /etc/cloud/cloud.cfg
log "Disabled manage_etc_hosts in /etc/cloud/cloud.cfg"
fi
fi
# Stop cloud-init from overwriting /etc/hosts
local TEMPLATE_FILE=""
if [[ -n "$ID" && -f "/etc/cloud/templates/hosts.${ID}.tmpl" ]]; then
TEMPLATE_FILE="/etc/cloud/templates/hosts.${ID}.tmpl"
elif [[ -f "/etc/cloud/templates/hosts.tmpl" ]]; then
TEMPLATE_FILE="/etc/cloud/templates/hosts.tmpl"
fi
if [[ -n "$TEMPLATE_FILE" ]]; then
print_info "Patching cloud-init hosts template ($TEMPLATE_FILE) to enforce persistence..."
cp "$TEMPLATE_FILE" "$BACKUP_DIR/$(basename "$TEMPLATE_FILE").backup"
sed -i "s/^127.0.1.1.*/127.0.1.1\t$SERVER_NAME/g" "$TEMPLATE_FILE"
log "Hardcoded hostname into $TEMPLATE_FILE"
else
if [[ -d /etc/cloud/templates ]]; then
print_warning "Could not locate a standard hosts template in /etc/cloud/templates."
log "Warning: Cloud-init template patching skipped (no matching template found)."
fi
fi
if grep -q "^127.0.1.1" /etc/hosts; then
if ! grep -qE "^127.0.1.1[[:space:]]+$SERVER_NAME" /etc/hosts; then
sed -i "s/^127.0.1.1.*/127.0.1.1\t$SERVER_NAME/" /etc/hosts
print_success "Fixed /etc/hosts to map 127.0.1.1 to $SERVER_NAME."
else
print_info "/etc/hosts is already correct."
fi
else
echo "127.0.1.1 $SERVER_NAME" >> /etc/hosts
print_success "Added missing 127.0.1.1 entry to /etc/hosts."
fi
print_success "Hostname configured: $SERVER_NAME"
else
print_info "Hostname already set to $SERVER_NAME."
fi
log "System configuration completed."
}
@ -3148,9 +3246,7 @@ cleanup_and_exit() {
else
print_warning "Could not determine previous SSH port for firewall rollback."
fi
rollback_ssh_changes
if [[ $? -ne 0 ]]; then
if ! rollback_ssh_changes; then
print_error "Rollback failed. SSH may not be accessible. Please check 'systemctl status $SSH_SERVICE' and 'journalctl -u $SSH_SERVICE'."
fi
fi
@ -3216,14 +3312,65 @@ configure_ssh() {
fi
print_warning "SSH Key Authentication Required for Next Steps!"
printf '%s\n' "${CYAN}Test SSH access from a SEPARATE terminal now:${NC}"
if [[ -n "$SERVER_IP_V4" && "$SERVER_IP_V4" != "unknown" ]]; then
printf '%s\n' "${CYAN} Using IPv4: ssh -p $CURRENT_SSH_PORT $USERNAME@$SERVER_IP_V4${NC}"
printf '%s\n' "${CYAN}Test SSH access from a SEPARATE terminal now.${NC}"
# --- Connection Display Function ---
show_connection_options() {
local port="$1"
local public_ip="$2"
local TS_IP=""
if command -v tailscale >/dev/null 2>&1 && tailscale ip >/dev/null 2>&1; then
TS_IP=$(tailscale ip -4 2>/dev/null)
fi
if [[ -n "$SERVER_IP_V6" && "$SERVER_IP_V6" != "not available" ]]; then
printf '%s\n' "${CYAN} Using IPv6: ssh -p $CURRENT_SSH_PORT $USERNAME@$SERVER_IP_V6${NC}"
printf "\n"
# 1. Public IP (Internet)
# Only show if valid and not "Unknown"
if [[ -n "$public_ip" && "$public_ip" != "Unknown" ]]; then
printf " %-20s ${CYAN}ssh -p %s %s@%s${NC}\n" "Public (Internet):" "$port" "$USERNAME" "$public_ip"
fi
# 2. Internal/LAN IPs
# scan all interfaces. exclude the Public IP (already shown) and Loopback.
local found_internal=false
while read -r ip_addr; do
# Remove subnet mask if present
local clean_ip="${ip_addr%/*}"
# Skip if empty, loopback, or matches the Public IP we just displayed
if [[ -n "$clean_ip" && "$clean_ip" != "127.0.0.1" && "$clean_ip" != "$public_ip" ]]; then
printf " %-20s ${CYAN}ssh -p %s %s@%s${NC}\n" "Internal/Private:" "$port" "$USERNAME" "$clean_ip"
found_internal=true
fi
done < <(ip -4 -o addr show scope global | awk '{print $4}')
# Fallback: If we found NO internal IPs and NO Public IP (local VM offline?),
# show the detected local IP from route (Home VM scenario)
if [[ "$found_internal" == false && "$public_ip" == "Unknown" ]]; then
local fallback_ip
fallback_ip=$(ip -4 route get 8.8.8.8 2>/dev/null | head -1 | awk '{print $7}')
if [[ -n "$fallback_ip" ]]; then
printf " %-20s ${CYAN}ssh -p %s %s@%s${NC}\n" "Local (LAN):" "$port" "$USERNAME" "$fallback_ip"
fi
fi
# 3. IPv6
if [[ -n "$SERVER_IP_V6" && "$SERVER_IP_V6" != "Not available" ]]; then
printf " %-20s ${CYAN}ssh -p %s %s@%s${NC}\n" "IPv6:" "$port" "$USERNAME" "$SERVER_IP_V6"
fi
# 4. Tailscale IP (VPN)
if [[ -n "$TS_IP" ]]; then
printf " %-20s ${CYAN}ssh -p %s %s@%s${NC}\n" "Tailscale (VPN):" "$port" "$USERNAME" "$TS_IP"
fi
printf "\n"
}
# Show options for CURRENT port
show_connection_options "$CURRENT_SSH_PORT" "$SERVER_IP_V4"
if ! confirm "Can you successfully log in using your SSH key?"; then
print_error "SSH key authentication is mandatory to proceed."
return 1
@ -3298,12 +3445,9 @@ EOF
print_warning "CRITICAL: Test new SSH connection in a SEPARATE terminal NOW!"
print_warning "ACTION REQUIRED: Check your VPS provider's edge/network firewall to allow $SSH_PORT/tcp."
if [[ -n "$SERVER_IP_V4" && "$SERVER_IP_V4" != "unknown" ]]; then
print_info "Use IPv4: ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V4"
fi
if [[ -n "$SERVER_IP_V6" && "$SERVER_IP_V6" != "not available" ]]; then
print_info "Use IPv6: ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V6"
fi
# Show options for NEW port
show_connection_options "$SSH_PORT" "$SERVER_IP_V4"
# Retry loop for SSH connection test
local retry_count=0
@ -3656,21 +3800,37 @@ configure_fail2ban() {
local -a INVALID_IPS=()
local prompt_change=""
# NEW: Auto-detect and offer to whitelist current SSH connection
if [[ -n "$SSH_CONNECTION" ]]; then
local CURRENT_IP="${SSH_CONNECTION%% *}"
print_info "Detected SSH connection from: $CURRENT_IP"
# Auto-detect and offer to whitelist current SSH connection
local DETECTED_IP=""
if [[ -n "${SSH_CONNECTION:-}" ]]; then
DETECTED_IP="${SSH_CONNECTION%% *}"
fi
if [[ -z "$DETECTED_IP" ]]; then
local WHO_IP
WHO_IP=$(who -m 2>/dev/null | awk '{print $NF}' | tr -d '()')
if validate_ip_or_cidr "$WHO_IP"; then
DETECTED_IP="$WHO_IP"
fi
fi
if [[ -z "$DETECTED_IP" ]]; then
local SS_IP
SS_IP=$(ss -tnH state established '( dport = :22 or sport = :22 )' 2>/dev/null | head -n 1 | awk '{print $NF}' | cut -d: -f1 | cut -d] -f1)
if validate_ip_or_cidr "$SS_IP"; then
DETECTED_IP="$SS_IP"
fi
fi
if [[ -n "$DETECTED_IP" ]]; then
print_info "Detected SSH connection from: $DETECTED_IP"
if confirm "Whitelist your current IP ($CURRENT_IP) in Fail2Ban?"; then
if validate_ip_or_cidr "$CURRENT_IP"; then
IGNORE_IPS+=("$CURRENT_IP")
if confirm "Whitelist your current IP ($DETECTED_IP) in Fail2Ban?"; then
IGNORE_IPS+=("$DETECTED_IP")
print_success "Added your current IP to whitelist."
log "Auto-whitelisted SSH connection IP: $CURRENT_IP"
log "Auto-whitelisted SSH connection IP: $DETECTED_IP"
fi
prompt_change=" additional"
else
print_warning "Could not validate current IP. Please add it manually."
fi
fi
prompt_change=" additional" # Modifies following prompt based on presence of SSH connection.
print_warning "Could not auto-detect current SSH IP. (This is normal in some VM/sudo environments)"
print_info "You can manually add your IP in the next step."
fi
if [[ $VERBOSE != false ]] && \
@ -3936,8 +4096,9 @@ install_docker() {
apt-get remove -y -qq docker docker-engine docker.io containerd runc 2>/dev/null || true
print_info "Adding Docker's official GPG key and repository..."
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
curl -fsSL "https://download.docker.com/linux/${ID}/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# shellcheck source=/dev/null
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${ID} $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list
print_info "Installing Docker packages..."
if ! apt-get update -qq || ! apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin; then
@ -4401,7 +4562,7 @@ setup_backup() {
else
print_error "SSH connection test failed. Please ensure the key was copied correctly and the port is open."
print_info " - Copy key: ssh-copy-id -p \"$BACKUP_PORT\" -i \"$ROOT_SSH_KEY.pub\" $SSH_COPY_ID_FLAGS \"$BACKUP_DEST\""
print_info " - Check port: nc -zv $(echo \"$BACKUP_DEST\" | cut -d'@' -f2) \"$BACKUP_PORT\""
print_info " - Check port: nc -zv $(echo "$BACKUP_DEST" | cut -d'@' -f2) \"$BACKUP_PORT\""
print_info " - Ensure key is in ~/.ssh/authorized_keys on the backup server."
if [[ -n "$SSH_COPY_ID_FLAGS" ]]; then
print_info " - For Hetzner, ensure ~/.ssh/ exists: ssh -p \"$BACKUP_PORT\" \"$BACKUP_DEST\" \"mkdir -p ~/.ssh && chmod 700 ~/.ssh\""
@ -4724,148 +4885,229 @@ test_backup() {
}
configure_swap() {
if [[ $IS_CONTAINER == true ]]; then
if [[ "$IS_CONTAINER" == true ]]; then
print_info "Swap configuration skipped in container."
return 0
fi
print_section "Swap Configuration"
# Check for existing swap partition
# Check for existing swap partition entries in fstab
if lsblk -r | grep -q '\[SWAP\]'; then
print_warning "Existing swap partition found. Verify with 'lsblk -f'. Proceed with caution."
print_warning "Existing swap partition found on disk."
fi
local existing_swap
existing_swap=$(swapon --show --noheadings | awk '{print $1}' || true)
# Detect active swap
local existing_swap swap_type
existing_swap=$(swapon --show=NAME,TYPE,SIZE --noheadings --bytes 2>/dev/null | head -n 1 | awk '{print $1}' || true)
if [[ -n "$existing_swap" ]]; then
local current_size
current_size=$(du -h "$existing_swap" | awk '{print $1}')
print_info "Existing swap file found: $existing_swap ($current_size)"
if confirm "Modify existing swap file size?"; then
local SWAP_SIZE
while true; do
read -rp "$(printf '%s' "${CYAN}Enter new swap size (e.g., 2G, 512M) [current: $current_size]: ${NC}")" SWAP_SIZE
SWAP_SIZE=${SWAP_SIZE:-$current_size}
if validate_swap_size "$SWAP_SIZE"; then
break
swap_type=$(swapon --show=NAME,TYPE,SIZE --noheadings | head -n 1 | awk '{print $2}')
local display_size
display_size=$(du -h "$existing_swap" 2>/dev/null | awk '{print $1}' || echo "Unknown")
print_info "Existing swap detected: $existing_swap (Type: $swap_type, Size: $display_size)"
# --- Case 1: Partition detected ---
if [[ "$swap_type" == "partition" ]] || [[ "$existing_swap" =~ ^/dev/ ]]; then
print_warning "The detected swap is a disk partition, which cannot be resized by this script."
if confirm "Disable this swap partition and create a standard /swapfile instead?" "n"; then
print_info "Disabling swap partition $existing_swap..."
if ! swapoff "$existing_swap"; then
print_error "Failed to disable swap partition. Keeping it active."
return 0
fi
sed -i "s|^${existing_swap}[[:space:]]|#&|" /etc/fstab
local swap_uuid
swap_uuid=$(blkid -s UUID -o value "$existing_swap" 2>/dev/null || true)
if [[ -n "$swap_uuid" ]]; then
sed -i "s|^UUID=${swap_uuid}[[:space:]]|#&|" /etc/fstab
fi
print_success "Swap partition disabled and removed from fstab."
existing_swap=""
else
print_error "Invalid size. Use format like '2G' or '512M'."
print_info "Keeping existing swap partition."
configure_swap_settings
return 0
fi
else
# --- Case 2: Resize Existing Swap File ---
if confirm "Modify existing swap file size?"; then
local SWAP_SIZE REQUIRED_MB
while true; do
read -rp "$(printf '%s' "${CYAN}Enter new swap size (e.g., 2G, 512M) [current: $display_size]: ${NC}")" SWAP_SIZE
SWAP_SIZE=${SWAP_SIZE:-$display_size}
# 1. Validate Format
if ! REQUIRED_MB=$(convert_to_mb "$SWAP_SIZE"); then
continue
fi
# 2. Min Size
if (( REQUIRED_MB < 128 )); then
print_error "Swap size too small (minimum: 128M)."
continue
fi
# 3. Disk Space Check
local AVAILABLE_KB AVAILABLE_MB
AVAILABLE_KB=$(df -k / | tail -n 1 | awk '{print $4}')
AVAILABLE_MB=$((AVAILABLE_KB / 1024))
if (( AVAILABLE_MB < REQUIRED_MB )); then
print_error "Insufficient disk space. Required: ${REQUIRED_MB}MB, Available: ${AVAILABLE_MB}MB"
# Suggest Max Safe Size (80% of free space)
local MAX_SAFE_MB=$(( AVAILABLE_MB * 80 / 100 ))
if (( MAX_SAFE_MB >= 128 )); then
print_info "Suggested maximum: ${MAX_SAFE_MB}M (leaves 20% free space)"
fi
continue
fi
break
done
local REQUIRED_SPACE
REQUIRED_SPACE=$(convert_to_bytes "$SWAP_SIZE")
local AVAILABLE_SPACE
AVAILABLE_SPACE=$(df -k / | tail -n 1 | awk '{print $4}')
if (( AVAILABLE_SPACE < REQUIRED_SPACE / 1024 )); then
print_error "Insufficient disk space for $SWAP_SIZE swap file. Available: $((AVAILABLE_SPACE / 1024))MB"
exit 1
fi
print_info "Disabling existing swap file..."
swapoff "$existing_swap" || { print_error "Failed to disable swap file."; exit 1; }
print_info "Resizing swap file to $SWAP_SIZE..."
if ! fallocate -l "$SWAP_SIZE" "$existing_swap" || ! chmod 600 "$existing_swap" || ! mkswap "$existing_swap" || ! swapon "$existing_swap"; then
print_error "Failed to resize or enable swap file."
# Try fallocate, fallback to dd
if ! fallocate -l "$SWAP_SIZE" "$existing_swap" 2>/dev/null; then
print_warning "fallocate failed. Using dd (slower)..."
rm -f "$existing_swap"
local dd_status=""
if dd --version 2>&1 | grep -q "progress"; then dd_status="status=progress"; fi
if ! dd if=/dev/zero of="$existing_swap" bs=1M count="$REQUIRED_MB" $dd_status; then
print_error "Failed to create swap file with dd."
exit 1
fi
fi
if ! chmod 600 "$existing_swap" || ! mkswap "$existing_swap" >/dev/null || ! swapon "$existing_swap"; then
print_error "Failed to configure swap file."
exit 1
fi
print_success "Swap file resized to $SWAP_SIZE."
else
print_info "Keeping existing swap file."
fi
configure_swap_settings
return 0
fi
else
fi
# --- Case 3: Create New Swap ---
if [[ -z "$existing_swap" ]]; then
if ! confirm "Configure a swap file (recommended for < 4GB RAM)?"; then
print_info "Skipping swap configuration."
return 0
fi
local SWAP_SIZE
local SWAP_SIZE REQUIRED_MB
while true; do
read -rp "$(printf '%s' "${CYAN}Enter swap file size (e.g., 2G, 512M) [2G]: ${NC}")" SWAP_SIZE
SWAP_SIZE=${SWAP_SIZE:-2G}
if validate_swap_size "$SWAP_SIZE"; then
# 1. Validate Format
if ! REQUIRED_MB=$(convert_to_mb "$SWAP_SIZE"); then
continue
fi
# 2. Min Size
if (( REQUIRED_MB < 128 )); then
print_error "Swap size too small (minimum: 128M)."
continue
fi
# 3. Disk Space Check
local AVAILABLE_KB AVAILABLE_MB
AVAILABLE_KB=$(df -k / | tail -n 1 | awk '{print $4}')
AVAILABLE_MB=$((AVAILABLE_KB / 1024))
if (( AVAILABLE_MB < REQUIRED_MB )); then
print_error "Insufficient disk space. Required: ${REQUIRED_MB}MB, Available: ${AVAILABLE_MB}MB"
# Suggest Max Safe Size
local MAX_SAFE_MB=$(( AVAILABLE_MB * 80 / 100 ))
if (( MAX_SAFE_MB >= 128 )); then
print_info "Suggested maximum: ${MAX_SAFE_MB}M (leaves 20% free space)"
fi
continue
fi
break
else
print_error "Invalid size. Use format like '2G' or '512M'."
fi
done
local REQUIRED_SPACE
REQUIRED_SPACE=$(convert_to_bytes "$SWAP_SIZE")
local AVAILABLE_SPACE
AVAILABLE_SPACE=$(df -k / | tail -n 1 | awk '{print $4}')
if (( AVAILABLE_SPACE < REQUIRED_SPACE / 1024 )); then
print_error "Insufficient disk space for $SWAP_SIZE swap file. Available: $((AVAILABLE_SPACE / 1024))MB"
exit 1
fi
print_info "Creating $SWAP_SIZE swap file..."
if ! fallocate -l "$SWAP_SIZE" /swapfile || ! chmod 600 /swapfile || ! mkswap /swapfile || ! swapon /swapfile; then
print_error "Failed to create or enable swap file."
print_info "Creating $SWAP_SIZE swap file at /swapfile..."
if ! fallocate -l "$SWAP_SIZE" /swapfile 2>/dev/null; then
print_warning "fallocate failed. Using dd (slower)..."
local dd_status=""
if dd --version 2>&1 | grep -q "progress"; then dd_status="status=progress"; fi
if ! dd if=/dev/zero of=/swapfile bs=1M count="$REQUIRED_MB" $dd_status; then
print_error "Failed to create swap file."
rm -f /swapfile || true
exit 1
fi
# Check for existing swap entry in /etc/fstab to prevent duplicates
if grep -q '^/swapfile ' /etc/fstab; then
print_info "Swap entry already exists in /etc/fstab. Skipping."
else
fi
if ! chmod 600 /swapfile || ! mkswap /swapfile >/dev/null || ! swapon /swapfile; then
print_error "Failed to enable swap file."
rm -f /swapfile || true
exit 1
fi
if ! grep -q '^/swapfile ' /etc/fstab; then
echo '/swapfile none swap sw 0 0' >> /etc/fstab
print_success "Swap entry added to /etc/fstab."
else
print_info "Swap entry already exists in /etc/fstab."
fi
print_success "Swap file created: $SWAP_SIZE"
fi
configure_swap_settings
}
# Helper: Apply Sysctl Settings for Swap
configure_swap_settings() {
print_info "Configuring swap settings..."
local SWAPPINESS=10
local CACHE_PRESSURE=50
if confirm "Customize swap settings (vm.swappiness and vm.vfs_cache_pressure)?"; then
while true; do
read -rp "$(printf '%s' "${CYAN}Enter vm.swappiness (0-100) [default: $SWAPPINESS]: ${NC}")" INPUT_SWAPPINESS
INPUT_SWAPPINESS=${INPUT_SWAPPINESS:-$SWAPPINESS}
if [[ "$INPUT_SWAPPINESS" =~ ^[0-9]+$ && "$INPUT_SWAPPINESS" -ge 0 && "$INPUT_SWAPPINESS" -le 100 ]]; then
SWAPPINESS=$INPUT_SWAPPINESS
break
else
print_error "Invalid value for vm.swappiness. Must be between 0 and 100."
fi
read -rp "$(printf '%s' "${CYAN}Enter vm.swappiness (0-100) [default: $SWAPPINESS]: ${NC}")" INPUT
INPUT=${INPUT:-$SWAPPINESS}
if [[ "$INPUT" =~ ^[0-9]+$ && "$INPUT" -ge 0 && "$INPUT" -le 100 ]]; then SWAPPINESS=$INPUT; break; fi
print_error "Invalid value (0-100)."
done
while true; do
read -rp "$(printf '%s' "${CYAN}Enter vm.vfs_cache_pressure (1-1000) [default: $CACHE_PRESSURE]: ${NC}")" INPUT_CACHE_PRESSURE
INPUT_CACHE_PRESSURE=${INPUT_CACHE_PRESSURE:-$CACHE_PRESSURE}
if [[ "$INPUT_CACHE_PRESSURE" =~ ^[0-9]+$ && "$INPUT_CACHE_PRESSURE" -ge 1 && "$INPUT_CACHE_PRESSURE" -le 1000 ]]; then
CACHE_PRESSURE=$INPUT_CACHE_PRESSURE
break
else
print_error "Invalid value for vm.vfs_cache_pressure. Must be between 1 and 1000."
fi
read -rp "$(printf '%s' "${CYAN}Enter vm.vfs_cache_pressure (1-1000) [default: $CACHE_PRESSURE]: ${NC}")" INPUT
INPUT=${INPUT:-$CACHE_PRESSURE}
if [[ "$INPUT" =~ ^[0-9]+$ && "$INPUT" -ge 1 && "$INPUT" -le 1000 ]]; then CACHE_PRESSURE=$INPUT; break; fi
print_error "Invalid value (1-1000)."
done
else
print_info "Using default swap settings (vm.swappiness=$SWAPPINESS, vm.vfs_cache_pressure=$CACHE_PRESSURE)."
fi
local NEW_SWAP_CONFIG
NEW_SWAP_CONFIG=$(mktemp)
tee "$NEW_SWAP_CONFIG" > /dev/null <<EOF
vm.swappiness=$SWAPPINESS
vm.vfs_cache_pressure=$CACHE_PRESSURE
EOF
# Check if sysctl settings are already correct to prevent duplicates
if [[ -f /etc/sysctl.d/99-swap.conf ]] && cmp -s "$NEW_SWAP_CONFIG" /etc/sysctl.d/99-swap.conf; then
print_info "Swap settings already correct in /etc/sysctl.d/99-swap.conf. Skipping."
print_info "Swap settings already correct. Skipping."
rm -f "$NEW_SWAP_CONFIG"
else
# Check for conflicting settings in /etc/sysctl.conf or other sysctl files
local sysctl_conflicts=false
# Scan for conflicts
for file in /etc/sysctl.conf /etc/sysctl.d/*.conf; do
if [[ -f "$file" && "$file" != "/etc/sysctl.d/99-swap.conf" ]]; then
if grep -E '^(vm\.swappiness|vm\.vfs_cache_pressure)=' "$file" >/dev/null; then
print_warning "Existing swap settings found in $file. Manual review recommended."
sysctl_conflicts=true
fi
fi
[[ -f "$file" && "$file" != "/etc/sysctl.d/99-swap.conf" ]] && \
grep -E '^(vm\.swappiness|vm\.vfs_cache_pressure)=' "$file" >/dev/null && \
print_warning "Note: Duplicate swap settings found in $file."
done
mv "$NEW_SWAP_CONFIG" /etc/sysctl.d/99-swap.conf
chmod 644 /etc/sysctl.d/99-swap.conf
sysctl -p /etc/sysctl.d/99-swap.conf >/dev/null
if [[ $sysctl_conflicts == true ]]; then
print_warning "Potential conflicting sysctl settings detected. Verify with 'sysctl -a | grep -E \"vm\.swappiness|vm\.vfs_cache_pressure\"'."
else
print_success "Swap settings applied to /etc/sysctl.d/99-swap.conf."
fi
fi
print_success "Swap configured successfully."
swapon --show | tee -a "$LOG_FILE"
free -h | tee -a "$LOG_FILE"
log "Swap configuration completed."
@ -4929,6 +5171,7 @@ configure_security_audit() {
fi
# Check if system is Debian before running debsecan
# shellcheck source=/dev/null
source /etc/os-release
if [[ "$ID" == "debian" ]]; then
if confirm "Also run debsecan to check for package vulnerabilities?"; then
@ -4963,16 +5206,30 @@ configure_security_audit() {
}
final_cleanup() {
print_section "Final System Cleanup"
print_info "Running final system update and cleanup..."
if ! apt-get update -qq; then
print_section "Final System Update & Cleanup"
print_info "Performing final system upgrade (dist-upgrade) and cleanup..."
print_info "This may take a moment. Please wait..."
# Upgrade ALL packages (including kernels)
if ! apt-get update -qq >/dev/null 2>&1; then
print_warning "Failed to update package lists during final cleanup."
log "Final apt-get update failed."
fi
if ! apt-get upgrade -y -qq || ! apt-get --purge autoremove -y -qq || ! apt-get autoclean -y -qq; then
print_warning "Final system cleanup failed on one or more commands."
if ! DEBIAN_FRONTEND=noninteractive apt-get dist-upgrade -y -qq -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" >> "$LOG_FILE" 2>&1; then
print_warning "Final system upgrade encountered issues. Check log for details."
log "Final apt-get dist-upgrade failed."
else
print_success "System packages (including kernels) upgraded successfully."
log "Final apt-get dist-upgrade completed."
fi
# Final cleanup
print_info "Removing unused packages..."
if ! apt-get --purge autoremove -y -qq >> "$LOG_FILE" 2>&1 || ! apt-get autoclean -y -qq >> "$LOG_FILE" 2>&1; then
print_warning "Cleanup commands encountered minor issues."
else
print_success "Unused packages removed."
fi
systemctl daemon-reload
print_success "Final system update and cleanup complete."
print_success "Final cleanup complete."
log "Final system cleanup completed."
}
@ -5040,10 +5297,10 @@ generate_summary() {
printf " %-15s %s\n" "Admin User:" "$USERNAME"
printf " %-15s %s\n" "Hostname:" "$SERVER_NAME"
printf " %-15s %s\n" "SSH Port:" "$SSH_PORT"
if [[ "$SERVER_IP_V4" != "unknown" ]]; then
if [[ "${SERVER_IP_V4:-}" != "unknown" && "${SERVER_IP_V4:-}" != "Unknown" ]]; then
printf " %-15s %s\n" "Server IPv4:" "$SERVER_IP_V4"
fi
if [[ "$SERVER_IP_V6" != "not available" ]]; then
if [[ "${SERVER_IP_V6:-}" != "not available" && "${SERVER_IP_V6:-}" != "Not available" ]]; then
printf " %-15s %s\n" "Server IPv6:" "$SERVER_IP_V6"
fi
@ -5123,28 +5380,73 @@ generate_summary() {
fi
printf '\n'
print_separator "Environment Information"
# --- System & Environment Information ---
print_separator "System & Environment Information"
# OS and Kernel Info
printf "%-20s %s\n" "OS:" "${PRETTY_NAME:-Unknown}"
printf "%-20s %s\n" "Kernel:" "$(uname -r)"
printf "%-20s %s\n" "Uptime:" "$(uptime -p 2>/dev/null || uptime | sed 's/.*up //;s/,.*//')"
# Hardware/Virtualization Info
printf "%-20s %s\n" "Virtualization:" "${DETECTED_VIRT_TYPE:-unknown}"
printf "%-20s %s\n" "Manufacturer:" "${DETECTED_MANUFACTURER:-unknown}"
printf "%-20s %s\n" "Product:" "${DETECTED_PRODUCT:-unknown}"
if [[ "$IS_CLOUD_PROVIDER" == "true" ]]; then
printf "%-20s %s\n" "Environment:" "${YELLOW}Cloud VPS${NC}"
elif [[ "$DETECTED_VIRT_TYPE" == "none" ]]; then
printf "%-20s %s\n" "Environment:" "${GREEN}Bare Metal${NC}"
else
printf "%-20s %s\n" "Environment:" "${CYAN}Personal VM${NC}"
if [[ "${DETECTED_MANUFACTURER:-unknown}" != "unknown" ]]; then
printf "%-20s %s\n" "Manufacturer:" "$DETECTED_MANUFACTURER"
fi
if [[ "${DETECTED_PRODUCT:-unknown}" != "unknown" ]]; then
printf "%-20s %s\n" "Product:" "$DETECTED_PRODUCT"
fi
if [[ "${DETECTED_BIOS_VENDOR:-unknown}" != "unknown" ]]; then
printf "%-20s %s\n" "BIOS Vendor:" "$DETECTED_BIOS_VENDOR"
fi
# Environment Classification
printf "%-20s " "Environment:"
case "$ENVIRONMENT_TYPE" in
commercial-cloud) printf "%sCloud VPS%s\n" "$YELLOW" "$NC" ;;
bare-metal) printf "%sBare Metal%s\n" "$GREEN" "$NC" ;;
uncertain-kvm) printf "%sGeneric KVM (Likely Cloud VPS)%s\n" "$YELLOW" "$NC" ;;
personal-vm) printf "%sPersonal VM%s\n" "$CYAN" "$NC" ;;
*) printf "Unknown\n" ;;
esac
if [[ -n "$DETECTED_PROVIDER_NAME" ]]; then
printf "%-20s %s\n" "Detected Provider:" "$DETECTED_PROVIDER_NAME"
fi
printf '\n'
# --- Post-Reboot Verification Steps ---
print_separator "Post-Reboot Verification Steps:"
printf ' - SSH access:\n'
if [[ "$SERVER_IP_V4" != "unknown" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- Using IPv4:" "ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V4"
# 1. Public Access
if [[ "${SERVER_IP_V4:-}" != "unknown" && "${SERVER_IP_V4:-}" != "Unknown" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- Public (Internet):" "ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V4"
fi
if [[ "$SERVER_IP_V6" != "not available" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- Using IPv6:" "ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V6"
# 2. Local Access
if [[ -n "${LOCAL_IP_V4:-}" ]]; then
# Show local if public is unknown OR if they are different IPs
if [[ "${SERVER_IP_V4:-}" == "Unknown" || "${SERVER_IP_V4:-}" == "unknown" || "${LOCAL_IP_V4:-}" != "${SERVER_IP_V4:-}" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- Local (LAN):" "ssh -p $SSH_PORT $USERNAME@$LOCAL_IP_V4"
fi
fi
# 3. Tailscale Access
if [[ -f /tmp/tailscale_ips.txt ]]; then
local TS_SUMMARY_IP
TS_SUMMARY_IP=$(grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' /tmp/tailscale_ips.txt | head -n 1)
if [[ -n "$TS_SUMMARY_IP" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- Tailscale (VPN):" "ssh -p $SSH_PORT $USERNAME@$TS_SUMMARY_IP"
fi
fi
# 4. IPv6 Access
if [[ "${SERVER_IP_V6:-}" != "not available" && "${SERVER_IP_V6:-}" != "Not available" ]]; then
printf " %-26s ${CYAN}%s${NC}\n" "- IPv6:" "ssh -p $SSH_PORT $USERNAME@$SERVER_IP_V6"
fi
# Other verification commands
printf " %-28s ${CYAN}%s${NC}\n" "- Firewall rules:" "sudo ufw status verbose"
printf " %-28s ${CYAN}%s${NC}\n" "- Time sync:" "chronyc tracking"
printf " %-28s ${CYAN}%s${NC}\n" "- Fail2Ban sshd jail:" "sudo fail2ban-client status sshd"
@ -5224,9 +5526,9 @@ main() {
# --- PRELIMINARY CHECKS ---
print_header
check_dependencies
check_system
run_update_check
check_dependencies
# --- HANDLE SPECIAL OPERATIONAL MODES ---
if [[ "$CLEANUP_ONLY" == "true" ]]; then

View File

@ -1 +1 @@
3ea3416cf0916ea92c382e1c899de0ef2f29535c9c93d1fa7466b4a78e1bb26d du_setup.sh
d7378f43082166cad26c7b232f3cd01e778a3377dea996c3f434e0f483317659 du_setup.sh