{ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "metadata": { "_generator": { "name": "bicep", "version": "0.46.1.21595", "templateHash": "128203215953929559" } }, "parameters": { "stackName": { "type": "string", "minLength": 3, "maxLength": 15, "metadata": { "description": "Stack name. Maximum 15 characters: used as the Azure Key Vault name prefix (\"-keyvault\"), which is limited to 24 characters." } }, "certificateType": { "type": "string", "defaultValue": "letsencrypt", "allowedValues": [ "selfsigned", "owncert", "letsencrypt" ], "metadata": { "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" } }, "domainName": { "type": "string", "defaultValue": "", "metadata": { "description": "Domain name for the OpenVidu Deployment." } }, "ownPublicCertificate": { "type": "string", "defaultValue": "", "metadata": { "description": "If certificate type is 'owncert', this parameter will be used to specify the public certificate in base64 format" } }, "ownPrivateCertificate": { "type": "string", "defaultValue": "", "metadata": { "description": "If certificate type is 'owncert', this parameter will be used to specify the private certificate in base64 format" } }, "publicIpAddressObject": { "type": "object", "metadata": { "description": "Name of the PublicIPAddress resource in Azure when using certificateType 'owncert' or 'letsencrypt'" } }, "openviduLicense": { "type": "securestring", "metadata": { "description": "Visit https://openvidu.io/account" } }, "rtcEngine": { "type": "string", "defaultValue": "mediasoup", "allowedValues": [ "pion", "mediasoup" ], "metadata": { "description": "RTCEngine media engine to use" } }, "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." } }, "masterNodeInstanceType": { "type": "string", "defaultValue": "Standard_B4s", "metadata": { "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." } }, "masterNodesDiskSize": { "type": "int", "defaultValue": 200, "metadata": { "description": "Size of the disk in GB" } }, "mediaNodeInstanceType": { "type": "string", "defaultValue": "Standard_B4s", "metadata": { "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." } }, "adminUsername": { "type": "string", "metadata": { "description": "Username for the Virtual Machine." } }, "adminSshKey": { "type": "secureObject", "metadata": { "description": "SSH Key for the Virtual Machine." } }, "initialNumberOfMediaNodes": { "type": "int", "defaultValue": 1, "metadata": { "description": "Number of initial media nodes to deploy. Ignored when fixedNumberOfMediaNodes > 0" } }, "minNumberOfMediaNodes": { "type": "int", "defaultValue": 1, "metadata": { "description": "Minimum number of media nodes to deploy. Ignored when fixedNumberOfMediaNodes > 0" } }, "maxNumberOfMediaNodes": { "type": "int", "defaultValue": 5, "metadata": { "description": "Maximum number of media nodes to deploy. Ignored when fixedNumberOfMediaNodes > 0" } }, "scaleTargetCPU": { "type": "int", "defaultValue": 50, "metadata": { "description": "Target CPU percentage to scale up or down. Ignored when fixedNumberOfMediaNodes > 0" } }, "fixedNumberOfMediaNodes": { "type": "int", "defaultValue": 0, "minValue": 0, "metadata": { "description": "If > 0, keeps this fixed number of media nodes: autoscaling is pinned to it (minimum = maximum = this number) and initialNumberOfMediaNodes, minNumberOfMediaNodes, maxNumberOfMediaNodes and scaleTargetCPU are ignored. When it is lowered, the extra media nodes are drained gracefully, one at a time, by the scale-in runbook. If 0 (default), the deployment is elastic and autoscaling is enabled" } }, "additionalInstallFlags": { "type": "string", "defaultValue": "" }, "datetime": { "type": "string", "defaultValue": "[utcNow('u')]" }, "automationAccountName": { "type": "string", "defaultValue": "", "metadata": { "description": "Automation Account Name to create a runbook inside it for scale in" } }, "scaleInWebhookNameSeed": { "type": "string", "defaultValue": "[utcNow()]", "metadata": { "description": "Do not set it. Azure Automation returns the URI of a webhook only when it creates it, and the scale-in action group needs that URI on every deployment, so every deployment creates the scale-in webhook under a new name: from this value (a new one each time it is not given) and the name of the deployment (a new one on every redeploy from the portal)." } }, "storageAccountName": { "type": "string", "defaultValue": "", "metadata": { "description": "Name of an existing storage account. It is essential that this parameter is filled just when you want to save recordings and still using the same container after an update. If not specified, a new storage account will be generated." } }, "appDataContainerName": { "type": "string", "defaultValue": "", "metadata": { "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." } } }, "variables": { "isEmptyIp": "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'none')]", "isEmptyDomain": "[equals(parameters('domainName'), '')]", "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'))]", "masterUbuntuSku": "[if(variables('isMasterArm64'), 'server-arm64', 'server')]", "mediaUbuntuSku": "[if(variables('isMediaArm64'), 'server-arm64', 'server')]", "masterNodeVMSettings": { "osDiskType": "StandardSSD_LRS", "osDiskSize": "[parameters('masterNodesDiskSize')]", "ubuntuOSVersion": { "publisher": "Canonical", "offer": "ubuntu-24_04-lts", "sku": "[variables('masterUbuntuSku')]", "version": "latest" }, "linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { "publicKeys": [ { "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", "keyData": "[parameters('adminSshKey').sshPublicKey]" } ] } } }, "mediaNodeVMSettings": { "vmName": "[format('{0}-VN-MediaNode', parameters('stackName'))]", "osDiskType": "StandardSSD_LRS", "ubuntuOSVersion": { "publisher": "Canonical", "offer": "ubuntu-24_04-lts", "sku": "[variables('mediaUbuntuSku')]", "version": "latest" }, "linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { "publicKeys": [ { "path": "[format('/home/{0}/.ssh/authorized_keys', parameters('adminUsername'))]", "keyData": "[parameters('adminSshKey').sshPublicKey]" } ] } } }, "keyVaultName": "[format('{0}-keyvault', parameters('stackName'))]", "location": "[resourceGroup().location]", "tenantId": "[subscription().tenantId]", "deploymentUser": "[deployer().objectId]", "installScriptTemplateMaster": "#!/bin/bash -x\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\n# Wait for the keyvault availability\nMAX_WAIT=300\nWAIT_INTERVAL=1\nELAPSED_TIME=0\nset +e\nwhile true; do\n # Check keyvault availability\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 # Esperar antes de la próxima comprobación\n sleep $WAIT_INTERVAL\ndone\nset -e\n\nMASTER_NODE_NUM=${masterNodeNum}\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# Store current private IP\nPRIVATE_IP=\"$(/usr/local/bin/store_secret.sh save MASTER-NODE-${masterNodeNum}-PRIVATE-IP $PRIVATE_IP)\"\n\n\nif [[ $MASTER_NODE_NUM -eq 1 ]] && [[ \"$ALL_SECRETS_GENERATED\" == \"\" || \"$ALL_SECRETS_GENERATED\" == \"false\" ]]; then\n\n # Configure Domain name\n if [[ \"${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\"\n else\n DOMAIN=${domainName}\n fi\n DOMAIN=\"$(/usr/local/bin/store_secret.sh save DOMAIN-NAME \"${DOMAIN}\")\"\n\n # Publish service URLs early (idempotent; after_install re-writes byte-identical values)\n OPENVIDU_URL=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-URL \"https://${DOMAIN}/\")\"\n LIVEKIT_URL=\"$(/usr/local/bin/store_secret.sh save LIVEKIT-URL \"wss://${DOMAIN}/\")\"\n DASHBOARD_URL=\"$(/usr/local/bin/store_secret.sh save DASHBOARD-URL \"https://${DOMAIN}/dashboard/\")\"\n GRAFANA_URL=\"$(/usr/local/bin/store_secret.sh save GRAFANA-URL \"https://${DOMAIN}/grafana/\")\"\n MINIO_URL=\"$(/usr/local/bin/store_secret.sh save MINIO-URL \"https://${DOMAIN}/minio-console/\")\"\n\n # Meet initial admin user and password\n MEET_INITIAL_ADMIN_USER=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-USER \"admin\")\"\n if [[ \"${initialMeetAdminPassword}\" != '' ]]; then\n MEET_INITIAL_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-PASSWORD \"${initialMeetAdminPassword}\")\"\n else\n MEET_INITIAL_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate MEET-INITIAL-ADMIN-PASSWORD)\"\n fi\n if [[ \"${initialMeetApiKey}\" != '' ]]; then\n MEET_INITIAL_API_KEY=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-API-KEY \"${initialMeetApiKey}\")\"\n else\n MEET_INITIAL_API_KEY=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-API-KEY \"\")\"\n fi\n\n # Store usernames and generate random passwords\n OPENVIDU_PRO_LICENSE=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-PRO-LICENSE \"${openviduLicense}\")\"\n OPENVIDU_RTC_ENGINE=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-RTC-ENGINE \"${rtcEngine}\")\"\n REDIS_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate REDIS-PASSWORD)\"\n MONGO_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save MONGO-ADMIN-USERNAME \"mongoadmin\")\"\n MONGO_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate MONGO-ADMIN-PASSWORD)\"\n MONGO_REPLICA_SET_KEY=\"$(/usr/local/bin/store_secret.sh generate MONGO-REPLICA-SET-KEY)\"\n MINIO_ACCESS_KEY=\"$(/usr/local/bin/store_secret.sh save MINIO-ACCESS-KEY \"minioadmin\")\"\n MINIO_SECRET_KEY=\"$(/usr/local/bin/store_secret.sh generate MINIO-SECRET-KEY)\"\n DASHBOARD_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save DASHBOARD-ADMIN-USERNAME \"dashboardadmin\")\"\n DASHBOARD_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate DASHBOARD-ADMIN-PASSWORD)\"\n GRAFANA_ADMIN_USERNAME=\"$(/usr/local/bin/store_secret.sh save GRAFANA-ADMIN-USERNAME \"grafanaadmin\")\"\n GRAFANA_ADMIN_PASSWORD=\"$(/usr/local/bin/store_secret.sh generate GRAFANA-ADMIN-PASSWORD)\"\n MEET_INITIAL_ADMIN_USER=\"$(/usr/local/bin/store_secret.sh save MEET-INITIAL-ADMIN-USER \"admin\")\"\n LIVEKIT_API_KEY=\"$(/usr/local/bin/store_secret.sh generate LIVEKIT-API-KEY \"API\" 12)\"\n LIVEKIT_API_SECRET=\"$(/usr/local/bin/store_secret.sh generate LIVEKIT-API-SECRET)\"\n OPENVIDU_VERSION=\"$(/usr/local/bin/store_secret.sh save OPENVIDU-VERSION \"${OPENVIDU_VERSION}\")\"\n ENABLED_MODULES=\"$(/usr/local/bin/store_secret.sh save ENABLED-MODULES \"observability,openviduMeet,v2compatibility\")\"\n ALL_SECRETS_GENERATED=\"$(/usr/local/bin/store_secret.sh save ALL-SECRETS-GENERATED \"true\")\"\nfi\n\nIP_WAIT_MAX_RETRIES=360\nIP_WAIT_INTERVAL=5\nIP_WAIT_RETRIES=0\nwhile true; do\n MASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv) || true\n MASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv) || true\n MASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv) || true\n MASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv) || true\n # Check if all master nodes have stored their private IPs\n if [[ \"$MASTER_NODE_1_PRIVATE_IP\" != \"\" ]] &&\n [[ \"$MASTER_NODE_2_PRIVATE_IP\" != \"\" ]] &&\n [[ \"$MASTER_NODE_3_PRIVATE_IP\" != \"\" ]] &&\n [[ \"$MASTER_NODE_4_PRIVATE_IP\" != \"\" ]]; then\n break\n fi\n IP_WAIT_RETRIES=$((IP_WAIT_RETRIES + 1))\n if [ $IP_WAIT_RETRIES -ge $IP_WAIT_MAX_RETRIES ]; then\n echo \"[OpenVidu] timed out after 30 min waiting for the 4 master nodes to publish their private IPs\"\n exit 1\n fi\n sleep $IP_WAIT_INTERVAL\ndone\n\n# Wait until master-node-1 has generated all shared secrets before fetching them\nSECRETS_WAIT_MAX_RETRIES=360\nSECRETS_WAIT_INTERVAL=5\nSECRETS_WAIT_RETRIES=0\nwhile [[ \"$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)\" != \"true\" ]]; do\n SECRETS_WAIT_RETRIES=$((SECRETS_WAIT_RETRIES + 1))\n if [ $SECRETS_WAIT_RETRIES -ge $SECRETS_WAIT_MAX_RETRIES ]; then\n echo \"[OpenVidu] timed out after 30 min waiting for ALL-SECRETS-GENERATED to become true\"\n exit 1\n fi\n sleep $SECRETS_WAIT_INTERVAL\ndone\n\n\n# Fetch the values in the keyvault\nMASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_PRIVATE_IP_LIST=\"$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP\"\n\nDOMAIN=$(az keyvault secret show --vault-name ${keyVaultName} --name DOMAIN-NAME --query value -o tsv)\nOPENVIDU_RTC_ENGINE=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-RTC-ENGINE --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)\nMONGO_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-ADMIN-USERNAME --query value -o tsv)\nMONGO_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-ADMIN-PASSWORD --query value -o tsv)\nMONGO_REPLICA_SET_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MONGO-REPLICA-SET-KEY --query value -o tsv)\nDASHBOARD_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-USERNAME --query value -o tsv)\nDASHBOARD_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-PASSWORD --query value -o tsv)\nMINIO_ACCESS_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MINIO-ACCESS-KEY --query value -o tsv)\nMINIO_SECRET_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MINIO-SECRET-KEY --query value -o tsv)\nGRAFANA_ADMIN_USERNAME=$(az keyvault secret show --vault-name ${keyVaultName} --name GRAFANA-ADMIN-USERNAME --query value -o tsv)\nGRAFANA_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name GRAFANA-ADMIN-PASSWORD --query value -o tsv)\nLIVEKIT_API_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name LIVEKIT-API-KEY --query value -o tsv)\nLIVEKIT_API_SECRET=$(az keyvault secret show --vault-name ${keyVaultName} --name LIVEKIT-API-SECRET --query value -o tsv)\nMEET_INITIAL_ADMIN_USER=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-USER --query value -o tsv)\nMEET_INITIAL_ADMIN_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-PASSWORD --query value -o tsv)\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n MEET_INITIAL_API_KEY=$(az keyvault secret show --vault-name ${keyVaultName} --name MEET-INITIAL-API-KEY --query value -o tsv)\nelse\n MEET_INITIAL_API_KEY=\"\"\nfi\nENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)\n\n\n# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure\nINSTALLER_SCRIPT=\"/tmp/install_ov_master_node.sh\"\ncurl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o \"$INSTALLER_SCRIPT\" \"https://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh\"\nif [ ! -s \"$INSTALLER_SCRIPT\" ]; then\n echo \"[OpenVidu] failed to download the master 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='ha'\"\n \"--node-role='master-node'\"\n \"--external-load-balancer\"\n \"--internal-tls-termination\"\n \"--master-node-private-ip-list='$MASTER_NODE_PRIVATE_IP_LIST'\"\n \"--openvidu-pro-license='$OPENVIDU_PRO_LICENSE'\"\n \"--domain-name='$DOMAIN'\"\n \"--enabled-modules='$ENABLED_MODULES'\"\n \"--rtc-engine=$OPENVIDU_RTC_ENGINE\"\n \"--redis-password=$REDIS_PASSWORD\"\n \"--mongo-admin-user=$MONGO_ADMIN_USERNAME\"\n \"--mongo-admin-password=$MONGO_ADMIN_PASSWORD\"\n \"--mongo-replica-set-key=$MONGO_REPLICA_SET_KEY\"\n \"--minio-access-key=$MINIO_ACCESS_KEY\"\n \"--minio-secret-key=$MINIO_SECRET_KEY\"\n \"--dashboard-admin-user=$DASHBOARD_ADMIN_USERNAME\"\n \"--dashboard-admin-password=$DASHBOARD_ADMIN_PASSWORD\"\n \"--grafana-admin-user=$GRAFANA_ADMIN_USERNAME\"\n \"--grafana-admin-password=$GRAFANA_ADMIN_PASSWORD\"\n \"--meet-initial-admin-password=$MEET_INITIAL_ADMIN_PASSWORD\"\n \"--meet-initial-api-key=$MEET_INITIAL_API_KEY\"\n \"--livekit-api-key=$LIVEKIT_API_KEY\"\n \"--livekit-api-secret=$LIVEKIT_API_SECRET\"\n)\n\n# Include additional installer flags provided by the user\nif [[ \"${additionalInstallFlags}\" != \"\" ]]; then\n IFS=',' read -ra EXTRA_FLAGS <<< \"${additionalInstallFlags}\"\n for extra_flag in \"${EXTRA_FLAGS[@]}\"; do\n # Trim whitespace around each flag\n extra_flag=\"$(echo -e \"${extra_flag}\" | sed -e 's/^[ \\t]*//' -e 's/[ \\t]*$//')\"\n if [[ \"$extra_flag\" != \"\" ]]; then\n COMMON_ARGS+=(\"$extra_flag\")\n fi\n done\nfi\n\n# Certificate arguments\nif [[ \"${certificateType}\" == \"selfsigned\" ]]; then\n CERT_ARGS=(\n \"--certificate-type=selfsigned\"\n )\nelif [[ \"${certificateType}\" == \"letsencrypt\" ]]; then\n CERT_ARGS=(\n \"--certificate-type=letsencrypt\"\n )\nelse\n # Use base64 encoded certificates directly\n OWN_CERT_CRT=${ownPublicCertificate}\n OWN_CERT_KEY=${ownPrivateCertificate}\n\n CERT_ARGS=(\n \"--certificate-type=owncert\"\n \"--owncert-public-key=$OWN_CERT_CRT\"\n \"--owncert-private-key=$OWN_CERT_KEY\"\n )\nfi\n\n# Construct the final command\nFINAL_COMMAND=\"$INSTALL_COMMAND $(printf \"%s \" \"${COMMON_ARGS[@]}\") $(printf \"%s \" \"${CERT_ARGS[@]}\")\"\n\n# Install OpenVidu\nexec bash -c \"$FINAL_COMMAND\"\n", "after_installScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --client-id ${masterIdentityClientId} --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", "update_config_from_secretScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --client-id ${masterIdentityClientId} --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\"\nsed -i \"s/MINIO_ACCESS_KEY=.*/MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/MINIO_SECRET_KEY=.*/MINIO_SECRET_KEY=$MINIO_SECRET_KEY/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/GRAFANA_ADMIN_USERNAME=.*/GRAFANA_ADMIN_USERNAME=$GRAFANA_ADMIN_USERNAME/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/GRAFANA_ADMIN_PASSWORD=.*/GRAFANA_ADMIN_PASSWORD=$GRAFANA_ADMIN_PASSWORD/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/LIVEKIT_API_KEY=.*/LIVEKIT_API_KEY=$LIVEKIT_API_KEY/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/LIVEKIT_API_SECRET=.*/LIVEKIT_API_SECRET=$LIVEKIT_API_SECRET/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\nsed -i \"s/MEET_INITIAL_ADMIN_USER=.*/MEET_INITIAL_ADMIN_USER=$MEET_INITIAL_ADMIN_USER/\" \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\"\nsed -i \"s/MEET_INITIAL_ADMIN_PASSWORD=.*/MEET_INITIAL_ADMIN_PASSWORD=$MEET_INITIAL_ADMIN_PASSWORD/\" \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\"\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n sed -i \"s/MEET_INITIAL_API_KEY=.*/MEET_INITIAL_API_KEY=$MEET_INITIAL_API_KEY/\" \"${CLUSTER_CONFIG_DIR}/master_node/meet.env\"\nfi\nsed -i \"s/ENABLED_MODULES=.*/ENABLED_MODULES=$ENABLED_MODULES/\" \"${CLUSTER_CONFIG_DIR}/openvidu.env\"\n\n# Update URLs in secret\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", "update_secret_from_configScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --client-id ${masterIdentityClientId} --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# Update shared secret\naz keyvault secret set --vault-name ${keyVaultName} --name REDIS-PASSWORD --value $REDIS_PASSWORD\naz keyvault secret set --vault-name ${keyVaultName} --name DOMAIN-NAME --value $DOMAIN_NAME\naz keyvault secret set --vault-name ${keyVaultName} --name OPENVIDU-RTC-ENGINE --value $OPENVIDU_RTC_ENGINE\naz keyvault secret set --vault-name ${keyVaultName} --name OPENVIDU-PRO-LICENSE --value $OPENVIDU_PRO_LICENSE\naz keyvault secret set --vault-name ${keyVaultName} --name MONGO-ADMIN-USERNAME --value $MONGO_ADMIN_USERNAME\naz keyvault secret set --vault-name ${keyVaultName} --name MONGO-ADMIN-PASSWORD --value $MONGO_ADMIN_PASSWORD\naz keyvault secret set --vault-name ${keyVaultName} --name MONGO-REPLICA-SET-KEY --value $MONGO_REPLICA_SET_KEY\naz keyvault secret set --vault-name ${keyVaultName} --name MINIO-ACCESS-KEY --value $MINIO_ACCESS_KEY\naz keyvault secret set --vault-name ${keyVaultName} --name MINIO-SECRET-KEY --value $MINIO_SECRET_KEY\naz keyvault secret set --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-USERNAME --value $DASHBOARD_ADMIN_USERNAME\naz keyvault secret set --vault-name ${keyVaultName} --name DASHBOARD-ADMIN-PASSWORD --value $DASHBOARD_ADMIN_PASSWORD\naz keyvault secret set --vault-name ${keyVaultName} --name GRAFANA-ADMIN-USERNAME --value $GRAFANA_ADMIN_USERNAME\naz keyvault secret set --vault-name ${keyVaultName} --name GRAFANA-ADMIN-PASSWORD --value $GRAFANA_ADMIN_PASSWORD\naz keyvault secret set --vault-name ${keyVaultName} --name LIVEKIT-API-KEY --value $LIVEKIT_API_KEY\naz keyvault secret set --vault-name ${keyVaultName} --name LIVEKIT-API-SECRET --value $LIVEKIT_API_SECRET\naz keyvault secret set --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-USER --value $MEET_INITIAL_ADMIN_USER\naz keyvault secret set --vault-name ${keyVaultName} --name MEET-INITIAL-ADMIN-PASSWORD --value $MEET_INITIAL_ADMIN_PASSWORD\nif [[ \"${initialMeetApiKey}\" != '' ]]; then\n az keyvault secret set --vault-name ${keyVaultName} --name MEET-INITIAL-API-KEY --value $MEET_INITIAL_API_KEY\nfi\naz keyvault secret set --vault-name ${keyVaultName} --name ENABLED-MODULES --value $ENABLED_MODULES\n", "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 \"\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", "store_secretScriptTemplateMaster": "#!/bin/bash\nset -e\n\naz login --identity --client-id ${masterIdentityClientId} --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", "get_public_ip": "#!/bin/bash\naz login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null\n\naz network public-ip show \\\n --id ${publicIPId} \\\n --query \"ipAddress\" -o tsv\n", "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", "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", "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 --client-id ${masterIdentityClientId}\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 # Esperar antes de la próxima comprobación\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", "base64get_value_from_configMaster": "[base64(variables('get_value_from_configScriptMaster'))]", "base64check_app_readyMaster": "[base64(variables('check_app_readyScriptMaster'))]", "base64restartMaster": "[base64(variables('restartScriptMaster'))]", "userDataTemplateMasterNode": "#!/bin/bash -x\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# 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# 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# 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 --client-id ${masterIdentityClientId} --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\nMASTER_NODE_NUM=${masterNodeNum}\nif [[ $MASTER_NODE_NUM -eq 4 ]]; then\n # Creating scale in lock\n set +e\n az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key\n set -e\nfi\n", "installScriptTemplateMedia": "#!/bin/bash -x\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 and IPs 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 MASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv 2>/dev/null)\n MASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv 2>/dev/null)\n MASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv 2>/dev/null)\n MASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv 2>/dev/null)\n\n if [ \"$ALL_SECRETS_GENERATED\" == \"true\" ] &&\n [ \"$MASTER_NODE_1_PRIVATE_IP\" != \"\" ] &&\n [ \"$MASTER_NODE_2_PRIVATE_IP\" != \"\" ] &&\n [ \"$MASTER_NODE_3_PRIVATE_IP\" != \"\" ] &&\n [ \"$MASTER_NODE_4_PRIVATE_IP\" != \"\" ]; then\n break\n fi\n\n RETRIES=$((RETRIES + 1))\n if [ $RETRIES -ge $MAX_RETRIES ]; then\n echo \"[OpenVidu] timed out after 30 min waiting for master nodes to generate secrets and publish their IPs\"\n exit 1\n fi\n\n sleep $WAIT_INTERVAL\ndone\nset -e\n\nMASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv)\nMASTER_NODE_PRIVATE_IP_LIST=\"$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP\"\nREDIS_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name REDIS-PASSWORD --query value -o tsv)\nOPENVIDU_VERSION=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-VERSION --query value -o tsv)\n\n# Download first: sh <(curl ...) 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/ha/$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='ha'\"\n\"--node-role='media-node'\"\n\"--master-node-private-ip-list=$MASTER_NODE_PRIVATE_IP_LIST\"\n\"--private-ip=$PRIVATE_IP\"\n\"--redis-password=$REDIS_PASSWORD\"\n)\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", "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 or egress\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 --client-id ${mediaIdentityClientId}\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\n\nTIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\")\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", "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\" in\n +PONG*|-NOAUTH*) return 0 ;;\n esac\n done\n return 1\n}\n\n# Request the replacement of this node ($1 = reason). Gated on a master answering, except\n# in the bootstrap phase before the master addresses are known.\nrequest_replacement() {\n now=$(uptime_sec)\n if [ -n \"$REQUESTED_AT\" ] && [ $((now - REQUESTED_AT)) -lt \"$REPLACE_RETRY_SEC\" ]; then\n return\n fi\n if [ -n \"$MASTER_ADDRS\" ] || [ -f \"$INSTALLED_MARKER\" ]; then\n if ! master_serving; then\n if [ -z \"$HOLD_LOGGED_AT\" ] || [ $((now - HOLD_LOGGED_AT)) -ge 600 ]; then\n log \"$1, but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not replacing this node\"\n HOLD_LOGGED_AT=$now\n fi\n return\n fi\n fi\n log \"$1: requesting the replacement of this node\"\n if \"$REPLACE_SCRIPT\" \"$1\"; then\n REQUESTED_AT=$now\n log \"replacement requested\"\n else\n log \"the replacement request failed, retrying in $INTERVAL_SEC s\"\n fi\n}\n\nmkdir -p \"$STATE_DIR\"\nREQUESTED_AT=\"\"\nHOLD_LOGGED_AT=\"\"\nFAILED_SEEN_AT=\"\"\nFAIL_SINCE=\"\"\nSATURATED=\"\"\nPREV_TOTAL=\"\"\nPREV_IDLE=\"\"\nlog \"started\"\n\nwhile true; do\n PROBE_URL=http://127.0.0.1:7880/\n INTERVAL_SEC=30\n HARD_UNHEALTHY_AFTER_SEC=300\n SOFT_UNHEALTHY_AFTER_SEC=600\n CPU_SATURATED_PCT=90\n WARMUP_SEC=900\n BOOTSTRAP_DEADLINE_SEC=5400\n BOOTSTRAP_FAILURE_COOLDOWN_SEC=600\n REPLACE_RETRY_SEC=600\n MASTER_ADDRS=\"\"\n [ -f \"$ENV_FILE\" ] && . \"$ENV_FILE\"\n NOW=$(uptime_sec)\n cpu_busy\n\n if [ -f \"$PAUSE_FILE\" ] || [ -f \"$DRAIN_PAUSE_FILE\" ]; then\n FAIL_SINCE=\"\"\n elif [ -f \"$BOOTSTRAP_FAILED_MARKER\" ]; then\n if [ -z \"$FAILED_SEEN_AT\" ]; then\n FAILED_SEEN_AT=$NOW\n log \"bootstrap failed ($(head -c 300 \"$BOOTSTRAP_FAILED_MARKER\")): replacing this node in $BOOTSTRAP_FAILURE_COOLDOWN_SEC s\"\n fi\n if [ $((NOW - FAILED_SEEN_AT)) -ge \"$BOOTSTRAP_FAILURE_COOLDOWN_SEC\" ]; then\n request_replacement \"bootstrap failed: $(head -c 300 \"$BOOTSTRAP_FAILED_MARKER\")\"\n fi\n elif [ ! -f \"$INSTALLED_MARKER\" ]; then\n if [ \"$NOW\" -ge \"$BOOTSTRAP_DEADLINE_SEC\" ]; then\n request_replacement \"bootstrap not finished $BOOTSTRAP_DEADLINE_SEC s after boot\"\n fi\n elif curl -sf -o /dev/null --connect-timeout 3 --max-time 15 \"$PROBE_URL\"; then\n [ -n \"$FAIL_SINCE\" ] && log \"LiveKit healthy again after $((NOW - FAIL_SINCE)) s\"\n FAIL_SINCE=\"\"\n SATURATED=\"\"\n else\n RC=$?\n case \"$RC\" in\n 7|52|56) KIND=hard ;;\n *) KIND=soft ;;\n esac\n if openvidu_starting; then\n FAIL_SINCE=\"\"\n elif ! master_serving; then\n # A master outage fails the health check of every Media Node at once: not counted,\n # so LiveKit gets the whole window to reconnect once a master answers again\n FAIL_SINCE=\"\"\n if [ -z \"$HOLD_LOGGED_AT\" ] || [ $((NOW - HOLD_LOGGED_AT)) -ge 600 ]; then\n log \"LiveKit health check failed (curl exit $RC), but no master answers a Redis PING (MASTER_ADDRS='$MASTER_ADDRS'): not counted\"\n HOLD_LOGGED_AT=$NOW\n fi\n elif [ \"$KIND\" = soft ] && [ \"$BUSY\" -ge \"$CPU_SATURATED_PCT\" ]; then\n [ -n \"$SATURATED\" ] || log \"LiveKit not ready (curl exit $RC) with the CPU $BUSY% busy: not counted\"\n SATURATED=1\n FAIL_SINCE=\"\"\n else\n SATURATED=\"\"\n if [ -z \"$FAIL_SINCE\" ]; then\n FAIL_SINCE=$NOW\n log \"LiveKit health check failed (curl exit $RC)\"\n fi\n LIMIT=$SOFT_UNHEALTHY_AFTER_SEC\n [ \"$KIND\" = hard ] && LIMIT=$HARD_UNHEALTHY_AFTER_SEC\n if [ $((NOW - FAIL_SINCE)) -ge \"$LIMIT\" ]; then\n request_replacement \"LiveKit unhealthy for $((NOW - FAIL_SINCE)) s (curl exit $RC)\"\n fi\n fi\n fi\n sleep \"$INTERVAL_SEC\"\ndone\n", "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", "media_replaceScriptMediaTemplate": "#!/bin/bash\n# openvidu-media-replace.sh \"\"\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 --client-id ${mediaIdentityClientId} >/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", "userDataMediaNodeTemplate": "#!/bin/bash -x\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 --client-id ${mediaIdentityClientId}\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=\"${privateIPMasterNode1}:7001 ${privateIPMasterNode2}:7001 ${privateIPMasterNode3}:7001 ${privateIPMasterNode4}:7001\"' > /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", "base64openviduMediaHealth": "[base64(variables('openviduMediaHealthScript'))]", "base64openviduMediaHealthUnit": "[base64(variables('openviduMediaHealthUnit'))]", "isEmptyAutomationAccountName": "[equals(parameters('automationAccountName'), '')]", "lbName": "[format('{0}-loadBalancer', parameters('stackName'))]", "lbFrontEndName": "LoadBalancerFrontEnd", "lbBackendPoolNameMasterNode": "LoadBalancerBackEndMasterNode", "ipExists": "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'existing')]", "ipNew": "[equals(parameters('publicIpAddressObject').newOrExistingOrNone, 'new')]", "publicIPId": "[if(variables('ipNew'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), if(variables('ipExists'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), ''))]", "networkSettings": { "vNetAddressPrefix": "10.0.0.0/16", "subnetAddressPrefixMaster1": "10.0.1.0/24", "subnetAddressPrefixMaster2": "10.0.2.0/24", "subnetAddressPrefixMedia": "10.0.0.0/24", "vNetName": "[format('{0}-virtualNetwork', parameters('stackName'))]" }, "isEmptyStorageAccountName": "[equals(parameters('storageAccountName'), '')]", "isEmptyAppDataContainerName": "[equals(parameters('appDataContainerName'), '')]" }, "resources": [ { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[format('{0}-master-identity', parameters('stackName'))]", "location": "[variables('location')]" }, { "type": "Microsoft.ManagedIdentity/userAssignedIdentities", "apiVersion": "2023-01-31", "name": "[format('{0}-media-identity', parameters('stackName'))]", "location": "[variables('location')]" }, { "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.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').principalId]", "tenantId": "[variables('tenantId')]", "permissions": { "secrets": [ "get", "set", "list" ] } }, { "objectId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))), '2023-01-31').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.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]", "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName')))]" ] }, { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2023-09-01", "name": "[format('{0}-VM-MasterNode1', parameters('stackName'))]", "location": "[variables('location')]", "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))))]": {} } }, "properties": { "hardwareProfile": { "vmSize": "[parameters('masterNodeInstanceType')]" }, "storageProfile": { "osDisk": { "createOption": "FromImage", "managedDisk": { "storageAccountType": "[variables('masterNodeVMSettings').osDiskType]" }, "diskSizeGB": "[variables('masterNodeVMSettings').osDiskSize]" }, "imageReference": "[variables('masterNodeVMSettings').ubuntuOSVersion]" }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName')))]" } ] }, "osProfile": { "computerName": "[format('{0}-VM-MasterNode1', parameters('stackName'))]", "adminUsername": "[parameters('adminUsername')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_public_ip', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('get_public_ip')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64update_secret_from_config', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'base64config_blobStorage', base64(reduce(items(createObject('masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, '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('isEmptyAppDataContainerName'), 'openvidu-appdata', format('{0}', parameters('appDataContainerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName')))]", "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2023-09-01", "name": "[format('{0}-VM-MasterNode2', parameters('stackName'))]", "location": "[variables('location')]", "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))))]": {} } }, "properties": { "hardwareProfile": { "vmSize": "[parameters('masterNodeInstanceType')]" }, "storageProfile": { "osDisk": { "createOption": "FromImage", "managedDisk": { "storageAccountType": "[variables('masterNodeVMSettings').osDiskType]" }, "diskSizeGB": "[variables('masterNodeVMSettings').osDiskSize]" }, "imageReference": "[variables('masterNodeVMSettings').ubuntuOSVersion]" }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName')))]" } ] }, "osProfile": { "computerName": "[format('{0}-VM-MasterNode2', parameters('stackName'))]", "adminUsername": "[parameters('adminUsername')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '2', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_public_ip', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('get_public_ip')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64update_secret_from_config', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '2', 'base64config_blobStorage', base64(reduce(items(createObject('masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, '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('isEmptyAppDataContainerName'), 'openvidu-appdata', format('{0}', parameters('appDataContainerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName')))]", "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2023-09-01", "name": "[format('{0}-VM-MasterNode3', parameters('stackName'))]", "location": "[variables('location')]", "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))))]": {} } }, "properties": { "hardwareProfile": { "vmSize": "[parameters('masterNodeInstanceType')]" }, "storageProfile": { "osDisk": { "createOption": "FromImage", "managedDisk": { "storageAccountType": "[variables('masterNodeVMSettings').osDiskType]" }, "diskSizeGB": "[variables('masterNodeVMSettings').osDiskSize]" }, "imageReference": "[variables('masterNodeVMSettings').ubuntuOSVersion]" }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName')))]" } ] }, "osProfile": { "computerName": "[format('{0}-VM-MasterNode3', parameters('stackName'))]", "adminUsername": "[parameters('adminUsername')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '3', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_public_ip', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('get_public_ip')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64update_secret_from_config', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '3', 'base64config_blobStorage', base64(reduce(items(createObject('masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, '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('isEmptyAppDataContainerName'), 'openvidu-appdata', format('{0}', parameters('appDataContainerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName')))]", "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "type": "Microsoft.Compute/virtualMachines", "apiVersion": "2023-09-01", "name": "[format('{0}-VM-MasterNode4', parameters('stackName'))]", "location": "[variables('location')]", "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))))]": {} } }, "properties": { "hardwareProfile": { "vmSize": "[parameters('masterNodeInstanceType')]" }, "storageProfile": { "osDisk": { "createOption": "FromImage", "managedDisk": { "storageAccountType": "[variables('masterNodeVMSettings').osDiskType]" }, "diskSizeGB": "[variables('masterNodeVMSettings').osDiskSize]" }, "imageReference": "[variables('masterNodeVMSettings').ubuntuOSVersion]" }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName')))]" } ] }, "osProfile": { "computerName": "[format('{0}-VM-MasterNode4', parameters('stackName'))]", "adminUsername": "[parameters('adminUsername')]", "linuxConfiguration": "[variables('masterNodeVMSettings').linuxConfiguration]" }, "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '4', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64after_install', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('after_installScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64get_public_ip', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('get_public_ip')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64update_config_from_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64update_secret_from_config', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), 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), 'base64get_value_from_config', variables('base64get_value_from_configMaster'), 'base64store_secret', base64(reduce(items(createObject('publicIPId', variables('publicIPId'), 'domainName', parameters('domainName'), 'certificateType', parameters('certificateType'), 'ownPublicCertificate', parameters('ownPublicCertificate'), 'ownPrivateCertificate', parameters('ownPrivateCertificate'), 'openviduLicense', parameters('openviduLicense'), 'rtcEngine', parameters('rtcEngine'), 'initialMeetAdminPassword', parameters('initialMeetAdminPassword'), 'initialMeetApiKey', parameters('initialMeetApiKey'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '1', 'additionalInstallFlags', parameters('additionalInstallFlags'))), createObject('value', variables('store_secretScriptTemplateMaster')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64check_app_ready', variables('base64check_app_readyMaster'), 'base64restart', variables('base64restartMaster'), 'keyVaultName', variables('keyVaultName'), 'masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, 'masterNodeNum', '4', 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName')), 'base64config_blobStorage', base64(reduce(items(createObject('masterIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').clientId, '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('isEmptyAppDataContainerName'), 'openvidu-appdata', format('{0}', parameters('appDataContainerName'))))), createObject('value', variables('config_blobStorageTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value))), createObject('value', variables('userDataTemplateMasterNode')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value)]" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName')))]", "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "type": "Microsoft.Compute/virtualMachineScaleSets", "apiVersion": "2024-07-01", "name": "[format('{0}-mediaNodeScaleSet', parameters('stackName'))]", "location": "[variables('location')]", "tags": { "InstanceDeleteTime": "[parameters('datetime')]", "storageAccount": "[if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName'))]" }, "identity": { "type": "UserAssigned", "userAssignedIdentities": { "[format('{0}', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))))]": {} } }, "sku": { "name": "[parameters('mediaNodeInstanceType')]", "tier": "Standard" }, "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}-mediaNodeNSG', parameters('stackName')))]" } } } ] }, "userData": "[base64(reduce(items(createObject('base64install', base64(reduce(items(createObject('privateIPMasterNode1', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode2', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode3', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode4', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', 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', base64(reduce(items(createObject('mediaIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))), '2023-01-31').clientId, 'subscriptionId', subscription().subscriptionId, 'resourceGroupName', resourceGroup().name, 'vmScaleSetName', format('{0}-mediaNodeScaleSet', parameters('stackName')), 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName')))), createObject('value', variables('stop_media_nodesScriptMediaTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'base64mediaHealth', variables('base64openviduMediaHealth'), 'base64mediaHealthUnit', variables('base64openviduMediaHealthUnit'), 'base64mediaReplace', base64(reduce(items(createObject('mediaIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))), '2023-01-31').clientId, 'subscriptionId', subscription().subscriptionId, 'resourceGroupName', resourceGroup().name, 'vmScaleSetName', format('{0}-mediaNodeScaleSet', parameters('stackName')), 'storageAccountName', if(variables('isEmptyStorageAccountName'), uniqueString(resourceGroup().id), parameters('storageAccountName')))), createObject('value', variables('media_replaceScriptMediaTemplate')), lambda('curr', 'next', createObject('value', replace(lambdaVariables('curr').value, format('${{{0}}}', lambdaVariables('next').key), lambdaVariables('next').value)))).value), 'privateIPMasterNode1', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode2', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode3', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'privateIPMasterNode4', reference(resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName'))), '2023-11-01').ipConfigurations[0].properties.privateIPAddress, 'mediaIdentityClientId', reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))), '2023-01-31').clientId, '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)]" } }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface1', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface2', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface3', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkInterfaces', format('{0}-masterNodeNetInterface4', parameters('stackName')))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]", "[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(if(greater(parameters('fixedNumberOfMediaNodes'), 0), parameters('fixedNumberOfMediaNodes'), parameters('minNumberOfMediaNodes')))]", "maximum": "[string(if(greater(parameters('fixedNumberOfMediaNodes'), 0), parameters('fixedNumberOfMediaNodes'), parameters('maxNumberOfMediaNodes')))]", "default": "[string(if(greater(parameters('fixedNumberOfMediaNodes'), 0), parameters('fixedNumberOfMediaNodes'), 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('roleAssignmentForMasterNodes', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))))]", "properties": { "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName'))), '2023-01-31').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-master-identity', parameters('stackName')))]" ] }, { "type": "Microsoft.Authorization/roleAssignments", "apiVersion": "2022-04-01", "name": "[guid('roleAssignmentForScaleSet', resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))))]", "properties": { "roleDefinitionId": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", "principalId": "[reference(resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName'))), '2023-01-31').principalId]", "principalType": "ServicePrincipal" }, "dependsOn": [ "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', format('{0}-media-identity', parameters('stackName')))]" ] }, { "type": "Microsoft.Insights/actionGroups", "apiVersion": "2023-01-01", "name": "actiongroupScaleIn", "location": "global", "properties": { "groupShortName": "scaleinag", "enabled": true, "automationRunbookReceivers": [ { "name": "scalein", "useCommonAlertSchema": false, "automationAccountId": "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.automationAccountId.value]", "runbookName": "scaleInRunbook", "webhookResourceId": "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.webhookId.value]", "isGlobalRunbook": false, "serviceUri": "[reference(resourceId('Microsoft.Resources/deployments', 'WebhookDeployment'), '2025-04-01').outputs.webhookUri.value]" } ] }, "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": [ { "actionGroupId": "[resourceId('Microsoft.Insights/actionGroups', 'actiongroupScaleIn')]" } ] }, "enabled": true }, "dependsOn": [ "[resourceId('Microsoft.Insights/actionGroups', 'actiongroupScaleIn')]", "[resourceId('Microsoft.Compute/virtualMachineScaleSets', format('{0}-mediaNodeScaleSet', parameters('stackName')))]" ] }, { "condition": "[equals(variables('ipNew'), true())]", "type": "Microsoft.Network/publicIPAddresses", "apiVersion": "2023-11-01", "name": "[parameters('publicIpAddressObject').name]", "location": "[variables('location')]", "sku": { "name": "Standard", "tier": "Regional" }, "properties": { "publicIPAddressVersion": "IPv4", "publicIPAllocationMethod": "Static", "dnsSettings": { "domainNameLabel": "[if(variables('isEmptyDomain'), toLower(format('{0}', parameters('stackName'))), null())]" } } }, { "type": "Microsoft.Network/loadBalancers", "apiVersion": "2024-05-01", "name": "[variables('lbName')]", "location": "[variables('location')]", "sku": { "name": "Standard" }, "properties": { "frontendIPConfigurations": [ { "name": "[variables('lbFrontEndName')]", "properties": { "publicIPAddress": "[if(variables('isEmptyIp'), null(), createObject('id', if(variables('ipNew'), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name), resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name))))]" } } ], "backendAddressPools": [ { "name": "[variables('lbBackendPoolNameMasterNode')]" } ], "loadBalancingRules": [ { "name": "HTTPSRuleforMasterNode", "properties": { "frontendIPConfiguration": { "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', variables('lbName'), variables('lbFrontEndName'))]" }, "backendAddressPool": { "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', variables('lbName'), variables('lbBackendPoolNameMasterNode'))]" }, "frontendPort": 443, "backendPort": 443, "enableFloatingIP": false, "protocol": "Tcp", "enableTcpReset": true, "loadDistribution": "Default", "disableOutboundSnat": true, "probe": { "id": "[resourceId('Microsoft.Network/loadBalancers/probes', variables('lbName'), 'probeForHTTPSRuleMasterNode')]" } } }, { "name": "RTMPRuleforMasterNode", "properties": { "frontendIPConfiguration": { "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', variables('lbName'), variables('lbFrontEndName'))]" }, "backendAddressPool": { "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', variables('lbName'), variables('lbBackendPoolNameMasterNode'))]" }, "frontendPort": 1945, "backendPort": 1945, "enableFloatingIP": false, "protocol": "Tcp", "enableTcpReset": true, "loadDistribution": "Default", "disableOutboundSnat": true, "probe": { "id": "[resourceId('Microsoft.Network/loadBalancers/probes', variables('lbName'), 'probeForRTMPRuleMasterNode')]" } } }, { "name": "HTTPRuleforMasterNode", "properties": { "frontendIPConfiguration": { "id": "[resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', variables('lbName'), variables('lbFrontEndName'))]" }, "backendAddressPool": { "id": "[resourceId('Microsoft.Network/loadBalancers/backendAddressPools', variables('lbName'), variables('lbBackendPoolNameMasterNode'))]" }, "frontendPort": 80, "backendPort": 80, "enableFloatingIP": false, "protocol": "Tcp", "enableTcpReset": true, "loadDistribution": "Default", "disableOutboundSnat": true, "probe": { "id": "[resourceId('Microsoft.Network/loadBalancers/probes', variables('lbName'), 'probeForHTTPSRuleMasterNode')]" } } } ], "probes": [ { "name": "probeForHTTPSRuleMasterNode", "properties": { "protocol": "Http", "requestPath": "/health/caddy", "port": 7880, "probeThreshold": 3, "intervalInSeconds": 10, "numberOfProbes": 5 } }, { "name": "probeForRTMPRuleMasterNode", "properties": { "protocol": "Tcp", "port": 1945, "intervalInSeconds": 5, "numberOfProbes": 2 } } ], "outboundRules": [] }, "dependsOn": [ "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIpAddressObject').name)]" ] }, { "type": "Microsoft.Network/natGateways", "apiVersion": "2021-05-01", "name": "[format('{0}-natGateway', parameters('stackName'))]", "location": "[variables('location')]", "sku": { "name": "Standard" }, "properties": { "idleTimeoutInMinutes": 4, "publicIpAddresses": [ { "id": "[resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIPnatGateway', parameters('stackName')))]" } ] }, "dependsOn": [ "[resourceId('Microsoft.Network/publicIPAddresses', format('{0}-publicIPnatGateway', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/publicIPAddresses", "apiVersion": "2021-05-01", "name": "[format('{0}-publicIPnatGateway', parameters('stackName'))]", "location": "[variables('location')]", "sku": { "name": "Standard" }, "properties": { "publicIPAddressVersion": "IPv4", "publicIPAllocationMethod": "Static", "idleTimeoutInMinutes": 4 } }, { "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}-mediaNodeNSG', parameters('stackName')))]" } } }, { "name": "firstSubnetForMasterNodes", "properties": { "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster1]", "privateEndpointNetworkPolicies": "Disabled", "privateLinkServiceNetworkPolicies": "Enabled", "natGateway": { "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" } } }, { "name": "secondSubnetForMasterNodes", "properties": { "addressPrefix": "[variables('networkSettings').subnetAddressPrefixMaster2]", "privateEndpointNetworkPolicies": "Disabled", "privateLinkServiceNetworkPolicies": "Enabled", "natGateway": { "id": "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]" } } } ] }, "dependsOn": [ "[resourceId('Microsoft.Network/natGateways', format('{0}-natGateway', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", "name": "[format('{0}-masterNodeNetInterface1', 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')))]" } ], "loadBalancerBackendAddressPools": [ { "id": "[reference(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '2024-05-01').backendAddressPools[0].id]" } ] } } ], "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" } }, "dependsOn": [ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", "name": "[format('{0}-masterNodeNetInterface2', parameters('stackName'))]", "location": "[variables('location')]", "properties": { "ipConfigurations": [ { "name": "primaryIPConfig", "properties": { "privateIPAllocationMethod": "Dynamic", "subnet": { "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'secondSubnetForMasterNodes')]" }, "applicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "loadBalancerBackendAddressPools": [ { "id": "[reference(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '2024-05-01').backendAddressPools[0].id]" } ] } } ], "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" } }, "dependsOn": [ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", "name": "[format('{0}-masterNodeNetInterface3', 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')))]" } ], "loadBalancerBackendAddressPools": [ { "id": "[reference(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '2024-05-01').backendAddressPools[0].id]" } ] } } ], "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" } }, "dependsOn": [ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { "type": "Microsoft.Network/networkInterfaces", "apiVersion": "2023-11-01", "name": "[format('{0}-masterNodeNetInterface4', parameters('stackName'))]", "location": "[variables('location')]", "properties": { "ipConfigurations": [ { "name": "primaryIPConfig", "properties": { "privateIPAllocationMethod": "Dynamic", "subnet": { "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('networkSettings').vNetName, 'secondSubnetForMasterNodes')]" }, "applicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "loadBalancerBackendAddressPools": [ { "id": "[reference(resourceId('Microsoft.Network/loadBalancers', variables('lbName')), '2024-05-01').backendAddressPools[0].id]" } ] } } ], "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" } }, "dependsOn": [ "[resourceId('Microsoft.Network/loadBalancers', variables('lbName'))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/virtualNetworks', variables('networkSettings').vNetName)]" ] }, { "type": "Microsoft.Network/networkSecurityGroups", "apiVersion": "2023-11-01", "name": "[format('{0}-masterNodeNSG', parameters('stackName'))]", "location": "[variables('location')]", "properties": { "securityRules": [ { "name": "SSH", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationAddressPrefix": "*", "destinationPortRange": "22", "access": "Allow", "priority": 100, "direction": "Inbound" } }, { "name": "ProbeAPI", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "AzureLoadBalancer", "sourcePortRange": "*", "destinationAddressPrefix": "*", "destinationPortRange": "7880", "access": "Allow", "priority": 500, "direction": "Inbound" } }, { "name": "HTTP", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationAddressPrefix": "*", "destinationPortRange": "80", "access": "Allow", "priority": 510, "direction": "Inbound" } } ] } }, { "type": "Microsoft.Network/applicationSecurityGroups", "apiVersion": "2024-03-01", "name": "[format('{0}-masterNodeASG', parameters('stackName'))]", "location": "[variables('location')]" }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'loadBalancer_to_masterNode_INGRESS')]", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "443", "access": "Allow", "priority": 110, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'loadBalancer_to_masterNode_RTMP_INGRESS')]", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "1945", "access": "Allow", "priority": 111, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_CLUSTER_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "7880", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "7880", "access": "Allow", "priority": 115, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_REDIS_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "7000", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "7001", "access": "Allow", "priority": 120, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'mediaNode_to_masterNode_REDIS_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "7000", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "7001", "access": "Allow", "priority": 130, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MINIO_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9100", "access": "Allow", "priority": 140, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MINIO_CONSOLE_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9101", "access": "Allow", "priority": 150, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', 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}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MONGO_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', 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}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', 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": 180, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MIMIRGRPC_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9095", "access": "Allow", "priority": 190, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MIMIRGOSSIP_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "7946", "access": "Allow", "priority": 200, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', 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": 210, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_LOKIGRPC_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9096", "access": "Allow", "priority": 220, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_LOKIGOSSIP_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "7947", "access": "Allow", "priority": 230, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_DASHBOARD_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "5000", "access": "Allow", "priority": 240, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_GRAFANA_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "3000", "access": "Allow", "priority": 250, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', 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": 260, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_V2COMPATIBILITY_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "4443", "access": "Allow", "priority": 270, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', 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": 280, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_MEET_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9080", "access": "Allow", "priority": 290, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'masterNode_to_masterNode_CUSTOM_APP_INGRESS')]", "properties": { "protocol": "Tcp", "sourceApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "6080", "access": "Allow", "priority": 310, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-masterNodeNSG', parameters('stackName')), 'mediaNode_to_masterNode_MEET_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": "9080", "access": "Allow", "priority": 300, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', 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}-masterNodeNSG', 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": 320, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-masterNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-masterNodeNSG', parameters('stackName')))]", "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups", "apiVersion": "2023-11-01", "name": "[format('{0}-mediaNodeNSG', 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": "*", "destinationPortRange": "50000-60000", "access": "Allow", "priority": 140, "direction": "Inbound" } }, { "name": "WebRTC_traffic_TCP", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "*", "sourcePortRange": "*", "destinationAddressPrefix": "*", "destinationPortRanges": [ "50000", "60000" ], "access": "Allow", "priority": 150, "direction": "Inbound" } } ] } }, { "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}-mediaNodeNSG', parameters('stackName')), 'loadBalancer_to_mediaNode_RTMP_INGRESS')]", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "AzureLoadBalancer", "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "1945", "access": "Allow", "priority": 160, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-mediaNodeNSG', parameters('stackName')), 'loadBalancer_to_mediaNode_HEALTHCHECK_INGRESS')]", "properties": { "protocol": "Tcp", "sourceAddressPrefix": "AzureLoadBalancer", "sourcePortRange": "*", "destinationApplicationSecurityGroups": [ { "id": "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]" } ], "destinationPortRange": "9092", "access": "Allow", "priority": 170, "direction": "Inbound" }, "dependsOn": [ "[resourceId('Microsoft.Network/applicationSecurityGroups', format('{0}-mediaNodeASG', parameters('stackName')))]", "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-mediaNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-mediaNodeNSG', 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", "priority": 200, "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}-mediaNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-mediaNodeNSG', parameters('stackName')), 'masterNode_to_mediaNode_CLIENT_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", "priority": 210, "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}-mediaNodeNSG', parameters('stackName')))]" ] }, { "type": "Microsoft.Network/networkSecurityGroups/securityRules", "apiVersion": "2023-11-01", "name": "[format('{0}/{1}', format('{0}-mediaNodeNSG', 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", "priority": 220, "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}-mediaNodeNSG', parameters('stackName')))]" ] }, { "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-01-01", "name": "[uniqueString(resourceGroup().id)]", "location": "[resourceGroup().location]", "sku": { "name": "Standard_LRS" }, "kind": "StorageV2", "properties": { "accessTier": "Cool", "supportsHttpsTrafficOnly": true } }, { "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[format('{0}/default/automation-locks', uniqueString(resourceGroup().id))]", "properties": { "publicAccess": "None" }, "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "condition": "[equals(variables('isEmptyStorageAccountName'), true())]", "type": "Microsoft.Storage/storageAccounts/blobServices/containers", "apiVersion": "2023-01-01", "name": "[if(variables('isEmptyAppDataContainerName'), format('{0}/default/openvidu-appdata', uniqueString(resourceGroup().id)), format('{0}/default/{1}', uniqueString(resourceGroup().id), parameters('appDataContainerName')))]", "properties": { "publicAccess": "None" }, "dependsOn": [ "[resourceId('Microsoft.Storage/storageAccounts', uniqueString(resourceGroup().id))]" ] }, { "type": "Microsoft.Resources/deployments", "apiVersion": "2025-04-01", "name": "WebhookDeployment", "properties": { "expressionEvaluationOptions": { "scope": "inner" }, "mode": "Incremental", "parameters": { "automationAccountName": "[if(variables('isEmptyAutomationAccountName'), createObject('value', uniqueString(resourceGroup().id, resourceId('Microsoft.Compute/virtualMachines', format('{0}-VM-MasterNode1', parameters('stackName'))))), createObject('value', parameters('automationAccountName')))]", "runbookName": { "value": "scaleInRunbook" }, "webhookName": { "value": "[format('webhookForScaleIn-{0}', uniqueString(deployment().name, parameters('scaleInWebhookNameSeed')))]" }, "WebhookExpiryTime": { "value": "2035-03-30T00:00:00Z" }, "_artifactsLocation": { "value": "https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1" } }, "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": { "defaultValue": "https://raw.githubusercontent.com/OpenVidu/openvidu/refs/heads/master/openvidu-deployment/pro/shared/scaleInRunbook.ps1", "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'))]" } } } }, "dependsOn": [ "[resourceId('Microsoft.Compute/virtualMachines', format('{0}-VM-MasterNode1', parameters('stackName')))]" ] } ] }