diff --git a/docker/certbot/.env.example b/docker/certbot/.env.example index e69de29..2a3ec08 100644 --- a/docker/certbot/.env.example +++ b/docker/certbot/.env.example @@ -0,0 +1,19 @@ +# SSL Certificate Configuration +# Copy this to .env in your project root and customize the values + +# Domain for SSL certificate (will also create wildcard *.domain) +CERTBOT_DOMAIN=dev.uggla.uamils.com + +# Email for Let's Encrypt notifications +CERTBOT_EMAIL=admin@uggla.uamils.com + +# Days before expiry to trigger renewal (default: 10) +CERTBOT_RENEWAL_DAYS=10 + +# Loopia DNS API credentials (optional, for wildcard certificates) +# If not provided, will use HTTP challenge (main domain only) +LOOPIA_USER=your_loopia_username +LOOPIA_PASSWORD=your_loopia_password + +# Note: If using HTTP challenge, make sure port 8888 is accessible +# from the internet and forwarded to the certbot container diff --git a/docker/certbot/Dockerfile b/docker/certbot/Dockerfile index e69de29..04621d3 100644 --- a/docker/certbot/Dockerfile +++ b/docker/certbot/Dockerfile @@ -0,0 +1,35 @@ +FROM certbot/certbot:latest + +# Install additional tools and python packages +RUN apk add --no-cache \ + curl \ + jq \ + bash \ + dcron \ + nginx \ + openssl + +# Install dns-lexicon for DNS providers support +RUN pip install dns-lexicon[full] + +# Create necessary directories +RUN mkdir -p /app/scripts /var/log/certbot + +# Copy renewal scripts +COPY scripts/ /app/scripts/ +RUN chmod +x /app/scripts/*.sh + +# Copy crontab +COPY crontab /etc/crontabs/root + +# Copy entrypoint +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh + +# Create a simple nginx config for HTTP challenges +COPY nginx.conf /etc/nginx/nginx.conf + +WORKDIR /app + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["crond", "-f", "-l", "2"] diff --git a/docker/certbot/crontab b/docker/certbot/crontab index e69de29..97d16d1 100644 --- a/docker/certbot/crontab +++ b/docker/certbot/crontab @@ -0,0 +1,5 @@ +# Run certificate renewal check daily at 2 AM +0 2 * * * /app/scripts/check-and-renew.sh >> /var/log/certbot/renewal.log 2>&1 + +# Optional: Run a quick status check every 6 hours +0 */6 * * * /app/scripts/status-check.sh >> /var/log/certbot/status.log 2>&1 diff --git a/docker/certbot/entrypoint.sh b/docker/certbot/entrypoint.sh index e69de29..5ac6c7c 100644 --- a/docker/certbot/entrypoint.sh +++ b/docker/certbot/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Entrypoint script for certbot container +set -e + +echo "Starting Certbot Auto-Renewal Container" +echo "=========================================" + +# Create log directory +mkdir -p /var/log/certbot + +# Set up environment +export DOMAIN="${DOMAIN:-dev.uggla.uamils.com}" +export EMAIL="${EMAIL:-admin@uggla.uamils.com}" +export RENEWAL_DAYS="${RENEWAL_DAYS:-10}" + +echo "Domain: $DOMAIN" +echo "Email: $EMAIL" +echo "Renewal threshold: $RENEWAL_DAYS days" + +# Initial certificate check/creation +/app/scripts/check-and-renew.sh + +# Start cron daemon +echo "Starting cron daemon..." +exec "$@" diff --git a/docker/certbot/nginx.conf b/docker/certbot/nginx.conf index e69de29..9b0f106 100644 --- a/docker/certbot/nginx.conf +++ b/docker/certbot/nginx.conf @@ -0,0 +1,28 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Simple server for HTTP challenges + server { + listen 80; + server_name _; + + root /var/www/html; + + # ACME challenge location + location /.well-known/acme-challenge/ { + root /var/www/html; + try_files $uri =404; + } + + # Default location + location / { + return 200 "Certbot HTTP Challenge Server"; + add_header Content-Type text/plain; + } + } +} diff --git a/docker/certbot/scripts/check-and-renew.sh b/docker/certbot/scripts/check-and-renew.sh index e69de29..b30bd63 100644 --- a/docker/certbot/scripts/check-and-renew.sh +++ b/docker/certbot/scripts/check-and-renew.sh @@ -0,0 +1,173 @@ +#!/bin/bash + +# Main certificate renewal script +set -e + +DOMAIN="${DOMAIN:-dev.uggla.uamils.com}" +EMAIL="${EMAIL:-admin@uggla.uamils.com}" +RENEWAL_DAYS="${RENEWAL_DAYS:-10}" +LOG_FILE="/var/log/certbot/renewal.log" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +check_certificate_expiry() { + local cert_path="/etc/letsencrypt/live/$DOMAIN/cert.pem" + + if [[ ! -f "$cert_path" ]]; then + log "Certificate not found at $cert_path - will attempt to obtain new certificate" + return 1 + fi + + # Get certificate expiry date + local expiry_date=$(openssl x509 -in "$cert_path" -noout -enddate | cut -d= -f2) + local expiry_epoch=$(date -d "$expiry_date" +%s) + local current_epoch=$(date +%s) + local days_until_expiry=$(( (expiry_epoch - current_epoch) / 86400 )) + + log "Certificate expires in $days_until_expiry days" + + if [[ $days_until_expiry -le $RENEWAL_DAYS ]]; then + log "Certificate expires in $days_until_expiry days - renewal needed" + return 1 + else + log "Certificate is valid for $days_until_expiry more days - no renewal needed" + return 0 + fi +} + +attempt_dns_challenge() { + log "Attempting DNS challenge with Loopia API..." + + if [[ -z "$LOOPIA_USER" || -z "$LOOPIA_PASSWORD" ]]; then + log "LOOPIA_USER or LOOPIA_PASSWORD not set - skipping DNS challenge" + return 1 + fi + + # Try with dns-lexicon + if command -v lexicon >/dev/null 2>&1; then + log "Using dns-lexicon for DNS challenge..." + certbot certonly \ + --dns-lexicon \ + --dns-lexicon-provider loopia \ + --dns-lexicon-loopia-username="$LOOPIA_USER" \ + --dns-lexicon-loopia-password="$LOOPIA_PASSWORD" \ + --email "$EMAIL" \ + --agree-tos \ + --non-interactive \ + --expand \ + -d "$DOMAIN" \ + -d "*.$DOMAIN" + return $? + fi + + # Fallback: Manual DNS with custom hook + if [[ -f "/app/scripts/loopia-hook.sh" ]]; then + log "Using custom Loopia hook for DNS challenge..." + certbot certonly \ + --manual \ + --preferred-challenges dns \ + --manual-auth-hook "/app/scripts/loopia-hook.sh auth" \ + --manual-cleanup-hook "/app/scripts/loopia-hook.sh cleanup" \ + --email "$EMAIL" \ + --agree-tos \ + --non-interactive \ + --expand \ + -d "$DOMAIN" \ + -d "*.$DOMAIN" + return $? + fi + + return 1 +} + +attempt_http_challenge() { + log "Attempting HTTP challenge..." + + # Start nginx for HTTP challenge + nginx -t && nginx + + certbot certonly \ + --webroot \ + --webroot-path=/var/www/html \ + --email "$EMAIL" \ + --agree-tos \ + --non-interactive \ + --expand \ + -d "$DOMAIN" + + local result=$? + + # Stop nginx + nginx -s quit || true + + return $result +} + +reload_nginx() { + log "Copying certificates to shared directory..." + + # Copy certificates to shared SSL directory + if [[ -d "/etc/letsencrypt/live/$DOMAIN" ]]; then + mkdir -p /shared/ssl + cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" "/shared/ssl/fullchain.pem" || log "Could not copy fullchain.pem" + cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem" "/shared/ssl/privkey.pem" || log "Could not copy privkey.pem" + chmod 644 /shared/ssl/fullchain.pem + chmod 600 /shared/ssl/privkey.pem + log "Certificates copied to /shared/ssl/" + fi + + log "Reloading nginx configuration..." + + # Check if nginx container exists and reload it + if command -v docker >/dev/null 2>&1; then + if docker ps --format "table {{.Names}}" | grep -q "nginx\|proxy"; then + log "Reloading nginx container..." + docker exec nginx nginx -s reload 2>/dev/null || \ + docker exec drone-detection-nginx nginx -s reload 2>/dev/null || \ + log "Could not reload nginx container - manual reload may be required" + fi + fi + + # Also try to reload host nginx if running + if pgrep nginx >/dev/null; then + log "Reloading host nginx..." + nginx -s reload || log "Could not reload host nginx" + fi +} + +main() { + log "Starting certificate renewal check for $DOMAIN" + + # Check if renewal is needed + if check_certificate_expiry; then + log "Certificate is still valid - no action needed" + exit 0 + fi + + log "Certificate renewal required - attempting to renew..." + + # Try DNS challenge first (for wildcard support) + if attempt_dns_challenge; then + log "DNS challenge successful!" + reload_nginx + log "Certificate renewal completed successfully" + exit 0 + fi + + # Fallback to HTTP challenge (for main domain only) + log "DNS challenge failed - falling back to HTTP challenge" + if attempt_http_challenge; then + log "HTTP challenge successful!" + reload_nginx + log "Certificate renewal completed successfully" + exit 0 + fi + + log "ERROR: All certificate renewal attempts failed" + exit 1 +} + +# Run main function +main "$@" diff --git a/docker/certbot/scripts/loopia-hook.sh b/docker/certbot/scripts/loopia-hook.sh index e69de29..8b4c7e6 100644 --- a/docker/certbot/scripts/loopia-hook.sh +++ b/docker/certbot/scripts/loopia-hook.sh @@ -0,0 +1,158 @@ +#!/bin/bash + +# Loopia DNS API hook for certbot +# This script handles DNS TXT record creation and cleanup for Let's Encrypt challenges + +set -e + +LOOPIA_USER="${LOOPIA_USER:-}" +LOOPIA_PASSWORD="${LOOPIA_PASSWORD:-}" +LOOPIA_API_URL="https://api.loopia.se/RPCSERV" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Loopia Hook: $1" >&2 +} + +# Extract domain and subdomain parts +extract_domain_parts() { + local full_domain="$1" + + # For *.dev.uggla.uamils.com -> domain=uamils.com, subdomain=_acme-challenge.dev + # For dev.uggla.uamils.com -> domain=uamils.com, subdomain=_acme-challenge.dev + + if [[ "$full_domain" == *.uamils.com ]]; then + DOMAIN="uamils.com" + local prefix="${full_domain%.uamils.com}" + if [[ "$prefix" == *"*"* ]]; then + # Wildcard domain *.dev.uggla.uamils.com + SUBDOMAIN="_acme-challenge.${prefix#*.}" + else + # Regular domain dev.uggla.uamils.com + SUBDOMAIN="_acme-challenge.$prefix" + fi + else + log "ERROR: Unsupported domain format: $full_domain" + exit 1 + fi +} + +# Call Loopia API +call_loopia_api() { + local method="$1" + shift + local params="$@" + + curl -s -X POST "$LOOPIA_API_URL" \ + -H "Content-Type: text/xml" \ + -d " + + $method + + $LOOPIA_USER + $LOOPIA_PASSWORD + $params + +" +} + +# Add DNS TXT record +add_txt_record() { + local domain="$1" + local subdomain="$2" + local txt_value="$3" + + log "Adding TXT record: $subdomain.$domain = $txt_value" + + local response=$(call_loopia_api "addZoneRecord" \ + "$domain + $subdomain + + + type + TXT + + + rdata + $txt_value + + + ttl + 300 + + ") + + if echo "$response" | grep -q "OK"; then + log "Successfully added TXT record" + else + log "ERROR adding TXT record: $response" + exit 1 + fi +} + +# Remove DNS TXT record +remove_txt_record() { + local domain="$1" + local subdomain="$2" + + log "Removing TXT records for: $subdomain.$domain" + + # Get existing records + local records=$(call_loopia_api "getZoneRecords" \ + "$domain + $subdomain") + + # Extract record IDs for TXT records + local record_ids=$(echo "$records" | grep -A5 "type" | grep -B5 "TXT" | grep "record_id" | sed 's/.*\([0-9]*\)<\/int>.*/\1/') + + for record_id in $record_ids; do + if [[ -n "$record_id" ]]; then + log "Removing TXT record ID: $record_id" + call_loopia_api "removeZoneRecord" \ + "$domain + $subdomain + $record_id" + fi + done +} + +# Main script logic +case "$1" in + "auth") + if [[ -z "$CERTBOT_DOMAIN" || -z "$CERTBOT_VALIDATION" ]]; then + log "ERROR: CERTBOT_DOMAIN or CERTBOT_VALIDATION not set" + exit 1 + fi + + if [[ -z "$LOOPIA_USER" || -z "$LOOPIA_PASSWORD" ]]; then + log "ERROR: LOOPIA_USER or LOOPIA_PASSWORD not set" + exit 1 + fi + + extract_domain_parts "$CERTBOT_DOMAIN" + add_txt_record "$DOMAIN" "$SUBDOMAIN" "$CERTBOT_VALIDATION" + + # Wait for DNS propagation + log "Waiting 60 seconds for DNS propagation..." + sleep 60 + ;; + + "cleanup") + if [[ -z "$CERTBOT_DOMAIN" ]]; then + log "ERROR: CERTBOT_DOMAIN not set" + exit 1 + fi + + if [[ -z "$LOOPIA_USER" || -z "$LOOPIA_PASSWORD" ]]; then + log "WARNING: LOOPIA_USER or LOOPIA_PASSWORD not set - skipping cleanup" + exit 0 + fi + + extract_domain_parts "$CERTBOT_DOMAIN" + remove_txt_record "$DOMAIN" "$SUBDOMAIN" + ;; + + *) + log "Usage: $0 {auth|cleanup}" + exit 1 + ;; +esac diff --git a/docker/certbot/scripts/status-check.sh b/docker/certbot/scripts/status-check.sh index e69de29..eaaab83 100644 --- a/docker/certbot/scripts/status-check.sh +++ b/docker/certbot/scripts/status-check.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Status check script for monitoring +set -e + +DOMAIN="${DOMAIN:-dev.uggla.uamils.com}" +LOG_FILE="/var/log/certbot/status.log" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +check_certificate_status() { + local cert_path="/etc/letsencrypt/live/$DOMAIN/cert.pem" + + if [[ ! -f "$cert_path" ]]; then + log "STATUS: No certificate found for $DOMAIN" + return 1 + fi + + # Get certificate details + local expiry_date=$(openssl x509 -in "$cert_path" -noout -enddate | cut -d= -f2) + local expiry_epoch=$(date -d "$expiry_date" +%s) + local current_epoch=$(date +%s) + local days_until_expiry=$(( (expiry_epoch - current_epoch) / 86400 )) + + # Get certificate subject + local subject=$(openssl x509 -in "$cert_path" -noout -subject | sed 's/subject=//') + + log "STATUS: Certificate for $DOMAIN" + log " Subject: $subject" + log " Expires: $expiry_date" + log " Days until expiry: $days_until_expiry" + + if [[ $days_until_expiry -le 10 ]]; then + log " WARNING: Certificate expires soon!" + elif [[ $days_until_expiry -le 30 ]]; then + log " NOTICE: Certificate expires in less than 30 days" + else + log " OK: Certificate is valid" + fi +} + +# Check certificate status +check_certificate_status diff --git a/sample_detection.json b/sample_detection.json index e69de29..821ac5f 100644 --- a/sample_detection.json +++ b/sample_detection.json @@ -0,0 +1,12 @@ +{ + "device_id": 1, + "drone_id": 2001, + "drone_type": 1, + "rssi": -72, + "freq": 2450, + "geo_lat": 59.3293, + "geo_lon": 18.0686, + "device_timestamp": 1692252000000, + "confidence_level": 0.92, + "signal_duration": 3200 +} diff --git a/start-healthprobe.bat b/start-healthprobe.bat index e69de29..8624db4 100644 --- a/start-healthprobe.bat +++ b/start-healthprobe.bat @@ -0,0 +1,46 @@ +@echo off +REM Health Probe Simulator Startup Script (Windows) +REM This script starts the health probe simulator that continuously sends heartbeats + +echo 🏥 Starting Health Probe Simulator... +echo ================================== + +REM Set defaults if not provided +if not defined PROBE_FAILRATE set PROBE_FAILRATE=30 +if not defined PROBE_INTERVAL_SECONDS set PROBE_INTERVAL_SECONDS=60 +if not defined API_BASE_URL set API_BASE_URL=https://selfservice.cqers.com/drones/api + +echo 🔧 Configuration: +echo Failure Rate: %PROBE_FAILRATE%%% +echo Probe Interval: %PROBE_INTERVAL_SECONDS% seconds +echo API URL: %API_BASE_URL% +echo. + +REM Check if running with Docker +if "%1"=="docker" ( + echo 🐳 Starting with Docker Compose... + docker-compose --profile healthprobe up healthprobe +) else ( + echo 💻 Running locally... + + REM Check if Python is available + python --version >nul 2>&1 + if errorlevel 1 ( + echo ❌ Python is required but not installed + pause + exit /b 1 + ) + + REM Check if requests module is available + python -c "import requests" >nul 2>&1 + if errorlevel 1 ( + echo 📦 Installing requests module... + pip install requests + ) + + REM Run the health probe simulator + echo 🚀 Starting health probe simulator... + python health_probe_simulator.py +) + +pause diff --git a/test-docker-builds.bat b/test-docker-builds.bat index e69de29..8d7e24f 100644 --- a/test-docker-builds.bat +++ b/test-docker-builds.bat @@ -0,0 +1,38 @@ +@echo off +setlocal + +echo 🐳 Testing Docker Builds +echo ======================= + +echo [INFO] Building backend container... +docker build -t drone-backend ./server +if %errorlevel% neq 0 ( + echo [ERROR] Backend build failed + exit /b 1 +) +echo [SUCCESS] Backend build completed + +echo [INFO] Building frontend container... +docker build -t drone-frontend ./client +if %errorlevel% neq 0 ( + echo [ERROR] Frontend build failed + exit /b 1 +) +echo [SUCCESS] Frontend build completed + +echo [INFO] Building simulator container... +docker build -f docker/simulator/Dockerfile -t drone-simulator . +if %errorlevel% neq 0 ( + echo [ERROR] Simulator build failed + exit /b 1 +) +echo [SUCCESS] Simulator build completed + +echo [SUCCESS] All builds completed successfully! +echo. +echo You can now run: +echo docker compose up -d +echo or +echo docker compose -f docker compose.simple.yml up -d + +pause