Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

OpenClaw on hungryhub-ai

OpenClaw provisions a CloudFormation stack that boots an Ubuntu 24.04 EC2 instance with openclaw, an AI agent gateway with multi-channel support (Slack, Discord, Telegram, WhatsApp, Feishu). Instances are provider-agnostic: use Amazon Bedrock or any OpenAI-compatible HTTP endpoint (e.g. LiteLLM).


Architecture

                        LiteLLM / Bedrock
                               │
                               ▼
                 openclaw gateway (EC2 :18789)
                               │
            ┌────────────────┼────────────────┐
            │                │                │
      WhatsApp       Discord       Telegram       Slack

Provider types:

  • bedrock — Amazon Bedrock models via IAM role
  • openai — any OpenAI-compatible HTTP endpoint (LiteLLM, OpenAI, Anthropic, etc.)

Deployment: CloudFormation on AWS EC2 (t4g.medium default), SSM Session Manager for operator access.


Key files

FilePurpose
openclaw/main.tfaws_cloudformation_stack.openclaw (for_each over instance map)
openclaw/variable.tfopenclaw_instances map (per-instance config)
openclaw/output.tfstack/instance IDs, SSM token paths
openclaw/templates/openclaw.yaml720-line CloudFormation (user_data, IAM, SG)
env/dev.tfvarsSandbox deployments (currently empty)
env/prod.tfvarsProduction deployments
.github/workflows/terraform.ymlCI: services: openclaw

Instance naming

Each entry in openclaw_instances maps short name → config:

Instance keyStack nameAuth modeSSM token path
claw-seoopenclaw-claw-seo-prodnone(not generated)
claw-marketingopenclaw-claw-marketing-prodtoken/openclaw/openclaw-claw-marketing-prod/gateway-token

Adding an instance: one map entry in env/<env>.tfvars.


Deployment

Prerequisites

  1. Shared VPCshared/ module must be applied first (provides VPC + subnets)
  2. SSM key — For Provider=openai, create a SecureString in SSM:
    aws ssm put-parameter \
      --name "/openclaw/openclaw-<instance>-<env>/<provider>-api-key" \
      --value "<api-key>" \
      --type SecureString \
      --region us-east-1 \
      --profile genai-prod
    

Apply

# Dev (genai-sandbox)
aws sso login --profile genai-sandbox
cd openclaw
terraform init -backend-config="bucket=hungryhub-ai-tf-state-us-965444437277" \
  -backend-config="key=openclaw/terraform.tfstate" \
  -backend-config="region=us-east-1" \
  -backend-config="dynamodb_table=terraform-state-lock"
terraform plan -var environment=dev -var-file=../env/dev.tfvars -out=tfplan
terraform apply tfplan

# Prod (genai-prod)
aws sso login --profile genai-prod
# ... same, with bucket ending in 512438352490

Validate

INSTANCE_ID=$(aws cloudformation describe-stacks \
  --stack-name openclaw-<instance>-<env> \
  --query 'Stacks[0].Outputs[?OutputKey==`InstanceId`].OutputValue' \
  --output text --region us-east-1 --profile genai-prod)

# Get token (only when auth_mode = "token"; ignored otherwise)
TOKEN=$(aws ssm get-parameter \
  --name /openclaw/openclaw-<instance>-<env>/gateway-token \
  --with-decryption --query Parameter.Value --output text \
  --region us-east-1 --profile genai-prod)

# Start session
aws ssm start-session --target $INSTANCE_ID --region us-east-1 \
  --profile genai-prod \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["18789"],"localPortNumber":["18789"]}'

# Or use the wrapper in hh-seo-geo (default local port 28789 to avoid
# clashing with another local OpenClaw tunnel on the upstream default):
#   ../hh-seo-geo/bin/openclaw-port-forward.sh           # localhost:28789
#   ../hh-seo-geo/bin/openclaw-port-forward.sh 29000     # custom port

# Test
# Open http://localhost:18789/                  (auth_mode = none)
# Open http://localhost:18789/#token=$TOKEN     (auth_mode = token; URL fragment, NOT query string)
# Or CLI:
aws ssm start-session --target $INSTANCE_ID --profile genai-prod
sudo su - ubuntu
openclaw agent -m "reply with just the word OK" --agent main

Instance lifecycle

Add

  1. Add SSM SecureString for the API key (if Provider=openai)
  2. Append entry to openclaw_instances in env/<env>.tfvars:
    openclaw_instances = {
      "my-instance" = {
        openclaw_version   = "latest"
        provider          = "openai"
        provider_base_url = "https://litellm.hungryhub.com/v1"
        model_id          = "minimax/MiniMax-M3"
        api_key_ssm_param = "/openclaw/openclaw-my-instance-prod/litellm-api-key"
        instance_type     = "t4g.medium"
        enable_sandbox    = true
        enable_monitoring = true
        auth_mode         = "none"   # default; set "token" to require a gateway token
      }
    }
    
  3. terraform plan -var environment=prod -var-file=../env/prod.tfvars -out=tfplan
  4. terraform apply tfplan

Retire

  1. Remove entry from openclaw_instances in tfvars
  2. terraform apply (CFN stack deletes EC2)
  3. Delete SSM params manually:
    aws ssm delete-parameter --name /openclaw/openclaw-<instance>-<env>/gateway-token --profile genai-prod
    aws ssm delete-parameter --name /openclaw/openclaw-<instance>-<env>/litellm-api-key --profile genai-prod
    

Parameters

