2025-06-26 16:22:23 +01:00
|
|
|
|
#!/bin/bash
|
|
|
|
|
|
|
2025-06-26 21:49:51 +01:00
|
|
|
|
# Debian 12 and Ubuntu Server Hardening Interactive Script
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# Version: 0.62 | 2025-08-04
|
2025-06-28 11:43:10 +01:00
|
|
|
|
# Changelog:
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# - v0.62: Added modular execution with interactive menu and --tasks flag
|
2025-08-03 23:05:44 +01:00
|
|
|
|
# - v0.61: Display Lynis suggestions in summary, hide tailscale auth key, cleanup temp files
|
2025-07-15 19:20:26 +01:00
|
|
|
|
# - v0.60: CI for shellcheck
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# - v0.59: Added sysctl security settings and self-update check
|
|
|
|
|
|
# - v0.58: Improved fail2ban to parse ufw logs
|
|
|
|
|
|
# - v0.57: Fixed silent failure in test_backup()
|
|
|
|
|
|
# - v0.56: Made tailscale config optional
|
|
|
|
|
|
# - v0.55: Improved setup_user() with ssh-keygen options
|
|
|
|
|
|
# - v0.54: Enhanced rollback_ssh_changes() for Ubuntu
|
|
|
|
|
|
# - v0.53: Fixed test_backup() for non-root sudo users
|
|
|
|
|
|
# - v0.52: Added SSH rollback for Ubuntu 24.10
|
|
|
|
|
|
# - v0.51: Corrected repo links
|
|
|
|
|
|
# - v0.50: Versioning format change
|
|
|
|
|
|
# - v4.3: Added SHA256 integrity verification
|
|
|
|
|
|
# - v4.2: Added Lynis and debsecan, backup testing
|
|
|
|
|
|
# - v4.1: Added Tailscale configuration
|
|
|
|
|
|
# - v4.0: Added automated backup configuration
|
2025-06-26 16:22:23 +01:00
|
|
|
|
#
|
2025-06-26 21:49:51 +01:00
|
|
|
|
# Description:
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# Provisions and hardens a fresh Debian 12 or Ubuntu server with security configurations,
|
|
|
|
|
|
# user management, SSH hardening, firewall, and optional features like Docker, Tailscale,
|
|
|
|
|
|
# and backups. Supports modular execution via --tasks or interactive menu.
|
2025-06-26 21:49:51 +01:00
|
|
|
|
#
|
|
|
|
|
|
# Usage:
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# sudo -E ./du_setup.sh [--quiet] [--tasks=<task1,task2,...]
|
2025-06-26 21:49:51 +01:00
|
|
|
|
#
|
|
|
|
|
|
# Options:
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --quiet: Suppress non-critical output
|
|
|
|
|
|
# --tasks: Comma-separated tasks (e.g., ssh,firewall,swap)
|
2025-06-26 21:49:51 +01:00
|
|
|
|
#
|
|
|
|
|
|
# Notes:
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# - Run as root on Debian 12 or Ubuntu 20.04/22.04/24.04.
|
|
|
|
|
|
# - Logs to /var/log/du_setup_*.log, backups to /root/setup_harden_backup_*.
|
|
|
|
|
|
# - Test in a VM before production use.
|
2025-06-26 16:22:23 +01:00
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
set -euo pipefail
|
2025-06-26 16:22:23 +01:00
|
|
|
|
|
2025-07-15 15:13:58 +01:00
|
|
|
|
# --- Update Configuration ---
|
2025-08-03 23:05:44 +01:00
|
|
|
|
CURRENT_VERSION="0.61"
|
2025-07-15 15:13:58 +01:00
|
|
|
|
SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh"
|
|
|
|
|
|
CHECKSUM_URL="${SCRIPT_URL}.sha256"
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Global Variables ---
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
|
LOG_FILE="/var/log/du_setup_$(date +%Y%m%d_%H%M%S).log"
|
|
|
|
|
|
BACKUP_LOG="/var/log/backup_rsync.log"
|
|
|
|
|
|
REPORT_FILE="/var/log/du_setup_report_$(date +%Y%m%d_%H%M%S).txt"
|
|
|
|
|
|
VERBOSE=true
|
|
|
|
|
|
BACKUP_DIR="/root/setup_harden_backup_$(date +%Y%m%d_%H%M%S)"
|
|
|
|
|
|
IS_CONTAINER=false
|
|
|
|
|
|
SSHD_BACKUP_FILE=""
|
|
|
|
|
|
LOCAL_KEY_ADDED=false
|
|
|
|
|
|
SSH_SERVICE=""
|
|
|
|
|
|
ID=""
|
|
|
|
|
|
FAILED_SERVICES=()
|
|
|
|
|
|
SELECTED_TASKS=()
|
|
|
|
|
|
|
|
|
|
|
|
# --- Parse Arguments ---
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
|
case $1 in
|
|
|
|
|
|
--quiet) VERBOSE=false; shift ;;
|
|
|
|
|
|
--tasks=*)
|
|
|
|
|
|
IFS=',' read -r -a SELECTED_TASKS <<< "${1#*=}"
|
|
|
|
|
|
shift
|
|
|
|
|
|
;;
|
|
|
|
|
|
*) shift ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
2025-06-26 16:22:23 +01:00
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Colors for Output ---
|
2025-07-02 20:22:10 +01:00
|
|
|
|
if command -v tput >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1; then
|
|
|
|
|
|
RED=$(tput setaf 1)
|
|
|
|
|
|
GREEN=$(tput setaf 2)
|
|
|
|
|
|
YELLOW="$(tput bold)$(tput setaf 3)"
|
|
|
|
|
|
BLUE=$(tput setaf 4)
|
|
|
|
|
|
PURPLE=$(tput setaf 5)
|
|
|
|
|
|
CYAN=$(tput setaf 6)
|
|
|
|
|
|
BOLD=$(tput bold)
|
|
|
|
|
|
NC=$(tput sgr0)
|
|
|
|
|
|
else
|
|
|
|
|
|
RED='\033[0;31m'
|
|
|
|
|
|
GREEN='\033[0;32m'
|
|
|
|
|
|
YELLOW='\033[1;33m'
|
|
|
|
|
|
BLUE='\033[0;34m'
|
|
|
|
|
|
PURPLE='\033[0;35m'
|
|
|
|
|
|
CYAN='\033[0;36m'
|
|
|
|
|
|
NC='\033[0m'
|
|
|
|
|
|
BOLD=''
|
|
|
|
|
|
fi
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Logging & Print Functions ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
log() {
|
|
|
|
|
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_header() {
|
|
|
|
|
|
[[ $VERBOSE == false ]] && return
|
|
|
|
|
|
echo -e "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}"
|
2025-06-26 22:19:35 +01:00
|
|
|
|
echo -e "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${CYAN}║ v0.21 | 2025-08-43 ║${NC}"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
echo -e "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}"
|
|
|
|
|
|
echo
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_section() {
|
|
|
|
|
|
[[ $VERBOSE == false ]] && return
|
|
|
|
|
|
echo -e "\n${BLUE}▓▓▓ $1 ▓▓▓${NC}" | tee -a "$LOG_FILE"
|
|
|
|
|
|
echo -e "${BLUE}$(printf '═%.0s' {1..65})${NC}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_success() {
|
|
|
|
|
|
[[ $VERBOSE == false ]] && return
|
|
|
|
|
|
echo -e "${GREEN}✓ $1${NC}" | tee -a "$LOG_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_error() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${RED}✗ $1${NC}" | tee -a "$LOG_FILE"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_warning() {
|
|
|
|
|
|
[[ $VERBOSE == false ]] && return
|
|
|
|
|
|
echo -e "${YELLOW}⚠ $1${NC}" | tee -a "$LOG_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
print_info() {
|
|
|
|
|
|
[[ $VERBOSE == false ]] && return
|
|
|
|
|
|
echo -e "${PURPLE}ℹ $1${NC}" | tee -a "$LOG_FILE"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- User Interaction ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
confirm() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
local prompt="$1" default="${2:-n}" response
|
2025-06-26 16:22:23 +01:00
|
|
|
|
[[ $VERBOSE == false ]] && return 0
|
2025-08-04 23:14:32 +01:00
|
|
|
|
prompt="$prompt [${default^^}/$( [[ $default == "y" ]] && echo "n" || echo "y" )]: "
|
2025-06-26 16:22:23 +01:00
|
|
|
|
while true; do
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}$prompt${NC}")" response
|
|
|
|
|
|
response=${response,,}
|
2025-08-04 23:14:32 +01:00
|
|
|
|
response=${response:-$default}
|
2025-06-26 16:22:23 +01:00
|
|
|
|
case $response in
|
|
|
|
|
|
y|yes) return 0 ;;
|
|
|
|
|
|
n|no) return 1 ;;
|
|
|
|
|
|
*) echo -e "${RED}Please answer yes or no.${NC}" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Validation Functions ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
validate_username() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "$1" =~ ^[a-z_][a-z0-9_-]*$ && ${#1} -le 32 ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_hostname() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "$1" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]{0,253}[a-zA-Z0-9]$ && ! "$1" =~ \.\. ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_port() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "$1" =~ ^[0-9]+$ && "$1" -ge 1024 && "$1" -le 65535 ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-28 12:24:23 +01:00
|
|
|
|
validate_backup_port() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "$1" =~ ^[0-9]+$ && "$1" -ge 1 && "$1" -le 65535 ]]
|
2025-06-28 12:24:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-26 16:22:23 +01:00
|
|
|
|
validate_ssh_key() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ -n "$1" && "$1" =~ ^(ssh-rsa|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521|ssh-ed25519)\ ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_timezone() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ -e "/usr/share/zoneinfo/$1" ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_swap_size() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "${1^^}" =~ ^[0-9]+[MG]$ && "${1%[MG]}" -ge 1 ]]
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_ufw_port() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
[[ "$1" =~ ^[0-9]+(/tcp|/udp)?$ ]]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
validate_cron_schedule() {
|
|
|
|
|
|
local schedule="$1" temp_cron
|
|
|
|
|
|
temp_cron=$(mktemp)
|
|
|
|
|
|
echo "$schedule /bin/true" > "$temp_cron"
|
|
|
|
|
|
if crontab -u root "$temp_cron" 2>/dev/null; then
|
|
|
|
|
|
rm -f "$temp_cron"
|
|
|
|
|
|
return 0
|
|
|
|
|
|
else
|
|
|
|
|
|
rm -f "$temp_cron"
|
|
|
|
|
|
return 1
|
|
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
convert_to_bytes() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
local size_upper="${1^^}" unit="${size_upper: -1}" value="${size_upper%[MG]}"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
if [[ "$unit" == "G" ]]; then
|
|
|
|
|
|
echo $((value * 1024 * 1024 * 1024))
|
|
|
|
|
|
elif [[ "$unit" == "M" ]]; then
|
|
|
|
|
|
echo $((value * 1024 * 1024))
|
|
|
|
|
|
else
|
|
|
|
|
|
echo 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Task Selection Function ---
|
|
|
|
|
|
select_tasks_interactive() {
|
|
|
|
|
|
print_section "Task Selection"
|
|
|
|
|
|
echo -e "${CYAN}Select tasks to run (use numbers, comma-separated, e.g., 1,3,5):${NC}"
|
|
|
|
|
|
echo -e "${YELLOW}Available tasks:${NC}"
|
|
|
|
|
|
echo -e " ${BOLD}1)${NC} Update Check - Check for script updates"
|
|
|
|
|
|
echo -e " ${BOLD}2)${NC} Dependency Check - Ensure required tools are installed"
|
|
|
|
|
|
echo -e " ${BOLD}3)${NC} System Check - Verify OS compatibility and root privileges"
|
|
|
|
|
|
echo -e " ${BOLD}4)${NC} Configuration Collection - Set up username, hostname, SSH port"
|
|
|
|
|
|
echo -e " ${BOLD}5)${NC} Package Installation - Install essential packages"
|
|
|
|
|
|
echo -e " ${BOLD}6)${NC} User Setup - Create admin user and configure SSH keys"
|
|
|
|
|
|
echo -e " ${BOLD}7)${NC} System Configuration - Set timezone, hostname, and locales"
|
|
|
|
|
|
echo -e " ${BOLD}8)${NC} SSH Hardening - Configure SSH with key-based auth and custom port"
|
|
|
|
|
|
echo -e " ${BOLD}9)${NC} Firewall Setup - Configure UFW with custom rules"
|
|
|
|
|
|
echo -e " ${BOLD}10)${NC} Fail2Ban Setup - Configure Fail2Ban for intrusion prevention"
|
|
|
|
|
|
echo -e " ${BOLD}11)${NC} Auto Updates - Enable unattended-upgrades"
|
|
|
|
|
|
echo -e " ${BOLD}12)${NC} Time Synchronization - Configure chrony for NTP"
|
|
|
|
|
|
echo -e " ${BOLD}13)${NC} Kernel Hardening - Apply sysctl security settings"
|
|
|
|
|
|
echo -e " ${BOLD}14)${NC} Docker Installation - Install Docker (optional)"
|
|
|
|
|
|
echo -e " ${BOLD}15)${NC} Tailscale Installation - Set up Tailscale VPN (optional)"
|
|
|
|
|
|
echo -e " ${BOLD}16)${NC} Backup Configuration - Set up rsync-based backups"
|
|
|
|
|
|
echo -e " ${BOLD}17)${NC} Swap Configuration - Configure swap file"
|
|
|
|
|
|
echo -e " ${BOLD}18)${NC} Security Audit - Run Lynis and debsecan (Debian only)"
|
|
|
|
|
|
echo -e " ${BOLD}19)${NC} Final Cleanup - Update system and clean up"
|
|
|
|
|
|
echo -e " ${BOLD}20)${NC} Generate Summary - Create final report"
|
|
|
|
|
|
echo -e "${CYAN}Enter numbers (e.g., 1,3,5) or 'all' for all tasks:${NC}"
|
|
|
|
|
|
read -rp " " task_choices
|
|
|
|
|
|
if [[ "$task_choices" == "all" ]]; then
|
|
|
|
|
|
SELECTED_TASKS=("update" "deps" "system" "config" "packages" "user" "system_config" "ssh" "firewall" "fail2ban" "auto_updates" "time_sync" "kernel" "docker" "tailscale" "backup" "swap" "audit" "cleanup" "summary")
|
|
|
|
|
|
else
|
|
|
|
|
|
IFS=',' read -r -a task_array <<< "$task_choices"
|
|
|
|
|
|
SELECTED_TASKS=()
|
|
|
|
|
|
for task_num in "${task_array[@]}"; do
|
|
|
|
|
|
case $task_num in
|
|
|
|
|
|
1) SELECTED_TASKS+=("update") ;;
|
|
|
|
|
|
2) SELECTED_TASKS+=("deps") ;;
|
|
|
|
|
|
3) SELECTED_TASKS+=("system") ;;
|
|
|
|
|
|
4) SELECTED_TASKS+=("config") ;;
|
|
|
|
|
|
5) SELECTED_TASKS+=("packages") ;;
|
|
|
|
|
|
6) SELECTED_TASKS+=("user") ;;
|
|
|
|
|
|
7) SELECTED_TASKS+=("system_config") ;;
|
|
|
|
|
|
8) SELECTED_TASKS+=("ssh") ;;
|
|
|
|
|
|
9) SELECTED_TASKS+=("firewall") ;;
|
|
|
|
|
|
10) SELECTED_TASKS+=("fail2ban") ;;
|
|
|
|
|
|
11) SELECTED_TASKS+=("auto_updates") ;;
|
|
|
|
|
|
12) SELECTED_TASKS+=("time_sync") ;;
|
|
|
|
|
|
13) SELECTED_TASKS+=("kernel") ;;
|
|
|
|
|
|
14) SELECTED_TASKS+=("docker") ;;
|
|
|
|
|
|
15) SELECTED_TASKS+=("tailscale") ;;
|
|
|
|
|
|
16) SELECTED_TASKS+=("backup") ;;
|
|
|
|
|
|
17) SELECTED_TASKS+=("swap") ;;
|
|
|
|
|
|
18) SELECTED_TASKS+=("audit") ;;
|
|
|
|
|
|
19) SELECTED_TASKS+=("cleanup") ;;
|
|
|
|
|
|
20) SELECTED_TASKS+=("summary") ;;
|
|
|
|
|
|
*) print_error "Invalid task number: $task_num. Skipping." ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ ${#SELECTED_TASKS[@]} -eq 0 ]]; then
|
|
|
|
|
|
print_error "No valid tasks selected. Exiting."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Selected tasks: ${SELECTED_TASKS[*]}"
|
|
|
|
|
|
log "Selected tasks for execution: ${SELECTED_TASKS[*]}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# --- Update Check ---
|
2025-07-15 15:13:58 +01:00
|
|
|
|
run_update_check() {
|
|
|
|
|
|
print_section "Checking for Script Updates"
|
|
|
|
|
|
local latest_version
|
|
|
|
|
|
if ! latest_version=$(curl -sL "$SCRIPT_URL" | grep '^CURRENT_VERSION=' | head -n 1 | awk -F'"' '{print $2}'); then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Could not check for updates. Check internet connection."
|
|
|
|
|
|
log "Update check failed: Could not fetch script."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
return
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -z "$latest_version" ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Failed to parse version from remote script."
|
|
|
|
|
|
log "Update check failed: Could not parse version."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
return
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if [[ "$(printf '%s\n' "$CURRENT_VERSION" "$latest_version" | sort -V | head -n 1)" == "$CURRENT_VERSION" && "$CURRENT_VERSION" != "$latest_version" ]]; then
|
2025-07-15 15:13:58 +01:00
|
|
|
|
print_success "A new version ($latest_version) is available!"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! confirm "Update to version $latest_version now?"; then
|
2025-07-15 15:13:58 +01:00
|
|
|
|
return
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
local temp_dir=$(mktemp -d)
|
2025-07-15 15:13:58 +01:00
|
|
|
|
trap 'rm -rf -- "$temp_dir"' EXIT
|
2025-08-04 23:14:32 +01:00
|
|
|
|
local temp_script="$temp_dir/du_setup.sh" temp_checksum="$temp_dir/checksum.sha256"
|
2025-07-15 15:13:58 +01:00
|
|
|
|
print_info "Downloading new script version..."
|
|
|
|
|
|
if ! curl -sL "$SCRIPT_URL" -o "$temp_script"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Failed to download new script. Update aborted."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Downloading checksum..."
|
|
|
|
|
|
if ! curl -sL "$CHECKSUM_URL" -o "$temp_checksum"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Failed to download checksum file. Update aborted."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Verifying checksum..."
|
|
|
|
|
|
if ! (cd "$temp_dir" && sha256sum -c "checksum.sha256" --quiet); then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Checksum verification failed. Update aborted."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Checking script syntax..."
|
|
|
|
|
|
if ! bash -n "$temp_script"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Downloaded script has syntax error. Update aborted."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! mv "$temp_script" "$0" || ! chmod +x "$0"; then
|
|
|
|
|
|
print_error "Failed to replace script file."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
trap - EXIT
|
|
|
|
|
|
rm -rf -- "$temp_dir"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Update successful. Please rerun the script."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
exit 0
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Running latest version ($CURRENT_VERSION)."
|
2025-07-15 15:13:58 +01:00
|
|
|
|
fi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Dependency Check ---
|
2025-06-26 21:49:51 +01:00
|
|
|
|
check_dependencies() {
|
|
|
|
|
|
print_section "Checking Dependencies"
|
|
|
|
|
|
local missing_deps=()
|
2025-08-04 23:14:32 +01:00
|
|
|
|
for dep in curl sudo gpg coreutils; do
|
|
|
|
|
|
command -v "$dep" >/dev/null || missing_deps+=("$dep")
|
|
|
|
|
|
done
|
2025-06-26 21:49:51 +01:00
|
|
|
|
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
|
|
|
|
|
print_info "Installing missing dependencies: ${missing_deps[*]}"
|
|
|
|
|
|
if ! apt-get update -qq || ! apt-get install -y -qq "${missing_deps[@]}"; then
|
|
|
|
|
|
print_error "Failed to install dependencies: ${missing_deps[*]}"
|
|
|
|
|
|
exit 1
|
2025-06-26 17:12:59 +01:00
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "All dependencies installed."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
log "Dependency check completed."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- System Check ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
check_system() {
|
|
|
|
|
|
print_section "System Compatibility Check"
|
|
|
|
|
|
if [[ $(id -u) -ne 0 ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "This script must be run as root."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_success "Running with root privileges."
|
|
|
|
|
|
if [[ -f /proc/1/cgroup ]] && grep -qE '(docker|lxc|kubepod)' /proc/1/cgroup; then
|
|
|
|
|
|
IS_CONTAINER=true
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Container environment detected. Some features will be skipped."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
if [[ -f /etc/os-release ]]; then
|
|
|
|
|
|
source /etc/os-release
|
2025-08-04 23:14:32 +01:00
|
|
|
|
ID=$ID
|
2025-06-26 16:22:23 +01:00
|
|
|
|
if [[ $ID == "debian" && $VERSION_ID == "12" ]] || \
|
2025-06-26 21:49:51 +01:00
|
|
|
|
[[ $ID == "ubuntu" && $VERSION_ID =~ ^(20.04|22.04|24.04)$ ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Compatible OS: $PRETTY_NAME"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Untested OS: $PRETTY_NAME. Designed for Debian 12 or Ubuntu 20.04/22.04/24.04."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
if ! confirm "Continue anyway?"; then exit 1; fi
|
|
|
|
|
|
fi
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Not a Debian or Ubuntu system."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
if ! dpkg -l openssh-server | grep -q ^ii; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "openssh-server not installed. Will be installed."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
if curl -s --head https://deb.debian.org >/dev/null || curl -s --head https://archive.ubuntu.com >/dev/null; then
|
|
|
|
|
|
print_success "Internet connectivity confirmed."
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "No internet connectivity."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ ! -w /var/log ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Cannot write to /var/log."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-06-26 21:49:51 +01:00
|
|
|
|
if [[ ! -w /etc/shadow ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "/etc/shadow not writable."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if [[ $(stat -c %a /etc/shadow) != "640" ]]; then
|
2025-06-26 16:22:23 +01:00
|
|
|
|
chmod 640 /etc/shadow
|
|
|
|
|
|
chown root:shadow /etc/shadow
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Fixed /etc/shadow permissions to 640."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
log "System check completed."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Configuration Collection ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
collect_config() {
|
|
|
|
|
|
print_section "Configuration Setup"
|
2025-06-26 21:49:51 +01:00
|
|
|
|
while true; do
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter username for new admin user: ${NC}")" USERNAME
|
|
|
|
|
|
if validate_username "$USERNAME"; then
|
|
|
|
|
|
if id "$USERNAME" &>/dev/null; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "User '$USERNAME' exists."
|
|
|
|
|
|
if confirm "Use existing user?"; then USER_EXISTS=true; break; fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
else
|
2025-06-26 21:49:51 +01:00
|
|
|
|
USER_EXISTS=false; break
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-26 21:49:51 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Invalid username (lowercase, numbers, hyphens, underscores, max 32 chars)."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-26 21:49:51 +01:00
|
|
|
|
done
|
|
|
|
|
|
while true; do
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter server hostname: ${NC}")" SERVER_NAME
|
|
|
|
|
|
if validate_hostname "$SERVER_NAME"; then break; else print_error "Invalid hostname."; fi
|
|
|
|
|
|
done
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter pretty hostname (optional): ${NC}")" PRETTY_NAME
|
|
|
|
|
|
PRETTY_NAME=${PRETTY_NAME:-$SERVER_NAME}
|
2025-06-26 21:49:51 +01:00
|
|
|
|
while true; do
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter custom SSH port (1024-65535) [2222]: ${NC}")" SSH_PORT
|
|
|
|
|
|
SSH_PORT=${SSH_PORT:-2222}
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if validate_port "$SSH_PORT"; then break; else print_error "Invalid port."; fi
|
2025-06-26 21:49:51 +01:00
|
|
|
|
done
|
2025-06-26 16:22:23 +01:00
|
|
|
|
SERVER_IP=$(curl -s https://ifconfig.me 2>/dev/null || echo "unknown")
|
|
|
|
|
|
print_info "Detected server IP: $SERVER_IP"
|
|
|
|
|
|
echo -e "\n${YELLOW}Configuration Summary:${NC}"
|
2025-06-26 22:19:35 +01:00
|
|
|
|
echo -e " Username: $USERNAME"
|
|
|
|
|
|
echo -e " Hostname: $SERVER_NAME"
|
|
|
|
|
|
echo -e " SSH Port: $SSH_PORT"
|
|
|
|
|
|
echo -e " Server IP: $SERVER_IP"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! confirm "Continue with this configuration?" "y"; then exit 0; fi
|
|
|
|
|
|
log "Configuration: USER=$USERNAME, HOST=$SERVER_NAME, PORT=$SSH_PORT"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Package Installation ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
install_packages() {
|
|
|
|
|
|
print_section "Package Installation"
|
|
|
|
|
|
print_info "Updating package lists and upgrading system..."
|
|
|
|
|
|
if ! apt-get update -qq || ! DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Failed to update/upgrade packages."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Installing essential packages..."
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! apt-get install -y -qq ufw fail2ban unattended-upgrades chrony rsync wget vim htop iotop nethogs netcat-traditional ncdu tree rsyslog cron jq gawk coreutils perl skopeo git openssh-client openssh-server; then
|
|
|
|
|
|
print_error "Failed to install packages."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Packages installed."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
log "Package installation completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- User Setup ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
setup_user() {
|
|
|
|
|
|
print_section "User Management"
|
2025-07-02 17:25:17 +01:00
|
|
|
|
if [[ -z "$USERNAME" ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "USERNAME not set."
|
2025-07-02 17:25:17 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
if [[ $USER_EXISTS == false ]]; then
|
|
|
|
|
|
print_info "Creating user '$USERNAME'..."
|
|
|
|
|
|
if ! adduser --disabled-password --gecos "" "$USERNAME"; then
|
|
|
|
|
|
print_error "Failed to create user '$USERNAME'."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
while true; do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -sp "$(echo -e "${CYAN}New password for '$USERNAME' (Enter twice to skip): ${NC}")" PASS1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
echo
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -sp "$(echo -e "${CYAN}Retype password: ${NC}")" PASS2
|
2025-06-26 16:22:23 +01:00
|
|
|
|
echo
|
|
|
|
|
|
if [[ -z "$PASS1" && -z "$PASS2" ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Password skipped. Using SSH key only."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
break
|
|
|
|
|
|
elif [[ "$PASS1" == "$PASS2" ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if echo "$USERNAME:$PASS1" | chpasswd; then
|
|
|
|
|
|
print_success "Password set."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
break
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Failed to set password."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Passwords do not match."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
done
|
|
|
|
|
|
USER_HOME=$(getent passwd "$USERNAME" | cut -d: -f6)
|
|
|
|
|
|
SSH_DIR="$USER_HOME/.ssh"
|
|
|
|
|
|
AUTH_KEYS="$SSH_DIR/authorized_keys"
|
2025-07-02 16:35:44 +01:00
|
|
|
|
if [[ ! -w "$USER_HOME" ]]; then
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$USER_HOME"
|
|
|
|
|
|
chmod 700 "$USER_HOME"
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Add SSH public key(s)?"; then
|
2025-07-02 16:35:44 +01:00
|
|
|
|
while true; do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Paste SSH public key: ${NC}")" SSH_PUBLIC_KEY
|
2025-07-02 16:35:44 +01:00
|
|
|
|
if validate_ssh_key "$SSH_PUBLIC_KEY"; then
|
|
|
|
|
|
mkdir -p "$SSH_DIR"
|
|
|
|
|
|
chmod 700 "$SSH_DIR"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$SSH_DIR"
|
|
|
|
|
|
echo "$SSH_PUBLIC_KEY" >> "$AUTH_KEYS"
|
|
|
|
|
|
awk '!seen[$0]++' "$AUTH_KEYS" > "$AUTH_KEYS.tmp" && mv "$AUTH_KEYS.tmp" "$AUTH_KEYS"
|
|
|
|
|
|
chmod 600 "$AUTH_KEYS"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$AUTH_KEYS"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "SSH key added."
|
2025-07-02 16:35:44 +01:00
|
|
|
|
LOCAL_KEY_ADDED=true
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Invalid SSH key format."
|
2025-07-02 16:35:44 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! confirm "Add another key?" "n"; then break; fi
|
2025-07-02 16:35:44 +01:00
|
|
|
|
done
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Generating SSH key pair..."
|
2025-07-02 16:35:44 +01:00
|
|
|
|
mkdir -p "$SSH_DIR"
|
|
|
|
|
|
chmod 700 "$SSH_DIR"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$SSH_DIR"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
sudo -u "$USERNAME" ssh-keygen -t ed25519 -f "$SSH_DIR/id_ed25519_user" -N "" -q
|
2025-07-02 22:09:11 +01:00
|
|
|
|
cat "$SSH_DIR/id_ed25519_user.pub" >> "$AUTH_KEYS"
|
2025-07-02 16:35:44 +01:00
|
|
|
|
chmod 600 "$AUTH_KEYS"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$AUTH_KEYS"
|
2025-07-02 17:25:17 +01:00
|
|
|
|
TEMP_KEY_FILE="/tmp/${USERNAME}_ssh_key_$(date +%s)"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
trap 'rm -f "$TEMP_KEY_FILE"' EXIT
|
2025-07-02 22:09:11 +01:00
|
|
|
|
cp "$SSH_DIR/id_ed25519_user" "$TEMP_KEY_FILE"
|
2025-07-02 16:35:44 +01:00
|
|
|
|
chmod 600 "$TEMP_KEY_FILE"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${YELLOW}Save PRIVATE key to ~/.ssh/${USERNAME}_key:${NC}"
|
2025-07-02 16:35:44 +01:00
|
|
|
|
cat "$TEMP_KEY_FILE"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${CYAN}PUBLIC key:${NC}"
|
2025-07-02 22:09:11 +01:00
|
|
|
|
cat "$SSH_DIR/id_ed25519_user.pub"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${CYAN}Set permissions: chmod 600 ~/.ssh/${USERNAME}_key${NC}"
|
|
|
|
|
|
echo -e "${CYAN}Connect with: ssh -i ~/.ssh/${USERNAME}_key -p $SSH_PORT $USERNAME@$SERVER_IP${NC}"
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Press Enter after saving keys...${NC}")"
|
|
|
|
|
|
print_success "SSH key generated."
|
2025-07-02 16:35:44 +01:00
|
|
|
|
LOCAL_KEY_ADDED=true
|
|
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
else
|
|
|
|
|
|
print_info "Using existing user: $USERNAME"
|
|
|
|
|
|
USER_HOME=$(getent passwd "$USERNAME" | cut -d: -f6)
|
2025-08-04 23:14:32 +01:00
|
|
|
|
AUTH_KEYS="$USER_HOME/.ssh/authorized_keys"
|
|
|
|
|
|
if [[ ! -s "$AUTH_KEYS" ]]; then
|
|
|
|
|
|
print_warning "No valid SSH keys found in $AUTH_KEYS."
|
2025-07-02 16:35:44 +01:00
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
if ! groups "$USERNAME" | grep -qw sudo; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
usermod -aG sudo "$USERNAME"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
print_success "User added to sudo group."
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "User already in sudo group."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
log "User setup completed."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- System Configuration ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
configure_system() {
|
|
|
|
|
|
print_section "System Configuration"
|
|
|
|
|
|
mkdir -p "$BACKUP_DIR" && chmod 700 "$BACKUP_DIR"
|
2025-06-26 21:49:51 +01:00
|
|
|
|
cp /etc/hosts "$BACKUP_DIR/hosts.backup"
|
|
|
|
|
|
cp /etc/fstab "$BACKUP_DIR/fstab.backup"
|
|
|
|
|
|
cp /etc/sysctl.conf "$BACKUP_DIR/sysctl.conf.backup" 2>/dev/null || true
|
|
|
|
|
|
while true; do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter timezone (e.g., Europe/London) [Etc/UTC]: ${NC}")" TIMEZONE
|
2025-06-26 21:49:51 +01:00
|
|
|
|
TIMEZONE=${TIMEZONE:-Etc/UTC}
|
|
|
|
|
|
if validate_timezone "$TIMEZONE"; then
|
|
|
|
|
|
if [[ $(timedatectl status | grep "Time zone" | awk '{print $3}') != "$TIMEZONE" ]]; then
|
|
|
|
|
|
timedatectl set-timezone "$TIMEZONE"
|
|
|
|
|
|
print_success "Timezone set to $TIMEZONE."
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Timezone already set."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
fi
|
|
|
|
|
|
break
|
|
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Invalid timezone."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
fi
|
|
|
|
|
|
done
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Configure locales interactively?"; then
|
2025-06-26 16:22:23 +01:00
|
|
|
|
dpkg-reconfigure locales
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ $(hostnamectl --static) != "$SERVER_NAME" ]]; then
|
|
|
|
|
|
hostnamectl set-hostname "$SERVER_NAME"
|
|
|
|
|
|
hostnamectl set-hostname "$PRETTY_NAME" --pretty
|
|
|
|
|
|
if grep -q "^127.0.1.1" /etc/hosts; then
|
|
|
|
|
|
sed -i "s/^127.0.1.1.*/127.0.1.1\t$SERVER_NAME/" /etc/hosts
|
|
|
|
|
|
else
|
|
|
|
|
|
echo "127.0.1.1 $SERVER_NAME" >> /etc/hosts
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Hostname set to $SERVER_NAME."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Hostname already set."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
log "System configuration completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- SSH Hardening ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
configure_ssh() {
|
2025-06-30 21:10:35 +01:00
|
|
|
|
trap cleanup_and_exit ERR
|
2025-06-26 16:22:23 +01:00
|
|
|
|
print_section "SSH Hardening"
|
|
|
|
|
|
if ! dpkg -l openssh-server | grep -q ^ii; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "openssh-server not installed."
|
2025-06-30 21:10:35 +01:00
|
|
|
|
return 1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-27 00:54:10 +01:00
|
|
|
|
if [[ $ID == "ubuntu" ]] && systemctl is-active ssh.socket >/dev/null 2>&1; then
|
|
|
|
|
|
SSH_SERVICE="ssh.socket"
|
2025-06-26 21:49:51 +01:00
|
|
|
|
elif systemctl is-enabled sshd.service >/dev/null 2>&1 || systemctl is-active sshd.service >/dev/null 2>&1; then
|
|
|
|
|
|
SSH_SERVICE="sshd.service"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
SSH_SERVICE="ssh.service"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-30 21:10:35 +01:00
|
|
|
|
PREVIOUS_SSH_PORT=$(ss -tuln | grep -E ":(22|.*$SSH_SERVICE.*)" | awk '{print $5}' | cut -d':' -f2 | head -n1 || echo "22")
|
2025-06-26 16:22:23 +01:00
|
|
|
|
USER_HOME=$(getent passwd "$USERNAME" | cut -d: -f6)
|
2025-08-04 23:14:32 +01:00
|
|
|
|
AUTH_KEYS="$USER_HOME/.ssh/authorized_keys"
|
|
|
|
|
|
if [[ $LOCAL_KEY_ADDED == false && ! -s "$AUTH_KEYS" ]]; then
|
|
|
|
|
|
print_info "Generating SSH key..."
|
|
|
|
|
|
mkdir -p "$USER_HOME/.ssh"
|
|
|
|
|
|
chmod 700 "$USER_HOME/.ssh"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$USER_HOME/.ssh"
|
|
|
|
|
|
sudo -u "$USERNAME" ssh-keygen -t ed25519 -f "$USER_HOME/.ssh/id_ed25519" -N "" -q
|
|
|
|
|
|
cat "$USER_HOME/.ssh/id_ed25519.pub" >> "$AUTH_KEYS"
|
|
|
|
|
|
chmod 600 "$AUTH_KEYS"
|
|
|
|
|
|
chown "$USERNAME:$USERNAME" "$AUTH_KEYS"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
print_success "SSH key generated."
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo -e "${YELLOW}Public key:${NC}"
|
|
|
|
|
|
cat "$USER_HOME/.ssh/id_ed25519.pub"
|
|
|
|
|
|
LOCAL_KEY_ADDED=true
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Test SSH: ssh -p $PREVIOUS_SSH_PORT $USERNAME@$SERVER_IP"
|
|
|
|
|
|
if ! confirm "SSH connection successful?"; then
|
|
|
|
|
|
print_error "SSH key authentication required."
|
2025-06-30 21:10:35 +01:00
|
|
|
|
return 1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
|
|
|
|
|
SSHD_BACKUP_FILE="$BACKUP_DIR/sshd_config.backup_$(date +%Y%m%d_%H%M%S)"
|
2025-06-26 21:49:51 +01:00
|
|
|
|
cp /etc/ssh/sshd_config "$SSHD_BACKUP_FILE"
|
2025-06-30 17:23:39 +01:00
|
|
|
|
if [[ $ID == "ubuntu" ]] && dpkg --compare-versions "$(lsb_release -rs)" ge "24.04"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
sed -i "s/^Port .*/Port $SSH_PORT/" /etc/ssh/sshd_config || echo "Port $SSH_PORT" >> /etc/ssh/sshd_config
|
2025-06-30 21:10:35 +01:00
|
|
|
|
elif [[ "$SSH_SERVICE" == "ssh.socket" ]]; then
|
|
|
|
|
|
mkdir -p /etc/systemd/system/ssh.socket.d
|
|
|
|
|
|
echo -e "[Socket]\nListenStream=\nListenStream=$SSH_PORT" > /etc/systemd/system/ssh.socket.d/override.conf
|
2025-06-30 17:23:39 +01:00
|
|
|
|
else
|
2025-06-30 21:10:35 +01:00
|
|
|
|
mkdir -p /etc/systemd/system/${SSH_SERVICE}.d
|
|
|
|
|
|
echo -e "[Service]\nExecStart=\nExecStart=/usr/sbin/sshd -D -p $SSH_PORT" > /etc/systemd/system/${SSH_SERVICE}.d/override.conf
|
2025-06-27 19:23:28 +01:00
|
|
|
|
fi
|
2025-06-30 21:10:35 +01:00
|
|
|
|
mkdir -p /etc/ssh/sshd_config.d
|
|
|
|
|
|
tee /etc/ssh/sshd_config.d/99-hardening.conf > /dev/null <<EOF
|
2025-06-26 16:22:23 +01:00
|
|
|
|
PermitRootLogin no
|
|
|
|
|
|
PasswordAuthentication no
|
|
|
|
|
|
PubkeyAuthentication yes
|
|
|
|
|
|
MaxAuthTries 3
|
|
|
|
|
|
ClientAliveInterval 300
|
|
|
|
|
|
X11Forwarding no
|
|
|
|
|
|
PrintMotd no
|
|
|
|
|
|
Banner /etc/issue.net
|
|
|
|
|
|
EOF
|
2025-06-30 21:10:35 +01:00
|
|
|
|
tee /etc/issue.net > /dev/null <<'EOF'
|
2025-06-26 16:22:23 +01:00
|
|
|
|
******************************************************************************
|
2025-06-27 15:50:40 +01:00
|
|
|
|
🔒AUTHORIZED ACCESS ONLY
|
2025-06-30 19:22:31 +01:00
|
|
|
|
════ all attempts are logged and reviewed ════
|
2025-06-26 16:22:23 +01:00
|
|
|
|
******************************************************************************
|
|
|
|
|
|
EOF
|
2025-06-27 00:54:10 +01:00
|
|
|
|
systemctl daemon-reload
|
2025-06-30 21:10:35 +01:00
|
|
|
|
systemctl restart "$SSH_SERVICE"
|
2025-06-27 00:54:10 +01:00
|
|
|
|
sleep 5
|
|
|
|
|
|
if ! ss -tuln | grep -q ":$SSH_PORT"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "SSH not listening on port $SSH_PORT."
|
|
|
|
|
|
rollback_ssh_changes
|
2025-06-30 21:10:35 +01:00
|
|
|
|
return 1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-30 21:10:35 +01:00
|
|
|
|
if ssh -p "$SSH_PORT" -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@localhost true 2>/dev/null; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Root SSH login still possible."
|
|
|
|
|
|
rollback_ssh_changes
|
2025-06-30 21:10:35 +01:00
|
|
|
|
return 1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Test new SSH: ssh -p $SSH_PORT $USERNAME@$SERVER_IP"
|
|
|
|
|
|
for ((i=1; i<=3; i++)); do
|
|
|
|
|
|
if confirm "New SSH connection successful?"; then
|
|
|
|
|
|
print_success "SSH hardening completed."
|
|
|
|
|
|
trap - ERR
|
|
|
|
|
|
log "SSH hardening completed."
|
|
|
|
|
|
return 0
|
2025-06-27 00:36:26 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Retry $i/3..."
|
|
|
|
|
|
sleep 5
|
2025-06-27 00:36:26 +01:00
|
|
|
|
done
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "SSH connection failed. Rolling back..."
|
|
|
|
|
|
rollback_ssh_changes
|
|
|
|
|
|
return 1
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- SSH Rollback ---
|
2025-06-30 17:23:39 +01:00
|
|
|
|
rollback_ssh_changes() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Rolling back SSH to port $PREVIOUS_SSH_PORT..."
|
|
|
|
|
|
rm -rf /etc/systemd/system/${SSH_SERVICE}.d /etc/systemd/system/ssh.socket.d /etc/ssh/sshd_config.d/99-hardening.conf 2>/dev/null
|
2025-06-30 19:36:22 +01:00
|
|
|
|
if [[ -f "$SSHD_BACKUP_FILE" ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
cp "$SSHD_BACKUP_FILE" /etc/ssh/sshd_config
|
|
|
|
|
|
print_info "Restored sshd_config."
|
2025-06-30 19:36:22 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Backup file $SSHD_BACKUP_FILE not found."
|
|
|
|
|
|
return 1
|
2025-06-30 19:36:22 +01:00
|
|
|
|
fi
|
2025-07-02 13:48:34 +01:00
|
|
|
|
if ! /usr/sbin/sshd -t >/tmp/sshd_config_test.log 2>&1; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Restored sshd_config invalid. Check /tmp/sshd_config_test.log."
|
|
|
|
|
|
return 1
|
2025-07-02 13:48:34 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
systemctl daemon-reload
|
|
|
|
|
|
if [[ "$SSH_SERVICE" == "ssh.socket" ]]; then
|
|
|
|
|
|
systemctl stop ssh.socket 2>/dev/null
|
|
|
|
|
|
systemctl restart ssh.service
|
|
|
|
|
|
systemctl restart ssh.socket
|
2025-07-02 13:48:34 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
systemctl restart "$SSH_SERVICE"
|
2025-07-02 13:48:34 +01:00
|
|
|
|
fi
|
2025-06-30 21:10:35 +01:00
|
|
|
|
for ((i=1; i<=10; i++)); do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ss -tuln | grep -q ":$PREVIOUS_SSH_PORT"; then
|
|
|
|
|
|
print_success "Rollback successful."
|
|
|
|
|
|
return 0
|
2025-06-30 21:10:35 +01:00
|
|
|
|
fi
|
2025-07-02 13:48:34 +01:00
|
|
|
|
sleep 3
|
2025-06-30 21:10:35 +01:00
|
|
|
|
done
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Rollback failed. Check systemctl status $SSH_SERVICE."
|
|
|
|
|
|
return 1
|
2025-06-30 17:23:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Firewall Configuration ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
configure_firewall() {
|
|
|
|
|
|
print_section "Firewall Configuration (UFW)"
|
|
|
|
|
|
if ufw status | grep -q "Status: active"; then
|
|
|
|
|
|
print_info "UFW already enabled."
|
|
|
|
|
|
else
|
|
|
|
|
|
ufw default deny incoming
|
|
|
|
|
|
ufw default allow outgoing
|
|
|
|
|
|
fi
|
|
|
|
|
|
if ! ufw status | grep -qw "$SSH_PORT/tcp"; then
|
|
|
|
|
|
ufw allow "$SSH_PORT"/tcp comment 'Custom SSH'
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "SSH rule added for port $SSH_PORT."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Allow HTTP (port 80)?"; then
|
|
|
|
|
|
ufw allow http comment 'HTTP'
|
|
|
|
|
|
print_success "HTTP allowed."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Allow HTTPS (port 443)?"; then
|
|
|
|
|
|
ufw allow https comment 'HTTPS'
|
|
|
|
|
|
print_success "HTTPS allowed."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Allow Tailscale (UDP 41641)?"; then
|
|
|
|
|
|
ufw allow 41641/udp comment 'Tailscale VPN'
|
|
|
|
|
|
print_success "Tailscale allowed."
|
2025-06-29 13:40:03 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if confirm "Add custom ports?"; then
|
2025-06-26 21:49:51 +01:00
|
|
|
|
while true; do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter ports (e.g., 8080/tcp 123/udp): ${NC}")" CUSTOM_PORTS
|
|
|
|
|
|
if [[ -z "$CUSTOM_PORTS" ]]; then break; fi
|
2025-06-26 22:19:35 +01:00
|
|
|
|
local valid=true
|
2025-06-26 21:49:51 +01:00
|
|
|
|
for port in $CUSTOM_PORTS; do
|
|
|
|
|
|
if ! validate_ufw_port "$port"; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Invalid port: $port."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
valid=false
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-06-26 21:49:51 +01:00
|
|
|
|
done
|
|
|
|
|
|
if [[ "$valid" == true ]]; then
|
|
|
|
|
|
for port in $CUSTOM_PORTS; do
|
2025-08-04 23:14:32 +01:00
|
|
|
|
ufw allow "$port" comment "Custom port $port"
|
|
|
|
|
|
print_success "Added rule for $port."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
done
|
2025-06-26 21:49:51 +01:00
|
|
|
|
break
|
|
|
|
|
|
fi
|
|
|
|
|
|
done
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! ufw status | grep -q "Status: active"; then
|
|
|
|
|
|
ufw --force enable
|
|
|
|
|
|
print_success "UFW enabled."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Firewall configured."
|
|
|
|
|
|
ufw status | tee -a "$LOG_FILE"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
log "Firewall configuration completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Fail2Ban Configuration ---
|
2025-06-28 12:51:55 +01:00
|
|
|
|
configure_fail2ban() {
|
|
|
|
|
|
print_section "Fail2Ban Configuration"
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if systemctl is-active --quiet fail2ban; then
|
|
|
|
|
|
print_info "Fail2Ban already active."
|
|
|
|
|
|
else
|
|
|
|
|
|
systemctl enable --now fail2ban
|
|
|
|
|
|
fi
|
|
|
|
|
|
if ! [[ -f /etc/fail2ban/jail.d/sshd.conf ]]; then
|
|
|
|
|
|
tee /etc/fail2ban/jail.d/sshd.conf > /dev/null <<EOF
|
2025-06-28 12:51:55 +01:00
|
|
|
|
[sshd]
|
|
|
|
|
|
enabled = true
|
2025-07-07 21:08:21 +01:00
|
|
|
|
port = $SSH_PORT
|
2025-08-04 23:14:32 +01:00
|
|
|
|
maxretry = 5
|
|
|
|
|
|
bantime = 3600
|
|
|
|
|
|
findtime = 600
|
|
|
|
|
|
EOF
|
|
|
|
|
|
print_success "Fail2Ban SSH jail configured."
|
|
|
|
|
|
fi
|
|
|
|
|
|
if ! [[ -f /etc/fail2ban/jail.d/ufw-probes.conf ]]; then
|
|
|
|
|
|
tee /etc/fail2ban/jail.d/ufw-probes.conf > /dev/null <<EOF
|
2025-07-07 21:08:21 +01:00
|
|
|
|
[ufw-probes]
|
|
|
|
|
|
enabled = true
|
2025-08-04 23:14:32 +01:00
|
|
|
|
banaction = ufw
|
|
|
|
|
|
port = 0:65535
|
|
|
|
|
|
filter = ufw
|
2025-07-07 21:08:21 +01:00
|
|
|
|
logpath = /var/log/ufw.log
|
2025-08-04 23:14:32 +01:00
|
|
|
|
maxretry = 5
|
|
|
|
|
|
bantime = 3600
|
|
|
|
|
|
findtime = 600
|
2025-06-28 12:51:55 +01:00
|
|
|
|
EOF
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Fail2Ban UFW jail configured."
|
2025-07-07 21:22:47 +01:00
|
|
|
|
fi
|
2025-06-28 12:51:55 +01:00
|
|
|
|
systemctl restart fail2ban
|
|
|
|
|
|
if systemctl is-active --quiet fail2ban; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Fail2Ban configured."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Fail2Ban service failed."
|
|
|
|
|
|
exit 1
|
2025-06-28 12:51:55 +01:00
|
|
|
|
fi
|
|
|
|
|
|
log "Fail2Ban configuration completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Auto Updates ---
|
2025-06-28 12:51:55 +01:00
|
|
|
|
configure_auto_updates() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Automatic Updates"
|
|
|
|
|
|
if [[ -f /etc/apt/apt.conf.d/50unattended-upgrades ]]; then
|
|
|
|
|
|
print_info "Unattended-upgrades already configured."
|
|
|
|
|
|
else
|
|
|
|
|
|
tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null <<EOF
|
|
|
|
|
|
Unattended-Upgrades::Allowed-Origins {
|
|
|
|
|
|
"${ID} ${VERSION_CODENAME}:security";
|
|
|
|
|
|
"${ID} ${VERSION_CODENAME}-security";
|
|
|
|
|
|
};
|
|
|
|
|
|
Unattended-Upgrades::Package-Blacklist { };
|
|
|
|
|
|
Unattended-Upgrades::AutoFixInterruptedDpkg "true";
|
|
|
|
|
|
Unattended-Upgrades::MinimalSteps "true";
|
|
|
|
|
|
Unattended-Upgrades::InstallOnShutdown "false";
|
|
|
|
|
|
Unattended-Upgrades::Remove-Unused-Dependencies "true";
|
|
|
|
|
|
Unattended-Upgrades::Automatic-Reboot "false";
|
|
|
|
|
|
EOF
|
|
|
|
|
|
tee /etc/apt/apt.conf.d/20auto-upgrades > /dev/null <<EOF
|
|
|
|
|
|
APT::Periodic::Update-Package-Lists "1";
|
|
|
|
|
|
APT::Periodic::Unattended-Upgrade "1";
|
|
|
|
|
|
APT::Periodic::AutocleanInterval "7";
|
|
|
|
|
|
EOF
|
|
|
|
|
|
systemctl enable --now unattended-upgrades
|
|
|
|
|
|
print_success "Unattended-upgrades configured."
|
|
|
|
|
|
fi
|
|
|
|
|
|
if systemctl is-active --quiet unattended-upgrades; then
|
|
|
|
|
|
print_success "Automatic updates enabled."
|
|
|
|
|
|
else
|
|
|
|
|
|
print_error "Unattended-upgrades failed."
|
|
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
log "Automatic updates configured."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# --- Time Synchronization ---
|
|
|
|
|
|
configure_time_sync() {
|
|
|
|
|
|
print_section "Time Synchronization"
|
|
|
|
|
|
systemctl enable --now chrony
|
|
|
|
|
|
sleep 2
|
|
|
|
|
|
if systemctl is-active --quiet chrony; then
|
|
|
|
|
|
print_success "Chrony active."
|
|
|
|
|
|
chronyc tracking | tee -a "$LOG_FILE"
|
2025-06-28 12:51:55 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Chrony failed to start."
|
|
|
|
|
|
exit 1
|
2025-06-28 12:51:55 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
log "Time synchronization completed."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Kernel Hardening ---
|
2025-07-15 13:51:34 +01:00
|
|
|
|
configure_kernel_hardening() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Kernel Hardening"
|
|
|
|
|
|
if [[ -f /etc/sysctl.d/99-du-hardening.conf ]]; then
|
|
|
|
|
|
print_info "Kernel hardening already applied."
|
2025-07-15 13:51:34 +01:00
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
tee /etc/sysctl.d/99-du-hardening.conf > /dev/null <<EOF
|
|
|
|
|
|
kernel.unprivileged_bpf_disabled=1
|
|
|
|
|
|
kernel.yama.ptrace_scope=1
|
|
|
|
|
|
fs.protected_symlinks=1
|
|
|
|
|
|
fs.protected_hardlinks=1
|
|
|
|
|
|
kernel.core_pattern=|/bin/false
|
|
|
|
|
|
kernel.modules_disabled=0
|
|
|
|
|
|
kernel.randomize_va_space=2
|
|
|
|
|
|
kernel.dmesg_restrict=1
|
|
|
|
|
|
kernel.perf_event_paranoid=2
|
2025-07-15 13:51:34 +01:00
|
|
|
|
net.ipv4.conf.all.accept_redirects=0
|
|
|
|
|
|
net.ipv4.conf.default.accept_redirects=0
|
2025-08-04 23:14:32 +01:00
|
|
|
|
net.ipv4.conf.all.send_redirects=0
|
|
|
|
|
|
net.ipv4.conf.default.send_redirects=0
|
|
|
|
|
|
net.ipv4.tcp_syncookies=1
|
|
|
|
|
|
net.ipv4.tcp_rfc1337=1
|
|
|
|
|
|
net.ipv4.icmp_echo_ignore_broadcasts=1
|
2025-07-15 13:51:34 +01:00
|
|
|
|
net.ipv6.conf.all.accept_redirects=0
|
|
|
|
|
|
net.ipv6.conf.default.accept_redirects=0
|
|
|
|
|
|
EOF
|
2025-08-04 23:14:32 +01:00
|
|
|
|
sysctl -p /etc/sysctl.d/99-du-hardening.conf >/dev/null
|
|
|
|
|
|
print_success "Kernel hardening applied."
|
|
|
|
|
|
log "Kernel hardening completed."
|
2025-07-15 13:51:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Docker Installation ---
|
2025-06-28 12:51:55 +01:00
|
|
|
|
install_docker() {
|
|
|
|
|
|
print_section "Docker Installation"
|
|
|
|
|
|
if command -v docker >/dev/null 2>&1; then
|
|
|
|
|
|
print_info "Docker already installed."
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! confirm "Install Docker?"; then
|
|
|
|
|
|
print_info "Skipping Docker installation."
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
print_info "Installing Docker..."
|
|
|
|
|
|
if ! apt-get update -qq || ! apt-get install -y -qq apt-transport-https ca-certificates curl gnupg lsb-release; then
|
|
|
|
|
|
print_error "Failed to install Docker prerequisites."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
|
|
|
|
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
|
|
|
|
|
|
if ! apt-get update -qq || ! apt-get install -y -qq docker-ce docker-ce-cli containerd.io; then
|
|
|
|
|
|
print_error "Failed to install Docker."
|
|
|
|
|
|
exit 1
|
2025-06-28 12:51:55 +01:00
|
|
|
|
fi
|
|
|
|
|
|
systemctl enable --now docker
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if systemctl is-active --quiet docker; then
|
|
|
|
|
|
print_success "Docker installed and running."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_error "Docker service failed."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
exit 1
|
|
|
|
|
|
fi
|
|
|
|
|
|
log "Docker installation completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Tailscale Installation ---
|
2025-06-28 12:51:55 +01:00
|
|
|
|
install_tailscale() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Tailscale Installation"
|
2025-06-28 12:51:55 +01:00
|
|
|
|
if command -v tailscale >/dev/null 2>&1; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Tailscale already installed."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if ! confirm "Install Tailscale VPN?"; then
|
|
|
|
|
|
print_info "Skipping Tailscale installation."
|
2025-07-04 14:16:27 +01:00
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
curl -fsSL https://pkgs.tailscale.com/stable/${ID}/${VERSION_CODENAME}/tailscale.list > /etc/apt/sources.list.d/tailscale.list
|
|
|
|
|
|
curl -fsSL https://pkgs.tailscale.com/stable/${ID}/${VERSION_CODENAME}/tailscale.asc | apt-key add -
|
|
|
|
|
|
if ! apt-get update -qq || ! apt-get install -y -qq tailscale; then
|
|
|
|
|
|
print_error "Failed to install Tailscale."
|
|
|
|
|
|
exit 1
|
2025-06-28 12:51:55 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
systemctl enable --now tailscaled
|
|
|
|
|
|
print_info "Configuring Tailscale..."
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter Tailscale auth key: ${NC}")" AUTH_KEY
|
|
|
|
|
|
if [[ -z "$AUTH_KEY" ]]; then
|
|
|
|
|
|
print_error "Tailscale auth key required."
|
|
|
|
|
|
return 1
|
2025-06-28 22:13:17 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
TS_COMMAND="tailscale up --auth-key=$AUTH_KEY --operator=$USERNAME"
|
2025-06-28 22:13:17 +01:00
|
|
|
|
if ! $TS_COMMAND; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Tailscale connection failed. Run manually: $TS_COMMAND"
|
|
|
|
|
|
return 0
|
2025-06-28 12:51:55 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
for ((i=1; i<=3; i++)); do
|
|
|
|
|
|
if TS_IPS=$(tailscale ip 2>/dev/null); then
|
|
|
|
|
|
TS_IPV4=$(echo "$TS_IPS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
|
|
|
|
|
|
if [[ -n "$TS_IPV4" ]]; then
|
|
|
|
|
|
print_success "Tailscale connected. IPv4: $TS_IPV4"
|
|
|
|
|
|
log "Tailscale connected: $TS_IPV4"
|
|
|
|
|
|
return 0
|
2025-06-28 22:13:17 +01:00
|
|
|
|
fi
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Waiting for Tailscale ($i/3)..."
|
|
|
|
|
|
sleep 5
|
|
|
|
|
|
done
|
|
|
|
|
|
print_warning "Tailscale connection not verified."
|
|
|
|
|
|
log "Tailscale connection not verified."
|
2025-06-28 12:51:55 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Backup Configuration ---
|
2025-06-28 11:43:10 +01:00
|
|
|
|
setup_backup() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Backup Configuration"
|
|
|
|
|
|
if [[ -f /etc/cron.d/du-backup ]]; then
|
|
|
|
|
|
print_info "Backup cron job already configured."
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
|
|
|
|
|
if ! confirm "Configure automated backups?"; then
|
2025-06-28 11:43:10 +01:00
|
|
|
|
print_info "Skipping backup configuration."
|
|
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter backup server hostname: ${NC}")" BACKUP_HOST
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter backup server SSH port [22]: ${NC}")" BACKUP_PORT
|
|
|
|
|
|
BACKUP_PORT=${BACKUP_PORT:-22}
|
|
|
|
|
|
if ! validate_backup_port "$BACKUP_PORT"; then
|
|
|
|
|
|
print_error "Invalid backup port."
|
2025-06-28 14:51:26 +01:00
|
|
|
|
return 1
|
2025-06-28 12:41:22 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter backup server username: ${NC}")" BACKUP_USER
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter remote backup path (e.g., /backups/server1): ${NC}")" REMOTE_BACKUP_PATH
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter local directories to back up (space-separated): ${NC}")" BACKUP_DIRS
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter cron schedule (e.g., '0 2 * * *' for daily at 2 AM): ${NC}")" CRON_SCHEDULE
|
|
|
|
|
|
if ! validate_cron_schedule "$CRON_SCHEDULE"; then
|
|
|
|
|
|
print_error "Invalid cron schedule."
|
|
|
|
|
|
return 1
|
2025-06-28 16:55:55 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
USER_HOME=$(getent passwd "$USERNAME" | cut -d: -f6)
|
|
|
|
|
|
SSH_DIR="$USER_HOME/.ssh"
|
|
|
|
|
|
SSH_KEY="$SSH_DIR/id_ed25519_backup"
|
|
|
|
|
|
if [[ ! -f "$SSH_KEY" ]]; then
|
|
|
|
|
|
sudo -u "$USERNAME" ssh-keygen -t ed25519 -f "$SSH_KEY" -N "" -q
|
|
|
|
|
|
print_success "Backup SSH key generated."
|
|
|
|
|
|
echo -e "${YELLOW}Add this public key to the backup server's authorized_keys:${NC}"
|
|
|
|
|
|
cat "$SSH_KEY.pub"
|
|
|
|
|
|
read -rp "$(echo -e "${CYAN}Press Enter after adding the key...${NC}")"
|
|
|
|
|
|
fi
|
|
|
|
|
|
SSH_COMMAND="ssh -i $SSH_KEY -p $BACKUP_PORT -o StrictHostKeyChecking=no"
|
|
|
|
|
|
print_info "Testing backup connection..."
|
|
|
|
|
|
TEST_DIR=$(mktemp -d)
|
|
|
|
|
|
timeout 10 rsync -avz --delete -e "$SSH_COMMAND" "$TEST_DIR/" "${BACKUP_USER}@${BACKUP_HOST}:${REMOTE_BACKUP_PATH}/test_backup/" >/tmp/backup_test.log 2>&1
|
|
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
|
|
|
|
print_success "Backup test successful."
|
|
|
|
|
|
rm -rf "$TEST_DIR"
|
|
|
|
|
|
else
|
|
|
|
|
|
print_error "Backup test failed. Check /tmp/backup_test.log."
|
|
|
|
|
|
return 1
|
2025-06-28 11:43:10 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
tee /usr/local/bin/backup.sh > /dev/null <<EOF
|
2025-06-28 11:43:10 +01:00
|
|
|
|
#!/bin/bash
|
2025-08-04 23:14:32 +01:00
|
|
|
|
rsync -avz --delete -e "$SSH_COMMAND" $BACKUP_DIRS "${BACKUP_USER}@${BACKUP_HOST}:${REMOTE_BACKUP_PATH}/" >> "$BACKUP_LOG" 2>&1
|
2025-06-28 11:43:10 +01:00
|
|
|
|
EOF
|
2025-08-04 23:14:32 +01:00
|
|
|
|
chmod +x /usr/local/bin/backup.sh
|
|
|
|
|
|
tee /etc/cron.d/du-backup > /dev/null <<EOF
|
|
|
|
|
|
$CRON_SCHEDULE $USERNAME /usr/local/bin/backup.sh
|
2025-06-28 11:43:10 +01:00
|
|
|
|
EOF
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Backup configured."
|
2025-06-28 11:43:10 +01:00
|
|
|
|
log "Backup configuration completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Swap Configuration ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
configure_swap() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Swap Configuration"
|
2025-06-26 21:49:51 +01:00
|
|
|
|
if [[ $IS_CONTAINER == true ]]; then
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_warning "Swap not supported in containers."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
return 0
|
|
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
if [[ -f /swapfile ]]; then
|
|
|
|
|
|
print_info "Swap file already exists."
|
|
|
|
|
|
return 0
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
read -rp "$(echo -e "${CYAN}Enter swap size (e.g., 2G, 512M): ${NC}")" SWAP_SIZE
|
|
|
|
|
|
if ! validate_swap_size "$SWAP_SIZE"; then
|
|
|
|
|
|
print_error "Invalid swap size."
|
|
|
|
|
|
return 1
|
2025-06-27 12:56:02 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
SWAP_BYTES=$(convert_to_bytes "$SWAP_SIZE")
|
|
|
|
|
|
print_info "Creating swap file of $SWAP_SIZE..."
|
|
|
|
|
|
fallocate -l "$SWAP_BYTES" /swapfile
|
|
|
|
|
|
chmod 600 /swapfile
|
|
|
|
|
|
mkswap /swapfile
|
|
|
|
|
|
swapon /swapfile
|
|
|
|
|
|
if ! grep -q "/swapfile" /etc/fstab; then
|
|
|
|
|
|
echo "/swapfile none swap sw 0 0" >> /etc/fstab
|
2025-06-26 16:22:23 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_success "Swap configured."
|
2025-06-26 16:22:23 +01:00
|
|
|
|
log "Swap configuration completed."
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Security Audit ---
|
|
|
|
|
|
run_security_audit() {
|
|
|
|
|
|
print_section "Security Audit"
|
|
|
|
|
|
if ! command -v lynis >/dev/null 2>&1; then
|
|
|
|
|
|
print_info "Installing Lynis..."
|
|
|
|
|
|
if [[ $ID == "debian" ]]; then
|
|
|
|
|
|
apt-get install -y -qq apt-transport-https
|
|
|
|
|
|
echo "deb https://packages.cisofy.com/community/lynis/deb/ stable main" > /etc/apt/sources.list.d/lynis.list
|
|
|
|
|
|
curl -s https://packages.cisofy.com/keys/cisofy-software-rpms-public.key | apt-key add -
|
2025-06-29 13:10:41 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
apt-get install -y -qq lynis
|
2025-06-29 13:10:41 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
apt-get update -qq && apt-get install -y -qq lynis
|
2025-06-29 13:10:41 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_info "Running Lynis audit..."
|
|
|
|
|
|
lynis audit system --quiet > /tmp/lynis_report.txt
|
|
|
|
|
|
print_success "Lynis audit completed. Report: /tmp/lynis_report.txt"
|
|
|
|
|
|
if [[ $ID == "debian" ]] && ! command -v debsecan >/dev/null 2>&1; then
|
|
|
|
|
|
apt-get install -y -qq debsecan
|
|
|
|
|
|
debsecan --suite "${VERSION_CODENAME}" > /tmp/debsecan_report.txt
|
|
|
|
|
|
print_success "debsecan report generated: /tmp/debsecan_report.txt"
|
2025-06-29 13:10:41 +01:00
|
|
|
|
fi
|
2025-08-04 23:14:32 +01:00
|
|
|
|
log "Security audit completed."
|
|
|
|
|
|
}
|
2025-06-29 13:10:41 +01:00
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Cleanup and Exit ---
|
|
|
|
|
|
cleanup_and_exit() {
|
|
|
|
|
|
local exit_code=$?
|
|
|
|
|
|
if [[ $exit_code -ne 0 && $(type -t rollback_ssh_changes) == "function" ]]; then
|
|
|
|
|
|
print_error "Error occurred. Rolling back SSH..."
|
|
|
|
|
|
rollback_ssh_changes
|
|
|
|
|
|
fi
|
|
|
|
|
|
exit $exit_code
|
2025-06-29 13:10:41 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Final Cleanup ---
|
2025-06-26 21:49:51 +01:00
|
|
|
|
final_cleanup() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Final Cleanup"
|
|
|
|
|
|
apt-get update -qq && apt-get upgrade -y -qq
|
|
|
|
|
|
apt-get autoremove -y -qq
|
|
|
|
|
|
apt-get autoclean -qq
|
|
|
|
|
|
print_success "System updated and cleaned."
|
|
|
|
|
|
log "Final cleanup completed."
|
2025-06-26 21:49:51 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Generate Summary ---
|
2025-06-26 21:49:51 +01:00
|
|
|
|
generate_summary() {
|
2025-08-04 23:14:32 +01:00
|
|
|
|
print_section "Generating Summary"
|
|
|
|
|
|
{
|
|
|
|
|
|
echo "Setup Report - $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
|
|
echo "==================================="
|
|
|
|
|
|
echo "Username: $USERNAME"
|
|
|
|
|
|
echo "Hostname: $SERVER_NAME"
|
|
|
|
|
|
echo "SSH Port: $SSH_PORT"
|
|
|
|
|
|
echo "Server IP: $SERVER_IP"
|
|
|
|
|
|
echo "Timezone: $TIMEZONE"
|
|
|
|
|
|
echo "Tasks Executed: ${SELECTED_TASKS[*]}"
|
|
|
|
|
|
if [[ -n "${FAILED_SERVICES[*]}" ]]; then
|
|
|
|
|
|
echo "Failed Services: ${FAILED_SERVICES[*]}"
|
2025-06-29 18:38:12 +01:00
|
|
|
|
else
|
2025-08-04 23:14:32 +01:00
|
|
|
|
echo "Failed Services: None"
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -f /tmp/lynis_report.txt ]]; then
|
|
|
|
|
|
echo "Lynis Suggestions:"
|
|
|
|
|
|
grep "suggestion" /tmp/lynis_report.txt || echo " None"
|
|
|
|
|
|
fi
|
|
|
|
|
|
if [[ -f /tmp/debsecan_report.txt ]]; then
|
|
|
|
|
|
echo "debsecan Vulnerabilities:"
|
|
|
|
|
|
head -n 10 /tmp/debsecan_report.txt || echo " None"
|
|
|
|
|
|
fi
|
|
|
|
|
|
echo "Log File: $LOG_FILE"
|
|
|
|
|
|
echo "Backup Directory: $BACKUP_DIR"
|
|
|
|
|
|
echo "==================================="
|
|
|
|
|
|
echo "Connect to server with: ssh -p $SSH_PORT $USERNAME@$SERVER_IP"
|
|
|
|
|
|
echo "Reboot recommended to apply all changes."
|
|
|
|
|
|
} > "$REPORT_FILE"
|
|
|
|
|
|
print_success "Summary generated: $REPORT_FILE"
|
|
|
|
|
|
cat "$REPORT_FILE"
|
|
|
|
|
|
log "Summary generated: $REPORT_FILE"
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Main Function ---
|
2025-06-26 16:22:23 +01:00
|
|
|
|
main() {
|
|
|
|
|
|
print_header
|
2025-08-04 23:14:32 +01:00
|
|
|
|
mkdir -p /var/log
|
|
|
|
|
|
touch "$LOG_FILE"
|
|
|
|
|
|
chmod 640 "$LOG_FILE"
|
|
|
|
|
|
if [[ ${#SELECTED_TASKS[@]} -eq 0 ]]; then
|
|
|
|
|
|
select_tasks_interactive
|
|
|
|
|
|
fi
|
|
|
|
|
|
for task in "${SELECTED_TASKS[@]}"; do
|
|
|
|
|
|
case $task in
|
|
|
|
|
|
update) run_update_check ;;
|
|
|
|
|
|
deps) check_dependencies ;;
|
|
|
|
|
|
system) check_system ;;
|
|
|
|
|
|
config) collect_config ;;
|
|
|
|
|
|
packages) install_packages ;;
|
|
|
|
|
|
user) setup_user ;;
|
|
|
|
|
|
system_config) configure_system ;;
|
|
|
|
|
|
ssh) configure_ssh ;;
|
|
|
|
|
|
firewall) configure_firewall ;;
|
|
|
|
|
|
fail2ban) configure_fail2ban ;;
|
|
|
|
|
|
auto_updates) configure_auto_updates ;;
|
|
|
|
|
|
time_sync) configure_time_sync ;;
|
|
|
|
|
|
kernel) configure_kernel_hardening ;;
|
|
|
|
|
|
docker) install_docker ;;
|
|
|
|
|
|
tailscale) install_tailscale ;;
|
|
|
|
|
|
backup) setup_backup ;;
|
|
|
|
|
|
swap) configure_swap ;;
|
|
|
|
|
|
audit) run_security_audit ;;
|
|
|
|
|
|
cleanup) final_cleanup ;;
|
|
|
|
|
|
summary) generate_summary ;;
|
|
|
|
|
|
*) print_error "Unknown task: $task" ;;
|
|
|
|
|
|
esac
|
|
|
|
|
|
done
|
|
|
|
|
|
print_success "Setup completed. Review $REPORT_FILE for details."
|
|
|
|
|
|
if confirm "Reboot now to apply changes?"; then
|
|
|
|
|
|
reboot
|
|
|
|
|
|
fi
|
2025-06-26 16:22:23 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-04 23:14:32 +01:00
|
|
|
|
# --- Execute Main ---
|
|
|
|
|
|
main
|