#!/bin/bash # Bakery IA HTTPS Setup Script # This script sets up HTTPS with cert-manager and Let's Encrypt for local development # Remove -e to handle errors more gracefully set -u echo "๐Ÿ”’ Setting up HTTPS for Bakery IA with cert-manager and Let's Encrypt" echo "===============================================================" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Function to print colored output print_status() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Check prerequisites check_prerequisites() { print_status "Checking prerequisites..." # Check required tools local missing_tools=() if ! command -v kubectl &> /dev/null; then missing_tools+=("kubectl") fi if ! command -v kind &> /dev/null; then missing_tools+=("kind") fi if ! command -v skaffold &> /dev/null; then missing_tools+=("skaffold") fi if ! command -v colima &> /dev/null; then missing_tools+=("colima") fi # Report missing tools if [ ${#missing_tools[@]} -ne 0 ]; then print_error "Missing required tools: ${missing_tools[*]}" print_error "Please install them with: brew install ${missing_tools[*]}" exit 1 fi # Check if Colima is running if ! colima status --profile k8s-local &> /dev/null; then print_warning "Colima is not running. Starting Colima..." colima start --cpu 4 --memory 8 --disk 100 --runtime docker --profile k8s-local if [ $? -ne 0 ]; then print_error "Failed to start Colima. Please check your Docker installation." exit 1 fi print_success "Colima started successfully" fi # Check if cluster is running or exists local cluster_exists=false local cluster_running=false # Check if Kind cluster exists if kind get clusters | grep -q "bakery-ia-local"; then cluster_exists=true print_status "Kind cluster 'bakery-ia-local' already exists" # Check if kubectl can connect to it if kubectl cluster-info --context kind-bakery-ia-local &> /dev/null; then cluster_running=true print_success "Kubernetes cluster is running and accessible" else print_warning "Kind cluster exists but is not accessible via kubectl" fi fi # Handle cluster creation/recreation if [ "$cluster_exists" = true ] && [ "$cluster_running" = false ]; then print_warning "Kind cluster exists but is not running. Recreating..." kind delete cluster --name bakery-ia-local || true cluster_exists=false fi if [ "$cluster_exists" = false ]; then print_warning "Creating new Kind cluster..." if [ ! -f "kind-config.yaml" ]; then print_error "kind-config.yaml not found. Please ensure you're running this script from the project root." exit 1 fi if kind create cluster --config kind-config.yaml; then print_success "Kind cluster created successfully" else print_error "Failed to create Kind cluster. Please check your Kind installation." exit 1 fi fi # Ensure we're using the correct kubectl context kubectl config use-context kind-bakery-ia-local || { print_error "Failed to set kubectl context to kind-bakery-ia-local" exit 1 } print_success "Prerequisites check passed" } # Install cert-manager install_cert_manager() { print_status "Installing cert-manager..." # Check if cert-manager is already installed if kubectl get namespace cert-manager &> /dev/null; then print_warning "cert-manager namespace already exists. Checking if installation is complete..." # Check if pods are running if kubectl get pods -n cert-manager | grep -q "Running"; then print_success "cert-manager is already installed and running" return 0 else print_status "cert-manager exists but pods are not ready. Waiting..." fi else # Install cert-manager print_status "Installing cert-manager from official release..." if kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.yaml; then print_success "cert-manager installation started" else print_error "Failed to install cert-manager. Please check your internet connection and try again." exit 1 fi fi # Wait for cert-manager namespace to be created print_status "Waiting for cert-manager namespace..." for i in {1..30}; do if kubectl get namespace cert-manager &> /dev/null; then break fi sleep 2 done # Wait for cert-manager pods to be created print_status "Waiting for cert-manager pods to be created..." for i in {1..60}; do if kubectl get pods -n cert-manager &> /dev/null && [ $(kubectl get pods -n cert-manager --no-headers | wc -l) -ge 3 ]; then print_success "cert-manager pods created" break fi print_status "Waiting for cert-manager pods... (attempt $i/60)" sleep 5 done # Wait for cert-manager pods to be ready print_status "Waiting for cert-manager pods to be ready..." # Use more reliable selectors for cert-manager components local components=( "app.kubernetes.io/name=cert-manager" "app.kubernetes.io/name=cainjector" "app.kubernetes.io/name=webhook" ) local component_names=("cert-manager" "cert-manager-cainjector" "cert-manager-webhook") for i in "${!components[@]}"; do local selector="${components[$i]}" local name="${component_names[$i]}" print_status "Waiting for $name to be ready..." # First check if pods exist with this selector local pod_count=0 for attempt in {1..30}; do pod_count=$(kubectl get pods -n cert-manager -l "$selector" --no-headers 2>/dev/null | wc -l) if [ "$pod_count" -gt 0 ]; then break fi sleep 2 done if [ "$pod_count" -eq 0 ]; then print_warning "No pods found for $name with selector $selector, trying alternative approach..." # Fallback: wait for any pods containing the component name if kubectl wait --for=condition=ready pod -n cert-manager --all --timeout=300s 2>/dev/null; then print_success "All cert-manager pods are ready" break else print_warning "$name pods not found, but continuing..." continue fi fi # Wait for the specific component to be ready if kubectl wait --for=condition=ready pod -l "$selector" -n cert-manager --timeout=300s 2>/dev/null; then print_success "$name is ready" else print_warning "$name is taking longer than expected. Checking status..." kubectl get pods -n cert-manager -l "$selector" 2>/dev/null || true # Continue anyway, sometimes it works despite timeout print_warning "Continuing with setup. $name may still be starting..." fi done # Final verification if kubectl get pods -n cert-manager | grep -q "Running"; then print_success "cert-manager installed successfully" else print_warning "cert-manager installation may not be complete. Current status:" kubectl get pods -n cert-manager print_status "Continuing with setup anyway..." fi } # Install NGINX Ingress Controller install_nginx_ingress() { print_status "Installing NGINX Ingress Controller for Kind..." # Check if NGINX Ingress is already installed if kubectl get namespace ingress-nginx &> /dev/null; then print_warning "NGINX Ingress Controller namespace already exists. Checking status..." # Check if controller is running if kubectl get pods -n ingress-nginx -l app.kubernetes.io/component=controller | grep -q "Running"; then print_success "NGINX Ingress Controller is already running" else print_status "NGINX Ingress Controller exists but not ready. Waiting..." kubectl wait --namespace ingress-nginx \ --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller \ --timeout=300s 2>/dev/null || { print_warning "Ingress controller taking longer than expected, but continuing..." } fi else # Install NGINX Ingress Controller for Kind (updated URL) print_status "Installing NGINX Ingress Controller for Kind..." if kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml; then print_success "NGINX Ingress Controller installation started" # Wait for ingress controller to be ready print_status "Waiting for NGINX Ingress Controller to be ready..." kubectl wait --namespace ingress-nginx \ --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller \ --timeout=300s 2>/dev/null || { print_warning "Ingress controller taking longer than expected, but continuing..." } else print_error "Failed to install NGINX Ingress Controller" exit 1 fi fi # Configure ingress for permanent localhost access print_status "Configuring permanent localhost access..." kubectl patch svc ingress-nginx-controller -n ingress-nginx -p '{"spec":{"type":"NodePort","ports":[{"name":"http","port":80,"targetPort":"http","nodePort":30080},{"name":"https","port":443,"targetPort":"https","nodePort":30443}]}}' || true print_success "NGINX Ingress Controller configured successfully" } # Setup cluster issuers setup_cluster_issuers() { print_status "Setting up cluster issuers..." # Check if cert-manager components exist if [ ! -f "infrastructure/kubernetes/base/components/cert-manager/cluster-issuer-staging.yaml" ]; then print_error "cert-manager component files not found. Please ensure you're running this script from the project root." exit 1 fi # Apply cluster issuers print_status "Applying cluster issuers..." local issuer_files=( "infrastructure/kubernetes/base/components/cert-manager/cluster-issuer-staging.yaml" "infrastructure/kubernetes/base/components/cert-manager/local-ca-issuer.yaml" "infrastructure/kubernetes/base/components/cert-manager/cluster-issuer-production.yaml" ) for issuer_file in "${issuer_files[@]}"; do if [ -f "$issuer_file" ]; then print_status "Applying $issuer_file..." kubectl apply -f "$issuer_file" || { print_warning "Failed to apply $issuer_file, but continuing..." } else print_warning "$issuer_file not found, skipping..." fi done # Wait for the issuers to be created print_status "Waiting for cluster issuers to be ready..." sleep 15 # Check if issuers are ready print_status "Checking cluster issuer status..." kubectl get clusterissuers 2>/dev/null || print_warning "No cluster issuers found yet" # Verify that the local CA issuer is ready (if it exists) if kubectl get clusterissuer local-ca-issuer &> /dev/null; then for i in {1..10}; do local issuer_ready=$(kubectl get clusterissuer local-ca-issuer -o jsonpath='{.status.conditions[0].type}' 2>/dev/null || echo "") if [[ "$issuer_ready" == "Ready" ]]; then print_success "Local CA issuer is ready" break fi print_status "Waiting for local CA issuer to be ready... (attempt $i/10)" sleep 10 done else print_warning "Local CA issuer not found, skipping readiness check" fi print_success "Cluster issuers configured successfully" } # Deploy the application with HTTPS using Skaffold deploy_with_https() { print_status "Deploying Bakery IA with HTTPS support using Skaffold..." # Check if Skaffold is available if ! command -v skaffold &> /dev/null; then print_error "Skaffold is not installed. Please install skaffold first:" print_error "brew install skaffold" exit 1 fi # Check if skaffold.yaml exists if [ ! -f "skaffold.yaml" ]; then print_error "skaffold.yaml not found. Please ensure you're running this script from the project root." exit 1 fi # Deploy with Skaffold (builds and deploys automatically with HTTPS support) print_status "Building and deploying with Skaffold (dev profile includes HTTPS)..." if skaffold run --profile=dev; then print_success "Skaffold deployment started" else print_warning "Skaffold deployment had issues, but continuing..." fi # Wait for namespace to be created print_status "Waiting for bakery-ia namespace..." for i in {1..30}; do if kubectl get namespace bakery-ia &> /dev/null; then print_success "bakery-ia namespace found" break fi sleep 2 done # Check if namespace was created if ! kubectl get namespace bakery-ia &> /dev/null; then print_warning "bakery-ia namespace not found. Deployment may have failed." return 0 fi # Wait for deployments to be ready print_status "Waiting for deployments to be ready..." if kubectl wait --for=condition=available --timeout=600s deployment --all -n bakery-ia 2>/dev/null; then print_success "All deployments are ready" else print_warning "Some deployments are taking longer than expected, but continuing..." fi # Verify ingress exists if kubectl get ingress bakery-ingress -n bakery-ia &> /dev/null; then print_success "HTTPS ingress configured successfully" else print_warning "Ingress not found, but continuing with setup..." fi print_success "Application deployed with HTTPS support using Skaffold" } # Check certificate status check_certificates() { print_status "Checking certificate status..." # Wait for certificate to be issued print_status "Waiting for certificates to be issued..." # Check if certificate exists for i in {1..12}; do if kubectl get certificate bakery-ia-tls-cert -n bakery-ia &> /dev/null; then print_success "Certificate found" break fi print_status "Waiting for certificate to be created... (attempt $i/12)" sleep 10 done # Wait for certificate to be ready for i in {1..20}; do if kubectl get certificate bakery-ia-tls-cert -n bakery-ia -o jsonpath='{.status.conditions[0].type}' 2>/dev/null | grep -q "Ready"; then print_success "Certificate is ready" break fi print_status "Waiting for certificate to be ready... (attempt $i/20)" sleep 15 done echo "" echo "๐Ÿ“‹ Certificate status:" kubectl get certificates -n bakery-ia 2>/dev/null || print_warning "No certificates found" echo "" echo "๐Ÿ” Certificate details:" kubectl describe certificate bakery-ia-tls-cert -n bakery-ia 2>/dev/null || print_warning "Certificate not found" echo "" echo "๐Ÿ” TLS secret status:" kubectl get secret bakery-ia-tls-cert -n bakery-ia 2>/dev/null || print_warning "TLS secret not found" } # Update hosts file update_hosts_file() { print_status "Checking hosts file configuration..." # Get the external IP for Kind EXTERNAL_IP="127.0.0.1" # Check if entries exist in hosts file if ! grep -q "bakery-ia.local" /etc/hosts 2>/dev/null; then print_warning "Adding entries to /etc/hosts file for named host access..." # Ask for user permission read -p "Do you want to add entries to /etc/hosts for named host access? (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then # Add hosts entries with proper error handling { echo "$EXTERNAL_IP bakery-ia.local" echo "$EXTERNAL_IP api.bakery-ia.local" echo "$EXTERNAL_IP monitoring.bakery-ia.local" } | sudo tee -a /etc/hosts > /dev/null if [ $? -eq 0 ]; then print_success "Hosts file entries added successfully" else print_error "Failed to update hosts file. You may need to add entries manually." fi else print_warning "Skipping hosts file update. You can still access via https://localhost" fi else print_success "Hosts file entries already exist" fi echo "" print_status "Available access methods:" echo " ๐ŸŒ Primary: https://localhost (no hosts file needed)" echo " ๐Ÿท๏ธ Named: https://bakery-ia.local (requires hosts file)" echo " ๐Ÿ”— API: https://localhost/api or https://api.bakery-ia.local" } # Export CA certificate for browser trust export_ca_certificate() { print_status "Exporting CA certificate for browser trust..." # Wait for CA certificate to be created for i in {1..10}; do if kubectl get secret local-ca-key-pair -n cert-manager &> /dev/null; then print_success "CA certificate secret found" break fi print_status "Waiting for CA certificate secret... (attempt $i/10)" sleep 10 done # Extract the CA certificate if kubectl get secret local-ca-key-pair -n cert-manager &> /dev/null; then if kubectl get secret local-ca-key-pair -n cert-manager -o jsonpath='{.data.tls\.crt}' | base64 -d > bakery-ia-ca.crt 2>/dev/null; then print_success "CA certificate exported as 'bakery-ia-ca.crt'" # Make the certificate file readable chmod 644 bakery-ia-ca.crt else print_warning "Failed to extract CA certificate from secret" fi print_warning "To trust this certificate and remove browser warnings:" echo "" echo "๐Ÿ“ฑ macOS:" echo " 1. Double-click 'bakery-ia-ca.crt' to open Keychain Access" echo " 2. Find 'bakery-ia-local-ca' in the certificates list" echo " 3. Double-click it and set to 'Always Trust'" echo "" echo "๐Ÿง Linux:" echo " sudo cp bakery-ia-ca.crt /usr/local/share/ca-certificates/" echo " sudo update-ca-certificates" echo "" echo "๐ŸชŸ Windows:" echo " 1. Double-click 'bakery-ia-ca.crt'" echo " 2. Click 'Install Certificate'" echo " 3. Choose 'Trusted Root Certification Authorities'" echo "" else print_warning "CA certificate secret not found. HTTPS will work but with browser warnings." print_warning "You can still access the application at https://localhost" fi } # Display access information display_access_info() { print_success "๐ŸŽ‰ HTTPS setup completed!" echo "" echo "๐ŸŒ Access your application at:" echo " Primary: https://localhost" echo " API: https://localhost/api" echo " Named Host: https://bakery-ia.local (if hosts file updated)" echo " API Named: https://api.bakery-ia.local (if hosts file updated)" echo "" echo "๐Ÿ› ๏ธ Useful commands:" echo " ๐Ÿ“‹ Check status: kubectl get all -n bakery-ia" echo " ๐Ÿ” Check ingress: kubectl get ingress -n bakery-ia" echo " ๐Ÿ“œ Check certificates: kubectl get certificates -n bakery-ia" echo " ๐Ÿ“ View service logs: kubectl logs -f deployment/ -n bakery-ia" echo " ๐Ÿš€ Development mode: skaffold dev --profile=dev" echo " ๐Ÿงน Clean up: skaffold delete --profile=dev" echo " ๐Ÿ”„ Restart service: kubectl rollout restart deployment/ -n bakery-ia" echo "" echo "๐Ÿ”ง Troubleshooting:" echo " ๐Ÿฉบ Get events: kubectl get events -n bakery-ia --sort-by='.firstTimestamp'" echo " ๐Ÿ” Describe pod: kubectl describe pod -n bakery-ia" echo " ๐Ÿ“Š Resource usage: kubectl top pods -n bakery-ia" echo " ๐Ÿ” Certificate details: kubectl describe certificate bakery-ia-tls-cert -n bakery-ia" echo "" if [ -f "bakery-ia-ca.crt" ]; then print_warning "๐Ÿ“‹ Next steps:" echo " 1. Import 'bakery-ia-ca.crt' into your browser to remove certificate warnings" echo " 2. Access https://localhost to verify the setup" echo " 3. Run 'skaffold dev --profile=dev' for development with hot-reload" else print_warning "โš ๏ธ Note: You may see certificate warnings until the CA certificate is properly configured" fi echo "" print_status "๐ŸŽฏ The application is now ready for secure development!" } # Check current cert-manager status for debugging check_current_cert_manager_status() { print_status "Checking current cert-manager status..." if kubectl get namespace cert-manager &> /dev/null; then echo "" echo "๐Ÿ“‹ Current cert-manager pods status:" kubectl get pods -n cert-manager echo "" echo "๐Ÿ” cert-manager deployments:" kubectl get deployments -n cert-manager # Check for any pending or failed pods local failed_pods=$(kubectl get pods -n cert-manager --field-selector=status.phase!=Running --no-headers 2>/dev/null | wc -l) if [ "$failed_pods" -gt 0 ]; then echo "" print_warning "Found $failed_pods non-running pods. Details:" kubectl get pods -n cert-manager --field-selector=status.phase!=Running fi echo "" else print_status "cert-manager namespace not found. Will install fresh." fi } # Cleanup function for failed installations cleanup_on_failure() { print_warning "Cleaning up due to failure..." # Optional cleanup - ask user read -p "Do you want to clean up the Kind cluster and start fresh? (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then print_status "Cleaning up Kind cluster..." kind delete cluster --name bakery-ia-local || true print_success "Cleanup completed. You can run the script again." else print_status "Keeping existing setup. You can continue manually or run the script again." fi } # Trap function to handle script interruption trap 'echo ""; print_warning "Script interrupted. Partial setup may be present."; cleanup_on_failure; exit 1' INT TERM # Main execution main() { echo "Starting HTTPS setup for Bakery IA..." # Set error handling for individual steps local step_failed=false check_prerequisites || { step_failed=true; } if [ "$step_failed" = false ]; then check_current_cert_manager_status || { step_failed=true; } fi if [ "$step_failed" = false ]; then install_cert_manager || { step_failed=true; } fi if [ "$step_failed" = false ]; then install_nginx_ingress || { step_failed=true; } fi if [ "$step_failed" = false ]; then setup_cluster_issuers || { step_failed=true; } fi if [ "$step_failed" = false ]; then deploy_with_https || { step_failed=true; } fi if [ "$step_failed" = false ]; then check_certificates || { step_failed=true; } fi if [ "$step_failed" = false ]; then update_hosts_file || { step_failed=true; } fi if [ "$step_failed" = false ]; then export_ca_certificate || { step_failed=true; } fi if [ "$step_failed" = false ]; then display_access_info print_success "Setup completed successfully! ๐Ÿš€" else print_error "Setup failed at one or more steps. Check the output above for details." cleanup_on_failure exit 1 fi } # Run main function main "$@"