Compare commits

...

23 Commits

Author SHA1 Message Date
Piwccle 3ff7e14baf Merge branch 'elastic-hardening': elastic deployment hardening (AWS/Azure/GCP)
Also brings the Azure HA user-assigned identity optimization and the
GCP elastic MinIO port closure. DOC-CHANGES-*.md helper files are
intentionally left out of master.
2026-07-29 12:08:30 +02:00
Piwccle fea2a9e9e8 openvidu-deployment: Azure HA — user-assigned identities so the Key Vault deploys first
Two user-assigned managed identities (master/media) replace the per-VM
system-assigned ones: the Key Vault's access policies now reference
principalIds known at deployment start, so the KV no longer depends on
the 4 VMs + VMSS existing and is created immediately in parallel
(removes the boot-time KV wait and its propagation window). Master
policy keeps get/set/list, media keeps get, deploymentUser untouched.
Role assignments consolidated 5 -> 2 and repointed to the identities
(principalType ServicePrincipal). All 10 az login calls use
--client-id; azure-cli pinned 2.87.0 -> 2.88.0 (verified available in
the Microsoft apt repo for noble). No public parameters/outputs change.

Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m9s, wait-ready 7m48s, destroy 8m20s.
2026-07-29 11:56:08 +02:00
Piwccle 85d7762887 openvidu-deployment: close unused MinIO port 9000 on GCP elastic master firewall
MinIO is not deployed on cloud deployments (native object storage is
used) and the docs list no use for 9000; it was exposed to 0.0.0.0/0.
2026-07-29 11:23:12 +02:00
Piwccle 03f6a8b810 openvidu-deployment: harden GCP elastic deployment
Coupled reboot fixes (premature install marker removed together with the
broken '| crontab' pipe that aborted the startup script), depends_on to
enabled APIs on 13 resources plus the new health check, robust installer
fetch, media reboot guard, bounded secret/health waits, media MIG
auto-healing (TCP 7880, 600s initial delay - conservative), surgical
set +x around secret-handling blocks, apt no-ops dropped.

Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 1m47s, wait-ready 4m0s, destroy 3m19s.
2026-07-28 20:16:04 +02:00
Piwccle 5f5503a106 openvidu-deployment: harden Azure elastic deployment
Bounded check_app_ready then FINISH-MASTER-NODE published only after the
master health gate (was: FINISH before a blind sleep 150), media double
gate (install on ALL-SECRETS-GENERATED, start on FINISH) overlapping the
heavy media install with the master's (~30-40% faster cluster-ready),
robust installer fetch, config_blobStorage RBAC-propagation retry,
Key Vault wait 100->300s, set -e on master install, terminal media
failure path, secret-leaking bash -x shebangs removed, apt no-ops
dropped. ARM JSON recompiled (bicep 0.45.15, metadata-only noise).

Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 1m40s, wait-ready 4m1s, destroy 7m14s.
2026-07-28 20:16:04 +02:00
Piwccle 20eef140c5 openvidu-deployment: harden AWS elastic deployment
WaitCondition PT10M -> PT20M (signal fires after the full install),
robust installer fetch (curl --retry to file), bounded master readiness
gate with one restart retry feeding cfn-signal, media waits for secrets
with content validation (read-until-valid, eventually-consistent reads)
and for a healthy master before starting instead of self-destructing,
idempotent secret generation with post-guard read-back, vestigial IAM
retry loop and duplicated saves removed, ASG DependsOn completed,
DOMAIN_NAME persisted explicitly.

Validated with ov-cloud-tester (sc-deploy-destroy, elastic, dev): PASS.
deploy 6m49s, wait-ready 32s, destroy 3m37s.
2026-07-28 20:16:04 +02:00
Piwccle 73f8b57498 Merge branch 'ha-optimizations': HA deployment time optimizations (AWS/Azure/GCP)
DOC-CHANGES helper files intentionally excluded from the merge (branch-only).
2026-07-28 18:22:25 +02:00
Piwccle 31a8488f23 openvidu-deployment: fix eventually-consistent secret reads in AWS HA
GetSecretValue is eventually consistent: with masters booting in
parallel, the final re-read of the shared JSON could return a stale
pre-generation version (ALL_SECRETS_GENERATED already observed true but
values still 'none'), so the installer aborted with e.g.
'mongo-admin-password: must have at least 10 characters'. The old
serialized boot masked this window (minutes between generation and
re-reads); parallel boot exposed it intermittently (1 of 3 tries).

Fix: read-until-valid — retry the re-read (5s, 5 min cap) until the same
snapshot has ALL_SECRETS_GENERATED=true AND every always-generated value
!= 'none'; the media gate now validates OPENVIDU_VERSION and
REDIS_PASSWORD content too, not just the flag.

Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev, --tries 3):
3/3 PASS, deploy mean 5m19s (spread 76ms), ready mean 21m24s.
Control run without the fix reproduced the race (1/3 failed).
2026-07-25 00:46:02 +02:00
Piwccle 4d6b7acd7c openvidu-deployment: optimize AWS HA deployment time
Parallel master creation: drop the MasterNodeN -> WaitConditionN-1
DependsOn chain. Each master gets a fixed MASTER_NODE_NUM via UserData
(replaces subnet-matching detection) and publishes its private IP to its
own SSM parameter (atomic per-key writes; the 4 parameters are stack
resources), removing the IP exchange through the shared Secrets Manager
JSON and its lost-update race. after_install.sh is now leader-only, so
the shared JSON keeps a single writer with parallel masters.

Ungate media nodes and the NLB from WaitCondition4: media wait for
secrets + the 4 SSM IPs (bounded polls) before the heavy install, and
for a healthy master (:7880/health/caddy) before starting the service.
Bounded timeout on the previously infinite IP wait, robust installer
fetch (curl --retry to file instead of sh <(curl)), SSM read/write IAM
statements, MEET_INITIAL_API_KEY dead-code fix, SubnetProcessor Lambda
cleanup (unused boto3 client and ec2:DescribeSubnets permission).

Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 5m24s (baseline ~12m), ready 21m14s (wait-ready dominated by
external DNS propagation), destroy 7m39s.
2026-07-24 17:11:58 +02:00
Piwccle b8fdde9751 openvidu-deployment: shorten comments in GCP/Azure HA templates 2026-07-24 17:11:58 +02:00
Piwccle 196b3f767f openvidu-deployment: optimize Azure HA deployment time
Parallel master VM creation (drop chained dependsOn), publish
FINISH-MASTER-NODE from the first healthy master after its health gate
instead of unconditionally from master 4 (removes the SPOF and the fixed
sleep 150), overlap the heavy media node install with the masters (gate 1:
secrets + 4 IPs before installing; gate 2: FINISH before service start,
5s polling instead of 1s), bounded timeouts on every Key Vault poll,
Key Vault/storage wait margins 100s -> 300s, robust installer fetch
(curl --retry to file instead of sh <(curl)), apt cleanup.

Fix createUiDefinition.json: the portal 'Container Name' field emitted
'containerName' but the template parameter is 'appDataContainerName',
so the user-provided value never reached the template.

ARM JSON recompiled with bicep 0.45.15 (committed JSON was 0.44.1;
codegen noise verified to be metadata-only against the pristine bicep).

Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 2m11s, ready 10m21s, destroy 7m14s.
2026-07-24 14:31:46 +02:00
Piwccle 0a587eb33e openvidu-deployment: translate GCP HA DOC-CHANGES to English 2026-07-24 14:13:50 +02:00
Piwccle 1794e7e16b openvidu-deployment: optimize GCP HA deployment time
Parallel master node creation (drop chained depends_on), static internal
IPs for masters passed via metadata (removes the runtime IP exchange
through Secret Manager and its unbounded wait), bounded timeouts on
ALL_SECRETS_GENERATED/check_app_ready polls, robust installer fetch
(curl --retry to file instead of sh <(curl)), media node reboot guard,
apt cleanup and depends_on on enabled APIs.

Validated with ov-cloud-tester (sc-deploy-destroy, ha, dev): PASS.
deploy 1m54s (baseline 3m30s), ready 6m26s, destroy 3m13s.
2026-07-24 13:42:09 +02:00
Pablo Fuente Pérez 5a3347c12a
Merge pull request #905 from OpenVidu/chore/update-openvidu-actions-v1.0.22
chore: update OpenVidu/actions to v1.0.22
2026-07-23 11:06:33 +02:00
github-actions[bot] a3ede6a24f chore: update OpenVidu/actions to v1.0.22 2026-07-23 09:01:09 +00:00
juancarmore 1345528004 components-angular: update generate-docs.sh to use MINOR_VERSION for documentation links 2026-07-17 11:57:32 +02:00
pabloFuente ea335e0715 openvidu-test-e2e: enable all RTSP audio-only tests 2026-07-12 00:46:57 +02:00
pabloFuente 9f68247dc5 openvidu-test-e2e: fix RTSP publication of MJPEG/MPEG-4 codecs with newer bluenviron/mediamtx ffmpeg container 2026-07-12 00:01:47 +02:00
pabloFuente 24151bca0b openvidu-test-e2e: update container bluenviron/mediamtx (for RTSP tests) from 1.17.1-ffmpeg to 1.19.2-ffmpeg 2026-07-11 17:40:58 +02:00
pabloFuente e688e92a34 openvidu-test-e2e: update container bluenviron/mediamtx (for RTSP tests) from 1.17.1-ffmpeg to 1.19.2-ffmpeg 2026-07-11 17:40:35 +02:00
pabloFuente 4651dd1414 openvidu-testapp: upgrade dependencies, including Angular 22 2026-07-11 16:10:20 +02:00
github-actions e831d35da1 openvidu-components-angular: Bumped version to 3.8.0 2026-07-09 12:24:06 +00:00
GitHub Actions de20380d95 Revert "Bump version to 3.8.0"
This reverts commit aa4274776e.
2026-07-09 10:53:05 +00:00
69 changed files with 2671 additions and 2264 deletions

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
{ {
"name": "openvidu-components-testapp", "name": "openvidu-components-testapp",
"version": "3.7.0", "version": "3.8.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "openvidu-components-testapp", "name": "openvidu-components-testapp",
"version": "3.7.0", "version": "3.8.0",
"dependencies": { "dependencies": {
"@angular/animations": "20.3.15", "@angular/animations": "20.3.15",
"@angular/cdk": "20.2.14", "@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", "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" "husky": "cd .. && husky install"
}, },
"version": "3.7.0" "version": "3.8.0"
} }

View File

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

View File

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

View File

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

View File

