Compare commits

..

No commits in common. "master" and "v3.8.0" have entirely different histories.

69 changed files with 2265 additions and 2672 deletions

View File

@ -35,7 +35,7 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install safe-chain
uses: OpenVidu/actions/install-safe-chain@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/install-safe-chain@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Commit URL
run: echo https://github.com/OpenVidu/openvidu/commit/${{ inputs.commit_sha || github.sha }}
- name: Send Dispatch Event
@ -96,7 +96,7 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install safe-chain
uses: OpenVidu/actions/install-safe-chain@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/install-safe-chain@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Install wait-on package
run: npm install -g wait-on
- name: Run Chrome
@ -107,15 +107,15 @@ jobs:
docker run --network=host -d -p 4444:4444 ${{ env.CHROME_IMAGE }}
fi
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/start-openvidu-local-deployment@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/start-openvidu-call@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/start-openvidu-components-testapp@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run ${{ matrix.script }} --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/cleanup@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Configure OpenVidu Local Deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/start-openvidu-local-deployment@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
with:
ref-openvidu-local-deployment: development
pre_startup_commands: |
@ -35,7 +35,7 @@ jobs:
with:
node-version: 24
- name: Install safe-chain
uses: OpenVidu/actions/install-safe-chain@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/install-safe-chain@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21
- name: Install dependencies
working-directory: ./openvidu/openvidu-test-integration
@ -54,5 +54,5 @@ jobs:
retention-days: 7
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@070fb73fda8fe7a08e28991b1c2d276f12a009ef # v1.0.22
uses: OpenVidu/actions/cleanup@040cbfd01320475801d7b12e33769e0d55a737a8 # v1.0.21

View File

@ -1,21 +1,12 @@
#!/bin/bash
if [[ -z "$BASEHREF_VERSION" ]]; then
echo "Example of use: \"BASEHREF_VERSION=3.8.0 ${0}\"" 1>&2
echo "Example of use: \"BASEHREF_VERSION=2.12.0 ${0}\"" 1>&2
exit 1
fi
# openvidu.io documentation is versioned by MINOR release (folders like "3.8"), so links to
# it must be pinned to the minor version (X.Y). The full X.Y.Z is left untouched: it is the
# library version, read from package.json by Compodoc and shown in the generated docs.
MINOR_VERSION=$(echo "$BASEHREF_VERSION" | grep -oE '^[0-9]+\.[0-9]+')
if [[ -z "$MINOR_VERSION" ]]; then
echo "BASEHREF_VERSION must start with X.Y (e.g. \"BASEHREF_VERSION=3.8.0 ${0}\")" 1>&2
exit 1
fi
# Replace version from "latest" to the specified minor one in all TypeDoc links
grep -rl '/latest/' projects src | xargs sed -i -e 's|/latest/|/'${MINOR_VERSION}'/|g'
# Replace version from "stable" to the specified one in all TypeDoc links
grep -rl '/latest/' projects src | xargs sed -i -e 's|/latest/|/'${BASEHREF_VERSION}'/|g'
# Replace testapp README by openvidu-components-angular README
mv README.md README-testapp.md
@ -24,8 +15,8 @@ cp ./projects/openvidu-components-angular/README.md .
# Generate Compodoc
npm run doc:build
# Return links to "latest" version
grep -rl '/'${MINOR_VERSION}'/' projects src | xargs sed -i -e 's|/'${MINOR_VERSION}'/|/latest/|g'
# Return links to "stable" version
grep -rl '/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/'${BASEHREF_VERSION}'/|/latest/|g'
# Undo changes with READMEs
rm README.md
@ -33,3 +24,4 @@ mv README-testapp.md README.md
# Clean previous docs from openvidu.io repo and copy new ones
npm run doc:clean-copy

View File

@ -1,12 +1,12 @@
{
"name": "openvidu-components-testapp",
"version": "3.8.0",
"version": "3.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvidu-components-testapp",
"version": "3.8.0",
"version": "3.7.0",
"dependencies": {
"@angular/animations": "20.3.15",
"@angular/cdk": "20.2.14",

View File

@ -106,5 +106,5 @@
"simulate:multiparty": "livekit-cli load-test --url ws://localhost:7880 --api-key devkey --api-secret secret --room daily-call --publishers 8 --audio-publishers 8 --identity-prefix Participant --identity publisher",
"husky": "cd .. && husky install"
},
"version": "3.8.0"
"version": "3.7.0"
}

View File

@ -18,5 +18,5 @@
"livekit-client": "^2.16.0",
"@livekit/track-processors": "^0.7.2"
},
"version": "3.8.0"
"version": "3.7.0"
}

View File

@ -1,7 +1,7 @@
services:
caddy-proxy:
image: docker.io/openvidu/openvidu-caddy-local:main
image: docker.io/openvidu/openvidu-caddy-local:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -88,7 +88,7 @@ services:
condition: service_completed_successfully
dashboard:
image: docker.io/openvidu/openvidu-dashboard:main
image: docker.io/openvidu/openvidu-dashboard:3.8.0
restart: unless-stopped
environment:
- SERVER_PORT=5000
@ -102,7 +102,7 @@ services:
condition: service_completed_successfully
openvidu:
image: docker.io/openvidu/openvidu-server:main
image: docker.io/openvidu/openvidu-server:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -123,7 +123,7 @@ services:
condition: service_completed_successfully
ingress:
image: docker.io/openvidu/ingress:main
image: docker.io/openvidu/ingress:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -141,7 +141,7 @@ services:
condition: service_completed_successfully
egress:
image: docker.io/openvidu/egress:main
image: docker.io/openvidu/egress:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -156,7 +156,7 @@ services:
condition: service_completed_successfully
operator:
image: docker.io/openvidu/openvidu-operator:main
image: docker.io/openvidu/openvidu-operator:3.8.0
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@ -179,7 +179,7 @@ services:
condition: service_completed_successfully
openvidu-meet:
image: docker.io/openvidu/openvidu-meet:main
image: docker.io/openvidu/openvidu-meet:3.8.0
restart: on-failure
extra_hosts:
- host.docker.internal:host-gateway
@ -219,7 +219,7 @@ services:
condition: service_completed_successfully
openvidu-meet-init:
image: docker.io/openvidu/openvidu-operator:main
image: docker.io/openvidu/openvidu-operator:3.8.0
restart: on-failure
environment:
- MODE=local-ready-check

View File

@ -680,7 +680,7 @@ Resources:
'/usr/local/bin/install.sh':
content: !Sub |
#!/bin/bash -x
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3

View File

