#cloud-config
package_update: true
package_upgrade: true

write_files:
  - owner: root:root
    path: /etc/aziot/config.toml
    defer: true
    content: |
      [provisioning]
      source = "dps"
      global_endpoint = "https://global.azure-devices-provisioning.net"
      id_scope = "REPLACE_WITH_YOUR_SCOPE_ID"
      [provisioning.attestation]
      method = "symmetric_key"
      registration_id = "REPLACE_WITH_YOUR_EDGE_DEVICE_ID"
      symmetric_key = { value = "REPLACE_WITH_YOUR_EDGE_DEVICE_PRIMARY_KEY" }
      [image_garbage_collection]
      enabled = true
      cleanup_recurrence = "1d"
      image_age_cleanup_threshold = "7d"
      cleanup_time = "00:00"

  - owner: root:root
    path: /srv/mosquitto/config/mosquitto.conf
    defer: true
    content: |
      per_listener_settings true
      log_timestamp_format %Y-%m-%dT%H:%M:%S  
      listener 1883
      protocol mqtt
      allow_anonymous true
      require_certificate false
      listener 9883
      protocol mqtt
      cafile /mosquitto/config/certs/bundle-ca.crt
      certfile /mosquitto/config/certs/server/server.crt
      keyfile /mosquitto/config/certs/server/server.key
      require_certificate true
      allow_anonymous true

  - owner: root:root
    path: /etc/docker/daemon.json
    defer: true
    content: |
      { "log-driver": "local" }

  - owner: root:root
    path: /usr/local/bin/configure-redis.sh
    permissions: "0755"
    defer: true
    content: |
      #!/bin/bash

      # Configuration constants
      readonly SCOPE_ID="REPLACE_WITH_YOUR_SCOPE_ID"
      readonly ORGANIZATION_ID="REPLACE_WITH_YOUR_ORGANIZATION_ID"
      readonly MODULE_PRIMARY_KEY="REPLACE_WITH_YOUR_SAS_DEVICES_KEY"
      readonly DEVICE_HUB_API_KEY="REPLACE_WITH_YOUR_SAS_SMART_ENROLLMENT_KEY"

      # Script configuration
      readonly LOG_FILE="/var/log/configure-redis.log"
      readonly TIMEOUT=600  # 10 minutes
      readonly SLEEP_INTERVAL=15
      readonly MAX_REDIS_RETRIES=3
      readonly RETRY_DELAY=10

      log() {
          echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
      }

      # Configuration validation function
      validate_config() {
          local errors=0

          log "Validating configuration parameters..."

          # Validate SCOPE_ID
          if [[ -z "$SCOPE_ID" || "$SCOPE_ID" == *"<"* ]]; then
              log "ERROR: SCOPE_ID is empty or contains placeholder"
              ((errors++))
          else
              log "SCOPE_ID validation passed (length: $${#SCOPE_ID})"
          fi

          # Validate ORGANIZATION_ID
          if [[ -z "$ORGANIZATION_ID" || "$ORGANIZATION_ID" == *"<"* ]]; then
              log "ERROR: ORGANIZATION_ID is empty or contains placeholder"
              ((errors++))
          else
              log "ORGANIZATION_ID validation passed (length: $${#ORGANIZATION_ID})"
          fi

          # Validate MODULE_PRIMARY_KEY
          if [[ -z "$MODULE_PRIMARY_KEY" || "$MODULE_PRIMARY_KEY" == *"<"* ]]; then
              log "ERROR: MODULE_PRIMARY_KEY is empty or contains placeholder"
              ((errors++))
          else
              log "MODULE_PRIMARY_KEY validation passed (length: $${#MODULE_PRIMARY_KEY})"
          fi

          # Validate DEVICE_HUB_API_KEY
          if [[ -z "$DEVICE_HUB_API_KEY" || "$DEVICE_HUB_API_KEY" == *"<"* ]]; then
              log "ERROR: DEVICE_HUB_API_KEY is empty or contains placeholder"
              ((errors++))
          else
              log "DEVICE_HUB_API_KEY validation passed (SharedAccessSignature format verified)"
          fi

          if [ $errors -gt 0 ]; then
              log "Configuration validation failed with $errors errors"
              return 1
          fi

          log "Configuration validation passed - all parameters are valid"
          return 0
      }

      # Resource validation function (runs after directory creation)
      validate_resources() {
          log "Validating system resources..."

          # Check if user ID 1000 exists
          if ! getent passwd 1000 > /dev/null 2>&1; then
              log "WARNING: User ID 1000 does not exist, directories will be owned by root"
              CHOWN_USER="root:root"
          else
              CHOWN_USER="1000:1000"
              log "User ID 1000 exists, using for directory ownership"
          fi

          # Check available disk space (minimum 2GB for IoT Edge)
          local available_space=$(df / | awk 'NR==2 {print $4}')
          if [ "$available_space" -lt 2097152 ]; then  # 2GB in KB
              log "WARNING: Low disk space available: $${available_space}KB (recommended: >2GB)"
          else
              log "Sufficient disk space available: $${available_space}KB"
          fi

          # Check if Docker is installed and running
          if ! command -v docker >/dev/null 2>&1; then
              log "WARNING: Docker command not found in PATH"
              return 1
          fi

          # Check if IoT Edge is installed
          if ! command -v iotedge >/dev/null 2>&1; then
              log "WARNING: IoT Edge command not found in PATH"
              return 1
          fi

          return 0
      }

      # Enhanced Redis configuration with retry logic
      configure_redis_with_retry() {
          local attempt=1

          # First, verify Redis is actually accessible
          if ! docker exec redis redis-cli ping >/dev/null 2>&1; then
              log "ERROR: Redis container is not responding to ping"
              return 1
          fi

          while [ $attempt -le $MAX_REDIS_RETRIES ]; do
              log "Attempting Redis configuration (attempt $attempt/$MAX_REDIS_RETRIES)..."

              # Use a single command with error checking
              if docker exec redis redis-cli HMSET itmConfig \
                  scopeId "$SCOPE_ID" \
                  tenantId "$ORGANIZATION_ID" \
                  modulePrimaryKey "$MODULE_PRIMARY_KEY" \
                  deviceHubApiKey "$DEVICE_HUB_API_KEY" >/dev/null 2>&1; then

                  log "Redis HMSET command successful on attempt $attempt"

                  # Verify the configuration was set with more robust check
                  local config_count=$(docker exec redis redis-cli HLEN itmConfig 2>/dev/null || echo "0")
                  if [ "$config_count" -eq 4 ]; then
                      log "Redis configuration verification successful (4 keys set)"
                      return 0
                  else
                      log "Redis configuration verification failed (expected 4 keys, got $config_count)"
                  fi
              else
                  log "Redis HMSET command failed on attempt $attempt"
              fi

              if [ $attempt -lt $MAX_REDIS_RETRIES ]; then
                  log "Waiting $${RETRY_DELAY} seconds before retry..."
                  sleep $RETRY_DELAY
              fi

              ((attempt++))
          done

          log "Redis configuration failed after $MAX_REDIS_RETRIES attempts"
          return 1
      }

      # Enhanced IoT Edge restart with retry
      restart_iotedge_with_retry() {
          local attempt=1
          local max_attempts=3

          while [ $attempt -le $max_attempts ]; do
              log "Attempting IoT Edge restart (attempt $attempt/$max_attempts)..."

              if iotedge system restart 2>&1 | tee -a "$LOG_FILE"; then
                  log "IoT Edge system restarted successfully on attempt $attempt"

                  # Wait and verify IoT Edge is running
                  sleep 10
                  if iotedge system status 2>&1 | grep -qi "Running"; then
                      log "IoT Edge system status verified as running"
                      return 0
                  else
                      log "IoT Edge system status check failed"
                      log "Current IoT Edge status:"
                      iotedge system status 2>&1 | tee -a "$LOG_FILE"
                  fi
              else
                  log "IoT Edge restart failed on attempt $attempt"
              fi

              if [ $attempt -lt $max_attempts ]; then
                  sleep 15
              fi
              ((attempt++))
          done

          log "IoT Edge restart failed after $max_attempts attempts"
          return 1
      }

      # Main execution
      log "Starting Redis configuration script..."

      # Validate configuration first
      if ! validate_config; then
          log "CRITICAL: Configuration validation failed, exiting"
          exit 1
      fi

      # Wait for Docker daemon to be ready
      log "Waiting for Docker daemon..."
      local docker_wait=0
      while ! docker info >/dev/null 2>&1; do
          log "Docker daemon not ready, waiting..."
          sleep 5
          ((docker_wait+=5))
          if [ $docker_wait -ge 120 ]; then
              log "ERROR: Docker daemon failed to start within 2 minutes"
              exit 1
          fi
      done
      log "Docker daemon is ready"

      # Validate system resources after Docker is ready
      if ! validate_resources; then
          log "WARNING: Resource validation failed, continuing with caution"
      fi

      # Wait for IoT Edge containers to be downloaded and running
      log "Waiting for IoT Edge and Redis containers to start..."

      # Wait for IoT Edge system to be ready first
      local iotedge_wait=0
      while ! iotedge system status >/dev/null 2>&1; do
          log "IoT Edge system not ready, waiting..."
          sleep 10
          ((iotedge_wait+=10))
          if [ $iotedge_wait -ge 300 ]; then  # 5 minutes max
              log "WARNING: IoT Edge system not ready after 5 minutes, continuing..."
              break
          fi
      done

      elapsed=0
      while [ $elapsed -lt $TIMEOUT ]; do
          if docker inspect -f '{{.State.Running}}' redis 2>/dev/null | grep -q true; then
              log "Redis container is running!"
              break
          fi

          # Show container status for debugging
          if [ $((elapsed % 60)) -eq 0 ]; then
              log "Container status after $${elapsed}s:"
              docker ps -a --format "table {{.Names}}\t{{.Status}}" 2>/dev/null | tee -a "$LOG_FILE" || log "Failed to get container status"
          fi

          log "Waiting for Redis container... ($${elapsed}s elapsed)"
          sleep $SLEEP_INTERVAL
          elapsed=$((elapsed + SLEEP_INTERVAL))
      done

      # Configure Redis if available
      if docker inspect -f '{{.State.Running}}' redis 2>/dev/null | grep -q true; then
          if configure_redis_with_retry; then
              log "Redis configuration completed successfully"

              # Restart IoT Edge to ensure configuration is picked up
              if restart_iotedge_with_retry; then
                  log "IoT Edge system restarted successfully"
              else
                  log "WARNING: IoT Edge restart failed, manual intervention may be required"
                  exit 1
              fi
          else
              log "CRITICAL: Redis configuration failed after all retries"
              exit 1
          fi
      else
          log "CRITICAL: Redis container not available after $${TIMEOUT}s timeout"
          log "Available containers:"
          docker ps -a 2>&1 | tee -a "$LOG_FILE"
          exit 1
      fi

      log "Redis configuration script completed successfully"

  - owner: root:root
    path: /srv/StorageModule/config/config.yaml
    defer: true
    content: |
      app:
        azure:
          blob-storage-url: REPLACE_WITH_YOUR_SAS_EDGE_STORAGE_KEY

  - owner: root:root
    path: /srv/ProvisioningModule/config/certificates.yaml
    defer: true
    content: |
      # Replace the placeholders with your actual values. At least one of the parameters must be replaced
      # and if one is not used, it must be removed from the configuration.
      # 
      # Example 1: Using common-name only
      # certificates:
      #   common-name: "my-iot-edge-device"
      #
      # Example 2: Using server-ip-address only
      # certificates:
      #   server-ip-address: "192.168.1.100"
      #
      # Example 3: Using both parameters
      # certificates:
      #   common-name: "my-iot-edge-device"
      #   server-ip-address: "192.168.1.100"
      certificates:
        common-name: ${certificates_common_name:REPLACE_WITH_CERTIFICATES_COMMON_NAME}
        server-ip-address: ${certificates_server_ip_address:REPLACE_WITH_CERTIFICATES_SERVER_IP_ADDRESS}

  - owner: root:root
    path: /srv/otel/config.yml
    defer: true
    content: |
      receivers:
        otlp:
          protocols:
            grpc:
              endpoint: 0.0.0.0:4317

      exporters:
        debug:
          verbosity: detailed
        azuremonitor:
          spaneventsenabled: true

      extensions:
        zpages:

      processors:
        filter:
          error_mode: ignore
          logs:
            log_record:
              - "severity_number < SEVERITY_NUMBER_WARN"

      service:
        extensions: [zpages]
        pipelines:
          traces:
            receivers: [otlp]
            exporters: [debug, azuremonitor]
          metrics:
            receivers: [otlp]
            exporters: [debug, azuremonitor]
          logs:
            receivers: [otlp]
            processors: [filter]
            exporters: [debug, azuremonitor]