@ -165,7 +165,7 @@ var stringInterpolationParams = {
var installScriptTemplate = ''' var installScriptTemplate = '''
#!/bin/bash -x #!/bin/bash -x
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout 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 #!/bin/bash -x
set -e set -e
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
YQ_VERSION=v4.53.3 YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

View File

@ -3,7 +3,7 @@
set -eu set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" 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 set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -1427,7 +1427,8 @@ Resources:
Type: 'AWS::CloudFormation::WaitCondition' Type: 'AWS::CloudFormation::WaitCondition'
CreationPolicy: CreationPolicy:
ResourceSignal: ResourceSignal:
Timeout: PT10M # Elastic signals AFTER the full install completes, so it needs a wider window than HA
Timeout: PT20M
Count: '1' Count: '1'
OpenViduMasterNode: OpenViduMasterNode:
@ -1441,7 +1442,7 @@ Resources:
content: !Sub | content: !Sub |
#!/bin/bash #!/bin/bash
set -e set -e
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
YQ_VERSION=v4.53.3 YQ_VERSION=v4.53.3
@ -1473,35 +1474,28 @@ Resources:
else else
DOMAIN=${DomainName} DOMAIN=${DomainName}
fi fi
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 # 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) 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. # Instance role already proven by cfn-init above, so a single read needs no IAM-availability retry
# 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 \ SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \ --region ${AWS::Region} \
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \ --secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text || echo 'none') --query SecretString --output text || echo 'none')
if [[ "$SHARED_SECRET" == "none" ]]; then
if [[ "$SHARED_SECRET" != "none" ]]; then echo "Error: Shared secret not found"
break
fi
RETRY_COUNT=$((RETRY_COUNT+1))
if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then
exit 1 exit 1
fi fi
sleep 6 ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
done
# Generate secrets only once: a re-run must never regenerate passwords already in use by the cluster
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
# Persist the domain so after_install.sh reads it from the secret instead of the installer
/usr/local/bin/store_secret.sh save DOMAIN_NAME "$DOMAIN"
OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
# Store version so media nodes can use it to install the same version
/usr/local/bin/store_secret.sh save OPENVIDU_VERSION "${!OPENVIDU_VERSION}"
# Meet initial admin user and password # Meet initial admin user and password
MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")" MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
@ -1517,8 +1511,6 @@ Resources:
fi fi
# Store usernames and generate random passwords # 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)" 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_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)" MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
@ -1533,9 +1525,59 @@ Resources:
LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)" 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")" 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")" ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
fi
# Base command # Source every installer value from the secret so a re-run (guard skipped) still has them
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)" # 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"
# Common arguments # Common arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -1858,20 +1900,6 @@ Resources:
mode: "000755" mode: "000755"
owner: "root" owner: "root"
group: "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': '/usr/local/bin/restart.sh':
content: | content: |
#!/bin/bash #!/bin/bash
@ -1950,9 +1978,30 @@ Resources:
# Launch on reboot # Launch on reboot
echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab echo "@reboot /usr/local/bin/restart.sh &> /var/log/openvidu-restart.log" | crontab
# Wait for the app # Local readiness gate: wait up to 300s for Caddy health, restart once if it does not converge
sleep 20 OPENVIDU_READY=false
/usr/local/bin/check_app_ready.sh 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" ]
# sending the finish call # sending the finish call
cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}
@ -2022,31 +2071,48 @@ Resources:
sleep 6 sleep 6
done done
# Get current shared secret # Wait until the master node has generated all shared secrets.
DOMAIN=$(echo $SHARED_SECRET | jq -r .DOMAIN_NAME) # Bounded (up to 1800s at 5s), validating content on each snapshot: stale eventually-consistent reads can return pre-generation values
OPENVIDU_PRO_LICENSE=$(echo $SHARED_SECRET | jq -r .OPENVIDU_PRO_LICENSE) SECRETS_WAIT_ATTEMPTS=0
REDIS_PASSWORD=$(echo $SHARED_SECRET | jq -r .REDIS_PASSWORD) SECRETS_WAIT_MAX=360
while true; do
# Get OpenVidu Media Nodes version to deploy if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION') break
fi
if [[ "$OPENVIDU_VERSION" == "none" ]]; then SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
echo "OpenVidu version not found" 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 exit 1
fi 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
ALL_SECRETS_GENERATED=$(echo $SHARED_SECRET | jq -r .ALL_SECRETS_GENERATED) # Read values only after the gate confirms the master node has published them
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
echo "Master node not ready" OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
/usr/local/bin/set_as_unhealthy.sh REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then
echo "OpenVidu version not found"
exit 1 exit 1
fi fi
# Get Master Node private IP # Get Master Node private IP
MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp} MASTER_NODE_IP=${OpenViduMasterNode.PrivateIp}
# Base command # Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)" 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"
# Common arguments # Common arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -2190,6 +2256,22 @@ Resources:
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; } /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 # Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; } systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
@ -2202,6 +2284,8 @@ Resources:
OpenViduMediaNodeASG: OpenViduMediaNodeASG:
DependsOn: DependsOn:
- OpenViduMediaNodeInstanceProfile
- OpenViduMasterNodeInstanceProfile
- StopMediaNodeCloudWatchEventRule - StopMediaNodeCloudWatchEventRule
Type: AWS::AutoScaling::AutoScalingGroup Type: AWS::AutoScaling::AutoScalingGroup
Properties: Properties:

View File

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

File diff suppressed because one or more lines are too long

View File