@ -165,7 +165,7 @@ var stringInterpolationParams = {
var installScriptTemplate = '''
#!/bin/bash -x
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

File diff suppressed because one or more lines are too long

View File

@ -152,7 +152,7 @@ locals {
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -1427,8 +1427,7 @@ Resources:
Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy:
ResourceSignal:
# Elastic signals AFTER the full install completes, so it needs a wider window than HA
Timeout: PT20M
Timeout: PT10M
Count: '1'
OpenViduMasterNode:
@ -1442,7 +1441,7 @@ Resources:
content: !Sub |
#!/bin/bash
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
@ -1474,29 +1473,36 @@ Resources:
else
DOMAIN=${DomainName}
fi
# Get own private IP
PRIVATE_IP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)
# Instance role already proven by cfn-init above, so a single read needs no IAM-availability retry
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
if [[ "$SHARED_SECRET" == "none" ]]; then
echo "Error: Shared secret not found"
exit 1
fi
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
# Generate secrets only once: a re-run must never regenerate passwords already in use by the cluster
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
# Persist the domain so after_install.sh reads it from the secret instead of the installer
/usr/local/bin/store_secret.sh save DOMAIN_NAME "$DOMAIN"
OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
# Store version so media nodes can use it to install the same version
/usr/local/bin/store_secret.sh save OPENVIDU_VERSION "${!OPENVIDU_VERSION}"
# Get own private IP
PRIVATE_IP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4)
# Unfortunately, EC2 instance assigned role is not immediately available after the instance is launched.
# Therefore, we need to retry this operation until the aws-cli command is successful.
MAX_RETRIES=10
RETRY_COUNT=0
while : ; do
# Get current shared secret and random seed
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
if [[ "$SHARED_SECRET" != "none" ]]; then
break
fi
RETRY_COUNT=$((RETRY_COUNT+1))
if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then
exit 1
fi
sleep 6
done
# Meet initial admin user and password
MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
if [[ "${InitialMeetAdminPassword}" != '' ]]; then
@ -1511,6 +1517,8 @@ Resources:
fi
# Store usernames and generate random passwords
OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
REDIS_PASSWORD="$(/usr/local/bin/store_secret.sh generate REDIS_PASSWORD)"
MONGO_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
@ -1525,59 +1533,9 @@ Resources:
LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)"
ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED_MODULES "observability,v2compatibility,openviduMeet")"
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
fi
# Source every installer value from the secret so a re-run (guard skipped) still has them
# GetSecretValue is eventually consistent: retry (up to 300s at 5s) until the read returns the generated values
SECRET_READ_ATTEMPTS=0
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .OPENVIDU_PRO_LICENSE, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
break
fi
SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
echo "Error: shared secret still incomplete after 5 minutes of stale reads"
exit 1
fi
sleep 5
done
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
MEET_INITIAL_ADMIN_USER=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_USER')
MEET_INITIAL_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
MEET_INITIAL_API_KEY=""
if [[ "${InitialMeetApiKey}" != '' ]]; then
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
fi
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
MONGO_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_USERNAME')
MONGO_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MONGO_ADMIN_PASSWORD')
MONGO_REPLICA_SET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MONGO_REPLICA_SET_KEY')
MINIO_ACCESS_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_ACCESS_KEY')
MINIO_SECRET_KEY=$(echo "$SHARED_SECRET" | jq -r '.MINIO_SECRET_KEY')
DASHBOARD_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_USERNAME')
DASHBOARD_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.DASHBOARD_ADMIN_PASSWORD')
GRAFANA_ADMIN_USERNAME=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_USERNAME')
GRAFANA_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_PASSWORD')
LIVEKIT_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_KEY')
LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET')
ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Base command
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -1900,6 +1858,20 @@ Resources:
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/check_app_ready.sh':
content: |
#!/bin/bash
set -e
while true; do
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy | head -n1 | awk '{print $2}')
if [ $HTTP_STATUS == 200 ]; then
break
fi
sleep 5
done
mode: "000755"
owner: "root"
group: "root"
'/usr/local/bin/restart.sh':
content: |
#!/bin/bash
@ -1978,30 +1950,9 @@ Resources:
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
# Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
OPENVIDU_READY=false
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
if [ "$OPENVIDU_READY" != "true" ]; then
echo "[OpenVidu] not healthy after 300s, restarting once"
systemctl restart openvidu || true
for i in $(seq 1 60); do
if curl -fsS http://127.0.0.1:7880/health/caddy >/dev/null 2>&1; then
OPENVIDU_READY=true
break
fi
sleep 5
done
fi
# Disable errexit so cfn-signal always runs and reports readiness via $? (fast-fail instead of a PT20M timeout)
set +e
[ "$OPENVIDU_READY" = "true" ]
# Wait for the app
sleep 20
/usr/local/bin/check_app_ready.sh
# sending the finish call
cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}
@ -2071,48 +2022,31 @@ Resources:
sleep 6
done
# Wait until the master node has generated all shared secrets.
# Bounded (up to 1800s at 5s), validating content on each snapshot: stale eventually-consistent reads can return pre-generation values
SECRETS_WAIT_ATTEMPTS=0
SECRETS_WAIT_MAX=360
while true; do
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
break
fi
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for the master node to generate the shared secrets"
/usr/local/bin/set_as_unhealthy.sh
exit 1
fi
sleep 5
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
done
# Get current shared secret
DOMAIN=$(echo $SHARED_SECRET | jq -r .DOMAIN_NAME)
OPENVIDU_PRO_LICENSE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_PRO_LICENSE)
REDIS_PASSWORD=$(echo $SHARED_SECRET | jq -r .REDIS_PASSWORD)
# Read values only after the gate confirms the master node has published them
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
# Get OpenVidu Media Nodes version to deploy
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then
echo "OpenVidu version not found"
exit 1
fi
ALL_SECRETS_GENERATED=$(echo $SHARED_SECRET | jq -r .ALL_SECRETS_GENERATED)
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
echo "Master node not ready"
/usr/local/bin/set_as_unhealthy.sh
exit 1
fi
# Get Master Node private IP
MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp}
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Base command
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -2256,22 +2190,6 @@ Resources:
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
# Wait for the master node to be healthy before starting (bounded: up to 1800s at 5s)
MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp}
MASTER_HEALTHY=false
for i in $(seq 1 360); do
if curl -sf "http://$MASTER_NODE_IP:7880/health/caddy" >/dev/null 2>&1; then
MASTER_HEALTHY=true
break
fi
sleep 5
done
if [ "$MASTER_HEALTHY" != "true" ]; then
echo "[OpenVidu] master node did not become healthy after 30 minutes"
/usr/local/bin/set_as_unhealthy.sh
exit 1
fi
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
@ -2284,8 +2202,6 @@ Resources:
OpenViduMediaNodeASG:
DependsOn:
- OpenViduMediaNodeInstanceProfile
- OpenViduMasterNodeInstanceProfile
- StopMediaNodeCloudWatchEventRule
Type: AWS::AutoScaling::AutoScalingGroup
Properties:

View File

@ -212,9 +212,8 @@ var stringInterpolationParamsMaster = {
}
var installScriptTemplateMaster = '''
#!/bin/bash
set -e
OPENVIDU_VERSION=main
#!/bin/bash -x
OPENVIDU_VERSION=3.8.0
DOMAIN=
# Assume azure cli is installed
@ -242,7 +241,7 @@ else
fi
# Wait for the keyvault availability
MAX_WAIT=300
MAX_WAIT=100
WAIT_INTERVAL=1
ELAPSED_TIME=0
while true; do
@ -307,16 +306,8 @@ OPENVIDU_VERSION="$(/usr/local/bin/store_secret.sh save OPENVIDU-VERSION "${OPEN
ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED-MODULES "observability,openviduMeet,v2compatibility")"
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL-SECRETS-GENERATED "true")"
# Download to a file first: process substitution would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "[OpenVidu] failed to download the master node installer script"
exit 1
fi
# Base command
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -639,18 +630,11 @@ az network public-ip show \
var check_app_readyScriptMaster = '''
#!/bin/bash
set -e
MAX_WAIT=1200
WAIT_INTERVAL=5
ELAPSED_TIME=0
while true; do
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy | head -n1 | awk '{print $2}')
if [ "$HTTP_STATUS" = "200" ]; then
if [ $HTTP_STATUS == 200 ]; then
break
fi
ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))
if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then
exit 1
fi
sleep 5
done
'''
@ -676,35 +660,11 @@ set -e
INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="${INSTALL_DIR}/config/cluster"
# Retry login + storage key fetch to allow the Contributor role assignment to propagate
MAX_WAIT=300
WAIT_INTERVAL=1
ELAPSED_TIME=0
set +e
while true; do
az login --identity
az login --identity
# Config azure blob storage
AZURE_ACCOUNT_NAME="${storageAccountName}"
AZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)
# If the key was fetched successfully, exit the loop
if [ $? -eq 0 ]; then
break
fi
# If not, wait and check again incrementing the time
ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))
# If exceeded the maximum time, exit with error
if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then
exit 1
fi
# Wait before the next check
sleep $WAIT_INTERVAL
done
set -e
# Config azure blob storage
AZURE_ACCOUNT_NAME="${storageAccountName}"
AZURE_ACCOUNT_KEY=$(az storage account keys list --account-name ${storageAccountName} --query '[0].value' -o tsv)
AZURE_CONTAINER_NAME="${storageAccountContainerName}"
sed -i "s|AZURE_ACCOUNT_NAME=.*|AZURE_ACCOUNT_NAME=$AZURE_ACCOUNT_NAME|" "${CLUSTER_CONFIG_DIR}/openvidu.env"
@ -787,7 +747,7 @@ var userDataParamsMasterNode = {
}
var userDataTemplateMasterNode = '''
#!/bin/bash
#!/bin/bash -x
set -eu -o pipefail
# Introduce the scripts in the instance
@ -842,6 +802,8 @@ az login --identity --allow-no-subscriptions
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y
export HOME="/root"
# Install OpenVidu
@ -859,14 +821,15 @@ systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1;
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log" 2>&1 | crontab
# check_app_ready.sh internally caps its wait at 1200s
/usr/local/bin/check_app_ready.sh || { echo "[OpenVidu] master node did not become healthy"; exit 1; }
az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value "true"
set +e
az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key
set -e
az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value "true"
# Wait for the app
sleep 150
/usr/local/bin/check_app_ready.sh
'''
var userDataMasterNode = reduce(
@ -917,7 +880,7 @@ var stringInterpolationParamsMedia = {
}
var installScriptTemplateMedia = '''
#!/bin/bash
#!/bin/bash -x
set -e
DOMAIN=
@ -933,21 +896,26 @@ apt-get update && apt-get install -y \
# Get own private IP
PRIVATE_IP=$(curl -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text")
# Gate 1: wait for master secrets before installing
WAIT_INTERVAL=5
MAX_RETRIES=360
RETRIES=0
WAIT_INTERVAL=1
MAX_WAIT=200
ELAPSED_TIME=0
set +e
while true; do
ALL_SECRETS_GENERATED=$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)
if [ "$ALL_SECRETS_GENERATED" == "true" ]; then
# get secret value
FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv)
# Check if the secret has been generated
if [ "$FINISH_MASTER_NODE" == "true" ]; then
break
fi
RETRIES=$((RETRIES + 1))
if [ $RETRIES -ge $MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for ALL-SECRETS-GENERATED"
ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))
# Check if the maximum waiting time has been reached
if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then
exit 1
fi
sleep $WAIT_INTERVAL
done
set -e
@ -961,16 +929,8 @@ OPENVIDU_VERSION="$(az keyvault secret show --vault-name ${keyVaultName} --name
# Get Master Node private IP
MASTER_NODE_IP=${privateIPMasterNode}
# Download to a file first: process substitution would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "[OpenVidu] failed to download the media node installer script"
exit 1
fi
# Base command
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -1058,7 +1018,7 @@ az vmss delete-instances --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_
'''
var userDataMediaNodeTemplate = '''
#!/bin/bash
#!/bin/bash -x
set -eu -o pipefail
# Introduce the scripts in the instance
@ -1076,7 +1036,8 @@ chmod +x /usr/local/bin/delete_media_node.sh
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y jq
apt-get update && apt-get install -y
apt-get install -y jq
# Install azure cli
AZURE_CLI_VERSION=2.87.0
@ -1098,27 +1059,7 @@ az vmss update --resource-group $RESOURCE_GROUP_NAME --name $VM_SCALE_SET_NAME -
export HOME="/root"
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/delete_media_node.sh; exit 1; }
# Gate 2: wait for master readiness before starting
WAIT_INTERVAL=5
MAX_RETRIES=360
RETRIES=0
set +e
while true; do
FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv 2>/dev/null)
if [ "$FINISH_MASTER_NODE" == "true" ]; then
break
fi
RETRIES=$((RETRIES + 1))
if [ $RETRIES -ge $MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for FINISH-MASTER-NODE"
/usr/local/bin/delete_media_node.sh
exit 1
fi
sleep $WAIT_INTERVAL
done
set -e
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/delete_media_node.sh; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/delete_media_node.sh; }
@ -1154,7 +1095,6 @@ var userDataParamsMedia = {
base64delete: base64delete_mediaNode_ScriptMedia
resourceGroupName: resourceGroup().name
vmScaleSetName: '${stackName}-mediaNodeScaleSet'
keyVaultName: keyVaultName
}
var userDataMediaNode = reduce(

File diff suppressed because one or more lines are too long

View File

@ -27,8 +27,6 @@ resource "google_secret_manager_secret" "openvidu_shared_info" {
replication {
auto {}
}
depends_on = [google_project_service.secretmanager_api]
}
# GCS bucket
@ -38,16 +36,12 @@ resource "google_storage_bucket" "bucket" {
location = var.region
force_destroy = true
uniform_bucket_level_access = true
depends_on = [google_project_service.storage_api]
}
# Service account for the instance
resource "google_service_account" "service_account" {
account_id = lower("${substr(var.stackName, 0, 12)}-sa")
display_name = "OpenVidu instance service account"
depends_on = [google_project_service.iam_api]
}
# IAM bindings for the service account so the instance can access Secret Manager and GCS
@ -55,8 +49,6 @@ resource "google_project_iam_member" "iam_project_role" {
project = var.projectId
role = "roles/owner"
member = "serviceAccount:${google_service_account.service_account.email}"
depends_on = [google_project_service.cloudresourcemanager_api]
}
resource "google_compute_firewall" "firewall_master" {
@ -65,13 +57,11 @@ resource "google_compute_firewall" "firewall_master" {
allow {
protocol = "tcp"
ports = ["22", "80", "443", "1935"]
ports = ["22", "80", "443", "1935", "9000"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
}
resource "google_compute_firewall" "firewall_media" {
@ -89,8 +79,6 @@ resource "google_compute_firewall" "firewall_media" {
source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-media-node")]
depends_on = [google_project_service.compute_api]
}
resource "google_compute_firewall" "firewall_media_to_master" {
@ -108,8 +96,6 @@ resource "google_compute_firewall" "firewall_media_to_master" {
target_tags = [
lower("${var.stackName}-master-node"),
]
depends_on = [google_project_service.compute_api]
}
resource "google_compute_firewall" "firewall_master_to_media" {
@ -127,8 +113,6 @@ resource "google_compute_firewall" "firewall_master_to_media" {
target_tags = [
lower("${var.stackName}-media-node")
]
depends_on = [google_project_service.compute_api]
}
# Create Public Ip address (if not provided)
@ -136,8 +120,6 @@ resource "google_compute_address" "public_ip_address" {
count = var.publicIpAddress == "" ? 1 : 0
name = lower("${var.stackName}-public-ip")
region = var.region
depends_on = [google_project_service.compute_api]
}
locals {
@ -199,9 +181,6 @@ resource "google_compute_instance" "openvidu_master_node" {
stack = var.stackName
node-type = "master"
}
# Explicit: transitive coverage via public_ip_address is absent when publicIpAddress is provided
depends_on = [google_project_service.compute_api]
}
locals {
@ -255,24 +234,6 @@ resource "google_compute_instance_template" "media_node_template" {
}
}
# Health check for media node auto-healing
resource "google_compute_region_health_check" "media_node_health_check" {
name = lower("${var.stackName}-media-node-health-check")
region = var.region
# TCP-only, generous thresholds: auto-heal recreation kills live WebRTC sessions
tcp_health_check {
port = 7880
}
check_interval_sec = 30
timeout_sec = 10
healthy_threshold = 2
unhealthy_threshold = 5
depends_on = [google_project_service.compute_api]
}
# Managed Instance Group for Media Nodes
resource "google_compute_region_instance_group_manager" "media_node_group" {
name = lower("${var.stackName}-media-node-group")
@ -289,12 +250,6 @@ resource "google_compute_region_instance_group_manager" "media_node_group" {
port = 7880
}
# initial_delay_sec generous so media nodes finish installing before health checks can recreate them
auto_healing_policies {
health_check = google_compute_region_health_check.media_node_health_check.id
initial_delay_sec = 600
}
depends_on = [google_compute_instance.openvidu_master_node]
}
@ -593,8 +548,6 @@ resource "google_storage_bucket_object" "function_source" {
name = "function-source.zip"
bucket = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName
source = data.archive_file.function_source.output_path
depends_on = [google_project_service.storage_api]
}
resource "google_cloudfunctions2_function" "scalein_function" {
@ -624,12 +577,6 @@ resource "google_cloudfunctions2_function" "scalein_function" {
}
service_account_email = google_service_account.service_account.email
}
depends_on = [
google_project_service.cloudfunctions_api,
google_project_service.cloudbuild_api,
google_project_service.run_api
]
}
# Cloud Scheduler to trigger the function every 5 minutes
@ -659,8 +606,6 @@ resource "google_cloud_scheduler_job" "scale_scheduler" {
service_account_email = google_service_account.service_account.email
}
}
depends_on = [google_project_service.cloudscheduler_api]
}
# ------------------------- local values -------------------------
@ -672,7 +617,7 @@ locals {
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
@ -695,8 +640,8 @@ gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
METADATA_URL="http://metadata.google.internal/computeMetadata/v1"
get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; }
# Disable command tracing so secrets are not printed to the serial console
set +x
# Create counter file for tracking script executions
echo 1 > /usr/local/bin/openvidu_install_counter.txt
# Configure domain
if [[ "${var.domainName}" == "" ]]; then
@ -745,14 +690,8 @@ OPENVIDU_VERSION="$(/usr/local/bin/store_secret.sh save OPENVIDU_VERSION "$OPENV
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Build install command and args
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -839,9 +778,6 @@ SERVICE_ACCOUNT_EMAIL=$(get_meta "instance/service-accounts/default/email")
# Create key for service account
gcloud iam service-accounts keys create credentials.json --iam-account=$SERVICE_ACCOUNT_EMAIL
# Disable command tracing so credentials are not printed to the serial console
set +x
# Create HMAC key and parse output
HMAC_OUTPUT=$(gcloud storage hmac create $SERVICE_ACCOUNT_EMAIL --format="json")
EXTERNAL_S3_ACCESS_KEY=$(echo "$HMAC_OUTPUT" | jq -r '.metadata.accessId')
@ -906,9 +842,6 @@ INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster"
MASTER_NODE_CONFIG_DIR="$${INSTALL_DIR}/config/node"
# Disable command tracing so secrets are not printed to the serial console
set +x
# Replace DOMAIN_NAME
export DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME)
if [[ -n "$DOMAIN" ]]; then
@ -1037,7 +970,7 @@ echo -n "$ENABLED_MODULES" | gcloud secrets versions add ENABLED_MODULES --data-
EOF
get_value_from_config_script = <<-EOF
#!/bin/bash
#!/bin/bash -x
set -e
# Function to get the value of a given key from the environment file
@ -1106,17 +1039,11 @@ EOF
check_app_ready_script = <<-EOF
#!/bin/bash
i=0
while true; do
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy 2>/dev/null | head -n1 | awk '{print $2}')
if [ "$HTTP_STATUS" == "200" ]; then
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy | head -n1 | awk '{print $2}')
if [ $HTTP_STATUS == 200 ]; then
break
fi
i=$((i + 1))
if [ "$i" -ge 240 ]; then
echo "Timed out after 20 minutes waiting for OpenVidu to become ready" >&2
exit 1
fi
sleep 5
done
EOF
@ -1201,6 +1128,8 @@ CONFIG_S3_EOF
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
@ -1228,6 +1157,9 @@ CONFIG_S3_EOF
# Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; }
# restart.sh
echo "@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log" 2>&1 | crontab
# Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
fi
@ -1264,21 +1196,12 @@ MASTER_NODE_PRIVATE_IP=$(get_meta "instance/attributes/masterNodePrivateIP")
STACK_NAME=$(get_meta "instance/attributes/stackName")
PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip")
# Wait for master node to be ready by checking secrets.
i=0
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null | grep -q "true"; do
i=$((i + 1))
if [ "$i" -ge 180 ]; then
echo "Timed out after 30 minutes waiting for master node to initialize secrets" >&2
exit 1
fi
# Wait for master node to be ready by checking secrets
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null; do
echo "Waiting for master node to initialize secrets..."
sleep 10
done
# Disable command tracing so secrets are not printed to the serial console
set +x
# Get all necessary values from secrets
DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME)
OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE)
@ -1292,14 +1215,8 @@ if [[ "$OPENVIDU_VERSION" == "none" ]]; then
exit 1
fi
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Build install command for media node
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Media node arguments
COMMON_ARGS=(
@ -1403,56 +1320,52 @@ EOF
#!/bin/bash -x
set -eu -o pipefail
# Check if installation already completed
if [ -f /usr/local/bin/openvidu_install_counter.txt ]; then
# Launch on reboot
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
else
# install.sh (media node)
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
# install.sh (media node)
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
${local.install_script_media}
INSTALL_EOF
chmod +x /usr/local/bin/install.sh
chmod +x /usr/local/bin/install.sh
# graceful_shutdown.sh
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
# graceful_shutdown.sh
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
${local.graceful_shutdown_script}
GRACEFUL_SHUTDOWN_EOF
chmod +x /usr/local/bin/graceful_shutdown.sh
chmod +x /usr/local/bin/graceful_shutdown.sh
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
fi
fi
# Authenticate with gcloud using instance service account
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
# Authenticate with gcloud using instance service account
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
export HOME="/root"
export HOME="/root"
# Install OpenVidu Media Node
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
# Install OpenVidu Media Node
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
# Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
# Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Add cron job to check if instance is abandoned every minute
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
# Add cron job to check if instance is abandoned every minute
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
${local.crontab_job_media}
CHECK_ABANDONED_EOF
chmod +x /usr/local/bin/check_abandoned.sh
chmod +x /usr/local/bin/check_abandoned.sh
echo "*/1 * * * * /usr/local/bin/check_abandoned.sh > /var/log/openvidu-abandoned-check.log 2>&1" | crontab -
fi
echo "*/1 * * * * /usr/local/bin/check_abandoned.sh > /var/log/openvidu-abandoned-check.log 2>&1" | crontab -
EOF
}

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -1242,39 +1242,14 @@ Resources:
"GRAFANA_ADMIN_USERNAME": "none",
"GRAFANA_ADMIN_PASSWORD": "none",
"ENABLED_MODULES": "none",
"MASTER_NODE_1_PRIVATE_IP": "none",
"MASTER_NODE_2_PRIVATE_IP": "none",
"MASTER_NODE_3_PRIVATE_IP": "none",
"MASTER_NODE_4_PRIVATE_IP": "none",
"OPENVIDU_VERSION": "none",
"ALL_SECRETS_GENERATED": "false"
}
# One SSM parameter per master: atomic per-key writes
MasterNode1PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-1-private-ip
Type: String
Value: "none"
MasterNode2PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-2-private-ip
Type: String
Value: "none"
MasterNode3PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-3-private-ip
Type: String
Value: "none"
MasterNode4PrivateIpParameter:
Type: AWS::SSM::Parameter
Properties:
Name: !Sub /openvidu/${AWS::StackName}/master-node-4-private-ip
Type: String
Value: "none"
S3AppDataBucketResource:
Type: 'AWS::S3::Bucket'
Properties:
@ -1306,7 +1281,8 @@ Resources:
Condition: CreateClusterDataBucket
# -------------------------
# Normalize the master node subnet list to exactly 4 entries, one per OpenVidu Master Node
# Preprocess subnets to allocate Volumes and ENIs across Availability Zones
# For OpenVidu Master Nodes
# -------------------------
SubnetProcessorFunction:
Type: AWS::Lambda::Function
@ -1317,11 +1293,13 @@ Resources:
Code:
ZipFile: |
import cfnresponse
import boto3
def lambda_handler(event, context):
try:
# Process event data
subnets = event['ResourceProperties']['Subnets']
ec2 = boto3.client('ec2')
# Ensure we have at least four subnets by cycling through the available subnets
subnets = (subnets * 4)[:4] # Repeat the list to have at least 4 elements and then take the first 4
@ -1377,6 +1355,10 @@ Resources:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}:*'
- Effect: Allow
Action:
- ec2:DescribeSubnets
Resource: '*'
OpenViduMasterNodeRole:
Type: AWS::IAM::Role
@ -1408,11 +1390,6 @@ Resources:
- secretsmanager:GetSecretValue
- secretsmanager:UpdateSecret
Resource: !Ref OpenViduSharedInfo
- Effect: Allow
Action:
- ssm:GetParameter
- ssm:PutParameter
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/openvidu/${AWS::StackName}/*
- Fn::If:
- CreateRecordingsBucket
- Effect: Allow
@ -1497,10 +1474,6 @@ Resources:
Action:
- secretsmanager:GetSecretValue
Resource: !Ref OpenViduSharedInfo
- Effect: Allow
Action:
- ssm:GetParameter
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/openvidu/${AWS::StackName}/*
- Effect: Allow
Action:
- autoscaling:SetInstanceHealth
@ -1553,6 +1526,8 @@ Resources:
OpenViduMediaInstanceProfile:
Type: AWS::IAM::InstanceProfile
DependsOn:
- MasterNodesWaitCondition4
Properties:
InstanceProfileName: !Sub OpenViduMediaInstanceProfile-${AWS::Region}-${AWS::StackName}
Roles:
@ -1569,7 +1544,7 @@ Resources:
content: !Sub |
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
@ -1591,15 +1566,42 @@ Resources:
rm -rf awscliv2.zip aws
fi
# Signal to notify instance is waiting
# Singal to notify instance is waiting
SIGNAL_NAME="$1"
# Exported earlier in this instance's UserData
MASTER_NODE_NUM="${!MASTER_NODE_NUM}"
# Token for IMDSv2
TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")"
# Subnets IDs
SUBNETS=(
"${SubnetProcessor.Subnet1}"
"${SubnetProcessor.Subnet2}"
"${SubnetProcessor.Subnet3}"
"${SubnetProcessor.Subnet4}"
)
MAC_ADDRESS="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254//latest/meta-data/mac)"
SUBNET_ID="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s "http://169.254.169.254/latest/meta-data/network/interfaces/macs/$MAC_ADDRESS/subnet-id")"
# Check master node number
MASTER_NODE_NUM=1
for subnet in "${!SUBNETS[@]}"; do
if [[ "$subnet" == "$SUBNET_ID" ]]; then
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')
# Check if current master node is reacheable with ping
ACUTAL_MASTER_NODE_IP=$(echo "$SHARED_SECRET" | jq -r ".MASTER_NODE_${!MASTER_NODE_NUM}_PRIVATE_IP")
if [[ "$ACUTAL_MASTER_NODE_IP" == "none" ]]; then
break
fi
fi
MASTER_NODE_NUM=$((MASTER_NODE_NUM + 1))
done
# Get own private IP
PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"
@ -1608,12 +1610,8 @@ Resources:
exit 1
fi
aws ssm put-parameter \
--region ${AWS::Region} \
--name "/openvidu/${AWS::StackName}/master-node-${!MASTER_NODE_NUM}-private-ip" \
--value "${!PRIVATE_IP}" \
--type String \
--overwrite > /dev/null
# Store current private IP
/usr/local/bin/store_secret.sh save MASTER_NODE_${!MASTER_NODE_NUM}_PRIVATE_IP "${!PRIVATE_IP}"
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
@ -1692,52 +1690,39 @@ Resources:
# sending the signal call
cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region}
IP_WAIT_ATTEMPTS=0
IP_WAIT_MAX=360
while true; do
MASTER_NODE_1_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_2_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_3_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_4_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
# Check if all master nodes have published their private IPs
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_3_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_4_PRIVATE_IP" ]]; then
break
fi
IP_WAIT_ATTEMPTS=$((IP_WAIT_ATTEMPTS + 1))
if [[ $IP_WAIT_ATTEMPTS -ge $IP_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for all master nodes to publish their private IPs to SSM"
exit 1
fi
sleep 5
done
# Comma-separated 1-4: must match installer's --master-node-private-ip-list format
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
# Re-read: other (non-IP) values still come from the shared secret.
# GetSecretValue is eventually consistent: retry until a read returns the generated values
SECRET_READ_ATTEMPTS=0
# Wait for all master nodes to store their private IPs
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
--query SecretString --output text || echo 'none')
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
# Check if all master nodes have stored their private IPs
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]]; then
break
fi
SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
echo "Error: shared secret still incomplete after 5 minutes of stale reads"
exit 1
fi
sleep 5
done
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text)
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
@ -1753,22 +1738,18 @@ Resources:
GRAFANA_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_PASSWORD')
MEET_INITIAL_ADMIN_USER=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_USER')
MEET_INITIAL_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
MEET_INITIAL_API_KEY=""
if [[ "${InitialMeetApiKey}" != '' ]]; then
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
else
MEET_INITIAL_API_KEY=""
fi
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
LIVEKIT_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_KEY')
LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET')
ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Base command
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -1856,10 +1837,6 @@ Resources:
content: !Sub |
#!/bin/bash
set -e
# Only master 1 writes the shared secret, to avoid a lost-update race
if [[ "${!MASTER_NODE_NUM:-}" != "1" ]]; then
exit 0
fi
# Get current shared secret
SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
@ -2126,8 +2103,6 @@ Resources:
OpenViduMasterNode1:
Type: AWS::EC2::Instance
# Depends on its own SSM parameter so its put-parameter can't race the parameter's creation
DependsOn: MasterNode1PrivateIpParameter
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2158,8 +2133,6 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=1
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2206,7 +2179,7 @@ Resources:
OpenViduMasterNode2:
Type: AWS::EC2::Instance
DependsOn: MasterNode2PrivateIpParameter
DependsOn: MasterNodesWaitCondition1
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2237,8 +2210,6 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=2
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2285,7 +2256,7 @@ Resources:
OpenViduMasterNode3:
Type: AWS::EC2::Instance
DependsOn: MasterNode3PrivateIpParameter
DependsOn: MasterNodesWaitCondition2
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2316,8 +2287,6 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=3
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2364,7 +2333,7 @@ Resources:
OpenViduMasterNode4:
Type: AWS::EC2::Instance
DependsOn: MasterNode4PrivateIpParameter
DependsOn: MasterNodesWaitCondition3
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2395,8 +2364,6 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=4
# Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2486,25 +2453,6 @@ Resources:
exit 1
fi
SECRETS_WAIT_ATTEMPTS=0
SECRETS_WAIT_MAX=360
while true; do
# Validate content, not just the flag: stale eventually-consistent reads can return pre-generation values
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
break
fi
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for shared secrets to be generated"
exit 1
fi
sleep 5
SHARED_SECRET="$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none')"
done
# Get OpenVidu Media Nodes version to deploy
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then
@ -2512,39 +2460,20 @@ Resources:
exit 1
fi
IP_WAIT_ATTEMPTS=0
IP_WAIT_MAX=360
while true; do
MASTER_NODE_1_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_2_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_3_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_NODE_4_PRIVATE_IP=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_3_PRIVATE_IP" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_4_PRIVATE_IP" ]]; then
break
fi
IP_WAIT_ATTEMPTS=$((IP_WAIT_ATTEMPTS + 1))
if [[ $IP_WAIT_ATTEMPTS -ge $IP_WAIT_MAX ]]; then
echo "Error: timed out after 30 minutes waiting for all master nodes to publish their private IPs to SSM"
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
if [[ "$ALL_SECRETS_GENERATED" == "none" ]]; then
echo "Error: Secrets not generated"
exit 1
fi
sleep 5
done
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Base command
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -2680,36 +2609,13 @@ Resources:
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi
cfn-init --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMediaNodeLaunchTemplate
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduMediaNodeLaunchTemplate
export HOME="/root"
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
MASTER_IP_1=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-1-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_2=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-2-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_3=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-3-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_IP_4=$(aws ssm get-parameter --region ${AWS::Region} --name "/openvidu/${AWS::StackName}/master-node-4-private-ip" --query Parameter.Value --output text 2>/dev/null || echo 'none')
MASTER_HEALTHY=false
for i in $(seq 1 360); do
for MASTER_IP in "$MASTER_IP_1" "$MASTER_IP_2" "$MASTER_IP_3" "$MASTER_IP_4"; do
if [ "$MASTER_IP" != "none" ] && [ -n "$MASTER_IP" ] && curl -sf "http://$MASTER_IP:7880/health/caddy" >/dev/null 2>&1; then
MASTER_HEALTHY=true
break
fi
done
if [ "$MASTER_HEALTHY" = "true" ]; then
break
fi
sleep 5
done
if [ "$MASTER_HEALTHY" != "true" ]; then
echo "[OpenVidu] no master node became healthy after 30 minutes"
/usr/local/bin/set_as_unhealthy.sh
exit 1
fi
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
@ -3285,6 +3191,8 @@ Resources:
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn:
- MasterNodesWaitCondition4
Properties:
Name:
Fn::Join:

View File

@ -158,19 +158,6 @@ var tenantId = subscription().tenantId
var deploymentUser = az.deployer().objectId
/*------------------------------------------- MANAGED IDENTITIES -------------------------------------------*/
// Split in two identities to keep the permission asymmetry: masters write secrets, media nodes only read them
resource masterIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: '${stackName}-master-identity'
location: location
}
resource mediaIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: '${stackName}-media-identity'
location: location
}
/*------------------------------------------- KEY VAULT -------------------------------------------*/
resource openviduSharedInfo 'Microsoft.KeyVault/vaults@2023-07-01' = {
@ -183,16 +170,36 @@ resource openviduSharedInfo 'Microsoft.KeyVault/vaults@2023-07-01' = {
tenantId: tenantId
enableSoftDelete: false
accessPolicies: [
// Pre-created identities: the vault no longer waits for the VMs to exist
{
objectId: masterIdentity.properties.principalId
objectId: openviduMasterNode1.identity.principalId
tenantId: tenantId
permissions: {
secrets: ['get', 'set', 'list']
}
}
{
objectId: mediaIdentity.properties.principalId
objectId: openviduMasterNode2.identity.principalId
tenantId: tenantId
permissions: {
secrets: ['get', 'set', 'list']
}
}
{
objectId: openviduMasterNode3.identity.principalId
tenantId: tenantId
permissions: {
secrets: ['get', 'set', 'list']
}
}
{
objectId: openviduMasterNode4.identity.principalId
tenantId: tenantId
permissions: {
secrets: ['get', 'set', 'list']
}
}
{
objectId: openviduScaleSetMediaNode.identity.principalId
tenantId: tenantId
permissions: {
secrets: ['get']
@ -230,7 +237,6 @@ var stringInterpolationParamsMaster1 = {
initialMeetAdminPassword: initialMeetAdminPassword
initialMeetApiKey: initialMeetApiKey
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '1'
additionalInstallFlags: additionalInstallFlags
}
@ -246,7 +252,6 @@ var stringInterpolationParamsMaster2 = {
initialMeetAdminPassword: initialMeetAdminPassword
initialMeetApiKey: initialMeetApiKey
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '2'
additionalInstallFlags: additionalInstallFlags
}
@ -262,7 +267,6 @@ var stringInterpolationParamsMaster3 = {
initialMeetAdminPassword: initialMeetAdminPassword
initialMeetApiKey: initialMeetApiKey
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '3'
additionalInstallFlags: additionalInstallFlags
}
@ -278,7 +282,6 @@ var stringInterpolationParamsMaster4 = {
initialMeetAdminPassword: initialMeetAdminPassword
initialMeetApiKey: initialMeetApiKey
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '4'
additionalInstallFlags: additionalInstallFlags
}
@ -286,7 +289,7 @@ var stringInterpolationParamsMaster4 = {
var installScriptTemplateMaster = '''
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
# Assume azure cli is installed
@ -301,7 +304,7 @@ apt-get update && apt-get install -y \
# Wait for the keyvault availability
MAX_WAIT=300
MAX_WAIT=100
WAIT_INTERVAL=1
ELAPSED_TIME=0
set +e
@ -394,9 +397,6 @@ if [[ $MASTER_NODE_NUM -eq 1 ]] && [[ "$ALL_SECRETS_GENERATED" == "" || "$ALL_SE
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL-SECRETS-GENERATED "true")"
fi
IP_WAIT_MAX_RETRIES=360
IP_WAIT_INTERVAL=5
IP_WAIT_RETRIES=0
while true; do
MASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv) || true
MASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv) || true
@ -409,26 +409,11 @@ while true; do
[[ "$MASTER_NODE_4_PRIVATE_IP" != "" ]]; then
break
fi
IP_WAIT_RETRIES=$((IP_WAIT_RETRIES + 1))
if [ $IP_WAIT_RETRIES -ge $IP_WAIT_MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for the 4 master nodes to publish their private IPs"
exit 1
fi
sleep $IP_WAIT_INTERVAL
sleep 5
done
# Wait until master-node-1 has generated all shared secrets before fetching them
SECRETS_WAIT_MAX_RETRIES=360
SECRETS_WAIT_INTERVAL=5
SECRETS_WAIT_RETRIES=0
while [[ "$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)" != "true" ]]; do
SECRETS_WAIT_RETRIES=$((SECRETS_WAIT_RETRIES + 1))
if [ $SECRETS_WAIT_RETRIES -ge $SECRETS_WAIT_MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for ALL-SECRETS-GENERATED to become true"
exit 1
fi
sleep $SECRETS_WAIT_INTERVAL
done
while [[ "$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)" != "true" ]]; do sleep 5; done
# Fetch the values in the keyvault
@ -463,16 +448,8 @@ fi
ENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "[OpenVidu] failed to download the master node installer script"
exit 1
fi
# Base command
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -548,7 +525,7 @@ var after_installScriptTemplateMaster = '''
#!/bin/bash
set -e
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null
az login --identity --allow-no-subscriptions > /dev/null
# Generate URLs
DOMAIN=$(az keyvault secret show --vault-name ${keyVaultName} --name DOMAIN-NAME --query value -o tsv)
@ -576,7 +553,7 @@ var update_config_from_secretScriptTemplateMaster = '''
#!/bin/bash
set -e
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null
az login --identity --allow-no-subscriptions > /dev/null
# Installation directory
INSTALL_DIR="/opt/openvidu"
@ -655,7 +632,7 @@ var update_secret_from_configScriptTemplateMaster = '''
#!/bin/bash
set -e
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null
az login --identity --allow-no-subscriptions > /dev/null
# Installation directory
INSTALL_DIR="/opt/openvidu"
@ -748,7 +725,7 @@ var store_secretScriptTemplateMaster = '''
#!/bin/bash
set -e
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null
az login --identity --allow-no-subscriptions > /dev/null
# Modes: save, generate
# save mode: save the secret in the secret manager
@ -786,7 +763,7 @@ fi
var get_public_ip = '''
#!/bin/bash
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions > /dev/null
az login --identity --allow-no-subscriptions > /dev/null
az network public-ip show \
--id ${publicIPId} \
@ -834,12 +811,12 @@ INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="${INSTALL_DIR}/config/cluster"
# Retry login + storage key fetch to allow the Contributor role assignment to propagate
MAX_WAIT=300
MAX_WAIT=100
WAIT_INTERVAL=1
ELAPSED_TIME=0
set +e
while true; do
az login --identity --client-id ${masterIdentityClientId}
az login --identity
# Config azure blob storage
AZURE_ACCOUNT_NAME="${storageAccountName}"
@ -924,7 +901,6 @@ var store_secretScriptMaster = reduce(
).value
var blobStorageParams = {
masterIdentityClientId: masterIdentity.properties.clientId
storageAccountName: isEmptyStorageAccountName ? storageAccount.name : existingStorageAccount.name
storageAccountKey: listKeys(storageAccount.id, '2021-04-01').keys[0].value
storageAccountContainerName: isEmptyAppDataContainerName ? 'openvidu-appdata' : '${appDataContainerName}'
@ -961,7 +937,6 @@ var userDataParamsMasterNode1 = {
base64check_app_ready: base64check_app_readyMaster
base64restart: base64restartMaster
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '1'
base64config_blobStorage: base64config_blobStorage
}
@ -977,7 +952,6 @@ var userDataParamsMasterNode2 = {
base64check_app_ready: base64check_app_readyMaster
base64restart: base64restartMaster
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '2'
base64config_blobStorage: base64config_blobStorage
}
@ -993,7 +967,6 @@ var userDataParamsMasterNode3 = {
base64check_app_ready: base64check_app_readyMaster
base64restart: base64restartMaster
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '3'
base64config_blobStorage: base64config_blobStorage
}
@ -1009,7 +982,6 @@ var userDataParamsMasterNode4 = {
base64check_app_ready: base64check_app_readyMaster
base64restart: base64restartMaster
keyVaultName: keyVaultName
masterIdentityClientId: masterIdentity.properties.clientId
masterNodeNum: '4'
storageAccountName: isEmptyStorageAccountName ? storageAccount.name : existingStorageAccount.name
base64config_blobStorage: base64config_blobStorage
@ -1060,17 +1032,19 @@ echo ${base64config_blobStorage} | base64 -d > /usr/local/bin/config_blobStorage
chmod +x /usr/local/bin/config_blobStorage.sh
# Install azure cli
AZURE_CLI_VERSION=2.88.0
AZURE_CLI_VERSION=2.87.0
apt-get install -y apt-transport-https ca-certificates gnupg lsb-release
curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list
apt-get update
apt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs)
az login --identity --client-id ${masterIdentityClientId} --allow-no-subscriptions
az login --identity --allow-no-subscriptions
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y
export HOME="/root"
# Install OpenVidu
@ -1088,18 +1062,20 @@ systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1;
# Launch on reboot
echo "@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log" 2>&1 | crontab
# check_app_ready.sh internally caps its wait at 1200s
/usr/local/bin/check_app_ready.sh || { echo "[OpenVidu] master node did not become healthy"; exit 1; }
az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value "true"
MASTER_NODE_NUM=${masterNodeNum}
if [[ $MASTER_NODE_NUM -eq 4 ]]; then
# Creating scale in lock
set +e
az storage blob upload --account-name ${storageAccountName} --container-name automation-locks --name lock.txt --file /dev/null --auth-mode key
set -e
#Finish all the nodes
az keyvault secret set --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --value "true"
fi
# Wait for the app
sleep 150
/usr/local/bin/check_app_ready.sh
'''
var userDataMasterNode1 = reduce(
@ -1129,12 +1105,7 @@ var userDataMasterNode4 = reduce(
resource openviduMasterNode1 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: '${stackName}-VM-MasterNode1'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${masterIdentity.id}': {}
}
}
identity: { type: 'SystemAssigned' }
properties: {
hardwareProfile: {
vmSize: masterNodeInstanceType
@ -1168,12 +1139,7 @@ resource openviduMasterNode1 'Microsoft.Compute/virtualMachines@2023-09-01' = {
resource openviduMasterNode2 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: '${stackName}-VM-MasterNode2'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${masterIdentity.id}': {}
}
}
identity: { type: 'SystemAssigned' }
properties: {
hardwareProfile: {
vmSize: masterNodeInstanceType
@ -1202,17 +1168,13 @@ resource openviduMasterNode2 'Microsoft.Compute/virtualMachines@2023-09-01' = {
}
userData: base64(userDataMasterNode2)
}
dependsOn: [openviduMasterNode1]
}
resource openviduMasterNode3 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: '${stackName}-VM-MasterNode3'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${masterIdentity.id}': {}
}
}
identity: { type: 'SystemAssigned' }
properties: {
hardwareProfile: {
vmSize: masterNodeInstanceType
@ -1241,17 +1203,13 @@ resource openviduMasterNode3 'Microsoft.Compute/virtualMachines@2023-09-01' = {
}
userData: base64(userDataMasterNode3)
}
dependsOn: [openviduMasterNode2]
}
resource openviduMasterNode4 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: '${stackName}-VM-MasterNode4'
location: location
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${masterIdentity.id}': {}
}
}
identity: { type: 'SystemAssigned' }
properties: {
hardwareProfile: {
vmSize: masterNodeInstanceType
@ -1280,6 +1238,7 @@ resource openviduMasterNode4 'Microsoft.Compute/virtualMachines@2023-09-01' = {
}
userData: base64(userDataMasterNode4)
}
dependsOn: [openviduMasterNode3]
}
/*------------------------------------------- MEDIA NODES -------------------------------------------*/
@ -1313,29 +1272,23 @@ apt-get update && apt-get install -y \
# Get own private IP
PRIVATE_IP=$(curl -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance/network/interface/0/ipv4/ipAddress/0/privateIpAddress?api-version=2017-08-01&format=text")
# Gate 1: wait for master secrets and IPs before installing
WAIT_INTERVAL=5
MAX_RETRIES=360
RETRIES=0
WAIT_INTERVAL=1
MAX_WAIT=10000
ELAPSED_TIME=0
set +e
while true; do
ALL_SECRETS_GENERATED=$(az keyvault secret show --vault-name ${keyVaultName} --name ALL-SECRETS-GENERATED --query value -o tsv 2>/dev/null)
MASTER_NODE_1_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-1-PRIVATE-IP --query value -o tsv 2>/dev/null)
MASTER_NODE_2_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-2-PRIVATE-IP --query value -o tsv 2>/dev/null)
MASTER_NODE_3_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-3-PRIVATE-IP --query value -o tsv 2>/dev/null)
MASTER_NODE_4_PRIVATE_IP=$(az keyvault secret show --vault-name ${keyVaultName} --name MASTER-NODE-4-PRIVATE-IP --query value -o tsv 2>/dev/null)
# get secret value
FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv)
if [ "$ALL_SECRETS_GENERATED" == "true" ] &&
[ "$MASTER_NODE_1_PRIVATE_IP" != "" ] &&
[ "$MASTER_NODE_2_PRIVATE_IP" != "" ] &&
[ "$MASTER_NODE_3_PRIVATE_IP" != "" ] &&
[ "$MASTER_NODE_4_PRIVATE_IP" != "" ]; then
# Check if all master nodes finished
if [ "$FINISH_MASTER_NODE" == "true" ]; then
break
fi
RETRIES=$((RETRIES + 1))
if [ $RETRIES -ge $MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for master nodes to generate secrets and publish their IPs"
ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL))
# Check if the maximum waiting time has been reached
if [ $ELAPSED_TIME -ge $MAX_WAIT ]; then
exit 1
fi
@ -1352,16 +1305,8 @@ REDIS_PASSWORD=$(az keyvault secret show --vault-name ${keyVaultName} --name RED
ENABLED_MODULES=$(az keyvault secret show --vault-name ${keyVaultName} --name ENABLED-MODULES --query value -o tsv)
OPENVIDU_VERSION=$(az keyvault secret show --vault-name ${keyVaultName} --name OPENVIDU-VERSION --query value -o tsv)
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "[OpenVidu] failed to download the media node installer script"
exit 1
fi
# Base command
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -1384,7 +1329,6 @@ exec bash -c "$FINAL_COMMAND"
'''
var stopMediaNodeParams = {
mediaIdentityClientId: mediaIdentity.properties.clientId
subscriptionId: subscription().subscriptionId
resourceGroupName: resourceGroup().name
vmScaleSetName: '${stackName}-mediaNodeScaleSet'
@ -1420,7 +1364,7 @@ if [ -x "$(command -v docker)" ]; then
done
fi
az login --identity --client-id ${mediaIdentityClientId}
az login --identity
RESOURCE_GROUP_NAME=${resourceGroupName}
VM_SCALE_SET_NAME=${vmScaleSetName}
@ -1439,7 +1383,7 @@ var delete_mediaNode_ScriptMediaTemplate = '''
#!/bin/bash
set -e
az login --identity --client-id ${mediaIdentityClientId}
az login --identity
RESOURCE_GROUP_NAME=${resourceGroupName}
VM_SCALE_SET_NAME=${vmScaleSetName}
@ -1469,17 +1413,18 @@ chmod +x /usr/local/bin/delete_media_node.sh
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y jq
apt-get update && apt-get install -y
apt-get install -y jq
# Install azure cli
AZURE_CLI_VERSION=2.88.0
AZURE_CLI_VERSION=2.87.0
apt-get install -y apt-transport-https ca-certificates gnupg lsb-release
curl -sLS https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] https://packages.microsoft.com/repos/azure-cli/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/azure-cli.list
apt-get update
apt-get install -y azure-cli=${AZURE_CLI_VERSION}-1~$(lsb_release -cs)
az login --identity --client-id ${mediaIdentityClientId}
az login --identity
# Protect from scale in actions
RESOURCE_GROUP_NAME=${resourceGroupName}
@ -1493,26 +1438,6 @@ export HOME="/root"
# Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/delete_media_node.sh; }
# Gate 2: wait for master readiness before starting
WAIT_INTERVAL=5
MAX_RETRIES=360
RETRIES=0
set +e
while true; do
FINISH_MASTER_NODE=$(az keyvault secret show --vault-name ${keyVaultName} --name FINISH-MASTER-NODE --query value -o tsv 2>/dev/null)
if [ "$FINISH_MASTER_NODE" == "true" ]; then
break
fi
RETRIES=$((RETRIES + 1))
if [ $RETRIES -ge $MAX_RETRIES ]; then
echo "[OpenVidu] timed out after 30 min waiting for FINISH-MASTER-NODE"
/usr/local/bin/delete_media_node.sh
exit 1
fi
sleep $WAIT_INTERVAL
done
set -e
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/delete_media_node.sh; }
'''
@ -1543,10 +1468,8 @@ var userDataParamsMedia = {
base64install: base64installMedia
base64stop: base64stopMediaNode
base64delete_mediaNode: base64delete_mediaNode_ScriptMedia
mediaIdentityClientId: mediaIdentity.properties.clientId
resourceGroupName: resourceGroup().name
vmScaleSetName: '${stackName}-mediaNodeScaleSet'
keyVaultName: keyVaultName
}
var userDataMediaNode = reduce(
@ -1565,12 +1488,7 @@ resource openviduScaleSetMediaNode 'Microsoft.Compute/virtualMachineScaleSets@20
InstanceDeleteTime: datetime
storageAccount: isEmptyStorageAccountName ? storageAccount.name : existingStorageAccount.name
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${mediaIdentity.id}': {}
}
}
identity: { type: 'SystemAssigned' }
sku: {
name: mediaNodeInstanceType
tier: 'Standard'
@ -1699,29 +1617,67 @@ resource openviduAutoScaleSettingsMediaNode 'Microsoft.Insights/autoscaleSetting
/*------------------------------------------- SCALE IN ------------------------------------------*/
// One assignment for the shared master identity replaces the four per-VM ones
resource roleAssignmentMasterNodes 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForMasterNodes', masterIdentity.id)
resource roleAssignmentMasterNode1 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForMasterNode${openviduMasterNode1.name}')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: masterIdentity.properties.principalId
principalId: openviduMasterNode1.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource roleAssignmentMasterNode2 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForMasterNode${openviduMasterNode2.name}')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: openviduMasterNode2.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource roleAssignmentMasterNode3 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForMasterNode${openviduMasterNode3.name}')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: openviduMasterNode3.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource roleAssignmentMasterNode4 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForMasterNode${openviduMasterNode4.name}')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: openviduMasterNode4.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid('roleAssignmentForScaleSet', mediaIdentity.id)
name: guid('roleAssignmentForScaleSet${openviduScaleSetMediaNode.name}')
scope: resourceGroup()
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
principalId: mediaIdentity.properties.principalId
principalId: openviduScaleSetMediaNode.identity.principalId
principalType: 'ServicePrincipal'
}
}

File diff suppressed because one or more lines are too long

View File

@ -553,7 +553,7 @@
"datetime": "[steps('parameters SCALING').datetime]",
"automationAccountName": "[steps('parameters SCALING').automationAccountName]",
"storageAccountName": "[steps('parameters STORAGE').storageAccountName]",
"appDataContainerName": "[steps('parameters STORAGE').containerName]",
"containerName": "[steps('parameters STORAGE').containerName]",
"additionalInstallFlags": "[steps('FLAGS').additionalInstallFlags]"
}
}

View File

@ -20,15 +20,14 @@ resource "google_secret_manager_secret" "openvidu_shared_info" {
"OPENVIDU_PRO_LICENSE", "OPENVIDU_RTC_ENGINE", "REDIS_PASSWORD", "MONGO_ADMIN_USERNAME",
"MONGO_ADMIN_PASSWORD", "MONGO_REPLICA_SET_KEY", "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY",
"DASHBOARD_ADMIN_USERNAME", "DASHBOARD_ADMIN_PASSWORD", "GRAFANA_ADMIN_USERNAME",
"GRAFANA_ADMIN_PASSWORD", "ENABLED_MODULES", "OPENVIDU_VERSION", "ALL_SECRETS_GENERATED"
"GRAFANA_ADMIN_PASSWORD", "ENABLED_MODULES", "OPENVIDU_VERSION", "ALL_SECRETS_GENERATED",
"MASTER_NODE_1_PRIVATE_IP", "MASTER_NODE_2_PRIVATE_IP", "MASTER_NODE_3_PRIVATE_IP", "MASTER_NODE_4_PRIVATE_IP"
])
secret_id = each.key
replication {
auto {}
}
depends_on = [google_project_service.secretmanager_api]
}
# GCS buckets for HA deployment
@ -38,8 +37,6 @@ resource "google_storage_bucket" "appdata_bucket" {
location = var.region
force_destroy = true
uniform_bucket_level_access = true
depends_on = [google_project_service.storage_api]
}
resource "google_storage_bucket" "clusterdata_bucket" {
@ -48,16 +45,12 @@ resource "google_storage_bucket" "clusterdata_bucket" {
location = var.region
force_destroy = true
uniform_bucket_level_access = true
depends_on = [google_project_service.storage_api]
}
# Service account for the instances
resource "google_service_account" "service_account" {
account_id = lower("${substr(var.stackName, 0, 12)}-sa")
display_name = "OpenVidu instance service account"
depends_on = [google_project_service.iam_api]
}
# IAM bindings for the service account
@ -65,8 +58,6 @@ resource "google_project_iam_member" "iam_project_role" {
project = var.projectId
role = "roles/owner"
member = "serviceAccount:${google_service_account.service_account.email}"
depends_on = [google_project_service.cloudresourcemanager_api]
}
# External SSH access to Master Nodes
@ -81,8 +72,6 @@ resource "google_compute_firewall" "external_master_ssh" {
source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
}
# External access to Media Nodes (SSH and media traffic)
@ -101,8 +90,6 @@ resource "google_compute_firewall" "external_media_access" {
source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-media-node")]
depends_on = [google_project_service.compute_api]
}
# Load Balancer health checks and HTTP traffic to Master Nodes
@ -117,8 +104,6 @@ resource "google_compute_firewall" "lb_to_master_http" {
source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
}
# Master node internal services communication
@ -144,8 +129,6 @@ resource "google_compute_firewall" "master_to_master_internal" {
source_tags = [lower("${var.stackName}-master-node")]
target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
}
# Media Nodes to Master Nodes communication
@ -170,8 +153,6 @@ resource "google_compute_firewall" "media_to_master_services" {
source_tags = [lower("${var.stackName}-media-node")]
target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
}
# Master Nodes to Media Nodes communication
@ -190,8 +171,6 @@ resource "google_compute_firewall" "master_to_media_services" {
source_tags = [lower("${var.stackName}-master-node")]
target_tags = [lower("${var.stackName}-media-node")]
depends_on = [google_project_service.compute_api]
}
@ -200,8 +179,6 @@ resource "google_compute_address" "nlb_ip" {
count = var.publicIpAddress == "" ? 1 : 0
name = lower("${var.stackName}-nlb-ip")
region = var.region
depends_on = [google_project_service.compute_api]
}
# Data source for existing IP address when publicIpAddress is provided
@ -229,8 +206,6 @@ resource "google_compute_region_health_check" "tcp_health_check" {
timeout_sec = 5
healthy_threshold = 3
unhealthy_threshold = 4
depends_on = [google_project_service.compute_api]
}
# Regional backend service for the TCP NLB
@ -314,29 +289,6 @@ locals {
is_c4a_instance = startswith(var.masterNodesInstanceType, "c4a-")
}
# Must match the subnetwork the master instances implicitly use (network = "default", no explicit subnetwork)
data "google_compute_subnetwork" "default" {
name = "default"
region = var.region
depends_on = [google_project_service.compute_api]
}
resource "google_compute_address" "master_internal_ip" {
count = 4
name = lower("${var.stackName}-master-node-${count.index + 1}-internal-ip")
address_type = "INTERNAL"
region = var.region
subnetwork = data.google_compute_subnetwork.default.id
depends_on = [google_project_service.compute_api]
}
locals {
# Comma-separated: must match the installer's --master-node-private-ip-list format
master_node_private_ip_list = join(",", google_compute_address.master_internal_ip[*].address)
}
# Master Node 1
resource "google_compute_instance" "openvidu_master_node_1" {
name = lower("${var.stackName}-master-node-1")
@ -354,14 +306,12 @@ resource "google_compute_instance" "openvidu_master_node_1" {
network_interface {
network = "default"
network_ip = google_compute_address.master_internal_ip[0].address
access_config {}
}
metadata = {
stackName = var.stackName
masterNodeNum = "1"
masterNodePrivateIPList = local.master_node_private_ip_list
domainName = var.domainName
certificateType = var.certificateType
ownPublicCertificate = var.ownPublicCertificate
@ -406,14 +356,12 @@ resource "google_compute_instance" "openvidu_master_node_2" {
network_interface {
network = "default"
network_ip = google_compute_address.master_internal_ip[1].address
access_config {}
}
metadata = {
stackName = var.stackName
masterNodeNum = "2"
masterNodePrivateIPList = local.master_node_private_ip_list
domainName = var.domainName
certificateType = var.certificateType
ownPublicCertificate = var.ownPublicCertificate
@ -439,6 +387,8 @@ resource "google_compute_instance" "openvidu_master_node_2" {
node-type = "master"
node-num = "2"
}
depends_on = [google_compute_instance.openvidu_master_node_1]
}
# Master Node 3
@ -458,14 +408,12 @@ resource "google_compute_instance" "openvidu_master_node_3" {
network_interface {
network = "default"
network_ip = google_compute_address.master_internal_ip[2].address
access_config {}
}
metadata = {
stackName = var.stackName
masterNodeNum = "3"
masterNodePrivateIPList = local.master_node_private_ip_list
domainName = var.domainName
certificateType = var.certificateType
ownPublicCertificate = var.ownPublicCertificate
@ -491,6 +439,8 @@ resource "google_compute_instance" "openvidu_master_node_3" {
node-type = "master"
node-num = "3"
}
depends_on = [google_compute_instance.openvidu_master_node_2]
}
# Master Node 4
@ -510,14 +460,12 @@ resource "google_compute_instance" "openvidu_master_node_4" {
network_interface {
network = "default"
network_ip = google_compute_address.master_internal_ip[3].address
access_config {}
}
metadata = {
stackName = var.stackName
masterNodeNum = "4"
masterNodePrivateIPList = local.master_node_private_ip_list
domainName = var.domainName
certificateType = var.certificateType
ownPublicCertificate = var.ownPublicCertificate
@ -543,6 +491,8 @@ resource "google_compute_instance" "openvidu_master_node_4" {
node-type = "master"
node-num = "4"
}
depends_on = [google_compute_instance.openvidu_master_node_3]
}
# ------------------------- scale in resources -------------------------
@ -850,12 +800,6 @@ resource "google_cloudfunctions2_function" "scalein_function" {
}
service_account_email = google_service_account.service_account.email
}
depends_on = [
google_project_service.cloudfunctions_api,
google_project_service.cloudbuild_api,
google_project_service.run_api
]
}
# Cloud Scheduler to trigger the function every 5 minutes
@ -885,8 +829,6 @@ resource "google_cloud_scheduler_job" "scale_scheduler" {
service_account_email = google_service_account.service_account.email
}
}
depends_on = [google_project_service.cloudscheduler_api]
}
locals {
@ -916,7 +858,7 @@ resource "google_compute_instance_template" "media_node_template" {
metadata = {
stackName = var.stackName
masterNodePrivateIPList = local.master_node_private_ip_list
masterNodePrivateIPList = "${google_compute_instance.openvidu_master_node_1.network_interface[0].network_ip},${google_compute_instance.openvidu_master_node_2.network_interface[0].network_ip},${google_compute_instance.openvidu_master_node_3.network_interface[0].network_ip},${google_compute_instance.openvidu_master_node_4.network_interface[0].network_ip}"
bucketAppDataName = local.isEmptyAppData ? google_storage_bucket.appdata_bucket[0].name : var.GCSAppDataBucketName
bucketClusterDataName = local.isEmptyClusterData ? google_storage_bucket.clusterdata_bucket[0].name : var.GCSClusterDataBucketName
region = var.region
@ -938,6 +880,13 @@ resource "google_compute_instance_template" "media_node_template" {
lifecycle {
create_before_destroy = true
}
depends_on = [
google_compute_instance.openvidu_master_node_1,
google_compute_instance.openvidu_master_node_2,
google_compute_instance.openvidu_master_node_3,
google_compute_instance.openvidu_master_node_4
]
}
# Managed Instance Group for Media Nodes
@ -955,6 +904,13 @@ resource "google_compute_region_instance_group_manager" "media_node_group" {
name = "http"
port = 7880
}
depends_on = [
google_compute_instance.openvidu_master_node_1,
google_compute_instance.openvidu_master_node_2,
google_compute_instance.openvidu_master_node_3,
google_compute_instance.openvidu_master_node_4
]
}
# Autoscaler for Media Nodes
@ -986,7 +942,7 @@ locals {
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
@ -1013,7 +969,11 @@ get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; }
# Get master node number from metadata
MASTER_NODE_NUM=$(get_meta "instance/attributes/masterNodeNum")
MASTER_NODE_PRIVATE_IP_LIST=$(get_meta "instance/attributes/masterNodePrivateIPList")
# Get own private IP
PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip")
# Store current private IP
PRIVATE_IP="$(/usr/local/bin/store_secret.sh save MASTER_NODE_$${MASTER_NODE_NUM}_PRIVATE_IP $PRIVATE_IP)"
# Check if secrets have been generated
ALL_SECRETS_GENERATED=$(gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null || echo "false")
@ -1073,19 +1033,33 @@ if [[ $MASTER_NODE_NUM -eq 1 ]] && [[ "$ALL_SECRETS_GENERATED" == "false" ]]; th
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
fi
# Wait for master-1 to finish generating all shared secrets before reading them.
i=0
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null | grep -q "true"; do
i=$((i + 1))
if [ "$i" -ge 360 ]; then
echo "Timed out after 30 minutes waiting for master-1 to finish generating secrets" >&2
exit 1
# Wait for all master nodes to store their private IPs
while true; do
MASTER_NODE_1_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_1_PRIVATE_IP 2>/dev/null || echo "")
MASTER_NODE_2_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_2_PRIVATE_IP 2>/dev/null || echo "")
MASTER_NODE_3_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_3_PRIVATE_IP 2>/dev/null || echo "")
MASTER_NODE_4_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_4_PRIVATE_IP 2>/dev/null || echo "")
# Check if all master nodes have stored their private IPs
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "" ]] &&
[[ "$MASTER_NODE_2_PRIVATE_IP" != "" ]] &&
[[ "$MASTER_NODE_3_PRIVATE_IP" != "" ]] &&
[[ "$MASTER_NODE_4_PRIVATE_IP" != "" ]]; then
break
fi
echo "Waiting for master-1 to finish generating secrets..."
sleep 5
done
# Wait for master-1 to finish generating all shared secrets before reading them
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null | grep -q "true"; do echo "Waiting for master-1 to finish generating secrets..."; sleep 5; done
# Fetch all values from Secret Manager
MASTER_NODE_1_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_1_PRIVATE_IP)
MASTER_NODE_2_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_2_PRIVATE_IP)
MASTER_NODE_3_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_3_PRIVATE_IP)
MASTER_NODE_4_PRIVATE_IP=$(gcloud secrets versions access latest --secret=MASTER_NODE_4_PRIVATE_IP)
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME)
OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE)
OPENVIDU_RTC_ENGINE=$(gcloud secrets versions access latest --secret=OPENVIDU_RTC_ENGINE)
@ -1108,14 +1082,8 @@ LIVEKIT_API_KEY=$(gcloud secrets versions access latest --secret=LIVEKIT_API_KEY
LIVEKIT_API_SECRET=$(gcloud secrets versions access latest --secret=LIVEKIT_API_SECRET)
ENABLED_MODULES=$(gcloud secrets versions access latest --secret=ENABLED_MODULES)
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Build install command
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh)"
# Common arguments
COMMON_ARGS=(
@ -1466,17 +1434,11 @@ EOF
check_app_ready_script = <<-EOF
#!/bin/bash
i=0
while true; do
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy 2>/dev/null | head -n1 | awk '{print $2}')
if [ "$HTTP_STATUS" == "200" ]; then
break
fi
i=$((i + 1))
if [ "$i" -ge 240 ]; then
echo "Timed out after 20 minutes waiting for OpenVidu to become ready" >&2
exit 1
fi
sleep 5
done
EOF
@ -1551,6 +1513,8 @@ CONFIG_S3_EOF
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
@ -1614,14 +1578,8 @@ MASTER_NODE_PRIVATE_IP_LIST=$(get_meta "instance/attributes/masterNodePrivateIPL
STACK_NAME=$(get_meta "instance/attributes/stackName")
PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip")
# Wait for master nodes to be ready by checking secrets.
i=0
# Wait for master nodes to be ready by checking secrets
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null | grep -q "true"; do
i=$((i + 1))
if [ "$i" -ge 180 ]; then
echo "Timed out after 30 minutes waiting for master nodes to initialize secrets" >&2
exit 1
fi
echo "Waiting for master nodes to initialize secrets..."
sleep 10
done
@ -1639,14 +1597,8 @@ if [[ "$OPENVIDU_VERSION" == "none" ]]; then
exit 1
fi
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALLER_SCRIPT="/tmp/install_ov_media_node.sh"
curl -fsSL --retry 8 --retry-all-errors --retry-delay 5 -o "$INSTALLER_SCRIPT" "http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh"
if [ ! -s "$INSTALLER_SCRIPT" ]; then
echo "Downloaded OpenVidu media node installer is empty or missing" >&2
exit 1
fi
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
# Build install command for media node
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)"
# Media node arguments
COMMON_ARGS=(
@ -1746,55 +1698,51 @@ EOF
#!/bin/bash -x
set -eu -o pipefail
# Check if installation already completed
if [ -f /usr/local/bin/openvidu_install_counter.txt ]; then
# Launch on reboot
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
else
# Create scripts
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
# Create scripts
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
${local.install_script_media}
INSTALL_EOF
chmod +x /usr/local/bin/install.sh
chmod +x /usr/local/bin/install.sh
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
${local.graceful_shutdown_script}
GRACEFUL_SHUTDOWN_EOF
chmod +x /usr/local/bin/graceful_shutdown.sh
chmod +x /usr/local/bin/graceful_shutdown.sh
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0
# Install google cli
if ! command -v gcloud >/dev/null 2>&1; then
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
fi
fi
# Authenticate with gcloud using instance service account
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
# Authenticate with gcloud using instance service account
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
export HOME="/root"
export HOME="/root"
# Install OpenVidu Media Node
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
# Install OpenVidu Media Node
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
# Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
# Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
# Add cron job to check if instance is abandoned every minute
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
# Add cron job to check if instance is abandoned every minute
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
${local.crontab_job_media}
CHECK_ABANDONED_EOF
chmod +x /usr/local/bin/check_abandoned.sh
chmod +x /usr/local/bin/check_abandoned.sh
echo "*/1 * * * * /usr/local/bin/check_abandoned.sh > /var/log/openvidu-abandoned-check.log 2>&1" | crontab -
fi
echo "*/1 * * * * /usr/local/bin/check_abandoned.sh > /var/log/openvidu-abandoned-check.log 2>&1" | crontab -
EOF
}

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -1,6 +1,6 @@
services:
caddy-proxy:
image: docker.io/openvidu/openvidu-caddy-local:main
image: docker.io/openvidu/openvidu-caddy-local:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -88,7 +88,7 @@ services:
condition: service_completed_successfully
dashboard:
image: docker.io/openvidu/openvidu-dashboard:main
image: docker.io/openvidu/openvidu-dashboard:3.8.0
restart: unless-stopped
volumes:
- /etc/localtime:/etc/localtime:ro
@ -102,7 +102,7 @@ services:
condition: service_completed_successfully
openvidu:
image: docker.io/openvidu/openvidu-server-pro:main
image: docker.io/openvidu/openvidu-server-pro:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -125,7 +125,7 @@ services:
condition: service_completed_successfully
ingress:
image: docker.io/openvidu/ingress:main
image: docker.io/openvidu/ingress:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -143,7 +143,7 @@ services:
condition: service_completed_successfully
egress:
image: docker.io/openvidu/egress:main
image: docker.io/openvidu/egress:3.8.0
restart: unless-stopped
extra_hosts:
- host.docker.internal:host-gateway
@ -157,7 +157,7 @@ services:
setup:
condition: service_completed_successfully
operator:
image: docker.io/openvidu/openvidu-operator:main
image: docker.io/openvidu/openvidu-operator:3.8.0
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@ -181,7 +181,7 @@ services:
openvidu-meet:
image: docker.io/openvidu/openvidu-meet:main
image: docker.io/openvidu/openvidu-meet:3.8.0
restart: on-failure
extra_hosts:
- host.docker.internal:host-gateway
@ -221,7 +221,7 @@ services:
condition: service_completed_successfully
openvidu-v2compatibility:
image: docker.io/openvidu/openvidu-v2compatibility:main
image: docker.io/openvidu/openvidu-v2compatibility:3.8.0
restart: unless-stopped
entrypoint: /bin/sh /scripts/entrypoint_openvidu_v2_compat.sh
extra_hosts:
@ -258,7 +258,7 @@ services:
condition: service_completed_successfully
openvidu-meet-init:
image: docker.io/openvidu/openvidu-operator:main
image: docker.io/openvidu/openvidu-operator:3.8.0
restart: on-failure
environment:
- MODE=local-ready-check

View File

@ -782,7 +782,7 @@ Resources:
'/usr/local/bin/install.sh':
content: !Sub |
#!/bin/bash -x
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3

View File

@ -176,7 +176,7 @@ var stringInterpolationParams = {
var installScriptTemplate = '''
#!/bin/bash -x
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

