Compare commits

..

2 commits

Author SHA1 Message Date
CeeLo Greenheart
9f86fc9c7c Merge branch 'restructure-hermes-openclaw' into main
Restructured Terraform configuration to separate Hermes and OpenClaw
frameworks into dedicated directories and files.

Changes:
- Split cloudinit.tf into cloudinit-hermes.tf and cloudinit-openclaw.tf
- Split variables.tf into variables-common.tf, variables-hermes.tf, variables-openclaw.tf
- Organized templates/ into hermes/templates/ and openclaw/templates/
- Organized models/ into openclaw/models/
- Moved Hermes docs to hermes/docs/
- Fixed Node.js version from 24 to 22 LTS for OpenClaw

Reviewed-by: Caroline (alignment), Atticus (quality), Ludacris (security)
2026-04-26 01:41:37 +00:00
Mermaid Man
ea73745147 refactor: restructure into hermes/ and openclaw/ directories
- Split cloudinit.tf into cloudinit-hermes.tf and cloudinit-openclaw.tf
- Split variables.tf into variables-common.tf, variables-hermes.tf, variables-openclaw.tf
- Move templates into hermes/templates/ and openclaw/templates/
- Move models/ into openclaw/models/
- Move hermes-openclaw.json to openclaw/openclaw-reference.json
- Move hermes docs to hermes/docs/
- OpenClaw cloudinit now uses variables instead of hardcoded values
- All 48 variable references verified against definitions
2026-04-24 19:45:03 +00:00
22 changed files with 303 additions and 249 deletions

View file

@ -1,102 +1,95 @@
# Makefile for OpenBoatmobile
# Convenience commands for common operations
# Auto-detect infrastructure-as-code binary: OpenTofu preferred, Terraform fallback.
# Override with: make TERRAFORM=terraform <target>
TERRAFORM ?= $(shell command -v tofu 2>/dev/null || command -v terraform 2>/dev/null || echo tofu)
.PHONY: help init plan apply destroy ssh logs health clean
# Default target
help:
@echo "OpenBoatmobile - deployment commands"
@echo ""
@echo "Binary: $(TERRAFORM) (OpenTofu preferred, Terraform fallback)"
@echo "Override: make TERRAFORM=terraform <target>"
@echo "OpenBoatmobile - Terraform deployment commands"
@echo ""
@echo "Usage: make <target>"
@echo ""
@echo "Targets:"
@echo " init Initialize $(TERRAFORM)"
@echo " init Initialize Terraform"
@echo " plan Show deployment plan"
@echo " apply Deploy infrastructure"
@echo " apply Deployinfrastructure"
@echo " destroy Destroy infrastructure"
@echo " output Show deployment outputs"
@echo " ssh SSH into server"
@echo " tunnel Create SSH tunnel to gateway"
@echo " logs Show gateway logs"
@echo " health Run health check"
@echo " clean Clean IaC state"
@echo " clean Clean Terraform state"
@echo ""
@echo "Prerequisites:"
@echo " source .env # Load secrets before running"
@echo ""
# Initialize
# Initialize Terraform
init:
$(TERRAFORM) init
terraform init
# Show deployment plan
plan:
$(TERRAFORM) plan
terraform plan
# Deploy infrastructure
apply:
$(TERRAFORM) apply
terraform apply
# Destroy infrastructure
destroy:
$(TERRAFORM) destroy
terraform destroy
# Show outputs
output:
$(TERRAFORM) output
terraform output
# SSH into server (extracts command from output)
# SSH into server (extracts command from Terraform output)
ssh:
@SSH_CMD=$$($(TERRAFORM) output -raw ssh_command); \
@SSH_CMD=$$(terraform output -raw ssh_command);
echo "$$SSH_CMD"; \
$$SSH_CMD
# Create SSH tunnel to gateway (extracts command from output)
tunnel:
@SSH_CMD=$$($(TERRAFORM) output -raw ssh_command); \
IP=$$($(TERRAFORM) output -raw server_ip); \
@SSH_CMD=$$(terraform output -raw ssh_command); \
IP=$$(terraform output -raw server_ip); \
echo "Creating tunnel to gateway..."; \
$$SSH_CMD -L 18789:localhost:18789
# Show gateway logs (requires SSH)
logs:
@SSH_CMD=$$($(TERRAFORM) output -raw ssh_command); \
@SSH_CMD=$$(terraform output -raw ssh_command); \
$$SSH_CMD "journalctl -u openclaw-gateway -f"
# Run health check (requires SSH)
health:
@SSH_CMD=$$($(TERRAFORM) output -raw ssh_command); \
@SSH_CMD=$$(terraform output -raw ssh_command); \
$$SSH_CMD "sudo /usr/local/bin/openclaw-health-check.sh"
# Clean state
# Clean Terraform state
clean:
rm -rf .terraform/
rm -f .terraform.lock.hcl
rm -f terraform.tfstate*
@echo "State cleaned. Run 'make init' to reinitialize."
@echo "Terraform state cleaned. Run 'make init' to reinitialize."
# Validate configuration
validate:
$(TERRAFORM) validate
terraform validate
# Format code
fmt:
$(TERRAFORM) fmt
terraform fmt
# Show workspace status
status:
@echo "=== Workspace ==="
@$(TERRAFORM) workspace show 2>/dev/null || echo "default"
@echo "=== Terraform Workspace ==="
@terraform workspace show 2>/dev/null || echo "default"
@echo ""
@echo "=== State Resources ==="
@$(TERRAFORM) state list 2>/dev/null || echo "No resources in state"
@terraform state list 2>/dev/null || echo "No resources in state"
@echo ""
@echo "=== Outputs ==="
@$(TERRAFORM) output 2>/dev/null || echo "No outputs defined"
@terraform output 2>/dev/null || echo "No outputs defined"

