terraform { required_version = ">= 1.5.0" required_providers { google = { source = "hashicorp/google" version = ">= 4.0" } random = { source = "hashicorp/random" version = ">= 3.0" } } } provider "google" { project = var.projectId region = var.region zone = var.zone } # Enable APIs the deployment needs resource "google_project_service" "compute_api" { service = "compute.googleapis.com" } resource "google_project_service" "secretmanager_api" { service = "secretmanager.googleapis.com" } resource "google_project_service" "storage_api" { service = "storage.googleapis.com" } resource "random_id" "bucket_suffix" { byte_length = 3 } # GCS bucket (conditional) resource "google_storage_bucket" "bucket" { count = 1 name = local.isEmpty ? "openvidu-appdata" : var.bucketName location = var.region force_destroy = false uniform_bucket_level_access = true } # Secret Manager secret that stores deployment info and seed secrets resource "google_secret_manager_secret" "openvidu" { secret_id = "openvidu-${var.region}-${var.stackName}" replication { auto {} } } resource "google_secret_manager_secret_version" "openvidu_version" { secret = google_secret_manager_secret.openvidu.id secret_data = jsonencode({ domainName = "none", LIVEKIT_turnDomainName = "none", LETSENCRYPT_EMAIL = "none", REDIS_PASSWORD = "none", MONGO_ADMIN_USERNAME = "none", MONGO_ADMIN_PASSWORD = "none", MONGO_REPLICA_SET_KEY = "none", MINIO_URL = "none", MINIO_ACCESS_KEY = "none", MINIO_SECRET_KEY = "none", DASHBOARD_URL = "none", DASHBOARD_ADMIN_USERNAME = "none", DASHBOARD_ADMIN_PASSWORD = "none", GRAFANA_URL = "none", GRAFANA_ADMIN_USERNAME = "none", GRAFANA_ADMIN_PASSWORD = "none", LIVEKIT_API_KEY = "none", LIVEKIT_API_SECRET = "none", MEET_ADMIN_USER = "none", MEET_ADMIN_SECRET = "none", MEET_API_KEY = "none", ENABLED_MODULES = "none" }) } # Service account for the instance resource "google_service_account" "openvidu_sa" { account_id = "openvidu-sa-${substr(var.stackName, 0, 12)}" display_name = "OpenVidu instance service account" } # IAM bindings for the service account so the instance can access Secret Manager and GCS resource "google_project_iam_member" "sa_secret_accessor" { project = var.projectId role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.openvidu_sa.email}" } resource "google_project_iam_member" "sa_storage_object_admin" { project = var.projectId role = "roles/storage.objectAdmin" member = "serviceAccount:${google_service_account.openvidu_sa.email}" } # Firewall (similar ports to your AWS SG) resource "google_compute_firewall" "openvidu_fw" { name = "openvidu-fw-${var.stackName}" network = "default" allow { protocol = "tcp" ports = ["22", "80", "443", "1935", "7881"] } allow { protocol = "udp" ports = ["443", "7885", "50000-60000"] } source_ranges = ["0.0.0.0/0"] target_tags = ["openvidu-server-${var.stackName}"] } # Static external IP (optional) resource "google_compute_address" "openvidu_ip" { count = var.publicStaticIp == "" ? 0 : 1 name = "openvidu-eip-${var.stackName}" address = var.publicStaticIp } # Compute instance for OpenVidu resource "google_compute_instance" "openvidu" { name = "openvidu-${var.stackName}" machine_type = var.instanceType zone = var.zone tags = ["openvidu-server-${var.stackName}"] boot_disk { initialize_params { image = var.boot_image size = 200 type = "pd-standard" } } network_interface { network = "default" access_config { nat_ip = length(google_compute_address.openvidu_ip) > 0 ? google_compute_address.openvidu_ip[0].address : null } } metadata = { # metadata values are accessible from the instance secret_name = google_secret_manager_secret.openvidu.secret_id region = var.region stackName = var.stackName certificateType = var.certificateType domainName = var.domainName letsEncryptEmail = var.letsEncryptEmail ownPublicCertificate = var.ownPublicCertificate ownPrivateCertificate = var.ownPrivateCertificate additional_install_flags = var.additional_install_flags turnDomainName = var.turnDomainName turnOwnPublicCertificate = var.turnOwnPublicCertificate turnOwnPrivateCertificate = var.turnOwnPrivateCertificate s3_bucket_name = var.bucketName == "" ? google_storage_bucket.appdata[0].name : var.bucketName } service_account { email = google_service_account.openvidu_sa.email scopes = ["https://www.googleapis.com/auth/cloud-platform"] } metadata_startup_script = </dev/null 2>&1; then echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] http://packages.cloud.google.com/apt cloud-sdk main" | tee /etc/apt/sources.list.d/google-cloud-sdk.list curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - apt-get update && apt-get install -y google-cloud-sdk fi # Install yq YQ_VERSION=v4.44.5 wget https://github.com/mikefarah/yq/releases/download/$${YQ_VERSION}/yq_linux_amd64.tar.gz -O - | tar xz && mv yq_linux_amd64 /usr/bin/yq # Fetch secret (the secret contains a JSON string as in Terraform) SHARED_SECRET_JSON=$(gcloud secrets versions access latest --secret="$${SECRET_NAME}" --project="$${projevar.projectId}") || SHARED_SECRET_JSON='{}' # Helper to update secret using gcloud (we will use it to save values) save_secret() { KEY=$1 VALUE=$2 # read current, update key, and write a new version TMP=$(mktemp) echo "$SHARED_SECRET_JSON" | jq ". + { \"$${KEY}\": \"$${VALUE}\" }" > "$TMP" || echo '{ }' > "$TMP" gcloud secrets versions add "$${SECRET_NAME}" --data-file="$TMP" --project="$${projevar.projectId}" >/dev/null SHARED_SECRET_JSON=$(cat "$TMP") rm -f "$TMP" } # Generate randoms and save to secret when needed (similar to CFN store_secret.sh) generate_and_save() { KEY=$1 PREFIX=$${2:-} LENGTH=$${3:-44} RAND=$(openssl rand -base64 64 | tr -d '+/=\n' | cut -c -$${LENGTH}) VALUE="$${PREFIX}$${RAND}" save_secret "$KEY" "$VALUE" echo "$VALUE" } # Configure domain if [[ -z "$domainName" || "$domainName" == "none" ]]; then # Use external IP EXTERNAL_IP=$(curl -s ifconfig.co || true) DOMAIN="$EXTERNAL_IP" else DOMAIN="$domainName" fi save_secret domainName "$DOMAIN" # Generate/store secrets used by OpenVidu REDIS_PASSWORD=$(generate_and_save REDIS_PASSWORD) MONGO_ADMIN_USERNAME=$(save_secret MONGO_ADMIN_USERNAME "mongoadmin") MONGO_ADMIN_PASSWORD=$(generate_and_save MONGO_ADMIN_PASSWORD) MONGO_REPLICA_SET_KEY=$(generate_and_save MONGO_REPLICA_SET_KEY) MINIO_ACCESS_KEY=$(save_secret MINIO_ACCESS_KEY "minioadmin") MINIO_SECRET_KEY=$(generate_and_save MINIO_SECRET_KEY) DASHBOARD_ADMIN_USERNAME=$(save_secret DASHBOARD_ADMIN_USERNAME "dashboardadmin") DASHBOARD_ADMIN_PASSWORD=$(generate_and_save DASHBOARD_ADMIN_PASSWORD) GRAFANA_ADMIN_USERNAME=$(save_secret GRAFANA_ADMIN_USERNAME "grafanaadmin") GRAFANA_ADMIN_PASSWORD=$(generate_and_save GRAFANA_ADMIN_PASSWORD) MEET_ADMIN_USER=$(save_secret MEET_ADMIN_USER "meetadmin") MEET_ADMIN_SECRET=$(generate_and_save MEET_ADMIN_SECRET) MEET_API_KEY=$(generate_and_save MEET_API_KEY) ENABLED_MODULES=$(save_secret ENABLED_MODULES "observability,openviduMeet") LIVEKIT_API_KEY=$(generate_and_save LIVEKIT_API_KEY "API" 12) LIVEKIT_API_SECRET=$(generate_and_save LIVEKIT_API_SECRET) # Build install command and args INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/community/singlenode/main/install.sh)" COMMON_ARGS=( "--no-tty" "--install" "--environment=gcp" "--deployment-type=single_node" "--domain-name=$DOMAIN" "--enabled-modules='$ENABLED_MODULES'" "--redis-password=$REDIS_PASSWORD" "--mongo-admin-user=$MONGO_ADMIN_USERNAME" "--mongo-admin-password=$MONGO_ADMIN_PASSWORD" "--mongo-replica-set-key=$MONGO_REPLICA_SET_KEY" "--minio-access-key=$MINIO_ACCESS_KEY" "--minio-secret-key=$MINIO_SECRET_KEY" "--dashboard-admin-user=$DASHBOARD_ADMIN_USERNAME" "--dashboard-admin-password=$DASHBOARD_ADMIN_PASSWORD" "--grafana-admin-user=$GRAFANA_ADMIN_USERNAME" "--grafana-admin-password=$GRAFANA_ADMIN_PASSWORD" "--meet-admin-user=$MEET_ADMIN_USER" "--meet-admin-password=$MEET_ADMIN_SECRET" "--meet-api-key=$MEET_API_KEY" "--livekit-api-key=$LIVEKIT_API_KEY" "--livekit-api-secret=$LIVEKIT_API_SECRET" ) # Include additional installer flags (trimmed) if [[ -n "$ADDITIONAL_FLAGS" && "$ADDITIONAL_FLAGS" != "none" ]]; then IFS=',' read -ra EXTRA_FLAGS <<< "$ADDITIONAL_FLAGS" for extra_flag in "$${EXTRA_FLAGS[@]}"; do extra_flag="$(echo -e "$extra_flag" | sed -e 's/^\s*//' -e 's/\s*$//')" if [[ -n "$extra_flag" ]]; then COMMON_ARGS+=("$extra_flag") fi done fi # TURN domain if [[ -n "$turnDomainName" && "$turnDomainName" != "none" ]]; then save_secret LIVEKIT_turnDomainName "$turnDomainName" COMMON_ARGS+=("--turn-domain-name=$turnDomainName") fi # Certificate handling if [[ "$CERT_TYPE" == "selfsigned" ]] ; then CERT_ARGS=("--certificate-type=selfsigned") elif [[ "$CERT_TYPE" == "letsencrypt" ]] ; then save_secret LETSENCRYPT_EMAIL "$LE_EMAIL" CERT_ARGS=("--certificate-type=letsencrypt" "--letsencrypt-email=$LE_EMAIL") else # owncert: download from provided URLs and convert to base64 mkdir -p /tmp/owncert if [[ -n "$OWN_CERT_URL" && -n "$OWN_KEY_URL" ]]; then wget -O /tmp/owncert/fullchain.pem "$OWN_CERT_URL" wget -O /tmp/owncert/privkey.pem "$OWN_KEY_URL" OWN_CERT_CRT=$(base64 -w 0 /tmp/owncert/fullchain.pem) OWN_CERT_KEY=$(base64 -w 0 /tmp/owncert/privkey.pem) CERT_ARGS=("--certificate-type=owncert" "--owncert-public-key=$OWN_CERT_CRT" "--owncert-private-key=$OWN_CERT_KEY") else echo "owncert selected but cert URLs not provided" exit 1 fi fi # Final command FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "$${COMMON_ARGS[@]}") $(printf "%s " "$${CERT_ARGS[@]}")" # Execute installation bash -c "$FINAL_COMMAND" # Configure GCS bucket in OpenVidu config if needed if [[ -n "$S3_BUCKET_NAME" && "$S3_BUCKET_NAME" != "none" ]]; then # Wait for openvidu config dir CONFIG_DIR="/opt/openvidu/config" if [[ -f "$${CONFIG_DIR}/openvidu.env" ]]; then sed -i "s|EXTERNAL_S3_BUCKET_APP_DATA=.*|EXTERNAL_S3_BUCKET_APP_DATA=$${S3_BUCKET_NAME}|" "$${CONFIG_DIR}/openvidu.env" || true fi fi EOF labels = { stack = var.stackName } } # ------------------------- outputs.tf ------------------------- output "openvidu_instance_name" { value = google_compute_instance.openvidu.name } output "openvidu_public_ip" { value = length(google_compute_address.openvidu_ip) > 0 ? google_compute_address.openvidu_ip[0].address : google_compute_instance.openvidu.network_interface[0].access_config[0].nat_ip } output "services_and_credentials_secret_id" { value = google_secret_manager_secret.openvidu.secret_id } output "appdata_bucket" { value = var.bucketName == "" ? google_storage_bucket.appdata[0].name : var.bucketName } # ------------------------- local values ------------------------- locals { isEmpty = var.bucketName == "" } # ------------------------- variables ------------------------- # Variables used by the configuration variable "projectId" { description = "GCP project id" type = string } variable "region" { description = "GCP region" type = string default = "europe-west1" } variable "zone" { description = "GCP zone" type = string default = "europe-west1-b" } variable "stackName" { description = "Stack name for OpenVidu deployment" type = string } variable "certificateType" { description = "[selfsigned] Not recommended for production use. If you don't have a FQDN, (DomainName parameter) you can use this option to generate a self-signed certificate. [owncert] Valid for productions environments. If you have a FQDN, (DomainName parameter) and an Elastic IP, you can use this option to use your own certificate. [letsencrypt] Valid for production environments. If you have a FQDN, (DomainName parameter) and an Elastic IP, you can use this option to generate a Let's Encrypt certificate." type = string default = "selfsigned" validation { condition = contains(["selfsigned", "owncert", "letsencrypt"], var.certificateType) error_message = "certificateType must be one of: selfsigned, owncert, letsencrypt" } } variable "publicStaticIp" { description = "Previously created Public IP address for the OpenVidu Deployment. Blank will generate a public IP" type = string default = "" } variable "domainName" { description = "Optional domain name for the deployment" type = string default = "" } variable "ownPublicCertificate" { description = "If owncert: URL to fullchain.pem" type = string default = "" } variable "ownPrivateCertificate" { description = "If owncert: URL to privkey.pem" type = string default = "" } variable "letsEncryptEmail" { description = "If letsencrypt: email for LE" type = string default = "" } variable "additional_install_flags" { description = "Comma-separated additional flags passed to the OpenVidu installer" type = string default = "" } variable "turnDomainName" { description = "Optional TURN server TLS domain" type = string default = "" } variable "turnOwnPublicCertificate" { description = "Optional TURN public cert URL for owncert" type = string default = "" } variable "turnOwnPrivateCertificate" { description = "Optional TURN private key URL for owncert" type = string default = "" } variable "instanceType" { description = "GCE machine type" type = string default = "e2-standard-8" } variable "boot_image" { description = "Boot image for the instance (family or specific image)" type = string default = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts" } variable "bucketName" { description = "If empty, a GCS bucket will be created for app data and recordings" type = string default = "" }