File diff suppressed because one or more lines are too long

View File

@ -148,7 +148,7 @@ locals {
#!/bin/bash -x
set -e
OPENVIDU_VERSION=main
OPENVIDU_VERSION=3.8.0
DOMAIN=
YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

View File

@ -3,7 +3,7 @@
set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"
export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -5,7 +5,7 @@ set -eu
export INSTALL_PREFIX="${INSTALL_PREFIX:-/opt/openvidu}"
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}"
export UPDATE_BASE_URL="${UPDATE_BASE_URL:-http://get.openvidu.io/update}"
export UPDATER_IMAGE="${UPDATER_IMAGE:-docker.io/openvidu/openvidu-updater:${OPENVIDU_VERSION}}"
export MINIO_SERVER_IMAGE="${MINIO_SERVER_IMAGE:-docker.io/openvidu/minio:RELEASE.2026-06-04T00-54-11Z-r0}"

View File

@ -54,7 +54,7 @@ public class OpenViduTestE2e {
private final static WaitStrategy waitBrowser = Wait.forLogMessage("^.*Started Selenium Standalone.*$", 1);
protected static String RTSP_SERVER_IMAGE = "bluenviron/mediamtx:1.19.2-ffmpeg";
protected static String RTSP_SERVER_IMAGE = "bluenviron/mediamtx:1.17.1-ffmpeg";
protected static int RTSP_SRT_PORT = 8554;
// Key is the common name of the video codec. It must match the output log of
@ -286,7 +286,7 @@ public class OpenViduTestE2e {
"MTX_HLS", "no", "MTX_RTSP", "yes", "MTX_WEBRTC", "no", "MTX_SRT", "no", "MTX_RTMP", "no",
"MTX_API", "no"))
.withNetworkMode("host")
.waitingFor(Wait.forLogMessage("^.*\\[RTSP\\] started with listeners on :" + rtspPort + ".*$", 1));
.waitingFor(Wait.forLogMessage("^.*\\[RTSP\\] listener opened on :" + rtspPort + ".*$", 1));
rtspServerContainer.start();
containers.add(rtspServerContainer);
@ -296,12 +296,7 @@ public class OpenViduTestE2e {
String codecs = getCodecs(videoCodec, audioCodec);
String rtspServerIp = "host.docker.internal";
// -re paces the publisher at native frame rate, like the live camera it
// simulates. Without it, cheap codecs (MJPEG, MPEG-4) encode the whole file
// many times faster than realtime on fast runners: mediamtx discards most
// frames and, when ffmpeg finishes early and exits, terminates the reader
// session, killing the ingress mid-test.
String ffmpegCommand = "ffmpeg -re -i " + fileUrl + " " + codecs + " "
String ffmpegCommand = "ffmpeg -i " + fileUrl + " " + codecs + " "
+ " -async 50 -strict -2 -f rtsp -rtsp_transport tcp rtsp://" + rtspServerIp + ":" + rtspPort + "/"
+ RTSP_PATH;
@ -342,9 +337,8 @@ public class OpenViduTestE2e {
String fileUrl = getFileUrl(videoCodec != null, audioCodec != null, true);
String codecs = getCodecs(videoCodec, audioCodec);
// -re: see startRtspServer.
String ffmpegCommand = "ffmpeg -re -i " + fileUrl + " " + codecs + " -strict -2 -f mpegts srt://:"
+ RTSP_SRT_PORT + "?mode=listener";
String ffmpegCommand = "ffmpeg -i " + fileUrl + " " + codecs + " -strict -2 -f mpegts srt://:" + RTSP_SRT_PORT
+ "?mode=listener";
// Clean adjacent white spaces or the ffmpeg command will fail
ffmpegCommand = ffmpegCommand.trim().replaceAll(" +", " ");

View File

@ -2151,8 +2151,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
participantNameInput.sendKeys("CHROME_USER");
this.forceCodec(chromeUser, 0, codec);
chromeUser.getDriver().findElement(By.className("connect-btn")).click();
chromeUser.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1, 120,
true);
chromeUser.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1);
// Check publisher's codec
WebElement publisherVideo = chromeUser.getDriver()
.findElement(By.cssSelector("#openvidu-instance-0 video.local"));
@ -2262,8 +2261,7 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
this.forceCodec(chromeUser, 0, publisherCodec);
this.setPublisherSimulcastLayersAndResolution(chromeUser, 0, "h360", 1920, 1080);
chromeUser.getDriver().findElement(By.className("connect-btn")).click();
chromeUser.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1, 120,
true);
chromeUser.getEventManager().waitUntilEventReaches("localTrackSubscribed", "ParticipantEvent", 1);
// Measure when the PUBLISHER itself starts actively sending the 1920 (rid
// "f") layer (active==true && frameWidth==1920). Compared with the
@ -4016,6 +4014,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@Test
@DisplayName("RTSP ingress AAC")
@Disabled // Audio ingress is flaky, only works if the ingress inmediately connects to the
// RTSP server
void rtspIngressAACTest() throws Exception {
log.info("RTSP ingress AAC");
String rtspUri = startRtspServer(null, "AAC");
@ -4024,6 +4024,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@Test
@DisplayName("RTSP ingress MP3")
@Disabled // Audio ingress is flaky, only works if the ingress inmediately connects to the
// RTSP server
void rtspIngressMP3Test() throws Exception {
log.info("RTSP ingress MP3");
String rtspUri = startRtspServer(null, "MP3");
@ -4032,6 +4034,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@Test
@DisplayName("RTSP ingress OPUS")
@Disabled // Audio ingress is flaky, only works if the ingress inmediately connects to the
// RTSP server
void rtspIngressOPUSTest() throws Exception {
log.info("RTSP ingress OPUS");
String rtspUri = startRtspServer(null, "OPUS");
@ -4040,6 +4044,8 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest {
@Test
@DisplayName("RTSP ingress G711")
@Disabled // Audio ingress is flaky, only works if the ingress inmediately connects to the
// RTSP server
void rtspIngressG711Test() throws Exception {
log.info("RTSP ingress G711");
String rtspUri = startRtspServer(null, "G711");

View File

@ -81,7 +81,6 @@
"test": {
"builder": "@angular/build:karma",
"options": {
"externalDependencies": ["node:crypto"],
"polyfills": [
"zone.js",
"zone.js/testing",

File diff suppressed because it is too large Load Diff

View File

@ -18,22 +18,22 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^22.0.6",
"@angular/cdk": "^22.0.4",
"@angular/common": "^22.0.6",
"@angular/compiler": "^22.0.6",
"@angular/core": "^22.0.6",
"@angular/forms": "^22.0.6",
"@angular/material": "^22.0.4",
"@angular/platform-browser": "^22.0.6",
"@angular/router": "^22.0.6",
"@livekit/protocol": "^1.49.0",
"@angular/animations": "^21.2.14",
"@angular/cdk": "^21.2.12",
"@angular/common": "^21.2.14",
"@angular/compiler": "^21.2.14",
"@angular/core": "^21.2.14",
"@angular/forms": "^21.2.14",
"@angular/material": "^21.2.12",
"@angular/platform-browser": "^21.2.14",
"@angular/router": "^21.2.14",
"@livekit/protocol": "^1.46.0",
"@livekit/track-processors": "^0.7.2",
"buffer": "^6.0.3",
"crypto-browserify": "^3.12.1",
"json-stringify-safe": "5.0.1",
"livekit-client": "2.20.1",
"livekit-server-sdk": "^2.17.0",
"livekit-client": "2.19.0",
"livekit-server-sdk": "^2.15.3",
"rxjs": "~7.8.2",
"stream-browserify": "^3.0.0",
"tslib": "^2.8.1",
@ -41,22 +41,21 @@
"zone.js": "~0.16.2"
},
"devDependencies": {
"@angular/build": "^22.0.6",
"@angular/cli": "~22.0.6",
"@angular/compiler-cli": "^22.0.6",
"@types/dom-mediacapture-transform": "^0.1.12",
"@angular/build": "^21.2.12",
"@angular/cli": "~21.2.12",
"@angular/compiler-cli": "^21.2.14",
"@types/dom-mediacapture-transform": "^0.1.11",
"@types/events": "^3.0.3",
"@types/jasmine": "~6.0.0",
"@types/json-stringify-safe": "^5.0.3",
"@types/node": "^26.1.1",
"@types/node": "^25.9.1",
"http-server": "^14.1.1",
"istanbul-lib-instrument": "^6.0.3",
"jasmine-core": "~6.3.0",
"jasmine-core": "~6.2.0",
"karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.2.0",
"typescript": "~6.0.3"
"typescript": "~5.9.3"
}
}

View File

@ -1,18 +1,29 @@
import { TestBed } from '@angular/core/testing';
import { provideRouter } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(() =>
TestBed.configureTestingModule({
imports: [AppComponent],
providers: [provideRouter([])],
})
);
beforeEach(() => TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [AppComponent]
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'openvidu-testapp-livekit'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('openvidu-testapp-livekit');
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('openvidu-testapp-livekit app is running!');
});
});

View File

@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Router, RouterOutlet, RouterLink } from '@angular/router';
import { LogLevel, setLogLevel } from 'livekit-client';
@ -13,7 +13,6 @@ import { MatButtonModule } from '@angular/material/button';
selector: 'app-root',
templateUrl: './app.component.html',
styleUrl: './app.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, RouterOutlet, RouterLink, MatSidenavModule, MatToolbarModule, MatFormFieldModule, MatInputModule, MatButtonModule],
})
export class AppComponent {

View File

@ -1,5 +1,4 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TrackPublication } from 'livekit-client';
import { AudioTrackComponent } from './audio-track.component';
@ -9,14 +8,10 @@ describe('AudioTrackComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AudioTrackComponent]
declarations: [AudioTrackComponent]
});
fixture = TestBed.createComponent(AudioTrackComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput('trackPublication', {
source: 'microphone',
} as unknown as TrackPublication);
fixture.componentRef.setInput('index', 0);
fixture.detectChanges();
});