58
cloudinit-hermes.tf Normal file
View file

@ -0,0 +1,58 @@
# Cloud-init Configuration Hermes Agent
# Only active when agent_framework = "hermes"
# Hermes Agent cloud-init
data "cloudinit_config" "hermes" {
count = var.agent_framework == "hermes" ? 1 : 0
gzip = false
base64_encode = true
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = templatefile("${path.module}/hermes/templates/userdata-hermes.tpl", {
# Server configuration
server_name = var.server_name
admin_user = local.effective_admin_user
location = var.location_hetzner
# Agent configuration
agent_name = var.agent_name
primary_model = var.primary_model
primary_model_name = var.primary_model_name
fallback_models = var.fallback_models
docker_enabled = var.docker_enabled
# SSH configuration
ssh_port = var.ssh_port
ssh_allowed_ips = var.ssh_allowed_ips
admin_ssh_keys = var.admin_ssh_keys
# API keys
venice_api_key = var.venice_api_key
venice_base_url = var.venice_base_url
brave_search_api_key = var.brave_search_api_key
# Discord
discord_bot_token = var.discord_bot_token
discord_server_id = var.discord_server_id
discord_user_id = var.discord_user_id
discord_home_channel = var.discord_home_channel
discord_allowed_users = var.discord_allowed_users
discord_auto_thread = var.discord_auto_thread
gateway_allow_all_users = var.gateway_allow_all_users
# Gateway
gateway_token = var.gateway_token != "" ? var.gateway_token : random_password.gateway_token[0].result
gateway_allowed_users = var.gateway_allowed_users
})
}
}
# Random password for gateway token if not provided
resource "random_password" "gateway_token" {
count = var.agent_framework == "hermes" && var.gateway_token == "" ? 1 : 0
length = 32
special = false
}

59
cloudinit-openclaw.tf Normal file
View file