runcmd:
  - mkdir /run/aziot
  - [
      wget,
      "https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb",
      -O,
      /run/aziot/packages-microsoft-prod.deb,
    ]
  - dpkg -i /run/aziot/packages-microsoft-prod.deb
  - rm /run/aziot/packages-microsoft-prod.deb
  - apt update
  - apt-get install moby-engine -y
  - apt-get install aziot-edge -y
  - systemctl restart docker
  - systemctl enable docker
  # Create directory structure for IoT Edge modules
  - mkdir -p /srv/redis
  - mkdir -p /srv/mosquitto/log
  - mkdir -p /srv/mosquitto/config
  - mkdir -p /srv/mosquitto/config/certs
  - mkdir -p /srv/MqttTranslationModule/log
  - mkdir -p /srv/MqttTranslationModule/certs
  - mkdir -p /srv/DeviceHubModule/log
  - mkdir -p /srv/DeviceHubModule/certs
  - mkdir -p /srv/DeviceHubModule/config
  - mkdir -p /srv/StorageModule/log
  - mkdir -p /srv/StorageModule/config
  - mkdir -p /srv/ProvisioningModule/log
  - mkdir -p /srv/ProvisioningModule/config
  - mkdir -p /tmp/edgeAgent
  - mkdir -p /tmp/edgeHub
  - mkdir -p /srv/shared/download
  - mkdir -p /srv/shared/certs
  - mkdir -p /srv/shared/certs/server
  - mkdir -p /srv/otel
  # Set ownership for module directories
  - chown 1000 /srv/redis
  - chown 1000 /srv/mosquitto/log
  - chown 1000 /srv/mosquitto/config
  - chown 1000 /srv/mosquitto/config/certs
  - chown 1000 /srv/MqttTranslationModule/log
  - chown 1000 /srv/MqttTranslationModule/certs
  - chown 1000 /srv/DeviceHubModule/log
  - chown 1000 /srv/DeviceHubModule/certs
  - chown 1000 /srv/DeviceHubModule/config
  - chown 1000 /srv/StorageModule/log
  - chown 1000 /srv/StorageModule/config
  - chown 1000 /srv/ProvisioningModule/log
  - chown 1000 /srv/ProvisioningModule/config
  - chown 1000 /srv/shared/download
  - chown 1001 /srv/shared/certs
  - chmod 755 /srv/shared/certs
  - chown 1001 /srv/shared/certs/server
  - chmod 755 /srv/shared/certs/server
  - chown 1000 /srv/otel
  - iotedge config apply || echo "IoT Edge config apply failed" >> /var/log/cloud-init-output-iot.log
  - nohup bash -c 'sleep 30 && /usr/local/bin/configure-redis.sh' > /dev/null 2>&1 & echo $! > /var/run/configure-redis.pid

output:
  all: "| tee -a /var/log/cloud-init-output-iot.log"
