AWSTemplateFormatVersion: 2010-09-09
Description: OpenVidu Pro - High Availability

Parameters:
  DomainName:
    Type: String
    Description: Domain name for the OpenVidu High Availability cluster
    AllowedPattern: ^$|^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$
    ConstraintDescription: The domain name does not have a valid domain name format

  OpenViduCertificateARN:
    Description: 'Amazon certificate arn resource to load into the LoadBalancer'
    Type: String
    AllowedPattern: '.+'
    ConstraintDescription: The Load Balancer domain name must be defined

  OpenViduLicense:
    Description: "Visit https://openvidu.io/account"
    Type: String
    AllowedPattern: ^(?!\s*$).+$
    NoEcho: true
    ConstraintDescription: OpenVidu Pro License is mandatory

  RTCEngine:
    Description: "RTCEngine media engine to use"
    Type: String
    AllowedValues:
      - pion
      - mediasoup
    Default: pion

  MasterNodeInstanceType:
    Description: "Specifies the EC2 instance type for your OpenVidu Master Node"
    Type: String
    Default: c6a.xlarge
    AllowedValues:
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
      - t3.2xlarge
      - t3a.nano
      - t3a.micro
      - t3a.small
      - t3a.medium
      - t3a.large
      - t3a.xlarge
      - t3a.2xlarge
      - t4g.nano
      - t4g.micro
      - t4g.small
      - t4g.medium
      - t4g.large
      - t4g.xlarge
      - t4g.2xlarge
      - c5.large
      - c5.xlarge
      - c5.2xlarge
      - c5.4xlarge
      - c5.9xlarge
      - c5.12xlarge
      - c5.18xlarge
      - c5.24xlarge
      - c5.metal
      - c5a.large
      - c5a.xlarge
      - c5a.2xlarge
      - c5a.4xlarge
      - c5a.8xlarge
      - c5a.12xlarge
      - c5a.16xlarge
      - c5a.24xlarge
      - c5ad.large
      - c5ad.xlarge
      - c5ad.2xlarge
      - c5ad.4xlarge
      - c5ad.8xlarge
      - c5ad.12xlarge
      - c5ad.16xlarge
      - c5ad.24xlarge
      - c5d.large
      - c5d.xlarge
      - c5d.2xlarge
      - c5d.4xlarge
      - c5d.9xlarge
      - c5d.12xlarge
      - c5d.18xlarge
      - c5d.24xlarge
      - c5d.metal
      - c5n.large
      - c5n.xlarge
      - c5n.2xlarge
      - c5n.4xlarge
      - c5n.9xlarge
      - c5n.18xlarge
      - c5n.metal
      - c6a.large
      - c6a.xlarge
      - c6a.2xlarge
      - c6a.4xlarge
      - c6a.8xlarge
      - c6a.12xlarge
      - c6a.16xlarge
      - c6a.24xlarge
      - c6a.32xlarge
      - c6a.48xlarge
      - c6a.metal
      - c6g.medium
      - c6g.large
      - c6g.xlarge
      - c6g.2xlarge
      - c6g.4xlarge
      - c6g.8xlarge
      - c6g.12xlarge
      - c6g.16xlarge
      - c6g.metal
      - c6gd.medium
      - c6gd.large
      - c6gd.xlarge
      - c6gd.2xlarge
      - c6gd.4xlarge
      - c6gd.8xlarge
      - c6gd.12xlarge
      - c6gd.16xlarge
      - c6gd.metal
      - c6gn.medium
      - c6gn.large
      - c6gn.xlarge
      - c6gn.2xlarge
      - c6gn.4xlarge
      - c6gn.8xlarge
      - c6gn.12xlarge
      - c6gn.16xlarge
      - c6i.large
      - c6i.xlarge
      - c6i.2xlarge
      - c6i.4xlarge
      - c6i.8xlarge
      - c6i.12xlarge
      - c6i.16xlarge
      - c6i.24xlarge
      - c6i.32xlarge
      - c6i.metal
      - c6id.large
      - c6id.xlarge
      - c6id.2xlarge
      - c6id.4xlarge
      - c6id.8xlarge
      - c6id.12xlarge
      - c6id.16xlarge
      - c6id.24xlarge
      - c6id.32xlarge
      - c6id.metal
      - c6in.large
      - c6in.xlarge
      - c6in.2xlarge
      - c6in.4xlarge
      - c6in.8xlarge
      - c6in.12xlarge
      - c6in.16xlarge
      - c6in.24xlarge
      - c6in.32xlarge
      - c6in.metal
      - c7a.medium
      - c7a.large
      - c7a.xlarge
      - c7a.2xlarge
      - c7a.4xlarge
      - c7a.8xlarge
      - c7a.12xlarge
      - c7a.16xlarge
      - c7a.24xlarge
      - c7a.32xlarge
      - c7a.48xlarge
      - c7a.metal-48xl
      - c7g.medium
      - c7g.large
      - c7g.xlarge
      - c7g.2xlarge
      - c7g.4xlarge
      - c7g.8xlarge
      - c7g.12xlarge
      - c7g.16xlarge
      - c7g.metal
      - c7gd.medium
      - c7gd.large
      - c7gd.xlarge
      - c7gd.2xlarge
      - c7gd.4xlarge
      - c7gd.8xlarge
      - c7gd.12xlarge
      - c7gd.16xlarge
      - c7gd.metal
      - c7gn.medium
      - c7gn.large
      - c7gn.xlarge
      - c7gn.2xlarge
      - c7gn.4xlarge
      - c7gn.8xlarge
      - c7gn.12xlarge
      - c7gn.16xlarge
      - c7gn.metal
      - c7i-flex.large
      - c7i-flex.xlarge
      - c7i-flex.2xlarge
      - c7i-flex.4xlarge
      - c7i-flex.8xlarge
      - c7i-flex.12xlarge
      - c7i-flex.16xlarge
      - c7i.large
      - c7i.xlarge
      - c7i.2xlarge
      - c7i.4xlarge
      - c7i.8xlarge
      - c7i.12xlarge
      - c7i.16xlarge
      - c7i.24xlarge
      - c7i.48xlarge
      - c7i.metal-24xl
      - c7i.metal-48xl
      - c8g.medium
      - c8g.large
      - c8g.xlarge
      - c8g.2xlarge
      - c8g.4xlarge
      - c8g.8xlarge
      - c8g.12xlarge
      - c8g.16xlarge
      - c8g.24xlarge
      - c8g.48xlarge
      - c8g.metal-24xl
      - c8g.metal-48xl
      - m6a.large
      - m6a.xlarge
      - m6a.2xlarge
      - m6a.4xlarge
      - m6a.8xlarge
      - m6a.12xlarge
      - m6a.16xlarge
      - m6a.24xlarge
      - m6a.32xlarge
      - m6a.48xlarge
      - m6a.metal
      - m6g.medium
      - m6g.large
      - m6g.xlarge
      - m6g.2xlarge
      - m6g.4xlarge
      - m6g.8xlarge
      - m6g.12xlarge
      - m6g.16xlarge
      - m6g.metal
      - m6gd.medium
      - m6gd.large
      - m6gd.xlarge
      - m6gd.2xlarge
      - m6gd.4xlarge
      - m6gd.8xlarge
      - m6gd.12xlarge
      - m6gd.16xlarge
      - m6gd.metal
      - m6i.large
      - m6i.xlarge
      - m6i.2xlarge
      - m6i.4xlarge
      - m6i.8xlarge
      - m6i.12xlarge
      - m6i.16xlarge
      - m6i.24xlarge
      - m6i.32xlarge
      - m6i.metal
      - m6id.large
      - m6id.xlarge
      - m6id.2xlarge
      - m6id.4xlarge
      - m6id.8xlarge
      - m6id.12xlarge
      - m6id.16xlarge
      - m6id.24xlarge
      - m6id.32xlarge
      - m6id.metal
      - m6idn.large
      - m6idn.xlarge
      - m6idn.2xlarge
      - m6idn.4xlarge
      - m6idn.8xlarge
      - m6idn.12xlarge
      - m6idn.16xlarge
      - m6idn.24xlarge
      - m6idn.32xlarge
      - m6idn.metal
      - m6in.large
      - m6in.xlarge
      - m6in.2xlarge
      - m6in.4xlarge
      - m6in.8xlarge
      - m6in.12xlarge
      - m6in.16xlarge
      - m6in.24xlarge
      - m6in.32xlarge
      - m6in.metal
      - m7a.medium
      - m7a.large
      - m7a.xlarge
      - m7a.2xlarge
      - m7a.4xlarge
      - m7a.8xlarge
      - m7a.12xlarge
      - m7a.16xlarge
      - m7a.24xlarge
      - m7a.32xlarge
      - m7a.48xlarge
      - m7a.metal-48xl
      - m7g.medium
      - m7g.large
      - m7g.xlarge
      - m7g.2xlarge
      - m7g.4xlarge
      - m7g.8xlarge
      - m7g.12xlarge
      - m7g.16xlarge
      - m7g.metal
      - m7gd.medium
      - m7gd.large
      - m7gd.xlarge
      - m7gd.2xlarge
      - m7gd.4xlarge
      - m7gd.8xlarge
      - m7gd.12xlarge
      - m7gd.16xlarge
      - m7gd.metal
      - m7i-flex.large
      - m7i-flex.xlarge
      - m7i-flex.2xlarge
      - m7i-flex.4xlarge
      - m7i-flex.8xlarge
      - m7i-flex.12xlarge
      - m7i-flex.16xlarge
      - m7i.large
      - m7i.xlarge
      - m7i.2xlarge
      - m7i.4xlarge
      - m7i.8xlarge
      - m7i.12xlarge
      - m7i.16xlarge
      - m7i.24xlarge
      - m7i.48xlarge
      - m7i.metal-24xl
      - m7i.metal-48xl
      - m8g.medium
      - m8g.large
      - m8g.xlarge
      - m8g.2xlarge
      - m8g.4xlarge
      - m8g.8xlarge
      - m8g.12xlarge
      - m8g.16xlarge
      - m8g.24xlarge
      - m8g.48xlarge
      - m8g.metal-24xl
      - m8g.metal-48xl
    ConstraintDescription: "Must be a valid EC2 instance type"

  MediaNodeInstanceType:
    Description: "Specifies the EC2 instance type for your OpenVidu Media Nodes"
    Type: String
    Default: c6a.xlarge
    AllowedValues:
      - t3.nano
      - t3.micro
      - t3.small
      - t3.medium
      - t3.large
      - t3.xlarge
      - t3.2xlarge
      - t3a.nano
      - t3a.micro
      - t3a.small
      - t3a.medium
      - t3a.large
      - t3a.xlarge
      - t3a.2xlarge
      - t4g.nano
      - t4g.micro
      - t4g.small
      - t4g.medium
      - t4g.large
      - t4g.xlarge
      - t4g.2xlarge
      - c5.large
      - c5.xlarge
      - c5.2xlarge
      - c5.4xlarge
      - c5.9xlarge
      - c5.12xlarge
      - c5.18xlarge
      - c5.24xlarge
      - c5.metal
      - c5a.large
      - c5a.xlarge
      - c5a.2xlarge
      - c5a.4xlarge
      - c5a.8xlarge
      - c5a.12xlarge
      - c5a.16xlarge
      - c5a.24xlarge
      - c5ad.large
      - c5ad.xlarge
      - c5ad.2xlarge
      - c5ad.4xlarge
      - c5ad.8xlarge
      - c5ad.12xlarge
      - c5ad.16xlarge
      - c5ad.24xlarge
      - c5d.large
      - c5d.xlarge
      - c5d.2xlarge
      - c5d.4xlarge
      - c5d.9xlarge
      - c5d.12xlarge
      - c5d.18xlarge
      - c5d.24xlarge
      - c5d.metal
      - c5n.large
      - c5n.xlarge
      - c5n.2xlarge
      - c5n.4xlarge
      - c5n.9xlarge
      - c5n.18xlarge
      - c5n.metal
      - c6a.large
      - c6a.xlarge
      - c6a.2xlarge
      - c6a.4xlarge
      - c6a.8xlarge
      - c6a.12xlarge
      - c6a.16xlarge
      - c6a.24xlarge
      - c6a.32xlarge
      - c6a.48xlarge
      - c6a.metal
      - c6g.medium
      - c6g.large
      - c6g.xlarge
      - c6g.2xlarge
      - c6g.4xlarge
      - c6g.8xlarge
      - c6g.12xlarge
      - c6g.16xlarge
      - c6g.metal
      - c6gd.medium
      - c6gd.large
      - c6gd.xlarge
      - c6gd.2xlarge
      - c6gd.4xlarge
      - c6gd.8xlarge
      - c6gd.12xlarge
      - c6gd.16xlarge
      - c6gd.metal
      - c6gn.medium
      - c6gn.large
      - c6gn.xlarge
      - c6gn.2xlarge
      - c6gn.4xlarge
      - c6gn.8xlarge
      - c6gn.12xlarge
      - c6gn.16xlarge
      - c6i.large
      - c6i.xlarge
      - c6i.2xlarge
      - c6i.4xlarge
      - c6i.8xlarge
      - c6i.12xlarge
      - c6i.16xlarge
      - c6i.24xlarge
      - c6i.32xlarge
      - c6i.metal
      - c6id.large
      - c6id.xlarge
      - c6id.2xlarge
      - c6id.4xlarge
      - c6id.8xlarge
      - c6id.12xlarge
      - c6id.16xlarge
      - c6id.24xlarge
      - c6id.32xlarge
      - c6id.metal
      - c6in.large
      - c6in.xlarge
      - c6in.2xlarge
      - c6in.4xlarge
      - c6in.8xlarge
      - c6in.12xlarge
      - c6in.16xlarge
      - c6in.24xlarge
      - c6in.32xlarge
      - c6in.metal
      - c7a.medium
      - c7a.large
      - c7a.xlarge
      - c7a.2xlarge
      - c7a.4xlarge
      - c7a.8xlarge
      - c7a.12xlarge
      - c7a.16xlarge
      - c7a.24xlarge
      - c7a.32xlarge
      - c7a.48xlarge
      - c7a.metal-48xl
      - c7g.medium
      - c7g.large
      - c7g.xlarge
      - c7g.2xlarge
      - c7g.4xlarge
      - c7g.8xlarge
      - c7g.12xlarge
      - c7g.16xlarge
      - c7g.metal
      - c7gd.medium
      - c7gd.large
      - c7gd.xlarge
      - c7gd.2xlarge
      - c7gd.4xlarge
      - c7gd.8xlarge
      - c7gd.12xlarge
      - c7gd.16xlarge
      - c7gd.metal
      - c7gn.medium
      - c7gn.large
      - c7gn.xlarge
      - c7gn.2xlarge
      - c7gn.4xlarge
      - c7gn.8xlarge
      - c7gn.12xlarge
      - c7gn.16xlarge
      - c7gn.metal
      - c7i-flex.large
      - c7i-flex.xlarge
      - c7i-flex.2xlarge
      - c7i-flex.4xlarge
      - c7i-flex.8xlarge
      - c7i-flex.12xlarge
      - c7i-flex.16xlarge
      - c7i.large
      - c7i.xlarge
      - c7i.2xlarge
      - c7i.4xlarge
      - c7i.8xlarge
      - c7i.12xlarge
      - c7i.16xlarge
      - c7i.24xlarge
      - c7i.48xlarge
      - c7i.metal-24xl
      - c7i.metal-48xl
      - c8g.medium
      - c8g.large
      - c8g.xlarge
      - c8g.2xlarge
      - c8g.4xlarge
      - c8g.8xlarge
      - c8g.12xlarge
      - c8g.16xlarge
      - c8g.24xlarge
      - c8g.48xlarge
      - c8g.metal-24xl
      - c8g.metal-48xl
      - m6a.large
      - m6a.xlarge
      - m6a.2xlarge
      - m6a.4xlarge
      - m6a.8xlarge
      - m6a.12xlarge
      - m6a.16xlarge
      - m6a.24xlarge
      - m6a.32xlarge
      - m6a.48xlarge
      - m6a.metal
      - m6g.medium
      - m6g.large
      - m6g.xlarge
      - m6g.2xlarge
      - m6g.4xlarge
      - m6g.8xlarge
      - m6g.12xlarge
      - m6g.16xlarge
      - m6g.metal
      - m6gd.medium
      - m6gd.large
      - m6gd.xlarge
      - m6gd.2xlarge
      - m6gd.4xlarge
      - m6gd.8xlarge
      - m6gd.12xlarge
      - m6gd.16xlarge
      - m6gd.metal
      - m6i.large
      - m6i.xlarge
      - m6i.2xlarge
      - m6i.4xlarge
      - m6i.8xlarge
      - m6i.12xlarge
      - m6i.16xlarge
      - m6i.24xlarge
      - m6i.32xlarge
      - m6i.metal
      - m6id.large
      - m6id.xlarge
      - m6id.2xlarge
      - m6id.4xlarge
      - m6id.8xlarge
      - m6id.12xlarge
      - m6id.16xlarge
      - m6id.24xlarge
      - m6id.32xlarge
      - m6id.metal
      - m6idn.large
      - m6idn.xlarge
      - m6idn.2xlarge
      - m6idn.4xlarge
      - m6idn.8xlarge
      - m6idn.12xlarge
      - m6idn.16xlarge
      - m6idn.24xlarge
      - m6idn.32xlarge
      - m6idn.metal
      - m6in.large
      - m6in.xlarge
      - m6in.2xlarge
      - m6in.4xlarge
      - m6in.8xlarge
      - m6in.12xlarge
      - m6in.16xlarge
      - m6in.24xlarge
      - m6in.32xlarge
      - m6in.metal
      - m7a.medium
      - m7a.large
      - m7a.xlarge
      - m7a.2xlarge
      - m7a.4xlarge
      - m7a.8xlarge
      - m7a.12xlarge
      - m7a.16xlarge
      - m7a.24xlarge
      - m7a.32xlarge
      - m7a.48xlarge
      - m7a.metal-48xl
      - m7g.medium
      - m7g.large
      - m7g.xlarge
      - m7g.2xlarge
      - m7g.4xlarge
      - m7g.8xlarge
      - m7g.12xlarge
      - m7g.16xlarge
      - m7g.metal
      - m7gd.medium
      - m7gd.large
      - m7gd.xlarge
      - m7gd.2xlarge
      - m7gd.4xlarge
      - m7gd.8xlarge
      - m7gd.12xlarge
      - m7gd.16xlarge
      - m7gd.metal
      - m7i-flex.large
      - m7i-flex.xlarge
      - m7i-flex.2xlarge
      - m7i-flex.4xlarge
      - m7i-flex.8xlarge
      - m7i-flex.12xlarge
      - m7i-flex.16xlarge
      - m7i.large
      - m7i.xlarge
      - m7i.2xlarge
      - m7i.4xlarge
      - m7i.8xlarge
      - m7i.12xlarge
      - m7i.16xlarge
      - m7i.24xlarge
      - m7i.48xlarge
      - m7i.metal-24xl
      - m7i.metal-48xl
      - m8g.medium
      - m8g.large
      - m8g.xlarge
      - m8g.2xlarge
      - m8g.4xlarge
      - m8g.8xlarge
      - m8g.12xlarge
      - m8g.16xlarge
      - m8g.24xlarge
      - m8g.48xlarge
      - m8g.metal-24xl
      - m8g.metal-48xl
    ConstraintDescription: "Must be a valid EC2 instance type"

  InitialMeetAdminPassword:
    Description: 'Initial password for the "admin" user in OpenVidu Meet. If not provided, a random password will be generated.'
    Type: String
    NoEcho: true
    Default: ''
    # Only allow alphanumeric characters
    AllowedPattern: '^[A-Za-z0-9_-]*$'
    ConstraintDescription: 'Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to generate a random password.'

  InitialMeetApiKey:
    Description: 'Initial API key for OpenVidu Meet. If not provided, no API key will be set and the user can set it later from Meet Console.'
    Type: String
    NoEcho: true
    Default: ''
    # Only allow alphanumeric characters
    AllowedPattern: '^[A-Za-z0-9_-]*$'
    ConstraintDescription: 'Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to not set an initial API key.'

  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: Name of an existing EC2 KeyPair to enable SSH access to the instances
    AllowedPattern: ^.+$
    ConstraintDescription: must be the name of an existing EC2 KeyPair.

  OperatingSystem:
    Description: EC2 Operating System
    Type: String
    Default: "Ubuntu-24"
    AllowedValues: ['Ubuntu-22', 'Ubuntu-24']

  InitialNumberOfMediaNodes:
    Type: Number
    Default: 1
    Description: Number of initial media nodes to deploy

  MinNumberOfMediaNodes:
    Type: Number
    Default: 1
    Description: Minimum number of media nodes to deploy

  MaxNumberOfMediaNodes:
    Type: Number
    Default: 5
    Description: Maximum number of media nodes to deploy

  ScaleTargetCPU:
    Type: Number
    Default: 50
    Description: Target CPU percentage to scale up or down

  S3AppDataBucketName:
    Type: String
    Description: Name of the S3 bucket to store data and recordings. If empty, a bucket will be created

  S3ClusterDataBucketName:
    Type: String
    Description: Name of the S3 bucket to store cluster data. If empty, a bucket will be created

  AdditionalInstallFlags:
    Description: Additional optional flags to pass to the OpenVidu installer (comma-separated, e.g., "--flag1=value, --flag2").
    Type: String
    Default: ""
    AllowedPattern: '^[A-Za-z0-9, =_.\-]*$'  # Allows letters, numbers, comma, space, underscore, dot, equals, and hyphen
    ConstraintDescription: Must be a comma-separated list of flags (for example, --flag=value, --bool-flag).

  OpenViduVPC:
    Description: "Dedicated VPC for OpenVidu cluster"
    Type: AWS::EC2::VPC::Id
    AllowedPattern: ^.+$
    ConstraintDescription: You must specify a VPC ID

  OpenViduMasterNodeSubnets:
    Description: "Subnets for OpenVidu Master Node"
    Type: List<AWS::EC2::Subnet::Id>
    AllowedPattern: ^.+$
    ConstraintDescription: You must specify a list of subnet IDs

  OpenViduMediaNodeSubnets:
    Description: "Subnets for OpenVidu Media Nodes"
    Type: List<AWS::EC2::Subnet::Id>
    AllowedPattern: ^.+$
    ConstraintDescription: You must specify a list of subnet IDs

  MasterNodesDiskSize:
    Description: Size of the disk in GB
    Type: Number
    Default: 100
    MinValue: 50
    ConstraintDescription: The disk size must be at least 50 GB

Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: Domain and Load Blancer configuration
        Parameters:
          - DomainName
          - OpenViduCertificateARN
      - Label:
          default: OpenVidu High Availability configuration
        Parameters:
          - OpenViduLicense
          - RTCEngine
      - Label:
          default: OpenVidu Meet configuration
        Parameters:
          - InitialMeetAdminPassword
          - InitialMeetApiKey
      - Label:
          default: EC2 Instance configuration
        Parameters:
          - MasterNodeInstanceType
          - MediaNodeInstanceType
          - KeyName
          - OperatingSystem
      - Label:
          default: Media Nodes Autoscaling Group configuration
        Parameters:
          - InitialNumberOfMediaNodes
          - MinNumberOfMediaNodes
          - MaxNumberOfMediaNodes
          - ScaleTargetCPU
      - Label:
          default: S3 bucket for application data, cluster data and recordings
        Parameters:
          - S3AppDataBucketName
          - S3ClusterDataBucketName
      - Label:
          default: VPC configuration
        Parameters:
          - OpenViduVPC
          - OpenViduMasterNodeSubnets
          - OpenViduMediaNodeSubnets
      - Label:
          default: Volumes configuration
        Parameters:
          - MasterNodesDiskSize
      - Label:
          default: "(Optional) Additional Installer Flags"
        Parameters:
          - AdditionalInstallFlags