View File

@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component } from '@angular/core';
import { NgClass } from '@angular/common';
import { LocalTrack } from 'livekit-client';
import { MatIconModule } from '@angular/material/icon';
@ -9,7 +9,6 @@ import { TrackComponent } from '../track/track.component';
selector: 'app-audio-track',
templateUrl: './audio-track.component.html',
styleUrl: './audio-track.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [NgClass, MatIconModule, MatTooltipModule],
})
export class AudioTrackComponent extends TrackComponent {

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
import { Component, EventEmitter, Input, Output, ChangeDetectorRef } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import {
@ -15,7 +15,6 @@ import {
selector: 'app-data-track',
templateUrl: './data-track.component.html',
styleUrl: './data-track.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [MatIconModule, MatTooltipModule],
})
export class DataTrackComponent {

View File

@ -1,4 +1,4 @@
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, inject } from '@angular/core';
import { SlicePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
@ -81,7 +81,6 @@ export interface EventGroup {
'.group-label:first-child { margin-top: 0; }',
'.extra-toggle { margin-top: 5px; }'
],
changeDetection: ChangeDetectionStrategy.Eager,
imports: [SlicePipe, FormsModule, MatDialogModule, MatSlideToggleModule, MatDividerModule, MatButtonModule],
})
export class EventsDialogComponent {

View File

@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { InfoDialogComponent } from './info-dialog.component';
describe('InfoDialogComponent', () => {
@ -9,8 +8,7 @@ describe('InfoDialogComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [InfoDialogComponent],
providers: [{ provide: MAT_DIALOG_DATA, useValue: {} }]
declarations: [InfoDialogComponent]
});
fixture = TestBed.createComponent(InfoDialogComponent);
component = fixture.componentInstance;

View File

@ -1,4 +1,4 @@
import { Component, NgZone, OnDestroy, ViewChild, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, NgZone, OnDestroy, ViewChild, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
import { take } from 'rxjs/operators';
@ -11,7 +11,6 @@ import { MatButtonModule } from '@angular/material/button';
selector: 'app-info-dialog',
templateUrl: './info-dialog.component.html',
styleUrl: './info-dialog.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, CdkTextareaAutosize, MatDialogModule, MatFormFieldModule, MatInputModule, MatButtonModule],
})
export class InfoDialogComponent implements OnDestroy {

View File

@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { OptionsDialogComponent } from './options-dialog.component';
describe('OptionsDialogComponent', () => {
@ -9,11 +8,7 @@ describe('OptionsDialogComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [OptionsDialogComponent],
providers: [
{ provide: MatDialogRef, useValue: {} },
{ provide: MAT_DIALOG_DATA, useValue: {} },
]
declarations: [OptionsDialogComponent]
});
fixture = TestBed.createComponent(OptionsDialogComponent);
component = fixture.componentInstance;

View File

@ -1,4 +1,4 @@
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, inject } from '@angular/core';
import { NgClass, UpperCasePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
@ -27,7 +27,6 @@ import {
selector: 'app-options-dialog',
templateUrl: './options-dialog.component.html',
styleUrl: './options-dialog.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [NgClass, UpperCasePipe, FormsModule, MatDialogModule, MatRadioModule, MatDividerModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, MatSelectModule, MatButtonModule, VideoResolutionComponent],
})
export class OptionsDialogComponent {

View File

@ -8,7 +8,7 @@ describe('VideoResolutionComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [VideoResolutionComponent]
declarations: [VideoResolutionComponent]
});
fixture = TestBed.createComponent(VideoResolutionComponent);
component = fixture.componentInstance;

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@ -7,7 +7,6 @@ import { MatInputModule } from '@angular/material/input';
selector: 'app-video-resolution',
templateUrl: './video-resolution.component.html',
styleUrl: './video-resolution.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, MatFormFieldModule, MatInputModule],
})
export class VideoResolutionComponent {

View File

@ -5,7 +5,6 @@ import {
OnDestroy,
ViewChild,
inject,
ChangeDetectionStrategy
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
@ -29,7 +28,6 @@ import {
selector: 'app-processor-dialog',
templateUrl: './processor-dialog.component.html',
styleUrl: './processor-dialog.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, MatDialogModule, MatSlideToggleModule, MatFormFieldModule, MatSelectModule, MatSliderModule, MatButtonModule, MatTooltipModule, MatDividerModule, MatIconModule, MatInputModule],
})
export class ProcessorDialogComponent implements AfterViewInit, OnDestroy {

View File

@ -1,6 +1,6 @@
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, inject } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatChipInputEvent, MatChipsModule } from '@angular/material/chips';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
@ -40,7 +40,6 @@ import {
selector: 'app-room-api-dialog',
templateUrl: './room-api-dialog.component.html',
styleUrl: './room-api-dialog.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatCheckboxModule, MatDividerModule, MatSelectModule, MatChipsModule, MatIconModule, MatTooltipModule, MatSlideToggleModule],
})
export class RoomApiDialogComponent {

View File

@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RoomConf } from '../test-sessions/test-sessions.component';
import { OpenviduInstanceComponent } from './openvidu-instance.component';
describe('OpenviduInstanceComponent', () => {
@ -9,18 +8,10 @@ describe('OpenviduInstanceComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [OpenviduInstanceComponent]
declarations: [OpenviduInstanceComponent]
});
fixture = TestBed.createComponent(OpenviduInstanceComponent);
component = fixture.componentInstance;
const roomConf: RoomConf = {
uid: 0,
subscriber: true,
publisher: true,
startSession: false,
};
fixture.componentRef.setInput('roomConf', roomConf);
fixture.componentRef.setInput('index', 0);
fixture.detectChanges();
});

