#!/bin/bash # Debian 12 Initial Setup Script # Run as root: bash setup.sh set -euo pipefail # Exit on error, undefined vars, pipe failures # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color ### === SETTINGS === ### SYSADMIN_USER="sysadmin" CREATE_ADDITIONAL_USER="" ADDITIONAL_USER="" USE_UFW="" USE_FAIL2BAN="" # Track which users were created during this setup SYSADMIN_USER_CREATED="" ADDITIONAL_USER_CREATED="" # Track sysadmin password (will be set to generated password) SYSADMIN_NEW_PASSWORD="" # Logging functions log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}" } warn() { echo -e "${YELLOW}[WARNING] $1${NC}" } error() { echo -e "${RED}[ERROR] $1${NC}" exit 1 } # Generate a strong random password (32 characters) generate_password() { # Use openssl to generate a secure 32-character password # Base64 encoding of 24 random bytes gives us 32 characters openssl rand -base64 24 | tr -d "=+/" | cut -c1-32 } # Check if running as root check_root() { if [[ $EUID -ne 0 ]]; then error "This script must be run as root" fi } # Check if running on Debian check_debian() { if ! grep -q "Debian" /etc/os-release; then error "This script is designed for Debian systems" fi } # Check for required commands check_commands() { for cmd in useradd passwd usermod; do if ! command -v $cmd >/dev/null 2>&1; then error "$cmd command not found. Please install required packages: apt install -y passwd" fi done } # Ask user about creating an additional user ask_additional_user() { echo "" echo -e "${BLUE}=== Additional User Creation ===${NC}" echo "This script will create the 'sysadmin' user by default." echo "You can also create an additional user account if needed." echo "" read -p "Would you like to create an additional user account? (y/N): " create_user_choice if [[ "$create_user_choice" =~ ^[Yy]$ ]]; then CREATE_ADDITIONAL_USER="yes" echo "" echo -e "${YELLOW}Enter username for the additional user:${NC}" echo "(This user will also have sudo privileges)" echo "" while true; do read -p "Username: " username_input # Validate username if [[ -z "$username_input" ]]; then echo -e "${RED}Username cannot be empty. Please try again.${NC}" continue elif [[ ! "$username_input" =~ ^[a-z][a-z0-9_-]*$ ]]; then echo -e "${RED}Invalid username. Use lowercase letters, numbers, hyphens, and underscores only.${NC}" echo -e "${RED}Username must start with a letter.${NC}" continue elif [[ "$username_input" == "$SYSADMIN_USER" ]]; then echo -e "${RED}Username cannot be the same as sysadmin user. Please choose a different name.${NC}" continue elif id "$username_input" &>/dev/null; then echo -e "${YELLOW}User '$username_input' already exists.${NC}" read -p "Continue with existing user '$username_input'? (y/N): " continue_existing if [[ "$continue_existing" =~ ^[Yy]$ ]]; then ADDITIONAL_USER="$username_input" log "Will use existing user: $ADDITIONAL_USER" break else echo "Please choose a different name." continue fi else ADDITIONAL_USER="$username_input" log "Will create additional user: $ADDITIONAL_USER" break fi done else CREATE_ADDITIONAL_USER="no" log "Skipping additional user creation" fi } # Ask user about firewall and security preferences ask_firewall_preferences() { echo "" echo -e "${BLUE}=== Firewall Configuration ===${NC}" echo "UFW (Uncomplicated Firewall) provides easy firewall management." echo "" read -p "Install and configure UFW? (y/N): " ufw_choice if [[ "$ufw_choice" =~ ^[Yy]$ ]]; then USE_UFW="yes" log "UFW will be installed and configured" else USE_UFW="no" log "UFW will be skipped" fi echo "" echo -e "${BLUE}=== Fail2ban Configuration ===${NC}" echo "Fail2ban protects against brute-force attacks." echo "" read -p "Install and configure fail2ban? (Y/n): " fail2ban_choice if [[ "$fail2ban_choice" =~ ^[Nn]$ ]]; then USE_FAIL2BAN="no" log "Fail2ban will be skipped" else USE_FAIL2BAN="yes" log "Fail2ban will be installed and configured" fi } # Ask user about SSH key setup for created users ask_ssh_key_setup() { # Check if any users were created or selected local users_for_ssh=() if [[ "$SYSADMIN_USER_CREATED" == "yes" ]]; then users_for_ssh+=("$SYSADMIN_USER") fi if [[ "$CREATE_ADDITIONAL_USER" == "yes" && -n "$ADDITIONAL_USER" ]]; then users_for_ssh+=("$ADDITIONAL_USER") fi # Only prompt if users were created or selected if [[ ${#users_for_ssh[@]} -eq 0 ]]; then return fi echo "" echo -e "${BLUE}=== SSH Key Setup ===${NC}" if [[ "$SYSADMIN_USER_CREATED" == "yes" ]]; then echo "Users created during setup: $SYSADMIN_USER" fi if [[ "$CREATE_ADDITIONAL_USER" == "yes" && -n "$ADDITIONAL_USER" ]]; then if [[ "$ADDITIONAL_USER_CREATED" == "yes" ]]; then echo "Additional user created: $ADDITIONAL_USER" else echo "Additional user selected: $ADDITIONAL_USER (existing)" fi fi echo "You can add your SSH public key for easier login." echo "" read -p "Add your SSH public key to selected users? (Y/n): " add_key_choice if [[ "$add_key_choice" =~ ^[Nn]$ ]]; then log "SSH key setup skipped" return fi local user_public_key="" # Special case for user "sergio" - offer pre-defined keys local sergio_keys_added=false if [[ " ${users_for_ssh[*]} " =~ " sergio " ]]; then echo "" echo "Detected user 'sergio' in selected users." echo "Use pre-configured SSH keys for sergio? (Y/n)" echo "Key 1 (mbpm1): ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBYyuGSa2wswiiObp2qj30MoiNRyFdBIBciFSbtrkZ8 mbpm1" echo "Key 2 (MacMini): ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINhVmvYRywoWQoviB72DGnuv5uEaiumpNAHhDVYFLL+M MacMini" echo "" read -p "Use these keys? (Y/n): " use_sergio_keys if [[ ! "$use_sergio_keys" =~ ^[Nn]$ ]]; then # Add both keys for sergio setup_ssh_key_for_user "sergio" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINBYyuGSa2wswiiObp2qj30MoiNRyFdBIBciFSbtrkZ8 mbpm1" setup_ssh_key_for_user "sergio" "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINhVmvYRywoWQoviB72DGnuv5uEaiumpNAHhDVYFLL+M MacMini" log "Using pre-configured keys for sergio" sergio_keys_added=true fi fi # If no pre-defined key was used, prompt for manual entry if [[ -z "$user_public_key" ]]; then echo "" echo "Please paste your SSH public key (starts with ssh-rsa, ssh-ed25519, etc.):" echo "You can find it with: cat ~/.ssh/id_ed25519.pub (or id_rsa.pub)" echo "" while true; do read -r user_public_key if [[ -z "$user_public_key" ]]; then echo -e "${RED}Public key cannot be empty. Please try again.${NC}" continue elif [[ "$user_public_key" =~ ^(ssh-rsa|ssh-dss|ssh-ed25519|ecdsa-sha2-) ]]; then break else echo -e "${RED}Invalid SSH public key format. Please ensure you copied the entire key.${NC}" continue fi done fi # Add key to all selected users and generate SSH keys for them for username in "${users_for_ssh[@]}"; do # Skip sergio if we already added their pre-configured keys if [[ "$username" == "sergio" && "$sergio_keys_added" == true ]]; then continue fi setup_ssh_key_for_user "$username" "$user_public_key" generate_ssh_key_for_user "$username" done # Always generate SSH key for sergio if present if [[ " ${users_for_ssh[*]} " =~ " sergio " ]]; then generate_ssh_key_for_user "sergio" fi echo "" log "SSH key added to: ${users_for_ssh[*]}" log "SSH keys generated for: ${users_for_ssh[*]}" } setup_ssh_key_for_user() { local username="$1" local public_key="$2" local user_home="/home/$username" local ssh_dir="$user_home/.ssh" local authorized_keys="$ssh_dir/authorized_keys" log "Setting up SSH key for user: $username" # Create .ssh directory if it doesn't exist if [ ! -d "$ssh_dir" ]; then sudo -u "$username" mkdir -p "$ssh_dir" sudo -u "$username" chmod 700 "$ssh_dir" fi # Check if key already exists if [ -f "$authorized_keys" ] && grep -Fxq "$public_key" "$authorized_keys"; then warn "SSH key already exists for $username" else echo "$public_key" | sudo -u "$username" tee -a "$authorized_keys" > /dev/null sudo -u "$username" chmod 600 "$authorized_keys" log "SSH key added for $username" fi } generate_ssh_key_for_user() { local username="$1" local user_home="/home/$username" local ssh_dir="$user_home/.ssh" local private_key="$ssh_dir/id_ed25519" local public_key="$ssh_dir/id_ed25519.pub" # Check if SSH key already exists if [[ -f "$private_key" ]]; then log "SSH key already exists for $username, skipping generation" return fi log "Generating SSH key for user: $username" # Create .ssh directory if it doesn't exist if [ ! -d "$ssh_dir" ]; then sudo -u "$username" mkdir -p "$ssh_dir" sudo -u "$username" chmod 700 "$ssh_dir" fi # Generate SSH key without passphrase sudo -u "$username" ssh-keygen -t ed25519 -f "$private_key" -N "" -C "$username@$(hostname)" # Set proper permissions sudo -u "$username" chmod 600 "$private_key" sudo -u "$username" chmod 644 "$public_key" log "SSH key pair generated for $username" echo " Private key: $private_key" echo " Public key: $public_key" } ### === DEBIAN SYSTEM SETUP === ### setup_system() { log "Starting Debian 12 initial setup..." # Update system packages log "Updating system packages..." apt update && apt upgrade -y # Install essential packages log "Installing essential packages..." # Build package list based on user preferences local packages="sudo curl wget vim htop unzip git openssh-server" if [[ "$USE_UFW" == "yes" ]]; then packages="$packages ufw" fi if [[ "$USE_FAIL2BAN" == "yes" ]]; then packages="$packages fail2ban" fi log "Installing packages: $packages" apt install -y $packages } create_sysadmin_user() { log "Creating user: $SYSADMIN_USER" log "Checking if user $SYSADMIN_USER already exists..." if id "$SYSADMIN_USER" &>/dev/null; then warn "User $SYSADMIN_USER already exists, skipping creation" log "User $SYSADMIN_USER found in system, UID: $(id -u $SYSADMIN_USER)" SYSADMIN_USER_CREATED="no" # Generate and set a new strong password for existing user log "Generating strong password for existing user $SYSADMIN_USER..." SYSADMIN_NEW_PASSWORD=$(generate_password) echo "$SYSADMIN_USER:$SYSADMIN_NEW_PASSWORD" | chpasswd log "Password set for existing user $SYSADMIN_USER" else # Create user with home directory log "Attempting to create user $SYSADMIN_USER with home directory..." log "Running command: useradd -m -s /bin/bash $SYSADMIN_USER" if useradd -m -s /bin/bash "$SYSADMIN_USER"; then log "User $SYSADMIN_USER created successfully" log "User details: $(getent passwd $SYSADMIN_USER)" log "Home directory: $(getent passwd $SYSADMIN_USER | cut -d: -f6)" SYSADMIN_USER_CREATED="yes" else error "Failed to create user $SYSADMIN_USER. Exit code: $?" fi # Generate and set strong password for new user log "Generating strong password for $SYSADMIN_USER..." SYSADMIN_NEW_PASSWORD=$(generate_password) echo "$SYSADMIN_USER:$SYSADMIN_NEW_PASSWORD" | chpasswd log "Password set successfully for $SYSADMIN_USER" fi # Add sysadmin to sudo group log "Adding $SYSADMIN_USER to sudo group..." log "Running command: usermod -aG sudo $SYSADMIN_USER" if usermod -aG sudo "$SYSADMIN_USER"; then log "Successfully added $SYSADMIN_USER to sudo group" log "User groups: $(groups $SYSADMIN_USER)" else error "Failed to add $SYSADMIN_USER to sudo group. Exit code: $?" fi } create_additional_user() { if [[ "$CREATE_ADDITIONAL_USER" == "yes" && -n "$ADDITIONAL_USER" ]]; then log "Creating additional user: $ADDITIONAL_USER" log "Checking if user $ADDITIONAL_USER already exists..." if id "$ADDITIONAL_USER" &>/dev/null; then log "Using existing user: $ADDITIONAL_USER" log "User $ADDITIONAL_USER found in system, UID: $(id -u $ADDITIONAL_USER)" ADDITIONAL_USER_CREATED="no" else # Create user with home directory log "Attempting to create user $ADDITIONAL_USER with home directory..." log "Running command: useradd -m -s /bin/bash $ADDITIONAL_USER" if useradd -m -s /bin/bash "$ADDITIONAL_USER"; then log "User $ADDITIONAL_USER created successfully" log "User details: $(getent passwd $ADDITIONAL_USER)" log "Home directory: $(getent passwd $ADDITIONAL_USER | cut -d: -f6)" ADDITIONAL_USER_CREATED="yes" else error "Failed to create user $ADDITIONAL_USER. Exit code: $?" fi # Set password for additional user with retry logic local max_attempts=3 local attempt=1 while [ $attempt -le $max_attempts ]; do echo "Please set a password for user $ADDITIONAL_USER (attempt $attempt of $max_attempts):" if passwd "$ADDITIONAL_USER"; then log "Password set successfully for $ADDITIONAL_USER" break else warn "Failed to set password for $ADDITIONAL_USER" if [ $attempt -eq $max_attempts ]; then error "Failed to set password after $max_attempts attempts. Exiting." fi echo "Please try again..." ((attempt++)) fi done fi # Add additional user to sudo group (existing or newly created) log "Adding $ADDITIONAL_USER to sudo group..." log "Running command: usermod -aG sudo $ADDITIONAL_USER" if usermod -aG sudo "$ADDITIONAL_USER"; then log "Successfully added $ADDITIONAL_USER to sudo group" log "User groups: $(groups $ADDITIONAL_USER)" else error "Failed to add $ADDITIONAL_USER to sudo group. Exit code: $?" fi fi } configure_security() { # Disable root SSH login log "Disabling root SSH login..." sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config # Sysadmin SSH login is ENABLED (no DenyUsers for sysadmin) log "Sysadmin SSH login will be enabled..." # Configure SSH settings log "Applying SSH security settings..." # Configure basic SSH security - enable password auth by default for safety sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config # Configure other SSH settings more safely sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config sed -i 's/^#*PermitEmptyPasswords.*/PermitEmptyPasswords no/' /etc/ssh/sshd_config sed -i 's/^#*X11Forwarding.*/X11Forwarding no/' /etc/ssh/sshd_config sed -i 's/^#*MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config # Add settings that might not exist if ! grep -q "^ClientAliveInterval" /etc/ssh/sshd_config; then echo "ClientAliveInterval 300" >> /etc/ssh/sshd_config else sed -i 's/^#*ClientAliveInterval.*/ClientAliveInterval 300/' /etc/ssh/sshd_config fi if ! grep -q "^ClientAliveCountMax" /etc/ssh/sshd_config; then echo "ClientAliveCountMax 2" >> /etc/ssh/sshd_config else sed -i 's/^#*ClientAliveCountMax.*/ClientAliveCountMax 2/' /etc/ssh/sshd_config fi # Configure AllowUsers (remove old entries first) # Sysadmin is always allowed to SSH in sed -i '/^AllowUsers/d' /etc/ssh/sshd_config if [[ "$CREATE_ADDITIONAL_USER" == "yes" && -n "$ADDITIONAL_USER" ]]; then echo "AllowUsers $SYSADMIN_USER $ADDITIONAL_USER" >> /etc/ssh/sshd_config log "SSH access enabled for: $SYSADMIN_USER and $ADDITIONAL_USER" else echo "AllowUsers $SYSADMIN_USER" >> /etc/ssh/sshd_config log "SSH access enabled for: $SYSADMIN_USER" fi # Test SSH configuration log "Testing SSH configuration..." sshd -t || error "SSH configuration is invalid!" # Remove/lock root password log "Locking root password..." passwd -l root # Sysadmin password is NOT locked - it has a strong generated password instead log "Sysadmin password remains unlocked with strong generated password" # Configure firewall if requested if [[ "$USE_UFW" == "yes" ]]; then configure_ufw fi # Configure fail2ban if requested if [[ "$USE_FAIL2BAN" == "yes" ]]; then configure_fail2ban fi # Set up automatic security updates log "Configuring automatic security updates..." apt install -y unattended-upgrades apt-listchanges # Configure unattended upgrades cat > /etc/apt/apt.conf.d/50unattended-upgrades << EOF Unattended-Upgrade::Allowed-Origins { "\${distro_id}:\${distro_codename}-security"; "\${distro_id}ESMApps:\${distro_codename}-apps-security"; "\${distro_id}ESM:\${distro_codename}-infra-security"; }; Unattended-Upgrade::AutoFixInterruptedDpkg "true"; Unattended-Upgrade::MinimalSteps "true"; Unattended-Upgrade::Remove-Unused-Dependencies "true"; Unattended-Upgrade::Automatic-Reboot "false"; EOF # Enable automatic updates echo 'APT::Periodic::Update-Package-Lists "1";' > /etc/apt/apt.conf.d/20auto-upgrades echo 'APT::Periodic::Unattended-Upgrade "1";' >> /etc/apt/apt.conf.d/20auto-upgrades # Set timezone (optional) log "Current timezone: $(timedatectl show --property=Timezone --value)" echo "If you want to change timezone, run: timedatectl set-timezone Your/Timezone" } configure_ufw() { log "Configuring UFW firewall..." # Reset UFW to defaults ufw --force reset # Set default policies ufw default deny incoming ufw default allow outgoing # Allow SSH (port 22) ufw allow ssh ufw allow 22/tcp # Allow HTTP (port 80) ufw allow http ufw allow 80/tcp # Allow HTTPS (port 443) ufw allow https ufw allow 443/tcp # Enable UFW ufw --force enable log "UFW status:" ufw status verbose } configure_fail2ban() { if [[ "$USE_FAIL2BAN" == "yes" ]]; then log "Configuring fail2ban..." # Ensure fail2ban directories exist mkdir -p /etc/fail2ban/jail.d # Create configuration in jail.d instead of overwriting jail.local cat > /etc/fail2ban/jail.d/custom.conf << EOF [DEFAULT] bantime = 1h findtime = 10m maxretry = 3 ignoreip = 127.0.0.1/8 ::1 [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 bantime = 1h EOF # Verify log file exists if [[ ! -f /var/log/auth.log ]]; then warn "Auth log file doesn't exist yet, creating it..." touch /var/log/auth.log chmod 640 /var/log/auth.log fi # Start and enable fail2ban with error handling systemctl enable fail2ban # Give a moment for systemd to process sleep 2 if systemctl start fail2ban; then # Verify it's actually running if systemctl is-active --quiet fail2ban; then log "Fail2ban configured and started successfully" # Show status briefly systemctl status fail2ban --no-pager --lines=3 else warn "Fail2ban started but may have issues. Check: systemctl status fail2ban" fi else error "Failed to start fail2ban service" fi fi } ### === CLEANUP AND FINALIZATION === ### finalize_setup() { # Restart SSH service safely log "Restarting SSH service..." if ! systemctl restart sshd; then error "Failed to restart SSH service! Check configuration and try again." log "You can restore SSH configuration from backup if needed:" log "ls /etc/ssh/sshd_config.backup.*" exit 1 fi # Verify SSH service is running if ! systemctl is-active --quiet sshd; then error "SSH service is not running after restart!" exit 1 fi log "SSH service restarted successfully" # Copy customization script to sysadmin user home folder (always replace if exists) log "Copying customization script..." if [[ -f "costumize.sh" ]]; then log "Local costumize.sh found, copying to /home/$SYSADMIN_USER/costumize.sh (replacing if exists)" cp -f costumize.sh /home/$SYSADMIN_USER/costumize.sh chmod +x /home/$SYSADMIN_USER/costumize.sh chown $SYSADMIN_USER:$SYSADMIN_USER /home/$SYSADMIN_USER/costumize.sh log "Customization script copied to /home/$SYSADMIN_USER/costumize.sh" else # Fallback to download if local file doesn't exist log "Local costumize.sh not found, attempting download..." if wget -O /home/$SYSADMIN_USER/costumize.sh "https://git.del-c.net/Del-c.net/debian-first-boot-setup/raw/branch/main/costumize.sh"; then chmod +x /home/$SYSADMIN_USER/costumize.sh chown $SYSADMIN_USER:$SYSADMIN_USER /home/$SYSADMIN_USER/costumize.sh log "Customization script downloaded to /home/$SYSADMIN_USER/costumize.sh" else warn "Failed to copy or download customization script" fi fi # Clean up log "Cleaning up..." apt autoremove -y apt autoclean log "Setup completed successfully!" echo "" echo -e "${BLUE}=== SETUP SUMMARY ===${NC}" echo -e "${YELLOW}• Root SSH login: DISABLED${NC}" echo -e "${YELLOW}• Root password: LOCKED${NC}" echo -e "${GREEN}• Sysadmin SSH login: ENABLED${NC}" echo -e "${GREEN}• Sysadmin password: UNLOCKED (strong password set)${NC}" echo -e "${YELLOW}• Main user: $SYSADMIN_USER (sudo access)${NC}" # Always display the generated password prominently if [[ -n "$SYSADMIN_NEW_PASSWORD" ]]; then echo "" echo -e "${RED}========================================${NC}" echo -e "${RED} IMPORTANT - SAVE THIS PASSWORD!${NC}" echo -e "${RED}========================================${NC}" echo -e "${RED}Sysadmin user: $SYSADMIN_USER${NC}" echo -e "${RED}Password: $SYSADMIN_NEW_PASSWORD${NC}" echo -e "${RED}========================================${NC}" echo "" fi if [[ "$CREATE_ADDITIONAL_USER" == "yes" && -n "$ADDITIONAL_USER" ]]; then echo -e "${YELLOW}• Additional user: $ADDITIONAL_USER (sudo access)${NC}" fi if [[ "$USE_UFW" == "yes" ]]; then echo -e "${YELLOW}• UFW firewall: ENABLED (SSH, HTTP, HTTPS)${NC}" fi if [[ "$USE_FAIL2BAN" == "yes" ]]; then echo -e "${YELLOW}• Fail2ban: ENABLED (SSH protection)${NC}" fi echo -e "${YELLOW}• Automatic security updates: ENABLED${NC}" echo "" echo -e "${GREEN}Connect with: ssh $SYSADMIN_USER@$(hostname -I | awk '{print $1}')${NC}" echo -e "${YELLOW}Reboot recommended${NC}" } ### === MAIN === ### main() { # Check prerequisites check_root check_debian check_commands echo -e "${BLUE}=== Debian 12 Initial Setup ===${NC}" echo "This script will set up a secure Debian 12 system with:" echo "• Sysadmin user with sudo access" echo "• Optional additional user" echo "• SSH security hardening" echo "• Optional UFW firewall and fail2ban" echo "" read -p "Continue? (y/N): " confirm if [[ ! "$confirm" =~ ^[Yy]$ ]]; then echo "Setup cancelled." exit 0 fi # Ask for user and security preferences ask_additional_user ask_firewall_preferences # System setup setup_system create_sysadmin_user create_additional_user # SSH key setup for created users ask_ssh_key_setup configure_security # Finalize finalize_setup # Self-delete the script after successful completion log "Cleaning up setup script..." rm -f "$0" log "Setup script deleted successfully" } main "$@"