mirror of https://github.com/OpenVidu/openvidu.git
Compare commits
23 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
3ff7e14baf | |
|
|
fea2a9e9e8 | |
|
|
85d7762887 | |
|
|
03f6a8b810 | |
|
|
5f5503a106 | |
|
|
20eef140c5 | |
|
|
73f8b57498 | |
|
|
31a8488f23 | |
|
|
4d6b7acd7c | |
|
|
b8fdde9751 | |
|
|
196b3f767f | |
|
|
0a587eb33e | |
|
|
1794e7e16b | |
|
|
5a3347c12a | |
|
|
a3ede6a24f | |
|
|
1345528004 | |
|
|
ea335e0715 | |
|
|
9f68247dc5 | |
|
|
24151bca0b | |
|
|
e688e92a34 | |
|
|
4651dd1414 | |
|
|
e831d35da1 | |
|
|
de20380d95 |
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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,69 +1474,110 @@ 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.
|
SHARED_SECRET=$(aws secretsmanager get-secret-value \
|
||||||
MAX_RETRIES=10
|
--region ${AWS::Region} \
|
||||||
RETRY_COUNT=0
|
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
|
||||||
while : ; do
|
--query SecretString --output text || echo 'none')
|
||||||
# Get current shared secret and random seed
|
if [[ "$SHARED_SECRET" == "none" ]]; then
|
||||||
SHARED_SECRET=$(aws secretsmanager get-secret-value \
|
echo "Error: Shared secret not found"
|
||||||
--region ${AWS::Region} \
|
exit 1
|
||||||
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
|
fi
|
||||||
--query SecretString --output text || echo 'none')
|
ALL_SECRETS_GENERATED=$(echo "$SHARED_SECRET" | jq -r '.ALL_SECRETS_GENERATED')
|
||||||
|
|
||||||
if [[ "$SHARED_SECRET" != "none" ]]; then
|
# Generate secrets only once: a re-run must never regenerate passwords already in use by the cluster
|
||||||
break
|
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
|
||||||
fi
|
# 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}"
|
||||||
|
|
||||||
RETRY_COUNT=$((RETRY_COUNT+1))
|
# Meet initial admin user and password
|
||||||
if [[ $RETRY_COUNT -ge $MAX_RETRIES ]]; then
|
MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
|
||||||
exit 1
|
if [[ "${InitialMeetAdminPassword}" != '' ]]; then
|
||||||
fi
|
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_PASSWORD "${InitialMeetAdminPassword}")"
|
||||||
sleep 6
|
else
|
||||||
|
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MEET_INITIAL_ADMIN_PASSWORD)"
|
||||||
|
fi
|
||||||
|
if [[ "${InitialMeetApiKey}" != '' ]]; then
|
||||||
|
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "${InitialMeetApiKey}")"
|
||||||
|
else
|
||||||
|
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Store usernames and generate random passwords
|
||||||
|
REDIS_PASSWORD="$(/usr/local/bin/store_secret.sh generate REDIS_PASSWORD)"
|
||||||
|
MONGO_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
|
||||||
|
MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
|
||||||
|
MONGO_REPLICA_SET_KEY="$(/usr/local/bin/store_secret.sh generate MONGO_REPLICA_SET_KEY)"
|
||||||
|
MINIO_ACCESS_KEY="$(/usr/local/bin/store_secret.sh save MINIO_ACCESS_KEY "minioadmin")"
|
||||||
|
MINIO_SECRET_KEY="$(/usr/local/bin/store_secret.sh generate MINIO_SECRET_KEY)"
|
||||||
|
DASHBOARD_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save DASHBOARD_ADMIN_USERNAME "dashboardadmin")"
|
||||||
|
DASHBOARD_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate DASHBOARD_ADMIN_PASSWORD)"
|
||||||
|
GRAFANA_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save GRAFANA_ADMIN_USERNAME "grafanaadmin")"
|
||||||
|
GRAFANA_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate GRAFANA_ADMIN_PASSWORD)"
|
||||||
|
LIVEKIT_API_KEY="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_KEY "API" 12)"
|
||||||
|
LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)"
|
||||||
|
ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED_MODULES "observability,v2compatibility,openviduMeet")"
|
||||||
|
ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Source every installer value from the secret so a re-run (guard skipped) still has them
|
||||||
|
# GetSecretValue is eventually consistent: retry (up to 300s at 5s) until the read returns the generated values
|
||||||
|
SECRET_READ_ATTEMPTS=0
|
||||||
|
while true; do
|
||||||
|
SHARED_SECRET=$(aws secretsmanager get-secret-value \
|
||||||
|
--region ${AWS::Region} \
|
||||||
|
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
|
||||||
|
--query SecretString --output text)
|
||||||
|
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .OPENVIDU_PRO_LICENSE, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
SECRET_READ_ATTEMPTS=$((SECRET_READ_ATTEMPTS + 1))
|
||||||
|
if [[ $SECRET_READ_ATTEMPTS -ge 60 ]]; then
|
||||||
|
echo "Error: shared secret still incomplete after 5 minutes of stale reads"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
done
|
done
|
||||||
|
|
||||||
# Meet initial admin user and password
|
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
|
||||||
MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")"
|
OPENVIDU_VERSION=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_VERSION')
|
||||||
if [[ "${InitialMeetAdminPassword}" != '' ]]; then
|
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
|
||||||
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_PASSWORD "${InitialMeetAdminPassword}")"
|
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
|
||||||
else
|
MEET_INITIAL_ADMIN_USER=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_USER')
|
||||||
MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MEET_INITIAL_ADMIN_PASSWORD)"
|
MEET_INITIAL_ADMIN_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_ADMIN_PASSWORD')
|
||||||
fi
|
MEET_INITIAL_API_KEY=""
|
||||||
if [[ "${InitialMeetApiKey}" != '' ]]; then
|
if [[ "${InitialMeetApiKey}" != '' ]]; then
|
||||||
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "${InitialMeetApiKey}")"
|
MEET_INITIAL_API_KEY=$(echo "$SHARED_SECRET" | jq -r '.MEET_INITIAL_API_KEY')
|
||||||
else
|
|
||||||
MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "")"
|
|
||||||
fi
|
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')
|
||||||
|
|
||||||
# Store usernames and generate random passwords
|
# Download first: sh <(curl ...) would silently run an empty script on a transient curl failure
|
||||||
OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${OpenViduLicense}")"
|
INSTALLER_SCRIPT="/tmp/install_ov_master_node.sh"
|
||||||
OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${RTCEngine}")"
|
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"
|
||||||
REDIS_PASSWORD="$(/usr/local/bin/store_secret.sh generate REDIS_PASSWORD)"
|
if [ ! -s "$INSTALLER_SCRIPT" ]; then
|
||||||
MONGO_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save MONGO_ADMIN_USERNAME "mongoadmin")"
|
echo "Downloaded OpenVidu master node installer is empty or missing" >&2
|
||||||
MONGO_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MONGO_ADMIN_PASSWORD)"
|
exit 1
|
||||||
MONGO_REPLICA_SET_KEY="$(/usr/local/bin/store_secret.sh generate MONGO_REPLICA_SET_KEY)"
|
fi
|
||||||
MINIO_ACCESS_KEY="$(/usr/local/bin/store_secret.sh save MINIO_ACCESS_KEY "minioadmin")"
|
INSTALL_COMMAND="sh $INSTALLER_SCRIPT"
|
||||||
MINIO_SECRET_KEY="$(/usr/local/bin/store_secret.sh generate MINIO_SECRET_KEY)"
|
|
||||||
DASHBOARD_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save DASHBOARD_ADMIN_USERNAME "dashboardadmin")"
|
|
||||||
DASHBOARD_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate DASHBOARD_ADMIN_PASSWORD)"
|
|
||||||
GRAFANA_ADMIN_USERNAME="$(/usr/local/bin/store_secret.sh save GRAFANA_ADMIN_USERNAME "grafanaadmin")"
|
|
||||||
GRAFANA_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate GRAFANA_ADMIN_PASSWORD)"
|
|
||||||
LIVEKIT_API_KEY="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_KEY "API" 12)"
|
|
||||||
LIVEKIT_API_SECRET="$(/usr/local/bin/store_secret.sh generate LIVEKIT_API_SECRET)"
|
|
||||||
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")"
|
|
||||||
|
|
||||||
# Base command
|
|
||||||
INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)"
|
|
||||||
|
|
||||||
# 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
|
||||||
|
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and (.OPENVIDU_VERSION != "none") and (.REDIS_PASSWORD != "none")' > /dev/null; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
SECRETS_WAIT_ATTEMPTS=$((SECRETS_WAIT_ATTEMPTS + 1))
|
||||||
|
if [[ $SECRETS_WAIT_ATTEMPTS -ge $SECRETS_WAIT_MAX ]]; then
|
||||||
|
echo "Error: timed out after 30 minutes waiting for the master node to generate the shared secrets"
|
||||||
|
/usr/local/bin/set_as_unhealthy.sh
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
SHARED_SECRET=$(aws secretsmanager get-secret-value \
|
||||||
|
--region ${AWS::Region} \
|
||||||
|
--secret-id openvidu-elastic-${AWS::Region}-${AWS::StackName} \
|
||||||
|
--query SecretString --output text || echo 'none')
|
||||||
|
done
|
||||||
|
|
||||||
# Get OpenVidu Media Nodes version to deploy
|
# Read values only after the gate confirms the master node has published them
|
||||||
|
DOMAIN=$(echo "$SHARED_SECRET" | jq -r '.DOMAIN_NAME')
|
||||||
|
OPENVIDU_PRO_LICENSE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_PRO_LICENSE')
|
||||||
|
REDIS_PASSWORD=$(echo "$SHARED_SECRET" | jq -r '.REDIS_PASSWORD')
|
||||||
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
|
||||||
echo "OpenVidu version not found"
|
echo "OpenVidu version not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ALL_SECRETS_GENERATED=$(echo $SHARED_SECRET | jq -r .ALL_SECRETS_GENERATED)
|
|
||||||
if [[ "$ALL_SECRETS_GENERATED" == "false" ]]; then
|
|
||||||
echo "Master node not ready"
|
|
||||||
/usr/local/bin/set_as_unhealthy.sh
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get Master Node private IP
|
# 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:
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
az login --identity
|
# 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
|
||||||
|
|
||||||
# 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
|
|
@ -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,52 +1403,56 @@ EOF
|
||||||
#!/bin/bash -x
|
#!/bin/bash -x
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# install.sh (media node)
|
# Check if installation already completed
|
||||||
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
|
if [ -f /usr/local/bin/openvidu_install_counter.txt ]; then
|
||||||
|
# Launch on reboot
|
||||||
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
||||||
|
else
|
||||||
|
# install.sh (media node)
|
||||||
|
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
|
||||||
${local.install_script_media}
|
${local.install_script_media}
|
||||||
INSTALL_EOF
|
INSTALL_EOF
|
||||||
chmod +x /usr/local/bin/install.sh
|
chmod +x /usr/local/bin/install.sh
|
||||||
|
|
||||||
# graceful_shutdown.sh
|
# graceful_shutdown.sh
|
||||||
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
|
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
|
||||||
${local.graceful_shutdown_script}
|
${local.graceful_shutdown_script}
|
||||||
GRACEFUL_SHUTDOWN_EOF
|
GRACEFUL_SHUTDOWN_EOF
|
||||||
chmod +x /usr/local/bin/graceful_shutdown.sh
|
chmod +x /usr/local/bin/graceful_shutdown.sh
|
||||||
|
|
||||||
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
|
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
|
||||||
|
|
||||||
apt-get update && apt-get install -y
|
GCLOUD_VERSION=573.0.0
|
||||||
|
# Install google cli
|
||||||
|
if ! command -v gcloud >/dev/null 2>&1; then
|
||||||
|
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
|
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
|
||||||
|
fi
|
||||||
|
|
||||||
GCLOUD_VERSION=573.0.0
|
# Authenticate with gcloud using instance service account
|
||||||
# Install google cli
|
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
|
||||||
if ! command -v gcloud >/dev/null 2>&1; then
|
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
|
||||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
|
||||||
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
|
|
||||||
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Authenticate with gcloud using instance service account
|
export HOME="/root"
|
||||||
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
|
|
||||||
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
|
|
||||||
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
|
|
||||||
|
|
||||||
export HOME="/root"
|
# Install OpenVidu Media Node
|
||||||
|
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
|
||||||
|
|
||||||
# Install OpenVidu Media Node
|
# Mark installation as complete
|
||||||
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
|
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
|
||||||
|
|
||||||
# Mark installation as complete
|
# Start OpenVidu
|
||||||
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
||||||
|
|
||||||
# Start OpenVidu
|
# Add cron job to check if instance is abandoned every minute
|
||||||
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
|
||||||
|
|
||||||
# Add cron job to check if instance is abandoned every minute
|
|
||||||
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
|
|
||||||
${local.crontab_job_media}
|
${local.crontab_job_media}
|
||||||
CHECK_ABANDONED_EOF
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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,39 +1692,52 @@ 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
|
||||||
|
|
||||||
SHARED_SECRET=$(aws secretsmanager get-secret-value \
|
# Comma-separated 1-4: must match installer's --master-node-private-ip-list format
|
||||||
--region ${AWS::Region} \
|
|
||||||
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
|
|
||||||
--query SecretString --output text)
|
|
||||||
|
|
||||||
MASTER_NODE_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
|
|
||||||
MASTER_NODE_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
|
|
||||||
MASTER_NODE_3_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_3_PRIVATE_IP')
|
|
||||||
MASTER_NODE_4_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_4_PRIVATE_IP')
|
|
||||||
MASTER_NODE_PRIVATE_IP_LIST="$MASTER_NODE_1_PRIVATE_IP,$MASTER_NODE_2_PRIVATE_IP,$MASTER_NODE_3_PRIVATE_IP,$MASTER_NODE_4_PRIVATE_IP"
|
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 \
|
||||||
|
--region ${AWS::Region} \
|
||||||
|
--secret-id openvidu-ha-${AWS::Region}-${AWS::StackName} \
|
||||||
|
--query SecretString --output text)
|
||||||
|
if echo "$SHARED_SECRET" | jq -e '(.ALL_SECRETS_GENERATED == "true") and ([.DOMAIN_NAME, .OPENVIDU_VERSION, .REDIS_PASSWORD, .MONGO_ADMIN_PASSWORD, .MONGO_REPLICA_SET_KEY, .MINIO_SECRET_KEY, .DASHBOARD_ADMIN_PASSWORD, .GRAFANA_ADMIN_PASSWORD, .LIVEKIT_API_KEY, .LIVEKIT_API_SECRET] | all(. != "none"))' > /dev/null; then
|
||||||
|
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')
|
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')
|
||||||
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
|
OPENVIDU_RTC_ENGINE=$(echo "$SHARED_SECRET" | jq -r '.OPENVIDU_RTC_ENGINE')
|
||||||
|
|
@ -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
|
||||||
exit 1
|
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')
|
||||||
fi
|
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_1_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_1_PRIVATE_IP')
|
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_2_PRIVATE_IP=$(echo "$SHARED_SECRET" | jq -r '.MASTER_NODE_2_PRIVATE_IP')
|
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_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')
|
if [[ "$MASTER_NODE_1_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_1_PRIVATE_IP" ]] &&
|
||||||
|
[[ "$MASTER_NODE_2_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_2_PRIVATE_IP" ]] &&
|
||||||
|
[[ "$MASTER_NODE_3_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_3_PRIVATE_IP" ]] &&
|
||||||
|
[[ "$MASTER_NODE_4_PRIVATE_IP" != "none" ]] && [[ -n "$MASTER_NODE_4_PRIVATE_IP" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
IP_WAIT_ATTEMPTS=$((IP_WAIT_ATTEMPTS + 1))
|
||||||
|
if [[ $IP_WAIT_ATTEMPTS -ge $IP_WAIT_MAX ]]; then
|
||||||
|
echo "Error: timed out after 30 minutes waiting for all master nodes to publish their private IPs to SSM"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -305,13 +353,15 @@ 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
|
||||||
|
|
@ -355,13 +405,15 @@ 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
|
||||||
|
|
@ -407,13 +457,15 @@ 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
|
||||||
|
|
@ -459,13 +509,15 @@ 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,51 +1746,55 @@ EOF
|
||||||
#!/bin/bash -x
|
#!/bin/bash -x
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# Create scripts
|
# Check if installation already completed
|
||||||
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
|
if [ -f /usr/local/bin/openvidu_install_counter.txt ]; then
|
||||||
|
# Launch on reboot
|
||||||
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
||||||
|
else
|
||||||
|
# Create scripts
|
||||||
|
cat > /usr/local/bin/install.sh << 'INSTALL_EOF'
|
||||||
${local.install_script_media}
|
${local.install_script_media}
|
||||||
INSTALL_EOF
|
INSTALL_EOF
|
||||||
chmod +x /usr/local/bin/install.sh
|
chmod +x /usr/local/bin/install.sh
|
||||||
|
|
||||||
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
|
cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF'
|
||||||
${local.graceful_shutdown_script}
|
${local.graceful_shutdown_script}
|
||||||
GRACEFUL_SHUTDOWN_EOF
|
GRACEFUL_SHUTDOWN_EOF
|
||||||
chmod +x /usr/local/bin/graceful_shutdown.sh
|
chmod +x /usr/local/bin/graceful_shutdown.sh
|
||||||
|
|
||||||
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
|
echo "DPkg::Lock::Timeout \"-1\";" > /etc/apt/apt.conf.d/99timeout
|
||||||
|
|
||||||
apt-get update && apt-get install -y
|
GCLOUD_VERSION=573.0.0
|
||||||
|
# Install google cli
|
||||||
|
if ! command -v gcloud >/dev/null 2>&1; then
|
||||||
|
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
||||||
|
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
|
||||||
|
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
|
||||||
|
fi
|
||||||
|
|
||||||
GCLOUD_VERSION=573.0.0
|
# Authenticate with gcloud using instance service account
|
||||||
# Install google cli
|
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
|
||||||
if ! command -v gcloud >/dev/null 2>&1; then
|
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
|
||||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
|
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
|
||||||
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
|
|
||||||
apt-get update && apt-get install -y google-cloud-cli=$${GCLOUD_VERSION}-0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Authenticate with gcloud using instance service account
|
export HOME="/root"
|
||||||
gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true
|
|
||||||
gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google")
|
|
||||||
gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
|
|
||||||
|
|
||||||
export HOME="/root"
|
# Install OpenVidu Media Node
|
||||||
|
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
|
||||||
|
|
||||||
# Install OpenVidu Media Node
|
# Mark installation as complete
|
||||||
/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; }
|
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
|
||||||
|
|
||||||
# Mark installation as complete
|
# Start OpenVidu
|
||||||
echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt
|
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
||||||
|
|
||||||
# Start OpenVidu
|
# Add cron job to check if instance is abandoned every minute
|
||||||
systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; }
|
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
|
||||||
|
|
||||||
# Add cron job to check if instance is abandoned every minute
|
|
||||||
cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF'
|
|
||||||
${local.crontab_job_media}
|
${local.crontab_job_media}
|
||||||
CHECK_ABANDONED_EOF
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
||||||
|
|
|
||||||
|
|
@ -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(" +", " ");
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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!');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ describe('TestScenariosComponent', () => {
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ TestScenariosComponent ]
|
imports: [TestScenariosComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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' },
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,13 @@
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.d.ts"
|
"src/**/*.d.ts"
|
||||||
]
|
],
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"extendedDiagnostics": {
|
||||||
|
"checks": {
|
||||||
|
"nullishCoalescingNotNullable": "suppress",
|
||||||
|
"optionalChainNotNullable": "suppress"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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"],
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue