2025-04-22 11:46:24 +02:00
{
"$schema" : "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" ,
"contentVersion" : "1.0.0.0" ,
"metadata" : {
"_generator" : {
"name" : "bicep" ,
2026-09-07 16:58:47 +02:00
"version" : "0.46.1.21595" ,
2026-10-23 15:09:52 +02:00
"templateHash" : "11077733597635088578"
2025-04-22 11:46:24 +02:00
}
} ,
"parameters" : {
"stackName" : {
"type" : "string" ,
2026-06-09 11:37:02 +02:00
"minLength" : 3 ,
"maxLength" : 15 ,
2025-04-22 11:46:24 +02:00
"metadata" : {
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."
2025-04-22 11:46:24 +02:00
}
} ,
"certificateType" : {
"type" : "string" ,
2025-09-09 13:17:37 +02:00
"defaultValue" : "letsencrypt" ,
2025-04-22 11:46:24 +02:00
"allowedValues" : [
"selfsigned" ,
"owncert" ,
"letsencrypt"
] ,
"metadata" : {
2026-07-02 17:21:42 +02:00
"description" : "[selfsigned] Not recommended for production use. Just for testing purposes or development environments. You don't need a FQDN to use this option.\n[owncert] Valid for production environments. Use your own certificate. You need a FQDN to use this option.\n[letsencrypt] Valid for production environments. Can be used with or without a FQDN (if no FQDN is provided, the public IP will be used).\n"
2025-04-22 11:46:24 +02:00
}
} ,
2025-05-23 13:53:24 +02:00
"publicIpAddressObject" : {
"type" : "object" ,
2025-04-22 11:46:24 +02:00
"metadata" : {
"description" : "Previously created Public IP address for the OpenVidu Deployment. Blank will generate a public IP"
}
} ,
"domainName" : {
"type" : "string" ,
"defaultValue" : "" ,
"metadata" : {
"description" : "Domain name for the OpenVidu Deployment. Blank will generate default domain"
}
} ,
"ownPublicCertificate" : {
"type" : "string" ,
"defaultValue" : "" ,
"metadata" : {
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
}
} ,
"ownPrivateCertificate" : {
"type" : "string" ,
"defaultValue" : "" ,
"metadata" : {
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
}
} ,
"openviduLicense" : {
"type" : "securestring" ,
"metadata" : {
"description" : "Visit https://openvidu.io/account"
}
} ,
"rtcEngine" : {
"type" : "string" ,
2026-09-29 14:56:16 +02:00
"defaultValue" : "mediasoup" ,
2025-04-22 11:46:24 +02:00
"allowedValues" : [
"pion" ,
"mediasoup"
] ,
"metadata" : {
"description" : "RTCEngine media engine to use"
}
} ,
2025-09-16 17:52:10 +02:00
"initialMeetAdminPassword" : {
"type" : "securestring" ,
"defaultValue" : "" ,
"metadata" : {
"description" : "Initial password for the 'admin' user in OpenVidu Meet. If not provided, a random password will be generated."
}
} ,
"initialMeetApiKey" : {
"type" : "securestring" ,
"defaultValue" : "" ,
"metadata" : {
"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."
}
} ,
2025-04-22 11:46:24 +02:00
"masterNodeInstanceType" : {
"type" : "string" ,
2025-12-02 19:21:16 +01:00
"defaultValue" : "Standard_B4s" ,
2025-04-22 11:46:24 +02:00
"metadata" : {
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."
2025-04-22 11:46:24 +02:00
}
} ,
"mediaNodeInstanceType" : {
"type" : "string" ,
2025-12-02 19:21:16 +01:00
"defaultValue" : "Standard_B4s" ,
2025-04-22 11:46:24 +02:00
"metadata" : {
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."
2025-04-22 11:46:24 +02:00
}
} ,
"adminUsername" : {
"type" : "string" ,
"metadata" : {
"description" : "Username for the Virtual Machine."
}
} ,
"adminSshKey" : {
2025-05-23 13:53:24 +02:00
"type" : "secureObject" ,
2025-04-22 11:46:24 +02:00
"metadata" : {
"description" : "SSH Key or password for the Virtual Machine"
}
} ,
"initialNumberOfMediaNodes" : {
"type" : "int" ,
"defaultValue" : 1 ,
"metadata" : {
"description" : "Number of initial media nodes to deploy"
}
} ,
"minNumberOfMediaNodes" : {
"type" : "int" ,
"defaultValue" : 1 ,
"metadata" : {
"description" : "Minimum number of media nodes to deploy"
}
} ,
"maxNumberOfMediaNodes" : {
"type" : "int" ,
"defaultValue" : 5 ,
"metadata" : {
"description" : "Maximum number of media nodes to deploy"
}
} ,
"scaleTargetCPU" : {
"type" : "int" ,
"defaultValue" : 50 ,
"metadata" : {
"description" : "Target CPU percentage to scale up or down"
}
} ,
2025-06-18 16:26:50 +02:00
"additionalInstallFlags" : {
"type" : "string" ,
"defaultValue" : ""
} ,
2025-04-22 11:46:24 +02:00
"datetime" : {
"type" : "string" ,
"defaultValue" : "[utcNow('u')]"
} ,
"automationAccountName" : {
"type" : "string" ,
2025-05-27 11:18:05 +02:00
"defaultValue" : "" ,
2025-04-22 11:46:24 +02:00
"metadata" : {
"description" : "Automation Account Name to create a runbook inside it for scale in"
}
2025-04-23 13:26:33 +02:00
} ,
2025-05-13 11:01:22 +02:00
"storageAccountName" : {
"type" : "string" ,
"defaultValue" : "" ,
"metadata" : {
"description" : "Name of the 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."
}
} ,
2025-04-23 13:26:33 +02:00
"containerName" : {
"type" : "string" ,
"defaultValue" : "" ,
"metadata" : {
2025-05-20 18:46:31 +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-04-23 13:26:33 +02:00
}
2025-04-22 11:46:24 +02:00
}
} ,
"variables" : {
2025-05-23 13:53:24 +02:00
"isEmptyIp" : "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'none')]" ,
2025-04-22 11:46:24 +02:00
"isEmptyDomain" : "[equals(parameters('domainName'), '')]" ,
2025-12-02 19:21:16 +01:00
"masterNodeInstanceTypeLower" : "[toLower(parameters('masterNodeInstanceType'))]" ,
"mediaNodeInstanceTypeLower" : "[toLower(parameters('mediaNodeInstanceType'))]" ,
"isMasterArm64" : "[or(or(or(or(or(or(or(contains(variables('masterNodeInstanceTypeLower'), 'ps_v'), contains(variables('masterNodeInstanceTypeLower'), 'pls_v')), contains(variables('masterNodeInstanceTypeLower'), 'pds_v')), contains(variables('masterNodeInstanceTypeLower'), 'plds_v')), contains(variables('masterNodeInstanceTypeLower'), 'psv')), contains(variables('masterNodeInstanceTypeLower'), 'plsv')), contains(variables('masterNodeInstanceTypeLower'), 'pdsv')), contains(variables('masterNodeInstanceTypeLower'), 'pldsv'))]" ,
"isMediaArm64" : "[or(or(or(or(or(or(or(contains(variables('mediaNodeInstanceTypeLower'), 'ps_v'), contains(variables('mediaNodeInstanceTypeLower'), 'pls_v')), contains(variables('mediaNodeInstanceTypeLower'), 'pds_v')), contains(variables('mediaNodeInstanceTypeLower'), 'plds_v')), contains(variables('mediaNodeInstanceTypeLower'), 'psv')), contains(variables('mediaNodeInstanceTypeLower'), 'plsv')), contains(variables('mediaNodeInstanceTypeLower'), 'pdsv')), contains(variables('mediaNodeInstanceTypeLower'), 'pldsv'))]" ,
2025-12-02 20:04:38 +01:00
"masterUbuntuSku" : "[if(variables('isMasterArm64'), 'server-arm64', 'server')]" ,
"mediaUbuntuSku" : "[if(variables('isMediaArm64'), 'server-arm64', 'server')]" ,
2025-04-22 11:46:24 +02:00
"masterNodeVMSettings" : {
2025-05-23 13:53:24 +02:00
"vmName" : "[format('{0}-VM-MasterNode', parameters('stackName'))]" ,
2025-04-22 11:46:24 +02:00
"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" : "[variables('masterUbuntuSku')]" ,
2025-04-22 11:46:24 +02:00
"version" : "latest"
} ,
"linuxConfiguration" : {
"disablePasswordAuthentication" : true ,
"ssh" : {
"publicKeys" : [
{
"path" : "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]" ,
2025-05-23 13:53:24 +02:00
"keyData" : "[parameters('adminSshKey').sshPublicKey]"
2025-04-22 11:46:24 +02:00
}
]
}
}
} ,
"mediaNodeVMSettings" : {
"vmName" : "[format('{0}-VM-MediaNode', parameters('stackName'))]" ,
"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" : "[variables('mediaUbuntuSku')]" ,
2025-04-22 11:46:24 +02:00
"version" : "latest"
} ,
"linuxConfiguration" : {
"disablePasswordAuthentication" : true ,
"ssh" : {
"publicKeys" : [
{
"path" : "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]" ,
2025-05-23 13:53:24 +02:00
"keyData" : "[parameters('adminSshKey').sshPublicKey]"
2025-04-22 11:46:24 +02:00
}
]
}
}
} ,
"networkSettings" : {
"vNetAddressPrefix" : "10.0.0.0/16" ,
"subnetAddressPrefixMaster1" : "10.0.1.0/24" ,
"subnetAddressPrefixMedia" : "10.0.0.0/24" ,
"vNetName" : "[format('{0}-virtualNetwork', parameters('stackName'))]"
} ,
"keyVaultName" : "[format('{0}-keyvault', parameters('stackName'))]" ,
"location" : "[resourceGroup().location]" ,
"tenantId" : "[subscription().tenantId]" ,
"deploymentUser" : "[deployer().objectId]" ,
2025-09-09 13:17:37 +02:00
"stringInterpolationParamsMaster" : {
2025-09-09 22:04:33 +02:00
"publicIPId" : "[variables('publicIPId')]" ,
2025-09-09 13:17:37 +02:00
"domainName" : "[parameters('domainName')]" ,
"certificateType" : "[parameters('certificateType')]" ,
"ownPublicCertificate" : "[parameters('ownPublicCertificate')]" ,
"ownPrivateCertificate" : "[parameters('ownPrivateCertificate')]" ,
"openviduLicense" : "[parameters('openviduLicense')]" ,
"rtcEngine" : "[parameters('rtcEngine')]" ,
2025-09-16 17:52:10 +02:00
"initialMeetAdminPassword" : "[parameters('initialMeetAdminPassword')]" ,
"initialMeetApiKey" : "[parameters('initialMeetApiKey')]" ,
2025-09-09 13:17:37 +02:00
"keyVaultName" : "[variables('keyVaultName')]" ,
"additionalInstallFlags" : "[parameters('additionalInstallFlags')]"
} ,
2026-10-23 14:57:54 +02:00
"installScriptTemplateMaster" : "#!/bin/bash\nset -e\nOPENVIDU_VERSION=main\nDOMAIN=\n\n# Assume azure cli is installed\n\necho \"DPkg::Lock::Timeout \\\"-1\\\";\" > /etc/apt/apt.conf.d/99timeout\n\napt-get update && apt-get install -y \\\n curl \\\n unzip \\\n jq \\\n wget\n\n# Configure Domain\nif [[ \"${domainName}\" == '' ]]; then\n # Get public IP using the get_public_ip.sh script\n PUBLIC_IP=$(/usr/local/bin/get_public_ip.sh 2>/dev/null)\n if [[ $? -ne 0 || -z \"${PUBLIC_IP}\" ]]; then\n echo \"Could not determine public IP.\"\n exit 1\n fi\n\n DOMAIN=\"$PUBLIC_IP\"\nelse\n DOMAIN=${domainName}\nfi\n\n# Wait for the keyvault availability\nMAX_WAIT=300\nWAIT_INTERVAL=1\nELAPSED_TIME=0\nwhile true; do\n # Check keyvault availability\n set +e\n az keyvault secret list --vault-name ${keyVaultName}\n\n # If it is available, exit the loop\n if [ $? -eq 0 ]; then\n break\n fi\n\n # If not, wait and check again incrementing the time\n ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))\n\n # If exceeded the maximum time, exit with error\n if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then\n exit 1\n fi\n\n # Wait for the next iteration\n sleep $WAIT_INTERVAL\ndone\nset -e\n\n# Get own private IP\nPRIVATE_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\")\n\n\n# Store usernames and generate random passwords\nDOMAIN=\"$(/usr/local/bin/store_secret.sh save DOMAIN-NAME \"$DOMAIN\")\"\n\n# Meet initial admin user and password\nMEET_INITIAL_ADMIN_USER=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-USER \"admin\")\"\nif [[ \"${initialMeetAdminPassword}\" != '' ]]; then\n MEET_INITIAL_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-PASSWORD \"${initialMeetAdminPassword}\")\"\nelse\n MEET_INITIAL_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate MEET-INITIAL-ADMIN-PASSWORD)\"\nfi\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n MEET_INITIAL_API_KEY=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-API-KEY \"${initialMeetApiKey}\")\"\nelse\n MEET_INITIAL_API_KEY=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-API-KEY \"\")\"\nfi\n\nOPENVIDU_PRO_LICENSE=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-PRO-LICENSE \"${openviduLicense}\")\"\nOPENVIDU_RTC_ENGINE=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-RTC-ENGINE \"${rtcEngine}\")\"\nREDIS_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate REDIS-PASSWORD)\"\nMONGO_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save MONGO-ADMIN-USERNAME \"mongoadmin\")\"\nMONGO_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate MONGO-ADMIN-PASSWORD)\"\nMONGO_REPLICA_SET_KEY=\"$(/usr/local/bin/store_secret.sh generate MONGO-REPLICA-SET-KEY)\"\nMINIO_ACCESS_KEY=\"$(/usr/local/bin/store_secret.sh save MINIO-ACCESS-KEY \"minioadmin\")\"\nMINIO_SECRET_KEY=\"$(/usr/local/bin/store_secret.sh generate MINIO-SECRET-KEY)\"\nDASHBOARD_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save DASHBOARD-ADMIN-USERNAME \"dashboardadmin\")\"\nDASHBOARD_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate DASHBOARD-ADMIN-PASSWORD)\"\nGRAFANA_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save GRAFANA-ADMIN-USERNAME \"grafanaadmin\")\"\nGRAFANA_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate GRAFANA-ADMIN-PASSWORD)\"\nMEET_INITIAL_ADMIN_USER=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-USER \"admin\")\"\nLIVEKIT_API_KEY=\"$(/usr/local/bin/store_secret.sh generate LIVEKIT-API-KEY \"API\" 12)\"\nLIVEKIT_API_SECRET=\"$(/usr/local/bin/store_secret.sh generate LIVEKIT-API-SECRET)\"\nOPENVIDU_VERSION=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-VERSION \"${OPENVIDU_VERSION}\")\"\nENABLED_MODULES=\"$(/usr/local/bin/store_secret.sh save ENABLED-MODULES \"observability,openviduMeet,v2compatibility\")\"\nALL_SECRETS_GENERATED=\"$(/usr/local/bin/store_secret.sh save ALL-SECRETS-GENERATED \"true\")\" \ n \ n # D o w n l o a d t o a f i l e f i r s t : p r o c e s s s u b s t i t u t i o n w o u l d s i l e n t l y r u n a
2025-09-09 13:17:37 +02:00
"after_installScriptTemplateMaster" : "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Generate URLs\nDOMAIN=$(az keyvault secret show --vault-name ${keyVaultName} --name DOMAIN-NAME --query value -o tsv)\nOPENVIDU_URL=\"https://${DOMAIN}/\"\nLIVEKIT_URL=\"wss://${DOMAIN}/\"\nDASHBOARD_URL=\"https://${DOMAIN}/dashboard/\"\nGRAFANA_URL=\"https://${DOMAIN}/grafana/\"\nMINIO_URL=\"https://${DOMAIN}/minio-console/\"\n\n# Update shared secret\naz keyvault secret set --vault-name ${keyVaultName} --name DOMAIN-NAME --value $DOMAIN\naz keyvault secret set --vault-name ${keyVaultName} --name OPENVIDU-URL --value $OPENVIDU_URL\naz keyvault secret set --vault-name ${keyVaultName} --name LIVEKIT-URL --value $LIVEKIT_URL\naz keyvault secret set --vault-name ${keyVaultName} --name DASHBOARD-URL --value $DASHBOARD_URL\naz keyvault secret set --vault-name ${keyVaultName} --name GRAFANA-URL --value $GRAFANA_URL\naz keyvault secret set --vault-name ${keyVaultName} --name MINIO-URL --value $MINIO_URL\n\naz keyvault secret show --vault-name ${keyVaultName} --name MINIO-URL\n\nif [[ $? -ne 0 ]]; then\n echo \"Error updating keyvault\"\nfi\n" ,
2026-01-27 22:41:33 +01:00
"update_config_from_secretScriptTemplateMaster" : "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Installation directory\nINSTALL_DIR=\"/opt/openvidu\"\nCLUSTER_CONFIG_DIR=\"${INSTALL_DIR}/config/cluster\"\nMASTER_NODE_CONFIG_DIR=\"${INSTALL_DIR}/config/node\"\n\n# Replace DOMAIN_NAME\nexport DOMAIN=$(az keyvault secret show --vault-name ${keyVaultName} --name DOMAIN-NAME --query value -o tsv)\nif [[ -n \"$DOMAIN\" ]]; then\n sed -i \"s/DOMAIN_NAME=.*/DOMAIN_NAME=$DOMAIN/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nelse\n exit 1\nfi\n\n# Get the rest of the values\nexport REDIS_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name REDIS-PASSWORD --query value -o tsv)\nexport OPENVIDU_RTC_ENGINE=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-RTC-ENGINE --query value -o tsv)\nexport OPENVIDU_PRO_LICENSE=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-PRO-LICENSE --query value -o tsv)\nexport MONGO_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-ADMIN-USERNAME --query value -o tsv)\nexport MONGO_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-ADMIN-PASSWORD --query value -o tsv)\nexport MONGO_REPLICA_SET_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-REPLICA-SET-KEY --query value -o tsv)\nexport DASHBOARD_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-USERNAME --query value -o tsv)\nexport DASHBOARD_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-PASSWORD --query value -o tsv)\nexport MINIO_ACCESS_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MINIO-ACCESS-KEY --query value -o tsv)\nexport MINIO_SECRET_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MINIO-SECRET-KEY --query value -o tsv)\nexport GRAFANA_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name GRAFANA-ADMIN-USERNAME --query value -o tsv)\nexport GRAFANA_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name GRAFANA-ADMIN-PASSWORD --query value -o tsv)\nexport LIVEKIT_API_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name LIVEKIT-API-KEY --query value -o tsv)\nexport LIVEKIT_API_SECRET=$(az keyvault secret show --vault-name ${keyVaultName} --name LIVEKIT-API-SECRET --query value -o tsv)\nexport MEET_INITIAL_ADMIN_USER=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-USER --query value -o tsv)\nexport MEET_INITIAL_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-PASSWORD --query value -o tsv)\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n export MEET_INITIAL_API_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-API-KEY --query value -o tsv)\nfi\nexport ENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)\n\n# Replace rest of the values\nsed -i \"s/REDIS_PASSWORD=.*/REDIS_PASSWORD=$REDIS_PASSWORD/\" \"${MASTER_NODE_CONFIG_DIR}/master_node.env\"\nsed -i \"s/OPENVIDU_RTC_ENGINE=.*/OPENVIDU_RTC_ENGINE=$OPENVIDU_RTC_ENGINE/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/OPENVIDU_PRO_LICENSE=.*/OPENVIDU_PRO_LICENSE=$OPENVIDU_PRO_LICENSE/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/MONGO_ADMIN_USERNAME=.*/MONGO_ADMIN_USERNAME=$MONGO_ADMIN_USERNAME/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/MONGO_ADMIN_PASSWORD=.*/MONGO_ADMIN_PASSWORD=$MONGO_ADMIN_PASSWORD/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/MONGO_REPLICA_SET_KEY=.*/MONGO_REPLICA_SET_KEY=$MONGO_REPLICA_SET_KEY/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/DASHBOARD_ADMIN_USERNAME=.*/DASHBOARD_ADMIN_USERNAME=$DASHBOARD_ADMIN_USERNAME/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/DASHBOARD_ADMIN_PASSWORD=.*/DASHBOARD_ADMIN_PASSWORD=$DASHBOARD_ADMIN_PASSWORD/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\" \ n s e d - i \
"update_secret_from_configScriptTemplateMaster" : "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Installation directory\nINSTALL_DIR=\"/opt/openvidu\"\nCLUSTER_CONFIG_DIR=\"${INSTALL_DIR}/config/cluster\"\nMASTER_NODE_CONFIG_DIR=\"${INSTALL_DIR}/config/node\"\n\n# Get current values of the config\nREDIS_PASSWORD=\"$(/usr/local/bin/get_value_from_config.sh REDIS_PASSWORD \"${MASTER_NODE_CONFIG_DIR}/master_node.env\")\"\nDOMAIN_NAME=\"$(/usr/local/bin/get_value_from_config.sh DOMAIN_NAME \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nOPENVIDU_RTC_ENGINE=\"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_RTC_ENGINE \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nOPENVIDU_PRO_LICENSE=\"$(/usr/local/bin/get_value_from_config.sh OPENVIDU_PRO_LICENSE \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMONGO_ADMIN_USERNAME=\"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_USERNAME \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMONGO_ADMIN_PASSWORD=\"$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_PASSWORD \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMONGO_REPLICA_SET_KEY=\"$(/usr/local/bin/get_value_from_config.sh MONGO_REPLICA_SET_KEY \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMINIO_ACCESS_KEY=\"$(/usr/local/bin/get_value_from_config.sh MINIO_ACCESS_KEY \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMINIO_SECRET_KEY=\"$(/usr/local/bin/get_value_from_config.sh MINIO_SECRET_KEY \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nDASHBOARD_ADMIN_USERNAME=\"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_USERNAME \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nDASHBOARD_ADMIN_PASSWORD=\"$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_PASSWORD \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nGRAFANA_ADMIN_USERNAME=\"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_USERNAME \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nGRAFANA_ADMIN_PASSWORD=\"$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_PASSWORD \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nLIVEKIT_API_KEY=\"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_KEY \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nLIVEKIT_API_SECRET=\"$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_SECRET \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\"\nMEET_INITIAL_ADMIN_USER=\"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_USER \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\")\"\nMEET_INITIAL_ADMIN_PASSWORD=\"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_PASSWORD \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\")\"\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n MEET_INITIAL_API_KEY=\"$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_API_KEY \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\")\"\nfi\nENABLED_MODULES=\"$(/usr/local/bin/get_value_from_config.sh ENABLED_MODULES \"${CLUSTER_CONFIG_DIR}/openvidu.env\")\" \ n \ n # U p d a t e s h a r e d s e c r e t \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e R E D I S - P A S S W O R D - - v a l u e $ R E D I S _ P A S S W O R D \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e D O M A I N - N A M E - - v a l u e $ D O M A I N _ N A M E \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e O P E N V I D U - R T C - E N G I N E - - v a l u e $ O P E N V I D U _ R T C _ E N G I N E \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e O P E N V I D U - P R O - L I C E N S E - - v a l u e $ O P E N V I D U _ P R O _ L I C E N S E \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e M O N G O - A D M I N - U S E R N A M E - - v a l u e $ M O N G O _ A D M I N _ U S E R N A M E \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e M O N G O - A D M I N - P A S S W O R D - - v a l u e $ M O N G O _ A D M I N _ P A S S W O R D \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e M O N G O - R E P L I C A - S E T - K E Y - - v a l u e $ M O N G O _ R E P L I C A _ S E T _ K E Y \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e M I N I O - A C C E S S - K E Y - - v a l u e $ M I N I O _ A C C E S S _ K E Y \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e M I N I O - S E C R E T - K E Y - - v a l u e $ M I N I O _ S E C R E T _ K E Y \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e D A S H B O A R D - A D M I N - U S E R N A M E - - v a l u e $ D A S H B O A R D _ A D M I N _ U S E R N A M E \ n a z k e y v a u l t s e c r e t s e t - - v a u l t - n a m e $ { k e y V a u l t N a m e } - - n a m e D A S H B O A R D - A D M I N - P A S S W O R D - - v a l u e $ D A S H B O A R D _ A D M I N _ P A S S W O R D \ n a z k e y v a u l t s e c r e t s e t - - v a u
2025-04-22 11:46:24 +02:00
"get_value_from_configScriptMaster" : "#!/bin/bash\nset -e\n\n# Function to get the value of a given key from the environment file\nget_value() {\n local key=\"$1\"\n local file_path=\"$2\"\n\n # Use grep to find the line with the key, ignoring lines starting with #\n # Use awk to split on '=' and print the second field, which is the value\n local value=$(grep -E \"^\\s*$key\\s*=\" \"$file_path\" | awk -F= '{print $2}' | sed 's/#.*//; s/^\\s*//; s/\\s*$//')\n\n # If the value is empty, return \"none\"\n if [ -z \"$value\" ]; then\n echo \"none\"\n else\n echo \"$value\"\n fi\n}\n\n# Check if the correct number of arguments are supplied\nif [ \"$#\" -ne 2 ]; then\n echo \"Usage: $0 <key> <file_path>\"\n exit 1\nfi\n\n# Get the key and file path from the arguments\nkey=\"$1\"\nfile_path=\"$2\"\n\n# Get and print the value\nget_value \"$key\" \"$file_path\"\n" ,
2025-09-16 19:17:20 +02:00
"store_secretScriptTemplateMaster" : "#!/bin/bash\nset -e\n\naz login --identity --allow-no-subscriptions > /dev/null\n\n# Modes: save, generate\n# save mode: save the secret in the secret manager\n# generate mode: generate a random password and save it in the secret manager\nMODE=\"$1\"\n\nif [[ \"$MODE\" == \"generate\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n PREFIX=\"${3:-}\"\n LENGTH=\"${4:-44}\"\n RANDOM_PASSWORD=\"$(openssl rand -base64 64 | tr -d '+/=\\n' | cut -c -${LENGTH})\"\n RANDOM_PASSWORD=\"${PREFIX}${RANDOM_PASSWORD}\"\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $RANDOM_PASSWORD > /dev/null\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$RANDOM_PASSWORD\"\nelif [[ \"$MODE\" == \"save\" ]]; then\n SECRET_KEY_NAME=\"$2\"\n SECRET_VALUE=\"$3\"\n # If empty value, store it empty\n if [[ -z \"$SECRET_VALUE\" ]]; then\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --file /dev/null > /dev/null\n else\n az keyvault secret set --vault-name ${keyVaultName} --name $SECRET_KEY_NAME --value $SECRET_VALUE > /dev/null\n fi\n if [[ $? -ne 0 ]]; then\n echo \"Error generating secret\"\n fi\n echo \"$SECRET_VALUE\"\nelse\n exit 1\nfi\n" ,
2025-09-09 22:04:33 +02:00
"get_public_ip" : "#!/bin/bash\naz login --identity --allow-no-subscriptions > /dev/null\n\naz network public-ip show \\\n --id ${publicIPId} \\\n --query \"ipAddress\" -o tsv\n" ,
openvidu-deployment: harden Azure elastic deployment
Bounded check_app_ready then FINISH-MASTER-NODE published only after the
master health gate (was: FINISH before a blind sleep 150), media double
gate (install on ALL-SECRETS-GENERATED, start on FINISH) overlapping the
heavy media install with the master's (~30-40% faster cluster-ready),
robust installer fetch, config_blobStorage RBAC-propagation retry,
Key Vault wait 100->300s, set -e on master install, terminal media
failure path, secret-leaking bash -x shebangs removed, apt no-ops
dropped. ARM JSON recompiled (bicep 0.45.15, metadata-only noise).
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 1m40s, wait-ready 4m1s, destroy 7m14s.
2026-07-28 20:16:04 +02:00
"check_app_readyScriptMaster" : "#!/bin/bash\nset -e\nMAX_WAIT=1200\nWAIT_INTERVAL=5\nELAPSED_TIME=0\nwhile true; do\n HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy | head -n1 | awk '{print $2}')\n if [ \"$HTTP_STATUS\" = \"200\" ]; then\n break\n fi\n ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))\n if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then\n exit 1\n fi\n sleep 5\ndone\n" ,
2025-04-22 11:46:24 +02:00
"restartScriptMaster" : "#!/bin/bash\nset -e\n# Stop all services\nsystemctl stop openvidu\n\n# Update config from secret\n/usr/local/bin/update_config_from_secret.sh\n\n# Start all services\nsystemctl start openvidu\n" ,
openvidu-deployment: harden Azure elastic deployment
Bounded check_app_ready then FINISH-MASTER-NODE published only after the
master health gate (was: FINISH before a blind sleep 150), media double
gate (install on ALL-SECRETS-GENERATED, start on FINISH) overlapping the
heavy media install with the master's (~30-40% faster cluster-ready),
robust installer fetch, config_blobStorage RBAC-propagation retry,
Key Vault wait 100->300s, set -e on master install, terminal media
failure path, secret-leaking bash -x shebangs removed, apt no-ops
dropped. ARM JSON recompiled (bicep 0.45.15, metadata-only noise).
Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 1m40s, wait-ready 4m1s, destroy 7m14s.
2026-07-28 20:16:04 +02:00
"config_blobStorageTemplate" : "#!/bin/bash\nset -e\n\n# Install dir and config dir\nINSTALL_DIR=\"/opt/openvidu\"\nCLUSTER_CONFIG_DIR=\"${INSTALL_DIR}/config/cluster\"\n\n# Retry login + storage key fetch to allow the Contributor role assignment to propagate\nMAX_WAIT=300\nWAIT_INTERVAL=1\nELAPSED_TIME=0\nset +e\nwhile true; do\n az login --identity\n\n # Config azure blob storage\n AZURE_ACCOUNT_NAME=\"${storageAccountName}\"\n AZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)\n\n # If the key was fetched successfully, exit the loop\n if [ $? -eq 0 ]; then\n break\n fi\n\n # If not, wait and check again incrementing the time\n ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))\n\n # If exceeded the maximum time, exit with error\n if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then\n exit 1\n fi\n\n # Wait before the next check\n sleep $WAIT_INTERVAL\ndone\nset -e\nAZURE_CONTAINER_NAME=\"${storageAccountContainerName}\"\n\nsed -i \"s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_ACCOUNT_KEY=.*|AZURE_ACCOUNT_KEY=$AZURE_ACCOUNT_KEY|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s|AZURE_CONTAINER_NAME=.*|AZURE_CONTAINER_NAME=$AZURE_CONTAINER_NAME|\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\n" ,
2025-09-09 13:17:37 +02:00
"installScriptMaster" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
"after_installScriptMaster" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
2025-09-09 22:04:33 +02:00
"get_public_ip_script" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('get_public_ip')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
2025-09-09 13:17:37 +02:00
"update_config_from_secretScriptMaster" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('update_config_from_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
"update_secret_from_configScriptMaster" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('update_secret_from_configScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
"store_secretScriptMaster" : "[reduce(items(variables('stringInterpolationParamsMaster')), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
"base64installMaster" : "[base64(variables('installScriptMaster'))]" ,
"base64after_installMaster" : "[base64(variables('after_installScriptMaster'))]" ,
"base64update_config_from_secretMaster" : "[base64(variables('update_config_from_secretScriptMaster'))]" ,
"base64update_secret_from_configMaster" : "[base64(variables('update_secret_from_configScriptMaster'))]" ,
2025-04-22 11:46:24 +02:00
"base64get_value_from_configMaster" : "[base64(variables('get_value_from_configScriptMaster'))]" ,
2025-09-09 13:17:37 +02:00
"base64store_secretMaster" : "[base64(variables('store_secretScriptMaster'))]" ,
2025-09-09 22:04:33 +02:00
"base64get_public_ipMaster" : "[base64(variables('get_public_ip_script'))]" ,
2025-04-22 11:46:24 +02:00
"base64check_app_readyMaster" : "[base64(variables('check_app_readyScriptMaster'))]" ,
"base64restartMaster" : "[base64(variables('restartScriptMaster'))]" ,
2026-09-07 16:58:47 +02:00
"userDataTemplateMasterNode" : "#!/bin/bash\nset -eu -o pipefail\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# after_install.sh\necho ${base64after_install} | base64 -d > /usr/local/bin/after_install.sh\nchmod +x /usr/local/bin/after_install.sh\n\n# update_config_from_secret.sh\necho ${base64update_config_from_secret} | base64 -d > /usr/local/bin/update_config_from_secret.sh\nchmod +x /usr/local/bin/update_config_from_secret.sh\n\n# update_secret_from_config.sh\necho ${base64update_secret_from_config} | base64 -d > /usr/local/bin/update_secret_from_config.sh\nchmod +x /usr/local/bin/update_secret_from_config.sh\n\n# get_value_from_config.sh\necho ${base64get_value_from_config} | base64 -d > /usr/local/bin/get_value_from_config.sh\nchmod +x /usr/local/bin/get_value_from_config.sh\n\n# store_secret.sh\necho ${base64store_secret} | base64 -d > /usr/local/bin/store_secret.sh\nchmod +x /usr/local/bin/store_secret.sh\n\n# get_public_ip.sh\necho ${base64get_public_ip} | base64 -d > /usr/local/bin/get_public_ip.sh\nchmod +x /usr/local/bin/get_public_ip.sh\n\n# check_app_ready.sh\necho ${base64check_app_ready} | base64 -d > /usr/local/bin/check_app_ready.sh\nchmod +x /usr/local/bin/check_app_ready.sh\n\n# restart.sh\necho ${base64restart} | base64 -d > /usr/local/bin/restart.sh\nchmod +x /usr/local/bin/restart.sh\n\necho ${base64config_blobStorage} | base64 -d > /usr/local/bin/config_blobStorage.sh\nchmod +x /usr/local/bin/config_blobStorage.sh\n\n# Install azure cli\nAZURE_CLI_VERSION=2.90.0\napt-get install -y apt-transport-https ca-certificates gnupg lsb-release\ncurl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg\necho \"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\napt-get update\napt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs)\n\naz login --identity --allow-no-subscriptions\n\necho \"DPkg::Lock::Timeout \\\"-1\\\";\" > /etc/apt/apt.conf.d/99timeout\n\nexport HOME=\"/root\"\n\n# Install OpenVidu\n/usr/local/bin/install.sh || { echo \"[OpenVidu] error installing OpenVidu\"; exit 1; }\n\n#Config blob storage\n/usr/local/bin/config_blobStorage.sh || { echo \"[OpenVidu] error configuring Blob Storage\"; exit 1; }\n\n# Start OpenVidu\nsystemctl start openvidu || { echo \"[OpenVidu] error starting OpenVidu\"; exit 1; }\n\n# Update shared secret\n/usr/local/bin/after_install.sh || { echo \"[OpenVidu] error updating shared secret\"; exit 1; }\n\n# Launch on reboot\necho \"@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log\" 2>&1 | crontab\n\n# check_app_ready.sh internally caps its wait at 1200s\n/usr/local/bin/check_app_ready.sh || { echo \"[OpenVidu] master node did not become healthy\"; exit 1; }\n\naz keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value \"true\"\n\nset +e\naz storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key\nset -e\n" ,
2026-10-23 14:57:54 +02:00
"installScriptTemplateMedia" : "#!/bin/bash\nset -e\nDOMAIN=\n\n# Install dependencies\necho \"DPkg::Lock::Timeout \\\"-1\\\";\" > /etc/apt/apt.conf.d/99timeout\n\napt-get update && apt-get install -y \\\n curl \\\n unzip \\\n jq \\\n wget\n\n# Get own private IP\nPRIVATE_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\")\n\n# Gate 1: wait for master secrets before installing\nWAIT_INTERVAL=5\nMAX_RETRIES=360\nRETRIES=0\nset +e\nwhile true; do\n ALL_SECRETS_GENERATED=$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)\n if [ \"$ALL_SECRETS_GENERATED\" == \"true\" ]; then\n break\n fi\n RETRIES=$((RETRIES + 1))\n if [ $RETRIES -ge $MAX_RETRIES ]; then\n echo \"[OpenVidu] timed out after 30 min waiting for ALL-SECRETS-GENERATED\"\n exit 1\n fi\n sleep $WAIT_INTERVAL\ndone\nset -e\n# Get current shared secret\nDOMAIN=$(az keyvault secret show --vault-name ${keyVaultName} --name DOMAIN-NAME --query value -o tsv)\nOPENVIDU_PRO_LICENSE=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-PRO-LICENSE --query value -o tsv)\nREDIS_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name REDIS-PASSWORD --query value -o tsv)\nENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)\nOPENVIDU_VERSION=\"$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-VERSION --query value -o tsv)\"\n\n# Get Master Node private IP\nMASTER_NODE_IP=${privateIPMasterNode}\n\n# Download to a file first: process substitution would silently run an empty script on a transient curl failure\nINSTALLER_SCRIPT=\"/tmp/install_ov_media_node.sh\"\ncurl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o \"$INSTALLER_SCRIPT\" \"https://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh\"\nif [ ! -s \"$INSTALLER_SCRIPT\" ]; then\n echo \"[OpenVidu] failed to download the media node installer script\"\n exit 1\nfi\n\n# Base command\nINSTALL_COMMAND=\"sh $INSTALLER_SCRIPT\"\n\n# Common arguments\nCOMMON_ARGS=(\n \"--no-tty\"\n \"--install\"\n \"--environment=azure\"\n \"--deployment-type=elastic\"\n \"--node-role='media-node'\"\n \"--master-node-private-ip=$MASTER_NODE_IP\"\n \"--private-ip=$PRIVATE_IP\"\n \"--enabled-modules='$ENABLED_MODULES'\"\n \"--redis-password=$REDIS_PASSWORD\"\n)\n# Construct the final command with all arguments\nFINAL_COMMAND=\"$INSTALL_COMMAND $(printf \"%s \" \"${COMMON_ARGS[@]}\")\"\n\n# Install OpenVidu\nexec bash -c \"$FINAL_COMMAND\"\n" ,
2025-04-22 11:46:24 +02:00
"stopMediaNodeParams" : {
"subscriptionId" : "[subscription().subscriptionId]" ,
"resourceGroupName" : "[resourceGroup().name]" ,
"vmScaleSetName" : "[format('{0}-mediaNodeScaleSet', parameters('stackName'))]" ,
2025-05-13 11:59:15 +02:00
"storageAccountName" : "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName'))]"
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
"stop_media_nodesScriptMediaTemplate" : "#!/bin/bash\nset -e\n\n# Pause the health watchdog before draining so it never reimages a node that is being\n# drained for scale-in (synchronous; the drain runs outside the watchdog's cgroup).\ntouch /run/openvidu-media-health.paused\nsystemctl stop openvidu-media-health 2>/dev/null || true\n\nif ! (set -o noclobber ; echo > /tmp/global.lock) ; then\n exit 1 # the global.lock already exists\nfi\n\n# Execute if docker is installed\nif [ -x \"$(command -v docker)\" ]; then\n\n echo \"Stopping media node services and waiting for termination...\"\n docker container kill --signal=SIGQUIT openvidu 2>/dev/null || true\n docker container kill --signal=SIGQUIT ingress 2>/dev/null || true\n docker container kill --signal=SIGQUIT egress 2>/dev/null || true\n for agent_container in $(docker ps --filter \"label=openvidu-agent=true\" --format '{{.Names}}' 2>/dev/null); do\n docker container kill --signal=SIGQUIT \"$agent_container\" 2>/dev/null || true\n done\n\n # If this node is being replaced because it is unhealthy, do not wait for sessions to end.\n if [ -f /run/openvidu-unhealthy ]; then\n docker ps -q | xargs -r docker kill >/dev/null 2>&1 || true\n fi\n\n # Wait for running containers to not be openvidu, ingress, egress or an openvidu agent\n while [ \"$(docker ps --filter 'label=openvidu-agent=true' -q 2>/dev/null | wc -l)\" -gt 0 ] || \\\n [ \"$(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null)\" = \"true\" ] || \\\n [ \"$(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null)\" = \"true\" ] || \\\n [ \"$(docker inspect -f '{{.State.Running}}' egress 2>/dev/null)\" = \"true\" ]; do\n echo \"Waiting for containers to stop...\"\n sleep 5\n done\nfi\n\naz login --identity\n\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nSUBSCRIPTION_ID=${subscriptionId}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\nRESOURCE_ID=/subscriptions/$SUBSCRIPTION_ID/resourcegroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Compute/virtualMachineScaleSets/$VM_SCALE_SET_NAME\nTIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\n\naz tag update --resource-id $RESOURCE_ID --operation replace --tags \"STATUS\"=\"HEALTHY\" \"InstanceDeleteTime\"=\"$TIMESTAMP\" \"storageAccount\"=\"${storageAccountName}\"\n\naz vmss delete-instances --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-ids $INSTANCE_ID\n" ,
2026-10-06 20:47:00 +02:00
"openviduMediaHealthScript" : "#!/bin/bash\n# OpenVidu Media Node health watchdog.\n#\n# Asks the cloud to replace this Media Node with a new one, keeping the number of\n# Media Nodes, through /usr/local/bin/openvidu-media-replace.sh, when:\n# - its bootstrap failed (the user-data wrote the bootstrap-failed marker): after a\n# cool-down, so a persistent failure does not churn nodes and can be inspected,\n# - its bootstrap did not finish BOOTSTRAP_DEADLINE_SEC after boot,\n# - LiveKit fails its health check (HTTP 200 on PROBE_URL): for HARD_UNHEALTHY_AFTER_SEC\n# while it is down (connection refused or closed), or for SOFT_UNHEALTHY_AFTER_SEC\n# while it answers an error or times out. Soft failures are not counted while the CPU\n# is saturated (an overloaded LiveKit reports Not Ready), and no failure is counted in\n# the WARMUP_SEC after openvidu.service (re)started.\n# Nothing is replaced while no master answers a Redis PING (MASTER_ADDRS): a master outage\n# fails the health check of every Media Node at once, and a new node could not bootstrap\n# either; LiveKit failures are not counted until a master answers again. Once requested,\n# the replacement is requested again every REPLACE_RETRY_SEC until the node goes away.\n#\n# Settings: /etc/openvidu/media-health.env (sourced every cycle; MASTER_ADDRS=\"ip:port ...\")\n# Pause: touch /etc/openvidu/media-health.disabled (drain scripts use /run/openvidu-media-health.paused)\n# Logs: journalctl -u openvidu-media-health\n#\n# This file is embedded verbatim in CloudFormation !Sub blocks, Bicep strings and\n# Terraform heredocs, so it must never contain a dollar sign or a percent sign followed\n# by an opening brace, nor two opening braces in a row.\n\nSTATE_DIR=/var/lib/openvidu-media-health\nINSTALLED_MARKER=\"$STATE_DIR/installed\"\nBOOTSTRAP_FAILED_MARKER=\"$STATE_DIR/bootstrap-failed\"\nREPLACE_SCRIPT=/usr/local/bin/openvidu-media-replace.sh\nENV_FILE=/etc/openvidu/media-health.env\nPAUSE_FILE=/etc/openvidu/media-health.disabled\nDRAIN_PAUSE_FILE=/run/openvidu-media-health.paused\n\nlog() { echo \"[media-health] $*\"; }\n\n# Seconds since boot: monotonic, unaffected by clock steps\nuptime_sec() { cut -d. -f1 /proc/uptime; }\n\n# Busy CPU percentage since the previous call, in BUSY (0 on the first call)\ncpu_busy() {\n read -r _ c_user c_nice c_system c_idle c_iowait c_irq c_softirq c_steal _ < /proc/stat\n total=$((c_user + c_nice + c_system + c_idle + c_iowait + c_irq + c_softirq + c_steal))\n idle=$((c_idle + c_iowait))\n BUSY=0\n if [ -n \"$PREV_TOTAL\" ] && [ \"$total\" -gt \"$PREV_TOTAL\" ]; then\n BUSY=$((100 * (total - PREV_TOTAL - idle + PREV_IDLE) / (total - PREV_TOTAL)))\n fi\n PREV_TOTAL=$total\n PREV_IDLE=$idle\n}\n\n# openvidu.service is running and entered the active state less than WARMUP_SEC ago\n# (a stopped or failed openvidu.service is not starting: no warm-up for it)\nopenvidu_starting() {\n case \"$(systemctl show -p ActiveState --value openvidu 2>/dev/null)\" in\n active|activating|reloading) ;;\n *) return 1 ;;\n esac\n since=$(systemctl show -p ActiveEnterTimestampMonotonic --value openvidu 2>/dev/null)\n case \"$since\" in ''|0|*[!0-9]*) return 1 ;; esac\n [ $(($(uptime_sec) - since / 1000000)) -lt \"$WARMUP_SEC\" ]\n}\n\n# A master answers a Redis PING (+PONG, or -NOAUTH when a password is required). A bare\n# TCP connect would also succeed against a wedged Redis.\nmaster_serving() {\n [ -n \"$MASTER_ADDRS\" ] || return 1\n for addr in $MASTER_ADDRS; do\n host=$(echo \"$addr\" | cut -d: -f1)\n port=$(echo \"$addr\" | cut -d: -f2)\n 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)\n case \"$reply\" i n \ n + P O N G * | - N O A U T H * ) r e t u r n 0 ; ; \ n e s a c \ n d o n e \ n r e t u r n 1 \ n } \ n \ n # R e q u e s t t h e r e p l a c e m e n t o f t h i s n o d e ( $ 1 = r e a s o n ) . G a t e d o n a m a s t e r a n s w e r i n g , e x c e p t \ n # i n t h e b o o t s t r a p p h a s e b e f o r e t h e m a s t e r a d d r e s s e s a r e k n o w n .
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
"openviduMediaHealthUnit" : "[Unit]\nDescription=OpenVidu Media Node health watchdog\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/openvidu-media-health.sh\nRestart=always\nRestartSec=30\n\n[Install]\nWantedBy=multi-user.target\n" ,
2026-10-16 13:02:11 +02:00
"media_replaceScriptMediaTemplate" : "#!/bin/bash\n# openvidu-media-replace.sh \"<reason>\"\n# Requests the reimage of THIS Media Node, keeping the scale set capacity and the\n# scale-in protection: reimage provisions a new OS disk and the userData bootstrap runs\n# again on the same instance id. Called by the health watchdog; it never signals it.\n\nmkdir -p /var/lib/openvidu-media-health\necho \"$(date -u +%FT%TZ) $1\" >> /var/lib/openvidu-media-health/replace-requests.log\ntouch /run/openvidu-unhealthy\n# Withdrawn if the request fails: the node then stays, and if it recovers\n# a later scale-in must still wait for its sessions\ntrap '[ $? -eq 0 ] || rm -f /run/openvidu-unhealthy' EXIT\n\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nINSTANCE_RESOURCE_ID=$(curl -s -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo \"$INSTANCE_RESOURCE_ID\" | awk -F'/' '{print $NF}')\nif [ -z \"$INSTANCE_ID\" ]; then\n echo \"[media-replace] could not read this instance id from IMDS\"\n exit 1\nfi\n\nn=0\nwhile true; do\n n=$((n + 1))\n if az login --identity >/dev/null 2>&1 && \\\n az vmss reimage --resource-group \"$RESOURCE_GROUP_NAME\" --name \"$VM_SCALE_SET_NAME\" --instance-ids \"$INSTANCE_ID\" --no-wait; then\n echo \"[media-replace] reimage requested for instance $INSTANCE_ID ($1)\"\n exit 0\n fi\n if [ \"$n\" -ge 5 ]; then\n echo \"[media-replace] reimage could not be requested after $n attempts\"\n exit 1\n fi\n echo \"[media-replace] reimage request failed (attempt $n), retrying in 12s\"\n sleep 12\ndone\n" ,
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
"userDataMediaNodeTemplate" : "#!/bin/bash\nset -eu -o pipefail\n\n# Mark this media node's bootstrap as failed so the health watchdog replaces it\nov_media_bootstrap_failed() {\n echo \"[OpenVidu] media node bootstrap failed: $1\"\n mkdir -p /var/lib/openvidu-media-health\n echo \"$1\" > /var/lib/openvidu-media-health/bootstrap-failed\n exit 1\n}\n\n# Introduce the scripts in the instance\n# install.sh\necho ${base64install} | base64 -d > /usr/local/bin/install.sh\nchmod +x /usr/local/bin/install.sh\n\n# stop_media_nodes.sh\necho ${base64stop} | base64 -d > /usr/local/bin/stop_media_node.sh\nchmod +x /usr/local/bin/stop_media_node.sh\n\n# openvidu-media-health.sh (health watchdog), openvidu-media-replace.sh (reimage this node) and its unit\necho ${base64mediaHealth} | base64 -d > /usr/local/bin/openvidu-media-health.sh\nchmod +x /usr/local/bin/openvidu-media-health.sh\necho ${base64mediaReplace} | base64 -d > /usr/local/bin/openvidu-media-replace.sh\nchmod +x /usr/local/bin/openvidu-media-replace.sh\necho ${base64mediaHealthUnit} | base64 -d > /etc/systemd/system/openvidu-media-health.service\n\necho \"DPkg::Lock::Timeout \\\"-1\\\";\" > /etc/apt/apt.conf.d/99timeout\n\napt-get update && apt-get install -y jq\n\n# Install azure cli\nAZURE_CLI_VERSION=2.90.0\napt-get install -y apt-transport-https ca-certificates gnupg lsb-release\ncurl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg\necho \"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\napt-get update\napt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs)\n\naz login --identity\n\n# Protect from scale in actions\nRESOURCE_GROUP_NAME=${resourceGroupName}\nVM_SCALE_SET_NAME=${vmScaleSetName}\nBEFORE_INSTANCE_ID=$(curl -H Metadata:true --noproxy \"*\" \"http://169.254.169.254/metadata/instance?api-version=2021-02-01\" | jq -r '.compute.resourceId')\nINSTANCE_ID=$(echo $BEFORE_INSTANCE_ID | awk -F'/' '{print $NF}')\naz vmss update --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME --instance-id $INSTANCE_ID --protect-from-scale-in true\n\nexport HOME=\"/root\"\n\n# Start the Media Node health watchdog before install.sh (az CLI is installed and the node\n# is scale-in protected): it backstops a hung or failed bootstrap and, once running, reimages\n# the node if LiveKit stays unhealthy. Write MASTER_ADDRS first so its master guard is armed.\nmkdir -p /etc/openvidu\necho 'MASTER_ADDRS=\"${privateIPMasterNode}:7000\"' > /etc/openvidu/media-health.env\nsystemctl daemon-reload\nsystemctl enable --now openvidu-media-health\n\n# Install OpenVidu\n/usr/local/bin/install.sh || ov_media_bootstrap_failed \"install.sh failed\"\n\n# Gate 2: wait for master readiness before starting\nWAIT_INTERVAL=5\nMAX_RETRIES=360\nRETRIES=0\nset +e\nwhile true; do\n FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv 2>/dev/null)\n if [ \"$FINISH_MASTER_NODE\" == \"true\" ]; then\n break\n fi\n RETRIES=$((RETRIES + 1))\n if [ $RETRIES -ge $MAX_RETRIES ]; then\n ov_media_bootstrap_failed \"timed out after 30 min waiting for FINISH-MASTER-NODE\"\n fi\n sleep $WAIT_INTERVAL\ndone\nset -e\n\n# Start OpenVidu\nsystemctl start openvidu || ov_media_bootstrap_failed \"systemctl start openvidu failed\"\n\n# Bootstrap finished: the watchdog switches from bootstrap-deadline mode to runtime health checks\nmkdir -p /var/lib/openvidu-media-health\ntouch /var/lib/openvidu-media-health/installed\n" ,
2025-04-22 11:46:24 +02:00
"stop_media_nodesScriptMedia" : "[reduce(items(variables('stopMediaNodeParams')), createObject('value', variables('stop_media_nodesScriptMediaTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
2025-04-23 13:26:33 +02:00
"base64stopMediaNode" : "[base64(variables('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
"media_replaceScriptMedia" : "[reduce(items(variables('stopMediaNodeParams')), createObject('value', variables('media_replaceScriptMediaTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value]" ,
"base64media_replaceScriptMedia" : "[base64(variables('media_replaceScriptMedia'))]" ,
"base64openviduMediaHealth" : "[base64(variables('openviduMediaHealthScript'))]" ,
"base64openviduMediaHealthUnit" : "[base64(variables('openviduMediaHealthUnit'))]" ,
2025-05-27 11:18:05 +02:00
"isEmptyAutomationAccountName" : "[equals(parameters('automationAccountName'), '')]" ,
2025-05-23 13:53:24 +02:00
"ipExists" : "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'existing')]" ,
"ipNew" : "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'new')]" ,
2025-09-09 22:04:33 +02:00
"publicIPId" : "[if(variables('ipNew'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), if(variables('ipExists'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), ''))]" ,
2025-05-13 11:01:22 +02:00
"isEmptyStorageAccountName" : "[equals(parameters('storageAccountName'), '')]" ,
2025-04-23 13:26:33 +02:00
"isEmptyContainerName" : "[equals(parameters('containerName'), '')]"
2025-04-22 11:46:24 +02:00
} ,
"resources" : [
{
"type" : "Microsoft.KeyVault/vaults" ,
"apiVersion" : "2023-07-01" ,
"name" : "[variables('keyVaultName')]" ,
"location" : "[variables('location')]" ,
"properties" : {
"enabledForDeployment" : true ,
"enabledForDiskEncryption" : false ,
"enabledForTemplateDeployment" : true ,
"tenantId" : "[variables('tenantId')]" ,
"enableSoftDelete" : false ,
"accessPolicies" : [
{
"objectId" : "[reference(resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName), '2023-09-01', 'full').identity.principalId]" ,
"tenantId" : "[variables('tenantId')]" ,
"permissions" : {
"secrets" : [
"get" ,
"set" ,
"list"
]
}
} ,
{
"objectId" : "[reference(resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName'))), '2024-07-01', 'full').identity.principalId]" ,
"tenantId" : "[variables('tenantId')]" ,
"permissions" : {
"secrets" : [
"get"
]
}
} ,
{
"objectId" : "[variables('deploymentUser')]" ,
"tenantId" : "[variables('tenantId')]" ,
"permissions" : {
"secrets" : [
"get" ,
"list" ,
"set" ,
"delete" ,
"recover" ,
"backup" ,
"restore"
]
}
}
] ,
"sku" : {
"name" : "standard" ,
"family" : "A"
} ,
"networkAcls" : {
"defaultAction" : "Allow" ,
"bypass" : "AzureServices"
}
} ,
"dependsOn" : [
"[resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName)]" ,
"[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Compute/virtualMachines" ,
"apiVersion" : "2023-09-01" ,
"name" : "[variables('masterNodeVMSettings').vmName]" ,
"location" : "[variables('location')]" ,
"identity" : {
"type" : "SystemAssigned"
} ,
"properties" : {
"hardwareProfile" : {
"vmSize" : "[parameters('masterNodeInstanceType')]"
} ,
"storageProfile" : {
"osDisk" : {
"createOption" : "FromImage" ,
"managedDisk" : {
"storageAccountType" : "[variables('masterNodeVMSettings').osDiskType]"
} ,
"diskSizeGB" : 100
} ,
"imageReference" : "[variables('masterNodeVMSettings').ubuntuOSVersion]"
} ,
"networkProfile" : {
"networkInterfaces" : [
{
"id" : "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName')))]"
}
]
} ,
"osProfile" : {
"computerName" : "[variables('masterNodeVMSettings').vmName]" ,
"adminUsername" : "[parameters('adminUsername')]" ,
"linuxConfiguration" : "[variables('masterNodeVMSettings').linuxConfiguration]"
} ,
2025-09-09 13:17:37 +02:00
"userData" : "[base64(reduce(items(createObject('base64install', variables('base64installMaster'), 'base64after_install', variables('base64after_installMaster'), 'base64update_config_from_secret', variables('base64update_config_from_secretMaster'), 'base64update_secret_from_config', variables('base64update_secret_from_configMaster'), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', variables('base64store_secretMaster'), 'base64get_public_ip', variables('base64get_public_ipMaster'), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'base64config_blobStorage', base64(reduce(items(createObject('storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName')), 'storageAccountKey', listKeys(resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id)), '2021-04-01').keys[0].value, 'storageAccountContainerName', if(variables('isEmptyContainerName'), 'openvidu-appdata', format('{0}', parameters('containerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'keyVaultName', variables('keyVaultName'), 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName')))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]"
2025-04-22 11:46:24 +02:00
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName')))]" ,
2025-09-09 22:04:33 +02:00
"[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]" ,
2025-04-23 13:26:33 +02:00
"[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]"
2025-04-22 11:46:24 +02:00
]
} ,
{
"type" : "Microsoft.Compute/virtualMachineScaleSets" ,
"apiVersion" : "2024-07-01" ,
"name" : "[format('{0}-mediaNodeScaleSet', parameters('stackName'))]" ,
"location" : "[variables('location')]" ,
"tags" : {
"InstanceDeleteTime" : "[parameters('datetime')]" ,
2025-05-13 11:59:15 +02:00
"storageAccount" : "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName'))]"
2025-04-22 11:46:24 +02:00
} ,
"identity" : {
"type" : "SystemAssigned"
} ,
"sku" : {
"name" : "[parameters('mediaNodeInstanceType')]" ,
"tier" : "Standard" ,
"capacity" : "[parameters('initialNumberOfMediaNodes')]"
} ,
"properties" : {
"overprovision" : true ,
"upgradePolicy" : {
"mode" : "Automatic"
} ,
"singlePlacementGroup" : true ,
"platformFaultDomainCount" : 1 ,
"virtualMachineProfile" : {
"storageProfile" : {
"osDisk" : {
"createOption" : "FromImage" ,
"managedDisk" : {
"storageAccountType" : "[variables('mediaNodeVMSettings').osDiskType]"
} ,
"diskSizeGB" : 50
} ,
"imageReference" : "[variables('mediaNodeVMSettings').ubuntuOSVersion]"
} ,
"osProfile" : {
"computerNamePrefix" : "[variables('mediaNodeVMSettings').vmName]" ,
"adminUsername" : "[parameters('adminUsername')]" ,
"linuxConfiguration" : "[variables('mediaNodeVMSettings').linuxConfiguration]"
} ,
"networkProfile" : {
"networkInterfaceConfigurations" : [
{
"name" : "[format('{0}-mediaNodeNetInterface', parameters('stackName'))]" ,
"properties" : {
"primary" : true ,
"ipConfigurations" : [
{
"name" : "ipconfigMediaNode" ,
"properties" : {
"subnet" : {
"id" : "[reference(resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName), '2023-11-01').subnets[0].id]"
} ,
"applicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"publicIPAddressConfiguration" : {
"name" : "publicIPAddressMediaNode" ,
"properties" : {
"publicIPAddressVersion" : "IPv4"
}
}
}
}
] ,
"networkSecurityGroup" : {
"id" : "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
}
}
}
]
} ,
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
"userData" : "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('privateIPMasterNode', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('installScriptTemplateMedia')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64stop', variables('base64stopMediaNode'), 'base64mediaHealth', variables('base64openviduMediaHealth'), 'base64mediaHealthUnit', variables('base64openviduMediaHealthUnit'), 'base64mediaReplace', variables('base64media_replaceScriptMedia'), 'privateIPMasterNode', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'resourceGroupName', resourceGroup().name, 'vmScaleSetName', format('{0}-mediaNodeScaleSet', parameters('stackName')), 'keyVaultName', variables('keyVaultName'))), createObject('value', variables('userDataMediaNodeTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]"
2025-04-22 11:46:24 +02:00
}
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNoderNetInterface', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]" ,
2025-04-23 13:26:33 +02:00
"[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ,
2025-04-22 11:46:24 +02:00
"[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]"
]
} ,
{
"type" : "Microsoft.Insights/autoscalesettings" ,
"apiVersion" : "2022-10-01" ,
"name" : "[format('{0}-autoscaleSettings', parameters('stackName'))]" ,
"location" : "[resourceGroup().location]" ,
"properties" : {
"profiles" : [
{
"name" : "openvidu-medianode-autoscale" ,
"capacity" : {
"minimum" : "[string(parameters('minNumberOfMediaNodes'))]" ,
"maximum" : "[string(parameters('maxNumberOfMediaNodes'))]" ,
"default" : "[string(parameters('initialNumberOfMediaNodes'))]"
} ,
"rules" : [
{
"metricTrigger" : {
"metricName" : "Percentage CPU" ,
"metricNamespace" : "Microsoft.Compute/virtualMachineScaleSets" ,
"metricResourceUri" : "[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]" ,
"statistic" : "Average" ,
"operator" : "GreaterThan" ,
"threshold" : "[parameters('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" : "[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]" ,
"statistic" : "Average" ,
"operator" : "LessThan" ,
"threshold" : "[parameters('scaleTargetCPU')]" ,
"timeAggregation" : "Average" ,
"timeWindow" : "PT5M" ,
"timeGrain" : "PT1M"
} ,
"scaleAction" : {
"direction" : "Decrease" ,
"type" : "ChangeCount" ,
"value" : "1" ,
"cooldown" : "PT5M"
}
}
]
}
] ,
"enabled" : true ,
"targetResourceUri" : "[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Authorization/roleAssignments" ,
"apiVersion" : "2022-04-01" ,
"name" : "[guid(format('roleAssignmentForMasterNode{0}', variables('masterNodeVMSettings').vmName))]" ,
"properties" : {
"roleDefinitionId" : "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]" ,
2025-09-11 20:28:23 +02:00
"principalId" : "[reference(resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName), '2023-09-01', 'full').identity.principalId]" ,
"principalType" : "ServicePrincipal"
2025-04-22 11:46:24 +02:00
} ,
"dependsOn" : [
"[resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName)]"
]
} ,
{
"type" : "Microsoft.Authorization/roleAssignments" ,
"apiVersion" : "2022-04-01" ,
"name" : "[guid(format('roleAssignmentForScaleSet{0}', format('{0}-mediaNodeScaleSet', parameters('stackName'))))]" ,
"properties" : {
"roleDefinitionId" : "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]" ,
2025-09-11 20:28:23 +02:00
"principalId" : "[reference(resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName'))), '2024-07-01', 'full').identity.principalId]" ,
"principalType" : "ServicePrincipal"
2025-04-22 11:46:24 +02:00
} ,
"dependsOn" : [
"[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Insights/actionGroups" ,
"apiVersion" : "2023-01-01" ,
2025-12-02 18:29:44 +01:00
"name" : "actiongroupScaleIn" ,
2025-04-22 11:46:24 +02:00
"location" : "global" ,
"properties" : {
"groupShortName" : "scaleinag" ,
"enabled" : true ,
"automationRunbookReceivers" : [
{
"name" : "scalein" ,
"useCommonAlertSchema" : false ,
2026-06-16 21:16:00 +02:00
"automationAccountId" : "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.automationAccountId.value]" ,
2025-04-22 11:46:24 +02:00
"runbookName" : "scaleInRunbook" ,
2026-06-16 21:16:00 +02:00
"webhookResourceId" : "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.webhookId.value]" ,
2025-04-22 11:46:24 +02:00
"isGlobalRunbook" : false ,
2026-06-16 21:16:00 +02:00
"serviceUri" : "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.webhookUri.value]"
2025-04-22 11:46:24 +02:00
}
]
} ,
"dependsOn" : [
"[resourceId('Microsoft.Resources/deployments', 'WebhookDeployment')]"
]
} ,
{
"type" : "Microsoft.Insights/activityLogAlerts" ,
"apiVersion" : "2020-10-01" ,
"name" : "ScaleInAlertRule" ,
"location" : "global" ,
"properties" : {
"scopes" : [
"[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
] ,
"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" : [
{
2025-12-02 18:29:44 +01:00
"actionGroupId" : "[resourceId('Microsoft.Insights/actionGroups', 'actiongroupScaleIn')]"
2025-04-22 11:46:24 +02:00
}
]
} ,
"enabled" : true
} ,
"dependsOn" : [
2025-12-02 18:29:44 +01:00
"[resourceId('Microsoft.Insights/actionGroups', 'actiongroupScaleIn')]" ,
2025-04-22 11:46:24 +02:00
"[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]"
]
} ,
{
2025-09-09 13:17:37 +02:00
"condition" : "[equals(variables('ipNew'), true())]" ,
2025-04-22 11:46:24 +02:00
"type" : "Microsoft.Network/publicIPAddresses" ,
"apiVersion" : "2023-11-01" ,
2025-09-09 13:17:37 +02:00
"name" : "[parameters('publicIpAddressObject').name]" ,
2025-04-22 11:46:24 +02:00
"location" : "[variables('location')]" ,
"sku" : {
"name" : "Standard" ,
"tier" : "Regional"
} ,
"properties" : {
"publicIPAddressVersion" : "IPv4" ,
"publicIPAllocationMethod" : "Static" ,
"dnsSettings" : {
2025-09-09 13:17:37 +02:00
"domainNameLabel" : "[if(variables('isEmptyDomain'), toLower(format('{0}', parameters('stackName'))), null())]"
2025-04-22 11:46:24 +02:00
}
}
} ,
{
"type" : "Microsoft.Network/virtualNetworks" ,
"apiVersion" : "2023-11-01" ,
"name" : "[variables('networkSettings').vNetName]" ,
"location" : "[variables('location')]" ,
"properties" : {
"addressSpace" : {
"addressPrefixes" : [
"[variables('networkSettings').vNetAddressPrefix]"
]
} ,
"subnets" : [
{
"name" : "subnetForMediaNodes" ,
"properties" : {
"addressPrefix" : "[variables('networkSettings').subnetAddressPrefixMedia]" ,
"privateEndpointNetworkPolicies" : "Disabled" ,
"privateLinkServiceNetworkPolicies" : "Enabled" ,
"networkSecurityGroup" : {
"id" : "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
}
}
}
]
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/virtualNetworks/subnets" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]" ,
"properties" : {
"addressPrefix" : "[variables('networkSettings').subnetAddressPrefixMaster1]" ,
"privateEndpointNetworkPolicies" : "Disabled" ,
"privateLinkServiceNetworkPolicies" : "Enabled"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]"
]
} ,
{
"type" : "Microsoft.Network/networkInterfaces" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}-masterNoderNetInterface', parameters('stackName'))]" ,
"location" : "[variables('location')]" ,
"properties" : {
"ipConfigurations" : [
{
"name" : "primaryIPConfig" ,
"properties" : {
"privateIPAllocationMethod" : "Dynamic" ,
"subnet" : {
"id" : "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]"
} ,
"applicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
2025-09-09 13:17:37 +02:00
"publicIPAddress" : "[if(variables('isEmptyIp'), null(), createObject('id', if(variables('ipNew'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name))))]"
2025-04-22 11:46:24 +02:00
}
}
] ,
"networkSecurityGroup" : {
"id" : "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]"
}
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
2025-09-09 13:17:37 +02:00
"[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]" ,
2025-04-22 11:46:24 +02:00
"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'firstSubnetForMasterNodes')]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}-masterNoderNSG', parameters('stackName'))]" ,
"location" : "[variables('location')]" ,
"properties" : {
"securityRules" : [
{
"name" : "SSH" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceAddressPrefix" : "*" ,
"sourcePortRange" : "*" ,
"destinationAddressPrefix" : "*" ,
"destinationPortRange" : "22" ,
"access" : "Allow" ,
"priority" : 100 ,
"direction" : "Inbound"
}
} ,
{
"name" : "HTTP" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceAddressPrefix" : "*" ,
"sourcePortRange" : "*" ,
"destinationAddressPrefix" : "*" ,
"destinationPortRange" : "80" ,
"access" : "Allow" ,
"priority" : 110 ,
"direction" : "Inbound"
}
} ,
{
"name" : "HTTPS" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceAddressPrefix" : "*" ,
"sourcePortRange" : "*" ,
"destinationAddressPrefix" : "*" ,
"destinationPortRange" : "443" ,
"access" : "Allow" ,
"priority" : 120 ,
"direction" : "Inbound"
}
} ,
{
"name" : "RTMP" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceAddressPrefix" : "*" ,
"sourcePortRange" : "*" ,
"destinationAddressPrefix" : "*" ,
"destinationPortRange" : "1935" ,
"access" : "Allow" ,
"priority" : 130 ,
"direction" : "Inbound"
}
} ,
{
"name" : "MinIO" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceAddressPrefix" : "*" ,
"sourcePortRange" : "*" ,
"destinationAddressPrefix" : "*" ,
"destinationPortRange" : "9000" ,
"access" : "Allow" ,
"priority" : 140 ,
"direction" : "Inbound"
}
}
]
}
} ,
{
"type" : "Microsoft.Network/applicationSecurityGroups" ,
"apiVersion" : "2024-03-01" ,
"name" : "[format('{0}-masterNodeASG', parameters('stackName'))]" ,
"location" : "[variables('location')]"
} ,
2025-10-10 14:51:55 +02:00
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_CLUSTER_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "7880" ,
"access" : "Allow" ,
"priority" : 145 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
2025-04-22 11:46:24 +02:00
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_REDIS_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "7000" ,
"access" : "Allow" ,
"priority" : 150 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_MINIO_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "9100" ,
"access" : "Allow" ,
"priority" : 160 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_MONGO_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "20000" ,
"access" : "Allow" ,
"priority" : 170 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_LOKI_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "3100" ,
"access" : "Allow" ,
"priority" : 180 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_MIMIR_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "9009" ,
"access" : "Allow" ,
"priority" : 190 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_V2COMPATIBILITY_WEBHOOK_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "4443" ,
"access" : "Allow" ,
"priority" : 200 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
2025-07-22 14:08:25 +02:00
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_MEET_WEBHOOK_INGRESS')]" ,
2025-04-22 11:46:24 +02:00
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
2026-02-05 12:30:22 +01:00
"destinationPortRange" : "9080" ,
2025-04-22 11:46:24 +02:00
"access" : "Allow" ,
"priority" : 210 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
2026-03-13 17:46:29 +01:00
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-masterNoderNSG', parameters('stackName')), 'mediaNode_to_masterNode_CUSTOM_APP_WEBHOOK_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "6080" ,
"access" : "Allow" ,
"priority" : 220 ,
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNoderNSG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
]
} ,
2025-04-22 11:46:24 +02:00
{
"type" : "Microsoft.Network/networkSecurityGroups" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}-mediaNoderNSG', parameters('stackName'))]" ,
"location" : "[variables('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" : "*" ,
"destinationPortRanges" : [
"50000" ,
"60000"
] ,
"access" : "Allow" ,
"priority" : 150 ,
"direction" : "Inbound"
}
2025-04-22 11:46:24 +02:00
}
]
}
} ,
{
"type" : "Microsoft.Network/applicationSecurityGroups" ,
"apiVersion" : "2024-03-01" ,
"name" : "[format('{0}-mediaNodeASG', parameters('stackName'))]" ,
"location" : "[variables('location')]"
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-mediaNoderNSG', parameters('stackName')), 'masterNode_to_mediaNode_RTMP_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "1935" ,
"access" : "Allow" ,
2025-07-21 21:21:04 +02:00
"priority" : 160 ,
2025-04-22 11:46:24 +02:00
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-mediaNoderNSG', parameters('stackName')), 'masterNode_to_mediaNode_TURN_TLS_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "5349" ,
"access" : "Allow" ,
2025-07-21 21:21:04 +02:00
"priority" : 170 ,
2025-04-22 11:46:24 +02:00
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-mediaNoderNSG', parameters('stackName')), 'masterNode_to_mediaNode_SERVER_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "7880" ,
"access" : "Allow" ,
2025-07-21 21:21:04 +02:00
"priority" : 180 ,
2025-04-22 11:46:24 +02:00
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
]
} ,
{
"type" : "Microsoft.Network/networkSecurityGroups/securityRules" ,
"apiVersion" : "2023-11-01" ,
"name" : "[format('{0}/{1}', format('{0}-mediaNoderNSG', parameters('stackName')), 'masterNode_to_mediaNode_HTTP_WHIP_INGRESS')]" ,
"properties" : {
"protocol" : "Tcp" ,
"sourceApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]"
}
] ,
"sourcePortRange" : "*" ,
"destinationApplicationSecurityGroups" : [
{
"id" : "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]"
}
] ,
"destinationPortRange" : "8080" ,
"access" : "Allow" ,
2025-07-21 21:21:04 +02:00
"priority" : 190 ,
2025-04-22 11:46:24 +02:00
"direction" : "Inbound"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ,
"[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNoderNSG', parameters('stackName')))]"
]
} ,
{
2025-05-13 11:01:22 +02:00
"condition" : "[equals(variables('isEmptyStorageAccountName'), true())]" ,
2025-04-22 11:46:24 +02:00
"type" : "Microsoft.Storage/storageAccounts" ,
"apiVersion" : "2023-01-01" ,
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 11:01:22 +02:00
"condition" : "[equals(variables('isEmptyStorageAccountName'), true())]" ,
2025-04-22 11:46:24 +02:00
"type" : "Microsoft.Storage/storageAccounts/blobServices/containers" ,
"apiVersion" : "2023-01-01" ,
2025-04-23 13:26:33 +02:00
"name" : "[format('{0}/default/automation-locks', uniqueString(resourceGroup().id))]" ,
"properties" : {
"publicAccess" : "None"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]"
]
} ,
{
2025-05-13 11:01:22 +02:00
"condition" : "[equals(variables('isEmptyStorageAccountName'), true())]" ,
2025-04-23 13:26:33 +02:00
"type" : "Microsoft.Storage/storageAccounts/blobServices/containers" ,
"apiVersion" : "2023-01-01" ,
"name" : "[if(variables('isEmptyContainerName'), format('{0}/default/openvidu-appdata', uniqueString(resourceGroup().id)), format('{0}/default/{1}', uniqueString(resourceGroup().id), parameters('containerName')))]" ,
2025-04-22 11:46:24 +02:00
"properties" : {
"publicAccess" : "None"
} ,
"dependsOn" : [
2025-04-23 13:26:33 +02:00
"[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]"
2025-04-22 11:46:24 +02:00
]
} ,
{
"type" : "Microsoft.Resources/deployments" ,
2026-06-16 21:16:00 +02:00
"apiVersion" : "2025-04-01" ,
2025-04-22 11:46:24 +02:00
"name" : "WebhookDeployment" ,
"properties" : {
"expressionEvaluationOptions" : {
"scope" : "inner"
} ,
"mode" : "Incremental" ,
"parameters" : {
2025-05-27 11:18:05 +02:00
"automationAccountName" : "[if(variables('isEmptyAutomationAccountName'), createObject('value', uniqueString(resourceGroup().id, resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName))), createObject('value', parameters('automationAccountName')))]" ,
2025-04-22 11:46:24 +02:00
"runbookName" : {
"value" : "scaleInRunbook"
} ,
"webhookName" : {
"value" : "webhookForScaleIn"
} ,
"WebhookExpiryTime" : {
"value" : "2035-03-30T00:00:00Z"
} ,
"_artifactsLocation" : {
2025-04-22 12:06:04 +02:00
"value" : "https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1"
2025-04-22 11:46:24 +02:00
}
} ,
"template" : {
"$schema" : "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#" ,
"contentVersion" : "1.0.0.0" ,
"parameters" : {
"automationAccountName" : {
"type" : "String" ,
"metadata" : {
"description" : "Automation account name"
}
} ,
"webhookName" : {
"type" : "String" ,
"metadata" : {
"description" : "Webhook Name"
}
} ,
"runbookName" : {
"type" : "String" ,
"metadata" : {
"description" : "Runbook Name for which webhook will be created"
}
} ,
"WebhookExpiryTime" : {
"type" : "String" ,
"metadata" : {
"description" : "Webhook Expiry time"
}
} ,
"_artifactsLocation" : {
2025-04-22 12:06:04 +02:00
"defaultValue" : "https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1" ,
2025-04-22 11:46:24 +02:00
"type" : "String" ,
"metadata" : {
"description" : "URI to artifacts location"
}
}
} ,
"resources" : [
{
"type" : "Microsoft.Automation/automationAccounts" ,
"apiVersion" : "2020-01-13-preview" ,
"name" : "[parameters('automationAccountName')]" ,
"location" : "[resourceGroup().location]" ,
"identity" : {
"type" : "SystemAssigned"
} ,
"properties" : {
"sku" : {
"name" : "Basic"
}
} ,
"resources" : [
{
"type" : "runbooks" ,
"apiVersion" : "2018-06-30" ,
"name" : "[parameters('runbookName')]" ,
"location" : "[resourceGroup().location]" ,
"dependsOn" : [
"[parameters('automationAccountName')]"
] ,
"properties" : {
"runbookType" : "PowerShell72" ,
"logProgress" : "true" ,
"description" : "Scale In Runbook" ,
"publishContentLink" : {
"uri" : "[parameters('_artifactsLocation')]" ,
"version" : "1.0.0.0"
}
}
} ,
{
"type" : "webhooks" ,
"apiVersion" : "2018-06-30" ,
"name" : "[parameters('webhookName')]" ,
"dependsOn" : [
"[parameters('automationAccountName')]" ,
"[parameters('runbookName')]"
] ,
"properties" : {
"isEnabled" : true ,
"expiryTime" : "[parameters('WebhookExpiryTime')]" ,
"runbook" : {
"name" : "[parameters('runbookName')]"
}
}
}
]
} ,
{
"type" : "Microsoft.Authorization/roleAssignments" ,
"apiVersion" : "2022-04-01" ,
"name" : "[guid(format('roleAutomationContributorAssignmentAutomationAccount{0}', parameters('automationAccountName')))]" ,
"properties" : {
"roleDefinitionId" : "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]" ,
"principalId" : "[reference(resourceId('Microsoft.Automation/automationAccounts', parameters('automationAccountName')), '2023-11-01', 'full').identity.principalId]" ,
"principalType" : "ServicePrincipal"
} ,
"dependsOn" : [
"[resourceId('Microsoft.Automation/automationAccounts', parameters('automationAccountName'))]"
]
}
] ,
"outputs" : {
"webhookUri" : {
"type" : "String" ,
"value" : "[reference(parameters('webhookName')).uri]"
} ,
"automationAccountId" : {
"type" : "string" ,
"value" : "[resourceId('Microsoft.Automation/automationAccounts', parameters('automationAccountName'))]"
} ,
"webhookId" : {
"type" : "string" ,
"value" : "[resourceId('Microsoft.Automation/automationAccounts/webhooks', parameters('automationAccountName'), parameters('webhookName'))]"
}
}
}
2025-05-27 11:18:05 +02:00
} ,
"dependsOn" : [
"[resourceId('Microsoft.Compute/virtualMachines', variables('masterNodeVMSettings').vmName)]"
]
2025-04-22 11:46:24 +02:00
}
2025-05-23 13:53:24 +02:00
]
2025-04-22 11:46:24 +02:00
}