VariableTypeDefaultDescription
openclaw_instancesmap(object){}Per-instance config
openclaw_versionstring"latest"Pin or track
providerstring"bedrock""bedrock" or "openai"
provider_base_urlstring""Required for openai provider
model_idstring"us.amazon.nova-pro-v1:0"Bedrock model ID or upstream model name
api_key_ssm_paramstring""SSM SecureString for openai key
instance_typestring"t4g.medium"EC2 instance size
enable_sandboxbooltrueInstall Docker for plugin sandboxing
enable_monitoringbooltrueCloudWatch log group + IAM
create_vpc_endpointsboolfalseSSM-only VPC endpoints (rare)
auth_modestring"none"Gateway auth: "none" (default; safe for loopback-only) or "token"

Authentication

The gateway binds to 127.0.0.1 (loopback) and is reachable only via SSM port-forward from your laptop. Anyone with SSM access to the instance already has root-equivalent privileges, so the gateway-level token was just adding friction (stale localStorage, manual URL fragments, no easy rotation) without buying real security on top of the SSM IAM boundary.

The auth_mode per-instance field controls this:

  • "none" (default)openclaw.json has gateway.auth.mode = "none". No token is generated or stored in SSM. The instance writes auth.mode = "none" on first boot. The dashboard is reachable directly via http://localhost:18789/ (no URL fragment needed).
  • "token" — On first boot the instance generates a random 48-char hex token via openssl rand -hex 24, stores it in openclaw.json (gateway.auth.token), and persists it in SSM at /openclaw/openclaw-<instance>-<env>/gateway-token. The dashboard URL is http://localhost:18789/#token=<token> (URL fragment, not query string — ?token= works too, but may appear in browser/server logs).

Changing auth_mode after the instance is up does not rewrite openclaw.json on the existing box (the user_data only runs on first boot). To switch an instance between none and token, either terraform apply with a forced instance replacement (e.g. via in-place=false + new AMI) or patch the live config with SSM and restart the systemd user service (systemctl --user restart openclaw-gateway).

Why auth_mode = "none" is the default

  • The gateway is loopback-only — there is no path from outside the EC2 instance to port 18789 except through SSM.
  • SSM port-forward is already gated by ssm:StartSession IAM, which is the team’s primary access boundary.
  • Token auth via URL fragment + browser localStorage is fragile — copying a token between profiles, switching browsers, or using an incognito window that previously connected to a different instance on the same localhost:18789 origin can all trigger spurious “Auth did not match” errors with no real security benefit.

Use "token" only when the gateway is reachable from a wider network (e.g. via Tailscale, ALB, or a non-loopback bind) or when a compliance requirement demands an application-level auth layer.


Legacy: hungryhub-terraform/lightsail/

A prior OpenClaw deployment lived in hungryhub-terraform/lightsail/ as aws_cloudformation_stack resources targeting EC2 + Bedrock. Removed in hungryhub-terraform PR #542 (merged 2026-07-07). The directory name was a misnomer — it was CloudFormation-on-EC2, not AWS Lightsail.

Why removed:

  • All five instances (end-user, vendor, platform, automation, data-internal) had been commented out in local.openclaw_instances since fe78a5f (PR #164, 2026-05-06).
  • The module was Bedrock-specific and incompatible with the LiteLLM path that the active hungryhub-ai/openclaw/ deployment uses.
  • Six orphan /openclaw/openclaw-*-prod/gateway-token SSM parameters in the prod account (ap-southeast-1) — matching the commented-out instance names — were deleted in the same cleanup pass.

That Terraform PR also updated terraform-pr-plan.yml so the auto-detect step skips modules whose directory no longer exists in the PR (the guard was previously hard-failing with “Module folder not found”). State cleanup for the deleted module:

# Verified empty (outputs: {}, no resources) — safe to delete
aws s3 rm s3://hungryhub-prod-terraform-202255947274/lightsail/lightsail-state.tfstate \
  --profile prod --region ap-southeast-1

Upstream

Template forked from aws-samples/sample-OpenClaw-on-AWS-with-Bedrock (commit 2026-06). Key upstream improvements cherry-picked:

  • OpenClawVersion parameter
  • EnableMonitoring (CloudWatch)
  • IMDSv2 required
  • OPENCLAW_STATE_DIR env propagation
  • systemd memory limits
  • .env file for credentials

Costs

ResourceEst. monthly (USD)
EC2 t4g.medium~$30
EBS 30GB gp3~$4
CloudWatch logs (14d)~$1
LiteLLM inferencePay-per-use


Changelog

  • 2026-07-07 — Legacy hungryhub-terraform/lightsail/ module removed (PR #542). Six orphan gateway-token SSM parameters in the prod account cleaned up. hungryhub-terraform/terraform-pr-plan.yml updated to skip deleted modules in the auto-detect step. Added a stable bin/openclaw-port-forward.sh wrapper in hh-seo-geo PR #1 (default local port 28789 to avoid the upstream 18789 clash).
  • 2026-06-24 — Added auth_mode per-instance field (default none). Disables token-based gateway auth, which was just adding friction without security benefit on a loopback-only, SSM-port-forwarded gateway. The two SSM/token-related CFN Outputs (Step3GetToken, GatewayTokenSsmPath) are gated by IsTokenAuth so they don’t appear when auth is disabled. See hungryhub-ai PR #31 (new commit on feat/hai-claw-seo).
  • 2026-06-23 — Module added to hungryhub-ai. Sandbox validation of claw-seo on genai-sandbox (MiniMax M3 via LiteLLM) completed successfully, then intentionally torn down to save cost. Production deployment on genai-prod (claw-seo-prod, instance i-06a521617f8b1bf9d) is live and validated — openclaw agent -m 'reply with the word PROD-OK' --agent main returns PROD-OK with provider=openai, model=minimax/MiniMax-M3. See hungryhub-ai PR #31 and knowledge-base PR #284.