2025-04-22 11:46:24 +02:00
AWSTemplateFormatVersion : 2010-09-09
Description : OpenVidu Pro - Elastic
Parameters :
CertificateType :
Description : |
2025-09-06 21:54:25 +02:00
[ selfsigned] Not recommended for production use. Just for testing purposes or development environments. You don't need a FQDN to use this option.
[ owncert] Valid for production environments. Use your own certificate. You need a FQDN to use this option.
2026-07-02 17:21:42 +02:00
[ letsencrypt] Valid for production environments. Can be used with or without a FQDN (if no FQDN is provided, the public IP will be used).
2025-04-22 11:46:24 +02:00
Type : String
AllowedValues :
- selfsigned
- letsencrypt
2025-09-06 20:39:44 +02:00
- owncert
Default : letsencrypt
2025-04-22 11:46:24 +02:00
PublicElasticIP :
Type : String
Description : Previously created Elastic IP for the OpenVidu Deployment.
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
DomainName :
Type : String
Description : Domain name for the OpenVidu Deployment.
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
OwnPublicCertificate :
2025-12-23 19:35:00 +01:00
Description : "If certificate type is 'owncert', this parameter will be used to specify the public certificate in base64 format"
2025-04-22 11:46:24 +02:00
Type : String
OwnPrivateCertificate :
2025-12-23 19:35:00 +01:00
Description : "If certificate type is 'owncert', this parameter will be used to specify the private certificate in base64 format"
2025-04-22 11:46:24 +02:00
Type : String
2025-09-15 13:40:58 +02:00
InitialMeetAdminPassword :
Description : 'Initial password for the "admin" user in OpenVidu Meet. If not provided, a random password will be generated.'
Type : String
2025-09-15 22:51:57 +02:00
NoEcho : true
2025-09-15 13:40:58 +02:00
Default : ''
# Only allow alphanumeric characters
2025-09-15 22:51:57 +02:00
AllowedPattern : '^[A-Za-z0-9_-]*$'
2025-09-15 13:40:58 +02:00
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
2025-09-15 22:51:57 +02:00
NoEcho : true
2025-09-15 13:40:58 +02:00
Default : ''
# Only allow alphanumeric characters
2025-09-15 22:51:57 +02:00
AllowedPattern : '^[A-Za-z0-9_-]*$'
2025-09-15 13:40:58 +02:00
ConstraintDescription : 'Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to not set an initial API key.'
2025-06-13 19:19:53 +02:00
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).
2025-04-22 11:46:24 +02:00
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
2026-09-29 14:56:16 +02:00
Default : mediasoup
2025-04-22 11:46:24 +02:00
MasterNodeInstanceType :
Description : "Specifies the EC2 instance type for your OpenVidu Master Node"
Type : String
Default : c6a.xlarge
AllowedValues :
2025-12-01 20:36:57 +01:00
- t3.nano
- t3.micro
- t3.small
2025-04-22 11:46:24 +02:00
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c6a.large
- c6a.xlarge
- c6a.2xlarge
- c6a.4xlarge
- c6a.8xlarge
- c6a.12xlarge
- c6a.16xlarge
- c6a.24xlarge
- c6a.32xlarge
- c6a.48xlarge
- c6a.metal
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c6i.large
- c6i.xlarge
- c6i.2xlarge
- c6i.4xlarge
- c6i.8xlarge
- c6i.12xlarge
- c6i.16xlarge
- c6i.24xlarge
- c6i.32xlarge
- c6i.metal
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- 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
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c7i.large
- c7i.xlarge
- c7i.2xlarge
- c7i.4xlarge
- c7i.8xlarge
- c7i.12xlarge
- c7i.16xlarge
- c7i.24xlarge
- c7i.48xlarge
- c7i.metal-24xl
- c7i.metal-48xl
2025-12-01 20:36:57 +01:00
- 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
openvidu-deployment: Add missing instance types to AllowedValues in CloudFormation templates
Add Graviton families (c8gd, m8gd, r6g, r6gd, r7g, r7gd, r8g) and
GPU/Nvidia families (g4dn, g5, g5g, g6, g6e, g6f, g7e, gr6, gr6f,
p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200) that were
referenced in Conditions (IsGraviton/IsNvidia) but missing from the
AllowedValues parameter selectors.
2026-02-27 18:49:34 +01:00
- c8gd.medium
- c8gd.large
- c8gd.xlarge
- c8gd.2xlarge
- c8gd.4xlarge
- c8gd.8xlarge
- c8gd.12xlarge
- c8gd.16xlarge
- c8gd.24xlarge
- c8gd.48xlarge
- c8gd.metal-24xl
- c8gd.metal-48xl
- g4dn.xlarge
- g4dn.2xlarge
- g4dn.4xlarge
- g4dn.8xlarge
- g4dn.12xlarge
- g4dn.16xlarge
- g4dn.metal
- g5.xlarge
- g5.2xlarge
- g5.4xlarge
- g5.8xlarge
- g5.12xlarge
- g5.16xlarge
- g5.24xlarge
- g5.48xlarge
- g5g.xlarge
- g5g.2xlarge
- g5g.4xlarge
- g5g.8xlarge
- g5g.16xlarge
- g5g.metal
- g6.xlarge
- g6.2xlarge
- g6.4xlarge
- g6.8xlarge
- g6.12xlarge
- g6.16xlarge
- g6.24xlarge
- g6.48xlarge
- g6e.xlarge
- g6e.2xlarge
- g6e.4xlarge
- g6e.8xlarge
- g6e.12xlarge
- g6e.16xlarge
- g6e.24xlarge
- g6e.48xlarge
- g6f.large
- g6f.xlarge
- g6f.2xlarge
- g6f.4xlarge
- g7e.2xlarge
- g7e.4xlarge
- g7e.8xlarge
- g7e.12xlarge
- g7e.24xlarge
- g7e.48xlarge
- gr6.4xlarge
- gr6.8xlarge
- gr6f.4xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- m6in.large
- m6in.xlarge
- m6in.2xlarge
- m6in.4xlarge
- m6in.8xlarge
- m6in.12xlarge
- m6in.16xlarge
- m6in.24xlarge
- m6in.32xlarge
2025-12-01 20:36:57 +01:00
- 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
openvidu-deployment: Add missing instance types to AllowedValues in CloudFormation templates
Add Graviton families (c8gd, m8gd, r6g, r6gd, r7g, r7gd, r8g) and
GPU/Nvidia families (g4dn, g5, g5g, g6, g6e, g6f, g7e, gr6, gr6f,
p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200) that were
referenced in Conditions (IsGraviton/IsNvidia) but missing from the
AllowedValues parameter selectors.
2026-02-27 18:49:34 +01:00
- m8gd.medium
- m8gd.large
- m8gd.xlarge
- m8gd.2xlarge
- m8gd.4xlarge
- m8gd.8xlarge
- m8gd.12xlarge
- m8gd.16xlarge
- m8gd.24xlarge
- m8gd.48xlarge
- m8gd.metal-24xl
- m8gd.metal-48xl
- p4d.24xlarge
- p4de.24xlarge
- p5.4xlarge
- p5.48xlarge
- p5e.48xlarge
- p5en.48xlarge
- p6-b200.48xlarge
- p6-b300.48xlarge
- p6e-gb200.36xlarge
- r6g.medium
- r6g.large
- r6g.xlarge
- r6g.2xlarge
- r6g.4xlarge
- r6g.8xlarge
- r6g.12xlarge
- r6g.16xlarge
- r6g.metal
- r6gd.medium
- r6gd.large
- r6gd.xlarge
- r6gd.2xlarge
- r6gd.4xlarge
- r6gd.8xlarge
- r6gd.12xlarge
- r6gd.16xlarge
- r6gd.metal
- r7g.medium
- r7g.large
- r7g.xlarge
- r7g.2xlarge
- r7g.4xlarge
- r7g.8xlarge
- r7g.12xlarge
- r7g.16xlarge
- r7g.metal
- r7gd.medium
- r7gd.large
- r7gd.xlarge
- r7gd.2xlarge
- r7gd.4xlarge
- r7gd.8xlarge
- r7gd.12xlarge
- r7gd.16xlarge
- r7gd.metal
- r8g.medium
- r8g.large
- r8g.xlarge
- r8g.2xlarge
- r8g.4xlarge
- r8g.8xlarge
- r8g.12xlarge
- r8g.16xlarge
- r8g.24xlarge
- r8g.48xlarge
- r8g.metal-24xl
- r8g.metal-48xl
2025-04-22 11:46:24 +02:00
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 :
2025-12-01 20:36:57 +01:00
- t3.nano
- t3.micro
- t3.small
2025-04-22 11:46:24 +02:00
- t3.medium
- t3.large
- t3.xlarge
- t3.2xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c5.large
- c5.xlarge
- c5.2xlarge
- c5.4xlarge
- c5.9xlarge
- c5.12xlarge
- c5.18xlarge
- c5.24xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c6a.large
- c6a.xlarge
- c6a.2xlarge
- c6a.4xlarge
- c6a.8xlarge
- c6a.12xlarge
- c6a.16xlarge
- c6a.24xlarge
- c6a.32xlarge
- c6a.48xlarge
- c6a.metal
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c6i.large
- c6i.xlarge
- c6i.2xlarge
- c6i.4xlarge
- c6i.8xlarge
- c6i.12xlarge
- c6i.16xlarge
- c6i.24xlarge
- c6i.32xlarge
- c6i.metal
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- 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
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- c7i.large
- c7i.xlarge
- c7i.2xlarge
- c7i.4xlarge
- c7i.8xlarge
- c7i.12xlarge
- c7i.16xlarge
- c7i.24xlarge
- c7i.48xlarge
- c7i.metal-24xl
- c7i.metal-48xl
2025-12-01 20:36:57 +01:00
- 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
openvidu-deployment: Add missing instance types to AllowedValues in CloudFormation templates
Add Graviton families (c8gd, m8gd, r6g, r6gd, r7g, r7gd, r8g) and
GPU/Nvidia families (g4dn, g5, g5g, g6, g6e, g6f, g7e, gr6, gr6f,
p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200) that were
referenced in Conditions (IsGraviton/IsNvidia) but missing from the
AllowedValues parameter selectors.
2026-02-27 18:49:34 +01:00
- c8gd.medium
- c8gd.large
- c8gd.xlarge
- c8gd.2xlarge
- c8gd.4xlarge
- c8gd.8xlarge
- c8gd.12xlarge
- c8gd.16xlarge
- c8gd.24xlarge
- c8gd.48xlarge
- c8gd.metal-24xl
- c8gd.metal-48xl
- g4dn.xlarge
- g4dn.2xlarge
- g4dn.4xlarge
- g4dn.8xlarge
- g4dn.12xlarge
- g4dn.16xlarge
- g4dn.metal
- g5.xlarge
- g5.2xlarge
- g5.4xlarge
- g5.8xlarge
- g5.12xlarge
- g5.16xlarge
- g5.24xlarge
- g5.48xlarge
- g5g.xlarge
- g5g.2xlarge
- g5g.4xlarge
- g5g.8xlarge
- g5g.16xlarge
- g5g.metal
- g6.xlarge
- g6.2xlarge
- g6.4xlarge
- g6.8xlarge
- g6.12xlarge
- g6.16xlarge
- g6.24xlarge
- g6.48xlarge
- g6e.xlarge
- g6e.2xlarge
- g6e.4xlarge
- g6e.8xlarge
- g6e.12xlarge
- g6e.16xlarge
- g6e.24xlarge
- g6e.48xlarge
- g6f.large
- g6f.xlarge
- g6f.2xlarge
- g6f.4xlarge
- g7e.2xlarge
- g7e.4xlarge
- g7e.8xlarge
- g7e.12xlarge
- g7e.24xlarge
- g7e.48xlarge
- gr6.4xlarge
- gr6.8xlarge
- gr6f.4xlarge
2025-12-01 20:36:57 +01:00
- 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
2025-04-22 11:46:24 +02:00
- m6in.large
- m6in.xlarge
- m6in.2xlarge
- m6in.4xlarge
- m6in.8xlarge
- m6in.12xlarge
- m6in.16xlarge
- m6in.24xlarge
- m6in.32xlarge
2025-12-01 20:36:57 +01:00
- 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
openvidu-deployment: Add missing instance types to AllowedValues in CloudFormation templates
Add Graviton families (c8gd, m8gd, r6g, r6gd, r7g, r7gd, r8g) and
GPU/Nvidia families (g4dn, g5, g5g, g6, g6e, g6f, g7e, gr6, gr6f,
p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200) that were
referenced in Conditions (IsGraviton/IsNvidia) but missing from the
AllowedValues parameter selectors.
2026-02-27 18:49:34 +01:00
- m8gd.medium
- m8gd.large
- m8gd.xlarge
- m8gd.2xlarge
- m8gd.4xlarge
- m8gd.8xlarge
- m8gd.12xlarge
- m8gd.16xlarge
- m8gd.24xlarge
- m8gd.48xlarge
- m8gd.metal-24xl
- m8gd.metal-48xl
- p4d.24xlarge
- p4de.24xlarge
- p5.4xlarge
- p5.48xlarge
- p5e.48xlarge
- p5en.48xlarge
- p6-b200.48xlarge
- p6-b300.48xlarge
- p6e-gb200.36xlarge
- r6g.medium
- r6g.large
- r6g.xlarge
- r6g.2xlarge
- r6g.4xlarge
- r6g.8xlarge
- r6g.12xlarge
- r6g.16xlarge
- r6g.metal
- r6gd.medium
- r6gd.large
- r6gd.xlarge
- r6gd.2xlarge
- r6gd.4xlarge
- r6gd.8xlarge
- r6gd.12xlarge
- r6gd.16xlarge
- r6gd.metal
- r7g.medium
- r7g.large
- r7g.xlarge
- r7g.2xlarge
- r7g.4xlarge
- r7g.8xlarge
- r7g.12xlarge
- r7g.16xlarge
- r7g.metal
- r7gd.medium
- r7gd.large
- r7gd.xlarge
- r7gd.2xlarge
- r7gd.4xlarge
- r7gd.8xlarge
- r7gd.12xlarge
- r7gd.16xlarge
- r7gd.metal
- r8g.medium
- r8g.large
- r8g.xlarge
- r8g.2xlarge
- r8g.4xlarge
- r8g.8xlarge
- r8g.12xlarge
- r8g.16xlarge
- r8g.24xlarge
- r8g.48xlarge
- r8g.metal-24xl
- r8g.metal-48xl
2025-04-22 11:46:24 +02:00
ConstraintDescription : "Must be a valid EC2 instance type"
KeyName :
Type : AWS::EC2::KeyPair::KeyName
Description : Name of an existing EC2 KeyPair to enable SSH access to the Deployment.
AllowedPattern : ^.+$
ConstraintDescription : must be the name of an existing EC2 KeyPair.
2025-12-01 20:36:57 +01:00
OperatingSystem :
2025-12-23 20:22:53 +01:00
Description : EC2 Operating System
2025-12-01 20:36:57 +01:00
Type : String
Default : "Ubuntu-24"
AllowedValues : [ 'Ubuntu-22' , 'Ubuntu-24' ]
2025-04-22 11:46:24 +02:00
InitialNumberOfMediaNodes :
Type : Number
Default : 1
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
Description : Number of initial media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
2025-04-22 11:46:24 +02:00
MinNumberOfMediaNodes :
Type : Number
Default : 1
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
Description : Minimum number of media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
2025-04-22 11:46:24 +02:00
MaxNumberOfMediaNodes :
Type : Number
Default : 5
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
Description : Maximum number of media nodes to deploy. Ignored when FixedNumberOfMediaNodes > 0
2025-04-22 11:46:24 +02:00
ScaleTargetCPU :
Type : Number
Default : 50
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
Description : Target CPU percentage to scale up or down. Ignored when FixedNumberOfMediaNodes > 0
FixedNumberOfMediaNodes :
Type : Number
Default : 0
MinValue : 0
Description : If > 0, deploys a fixed number of media nodes with no autoscaling (InitialNumberOfMediaNodes, MinNumberOfMediaNodes, MaxNumberOfMediaNodes and ScaleTargetCPU are ignored). If 0 (default), the deployment is elastic and autoscaling is enabled
2025-04-22 11:46:24 +02:00
S3AppDataBucketName :
Type : String
Description : Name of the S3 bucket to store data and recordings. If empty, a bucket will be created
OpenViduVPC :
Description : "Dedicated VPC for OpenVidu cluster"
Type : AWS::EC2::VPC::Id
AllowedPattern : ^.+$
ConstraintDescription : You must specify a VPC ID
OpenViduMasterNodeSubnet :
Description : "Subnet for OpenVidu cluster"
Type : AWS::EC2::Subnet::Id
AllowedPattern : ^.+$
ConstraintDescription : You must specify a subnet ID
OpenViduMediaNodeSubnets :
Description : "Subnets for OpenVidu Media Nodes"
Type : List<AWS::EC2::Subnet::Id>
AllowedPattern : ^.+$
ConstraintDescription : You must specify a list of subnet IDs
Metadata :
'AWS::CloudFormation::Interface' :
ParameterGroups :
- Label :
default : Domain and SSL certificate configuration
Parameters :
- CertificateType
- PublicElasticIP
- DomainName
- OwnPublicCertificate
- OwnPrivateCertificate
2025-09-06 21:44:34 +02:00
- Label :
default : OpenVidu Elastic configuration
Parameters :
- OpenViduLicense
- RTCEngine
2025-09-15 13:40:58 +02:00
- Label :
default : OpenVidu Meet configuration
Parameters :
- InitialMeetAdminPassword
- InitialMeetApiKey
2025-04-22 11:46:24 +02:00
- Label :
default : EC2 Instance configuration
Parameters :
- MasterNodeInstanceType
- MediaNodeInstanceType
2025-09-06 21:44:34 +02:00
- KeyName
2025-12-01 20:36:57 +01:00
- OperatingSystem
2025-04-22 11:46:24 +02:00
- Label :
default : Media Nodes Autoscaling Group configuration
Parameters :
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
- FixedNumberOfMediaNodes
2025-04-22 11:46:24 +02:00
- InitialNumberOfMediaNodes
- MinNumberOfMediaNodes
- MaxNumberOfMediaNodes
- ScaleTargetCPU
- Label :
default : S3 bucket for application data and recordings
Parameters :
- S3AppDataBucketName
- Label :
default : VPC configuration
Parameters :
- OpenViduVPC
- OpenViduMasterNodeSubnet
- OpenViduMediaNodeSubnets
2025-06-13 19:19:53 +02:00
- Label :
default : "(Optional) Additional Installer Flags"
Parameters :
- AdditionalInstallFlags
2025-04-22 11:46:24 +02:00
Conditions :
PublicElasticIPPresent : !Not [ !Equals [!Ref PublicElasticIP, ""] ]
2025-09-06 20:39:44 +02:00
PublicElasticIPAbsent : !Equals [!Ref PublicElasticIP, ""]
2025-04-22 11:46:24 +02:00
CreateRecordingsBucket : !Equals [!Ref S3AppDataBucketName, ""]
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
FixedNumberOfMediaNodesEnabled : !Not [ !Equals [!Ref FixedNumberOfMediaNodes, 0] ]
AutoscalingEnabled : !Equals [!Ref FixedNumberOfMediaNodes, 0]
2025-12-01 20:36:57 +01:00
IsMasterGraviton : !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 't4g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6g']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c6gn']
2025-12-01 20:36:57 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7gd']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c7gn']
2025-12-01 20:36:57 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c8g']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'c8gd']
- !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']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'm8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r7g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'r8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6e-gb200']
2025-12-01 20:36:57 +01:00
IsMediaGraviton : !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 't4g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6g']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c6gn']
2025-12-01 20:36:57 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7gd']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c7gn']
2025-12-01 20:36:57 +01:00
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c8g']
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'c8gd']
- !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']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'm8gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r6g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r6gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r7g']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r7gd']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'r8g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6e-gb200']
IsMasterNodeNvidia : !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g4dn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'gr6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'gr6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'g7e']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p4d']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p4de']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p5en']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6-b200']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6-b300']
- !Equals [ !Select [ 0, !Split ['.', !Ref MasterNodeInstanceType ]], 'p6e-gb200']
IsMediaNodeNvidia : !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g4dn']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g5g']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'gr6']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'gr6f']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'g7e']
- !Or
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p4d']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p4de']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5e']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p5en']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6-b200']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6-b300']
- !Equals [ !Select [ 0, !Split ['.', !Ref MediaNodeInstanceType ]], 'p6e-gb200']
2025-12-01 20:36:57 +01:00
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}}'
2025-04-22 11:46:24 +02:00
Resources :
OpenViduSharedInfo :
Type : AWS::SecretsManager::Secret
UpdateReplacePolicy : Retain
DeletionPolicy : Delete
Properties :
Name : !Sub openvidu-elastic-${AWS::Region}-${AWS::StackName}
Description : Secret for OpenVidu Elastic to store deployment info and seed secret
# All the values are initialized by the master node
SecretString : |
{
2025-09-06 20:39:44 +02:00
"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" ,
2025-04-22 11:46:24 +02:00
"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" ,
"OPENVIDU_VERSION": "none" ,
2025-09-06 20:39:44 +02:00
"ENABLED_MODULES": "none" ,
"ALL_SECRETS_GENERATED": "false"
2025-04-22 11:46:24 +02:00
}
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
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 :
- 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}
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]]]]
OpenViduMasterNodeInstanceProfile :
Type : 'AWS::IAM::InstanceProfile'
Properties :
Roles :
- !Ref OpenViduMasterNodeRole
InstanceProfileName : !Sub openvidu-elastic-instance-profile-${AWS::Region}-${AWS::StackName}
OpenViduMediaNodeInstanceProfile :
Type : 'AWS::IAM::InstanceProfile'
Properties :
Roles :
- !Ref OpenViduMediaNodeRole
InstanceProfileName : !Sub openvidu-elastic-media-instance-profile-${AWS::Region}-${AWS::StackName}
2025-09-06 20:39:44 +02:00
NewEIP :
Type : 'AWS::EC2::EIP'
Condition : PublicElasticIPAbsent
2026-10-06 16:00:49 +02:00
NewEIPAssociation :
Type : 'AWS::EC2::EIPAssociation'
Condition : PublicElasticIPAbsent
2025-09-06 20:39:44 +02:00
Properties :
InstanceId : !Ref OpenViduMasterNode
2026-10-06 16:00:49 +02:00
AllocationId : !GetAtt NewEIP.AllocationId
2025-09-06 20:39:44 +02:00
2025-04-22 11:46:24 +02:00
IMDSv2LaunchTemplateMasterNode :
Type : AWS::EC2::LaunchTemplate
Properties :
LaunchTemplateName : !Sub openvidu-elastic-master-node-${AWS::Region}-${AWS::StackName}
LaunchTemplateData :
MetadataOptions :
HttpEndpoint : enabled
HttpPutResponseHopLimit : 1
HttpTokens : required
WaitCondition :
Type : 'AWS::CloudFormation::WaitCondition'
CreationPolicy :
ResourceSignal :
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Elastic signals AFTER the full install completes, so it needs a wider window than HA
Timeout : PT20M
2025-04-22 11:46:24 +02:00
Count : '1'
OpenViduMasterNode :
Type : AWS::EC2::Instance
Metadata :
Comment : 'Install and configure OpenVidu Elastic Master Node'
AWS::CloudFormation::Init :
config :
files :
'/usr/local/bin/install.sh' :
content : !Sub |
#!/bin/bash
set -e
2026-09-29 18:27:35 +02:00
OPENVIDU_VERSION=main
2025-04-22 11:46:24 +02:00
DOMAIN=
2026-09-07 16:58:47 +02:00
YQ_VERSION=v4.53.6
2025-04-22 11:46:24 +02:00
# Install dependencies
apt-get update && apt-get install -y \
curl \
unzip \
jq \
wget
2025-09-06 20:39:44 +02:00
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
2025-04-22 11:46:24 +02:00
2026-09-07 16:58:47 +02:00
AWS_CLI_VERSION=2.36.40
2026-02-28 00:37:17 +01:00
# Install aws-cli if not already installed
if ! command -v aws &> /dev/null; then
2026-03-02 16:45:35 +01:00
curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m)-${!AWS_CLI_VERSION}.zip" -o "awscliv2.zip"
2026-02-28 00:37:17 +01:00
unzip -qq awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
fi
2025-04-22 11:46:24 +02:00
# Token for IMDSv2
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# Configure Domain and other parameters
if [[ "${DomainName}" == '' ]]; then
2026-10-06 16:00:49 +02:00
DOMAIN=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4)
2025-04-22 11:46:24 +02:00
else
DOMAIN=${DomainName}
fi
# Get own private IP
PRIVATE_IP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Instance role already proven by cfn-init above, so a single read needs no IAM-availability retry
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
if [[ "$SHARED_SECRET" == "none" ]]; then
echo "Error: Shared secret not found"
exit 1
fi
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
2025-04-22 11:46:24 +02:00
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Generate secrets only once: a re-run must never regenerate passwords already in use by the cluster
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
# Persist the domain so after_install.sh reads it from the secret instead of the installer
/usr/local/bin/store_secret.sh save DOMAIN_NAME "$DOMAIN"
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
2025-04-22 11:46:24 +02:00
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# 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
# Source every installer value from the secret so a re-run (guard skipped) still has them
# GetSecretValue is eventually consistent: retry (up to 300s at 5s) until the read returns the generated values
SECRET_READ_ATTEMPTS=0
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .OPENVIDU_PRO_LICENSE, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
break
fi
SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
echo "Error: shared secret still incomplete after 5 minutes of stale reads"
exit 1
fi
sleep 5
2025-04-22 11:46:24 +02:00
done
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
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')
MEET_INITIAL_API_KEY=""
2025-09-15 13:40:58 +02:00
if [[ "${InitialMeetApiKey}" != '' ]]; then
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
2025-09-15 13:40:58 +02:00
fi
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
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')
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')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
2026-10-23 14:57:54 +02:00
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "https://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh"
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS=(
"--no-tty"
"--install"
"--environment=aws"
"--deployment-type=elastic"
"--node-role='master-node'"
"--openvidu-pro-license=$OPENVIDU_PRO_LICENSE"
"--private-ip=$PRIVATE_IP"
"--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"
2025-08-28 13:58:03 +02:00
"--meet-initial-admin-password=$MEET_INITIAL_ADMIN_PASSWORD"
"--meet-initial-api-key=$MEET_INITIAL_API_KEY"
2025-04-22 11:46:24 +02:00
"--livekit-api-key=$LIVEKIT_API_KEY"
"--livekit-api-secret=$LIVEKIT_API_SECRET"
)
2025-06-13 19:19:53 +02:00
# 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
2025-04-22 11:46:24 +02:00
# Certificate arguments
if [[ "${CertificateType}" == "selfsigned" ]]; then
CERT_ARGS=(
"--certificate-type=selfsigned"
)
elif [[ "${CertificateType}" == "letsencrypt" ]]; then
CERT_ARGS=(
"--certificate-type=letsencrypt"
)
else
2025-12-23 19:35:00 +01:00
# Use base64 encoded certificates directly
OWN_CERT_CRT=${OwnPublicCertificate}
OWN_CERT_KEY=${OwnPrivateCertificate}
2025-04-22 11:46:24 +02:00
CERT_ARGS=(
"--certificate-type=owncert"
"--owncert-public-key=$OWN_CERT_CRT"
"--owncert-private-key=$OWN_CERT_KEY"
)
fi
# Construct the final command with all arguments
FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "${!COMMON_ARGS[@]}") $(printf "%s " "${!CERT_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=${S3RecordingsBucketResourceName}
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"
- S3RecordingsBucketResourceName : !If
- CreateRecordingsBucket
- !Ref S3AppDataBucketResource
- !Ref S3AppDataBucketName
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-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
2025-09-09 22:26:41 +02:00
DOMAIN=$(echo $SHARED_SECRET | jq -r .DOMAIN_NAME)
2025-04-22 11:46:24 +02:00
# Generate URLs
2025-09-06 20:39:44 +02:00
OPENVIDU_URL="https://${!DOMAIN}/"
LIVEKIT_URL="wss://${!DOMAIN}/"
2025-04-22 11:46:24 +02:00
DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
GRAFANA_URL="https://${!DOMAIN}/grafana/"
MINIO_URL="https://${!DOMAIN}/minio-console/"
# Update shared secret
2025-09-06 20:39:44 +02:00
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
2025-04-22 11:46:24 +02:00
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-elastic-${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-elastic-${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/MONGO_REPLICA_SET_KEY=.*/MONGO_REPLICA_SET_KEY=$(echo $SHARED_SECRET | jq -r .MONGO_REPLICA_SET_KEY)/" "${!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"
2025-08-28 13:58:03 +02:00
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"
2025-09-15 22:51:57 +02:00
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
2025-04-22 11:46:24 +02:00
sed -i "s/ENABLED_MODULES=.*/ENABLED_MODULES=$(echo $SHARED_SECRET | jq -r .ENABLED_MODULES)/" "${!CLUSTER_CONFIG_DIR}/openvidu.env"
# Update URLs in secret
2025-09-06 20:39:44 +02:00
OPENVIDU_URL="https://${!DOMAIN}/"
LIVEKIT_URL="wss://${!DOMAIN}/"
2025-04-22 11:46:24 +02:00
DASHBOARD_URL="https://${!DOMAIN}/dashboard/"
GRAFANA_URL="https://${!DOMAIN}/grafana/"
MINIO_URL="https://${!DOMAIN}/minio-console/"
2025-09-06 20:39:44 +02:00
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"OPENVIDU_URL": "'"$OPENVIDU_URL"'" }')"
SHARED_SECRET="$(echo "$SHARED_SECRET" | jq '. + {"LIVEKIT_URL": "'"$LIVEKIT_URL"'" }')"
2025-04-22 11:46:24 +02:00
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-elastic-${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-elastic-${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 '. + {"MONGO_REPLICA_SET_KEY": "'"$(/usr/local/bin/get_value_from_config.sh MONGO_REPLICA_SET_KEY "${!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")"'"}')"
2025-08-28 13:58:03 +02:00
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")"'"}')"
2025-09-15 22:51:57 +02:00
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
2025-04-22 11:46:24 +02:00
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-elastic-${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 :
2025-12-01 20:36:57 +01:00
ImageId : !If
- IsMasterGraviton
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !If
- IsMasterNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/arm64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
- !If
- IsMasterNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/x86_64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
2025-04-22 11:46:24 +02:00
LaunchTemplate :
# Enable IMDSv2 by default
LaunchTemplateId : !Ref IMDSv2LaunchTemplateMasterNode
Version : !GetAtt IMDSv2LaunchTemplateMasterNode.DefaultVersionNumber
InstanceType : !Ref MasterNodeInstanceType
IamInstanceProfile : !Ref OpenViduMasterNodeInstanceProfile
SubnetId : !Ref OpenViduMasterNodeSubnet
SecurityGroupIds :
- !GetAtt OpenViduMasterNodeSG.GroupId
KeyName : !Ref KeyName
Tags :
- Key : Name
Value : !Sub ${AWS::StackName} - OpenVidu Elastic - Master Node
UserData :
2026-10-06 16:00:49 +02:00
Fn::Base64 : !Sub
- |
#!/bin/bash
set -eu -o pipefail
# Wait for the Elastic IP: CloudFormation associates it only after the instance
# is created, and until then the instance may have another public IP (or none,
# and no Internet access)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
CURRENT_PUBLIC_IP=""
for _ in $(seq 1 120); do
CURRENT_PUBLIC_IP=$(curl -sf -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/public-ipv4 || true)
if [[ "$CURRENT_PUBLIC_IP" == "${ExpectedPublicIp}" ]]; then
break
fi
sleep 5
done
if [[ "$CURRENT_PUBLIC_IP" != "${ExpectedPublicIp}" ]]; then
echo "[OpenVidu] Elastic IP ${ExpectedPublicIp} not attached after 10 minutes"
exit 1
fi
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-12-16 12:49:34 +01:00
2026-10-06 16:00:49 +02:00
apt-get update && apt-get install -y \
python3-pip \
ec2-instance-connect
2025-09-06 20:39:44 +02:00
2026-10-06 16:00:49 +02:00
CFN_BOOTSTRAP_VERSION=2.0-39
# 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-${!CFN_BOOTSTRAP_VERSION}.tar.gz
else
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduMasterNode
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
export HOME="/root"
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Config S3 bucket
/usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; }
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
2026-10-06 16:00:49 +02:00
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
2026-10-06 16:00:49 +02:00
# Disable errexit so cfn-signal always runs and reports readiness via $? (fast-fail instead of a PT20M timeout)
set +e
[ "$OPENVIDU_READY" = "true" ]
2025-04-22 11:46:24 +02:00
2026-10-06 16:00:49 +02:00
# sending the finish call
cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}
- ExpectedPublicIp : !If [PublicElasticIPPresent, !Ref PublicElasticIP, !Ref NewEIP]
2025-04-22 11:46:24 +02:00
BlockDeviceMappings :
- DeviceName : /dev/sda1
Ebs :
2026-01-15 17:55:52 +01:00
VolumeType : gp3
2025-04-22 11:46:24 +02:00
DeleteOnTermination : true
VolumeSize : 200
OpenViduMediaNodeLaunchTemplate :
Type : AWS::EC2::LaunchTemplate
Metadata :
Comment : 'Launch template for OpenVidu Elastic Media Node'
AWS::CloudFormation::Init :
config :
files :
'/usr/local/bin/install.sh' :
content : !Sub |
#!/bin/bash
set -e
DOMAIN=
2026-09-07 16:58:47 +02:00
YQ_VERSION=v4.53.6
2025-04-22 11:46:24 +02:00
# Install dependencies
apt-get update && apt-get install -y \
curl \
unzip \
jq \
wget
2025-09-07 03:56:21 +02:00
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
2025-04-22 11:46:24 +02:00
2026-09-07 16:58:47 +02:00
AWS_CLI_VERSION=2.36.40
2026-02-28 00:37:17 +01:00
# Install aws-cli if not already installed
if ! command -v aws &> /dev/null; then
2026-03-02 16:45:35 +01:00
curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m)-${!AWS_CLI_VERSION}.zip" -o "awscliv2.zip"
2026-02-28 00:37:17 +01:00
unzip -qq awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
fi
2025-04-22 11:46:24 +02:00
# 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 -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)
# Unfortunately, EC2 instance role is not immediately available after the instance is launched.
# Therefore, we need to retry this operation until the aws-cli command is successful.
MAX_RETRIES=10
RETRY_COUNT=0
while : ; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
if [[ "$SHARED_SECRET" != "none" ]]; then
break
fi
2026-01-27 16:37:20 +01:00
RETRY_COUNT=$((RETRY_COUNT+1))
2025-04-22 11:46:24 +02:00
if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then
exit 1
fi
sleep 6
done
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Wait until the master node has generated all shared secrets.
# Bounded (up to 1800s at 5s), validating content on each snapshot: stale eventually-consistent reads can return pre-generation values
SECRETS_WAIT_ATTEMPTS=0
SECRETS_WAIT_MAX=360
while true; do
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
break
fi
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for the master node to generate the shared secrets"
exit 1
fi
sleep 5
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
done
2025-04-22 11:46:24 +02:00
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Read values only after the gate confirms the master node has published them
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
2025-04-22 11:46:24 +02:00
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then
echo "OpenVidu version not found"
exit 1
fi
# Get Master Node private IP
MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp}
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
2026-10-23 14:57:54 +02:00
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "https://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh"
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS=(
"--no-tty"
"--install"
"--environment=aws"
"--deployment-type=elastic"
"--node-role='media-node'"
"--master-node-private-ip=$MASTER_NODE_IP"
"--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"
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
'/usr/local/bin/openvidu-media-health.sh' :
content : |
#!/bin/bash
# OpenVidu Media Node health watchdog.
#
# Asks the cloud to replace this Media Node with a new one, keeping the number of
# Media Nodes, through /usr/local/bin/openvidu-media-replace.sh, when:
# - its bootstrap failed (the user-data wrote the bootstrap-failed marker): after a
# cool-down, so a persistent failure does not churn nodes and can be inspected,
# - its bootstrap did not finish BOOTSTRAP_DEADLINE_SEC after boot,
# - LiveKit fails its health check (HTTP 200 on PROBE_URL): for HARD_UNHEALTHY_AFTER_SEC
# while it is down (connection refused or closed), or for SOFT_UNHEALTHY_AFTER_SEC
# while it answers an error or times out. Soft failures are not counted while the CPU
# is saturated (an overloaded LiveKit reports Not Ready), and no failure is counted in
# the WARMUP_SEC after openvidu.service (re)started.
# Nothing is replaced while no master answers a Redis PING (MASTER_ADDRS): a master outage
# fails the health check of every Media Node at once, and a new node could not bootstrap
2026-10-06 20:47:00 +02:00
# either; LiveKit failures are not counted until a master answers again. Once requested,
# the replacement is requested again every REPLACE_RETRY_SEC until the node goes away.
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
#
# Settings: /etc/openvidu/media-health.env (sourced every cycle; MASTER_ADDRS="ip:port ...")
# Pause: touch /etc/openvidu/media-health.disabled (drain scripts use /run/openvidu-media-health.paused)
# Logs: journalctl -u openvidu-media-health
#
# This file is embedded verbatim in CloudFormation !Sub blocks, Bicep strings and
# Terraform heredocs, so it must never contain a dollar sign or a percent sign followed
# by an opening brace, nor two opening braces in a row.
STATE_DIR=/var/lib/openvidu-media-health
INSTALLED_MARKER="$STATE_DIR/installed"
BOOTSTRAP_FAILED_MARKER="$STATE_DIR/bootstrap-failed"
REPLACE_SCRIPT=/usr/local/bin/openvidu-media-replace.sh
ENV_FILE=/etc/openvidu/media-health.env
PAUSE_FILE=/etc/openvidu/media-health.disabled
DRAIN_PAUSE_FILE=/run/openvidu-media-health.paused
log() { echo "[media-health] $*"; }
# Seconds since boot: monotonic, unaffected by clock steps
uptime_sec() { cut -d. -f1 /proc/uptime; }
# Busy CPU percentage since the previous call, in BUSY (0 on the first call)
cpu_busy() {
read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ < /proc/stat
total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))
idle=$((c_idle + c_iowait))
BUSY=0
if [ -n "$PREV_TOTAL" ] && [ "$total" -gt "$PREV_TOTAL" ]; then
BUSY=$((100 * (total - PREV_TOTAL - idle + PREV_IDLE) / (total - PREV_TOTAL)))
fi
PREV_TOTAL=$total
PREV_IDLE=$idle
}
2026-10-06 17:42:46 +02:00
# openvidu.service is running and entered the active state less than WARMUP_SEC ago
# (a stopped or failed openvidu.service is not starting: no warm-up for it)
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
openvidu_starting() {
2026-10-06 17:42:46 +02:00
case "$(systemctl show -p ActiveState --value openvidu 2>/dev/null)" in
active|activating|reloading) ;;
*) return 1 ;;
esac
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
since=$(systemctl show -p ActiveEnterTimestampMonotonic --value openvidu 2>/dev/null)
case "$since" in ''|0|*[!0-9]*) return 1 ;; esac
[ $(($(uptime_sec) - since / 1000000)) -lt "$WARMUP_SEC" ]
}
# A master answers a Redis PING (+PONG, or -NOAUTH when a password is required). A bare
# TCP connect would also succeed against a wedged Redis.
master_serving() {
[ -n "$MASTER_ADDRS" ] || return 1
for addr in $MASTER_ADDRS; do
host=$(echo "$addr" | cut -d: -f1)
port=$(echo "$addr" | cut -d: -f2)
reply=$(timeout 5 bash -c 'exec 3<>"/dev/tcp/$0/$1" && printf "PING\r\n" >&3 && head -c 7 <&3' "$host" "$port" 2>/dev/null)
case "$reply" in
+PONG*|-NOAUTH*) return 0 ;;
esac
done
return 1
}
# Request the replacement of this node ($1 = reason). Gated on a master answering, except
# in the bootstrap phase before the master addresses are known.
request_replacement() {
now=$(uptime_sec)
if [ -n "$REQUESTED_AT" ] && [ $((now - REQUESTED_AT)) -lt "$REPLACE_RETRY_SEC" ]; then
return
fi
if [ -n "$MASTER_ADDRS" ] || [ -f "$INSTALLED_MARKER" ]; then
if ! master_serving; then
if [ -z "$HOLD_LOGGED_AT" ] || [ $((now - HOLD_LOGGED_AT)) -ge 600 ]; then
log "$1, but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not replacing this node"
HOLD_LOGGED_AT=$now
fi
return
fi
fi
log "$1: requesting the replacement of this node"
if "$REPLACE_SCRIPT" "$1"; then
REQUESTED_AT=$now
log "replacement requested"
else
log "the replacement request failed, retrying in $INTERVAL_SEC s"
fi
}
mkdir -p "$STATE_DIR"
REQUESTED_AT=""
HOLD_LOGGED_AT=""
FAILED_SEEN_AT=""
FAIL_SINCE=""
SATURATED=""
PREV_TOTAL=""
PREV_IDLE=""
log "started"
while true; do
PROBE_URL=http://127.0.0.1:7880/
INTERVAL_SEC=30
HARD_UNHEALTHY_AFTER_SEC=300
SOFT_UNHEALTHY_AFTER_SEC=600
CPU_SATURATED_PCT=90
WARMUP_SEC=900
BOOTSTRAP_DEADLINE_SEC=5400
BOOTSTRAP_FAILURE_COOLDOWN_SEC=600
REPLACE_RETRY_SEC=600
MASTER_ADDRS=""
[ -f "$ENV_FILE" ] && . "$ENV_FILE"
NOW=$(uptime_sec)
cpu_busy
if [ -f "$PAUSE_FILE" ] || [ -f "$DRAIN_PAUSE_FILE" ]; then
FAIL_SINCE=""
elif [ -f "$BOOTSTRAP_FAILED_MARKER" ]; then
if [ -z "$FAILED_SEEN_AT" ]; then
FAILED_SEEN_AT=$NOW
log "bootstrap failed ($(head -c 300 "$BOOTSTRAP_FAILED_MARKER")): replacing this node in $BOOTSTRAP_FAILURE_COOLDOWN_SEC s"
fi
if [ $((NOW - FAILED_SEEN_AT)) -ge "$BOOTSTRAP_FAILURE_COOLDOWN_SEC" ]; then
request_replacement "bootstrap failed: $(head -c 300 "$BOOTSTRAP_FAILED_MARKER")"
fi
elif [ ! -f "$INSTALLED_MARKER" ]; then
if [ "$NOW" -ge "$BOOTSTRAP_DEADLINE_SEC" ]; then
request_replacement "bootstrap not finished $BOOTSTRAP_DEADLINE_SEC s after boot"
fi
elif curl -sf -o /dev/null --connect-timeout 3 --max-time 15 "$PROBE_URL"; then
[ -n "$FAIL_SINCE" ] && log "LiveKit healthy again after $((NOW - FAIL_SINCE)) s"
FAIL_SINCE=""
SATURATED=""
else
RC=$?
case "$RC" in
7 |52|56) KIND=hard ;;
*) KIND=soft ;;
esac
if openvidu_starting; then
FAIL_SINCE=""
2026-10-06 20:47:00 +02:00
elif ! master_serving; then
# A master outage fails the health check of every Media Node at once: not counted,
# so LiveKit gets the whole window to reconnect once a master answers again
FAIL_SINCE=""
if [ -z "$HOLD_LOGGED_AT" ] || [ $((NOW - HOLD_LOGGED_AT)) -ge 600 ]; then
log "LiveKit health check failed (curl exit $RC), but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not counted"
HOLD_LOGGED_AT=$NOW
fi
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
elif [ "$KIND" = soft ] && [ "$BUSY" -ge "$CPU_SATURATED_PCT" ]; then
[ -n "$SATURATED" ] || log "LiveKit not ready (curl exit $RC) with the CPU $BUSY% busy: not counted"
SATURATED=1
FAIL_SINCE=""
else
SATURATED=""
if [ -z "$FAIL_SINCE" ]; then
FAIL_SINCE=$NOW
log "LiveKit health check failed (curl exit $RC)"
fi
LIMIT=$SOFT_UNHEALTHY_AFTER_SEC
[ "$KIND" = hard ] && LIMIT=$HARD_UNHEALTHY_AFTER_SEC
if [ $((NOW - FAIL_SINCE)) -ge "$LIMIT" ]; then
request_replacement "LiveKit unhealthy for $((NOW - FAIL_SINCE)) s (curl exit $RC)"
fi
fi
fi
sleep "$INTERVAL_SEC"
done
mode : "000755"
owner : "root"
group : "root"
'/usr/local/bin/openvidu-media-replace.sh' :
2025-04-22 11:46:24 +02:00
content : !Sub |
#!/bin/bash
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# Ask the Auto Scaling group to replace this Media Node with a new one, keeping the
# count. Called by openvidu-media-health.sh (same cgroup): it never stops or signals
# the watchdog, which re-requests until this node goes away.
mkdir -p /var/lib/openvidu-media-health
echo "$(date -u +%FT%TZ) $1" >> /var/lib/openvidu-media-health/replace-requests.log
# Let stop_media_node.sh know this node is unhealthy, so it does not wait for sessions
touch /run/openvidu-unhealthy
2026-10-16 13:02:11 +02:00
# Withdrawn if the request fails: the node then stays, and if it recovers
# a later scale-in must still wait for its sessions
trap '[ $? -eq 0 ] || rm -f /run/openvidu-unhealthy' EXIT
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
2025-04-22 11:46:24 +02:00
# 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)
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# set-instance-health Unhealthy makes the ASG terminate this node through the existing
# lifecycle hook (drain) and launch a replacement. Idempotent; retry transient API errors.
for attempt in $(seq 1 5); do
if aws autoscaling set-instance-health \
--region ${AWS::Region} \
--instance-id "$INSTANCE_ID" \
--health-status Unhealthy; then
exit 0
fi
echo "[openvidu-media-replace] set-instance-health failed (attempt $attempt/5), retrying in 12s" >&2
sleep 12
done
exit 1
2025-04-22 11:46:24 +02:00
mode : "000755"
owner : "root"
group : "root"
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
'/etc/systemd/system/openvidu-media-health.service' :
content : |
[ Unit]
Description=OpenVidu Media Node health watchdog
After=network-online.target
Wants=network-online.target
[ Service]
Type=simple
ExecStart=/usr/local/bin/openvidu-media-health.sh
Restart=always
RestartSec=30
[ Install]
WantedBy=multi-user.target
mode : "000644"
owner : "root"
group : "root"
2025-04-22 11:46:24 +02:00
'/usr/local/bin/stop_media_node.sh' :
content : !Sub |
#!/bin/bash
set -e
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# Drain: pause and stop the health watchdog so it does not replace this node
touch /run/openvidu-media-health.paused
systemctl stop openvidu-media-health 2>/dev/null || true
2025-04-22 11:46:24 +02:00
# 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-elastic-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..."
2026-09-10 13:13:42 +02:00
docker container kill --signal=SIGQUIT openvidu 2>/dev/null || true
docker container kill --signal=SIGQUIT ingress 2>/dev/null || true
docker container kill --signal=SIGQUIT egress 2>/dev/null || true
for agent_container in $(docker ps --filter "label=openvidu-agent=true" --format '{{.Names}}' 2>/dev/null); do
docker container kill --signal=SIGQUIT "$agent_container" 2>/dev/null || true
2025-06-25 19:03:01 +02:00
done
2025-04-22 11:46:24 +02:00
TIME_PASSED=0
HEARTBEAT_MAX=1800
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# An unhealthy node being replaced should not wait for sessions: kill now
if [ -f /run/openvidu-unhealthy ]; then
docker ps -q | xargs -r docker kill >/dev/null 2>&1 || true
fi
2025-06-25 19:03:01 +02:00
# Wait for running containers to not be openvidu, ingress, egress or an openvidu agent
2026-09-10 13:13:42 +02:00
while [ "$(docker ps --filter 'label=openvidu-agent=true' -q 2>/dev/null | 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
2025-04-22 11:46:24 +02:00
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-elastic-media-${AWS::Region}-${AWS::StackName}
LaunchTemplateData :
# Enable IMDSv2 by default
MetadataOptions :
HttpEndpoint : enabled
HttpPutResponseHopLimit : 1
HttpTokens : required
IamInstanceProfile :
Arn : !GetAtt OpenViduMediaNodeInstanceProfile.Arn
SecurityGroupIds :
- !GetAtt OpenViduMediaNodeSG.GroupId
2025-12-01 20:36:57 +01:00
ImageId : !If
- IsMediaGraviton
openvidu-deployment: Add missing Graviton instances, Nvidia GPU instances, and Nvidia driver AMI selection
- Add missing Graviton/ARM instance families to CloudFormation conditions:
r6g, r6gd, r7g, r7gd, r8g, c8gd, m8gd
- Add Nvidia GPU instance detection conditions (IsNvidia/IsMasterNodeNvidia/
IsMediaNodeNvidia) for families: g4dn, g5, g5g, g6, g6e, g6f, gr6, gr6f,
g7e, p4d, p4de, p5, p5e, p5en, p6-b200, p6-b300, p6e-gb200
- Use Ubuntu Deep Learning AMIs with pre-installed Nvidia drivers when a
GPU instance type is selected, choosing arm64 or x86_64 variant based
on the Graviton condition
2026-02-27 18:12:59 +01:00
- !If
- IsMediaNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/arm64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [ArmImage, !Ref OperatingSystem, ImageId]
- !If
- IsMediaNodeNvidia
- '{{resolve:ssm:/aws/service/deeplearning/ami/x86_64/base-oss-nvidia-driver-gpu-ubuntu-24.04/latest/ami-id}}'
- !FindInMap [AmdImage, !Ref OperatingSystem, ImageId]
2025-04-22 11:46:24 +02:00
KeyName : !Ref KeyName
InstanceType : !Ref MediaNodeInstanceType
UserData :
Fn::Base64 : !Sub |
#!/bin/bash
set -eu -o pipefail
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# Record a media-node bootstrap failure so the health watchdog (started below)
# replaces this node after a cool-down. Route user-data failure exits through this.
ov_media_bootstrap_failed() {
echo "[OpenVidu] media node bootstrap failed: $1"
mkdir -p /var/lib/openvidu-media-health
echo "$1" > /var/lib/openvidu-media-health/bootstrap-failed
exit 1
}
2025-12-16 12:49:34 +01:00
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
2025-04-22 11:46:24 +02:00
apt-get update && apt-get install -y \
python3-pip \
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
ec2-instance-connect \
curl \
unzip
2025-09-06 20:39:44 +02:00
2026-06-16 21:16:00 +02:00
CFN_BOOTSTRAP_VERSION=2.0-39
2025-09-06 20:39:44 +02:00
# Detect Ubuntu version and install cfn-bootstrap accordingly
UBUNTU_VERSION=$(lsb_release -rs | cut -d. -f1)
if [ "$UBUNTU_VERSION" -ge 24 ]; then
2026-03-02 16:45:35 +01:00
python3 -m pip install --break-system-packages https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
2025-09-06 20:39:44 +02:00
else
2026-03-02 16:45:35 +01:00
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
2025-09-06 20:39:44 +02:00
fi
2025-04-22 11:46:24 +02:00
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduMediaNodeLaunchTemplate
export HOME="/root"
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
# aws-cli v2, hoisted from install.sh so the watchdog's replace script can call
# the ASG API even if bootstrap hangs before install.sh runs
AWS_CLI_VERSION=2.36.40
if ! command -v aws &> /dev/null; then
curl "https://awscli.amazonaws.com/awscli-exe-linux-$(uname -m)-${!AWS_CLI_VERSION}.zip" -o "awscliv2.zip"
unzip -qq awscliv2.zip
./aws/install
rm -rf awscliv2.zip aws
fi
# Health watchdog config: master address for the anti-storm Redis PING guard
mkdir -p /etc/openvidu
echo 'MASTER_ADDRS="${OpenViduMasterNode.PrivateIp}:7000"' > /etc/openvidu/media-health.env
# Start the health watchdog before install.sh so a hung bootstrap is covered too
systemctl daemon-reload
systemctl enable --now openvidu-media-health
2025-04-22 11:46:24 +02:00
# Install OpenVidu
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
/usr/local/bin/install.sh || ov_media_bootstrap_failed "install.sh failed"
2025-04-22 11:46:24 +02:00
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
# Wait for the master node to be healthy before starting (bounded: up to 1800s at 5s)
MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp}
MASTER_HEALTHY=false
for i in $(seq 1 360); do
if curl -sf "http://$MASTER_NODE_IP:7880/health/caddy" >/dev/null 2>&1; then
MASTER_HEALTHY=true
break
fi
sleep 5
done
if [ "$MASTER_HEALTHY" != "true" ]; then
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
ov_media_bootstrap_failed "master node did not become healthy after 30 minutes"
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
fi
2025-04-22 11:46:24 +02:00
# Start OpenVidu
openvidu-deployment: replace unhealthy Media Nodes (AWS, GCP, Azure elastic and HA)
Every Media Node runs openvidu-media-health, a watchdog that asks the cloud to
replace it with a new node (keeping the Media Node count) when its bootstrap
failed or never finished, or when LiveKit fails its health check on :7880 for
5 minutes (down) or 10 minutes (errors or timeouts; not counted while the CPU is
saturated). Nothing is replaced while no master answers a Redis PING, so a
master outage cannot replace the whole media fleet. Drain scripts pause the
watchdog, and skip waiting for sessions on a node being replaced.
- AWS: the node marks itself Unhealthy in its Auto Scaling group (the
lifecycle hook drains it and a new instance is launched). aws-cli is
installed before the watchdog starts; set_as_unhealthy.sh is gone.
- GCP: the node asks its managed instance group to recreate it. Media
autohealing (TCP 7880) now waits 3600 s before judging a new node and needs
5 minutes of failures; HA gets the same autohealing plus the firewall rule
for the health check ranges.
- Azure: the node reimages itself (new OS disk, bootstrap runs again, same
instance, scale-in protection kept) instead of deleting itself, which shrank
the scale set. The .json twins are rebuilt from the .bicep.
2026-09-30 17:30:23 +02:00
systemctl start openvidu || ov_media_bootstrap_failed "error starting OpenVidu"
# Bootstrap finished: switch the watchdog from the bootstrap deadline to runtime checks
mkdir -p /var/lib/openvidu-media-health
touch /var/lib/openvidu-media-health/installed
2025-04-22 11:46:24 +02:00
BlockDeviceMappings :
- DeviceName : /dev/sda1
Ebs :
2026-01-15 17:55:52 +01:00
VolumeType : gp3
2025-04-22 11:46:24 +02:00
DeleteOnTermination : true
2026-02-27 23:25:28 +01:00
VolumeSize : !If [IsMediaNodeNvidia, 100, 50]
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeASG :
DependsOn :
openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
- OpenViduMediaNodeInstanceProfile
- OpenViduMasterNodeInstanceProfile
2025-04-22 11:46:24 +02:00
- StopMediaNodeCloudWatchEventRule
Type : AWS::AutoScaling::AutoScalingGroup
Properties :
AutoScalingGroupName : !Sub openvidu-elastic-media-asg-${AWS::Region}-${AWS::StackName}
LaunchTemplate :
LaunchTemplateId : !Ref OpenViduMediaNodeLaunchTemplate
Version : !GetAtt OpenViduMediaNodeLaunchTemplate.DefaultVersionNumber
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
MinSize : !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !Ref MinNumberOfMediaNodes]
MaxSize : !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !Ref MaxNumberOfMediaNodes]
DesiredCapacity : !If [FixedNumberOfMediaNodesEnabled, !Ref FixedNumberOfMediaNodes, !Ref InitialNumberOfMediaNodes]
2025-04-22 11:46:24 +02:00
VPCZoneIdentifier : !Ref OpenViduMediaNodeSubnets
Tags :
- Key : Name
Value : !Sub ${AWS::StackName} - OpenVidu Elastic - 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
2026-01-27 20:00:40 +01:00
Resource :
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-execution/*
- !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:document/${StopMediaNodeAutomationDocument}
2025-04-22 11:46:24 +02:00
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-elastic-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
openvidu-deployment: a fixed number of Media Nodes on AWS, Azure and Google Cloud
Elastic and High Availability deployments on AWS, Azure and Google Cloud can
run a fixed number of Media Nodes, as DigitalOcean and Oracle Cloud already
could, with the same parameter: FixedNumberOfMediaNodes on AWS,
fixedNumberOfMediaNodes on the other clouds. 0, the default, keeps
autoscaling; a value greater than 0 runs exactly that many Media Nodes and
ignores the initial, minimum, maximum and target CPU parameters. A redeploy
with another value changes the number or switches mode, both ways.
- AWS: the Auto Scaling Group's min, max and desired are the fixed number
and the scaling policy is not created. A Media Node the number no longer
needs is drained by the existing lifecycle hook, as in a scale-in.
- Google Cloud: no autoscaler; the scale-in function, which now runs in both
modes, keeps the managed instance group at the fixed number, abandoning an
extra Media Node so it drains and deletes itself, or resizing the group up.
The group's target size is set at creation only, the function's source is
named by its hash so a code change redeploys it, and `moved` blocks keep
existing autoscalers when the template is applied again.
- Azure: the autoscale setting stays, pinned to the fixed number (minimum =
maximum = default). Azure refuses an autoscale minimum below the Media
Nodes running, which protect themselves from scale-in, so lowering it is a
manual drain first (manual scale, then stop_media_node.sh on each extra
Media Node) and then the redeploy; the CHANGELOG and the documentation
describe it. The portal's form gets the field.
Verified live with ov-cloud-tester's sc-fixed-media-nodes scenario on
Elastic (Google Cloud, AWS and Azure): deploy with 2, lower to 1 (the Media
Node removed served its Room through a 1200 s hold and left once it was
closed), back to autoscaling with no Media Node added or removed.
2026-10-27 19:09:59 +01:00
Condition : AutoscalingEnabled
2025-04-22 11:46:24 +02:00
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-elastic-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
- 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
2025-10-10 14:51:55 +02:00
OpenViduMediaNodeToMasterNodeClusterPortIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 7880
ToPort : 7880
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeToMasterNodeRedisIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 7000
ToPort : 7000
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterNodeMinioIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 9100
ToPort : 9100
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterNodeMongoIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 20000
ToPort : 20000
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterNodeLokiIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 3100
ToPort : 3100
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterNodeMimirIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 9009
ToPort : 9009
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
OpenViduMediaNodeToMasterV2CompatibilityWebhookIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 4443
ToPort : 4443
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
2025-07-22 14:08:25 +02:00
OpenViduMediaNodeToMasterMeetWebhookIngress :
2025-04-22 11:46:24 +02:00
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
2026-02-05 12:30:22 +01:00
FromPort : 9080
ToPort : 9080
2025-04-22 11:46:24 +02:00
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
2026-03-13 17:46:29 +01:00
OpenViduMediaNodeToMasterCustomAppIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMasterNodeSG.GroupId
IpProtocol : tcp
FromPort : 6080
ToPort : 6080
SourceSecurityGroupId : !GetAtt OpenViduMediaNodeSG.GroupId
2025-04-22 11:46:24 +02:00
OpenViduMediaNodeSG :
Type : AWS::EC2::SecurityGroup
Properties :
GroupDescription : Security group for OpenVidu Media Node
GroupName : !Sub openvidu-elastic-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
2025-07-11 21:33:05 +02:00
- IpProtocol : tcp
FromPort : 7881
ToPort : 7881
CidrIp : 0.0 .0 .0 /0
- IpProtocol : tcp
FromPort : 7881
ToPort : 7881
CidrIpv6 : : : /0
2025-04-22 11:46:24 +02:00
- 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
2025-07-11 21:33:05 +02:00
- IpProtocol : tcp
FromPort : 50000
ToPort : 60000
CidrIp : 0.0 .0 .0 /0
- IpProtocol : tcp
FromPort : 50000
ToPort : 60000
CidrIpv6 : : : /0
2025-04-22 11:46:24 +02:00
OpenViduMasterNodeToMediaNodeRTMPIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMediaNodeSG.GroupId
IpProtocol : tcp
FromPort : 1935
ToPort : 1935
SourceSecurityGroupId : !GetAtt OpenViduMasterNodeSG.GroupId
OpenViduMasterNodeTurnTLSToMediaNodeIngressSG :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !Ref OpenViduMediaNodeSG
IpProtocol : tcp
FromPort : 5349
ToPort : 5349
SourceSecurityGroupId : !GetAtt OpenViduMasterNodeSG.GroupId
OpenViduMasterNodeToMediaNodeServerIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMediaNodeSG.GroupId
IpProtocol : tcp
FromPort : 7880
ToPort : 7880
SourceSecurityGroupId : !GetAtt OpenViduMasterNodeSG.GroupId
OpenViduMasterToMediaNodeHTTPWHIPIngress :
Type : AWS::EC2::SecurityGroupIngress
Properties :
GroupId : !GetAtt OpenViduMediaNodeSG.GroupId
IpProtocol : tcp
FromPort : 8080
ToPort : 8080
SourceSecurityGroupId : !GetAtt OpenViduMasterNodeSG.GroupId
ElasticIPAssociation :
Type : AWS::EC2::EIPAssociation
Condition : PublicElasticIPPresent
Properties :
InstanceId : !Ref OpenViduMasterNode
EIP : !Ref PublicElasticIP
Outputs :
ServicesAndCredentials :
Description : Services and credentials
Value : !Sub https://${AWS::Region}.console.aws.amazon.com/secretsmanager/home?region=${AWS::Region}#!/secret?name=openvidu-elastic-${AWS::Region}-${AWS::StackName}