Skip to content

futu openD support#26

Open
PCBZ wants to merge 9 commits into
mainfrom
futu-support
Open

futu openD support#26
PCBZ wants to merge 9 commits into
mainfrom
futu-support

Conversation

@PCBZ
Copy link
Copy Markdown
Owner

@PCBZ PCBZ commented May 13, 2026

No description provided.

@PCBZ PCBZ self-assigned this May 13, 2026
@PCBZ PCBZ added the enhancement New feature or request label May 13, 2026
@PCBZ PCBZ requested a review from Copilot May 13, 2026 02:53
Comment thread terraform/gcp_cloudrun/platform.tf Fixed
Comment thread terraform/gcp_cloudrun/platform.tf Fixed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds optional Futu OpenD support to the existing GCP Cloud Run Terraform module by provisioning a GCE VM running FutuOpenD, wiring Cloud Run to reach it over private ranges, and installing the required OpenD “skills”/SDK bits at runtime.

Changes:

  • Added futu_account / futu_password_md5 module variables and .envrc wiring to enable/disable Futu OpenD resources.
  • Provisioned a GCE VM (+ firewall rule) and generated/stored an RSA private key in Secret Manager for encrypted trade connections.
  • Updated the Cloud Run service to enable VPC access, inject FUTU env vars, and run a runtime installer script for Futu skills/Python dependencies.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
terraform/gcp_cloudrun/variables.tf Adds Futu OpenD configuration variables.
terraform/gcp_cloudrun/secrets.tf Adds a Secret Manager secret/version for the Futu RSA private key.
terraform/gcp_cloudrun/provider.tf Adds the hashicorp/tls provider requirement.
terraform/gcp_cloudrun/platform.tf Adds futu_enabled locals, TLS key generation, GCE VM + firewall, and enables Compute API.
terraform/gcp_cloudrun/main.tf Adds conditional Cloud Run VPC access, injects FUTU env vars, and runs the Futu installer during container startup.
terraform/gcp_cloudrun/futu-skills-install.sh New runtime install script to fetch Futu skills and set up Python/futu-api + RSA handling.
terraform/gcp_cloudrun/futu-opend-startup.sh New VM startup script to install and run FutuOpenD via systemd.
terraform/gcp_cloudrun/.envrc Exports TF vars for the new Futu settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +115 to +120
variable "futu_password_md5" {
description = "MD5 hash of Futu login password (echo -n 'password' | md5)"
type = string
sensitive = true
default = ""
}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will update the variable description to include both md5 (macOS) and md5sum (Linux) in a follow-up.

resource "tls_private_key" "futu_rsa" {
count = local.futu_enabled ? 1 : 0
algorithm = "RSA"
rsa_bits = 1024
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Futu OpenD SDK enforces a 1024-bit key requirement at the protocol level — using 2048-bit results in a "Ciphertext with incorrect length" error at handshake time. This is a hard constraint from the Futu API, not a choice. Documented in the startup script comments.

Comment thread terraform/gcp_cloudrun/platform.tf
Comment on lines +64 to +67
name = "${var.service_name}-futu-opend"
machine_type = "e2-micro"
zone = "${var.region}-b"
project = var.project_id
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional trade-off — the VM is co-located with the Cloud Run region to minimise latency. Can be extracted to var.zone in a future iteration if multi-region support is needed.

Comment on lines +94 to +107
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"]
}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — restricts to RFC1918 private ranges only. Port 11111 is not exposed publicly. Can be narrowed to the specific VPC subnet CIDR in a future hardening pass.

Comment thread terraform/gcp_cloudrun/main.tf Outdated
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"]
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged trade-off. The skills ZIP is fetched from Futu's official distribution endpoint (not available at image build time), and the installed artifacts are persisted on the rclone-synced R2 volume — so subsequent cold starts skip the install entirely ([ ! -d ...futuapi ] guard). Moving to image build would require vendoring a third-party package with no stable release URL.

Comment thread terraform/gcp_cloudrun/futu-skills-install.sh Outdated
Comment on lines +109 to +114
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
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted risk — Terraform state is stored in a secured backend. The key is no longer exposed via instance metadata (fixed in this PR): the VM now fetches it from Secret Manager at boot using its dedicated service account.

@PCBZ PCBZ requested a review from Copilot May 13, 2026 03:33
Comment thread terraform/gcp_cloudrun/platform.tf Fixed
Comment thread terraform/gcp_cloudrun/platform.tf Fixed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 15 comments.

Comments suppressed due to low confidence (1)

terraform/gcp_cloudrun/futu-skills-install.sh:66

  • The conversion pipeline redirects stderr to /dev/null and then falls back to writing the original key. This makes failures hard to diagnose in Cloud Run logs. Consider logging a clear warning/error when conversion fails (and/or making the failure fatal if encryption is required).
" > /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

}

variable "futu_password_md5" {
description = "MD5 hash of Futu login password (echo -n 'password' | md5)"
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will update the variable description to include both md5 (macOS) and md5sum (Linux) in a follow-up.


resource "tls_private_key" "futu_rsa" {
algorithm = "RSA"
rsa_bits = 1024
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Futu OpenD SDK enforces a 1024-bit key requirement at the protocol level — using 2048-bit results in a "Ciphertext with incorrect length" error at handshake time. This is a hard constraint from the Futu API, not a choice. Documented in the startup script comments.

Comment on lines +61 to +64
name = "${var.service_name}-futu-opend"
machine_type = "e2-micro"
zone = "${var.region}-b"
project = var.project_id
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional trade-off — the VM is co-located with the Cloud Run region to minimise latency. Can be extracted to var.zone in a future iteration if multi-region support is needed.


network_interface {
network = "default"
access_config {}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required for the startup script to reach external endpoints (apt, Futu download servers, Secret Manager). No inbound public ports are open — port 11111 is restricted to 10.0.0.0/8 via the firewall rule. Cloud NAT is a valid future hardening option.

Comment thread terraform/gcp_cloudrun/main.tf Outdated
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"]
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged trade-off. The skills ZIP is fetched from Futu's official distribution endpoint (not available at image build time), and the installed artifacts are persisted on the rclone-synced R2 volume — so subsequent cold starts skip the install entirely ([ ! -d ...futuapi ] guard). Moving to image build would require vendoring a third-party package with no stable release URL.

Comment thread terraform/gcp_cloudrun/futu-skills-install.sh Outdated
Comment thread terraform/gcp_cloudrun/futu-skills-install.sh
Comment thread terraform/gcp_cloudrun/futu-opend-startup.sh Outdated
Comment on lines +37 to +41
ExecStart=/opt/opend/FutuOpenD \
-login_account=${futu_account} \
-login_pwd_md5=${futu_password_md5} \
-api_ip=0.0.0.0 \
-api_port=11111 \
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account is a public email address and the password is already MD5-hashed. Both are simple alphanumeric strings with no special characters, so quoting/escaping is not a practical concern. Moving to EnvironmentFile is a valid future hardening step.

Comment on lines +101 to +106
resource "google_secret_manager_secret" "futu_rsa_private_key" {
secret_id = "${var.service_name}-futu-rsa-private-key"
replication {
auto {}
}
}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional — Futu is always enabled in this module. The futu_enabled conditional was removed to reduce complexity.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants