feat: Add optional provider cleanup with environment detection

This commit is contained in:
buildplan
2025-10-13 22:52:07 +01:00
parent 663276952b
commit 7412fcf229

View File

@@ -1,8 +1,9 @@
#!/bin/bash
# Debian and Ubuntu Server Hardening Interactive Script
# Version: 0.69 | 2025-10-13
# Version: 0.70 | 2025-10-14
# Changelog:
# - v0.70: Option to remove cloud VPS provider packages (like cloud-init)
# - v0.69: Ensure .ssh directory ownership is set for new user.
# - v0.68: Enable UFW IPv6 support if available
# - v0.67: Do not log taiscale auth key in log file
@@ -69,7 +70,7 @@
set -euo pipefail # Exit on error, undefined vars, pipe failures
# --- Update Configuration ---
CURRENT_VERSION="0.69"
CURRENT_VERSION="0.70"
SCRIPT_URL="https://raw.githubusercontent.com/buildplan/du_setup/refs/heads/main/du_setup.sh"
CHECKSUM_URL="${SCRIPT_URL}.sha256"
@@ -104,6 +105,15 @@ 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)"
CLEANUP_PREVIEW=false # If true, show what would be cleaned up without making changes
CLEANUP_ONLY=false # If true, only perform cleanup tasks
SKIP_CLEANUP=false # If true, skip cleanup tasks
DETECTED_VIRT_TYPE=""
DETECTED_MANUFACTURER=""
DETECTED_PRODUCT=""
IS_CLOUD_PROVIDER=false
IS_CONTAINER=false
SSHD_BACKUP_FILE=""
LOCAL_KEY_ADDED=false
@@ -115,10 +125,46 @@ FAILED_SERVICES=()
while [[ $# -gt 0 ]]; do
case $1 in
--quiet) VERBOSE=false; shift ;;
--cleanup-preview) CLEANUP_PREVIEW=true; shift ;;
--cleanup-only) CLEANUP_ONLY=true; shift ;;
--skip-cleanup) SKIP_CLEANUP=true; shift ;;
-h|--help) show_usage ;;
*) shift ;;
esac
done
show_usage() {
cat << EOF
${CYAN}Debian/Ubuntu Server Setup and Hardening Script${NC}
Usage: $(basename "$0") [OPTIONS]
Options:
--quiet Suppress verbose output
--cleanup-preview Preview what cleanup would do without making changes
--cleanup-only Run only the provider cleanup function (for existing servers)
--skip-cleanup Skip the provider cleanup function entirely
-h, --help Display this help message
Examples:
# Normal interactive run (includes cleanup prompt)
sudo ./du_setup.sh
# Preview what cleanup would detect and remove
sudo ./du_setup.sh --cleanup-preview
# Run only cleanup on an existing server
sudo ./du_setup.sh --cleanup-only
# Run full setup but skip cleanup
sudo ./du_setup.sh --skip-cleanup
For more information: https://github.com/buildplan/du-setup
EOF
exit 0
}
# --- LOGGING & PRINT FUNCTIONS ---
log() {
@@ -130,7 +176,7 @@ print_header() {
echo -e "${CYAN}╔═════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ ║${NC}"
echo -e "${CYAN}║ DEBIAN/UBUNTU SERVER SETUP AND HARDENING SCRIPT ║${NC}"
echo -e "${CYAN}║ v0.69 | 2025-10-13${NC}"
echo -e "${CYAN}║ v0.70 | 2025-10-14${NC}"
echo -e "${CYAN}║ ║${NC}"
echo -e "${CYAN}╚═════════════════════════════════════════════════════════════════╝${NC}"
echo
@@ -161,6 +207,685 @@ print_info() {
echo -e "${PURPLE} $1${NC}" | tee -a "$LOG_FILE"
}
# --- CLEANUP HELPER FUNCTIONS ---
execute_check() {
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
"$@" 2>/dev/null
return $?
fi
"$@"
}
execute_command() {
local cmd_string="$*"
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
echo -e "${CYAN}[PREVIEW]${NC} Would execute: ${BOLD}$cmd_string${NC}" | tee -a "$LOG_FILE"
return 0
else
"$@"
return $?
fi
}
# --- ENVIRONMENT DETECTION (Cloud VPS or Trusted VM) ---
detect_environment() {
local VIRT_TYPE=""
local MANUFACTURER=""
local PRODUCT=""
local IS_CLOUD_VPS=false
# Method 1: systemd-detect-virt (most reliable)
if command -v systemd-detect-virt &>/dev/null; then
VIRT_TYPE=$(systemd-detect-virt 2>/dev/null || echo "none")
fi
# Method 2: dmidecode for hardware info (requires root)
if command -v dmidecode &>/dev/null && [[ $(id -u) -eq 0 ]]; then
MANUFACTURER=$(dmidecode -s system-manufacturer 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
PRODUCT=$(dmidecode -s system-product-name 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
fi
# Method 3: Check /sys/class/dmi/id/ (fallback, doesn't require dmidecode)
if [[ -z "$MANUFACTURER" || "$MANUFACTURER" == "unknown" ]]; then
if [[ -r /sys/class/dmi/id/sys_vendor ]]; then
MANUFACTURER=$(cat /sys/class/dmi/id/sys_vendor 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
fi
fi
if [[ -z "$PRODUCT" || "$PRODUCT" == "unknown" ]]; then
if [[ -r /sys/class/dmi/id/product_name ]]; then
PRODUCT=$(cat /sys/class/dmi/id/product_name 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "unknown")
fi
fi
# Cloud provider detection patterns
local CLOUD_PATTERNS=(
# VPS/Cloud Providers
"digitalocean"
"linode"
"vultr"
"hetzner"
"ovh"
"scaleway"
"contabo"
"upcloud"
"dreamhost"
"kimsufi"
"online.net"
"equinix metal"
"lightsail"
"scaleway"
# Major Cloud Platforms
"amazon"
"amazon ec2"
"aws"
"google"
"gce"
"google compute engine"
"microsoft"
"azure"
"oracle cloud"
"alibaba"
"tencent"
"rackspace"
# Virtualization indicating cloud VPS
"droplet"
"linodekvm"
"kvm"
"openstack"
)
# Check if manufacturer or product matches cloud patterns
for pattern in "${CLOUD_PATTERNS[@]}"; do
if [[ "$MANUFACTURER" == *"$pattern"* ]] || [[ "$PRODUCT" == *"$pattern"* ]]; then
IS_CLOUD_VPS=true
break
fi
done
# Additional checks based on virtualization type
case "$VIRT_TYPE" in
kvm|qemu)
if [[ "$MANUFACTURER" =~ (qemu|bochs|ovmf) ]]; then
if [[ "$MANUFACTURER" == "qemu" && "$PRODUCT" =~ ^(standard pc|pc-|pc ) ]]; then
IS_CLOUD_VPS=false
else
IS_CLOUD_VPS=true
fi
fi
;;
vmware)
IS_CLOUD_VPS=false
;;
oracle|virtualbox)
IS_CLOUD_VPS=false
;;
xen)
IS_CLOUD_VPS=true
;;
hyperv|microsoft)
if [[ "$MANUFACTURER" == *"microsoft"* ]] && [[ "$PRODUCT" == *"virtual machine"* ]]; then
IS_CLOUD_VPS=false
fi
;;
none)
IS_CLOUD_VPS=false
;;
esac
# Export results as global variables
DETECTED_VIRT_TYPE="$VIRT_TYPE"
DETECTED_MANUFACTURER="$MANUFACTURER"
DETECTED_PRODUCT="$PRODUCT"
IS_CLOUD_PROVIDER="$IS_CLOUD_VPS"
log "Environment detection: VIRT=$VIRT_TYPE, MANUFACTURER=$MANUFACTURER, PRODUCT=$PRODUCT, IS_CLOUD=$IS_CLOUD_VPS"
}
cleanup_provider_packages() {
print_section "Provider Package Cleanup (Optional)"
# Detect environment first
detect_environment
# Display environment information
echo -e "${CYAN}=== Environment Detection ===${NC}"
echo "Virtualization Type: ${DETECTED_VIRT_TYPE:-unknown}"
echo "System Manufacturer: ${DETECTED_MANUFACTURER:-unknown}"
echo "Product Name: ${DETECTED_PRODUCT:-unknown}"
echo
# Determine recommendation based on detection
local CLEANUP_RECOMMENDED=false
local RECOMMENDATION_TEXT=""
if [[ "$IS_CLOUD_PROVIDER" == "true" ]]; then
CLEANUP_RECOMMENDED=true
echo -e "${YELLOW}⚠ Cloud VPS Provider Detected${NC}"
echo "This appears to be a cloud VPS from an external provider."
RECOMMENDATION_TEXT="Provider cleanup is ${BOLD}RECOMMENDED${NC} for security."
echo -e "$RECOMMENDATION_TEXT"
echo "Providers may install monitoring agents, pre-configured users, and other tools."
echo
elif [[ "$DETECTED_VIRT_TYPE" == "none" ]]; then
echo -e "${GREEN}✓ Bare Metal Server Detected${NC}"
echo "This appears to be a physical (bare metal) server."
RECOMMENDATION_TEXT="Provider cleanup is ${BOLD}NOT NEEDED${NC} for bare metal."
echo -e "$RECOMMENDATION_TEXT"
echo
else
echo -e "${CYAN} Personal/Private Virtualization Detected${NC}"
echo "This appears to be a personal VM (VirtualBox, VMware, Proxmox, etc.)"
RECOMMENDATION_TEXT="Provider cleanup is ${BOLD}OPTIONAL${NC} for trusted environments."
echo -e "$RECOMMENDATION_TEXT"
echo "If you control the hypervisor/host, you likely don't need cleanup."
echo
fi
# Decision point based on environment and flags
if [[ "$CLEANUP_PREVIEW" == "false" ]] && [[ "$CLEANUP_ONLY" == "false" ]]; then
if [[ "$IS_CLOUD_PROVIDER" == "true" ]]; then
# Cloud VPS - recommend cleanup with default "yes"
if ! confirm "Run provider package cleanup? (Recommended for cloud VPS)" "y"; then
print_info "Skipping provider package cleanup."
log "Provider package cleanup skipped by user (cloud VPS detected)."
return 0
fi
else
# Personal VM or bare metal - recommend skip with default "no"
echo -e "${YELLOW}This cleanup is intended for untrusted cloud VPS providers.${NC}"
echo "If you trust your virtualization environment (personal Proxmox, ESXi, etc.),"
echo "you should skip this step."
echo
if ! confirm "Run provider package cleanup anyway?" "n"; then
print_info "Skipping provider package cleanup (recommended for trusted environments)."
log "Provider package cleanup skipped by user (trusted environment detected)."
return 0
fi
print_warning "Proceeding with cleanup on a trusted environment."
print_warning "Exercise caution - this may remove useful tools."
echo
fi
fi
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_warning "=== PREVIEW MODE ENABLED ==="
print_info "No changes will be made. This is a simulation only."
echo
fi
if [[ "$CLEANUP_PREVIEW" == "false" ]]; then
print_warning "RECOMMENDED: Create a snapshot/backup via provider dashboard before cleanup."
if ! confirm "Have you created a backup snapshot?" "n"; then
print_info "Please create a backup first. Exiting cleanup."
log "User declined to proceed without backup snapshot."
return 0
fi
fi
print_warning "This will identify packages and configurations installed by your VPS provider."
if [[ "$CLEANUP_PREVIEW" == "false" ]]; then
print_warning "Removing critical packages can break system functionality."
fi
# Arrays to track findings
local PROVIDER_PACKAGES=()
local PROVIDER_SERVICES=()
local PROVIDER_USERS=()
local ROOT_SSH_KEYS=()
# Extended list of common provider and virtualization packages
local COMMON_PROVIDER_PKGS=(
# QEMU/KVM, Virtio, Generic virtualization
"qemu-guest-agent"
"virtio-utils"
"virt-what"
"libvirt-daemon-system"
"libvirt-clients"
"libguestfs-tools"
# Cloud-init and cloud utilities
"cloud-init"
"cloud-guest-utils"
"cloud-initramfs-growroot"
"cloud-utils"
# VMware, Xen, Hyper-V, Oracle
"open-vm-tools"
"xe-guest-utilities"
"oracle-cloud-agent"
"ovm-tools"
"xen-tools"
"xenserver-guest-tools"
"xenserver-guest-utils"
"xenserver-libs"
"hyperv-daemons"
"hv-kvp-daemon-init"
"hv-fcopy-daemon-init"
"hv-vss-daemon-init"
"hv-utils"
# Cloud Provider Agents (by provider)
# AWS
"aws-systems-manager-agent"
"amazon-ssm-agent"
# Google Cloud
"google-compute-engine"
"google-osconfig-agent"
# Azure
"azure-agent"
"walinuxagent"
# Oracle
"oracle-cloud-agent"
# Common third-party host agents
"hetzner-needrestart"
"digitalocean-agent"
"do-agent"
"linode-agent"
"vultr-monitoring"
"scaleway-ecosystem"
"ovh-rtm"
# Contabo
"contabo-monitoring"
# Hetzner
"hcloud"
"hcloud-agent"
# Alibaba Cloud
"aliyun-assist"
# Tencent Cloud
"qcloud-agent"
# IBM Cloud
"ibm-cloud-agent"
# OpenStack
"openstack-guest-utils"
"openstack-nova-agent"
"openstack-neutron-agent"
"openstack-ceilometer-agent"
"openstack-glance"
"openstack-keystone"
"openstack-swift"
"openstack-cinder"
"openstack-dashboard"
"openstack-nova-compute"
"openstack-neutron-server"
"openstack-ceilometer-api"
"openstack-ceilometer-collector"
"openstack-ceilometer-notification"
"openstack-glance-api"
"openstack-glance-registry"
"openstack-keystone"
"openstack-swift-proxy"
"openstack-cinder-api"
"openstack-cinder-scheduler"
"openstack-cinder-volume"
"openstack-dashboard"
)
# Common provider-created default users
local COMMON_PROVIDER_USERS=(
"ubuntu"
"debian"
"admin"
"cloud-user"
"ec2-user"
)
print_info "Scanning for provider-installed packages..."
for pkg in "${COMMON_PROVIDER_PKGS[@]}"; do
if execute_check dpkg -l "$pkg" 2>/dev/null | grep -q '^ii'; then
PROVIDER_PACKAGES+=("$pkg")
fi
done
# Detect associated services
print_info "Scanning for provider-related services..."
for pkg in "${PROVIDER_PACKAGES[@]}"; do
local service_name="${pkg}.service"
if execute_check systemctl list-unit-files "$service_name" 2>/dev/null | grep -q "$service_name"; then
if execute_check systemctl is-enabled "$service_name" 2>/dev/null | grep -qE 'enabled|static'; then
PROVIDER_SERVICES+=("$service_name")
fi
fi
done
# Check for provider-created users (excluding current admin user)
print_info "Scanning for default provisioning users..."
for user in "${COMMON_PROVIDER_USERS[@]}"; do
if execute_check id "$user" &>/dev/null && [[ "$user" != "$USERNAME" ]]; then
PROVIDER_USERS+=("$user")
fi
done
# Audit root SSH keys
print_info "Auditing /root/.ssh/authorized_keys for unexpected keys..."
if [[ -f /root/.ssh/authorized_keys ]]; then
local key_count
key_count=$(grep -cE '^ssh-(rsa|ed25519|ecdsa)' /root/.ssh/authorized_keys 2>/dev/null || echo 0)
if [[ $key_count -gt 0 ]]; then
print_warning "Found $key_count SSH key(s) in /root/.ssh/authorized_keys"
ROOT_SSH_KEYS=("present")
fi
fi
# Summary of findings
echo
print_info "=== Scan Results ==="
echo "Packages found: ${#PROVIDER_PACKAGES[@]}"
echo "Services found: ${#PROVIDER_SERVICES[@]}"
echo "Default users found: ${#PROVIDER_USERS[@]}"
echo "Root SSH keys: ${#ROOT_SSH_KEYS[@]}"
echo
if [[ ${#PROVIDER_PACKAGES[@]} -eq 0 && ${#PROVIDER_USERS[@]} -eq 0 && ${#ROOT_SSH_KEYS[@]} -eq 0 ]]; then
print_success "No common provider packages or users detected."
return 0
fi
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "=== PREVIEW: Showing what would be done ==="
echo
fi
# 1. SSH KEY AUDIT
if [[ ${#ROOT_SSH_KEYS[@]} -gt 0 ]]; then
print_section "Root SSH Key Audit"
print_warning "SSH keys in /root/.ssh/authorized_keys can allow provider or previous admins access."
echo
echo -e "${YELLOW}Current keys in /root/.ssh/authorized_keys:${NC}"
awk '{print NR". "$0}' /root/.ssh/authorized_keys 2>/dev/null | head -20
echo
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would offer to review and edit /root/.ssh/authorized_keys"
print_info "[PREVIEW] Would backup to $BACKUP_DIR/root_authorized_keys.backup.<timestamp>"
else
if confirm "Review and potentially remove root SSH keys?" "n"; then
local backup_file="$BACKUP_DIR/root_authorized_keys.backup.$(date +%Y%m%d_%H%M%S)"
execute_command cp /root/.ssh/authorized_keys "$backup_file"
log "Backed up /root/.ssh/authorized_keys to $backup_file"
print_warning "IMPORTANT: Do NOT delete ALL keys or you'll be locked out!"
print_info "Opening /root/.ssh/authorized_keys for manual review..."
print_warning "Delete any keys you don't recognize. Save and exit when done."
read -rp "Press Enter to continue..."
"${EDITOR:-nano}" /root/.ssh/authorized_keys
if [[ ! -s /root/.ssh/authorized_keys ]]; then
print_error "WARNING: authorized_keys is empty! This could lock you out."
if confirm "Restore from backup?" "y"; then
execute_command cp "$backup_file" /root/.ssh/authorized_keys
print_info "Restored backup."
log "Restored /root/.ssh/authorized_keys from backup due to empty file."
fi
fi
local new_key_count
new_key_count=$(grep -cE '^ssh-(rsa|ed25519|ecdsa)' /root/.ssh/authorized_keys 2>/dev/null || echo 0)
print_info "Keys remaining: $new_key_count"
log "Root SSH keys audit completed. Keys remaining: $new_key_count"
else
print_info "Skipping root SSH key audit."
fi
fi
echo
fi
# 2. CLOUD-INIT HANDLING
if [[ " ${PROVIDER_PACKAGES[*]} " =~ " cloud-init " ]]; then
print_section "Cloud-Init Management"
echo -e "${CYAN} cloud-init${NC}"
echo " Purpose: Initial VM provisioning (SSH keys, hostname, network)"
echo " ${YELLOW}Official recommendation: DISABLE rather than remove${NC}"
echo " Benefits of disabling vs removing:"
echo " - Can be re-enabled if needed for reprovisioning"
echo " - Safer than package removal"
echo " - No dependency issues"
echo
if [[ "$CLEANUP_PREVIEW" == "true" ]] || confirm "Disable cloud-init (recommended over removal)?" "y"; then
print_info "Disabling cloud-init..."
if ! [[ -f /etc/cloud/cloud-init.disabled ]]; then
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would create /etc/cloud/cloud-init.disabled"
else
execute_command touch /etc/cloud/cloud-init.disabled
print_success "Created /etc/cloud/cloud-init.disabled"
log "Created /etc/cloud/cloud-init.disabled"
fi
else
print_info "/etc/cloud/cloud-init.disabled already exists."
fi
local cloud_services=(
"cloud-init.service"
"cloud-init-local.service"
"cloud-config.service"
"cloud-final.service"
)
for service in "${cloud_services[@]}"; do
if execute_check systemctl is-enabled "$service" &>/dev/null; then
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would stop and disable $service"
else
execute_command systemctl stop "$service" 2>/dev/null || true
execute_command systemctl disable "$service" 2>/dev/null || true
print_success "Disabled $service"
log "Disabled $service"
fi
fi
done
if [[ "$CLEANUP_PREVIEW" == "false" ]]; then
print_success "cloud-init disabled successfully."
print_info "To re-enable: sudo rm /etc/cloud/cloud-init.disabled && systemctl enable cloud-init.service"
fi
PROVIDER_PACKAGES=("${PROVIDER_PACKAGES[@]/cloud-init/}")
else
print_info "Keeping cloud-init enabled."
fi
echo
fi
# 3. PACKAGE REMOVAL
if [[ ${#PROVIDER_PACKAGES[@]} -gt 0 ]]; then
print_section "Provider Package Removal"
for pkg in "${PROVIDER_PACKAGES[@]}"; do
[[ -z "$pkg" ]] && continue
case "$pkg" in
qemu-guest-agent)
echo -e "${RED}$pkg${NC}"
echo " Purpose: VM-host communication for snapshots and graceful shutdowns"
echo " ${RED}CRITICAL RISKS if removed:${NC}"
echo " - Snapshot backups will FAIL or be inconsistent"
echo " - Console access may break"
echo " - Graceful shutdowns replaced with forced stops"
echo " - Provider backup systems will malfunction"
echo " ${RED}STRONGLY RECOMMENDED to keep${NC}"
;;
*-agent|*-monitoring)
echo -e "${YELLOW}$pkg${NC}"
echo " Purpose: Provider monitoring/management"
echo " Risks if removed:"
echo " - Provider dashboard metrics will disappear"
echo " - May affect support troubleshooting"
echo " ${YELLOW}Remove only if you don't need provider monitoring${NC}"
;;
*)
echo -e "${CYAN} $pkg${NC}"
echo " Purpose: Provider-specific tooling"
echo " ${YELLOW}Review before removing${NC}"
;;
esac
echo
if [[ "$CLEANUP_PREVIEW" == "true" ]] || confirm "Remove $pkg?" "n"; then
if [[ "$pkg" == "qemu-guest-agent" && "$CLEANUP_PREVIEW" == "false" ]]; then
print_error "FINAL WARNING: Removing qemu-guest-agent will break backups and console access!"
if ! confirm "Are you ABSOLUTELY SURE?" "n"; then
print_info "Keeping $pkg (wise choice)."
continue
fi
fi
local service_name="${pkg}.service"
if execute_check systemctl is-active "$service_name" &>/dev/null; then
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would stop and disable $service_name"
else
print_info "Stopping $service_name..."
execute_command systemctl stop "$service_name" 2>/dev/null || true
execute_command systemctl disable "$service_name" 2>/dev/null || true
log "Stopped and disabled $service_name"
fi
fi
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would remove package: $pkg (with --purge flag)"
log "[PREVIEW] Would remove provider package: $pkg"
else
print_info "Removing $pkg..."
if execute_command apt-get remove --purge -y "$pkg" 2>&1 | tee -a "$LOG_FILE"; then
print_success "$pkg removed."
log "Removed provider package: $pkg"
else
print_error "Failed to remove $pkg. Check logs."
log "Failed to remove: $pkg"
fi
fi
else
print_info "Keeping $pkg."
fi
done
echo
fi
# 4. SYSTEM USER CLEANUP
if [[ ${#PROVIDER_USERS[@]} -gt 0 ]]; then
print_section "Provider User Cleanup"
print_warning "Default users created during provisioning can be security risks."
echo
for user in "${PROVIDER_USERS[@]}"; do
echo -e "${YELLOW}Found user: $user${NC}"
local proc_count
proc_count=$(ps -u "$user" 2>/dev/null | wc -l)
if [[ $proc_count -gt 1 ]]; then
print_warning "User $user has $((proc_count - 1)) running process(es)."
fi
if [[ -f "/home/$user/.ssh/authorized_keys" ]]; then
local key_count
key_count=$(grep -cE '^ssh-(rsa|ed25519|ecdsa)' "/home/$user/.ssh/authorized_keys" 2>/dev/null || echo 0)
if [[ $key_count -gt 0 ]]; then
print_warning "User $user has $key_count SSH key(s) configured."
fi
fi
if execute_check groups "$user" 2>/dev/null | grep -qE '\bsudo\b|\badmin\b'; then
print_warning "User $user has sudo/admin privileges!"
fi
echo
if [[ "$CLEANUP_PREVIEW" == "true" ]] || confirm "Remove user $user and their home directory?" "n"; then
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would terminate processes owned by $user"
print_info "[PREVIEW] Would remove user $user with home directory"
if [[ -f "/etc/sudoers.d/$user" ]]; then
print_info "[PREVIEW] Would remove /etc/sudoers.d/$user"
fi
log "[PREVIEW] Would remove provider user: $user"
else
if [[ $proc_count -gt 1 ]]; then
print_info "Terminating processes owned by $user..."
execute_command pkill -u "$user" 2>/dev/null || true
sleep 2
if ps -u "$user" &>/dev/null; then
print_warning "Some processes didn't terminate gracefully. Force killing..."
execute_command pkill -9 -u "$user" 2>/dev/null || true
sleep 1
fi
if ps -u "$user" &>/dev/null; then
print_error "Unable to kill all processes for $user. Manual intervention needed."
log "Failed to terminate all processes for user: $user"
continue
fi
fi
print_info "Removing user $user..."
local user_removed=false
if command -v deluser &>/dev/null; then
if execute_command deluser --remove-home "$user" 2>&1 | tee -a "$LOG_FILE"; then
user_removed=true
fi
else
if execute_command userdel -r "$user" 2>&1 | tee -a "$LOG_FILE"; then
user_removed=true
fi
fi
if [[ "$user_removed" == "true" ]]; then
print_success "User $user removed."
log "Removed provider user: $user"
if [[ -f "/etc/sudoers.d/$user" ]]; then
execute_command rm -f "/etc/sudoers.d/$user"
print_info "Removed sudo configuration for $user."
fi
else
print_error "Failed to remove user $user. Check logs."
log "Failed to remove user: $user"
fi
fi
else
print_info "Keeping user $user."
fi
done
echo
fi
# 5. CLEANUP
if [[ "$CLEANUP_PREVIEW" == "true" ]] || confirm "Remove residual configuration files and unused dependencies?" "y"; then
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "[PREVIEW] Would run: apt-get autoremove --purge -y"
print_info "[PREVIEW] Would run: apt-get autoclean -y"
else
print_info "Cleaning up..."
execute_command apt-get autoremove --purge -y 2>&1 | tee -a "$LOG_FILE" || true
execute_command apt-get autoclean -y 2>&1 | tee -a "$LOG_FILE" || true
print_success "Cleanup complete."
log "Ran apt autoremove and autoclean."
fi
fi
log "Provider package cleanup completed."
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
echo
print_success "=== PREVIEW COMPLETED ==="
print_info "No changes were made to the system."
print_info "Run without --cleanup-preview flag to execute these actions."
else
print_success "Cleanup function completed successfully."
fi
}
# --- USER INTERACTION ---
confirm() {
@@ -2346,6 +3071,20 @@ generate_summary() {
echo -e "${CYAN}📜 The full execution log is available at:${NC} ${BOLD}$LOG_FILE${NC}"
echo
echo -e "${YELLOW}Environment Information${NC}"
echo "====================================="
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}"
fi
echo
echo -e "${YELLOW}Final Service Status Check:${NC}"
for service in "$SSH_SERVICE" fail2ban chrony; do
if systemctl is-active --quiet "$service"; then
@@ -2565,6 +3304,38 @@ main() {
touch "$LOG_FILE" && chmod 600 "$LOG_FILE"
log "Starting Debian/Ubuntu hardening script."
# Existing system checks
check_system
# Handle --cleanup-only flag
if [[ "$CLEANUP_ONLY" == "true" ]]; then
print_info "Running in cleanup-only mode..."
detect_environment
cleanup_provider_packages
print_success "Cleanup-only mode completed."
exit 0
fi
# Handle --cleanup-preview flag
if [[ "$CLEANUP_PREVIEW" == "true" ]]; then
print_info "Running cleanup preview mode..."
detect_environment
cleanup_provider_packages
print_success "Cleanup preview completed."
exit 0
fi
# Normal flow - detect environment first
detect_environment
# Run cleanup unless --skip-cleanup is set
if [[ "$SKIP_CLEANUP" == "false" ]]; then
cleanup_provider_packages
else
print_info "Skipping provider cleanup (--skip-cleanup flag set)."
log "Provider cleanup skipped via --skip-cleanup flag."
fi
run_update_check
print_header
check_dependencies