AWSTemplateFormatVersion: 2010-09-09 Description: OpenVidu Platform Parameters: # Domain and SSL certificate configuration WhichCert: Description: > [selfsigned] Self signed certificate. Not recommended for production use. [owncert] Valid certificate purchased in a Internet services company. [letsencrypt] Generate a new certificate using Let's Encrypt. Type: String AllowedValues: - selfsigned - owncert - letsencrypt Default: selfsigned PublicElasticIP: Description: "Previously created AWS Elastic IP to associate it to the OpenVidu EC2 instance. If certificate type is 'selfsigned' this value is optional. If certificate type is 'owncert' or 'letsencrypt' this value is mandatory. Example 13.33.145.23." Type: String AllowedPattern: ^$|^([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])\.([01]?\d{1,2}|2[0-4]\d|25[0-5])$ ConstraintDescription: The public Elastic IP does not have a valid IPv4 format MyDomainName: Description: "Valid domain name pointing to previous IP. If certificate type is 'selfsigned' this value is optional. If certificate type is 'owncert' or 'letsencrypt' this value is mandatory. Example: openvidu.company.com" Type: String 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 OwnCertCRT: Description: "If certificate type is 'owncert' this is the URL where CRT file will be downloaded" Type: String OwnCertKEY: Description: "If certificate type is 'owncert' this is the URL where KEY file will be downloaded" Type: String LetsEncryptEmail: Description: "If certificate type is 'letsencrypt', this email will be used for Let's Encrypt notifications" Type: String # OpenVidu configuration OpenViduSecret: Description: "Secret to connect to this OpenVidu Platform. Cannot be empty and must contain only alphanumeric characters [a-zA-Z0-9], hypens ('-') and underscores ('_')" Type: String AllowedPattern: ^[a-zA-Z0-9_-]+$ NoEcho: true ConstraintDescription: "Cannot be empty and must contain only alphanumeric characters [a-zA-Z0-9], hypens ('-') and underscores ('_')" # EC2 Instance configuration InstanceType: Description: "Specifies the EC2 instance type for your OpenVidu instance" Type: String Default: c5.xlarge AllowedValues: - t2.large - t2.xlarge - t2.2xlarge - t3.large - t3.xlarge - t3.2xlarge - m4.large - m4.xlarge - m4.2xlarge - m4.4xlarge - m4.10xlarge - m4.16xlarge - m5.large - m5.xlarge - m5.2xlarge - m5.4xlarge - m5.8xlarge - m5.12xlarge - m5.16xlarge - m5.24xlarge - c4.large - c4.xlarge - c4.2xlarge - c4.4xlarge - c4.8xlarge - c5.large - c5.xlarge - c5.2xlarge - c5.4xlarge - c5.9xlarge - c5.12xlarge - c5.18xlarge - c5.24xlarge - c6a.large - c6a.xlarge - c6a.2xlarge - c6a.4xlarge - c6a.8xlarge - c6a.12xlarge - c6a.16xlarge - c6a.24xlarge - c6a.32xlarge - c6a.48xlarge - c6a.metal ConstraintDescription: "Must be a valid EC2 instance type" KeyName: Description: "Name of an existing EC2 KeyPair to enable SSH access to the instance. It is mandatory to perform some administrative tasks of OpenVidu." Type: 'AWS::EC2::KeyPair::KeyName' ConstraintDescription: "must be the name of an existing EC2 KeyPair" # Other configuration WantToDeployDemos: Description: "Choose if you want to deploy OpenVidu Call application alongside OpenVidu platform." Type: String AllowedValues: - true - false Default: true WantToSendInfo: Description: "Choose if you want to send to OpenVidu team the version deployed and AWS region." Type: String AllowedValues: - true - false Default: true #start_mappings Mappings: OVAMIMAP: eu-west-1: AMI: OV_AMI_ID #end_mappings Metadata: 'AWS::CloudFormation::Interface': ParameterGroups: - Label: default: Domain and SSL certificate configuration Parameters: - WhichCert - PublicElasticIP - MyDomainName - OwnCertCRT - OwnCertKEY - LetsEncryptEmail - Label: default: OpenVidu configuration Parameters: - OpenViduSecret - Label: default: EC2 Instance configuration Parameters: - InstanceType - KeyName - Label: default: Other configuration Parameters: - WantToDeployDemos - WantToSendInfo ParameterLabels: # SSL certificate configuration WhichCert: default: "Certificate Type" PublicElasticIP: default: "AWS Elastic IP (EIP)" MyDomainName: default: "Domain Name pointing to Elastic IP" OwnCertCRT: default: "URL to the CRT file (owncert)" OwnCertKEY: default: "URL to the key file (owncert)" LetsEncryptEmail: default: "Email for Let's Encrypt (letsencrypt)" # OpenVidu configuration OpenViduSecret: default: "Openvidu Secret" # EC2 Instance configuration InstanceType: default: "Instance type" KeyName: default: "SSH Key" # Other configuration WantToDeployDemos: default: "Deploy OpenVidu Call application" WantToSendInfo: default: "Send deployment info to OpenVidu team" Conditions: WhichCertPresent: !Not [ !Equals [!Ref WhichCert, ""] ] PublicElasticIPPresent: !Not [ !Equals [!Ref PublicElasticIP, ""] ] Resources: LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateName: IMDSV2 LaunchTemplateData: MetadataOptions: HttpEndpoint: enabled HttpPutResponseHopLimit: 1 HttpTokens: required OpenviduServer: Type: 'AWS::EC2::Instance' Metadata: Comment: 'Install and configure OpenVidu Server and Demos' AWS::CloudFormation::Init: config: files: '/usr/local/bin/ping.sh': content: | #!/bin/bash INXDB_URL=193.147.51.51 INXDB_DB=ov_server INXDB_MEASUREMENT=server OV_VERSION=OPENVIDU_VERSION TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") EC2_AVAIL_ZONE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/availability-zone) EC2_REGION=$(echo "$EC2_AVAIL_ZONE" | sed 's/[a-z]$//') curl -i -XPOST "http://$INXDB_URL:8086/write?db=$INXDB_DB" \ --data-binary "$INXDB_MEASUREMENT,region=$EC2_REGION ov_version=\"$OV_VERSION\" " mode: "000755" owner: "root" group: "root" '/usr/local/bin/check_app_ready.sh': content: | #!/bin/bash while true; do HTTP_STATUS=$(curl -Ik http://localhost:5443 | head -n1 | awk '{print $2}') if [ $HTTP_STATUS == 200 ]; then break fi sleep 5 done mode: "000755" owner: "root" group: "root" '/usr/local/bin/feedGroupVars.sh': content: !Sub | #!/bin/bash -x WORKINGDIR=/opt/openvidu # Replace secret sed -i "s/OPENVIDU_SECRET=/OPENVIDU_SECRET=${OpenViduSecret}/" $WORKINGDIR/.env # Replace domain name if [[ "${MyDomainName}" != '' && "${PublicElasticIP}" != '' ]]; then sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=${MyDomainName}/" $WORKINGDIR/.env elif [[ "${MyDomainName}" == '' && "${PublicElasticIP}" != '' ]]; then sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=${PublicElasticIP}/" $WORKINGDIR/.env else [ ! -d "/usr/share/openvidu" ] && mkdir -p /usr/share/openvidu TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") PublicHostname=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-hostname) sed -i "s/DOMAIN_OR_PUBLIC_IP=/DOMAIN_OR_PUBLIC_IP=$PublicHostname/" $WORKINGDIR/.env echo $PublicHostname > /usr/share/openvidu/old-host-name fi # Replace certificated type sed -i "s/CERTIFICATE_TYPE=selfsigned/CERTIFICATE_TYPE=${WhichCert}/" $WORKINGDIR/.env sed -i "s/LETSENCRYPT_EMAIL=user@example.com/LETSENCRYPT_EMAIL=${LetsEncryptEmail}/" $WORKINGDIR/.env # Without Application if [ "${WantToDeployDemos}" == "false" ]; then sed -i "s/WITH_APP=true/WITH_APP=false/" $WORKINGDIR/docker-compose.yml rm $WORKINGDIR/docker-compose.override.yml fi mode: "000755" owner: "root" group: "root" '/usr/local/bin/buildCerts.sh': content: !Sub | #!/bin/bash -x WORKINGDIR=/opt/openvidu wget --no-check-certificate -O $WORKINGDIR/owncert/certificate.cert ${OwnCertCRT} wget --no-check-certificate -O $WORKINGDIR/owncert/certificate.key ${OwnCertKEY} mode: "000755" owner: "root" group: "root" '/usr/local/bin/restartCE.sh': content: !Sub | #!/bin/bash -x WORKINGDIR=/opt/openvidu # Get new amazon URL OldPublicHostname=$(cat /usr/share/openvidu/old-host-name) TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") PublicHostname=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-hostname) sed -i "s/$OldPublicHostname/$PublicHostname/" $WORKINGDIR/.env echo $PublicHostname > /usr/share/openvidu/old-host-name # Restart all services pushd "$WORKINGDIR" ./openvidu restart >/dev/null 2>&1 & popd mode: "000755" owner: "root" group: "root" Properties: ImageId: !GetAtt CloudformationLambdaInvoke.ImageId LaunchTemplate: LaunchTemplateName: IMDSV2 Version: 1 InstanceType: !Ref InstanceType SecurityGroups: - !Ref WebServerSecurityGroup KeyName: !Ref KeyName Tags: - Key: Name Value: !Ref 'AWS::StackName' UserData: Fn::Base64: !Sub | #!/bin/bash -x set -eu -o pipefail cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenviduServer export HOME="/root" # Replace .env variables /usr/local/bin/feedGroupVars.sh || { echo "[OpenVidu] Parameters incorrect/insufficient"; exit 1; } # Launch on reboot echo "@reboot /usr/local/bin/restartCE.sh" | crontab # Download certs if "WichCert" mode if [ "${WhichCert}" == "owncert" ]; then /usr/local/bin/buildCerts.sh || { echo "[OpenVidu] error with the certificate files"; exit 1; } fi # Start openvidu application pushd /opt/openvidu ./openvidu start >/dev/null 2>&1 & popd # Send info to openvidu if [ "${WantToSendInfo}" == "true" ]; then /usr/local/bin/ping.sh || true fi rm /usr/local/bin/ping.sh # Wait for the app /usr/local/bin/check_app_ready.sh # sending the finish call /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} BlockDeviceMappings: - DeviceName: /dev/sda1 Ebs: VolumeType: gp3 DeleteOnTermination: true VolumeSize: 200 MyEIP: Type: 'AWS::EC2::EIPAssociation' Condition: PublicElasticIPPresent Properties: InstanceId: !Ref OpenviduServer EIP: !Ref PublicElasticIP WaitCondition: Type: 'AWS::CloudFormation::WaitCondition' CreationPolicy: ResourceSignal: Timeout: PT30M Count: '1' WebServerSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: SSH, Proxy and OpenVidu WebRTC Ports SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIpv6: ::/0 - 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: 3478 ToPort: 3478 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 3478 ToPort: 3478 CidrIpv6: ::/0 - IpProtocol: udp FromPort: 3478 ToPort: 3478 CidrIp: 0.0.0.0/0 - IpProtocol: udp FromPort: 3478 ToPort: 3478 CidrIpv6: ::/0 - IpProtocol: udp FromPort: 40000 ToPort: 57000 CidrIp: 0.0.0.0/0 - IpProtocol: udp FromPort: 40000 ToPort: 57000 CidrIpv6: ::/0 - IpProtocol: tcp FromPort: 40000 ToPort: 57000 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 40000 ToPort: 57000 CidrIpv6: ::/0 ########## # Lambda to Copy original AMI to the deployment region ########## CloudformationLambdaRole: Type: 'AWS::IAM::Role' DeletionPolicy: Delete Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / Policies: - PolicyName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda-policy'] ] PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'ec2:DescribeImages' - 'ec2:CopyImage' Resource: '*' RoleName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda-role'] ] CloudformationLambda: Type: AWS::Lambda::Function DeletionPolicy: Delete Properties: FunctionName: !Join ['', [ !Ref AWS::StackName, '-cf-lambda'] ] Code: ZipFile: | import boto3 import cfnresponse from botocore.config import Config def handler(event, context): try: if (event['RequestType'] == 'Create'): copy_ami(event, context) return else: cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) except Exception: cfnresponse.send(event, context, cfnresponse.FAILED, {}) def copy_ami(event, context): new_images=[] cfn_output = {} source_image_id = event['ResourceProperties']['AmiSourceId'] source_region = event['ResourceProperties']['AmiSourceRegion'] deployment_region = event['ResourceProperties']['DeploymentRegion'] # Clients init ec2_client = boto3.client('ec2', config = Config(region_name=deployment_region)) ec2_client_ov = boto3.client('ec2', config = Config(region_name=source_region)) img_exists_waiter= ec2_client.get_waiter('image_exists') img_avail_waiter = ec2_client.get_waiter('image_available') # Get original ami name public_ami_filter = [{ 'Name': 'image-id', 'Values': [ source_image_id ] }] response = ec2_client_ov.describe_images(Filters=public_ami_filter) new_ami_name= "[ OpenVidu CE AMI Copy ] - " + response['Images'][0]['Name'] own_ami_filter = [{ 'Name': 'name', 'Values': [new_ami_name] }] response = ec2_client.describe_images(Filters=own_ami_filter) if (len(response['Images']) == 1): # If AMI exists, don't copy new_images.append(response['Images'][0]['ImageId']) cfn_output['ImageId'] = response['Images'][0]['ImageId'] else: # If AMI does not exist, copy response = ec2_client.copy_image( SourceImageId=source_image_id, SourceRegion=source_region, Name=new_ami_name ) new_images.append(response['ImageId']) cfn_output['ImageId'] = response['ImageId'] # Wait images to be available waiter_config = {'Delay': 15, 'MaxAttempts': 59 } # Wait image to exist response = img_exists_waiter.wait(ImageIds=new_images, WaiterConfig=waiter_config ) # Wait image to be available response = img_avail_waiter.wait(ImageIds=new_images, WaiterConfig=waiter_config) # Return AMI cfnresponse.send(event, context, cfnresponse.SUCCESS, cfn_output) Handler: index.handler Role: !GetAtt CloudformationLambdaRole.Arn Runtime: python3.11 Timeout: 900 CloudformationLambdaInvoke: Type: AWS::CloudFormation::CustomResource DeletionPolicy: Delete Version: "1.0" Properties: ServiceToken: !GetAtt CloudformationLambda.Arn AmiSourceRegion: 'eu-west-1' AmiSourceId: !FindInMap [OVAMIMAP, 'eu-west-1', AMI] DeploymentRegion: !Ref AWS::Region Outputs: OpenViduServerURL: Description: Use this URL to connect OpenVidu Server Value: !Join - '' - - 'https://' - !GetAtt - OpenviduServer - PublicDnsName OpenViduServerURLLE: Description: Use this URL to connect OpenVidu Server Value: !Join - '' - - 'https://' - !Ref MyDomainName Condition: WhichCertPresent OpenViduCallURL: Description: If you choose to deploy OpenVidu Call application, use this URL Value: !Join - '' - - 'https://' - !GetAtt - OpenviduServer - PublicDnsName OpenViduCallURLLE: Description: If you choose to deploy OpenVidu Call application, use this URL Value: !Join - '' - - 'https://' - !Ref MyDomainName Condition: WhichCertPresent