From 415f2dc20a478c12657e08f5058f153ac670f4f7 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 13:44:21 -0700 Subject: [PATCH 1/7] fix(gcp-vm): fix R2 rclone path to use .openclaw/ prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloud Run stores openclaw state at HOME/.openclaw/ (HOME=/tmp/openclaw-state), so rclone syncs /data/ → R2 with .openclaw/ as subdir prefix in R2. GCP VM must therefore use r2:bucket/.openclaw/ ↔ /root/.openclaw/ to match. Fixes BOOTSTRAP.md re-init and soul/user template regression after Cloud Run → Compute Engine failover. Co-Authored-By: Claude Sonnet 4.6 --- terraform/gcp_vm/bootstrap.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index 2fbff62..e5be040 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -61,7 +61,7 @@ RCLONEEOF echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." # Exclude openclaw.json: each platform maintains its own config so the same # R2 bucket can be shared between Cloud Run and Compute Engine for failover. -rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ +rclone sync r2:${r2_bucket_name}/.openclaw/ /root/.openclaw/ --create-empty-src-dirs \ --exclude "openclaw.json" --exclude "openclaw.json.bak" 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" @@ -162,7 +162,7 @@ After=network.target Type=simple Restart=always RestartSec=5 -ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" 2>/dev/null; sleep 60; done' +ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/.openclaw/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" 2>/dev/null; sleep 60; done' [Install] WantedBy=multi-user.target @@ -178,7 +178,7 @@ After=network.target [Service] Type=oneshot -ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" +ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/.openclaw/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" TimeoutStartSec=30 RemainAfterExit=yes From 591c6fc81b7a02faf2c18437f5deba25fcdaa465 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 18:23:49 -0700 Subject: [PATCH 2/7] reset memory --- terraform/gcp_cloudrun/rclone-sync.sh | 14 +++++++------- terraform/gcp_vm/bootstrap.sh | 11 +++++++---- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/terraform/gcp_cloudrun/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index f903ef5..f768f29 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -1,13 +1,13 @@ #!/bin/sh set -e -# openclaw.json is platform-specific (Cloud Run vs VM have different plugin config). -# Exclude it from all sync operations so platforms can share the same R2 bucket -# for soul/memory/sessions without overwriting each other's config. -RCLONE_EXCLUDE="--exclude openclaw.json --exclude openclaw.json.bak" +# Whitelist: only sync memory-related files that should be shared across platforms. +# Runtime config (models.json, auth-profiles.json, openclaw.json) is platform-specific +# and must NOT be shared — each platform writes its own on startup. +RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/soul.md --include /workspace/user.md --include /workspace/AGENTS.md --include /credentials/telegram-allowFrom.json --include /sessions/** --exclude *" # ── Restore from R2 on startup ──────────────────────────────── -rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_EXCLUDE 2>/dev/null || true +rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true # Fix permissions: rclone runs as root, openclaw runs as node (uid 1000). chmod -R a+rwX /data/ 2>/dev/null || true touch /tmp/rclone-ready @@ -19,7 +19,7 @@ echo "rclone: initial restore complete" # ── Final sync on shutdown (SIGTERM) ───────────────────────── cleanup() { echo "rclone: final sync on shutdown..." - rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_EXCLUDE 2>/dev/null || true + rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true exit 0 } trap cleanup TERM INT @@ -27,5 +27,5 @@ trap cleanup TERM INT # ── Periodic sync every 60s ─────────────────────────────────── while true; do sleep 60 - rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_EXCLUDE 2>/dev/null || true + rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true done diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index e5be040..9abb21d 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -61,8 +61,11 @@ RCLONEEOF echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." # Exclude openclaw.json: each platform maintains its own config so the same # R2 bucket can be shared between Cloud Run and Compute Engine for failover. -rclone sync r2:${r2_bucket_name}/.openclaw/ /root/.openclaw/ --create-empty-src-dirs \ - --exclude "openclaw.json" --exclude "openclaw.json.bak" 2>/dev/null || true +rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ + --include "/workspace/MEMORY.md" --include "/workspace/soul.md" \ + --include "/workspace/user.md" --include "/workspace/AGENTS.md" \ + --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" \ + --exclude "*" 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" %{ endif ~} @@ -162,7 +165,7 @@ After=network.target Type=simple Restart=always RestartSec=5 -ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/.openclaw/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" 2>/dev/null; sleep 60; done' +ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/soul.md" --include "/workspace/user.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' [Install] WantedBy=multi-user.target @@ -178,7 +181,7 @@ After=network.target [Service] Type=oneshot -ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/.openclaw/ --create-empty-src-dirs --exclude "openclaw.json" --exclude "openclaw.json.bak" +ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/soul.md" --include "/workspace/user.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" --exclude "*" TimeoutStartSec=30 RemainAfterExit=yes From e3d55eecece97115a4569cf38c50a3c69e4446c6 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 18:28:32 -0700 Subject: [PATCH 3/7] fix file names --- terraform/gcp_cloudrun/rclone-sync.sh | 2 +- terraform/gcp_vm/bootstrap.sh | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/terraform/gcp_cloudrun/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index f768f29..eb4348e 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -4,7 +4,7 @@ set -e # Whitelist: only sync memory-related files that should be shared across platforms. # Runtime config (models.json, auth-profiles.json, openclaw.json) is platform-specific # and must NOT be shared — each platform writes its own on startup. -RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/soul.md --include /workspace/user.md --include /workspace/AGENTS.md --include /credentials/telegram-allowFrom.json --include /sessions/** --exclude *" +RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/SOUL.md --include /workspace/USER.md --include /workspace/AGENTS.md --include /credentials/telegram-allowFrom.json --include /agents/main/sessions/** --exclude *" # ── Restore from R2 on startup ──────────────────────────────── rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index 9abb21d..5585cab 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -62,9 +62,9 @@ echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." # Exclude openclaw.json: each platform maintains its own config so the same # R2 bucket can be shared between Cloud Run and Compute Engine for failover. rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ - --include "/workspace/MEMORY.md" --include "/workspace/soul.md" \ - --include "/workspace/user.md" --include "/workspace/AGENTS.md" \ - --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" \ + --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" \ + --include "/workspace/USER.md" --include "/workspace/AGENTS.md" \ + --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" \ --exclude "*" 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" @@ -165,7 +165,7 @@ After=network.target Type=simple Restart=always RestartSec=5 -ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/soul.md" --include "/workspace/user.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' +ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' [Install] WantedBy=multi-user.target @@ -181,7 +181,7 @@ After=network.target [Service] Type=oneshot -ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/soul.md" --include "/workspace/user.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/sessions/**" --exclude "*" +ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" --exclude "*" TimeoutStartSec=30 RemainAfterExit=yes From 5afd3f11530fbc6e299c6629e9acfb37da041a94 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 20:43:25 -0700 Subject: [PATCH 4/7] refactor: update Cloud Run service configuration and secret management for improved state handling --- terraform/gcp_cloudrun/main.tf | 28 +++++++++++++++++++++++-- terraform/gcp_cloudrun/r2.tf | 30 ++------------------------- terraform/gcp_cloudrun/rclone-sync.sh | 7 +++---- terraform/gcp_cloudrun/secrets.tf | 26 +++++++++++++++++++++++ terraform/gcp_vm/bootstrap.sh | 6 +++--- 5 files changed, 60 insertions(+), 37 deletions(-) diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index da1a5ee..c05c644 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -79,7 +79,7 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = ["rclone-sync"] image = local.effective_container_image command = ["/bin/sh"] - args = ["-lc", "openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] + args = ["-lc", "mkdir -p /tmp/openclaw-state/agents/main/agent /tmp/openclaw-state/credentials; [ -n \"$OPENCLAW_JSON\" ] && echo \"$OPENCLAW_JSON\" > /tmp/openclaw-state/openclaw.json; [ -n \"$TELEGRAM_ALLOW_FROM\" ] && echo \"$TELEGRAM_ALLOW_FROM\" > /tmp/openclaw-state/credentials/telegram-allowFrom.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /tmp/openclaw-state/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"OPENROUTER_API_KEY\"}}}' > /tmp/openclaw-state/agents/main/agent/models.json; exec openclaw gateway run --bind lan --port \"$${PORT:-8080}\" --allow-unconfigured"] ports { container_port = 8080 @@ -123,6 +123,16 @@ resource "google_cloud_run_v2_service" "openclaw" { value = "8080" } + env { + name = "OPENCLAW_JSON" + value_source { + secret_key_ref { + secret = google_secret_manager_secret.openclaw_json.secret_id + version = "latest" + } + } + } + env { name = "OPENROUTER_API_KEY" value_source { @@ -166,6 +176,19 @@ 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 { @@ -196,7 +219,8 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = [ google_artifact_registry_repository_iam_member.ghcr_remote_reader, - null_resource.openclaw_json_r2, + 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 index 8411210..2c9b059 100644 --- a/terraform/gcp_cloudrun/r2.tf +++ b/terraform/gcp_cloudrun/r2.tf @@ -1,29 +1,3 @@ # R2 bucket is managed in terraform/shared/ (independent state). -# Cloud Run only uploads openclaw.json to the existing bucket. - -resource "local_sensitive_file" "openclaw_json" { - content = local.openclaw_json_content - filename = "${path.module}/.terraform/tmp/openclaw.json" -} - -resource "null_resource" "openclaw_json_r2" { - triggers = { - content_hash = md5(local.openclaw_json_content) - } - - provisioner "local-exec" { - command = <<-EOT - aws s3 cp "${local_sensitive_file.openclaw_json.filename}" \ - "s3://${var.r2_bucket_name}/openclaw.json" \ - --endpoint-url "https://${var.cloudflare_account_id}.r2.cloudflarestorage.com" \ - --content-type "application/json" - EOT - environment = { - AWS_ACCESS_KEY_ID = var.r2_access_key_id - AWS_SECRET_ACCESS_KEY = var.r2_secret_access_key - AWS_DEFAULT_REGION = "auto" - } - } - - depends_on = [local_sensitive_file.openclaw_json] -} +# 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/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index eb4348e..e11b859 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -1,10 +1,9 @@ #!/bin/sh set -e -# Whitelist: only sync memory-related files that should be shared across platforms. -# Runtime config (models.json, auth-profiles.json, openclaw.json) is platform-specific -# and must NOT be shared — each platform writes its own on startup. -RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/SOUL.md --include /workspace/USER.md --include /workspace/AGENTS.md --include /credentials/telegram-allowFrom.json --include /agents/main/sessions/** --exclude *" +# Whitelist: only sync memory files shared across platforms. +# Config (openclaw.json, auth) is injected via Secret Manager, never stored in R2. +RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/SOUL.md --include /workspace/USER.md --include /workspace/AGENTS.md --include /agents/main/sessions/** --exclude *" # ── Restore from R2 on startup ──────────────────────────────── rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true diff --git a/terraform/gcp_cloudrun/secrets.tf b/terraform/gcp_cloudrun/secrets.tf index ecf28c9..c2c68cc 100644 --- a/terraform/gcp_cloudrun/secrets.tf +++ b/terraform/gcp_cloudrun/secrets.tf @@ -1,3 +1,15 @@ +resource "google_secret_manager_secret" "openclaw_json" { + secret_id = "${var.service_name}-openclaw-json" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "openclaw_json" { + secret = google_secret_manager_secret.openclaw_json.id + secret_data = local.openclaw_json_content +} + resource "google_secret_manager_secret" "openrouter_api_key" { secret_id = "${var.service_name}-openrouter-api-key" replication { @@ -72,6 +84,20 @@ 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" "r2_access_key_id" { secret_id = "${var.service_name}-r2-access-key-id" replication { diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index 5585cab..70ddb26 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -64,7 +64,7 @@ echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" \ --include "/workspace/USER.md" --include "/workspace/AGENTS.md" \ - --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" \ + --include "/agents/main/sessions/**" \ --exclude "*" 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" @@ -165,7 +165,7 @@ After=network.target Type=simple Restart=always RestartSec=5 -ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' +ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/agents/main/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' [Install] WantedBy=multi-user.target @@ -181,7 +181,7 @@ After=network.target [Service] Type=oneshot -ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/credentials/telegram-allowFrom.json" --include "/agents/main/sessions/**" --exclude "*" +ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/agents/main/sessions/**" --exclude "*" TimeoutStartSec=30 RemainAfterExit=yes From c149d3c233082b8608f70fc5d95e6e4b7efd59d7 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 21:15:07 -0700 Subject: [PATCH 5/7] fix: correct rclone filter paths in sync scripts for consistency --- terraform/gcp_cloudrun/main.tf | 8 ++++---- terraform/gcp_cloudrun/rclone-sync.sh | 2 +- terraform/gcp_vm/bootstrap.sh | 7 +++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/terraform/gcp_cloudrun/main.tf b/terraform/gcp_cloudrun/main.tf index c05c644..2206b0e 100644 --- a/terraform/gcp_cloudrun/main.tf +++ b/terraform/gcp_cloudrun/main.tf @@ -79,7 +79,7 @@ resource "google_cloud_run_v2_service" "openclaw" { depends_on = ["rclone-sync"] image = local.effective_container_image command = ["/bin/sh"] - args = ["-lc", "mkdir -p /tmp/openclaw-state/agents/main/agent /tmp/openclaw-state/credentials; [ -n \"$OPENCLAW_JSON\" ] && echo \"$OPENCLAW_JSON\" > /tmp/openclaw-state/openclaw.json; [ -n \"$TELEGRAM_ALLOW_FROM\" ] && echo \"$TELEGRAM_ALLOW_FROM\" > /tmp/openclaw-state/credentials/telegram-allowFrom.json; printf '{\"openrouter\":{\"apiKey\":\"%s\"}}' \"$OPENROUTER_API_KEY\" > /tmp/openclaw-state/agents/main/agent/auth-profiles.json; printf '{\"providers\":{\"openrouter\":{\"baseUrl\":\"https://openrouter.ai/api/v1\",\"api\":\"openai-completions\",\"apiKey\":\"OPENROUTER_API_KEY\"}}}' > /tmp/openclaw-state/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\" ] && 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"] ports { container_port = 8080 @@ -95,7 +95,7 @@ resource "google_cloud_run_v2_service" "openclaw" { volume_mounts { name = "openclaw-runtime" - mount_path = "/tmp/openclaw-state" + mount_path = "/home/node/.openclaw" } env { @@ -105,12 +105,12 @@ resource "google_cloud_run_v2_service" "openclaw" { env { name = "OPENCLAW_STATE_DIR" - value = "/tmp/openclaw-state" + value = "/home/node/.openclaw" } env { name = "OPENCLAW_CONFIG_PATH" - value = "/tmp/openclaw-state/openclaw.json" + value = "/home/node/.openclaw/openclaw.json" } env { diff --git a/terraform/gcp_cloudrun/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index e11b859..08a0866 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -3,7 +3,7 @@ set -e # Whitelist: only sync memory files shared across platforms. # Config (openclaw.json, auth) is injected via Secret Manager, never stored in R2. -RCLONE_FILTER="--include /workspace/MEMORY.md --include /workspace/SOUL.md --include /workspace/USER.md --include /workspace/AGENTS.md --include /agents/main/sessions/** --exclude *" +RCLONE_FILTER="--include workspace/MEMORY.md --include workspace/SOUL.md --include workspace/USER.md --include workspace/AGENTS.md --include agents/main/sessions/**" # ── Restore from R2 on startup ──────────────────────────────── rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index 70ddb26..5467d4f 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -62,10 +62,9 @@ echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." # Exclude openclaw.json: each platform maintains its own config so the same # R2 bucket can be shared between Cloud Run and Compute Engine for failover. rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ - --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" \ - --include "/workspace/USER.md" --include "/workspace/AGENTS.md" \ - --include "/agents/main/sessions/**" \ - --exclude "*" 2>/dev/null || true + --include "workspace/MEMORY.md" --include "workspace/SOUL.md" \ + --include "workspace/USER.md" --include "workspace/AGENTS.md" \ + --include "agents/main/sessions/**" 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" %{ endif ~} From 5a6f0d1cc0333af36e58716bc108d901f1072890 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 21:55:15 -0700 Subject: [PATCH 6/7] fix MEMORY.md save --- terraform/gcp_cloudrun/rclone-sync.sh | 17 +++++++++++++---- terraform/gcp_vm/bootstrap.sh | 19 ++++++++++++------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/terraform/gcp_cloudrun/rclone-sync.sh b/terraform/gcp_cloudrun/rclone-sync.sh index 08a0866..bdca051 100644 --- a/terraform/gcp_cloudrun/rclone-sync.sh +++ b/terraform/gcp_cloudrun/rclone-sync.sh @@ -3,10 +3,19 @@ set -e # Whitelist: only sync memory files shared across platforms. # Config (openclaw.json, auth) is injected via Secret Manager, never stored in R2. -RCLONE_FILTER="--include workspace/MEMORY.md --include workspace/SOUL.md --include workspace/USER.md --include workspace/AGENTS.md --include agents/main/sessions/**" +# Uses a filter file to avoid shell glob expansion of ** patterns. +FILTER_FILE=/tmp/rclone-filter.txt +cat > "$FILTER_FILE" << 'EOF' ++ workspace/MEMORY.md ++ workspace/SOUL.md ++ workspace/USER.md ++ workspace/AGENTS.md ++ agents/main/sessions/** +- * +EOF # ── Restore from R2 on startup ──────────────────────────────── -rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true +rclone sync r2:$R2_BUCKET/ /data/ --create-empty-src-dirs --filter-from "$FILTER_FILE" 2>/dev/null || true # Fix permissions: rclone runs as root, openclaw runs as node (uid 1000). chmod -R a+rwX /data/ 2>/dev/null || true touch /tmp/rclone-ready @@ -18,7 +27,7 @@ echo "rclone: initial restore complete" # ── Final sync on shutdown (SIGTERM) ───────────────────────── cleanup() { echo "rclone: final sync on shutdown..." - rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true + rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs --filter-from "$FILTER_FILE" 2>/dev/null || true exit 0 } trap cleanup TERM INT @@ -26,5 +35,5 @@ trap cleanup TERM INT # ── Periodic sync every 60s ─────────────────────────────────── while true; do sleep 60 - rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs $RCLONE_FILTER 2>/dev/null || true + rclone copy /data/ r2:$R2_BUCKET/ --create-empty-src-dirs --filter-from "$FILTER_FILE" 2>/dev/null || true done diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_vm/bootstrap.sh index 5467d4f..b474d64 100644 --- a/terraform/gcp_vm/bootstrap.sh +++ b/terraform/gcp_vm/bootstrap.sh @@ -59,12 +59,17 @@ endpoint = https://${cloudflare_account_id}.r2.cloudflarestorage.com RCLONEEOF echo "Restoring OpenClaw state from R2 (${r2_bucket_name})..." -# Exclude openclaw.json: each platform maintains its own config so the same -# R2 bucket can be shared between Cloud Run and Compute Engine for failover. +# Filter file avoids shell glob expansion of ** patterns. +cat > /etc/rclone-openclaw-filter.txt << 'FILTEREOF' ++ workspace/MEMORY.md ++ workspace/SOUL.md ++ workspace/USER.md ++ workspace/AGENTS.md ++ agents/main/sessions/** +- * +FILTEREOF rclone sync r2:${r2_bucket_name}/ /root/.openclaw/ --create-empty-src-dirs \ - --include "workspace/MEMORY.md" --include "workspace/SOUL.md" \ - --include "workspace/USER.md" --include "workspace/AGENTS.md" \ - --include "agents/main/sessions/**" 2>/dev/null || true + --filter-from /etc/rclone-openclaw-filter.txt 2>/dev/null || true chmod -R a+rX /root/.openclaw/ 2>/dev/null || true echo "R2 restore complete" %{ endif ~} @@ -164,7 +169,7 @@ After=network.target Type=simple Restart=always RestartSec=5 -ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/agents/main/sessions/**" --exclude "*" 2>/dev/null; sleep 60; done' +ExecStart=/bin/sh -c 'while true; do rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --filter-from /etc/rclone-openclaw-filter.txt 2>/dev/null; sleep 60; done' [Install] WantedBy=multi-user.target @@ -180,7 +185,7 @@ After=network.target [Service] Type=oneshot -ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --include "/workspace/MEMORY.md" --include "/workspace/SOUL.md" --include "/workspace/USER.md" --include "/workspace/AGENTS.md" --include "/agents/main/sessions/**" --exclude "*" +ExecStart=/usr/bin/rclone copy /root/.openclaw/ r2:${r2_bucket_name}/ --create-empty-src-dirs --filter-from /etc/rclone-openclaw-filter.txt TimeoutStartSec=30 RemainAfterExit=yes From ffb2696a202dd7638cd75046355d53646444c930 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 6 May 2026 22:09:15 -0700 Subject: [PATCH 7/7] rename dir --- terraform/{gcp_vm => gcp_compute_engine}/.envrc | 0 .../{gcp_vm => gcp_compute_engine}/approve_operator_approvals.py | 0 terraform/{gcp_vm => gcp_compute_engine}/bootstrap.sh | 0 terraform/{gcp_vm => gcp_compute_engine}/main.tf | 0 terraform/{gcp_vm => gcp_compute_engine}/outputs.tf | 0 terraform/{gcp_vm => gcp_compute_engine}/provider.tf | 0 terraform/{gcp_vm => gcp_compute_engine}/terraform.tfvars.example | 0 terraform/{gcp_vm => gcp_compute_engine}/variables.tf | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename terraform/{gcp_vm => gcp_compute_engine}/.envrc (100%) rename terraform/{gcp_vm => gcp_compute_engine}/approve_operator_approvals.py (100%) rename terraform/{gcp_vm => gcp_compute_engine}/bootstrap.sh (100%) rename terraform/{gcp_vm => gcp_compute_engine}/main.tf (100%) rename terraform/{gcp_vm => gcp_compute_engine}/outputs.tf (100%) rename terraform/{gcp_vm => gcp_compute_engine}/provider.tf (100%) rename terraform/{gcp_vm => gcp_compute_engine}/terraform.tfvars.example (100%) rename terraform/{gcp_vm => gcp_compute_engine}/variables.tf (100%) diff --git a/terraform/gcp_vm/.envrc b/terraform/gcp_compute_engine/.envrc similarity index 100% rename from terraform/gcp_vm/.envrc rename to terraform/gcp_compute_engine/.envrc diff --git a/terraform/gcp_vm/approve_operator_approvals.py b/terraform/gcp_compute_engine/approve_operator_approvals.py similarity index 100% rename from terraform/gcp_vm/approve_operator_approvals.py rename to terraform/gcp_compute_engine/approve_operator_approvals.py diff --git a/terraform/gcp_vm/bootstrap.sh b/terraform/gcp_compute_engine/bootstrap.sh similarity index 100% rename from terraform/gcp_vm/bootstrap.sh rename to terraform/gcp_compute_engine/bootstrap.sh diff --git a/terraform/gcp_vm/main.tf b/terraform/gcp_compute_engine/main.tf similarity index 100% rename from terraform/gcp_vm/main.tf rename to terraform/gcp_compute_engine/main.tf diff --git a/terraform/gcp_vm/outputs.tf b/terraform/gcp_compute_engine/outputs.tf similarity index 100% rename from terraform/gcp_vm/outputs.tf rename to terraform/gcp_compute_engine/outputs.tf diff --git a/terraform/gcp_vm/provider.tf b/terraform/gcp_compute_engine/provider.tf similarity index 100% rename from terraform/gcp_vm/provider.tf rename to terraform/gcp_compute_engine/provider.tf diff --git a/terraform/gcp_vm/terraform.tfvars.example b/terraform/gcp_compute_engine/terraform.tfvars.example similarity index 100% rename from terraform/gcp_vm/terraform.tfvars.example rename to terraform/gcp_compute_engine/terraform.tfvars.example diff --git a/terraform/gcp_vm/variables.tf b/terraform/gcp_compute_engine/variables.tf similarity index 100% rename from terraform/gcp_vm/variables.tf rename to terraform/gcp_compute_engine/variables.tf