#!/bin/bash

# devsecops_setup.sh
# Creates a DevSecOps learning environment on Kali Linux

set -euo pipefail
IFS=$'\n\t'

# Setup logging
LOG_FILE="/var/log/devsecops_setup.log"
SCRIPT_NAME=$(basename "$0")

# Log functions with timestamps
log_info() {
    local msg="[$(date +'%Y-%m-%d %H:%M:%S')] [INFO] $1"
    echo "$msg" | tee -a "$LOG_FILE"
}

log_error() {
    local msg="[$(date +'%Y-%m-%d %H:%M:%S')] [ERROR] $1"
    echo "$msg" | tee -a "$LOG_FILE" >&2
}

log_warning() {
    local msg="[$(date +'%Y-%m-%d %H:%M:%S')] [WARNING] $1"
    echo "$msg" | tee -a "$LOG_FILE"
}

# Error handler
handle_error() {
    local line_num=$1
    local error_code=$2
    log_error "Error in $SCRIPT_NAME at line $line_num (Exit code: $error_code)"
}

# Set up error handling
trap 'handle_error ${LINENO} $?' ERR

# Initialize log file
init_logging() {
    if [[ ! -f "$LOG_FILE" ]]; then
        touch "$LOG_FILE"
        chmod 644 "$LOG_FILE"
    fi
    log_info "Starting setup script execution"
    log_info "Logging to $LOG_FILE"
}

# Check if running as root
if [[ $EUID -ne 0 ]]; then
   log_error "This script must be run as root"
   exit 1
fi

# Verify we're running on Kali Linux
if ! grep -q "Kali" /etc/os-release; then
    log_error "This script must be run on Kali Linux"
    exit 1
fi

# Install additional development tools
install_dev_tools() {
    log_info "Installing development tools..."
    # Prevent interactive prompts for service restarts
    export DEBIAN_FRONTEND=noninteractive

    apt-get update >> "$LOG_FILE" 2>&1
    apt-get install -y \
        docker.io \
        docker-compose \
        openjdk-11-jdk \
        maven \
        gradle \
        python3-venv \
        python3-full \
        jq \
        pipx >> "$LOG_FILE" 2>&1 || {
            log_error "Failed to install development tools"
            return 1
        }

    # Initialize pipx and update PATH
    pipx ensurepath >> "$LOG_FILE" 2>&1
    export PATH="/root/.local/bin:$PATH"
    
    # Add PATH to root's bashrc if not already present
    if ! grep -q "/root/.local/bin" /root/.bashrc; then
        echo 'export PATH="/root/.local/bin:$PATH"' >> /root/.bashrc
        log_info "Added .local/bin to PATH in .bashrc"
    fi

    # Install Python tools using pipx
    log_info "Installing Python tools using pipx..."
    pipx install safety >> "$LOG_FILE" 2>&1 || log_error "Failed to install safety"
    pipx install bandit >> "$LOG_FILE" 2>&1 || log_error "Failed to install bandit"
}

# Install dependency scanning tools
install_dep_scanners() {
    log_info "Installing dependency scanners..."
    
    # OWASP Dependency-Check
    log_info "Installing OWASP Dependency-Check..."
    wget https://github.com/jeremylong/DependencyCheck/releases/download/v7.1.1/dependency-check-7.1.1-release.zip >> "$LOG_FILE" 2>&1
    unzip dependency-check-7.1.1-release.zip -d /opt/ >> "$LOG_FILE" 2>&1
    ln -sf /opt/dependency-check/bin/dependency-check.sh /usr/local/bin/dependency-check
    
    # Install Trivy directly from .deb package
    log_info "Installing Trivy..."
    TRIVY_VERSION=$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
    log_info "Latest Trivy version: $TRIVY_VERSION"
    
    wget "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.deb" -O trivy.deb >> "$LOG_FILE" 2>&1 || {
        log_error "Failed to download Trivy"
        return 1
    }
    
    dpkg -i trivy.deb >> "$LOG_FILE" 2>&1 || {
        log_error "Failed to install Trivy"
        return 1
    }
    
    rm trivy.deb
    log_info "Trivy installation completed"
}

