From 4b37bf4304218ceb71bd23b5c479cd5f950e6ca9 Mon Sep 17 00:00:00 2001 From: Piwccle Date: Wed, 27 Aug 2025 17:38:50 +0200 Subject: [PATCH] Add Terraform configuration for OpenVidu deployment on GCP --- .../gcp/tf-gpc-openvidu-singlenode.tf | 467 ++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf diff --git a/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf b/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf new file mode 100644 index 00000000..6bbb0cd5 --- /dev/null +++ b/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf @@ -0,0 +1,467 @@ +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 = "" +}