View File

@ -5,7 +5,6 @@ import {
Input,
Output,
inject,
ChangeDetectionStrategy
} from '@angular/core';
import { NgClass, KeyValuePipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
@ -78,7 +77,6 @@ import {
selector: 'app-openvidu-instance',
templateUrl: './openvidu-instance.component.html',
styleUrl: './openvidu-instance.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [NgClass, KeyValuePipe, FormsModule, MatCardModule, MatFormFieldModule, MatInputModule, MatIconModule, MatButtonModule, MatCheckboxModule, MatExpansionModule, ParticipantComponent],
})
export class OpenviduInstanceComponent {

View File

@ -1,5 +1,4 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Room } from 'livekit-client';
import { ParticipantComponent } from './participant.component';
@ -9,14 +8,10 @@ describe('ParticipantComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ParticipantComponent]
declarations: [ParticipantComponent]
});
fixture = TestBed.createComponent(ParticipantComponent);
component = fixture.componentInstance;
const room = new Room();
fixture.componentRef.setInput('room', room);
fixture.componentRef.setInput('participant', room.localParticipant);
fixture.componentRef.setInput('index', 0);
fixture.detectChanges();
});

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output, ChangeDetectorRef, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, EventEmitter, Input, Output, ChangeDetectorRef, inject } from '@angular/core';
import { NgClass, KeyValuePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
@ -47,7 +47,6 @@ import {
selector: 'app-participant',
templateUrl: './participant.component.html',
styleUrl: './participant.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [NgClass, KeyValuePipe, MatIconModule, MatTooltipModule, MatExpansionModule, VideoTrackComponent, AudioTrackComponent, DataTrackComponent],
})
export class ParticipantComponent {

View File

@ -8,7 +8,7 @@ describe('TestScenariosComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TestScenariosComponent]
declarations: [ TestScenariosComponent ]
})
.compileComponents();
}));

