diff --git a/install-mailcow-ubuntu.sh b/install-mailcow-ubuntu.sh index 5979ac2..1243d7e 100644 --- a/install-mailcow-ubuntu.sh +++ b/install-mailcow-ubuntu.sh @@ -1,75 +1,99 @@ #!/bin/bash -# Exit on any error +# Exit on error set -e +# Checkpoint file +CHECKPOINT_FILE="/tmp/mailcow_install_progress" +MAILCOW_DIR="/opt/mailcow-dockerized" + # Function to log messages log_message() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } +# Function to save checkpoint +save_checkpoint() { + echo "$1" > "$CHECKPOINT_FILE" + log_message "Checkpoint saved: $1" +} + +# Function to get last checkpoint +get_checkpoint() { + if [ -f "$CHECKPOINT_FILE" ]; then + cat "$CHECKPOINT_FILE" + else + echo "start" + fi +} + +# Function to check if a step is completed +is_step_completed() { + local current_checkpoint=$(get_checkpoint) + local step=$1 + + case $current_checkpoint in + "docker_installed"|"fail2ban_configured"|"mailcow_installed"|"complete") + if [ "$step" = "system_updated" ]; then return 0; fi + ;; + "fail2ban_configured"|"mailcow_installed"|"complete") + if [ "$step" = "docker_installed" ]; then return 0; fi + ;; + "mailcow_installed"|"complete") + if [ "$step" = "fail2ban_configured" ]; then return 0; fi + ;; + "complete") + if [ "$step" = "mailcow_installed" ]; then return 0; fi + ;; + esac + return 1 +} + # Check if script is run as root if [ "$EUID" -ne 0 ]; then echo "Please run as root" exit 1 fi -# Update system -log_message "Updating system packages..." -apt-get update && apt-get upgrade -y +# Start or resume installation +log_message "Starting or resuming installation from checkpoint: $(get_checkpoint)" -# Install essential packages -log_message "Installing essential packages..." -apt-get install -y \ - ca-certificates \ - curl \ - gnupg \ - ufw \ - fail2ban \ - git \ - sudo +# Step 1: Update system +if ! is_step_completed "system_updated"; then + log_message "Updating system packages..." + apt-get update && apt-get upgrade -y + apt-get install -y ca-certificates curl gnupg ufw fail2ban git sudo + save_checkpoint "system_updated" +fi -# Install Docker -log_message "Installing Docker..." -install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc -chmod a+r /etc/apt/keyrings/docker.asc +# Step 2: Install Docker +if ! is_step_completed "docker_installed"; then + log_message "Installing Docker..." + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc -# Add Docker repository -echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ - tee /etc/apt/sources.list.d/docker.list > /dev/null + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + tee /etc/apt/sources.list.d/docker.list > /dev/null -apt-get update -apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -# Create docker user and set up permissions -log_message "Creating docker user and setting up permissions..." -useradd -m -s /bin/bash dockeruser -usermod -aG docker dockeruser -echo "dockeruser ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/docker-compose" | sudo tee /etc/sudoers.d/dockeruser + # Create docker user if not exists + if ! id "dockeruser" &>/dev/null; then + useradd -m -s /bin/bash dockeruser + usermod -aG docker dockeruser + fi -# Configure UFW -log_message "Configuring firewall..." -ufw default deny incoming -ufw default allow outgoing -ufw allow 22/tcp -ufw allow 25/tcp -ufw allow 80/tcp -ufw allow 110/tcp -ufw allow 143/tcp -ufw allow 443/tcp -ufw allow 465/tcp -ufw allow 587/tcp -ufw allow 993/tcp -ufw allow 995/tcp -ufw allow 4190/tcp -ufw --force enable + save_checkpoint "docker_installed" +fi -# Configure fail2ban for SSH -log_message "Configuring fail2ban for SSH protection..." -cat > /etc/fail2ban/jail.local << EOL +# Step 3: Configure fail2ban +if ! is_step_completed "fail2ban_configured"; then + log_message "Configuring fail2ban..." + cat > /etc/fail2ban/jail.local << EOL [DEFAULT] bantime = 1h findtime = 10m @@ -89,129 +113,83 @@ bantime = 1d findtime = 10m EOL -# Reload systemd daemon -systemctl daemon-reload - -# Restart fail2ban -systemctl restart fail2ban - -# Restart fail2ban -systemctl restart fail2ban - -# Set up Mailcow directory and permissions -log_message "Setting up Mailcow directory..." -mkdir -p /opt/mailcow-dockerized -chown dockeruser:dockeruser /opt/mailcow-dockerized - -# Function to get FQDN input -get_fqdn() { - local fqdn - while true; do - read -p "Please enter your Fully Qualified Domain Name (FQDN) (e.g., mail.example.com): " fqdn - if [[ $fqdn =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then - echo "$fqdn" - return 0 - else - echo "Invalid FQDN format. Please try again." - fi - done -} - -# Get timezone -get_timezone() { - local timezone - while true; do - read -p "Please enter your timezone (e.g., Europe/Berlin) [default: UTC]: " timezone - timezone=${timezone:-UTC} - if [ -f "/usr/share/zoneinfo/$timezone" ]; then - echo "$timezone" - return 0 - else - echo "Invalid timezone. Please try again." - fi - done -} - -# Function to configure mailcow -configure_mailcow() { - local fqdn=$1 - local timezone=$2 - - # Create temporary config file - cat > /tmp/mailcow_config << EOL -MAILCOW_HOSTNAME=${fqdn} -TIMEZONE=${timezone} -EOL - - # Ask for additional configuration - read -p "Do you want to customize additional mailcow configuration? (y/N): " customize - if [[ $customize =~ ^[Yy]$ ]]; then - nano /tmp/mailcow_config - fi - - return 0 -} - -# Switch to dockeruser and install Mailcow -log_message "Installing Mailcow as dockeruser..." - -# Get configuration parameters -FQDN=$(get_fqdn) -TIMEZONE=$(get_timezone) - -# Store configuration for dockeruser -echo "FQDN=$FQDN" > /tmp/mailcow_vars -echo "TIMEZONE=$TIMEZONE" >> /tmp/mailcow_vars - -configure_mailcow "$FQDN" "$TIMEZONE" - -su - dockeruser << 'EOF' -# Source the configuration variables -source /tmp/mailcow_vars - -# Clone and set up mailcow -cd /opt -git clone https://github.com/mailcow/mailcow-dockerized -cd mailcow-dockerized - -# Use the prepared configuration -cat /tmp/mailcow_config > mailcow.conf - -# Generate config with the provided FQDN -printf "%s\n" "$FQDN" | ./generate_config.sh - -# Offer to edit the full configuration -read -p "Would you like to review and edit the full mailcow configuration? (y/N): " edit_conf -if [[ $edit_conf =~ ^[Yy]$ ]]; then - nano mailcow.conf + systemctl restart fail2ban + save_checkpoint "fail2ban_configured" fi -# Start Mailcow -docker compose pull -docker compose up -d -EOF +# Step 4: Install and configure Mailcow +if ! is_step_completed "mailcow_installed"; then + log_message "Setting up Mailcow..." + + # Check if FQDN is already set + if [ ! -f "/tmp/mailcow_hostname" ]; then + read -p "Please enter your Fully Qualified Domain Name (FQDN) (e.g., mail.example.com): " MAILCOW_HOSTNAME + echo "$MAILCOW_HOSTNAME" > /tmp/mailcow_hostname + else + MAILCOW_HOSTNAME=$(cat /tmp/mailcow_hostname) + log_message "Using saved FQDN: $MAILCOW_HOSTNAME" + fi -# Clean up temporary files -rm -f /tmp/mailcow_vars /tmp/mailcow_config + # Create mailcow directory if not exists + if [ ! -d "$MAILCOW_DIR" ]; then + mkdir -p "$MAILCOW_DIR" + chown dockeruser:dockeruser "$MAILCOW_DIR" + + # Clone repository + su - dockeruser -c "cd /opt && git clone https://github.com/mailcow/mailcow-dockerized" + fi -# Final security checks -log_message "Performing final security checks..." -su - dockeruser -c "cd /opt/mailcow-dockerized && docker compose ps" -ufw status -systemctl status fail2ban + # Generate config if not already generated + if [ ! -f "$MAILCOW_DIR/mailcow.conf" ]; then + cd "$MAILCOW_DIR" + echo "$MAILCOW_HOSTNAME" | ./generate_config.sh + + echo "Would you like to edit the mailcow configuration? (y/N)" + read -n1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + nano mailcow.conf + fi + fi -log_message "Installation complete! Please check the logs above for any errors." -log_message "Remember to:" -log_message "1. Change the default passwords" -log_message "2. Configure your DNS records" -log_message "3. Set up SSL certificates" -log_message "4. Regular backup your configuration" + # Start Mailcow services + log_message "Starting Mailcow services..." + su - dockeruser -c "cd $MAILCOW_DIR && docker compose pull && docker compose up -d" + + save_checkpoint "mailcow_installed" +fi -# Print access information -echo "================================================================" -echo "Mailcow web interface: https://$(hostname)" -echo "Docker user: dockeruser" -echo "Mailcow location: /opt/mailcow-dockerized" -echo "Firewall status:" -ufw status numbered -echo "================================================================" \ No newline at end of file +# Configure UFW if not already configured +if ! ufw status | grep -q "Status: active"; then + log_message "Configuring firewall..." + ufw default deny incoming + ufw default allow outgoing + ufw allow 22/tcp + ufw allow 25/tcp + ufw allow 80/tcp + ufw allow 110/tcp + ufw allow 143/tcp + ufw allow 443/tcp + ufw allow 465/tcp + ufw allow 587/tcp + ufw allow 993/tcp + ufw allow 995/tcp + ufw allow 4190/tcp + ufw --force enable +fi + +# Final steps and cleanup +if [ "$(get_checkpoint)" = "mailcow_installed" ]; then + log_message "Installation completed successfully!" + rm -f "$CHECKPOINT_FILE" "/tmp/mailcow_hostname" + save_checkpoint "complete" + + # Print access information + echo "================================================================" + echo "Mailcow web interface: https://$MAILCOW_HOSTNAME" + echo "Docker user: dockeruser" + echo "Mailcow location: $MAILCOW_DIR" + echo "Firewall status:" + ufw status numbered + echo "================================================================" +fi \ No newline at end of file