@ -0,0 +1,59 @@
# Cloud-init Configuration OpenClaw Gateway
# Only active when agent_framework = "openclaw"
# OpenClaw cloud-init
data "cloudinit_config" "openclaw" {
count = var.agent_framework == "openclaw" ? 1 : 0
gzip = false
base64_encode = true
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = templatefile("${path.module}/openclaw/templates/userdata-openclaw.tpl", {
# Server configuration
server_name = var.server_name
admin_user = local.effective_admin_user
# SSH configuration
ssh_port = var.ssh_port
ssh_allowed_ips = var.ssh_allowed_ips
admin_ssh_keys = var.admin_ssh_keys
# OpenClaw configuration
openclaw_version = var.openclaw_version
node_version = var.node_version
agent_name = var.agent_name
agent_timezone = var.agent_timezone
# System configuration
enable_swap = var.enable_swap
swap_size = var.swap_size
enable_fail2ban = var.enable_fail2ban
enable_unattended_upgrades = var.enable_unattended_upgrades
# Tailscale
enable_tailscale = var.enable_tailscale
tailscale_auth_key = var.tailscale_auth_key
# API keys
venice_api_key = var.venice_api_key
default_model = var.primary_model
brave_search_api_key = var.brave_search_api_key
# Discord
discord_bot_token = var.discord_bot_token
discord_server_id = var.discord_server_id
discord_user_id = var.discord_user_id
discord_home_channel = var.discord_home_channel
discord_allowed_users = var.discord_allowed_users
discord_auto_thread = var.discord_auto_thread
# Inference models configuration
primary_model = var.primary_model
fallback_models = jsonencode(var.fallback_models)
models_config = file("${path.module}/openclaw/models/venice.json")
})
}
}

View file

@ -1,120 +0,0 @@
# Cloud-init Configuration
# Selects template based on agent_framework variable
# Hermes Agent cloud-init
data "cloudinit_config" "hermes" {
count = var.agent_framework == "hermes" ? 1 : 0
gzip = false
base64_encode = true
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = templatefile("${path.module}/templates/userdata-hermes.tpl", {
# Server configuration
server_name = var.server_name
admin_user = local.effective_admin_user
location = var.location_hetzner
# Agent configuration
agent_name = var.agent_name
primary_model = var.primary_model
primary_model_name = var.primary_model_name
fallback_models = var.fallback_models
docker_enabled = var.docker_enabled
# SSH configuration
ssh_port = var.ssh_port
ssh_allowed_ips = var.ssh_allowed_ips
admin_ssh_keys = var.admin_ssh_keys
# API keys
venice_api_key = var.venice_api_key
venice_base_url = var.venice_base_url
brave_search_api_key = var.brave_search_api_key
# Discord
discord_bot_token = var.discord_bot_token
discord_server_id = var.discord_server_id
discord_user_id = var.discord_user_id
discord_home_channel = var.discord_home_channel
discord_allowed_users = var.discord_allowed_users
discord_auto_thread = var.discord_auto_thread
gateway_allow_all_users = var.gateway_allow_all_users
# Gateway
gateway_token = var.gateway_token != "" ? var.gateway_token : random_password.gateway_token[0].result
gateway_allowed_users = var.gateway_allowed_users
})
}
}
# OpenClaw cloud-init
data "cloudinit_config" "openclaw" {
count = var.agent_framework == "openclaw" ? 1 : 0
gzip = false
base64_encode = true
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = templatefile("${path.module}/templates/userdata-openclaw.tpl", {
# Server configuration
server_name = var.server_name
admin_user = local.effective_admin_user
# SSH configuration
ssh_port = var.ssh_port
ssh_allowed_ips = var.ssh_allowed_ips
admin_ssh_keys = var.admin_ssh_keys
# OpenClaw configuration
openclaw_version = "lts"
node_version = "22"
agent_name = var.agent_name
agent_timezone = "UTC"
# System configuration
enable_swap = true
swap_size = 2
enable_fail2ban = true
enable_unattended_upgrades = true
# Tailscale
enable_tailscale = var.enable_tailscale
tailscale_auth_key = var.tailscale_auth_key
# API keys
venice_api_key = var.venice_api_key
default_model = var.primary_model
brave_search_api_key = var.brave_search_api_key
# Discord
discord_bot_token = var.discord_bot_token
discord_server_id = var.discord_server_id
discord_user_id = var.discord_user_id
discord_home_channel = var.discord_home_channel
discord_allowed_users = var.discord_allowed_users
discord_auto_thread = var.discord_auto_thread
# Inference models configuration
primary_model = var.primary_model
fallback_models = jsonencode(var.fallback_models)
models_config = file("${path.module}/models/venice.json")
})
}
}
# Random password for gateway token if not provided
resource "random_password" "gateway_token" {
count = var.agent_framework == "hermes" && var.gateway_token == "" ? 1 : 0
length = 32
special = false
}
# Output selected userdata
locals {
userdata = var.agent_framework == "hermes" ? data.cloudinit_config.hermes[0].rendered : data.cloudinit_config.openclaw[0].rendered
}