@ -27,6 +27,8 @@ resource "google_secret_manager_secret" "openvidu_shared_info" {
replication { replication {
auto {} auto {}
} }
depends_on = [google_project_service.secretmanager_api]
} }
# GCS bucket # GCS bucket
@ -36,12 +38,16 @@ resource "google_storage_bucket" "bucket" {
location = var.region location = var.region
force_destroy = true force_destroy = true
uniform_bucket_level_access = true uniform_bucket_level_access = true
depends_on = [google_project_service.storage_api]
} }
# Service account for the instance # Service account for the instance
resource "google_service_account" "service_account" { resource "google_service_account" "service_account" {
account_id = lower("${substr(var.stackName, 0, 12)}-sa") account_id = lower("${substr(var.stackName, 0, 12)}-sa")
display_name = "OpenVidu instance service account" 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 # IAM bindings for the service account so the instance can access Secret Manager and GCS
@ -49,6 +55,8 @@ resource "google_project_iam_member" "iam_project_role" {
project = var.projectId project = var.projectId
role = "roles/owner" role = "roles/owner"
member = "serviceAccount:${google_service_account.service_account.email}" member = "serviceAccount:${google_service_account.service_account.email}"
depends_on = [google_project_service.cloudresourcemanager_api]
} }
resource "google_compute_firewall" "firewall_master" { resource "google_compute_firewall" "firewall_master" {
@ -57,11 +65,13 @@ resource "google_compute_firewall" "firewall_master" {
allow { allow {
protocol = "tcp" protocol = "tcp"
ports = ["22", "80", "443", "1935", "9000"] ports = ["22", "80", "443", "1935"]
} }
source_ranges = ["0.0.0.0/0"] source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-master-node")] target_tags = [lower("${var.stackName}-master-node")]
depends_on = [google_project_service.compute_api]
} }
resource "google_compute_firewall" "firewall_media" { resource "google_compute_firewall" "firewall_media" {
@ -79,6 +89,8 @@ resource "google_compute_firewall" "firewall_media" {
source_ranges = ["0.0.0.0/0"] source_ranges = ["0.0.0.0/0"]
target_tags = [lower("${var.stackName}-media-node")] target_tags = [lower("${var.stackName}-media-node")]
depends_on = [google_project_service.compute_api]
} }
resource "google_compute_firewall" "firewall_media_to_master" { resource "google_compute_firewall" "firewall_media_to_master" {
@ -96,6 +108,8 @@ resource "google_compute_firewall" "firewall_media_to_master" {
target_tags = [ target_tags = [
lower("${var.stackName}-master-node"), lower("${var.stackName}-master-node"),
] ]
depends_on = [google_project_service.compute_api]
} }
resource "google_compute_firewall" "firewall_master_to_media" { resource "google_compute_firewall" "firewall_master_to_media" {
@ -113,6 +127,8 @@ resource "google_compute_firewall" "firewall_master_to_media" {
target_tags = [ target_tags = [
lower("${var.stackName}-media-node") lower("${var.stackName}-media-node")
] ]
depends_on = [google_project_service.compute_api]
} }
# Create Public Ip address (if not provided) # Create Public Ip address (if not provided)
@ -120,6 +136,8 @@ resource "google_compute_address" "public_ip_address" {
count = var.publicIpAddress == "" ? 1 : 0 count = var.publicIpAddress == "" ? 1 : 0
name = lower("${var.stackName}-public-ip") name = lower("${var.stackName}-public-ip")
region = var.region region = var.region
depends_on = [google_project_service.compute_api]
} }
locals { locals {
@ -181,6 +199,9 @@ resource "google_compute_instance" "openvidu_master_node" {
stack = var.stackName stack = var.stackName
node-type = "master" node-type = "master"
} }
# Explicit: transitive coverage via public_ip_address is absent when publicIpAddress is provided
depends_on = [google_project_service.compute_api]
} }
locals { locals {
@ -234,6 +255,24 @@ 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 # Managed Instance Group for Media Nodes
resource "google_compute_region_instance_group_manager" "media_node_group" { resource "google_compute_region_instance_group_manager" "media_node_group" {
name = lower("${var.stackName}-media-node-group") name = lower("${var.stackName}-media-node-group")
@ -250,6 +289,12 @@ resource "google_compute_region_instance_group_manager" "media_node_group" {
port = 7880 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] depends_on = [google_compute_instance.openvidu_master_node]
} }
@ -548,6 +593,8 @@ resource "google_storage_bucket_object" "function_source" {
name = "function-source.zip" name = "function-source.zip"
bucket = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName bucket = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName
source = data.archive_file.function_source.output_path source = data.archive_file.function_source.output_path
depends_on = [google_project_service.storage_api]
} }
resource "google_cloudfunctions2_function" "scalein_function" { resource "google_cloudfunctions2_function" "scalein_function" {
@ -577,6 +624,12 @@ resource "google_cloudfunctions2_function" "scalein_function" {
} }
service_account_email = google_service_account.service_account.email 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 # Cloud Scheduler to trigger the function every 5 minutes
@ -606,6 +659,8 @@ resource "google_cloud_scheduler_job" "scale_scheduler" {
service_account_email = google_service_account.service_account.email service_account_email = google_service_account.service_account.email
} }
} }
depends_on = [google_project_service.cloudscheduler_api]
} }
# ------------------------- local values ------------------------- # ------------------------- local values -------------------------
@ -617,7 +672,7 @@ locals {
#!/bin/bash -x #!/bin/bash -x
set -e set -e
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
YQ_VERSION=v4.53.3 YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
@ -640,8 +695,8 @@ gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
METADATA_URL="http://metadata.google.internal/computeMetadata/v1" METADATA_URL="http://metadata.google.internal/computeMetadata/v1"
get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; }
# Create counter file for tracking script executions # Disable command tracing so secrets are not printed to the serial console
echo 1 > /usr/local/bin/openvidu_install_counter.txt set +x
# Configure domain # Configure domain
if [[ "${var.domainName}" == "" ]]; then if [[ "${var.domainName}" == "" ]]; then
@ -690,8 +745,14 @@ 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")" ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
# Build install command and args # Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)" 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"
# Common arguments # Common arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -778,6 +839,9 @@ SERVICE_ACCOUNT_EMAIL=$(get_meta "instance/service-accounts/default/email")
# Create key for service account # Create key for service account
gcloud iam service-accounts keys create credentials.json --iam-account=$SERVICE_ACCOUNT_EMAIL 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 # Create HMAC key and parse output
HMAC_OUTPUT=$(gcloud storage hmac create $SERVICE_ACCOUNT_EMAIL --format="json") HMAC_OUTPUT=$(gcloud storage hmac create $SERVICE_ACCOUNT_EMAIL --format="json")
EXTERNAL_S3_ACCESS_KEY=$(echo "$HMAC_OUTPUT" | jq -r '.metadata.accessId') EXTERNAL_S3_ACCESS_KEY=$(echo "$HMAC_OUTPUT" | jq -r '.metadata.accessId')
@ -842,6 +906,9 @@ INSTALL_DIR="/opt/openvidu"
CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster" CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster"
MASTER_NODE_CONFIG_DIR="$${INSTALL_DIR}/config/node" 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 # Replace DOMAIN_NAME
export DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME) export DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME)
if [[ -n "$DOMAIN" ]]; then if [[ -n "$DOMAIN" ]]; then
@ -970,7 +1037,7 @@ echo -n "$ENABLED_MODULES" | gcloud secrets versions add ENABLED_MODULES --data-
EOF EOF
get_value_from_config_script = <<-EOF get_value_from_config_script = <<-EOF
#!/bin/bash -x #!/bin/bash
set -e set -e
# Function to get the value of a given key from the environment file # Function to get the value of a given key from the environment file
@ -1039,11 +1106,17 @@ EOF
check_app_ready_script = <<-EOF check_app_ready_script = <<-EOF
#!/bin/bash #!/bin/bash
i=0
while true; do while true; do
HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy | head -n1 | awk '{print $2}') HTTP_STATUS=$(curl -Ik http://localhost:7880/health/caddy 2>/dev/null | head -n1 | awk '{print $2}')
if [ $HTTP_STATUS == 200 ]; then if [ "$HTTP_STATUS" == "200" ]; then
break break
fi 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 sleep 5
done done
EOF EOF
@ -1128,8 +1201,6 @@ CONFIG_S3_EOF
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0 GCLOUD_VERSION=573.0.0
# Install google cli # Install google cli
if ! command -v gcloud >/dev/null 2>&1; then if ! command -v gcloud >/dev/null 2>&1; then
@ -1157,9 +1228,6 @@ CONFIG_S3_EOF
# Update shared secret # Update shared secret
/usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; } /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 # Mark installation as complete
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
fi fi
@ -1196,12 +1264,21 @@ MASTER_NODE_PRIVATE_IP=$(get_meta "instance/attributes/masterNodePrivateIP")
STACK_NAME=$(get_meta "instance/attributes/stackName") STACK_NAME=$(get_meta "instance/attributes/stackName")
PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip") PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip")
# Wait for master node to be ready by checking secrets # Wait for master node to be ready by checking secrets.
while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null; do 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
echo "Waiting for master node to initialize secrets..." echo "Waiting for master node to initialize secrets..."
sleep 10 sleep 10
done done
# Disable command tracing so secrets are not printed to the serial console
set +x
# Get all necessary values from secrets # Get all necessary values from secrets
DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME) DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME)
OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE) OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE)
@ -1215,8 +1292,14 @@ if [[ "$OPENVIDU_VERSION" == "none" ]]; then
exit 1 exit 1
fi fi
# Build install command for media node # Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)" 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"
# Media node arguments # Media node arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -1320,6 +1403,11 @@ EOF
#!/bin/bash -x #!/bin/bash -x
set -eu -o pipefail 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) # install.sh (media node)
cat > /usr/local/bin/install.sh << 'INSTALL_EOF' cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
${local.install_script_media} ${local.install_script_media}
@ -1334,8 +1422,6 @@ 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
apt-get update && apt-get install -y
GCLOUD_VERSION=573.0.0 GCLOUD_VERSION=573.0.0
# Install google cli # Install google cli
if ! command -v gcloud >/dev/null 2>&1; then if ! command -v gcloud >/dev/null 2>&1; then
@ -1367,5 +1453,6 @@ 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 - echo "*/1 * * * * /usr/local/bin/check_abandoned.sh > /var/log/openvidu-abandoned-check.log 2>&1" | crontab -
fi
EOF EOF
} }

