diff --git a/openvidu-deployment/community/singlenode/gcp/output.tf b/openvidu-deployment/community/singlenode/gcp/output.tf index 2156ffe5c..a2ace282e 100644 --- a/openvidu-deployment/community/singlenode/gcp/output.tf +++ b/openvidu-deployment/community/singlenode/gcp/output.tf @@ -1,13 +1,5 @@ # ------------------------- outputs.tf ------------------------- -output "openvidu_instance_name" { - value = google_compute_instance.openvidu_server.name -} - -output "openvidu_public_ip" { - value = length(google_compute_address.public_ip_address) > 0 ? google_compute_address.public_ip_address[0].address : google_compute_instance.openvidu_server.network_interface[0].access_config[0].nat_ip -} - -output "appdata_bucket" { - value = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName +output "secrets_manager" { + value = "https://console.cloud.google.com/security/secret-manager?project=${var.projectId}" } diff --git a/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf b/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf index 7be2f395f..cb62e3573 100644 --- a/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf +++ b/openvidu-deployment/community/singlenode/gcp/tf-gpc-openvidu-singlenode.tf @@ -8,6 +8,24 @@ resource "google_project_service" "cloudresourcemanager_api" { service = "cloudr resource "random_id" "bucket_suffix" { byte_length = 3 } + +# Secret Manager secrets for OpenVidu deployment information +resource "google_secret_manager_secret" "openvidu_shared_info" { + for_each = toset([ + "OPENVIDU_URL", "MEET_INITIAL_ADMIN_USER", "MEET_INITIAL_ADMIN_PASSWORD", + "MEET_INITIAL_API_KEY", "LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET", + "DASHBOARD_URL", "GRAFANA_URL", "MINIO_URL", "DOMAIN_NAME", "LIVEKIT_TURN_DOMAIN_NAME", + "REDIS_PASSWORD", "MONGO_ADMIN_USERNAME", "MONGO_ADMIN_PASSWORD", "MONGO_REPLICA_SET_KEY", + "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY", "DASHBOARD_ADMIN_USERNAME", "DASHBOARD_ADMIN_PASSWORD", + "GRAFANA_ADMIN_USERNAME", "GRAFANA_ADMIN_PASSWORD", "ENABLED_MODULES" + ]) + + secret_id = each.key + replication { + auto {} + } +} + # GCS bucket resource "google_storage_bucket" "bucket" { count = 1 @@ -149,31 +167,6 @@ get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } # Create counter file for tracking script executions echo 1 > /usr/local/bin/openvidu_install_counter.txt -# Create all the secrets -gcloud secrets create OPENVIDU_URL --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_ADMIN_USER --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_API_KEY --replication-policy=automatic || true -gcloud secrets create LIVEKIT_URL --replication-policy=automatic || true -gcloud secrets create LIVEKIT_API_KEY --replication-policy=automatic || true -gcloud secrets create LIVEKIT_API_SECRET --replication-policy=automatic || true -gcloud secrets create DASHBOARD_URL --replication-policy=automatic || true -gcloud secrets create GRAFANA_URL --replication-policy=automatic || true -gcloud secrets create MINIO_URL --replication-policy=automatic || true -gcloud secrets create DOMAIN_NAME --replication-policy=automatic || true -gcloud secrets create LIVEKIT_TURN_DOMAIN_NAME --replication-policy=automatic || true -gcloud secrets create REDIS_PASSWORD --replication-policy=automatic || true -gcloud secrets create MONGO_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create MONGO_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create MONGO_REPLICA_SET_KEY --replication-policy=automatic || true -gcloud secrets create MINIO_ACCESS_KEY --replication-policy=automatic || true -gcloud secrets create MINIO_SECRET_KEY --replication-policy=automatic || true -gcloud secrets create DASHBOARD_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create DASHBOARD_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create GRAFANA_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create GRAFANA_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create ENABLED_MODULES --replication-policy=automatic || true - # Configure domain if [[ "${var.domainName}" == "" ]]; then [ ! -d "/usr/share/openvidu" ] && mkdir -p /usr/share/openvidu diff --git a/openvidu-deployment/community/singlenode/gcp/variables.tf b/openvidu-deployment/community/singlenode/gcp/variables.tf index 278b58418..a5d830e15 100644 --- a/openvidu-deployment/community/singlenode/gcp/variables.tf +++ b/openvidu-deployment/community/singlenode/gcp/variables.tf @@ -88,7 +88,7 @@ variable "initialMeetApiKey" { variable "instanceType" { description = "Specifies the GCE machine type for your OpenVidu instance" type = string - default = "e2-standard-8" + default = "e2-standard-2" validation { condition = can(regex("^(e2-(micro|small|medium|standard-[2-9]|standard-1[0-6]|highmem-[2-9]|highmem-1[0-6]|highcpu-[2-9]|highcpu-1[0-6])|n1-(standard-[1-9]|standard-[1-9][0-9]|highmem-[2-9]|highmem-[1-9][0-9]|highcpu-[1-9]|highcpu-[1-9][0-9])|n2-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-2][0-8]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-2][0-8]|highcpu-[1-9][0-9]|highcpu-1[0-2][0-8])|n2d-(standard-[2-9]|standard-[1-9][0-9]|standard-2[0-2][0-4]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-9[0-6]|highcpu-[1-9][0-9]|highcpu-2[0-2][0-4])|c2-(standard-[4-9]|standard-[1-5][0-9]|standard-60)|c2d-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-1][0-2]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-1][0-2]|highcpu-[1-9][0-9]|highcpu-1[0-1][0-2])|m1-(ultramem-[4-9][0-9]|ultramem-160)|m2-(ultramem-208|ultramem-416|megamem-416)|m3-(ultramem-32|ultramem-64|ultramem-128|megamem-64|megamem-128)|a2-(standard-[1-9]|standard-[1-9][0-9]|standard-96|highmem-1g|ultramem-1g|megamem-1g)|a3-(standard-[1-9]|standard-[1-9][0-9]|standard-80|highmem-1g|megamem-1g)|g2-(standard-[4-9]|standard-[1-9][0-9]|standard-96)|t2d-(standard-[1-9]|standard-[1-9][0-9]|standard-60)|t2a-(standard-[1-9]|standard-[1-9][0-9]|standard-48)|h3-(standard-88)|f1-(micro)|t4g-(micro|small|medium|standard-[1-9]|standard-[1-9][0-9]))$", var.instanceType)) error_message = "The instance type is not valid" @@ -127,4 +127,4 @@ variable "turnOwnPrivateCertificate" { description = "(Optional) This setting is applicable if the certificate type is set to 'owncert' and the TurnDomainName is specified." type = string default = "" -} \ No newline at end of file +} diff --git a/openvidu-deployment/pro/elastic/gcp/output.tf b/openvidu-deployment/pro/elastic/gcp/output.tf new file mode 100644 index 000000000..56ad5826b --- /dev/null +++ b/openvidu-deployment/pro/elastic/gcp/output.tf @@ -0,0 +1,6 @@ +# ------------------------- outputs.tf ------------------------- + +output "secrets_manager" { + value = "https://console.cloud.google.com/security/secret-manager?project=${var.projectId}" +} + diff --git a/openvidu-deployment/pro/elastic/gcp/tf-gpc-openvidu-elastic.tf b/openvidu-deployment/pro/elastic/gcp/tf-gpc-openvidu-elastic.tf new file mode 100644 index 000000000..a7bfd284d --- /dev/null +++ b/openvidu-deployment/pro/elastic/gcp/tf-gpc-openvidu-elastic.tf @@ -0,0 +1,1381 @@ +# 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 "google_project_service" "iam_api" { service = "iam.googleapis.com" } +resource "google_project_service" "cloudresourcemanager_api" { service = "cloudresourcemanager.googleapis.com" } +resource "google_project_service" "cloudfunctions_api" { service = "cloudfunctions.googleapis.com" } +resource "google_project_service" "cloudscheduler_api" { service = "cloudscheduler.googleapis.com" } +resource "google_project_service" "cloudbuild_api" { service = "cloudbuild.googleapis.com" } +resource "google_project_service" "run_api" { service = "run.googleapis.com" } + +resource "random_id" "bucket_suffix" { byte_length = 3 } + +# Secret Manager secrets for OpenVidu deployment information +resource "google_secret_manager_secret" "openvidu_shared_info" { + for_each = toset([ + "OPENVIDU_URL", "MEET_INITIAL_ADMIN_USER", "MEET_INITIAL_ADMIN_PASSWORD", + "MEET_INITIAL_API_KEY", "LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET", + "DASHBOARD_URL", "GRAFANA_URL", "MINIO_URL", "DOMAIN_NAME", "LIVEKIT_TURN_DOMAIN_NAME", + "OPENVIDU_PRO_LICENSE", "OPENVIDU_RTC_ENGINE", "REDIS_PASSWORD", "MONGO_ADMIN_USERNAME", + "MONGO_ADMIN_PASSWORD", "MONGO_REPLICA_SET_KEY", "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY", + "DASHBOARD_ADMIN_USERNAME", "DASHBOARD_ADMIN_PASSWORD", "GRAFANA_ADMIN_USERNAME", + "GRAFANA_ADMIN_PASSWORD", "ENABLED_MODULES", "OPENVIDU_VERSION", "ALL_SECRETS_GENERATED" + ]) + + secret_id = each.key + replication { + auto {} + } +} + +# GCS bucket +resource "google_storage_bucket" "bucket" { + count = local.isEmpty ? 1 : 0 + name = "${var.projectId}-openvidu-${var.stackName}-${random_id.bucket_suffix.hex}" + location = var.region + force_destroy = true + uniform_bucket_level_access = true +} + +# Service account for the instance +resource "google_service_account" "service_account" { + account_id = lower("${substr(var.stackName, 0, 12)}-sa") + display_name = "OpenVidu instance service account" +} + +# IAM bindings for the service account so the instance can access Secret Manager and GCS +resource "google_project_iam_member" "iam_project_role" { + project = var.projectId + role = "roles/owner" + member = "serviceAccount:${google_service_account.service_account.email}" +} + +resource "google_compute_firewall" "firewall_master" { + name = lower("${var.stackName}-master-firewall") + network = "default" + + allow { + protocol = "tcp" + ports = ["22", "80", "443", "1935", "9000"] + } + + source_ranges = ["0.0.0.0/0"] + target_tags = [lower("${var.stackName}-master-node")] +} + +resource "google_compute_firewall" "firewall_media" { + name = lower("${var.stackName}-media-firewall") + network = "default" + + allow { + protocol = "tcp" + ports = ["22", "7881", "7880", "50000-60000"] + } + allow { + protocol = "udp" + ports = ["443", "7885", "50000-60000"] + } + + source_ranges = ["0.0.0.0/0"] + target_tags = [lower("${var.stackName}-media-node")] +} + +resource "google_compute_firewall" "firewall_media_to_master" { + name = lower("${var.stackName}-firewall-media-to-master") + network = "default" + + allow { + protocol = "tcp" + ports = ["7000", "9100", "20000", "3100", "9009", "4443", "6080"] + } + + source_tags = [ + lower("${var.stackName}-media-node") + ] + target_tags = [ + lower("${var.stackName}-master-node"), + ] +} + +resource "google_compute_firewall" "firewall_master_to_media" { + name = lower("${var.stackName}-firewall-master-to-media") + network = "default" + + allow { + protocol = "tcp" + ports = ["1935", "5349", "7880", "8080"] + } + + source_tags = [ + lower("${var.stackName}-master-node"), + ] + target_tags = [ + lower("${var.stackName}-media-node") + ] +} + +# Create Public Ip address (if not provided) +resource "google_compute_address" "public_ip_address" { + count = var.publicIpAddress == "" ? 1 : 0 + name = lower("${var.stackName}-public-ip") + region = var.region +} + +# Compute instance for OpenVidu +resource "google_compute_instance" "openvidu_master_node" { + name = lower("${var.stackName}-master-node") + machine_type = var.masterNodeInstanceType + zone = var.zone + + tags = [lower("${var.stackName}-master-node")] + + boot_disk { + initialize_params { + image = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts" + size = 100 + type = "pd-standard" + } + } + + network_interface { + network = "default" + access_config { + nat_ip = var.publicIpAddress == "" ? google_compute_address.public_ip_address[0].address : var.publicIpAddress + } + } + + metadata = { + # metadata values are accessible from the instance + publicIpAddress = var.publicIpAddress == "" ? google_compute_address.public_ip_address[0].address : var.publicIpAddress + region = var.region + stackName = var.stackName + certificateType = var.certificateType + domainName = var.domainName + ownPublicCertificate = var.ownPublicCertificate + ownPrivateCertificate = var.ownPrivateCertificate + openviduLicense = var.openviduLicense + rtcEngine = var.rtcEngine + initialMeetAdminPassword = var.initialMeetAdminPassword + initialMeetApiKey = var.initialMeetApiKey + additionalInstallFlags = var.additionalInstallFlags + turnDomainName = var.turnDomainName + turnOwnPublicCertificate = var.turnOwnPublicCertificate + turnOwnPrivateCertificate = var.turnOwnPrivateCertificate + bucketName = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName + } + + service_account { + email = google_service_account.service_account.email + scopes = ["https://www.googleapis.com/auth/cloud-platform"] + } + + metadata_startup_script = local.user_data_master + + labels = { + stack = var.stackName + node-type = "master" + } +} + +# Media Node Instance Template +resource "google_compute_instance_template" "media_node_template" { + name = lower("${var.stackName}-media-node-template") + machine_type = var.mediaNodeInstanceType + + tags = [lower("${var.stackName}-media-node")] + + disk { + source_image = "projects/ubuntu-os-cloud/global/images/family/ubuntu-2204-lts" + auto_delete = true + boot = true + disk_size_gb = 100 + disk_type = "pd-standard" + } + + network_interface { + network = "default" + access_config {} + } + + metadata = { + stackName = var.stackName + masterNodePrivateIP = google_compute_instance.openvidu_master_node.network_interface[0].network_ip + bucketName = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName + region = var.region + shutdown-script = local.graceful_shutdown_script + } + + service_account { + email = google_service_account.service_account.email + scopes = ["https://www.googleapis.com/auth/cloud-platform"] + } + + metadata_startup_script = local.user_data_media + labels = { + stack = var.stackName + node-type = "media" + } + + lifecycle { + create_before_destroy = true + } +} + +# Managed Instance Group for Media Nodes +resource "google_compute_region_instance_group_manager" "media_node_group" { + name = lower("${var.stackName}-media-node-group") + base_instance_name = lower("${var.stackName}-media-node") + region = var.region + target_size = var.initialNumberOfMediaNodes + + version { + instance_template = google_compute_instance_template.media_node_template.self_link_unique + } + + named_port { + name = "http" + port = 7880 + } + + depends_on = [google_compute_instance.openvidu_master_node] +} + +# Autoscaler for Media Nodes +resource "google_compute_region_autoscaler" "media_node_autoscaler" { + name = lower("${var.stackName}-media-node-autoscaler") + region = var.region + target = google_compute_region_instance_group_manager.media_node_group.self_link + + autoscaling_policy { + max_replicas = var.maxNumberOfMediaNodes + min_replicas = var.minNumberOfMediaNodes + cooldown_period = 260 + + cpu_utilization { + target = var.scaleTargetCPU / 100.0 + } + + mode = "ONLY_SCALE_OUT" + } +} + +# ------------------------- scale in resources ------------------------- + +# Create the function source code +data "archive_file" "function_source" { + type = "zip" + output_path = "/tmp/function-source.zip" + source { + content = local.scalein_function_code + filename = "main.py" + } + source { + content = local.function_requirements + filename = "requirements.txt" + } +} + +# Local values for the function source code +locals { + scalein_function_code = <= current_size: + message = "No scale-in operation needed" + print(message) + logger.info(message) + return { + "message": message, + "current_size": current_size, + "recommended_size": recommended_size + } + + # Perform scale-in + instances_to_abandon = current_size - recommended_size + print(f"Scale-in needed: abandoning {instances_to_abandon} instances") + + result = _perform_scalein( + clients, project_id, region, mig_name, instances_to_abandon + ) + + success_message = f"Successfully abandoned {result['abandoned_count']} instances" + print(success_message) + logger.info(success_message) + + return { + "message": success_message, + "abandoned_instances": result['abandoned_instances'], + "operation_name": result['operation_name'] + } + + except ValueError as e: + error_msg = f"Validation error: {str(e)}" + print(error_msg) + logger.error(error_msg) + return {"error": str(e)}, 400 + except Exception as e: + error_msg = f"Unexpected error in scaling function: {str(e)}" + print(error_msg) + logger.error(error_msg) + return {"error": f"Internal server error: {str(e)}"}, 500 + + +def _parse_and_validate_request(request) -> Tuple[str, str, str]: + """Parse and validate the incoming request.""" + print("Parsing request...") + request_json = request.get_json() + if not request_json: + raise ValueError("Request body must be valid JSON") + + project_id = request_json.get('project_id') + region = request_json.get('region') + stack_name = request_json.get('stack_name') + + if not all([project_id, region, stack_name]): + raise ValueError("Missing required parameters: project_id, region, stack_name") + + print(f"Request parsed successfully: {project_id}, {region}, {stack_name}") + return project_id, region, stack_name + + +def _initialize_gcp_clients() -> Dict: + """Initialize all required GCP clients.""" + print("Initializing GCP clients...") + clients = { + 'mig': compute_v1.RegionInstanceGroupManagersClient(), + 'autoscaler': compute_v1.RegionAutoscalersClient() + } + print("GCP clients initialized successfully") + return clients + + +def _get_scaling_info(clients: Dict, project_id: str, region: str, + mig_name: str, autoscaler_name: str) -> Tuple[int, int]: + """Get current MIG size and autoscaler recommendation.""" + print(f"Getting scaling info for MIG: {mig_name}") + try: + # Get current MIG state + mig_request = compute_v1.GetRegionInstanceGroupManagerRequest( + project=project_id, + region=region, + instance_group_manager=mig_name + ) + mig = clients['mig'].get(request=mig_request) + current_size = mig.target_size + + # Get autoscaler recommendation + autoscaler_request = compute_v1.GetRegionAutoscalerRequest( + project=project_id, + region=region, + autoscaler=autoscaler_name + ) + autoscaler = clients['autoscaler'].get(request=autoscaler_request) + recommended_size = autoscaler.recommended_size + + print(f"Retrieved scaling info - Current: {current_size}, Recommended: {recommended_size}") + return current_size, recommended_size + + except Exception as e: + error_msg = f"Failed to get scaling information: {str(e)}" + print(error_msg) + raise Exception(error_msg) + + +def _perform_scalein(clients: Dict, project_id: str, region: str, + mig_name: str, instances_count: int) -> Dict: + """Perform scale-in by abandoning oldest instances.""" + print(f"Starting scale-in for {instances_count} instances") + + # Get instances to abandon + instances_to_abandon = _get_oldest_instances( + clients['mig'], project_id, region, mig_name, instances_count + ) + + if not instances_to_abandon: + print("No instances to abandon") + return {"abandoned_count": 0, "abandoned_instances": [], "operation_name": None} + + print(f"Preparing to abandon {len(instances_to_abandon)} instances") + logger.info(f"Preparing to abandon {len(instances_to_abandon)} instances") + + # Abandon instances + operation = _abandon_instances(clients['mig'], project_id, region, mig_name, instances_to_abandon) + + print(f"Instances abandoned successfully, operation: {operation.name}") + return { + "abandoned_count": len(instances_to_abandon), + "abandoned_instances": instances_to_abandon, + "operation_name": operation.name + } + + +def _get_oldest_instances(mig_client, project_id: str, region: str, + mig_name: str, count: int) -> List[str]: + """Get the oldest instances from the MIG.""" + print(f"Getting {count} oldest instances from MIG") + try: + list_request = compute_v1.ListManagedInstancesRegionInstanceGroupManagersRequest( + project=project_id, + region=region, + instance_group_manager=mig_name + ) + instances = list(mig_client.list_managed_instances(request=list_request)) + + print(f"Found {len(instances)} total instances in MIG") + + # Sort by creation timestamp (oldest first) - reverse the sort order + instances.sort(key=lambda x: _parse_timestamp( + x.instance_status.time_created if x.instance_status and + hasattr(x.instance_status, 'time_created') else None + ), reverse=True) + + # Select instances to abandon + instances_to_abandon = [] + for i in range(min(count, len(instances))): + instance = instances[i] + if instance.instance: + instances_to_abandon.append(instance.instance) + + print(f"Selected {len(instances_to_abandon)} instances to abandon") + return instances_to_abandon + + except Exception as e: + error_msg = f"Error getting oldest instances: {str(e)}" + print(error_msg) + logger.error(error_msg) + return [] + +def _parse_timestamp(timestamp_str: Optional[str]) -> datetime: + """Parse timestamp string to datetime object.""" + if not timestamp_str: + return datetime.min + try: + return datetime.fromisoformat(timestamp_str.replace('Z', '+00:00')) + except Exception: + return datetime.min + +def _abandon_instances(mig_client, project_id: str, region: str, + mig_name: str, instance_urls: List[str]): + """Abandon instances from the MIG.""" + print(f"Abandoning {len(instance_urls)} instances from MIG") + abandon_request = compute_v1.AbandonInstancesRegionInstanceGroupManagerRequest( + project=project_id, + region=region, + instance_group_manager=mig_name, + region_instance_group_managers_abandon_instances_request_resource=compute_v1.RegionInstanceGroupManagersAbandonInstancesRequest( + instances=instance_urls + ) + ) + + operation = mig_client.abandon_instances(request=abandon_request) + print(f"Abandon operation initiated: {operation.name}") + return operation +EOF + + function_requirements = </dev/null || true +METADATA_URL="http://metadata.google.internal/computeMetadata/v1" +get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } + +# Create counter file for tracking script executions +echo 1 > /usr/local/bin/openvidu_install_counter.txt + +# Configure domain +if [[ "${var.domainName}" == "" ]]; then + [ ! -d "/usr/share/openvidu" ] && mkdir -p /usr/share/openvidu + EXTERNAL_IP=$(get_meta "instance/network-interfaces/0/access-configs/0/external-ip") + RANDOM_DOMAIN_STRING=$(tr -dc 'a-z' < /dev/urandom | head -c 8) + DOMAIN=openvidu-$RANDOM_DOMAIN_STRING-$(echo $EXTERNAL_IP | tr '.' '-').sslip.io + TURN_DOMAIN_NAME_SSLIP_IO=turn-$RANDOM_DOMAIN_STRING-$(echo $EXTERNAL_IP | tr '.' '-').sslip.io +else + DOMAIN="${var.domainName}" +fi +DOMAIN="$(/usr/local/bin/store_secret.sh save DOMAIN_NAME "$DOMAIN")" + +# Meet initial admin user and password +MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_USER "admin")" +if [[ "${var.initialMeetAdminPassword}" != '' ]]; then + MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_ADMIN_PASSWORD "${var.initialMeetAdminPassword}")" +else + MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/store_secret.sh generate MEET_INITIAL_ADMIN_PASSWORD)" +fi + +if [[ "${var.initialMeetApiKey}" != '' ]]; then + MEET_INITIAL_API_KEY="$(/usr/local/bin/store_secret.sh save MEET_INITIAL_API_KEY "${var.initialMeetApiKey}")" +fi + +#Get own private IP +PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip") + +# 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)" +ENABLED_MODULES="$(/usr/local/bin/store_secret.sh save ENABLED_MODULES "observability,openviduMeet,v2compatibility")" +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)" + +# Store OpenVidu Pro license and RTC engine and Openvidu version +OPENVIDU_PRO_LICENSE="$(/usr/local/bin/store_secret.sh save OPENVIDU_PRO_LICENSE "${var.openviduLicense}")" +OPENVIDU_RTC_ENGINE="$(/usr/local/bin/store_secret.sh save OPENVIDU_RTC_ENGINE "${var.rtcEngine}")" +OPENVIDU_VERSION="$(/usr/local/bin/store_secret.sh save OPENVIDU_VERSION "$OPENVIDU_VERSION")" + +ALL_SECRETS_GENERATED="$(/usr/local/bin/store_secret.sh save ALL_SECRETS_GENERATED "true")" + +# Build install command and args +INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_master_node.sh)" + +# Common arguments +COMMON_ARGS=( + "--no-tty" + "--install" + "--environment=gcp" + "--deployment-type=elastic" + "--node-role=master-node" + "--openvidu-pro-license=$OPENVIDU_PRO_LICENSE" + "--private-ip=$PRIVATE_IP" + "--domain-name=$DOMAIN" + "--enabled-modules='$ENABLED_MODULES'" + "--rtc-engine=$OPENVIDU_RTC_ENGINE" + "--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-initial-admin-password=$MEET_INITIAL_ADMIN_PASSWORD" + "--meet-initial-api-key=$MEET_INITIAL_API_KEY" + "--livekit-api-key=$LIVEKIT_API_KEY" + "--livekit-api-secret=$LIVEKIT_API_SECRET" +) + +# Include additional installer flags provided by the user +if [[ "${var.additionalInstallFlags}" != "" ]]; then + IFS=',' read -ra EXTRA_FLAGS <<< "${var.additionalInstallFlags}" + for extra_flag in "$${EXTRA_FLAGS[@]}"; do + # Trim whitespace around each flag + extra_flag="$(echo -e "$${extra_flag}" | sed -e 's/^[ \t]*//' -e 's/[ \t]*$//')" + if [[ "$extra_flag" != "" ]]; then + COMMON_ARGS+=("$extra_flag") + fi + done +fi + +# Turn with TLS +if [[ "$TURN_DOMAIN_NAME_SSLIP_IO" != "" ]]; then + LIVEKIT_TURN_DOMAIN_NAME=$(/usr/local/bin/store_secret.sh save LIVEKIT_TURN_DOMAIN_NAME "$TURN_DOMAIN_NAME_SSLIP_IO") + COMMON_ARGS+=( + "--turn-domain-name=$LIVEKIT_TURN_DOMAIN_NAME" + ) +elif [[ "${var.turnDomainName}" != '' ]]; then + LIVEKIT_TURN_DOMAIN_NAME=$(/usr/local/bin/store_secret.sh save LIVEKIT_TURN_DOMAIN_NAME "${var.turnDomainName}") + COMMON_ARGS+=( + "--turn-domain-name=$LIVEKIT_TURN_DOMAIN_NAME" + ) +fi + +# Certificate arguments +if [[ "${var.certificateType}" == "selfsigned" ]]; then + CERT_ARGS=( + "--certificate-type=selfsigned" + ) +elif [[ "${var.certificateType}" == "letsencrypt" ]]; then + CERT_ARGS=( + "--certificate-type=letsencrypt" + ) +else + # Download owncert files + mkdir -p /tmp/owncert + wget -O /tmp/owncert/fullchain.pem ${var.ownPublicCertificate} + wget -O /tmp/owncert/privkey.pem ${var.ownPrivateCertificate} + + # Convert to base64 + 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" + ) + + # Turn with TLS and own certificate + if [[ "${var.turnDomainName}" != '' ]]; then + # Download owncert files + mkdir -p /tmp/owncert-turn + wget -O /tmp/owncert-turn/fullchain.pem ${var.turnOwnPublicCertificate} + wget -O /tmp/owncert-turn/privkey.pem ${var.turnOwnPrivateCertificate} + # Convert to base64 + OWN_CERT_CRT_TURN=$(base64 -w 0 /tmp/owncert-turn/fullchain.pem) + OWN_CERT_KEY_TURN=$(base64 -w 0 /tmp/owncert-turn/privkey.pem) + CERT_ARGS+=( + "--turn-owncert-private-key=$OWN_CERT_KEY_TURN" + "--turn-owncert-public-key=$OWN_CERT_CRT_TURN" + ) + fi +fi + +# Final command +FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "$${COMMON_ARGS[@]}") $(printf "%s " "$${CERT_ARGS[@]}")" + +# Execute installation +exec bash -c "$FINAL_COMMAND" +EOF + + config_s3_script = <<-EOF +#!/bin/bash -x +set -e + +# Configure gcloud with instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +# Install dir and config dir +INSTALL_DIR="/opt/openvidu" +CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster" + +METADATA_URL="http://metadata.google.internal/computeMetadata/v1" +get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } +SERVICE_ACCOUNT_EMAIL=$(get_meta "instance/service-accounts/default/email") + +# Create key for service account +gcloud iam service-accounts keys create credentials.json --iam-account=$SERVICE_ACCOUNT_EMAIL + +# Create HMAC key and parse output +HMAC_OUTPUT=$(gcloud storage hmac create $SERVICE_ACCOUNT_EMAIL --format="json") +EXTERNAL_S3_ACCESS_KEY=$(echo "$HMAC_OUTPUT" | jq -r '.metadata.accessId') +EXTERNAL_S3_SECRET_KEY=$(echo "$HMAC_OUTPUT" | jq -r '.secret') + +# Config S3 bucket +EXTERNAL_S3_ENDPOINT="https://storage.googleapis.com" +EXTERNAL_S3_REGION="${var.region}" +EXTERNAL_S3_PATH_STYLE_ACCESS="true" +EXTERNAL_S3_BUCKET_APP_DATA=$(get_meta "instance/attributes/bucketName") + +# Update egress.yaml to use hardcoded credentials instead of env variable +if [ -f "$${CLUSTER_CONFIG_DIR}/media_node/egress.yaml" ]; then + yq eval --inplace '.storage.gcp.credentials_json = (load("/credentials.json") | tostring) | .storage.gcp.credentials_json style="single"' $${CLUSTER_CONFIG_DIR}/media_node/egress.yaml +fi + +sed -i "s|EXTERNAL_S3_ENDPOINT=.*|EXTERNAL_S3_ENDPOINT=$EXTERNAL_S3_ENDPOINT|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|EXTERNAL_S3_REGION=.*|EXTERNAL_S3_REGION=$EXTERNAL_S3_REGION|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|EXTERNAL_S3_PATH_STYLE_ACCESS=.*|EXTERNAL_S3_PATH_STYLE_ACCESS=$EXTERNAL_S3_PATH_STYLE_ACCESS|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|EXTERNAL_S3_BUCKET_APP_DATA=.*|EXTERNAL_S3_BUCKET_APP_DATA=$EXTERNAL_S3_BUCKET_APP_DATA|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|EXTERNAL_S3_ACCESS_KEY=.*|EXTERNAL_S3_ACCESS_KEY=$EXTERNAL_S3_ACCESS_KEY|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s|EXTERNAL_S3_SECRET_KEY=.*|EXTERNAL_S3_SECRET_KEY=$EXTERNAL_S3_SECRET_KEY|" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +EOF + + after_install_script = <<-EOF +#!/bin/bash +set -e + +# Configure gcloud with instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +# Generate URLs +DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME) +OPENVIDU_URL="https://$${DOMAIN}/" +LIVEKIT_URL="wss://$${DOMAIN}/" +DASHBOARD_URL="https://$${DOMAIN}/dashboard/" +GRAFANA_URL="https://$${DOMAIN}/grafana/" +MINIO_URL="https://$${DOMAIN}/minio-console/" + +# Update shared secret +echo -n "$DOMAIN" | gcloud secrets versions add DOMAIN_NAME --data-file=- +echo -n "$OPENVIDU_URL" | gcloud secrets versions add OPENVIDU_URL --data-file=- +echo -n "$LIVEKIT_URL" | gcloud secrets versions add LIVEKIT_URL --data-file=- +echo -n "$DASHBOARD_URL" | gcloud secrets versions add DASHBOARD_URL --data-file=- +echo -n "$GRAFANA_URL" | gcloud secrets versions add GRAFANA_URL --data-file=- +echo -n "$MINIO_URL" | gcloud secrets versions add MINIO_URL --data-file=- +gcloud secrets versions access latest --secret=MINIO_URL +if [[ $? -ne 0 ]]; then + echo "Error updating secret_manager" +fi +EOF + + update_config_from_secret_script = <<-EOF +#!/bin/bash -x +set -e + +# Configure gcloud with instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +# Installation directory +INSTALL_DIR="/opt/openvidu" +CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster" +MASTER_NODE_CONFIG_DIR="$${INSTALL_DIR}/config/node" + +# Replace DOMAIN_NAME +export DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME) +if [[ -n "$DOMAIN" ]]; then + sed -i "s/DOMAIN_NAME=.*/DOMAIN_NAME=$DOMAIN/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +else + exit 1 +fi + +# Replace LIVEKIT_TURN_DOMAIN_NAME +export LIVEKIT_TURN_DOMAIN_NAME=$(gcloud secrets versions access latest --secret=LIVEKIT_TURN_DOMAIN_NAME) +if [[ -n "$LIVEKIT_TURN_DOMAIN_NAME" ]]; then + sed -i "s/LIVEKIT_TURN_DOMAIN_NAME=.*/LIVEKIT_TURN_DOMAIN_NAME=$LIVEKIT_TURN_DOMAIN_NAME/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +fi + +# Get the rest of the values +export REDIS_PASSWORD=$(gcloud secrets versions access latest --secret=REDIS_PASSWORD) +export OPENVIDU_RTC_ENGINE=$(gcloud secrets versions access latest --secret=OPENVIDU_RTC_ENGINE) +export OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE) +export MONGO_ADMIN_USERNAME=$(gcloud secrets versions access latest --secret=MONGO_ADMIN_USERNAME) +export MONGO_ADMIN_PASSWORD=$(gcloud secrets versions access latest --secret=MONGO_ADMIN_PASSWORD) +export MONGO_REPLICA_SET_KEY=$(gcloud secrets versions access latest --secret=MONGO_REPLICA_SET_KEY) +export DASHBOARD_ADMIN_USERNAME=$(gcloud secrets versions access latest --secret=DASHBOARD_ADMIN_USERNAME) +export DASHBOARD_ADMIN_PASSWORD=$(gcloud secrets versions access latest --secret=DASHBOARD_ADMIN_PASSWORD) +export MINIO_ACCESS_KEY=$(gcloud secrets versions access latest --secret=MINIO_ACCESS_KEY) +export MINIO_SECRET_KEY=$(gcloud secrets versions access latest --secret=MINIO_SECRET_KEY) +export GRAFANA_ADMIN_USERNAME=$(gcloud secrets versions access latest --secret=GRAFANA_ADMIN_USERNAME) +export GRAFANA_ADMIN_PASSWORD=$(gcloud secrets versions access latest --secret=GRAFANA_ADMIN_PASSWORD) +export LIVEKIT_API_KEY=$(gcloud secrets versions access latest --secret=LIVEKIT_API_KEY) +export LIVEKIT_API_SECRET=$(gcloud secrets versions access latest --secret=LIVEKIT_API_SECRET) +export MEET_INITIAL_ADMIN_USER=$(gcloud secrets versions access latest --secret=MEET_INITIAL_ADMIN_USER) +export MEET_INITIAL_ADMIN_PASSWORD=$(gcloud secrets versions access latest --secret=MEET_INITIAL_ADMIN_PASSWORD) +if [[ "${var.initialMeetApiKey}" != '' ]]; then + export MEET_INITIAL_API_KEY=$(gcloud secrets versions access latest --secret=MEET_INITIAL_API_KEY) +fi +export ENABLED_MODULES=$(gcloud secrets versions access latest --secret=ENABLED_MODULES) + +# Replace rest of the values +sed -i "s/REDIS_PASSWORD=.*/REDIS_PASSWORD=$REDIS_PASSWORD/" "$${MASTER_NODE_CONFIG_DIR}/master_node.env" +sed -i "s/OPENVIDU_RTC_ENGINE=.*/OPENVIDU_RTC_ENGINE=$OPENVIDU_RTC_ENGINE/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/OPENVIDU_PRO_LICENSE=.*/OPENVIDU_PRO_LICENSE=$OPENVIDU_PRO_LICENSE/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MONGO_ADMIN_USERNAME=.*/MONGO_ADMIN_USERNAME=$MONGO_ADMIN_USERNAME/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MONGO_ADMIN_PASSWORD=.*/MONGO_ADMIN_PASSWORD=$MONGO_ADMIN_PASSWORD/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MONGO_REPLICA_SET_KEY=.*/MONGO_REPLICA_SET_KEY=$MONGO_REPLICA_SET_KEY/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/DASHBOARD_ADMIN_USERNAME=.*/DASHBOARD_ADMIN_USERNAME=$DASHBOARD_ADMIN_USERNAME/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/DASHBOARD_ADMIN_PASSWORD=.*/DASHBOARD_ADMIN_PASSWORD=$DASHBOARD_ADMIN_PASSWORD/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MINIO_ACCESS_KEY=.*/MINIO_ACCESS_KEY=$MINIO_ACCESS_KEY/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MINIO_SECRET_KEY=.*/MINIO_SECRET_KEY=$MINIO_SECRET_KEY/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/GRAFANA_ADMIN_USERNAME=.*/GRAFANA_ADMIN_USERNAME=$GRAFANA_ADMIN_USERNAME/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/GRAFANA_ADMIN_PASSWORD=.*/GRAFANA_ADMIN_PASSWORD=$GRAFANA_ADMIN_PASSWORD/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/LIVEKIT_API_KEY=.*/LIVEKIT_API_KEY=$LIVEKIT_API_KEY/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/LIVEKIT_API_SECRET=.*/LIVEKIT_API_SECRET=$LIVEKIT_API_SECRET/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" +sed -i "s/MEET_INITIAL_ADMIN_USER=.*/MEET_INITIAL_ADMIN_USER=$MEET_INITIAL_ADMIN_USER/" "$${CLUSTER_CONFIG_DIR}/master_node/meet.env" +sed -i "s/MEET_INITIAL_ADMIN_PASSWORD=.*/MEET_INITIAL_ADMIN_PASSWORD=$MEET_INITIAL_ADMIN_PASSWORD/" "$${CLUSTER_CONFIG_DIR}/master_node/meet.env" +if [[ "${var.initialMeetApiKey}" != '' ]]; then + sed -i "s/MEET_INITIAL_API_KEY=.*/MEET_INITIAL_API_KEY=$MEET_INITIAL_API_KEY/" "$${CLUSTER_CONFIG_DIR}/master_node/meet.env" +fi +sed -i "s/ENABLED_MODULES=.*/ENABLED_MODULES=$ENABLED_MODULES/" "$${CLUSTER_CONFIG_DIR}/openvidu.env" + +# Update URLs in secret +OPENVIDU_URL="https://$${DOMAIN}/" +LIVEKIT_URL="wss://$${DOMAIN}/" +DASHBOARD_URL="https://$${DOMAIN}/dashboard/" +GRAFANA_URL="https://$${DOMAIN}/grafana/" +MINIO_URL="https://$${DOMAIN}/minio-console/" + +# Update shared secret +echo -n "$DOMAIN" | gcloud secrets versions add DOMAIN_NAME --data-file=- +echo -n "$OPENVIDU_URL" | gcloud secrets versions add OPENVIDU_URL --data-file=- +echo -n "$LIVEKIT_URL" | gcloud secrets versions add LIVEKIT_URL --data-file=- +echo -n "$DASHBOARD_URL" | gcloud secrets versions add DASHBOARD_URL --data-file=- +echo -n "$GRAFANA_URL" | gcloud secrets versions add GRAFANA_URL --data-file=- +echo -n "$MINIO_URL" | gcloud secrets versions add MINIO_URL --data-file=- +EOF + + update_secret_from_config_script = <<-EOF +#!/bin/bash +set -e + +# Configure gcloud with instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +# Installation directory +INSTALL_DIR="/opt/openvidu" +CLUSTER_CONFIG_DIR="$${INSTALL_DIR}/config/cluster" +MASTER_NODE_CONFIG_DIR="$${INSTALL_DIR}/config/node" + +# Get current values of the config +REDIS_PASSWORD="$(/usr/local/bin/get_value_from_config.sh REDIS_PASSWORD "$${MASTER_NODE_CONFIG_DIR}/master_node.env")" +DOMAIN_NAME="$(/usr/local/bin/get_value_from_config.sh DOMAIN_NAME "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +LIVEKIT_TURN_DOMAIN_NAME="$(/usr/local/bin/get_value_from_config.sh LIVEKIT_TURN_DOMAIN_NAME "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +OPENVIDU_RTC_ENGINE="$(/usr/local/bin/get_value_from_config.sh OPENVIDU_RTC_ENGINE "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +OPENVIDU_PRO_LICENSE="$(/usr/local/bin/get_value_from_config.sh OPENVIDU_PRO_LICENSE "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MONGO_ADMIN_USERNAME="$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_USERNAME "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MONGO_ADMIN_PASSWORD="$(/usr/local/bin/get_value_from_config.sh MONGO_ADMIN_PASSWORD "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MONGO_REPLICA_SET_KEY="$(/usr/local/bin/get_value_from_config.sh MONGO_REPLICA_SET_KEY "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MINIO_ACCESS_KEY="$(/usr/local/bin/get_value_from_config.sh MINIO_ACCESS_KEY "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MINIO_SECRET_KEY="$(/usr/local/bin/get_value_from_config.sh MINIO_SECRET_KEY "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +DASHBOARD_ADMIN_USERNAME="$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_USERNAME "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +DASHBOARD_ADMIN_PASSWORD="$(/usr/local/bin/get_value_from_config.sh DASHBOARD_ADMIN_PASSWORD "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +GRAFANA_ADMIN_USERNAME="$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_USERNAME "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +GRAFANA_ADMIN_PASSWORD="$(/usr/local/bin/get_value_from_config.sh GRAFANA_ADMIN_PASSWORD "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +LIVEKIT_API_KEY="$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_KEY "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +LIVEKIT_API_SECRET="$(/usr/local/bin/get_value_from_config.sh LIVEKIT_API_SECRET "$${CLUSTER_CONFIG_DIR}/openvidu.env")" +MEET_INITIAL_ADMIN_USER="$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_USER "$${CLUSTER_CONFIG_DIR}/master_node/meet.env")" +MEET_INITIAL_ADMIN_PASSWORD="$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_ADMIN_PASSWORD "$${CLUSTER_CONFIG_DIR}/master_node/meet.env")" +if [[ "${var.initialMeetApiKey}" != '' ]]; then + MEET_INITIAL_API_KEY="$(/usr/local/bin/get_value_from_config.sh MEET_INITIAL_API_KEY "$${CLUSTER_CONFIG_DIR}/master_node/meet.env")" +fi +ENABLED_MODULES="$(/usr/local/bin/get_value_from_config.sh ENABLED_MODULES "$${CLUSTER_CONFIG_DIR}/openvidu.env")" + +# Update shared secret +echo -n "$REDIS_PASSWORD" | gcloud secrets versions add REDIS_PASSWORD --data-file=- +echo -n "$DOMAIN_NAME" | gcloud secrets versions add DOMAIN_NAME --data-file=- +echo -n "$LIVEKIT_TURN_DOMAIN_NAME" | gcloud secrets versions add LIVEKIT_TURN_DOMAIN_NAME --data-file=- +echo -n "$OPENVIDU_RTC_ENGINE" | gcloud secrets versions add OPENVIDU_RTC_ENGINE --data-file=- +echo -n "$OPENVIDU_PRO_LICENSE" | gcloud secrets versions add OPENVIDU_PRO_LICENSE --data-file=- +echo -n "$MONGO_ADMIN_USERNAME" | gcloud secrets versions add MONGO_ADMIN_USERNAME --data-file=- +echo -n "$MONGO_ADMIN_PASSWORD" | gcloud secrets versions add MONGO_ADMIN_PASSWORD --data-file=- +echo -n "$MONGO_REPLICA_SET_KEY" | gcloud secrets versions add MONGO_REPLICA_SET_KEY --data-file=- +echo -n "$MINIO_ACCESS_KEY" | gcloud secrets versions add MINIO_ACCESS_KEY --data-file=- +echo -n "$MINIO_SECRET_KEY" | gcloud secrets versions add MINIO_SECRET_KEY --data-file=- +echo -n "$DASHBOARD_ADMIN_USERNAME" | gcloud secrets versions add DASHBOARD_ADMIN_USERNAME --data-file=- +echo -n "$DASHBOARD_ADMIN_PASSWORD" | gcloud secrets versions add DASHBOARD_ADMIN_PASSWORD --data-file=- +echo -n "$GRAFANA_ADMIN_USERNAME" | gcloud secrets versions add GRAFANA_ADMIN_USERNAME --data-file=- +echo -n "$GRAFANA_ADMIN_PASSWORD" | gcloud secrets versions add GRAFANA_ADMIN_PASSWORD --data-file=- +echo -n "$LIVEKIT_API_KEY" | gcloud secrets versions add LIVEKIT_API_KEY --data-file=- +echo -n "$LIVEKIT_API_SECRET" | gcloud secrets versions add LIVEKIT_API_SECRET --data-file=- +echo -n "$MEET_INITIAL_ADMIN_USER" | gcloud secrets versions add MEET_INITIAL_ADMIN_USER --data-file=- +echo -n "$MEET_INITIAL_ADMIN_PASSWORD" | gcloud secrets versions add MEET_INITIAL_ADMIN_PASSWORD --data-file=- +if [[ "${var.initialMeetApiKey}" != '' ]]; then + echo -n "$MEET_INITIAL_API_KEY" | gcloud secrets versions add MEET_INITIAL_API_KEY --data-file=- +fi +echo -n "$ENABLED_MODULES" | gcloud secrets versions add ENABLED_MODULES --data-file=- +EOF + + get_value_from_config_script = <<-EOF +#!/bin/bash -x +set -e + +# Function to get the value of a given key from the environment file +get_value() { + local key="$1" + local file_path="$2" + # Use grep to find the line with the key, ignoring lines starting with # + # Use awk to split on '=' and print the second field, which is the value + local value=$(grep -E "^\s*$key\s*=" "$file_path" | awk -F= '{print $2}' | sed 's/#.*//; s/^\s*//; s/\s*$//') + # If the value is empty, return "none" + if [ -z "$value" ]; then + echo "none" + else + echo "$value" + fi +} + +# Check if the correct number of arguments are supplied +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Get the key and file path from the arguments +key="$1" +file_path="$2" + +# Get and print the value +get_value "$key" "$file_path" + EOF + + store_secret_script = <<-EOF +#!/bin/bash +set -e + +# Authenticate using instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +# Modes: save, generate +# save mode: save the secret in the secret manager +# generate mode: generate a random password and save it in the secret manager +MODE="$1" +if [[ "$MODE" == "generate" ]]; then + SECRET_KEY_NAME="$2" + PREFIX="$${3:-}" + LENGTH="$${4:-44}" + RANDOM_PASSWORD="$(openssl rand -base64 64 | tr -d '+/=\n' | cut -c -$${LENGTH})" + RANDOM_PASSWORD="$${PREFIX}$${RANDOM_PASSWORD}" + echo -n "$RANDOM_PASSWORD" | gcloud secrets versions add $SECRET_KEY_NAME --data-file=- + if [[ $? -ne 0 ]]; then + echo "Error generating secret" + fi + echo "$RANDOM_PASSWORD" +elif [[ "$MODE" == "save" ]]; then + SECRET_KEY_NAME="$2" + SECRET_VALUE="$3" + echo -n "$SECRET_VALUE" | gcloud secrets versions add $SECRET_KEY_NAME --data-file=- + if [[ $? -ne 0 ]]; then + echo "Error generating secret" + fi + echo "$SECRET_VALUE" +else + exit 1 +fi +EOF + + check_app_ready_script = <<-EOF +#!/bin/bash +while true; do + HTTP_STATUS=$(curl -Ik http://localhost:7880 | head -n1 | awk '{print $2}') + if [ $HTTP_STATUS == 200 ]; then + break + fi + sleep 5 +done +EOF + + restart_script = <<-EOF +#!/bin/bash -x +set -e + +# Stop all services +systemctl stop openvidu + +# Update config from secret +/usr/local/bin/update_config_from_secret.sh + +# Start all services +systemctl start openvidu +EOF + + user_data_master = <<-EOF +#!/bin/bash -x +set -eu -o pipefail + +# restart.sh +cat > /usr/local/bin/restart.sh << 'RESTART_EOF' +${local.restart_script} +RESTART_EOF +chmod +x /usr/local/bin/restart.sh + +# Check if installation already completed +if [ -f /usr/local/bin/openvidu_install_counter.txt ]; then + # Launch on reboot + /usr/local/bin/restart.sh || { echo "[OpenVidu] error restarting OpenVidu"; exit 1; } +else + # install.sh + cat > /usr/local/bin/install.sh << 'INSTALL_EOF' +${local.install_script_master} +INSTALL_EOF + chmod +x /usr/local/bin/install.sh + + # after_install.sh + cat > /usr/local/bin/after_install.sh << 'AFTER_INSTALL_EOF' +${local.after_install_script} +AFTER_INSTALL_EOF + chmod +x /usr/local/bin/after_install.sh + + # update_config_from_secret.sh + cat > /usr/local/bin/update_config_from_secret.sh << 'UPDATE_CONFIG_EOF' +${local.update_config_from_secret_script} +UPDATE_CONFIG_EOF + chmod +x /usr/local/bin/update_config_from_secret.sh + + # update_secret_from_config.sh + cat > /usr/local/bin/update_secret_from_config.sh << 'UPDATE_SECRET_EOF' +${local.update_secret_from_config_script} +UPDATE_SECRET_EOF + chmod +x /usr/local/bin/update_secret_from_config.sh + + # get_value_from_config.sh + cat > /usr/local/bin/get_value_from_config.sh << 'GET_VALUE_EOF' +${local.get_value_from_config_script} +GET_VALUE_EOF + chmod +x /usr/local/bin/get_value_from_config.sh + + # store_secret.sh + cat > /usr/local/bin/store_secret.sh << 'STORE_SECRET_EOF' +${local.store_secret_script} +STORE_SECRET_EOF + chmod +x /usr/local/bin/store_secret.sh + + # check_app_ready.sh + cat > /usr/local/bin/check_app_ready.sh << 'CHECK_APP_EOF' +${local.check_app_ready_script} +CHECK_APP_EOF + chmod +x /usr/local/bin/check_app_ready.sh + + # config_s3.sh + cat > /usr/local/bin/config_s3.sh << 'CONFIG_S3_EOF' +${local.config_s3_script} +CONFIG_S3_EOF + chmod +x /usr/local/bin/config_s3.sh + + + apt-get update && apt-get install -y + + # 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 + fi + + # Authenticate with gcloud using instance service account + gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google") + gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") + + export HOME="/root" + + # Install OpenVidu + /usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu"; exit 1; } + + # Config S3 bucket + /usr/local/bin/config_s3.sh || { echo "[OpenVidu] error configuring S3 bucket"; exit 1; } + + # Start OpenVidu + systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; } + + # Update shared secret + /usr/local/bin/after_install.sh || { echo "[OpenVidu] error updating shared secret"; exit 1; } + + # restart.sh + echo "@reboot /usr/local/bin/restart.sh >> /var/log/openvidu-restart.log" 2>&1 | crontab + + # Mark installation as complete + echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt +fi + +# Wait for the app +/usr/local/bin/check_app_ready.sh +EOF + + install_script_media = <<-EOF +#!/bin/bash -x +set -e + +# Install dependencies +apt-get update && apt-get install -y \ + curl \ + unzip \ + jq \ + wget \ + ca-certificates \ + gnupg \ + lsb-release \ + openssl + +# Configure gcloud with instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true + +METADATA_URL="http://metadata.google.internal/computeMetadata/v1" +get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } + +# Get metadata +MASTER_NODE_PRIVATE_IP=$(get_meta "instance/attributes/masterNodePrivateIP") +STACK_NAME=$(get_meta "instance/attributes/stackName") +PRIVATE_IP=$(get_meta "instance/network-interfaces/0/ip") + +# Wait for master node to be ready by checking secrets +while ! gcloud secrets versions access latest --secret=ALL_SECRETS_GENERATED 2>/dev/null; do + echo "Waiting for master node to initialize secrets..." + sleep 10 +done + +# Get all necessary values from secrets +DOMAIN=$(gcloud secrets versions access latest --secret=DOMAIN_NAME) +OPENVIDU_PRO_LICENSE=$(gcloud secrets versions access latest --secret=OPENVIDU_PRO_LICENSE) +REDIS_PASSWORD=$(gcloud secrets versions access latest --secret=REDIS_PASSWORD) + +# Get OpenVidu version from secret +OPENVIDU_VERSION=$(gcloud secrets versions access latest --secret=OPENVIDU_VERSION) + +if [[ "$OPENVIDU_VERSION" == "none" ]]; then + echo "OpenVidu version not found" + exit 1 +fi + +# Build install command for media node +INSTALL_COMMAND="sh <(curl -fsSL http://get.openvidu.io/pro/elastic/$OPENVIDU_VERSION/install_ov_media_node.sh)" + +# Media node arguments +COMMON_ARGS=( + "--no-tty" + "--install" + "--environment=gcp" + "--deployment-type=elastic" + "--node-role=media-node" + "--master-node-private-ip=$MASTER_NODE_PRIVATE_IP" + "--private-ip=$PRIVATE_IP" + "--redis-password=$REDIS_PASSWORD" +) + +# Construct the final command +FINAL_COMMAND="$INSTALL_COMMAND $(printf "%s " "$${COMMON_ARGS[@]}")" + +# Execute installation +exec bash -c "$FINAL_COMMAND" +EOF + + graceful_shutdown_script = <<-EOF +#!/bin/bash -x +set -e + +echo "Starting graceful shutdown of OpenVidu Media Node..." + +INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/name" -H "Metadata-Flavor: Google") +PROJECT_ID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") +ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/zone" -H "Metadata-Flavor: Google" | cut -d'/' -f4) + +# Execute if docker is installed +if [ -x "$(command -v docker)" ]; then + + echo "Stopping media node services and waiting for termination..." + docker container kill --signal=SIGQUIT openvidu || true + docker container kill --signal=SIGQUIT ingress || true + docker container kill --signal=SIGQUIT egress || true + for agent_container in $(docker ps --filter "label=openvidu-agent=true" --format '{{.Names}}'); do + docker container kill --signal=SIGQUIT "$agent_container" + done + + # Wait for running containers to not be openvidu, ingress, egress or an openvidu agent + while [ $(docker ps --filter "label=openvidu-agent=true" -q | wc -l) -gt 0 ] || \ + [ $(docker inspect -f '{{.State.Running}}' openvidu 2>/dev/null) == "true" ] || \ + [ $(docker inspect -f '{{.State.Running}}' ingress 2>/dev/null) == "true" ] || \ + [ $(docker inspect -f '{{.State.Running}}' egress 2>/dev/null) == "true" ]; do + echo "Waiting for containers to stop..." + sleep 10 + done +fi + +# Self-delete using gcloud API + +# Delete this instance +gcloud compute instances delete "$INSTANCE_NAME" \ + --zone="$ZONE" \ + --project="$PROJECT_ID" \ + --quiet \ + --no-user-output-enabled || echo "Failed to self-delete, instance may already be terminating" + +echo "Graceful shutdown completed." +EOF + + crontab_job_media = <<-EOF +#!/bin/bash -x +set -e + +# Get current instance details +INSTANCE_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/name" -H "Metadata-Flavor: Google") +PROJECT_ID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") +ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/zone" -H "Metadata-Flavor: Google" | cut -d'/' -f4) +STACK_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/stackName" -H "Metadata-Flavor: Google") + +# Check if instance is still managed by the MIG +MIG_NAME=$(echo "${var.stackName}" | tr '[:upper:]' '[:lower:]')-media-node-group +REGION=$(echo $ZONE | sed 's/-[a-z]$//') + +# Check if this instance is still part of the MIG +INSTANCE_IN_MIG=$(gcloud compute instance-groups managed list-instances "$MIG_NAME" --region="$REGION" --project="$PROJECT_ID" --format="value(NAME)" | grep "$INSTANCE_NAME" || echo "") + +if [ -n "$INSTANCE_IN_MIG" ]; then + echo "Instance $INSTANCE_NAME is still managed by MIG. Continuing normal operation..." + exit 0 +else + echo "Instance $INSTANCE_NAME has been abandoned from MIG. Starting graceful shutdown..." + /usr/local/bin/graceful_shutdown.sh +fi +EOF + + user_data_media = <<-EOF +#!/bin/bash -x +set -eu -o pipefail + +# install.sh (media node) +cat > /usr/local/bin/install.sh << 'INSTALL_EOF' +${local.install_script_media} +INSTALL_EOF +chmod +x /usr/local/bin/install.sh + +# graceful_shutdown.sh +cat > /usr/local/bin/graceful_shutdown.sh << 'GRACEFUL_SHUTDOWN_EOF' +${local.graceful_shutdown_script} +GRACEFUL_SHUTDOWN_EOF +chmod +x /usr/local/bin/graceful_shutdown.sh + +apt-get update && apt-get install -y + +# 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 +fi + +# Authenticate with gcloud using instance service account +gcloud auth activate-service-account --key-file=/dev/null 2>/dev/null || true +gcloud config set account $(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email" -H "Metadata-Flavor: Google") +gcloud config set project $(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google") + +export HOME="/root" + +# Install OpenVidu Media Node +/usr/local/bin/install.sh || { echo "[OpenVidu] error installing OpenVidu Media Node"; exit 1; } + +# Mark installation as complete +echo "installation_complete" > /usr/local/bin/openvidu_install_counter.txt + +# Start OpenVidu +systemctl start openvidu || { echo "[OpenVidu] error starting OpenVidu"; exit 1; } + +# Add cron job to check if instance is abandoned every minute +cat > /usr/local/bin/check_abandoned.sh << 'CHECK_ABANDONED_EOF' +${local.crontab_job_media} +CHECK_ABANDONED_EOF +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 - +EOF +} diff --git a/openvidu-deployment/pro/elastic/gcp/variables.tf b/openvidu-deployment/pro/elastic/gcp/variables.tf new file mode 100644 index 000000000..29736d9e0 --- /dev/null +++ b/openvidu-deployment/pro/elastic/gcp/variables.tf @@ -0,0 +1,180 @@ +# ------------------------- variables ------------------------- + +# Variables used by the configuration +variable "projectId" { + description = "GCP project id where the resourw es will be created." + type = string +} + +variable "region" { + description = "GCP region where resources will be created." + type = string + default = "europe-west2" +} + +variable "zone" { + description = "GCP zone that some resources will use." + type = string + default = "europe-west2-b" +} + +variable "stackName" { + description = "Stack name for OpenVidu deployment." + type = string +} + +variable "certificateType" { + description = "[selfsigned] Not recommended for production use. Just for testing purposes or development environments. You don't need a FQDN to use this option. [owncert] Valid for production environments. Use your own certificate. You need a FQDN to use this option. [letsencrypt] Valid for production environments. Can be used with or without a FQDN (if no FQDN is provided, a random sslip.io domain will be used)." + type = string + default = "letsencrypt" + validation { + condition = contains(["selfsigned", "owncert", "letsencrypt"], var.certificateType) + error_message = "certificateType must be one of: selfsigned, owncert, letsencrypt" + } +} + +variable "publicIpAddress" { + description = "Previously created Public IP address for the OpenVidu Deployment. Blank will generate a public IP." + type = string + default = "" + validation { + condition = can(regex("^$|^([01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.([01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.([01]?\\d{1,2}|2[0-4]\\d|25[0-5])\\.([01]?\\d{1,2}|2[0-4]\\d|25[0-5])$", var.publicIpAddress)) + error_message = "The Public Elastic IP does not have a valid IPv4 format" + } +} + +variable "domainName" { + description = "Domain name for the OpenVidu Deployment." + type = string + default = "" + validation { + condition = can(regex("^$|^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$", var.domainName)) + error_message = "The domain name does not have a valid domain name format" + } +} + +variable "ownPublicCertificate" { + description = "If certificate type is 'owncert', this parameter will be used to specify the public certificate" + type = string + default = "" +} + +variable "ownPrivateCertificate" { + description = "If certificate type is 'owncert', this parameter will be used to specify the private certificate" + type = string + default = "" +} + +variable "initialMeetAdminPassword" { + description = "Initial password for the 'admin' user in OpenVidu Meet. If not provided, a random password will be generated." + type = string + default = "" + validation { + condition = can(regex("^[A-Za-z0-9_-]*$", var.initialMeetAdminPassword)) + error_message = "Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to generate a random password." + } +} + +variable "initialMeetApiKey" { + description = "Initial API key for OpenVidu Meet. If not provided, no API key will be set and the user can set it later from Meet Console." + type = string + default = "" + validation { + condition = can(regex("^[A-Za-z0-9_-]*$", var.initialMeetApiKey)) + error_message = "Must contain only alphanumeric characters (A-Z, a-z, 0-9). Leave empty to not set an initial API key." + } +} + +variable "masterNodeInstanceType" { + description = "Specifies the GCE machine type for your OpenVidu Master Node" + type = string + default = "e2-standard-2" + validation { + condition = can(regex("^(e2-(micro|small|medium|standard-[2-9]|standard-1[0-6]|highmem-[2-9]|highmem-1[0-6]|highcpu-[2-9]|highcpu-1[0-6])|n1-(standard-[1-9]|standard-[1-9][0-9]|highmem-[2-9]|highmem-[1-9][0-9]|highcpu-[1-9]|highcpu-[1-9][0-9])|n2-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-2][0-8]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-2][0-8]|highcpu-[1-9][0-9]|highcpu-1[0-2][0-8])|n2d-(standard-[2-9]|standard-[1-9][0-9]|standard-2[0-2][0-4]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-9[0-6]|highcpu-[1-9][0-9]|highcpu-2[0-2][0-4])|c2-(standard-[4-9]|standard-[1-5][0-9]|standard-60)|c2d-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-1][0-2]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-1][0-2]|highcpu-[1-9][0-9]|highcpu-1[0-1][0-2])|m1-(ultramem-[4-9][0-9]|ultramem-160)|m2-(ultramem-208|ultramem-416|megamem-416)|m3-(ultramem-32|ultramem-64|ultramem-128|megamem-64|megamem-128)|a2-(standard-[1-9]|standard-[1-9][0-9]|standard-96|highmem-1g|ultramem-1g|megamem-1g)|a3-(standard-[1-9]|standard-[1-9][0-9]|standard-80|highmem-1g|megamem-1g)|g2-(standard-[4-9]|standard-[1-9][0-9]|standard-96)|t2d-(standard-[1-9]|standard-[1-9][0-9]|standard-60)|t2a-(standard-[1-9]|standard-[1-9][0-9]|standard-48)|h3-(standard-88)|f1-(micro)|t4g-(micro|small|medium|standard-[1-9]|standard-[1-9][0-9]))$", var.masterNodeInstanceType)) + error_message = "The instance type is not valid" + } +} + +variable "mediaNodeInstanceType" { + description = "Specifies the GCE machine type for your OpenVidu Media Nodes" + type = string + default = "e2-standard-2" + validation { + condition = can(regex("^(e2-(micro|small|medium|standard-[2-9]|standard-1[0-6]|highmem-[2-9]|highmem-1[0-6]|highcpu-[2-9]|highcpu-1[0-6])|n1-(standard-[1-9]|standard-[1-9][0-9]|highmem-[2-9]|highmem-[1-9][0-9]|highcpu-[1-9]|highcpu-[1-9][0-9])|n2-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-2][0-8]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-2][0-8]|highcpu-[1-9][0-9]|highcpu-1[0-2][0-8])|n2d-(standard-[2-9]|standard-[1-9][0-9]|standard-2[0-2][0-4]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-9[0-6]|highcpu-[1-9][0-9]|highcpu-2[0-2][0-4])|c2-(standard-[4-9]|standard-[1-5][0-9]|standard-60)|c2d-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-1][0-2]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-1][0-2]|highcpu-[1-9][0-9]|highcpu-1[0-1][0-2])|m1-(ultramem-[4-9][0-9]|ultramem-160)|m2-(ultramem-208|ultramem-416|megamem-416)|m3-(ultramem-32|ultramem-64|ultramem-128|megamem-64|megamem-128)|a2-(standard-[1-9]|standard-[1-9][0-9]|standard-96|highmem-1g|ultramem-1g|megamem-1g)|a3-(standard-[1-9]|standard-[1-9][0-9]|standard-80|highmem-1g|megamem-1g)|g2-(standard-[4-9]|standard-[1-9][0-9]|standard-96)|t2d-(standard-[1-9]|standard-[1-9][0-9]|standard-60)|t2a-(standard-[1-9]|standard-[1-9][0-9]|standard-48)|h3-(standard-88)|f1-(micro)|t4g-(micro|small|medium|standard-[1-9]|standard-[1-9][0-9]))$", var.mediaNodeInstanceType)) + error_message = "The instance type is not valid" + } +} + +variable "initialNumberOfMediaNodes" { + description = "Number of initial media nodes to deploy" + type = number + default = 1 +} + +variable "minNumberOfMediaNodes" { + description = "Minimum number of media nodes to deploy" + type = number + default = 1 +} + +variable "maxNumberOfMediaNodes" { + description = "Maximum number of media nodes to deploy" + type = number + default = 2 +} + +variable "scaleTargetCPU" { + description = "Target CPU percentage to scale up or down" + type = number + default = 50 +} + +variable "bucketName" { + description = "Name of the GCS bucket to store data and recordings. If empty, a bucket will be created" + type = string + default = "" +} + +variable "openviduLicense" { + description = "Visit https://openvidu.io/account" + type = string + sensitive = true +} + +variable "rtcEngine" { + description = "RTCEngine media engine to use" + type = string + default = "pion" + validation { + condition = contains(["pion", "mediasoup"], var.rtcEngine) + error_message = "rtcEngine must be one of: pion, mediasoup" + } +} + +variable "additionalInstallFlags" { + description = "Additional optional flags to pass to the OpenVidu installer (comma-separated, e.g.,'--flag1=value, --flag2')." + type = string + default = "" + validation { + condition = can(regex("^[A-Za-z0-9, =_.\\-]*$", var.additionalInstallFlags)) + error_message = "Must be a comma-separated list of flags (for example, --flag=value, --bool-flag)." + } +} + +variable "turnDomainName" { + description = "(Optional) Domain name for the TURN server with TLS. Only needed if your users are behind restrictive firewalls" + type = string + default = "" +} + +variable "turnOwnPublicCertificate" { + description = "(Optional) This setting is applicable if the certificate type is set to 'owncert' and the TurnDomainName is specified." + type = string + default = "" +} + +variable "turnOwnPrivateCertificate" { + description = "(Optional) This setting is applicable if the certificate type is set to 'owncert' and the TurnDomainName is specified." + type = string + default = "" +} diff --git a/openvidu-deployment/pro/elastic/gcp/versions.tf b/openvidu-deployment/pro/elastic/gcp/versions.tf new file mode 100644 index 000000000..0eb8bf7d6 --- /dev/null +++ b/openvidu-deployment/pro/elastic/gcp/versions.tf @@ -0,0 +1,20 @@ +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 +} + diff --git a/openvidu-deployment/pro/singlenode/gcp/output.tf b/openvidu-deployment/pro/singlenode/gcp/output.tf index 2156ffe5c..56ad5826b 100644 --- a/openvidu-deployment/pro/singlenode/gcp/output.tf +++ b/openvidu-deployment/pro/singlenode/gcp/output.tf @@ -1,13 +1,6 @@ # ------------------------- outputs.tf ------------------------- -output "openvidu_instance_name" { - value = google_compute_instance.openvidu_server.name +output "secrets_manager" { + value = "https://console.cloud.google.com/security/secret-manager?project=${var.projectId}" } -output "openvidu_public_ip" { - value = length(google_compute_address.public_ip_address) > 0 ? google_compute_address.public_ip_address[0].address : google_compute_instance.openvidu_server.network_interface[0].access_config[0].nat_ip -} - -output "appdata_bucket" { - value = local.isEmpty ? google_storage_bucket.bucket[0].name : var.bucketName -} diff --git a/openvidu-deployment/pro/singlenode/gcp/tf-gpc-openvidu-singlenode.tf b/openvidu-deployment/pro/singlenode/gcp/tf-gpc-openvidu-singlenode.tf index bac808a7f..3159e6e84 100644 --- a/openvidu-deployment/pro/singlenode/gcp/tf-gpc-openvidu-singlenode.tf +++ b/openvidu-deployment/pro/singlenode/gcp/tf-gpc-openvidu-singlenode.tf @@ -8,6 +8,24 @@ resource "google_project_service" "cloudresourcemanager_api" { service = "cloudr resource "random_id" "bucket_suffix" { byte_length = 3 } +# Secret Manager secrets for OpenVidu deployment information +resource "google_secret_manager_secret" "openvidu_shared_info" { + for_each = toset([ + "OPENVIDU_URL", "MEET_INITIAL_ADMIN_USER", "MEET_INITIAL_ADMIN_PASSWORD", + "MEET_INITIAL_API_KEY", "LIVEKIT_URL", "LIVEKIT_API_KEY", "LIVEKIT_API_SECRET", + "DASHBOARD_URL", "GRAFANA_URL", "MINIO_URL", "DOMAIN_NAME", "LIVEKIT_TURN_DOMAIN_NAME", + "OPENVIDU_PRO_LICENSE", "OPENVIDU_RTC_ENGINE", "REDIS_PASSWORD", "MONGO_ADMIN_USERNAME", + "MONGO_ADMIN_PASSWORD", "MONGO_REPLICA_SET_KEY", "MINIO_ACCESS_KEY", "MINIO_SECRET_KEY", + "DASHBOARD_ADMIN_USERNAME", "DASHBOARD_ADMIN_PASSWORD", "GRAFANA_ADMIN_USERNAME", + "GRAFANA_ADMIN_PASSWORD", "ENABLED_MODULES" + ]) + + secret_id = each.key + replication { + auto {} + } +} + # GCS bucket resource "google_storage_bucket" "bucket" { count = 1 @@ -56,7 +74,7 @@ resource "google_compute_firewall" "firewall" { } source_ranges = ["0.0.0.0/0"] - target_tags = [lower("${var.stackName}-vm-ce")] + target_tags = [lower("${var.stackName}-vm-pro")] } # Create Public Ip address (if not provided) @@ -68,11 +86,11 @@ resource "google_compute_address" "public_ip_address" { # Compute instance for OpenVidu resource "google_compute_instance" "openvidu_server" { - name = lower("${var.stackName}-vm-ce") + name = lower("${var.stackName}-vm-pro") machine_type = var.instanceType zone = var.zone - tags = [lower("${var.stackName}-vm-ce")] + tags = [lower("${var.stackName}-vm-pro")] boot_disk { initialize_params { @@ -149,33 +167,6 @@ get_meta() { curl -s -H "Metadata-Flavor: Google" "$${METADATA_URL}/$1"; } # Create counter file for tracking script executions echo 1 > /usr/local/bin/openvidu_install_counter.txt -# Create all the secrets -gcloud secrets create OPENVIDU_URL --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_ADMIN_USER --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create MEET_INITIAL_API_KEY --replication-policy=automatic || true -gcloud secrets create LIVEKIT_URL --replication-policy=automatic || true -gcloud secrets create LIVEKIT_API_KEY --replication-policy=automatic || true -gcloud secrets create LIVEKIT_API_SECRET --replication-policy=automatic || true -gcloud secrets create DASHBOARD_URL --replication-policy=automatic || true -gcloud secrets create GRAFANA_URL --replication-policy=automatic || true -gcloud secrets create MINIO_URL --replication-policy=automatic || true -gcloud secrets create DOMAIN_NAME --replication-policy=automatic || true -gcloud secrets create LIVEKIT_TURN_DOMAIN_NAME --replication-policy=automatic || true -gcloud secrets create OPENVIDU_PRO_LICENSE --replication-policy=automatic || true -gcloud secrets create OPENVIDU_RTC_ENGINE --replication-policy=automatic || true -gcloud secrets create REDIS_PASSWORD --replication-policy=automatic || true -gcloud secrets create MONGO_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create MONGO_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create MONGO_REPLICA_SET_KEY --replication-policy=automatic || true -gcloud secrets create MINIO_ACCESS_KEY --replication-policy=automatic || true -gcloud secrets create MINIO_SECRET_KEY --replication-policy=automatic || true -gcloud secrets create DASHBOARD_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create DASHBOARD_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create GRAFANA_ADMIN_USERNAME --replication-policy=automatic || true -gcloud secrets create GRAFANA_ADMIN_PASSWORD --replication-policy=automatic || true -gcloud secrets create ENABLED_MODULES --replication-policy=automatic || true - # Configure domain if [[ "${var.domainName}" == "" ]]; then [ ! -d "/usr/share/openvidu" ] && mkdir -p /usr/share/openvidu diff --git a/openvidu-deployment/pro/singlenode/gcp/variables.tf b/openvidu-deployment/pro/singlenode/gcp/variables.tf index 791acb862..d952384fb 100644 --- a/openvidu-deployment/pro/singlenode/gcp/variables.tf +++ b/openvidu-deployment/pro/singlenode/gcp/variables.tf @@ -88,7 +88,7 @@ variable "initialMeetApiKey" { variable "instanceType" { description = "Specifies the GCE machine type for your OpenVidu instance" type = string - default = "e2-standard-8" + default = "e2-standard-2" validation { condition = can(regex("^(e2-(micro|small|medium|standard-[2-9]|standard-1[0-6]|highmem-[2-9]|highmem-1[0-6]|highcpu-[2-9]|highcpu-1[0-6])|n1-(standard-[1-9]|standard-[1-9][0-9]|highmem-[2-9]|highmem-[1-9][0-9]|highcpu-[1-9]|highcpu-[1-9][0-9])|n2-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-2][0-8]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-2][0-8]|highcpu-[1-9][0-9]|highcpu-1[0-2][0-8])|n2d-(standard-[2-9]|standard-[1-9][0-9]|standard-2[0-2][0-4]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-9[0-6]|highcpu-[1-9][0-9]|highcpu-2[0-2][0-4])|c2-(standard-[4-9]|standard-[1-5][0-9]|standard-60)|c2d-(standard-[2-9]|standard-[1-9][0-9]|standard-1[0-1][0-2]|highmem-[2-9]|highmem-[1-9][0-9]|highmem-1[0-1][0-2]|highcpu-[1-9][0-9]|highcpu-1[0-1][0-2])|m1-(ultramem-[4-9][0-9]|ultramem-160)|m2-(ultramem-208|ultramem-416|megamem-416)|m3-(ultramem-32|ultramem-64|ultramem-128|megamem-64|megamem-128)|a2-(standard-[1-9]|standard-[1-9][0-9]|standard-96|highmem-1g|ultramem-1g|megamem-1g)|a3-(standard-[1-9]|standard-[1-9][0-9]|standard-80|highmem-1g|megamem-1g)|g2-(standard-[4-9]|standard-[1-9][0-9]|standard-96)|t2d-(standard-[1-9]|standard-[1-9][0-9]|standard-60)|t2a-(standard-[1-9]|standard-[1-9][0-9]|standard-48)|h3-(standard-88)|f1-(micro)|t4g-(micro|small|medium|standard-[1-9]|standard-[1-9][0-9]))$", var.instanceType)) error_message = "The instance type is not valid" @@ -101,6 +101,22 @@ variable "bucketName" { default = "" } +variable "openviduLicense" { + description = "Visit https://openvidu.io/account" + type = string + sensitive = true +} + +variable "RTCEngine" { + description = "RTCEngine media engine to use. Allowed values are 'pion' and 'mediasoup'." + type = string + default = "pion" + validation { + condition = contains(["pion", "mediasoup"], var.RTCEngine) + error_message = "RTCEngine must be one of: pion, mediasoup" + } +} + variable "additionalInstallFlags" { description = "Additional optional flags to pass to the OpenVidu installer (comma-separated, e.g.,'--flag1=value, --flag2')." type = string @@ -128,23 +144,3 @@ variable "turnOwnPrivateCertificate" { type = string default = "" } - -variable "openviduLicense" { - description = "Visit https://openvidu.io/account" - type = string - validation { - condition = can(regex("^(?!\\s*$).+$", var.openviduLicense)) - error_message = "OpenVidu Pro License is mandatory." - } - sensitive = true -} - -variable "RTCEngine" { - description = "RTCEngine media engine to use. Allowed values are 'pion' and 'mediasoup'." - type = string - default = "pion" - validation { - condition = contains(["pion", "mediasoup"], var.RTCEngine) - error_message = "RTCEngine must be one of: pion, mediasoup" - } -} \ No newline at end of file