# Set up GitLab with CI/CD
setup_gitlab_cicd() {
    log_info "Setting up GitLab with CI/CD..."
    
    # Function to check if a port is being listened on
    check_port() {
        local port=$1
        local max_attempts=$2
        local attempt=1
        local delay=30  # seconds between attempts

        log_info "Checking port $port (max $max_attempts attempts, ${delay}s intervals)..."
        
        while [ $attempt -le $max_attempts ]; do
            if netstat -tln | grep -q ":$port "; then
                log_info "Port $port is now listening"
                return 0
            fi
            log_info "Attempt $attempt/$max_attempts: Port $port not yet listening, waiting ${delay}s..."
            sleep $delay
            attempt=$((attempt + 1))
        done
        
        return 1
    }

    # Function to check GitLab container health
    check_gitlab_health() {
        local max_attempts=$1
        local attempt=1
        local delay=30  # seconds between attempts

        log_info "Checking GitLab container health (max $max_attempts attempts, ${delay}s intervals)..."
        
        while [ $attempt -le $max_attempts ]; do
            if docker ps | grep -q gitlab; then
                local status=$(docker inspect --format='{{.State.Health.Status}}' gitlab 2>/dev/null)
                local running=$(docker inspect --format='{{.State.Running}}' gitlab 2>/dev/null)
                
                if [ "$running" = "true" ]; then
                    if [ "$status" = "healthy" ] || [ -z "$status" ]; then
                        log_info "GitLab container is running properly"
                        return 0
                    fi
                fi
            fi
            
            log_info "Attempt $attempt/$max_attempts: GitLab not yet healthy, waiting ${delay}s..."
            docker logs gitlab --tail 50 >> "$LOG_FILE" 2>&1
            sleep $delay
            attempt=$((attempt + 1))
        done
        
        return 1
    }

    # Stop and remove existing GitLab container if it exists
    if docker ps -a | grep -q gitlab; then
        log_info "Removing existing GitLab container..."
        docker stop gitlab >> "$LOG_FILE" 2>&1 || true
        docker rm gitlab >> "$LOG_FILE" 2>&1 || true
    fi
    
    # Clean up existing volumes if they exist
    log_info "Cleaning up existing GitLab volumes..."
    rm -rf /srv/gitlab/* || true
    
    # GitLab with runners and health check - using port 2222 for SSH
    log_info "Starting GitLab container..."
    docker run --detach \
        --hostname gitlab.local \
        --publish 443:443 --publish 80:80 --publish 22:22 \
        --name gitlab \
        --restart always \
        --volume /srv/gitlab/config:/etc/gitlab \
        --volume /srv/gitlab/logs:/var/log/gitlab \
        --volume /srv/gitlab/data:/var/opt/gitlab \
        --health-cmd='curl -f http://localhost/-/health || exit 1' \
        --health-interval=60s \
        --health-timeout=10s \
        --health-retries=5 \
        gitlab/gitlab-ce:latest >> "$LOG_FILE" 2>&1 || {
            log_error "Failed to start GitLab container"
            return 1
        }

    # Wait for GitLab configuration directory to be populated
    log_info "Waiting for GitLab configuration to be initialized..."
    while [ ! -f /srv/gitlab/config/gitlab.rb ]; do
        sleep 5
    done

    # Update GitLab configuration to use the new SSH port
    log_info "Updating GitLab configuration for SSH port..."
    sed -i 's/# gitlab_rails\[\"gitlab_shell_ssh_port\"\] = 22/gitlab_rails\[\"gitlab_shell_ssh_port\"\] = 2222/' /srv/gitlab/config/gitlab.rb

    # Reconfigure GitLab to apply changes
    log_info "Reconfiguring GitLab to apply SSH port change..."
    docker exec gitlab gitlab-ctl reconfigure >> "$LOG_FILE" 2>&1

    # Check GitLab container health
    log_info "Waiting for GitLab to initialize (this may take several minutes)..."
    if ! check_gitlab_health 20; then
        log_error "GitLab failed to become healthy within the timeout period"
        log_error "Check docker logs with: docker logs gitlab"
        return 1
    fi

    # Verify ports are listening
    if ! check_port 80 10; then
        log_error "GitLab is not listening on port 80"
        log_error "Please check GitLab logs with: docker logs gitlab"
        return 1
    fi

    # Install GitLab Runner using Debian packages
    log_info "Installing GitLab Runner..."
    
    # Add GitLab Runner repository using Debian repository
    log_info "Adding GitLab Runner repository..."
    curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | \
        os=debian dist=bullseye bash >> "$LOG_FILE" 2>&1 || {
        log_error "Failed to add GitLab Runner repository"
        return 1
    }
    
    # Install the runner
    log_info "Installing GitLab Runner package..."
    apt-get update >> "$LOG_FILE" 2>&1
    apt-get install -y gitlab-runner >> "$LOG_FILE" 2>&1 || {
        if ! dpkg -l | grep -q gitlab-runner; then
            log_info "Attempting alternative installation method..."
            # Direct .deb package installation as fallback
            local ARCH=$(dpkg --print-architecture)
            wget "https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_${ARCH}.deb" -O gitlab-runner.deb >> "$LOG_FILE" 2>&1 && \
            dpkg -i gitlab-runner.deb >> "$LOG_FILE" 2>&1 && \
            rm gitlab-runner.deb
        fi
    }
    
    # Verify installation
    if ! command -v gitlab-runner >/dev/null 2>&1; then
        log_error "GitLab Runner installation failed"
        return 1
    else
        log_info "GitLab Runner installed successfully"
    fi
    
    # Start GitLab Runner service
    systemctl enable gitlab-runner >> "$LOG_FILE" 2>&1
    systemctl start gitlab-runner >> "$LOG_FILE" 2>&1
    
    log_info "GitLab installation completed"
    log_info "Initial root password can be found using: docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password"
    log_info "Please register your GitLab runner manually after GitLab is fully started"
}

# Create workspace and example files
create_workspace() {
    log_info "Creating workspace..."
    mkdir -p /opt/devsecops/{scripts,tools,reports,pipelines}
    
    # Create example pipeline
    cat > /opt/devsecops/pipelines/example-pipeline.yml <<EOF
stages:
  - static-analysis
  - dependency-check
  - container-scan
  - dynamic-scan

sast:
  stage: static-analysis
  script:
    - shellcheck **/*.sh
    - bandit -r .

dependencies:
  stage: dependency-check
  script:
    - dependency-check --scan .

containers:
  stage: container-scan
  script:
    - trivy image my-app:latest

dast:
  stage: dynamic-scan
  script:
    - zap-baseline.py -t https://target-app.com
EOF
    
    chown -R "$SUDO_USER:$SUDO_USER" /opt/devsecops
    log_info "Workspace created at /opt/devsecops"
}

# Main execution
main() {
    init_logging
    log_info "Starting DevSecOps environment setup on Kali Linux..."
    
    install_dev_tools
    install_dep_scanners
    setup_gitlab_cicd
    create_workspace
    
    log_info "Setup complete! Environment is ready for learning."
    log_info "Access points:"
    echo "GitLab: http://localhost" | tee -a "$LOG_FILE"
}

main