View File

@ -3,7 +3,7 @@
set -eu set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" 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 set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" export MINIO_CLIENT_IMAGE="${MINIO_CLIENT_IMAGE:-docker.io/openvidu/minio-client:RELEASE.2026-06-16T16-03-47Z}"

View File

@ -1242,14 +1242,39 @@ Resources:
"GRAFANA_ADMIN_USERNAME": "none", "GRAFANA_ADMIN_USERNAME": "none",
"GRAFANA_ADMIN_PASSWORD": "none", "GRAFANA_ADMIN_PASSWORD": "none",
"ENABLED_MODULES": "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", "OPENVIDU_VERSION": "none",
"ALL_SECRETS_GENERATED": "false" "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: S3AppDataBucketResource:
Type: 'AWS::S3::Bucket' Type: 'AWS::S3::Bucket'
Properties: Properties:
@ -1281,8 +1306,7 @@ Resources:
Condition: CreateClusterDataBucket Condition: CreateClusterDataBucket
# ------------------------- # -------------------------
# Preprocess subnets to allocate Volumes and ENIs across Availability Zones # Normalize the master node subnet list to exactly 4 entries, one per OpenVidu Master Node
# For OpenVidu Master Nodes
# ------------------------- # -------------------------
SubnetProcessorFunction: SubnetProcessorFunction:
Type: AWS::Lambda::Function Type: AWS::Lambda::Function
@ -1293,13 +1317,11 @@ Resources:
Code: Code:
ZipFile: | ZipFile: |
import cfnresponse import cfnresponse
import boto3
def lambda_handler(event, context): def lambda_handler(event, context):
try: try:
# Process event data # Process event data
subnets = event['ResourceProperties']['Subnets'] subnets = event['ResourceProperties']['Subnets']
ec2 = boto3.client('ec2')
# Ensure we have at least four subnets by cycling through the available subnets # 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 subnets = (subnets * 4)[:4] # Repeat the list to have at least 4 elements and then take the first 4
@ -1355,10 +1377,6 @@ Resources:
- logs:CreateLogStream - logs:CreateLogStream
- logs:PutLogEvents - logs:PutLogEvents
Resource: !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/SubnetProcessor-${AWS::Region}-${AWS::StackName}:*' 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: OpenViduMasterNodeRole:
Type: AWS::IAM::Role Type: AWS::IAM::Role
@ -1390,6 +1408,11 @@ Resources:
- secretsmanager:GetSecretValue - secretsmanager:GetSecretValue
- secretsmanager:UpdateSecret - secretsmanager:UpdateSecret
Resource: !Ref OpenViduSharedInfo 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: - Fn::If:
- CreateRecordingsBucket - CreateRecordingsBucket
- Effect: Allow - Effect: Allow
@ -1474,6 +1497,10 @@ Resources:
Action: Action:
- secretsmanager:GetSecretValue - secretsmanager:GetSecretValue
Resource: !Ref OpenViduSharedInfo Resource: !Ref OpenViduSharedInfo
- Effect: Allow
Action:
- ssm:GetParameter
Resource: !Sub arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/openvidu/${AWS::StackName}/*
- Effect: Allow - Effect: Allow
Action: Action:
- autoscaling:SetInstanceHealth - autoscaling:SetInstanceHealth
@ -1526,8 +1553,6 @@ Resources:
OpenViduMediaInstanceProfile: OpenViduMediaInstanceProfile:
Type: AWS::IAM::InstanceProfile Type: AWS::IAM::InstanceProfile
DependsOn:
- MasterNodesWaitCondition4
Properties: Properties:
InstanceProfileName: !Sub OpenViduMediaInstanceProfile-${AWS::Region}-${AWS::StackName} InstanceProfileName: !Sub OpenViduMediaInstanceProfile-${AWS::Region}-${AWS::StackName}
Roles: Roles:
@ -1544,7 +1569,7 @@ Resources:
content: !Sub | content: !Sub |
#!/bin/bash -x #!/bin/bash -x
set -e set -e
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
YQ_VERSION=v4.53.3 YQ_VERSION=v4.53.3
@ -1566,42 +1591,15 @@ Resources:
rm -rf awscliv2.zip aws rm -rf awscliv2.zip aws
fi fi
# Singal to notify instance is waiting # Signal to notify instance is waiting
SIGNAL_NAME="$1" SIGNAL_NAME="$1"
# Exported earlier in this instance's UserData
MASTER_NODE_NUM="${!MASTER_NODE_NUM}"
# Token for IMDSv2 # Token for IMDSv2
TOKEN="$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")" 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 # 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)" PRIVATE_IP="$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/local-ipv4)"
@ -1610,8 +1608,12 @@ Resources:
exit 1 exit 1
fi fi
# Store current private IP aws ssm put-parameter \
/usr/local/bin/store_secret.sh save MASTER_NODE_${!MASTER_NODE_NUM}_PRIVATE_IP "${!PRIVATE_IP}" --region ${AWS::Region} \
--name "/openvidu/${AWS::StackName}/master-node-${!MASTER_NODE_NUM}-private-ip" \
--value "${!PRIVATE_IP}" \
--type String \
--overwrite > /dev/null
SHARED_SECRET=$(aws secretsmanager get-secret-value \ SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \ --region ${AWS::Region} \
@ -1690,38 +1692,51 @@ Resources:
# sending the signal call # sending the signal call
cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region} cfn-signal -e $? --stack ${AWS::StackId} --resource "$SIGNAL_NAME" --region ${AWS::Region}
# Wait for all master nodes to store their private IPs IP_WAIT_ATTEMPTS=0
IP_WAIT_MAX=360
while true; do while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \ 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')
--region ${AWS::Region} \ 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')
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \ 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')
--query SecretString --output text || 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')
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP') # Check if all master nodes have published their private IPs
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP') if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP') [[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_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
# 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 break
fi 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 sleep 5
done 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
while true; do
SHARED_SECRET=$(aws secretsmanager get-secret-value \ SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \ --region ${AWS::Region} \
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \ --secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
--query SecretString --output text) --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
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP') break
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP') fi
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP') SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP') if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
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" 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') DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE') OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
@ -1738,18 +1753,22 @@ Resources:
GRAFANA_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.GRAFANA_ADMIN_PASSWORD') 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_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_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
MEET_INITIAL_API_KEY=""
if [[ "${InitialMeetApiKey}" != '' ]]; then if [[ "${InitialMeetApiKey}" != '' ]]; then
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY') MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
else
MEET_INITIAL_API_KEY=""
fi 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_KEY=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_KEY')
LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET') LIVEKIT_API_SECRET=$(echo "$SHARED_SECRET" | jq -r '.LIVEKIT_API_SECRET')
ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES') ENABLED_MODULES=$(echo "$SHARED_SECRET" | jq -r '.ENABLED_MODULES')
# Base command # Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_master_node.sh)" 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"
# Common arguments # Common arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -1837,6 +1856,10 @@ Resources:
content: !Sub | content: !Sub |
#!/bin/bash #!/bin/bash
set -e 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 # Get current shared secret
SHARED_SECRET=$(aws secretsmanager get-secret-value \ SHARED_SECRET=$(aws secretsmanager get-secret-value \
--region ${AWS::Region} \ --region ${AWS::Region} \
@ -2103,6 +2126,8 @@ Resources:
OpenViduMasterNode1: OpenViduMasterNode1:
Type: AWS::EC2::Instance Type: AWS::EC2::Instance
# Depends on its own SSM parameter so its put-parameter can't race the parameter's creation
DependsOn: MasterNode1PrivateIpParameter
Properties: Properties:
LaunchTemplate: LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2133,6 +2158,8 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=1
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; } /usr/local/bin/install.sh "MasterNodesWaitCondition1" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2179,7 +2206,7 @@ Resources:
OpenViduMasterNode2: OpenViduMasterNode2:
Type: AWS::EC2::Instance Type: AWS::EC2::Instance
DependsOn: MasterNodesWaitCondition1 DependsOn: MasterNode2PrivateIpParameter
Properties: Properties:
LaunchTemplate: LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2210,6 +2237,8 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=2
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; } /usr/local/bin/install.sh "MasterNodesWaitCondition2" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2256,7 +2285,7 @@ Resources:
OpenViduMasterNode3: OpenViduMasterNode3:
Type: AWS::EC2::Instance Type: AWS::EC2::Instance
DependsOn: MasterNodesWaitCondition2 DependsOn: MasterNode3PrivateIpParameter
Properties: Properties:
LaunchTemplate: LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2287,6 +2316,8 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=3
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; } /usr/local/bin/install.sh "MasterNodesWaitCondition3" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2333,7 +2364,7 @@ Resources:
OpenViduMasterNode4: OpenViduMasterNode4:
Type: AWS::EC2::Instance Type: AWS::EC2::Instance
DependsOn: MasterNodesWaitCondition3 DependsOn: MasterNode4PrivateIpParameter
Properties: Properties:
LaunchTemplate: LaunchTemplate:
LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate LaunchTemplateId: !Ref OpenViduMasterLaunchTemplate
@ -2364,6 +2395,8 @@ Resources:
cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate cfn-init -v --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMasterLaunchTemplate
export MASTER_NODE_NUM=4
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; } /usr/local/bin/install.sh "MasterNodesWaitCondition4" || { echo "[OpenVidu] error installing OpenVidu"; exit 1; }
@ -2453,6 +2486,25 @@ Resources:
exit 1 exit 1
fi 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 # Get OpenVidu Media Nodes version to deploy
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION') OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
if [[ "$OPENVIDU_VERSION" == "none" ]]; then if [[ "$OPENVIDU_VERSION" == "none" ]]; then
@ -2460,20 +2512,39 @@ Resources:
exit 1 exit 1
fi fi
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED') IP_WAIT_ATTEMPTS=0
if [[ "$ALL_SECRETS_GENERATED" == "none" ]]; then IP_WAIT_MAX=360
echo "Error: Secrets not generated" 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"
exit 1 exit 1
fi fi
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP') sleep 5
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP') done
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" 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') REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
# Base command # Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/ha/$OPENVIDU_VERSION/install_ov_media_node.sh)" 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"
# Common arguments # Common arguments
COMMON_ARGS=( COMMON_ARGS=(
@ -2609,13 +2680,36 @@ Resources:
python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz python3 -m pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-py3-${!CFN_BOOTSTRAP_VERSION}.tar.gz
fi fi
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenViduMediaNodeLaunchTemplate cfn-init --region ${AWS::Region} --stack ${AWS::StackName} --resource OpenViduMediaNodeLaunchTemplate
export HOME="/root" export HOME="/root"
# Install OpenVidu # Install OpenVidu
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; } /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 # Start OpenVidu
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; } systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; /usr/local/bin/set_as_unhealthy.sh; exit 1; }
@ -3191,8 +3285,6 @@ Resources:
LoadBalancer: LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer Type: AWS::ElasticLoadBalancingV2::LoadBalancer
DependsOn:
- MasterNodesWaitCondition4
Properties: Properties:
Name: Name:
Fn::Join: Fn::Join:

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -3,7 +3,7 @@
set -eu set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" 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 set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" 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: services:
caddy-proxy: caddy-proxy:
image: docker.io/openvidu/openvidu-caddy-local:3.8.0 image: docker.io/openvidu/openvidu-caddy-local:main
restart: unless-stopped restart: unless-stopped
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@ -88,7 +88,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
dashboard: dashboard:
image: docker.io/openvidu/openvidu-dashboard:3.8.0 image: docker.io/openvidu/openvidu-dashboard:main
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /etc/localtime:/etc/localtime:ro - /etc/localtime:/etc/localtime:ro
@ -102,7 +102,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
openvidu: openvidu:
image: docker.io/openvidu/openvidu-server-pro:3.8.0 image: docker.io/openvidu/openvidu-server-pro:main
restart: unless-stopped restart: unless-stopped
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@ -125,7 +125,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
ingress: ingress:
image: docker.io/openvidu/ingress:3.8.0 image: docker.io/openvidu/ingress:main
restart: unless-stopped restart: unless-stopped
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@ -143,7 +143,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
egress: egress:
image: docker.io/openvidu/egress:3.8.0 image: docker.io/openvidu/egress:main
restart: unless-stopped restart: unless-stopped
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@ -157,7 +157,7 @@ services:
setup: setup:
condition: service_completed_successfully condition: service_completed_successfully
operator: operator:
image: docker.io/openvidu/openvidu-operator:3.8.0 image: docker.io/openvidu/openvidu-operator:main
restart: unless-stopped restart: unless-stopped
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
@ -181,7 +181,7 @@ services:
openvidu-meet: openvidu-meet:
image: docker.io/openvidu/openvidu-meet:3.8.0 image: docker.io/openvidu/openvidu-meet:main
restart: on-failure restart: on-failure
extra_hosts: extra_hosts:
- host.docker.internal:host-gateway - host.docker.internal:host-gateway
@ -221,7 +221,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
openvidu-v2compatibility: openvidu-v2compatibility:
image: docker.io/openvidu/openvidu-v2compatibility:3.8.0 image: docker.io/openvidu/openvidu-v2compatibility:main
restart: unless-stopped restart: unless-stopped
entrypoint: /bin/sh /scripts/entrypoint_openvidu_v2_compat.sh entrypoint: /bin/sh /scripts/entrypoint_openvidu_v2_compat.sh
extra_hosts: extra_hosts:
@ -258,7 +258,7 @@ services:
condition: service_completed_successfully condition: service_completed_successfully
openvidu-meet-init: openvidu-meet-init:
image: docker.io/openvidu/openvidu-operator:3.8.0 image: docker.io/openvidu/openvidu-operator:main
restart: on-failure restart: on-failure
environment: environment:
- MODE=local-ready-check - MODE=local-ready-check

View File

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

View File

@ -176,7 +176,7 @@ var stringInterpolationParams = {
var installScriptTemplate = ''' var installScriptTemplate = '''
#!/bin/bash -x #!/bin/bash -x
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout 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 #!/bin/bash -x
set -e set -e
OPENVIDU_VERSION=3.8.0 OPENVIDU_VERSION=main
DOMAIN= DOMAIN=
YQ_VERSION=v4.53.3 YQ_VERSION=v4.53.3
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout

View File

@ -3,7 +3,7 @@
set -eu set -eu
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export INSTALLER_IMAGE="${INSTALLER_IMAGE:-docker.io/openvidu/openvidu-installer:${OPENVIDU_VERSION}}" 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_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}" 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 INSTALL_PREFIX="${INSTALL_PREFIX:-/opt/openvidu}"
export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}" export DOCKER_VERSION="${DOCKER_VERSION:-29.5.3}"
export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}" export DOCKER_COMPOSE_VERSION="${DOCKER_COMPOSE_VERSION:-v5.1.4}"
export OPENVIDU_VERSION="${OPENVIDU_VERSION:-3.8.0}" export OPENVIDU_VERSION="${OPENVIDU_VERSION:-main}"
export UPDATE_BASE_URL="${UPDATE_BASE_URL:-http://get.openvidu.io/update}" 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 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}" 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); private final static WaitStrategy waitBrowser = Wait.forLogMessage("^.*Started Selenium Standalone.*$", 1);
protected static String RTSP_SERVER_IMAGE = "bluenviron/mediamtx:1.17.1-ffmpeg"; protected static String RTSP_SERVER_IMAGE = "bluenviron/mediamtx:1.19.2-ffmpeg";
protected static int RTSP_SRT_PORT = 8554; protected static int RTSP_SRT_PORT = 8554;
// Key is the common name of the video codec. It must match the output log of // 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_HLS", "no", "MTX_RTSP", "yes", "MTX_WEBRTC", "no", "MTX_SRT", "no", "MTX_RTMP", "no",
"MTX_API", "no")) "MTX_API", "no"))
.withNetworkMode("host") .withNetworkMode("host")
.waitingFor(Wait.forLogMessage("^.*\\[RTSP\\] listener opened on :" + rtspPort + ".*$", 1)); .waitingFor(Wait.forLogMessage("^.*\\[RTSP\\] started with listeners on :" + rtspPort + ".*$", 1));
rtspServerContainer.start(); rtspServerContainer.start();
containers.add(rtspServerContainer); containers.add(rtspServerContainer);
@ -296,7 +296,12 @@ public class OpenViduTestE2e {
String codecs = getCodecs(videoCodec, audioCodec); String codecs = getCodecs(videoCodec, audioCodec);
String rtspServerIp = "host.docker.internal"; String rtspServerIp = "host.docker.internal";
String ffmpegCommand = "ffmpeg -i " + fileUrl + " " + codecs + " " // -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 + " "
+ " -async 50 -strict -2 -f rtsp -rtsp_transport tcp rtsp://" + rtspServerIp + ":" + rtspPort + "/" + " -async 50 -strict -2 -f rtsp -rtsp_transport tcp rtsp://" + rtspServerIp + ":" + rtspPort + "/"
+ RTSP_PATH; + RTSP_PATH;
@ -337,8 +342,9 @@ public class OpenViduTestE2e {
String fileUrl = getFileUrl(videoCodec != null, audioCodec != null, true); String fileUrl = getFileUrl(videoCodec != null, audioCodec != null, true);
String codecs = getCodecs(videoCodec, audioCodec); String codecs = getCodecs(videoCodec, audioCodec);
String ffmpegCommand = "ffmpeg -i " + fileUrl + " " + codecs + " -strict -2 -f mpegts srt://:" + RTSP_SRT_PORT // -re: see startRtspServer.
+ "?mode=listener"; String ffmpegCommand = "ffmpeg -re -i " + fileUrl + " " + codecs + " -strict -2 -f mpegts srt://:"
+ RTSP_SRT_PORT + "?mode=listener";
// Clean adjacent white spaces or the ffmpeg command will fail // Clean adjacent white spaces or the ffmpeg command will fail
ffmpegCommand = ffmpegCommand.trim().replaceAll(" +", " "); ffmpegCommand = ffmpegCommand.trim().replaceAll(" +", " ");

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,29 +1,18 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing'; import { provideRouter } from '@angular/router';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
describe('AppComponent', () => { describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({ beforeEach(() =>
imports: [RouterTestingModule], TestBed.configureTestingModule({
declarations: [AppComponent] imports: [AppComponent],
})); providers: [provideRouter([])],
})
);
it('should create the app', () => { it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app).toBeTruthy(); 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 } from '@angular/core'; import { Component, ChangeDetectionStrategy } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { Router, RouterOutlet, RouterLink } from '@angular/router'; import { Router, RouterOutlet, RouterLink } from '@angular/router';
import { LogLevel, setLogLevel } from 'livekit-client'; import { LogLevel, setLogLevel } from 'livekit-client';
@ -13,6 +13,7 @@ import { MatButtonModule } from '@angular/material/button';
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css', styleUrl: './app.component.css',
changeDetection: ChangeDetectionStrategy.Eager,
imports: [FormsModule, RouterOutlet, RouterLink, MatSidenavModule, MatToolbarModule, MatFormFieldModule, MatInputModule, MatButtonModule], imports: [FormsModule, RouterOutlet, RouterLink, MatSidenavModule, MatToolbarModule, MatFormFieldModule, MatInputModule, MatButtonModule],
}) })
export class AppComponent { export class AppComponent {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RoomConf } from '../test-sessions/test-sessions.component';
import { OpenviduInstanceComponent } from './openvidu-instance.component'; import { OpenviduInstanceComponent } from './openvidu-instance.component';
describe('OpenviduInstanceComponent', () => { describe('OpenviduInstanceComponent', () => {
@ -8,10 +9,18 @@ describe('OpenviduInstanceComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [OpenviduInstanceComponent] imports: [OpenviduInstanceComponent]
}); });
fixture = TestBed.createComponent(OpenviduInstanceComponent); fixture = TestBed.createComponent(OpenviduInstanceComponent);
component = fixture.componentInstance; 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(); fixture.detectChanges();
}); });

View File

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

View File

@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Room } from 'livekit-client';
import { ParticipantComponent } from './participant.component'; import { ParticipantComponent } from './participant.component';
@ -8,10 +9,14 @@ describe('ParticipantComponent', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ParticipantComponent] imports: [ParticipantComponent]
}); });
fixture = TestBed.createComponent(ParticipantComponent); fixture = TestBed.createComponent(ParticipantComponent);
component = fixture.componentInstance; 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(); fixture.detectChanges();
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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