Files
debian-first-boot-setup/setup.sh

534 lines
16 KiB
Bash
Executable File

#!/bin/bash
# Debian 12 Initial Setup Script with Git Integration
# 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 === ###
GIT_SERVER="git.del-c.net"
USERNAME=""
SELECTED_REPO=""
SELECTED_DIR=""
SYSADMIN_USER="sysadmin"
# 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
}
# 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
}
### === 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..."
apt install -y sudo ufw curl wget vim htop unzip fail2ban git openssh-server
}
create_sysadmin_user() {
log "Creating user: $SYSADMIN_USER"
if id "$SYSADMIN_USER" &>/dev/null; then
warn "User $SYSADMIN_USER already exists, skipping creation"
else
# Create user with home directory
useradd -m -s /bin/bash "$SYSADMIN_USER"
log "User $SYSADMIN_USER created successfully"
# Set password for sysadmin user
echo "Please set a password for user $SYSADMIN_USER:"
passwd "$SYSADMIN_USER"
fi
# Add sysadmin to sudo group
log "Adding $SYSADMIN_USER to sudo group..."
usermod -aG sudo "$SYSADMIN_USER"
}
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
# Additional SSH hardening
log "Applying SSH security settings..."
{
echo "Protocol 2"
echo "PasswordAuthentication no"
echo "PubkeyAuthentication yes"
echo "PermitEmptyPasswords no"
echo "X11Forwarding no"
echo "MaxAuthTries 3"
echo "ClientAliveInterval 300"
echo "ClientAliveCountMax 2"
echo "AllowUsers $SYSADMIN_USER"
} >> /etc/ssh/sshd_config
# Remove/lock root password
log "Locking root password..."
passwd -l root
# Configure UFW (Uncomplicated Firewall)
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 for SSH protection
log "Configuring fail2ban..."
cat > /etc/fail2ban/jail.local << EOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 1h
EOF
# Start and enable fail2ban
systemctl enable fail2ban
systemctl start fail2ban
# 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"
}
### === GET USERNAME === ###
get_username() {
echo "[?] Enter your git username:"
read -p "Username: " USERNAME
if [ -z "$USERNAME" ]; then
echo "[!] Username cannot be empty. Please try again."
get_username
fi
echo "[*] Using username: $USERNAME"
echo
}
### === SSH KEY GENERATION FOR SYSADMIN === ###
generate_ssh_key() {
local sysadmin_home="/home/$SYSADMIN_USER"
local ssh_dir="$sysadmin_home/.ssh"
local ssh_key="$ssh_dir/id_ed25519"
log "Setting up SSH key for user: $SYSADMIN_USER"
# Create .ssh directory for sysadmin user if it doesn't exist
if [ ! -d "$ssh_dir" ]; then
sudo -u "$SYSADMIN_USER" mkdir -p "$ssh_dir"
sudo -u "$SYSADMIN_USER" chmod 700 "$ssh_dir"
fi
if [ ! -f "$ssh_key.pub" ]; then
log "Generating SSH key for $SYSADMIN_USER..."
echo "[+] Please enter your email for the SSH key:"
read -p "Enter your email: " user_email
# Generate SSH key as sysadmin user
sudo -u "$SYSADMIN_USER" ssh-keygen -t ed25519 -C "$user_email" -f "$ssh_key" -N ""
# Set proper permissions
sudo -u "$SYSADMIN_USER" chmod 600 "$ssh_key"
sudo -u "$SYSADMIN_USER" chmod 644 "$ssh_key.pub"
log "SSH key generated successfully for $SYSADMIN_USER"
else
warn "SSH key already exists for $SYSADMIN_USER"
fi
}
show_ssh_key() {
local sysadmin_home="/home/$SYSADMIN_USER"
local ssh_key="$sysadmin_home/.ssh/id_ed25519.pub"
echo ""
echo -e "${BLUE}=== SSH PUBLIC KEY ===${NC}"
echo "Add the following public key to your Git server and any other systems:"
echo "----------------------------------------"
cat "$ssh_key"
echo "----------------------------------------"
echo ""
echo -e "${YELLOW}Instructions:${NC}"
echo "1. Copy the key above"
echo "2. Go to your git server ($GIT_SERVER) and add it to your account"
echo "3. Add it to any other systems you need SSH access to"
echo ""
read -p "Press Enter after you've added the key to continue..."
}
### === LIST AVAILABLE GIT PROJECTS === ###
list_git_projects() {
echo "[+] Fetching all accessible git projects..."
local repo_count=1
local found_repos=()
# Try to use git server API if available (GitLab/Gitea style)
if command -v curl &> /dev/null; then
echo "[*] Attempting to fetch repositories via API..."
# Try GitLab API format
local gitlab_repos=$(curl -s -H "Authorization: Bearer $(ssh-add -L 2>/dev/null || echo '')" \
"https://$GIT_SERVER/api/v4/projects?membership=true&per_page=100" 2>/dev/null | \
grep -o '"path_with_namespace":"[^"]*' | cut -d'"' -f4 2>/dev/null || echo "")
# Try Gitea API format
if [ -z "$gitlab_repos" ]; then
local gitea_repos=$(curl -s "https://$GIT_SERVER/api/v1/user/repos" 2>/dev/null | \
grep -o '"full_name":"[^"]*' | cut -d'"' -f4 2>/dev/null || echo "")
gitlab_repos="$gitea_repos"
fi
if [ -n "$gitlab_repos" ]; then
echo "[*] Found repositories via API:"
while IFS= read -r repo; do
if [ -n "$repo" ]; then
echo "$repo_count. $repo"
found_repos+=("$repo")
((repo_count++))
fi
done <<< "$gitlab_repos"
fi
fi
# Fallback: Check common patterns for multiple users and the current user
if [ ${#found_repos[@]} -eq 0 ]; then
echo "[*] API not available, scanning common repository patterns..."
# Check user's own repositories first
for repo in "dotfiles" "scripts" "configs" "tools" "projects" "notes" "backup" "workspace"; do
local full_repo="$USERNAME/$repo"
if git ls-remote "git@$GIT_SERVER:$full_repo.git" &>/dev/null; then
echo "$repo_count. $full_repo (owner)"
found_repos+=("$full_repo")
((repo_count++))
fi
done
# Check common shared repositories and other users' public repos
local common_users=("admin" "shared" "public" "team" "common" "devops" "infrastructure")
local common_repos=("dotfiles" "scripts" "configs" "tools" "projects" "notes" "backup" "workspace" "templates" "shared-configs" "common-tools")
for user in "${common_users[@]}"; do
for repo in "${common_repos[@]}"; do
local full_repo="$user/$repo"
if [ "$user" != "$USERNAME" ] && git ls-remote "git@$GIT_SERVER:$full_repo.git" &>/dev/null; then
echo "$repo_count. $full_repo (shared)"
found_repos+=("$full_repo")
((repo_count++))
fi
done
done
# Try to discover repositories by checking SSH access to common paths
echo "[*] Checking for additional accessible repositories..."
# This is a more aggressive approach - try common project names
local project_names=("website" "api" "frontend" "backend" "database" "monitoring" "deployment" "ci-cd" "documentation")
for user in "${common_users[@]}" "$USERNAME"; do
for project in "${project_names[@]}"; do
local full_repo="$user/$project"
if git ls-remote "git@$GIT_SERVER:$full_repo.git" &>/dev/null 2>&1; then
# Check if we haven't already added this repo
local already_added=false
for existing_repo in "${found_repos[@]}"; do
if [ "$existing_repo" = "$full_repo" ]; then
already_added=true
break
fi
done
if [ "$already_added" = false ]; then
local access_type="shared"
if [ "$user" = "$USERNAME" ]; then
access_type="owner"
fi
echo "$repo_count. $full_repo ($access_type)"
found_repos+=("$full_repo")
((repo_count++))
fi
fi
done
done
fi
if [ ${#found_repos[@]} -eq 0 ]; then
echo "[!] No accessible repositories found"
echo "[*] You can still enter a custom repository path."
else
echo
echo "[*] Found ${#found_repos[@]} accessible repositories"
fi
echo
}
### === SELECT PROJECT TO DOWNLOAD === ###
select_project() {
list_git_projects
echo "[?] Which project would you like to download?"
echo "Enter the number of the repository from the list above, or:"
echo "- Press Enter to use $USERNAME/dotfiles (if available)"
echo "- Type 'custom' to enter a custom repository path"
echo
read -p "Enter your choice: " choice
if [ -z "$choice" ]; then
# Default to dotfiles if it exists
if git ls-remote "git@$GIT_SERVER:$USERNAME/dotfiles.git" &>/dev/null; then
SELECTED_REPO="git@$GIT_SERVER:$USERNAME/dotfiles.git"
SELECTED_DIR="$HOME/.dotfiles"
else
echo "[!] $USERNAME/dotfiles not found. Please select a repository."
select_project
return
fi
elif [ "$choice" = "custom" ]; then
read -p "Enter repository path (e.g., $USERNAME/myproject): " custom_repo
SELECTED_REPO="git@$GIT_SERVER:$custom_repo.git"
SELECTED_DIR="$HOME/$(basename $custom_repo)"
elif [[ "$choice" =~ ^[0-9]+$ ]]; then
# User selected a number from the list
local repo_count=1
local selected_repo=""
for repo in "dotfiles" "scripts" "configs" "tools" "projects" "notes" "backup" "workspace"; do
local full_repo="$USERNAME/$repo"
if git ls-remote "git@$GIT_SERVER:$full_repo.git" &>/dev/null; then
if [ "$repo_count" -eq "$choice" ]; then
selected_repo="$full_repo"
break
fi
((repo_count++))
fi
done
if [ -n "$selected_repo" ]; then
SELECTED_REPO="git@$GIT_SERVER:$selected_repo.git"
if [ "$(basename $selected_repo)" = "dotfiles" ]; then
SELECTED_DIR="$HOME/.dotfiles"
else
SELECTED_DIR="$HOME/$(basename $selected_repo)"
fi
else
echo "[!] Invalid choice. Please try again."
select_project
return
fi
else
echo "[!] Invalid choice. Please try again."
select_project
return
fi
echo "[*] Selected repository: $SELECTED_REPO"
echo "[*] Download directory: $SELECTED_DIR"
echo
}
### === CLONE SELECTED REPO === ###
clone_selected_repo() {
local sysadmin_home="/home/$SYSADMIN_USER"
# Update selected directory to be relative to sysadmin user's home
if [[ "$SELECTED_DIR" == "$HOME/"* ]]; then
SELECTED_DIR="${SELECTED_DIR/$HOME/$sysadmin_home}"
fi
if [ ! -d "$SELECTED_DIR" ]; then
log "Cloning selected repository as $SYSADMIN_USER..."
sudo -u "$SYSADMIN_USER" git clone "$SELECTED_REPO" "$SELECTED_DIR"
else
warn "Repository already cloned. Force pulling from remote (overwriting local changes)..."
cd "$SELECTED_DIR"
sudo -u "$SYSADMIN_USER" git fetch origin
sudo -u "$SYSADMIN_USER" git reset --hard origin/$(sudo -u "$SYSADMIN_USER" git symbolic-ref --short HEAD)
sudo -u "$SYSADMIN_USER" git clean -fd
fi
# Only run setup if it's the dotfiles repository
if [[ "$SELECTED_REPO" == *"dotfiles.git" && -f "$SELECTED_DIR/setup.sh" ]]; then
log "Launching dotfiles setup as $SYSADMIN_USER..."
sudo -u "$SYSADMIN_USER" bash "$SELECTED_DIR/setup.sh"
else
log "Repository cloned successfully to: $SELECTED_DIR"
log "Repository is owned by: $SYSADMIN_USER"
fi
}
### === CLEANUP AND FINALIZATION === ###
finalize_setup() {
# Restart SSH service
log "Restarting SSH service..."
systemctl restart sshd
# Clean up
log "Cleaning up..."
apt autoremove -y
apt autoclean
# Final security check
log "Final system status:"
echo "================================"
echo "UFW Status:"
ufw status
echo "================================"
echo "SSH Configuration:"
grep -E "PermitRootLogin|PasswordAuthentication|PubkeyAuthentication|AllowUsers" /etc/ssh/sshd_config
echo "================================"
echo "Sudo users:"
grep -E "sudo|wheel" /etc/group
echo "================================"
log "Setup completed successfully!"
echo ""
echo -e "${BLUE}=== IMPORTANT NOTES ===${NC}"
echo -e "${YELLOW}1. Root SSH login is now DISABLED${NC}"
echo -e "${YELLOW}2. Root password is LOCKED${NC}"
echo -e "${YELLOW}3. SSH password authentication is DISABLED${NC}"
echo -e "${YELLOW}4. Use '$SYSADMIN_USER' user with SSH key to connect${NC}"
echo -e "${YELLOW}5. UFW firewall is active (SSH, HTTP, HTTPS allowed)${NC}"
echo -e "${YELLOW}6. Fail2ban is protecting SSH${NC}"
echo -e "${YELLOW}7. Automatic security updates are enabled${NC}"
echo ""
echo -e "${RED}REBOOT RECOMMENDED${NC}"
echo ""
echo "To connect: ssh $SYSADMIN_USER@$(hostname -I | awk '{print $1}')"
}
### === MAIN === ###
main() {
# Check prerequisites
check_root
check_debian
echo -e "${BLUE}=== Debian 12 Initial Setup with Git Integration ===${NC}"
echo "This script will:"
echo "1. Set up a secure Debian 12 system"
echo "2. Create a sysadmin user with SSH key authentication"
echo "3. Optionally clone a git repository"
echo ""
read -p "Continue? (y/N): " confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo "Setup cancelled."
exit 0
fi
# System setup
setup_system
create_sysadmin_user
configure_security
# SSH key setup
generate_ssh_key
show_ssh_key
# Optional git repository setup
echo ""
echo -e "${BLUE}=== Optional Git Repository Setup ===${NC}"
read -p "Would you like to clone a git repository? (y/N): " git_setup
if [[ "$git_setup" =~ ^[Yy]$ ]]; then
get_username
select_project
clone_selected_repo
else
log "Skipping git repository setup"
fi
# Finalize
finalize_setup
}
main "$@"