diff --git a/hermes/docs/FIX_SUMMARY.md b/HERMES_FIX_SUMMARY.md similarity index 100% rename from hermes/docs/FIX_SUMMARY.md rename to HERMES_FIX_SUMMARY.md diff --git a/hermes/docs/VERIFICATION_CHECKLIST.md b/HERMES_VERIFICATION_CHECKLIST.md similarity index 100% rename from hermes/docs/VERIFICATION_CHECKLIST.md rename to HERMES_VERIFICATION_CHECKLIST.md diff --git a/Makefile b/Makefile index a15d3bc..05159cd 100644 --- a/Makefile +++ b/Makefile @@ -1,95 +1,102 @@ # Makefile for OpenBoatmobile # Convenience commands for common operations +# Auto-detect infrastructure-as-code binary: OpenTofu preferred, Terraform fallback. +# Override with: make TERRAFORM=terraform +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 - Terraform deployment commands" + @echo "OpenBoatmobile - deployment commands" + @echo "" + @echo "Binary: $(TERRAFORM) (OpenTofu preferred, Terraform fallback)" + @echo "Override: make TERRAFORM=terraform " @echo "" @echo "Usage: make " @echo "" @echo "Targets:" - @echo " init Initialize Terraform" + @echo " init Initialize $(TERRAFORM)" @echo " plan Show deployment plan" - @echo " apply Deployinfrastructure" + @echo " apply Deploy infrastructure" @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 Terraform state" + @echo " clean Clean IaC state" @echo "" @echo "Prerequisites:" @echo " source .env # Load secrets before running" @echo "" -# Initialize Terraform +# Initialize 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 Terraform output) +# SSH into server (extracts command from 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 Terraform state +# Clean state clean: rm -rf .terraform/ rm -f .terraform.lock.hcl rm -f terraform.tfstate* - @echo "Terraform state cleaned. Run 'make init' to reinitialize." + @echo "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 "=== Terraform Workspace ===" - @terraform workspace show 2>/dev/null || echo "default" + @echo "=== 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" \ No newline at end of file + @$(TERRAFORM) output 2>/dev/null || echo "No outputs defined" \ No newline at end of file diff --git a/cloudinit-hermes.tf b/cloudinit-hermes.tf deleted file mode 100644 index bc1b2cd..0000000 --- a/cloudinit-hermes.tf +++ /dev/null @@ -1,58 +0,0 @@ -# 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 -} diff --git a/cloudinit-openclaw.tf b/cloudinit-openclaw.tf deleted file mode 100644 index c38b191..0000000 --- a/cloudinit-openclaw.tf +++ /dev/null @@ -1,59 +0,0 @@ -# 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") - }) - } -} diff --git a/cloudinit.tf b/cloudinit.tf new file mode 100644 index 0000000..da8ed45 --- /dev/null +++ b/cloudinit.tf @@ -0,0 +1,120 @@ +# 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 +} \ No newline at end of file diff --git a/hermes/docs/AUDIT_REPORT.md b/docs/HERMES_AUDIT_REPORT.md similarity index 100% rename from hermes/docs/AUDIT_REPORT.md rename to docs/HERMES_AUDIT_REPORT.md diff --git a/hermes/docs/DEBUGGING.md b/docs/HERMES_DEBUGGING.md similarity index 100% rename from hermes/docs/DEBUGGING.md rename to docs/HERMES_DEBUGGING.md diff --git a/openclaw/openclaw-reference.json b/hermes-openclaw.json similarity index 100% rename from openclaw/openclaw-reference.json rename to hermes-openclaw.json diff --git a/main.tf b/main.tf index 4555a21..a162676 100644 --- a/main.tf +++ b/main.tf @@ -2,7 +2,7 @@ # Provider-agnostic infrastructure for OpenClaw or Hermes agents terraform { - required_version = ">= 1.5.4" + required_version = ">= 1.6.0" required_providers { digitalocean = { @@ -49,10 +49,7 @@ locals { # Common tags/labels for resource tracking common_tags = { project = var.project_name - managed = "terraform" + managed = "tofu" 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 } \ No newline at end of file diff --git a/openclaw/models/anthropic.json b/models/anthropic.json similarity index 100% rename from openclaw/models/anthropic.json rename to models/anthropic.json diff --git a/openclaw/models/combined.json b/models/combined.json similarity index 100% rename from openclaw/models/combined.json rename to models/combined.json diff --git a/openclaw/models/gemini.json b/models/gemini.json similarity index 100% rename from openclaw/models/gemini.json rename to models/gemini.json diff --git a/openclaw/models/groq.json b/models/groq.json similarity index 100% rename from openclaw/models/groq.json rename to models/groq.json diff --git a/openclaw/models/openai.json b/models/openai.json similarity index 100% rename from openclaw/models/openai.json rename to models/openai.json diff --git a/openclaw/models/openrouter.json b/models/openrouter.json similarity index 100% rename from openclaw/models/openrouter.json rename to models/openrouter.json diff --git a/openclaw/models/venice.json b/models/venice.json similarity index 100% rename from openclaw/models/venice.json rename to models/venice.json diff --git a/hermes/templates/userdata-hermes.tpl b/templates/userdata-hermes.tpl similarity index 100% rename from hermes/templates/userdata-hermes.tpl rename to templates/userdata-hermes.tpl diff --git a/openclaw/templates/userdata-openclaw.tpl b/templates/userdata-openclaw.tpl similarity index 100% rename from openclaw/templates/userdata-openclaw.tpl rename to templates/userdata-openclaw.tpl diff --git a/variables-hermes.tf b/variables-hermes.tf deleted file mode 100644 index d2155d2..0000000 --- a/variables-hermes.tf +++ /dev/null @@ -1,105 +0,0 @@ -# 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" -} diff --git a/variables-openclaw.tf b/variables-openclaw.tf deleted file mode 100644 index 5d135e6..0000000 --- a/variables-openclaw.tf +++ /dev/null @@ -1,46 +0,0 @@ -# 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 -} diff --git a/variables-common.tf b/variables.tf similarity index 69% rename from variables-common.tf rename to variables.tf index cfa8051..ea031de 100644 --- a/variables-common.tf +++ b/variables.tf @@ -1,5 +1,4 @@ -# OpenBoatmobile Configuration Variables — Common -# Shared by both Hermes and OpenClaw deployments +# OpenBoatmobile Configuration Variables # Environment-based secrets: Set TF_VAR_ in your shell or .env file # ============================================================================= @@ -149,16 +148,66 @@ variable "admin_ssh_keys" { } # ============================================================================= -# API KEYS — Shared (Set via environment: TF_VAR_) +# 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_) # ============================================================================= variable "venice_api_key" { - description = "Venice AI API key for inference" + description = "Venice AI API key for inference (used as OPENAI_API_KEY for custom endpoint)" 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 @@ -167,7 +216,7 @@ variable "brave_search_api_key" { } # ============================================================================= -# DISCORD CONFIGURATION — Shared +# DISCORD CONFIGURATION # ============================================================================= variable "discord_bot_token" { @@ -189,6 +238,47 @@ 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 # ============================================================================= @@ -226,4 +316,4 @@ variable "tailscale_tailnet_domain" { description = "Tailscale tailnet domain (without .ts.net suffix)" type = string default = "tailnet" -} +} \ No newline at end of file