From 9c7cd5d038d5590f6677670037c2a2a862983cb5 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 12 May 2026 19:53:02 -0700 Subject: [PATCH 1/9] futu openD support --- terraform/gcp_cloudrun/.envrc | 2 + terraform/gcp_cloudrun/futu-opend-startup.sh | 55 +++++++++++ terraform/gcp_cloudrun/futu-skills-install.sh | 93 +++++++++++++++++++ terraform/gcp_cloudrun/main.tf | 35 ++++++- terraform/gcp_cloudrun/platform.tf | 58 +++++++++++- terraform/gcp_cloudrun/provider.tf | 4 + terraform/gcp_cloudrun/secrets.tf | 14 +++ terraform/gcp_cloudrun/variables.tf | 15 +++ 8 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 terraform/gcp_cloudrun/futu-opend-startup.sh create mode 100644 terraform/gcp_cloudrun/futu-skills-install.sh diff --git a/terraform/gcp_cloudrun/.envrc b/terraform/gcp_cloudrun/.envrc index e257fa0..9c73e27 100644 --- a/terraform/gcp_cloudrun/.envrc +++ b/terraform/gcp_cloudrun/.envrc @@ -24,3 +24,5 @@ export TF_VAR_cloudflare_api_token="${CF_API_TOKEN:-}" export TF_VAR_r2_access_key_id="${R2_ACCESS_KEY_ID:-}" export TF_VAR_r2_secret_access_key="${R2_SECRET_ACCESS_KEY:-}" export TF_VAR_r2_bucket_name="${R2_BUCKET_NAME:-}" +export TF_VAR_futu_account="${FUTU_ACCOUNT:-}" +export TF_VAR_futu_password_md5="${FUTU_PASSWORD_MD5:-}" diff --git a/terraform/gcp_cloudrun/futu-opend-startup.sh b/terraform/gcp_cloudrun/futu-opend-startup.sh new file mode 100644 index 0000000..150d69e --- /dev/null +++ b/terraform/gcp_cloudrun/futu-opend-startup.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e + +apt-get update -y +apt-get install -y curl ca-certificates libatomic1 + +mkdir -p /opt/opend +cd /opt/opend +curl -fsSL "https://www.futunn.com/download/fetch-lasted-link?name=opend-ubuntu" \ + -o opend.tar.gz +tar -xzf opend.tar.gz --strip-components=2 --exclude='*GUI*' +rm opend.tar.gz +chmod +x FutuOpenD + +mkdir -p /root/.com.futunn.FutuOpenD/F3CNN + +# Write RSA private key as PKCS#1 (FutuOpenD -rsa_private_key requires -----BEGIN RSA PRIVATE KEY-----) +# tls_private_key generates PKCS#8; convert using openssl rsa (always outputs PKCS#1 traditional format) +cat > /root/futu-rsa-private.pem << RSAEOF +${futu_rsa_private_key} +RSAEOF +openssl rsa -in /root/futu-rsa-private.pem -out /root/futu-rsa-private.pem 2>/dev/null || true +chmod 600 /root/futu-rsa-private.pem + +cat > /etc/systemd/system/futu-opend.service << 'SVCEOF' +[Unit] +Description=Futu OpenD +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=10 +WorkingDirectory=/opt/opend +Environment=LD_LIBRARY_PATH=/opt/opend +ExecStart=/opt/opend/FutuOpenD \ + -login_account=${futu_account} \ + -login_pwd_md5=${futu_password_md5} \ + -api_ip=0.0.0.0 \ + -api_port=11111 \ + -telnet_ip=127.0.0.1 \ + -telnet_port=22222 \ + -rsa_private_key=/root/futu-rsa-private.pem \ + -lang=en +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target +SVCEOF + +systemctl daemon-reload +systemctl enable futu-opend.service +systemctl start futu-opend.service diff --git a/terraform/gcp_cloudrun/futu-skills-install.sh b/terraform/gcp_cloudrun/futu-skills-install.sh new file mode 100644 index 0000000..2852077 --- /dev/null +++ b/terraform/gcp_cloudrun/futu-skills-install.sh @@ -0,0 +1,93 @@ +#!/bin/sh +mkdir -p /home/node/.openclaw/skills + +if [ ! -d /home/node/.openclaw/skills/futuapi ]; then + curl -fsSL https://openapi.futunn.com/skills/opend-skills.zip -o /tmp/fs.zip \ + && mkdir -p /tmp/fs-extract \ + && node -e " + const fs=require('fs'),zl=require('zlib'); + try { + const d=fs.readFileSync('/tmp/fs.zip'); + let o=0; + while(o/dev/null + rm -rf /tmp/fs.zip /tmp/fs-extract +fi + +mkdir -p /home/node/.openclaw/workspace +ln -sf /home/node/.openclaw/skills /home/node/.openclaw/workspace/skills 2>/dev/null || true +mkdir -p /home/node/.local/bin +curl -LsSf https://astral.sh/uv/install.sh \ + | UV_INSTALL_DIR=/home/node/.local/bin sh +UV=/home/node/.local/bin/uv +$UV python install 3.11 +$UV venv /home/node/.futu-venv --python 3.11 +$UV pip install futu-api --python /home/node/.futu-venv/bin/python +printf '#!/bin/sh\nexec /home/node/.futu-venv/bin/python "$@"\n' > /home/node/.local/bin/python +printf '#!/bin/sh\nexec /home/node/.futu-venv/bin/python "$@"\n' > /home/node/.local/bin/python3 +chmod +x /home/node/.local/bin/python /home/node/.local/bin/python3 +export PATH=/home/node/.local/bin:$PATH +export PYTHONPATH=/home/node/.futu-venv/lib/python3.11/site-packages${PYTHONPATH:+:$PYTHONPATH} + +# Write RSA private key as PKCS#1 (futu SDK requires -----BEGIN RSA PRIVATE KEY-----) +# tls_private_key generates PKCS#8; convert using Python cryptography +if [ -n "$FUTU_RSA_PRIVATE_KEY" ]; then + mkdir -p /home/node/.openclaw/credentials + printf '%s' "$FUTU_RSA_PRIVATE_KEY" | \ + /home/node/.futu-venv/bin/python3 -c " +import sys +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.backends import default_backend +data = sys.stdin.buffer.read() +key = serialization.load_pem_private_key(data, password=None, backend=default_backend()) +sys.stdout.buffer.write(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() +)) +" > /home/node/.openclaw/credentials/futu-rsa-private.pem 2>/dev/null || \ + printf '%s' "$FUTU_RSA_PRIVATE_KEY" > /home/node/.openclaw/credentials/futu-rsa-private.pem + chmod 600 /home/node/.openclaw/credentials/futu-rsa-private.pem +fi + +# Patch common.py: remove any old RSA block and append the current version +sed -i '/^# RSA encryption for cross-network trade connections/,$d' \ + /home/node/.openclaw/skills/futuapi/scripts/common.py 2>/dev/null || true +cat >> /home/node/.openclaw/skills/futuapi/scripts/common.py << 'PYEOF' + +# RSA encryption for cross-network trade connections +_futu_rsa_key_file = os.path.expanduser('~/.openclaw/credentials/futu-rsa-private.pem') +if os.path.exists(_futu_rsa_key_file): + try: + from futu import SysConfig + SysConfig.set_init_rsa_file(_futu_rsa_key_file) + except Exception: + pass + _orig_create_trade_context = create_trade_context + def create_trade_context(market=None, security_firm=None): + host, port = get_opend_config() + _check_opend_alive(host, port) + trd_market = parse_market(market) if market else get_default_market() + kwargs = dict(host=host, port=port, filter_trdmarket=trd_market, is_encrypt=True) + if _sdk_supports_ai_type: + kwargs["ai_type"] = 1 + sf = security_firm if security_firm is not None else (get_default_security_firm() or SecurityFirm.NONE) + kwargs["security_firm"] = sf + return OpenSecTradeContext(**kwargs) +PYEOF diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index 2206b0e..854dc81 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -13,6 +13,17 @@ resource "google_cloud_run_v2_service" "openclaw" { max_instance_count = var.max_instances } + dynamic "vpc_access" { + for_each = local.futu_enabled ? [1] : [] + content { + network_interfaces { + network = "default" + subnetwork = "default" + } + egress = "PRIVATE_RANGES_ONLY" + } + } + containers { name = "rclone-sync" image = "rclone/rclone:latest" @@ -79,7 +90,7 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = ["rclone-sync"] image = local.effective_container_image command = ["/bin/sh"] - args = ["-lc", "mkdir -p /home/node/.openclaw/agents/main/agent /home/node/.openclaw/credentials; [ -n \"$OPENCLAW_JSON\" ] && echo \"$OPENCLAW_JSON\" > /home/node/.openclaw/openclaw.json; [ -n \"$TELEGRAM_ALLOW_FROM\" ] && echo \"$TELEGRAM_ALLOW_FROM\" > /home/node/.openclaw/credentials/telegram-allowFrom.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"OPENROUTER_API_KEY\"}}}' > /home/node/.openclaw/agents/main/agent/models.json; exec openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] + args = ["-lc", "mkdir -p /home/node/.openclaw/agents/main/agent /home/node/.openclaw/credentials; [ -n \"$OPENCLAW_JSON\" ] && printf '%s' \"$OPENCLAW_JSON\" > /home/node/.openclaw/openclaw.json; [ -n \"$TELEGRAM_ALLOW_FROM\" ] && printf '%s' \"$TELEGRAM_ALLOW_FROM\" > /home/node/.openclaw/credentials/telegram-allowFrom.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"%s\"}}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/models.json; ${local.futu_skills_install}exec openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] ports { container_port = 8080 @@ -208,6 +219,27 @@ resource "google_cloud_run_v2_service" "openclaw" { } } } + + dynamic "env" { + for_each = local.futu_enabled ? [1] : [] + content { + name = "FUTU_OPEND_HOST" + value = google_compute_instance.futu_opend[0].network_interface[0].network_ip + } + } + + dynamic "env" { + for_each = local.futu_enabled ? [1] : [] + content { + name = "FUTU_RSA_PRIVATE_KEY" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.futu_rsa_private_key[0].secret_id + version = "latest" + } + } + } + } } volumes { @@ -224,5 +256,6 @@ resource "google_cloud_run_v2_service" "openclaw" { google_project_iam_member.secret_accessor, google_secret_manager_secret_version.r2_access_key_id, google_secret_manager_secret_version.r2_secret_access_key, + google_secret_manager_secret_version.futu_rsa_private_key, ] } diff --git a/terraform/gcp_cloudrun/platform.tf b/terraform/gcp_cloudrun/platform.tf index 6c63bcf..b96e0c2 100644 --- a/terraform/gcp_cloudrun/platform.tf +++ b/terraform/gcp_cloudrun/platform.tf @@ -1,5 +1,7 @@ locals { effective_container_image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.ghcr_remote_repository_id}/${var.ghcr_image_path}:${var.ghcr_image_tag}" + futu_enabled = var.futu_account != "" && var.futu_password_md5 != "" + futu_skills_install = local.futu_enabled ? file("${path.module}/futu-skills-install.sh") : "" openclaw_json_content = templatefile("${path.module}/../shared/openclaw.json.tpl", { openclaw_gateway_token = var.openclaw_gateway_token @@ -21,7 +23,8 @@ resource "google_project_service" "required" { "artifactregistry.googleapis.com", "secretmanager.googleapis.com", "iam.googleapis.com", - "serviceusage.googleapis.com" + "serviceusage.googleapis.com", + "compute.googleapis.com" ]) project = var.project_id @@ -50,6 +53,59 @@ resource "google_artifact_registry_repository" "ghcr_remote" { depends_on = [google_project_service.required] } +resource "tls_private_key" "futu_rsa" { + count = local.futu_enabled ? 1 : 0 + algorithm = "RSA" + rsa_bits = 1024 +} + +resource "google_compute_instance" "futu_opend" { + count = local.futu_enabled ? 1 : 0 + name = "${var.service_name}-futu-opend" + machine_type = "e2-micro" + zone = "${var.region}-b" + project = var.project_id + tags = ["${var.service_name}-futu-opend"] + + boot_disk { + initialize_params { + image = "ubuntu-os-cloud/ubuntu-2204-lts" + size = 20 + type = "pd-balanced" + } + } + + network_interface { + network = "default" + access_config {} + } + + metadata = { + startup-script = templatefile("${path.module}/futu-opend-startup.sh", { + futu_account = var.futu_account + futu_password_md5 = var.futu_password_md5 + futu_rsa_private_key = tls_private_key.futu_rsa[0].private_key_pem + }) + } + + depends_on = [google_project_service.required] +} + +resource "google_compute_firewall" "futu_opend_api" { + count = local.futu_enabled ? 1 : 0 + name = "${var.service_name}-futu-opend-api" + network = "default" + project = var.project_id + + allow { + protocol = "tcp" + ports = ["11111"] + } + + source_ranges = ["10.0.0.0/8"] + target_tags = ["${var.service_name}-futu-opend"] +} + resource "google_artifact_registry_repository_iam_member" "ghcr_remote_reader" { project = var.project_id location = var.region diff --git a/terraform/gcp_cloudrun/provider.tf b/terraform/gcp_cloudrun/provider.tf index e622abf..95a5c3f 100644 --- a/terraform/gcp_cloudrun/provider.tf +++ b/terraform/gcp_cloudrun/provider.tf @@ -13,6 +13,10 @@ terraform { source = "hashicorp/local" version = "~> 2.0" } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } } } diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index c2c68cc..2364ea0 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -98,6 +98,20 @@ resource "google_secret_manager_secret_version" "telegram_allow_from" { secret_data = jsonencode({ version = 1, allowFrom = [var.telegram_owner_id] }) } +resource "google_secret_manager_secret" "futu_rsa_private_key" { + count = local.futu_enabled ? 1 : 0 + secret_id = "${var.service_name}-futu-rsa-private-key" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "futu_rsa_private_key" { + count = local.futu_enabled ? 1 : 0 + secret = google_secret_manager_secret.futu_rsa_private_key[0].id + secret_data = tls_private_key.futu_rsa[0].private_key_pem +} + resource "google_secret_manager_secret" "r2_access_key_id" { secret_id = "${var.service_name}-r2-access-key-id" replication { diff --git a/terraform/gcp_cloudrun/variables.tf b/terraform/gcp_cloudrun/variables.tf index f0a0895..53fcde7 100644 --- a/terraform/gcp_cloudrun/variables.tf +++ b/terraform/gcp_cloudrun/variables.tf @@ -104,6 +104,21 @@ variable "slack_bot_token" { sensitive = true } +# ── Futu OpenD (GCE VM) ─────────────────────────────────────── +variable "futu_account" { + description = "Futu/Moomoo login account (phone or email). Leave empty to disable Futu OpenD VM." + type = string + sensitive = true + default = "" +} + +variable "futu_password_md5" { + description = "MD5 hash of Futu login password (echo -n 'password' | md5)" + type = string + sensitive = true + default = "" +} + # ── Cloudflare R2 ───────────────────────────────────────────── variable "cloudflare_account_id" { description = "Cloudflare Account ID (visible on Dashboard sidebar)" From 1a862d964686670e28c16e14533b6f4dd4f152c3 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 12 May 2026 20:33:22 -0700 Subject: [PATCH 2/9] remove futu_enabled --- terraform/gcp_cloudrun/main.tf | 37 +++++++++++------------------- terraform/gcp_cloudrun/platform.tf | 12 ++++------ terraform/gcp_cloudrun/secrets.tf | 6 ++--- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index 854dc81..62bc41f 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -13,15 +13,12 @@ resource "google_cloud_run_v2_service" "openclaw" { max_instance_count = var.max_instances } - dynamic "vpc_access" { - for_each = local.futu_enabled ? [1] : [] - content { - network_interfaces { - network = "default" - subnetwork = "default" - } - egress = "PRIVATE_RANGES_ONLY" + vpc_access { + network_interfaces { + network = "default" + subnetwork = "default" } + egress = "PRIVATE_RANGES_ONLY" } containers { @@ -220,23 +217,17 @@ resource "google_cloud_run_v2_service" "openclaw" { } } - dynamic "env" { - for_each = local.futu_enabled ? [1] : [] - content { - name = "FUTU_OPEND_HOST" - value = google_compute_instance.futu_opend[0].network_interface[0].network_ip - } + env { + name = "FUTU_OPEND_HOST" + value = google_compute_instance.futu_opend.network_interface[0].network_ip } - dynamic "env" { - for_each = local.futu_enabled ? [1] : [] - content { - name = "FUTU_RSA_PRIVATE_KEY" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.futu_rsa_private_key[0].secret_id - version = "latest" - } + env { + name = "FUTU_RSA_PRIVATE_KEY" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.futu_rsa_private_key.secret_id + version = "latest" } } } diff --git a/terraform/gcp_cloudrun/platform.tf b/terraform/gcp_cloudrun/platform.tf index b96e0c2..7c35506 100644 --- a/terraform/gcp_cloudrun/platform.tf +++ b/terraform/gcp_cloudrun/platform.tf @@ -1,7 +1,6 @@ locals { effective_container_image = "${var.region}-docker.pkg.dev/${var.project_id}/${var.ghcr_remote_repository_id}/${var.ghcr_image_path}:${var.ghcr_image_tag}" - futu_enabled = var.futu_account != "" && var.futu_password_md5 != "" - futu_skills_install = local.futu_enabled ? file("${path.module}/futu-skills-install.sh") : "" + futu_skills_install = file("${path.module}/futu-skills-install.sh") openclaw_json_content = templatefile("${path.module}/../shared/openclaw.json.tpl", { openclaw_gateway_token = var.openclaw_gateway_token @@ -54,13 +53,11 @@ resource "google_artifact_registry_repository" "ghcr_remote" { } resource "tls_private_key" "futu_rsa" { - count = local.futu_enabled ? 1 : 0 algorithm = "RSA" rsa_bits = 1024 } resource "google_compute_instance" "futu_opend" { - count = local.futu_enabled ? 1 : 0 name = "${var.service_name}-futu-opend" machine_type = "e2-micro" zone = "${var.region}-b" @@ -82,9 +79,9 @@ resource "google_compute_instance" "futu_opend" { metadata = { startup-script = templatefile("${path.module}/futu-opend-startup.sh", { - futu_account = var.futu_account - futu_password_md5 = var.futu_password_md5 - futu_rsa_private_key = tls_private_key.futu_rsa[0].private_key_pem + futu_account = var.futu_account + futu_password_md5 = var.futu_password_md5 + futu_rsa_private_key = tls_private_key.futu_rsa.private_key_pem }) } @@ -92,7 +89,6 @@ resource "google_compute_instance" "futu_opend" { } resource "google_compute_firewall" "futu_opend_api" { - count = local.futu_enabled ? 1 : 0 name = "${var.service_name}-futu-opend-api" network = "default" project = var.project_id diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index 2364ea0..cc5dc22 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -99,7 +99,6 @@ resource "google_secret_manager_secret_version" "telegram_allow_from" { } resource "google_secret_manager_secret" "futu_rsa_private_key" { - count = local.futu_enabled ? 1 : 0 secret_id = "${var.service_name}-futu-rsa-private-key" replication { auto {} @@ -107,9 +106,8 @@ resource "google_secret_manager_secret" "futu_rsa_private_key" { } resource "google_secret_manager_secret_version" "futu_rsa_private_key" { - count = local.futu_enabled ? 1 : 0 - secret = google_secret_manager_secret.futu_rsa_private_key[0].id - secret_data = tls_private_key.futu_rsa[0].private_key_pem + secret = google_secret_manager_secret.futu_rsa_private_key.id + secret_data = tls_private_key.futu_rsa.private_key_pem } resource "google_secret_manager_secret" "r2_access_key_id" { From 303600b60e88a02a25599441c65ef41e1d3992df Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 12 May 2026 20:39:49 -0700 Subject: [PATCH 3/9] fix CI error --- terraform/gcp_cloudrun/platform.tf | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/terraform/gcp_cloudrun/platform.tf b/terraform/gcp_cloudrun/platform.tf index 7c35506..40735df 100644 --- a/terraform/gcp_cloudrun/platform.tf +++ b/terraform/gcp_cloudrun/platform.tf @@ -78,6 +78,7 @@ resource "google_compute_instance" "futu_opend" { } metadata = { + block-project-ssh-keys = "true" startup-script = templatefile("${path.module}/futu-opend-startup.sh", { futu_account = var.futu_account futu_password_md5 = var.futu_password_md5 @@ -85,6 +86,12 @@ resource "google_compute_instance" "futu_opend" { }) } + shielded_instance_config { + enable_secure_boot = true + enable_vtpm = true + enable_integrity_monitoring = true + } + depends_on = [google_project_service.required] } From ee1c957f2611a8ecbcbcd5134634ab73a90112b1 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Tue, 12 May 2026 21:10:31 -0700 Subject: [PATCH 4/9] fix code review --- terraform/gcp_cloudrun/futu-opend-startup.sh | 27 ++++++++++++++----- terraform/gcp_cloudrun/futu-send-sms-code.sh | 19 +++++++++++++ terraform/gcp_cloudrun/futu-skills-install.sh | 9 ++++--- terraform/gcp_cloudrun/identity_iam.tf | 14 ++++++++++ terraform/gcp_cloudrun/platform.tf | 20 +++++++++++--- 5 files changed, 75 insertions(+), 14 deletions(-) create mode 100755 terraform/gcp_cloudrun/futu-send-sms-code.sh diff --git a/terraform/gcp_cloudrun/futu-opend-startup.sh b/terraform/gcp_cloudrun/futu-opend-startup.sh index 150d69e..76a7749 100644 --- a/terraform/gcp_cloudrun/futu-opend-startup.sh +++ b/terraform/gcp_cloudrun/futu-opend-startup.sh @@ -2,7 +2,7 @@ set -e apt-get update -y -apt-get install -y curl ca-certificates libatomic1 +apt-get install -y curl ca-certificates libatomic1 openssl mkdir -p /opt/opend cd /opt/opend @@ -14,12 +14,25 @@ chmod +x FutuOpenD mkdir -p /root/.com.futunn.FutuOpenD/F3CNN -# Write RSA private key as PKCS#1 (FutuOpenD -rsa_private_key requires -----BEGIN RSA PRIVATE KEY-----) -# tls_private_key generates PKCS#8; convert using openssl rsa (always outputs PKCS#1 traditional format) -cat > /root/futu-rsa-private.pem << RSAEOF -${futu_rsa_private_key} -RSAEOF -openssl rsa -in /root/futu-rsa-private.pem -out /root/futu-rsa-private.pem 2>/dev/null || true +# Fetch RSA private key from Secret Manager (never embedded in metadata) +gcloud secrets versions access latest \ + --secret="${rsa_secret_name}" \ + --project="${project_id}" \ + > /root/futu-rsa-raw.pem + +if [ ! -s /root/futu-rsa-raw.pem ]; then + echo "ERROR: Failed to fetch RSA private key from Secret Manager" >&2 + exit 1 +fi + +# Convert PKCS#8 to PKCS#1 (FutuOpenD -rsa_private_key requires -----BEGIN RSA PRIVATE KEY-----) +openssl rsa -in /root/futu-rsa-raw.pem -out /root/futu-rsa-private.pem 2>&1 +rm -f /root/futu-rsa-raw.pem + +if ! grep -q "BEGIN RSA PRIVATE KEY" /root/futu-rsa-private.pem; then + echo "ERROR: RSA key conversion to PKCS#1 failed" >&2 + exit 1 +fi chmod 600 /root/futu-rsa-private.pem cat > /etc/systemd/system/futu-opend.service << 'SVCEOF' diff --git a/terraform/gcp_cloudrun/futu-send-sms-code.sh b/terraform/gcp_cloudrun/futu-send-sms-code.sh new file mode 100755 index 0000000..7647475 --- /dev/null +++ b/terraform/gcp_cloudrun/futu-send-sms-code.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Usage: ./futu-send-sms-code.sh <6-digit-code> + +set -e + +CODE="${1:-}" +if [ -z "$CODE" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +PROJECT=$(gcloud config get-value project 2>/dev/null) +ZONE=$(gcloud compute instances list \ + --project="$PROJECT" --filter="name~futu-opend" --format="value(zone)" | head -1) +INSTANCE=$(gcloud compute instances list \ + --project="$PROJECT" --filter="name~futu-opend" --format="value(name)" | head -1) + +gcloud compute ssh "$INSTANCE" --zone="$ZONE" --project="$PROJECT" \ + -- "printf 'input_phone_verify_code -code=$CODE\r\n' | sudo nc -q 2 127.0.0.1 22222" diff --git a/terraform/gcp_cloudrun/futu-skills-install.sh b/terraform/gcp_cloudrun/futu-skills-install.sh index 2852077..02fa02c 100644 --- a/terraform/gcp_cloudrun/futu-skills-install.sh +++ b/terraform/gcp_cloudrun/futu-skills-install.sh @@ -38,7 +38,7 @@ curl -LsSf https://astral.sh/uv/install.sh \ UV=/home/node/.local/bin/uv $UV python install 3.11 $UV venv /home/node/.futu-venv --python 3.11 -$UV pip install futu-api --python /home/node/.futu-venv/bin/python +$UV pip install futu-api cryptography --python /home/node/.futu-venv/bin/python printf '#!/bin/sh\nexec /home/node/.futu-venv/bin/python "$@"\n' > /home/node/.local/bin/python printf '#!/bin/sh\nexec /home/node/.futu-venv/bin/python "$@"\n' > /home/node/.local/bin/python3 chmod +x /home/node/.local/bin/python /home/node/.local/bin/python3 @@ -61,8 +61,11 @@ sys.stdout.buffer.write(key.private_bytes( format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() )) -" > /home/node/.openclaw/credentials/futu-rsa-private.pem 2>/dev/null || \ - printf '%s' "$FUTU_RSA_PRIVATE_KEY" > /home/node/.openclaw/credentials/futu-rsa-private.pem +" > /home/node/.openclaw/credentials/futu-rsa-private.pem + if ! grep -q "BEGIN RSA PRIVATE KEY" /home/node/.openclaw/credentials/futu-rsa-private.pem 2>/dev/null; then + echo "ERROR: RSA key conversion to PKCS#1 failed - futu SDK requires -----BEGIN RSA PRIVATE KEY-----" >&2 + exit 1 + fi chmod 600 /home/node/.openclaw/credentials/futu-rsa-private.pem fi diff --git a/terraform/gcp_cloudrun/identity_iam.tf b/terraform/gcp_cloudrun/identity_iam.tf index ac59a4b..f724b50 100644 --- a/terraform/gcp_cloudrun/identity_iam.tf +++ b/terraform/gcp_cloudrun/identity_iam.tf @@ -10,3 +10,17 @@ resource "google_project_iam_member" "secret_accessor" { role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloudrun.email}" } + +resource "google_service_account" "futu_opend" { + account_id = "${var.service_name}-futu-opend-sa" + display_name = "Futu OpenD VM runtime" + + depends_on = [google_project_service.required] +} + +resource "google_secret_manager_secret_iam_member" "futu_opend_rsa_key" { + project = var.project_id + secret_id = google_secret_manager_secret.futu_rsa_private_key.secret_id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.futu_opend.email}" +} diff --git a/terraform/gcp_cloudrun/platform.tf b/terraform/gcp_cloudrun/platform.tf index 40735df..f9a4819 100644 --- a/terraform/gcp_cloudrun/platform.tf +++ b/terraform/gcp_cloudrun/platform.tf @@ -77,12 +77,18 @@ resource "google_compute_instance" "futu_opend" { access_config {} } + service_account { + email = google_service_account.futu_opend.email + scopes = ["cloud-platform"] + } + metadata = { block-project-ssh-keys = "true" startup-script = templatefile("${path.module}/futu-opend-startup.sh", { - futu_account = var.futu_account - futu_password_md5 = var.futu_password_md5 - futu_rsa_private_key = tls_private_key.futu_rsa.private_key_pem + futu_account = var.futu_account + futu_password_md5 = var.futu_password_md5 + rsa_secret_name = google_secret_manager_secret.futu_rsa_private_key.secret_id + project_id = var.project_id }) } @@ -92,7 +98,13 @@ resource "google_compute_instance" "futu_opend" { enable_integrity_monitoring = true } - depends_on = [google_project_service.required] + allow_stopping_for_update = true + + depends_on = [ + google_project_service.required, + google_secret_manager_secret_iam_member.futu_opend_rsa_key, + google_secret_manager_secret_version.futu_rsa_private_key, + ] } resource "google_compute_firewall" "futu_opend_api" { From dc5a9ba918410d232ea1c5cbb2a7134c19015b2b Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 13 May 2026 22:02:23 -0700 Subject: [PATCH 5/9] add futu bot --- terraform/gcp_cloudrun/.envrc | 1 + terraform/gcp_cloudrun/futu-skills-install.sh | 18 ++++++++----- terraform/gcp_cloudrun/main.tf | 11 ++++++++ terraform/gcp_cloudrun/platform.tf | 21 ++++++++------- terraform/gcp_cloudrun/rclone-sync.sh | 5 ++++ terraform/gcp_cloudrun/secrets.tf | 12 +++++++++ terraform/gcp_cloudrun/variables.tf | 6 +++++ terraform/shared/openclaw.json.tpl | 26 +++++++++++++++++++ 8 files changed, 83 insertions(+), 17 deletions(-) diff --git a/terraform/gcp_cloudrun/.envrc b/terraform/gcp_cloudrun/.envrc index 9c73e27..5998789 100644 --- a/terraform/gcp_cloudrun/.envrc +++ b/terraform/gcp_cloudrun/.envrc @@ -14,6 +14,7 @@ fi export TF_VAR_openrouter_api_key="$OPENROUTER_API_KEY" export TF_VAR_telegram_bot_token="$TELEGRAM_BOT_TOKEN" +export TF_VAR_futu_telegram_bot_token="${FUTU_TELEGRAM_BOT_TOKEN:-}" export TF_VAR_openclaw_gateway_token="$OPENCLAW_GATEWAY_TOKEN" export TF_VAR_brave_api_key="${BRAVE_API_KEY:-}" export TF_VAR_telegram_owner_id="${TELEGRAM_OWNER_ID:-}" diff --git a/terraform/gcp_cloudrun/futu-skills-install.sh b/terraform/gcp_cloudrun/futu-skills-install.sh index 02fa02c..bac641a 100644 --- a/terraform/gcp_cloudrun/futu-skills-install.sh +++ b/terraform/gcp_cloudrun/futu-skills-install.sh @@ -1,7 +1,10 @@ #!/bin/sh -mkdir -p /home/node/.openclaw/skills +# Install futuapi ONLY into workspace-futu/skills — never into the global managed +# skills dir (~/.openclaw/skills/). The main agent's workspace (~/.openclaw/workspace/) +# therefore has NO skills dir and cannot discover futuapi regardless of config filtering. +mkdir -p /home/node/.openclaw/workspace-futu/skills -if [ ! -d /home/node/.openclaw/skills/futuapi ]; then +if [ ! -d /home/node/.openclaw/workspace-futu/skills/futuapi ]; then curl -fsSL https://openapi.futunn.com/skills/opend-skills.zip -o /tmp/fs.zip \ && mkdir -p /tmp/fs-extract \ && node -e " @@ -26,12 +29,13 @@ if [ ! -d /home/node/.openclaw/skills/futuapi ]; then " \ && cp -r /tmp/fs-extract/skills/futuapi \ /tmp/fs-extract/skills/install-futu-opend \ - /home/node/.openclaw/skills/ 2>/dev/null + /home/node/.openclaw/workspace-futu/skills/ 2>/dev/null rm -rf /tmp/fs.zip /tmp/fs-extract fi -mkdir -p /home/node/.openclaw/workspace -ln -sf /home/node/.openclaw/skills /home/node/.openclaw/workspace/skills 2>/dev/null || true +# No workspace/skills symlink for the main agent — this is intentional. +# The main agent's workspace (~/.openclaw/workspace/) has no skills/ directory, +# so futuapi is physically unreachable from it. mkdir -p /home/node/.local/bin curl -LsSf https://astral.sh/uv/install.sh \ | UV_INSTALL_DIR=/home/node/.local/bin sh @@ -71,8 +75,8 @@ fi # Patch common.py: remove any old RSA block and append the current version sed -i '/^# RSA encryption for cross-network trade connections/,$d' \ - /home/node/.openclaw/skills/futuapi/scripts/common.py 2>/dev/null || true -cat >> /home/node/.openclaw/skills/futuapi/scripts/common.py << 'PYEOF' + /home/node/.openclaw/workspace-futu/skills/futuapi/scripts/common.py 2>/dev/null || true +cat >> /home/node/.openclaw/workspace-futu/skills/futuapi/scripts/common.py << 'PYEOF' # RSA encryption for cross-network trade connections _futu_rsa_key_file = os.path.expanduser('~/.openclaw/credentials/futu-rsa-private.pem') diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index 62bc41f..00d3d1e 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -161,6 +161,16 @@ resource "google_cloud_run_v2_service" "openclaw" { } } + env { + name = "FUTU_TELEGRAM_BOT_TOKEN" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.futu_telegram_bot_token.secret_id + version = "latest" + } + } + } + env { name = "OPENCLAW_GATEWAY_TOKEN" value_source { @@ -248,5 +258,6 @@ resource "google_cloud_run_v2_service" "openclaw" { google_secret_manager_secret_version.r2_access_key_id, google_secret_manager_secret_version.r2_secret_access_key, google_secret_manager_secret_version.futu_rsa_private_key, + google_secret_manager_secret_version.futu_telegram_bot_token, ] } diff --git a/terraform/gcp_cloudrun/platform.tf b/terraform/gcp_cloudrun/platform.tf index f9a4819..f8337a3 100644 --- a/terraform/gcp_cloudrun/platform.tf +++ b/terraform/gcp_cloudrun/platform.tf @@ -3,16 +3,17 @@ locals { futu_skills_install = file("${path.module}/futu-skills-install.sh") openclaw_json_content = templatefile("${path.module}/../shared/openclaw.json.tpl", { - openclaw_gateway_token = var.openclaw_gateway_token - openrouter_api_key = var.openrouter_api_key - brave_api_key = var.brave_api_key - telegram_bot_token = var.telegram_bot_token - slack_app_token = var.slack_app_token - slack_bot_token = var.slack_bot_token - slack_enabled = true # Cloud Run always provisions Slack secrets - bonjour_enabled = true # Cloud Run: disable bonjour discovery - use_plugin_load_paths = false # Cloud Run: extensions bundled in container image - telegram_owner_id = var.telegram_owner_id + openclaw_gateway_token = var.openclaw_gateway_token + openrouter_api_key = var.openrouter_api_key + brave_api_key = var.brave_api_key + telegram_bot_token = var.telegram_bot_token + futu_telegram_bot_token = var.futu_telegram_bot_token + slack_app_token = var.slack_app_token + slack_bot_token = var.slack_bot_token + slack_enabled = true # Cloud Run always provisions Slack secrets + bonjour_enabled = true # Cloud Run: disable bonjour discovery + use_plugin_load_paths = false # Cloud Run: extensions bundled in container image + telegram_owner_id = var.telegram_owner_id }) } diff --git a/terraform/gcp_cloudrun/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index bdca051..40f6e37 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -11,6 +11,11 @@ cat > "$FILTER_FILE" << 'EOF' + workspace/USER.md + workspace/AGENTS.md + agents/main/sessions/** ++ agents/futu/sessions/** ++ workspace-futu/MEMORY.md ++ workspace-futu/SOUL.md ++ workspace-futu/USER.md ++ workspace-futu/AGENTS.md - * EOF diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index cc5dc22..0b9764e 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -34,6 +34,18 @@ resource "google_secret_manager_secret_version" "telegram_bot_token" { secret_data = var.telegram_bot_token } +resource "google_secret_manager_secret" "futu_telegram_bot_token" { + secret_id = "${var.service_name}-futu-telegram-bot-token" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "futu_telegram_bot_token" { + secret = google_secret_manager_secret.futu_telegram_bot_token.id + secret_data = var.futu_telegram_bot_token +} + resource "google_secret_manager_secret" "gateway_token" { secret_id = "${var.service_name}-gateway-token" replication { diff --git a/terraform/gcp_cloudrun/variables.tf b/terraform/gcp_cloudrun/variables.tf index 53fcde7..08dc590 100644 --- a/terraform/gcp_cloudrun/variables.tf +++ b/terraform/gcp_cloudrun/variables.tf @@ -80,6 +80,12 @@ variable "telegram_owner_id" { default = "" } +variable "futu_telegram_bot_token" { + description = "Telegram bot token for the Futu-dedicated bot. Only responds to telegram_owner_id." + type = string + sensitive = true +} + variable "openclaw_gateway_token" { type = string sensitive = true diff --git a/terraform/shared/openclaw.json.tpl b/terraform/shared/openclaw.json.tpl index 140aaae..a957725 100644 --- a/terraform/shared/openclaw.json.tpl +++ b/terraform/shared/openclaw.json.tpl @@ -6,6 +6,18 @@ "remote": { "token": "${openclaw_gateway_token}" } }, "agents": { + "list": [ + { + "id": "main", + "default": true, + "skills": [], + "tools": { "allow": ["web_search", "web_fetch", "message", "cron", "tts", "image", "image_generate", "video_generate", "gateway", "session_status", "sessions_list", "sessions_history", "sessions_send", "agents_list"] } + }, + { + "id": "futu", + "skills": ["futuapi", "install-futu-opend"] + } + ], "defaults": { "model": { "primary": "openrouter/auto" @@ -70,6 +82,13 @@ } } }, + "bindings": [ + { + "type": "route", + "agentId": "futu", + "match": { "channel": "telegram", "accountId": "futu" } + } + ], "messages": { "groupChat": { "visibleReplies": "automatic" @@ -88,6 +107,13 @@ "dmPolicy": "open", %{ endif ~} "groupPolicy": "open" + }, + "futu": { + "botToken": "${futu_telegram_bot_token}", + "allowFrom": ["${telegram_owner_id}"], + "dmPolicy": "allowlist", + "groupPolicy": "allowlist", + "groupAllowFrom": ["${telegram_owner_id}"] } } }%{ if slack_enabled }, From 2d3fd24a2d162ad1c60fda6198071330fb3877e1 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 13 May 2026 22:34:17 -0700 Subject: [PATCH 6/9] enable tufu bot answer me in group --- terraform/shared/openclaw.json.tpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terraform/shared/openclaw.json.tpl b/terraform/shared/openclaw.json.tpl index a957725..42ac312 100644 --- a/terraform/shared/openclaw.json.tpl +++ b/terraform/shared/openclaw.json.tpl @@ -113,7 +113,8 @@ "allowFrom": ["${telegram_owner_id}"], "dmPolicy": "allowlist", "groupPolicy": "allowlist", - "groupAllowFrom": ["${telegram_owner_id}"] + "groupAllowFrom": ["${telegram_owner_id}"], + "groups": { "*": {} } } } }%{ if slack_enabled }, From c76a564d9a45f06034a39d3a45ac46b211ca5a78 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 13 May 2026 22:40:32 -0700 Subject: [PATCH 7/9] simplify openclaw.json --- terraform/shared/openclaw.json.tpl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/terraform/shared/openclaw.json.tpl b/terraform/shared/openclaw.json.tpl index 42ac312..541a50c 100644 --- a/terraform/shared/openclaw.json.tpl +++ b/terraform/shared/openclaw.json.tpl @@ -100,12 +100,7 @@ "accounts": { "default": { "botToken": "${telegram_bot_token}", -%{ if telegram_owner_id != "" ~} - "allowFrom": ["${telegram_owner_id}"], - "dmPolicy": "allowlist", -%{ else ~} "dmPolicy": "open", -%{ endif ~} "groupPolicy": "open" }, "futu": { From 33f7d9f22890579fd955532872f85debb0c232a4 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 15 May 2026 23:32:13 -0700 Subject: [PATCH 8/9] delete if condition --- terraform/gcp_cloudrun/main.tf | 16 +--------------- terraform/gcp_cloudrun/r2.tf | 3 --- terraform/gcp_cloudrun/secrets.tf | 14 -------------- 3 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 terraform/gcp_cloudrun/r2.tf diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index 00d3d1e..9fe0ef2 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -87,7 +87,7 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = ["rclone-sync"] image = local.effective_container_image command = ["/bin/sh"] - args = ["-lc", "mkdir -p /home/node/.openclaw/agents/main/agent /home/node/.openclaw/credentials; [ -n \"$OPENCLAW_JSON\" ] && printf '%s' \"$OPENCLAW_JSON\" > /home/node/.openclaw/openclaw.json; [ -n \"$TELEGRAM_ALLOW_FROM\" ] && printf '%s' \"$TELEGRAM_ALLOW_FROM\" > /home/node/.openclaw/credentials/telegram-allowFrom.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"%s\"}}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/models.json; ${local.futu_skills_install}exec openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] + args = ["-lc", "mkdir -p /home/node/.openclaw/agents/main/agent /home/node/.openclaw/credentials; [ -n \"$OPENCLAW_JSON\" ] && printf '%s' \"$OPENCLAW_JSON\" > /home/node/.openclaw/openclaw.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"%s\"}}}' \"$OPENROUTER_API_KEY\" > /home/node/.openclaw/agents/main/agent/models.json; ${local.futu_skills_install}exec openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] ports { container_port = 8080 @@ -194,19 +194,6 @@ resource "google_cloud_run_v2_service" "openclaw" { } } - dynamic "env" { - for_each = var.telegram_owner_id != "" ? [1] : [] - content { - name = "TELEGRAM_ALLOW_FROM" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.telegram_allow_from[0].secret_id - version = "latest" - } - } - } - } - env { name = "SLACK_APP_TOKEN" value_source { @@ -253,7 +240,6 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = [ google_artifact_registry_repository_iam_member.ghcr_remote_reader, google_secret_manager_secret_version.openclaw_json, - google_secret_manager_secret_version.telegram_allow_from, google_project_iam_member.secret_accessor, google_secret_manager_secret_version.r2_access_key_id, google_secret_manager_secret_version.r2_secret_access_key, diff --git a/terraform/gcp_cloudrun/r2.tf b/terraform/gcp_cloudrun/r2.tf deleted file mode 100644 index 2c9b059..0000000 --- a/terraform/gcp_cloudrun/r2.tf +++ /dev/null @@ -1,3 +0,0 @@ -# R2 bucket is managed in terraform/shared/ (independent state). -# openclaw.json is injected via Secret Manager, not stored in R2. -# R2 is used exclusively for persistent memory (workspace files, sessions). diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index 0b9764e..11707f8 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -96,20 +96,6 @@ resource "google_secret_manager_secret_version" "slack_bot_token" { secret_data = var.slack_bot_token } -resource "google_secret_manager_secret" "telegram_allow_from" { - count = var.telegram_owner_id != "" ? 1 : 0 - secret_id = "${var.service_name}-telegram-allow-from" - replication { - auto {} - } -} - -resource "google_secret_manager_secret_version" "telegram_allow_from" { - count = var.telegram_owner_id != "" ? 1 : 0 - secret = google_secret_manager_secret.telegram_allow_from[0].id - secret_data = jsonencode({ version = 1, allowFrom = [var.telegram_owner_id] }) -} - resource "google_secret_manager_secret" "futu_rsa_private_key" { secret_id = "${var.service_name}-futu-rsa-private-key" replication { From e9dd06daa9b001fea7561330c45f8593d8dfa65a Mon Sep 17 00:00:00 2001 From: PCBZ Date: Fri, 15 May 2026 23:38:25 -0700 Subject: [PATCH 9/9] remove useless conditions --- terraform/gcp_cloudrun/main.tf | 15 ++++++--------- terraform/gcp_cloudrun/provider.tf | 2 +- terraform/gcp_cloudrun/secrets.tf | 4 +--- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index 9fe0ef2..43a5073 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -181,15 +181,12 @@ resource "google_cloud_run_v2_service" "openclaw" { } } - dynamic "env" { - for_each = var.brave_api_key != "" ? [1] : [] - content { - name = "BRAVE_API_KEY" - value_source { - secret_key_ref { - secret = google_secret_manager_secret.brave_api_key[0].secret_id - version = "latest" - } + env { + name = "BRAVE_API_KEY" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.brave_api_key.secret_id + version = "latest" } } } diff --git a/terraform/gcp_cloudrun/provider.tf b/terraform/gcp_cloudrun/provider.tf index 95a5c3f..d42409d 100644 --- a/terraform/gcp_cloudrun/provider.tf +++ b/terraform/gcp_cloudrun/provider.tf @@ -23,5 +23,5 @@ terraform { provider "google" { project = var.project_id region = var.region - credentials = var.gcp_credentials_json != "" ? var.gcp_credentials_json : null + credentials = var.gcp_credentials_json } diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index 11707f8..62cd333 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -59,7 +59,6 @@ resource "google_secret_manager_secret_version" "gateway_token" { } resource "google_secret_manager_secret" "brave_api_key" { - count = var.brave_api_key != "" ? 1 : 0 secret_id = "${var.service_name}-brave-api-key" replication { auto {} @@ -67,8 +66,7 @@ resource "google_secret_manager_secret" "brave_api_key" { } resource "google_secret_manager_secret_version" "brave_api_key" { - count = var.brave_api_key != "" ? 1 : 0 - secret = google_secret_manager_secret.brave_api_key[0].id + secret = google_secret_manager_secret.brave_api_key.id secret_data = var.brave_api_key }