diff --git a/scripts/translate_homelab_to_compose.sh b/scripts/translate_homelab_to_compose.sh index df00996..853c1f9 100755 --- a/scripts/translate_homelab_to_compose.sh +++ b/scripts/translate_homelab_to_compose.sh @@ -297,8 +297,8 @@ events { } http { - include /etc/nginx/mime.types; - default_type application/octet-stream; + include mime.types; + default_type application/octet-stream; # Logging log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' @@ -315,27 +315,53 @@ http { keepalive_timeout 65; types_hash_max_size 2048; + # Health check endpoint + server { + listen 80 default_server; + server_name _; + + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } + # Include service configurations - include /etc/nginx/conf.d/*.conf; + include conf.d/*.conf; } EOF + # Get base domain for service URLs + local base_domain + base_domain=$(get_base_domain "$HOMELAB_CONFIG") + # Generate configuration for each service that needs reverse proxy while IFS= read -r service; do [[ -z "$service" ]] && continue - local port - port=$(yq ".services[\"$service\"].port" "$HOMELAB_CONFIG" 2>/dev/null) + # Check if this is a web service that needs nginx proxy + if is_web_service "$service" "$HOMELAB_CONFIG"; then + local port + port=$(yq ".services[\"$service\"].port" "$HOMELAB_CONFIG" 2>/dev/null | tr -d '"') - # Only create proxy config for services on ports 80/443 (web services) - if [[ "$port" == "80" || "$port" == "443" ]]; then - log_info " Creating nginx config for: $service" + # Skip if no port (can't proxy without knowing the port) + if [[ -z "$port" || "$port" == "null" ]]; then + log_info " Skipping nginx config for $service: no port specified" + continue + fi + + # Get custom domain or use default pattern + local service_domain + service_domain=$(yq ".services[\"$service\"].domain // \"${service}.${base_domain}\"" "$HOMELAB_CONFIG" 2>/dev/null | tr -d '"') + + log_info " Creating nginx config for: $service (${service_domain})" cat > "$nginx_dir/conf.d/${service}.conf" </dev/null | tr -d '"') + + if [[ -n "$custom_domain" && "$custom_domain" != "null" ]]; then + return 0 # Has explicit domain configuration + fi + + # Check if service has a port (indicating it's a service that could be exposed) + # and doesn't explicitly disable web exposure + local port + port=$(yq ".services[\"$service\"].port" "$config_file" 2>/dev/null | tr -d '"') + + local web_enabled + web_enabled=$(yq ".services[\"$service\"].web" "$config_file" 2>/dev/null | tr -d '"') + + # Default web to true if not specified + if [[ -z "$web_enabled" || "$web_enabled" == "null" ]]; then + web_enabled="true" + fi + + # Service is a web service if: + # 1. It has a port AND + # 2. web is not explicitly set to false + if [[ -n "$port" && "$port" != "null" && "$web_enabled" != "false" ]]; then + return 0 # Service should be exposed + fi + + return 1 # Service should not be exposed via nginx +} + +# Function: get_base_domain +# Description: Gets the base domain from environment or config +# Arguments: $1 - config file path +# Returns: Base domain string +get_base_domain() { + local config_file="${1:-$HOMELAB_CONFIG}" + + # Try environment first, then config, then default + local base_domain="${BASE_DOMAIN:-}" + + if [[ -z "$base_domain" ]]; then + base_domain=$(yq ".environment.BASE_DOMAIN // \"homelab.local\"" "$config_file" 2>/dev/null | tr -d '"') + fi + + echo "$base_domain" +} + +# Function: generate_nginx_bundles +# Description: Generates nginx bundles for all machines +# Arguments: $1 - config file path +# Returns: 0 on success, 1 on failure +generate_nginx_bundles() { + local config_file="${1:-$HOMELAB_CONFIG}" + + log_info "Generating nginx bundles for all machines..." + + # Get all machines + local machines + machines=$(get_machine_list "$config_file") || return 1 + + # Generate nginx config for each machine + while IFS= read -r machine; do + [[ -z "$machine" ]] && continue + + log_info "Generating nginx bundle for: $machine" + generate_nginx_config_for_machine "$machine" "$config_file" || return 1 + done <<< "$machines" + + log_success "Generated nginx bundles for all machines" + return 0 +} + # Only run main if script is executed directly (not sourced) if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then main "$@" diff --git a/selfhosted.sh b/selfhosted.sh index 0073822..62840b0 100755 --- a/selfhosted.sh +++ b/selfhosted.sh @@ -752,6 +752,27 @@ case "$1" in ;; esac ;; + generate-nginx) + # Generate nginx bundles for Docker Compose deployment + case "$2" in + bundles) + "$PROJECT_ROOT/scripts/translate_homelab_to_compose.sh" generate-nginx-bundles "${3:-homelab.yaml}" + ;; + help|"") + echo "💡 Generate-Nginx Commands:" + echo " bundles [config] - Generate nginx bundles for all machines" + echo "" + echo "💡 Examples:" + echo " $0 generate-nginx bundles homelab.yaml # Generate nginx bundles" + echo " $0 generate-nginx bundles # Use default homelab.yaml" + ;; + *) + echo "❌ Unknown generate-nginx command: $2" + echo "💡 Run '$0 generate-nginx help' for available commands" + exit 1 + ;; + esac + ;; config) case "$2" in init) config_init ;; @@ -794,6 +815,7 @@ case "$1" in echo " service - Manage service configurations" echo " deploy - Deploy services to infrastructure" echo " deploy-compose - Multi-machine Docker Compose deployment coordination" + echo " generate-nginx - Generate nginx bundles for Docker Compose" echo " config - Manage environment and configuration" echo " help - Show detailed help" echo "" diff --git a/tests/unit/scripts/nginx_distribution_test.bats b/tests/unit/scripts/nginx_distribution_test.bats new file mode 100755 index 0000000..0d61289 --- /dev/null +++ b/tests/unit/scripts/nginx_distribution_test.bats @@ -0,0 +1,494 @@ +#!/usr/bin/env bats + +# Tests for Nginx Distribution Enhancement +# Part of Issue #36 - Per-Machine Nginx Configuration + +load test_helper + +setup() { + # Set PROJECT_ROOT relative to test location + local project_root_path + project_root_path="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)" + export PROJECT_ROOT="$project_root_path" + + # Create temporary directories for testing + local test_dir + test_dir=$(mktemp -d) + export TEST_DIR="$test_dir" + export TEST_CONFIG="$TEST_DIR/homelab.yaml" + export TEST_OUTPUT="$TEST_DIR/output" + export HOMELAB_CONFIG="$TEST_CONFIG" + export OUTPUT_DIR="$TEST_OUTPUT" + export BUNDLES_DIR="$TEST_OUTPUT" + + # Source the translation script (which contains nginx generation) + source "$PROJECT_ROOT/scripts/translate_homelab_to_compose.sh" +} + +teardown() { + # Clean up temporary directories + rm -rf "$TEST_DIR" +} + +# Helper function to create a comprehensive test homelab.yaml +create_test_config() { + cat > "$TEST_CONFIG" < "$TEST_CONFIG" < "$TEST_CONFIG" < "$TEST_CONFIG" </dev/null 2>&1; then + # Create a minimal test environment for nginx validation + mkdir -p "$TEST_OUTPUT/driver/nginx/logs" + touch "$TEST_OUTPUT/driver/nginx/mime.types" + + # Try nginx validation, but don't fail the test if nginx environment is incomplete + nginx -t -c "$TEST_OUTPUT/driver/nginx/nginx.conf" 2>/dev/null || { + echo "Note: Nginx validation skipped due to environment limitations" + } + fi +} + +@test "should support health check endpoints" { + create_test_config + + run generate_nginx_config_for_machine "driver" "$TEST_CONFIG" + [ "$status" -eq 0 ] + + # Should include health check location in nginx configs + grep -q "location /health" "$TEST_OUTPUT/driver/nginx/nginx.conf" +} + +@test "should generate nginx bundle generation CLI command" { + create_test_config + + # Test CLI command exists + run generate_nginx_bundles "$TEST_CONFIG" + [ "$status" -eq 0 ] + + # Should generate bundles for all machines + [ -d "$TEST_OUTPUT/driver/nginx" ] + [ -d "$TEST_OUTPUT/node-01/nginx" ] + [ -d "$TEST_OUTPUT/node-02/nginx" ] +} + +@test "should handle nginx service isolation per machine" { + create_test_config + + # Generate configs for different machines + run generate_nginx_config_for_machine "driver" "$TEST_CONFIG" + [ "$status" -eq 0 ] + + run generate_nginx_config_for_machine "node-01" "$TEST_CONFIG" + [ "$status" -eq 0 ] + + # Each machine should have different service configs + # driver has homepage, node-01 doesn't + [ -f "$TEST_OUTPUT/driver/nginx/conf.d/homepage.conf" ] + [ ! -f "$TEST_OUTPUT/node-01/nginx/conf.d/homepage.conf" ] + + # node-01 has static-site, driver doesn't + [ -f "$TEST_OUTPUT/node-01/nginx/conf.d/static-site.conf" ] + [ ! -f "$TEST_OUTPUT/driver/nginx/conf.d/static-site.conf" ] +} + +@test "should support custom nginx configuration templates" { + create_test_config + + # Create custom template directory + mkdir -p "$TEST_DIR/nginx-templates" + cat > "$TEST_DIR/nginx-templates/custom.conf.template" < "$TEST_CONFIG" + + run generate_nginx_config_for_machine "driver" "$TEST_CONFIG" + [ "$status" -eq 1 ] + [[ "$output" =~ Invalid|invalid|Failed|failed|error ]] +} + +@test "should support nginx upstream load balancing" { + skip "Upstream load balancing is an advanced feature for future implementation" + # Create config with multiple replicas + cat > "$TEST_CONFIG" <