View File

@ -1,4 +1,4 @@
import { Component, OnDestroy, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Subscription } from 'rxjs';
@ -49,7 +49,6 @@ export interface User {
selector: 'app-test-scenarios',
templateUrl: './test-scenarios.component.html',
styleUrl: './test-scenarios.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatIconModule, UsersTableComponent],
})
export class TestScenariosComponent implements OnInit, OnDestroy {

View File

@ -8,7 +8,7 @@ describe('TestSessionsComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestSessionsComponent]
declarations: [TestSessionsComponent]
});
fixture = TestBed.createComponent(TestSessionsComponent);
component = fixture.componentInstance;

View File

@ -1,5 +1,5 @@
import { animate, style, transition, trigger } from '@angular/animations';
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Subscription } from 'rxjs';
import { TestFeedService } from 'src/app/services/test-feed.service';
@ -30,7 +30,6 @@ export interface RoomConf {
]),
]),
],
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, MatButtonModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, OpenviduInstanceComponent],
})
export class TestSessionsComponent {

View File

@ -5,7 +5,6 @@ import {
Input,
Output,
ViewChild,
ChangeDetectionStrategy
} from '@angular/core';
import {
TrackPublication,
@ -29,7 +28,6 @@ import {
@Component({
selector: 'app-track',
changeDetection: ChangeDetectionStrategy.Eager,
template: '',
})
export class TrackComponent {

View File

@ -4,7 +4,6 @@ import {
AfterViewInit,
ViewChild,
ElementRef,
ChangeDetectionStrategy
} from '@angular/core';
import { AudioTrack, VideoTrack } from 'livekit-client';
@ -13,7 +12,6 @@ import { AudioTrack, VideoTrack } from 'livekit-client';
template: `
<video #mediaElement [id]="videoId" autoplay playsinline></video>
`,
changeDetection: ChangeDetectionStrategy.Eager,
styles: [
`
video {

View File

@ -1,4 +1,4 @@
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { Component, Input } from '@angular/core';
import { AudioTrack, VideoTrack } from 'livekit-client';
import { MatBadgeModule } from '@angular/material/badge';
import { CallbackPipe } from 'src/app/pipes/callback.pipe';
@ -9,7 +9,6 @@ import { User } from '../test-scenarios/test-scenarios.component';
selector: 'app-users-table',
styleUrl: 'users-table.component.css',
templateUrl: 'users-table.component.html',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [MatBadgeModule, CallbackPipe, TableVideoComponent],
})
export class UsersTableComponent {

View File

@ -8,7 +8,7 @@ describe('VideoTrackComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [VideoTrackComponent]
declarations: [VideoTrackComponent]
});
fixture = TestBed.createComponent(VideoTrackComponent);
component = fixture.componentInstance;

View File

@ -1,4 +1,4 @@
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { Component, inject } from '@angular/core';
import { NgClass } from '@angular/common';
import {
LocalTrack,
@ -20,7 +20,6 @@ import { MatSelectModule } from '@angular/material/select';
selector: 'app-video-track',
templateUrl: './video-track.component.html',
styleUrl: './video-track.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [NgClass, MatIconModule, MatTooltipModule, MatFormFieldModule, MatSelectModule],
})
export class VideoTrackComponent extends TrackComponent {

View File

@ -2,7 +2,7 @@ import { provideZoneChangeDetection } from "@angular/core";
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter, withHashLocation } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { provideHttpClient, withXhr } from '@angular/common/http';
import { provideHttpClient } from '@angular/common/http';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
import { AppComponent } from './app/app.component';
@ -12,7 +12,7 @@ bootstrapApplication(AppComponent, {
providers: [
provideZoneChangeDetection(),provideRouter(routes, withHashLocation()),
provideAnimationsAsync(),
provideHttpClient(withXhr()),
provideHttpClient(),
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: { appearance: 'outline', subscriptSizing: 'dynamic' },

View File

@ -14,13 +14,5 @@
],
"include": [
"src/**/*.d.ts"
],
"angularCompilerOptions": {
"extendedDiagnostics": {
"checks": {
"nullishCoalescingNotNullable": "suppress",
"optionalChainNotNullable": "suppress"
}
}
}
]
}

View File

@ -2,6 +2,7 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
@ -21,8 +22,6 @@
"strictPropertyInitialization": false,
"skipLibCheck": true,
"paths": {
"src/*": ["./src/*"],
"node_modules/*": ["./node_modules/*"],
"crypto": ["./node_modules/crypto-browserify"],
"node:crypto": ["./node_modules/crypto-browserify"],
"stream": ["./node_modules/stream-browserify"],

View File

@ -10,13 +10,5 @@
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
],
"angularCompilerOptions": {
"extendedDiagnostics": {
"checks": {
"nullishCoalescingNotNullable": "suppress",
"optionalChainNotNullable": "suppress"
}
}
}
]
}