View file

@ -2,7 +2,7 @@
# Provider-agnostic infrastructure for OpenClaw or Hermes agents
terraform {
required_version = ">= 1.6.0"
required_version = ">= 1.5.4"
required_providers {
digitalocean = {
@ -49,7 +49,10 @@ locals {
# Common tags/labels for resource tracking
common_tags = {
project = var.project_name
managed = "tofu"
managed = "terraform"
component = var.agent_framework == "hermes" ? "hermes-agent" : "openclaw-gateway"
}
# Select userdata based on framework
userdata = var.agent_framework == "hermes" ? data.cloudinit_config.hermes[0].rendered : data.cloudinit_config.openclaw[0].rendered
}

View file

@ -1,4 +1,5 @@
# OpenBoatmobile Configuration Variables
# OpenBoatmobile Configuration Variables Common
# Shared by both Hermes and OpenClaw deployments
# Environment-based secrets: Set TF_VAR_<name> in your shell or .env file
# =============================================================================
@ -148,66 +149,16 @@ variable "admin_ssh_keys" {
}
# =============================================================================
# AGENT CONFIGURATION
# =============================================================================
variable "agent_name" {
description = "Name for the agent"
type = string
default = "hermes"
}
variable "docker_enabled" {
description = "Whether to deploy Hermes in Docker container (true) or install directly on host (false)"
type = bool
default = true
}
variable "agent_timezone" {
description = "Timezone for the agent"
type = string
default = "UTC"
}
# =============================================================================
# MODEL CONFIGURATION
# =============================================================================
variable "primary_model" {
description = "Primary model for inference (without venice/ prefix when using Venice API directly)"
type = string
default = "olafangensan-glm-4.7-flash-heretic"
}
variable "primary_model_name" {
description = "Human-readable name for the primary model"
type = string
default = "GLM 4.7 Flash Heretic"
}
variable "fallback_models" {
description = "List of fallback models in priority order (without venice/ prefix)"
type = list(string)
default = ["zai-org-glm-5"]
}
# =============================================================================
# API KEYS (Set via environment: TF_VAR_<name>)
# API KEYS Shared (Set via environment: TF_VAR_<name>)
# =============================================================================
variable "venice_api_key" {
description = "Venice AI API key for inference (used as OPENAI_API_KEY for custom endpoint)"
description = "Venice AI API key for inference"
type = string
sensitive = true
default = ""
}
variable "venice_base_url" {
description = "Venice AI base URL (default: https://api.venice.ai/api/v1)"
type = string
default = "https://api.venice.ai/api/v1"
}
variable "brave_search_api_key" {
description = "Brave Search API key"
type = string
@ -216,7 +167,7 @@ variable "brave_search_api_key" {
}
# =============================================================================
# DISCORD CONFIGURATION
# DISCORD CONFIGURATION Shared
# =============================================================================
variable "discord_bot_token" {
@ -238,47 +189,6 @@ variable "discord_user_id" {
default = []
}
variable "discord_home_channel" {
description = "Discord channel ID for home channel (cron delivery, notifications)"
type = string
default = ""
}
variable "discord_allowed_users" {
description = "Comma-separated Discord user IDs allowed (DISCORD_ALLOWED_USERS)"
type = string
default = ""
}
variable "discord_auto_thread" {
description = "Auto-create threads on @mention (DISCORD_AUTO_THREAD)"
type = bool
default = true
}
variable "gateway_allow_all_users" {
description = "Allow all users without allowlist (GATEWAY_ALLOW_ALL_USERS)"
type = bool
default = true
}
# =============================================================================
# GATEWAY CONFIGURATION
# =============================================================================
variable "gateway_token" {
description = "Gateway authentication token"
type = string
sensitive = true
default = ""
}
variable "gateway_allowed_users" {
description = "Comma-separated list of allowed user IDs"
type = string
default = ""
}
# =============================================================================
# PROJECT METADATA
# =============================================================================
@ -316,4 +226,4 @@ variable "tailscale_tailnet_domain" {
description = "Tailscale tailnet domain (without .ts.net suffix)"
type = string
default = "tailnet"
}
}

105
variables-hermes.tf Normal file
View file

@ -0,0 +1,105 @@
# OpenBoatmobile Configuration Variables Hermes-specific
# Only used when agent_framework = "hermes"
# =============================================================================
# AGENT CONFIGURATION
# =============================================================================
variable "agent_name" {
description = "Name for the agent"
type = string
default = "hermes"
}
variable "docker_enabled" {
description = "Whether to deploy Hermes in Docker container (true) or install directly on host (false)"
type = bool
default = true
}
# =============================================================================
# MODEL CONFIGURATION
# =============================================================================
variable "primary_model" {
description = "Primary model for inference (without venice/ prefix when using Venice API directly)"
type = string
default = "olafangensan-glm-4.7-flash-heretic"
}
variable "primary_model_name" {
description = "Human-readable name for the primary model"
type = string
default = "GLM 4.7 Flash Heretic"
}
variable "fallback_models" {
description = "List of fallback models in priority order (without venice/ prefix)"
type = list(string)
default = ["zai-org-glm-5"]
}
# =============================================================================
# HERMES API CONFIGURATION
# =============================================================================
variable "venice_base_url" {
description = "Venice AI base URL (default: https://api.venice.ai/api/v1)"
type = string
default = "https://api.venice.ai/api/v1"
}
# =============================================================================
# DISCORD Hermes-specific
# =============================================================================
variable "discord_home_channel" {
description = "Discord channel ID for home channel (cron delivery, notifications)"
type = string
default = ""
}
variable "discord_allowed_users" {
description = "Comma-separated Discord user IDs allowed (DISCORD_ALLOWED_USERS)"
type = string
default = ""
}
variable "discord_auto_thread" {
description = "Auto-create threads on @mention (DISCORD_AUTO_THREAD)"
type = bool
default = true
}
# =============================================================================
# GATEWAY CONFIGURATION Hermes-specific
# =============================================================================
variable "gateway_token" {
description = "Gateway authentication token"
type = string
sensitive = true
default = ""
}
variable "gateway_allowed_users" {
description = "Comma-separated list of allowed user IDs"
type = string
default = ""
}
variable "gateway_allow_all_users" {
description = "Allow all users without allowlist (GATEWAY_ALLOW_ALL_USERS)"
type = bool
default = true
}
# =============================================================================
# TIMEZONE
# =============================================================================
variable "agent_timezone" {
description = "Timezone for the agent"
type = string
default = "UTC"
}

46
variables-openclaw.tf Normal file
View file

@ -0,0 +1,46 @@
# OpenBoatmobile Configuration Variables OpenClaw-specific
# Only used when agent_framework = "openclaw"
# =============================================================================
# OPENCLAW INSTALLATION
# =============================================================================
variable "openclaw_version" {
description = "OpenClaw version to install: 'latest', 'lts', or a specific version string"
type = string
default = "lts"
}
variable "node_version" {
description = "Node.js major version for OpenClaw runtime (LTS recommended: 22)"
type = string
default = "22"
}
# =============================================================================
# SYSTEM CONFIGURATION OpenClaw defaults
# =============================================================================
variable "enable_swap" {
description = "Create a swap file on the server"
type = bool
default = true
}
variable "swap_size" {
description = "Swap file size in GB"
type = number
default = 2
}
variable "enable_fail2ban" {
description = "Install and configure fail2ban for SSH protection"
type = bool
default = true
}
variable "enable_unattended_upgrades" {
description = "Enable automatic security updates"
type = bool
default = true
}