Conditions:
  CreateRecordingsBucket: !Equals [!Ref S3AppDataBucketName, ""]
  CreateClusterDataBucket: !Equals [!Ref S3ClusterDataBucketName, ""]
  IsMasterGraviton: !Or
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 't4g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c8g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm6g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm6gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm7g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm7gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm8g']
  IsMediaGraviton: !Or
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 't4g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c8g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm6g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm6gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm7g']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm7gd']
    - !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm8g']

Mappings:
  ArmImage:
  # aws ssm get-parameters-by-path --path "/aws/service/canonical/ubuntu/" --recursive --query "Parameters[*].Name"  > canonical-ami.txt
    Ubuntu-22:
      ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/jammy/stable/current/arm64/hvm/ebs-gp2/ami-id}}'
    Ubuntu-24:
      ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/noble/stable/current/arm64/hvm/ebs-gp3/ami-id}}'
  AmdImage:
    Ubuntu-22:
      ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/jammy/stable/current/amd64/hvm/ebs-gp2/ami-id}}'
    Ubuntu-24:
      ImageId: '{{resolve:ssm:/aws/service/canonical/ubuntu/server/noble/stable/current/amd64/hvm/ebs-gp3/ami-id}}'

Resources:

  OpenViduSharedInfo:
    Type: AWS::SecretsManager::Secret
    UpdateReplacePolicy: Retain
    DeletionPolicy: Delete
    Properties:
      Name: !Sub openvidu-ha-${AWS::Region}-${AWS::StackName}
      Description: Secret for OpenVidu High Availability to store deployment info and seed secret
      # All the values are initialized by one master node and shared with the rest of the nodes
      SecretString: |
        {
          "OPENVIDU_URL": "none",
          "MEET_INITIAL_ADMIN_USER": "none",
          "MEET_INITIAL_ADMIN_PASSWORD": "none",
          "MEET_INITIAL_API_KEY": "none",
          "LIVEKIT_URL": "none",
          "LIVEKIT_API_KEY": "none",
          "LIVEKIT_API_SECRET": "none",
          "DASHBOARD_URL": "none",
          "GRAFANA_URL": "none",
          "MINIO_URL": "none",
          "DOMAIN_NAME": "none",
          "OPENVIDU_PRO_LICENSE": "none",
          "OPENVIDU_RTC_ENGINE": "none",
          "REDIS_PASSWORD": "none",
          "MONGO_ADMIN_USERNAME": "none",
          "MONGO_ADMIN_PASSWORD": "none",
          "MONGO_REPLICA_SET_KEY": "none",
          "MINIO_ACCESS_KEY": "none",
          "MINIO_SECRET_KEY": "none",
          "DASHBOARD_ADMIN_USERNAME": "none",
          "DASHBOARD_ADMIN_PASSWORD": "none",
          "GRAFANA_ADMIN_USERNAME": "none",
          "GRAFANA_ADMIN_PASSWORD": "none",
          "ENABLED_MODULES": "none",
          "MASTER_NODE_1_PRIVATE_IP": "none",
          "MASTER_NODE_2_PRIVATE_IP": "none",
          "MASTER_NODE_3_PRIVATE_IP": "none",
          "MASTER_NODE_4_PRIVATE_IP": "none",
          "OPENVIDU_VERSION": "none",
          "ALL_SECRETS_GENERATED": "false"
        }

  S3AppDataBucketResource:
    Type: 'AWS::S3::Bucket'
    Properties:
      ### Unique bucket name using Stack ID
      BucketName: !Join ["-" , [ 'openvidu-appdata', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls : true
        RestrictPublicBuckets: true
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Condition: CreateRecordingsBucket

  S3ClusterDataBucketResource:
    Type: 'AWS::S3::Bucket'
    Properties:
      ### Unique bucket name using Stack ID
      BucketName: !Join ["-" , [ 'openvidu-clusterdata', !Select [0, !Split ["-", !Select [2, !Split [/, !Ref AWS::StackId ]]]]]]
      AccessControl: Private
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls : true
        RestrictPublicBuckets: true
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Condition: CreateClusterDataBucket

  # -------------------------
  # Preprocess subnets to allocate Volumes and ENIs across Availability Zones
  # For OpenVidu Master Nodes
  # -------------------------
  SubnetProcessorFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub 'SubnetProcessor-${AWS::Region}-${AWS::StackName}'
      Handler: index.lambda_handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          import cfnresponse
          import boto3

          def lambda_handler(event, context):
              try:
                  # Process event data
                  subnets = event['ResourceProperties']['Subnets']
                  ec2 = boto3.client('ec2')

                  # Ensure we have at least four subnets by cycling through the available subnets
                  subnets = (subnets * 4)[:4]  # Repeat the list to have at least 4 elements and then take the first 4

                  # Prepare the response
                  responseData = {
                      'Subnet1': subnets[0],
                      'Subnet2': subnets[1],
                      'Subnet3': subnets[2],
                      'Subnet4': subnets[3],
                  }
                  cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {'Message': str(e)})


      Runtime: python3.12
      Timeout: 120

  SubnetProcessor:
    Type: Custom::SubnetProcessor
    Properties:
      ServiceToken: !GetAtt SubnetProcessorFunction.Arn
      Subnets: !Ref OpenViduMasterNodeSubnets

  LambdaLogGroup:
    UpdateReplacePolicy: Retain
    DeletionPolicy: Delete
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}'
      RetentionInDays: 7

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: LambdaLogsPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}:*'
              - Effect: Allow
                Action:
                  - ec2:DescribeSubnets
                Resource: '*'

  OpenViduMasterNodeRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: "/"
      Policies:
        - PolicyName: !Sub openvidu-master-policy-${AWS::Region}-${AWS::StackName}
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - autoscaling:SetInstanceHealth
                Resource: '*'
                Condition:
                  StringEquals:
                    'aws:ResourceTag/aws:cloudformation:stack-id': !Ref 'AWS::StackId'
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                  - secretsmanager:UpdateSecret
                Resource: !Ref OpenViduSharedInfo
              - Fn::If:
                - CreateRecordingsBucket
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub ${S3AppDataBucketResource.Arn}/*
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}/*
              - Fn::If:
                - CreateRecordingsBucket
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !GetAtt S3AppDataBucketResource.Arn
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}
              - Fn::If:
                - CreateClusterDataBucket
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub ${S3ClusterDataBucketResource.Arn}/*
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3ClusterDataBucketName}/*
              - Fn::If:
                - CreateClusterDataBucket
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !GetAtt S3ClusterDataBucketResource.Arn
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3ClusterDataBucketName}
      RoleName:
        Fn::Join:
          # Generate a not too long and unique role name
          # Getting a unique identifier from the stack id
          - ''
          - - openvidu-master-role-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]

  OpenViduMediaNodeRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - 'sts:AssumeRole'
      Path: /
      ManagedPolicyArns:
           - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore
      Policies:
        - PolicyName: !Sub openvidu-media-policy-${AWS::Region}-${AWS::StackName}
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Ref OpenViduSharedInfo
              - Effect: Allow
                Action:
                  - autoscaling:SetInstanceHealth
                  - autoscaling:CompleteLifecycleAction
                  - autoscaling:RecordLifecycleActionHeartbeat
                Resource: '*'
                Condition:
                  StringEquals:
                    'aws:ResourceTag/aws:cloudformation:stack-name': !Ref 'AWS::StackName'
              - Fn::If:
                - CreateRecordingsBucket
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub ${S3AppDataBucketResource.Arn}/*
                - Effect: Allow
                  Action:
                    - s3:DeleteObject
                    - s3:GetObject
                    - s3:PutObject
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}/*
              - Fn::If:
                - CreateRecordingsBucket
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !GetAtt S3AppDataBucketResource.Arn
                - Effect: Allow
                  Action:
                    - s3:ListBucket
                    - s3:GetBucketLocation
                  Resource: !Sub arn:${AWS::Partition}:s3:::${S3AppDataBucketName}
      RoleName:
        Fn::Join:
          # Generate a not too long and unique role name
          # Getting a unique identifier from the stack id
          - ''
          - - openvidu-media-role-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]

  OpenViduMasterInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub OpenViduMasterInstanceProfile-${AWS::Region}-${AWS::StackName}
      Roles:
        - !Ref OpenViduMasterNodeRole

  OpenViduMediaInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    DependsOn:
         - MasterNodesWaitCondition4
    Properties:
      InstanceProfileName: !Sub OpenViduMediaInstanceProfile-${AWS::Region}-${AWS::StackName}
      Roles:
        - !Ref OpenViduMediaNodeRole

  OpenViduMasterLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Metadata:
      Comment: Launch template for OpenVidu Master Node
      AWS::CloudFormation::Init:
        config:
          files:
            '/usr/local/bin/install.sh':
              content: !Sub |
                #!/bin/bash -x
                set -e
                OPENVIDU_VERSION=main
                DOMAIN=
                YQ_VERSION=v4.44.5

                # Install dependencies
                apt-get update && apt-get install -y \
                  curl \
                  unzip \
                  jq \
                  wget
                wget https://github.com/mikefarah/yq/releases/download/${!YQ_VERSION}/yq_linux_$(dpkg --print-architecture).tar.gz -O - |\
                  tar xz && mv yq_linux_$(dpkg --print-architecture) /usr/bin/yq

                # Install aws-cli
                curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip" -o "awscliv2.zip"
                unzip -qq awscliv2.zip
                ./aws/install
                rm -rf awscliv2.zip aws

                # Singal to notify instance is waiting
                SIGNAL_NAME="$1"

                # Token for IMDSv2
                TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"

                # Subnets IDs
                SUBNETS=(
                  "${SubnetProcessor.Subnet1}"
                  "${SubnetProcessor.Subnet2}"
                  "${SubnetProcessor.Subnet3}"
                  "${SubnetProcessor.Subnet4}"
                )

                MAC_ADDRESS="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254//latest/meta-data/mac)"
                SUBNET_ID="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s "http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS/subnet-id")"

                # Check master node number
                MASTER_NODE_NUM=1
                for subnet in "${!SUBNETS[@]}"; do
                  if [[ "$subnet" == "$SUBNET_ID" ]]; then

                    SHARED_SECRET=$(aws secretsmanager get-secret-value \
                      --region ${AWS::Region} \
                      --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                      --query SecretString --output text || echo 'none')

                      # Check if current master node is reacheable with ping
                      ACUTAL_MASTER_NODE_IP=$(echo "$SHARED_SECRET" | jq -r ".MASTER_NODE_${!MASTER_NODE_NUM}_PRIVATE_IP")
                      if [[ "$ACUTAL_MASTER_NODE_IP" == "none" ]]; then
                        break
                      fi
                  fi
                  MASTER_NODE_NUM=$((MASTER_NODE_NUM + 1))
                done

                # Get own private IP
                PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"

                if [[ "$PRIVATE_IP" == "" ]]; then
                  echo "Error: Private IP not found"
                  exit 1
                fi

                # Store current private IP
                /usr/local/bin/store_secret.sh save MASTER_NODE_${!MASTER_NODE_NUM}_PRIVATE_IP "${!PRIVATE_IP}"

                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)
                ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')

                # If the private IP is the same as the first master node, generate the secrets
                if [[ $MASTER_NODE_NUM -eq 1 ]] && [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
                  DOMAIN="$(/usr/local/bin/store_secret.sh save DOMAIN_NAME "${DomainName}")"
                  OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
                  OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
                  # Store version so media nodes can use it to install the same version
                  /usr/local/bin/store_secret.sh save OPENVIDU_VERSION "${!OPENVIDU_VERSION}"

                  # Meet initial admin user and password
                  MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
                  if [[ "${InitialMeetAdminPassword}" != '' ]]; then
                    MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_PASSWORD "${InitialMeetAdminPassword}")"
                  else
                    MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MEET_INITIAL_ADMIN_PASSWORD)"
                  fi
                  if [[ "${InitialMeetApiKey}" != '' ]]; then
                    MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "${InitialMeetApiKey}")"
                  else
                    MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "")"
                  fi

                  # Store usernames and generate random passwords
                  REDIS_PASSWORD="$(/usr/local/bin/store_secret.sh generate REDIS_PASSWORD)"
                  MONGO_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
                  MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
                  MONGO_REPLICA_SET_KEY="$(/usr/local/bin/store_secret.sh generate MONGO_REPLICA_SET_KEY)"
                  MINIO_ACCESS_KEY="$(/usr/local/bin/store_secret.sh save MINIO_ACCESS_KEY "minioadmin")"
                  MINIO_SECRET_KEY="$(/usr/local/bin/store_secret.sh generate MINIO_SECRET_KEY)"
                  DASHBOARD_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save DASHBOARD_ADMIN_USERNAME "dashboardadmin")"
                  DASHBOARD_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate DASHBOARD_ADMIN_PASSWORD)"
                  GRAFANA_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save GRAFANA_ADMIN_USERNAME "grafanaadmin")"
                  GRAFANA_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate GRAFANA_ADMIN_PASSWORD)"
                  LIVEKIT_API_KEY="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_KEY "API" 12)"
                  LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)"
                  ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED_MODULES "observability,v2compatibility,openviduMeet")"
                  ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
                fi

                # Fetch the shared secret again
                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)

                ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
                if [[ "${!ALL_SECRETS_GENERATED}" == "false" ]]; then
                  echo "Error: Secrets not generated"
                  exit 1
                fi

                # sending the signal call
                cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region}

                # Wait for all master nodes to store their private IPs
                while true; do
                  SHARED_SECRET=$(aws secretsmanager get-secret-value \
                    --region ${AWS::Region} \
                    --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                    --query SecretString --output text || echo 'none')

                  MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
                  MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
                  MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
                  MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')

                  # Check if all master nodes have stored their private IPs
                  if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] &&
                      [[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] &&
                      [[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] &&
                      [[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]]; then
                    break
                  fi
                  sleep 5
                done

                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)

                MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
                MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
                MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
                MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
                MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"

                DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
                OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
                OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
                REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
                MONGO_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_USERNAME')
                MONGO_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_PASSWORD')
                MONGO_REPLICA_SET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MONGO_REPLICA_SET_KEY')
                MINIO_ACCESS_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_ACCESS_KEY')
                MINIO_SECRET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_SECRET_KEY')
                DASHBOARD_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_USERNAME')
                DASHBOARD_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_PASSWORD')
                GRAFANA_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_USERNAME')
                GRAFANA_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_PASSWORD')
                MEET_INITIAL_ADMIN_USER=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_USER')
                MEET_INITIAL_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
                if [[ "${InitialMeetApiKey}" != '' ]]; then
                  MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
                else
                  MEET_INITIAL_API_KEY=""
                fi
                MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
                LIVEKIT_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_KEY')
                LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET')
                ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES')

                # Base command
                INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh)"

                # Common arguments
                COMMON_ARGS=(
                  "--no-tty"
                  "--install"
                  "--environment=aws"
                  "--deployment-type='ha'"
                  "--node-role='master-node'"
                  "--external-load-balancer"
                  "--master-node-private-ip-list='$MASTER_NODE_PRIVATE_IP_LIST'"
                  "--openvidu-pro-license='$OPENVIDU_PRO_LICENSE'"
                  "--domain-name='$DOMAIN'"
                  "--enabled-modules='$ENABLED_MODULES'"
                  "--rtc-engine=$OPENVIDU_RTC_ENGINE"
                  "--redis-password=$REDIS_PASSWORD"
                  "--mongo-admin-user=$MONGO_ADMIN_USERNAME"
                  "--mongo-admin-password=$MONGO_ADMIN_PASSWORD"
                  "--mongo-replica-set-key=$MONGO_REPLICA_SET_KEY"
                  "--minio-access-key=$MINIO_ACCESS_KEY"
                  "--minio-secret-key=$MINIO_SECRET_KEY"
                  "--dashboard-admin-user=$DASHBOARD_ADMIN_USERNAME"
                  "--dashboard-admin-password=$DASHBOARD_ADMIN_PASSWORD"
                  "--grafana-admin-user=$GRAFANA_ADMIN_USERNAME"
                  "--grafana-admin-password=$GRAFANA_ADMIN_PASSWORD"
                  "--meet-initial-admin-password=$MEET_INITIAL_ADMIN_PASSWORD"
                  "--meet-initial-api-key=$MEET_INITIAL_API_KEY"
                  "--livekit-api-key=$LIVEKIT_API_KEY"
                  "--livekit-api-secret=$LIVEKIT_API_SECRET"
                )

                # Include additional installer flags provided by the user
                if [[ "${AdditionalInstallFlags}" != "" ]]; then
                  IFS=',' read -ra EXTRA_FLAGS <<< "${AdditionalInstallFlags}"
                  for extra_flag in "${!EXTRA_FLAGS[@]}"; do
                    # Trim whitespace around each flag
                    extra_flag="$(echo -e "${!extra_flag}" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')"
                    if [[ "$extra_flag" != "" ]]; then
                      COMMON_ARGS+=("$extra_flag")
                    fi
                  done
                fi

                # Construct the final command
                FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}")"

                # Install OpenVidu
                exec bash -c "$FINAL_COMMAND"

              mode: '000755'
              owner: root
              group: root
            '/usr/local/bin/config_s3.sh':
              content: !Sub
                - |
                  #!/bin/bash
                  set -e

                  # Install dir and config dir
                  INSTALL_DIR="/opt/openvidu"
                  CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"

                  # Config S3 bucket
                  EXTERNAL_S3_ENDPOINT="https://s3.${AWS::Region}.amazonaws.com"
                  EXTERNAL_S3_REGION="${AWS::Region}"
                  EXTERNAL_S3_PATH_STYLE_ACCESS="false"
                  EXTERNAL_S3_BUCKET_APP_DATA=${S3AppDataBucketResourceName}
                  EXTERNAL_S3_BUCKET_CLUSTER_DATA=${S3ClusterDataBucketResourceName}
                  sed -i "s|EXTERNAL_S3_ENDPOINT=.*|EXTERNAL_S3_ENDPOINT=$EXTERNAL_S3_ENDPOINT|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                  sed -i "s|EXTERNAL_S3_REGION=.*|EXTERNAL_S3_REGION=$EXTERNAL_S3_REGION|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                  sed -i "s|EXTERNAL_S3_PATH_STYLE_ACCESS=.*|EXTERNAL_S3_PATH_STYLE_ACCESS=$EXTERNAL_S3_PATH_STYLE_ACCESS|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                  sed -i "s|EXTERNAL_S3_BUCKET_APP_DATA=.*|EXTERNAL_S3_BUCKET_APP_DATA=$EXTERNAL_S3_BUCKET_APP_DATA|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                  sed -i "s|EXTERNAL_S3_BUCKET_CLUSTER_DATA=.*|EXTERNAL_S3_BUCKET_CLUSTER_DATA=$EXTERNAL_S3_BUCKET_CLUSTER_DATA|" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                - S3AppDataBucketResourceName: !If
                    - CreateRecordingsBucket
                    - !Ref S3AppDataBucketResource
                    - !Ref S3AppDataBucketName
                  S3ClusterDataBucketResourceName: !If
                    - CreateClusterDataBucket
                    - !Ref S3ClusterDataBucketResource
                    - !Ref S3ClusterDataBucketName
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/after_install.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Get current shared secret
                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)

                # Save access URLs
                DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
                OPENVIDU_URL="https://${!DOMAIN}/"
                LIVEKIT_URL="wss://${!DOMAIN}/"
                DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
                GRAFANA_URL="https://${!DOMAIN}/grafana/"
                MINIO_URL="https://${!DOMAIN}/minio-console/"

                # Update shared secret
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$DOMAIN"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_URL": "'"$DASHBOARD_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_URL": "'"$GRAFANA_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_URL": "'"$MINIO_URL"'" }')"

                # Update shared secret
                aws secretsmanager update-secret \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --secret-string "$SHARED_SECRET"
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/update_config_from_secret.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Token for IMDSv2
                TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
                # Get current shared secret
                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)

                # Installation directory
                INSTALL_DIR="/opt/openvidu"
                CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"
                MASTER_NODE_CONFIG_DIR="${!INSTALL_DIR}/config/node"

                # Replace DOMAIN_NAME
                export DOMAIN=$(echo $SHARED_SECRET | jq -r .DOMAIN_NAME)
                if [[ -n "$DOMAIN" ]]; then
                  sed -i "s/DOMAIN_NAME=.*/DOMAIN_NAME=$DOMAIN/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                else
                  exit 1
                fi

                # Replace rest of the values
                sed -i "s/REDIS_PASSWORD=.*/REDIS_PASSWORD=$(echo $SHARED_SECRET | jq -r .REDIS_PASSWORD)/" "${!MASTER_NODE_CONFIG_DIR}/master_node.env"
                sed -i "s/OPENVIDU_RTC_ENGINE=.*/OPENVIDU_RTC_ENGINE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_RTC_ENGINE)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/OPENVIDU_PRO_LICENSE=.*/OPENVIDU_PRO_LICENSE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_PRO_LICENSE)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/MONGO_ADMIN_USERNAME=.*/MONGO_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .MONGO_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/MONGO_ADMIN_PASSWORD=.*/MONGO_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .MONGO_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/DASHBOARD_ADMIN_USERNAME=.*/DASHBOARD_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .DASHBOARD_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/DASHBOARD_ADMIN_PASSWORD=.*/DASHBOARD_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .DASHBOARD_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/MINIO_ACCESS_KEY=.*/MINIO_ACCESS_KEY=$(echo $SHARED_SECRET | jq -r .MINIO_ACCESS_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/MINIO_SECRET_KEY=.*/MINIO_SECRET_KEY=$(echo $SHARED_SECRET | jq -r .MINIO_SECRET_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/GRAFANA_ADMIN_USERNAME=.*/GRAFANA_ADMIN_USERNAME=$(echo $SHARED_SECRET | jq -r .GRAFANA_ADMIN_USERNAME)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/GRAFANA_ADMIN_PASSWORD=.*/GRAFANA_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .GRAFANA_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/LIVEKIT_API_KEY=.*/LIVEKIT_API_KEY=$(echo $SHARED_SECRET | jq -r .LIVEKIT_API_KEY)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/LIVEKIT_API_SECRET=.*/LIVEKIT_API_SECRET=$(echo $SHARED_SECRET | jq -r .LIVEKIT_API_SECRET)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
                sed -i "s/MEET_INITIAL_ADMIN_USER=.*/MEET_INITIAL_ADMIN_USER=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_ADMIN_USER)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
                sed -i "s/MEET_INITIAL_ADMIN_PASSWORD=.*/MEET_INITIAL_ADMIN_PASSWORD=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_ADMIN_PASSWORD)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
                if [[ "${InitialMeetApiKey}" != '' ]]; then
                  sed -i "s/MEET_INITIAL_API_KEY=.*/MEET_INITIAL_API_KEY=$(echo $SHARED_SECRET | jq -r .MEET_INITIAL_API_KEY)/" "${!CLUSTER_CONFIG_DIR}/master_node/meet.env"
                fi
                sed -i "s/ENABLED_MODULES=.*/ENABLED_MODULES=$(echo $SHARED_SECRET | jq -r .ENABLED_MODULES)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"

                # Update URLs in secret
                OPENVIDU_URL="https://${!DOMAIN}/"
                LIVEKIT_URL="wss://${!DOMAIN}/"
                DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
                GRAFANA_URL="https://${!DOMAIN}/grafana/"
                MINIO_URL="https://${!DOMAIN}/minio-console/"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$DOMAIN"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_URL": "'"$DASHBOARD_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_URL": "'"$GRAFANA_URL"'" }')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_URL": "'"$MINIO_URL"'" }')"
                aws secretsmanager update-secret \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --secret-string "$SHARED_SECRET"
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/update_secret_from_config.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Get current shared secret
                SHARED_SECRET=$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text)

                # Installation directory
                INSTALL_DIR="/opt/openvidu"
                CLUSTER_CONFIG_DIR="${!INSTALL_DIR}/config/cluster"
                MASTER_NODE_CONFIG_DIR="${!INSTALL_DIR}/config/node"


                # Update shared secret
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"REDIS_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh REDIS_PASSWORD "${!MASTER_NODE_CONFIG_DIR}/master_node.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DOMAIN_NAME": "'"$(/usr/local/bin/get_value_from_config.sh DOMAIN_NAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_RTC_ENGINE": "'"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_RTC_ENGINE "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_PRO_LICENSE": "'"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_PRO_LICENSE "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MONGO_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MONGO_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_ACCESS_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MINIO_ACCESS_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MINIO_SECRET_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MINIO_SECRET_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"DASHBOARD_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_ADMIN_USERNAME": "'"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_USERNAME "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"GRAFANA_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_API_KEY": "'"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_KEY "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_API_SECRET": "'"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_SECRET "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_ADMIN_USER": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_USER "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_ADMIN_PASSWORD": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_PASSWORD "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
                if [[ "${InitialMeetApiKey}" != '' ]]; then
                  SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"MEET_INITIAL_API_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_API_KEY "${!CLUSTER_CONFIG_DIR}/master_node/meet.env")"'"}')"
                fi
                SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"ENABLED_MODULES": "'"$(/usr/local/bin/get_value_from_config.sh ENABLED_MODULES "${!CLUSTER_CONFIG_DIR}/openvidu.env")"'"}')"

                # Update shared secret
                aws secretsmanager update-secret \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --secret-string "$SHARED_SECRET"

              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/get_value_from_config.sh':
              content: |
                #!/bin/bash
                set -e

                # Function to get the value of a given key from the environment file
                get_value() {
                    local key="$1"
                    local file_path="$2"

                    # Use grep to find the line with the key, ignoring lines starting with #
                    # Use awk to split on '=' and print the second field, which is the value
                    local value=$(grep -E "^\s*$key\s*=" "$file_path" | awk -F= '{print $2}' | sed 's/#.*//; s/^\s*//; s/\s*$//')

                    # If the value is empty, return "none"
                    if [ -z "$value" ]; then
                        echo "none"
                    else
                        echo "$value"
                    fi
                }

                # Check if the correct number of arguments are supplied
                if [ "$#" -ne 2 ]; then
                    echo "Usage: $0 <key> <file_path>"
                    exit 1
                fi

                # Get the key and file path from the arguments
                key="$1"
                file_path="$2"

                # Get and print the value
                get_value "$key" "$file_path"
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/store_secret.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Modes: save, generate
                # save mode: save the secret in the secret manager
                # generate mode: generate a random password and save it in the secret manager
                MODE="$1"
                SHARED_SECRET="$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id ${OpenViduSharedInfo} \
                  --query SecretString --output text)"
                if [[ "$MODE" == "generate" ]]; then
                  SECRET_KEY_NAME="$2"
                  PREFIX="${!3:-}"
                  LENGTH="${!4:-44}"
                  RANDOM_PASSWORD="$(openssl rand -base64 64 | tr -d '+/=\n' | cut -c -${!LENGTH})"
                  RANDOM_PASSWORD="${!PREFIX}${!RANDOM_PASSWORD}"
                  SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"'"$SECRET_KEY_NAME"'": "'"$RANDOM_PASSWORD"'"}')"
                  aws secretsmanager update-secret \
                    --region ${AWS::Region} \
                    --secret-id ${OpenViduSharedInfo} \
                    --secret-string "$SHARED_SECRET" > /dev/null 2>&1
                  echo "$RANDOM_PASSWORD"
                elif [[ "$MODE" == "save" ]]; then
                  SECRET_KEY_NAME="$2"
                  SECRET_VALUE="$3"
                  SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"'"$SECRET_KEY_NAME"'": "'"$SECRET_VALUE"'"}')"
                  aws secretsmanager update-secret \
                    --region ${AWS::Region} \
                    --secret-id ${OpenViduSharedInfo} \
                    --secret-string "$SHARED_SECRET" > /dev/null 2>&1
                  echo "$SECRET_VALUE"
                else
                  exit 1
                fi
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/restart.sh':
              content: |
                #!/bin/bash
                set -e
                # Stop all services
                systemctl stop openvidu

                # Update config from secret
                /usr/local/bin/update_config_from_secret.sh

                # Start all services
                systemctl start openvidu
              mode: "000755"
              owner: "root"
              group: "root"
    Properties:
      LaunchTemplateName: !Sub 'openvidu-ha-master-${AWS::Region}-${AWS::StackName}'
      LaunchTemplateData:
        # Enable IMDSv2
        MetadataOptions:
          HttpEndpoint: enabled
          HttpPutResponseHopLimit: 1
          HttpTokens: required
        IamInstanceProfile:
          Name: !Ref OpenViduMasterInstanceProfile
        ImageId: !If
          - IsMasterGraviton
          - !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
          - !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
        InstanceType: !Ref MasterNodeInstanceType
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref OpenViduMasterNodeSG
        BlockDeviceMappings:
          - DeviceName: /dev/sda1
            Ebs:
              VolumeSize: !Ref MasterNodesDiskSize
              VolumeType: gp3
              DeleteOnTermination: true

  OpenViduMasterNode1:
    Type: AWS::EC2::Instance
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
        Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 1
      SubnetId: !GetAtt SubnetProcessor.Subnet1
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -eu -o pipefail

          echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

          apt-get update && apt-get install -y \
                python3-pip \
                ec2-instance-connect

          # Detect Ubuntu version and install cfn-bootstrap accordingly
          UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
          if [ "$UBUNTU_VERSION" -ge 24 ]; then
            python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          else
            python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          fi

          cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate

          # Install OpenVidu
          /usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }

          # Config S3 bucket
          /usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }

          # Start OpenVidu
          systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }

          # Update shared secret
          /usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }

          # Launch on reboot
          echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab


  MasterNodesWaitCondition1:
    Type: 'AWS::CloudFormation::WaitCondition'
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M
        Count: '1'

  OpenViduMasterNode2:
    Type: AWS::EC2::Instance
    DependsOn: MasterNodesWaitCondition1
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
        Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 2
      SubnetId: !GetAtt SubnetProcessor.Subnet2
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -eu -o pipefail

          echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

          apt-get update && apt-get install -y \
                python3-pip \
                ec2-instance-connect

          # Detect Ubuntu version and install cfn-bootstrap accordingly
          UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
          if [ "$UBUNTU_VERSION" -ge 24 ]; then
            python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          else
            python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          fi

          cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate

          # Install OpenVidu
          /usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }

          # Config S3 bucket
          /usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }

          # Start OpenVidu
          systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }

          # Update shared secret
          /usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }

          # Launch on reboot
          echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab


  MasterNodesWaitCondition2:
    Type: 'AWS::CloudFormation::WaitCondition'
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M
        Count: '1'

  OpenViduMasterNode3:
    Type: AWS::EC2::Instance
    DependsOn: MasterNodesWaitCondition2
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
        Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 3
      SubnetId: !GetAtt SubnetProcessor.Subnet3
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -eu -o pipefail

          echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

          apt-get update && apt-get install -y \
                python3-pip \
                ec2-instance-connect

          # Detect Ubuntu version and install cfn-bootstrap accordingly
          UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
          if [ "$UBUNTU_VERSION" -ge 24 ]; then
            python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          else
            python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          fi

          cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate

          # Install OpenVidu
          /usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }

          # Config S3 bucket
          /usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }

          # Start OpenVidu
          systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }

          # Update shared secret
          /usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }

          # Launch on reboot
          echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab


  MasterNodesWaitCondition3:
    Type: 'AWS::CloudFormation::WaitCondition'
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M
        Count: '1'

  OpenViduMasterNode4:
    Type: AWS::EC2::Instance
    DependsOn: MasterNodesWaitCondition3
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
        Version: !GetAtt OpenViduMasterLaunchTemplate.LatestVersionNumber
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Master Node 4
      SubnetId: !GetAtt SubnetProcessor.Subnet4
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -eu -o pipefail

          echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

          apt-get update && apt-get install -y \
                python3-pip \
                ec2-instance-connect

          # Detect Ubuntu version and install cfn-bootstrap accordingly
          UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
          if [ "$UBUNTU_VERSION" -ge 24 ]; then
            python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          else
            python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
          fi

          cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate

          # Install OpenVidu
          /usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }

          # Config S3 bucket
          /usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }

          # Start OpenVidu
          systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }

          # Update shared secret
          /usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }

          # Launch on reboot
          echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab

  MasterNodesWaitCondition4:
    Type: 'AWS::CloudFormation::WaitCondition'
    CreationPolicy:
      ResourceSignal:
        Timeout: PT20M
        Count: '1'

  OpenViduMediaNodeLaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Metadata:
      Comment: Launch template for OpenVidu Media Node
      AWS::CloudFormation::Init:
        config:
          files:
            '/usr/local/bin/install.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                YQ_VERSION=v4.44.5

                # Install dependencies
                apt-get update && apt-get install -y \
                  curl \
                  unzip \
                  jq \
                  wget
                wget https://github.com/mikefarah/yq/releases/download/${!YQ_VERSION}/yq_linux_$(dpkg --print-architecture).tar.gz -O - |\
                  tar xz && mv yq_linux_$(dpkg --print-architecture) /usr/bin/yq

                # Install aws-cli
                curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m).zip" -o "awscliv2.zip"
                unzip -qq awscliv2.zip
                ./aws/install
                rm -rf awscliv2.zip aws

                # Token for IMDSv2
                TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"

                # Get own private IP
                PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"

                SHARED_SECRET="$(aws secretsmanager get-secret-value \
                  --region ${AWS::Region} \
                  --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
                  --query SecretString --output text || echo 'none')"
                if [[ "$SHARED_SECRET" == "none" ]]; then
                  echo "Error: Shared secret not found"
                  exit 1
                fi

                # Get OpenVidu Media Nodes version to deploy
                OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
                if [[ "$OPENVIDU_VERSION" == "none" ]]; then
                  echo "OpenVidu version not found"
                  exit 1
                fi

                ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
                if [[ "$ALL_SECRETS_GENERATED" == "none" ]]; then
                  echo "Error: Secrets not generated"
                  exit 1
                fi
                MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
                MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
                MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
                MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
                MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
                REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')

                # Base command
                INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)"

                # Common arguments
                COMMON_ARGS=(
                  "--no-tty"
                  "--install"
                  "--environment=aws"
                  "--deployment-type='ha'"
                  "--node-role='media-node'"
                  "--master-node-private-ip-list=$MASTER_NODE_PRIVATE_IP_LIST"
                  "--private-ip=$PRIVATE_IP"
                  "--redis-password=$REDIS_PASSWORD"
                )

                # Construct the final command with all arguments
                FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}")"

                # Install OpenVidu
                exec bash -c "$FINAL_COMMAND"
              mode: '000755'
              owner: root
              group: root
            '/usr/local/bin/set_as_unhealthy.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Token for IMDSv2
                TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

                # Get own instance ID
                INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)

                # Set instance as unhealthy
                aws autoscaling set-instance-health \
                  --region ${AWS::Region} \
                  --instance-id "$INSTANCE_ID" \
                  --health-status Unhealthy
              mode: "000755"
              owner: "root"
              group: "root"
            '/usr/local/bin/stop_media_node.sh':
              content: !Sub |
                #!/bin/bash
                set -e
                # Token for IMDSv2
                TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

                # Get own instance ID
                INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id)
                ASG_NAME=openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}

                # Execute if docker is installed
                if [ -x "$(command -v docker)" ]; then
                  echo "Stopping media node services and waiting for termination..."
                  docker container kill --signal=SIGQUIT openvidu || true
                  docker container kill --signal=SIGQUIT ingress || true
                  docker container kill --signal=SIGQUIT egress || true
                  for agent_container in $(docker ps --filter "label=openvidu-agent=true" --format '{{.Names}}'); do
                    docker container kill --signal=SIGQUIT "$agent_container"
                  done
                  TIME_PASSED=0
                  HEARTBEAT_MAX=1800
                  # Wait for running containers to not be openvidu, ingress, egress or an openvidu agent
                  while [ $(docker ps --filter "label=openvidu-agent=true" -q | wc -l) -gt 0 ] || \
                        [ $(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null) == "true" ] || \
                        [ $(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null) == "true" ] || \
                        [ $(docker inspect -f '{{.State.Running}}' egress 2>/dev/null) == "true" ]; do
                    echo "Waiting for containers to stop..."
                    sleep 5
                    TIME_PASSED=$((TIME_PASSED+5))
                    if [ $TIME_PASSED -ge $HEARTBEAT_MAX ]; then
                      echo "Increase lifecycle hook timeout to continue waiting for termination"
                      # Increase lifecycle hook timeout
                      aws autoscaling record-lifecycle-action-heartbeat \
                        --region ${AWS::Region} \
                        --lifecycle-hook-name StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName} \
                        --auto-scaling-group-name "$ASG_NAME" \
                        --instance-id "$INSTANCE_ID"
                      TIME_PASSED=0
                    fi
                  done
                fi

                aws autoscaling complete-lifecycle-action \
                  --region ${AWS::Region} \
                  --lifecycle-hook-name StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName} \
                  --auto-scaling-group-name "$ASG_NAME" \
                  --lifecycle-action-result CONTINUE \
                  --instance-id "$INSTANCE_ID"
              mode: "000755"
              owner: "root"
              group: "root"
    Properties:
      LaunchTemplateName: !Sub 'openvidu-ha-media-${AWS::Region}-${AWS::StackName}'
      LaunchTemplateData:
        # Enable IMDSv2 by default
        MetadataOptions:
          HttpEndpoint: enabled
          HttpPutResponseHopLimit: 1
          HttpTokens: required
        IamInstanceProfile:
          Arn: !GetAtt OpenViduMediaInstanceProfile.Arn
        SecurityGroupIds:
          - !GetAtt OpenViduMediaNodeSG.GroupId
        ImageId: !If
          - IsMediaGraviton
          - !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
          - !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
        KeyName: !Ref KeyName
        InstanceType: !Ref MediaNodeInstanceType
        UserData:
          Fn::Base64: !Sub |
            #!/bin/bash
            set -eu -o pipefail

            echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

            apt-get update && apt-get install -y \
                python3-pip \
                ec2-instance-connect

            # Detect Ubuntu version and install cfn-bootstrap accordingly
            UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
            if [ "$UBUNTU_VERSION" -ge 24 ]; then
              python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
            else
              python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-latest.tar.gz
            fi

            cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduMediaNodeLaunchTemplate

            export HOME="/root"

            # Install OpenVidu
            /usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }

            # Start OpenVidu
            systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }

            # Wait for the app
            # /usr/local/bin/check_app_ready.sh
        BlockDeviceMappings:
          - DeviceName: /dev/sda1
            Ebs:
              VolumeType: gp3
              DeleteOnTermination: true
              VolumeSize: 50

  OpenViduMediaNodeASG:
    DependsOn:
      - OpenViduMediaInstanceProfile
      - OpenViduMasterInstanceProfile
      - StopMediaNodeCloudWatchEventRule
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AutoScalingGroupName: !Sub openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
      LaunchTemplate:
        LaunchTemplateId: !Ref OpenViduMediaNodeLaunchTemplate
        Version: !GetAtt OpenViduMediaNodeLaunchTemplate.DefaultVersionNumber
      TargetGroupARNs:
        - !Ref OpenViduMediaNodeRTMPTG
      MinSize: !Ref MinNumberOfMediaNodes
      MaxSize: !Ref MaxNumberOfMediaNodes
      DesiredCapacity: !Ref InitialNumberOfMediaNodes
      VPCZoneIdentifier: !Ref OpenViduMediaNodeSubnets
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Media Node
          PropagateAtLaunch: true

  StopMediaNodeLifecycleHook:
    Type: 'AWS::AutoScaling::LifecycleHook'
    Properties:
      LifecycleHookName: !Sub StopMediaNodeLifecycleHook-${AWS::Region}-${AWS::StackName}
      AutoScalingGroupName: !Ref OpenViduMediaNodeASG
      LifecycleTransition: 'autoscaling:EC2_INSTANCE_TERMINATING'
      DefaultResult: 'CONTINUE'
      HeartbeatTimeout: 3600

  StopMediaNodeDocumentRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ssm.amazonaws.com
          Action:
          - sts:AssumeRole
      Policies:
        - PolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Action:
                - ssm:DescribeInstanceInformation
                - ssm:ListCommands
                - ssm:ListCommandInvocations
              Resource: "*"
            - Effect: Allow
              Action:
                - ssm:SendCommand
              Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}::document/AWS-RunShellScript
            - Action:
                - ssm:SendCommand
              Resource: !Sub arn:${AWS::Partition}:ec2:*:*:instance/*
              Condition:
                StringEquals:
                  'aws:ResourceTag/aws:cloudformation:stack-name': !Ref 'AWS::StackName'
              Effect: Allow
          PolicyName: SSM-Automation-Policy

  StopMediaNodeAutomationDocument:
    Type: AWS::SSM::Document
    Properties:
      Name:
        Fn::Join:
          # Generate a not too long and unique document name
          # Getting a unique identifier from the stack id
          - ''
          - - 'StopMediaNodeAutomationDocument-'
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      DocumentType: Automation
      Content:
        schemaVersion: '0.3'
        assumeRole: "{{automationAssumeRole}}"
        description: This document stops the OpenVidu services in a Media Node and
          terminates the instance. It stop the OpenVidu services without interrupting
          the running sessions, ingress and egress running in the Media Node.
          Also it sends a CONTINUE signal to the Auto Scaling Group to continue the
          instance termination when the services are stopped.
        parameters:
          InstanceId:
            type: String
          automationAssumeRole:
            type: String
            default: !GetAtt StopMediaNodeDocumentRole.Arn
            description: "(Required) The ARN of the role that allows Automation to
              perform the actions."
        mainSteps:
          - name: RunCommand
            action: aws:runCommand
            inputs:
              DocumentName: AWS-RunShellScript
              InstanceIds:
                - "{{ InstanceId }}"
              Parameters:
                # 24 hours as a timeout to wait for the instance to stop all services
                executionTimeout: "60"
                commands:
                  - nohup /usr/local/bin/stop_media_node.sh > /var/log/stop_media_node.log 2>&1 &


  StopMediaNodeCloudWatchEventRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - events.amazonaws.com
          Action:
            - sts:AssumeRole
      Policies:
      - PolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - ssm:StartAutomationExecution
              Resource:
                - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-execution/*
                - !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:document/${StopMediaNodeAutomationDocument}
        PolicyName: !Sub StopMediaNodeCloudWatchEventPolicy-${AWS::Region}-${AWS::StackName}
      - PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - iam:PassRole
            Resource: !GetAtt StopMediaNodeDocumentRole.Arn
        PolicyName: Pass-Role-SSM-Automation-Policy

  StopMediaNodeCloudWatchEventRule:
    Type: AWS::Events::Rule
    Properties:
      Description: Rule to trigger the StopMediaNodeAutomationDocument when an instance is terminated
      EventPattern:
        source:
          - aws.autoscaling
        detail-type:
          - EC2 Instance-terminate Lifecycle Action
        detail:
          AutoScalingGroupName:
            - !Sub openvidu-ha-media-asg-${AWS::Region}-${AWS::StackName}
      Name:
        Fn::Join:
          # Generate a not too long and unique rule name
          # Getting a unique identifier from the stack id
          - ''
          - - StopMediaNodeCloudWatchEventRule-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      Targets:
        - Arn: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/${StopMediaNodeAutomationDocument}:$DEFAULT
          RoleArn: !GetAtt StopMediaNodeCloudWatchEventRole.Arn
          Id:
            Fn::Join:
              # Generate a not too long and unique target id
              # Getting a unique identifier from the stack id
              - ''
              - - StopMediaNodeCloudWatchEventRule-
                - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
          InputTransformer:
            InputPathsMap:
              instanceid: "$.detail.EC2InstanceId"
            InputTemplate: |
              {
                "InstanceId": [ <instanceid> ]
              }

  OpenViduMediaNodeASGScalingPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref OpenViduMediaNodeASG
      PolicyType: TargetTrackingScaling
      EstimatedInstanceWarmup: 120
      TargetTrackingConfiguration:
        PredefinedMetricSpecification:
          PredefinedMetricType: ASGAverageCPUUtilization
        TargetValue: !Ref ScaleTargetCPU

  OpenViduMasterNodeSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for OpenVidu Master Node
      GroupName: !Sub openvidu-ha-master-sg-${AWS::Region}-${AWS::StackName}
      VpcId: !Ref OpenViduVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIpv6: ::/0

  OpenViduLoadBalancerToMasterHTTPIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 80
      ToPort: 80
      SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG

  OpenViduLoadBalancerToMasterIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7880
      ToPort: 7880
      SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG

  OpenViduMasterToMasterRedisIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7000
      ToPort: 7001
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterNodeClusterPortIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7880
      ToPort: 7880
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMediaNodeToMasterRedisIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7000
      ToPort: 7001
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMasterToMasterMinioIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9100
      ToPort: 9100
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMasterMinioConsoleSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9101
      ToPort: 9101
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterMinioIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9100
      ToPort: 9100
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMasterToMasterMongoIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 20000
      ToPort: 20000
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterMongoIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 20000
      ToPort: 20000
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMasterToMasterMimirGrpcIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9095
      ToPort: 9095
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMasterMimirGossipIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7946
      ToPort: 7946
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterHTTPMimirSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9009
      ToPort: 9009
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMasterToMasterLokiGrpcIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 9096
      ToPort: 9096
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMasterLokiGossipIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 7947
      ToPort: 7947
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMasterDashboardsIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 5000
      ToPort: 5000
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMasterGrafanaIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 3000
      ToPort: 3000
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterHTTPLokiSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 3100
      ToPort: 3100
      SourceSecurityGroupId: !Ref OpenViduMediaNodeSG

  OpenViduMasterToMasterV2CompatibilityIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 4443
      ToPort: 4443
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterV2CompatibilityWebhookIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
      IpProtocol: tcp
      FromPort: 4443
      ToPort: 4443
      SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId

  OpenViduMasterToMasterMeetIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 6080
      ToPort: 6080
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMediaNodeToMasterMeetWebhookIngress:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !GetAtt OpenViduMasterNodeSG.GroupId
      IpProtocol: tcp
      FromPort: 6080
      ToPort: 6080
      SourceSecurityGroupId: !GetAtt OpenViduMediaNodeSG.GroupId

  OpenViduMediaNodeSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for OpenVidu Media Node
      GroupName: !Sub openvidu-ha-media-sg-${AWS::Region}-${AWS::StackName}
      VpcId: !Ref OpenViduVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIpv6: ::/0
        - IpProtocol: udp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        - IpProtocol: udp
          FromPort: 443
          ToPort: 443
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 7881
          ToPort: 7881
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 7881
          ToPort: 7881
          CidrIpv6: ::/0
        - IpProtocol: udp
          FromPort: 7885
          ToPort: 7885
          CidrIp: 0.0.0.0/0
        - IpProtocol: udp
          FromPort: 7885
          ToPort: 7885
          CidrIpv6: ::/0
        - IpProtocol: udp
          FromPort: 50000
          ToPort: 60000
          CidrIp: 0.0.0.0/0
        - IpProtocol: udp
          FromPort: 50000
          ToPort: 60000
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 50000
          ToPort: 60000
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 50000
          ToPort: 60000
          CidrIpv6: ::/0

  OpenViduLoadBalancerToMediaNodeRTMPIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMediaNodeSG
      IpProtocol: tcp
      FromPort: 1945
      ToPort: 1945
      SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG

  OpenViduLoadBalancerToMediaNodeIngressHealthCheckSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMediaNodeSG
      IpProtocol: tcp
      FromPort: 9092
      ToPort: 9092
      SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG

  OpenViduMasterToMediaNodeServerIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMediaNodeSG
      IpProtocol: tcp
      FromPort: 7880
      ToPort: 7880
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduMasterToMediaNodeClientIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMediaNodeSG
      IpProtocol: tcp
      FromPort: 8080
      ToPort: 8080
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduTurnTLSMasterNodeToMediaNodeIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMediaNodeSG
      IpProtocol: tcp
      FromPort: 5349
      ToPort: 5349
      SourceSecurityGroupId: !Ref OpenViduMasterNodeSG

  OpenViduTurnTLSLoadBalancerToMediaNodeIngressSG:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref OpenViduMasterNodeSG
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      SourceSecurityGroupId: !Ref OpenViduLoadBalancerSG

  OpenViduLoadBalancerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for the Load Balancer
      GroupName: !Sub openvidu-ha-lb-sg-${AWS::Region}-${AWS::StackName}
      VpcId: !Ref OpenViduVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIpv6: ::/0
        - IpProtocol: tcp
          FromPort: 1935
          ToPort: 1935
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 1935
          ToPort: 1935
          CidrIpv6: ::/0

  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    DependsOn:
      - MasterNodesWaitCondition4
    Properties:
      Name:
        Fn::Join:
          # Generate a not too long and unique load balancer name
          # Getting a unique identifier from the stack id
          - ''
          - - OpenViduHA-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      Subnets: !Ref OpenViduMasterNodeSubnets
      SecurityGroups:
        - !Ref OpenViduLoadBalancerSG
      Type: network
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Load Balancer

  OpenViduMasterNodeWithTurnTLSHTTPListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref OpenViduMasterNodeWithTurnTLSTG
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: TCP

  OpenViduMasterNodeWithTurnTLSListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref OpenViduMasterNodeWithTurnTLSTG
      LoadBalancerArn: !Ref LoadBalancer
      Port: 443
      Protocol: TLS
      Certificates:
        - CertificateArn: !Ref OpenViduCertificateARN

  OpenViduRTMPMediaNodeListener:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref OpenViduMediaNodeRTMPTG
      LoadBalancerArn: !Ref LoadBalancer
      Port: 1935
      Protocol: TLS
      Certificates:
        - CertificateArn: !Ref OpenViduCertificateARN

  OpenViduMasterNodeHTTPTG:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name:
        Fn::Join:
          # Generate a not too long and unique target id
          # Getting a unique identifier from the stack id
          - ''
          - - OVHTTP-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      TargetType: instance
      Targets:
        - Id: !Ref OpenViduMasterNode1
        - Id: !Ref OpenViduMasterNode2
        - Id: !Ref OpenViduMasterNode3
        - Id: !Ref OpenViduMasterNode4
      VpcId: !Ref OpenViduVPC
      Port: 80
      Protocol: TCP
      Matcher:
        HttpCode: '200'
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /health/caddy
      HealthCheckProtocol: HTTP
      HealthCheckPort: '7880'
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 4
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - Master HTTP Target Group

  OpenViduMasterNodeWithTurnTLSTG:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name:
        Fn::Join:
          # Generate a not too long and unique target id
          # Getting a unique identifier from the stack id
          - ''
          - - OVTurnTLSMaster-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      TargetType: instance
      Targets:
        - Id: !Ref OpenViduMasterNode1
        - Id: !Ref OpenViduMasterNode2
        - Id: !Ref OpenViduMasterNode3
        - Id: !Ref OpenViduMasterNode4
      VpcId: !Ref OpenViduVPC
      Port: 443
      Protocol: TCP
      Matcher:
        HttpCode: '200'
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /health/caddy
      HealthCheckProtocol: HTTP
      HealthCheckPort: '7880'
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 4
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - TURN TLS Master Target Group

  OpenViduMediaNodeRTMPTG:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name:
        Fn::Join:
          # Generate a not too long and unique target id
          # Getting a unique identifier from the stack id
          - ''
          - - OVRTMP-
            - !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref AWS::StackId]]]]
      VpcId: !Ref OpenViduVPC
      Port: 1945
      Protocol: TCP
      Matcher:
        HttpCode: '200'
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /
      HealthCheckProtocol: HTTP
      # Ingress health check port
      HealthCheckPort: '9092'
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 4
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 60
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName} - OpenVidu HA - RTMP Target Group

Outputs:
  ServicesAndCredentials:
    Description: Services and credentials
    Value: !Sub https://${AWS::Region}.console.aws.amazon.com/secretsmanager/home?region=${AWS::Region}#!/secret?name=openvidu-ha-${AWS::Region}-${AWS::StackName}
