2026-06-09 11:37:02 +02:00
@ description ( 'Stack name. Maximum 15 characters: used as the Azure Key Vault name prefix ("<stackName>-keyvault"), which is limited to 24 characters.' )
@ minLength ( 3)
@ maxLength ( 15)
2025-04-22 11:46:24 +02:00
param stackName string
@ description ( '' '
2025-09-09 22:06:36 +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
'' ' )
@ allowed ( [
'selfsigned'
'owncert'
'letsencrypt'
] )
2025-09-09 22:06:36 +02:00
param certificateType string = 'letsencrypt'
2025-04-22 11:46:24 +02:00
2025-05-13 11:01:22 +02:00
@ description ( 'Domain name for the OpenVidu Deployment.' )
2025-09-09 22:06:36 +02:00
param domainName string = ''
2025-04-22 11:46:24 +02:00
2025-12-23 22:11:16 +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
param ownPublicCertificate string = ''
2025-12-23 22:11:16 +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
param ownPrivateCertificate string = ''
@ description ( 'Name of the PublicIPAddress resource in Azure when using certificateType \' owncert\ ' or \' letsencrypt\ '' )
2025-05-23 13:19:59 +02:00
param publicIpAddressObject object
2025-04-22 11:46:24 +02:00
@ description ( 'Visit https://openvidu.io/account' )
@ secure ( )
param openviduLicense string
@ description ( 'RTCEngine media engine to use' )
@ allowed ( [
'pion'
'mediasoup'
] )
2026-09-29 14:56:16 +02:00
param rtcEngine string = 'mediasoup'
2025-04-22 11:46:24 +02:00
2025-09-16 17:52:10 +02:00
@ description ( 'Initial password for the \' admin\ ' user in OpenVidu Meet. If not provided, a random password will be generated.' )
@ secure ( )
param initialMeetAdminPassword string = ''
@ 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.' )
@ secure ( )
param initialMeetApiKey string = ''
2025-12-02 19:21:16 +01:00
@ description ( 'Specifies the VM size for your OpenVidu Master Node. You can use any valid Azure VM size (e.g., Standard_B4s, Standard_D4s_v5, Standard_E4ps_v5). See https://learn.microsoft.com/en-us/azure/virtual-machines/sizes for available sizes.' )
param masterNodeInstanceType string = 'Standard_B4s'
2025-04-22 11:46:24 +02:00
@ description ( 'Size of the disk in GB' )
openvidu-deployment: the same defaults and parameter names on every cloud
The templates of the five clouds had drifted apart. On AWS, Azure and Google
Cloud:
- 200 GB disk for the single node and the Master Nodes, the default where
the template sets its size: Azure and Google Cloud raise it from 100 GB,
as do the AWS High Availability Master Nodes. On Google Cloud an existing
single node or Elastic Master Node keeps its disk when the template is
applied again.
- Google Cloud boots the latest Ubuntu 24.04 (the ubuntu-2404-lts image
family) instead of an image pinned in December 2024; an existing single
node or Master Node is not recreated when a newer image is published.
- Same parameter names on every Terraform and Bicep template, with the old
names deprecated but still honored (they win when set): rtcEngine on the
Google Cloud single node, whose documented name was not applied, and
masterNodeInstanceType on Google Cloud High Availability.
- Azure no longer opens port 9000 (MinIO) on the single node and Elastic:
MinIO does not run there, and the other clouds closed it in 3.9.0.
- The Media Nodes install with the same arguments on every cloud: Azure
passes --master-node-private-ip and no longer --enabled-modules, and the
user-data names the variable MASTER_NODE_PRIVATE_IP everywhere.
The CHANGELOG also lists the DigitalOcean and Oracle Cloud changes made in
their repositories (v2compatibility by default, masterNodeInstanceType,
the Oracle single node size and disk, rtcEngine).
2026-10-27 19:11:15 +01:00
param masterNodesDiskSize int = 200
2025-04-22 11:46:24 +02:00
2025-12-02 19:21:16 +01:00
@ description ( 'Specifies the VM size for your OpenVidu Media Nodes. You can use any valid Azure VM size (e.g., Standard_B4s, Standard_D4s_v5, Standard_E4ps_v5). See https://learn.microsoft.com/en-us/azure/virtual-machines/sizes for available sizes.' )
param mediaNodeInstanceType string = 'Standard_B4s'
2025-04-22 11:46:24 +02:00
@ description ( 'Username for the Virtual Machine.' )
param adminUsername string
@ description ( 'SSH Key for the Virtual Machine.' )
@ secure ( )
2025-05-23 13:19:59 +02:00
param adminSshKey object
2025-04-22 11:46:24 +02:00
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
param initialNumberOfMediaNodes int = 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
param minNumberOfMediaNodes int = 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 ( 'Maximum number of media nodes to deploy. Ignored when fixedNumberOfMediaNodes > 0' )
2025-04-22 11:46:24 +02:00
param maxNumberOfMediaNodes int = 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 ( 'Target CPU percentage to scale up or down. Ignored when fixedNumberOfMediaNodes > 0' )
2025-04-22 11:46:24 +02:00
param scaleTargetCPU int = 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 ( 'If > 0, keeps this fixed number of media nodes: autoscaling is pinned to it (minimum = maximum = this number) and initialNumberOfMediaNodes, minNumberOfMediaNodes, maxNumberOfMediaNodes and scaleTargetCPU are ignored. When it is lowered, the extra media nodes are drained gracefully, one at a time, by the scale-in runbook. If 0 (default), the deployment is elastic and autoscaling is enabled' )
@ minValue ( 0)
param fixedNumberOfMediaNodes int = 0
2025-06-18 16:26:50 +02:00
param additionalInstallFlags string = ''
2025-04-22 11:46:24 +02:00
/*------------------------------------------- VARIABLES AND VALIDATIONS -------------------------------------------*/
2025-09-09 22:06:36 +02:00
var isEmptyIp = publicIpAddressObject. newOrExistingOrNone == 'none'
var isEmptyDomain = domainName == ''
2025-12-02 19:21:16 +01:00
// ARM64 instances are detected by checking for 'p' in the instance type name pattern.
// Azure ARM-based VMs use 'p' to indicate ARM processors (Ampere Altra, Microsoft Cobalt, etc.)
// Examples: Standard_D2ps_v5, Standard_E4pds_v5, Standard_B2pls_v2, etc.
// The pattern checks for 'p' followed by optional letters (like 'l', 'd', 's') before '_v' version suffix
var masterNodeInstanceTypeLower = toLower ( masterNodeInstanceType)
var mediaNodeInstanceTypeLower = toLower ( mediaNodeInstanceType)
2026-01-29 17:34:25 +01:00
var isMasterArm64 = contains ( masterNodeInstanceTypeLower, 'ps_v' ) | | contains ( masterNodeInstanceTypeLower, 'pls_v' ) | | contains (
masterNodeInstanceTypeLower,
'pds_v'
) | | contains ( masterNodeInstanceTypeLower, 'plds_v' ) | | contains ( masterNodeInstanceTypeLower, 'psv' ) | | contains (
masterNodeInstanceTypeLower,
'plsv'
) | | contains ( masterNodeInstanceTypeLower, 'pdsv' ) | | contains ( masterNodeInstanceTypeLower, 'pldsv' )
var isMediaArm64 = contains ( mediaNodeInstanceTypeLower, 'ps_v' ) | | contains ( mediaNodeInstanceTypeLower, 'pls_v' ) | | contains (
mediaNodeInstanceTypeLower,
'pds_v'
) | | contains ( mediaNodeInstanceTypeLower, 'plds_v' ) | | contains ( mediaNodeInstanceTypeLower, 'psv' ) | | contains (
mediaNodeInstanceTypeLower,
'plsv'
) | | contains ( mediaNodeInstanceTypeLower, 'pdsv' ) | | contains ( mediaNodeInstanceTypeLower, 'pldsv' )
2025-12-02 19:21:16 +01:00
2025-12-02 20:04:38 +01:00
var masterUbuntuSku = isMasterArm64 ? 'server-arm64' : 'server'
var mediaUbuntuSku = isMediaArm64 ? 'server-arm64' : 'server'
2025-12-02 18:29:44 +01:00
2025-04-22 11:46:24 +02:00
var masterNodeVMSettings = {
osDiskType : 'StandardSSD_LRS'
osDiskSize : masterNodesDiskSize
ubuntuOSVersion : {
publisher : 'Canonical'
2025-12-02 20:04:38 +01:00
offer : 'ubuntu-24_04-lts'
2025-12-02 18:29:44 +01:00
sku : masterUbuntuSku
2025-04-22 11:46:24 +02:00
version : 'latest'
}
linuxConfiguration : {
disablePasswordAuthentication : true
ssh : {
publicKeys : [
{
path : '/home/${adminUsername}/.ssh/authorized_keys'
2025-05-23 13:19:59 +02:00
keyData : adminSshKey. sshPublicKey
2025-04-22 11:46:24 +02:00
}
]
}
}
}
var mediaNodeVMSettings = {
vmName : '${stackName}-VN-MediaNode'
osDiskType : 'StandardSSD_LRS'
ubuntuOSVersion : {
publisher : 'Canonical'
2025-12-02 20:04:38 +01:00
offer : 'ubuntu-24_04-lts'
2025-12-02 18:29:44 +01:00
sku : mediaUbuntuSku
2025-04-22 11:46:24 +02:00
version : 'latest'
}
linuxConfiguration : {
disablePasswordAuthentication : true
ssh : {
publicKeys : [
{
path : '/home/${adminUsername}/.ssh/authorized_keys'
2025-05-23 13:19:59 +02:00
keyData : adminSshKey. sshPublicKey
2025-04-22 11:46:24 +02:00
}
]
}
}
}
var keyVaultName = '${stackName}-keyvault'
var location = resourceGroup ( ) . location
var tenantId = subscription ( ) . tenantId
var deploymentUser = az. deployer( ) . objectId
2026-07-29 11:56:08 +02:00
/*------------------------------------------- MANAGED IDENTITIES -------------------------------------------*/
// Split in two identities to keep the permission asymmetry: masters write secrets, media nodes only read them
resource masterIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name : '${stackName}-master-identity'
location : location
}
resource mediaIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name : '${stackName}-media-identity'
location : location
}
2025-04-22 11:46:24 +02:00
/*------------------------------------------- KEY VAULT -------------------------------------------*/
resource openviduSharedInfo 'Microsoft.KeyVault/vaults@2023-07-01' = {
name : keyVaultName
location : location
properties : {
enabledForDeployment : true
enabledForDiskEncryption : false
enabledForTemplateDeployment : true
tenantId : tenantId
enableSoftDelete : false
accessPolicies : [
2026-07-29 11:56:08 +02:00
// Pre-created identities: the vault no longer waits for the VMs to exist
2025-04-22 11:46:24 +02:00
{
2026-07-29 11:56:08 +02:00
objectId : masterIdentity. properties. principalId
2025-04-22 11:46:24 +02:00
tenantId : tenantId
permissions : {
secrets : [ 'get' , 'set' , 'list' ]
}
}
{
2026-07-29 11:56:08 +02:00
objectId : mediaIdentity. properties. principalId
2025-04-22 11:46:24 +02:00
tenantId : tenantId
permissions : {
secrets : [ 'get' ]
}
}
{
objectId : deploymentUser
tenantId : tenantId
permissions : {
secrets : [ 'get' , 'list' , 'set' , 'delete' , 'recover' , 'backup' , 'restore' ]
}
}
]
sku : {
name : 'standard'
family : 'A'
}
networkAcls : {
defaultAction : 'Allow'
bypass : 'AzureServices'
}
}
}
/*------------------------------------------- MASTER NODE -------------------------------------------*/
var stringInterpolationParamsMaster1 = {
2025-09-09 22:06:36 +02:00
publicIPId : publicIPId
2025-04-22 11:46:24 +02:00
domainName : domainName
certificateType : certificateType
ownPublicCertificate : ownPublicCertificate
ownPrivateCertificate : ownPrivateCertificate
openviduLicense : openviduLicense
rtcEngine : rtcEngine
2025-09-16 17:52:10 +02:00
initialMeetAdminPassword : initialMeetAdminPassword
initialMeetApiKey : initialMeetApiKey
2025-04-22 11:46:24 +02:00
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '1'
2025-06-18 16:26:50 +02:00
additionalInstallFlags : additionalInstallFlags
2025-04-22 11:46:24 +02:00
}
var stringInterpolationParamsMaster2 = {
2025-09-09 22:06:36 +02:00
publicIPId : publicIPId
2025-04-22 11:46:24 +02:00
domainName : domainName
certificateType : certificateType
ownPublicCertificate : ownPublicCertificate
ownPrivateCertificate : ownPrivateCertificate
openviduLicense : openviduLicense
rtcEngine : rtcEngine
2025-09-16 17:52:10 +02:00
initialMeetAdminPassword : initialMeetAdminPassword
initialMeetApiKey : initialMeetApiKey
2025-04-22 11:46:24 +02:00
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '2'
2025-06-18 16:26:50 +02:00
additionalInstallFlags : additionalInstallFlags
2025-04-22 11:46:24 +02:00
}
var stringInterpolationParamsMaster3 = {
2025-09-09 22:06:36 +02:00
publicIPId : publicIPId
2025-04-22 11:46:24 +02:00
domainName : domainName
certificateType : certificateType
ownPublicCertificate : ownPublicCertificate
ownPrivateCertificate : ownPrivateCertificate
openviduLicense : openviduLicense
rtcEngine : rtcEngine
2025-09-16 17:52:10 +02:00
initialMeetAdminPassword : initialMeetAdminPassword
initialMeetApiKey : initialMeetApiKey
2025-04-22 11:46:24 +02:00
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '3'
2025-06-18 16:26:50 +02:00
additionalInstallFlags : additionalInstallFlags
2025-04-22 11:46:24 +02:00
}
var stringInterpolationParamsMaster4 = {
2025-09-09 22:06:36 +02:00
publicIPId : publicIPId
2025-04-22 11:46:24 +02:00
domainName : domainName
certificateType : certificateType
ownPublicCertificate : ownPublicCertificate
ownPrivateCertificate : ownPrivateCertificate
openviduLicense : openviduLicense
rtcEngine : rtcEngine
2025-09-16 17:52:10 +02:00
initialMeetAdminPassword : initialMeetAdminPassword
initialMeetApiKey : initialMeetApiKey
2025-04-22 11:46:24 +02:00
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '4'
2025-06-18 16:26:50 +02:00
additionalInstallFlags : additionalInstallFlags
2025-04-22 11:46:24 +02:00
}
var installScriptTemplateMaster = '' '
# ! / bin/ bash - x
set - e
2026-09-29 18:27:35 +02:00
OPENVIDU_VERSION= main
2025-04-22 11:46:24 +02:00
DOMAIN=
# Assume azure cli is installed
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 \
curl \
unzip \
jq \
wget
# Wait for the keyvault availability
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
MAX_WAIT= 300
2025-04-22 11:46:24 +02:00
WAIT_INTERVAL= 1
ELAPSED_TIME= 0
set + e
while true; do
# Check keyvault availability
az keyvault secret list - - vault-name $ { keyVaultName}
# If it is available, exit the loop
if [ $ ? - eq 0 ] ; then
break
fi
# If not, wait and check again incrementing the time
ELAPSED_TIME= $ ( ( ELAPSED_TIME + WAIT_INTERVAL) )
# If exceeded the maximum time, exit with error
if [ $ ELAPSED_TIME - ge $ MAX_WAIT ] ; then
exit 1
fi
# Esperar antes de la pró xima comprobació n
sleep $ WAIT_INTERVAL
done
set - e
MASTER_NODE_NUM= $ { masterNodeNum}
# Get own private IP
PRIVATE_IP= $ ( curl - H Metadata : true - - noproxy " * " " http : //169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text")
# Store current private IP
PRIVATE_IP= " $ ( / usr/ local/ bin/ store_secret. sh save MASTER-NODE-$ { masterNodeNum} - PRIVATE-IP $ PRIVATE_IP) "
if [ [ $ MASTER_NODE_NUM - eq 1 ] ] && [ [ " $ ALL_SECRETS_GENERATED" == " " | | " $ ALL_SECRETS_GENERATED" == " false" ] ] ; then
2025-09-09 22:06:36 +02:00
# Configure Domain name
if [ [ " $ { domainName} " == '' ] ] ; then
# Get public IP using the get_public_ip. sh script
PUBLIC_IP= $ ( / usr/ local/ bin/ get_public_ip. sh 2> / dev/ null)
if [ [ $ ? - ne 0 | | - z " $ { PUBLIC_IP} " ] ] ; then
echo " Could not determine public IP. "
exit 1
fi
2026-07-02 17:21:42 +02:00
DOMAIN= " $ PUBLIC_IP"
2025-09-09 22:06:36 +02:00
else
DOMAIN= $ { domainName}
fi
2025-09-11 21:03:11 +02:00
DOMAIN= " $ ( / usr/ local/ bin/ store_secret. sh save DOMAIN-NAME " $ { DOMAIN} " ) "
2025-09-09 22:06:36 +02:00
2026-07-08 17:32:27 +02:00
# Publish service URLs early ( idempotent; after_install re-writes byte-identical values)
OPENVIDU_URL= " $ ( / usr/ local/ bin/ store_secret. sh save OPENVIDU-URL " https : //${DOMAIN}/")"
LIVEKIT_URL= " $ ( / usr/ local/ bin/ store_secret. sh save LIVEKIT-URL " wss : //${DOMAIN}/")"
DASHBOARD_URL= " $ ( / usr/ local/ bin/ store_secret. sh save DASHBOARD-URL " https : //${DOMAIN}/dashboard/")"
GRAFANA_URL= " $ ( / usr/ local/ bin/ store_secret. sh save GRAFANA-URL " https : //${DOMAIN}/grafana/")"
MINIO_URL= " $ ( / usr/ local/ bin/ store_secret. sh save MINIO-URL " https : //${DOMAIN}/minio-console/")"
2025-09-16 17:52:10 +02:00
# 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
# Store usernames and generate random passwords
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} " ) "
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) "
2025-09-11 14:39:41 +02:00
MEET_INITIAL_ADMIN_USER= " $ ( / usr/ local/ bin/ store_secret. sh save MEET-INITIAL-ADMIN-USER " admin" ) "
2025-04-22 11:46:24 +02:00
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) "
OPENVIDU_VERSION= " $ ( / usr/ local/ bin/ store_secret. sh save OPENVIDU-VERSION " $ { OPENVIDU_VERSION} " ) "
2025-07-22 14:00:56 +02:00
ENABLED_MODULES= " $ ( / usr/ local/ bin/ store_secret. sh save ENABLED-MODULES " observability, openviduMeet, v2compatibility" ) "
2025-04-22 11:46:24 +02:00
ALL_SECRETS_GENERATED= " $ ( / usr/ local/ bin/ store_secret. sh save ALL-SECRETS-GENERATED " true" ) "
fi
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
IP_WAIT_MAX_RETRIES= 360
IP_WAIT_INTERVAL= 5
IP_WAIT_RETRIES= 0
2025-04-22 11:46:24 +02:00
while true; do
MASTER_NODE_1_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-1-PRIVATE-IP - - query value - o tsv) | | true
MASTER_NODE_2_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-2-PRIVATE-IP - - query value - o tsv) | | true
MASTER_NODE_3_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-3-PRIVATE-IP - - query value - o tsv) | | true
MASTER_NODE_4_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-4-PRIVATE-IP - - query value - o tsv) | | true
# Check if all master nodes have stored their private IPs
if [ [ " $ MASTER_NODE_1_PRIVATE_IP" != " " ] ] &&
[ [ " $ MASTER_NODE_2_PRIVATE_IP" != " " ] ] &&
[ [ " $ MASTER_NODE_3_PRIVATE_IP" != " " ] ] &&
[ [ " $ MASTER_NODE_4_PRIVATE_IP" != " " ] ] ; then
break
fi
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
IP_WAIT_RETRIES= $ ( ( IP_WAIT_RETRIES + 1) )
if [ $ IP_WAIT_RETRIES - ge $ IP_WAIT_MAX_RETRIES ] ; then
echo " [ OpenVidu] timed out after 30 min waiting for the 4 master nodes to publish their private IPs"
exit 1
fi
sleep $ IP_WAIT_INTERVAL
2025-04-22 11:46:24 +02:00
done
2026-07-24 17:11:58 +02:00
# Wait until master-node-1 has generated all shared secrets before fetching them
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
SECRETS_WAIT_MAX_RETRIES= 360
SECRETS_WAIT_INTERVAL= 5
SECRETS_WAIT_RETRIES= 0
while [ [ " $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name ALL-SECRETS-GENERATED - - query value - o tsv 2> / dev/ null) " != " true" ] ] ; do
SECRETS_WAIT_RETRIES= $ ( ( SECRETS_WAIT_RETRIES + 1) )
if [ $ SECRETS_WAIT_RETRIES - ge $ SECRETS_WAIT_MAX_RETRIES ] ; then
echo " [ OpenVidu] timed out after 30 min waiting for ALL-SECRETS-GENERATED to become true"
exit 1
fi
sleep $ SECRETS_WAIT_INTERVAL
done
2026-07-08 17:32:27 +02:00
2025-04-22 11:46:24 +02:00
# Fetch the values in the keyvault
MASTER_NODE_1_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-1-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_2_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-2-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_3_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-3-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_4_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-4-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_PRIVATE_IP_LIST= " $ MASTER_NODE_1_PRIVATE_IP, $ MASTER_NODE_2_PRIVATE_IP, $ MASTER_NODE_3_PRIVATE_IP, $ MASTER_NODE_4_PRIVATE_IP"
DOMAIN= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - query value - o tsv)
OPENVIDU_RTC_ENGINE= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name OPENVIDU-RTC-ENGINE - - query value - o tsv)
OPENVIDU_PRO_LICENSE= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name OPENVIDU-PRO-LICENSE - - query value - o tsv)
REDIS_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name REDIS-PASSWORD - - query value - o tsv)
MONGO_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-USERNAME - - query value - o tsv)
MONGO_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-PASSWORD - - query value - o tsv)
MONGO_REPLICA_SET_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-REPLICA-SET-KEY - - query value - o tsv)
DASHBOARD_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-USERNAME - - query value - o tsv)
DASHBOARD_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-PASSWORD - - query value - o tsv)
MINIO_ACCESS_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MINIO-ACCESS-KEY - - query value - o tsv)
MINIO_SECRET_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MINIO-SECRET-KEY - - query value - o tsv)
GRAFANA_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-USERNAME - - query value - o tsv)
GRAFANA_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-PASSWORD - - query value - o tsv)
LIVEKIT_API_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name LIVEKIT-API-KEY - - query value - o tsv)
LIVEKIT_API_SECRET= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name LIVEKIT-API-SECRET - - query value - o tsv)
2025-08-28 13:58:03 +02:00
MEET_INITIAL_ADMIN_USER= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-USER - - query value - o tsv)
MEET_INITIAL_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-PASSWORD - - query value - o tsv)
2025-09-16 17:52:10 +02:00
if [ [ " $ { initialMeetApiKey} " != '' ] ] ; then
MEET_INITIAL_API_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-API-KEY - - query value - o tsv)
else
MEET_INITIAL_API_KEY= " "
fi
2025-04-22 11:46:24 +02:00
ENABLED_MODULES= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name ENABLED-MODULES - - query value - o tsv)
2026-07-24 17:11:58 +02:00
# Download first : sh < ( curl . . . ) would silently run an empty script on a transient curl failure
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
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/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
if [ ! - s " $ INSTALLER_SCRIPT" ] ; then
echo " [ OpenVidu] failed to download the master node installer script"
exit 1
fi
2025-04-22 11:46:24 +02:00
# Base command
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
INSTALL_COMMAND= " sh $ INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS= (
" - - no-tty"
" - - install"
" - - environment = azure"
" - - deployment - type= 'ha' "
" - - node-role= 'master-node' "
" - - external-load-balancer"
" - - internal-tls-termination"
" - - master-node-private-ip-list= '$MASTER_NODE_PRIVATE_IP_LIST' "
" - - openvidu-pro-license= '$OPENVIDU_PRO_LICENSE' "
" - - domain-name= '$DOMAIN' "
" - - enabled-modules= '$ENABLED_MODULES' "
" - - rtc-engine= $ OPENVIDU_RTC_ENGINE"
" - - redis-password= $ REDIS_PASSWORD"
" - - mongo-admin-user= $ MONGO_ADMIN_USERNAME"
" - - mongo-admin-password= $ MONGO_ADMIN_PASSWORD"
" - - mongo-replica-set-key= $ MONGO_REPLICA_SET_KEY"
" - - minio-access-key= $ MINIO_ACCESS_KEY"
" - - minio-secret-key= $ MINIO_SECRET_KEY"
" - - dashboard-admin-user= $ DASHBOARD_ADMIN_USERNAME"
" - - dashboard-admin-password= $ DASHBOARD_ADMIN_PASSWORD"
" - - grafana-admin-user= $ GRAFANA_ADMIN_USERNAME"
" - - grafana-admin-password= $ GRAFANA_ADMIN_PASSWORD"
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-18 16:26:50 +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 22:11:16 +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
FINAL_COMMAND= " $ INSTALL_COMMAND $ ( printf " % s " " $ { COMMON_ARGS[ @ ] } " ) $ ( printf " % s " " $ { CERT_ARGS[ @ ] } " ) "
# Install OpenVidu
2025-09-16 17:52:10 +02:00
exec bash - c " $ FINAL_COMMAND"
2025-04-22 11:46:24 +02:00
'' '
var after_installScriptTemplateMaster = '' '
# ! / bin/ bash
set - e
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions > / dev/ null
2025-04-22 11:46:24 +02:00
# Generate URLs
DOMAIN= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - query value - o tsv)
2026-01-29 17:59:19 +01: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
az keyvault secret set - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - value $ DOMAIN
2026-01-29 17:34:25 +01:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name OPENVIDU-URL - - value $ OPENVIDU_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name LIVEKIT-URL - - value $ LIVEKIT_URL
2025-04-22 11:46:24 +02:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name DASHBOARD-URL - - value $ DASHBOARD_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name GRAFANA-URL - - value $ GRAFANA_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name MINIO-URL - - value $ MINIO_URL
az keyvault secret show - - vault-name $ { keyVaultName} - - name MINIO-URL
if [ [ $ ? - ne 0 ] ] ; then
echo " Error updating keyvault"
fi
'' '
var update_config_from_secretScriptTemplateMaster = '' '
# ! / bin/ bash
set - e
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions > / dev/ null
2025-04-22 11:46:24 +02:00
# 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= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - query value - o tsv)
if [ [ - n " $ DOMAIN" ] ] ; then
sed - i " s/ DOMAIN_NAME= . * / DOMAIN_NAME= $ DOMAIN/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
else
exit 1
fi
# Get the rest of the values
export REDIS_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name REDIS-PASSWORD - - query value - o tsv)
export OPENVIDU_RTC_ENGINE= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name OPENVIDU-RTC-ENGINE - - query value - o tsv)
export OPENVIDU_PRO_LICENSE= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name OPENVIDU-PRO-LICENSE - - query value - o tsv)
export MONGO_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-USERNAME - - query value - o tsv)
export MONGO_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-PASSWORD - - query value - o tsv)
export MONGO_REPLICA_SET_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MONGO-REPLICA-SET-KEY - - query value - o tsv)
export DASHBOARD_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-USERNAME - - query value - o tsv)
export DASHBOARD_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-PASSWORD - - query value - o tsv)
export MINIO_ACCESS_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MINIO-ACCESS-KEY - - query value - o tsv)
export MINIO_SECRET_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MINIO-SECRET-KEY - - query value - o tsv)
export GRAFANA_ADMIN_USERNAME= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-USERNAME - - query value - o tsv)
export GRAFANA_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-PASSWORD - - query value - o tsv)
export LIVEKIT_API_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name LIVEKIT-API-KEY - - query value - o tsv)
export LIVEKIT_API_SECRET= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name LIVEKIT-API-SECRET - - query value - o tsv)
2025-08-28 13:58:03 +02:00
export MEET_INITIAL_ADMIN_USER= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-USER - - query value - o tsv)
export MEET_INITIAL_ADMIN_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-PASSWORD - - query value - o tsv)
2025-09-16 17:52:10 +02:00
if [ [ " $ { initialMeetApiKey} " != '' ] ] ; then
export MEET_INITIAL_API_KEY= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MEET-INITIAL-API-KEY - - query value - o tsv)
fi
2025-04-22 11:46:24 +02:00
export ENABLED_MODULES= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name ENABLED-MODULES - - query value - o tsv)
# Replace rest of the values
sed - i " s/ REDIS_PASSWORD= . * / REDIS_PASSWORD= $ REDIS_PASSWORD/ " " $ { MASTER_NODE_CONFIG_DIR} / master_node. env"
sed - i " s/ OPENVIDU_RTC_ENGINE= . * / OPENVIDU_RTC_ENGINE= $ OPENVIDU_RTC_ENGINE/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ OPENVIDU_PRO_LICENSE= . * / OPENVIDU_PRO_LICENSE= $ OPENVIDU_PRO_LICENSE/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ MONGO_ADMIN_USERNAME= . * / MONGO_ADMIN_USERNAME= $ MONGO_ADMIN_USERNAME/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ MONGO_ADMIN_PASSWORD= . * / MONGO_ADMIN_PASSWORD= $ MONGO_ADMIN_PASSWORD/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ MONGO_REPLICA_SET_KEY= . * / MONGO_REPLICA_SET_KEY= $ MONGO_REPLICA_SET_KEY/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ DASHBOARD_ADMIN_USERNAME= . * / DASHBOARD_ADMIN_USERNAME= $ DASHBOARD_ADMIN_USERNAME/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ DASHBOARD_ADMIN_PASSWORD= . * / DASHBOARD_ADMIN_PASSWORD= $ DASHBOARD_ADMIN_PASSWORD/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ MINIO_ACCESS_KEY= . * / MINIO_ACCESS_KEY= $ MINIO_ACCESS_KEY/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ MINIO_SECRET_KEY= . * / MINIO_SECRET_KEY= $ MINIO_SECRET_KEY/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ GRAFANA_ADMIN_USERNAME= . * / GRAFANA_ADMIN_USERNAME= $ GRAFANA_ADMIN_USERNAME/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ GRAFANA_ADMIN_PASSWORD= . * / GRAFANA_ADMIN_PASSWORD= $ GRAFANA_ADMIN_PASSWORD/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ LIVEKIT_API_KEY= . * / LIVEKIT_API_KEY= $ LIVEKIT_API_KEY/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s/ LIVEKIT_API_SECRET= . * / LIVEKIT_API_SECRET= $ 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= $ MEET_INITIAL_ADMIN_USER/ " " $ { CLUSTER_CONFIG_DIR} / master_node/ meet. env"
sed - i " s/ MEET_INITIAL_ADMIN_PASSWORD= . * / MEET_INITIAL_ADMIN_PASSWORD= $ MEET_INITIAL_ADMIN_PASSWORD/ " " $ { CLUSTER_CONFIG_DIR} / master_node/ meet. env"
2025-09-16 17:52:10 +02:00
if [ [ " $ { initialMeetApiKey} " != '' ] ] ; then
sed - i " s/ MEET_INITIAL_API_KEY= . * / MEET_INITIAL_API_KEY= $ 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= $ ENABLED_MODULES/ " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
# Update URLs in secret
2026-02-03 13:23:28 +01: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
az keyvault secret set - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - value $ DOMAIN
2026-02-03 13:23:28 +01:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name OPENVIDU-URL - - value $ OPENVIDU_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name LIVEKIT-URL - - value $ LIVEKIT_URL
2025-04-22 11:46:24 +02:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name DASHBOARD-URL - - value $ DASHBOARD_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name GRAFANA-URL - - value $ GRAFANA_URL
az keyvault secret set - - vault-name $ { keyVaultName} - - name MINIO-URL - - value $ MINIO_URL
'' '
var update_secret_from_configScriptTemplateMaster = '' '
# ! / bin/ bash
set - e
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions > / dev/ null
2025-04-22 11:46:24 +02:00
# Installation directory
INSTALL_DIR= " / opt/ openvidu"
CLUSTER_CONFIG_DIR= " $ { INSTALL_DIR} / config/ cluster"
MASTER_NODE_CONFIG_DIR= " $ { INSTALL_DIR} / config/ node"
# Get current values of the config
REDIS_PASSWORD= " $ ( / usr/ local/ bin/ get_value_from_config. sh REDIS_PASSWORD " $ { MASTER_NODE_CONFIG_DIR} / master_node. env" ) "
DOMAIN_NAME= " $ ( / usr/ local/ bin/ get_value_from_config. sh DOMAIN_NAME " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
OPENVIDU_RTC_ENGINE= " $ ( / usr/ local/ bin/ get_value_from_config. sh OPENVIDU_RTC_ENGINE " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
OPENVIDU_PRO_LICENSE= " $ ( / usr/ local/ bin/ get_value_from_config. sh OPENVIDU_PRO_LICENSE " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
MONGO_ADMIN_USERNAME= " $ ( / usr/ local/ bin/ get_value_from_config. sh MONGO_ADMIN_USERNAME " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
MONGO_ADMIN_PASSWORD= " $ ( / usr/ local/ bin/ get_value_from_config. sh MONGO_ADMIN_PASSWORD " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
MONGO_REPLICA_SET_KEY= " $ ( / usr/ local/ bin/ get_value_from_config. sh MONGO_REPLICA_SET_KEY " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
MINIO_ACCESS_KEY= " $ ( / usr/ local/ bin/ get_value_from_config. sh MINIO_ACCESS_KEY " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
MINIO_SECRET_KEY= " $ ( / usr/ local/ bin/ get_value_from_config. sh MINIO_SECRET_KEY " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
DASHBOARD_ADMIN_USERNAME= " $ ( / usr/ local/ bin/ get_value_from_config. sh DASHBOARD_ADMIN_USERNAME " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
DASHBOARD_ADMIN_PASSWORD= " $ ( / usr/ local/ bin/ get_value_from_config. sh DASHBOARD_ADMIN_PASSWORD " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
GRAFANA_ADMIN_USERNAME= " $ ( / usr/ local/ bin/ get_value_from_config. sh GRAFANA_ADMIN_USERNAME " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
GRAFANA_ADMIN_PASSWORD= " $ ( / usr/ local/ bin/ get_value_from_config. sh GRAFANA_ADMIN_PASSWORD " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
LIVEKIT_API_KEY= " $ ( / usr/ local/ bin/ get_value_from_config. sh LIVEKIT_API_KEY " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
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
MEET_INITIAL_ADMIN_USER= " $ ( / usr/ local/ bin/ get_value_from_config. sh MEET_INITIAL_ADMIN_USER " $ { CLUSTER_CONFIG_DIR} / master_node/ meet. env" ) "
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-16 17:52:10 +02:00
if [ [ " $ { initialMeetApiKey} " != '' ] ] ; then
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
ENABLED_MODULES= " $ ( / usr/ local/ bin/ get_value_from_config. sh ENABLED_MODULES " $ { CLUSTER_CONFIG_DIR} / openvidu. env" ) "
# Update shared secret
az keyvault secret set - - vault-name $ { keyVaultName} - - name REDIS-PASSWORD - - value $ REDIS_PASSWORD
az keyvault secret set - - vault-name $ { keyVaultName} - - name DOMAIN-NAME - - value $ DOMAIN_NAME
az keyvault secret set - - vault-name $ { keyVaultName} - - name OPENVIDU-RTC-ENGINE - - value $ OPENVIDU_RTC_ENGINE
az keyvault secret set - - vault-name $ { keyVaultName} - - name OPENVIDU-PRO-LICENSE - - value $ OPENVIDU_PRO_LICENSE
az keyvault secret set - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-USERNAME - - value $ MONGO_ADMIN_USERNAME
az keyvault secret set - - vault-name $ { keyVaultName} - - name MONGO-ADMIN-PASSWORD - - value $ MONGO_ADMIN_PASSWORD
az keyvault secret set - - vault-name $ { keyVaultName} - - name MONGO-REPLICA-SET-KEY - - value $ MONGO_REPLICA_SET_KEY
az keyvault secret set - - vault-name $ { keyVaultName} - - name MINIO-ACCESS-KEY - - value $ MINIO_ACCESS_KEY
az keyvault secret set - - vault-name $ { keyVaultName} - - name MINIO-SECRET-KEY - - value $ MINIO_SECRET_KEY
az keyvault secret set - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-USERNAME - - value $ DASHBOARD_ADMIN_USERNAME
az keyvault secret set - - vault-name $ { keyVaultName} - - name DASHBOARD-ADMIN-PASSWORD - - value $ DASHBOARD_ADMIN_PASSWORD
az keyvault secret set - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-USERNAME - - value $ GRAFANA_ADMIN_USERNAME
az keyvault secret set - - vault-name $ { keyVaultName} - - name GRAFANA-ADMIN-PASSWORD - - value $ GRAFANA_ADMIN_PASSWORD
az keyvault secret set - - vault-name $ { keyVaultName} - - name LIVEKIT-API-KEY - - value $ LIVEKIT_API_KEY
az keyvault secret set - - vault-name $ { keyVaultName} - - name LIVEKIT-API-SECRET - - value $ LIVEKIT_API_SECRET
2025-08-28 13:58:03 +02:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-USER - - value $ MEET_INITIAL_ADMIN_USER
az keyvault secret set - - vault-name $ { keyVaultName} - - name MEET-INITIAL-ADMIN-PASSWORD - - value $ MEET_INITIAL_ADMIN_PASSWORD
2025-09-16 17:52:10 +02:00
if [ [ " $ { initialMeetApiKey} " != '' ] ] ; then
az keyvault secret set - - vault-name $ { keyVaultName} - - name MEET-INITIAL-API-KEY - - value $ MEET_INITIAL_API_KEY
fi
2025-04-22 11:46:24 +02:00
az keyvault secret set - - vault-name $ { keyVaultName} - - name ENABLED-MODULES - - value $ ENABLED_MODULES
'' '
var get_value_from_configScriptMaster = '' '
# ! / 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"
'' '
var store_secretScriptTemplateMaster = '' '
# ! / bin/ bash
set - e
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions > / dev/ null
2025-04-22 11:46:24 +02:00
# 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"
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} "
az keyvault secret set - - vault-name $ { keyVaultName} - - name $ SECRET_KEY_NAME - - value $ RANDOM_PASSWORD > / dev/ null
if [ [ $ ? - ne 0 ] ] ; then
echo " Error generating secret"
fi
echo " $ RANDOM_PASSWORD"
elif [ [ " $ MODE" == " save" ] ] ; then
SECRET_KEY_NAME= " $ 2"
SECRET_VALUE= " $ 3"
2025-09-16 19:17:20 +02:00
# If empty value, store it empty
if [ [ - z " $ SECRET_VALUE" ] ] ; then
az keyvault secret set - - vault-name $ { keyVaultName} - - name $ SECRET_KEY_NAME - - file / dev/ null > / dev/ null
else
az keyvault secret set - - vault-name $ { keyVaultName} - - name $ SECRET_KEY_NAME - - value $ SECRET_VALUE > / dev/ null
fi
2025-04-22 11:46:24 +02:00
if [ [ $ ? - ne 0 ] ] ; then
echo " Error generating secret"
fi
echo " $ SECRET_VALUE"
else
exit 1
fi
'' '
2025-09-09 22:06:36 +02:00
var get_public_ip = '' '
# ! / bin/ bash
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions > / dev/ null
2025-09-09 22:06:36 +02:00
az network public-ip show \
- - id $ { publicIPId} \
- - query " ipAddress" - o tsv
'' '
2025-04-22 11:46:24 +02:00
var check_app_readyScriptMaster = '' '
# ! / bin/ bash
set - e
2026-07-08 17:32:27 +02:00
MAX_WAIT= 1200
WAIT_INTERVAL= 5
ELAPSED_TIME= 0
2025-04-22 11:46:24 +02:00
while true; do
2026-02-05 13:16:31 +01:00
HTTP_STATUS= $ ( curl - Ik http : //localhost:7880/health/caddy | head -n1 | awk '{print $2}')
2026-07-08 17:32:27 +02:00
if [ " $ HTTP_STATUS" = " 200" ] ; then
2025-04-22 11:46:24 +02:00
break
fi
2026-07-08 17:32:27 +02:00
ELAPSED_TIME= $ ( ( ELAPSED_TIME + WAIT_INTERVAL) )
if [ $ ELAPSED_TIME - ge $ MAX_WAIT ] ; then
exit 1
fi
2025-04-22 11:46:24 +02:00
sleep 5
done
'' '
var restartScriptMaster = '' '
# ! / 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
'' '
2025-05-13 11:01:22 +02:00
var config_blobStorageTemplate = '' '
# ! / bin/ bash
set - e
# Install dir and config dir
INSTALL_DIR= " / opt/ openvidu"
CLUSTER_CONFIG_DIR= " $ { INSTALL_DIR} / config/ cluster"
2026-07-08 17:32:27 +02:00
# Retry login + storage key fetch to allow the Contributor role assignment to propagate
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
MAX_WAIT= 300
2026-07-08 17:32:27 +02:00
WAIT_INTERVAL= 1
ELAPSED_TIME= 0
set + e
while true; do
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId}
2025-05-13 11:01:22 +02:00
2026-07-08 17:32:27 +02:00
# Config azure blob storage
AZURE_ACCOUNT_NAME= " $ { storageAccountName} "
AZURE_ACCOUNT_KEY= $ ( az storage account keys list - - account-name $ { storageAccountName} - - query '[0].value' - o tsv)
# If the key was fetched successfully, exit the loop
if [ $ ? - eq 0 ] ; then
break
fi
# If not, wait and check again incrementing the time
ELAPSED_TIME= $ ( ( ELAPSED_TIME + WAIT_INTERVAL) )
# If exceeded the maximum time, exit with error
if [ $ ELAPSED_TIME - ge $ MAX_WAIT ] ; then
exit 1
fi
# Esperar antes de la pró xima comprobació n
sleep $ WAIT_INTERVAL
done
set - e
2025-05-13 11:01:22 +02:00
AZURE_CONTAINER_NAME= " $ { storageAccountContainerName} "
sed - i " s| AZURE_ACCOUNT_NAME= . * | AZURE_ACCOUNT_NAME= $ AZURE_ACCOUNT_NAME| " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s| AZURE_ACCOUNT_KEY= . * | AZURE_ACCOUNT_KEY= $ AZURE_ACCOUNT_KEY| " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
sed - i " s| AZURE_CONTAINER_NAME= . * | AZURE_CONTAINER_NAME= $ AZURE_CONTAINER_NAME| " " $ { CLUSTER_CONFIG_DIR} / openvidu. env"
'' '
2025-04-22 11:46:24 +02:00
var installScriptMaster1 = reduce(
items ( stringInterpolationParamsMaster1) ,
{ value : installScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var installScriptMaster2 = reduce(
items ( stringInterpolationParamsMaster2) ,
{ value : installScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var installScriptMaster3 = reduce(
items ( stringInterpolationParamsMaster3) ,
{ value : installScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var installScriptMaster4 = reduce(
items ( stringInterpolationParamsMaster4) ,
{ value : installScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var after_installScriptMaster = reduce(
items ( stringInterpolationParamsMaster1) ,
{ value : after_installScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var update_config_from_secretScriptMaster = reduce(
items ( stringInterpolationParamsMaster1) ,
{ value : update_config_from_secretScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
2025-09-09 22:06:36 +02:00
var get_public_ip_script = reduce(
items ( stringInterpolationParamsMaster1) ,
2025-10-15 18:45:25 +02:00
{ value : get_public_ip } ,
2025-09-09 22:06:36 +02:00
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
2025-04-22 11:46:24 +02:00
var update_secret_from_configScriptMaster = reduce(
items ( stringInterpolationParamsMaster1) ,
{ value : update_secret_from_configScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var store_secretScriptMaster = reduce(
items ( stringInterpolationParamsMaster1) ,
{ value : store_secretScriptTemplateMaster } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
2025-05-13 11:01:22 +02:00
var blobStorageParams = {
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-05-13 14:36:12 +02:00
storageAccountName : isEmptyStorageAccountName ? storageAccount. name : existingStorageAccount. name
2025-05-13 11:01:22 +02:00
storageAccountKey : listKeys ( storageAccount. id, '2021-04-01' ) . keys[ 0] . value
2025-06-10 16:30:41 +02:00
storageAccountContainerName : isEmptyAppDataContainerName ? 'openvidu-appdata' : '${appDataContainerName}'
2025-05-13 11:01:22 +02:00
}
var config_blobStorageScript = reduce(
items ( blobStorageParams) ,
{ value : config_blobStorageTemplate } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
2025-04-22 11:46:24 +02:00
var base64installMaster1 = base64 ( installScriptMaster1)
var base64installMaster2 = base64 ( installScriptMaster2)
var base64installMaster3 = base64 ( installScriptMaster3)
var base64installMaster4 = base64 ( installScriptMaster4)
var base64after_installMaster = base64 ( after_installScriptMaster)
2025-09-09 22:06:36 +02:00
var base64get_public_ip = base64 ( get_public_ip_script)
2025-04-22 11:46:24 +02:00
var base64update_config_from_secretMaster = base64 ( update_config_from_secretScriptMaster)
var base64update_secret_from_configMaster = base64 ( update_secret_from_configScriptMaster)
var base64get_value_from_configMaster = base64 ( get_value_from_configScriptMaster)
var base64store_secretMaster = base64 ( store_secretScriptMaster)
var base64check_app_readyMaster = base64 ( check_app_readyScriptMaster)
var base64restartMaster = base64 ( restartScriptMaster)
2025-05-13 11:01:22 +02:00
var base64config_blobStorage = base64 ( config_blobStorageScript)
2025-04-22 11:46:24 +02:00
var userDataParamsMasterNode1 = {
base64install : base64installMaster1
base64after_install : base64after_installMaster
2025-09-09 22:06:36 +02:00
base64get_public_ip : base64get_public_ip
2025-04-22 11:46:24 +02:00
base64update_config_from_secret : base64update_config_from_secretMaster
base64update_secret_from_config : base64update_secret_from_configMaster
base64get_value_from_config : base64get_value_from_configMaster
base64store_secret : base64store_secretMaster
base64check_app_ready : base64check_app_readyMaster
base64restart : base64restartMaster
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '1'
2025-05-29 16:51:59 +02:00
base64config_blobStorage : base64config_blobStorage
2025-04-22 11:46:24 +02:00
}
var userDataParamsMasterNode2 = {
base64install : base64installMaster2
base64after_install : base64after_installMaster
2025-09-09 22:06:36 +02:00
base64get_public_ip : base64get_public_ip
2025-04-22 11:46:24 +02:00
base64update_config_from_secret : base64update_config_from_secretMaster
base64update_secret_from_config : base64update_secret_from_configMaster
base64get_value_from_config : base64get_value_from_configMaster
base64store_secret : base64store_secretMaster
base64check_app_ready : base64check_app_readyMaster
base64restart : base64restartMaster
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '2'
2025-05-29 16:51:59 +02:00
base64config_blobStorage : base64config_blobStorage
2025-04-22 11:46:24 +02:00
}
var userDataParamsMasterNode3 = {
base64install : base64installMaster3
base64after_install : base64after_installMaster
2025-09-09 22:06:36 +02:00
base64get_public_ip : base64get_public_ip
2025-04-22 11:46:24 +02:00
base64update_config_from_secret : base64update_config_from_secretMaster
base64update_secret_from_config : base64update_secret_from_configMaster
base64get_value_from_config : base64get_value_from_configMaster
base64store_secret : base64store_secretMaster
base64check_app_ready : base64check_app_readyMaster
base64restart : base64restartMaster
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '3'
2025-05-29 16:51:59 +02:00
base64config_blobStorage : base64config_blobStorage
2025-04-22 11:46:24 +02:00
}
var userDataParamsMasterNode4 = {
base64install : base64installMaster4
base64after_install : base64after_installMaster
2025-09-09 22:06:36 +02:00
base64get_public_ip : base64get_public_ip
2025-04-22 11:46:24 +02:00
base64update_config_from_secret : base64update_config_from_secretMaster
base64update_secret_from_config : base64update_secret_from_configMaster
base64get_value_from_config : base64get_value_from_configMaster
base64store_secret : base64store_secretMaster
base64check_app_ready : base64check_app_readyMaster
base64restart : base64restartMaster
keyVaultName : keyVaultName
2026-07-29 11:56:08 +02:00
masterIdentityClientId : masterIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
masterNodeNum : '4'
2025-05-13 14:36:12 +02:00
storageAccountName : isEmptyStorageAccountName ? storageAccount. name : existingStorageAccount. name
2025-05-13 11:01:22 +02:00
base64config_blobStorage : base64config_blobStorage
2025-04-22 11:46:24 +02:00
}
var userDataTemplateMasterNode = '' '
# ! / bin/ bash - x
set - eu - o pipefail
# Introduce the scripts in the instance
# install. sh
echo $ { base64install} | base64 - d > / usr/ local/ bin/ install. sh
chmod + x / usr/ local/ bin/ install. sh
# after_install. sh
echo $ { base64after_install} | base64 - d > / usr/ local/ bin/ after_install. sh
chmod + x / usr/ local/ bin/ after_install. sh
2025-09-09 22:06:36 +02:00
# get_public_ip. sh
echo $ { base64get_public_ip} | base64 - d > / usr/ local/ bin/ get_public_ip. sh
chmod + x / usr/ local/ bin/ get_public_ip. sh
2025-04-22 11:46:24 +02:00
# update_config_from_secret. sh
echo $ { base64update_config_from_secret} | base64 - d > / usr/ local/ bin/ update_config_from_secret. sh
chmod + x / usr/ local/ bin/ update_config_from_secret. sh
# update_secret_from_config. sh
echo $ { base64update_secret_from_config} | base64 - d > / usr/ local/ bin/ update_secret_from_config. sh
chmod + x / usr/ local/ bin/ update_secret_from_config. sh
# get_value_from_config. sh
echo $ { base64get_value_from_config} | base64 - d > / usr/ local/ bin/ get_value_from_config. sh
chmod + x / usr/ local/ bin/ get_value_from_config. sh
# store_secret. sh
echo $ { base64store_secret} | base64 - d > / usr/ local/ bin/ store_secret. sh
chmod + x / usr/ local/ bin/ store_secret. sh
# check_app_ready. sh
echo $ { base64check_app_ready} | base64 - d > / usr/ local/ bin/ check_app_ready. sh
chmod + x / usr/ local/ bin/ check_app_ready. sh
# restart. sh
echo $ { base64restart} | base64 - d > / usr/ local/ bin/ restart. sh
chmod + x / usr/ local/ bin/ restart. sh
2025-05-29 16:51:59 +02:00
echo $ { base64config_blobStorage} | base64 - d > / usr/ local/ bin/ config_blobStorage. sh
chmod + x / usr/ local/ bin/ config_blobStorage. sh
2025-04-22 11:46:24 +02:00
# Install azure cli
2026-09-07 16:58:47 +02:00
AZURE_CLI_VERSION= 2. 90. 0
2026-03-02 16:45:35 +01:00
apt-get install - y apt-transport-https ca-certificates gnupg lsb-release
curl - sLS https : //packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg
echo " deb [ arch= $ ( dpkg - - print-architecture) signed-by= / etc/ apt/ trusted. gpg. d/ microsoft. gpg] https : //packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list
apt-get update
apt-get install - y azure-cli= $ { AZURE_CLI_VERSION} - 1~ $ ( lsb_release - cs)
2025-04-22 11:46:24 +02:00
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { masterIdentityClientId} - - allow-no-subscriptions
2025-04-22 11:46:24 +02:00
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
export HOME= " / root"
# Install OpenVidu
/ usr/ local/ bin/ install. sh | | { echo " [ OpenVidu] error installing OpenVidu" ; exit 1; }
2025-05-29 16:51:59 +02:00
# Config blob storage
/ usr/ local/ bin/ config_blobStorage. sh | | { echo " [ OpenVidu] error configuring Blob Storage" ; exit 1; }
2025-04-22 11:46:24 +02:00
# Start OpenVidu
systemctl start openvidu | | { echo " [ OpenVidu] error starting OpenVidu" ; exit 1; }
# Update shared secret
/ usr/ local/ bin/ after_install. sh | | { echo " [ OpenVidu] error updating shared secret" ; exit 1; }
# Launch on reboot
echo " @ reboot / usr/ local/ bin/ restart. sh > > / var / log/ openvidu-restart. log" 2> & 1 | crontab
2026-07-24 17:11:58 +02:00
# check_app_ready. sh internally caps its wait at 1200s
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
/ usr/ local/ bin/ check_app_ready. sh | | { echo " [ OpenVidu] master node did not become healthy" ; exit 1; }
az keyvault secret set - - vault-name $ { keyVaultName} - - name FINISH-MASTER-NODE - - value " true"
2025-04-22 11:46:24 +02:00
MASTER_NODE_NUM= $ { masterNodeNum}
if [ [ $ MASTER_NODE_NUM - eq 4 ] ] ; then
2025-05-13 11:01:22 +02:00
# Creating scale in lock
2025-04-22 11:46:24 +02:00
set + e
az storage blob upload - - account-name $ { storageAccountName} - - container-name automation-locks - - name lock. txt - - file / dev/ null - - auth-mode key
set - e
fi
'' '
var userDataMasterNode1 = reduce(
items ( userDataParamsMasterNode1) ,
{ value : userDataTemplateMasterNode } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var userDataMasterNode2 = reduce(
items ( userDataParamsMasterNode2) ,
{ value : userDataTemplateMasterNode } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var userDataMasterNode3 = reduce(
items ( userDataParamsMasterNode3) ,
{ value : userDataTemplateMasterNode } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var userDataMasterNode4 = reduce(
items ( userDataParamsMasterNode4) ,
{ value : userDataTemplateMasterNode } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
resource openviduMasterNode1 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name : '${stackName}-VM-MasterNode1'
location : location
2026-07-29 11:56:08 +02:00
identity : {
type : 'UserAssigned'
userAssignedIdentities : {
'${masterIdentity.id}' : { }
}
}
2025-04-22 11:46:24 +02:00
properties : {
hardwareProfile : {
vmSize : masterNodeInstanceType
}
storageProfile : {
osDisk : {
createOption : 'FromImage'
managedDisk : {
storageAccountType : masterNodeVMSettings. osDiskType
}
diskSizeGB : masterNodeVMSettings. osDiskSize
}
imageReference : masterNodeVMSettings. ubuntuOSVersion
}
networkProfile : {
networkInterfaces : [
{
id : netInterfaceMasterNode1. id
}
]
}
osProfile : {
computerName : '${stackName}-VM-MasterNode1'
adminUsername : adminUsername
linuxConfiguration : masterNodeVMSettings. linuxConfiguration
}
userData : base64 ( userDataMasterNode1)
}
}
resource openviduMasterNode2 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name : '${stackName}-VM-MasterNode2'
location : location
2026-07-29 11:56:08 +02:00
identity : {
type : 'UserAssigned'
userAssignedIdentities : {
'${masterIdentity.id}' : { }
}
}
2025-04-22 11:46:24 +02:00
properties : {
hardwareProfile : {
vmSize : masterNodeInstanceType
}
storageProfile : {
osDisk : {
createOption : 'FromImage'
managedDisk : {
storageAccountType : masterNodeVMSettings. osDiskType
}
diskSizeGB : masterNodeVMSettings. osDiskSize
}
imageReference : masterNodeVMSettings. ubuntuOSVersion
}
networkProfile : {
networkInterfaces : [
{
id : netInterfaceMasterNode2. id
}
]
}
osProfile : {
computerName : '${stackName}-VM-MasterNode2'
adminUsername : adminUsername
linuxConfiguration : masterNodeVMSettings. linuxConfiguration
}
userData : base64 ( userDataMasterNode2)
}
}
resource openviduMasterNode3 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name : '${stackName}-VM-MasterNode3'
location : location
2026-07-29 11:56:08 +02:00
identity : {
type : 'UserAssigned'
userAssignedIdentities : {
'${masterIdentity.id}' : { }
}
}
2025-04-22 11:46:24 +02:00
properties : {
hardwareProfile : {
vmSize : masterNodeInstanceType
}
storageProfile : {
osDisk : {
createOption : 'FromImage'
managedDisk : {
storageAccountType : masterNodeVMSettings. osDiskType
}
diskSizeGB : masterNodeVMSettings. osDiskSize
}
imageReference : masterNodeVMSettings. ubuntuOSVersion
}
networkProfile : {
networkInterfaces : [
{
id : netInterfaceMasterNode3. id
}
]
}
osProfile : {
computerName : '${stackName}-VM-MasterNode3'
adminUsername : adminUsername
linuxConfiguration : masterNodeVMSettings. linuxConfiguration
}
userData : base64 ( userDataMasterNode3)
}
}
resource openviduMasterNode4 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name : '${stackName}-VM-MasterNode4'
location : location
2026-07-29 11:56:08 +02:00
identity : {
type : 'UserAssigned'
userAssignedIdentities : {
'${masterIdentity.id}' : { }
}
}
2025-04-22 11:46:24 +02:00
properties : {
hardwareProfile : {
vmSize : masterNodeInstanceType
}
storageProfile : {
osDisk : {
createOption : 'FromImage'
managedDisk : {
storageAccountType : masterNodeVMSettings. osDiskType
}
diskSizeGB : masterNodeVMSettings. osDiskSize
}
imageReference : masterNodeVMSettings. ubuntuOSVersion
}
networkProfile : {
networkInterfaces : [
{
id : netInterfaceMasterNode4. id
}
]
}
osProfile : {
computerName : '${stackName}-VM-MasterNode4'
adminUsername : adminUsername
linuxConfiguration : masterNodeVMSettings. linuxConfiguration
}
userData : base64 ( userDataMasterNode4)
}
}
/*------------------------------------------- MEDIA NODES -------------------------------------------*/
var privateIPMasterNode1 = netInterfaceMasterNode1. properties. ipConfigurations[ 0] . properties. privateIPAddress
var privateIPMasterNode2 = netInterfaceMasterNode2. properties. ipConfigurations[ 0] . properties. privateIPAddress
var privateIPMasterNode3 = netInterfaceMasterNode3. properties. ipConfigurations[ 0] . properties. privateIPAddress
var privateIPMasterNode4 = netInterfaceMasterNode4. properties. ipConfigurations[ 0] . properties. privateIPAddress
var stringInterpolationParamsMedia = {
privateIPMasterNode1 : privateIPMasterNode1
privateIPMasterNode2 : privateIPMasterNode2
privateIPMasterNode3 : privateIPMasterNode3
privateIPMasterNode4 : privateIPMasterNode4
keyVaultName : keyVaultName
}
var installScriptTemplateMedia = '' '
# ! / bin/ bash - x
DOMAIN=
# Install dependencies
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 \
curl \
unzip \
jq \
wget
# Get own private IP
PRIVATE_IP= $ ( curl - H Metadata : true - - noproxy " * " " http : //169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text")
2026-07-24 17:11:58 +02:00
# Gate 1 : wait for master secrets and IPs before installing
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
WAIT_INTERVAL= 5
MAX_RETRIES= 360
RETRIES= 0
2025-04-22 11:46:24 +02:00
set + e
while true; do
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
ALL_SECRETS_GENERATED= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name ALL-SECRETS-GENERATED - - query value - o tsv 2> / dev/ null)
MASTER_NODE_1_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-1-PRIVATE-IP - - query value - o tsv 2> / dev/ null)
MASTER_NODE_2_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-2-PRIVATE-IP - - query value - o tsv 2> / dev/ null)
MASTER_NODE_3_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-3-PRIVATE-IP - - query value - o tsv 2> / dev/ null)
MASTER_NODE_4_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-4-PRIVATE-IP - - query value - o tsv 2> / dev/ null)
if [ " $ ALL_SECRETS_GENERATED" == " true" ] &&
[ " $ MASTER_NODE_1_PRIVATE_IP" != " " ] &&
[ " $ MASTER_NODE_2_PRIVATE_IP" != " " ] &&
[ " $ MASTER_NODE_3_PRIVATE_IP" != " " ] &&
[ " $ MASTER_NODE_4_PRIVATE_IP" != " " ] ; then
2025-04-22 11:46:24 +02:00
break
fi
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
RETRIES= $ ( ( RETRIES + 1) )
if [ $ RETRIES - ge $ MAX_RETRIES ] ; then
echo " [ OpenVidu] timed out after 30 min waiting for master nodes to generate secrets and publish their IPs"
2025-04-22 11:46:24 +02:00
exit 1
fi
sleep $ WAIT_INTERVAL
done
set - e
MASTER_NODE_1_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-1-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_2_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-2-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_3_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-3-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_4_PRIVATE_IP= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name MASTER-NODE-4-PRIVATE-IP - - query value - o tsv)
MASTER_NODE_PRIVATE_IP_LIST= " $ MASTER_NODE_1_PRIVATE_IP, $ MASTER_NODE_2_PRIVATE_IP, $ MASTER_NODE_3_PRIVATE_IP, $ MASTER_NODE_4_PRIVATE_IP"
REDIS_PASSWORD= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name REDIS-PASSWORD - - query value - o tsv)
OPENVIDU_VERSION= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name OPENVIDU-VERSION - - query value - o tsv)
2026-07-24 17:11:58 +02:00
# Download first : sh < ( curl . . . ) would silently run an empty script on a transient curl failure
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
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/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
if [ ! - s " $ INSTALLER_SCRIPT" ] ; then
echo " [ OpenVidu] failed to download the media node installer script"
exit 1
fi
2025-04-22 11:46:24 +02:00
# Base command
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
INSTALL_COMMAND= " sh $ INSTALLER_SCRIPT"
2025-04-22 11:46:24 +02:00
# Common arguments
COMMON_ARGS= (
" - - no-tty"
" - - install"
" - - environment = azure"
" - - deployment - type= 'ha' "
" - - node-role= 'media-node' "
" - - master-node-private-ip-list= $ MASTER_NODE_PRIVATE_IP_LIST"
" - - private-ip= $ PRIVATE_IP"
" - - redis-password= $ REDIS_PASSWORD"
)
# Construct the final command with all arguments
FINAL_COMMAND= " $ INSTALL_COMMAND $ ( printf " % s " " $ { COMMON_ARGS[ @ ] } " ) "
# Install OpenVidu
exec bash - c " $ FINAL_COMMAND"
'' '
var stopMediaNodeParams = {
2026-07-29 11:56:08 +02:00
mediaIdentityClientId : mediaIdentity. properties. clientId
2025-04-22 11:46:24 +02:00
subscriptionId : subscription ( ) . subscriptionId
resourceGroupName : resourceGroup ( ) . name
vmScaleSetName : '${stackName}-mediaNodeScaleSet'
2025-05-13 14:36:12 +02:00
storageAccountName : isEmptyStorageAccountName ? storageAccount. name : existingStorageAccount. name
2025-04-22 11:46:24 +02:00
}
var stop_media_nodesScriptMediaTemplate = '' '
# ! / 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
# Pause the health watchdog before draining so it never reimages a node that is being
# drained for scale-in ( synchronous; the drain runs outside the watchdog' s cgroup) .
touch / run/ openvidu-media-health. paused
systemctl stop openvidu-media-health 2> / dev/ null | | true
2025-04-22 11:46:24 +02:00
if ! ( set - o noclobber ; echo > / tmp/ global. lock) ; then
exit 1 # the global. lock already exists
fi
# 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
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
# If this node is being replaced because it is unhealthy, do not wait for sessions to end.
if [ - f / run/ openvidu-unhealthy ] ; then
docker ps - q | xargs - r docker kill > / dev/ null 2> & 1 | | true
fi
2025-04-22 11:46:24 +02:00
# Wait for running containers to not be openvidu, ingress or egress
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
done
fi
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { mediaIdentityClientId}
2025-04-22 11:46:24 +02:00
RESOURCE_GROUP_NAME= $ { resourceGroupName}
VM_SCALE_SET_NAME= $ { vmScaleSetName}
SUBSCRIPTION_ID= $ { subscriptionId}
BEFORE_INSTANCE_ID= $ ( curl - H Metadata : true - - noproxy " * " " http : //169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -r '.compute.resourceId')
INSTANCE_ID= $ ( echo $ BEFORE_INSTANCE_ID | awk - F'/' '{print $NF}' )
RESOURCE_ID= / subscriptions/ $ SUBSCRIPTION_ID/ resourcegroups/ $ RESOURCE_GROUP_NAME/ providers/ Microsoft. Compute/ virtualMachineScaleSets/ $ VM_SCALE_SET_NAME
TIMESTAMP= $ ( date - u + " % Y-% m-% dT% H : % M : % SZ" )
az tag update - - resource - id $ RESOURCE_ID - - operation replace - - tags " STATUS" = " HEALTHY" " InstanceDeleteTime" = " $ TIMESTAMP" " storageAccount" = " $ { storageAccountName} "
2025-06-10 10:56:50 +02:00
az vmss delete-instances - - resource - group $ RESOURCE_GROUP_NAME - - name $ VM_SCALE_SET_NAME - - instance-ids $ 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
var openviduMediaHealthScript = '' '
2025-06-10 10:56:50 +02:00
# ! / 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
# 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
}
2025-06-10 10:56:50 +02:00
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
'' '
var openviduMediaHealthUnit = '' '
[ 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
'' '
var media_replaceScriptMediaTemplate = '' '
# ! / bin/ bash
# openvidu-media-replace. sh " < reason> "
# Requests the reimage of THIS Media Node, keeping the scale set capacity and the
# scale-in protection : reimage provisions a new OS disk and the userData bootstrap runs
# again on the same instance id. Called by the health watchdog; it never signals it.
mkdir - p / var / lib/ openvidu-media-health
echo " $ ( date - u + % FT% TZ) $ 1" > > / var / lib/ openvidu-media-health/ replace - requests. log
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
2025-06-10 10:56:50 +02:00
RESOURCE_GROUP_NAME= $ { resourceGroupName}
VM_SCALE_SET_NAME= $ { vmScaleSetName}
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
INSTANCE_RESOURCE_ID= $ ( curl - s - H Metadata : true - - noproxy " * " " http : //169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -r '.compute.resourceId')
INSTANCE_ID= $ ( echo " $ INSTANCE_RESOURCE_ID" | awk - F'/' '{print $NF}' )
if [ - z " $ INSTANCE_ID" ] ; then
echo " [ media-replace] could not read this instance id from IMDS"
exit 1
fi
2025-06-10 10:56:50 +02:00
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
n= 0
while true; do
n= $ ( ( n + 1) )
if az login - - identity - - client-id $ { mediaIdentityClientId} > / dev/ null 2> & 1 && \
az vmss reimage - - resource - group " $ RESOURCE_GROUP_NAME" - - name " $ VM_SCALE_SET_NAME" - - instance-ids " $ INSTANCE_ID" - - no-wait; then
echo " [ media-replace] reimage requested for instance $ INSTANCE_ID ( $ 1) "
exit 0
fi
if [ " $ n" - ge 5 ] ; then
echo " [ media-replace] reimage could not be requested after $ n attempts"
exit 1
fi
echo " [ media-replace] reimage request failed ( attempt $ n) , retrying in 12s"
sleep 12
done
2025-04-22 11:46:24 +02:00
'' '
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
var userDataMediaNodeTemplate = '' '
# ! / bin/ bash - x
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
# Mark this media node' s bootstrap as failed so the health watchdog replaces it
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-04-22 11:46:24 +02:00
# Introduce the scripts in the instance
# install. sh
echo $ { base64install} | base64 - d > / usr/ local/ bin/ install. sh
chmod + x / usr/ local/ bin/ install. sh
# stop_media_nodes. sh
echo $ { base64stop} | base64 - d > / usr/ local/ bin/ stop_media_node. sh
chmod + x / usr/ local/ bin/ stop_media_node. sh
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-media-health. sh ( health watchdog) , openvidu-media-replace. sh ( reimage this node) and its unit
echo $ { base64mediaHealth} | base64 - d > / usr/ local/ bin/ openvidu-media-health. sh
chmod + x / usr/ local/ bin/ openvidu-media-health. sh
echo $ { base64mediaReplace} | base64 - d > / usr/ local/ bin/ openvidu-media-replace. sh
chmod + x / usr/ local/ bin/ openvidu-media-replace. sh
echo $ { base64mediaHealthUnit} | base64 - d > / etc/ systemd/ system/ openvidu-media-health. service
2026-06-04 14:06:39 +02:00
2025-12-16 12:49:34 +01:00
echo " DPkg : : Lock : : Timeout \ " - 1\ " ; " > / etc/ apt/ apt. conf. d/ 99timeout
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
apt-get update && apt-get install - y jq
2025-04-22 11:46:24 +02:00
# Install azure cli
2026-09-07 16:58:47 +02:00
AZURE_CLI_VERSION= 2. 90. 0
2026-03-02 16:45:35 +01:00
apt-get install - y apt-transport-https ca-certificates gnupg lsb-release
curl - sLS https : //packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg
echo " deb [ arch= $ ( dpkg - - print-architecture) signed-by= / etc/ apt/ trusted. gpg. d/ microsoft. gpg] https : //packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list
apt-get update
apt-get install - y azure-cli= $ { AZURE_CLI_VERSION} - 1~ $ ( lsb_release - cs)
2025-04-22 11:46:24 +02:00
2026-07-29 11:56:08 +02:00
az login - - identity - - client-id $ { mediaIdentityClientId}
2025-04-22 11:46:24 +02:00
# Protect from scale in actions
RESOURCE_GROUP_NAME= $ { resourceGroupName}
VM_SCALE_SET_NAME= $ { vmScaleSetName}
BEFORE_INSTANCE_ID= $ ( curl - H Metadata : true - - noproxy " * " " http : //169.254.169.254/metadata/instance?api-version=2021-02-01" | jq -r '.compute.resourceId')
INSTANCE_ID= $ ( echo $ BEFORE_INSTANCE_ID | awk - F'/' '{print $NF}' )
az vmss update - - resource - group $ RESOURCE_GROUP_NAME - - name $ VM_SCALE_SET_NAME - - instance-id $ INSTANCE_ID - - protect-from-scale-in true
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
# Start the Media Node health watchdog before install. sh ( az CLI is installed and the node
# is scale-in protected) : it backstops a hung or failed bootstrap and, once running, reimages
# the node if LiveKit stays unhealthy. Write MASTER_ADDRS first so its master guard is armed.
mkdir - p / etc/ openvidu
echo 'MASTER_ADDRS="${privateIPMasterNode1}:7001 ${privateIPMasterNode2}:7001 ${privateIPMasterNode3}:7001 ${privateIPMasterNode4}:7001"' > / etc/ openvidu/ media-health. env
systemctl daemon-reload
systemctl enable - - now openvidu-media-health
2026-07-24 17:11:58 +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
2026-07-24 17:11:58 +02:00
# Gate 2 : wait for master readiness before starting
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
WAIT_INTERVAL= 5
MAX_RETRIES= 360
RETRIES= 0
set + e
while true; do
FINISH_MASTER_NODE= $ ( az keyvault secret show - - vault-name $ { keyVaultName} - - name FINISH-MASTER-NODE - - query value - o tsv 2> / dev/ null)
if [ " $ FINISH_MASTER_NODE" == " true" ] ; then
break
fi
RETRIES= $ ( ( RETRIES + 1) )
if [ $ RETRIES - ge $ MAX_RETRIES ] ; 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 " timed out after 30 min waiting for FINISH-MASTER-NODE"
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
fi
sleep $ WAIT_INTERVAL
done
set - e
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 " systemctl start openvidu failed"
# Bootstrap finished : the watchdog switches from bootstrap-deadline mode to runtime health checks
mkdir - p / var / lib/ openvidu-media-health
touch / var / lib/ openvidu-media-health/ installed
2025-04-22 11:46:24 +02:00
'' '
var installScriptMedia = reduce(
items ( stringInterpolationParamsMedia) ,
{ value : installScriptTemplateMedia } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var stop_media_nodesScriptMedia = reduce(
items ( stopMediaNodeParams) ,
{ value : stop_media_nodesScriptMediaTemplate } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
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
var media_replaceScriptMedia = reduce(
2025-06-10 10:56:50 +02:00
items ( stopMediaNodeParams) ,
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
{ value : media_replaceScriptMediaTemplate } ,
2025-06-10 10:56:50 +02:00
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
2025-04-22 11:46:24 +02:00
var base64installMedia = base64 ( installScriptMedia)
var base64stopMediaNode = base64 ( stop_media_nodesScriptMedia)
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
var base64media_replaceScriptMedia = base64 ( media_replaceScriptMedia)
var base64openviduMediaHealth = base64 ( openviduMediaHealthScript)
var base64openviduMediaHealthUnit = base64 ( openviduMediaHealthUnit)
2025-04-22 11:46:24 +02:00
var userDataParamsMedia = {
base64install : base64installMedia
base64stop : base64stopMediaNode
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
base64mediaHealth : base64openviduMediaHealth
base64mediaHealthUnit : base64openviduMediaHealthUnit
base64mediaReplace : base64media_replaceScriptMedia
privateIPMasterNode1 : privateIPMasterNode1
privateIPMasterNode2 : privateIPMasterNode2
privateIPMasterNode3 : privateIPMasterNode3
privateIPMasterNode4 : privateIPMasterNode4
2026-07-29 11:56:08 +02:00
mediaIdentityClientId : mediaIdentity. properties. clientId
2025-05-13 11:01:22 +02:00
resourceGroupName : resourceGroup ( ) . name
vmScaleSetName : '${stackName}-mediaNodeScaleSet'
openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.
Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.
ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).
Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
keyVaultName : keyVaultName
2025-04-22 11:46:24 +02:00
}
var userDataMediaNode = reduce(
items ( userDataParamsMedia) ,
{ value : userDataMediaNodeTemplate } ,
( curr, next) = > { value : replace ( curr. value, '\${${next.key}}' , next. value) }
) . value
var base64userDataMediaNode = base64 ( userDataMediaNode)
param datetime string = utcNow ( 'u' )
resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@2024-07-01' = {
name : '${stackName}-mediaNodeScaleSet'
location : location
tags : {
InstanceDeleteTime : datetime
2025-05-13 14:36:12 +02:00
storageAccount : isEmptyStorageAccountName ? storageAccount. name : existingStorageAccount. name
2025-04-22 11:46:24 +02:00
}
2026-07-29 11:56:08 +02:00
identity : {
type : 'UserAssigned'
userAssignedIdentities : {
'${mediaIdentity.id}' : { }
}
}
2025-04-22 11:46:24 +02:00
sku : {
name : mediaNodeInstanceType
tier : 'Standard'
2026-10-27 19:03:50 +01:00
// No capacity: the template never writes the number of Media Nodes, so a redeploy can
// never fail on the scale-in-protected Media Nodes. The autoscale profile's default
// seeds the size at creation and its minimum/maximum keep it.
2025-04-22 11:46:24 +02:00
}
properties : {
overprovision : true
upgradePolicy : {
mode : 'Automatic'
}
singlePlacementGroup : true
platformFaultDomainCount : 1
virtualMachineProfile : {
storageProfile : {
osDisk : {
createOption : 'FromImage'
managedDisk : {
storageAccountType : mediaNodeVMSettings. osDiskType
}
diskSizeGB : 50
}
imageReference : mediaNodeVMSettings. ubuntuOSVersion
}
osProfile : {
computerNamePrefix : mediaNodeVMSettings. vmName
adminUsername : adminUsername
linuxConfiguration : mediaNodeVMSettings. linuxConfiguration
}
networkProfile : {
networkInterfaceConfigurations : [
{
name : '${stackName}-mediaNodeNetInterface'
properties : {
primary : true
ipConfigurations : [
{
name : 'ipconfigMediaNode'
properties : {
subnet : {
id : vnet_OV. properties. subnets[ 0] . id
}
applicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
publicIPAddressConfiguration : {
name : 'publicIPAddressMediaNode'
properties : {
publicIPAddressVersion : 'IPv4'
}
}
}
}
]
networkSecurityGroup : {
id : openviduMediaNodeNSG. id
}
}
}
]
}
userData : base64userDataMediaNode
}
}
}
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
// Always deployed and always enabled, so a redeploy switches modes both ways (an incremental
// ARM deployment never deletes a resource the template stops declaring). With a fixed number
// of Media Nodes autoscaling is pinned to it, not disabled: minimum = maximum = default, so
// the CPU rules are inert. Azure refuses a minimum or a maximum below the Media Nodes that are
// running, because they protect themselves from scale-in: a redeploy can raise the fixed
// number (or minNumberOfMediaNodes) but not lower it below the Media Nodes running. To lower
// it, switch the scale set to manual scale, drain the extra Media Nodes with
// /usr/local/bin/stop_media_node.sh (each one removes itself once its Rooms end), and then
// redeploy.
2025-04-22 11:46:24 +02:00
resource openviduAutoScaleSettingsMediaNode 'Microsoft.Insights/autoscaleSettings@2022-10-01' = {
name : '${stackName}-autoscaleSettings'
location : resourceGroup ( ) . location
properties : {
profiles : [
{
name : 'openvidu-medianode-autoscale'
capacity : {
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
minimum : string ( fixedNumberOfMediaNodes > 0 ? fixedNumberOfMediaNodes : minNumberOfMediaNodes)
maximum : string ( fixedNumberOfMediaNodes > 0 ? fixedNumberOfMediaNodes : maxNumberOfMediaNodes)
default : string ( fixedNumberOfMediaNodes > 0 ? fixedNumberOfMediaNodes : initialNumberOfMediaNodes)
2025-04-22 11:46:24 +02:00
}
rules : [
{
metricTrigger : {
metricName : 'Percentage CPU'
metricNamespace : 'Microsoft.Compute/virtualMachineScaleSets'
metricResourceUri : openviduScaleSetMediaNode. id
statistic : 'Average'
operator : 'GreaterThan'
threshold : scaleTargetCPU
timeAggregation : 'Average'
timeWindow : 'PT5M'
timeGrain : 'PT1M'
}
scaleAction : {
direction : 'Increase'
type : 'ChangeCount'
value : '1'
cooldown : 'PT5M'
}
}
{
metricTrigger : {
metricName : 'Percentage CPU'
metricNamespace : 'Microsoft.Compute/virtualMachineScaleSets'
metricResourceUri : openviduScaleSetMediaNode. id
statistic : 'Average'
operator : 'LessThan'
threshold : scaleTargetCPU
timeAggregation : 'Average'
timeWindow : 'PT5M'
timeGrain : 'PT1M'
}
scaleAction : {
direction : 'Decrease'
type : 'ChangeCount'
value : '1'
cooldown : 'PT5M'
}
}
]
}
]
enabled : true
targetResourceUri : openviduScaleSetMediaNode. id
}
}
/*------------------------------------------- SCALE IN ------------------------------------------*/
2026-07-29 11:56:08 +02:00
// One assignment for the shared master identity replaces the four per-VM ones
resource roleAssignmentMasterNodes 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name : guid ( 'roleAssignmentForMasterNodes' , masterIdentity. id)
2025-04-22 11:46:24 +02:00
scope : resourceGroup ( )
properties : {
roleDefinitionId : subscriptionResourceId (
'Microsoft.Authorization/roleDefinitions' ,
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
2026-07-29 11:56:08 +02:00
principalId : masterIdentity. properties. principalId
2025-09-11 20:28:23 +02:00
principalType : 'ServicePrincipal'
2025-04-22 11:46:24 +02:00
}
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
2026-07-29 11:56:08 +02:00
name : guid ( 'roleAssignmentForScaleSet' , mediaIdentity. id)
2025-04-22 11:46:24 +02:00
scope : resourceGroup ( )
properties : {
roleDefinitionId : subscriptionResourceId (
'Microsoft.Authorization/roleDefinitions' ,
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
2026-07-29 11:56:08 +02:00
principalId : mediaIdentity. properties. principalId
2025-09-11 20:28:23 +02:00
principalType : 'ServicePrincipal'
2025-04-22 11:46:24 +02:00
}
}
@ description ( 'Automation Account Name to create a runbook inside it for scale in' )
2025-05-27 11:18:05 +02:00
param automationAccountName string = ''
var isEmptyAutomationAccountName = automationAccountName == ''
2025-04-22 11:46:24 +02:00
2026-10-27 19:03:50 +01:00
@ description ( 'Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal).' )
param scaleInWebhookNameSeed string = utcNow ( )
2025-04-22 11:46:24 +02:00
module webhookModule '../../shared/webhookdeployment.json' = {
params : {
2025-05-27 11:18:05 +02:00
automationAccountName : isEmptyAutomationAccountName
? uniqueString ( resourceGroup ( ) . id, openviduMasterNode1. id)
: automationAccountName
2025-04-22 11:46:24 +02:00
runbookName : 'scaleInRunbook'
2026-10-27 19:03:50 +01:00
webhookName : 'webhookForScaleIn-${uniqueString(deployment().name, scaleInWebhookNameSeed)}'
2025-04-22 11:46:24 +02:00
WebhookExpiryTime : '2035-03-30T00:00:00Z'
2025-04-23 13:26:33 +02:00
_artifactsLocation : 'https : //raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1'
2025-04-22 11:46:24 +02:00
}
name : 'WebhookDeployment'
}
resource actionGroupScaleIn 'Microsoft.Insights/actionGroups@2023-01-01' = {
2025-10-15 18:45:25 +02:00
name : 'actiongroupScaleIn'
2025-04-22 11:46:24 +02:00
location : 'global'
properties : {
groupShortName : 'scaleinag'
enabled : true
automationRunbookReceivers : [
{
name : 'scalein'
useCommonAlertSchema : false
automationAccountId : webhookModule. outputs. automationAccountId
runbookName : 'scaleInRunbook'
webhookResourceId : webhookModule. outputs. webhookId
isGlobalRunbook : false
serviceUri : webhookModule. outputs. webhookUri
}
]
}
}
resource scaleInActivityLogRule 'Microsoft.Insights/activityLogAlerts@2020-10-01' = {
name : 'ScaleInAlertRule'
location : 'global'
properties : {
scopes : [
openviduScaleSetMediaNode. id
]
condition : {
allOf : [
{
field : 'category'
equals : 'Administrative'
}
{
field : 'operationName'
equals : 'Microsoft.Compute/virtualMachineScaleSets/write'
}
{
field : 'level'
containsAny : [
'error'
]
}
{
field : 'status'
containsAny : [
'failed'
]
}
{
field : 'caller'
equals : '42628537-ebd8-40bf-941a-dddd338e1fe9'
}
]
}
actions : {
actionGroups : [
{
actionGroupId : actionGroupScaleIn. id
}
]
}
enabled : true
}
}
/*------------------------------------------- NETWORK -------------------------------------------*/
var lbName = '${stackName}-loadBalancer'
var lbFrontEndName = 'LoadBalancerFrontEnd'
var lbBackendPoolNameMasterNode = 'LoadBalancerBackEndMasterNode'
2025-09-09 22:06:36 +02:00
var ipExists = publicIpAddressObject. newOrExistingOrNone == 'existing'
2025-10-15 18:45:25 +02:00
resource publicIP_OV_ifExisting 'Microsoft.Network/publicIPAddresses@2023-11-01' existing = if ( ipExists == true) {
2025-09-09 22:06:36 +02:00
name : publicIpAddressObject. name
}
var ipNew = publicIpAddressObject. newOrExistingOrNone == 'new'
resource publicIP_OV_ifNew 'Microsoft.Network/publicIPAddresses@2023-11-01' = if ( ipNew == true) {
name : publicIpAddressObject. name
2025-04-22 11:46:24 +02:00
location : location
sku : {
name : 'Standard'
2025-09-09 22:06:36 +02:00
tier : 'Regional'
2025-04-22 11:46:24 +02:00
}
properties : {
publicIPAddressVersion : 'IPv4'
publicIPAllocationMethod : 'Static'
2025-09-09 22:06:36 +02:00
dnsSettings : {
domainNameLabel : isEmptyDomain ? toLower ( '${stackName}' ) : null
}
2025-04-22 11:46:24 +02:00
}
}
2025-09-09 22:06:36 +02:00
var publicIPId = ipNew ? publicIP_OV_ifNew. id : ipExists ? publicIP_OV_ifExisting. id : ''
2025-04-22 11:46:24 +02:00
resource LoadBalancer 'Microsoft.Network/loadBalancers@2024-05-01' = {
name : lbName
location : location
sku : {
name : 'Standard'
}
properties : {
frontendIPConfigurations : [
{
name : lbFrontEndName
properties : {
2025-10-15 18:45:25 +02:00
publicIPAddress : isEmptyIp
? null
: {
id : ipNew ? publicIP_OV_ifNew. id : publicIP_OV_ifExisting. id
}
2025-04-22 11:46:24 +02:00
}
}
]
backendAddressPools : [
{
name : lbBackendPoolNameMasterNode
}
]
loadBalancingRules : [
{
name : 'HTTPSRuleforMasterNode'
properties : {
frontendIPConfiguration : {
id : resourceId ( 'Microsoft.Network/loadBalancers/frontendIPConfigurations' , lbName, lbFrontEndName)
}
backendAddressPool : {
id : resourceId ( 'Microsoft.Network/loadBalancers/backendAddressPools' , lbName, lbBackendPoolNameMasterNode)
}
frontendPort : 443
backendPort : 443
enableFloatingIP : false
protocol : 'Tcp'
enableTcpReset : true
loadDistribution : 'Default'
disableOutboundSnat : true
probe : {
id : resourceId ( 'Microsoft.Network/loadBalancers/probes' , lbName, 'probeForHTTPSRuleMasterNode' )
}
}
}
{
name : 'RTMPRuleforMasterNode'
properties : {
frontendIPConfiguration : {
id : resourceId ( 'Microsoft.Network/loadBalancers/frontendIPConfigurations' , lbName, lbFrontEndName)
}
backendAddressPool : {
id : resourceId ( 'Microsoft.Network/loadBalancers/backendAddressPools' , lbName, lbBackendPoolNameMasterNode)
}
2026-10-06 20:40:28 +02:00
frontendPort : 1945
2025-04-22 11:46:24 +02:00
backendPort : 1945
enableFloatingIP : false
protocol : 'Tcp'
enableTcpReset : true
loadDistribution : 'Default'
disableOutboundSnat : true
probe : {
id : resourceId ( 'Microsoft.Network/loadBalancers/probes' , lbName, 'probeForRTMPRuleMasterNode' )
}
}
}
{
name : 'HTTPRuleforMasterNode'
properties : {
frontendIPConfiguration : {
id : resourceId ( 'Microsoft.Network/loadBalancers/frontendIPConfigurations' , lbName, lbFrontEndName)
}
backendAddressPool : {
id : resourceId ( 'Microsoft.Network/loadBalancers/backendAddressPools' , lbName, lbBackendPoolNameMasterNode)
}
frontendPort : 80
backendPort : 80
enableFloatingIP : false
protocol : 'Tcp'
enableTcpReset : true
loadDistribution : 'Default'
disableOutboundSnat : true
probe : {
id : resourceId ( 'Microsoft.Network/loadBalancers/probes' , lbName, 'probeForHTTPSRuleMasterNode' )
}
}
}
]
probes : [
{
name : 'probeForHTTPSRuleMasterNode'
properties : {
protocol : 'Http'
requestPath : '/health/caddy'
port : 7880
probeThreshold : 3
intervalInSeconds : 10
numberOfProbes : 5
}
}
{
name : 'probeForRTMPRuleMasterNode'
properties : {
protocol : 'Tcp'
port : 1945
intervalInSeconds : 5
numberOfProbes : 2
}
}
]
outboundRules : [ ]
}
}
resource natGateway 'Microsoft.Network/natGateways@2021-05-01' = {
name : '${stackName}-natGateway'
location : location
sku : {
name : 'Standard'
}
properties : {
idleTimeoutInMinutes : 4
publicIpAddresses : [
{
id : natGatewayPublicIPAddress. id
}
]
}
}
resource natGatewayPublicIPAddress 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
name : '${stackName}-publicIPnatGateway'
location : location
sku : {
name : 'Standard'
}
properties : {
publicIPAddressVersion : 'IPv4'
publicIPAllocationMethod : 'Static'
idleTimeoutInMinutes : 4
}
}
var networkSettings = {
vNetAddressPrefix : '10.0.0.0/16'
subnetAddressPrefixMaster1 : '10.0.1.0/24'
subnetAddressPrefixMaster2 : '10.0.2.0/24'
subnetAddressPrefixMedia : '10.0.0.0/24'
vNetName : '${stackName}-virtualNetwork'
}
resource vnet_OV 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name : networkSettings. vNetName
location : location
properties : {
addressSpace : {
addressPrefixes : [
networkSettings. vNetAddressPrefix
]
}
subnets : [
{
name : 'subnetForMediaNodes'
properties : {
addressPrefix : networkSettings. subnetAddressPrefixMedia
privateEndpointNetworkPolicies : 'Disabled'
privateLinkServiceNetworkPolicies : 'Enabled'
networkSecurityGroup : {
id : openviduMediaNodeNSG. id
}
}
}
2026-10-27 19:03:50 +01:00
{
name : 'firstSubnetForMasterNodes'
properties : {
addressPrefix : networkSettings. subnetAddressPrefixMaster1
privateEndpointNetworkPolicies : 'Disabled'
privateLinkServiceNetworkPolicies : 'Enabled'
natGateway : {
id : natGateway. id
}
}
}
{
name : 'secondSubnetForMasterNodes'
properties : {
addressPrefix : networkSettings. subnetAddressPrefixMaster2
privateEndpointNetworkPolicies : 'Disabled'
privateLinkServiceNetworkPolicies : 'Enabled'
natGateway : {
id : natGateway. id
}
}
}
2025-04-22 11:46:24 +02:00
]
}
}
2026-10-27 19:03:50 +01:00
// Every subnet is declared INSIDE the virtual network. A redeploy PUTs the virtual network with
// the subnets the template lists, so a subnet declared as a separate child resource was missing
// from that list and ARM tried to delete it while the Master Node used it
// (InUseSubnetCannotBeDeleted): no Elastic/HA deployment could be redeployed. The symbolic names
// below are `existing` references, so the rest of the template uses them as before.
resource subnetMasterNode1 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
2025-04-22 11:46:24 +02:00
parent : vnet_OV
name : 'firstSubnetForMasterNodes'
}
2026-10-27 19:03:50 +01:00
resource subnetMasterNode2 'Microsoft.Network/virtualNetworks/subnets@2023-11-01' existing = {
2025-04-22 11:46:24 +02:00
parent : vnet_OV
name : 'secondSubnetForMasterNodes'
}
resource netInterfaceMasterNode1 'Microsoft.Network/networkInterfaces@2023-11-01' = {
name : '${stackName}-masterNodeNetInterface1'
location : location
properties : {
ipConfigurations : [
{
name : 'primaryIPConfig'
properties : {
privateIPAllocationMethod : 'Dynamic'
subnet : {
id : subnetMasterNode1. id
}
applicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
loadBalancerBackendAddressPools : [
{
id : LoadBalancer. properties. backendAddressPools[ 0] . id
}
]
}
}
]
networkSecurityGroup : {
id : openviduMasterNodeNSG. id
}
}
}
resource netInterfaceMasterNode2 'Microsoft.Network/networkInterfaces@2023-11-01' = {
name : '${stackName}-masterNodeNetInterface2'
location : location
properties : {
ipConfigurations : [
{
name : 'primaryIPConfig'
properties : {
privateIPAllocationMethod : 'Dynamic'
subnet : {
id : subnetMasterNode2. id
}
applicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
loadBalancerBackendAddressPools : [
{
id : LoadBalancer. properties. backendAddressPools[ 0] . id
}
]
}
}
]
networkSecurityGroup : {
id : openviduMasterNodeNSG. id
}
}
}
resource netInterfaceMasterNode3 'Microsoft.Network/networkInterfaces@2023-11-01' = {
name : '${stackName}-masterNodeNetInterface3'
location : location
properties : {
ipConfigurations : [
{
name : 'primaryIPConfig'
properties : {
privateIPAllocationMethod : 'Dynamic'
subnet : {
id : subnetMasterNode1. id
}
applicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
loadBalancerBackendAddressPools : [
{
id : LoadBalancer. properties. backendAddressPools[ 0] . id
}
]
}
}
]
networkSecurityGroup : {
id : openviduMasterNodeNSG. id
}
}
}
resource netInterfaceMasterNode4 'Microsoft.Network/networkInterfaces@2023-11-01' = {
name : '${stackName}-masterNodeNetInterface4'
location : location
properties : {
ipConfigurations : [
{
name : 'primaryIPConfig'
properties : {
privateIPAllocationMethod : 'Dynamic'
subnet : {
id : subnetMasterNode2. id
}
applicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
loadBalancerBackendAddressPools : [
{
id : LoadBalancer. properties. backendAddressPools[ 0] . id
}
]
}
}
]
networkSecurityGroup : {
id : openviduMasterNodeNSG. id
}
}
}
resource openviduMasterNodeNSG 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
name : '${stackName}-masterNodeNSG'
location : location
properties : {
securityRules : [
{
name : 'SSH'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '22'
access : 'Allow'
priority : 100
direction : 'Inbound'
}
}
{
name : 'ProbeAPI'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : 'AzureLoadBalancer'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '7880'
access : 'Allow'
priority : 500
direction : 'Inbound'
}
}
{
name : 'HTTP'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '80'
access : 'Allow'
priority : 510
direction : 'Inbound'
}
}
]
}
}
resource openviduMasterNodeASG 'Microsoft.Network/applicationSecurityGroups@2024-03-01' = {
name : '${stackName}-masterNodeASG'
location : location
}
resource loadBalancerToMasterIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'loadBalancer_to_masterNode_INGRESS'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '443'
access : 'Allow'
priority : 110
direction : 'Inbound'
}
}
2026-10-06 20:40:28 +02:00
// RTMPS Ingress: the Load Balancer keeps the client IP, so the masters must accept 1945 from anywhere
resource loadBalancerToMasterRtmpIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'loadBalancer_to_masterNode_RTMP_INGRESS'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '1945'
access : 'Allow'
priority : 111
direction : 'Inbound'
}
}
2025-10-10 14:51:55 +02:00
resource masterToMasterClusterIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_CLUSTER_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '7880'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '7880'
access : 'Allow'
priority : 115
direction : 'Inbound'
}
}
2025-04-22 11:46:24 +02:00
resource masterToMasterRedisIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_REDIS_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '7000'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '7001'
access : 'Allow'
priority : 120
direction : 'Inbound'
}
}
resource mediaToMasterRedisIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_REDIS_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '7000'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '7001'
access : 'Allow'
priority : 130
direction : 'Inbound'
}
}
resource masterToMasterMinioIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_MINIO_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9100'
access : 'Allow'
priority : 140
direction : 'Inbound'
}
}
resource masterToMasterMinioConsoleIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_MINIO_CONSOLE_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9101'
access : 'Allow'
priority : 150
direction : 'Inbound'
}
}
resource mediaToMasterMinioIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_MINIO_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9100'
access : 'Allow'
priority : 160
direction : 'Inbound'
}
}
resource masterToMasterMongoIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_MONGO_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '20000'
access : 'Allow'
priority : 170
direction : 'Inbound'
}
}
resource mediaToMasterMongoIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_MONGO_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '20000'
access : 'Allow'
priority : 180
direction : 'Inbound'
}
}
resource masterToMasterMimirGrpcIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_MIMIRGRPC_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9095'
access : 'Allow'
priority : 190
direction : 'Inbound'
}
}
resource masterToMasterMimirGossipIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_MIMIRGOSSIP_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '7946'
access : 'Allow'
priority : 200
direction : 'Inbound'
}
}
resource mediaToMasterMimirIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_MIMIR_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9009'
access : 'Allow'
priority : 210
direction : 'Inbound'
}
}
resource masterToMasterLokiGrpcIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_LOKIGRPC_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '9096'
access : 'Allow'
priority : 220
direction : 'Inbound'
}
}
resource masterToMasterLokiGossipIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_LOKIGOSSIP_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '7947'
access : 'Allow'
priority : 230
direction : 'Inbound'
}
}
resource masterToMasterDashboardIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_DASHBOARD_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '5000'
access : 'Allow'
priority : 240
direction : 'Inbound'
}
}
resource masterToMasterGrafanaIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_GRAFANA_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '3000'
access : 'Allow'
priority : 250
direction : 'Inbound'
}
}
resource mediaToMasterLokiIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_LOKI_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '3100'
access : 'Allow'
priority : 260
direction : 'Inbound'
}
}
resource masterToMasterV2CompatibilityIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_V2COMPATIBILITY_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '4443'
access : 'Allow'
priority : 270
direction : 'Inbound'
}
}
resource mediaToMasterV2CompatibilityWebhookIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_V2COMPATIBILITY_WEBHOOK_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '4443'
access : 'Allow'
priority : 280
direction : 'Inbound'
}
}
2025-07-22 14:08:25 +02:00
resource masterToMasterMeet 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
2025-04-22 11:46:24 +02:00
parent : openviduMasterNodeNSG
2025-07-22 14:08:25 +02:00
name : 'masterNode_to_masterNode_MEET_INGRESS'
2025-04-22 11:46:24 +02:00
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
2026-02-05 12:30:22 +01:00
destinationPortRange : '9080'
2025-04-22 11:46:24 +02:00
access : 'Allow'
priority : 290
direction : 'Inbound'
}
}
2026-03-13 17:46:29 +01:00
resource masterToMasterCustomApp 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'masterNode_to_masterNode_CUSTOM_APP_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '6080'
access : 'Allow'
priority : 310
direction : 'Inbound'
}
}
2025-07-22 14:08:25 +02:00
resource mediaToMasterMeetWebhookIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
2025-04-22 11:46:24 +02:00
parent : openviduMasterNodeNSG
2025-07-22 14:08:25 +02:00
name : 'mediaNode_to_masterNode_MEET_WEBHOOK_INGRESS'
2025-04-22 11:46:24 +02:00
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
2026-02-05 12:30:22 +01:00
destinationPortRange : '9080'
2025-04-22 11:46:24 +02:00
access : 'Allow'
priority : 300
direction : 'Inbound'
}
}
2026-03-13 17:46:29 +01:00
resource mediaToMasterCustomAppWebhookIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMasterNodeNSG
name : 'mediaNode_to_masterNode_CUSTOM_APP_WEBHOOK_INGRESS'
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
destinationPortRange : '6080'
access : 'Allow'
priority : 320
direction : 'Inbound'
}
}
2025-04-22 11:46:24 +02:00
resource openviduMediaNodeNSG 'Microsoft.Network/networkSecurityGroups@2023-11-01' = {
name : '${stackName}-mediaNodeNSG'
location : location
properties : {
securityRules : [
{
name : 'SSH'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '22'
access : 'Allow'
priority : 100
direction : 'Inbound'
}
}
{
name : 'TURN'
properties : {
protocol : 'Udp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '443'
access : 'Allow'
priority : 110
direction : 'Inbound'
}
}
{
name : 'WebRTC_over_TCP'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '7881'
access : 'Allow'
priority : 120
direction : 'Inbound'
}
}
{
name : 'WebRTC_using_WHIP'
properties : {
protocol : 'Udp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
destinationPortRange : '7885'
access : 'Allow'
priority : 130
direction : 'Inbound'
}
}
{
name : 'WebRTC_traffic_UDP'
properties : {
protocol : 'Udp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
2026-09-29 14:57:02 +02:00
destinationPortRange : '50000-60000'
2025-04-22 11:46:24 +02:00
access : 'Allow'
priority : 140
direction : 'Inbound'
}
}
2025-07-11 21:33:05 +02:00
{
name : 'WebRTC_traffic_TCP'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : '*'
sourcePortRange : '*'
destinationAddressPrefix : '*'
2026-10-28 11:59:09 +01:00
destinationPortRange : '50000-60000'
2025-07-11 21:33:05 +02:00
access : 'Allow'
priority : 150
direction : 'Inbound'
}
}
2025-04-22 11:46:24 +02:00
]
}
}
resource openviduMediaNodeASG 'Microsoft.Network/applicationSecurityGroups@2024-03-01' = {
name : '${stackName}-mediaNodeASG'
location : location
}
resource loadBalancerToMediaRtmpIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMediaNodeNSG
name : 'loadBalancer_to_mediaNode_RTMP_INGRESS'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : 'AzureLoadBalancer'
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
destinationPortRange : '1945'
access : 'Allow'
2025-07-22 13:52:54 +02:00
priority : 160
2025-04-22 11:46:24 +02:00
direction : 'Inbound'
}
}
resource loadBalancerToMediaHealthcheckIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
parent : openviduMediaNodeNSG
name : 'loadBalancer_to_mediaNode_HEALTHCHECK_INGRESS'
properties : {
protocol : 'Tcp'
sourceAddressPrefix : 'AzureLoadBalancer'
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
destinationPortRange : '9092'
access : 'Allow'
2025-07-22 13:52:54 +02:00
priority : 170
2025-04-22 11:46:24 +02:00
direction : 'Inbound'
}
}
2026-01-27 22:41:33 +01:00
resource masterToMediaServerIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
2025-04-22 11:46:24 +02:00
parent : openviduMediaNodeNSG
2026-01-27 22:41:33 +01:00
name : 'masterNode_to_mediaNode_SERVER_INGRESS'
2025-04-22 11:46:24 +02:00
properties : {
protocol : 'Tcp'
2026-01-27 22:41:33 +01:00
sourceApplicationSecurityGroups : [
2025-04-22 11:46:24 +02:00
{
2026-01-27 22:41:33 +01:00
id : openviduMasterNodeASG. id
2025-04-22 11:46:24 +02:00
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
destinationPortRange : '7880'
access : 'Allow'
2026-01-27 22:41:33 +01:00
priority : 200
2025-04-22 11:46:24 +02:00
direction : 'Inbound'
}
}
2026-01-27 22:41:33 +01:00
resource masterToMediaClientIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
2025-04-22 11:46:24 +02:00
parent : openviduMediaNodeNSG
2026-01-27 22:41:33 +01:00
name : 'masterNode_to_mediaNode_CLIENT_INGRESS'
2025-04-22 11:46:24 +02:00
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
2026-01-27 22:41:33 +01:00
destinationPortRange : '8080'
2025-04-22 11:46:24 +02:00
access : 'Allow'
2026-01-27 22:41:33 +01:00
priority : 210
2025-04-22 11:46:24 +02:00
direction : 'Inbound'
}
}
2026-01-27 22:41:33 +01:00
resource masterToMediaTurnTlsIngress 'Microsoft.Network/networkSecurityGroups/securityRules@2023-11-01' = {
2025-04-22 11:46:24 +02:00
parent : openviduMediaNodeNSG
2026-01-27 22:41:33 +01:00
name : 'masterNode_to_mediaNode_TURN_TLS_INGRESS'
2025-04-22 11:46:24 +02:00
properties : {
protocol : 'Tcp'
sourceApplicationSecurityGroups : [
{
id : openviduMasterNodeASG. id
}
]
sourcePortRange : '*'
destinationApplicationSecurityGroups : [
{
id : openviduMediaNodeASG. id
}
]
2026-01-27 22:41:33 +01:00
destinationPortRange : '5349'
2025-04-22 11:46:24 +02:00
access : 'Allow'
2026-01-27 22:41:33 +01:00
priority : 220
2025-04-22 11:46:24 +02:00
direction : 'Inbound'
}
}
/*------------------------------------------- STORAGE ACCOUNT ----------------------------------------*/
2025-05-13 11:01:22 +02:00
@ description ( 'Name of an existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated.' )
param storageAccountName string = ''
var isEmptyStorageAccountName = storageAccountName == ''
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = if ( isEmptyStorageAccountName == true) {
2025-04-23 13:26:33 +02:00
name : uniqueString ( resourceGroup ( ) . id)
2025-04-22 11:46:24 +02:00
location : resourceGroup ( ) . location
sku : {
name : 'Standard_LRS'
}
kind : 'StorageV2'
properties : {
accessTier : 'Cool'
supportsHttpsTrafficOnly : true
}
}
2025-05-13 14:36:12 +02:00
resource existingStorageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if ( isEmptyStorageAccountName == false) {
2025-05-13 11:01:22 +02:00
name : storageAccountName
}
resource blobContainerScaleIn 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if ( isEmptyStorageAccountName == true) {
2025-04-22 11:46:24 +02:00
name : '${storageAccount.name}/default/automation-locks'
properties : {
publicAccess : 'None'
}
}
2025-05-13 14:36:12 +02:00
@ description ( 'Name of the bucket where OpenVidu will store the recordings if a new Storage account is being creating. If not specified, a default bucket will be created. If you want to use an existing storage account, fill this parameter with the name of the container where the recordings are stored.' )
2025-06-10 16:30:41 +02:00
param appDataContainerName string = ''
2025-04-23 13:26:33 +02:00
2025-06-10 16:30:41 +02:00
var isEmptyAppDataContainerName = appDataContainerName == ''
2025-04-23 13:26:33 +02:00
2025-05-13 11:01:22 +02:00
resource blobContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = if ( isEmptyStorageAccountName == true) {
2025-06-10 16:30:41 +02:00
name : isEmptyAppDataContainerName
2025-04-23 13:26:33 +02:00
? '${storageAccount.name}/default/openvidu-appdata'
2025-06-10 16:30:41 +02:00
: '${storageAccount.name}/default/${appDataContainerName}'
properties : {
publicAccess : 'None'
}
}
2025-04-22 11:46:24 +02:00
/*------------------------------------------- OUTPUTS -------------------------------------------*/