// Package config handles loading, parsing, and writing obm configuration. // It supports .env files (TF_VAR_ prefixed) and terraform.tfvars generation. package config // ValueType represents the Terraform variable type. type ValueType string const ( TypeString ValueType = "string" TypeNumber ValueType = "number" TypeBool ValueType = "bool" TypeList ValueType = "list" ) // VarGroup categorizes variables for organized output. type VarGroup string const ( GroupProvider VarGroup = "PROVIDER" GroupProviderDO VarGroup = "PROVIDER — DigitalOcean" GroupProviderHetzner VarGroup = "PROVIDER — Hetzner" GroupServer VarGroup = "SERVER CONFIGURATION" GroupSSH VarGroup = "SSH CONFIGURATION" GroupAPIKeys VarGroup = "API KEYS" GroupDiscord VarGroup = "DISCORD" GroupTailscale VarGroup = "TAILSCALE" GroupHermes VarGroup = "HERMES-SPECIFIC" GroupOpenClaw VarGroup = "OPENCLAW-SPECIFIC" GroupSecurity VarGroup = "SECURITY" GroupProject VarGroup = "PROJECT METADATA" GroupModel VarGroup = "MODEL CONFIGURATION" ) // VarDef defines a single Terraform variable with metadata. type VarDef struct { Name string // TF variable name (e.g. "cloud_provider") Type ValueType // string, number, bool, list Default string // Default value as string (empty = no default) Required bool // Must be set by user Sensitive bool // Secret value (redacted in output) Description string // Human-readable description Group VarGroup // Section for organized output EnvComment string // Additional .env comment hint (e.g. "or digitalocean") } // schemaCache stores the schema to avoid reallocation. Must be initialized first. var schemaCache []VarDef // init initializes the schema cache. func init() { schemaCache = buildSchema() } // buildSchema constructs the complete variable schema. // Order matters — this controls the output order in .env and tfvars. func buildSchema() []VarDef { return []VarDef{ // --- Provider Selection --- { Name: "cloud_provider", Type: TypeString, Default: "hetzner", Required: true, Sensitive: false, Description: "Cloud provider to use: 'digitalocean' or 'hetzner'", Group: GroupProvider, EnvComment: "or digitalocean", }, { Name: "agent_framework", Type: TypeString, Default: "hermes", Required: false, Sensitive: false, Description: "Agent framework to deploy: 'openclaw' or 'hermes'", Group: GroupProvider, }, // --- Provider Tokens --- { Name: "hcloud_token", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Hetzner Cloud API token", Group: GroupProviderHetzner, }, { Name: "do_token", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "DigitalOcean API token", Group: GroupProviderDO, }, // --- Server Configuration --- { Name: "server_name", Type: TypeString, Default: "agent-gateway", Required: false, Sensitive: false, Description: "Hostname for the server", Group: GroupServer, }, { Name: "server_type_hetzner", Type: TypeString, Default: "cpx21", Required: false, Sensitive: false, Description: "Hetzner server type (e.g., cx23, cpx21)", Group: GroupProviderHetzner, }, { Name: "server_image", Type: TypeString, Default: "ubuntu-24.04", Required: false, Sensitive: false, Description: "Hetzner server image (e.g., ubuntu-24.04)", Group: GroupProviderHetzner, }, { Name: "location_hetzner", Type: TypeString, Default: "ash", Required: false, Sensitive: false, Description: "Hetzner location (nbg1, fsn1, hel1, ash)", Group: GroupProviderHetzner, }, { Name: "droplet_size_digitalocean", Type: TypeString, Default: "s-2vcpu-4gb", Required: false, Sensitive: false, Description: "DigitalOcean droplet size (e.g., s-2vcpu-4gb)", Group: GroupProviderDO, }, { Name: "region_digitalocean", Type: TypeString, Default: "nyc3", Required: false, Sensitive: false, Description: "DigitalOcean region (e.g., nyc3, sfo2, ams3)", Group: GroupProviderDO, }, { Name: "create_network", Type: TypeBool, Default: "false", Required: false, Sensitive: false, Description: "Create a private network for multi-server deployments", Group: GroupServer, }, { Name: "network_ip_range", Type: TypeString, Default: "10.10.0.0/16", Required: false, Sensitive: false, Description: "IP range for private network", Group: GroupServer, }, { Name: "network_zone", Type: TypeString, Default: "eu-central", Required: false, Sensitive: false, Description: "Hetzner network zone", Group: GroupProviderHetzner, }, // --- SSH Configuration --- { Name: "ssh_key_names", Type: TypeList, Default: "[]", Required: false, Sensitive: false, Description: "SSH key names (Hetzner: key name in console)", Group: GroupSSH, }, { Name: "ssh_key_fingerprints", Type: TypeList, Default: "[]", Required: false, Sensitive: false, Description: "DigitalOcean SSH key fingerprints", Group: GroupSSH, }, { Name: "ssh_port", Type: TypeNumber, Default: "22", Required: false, Sensitive: false, Description: "SSH port (non-standard can be more secure)", Group: GroupSSH, }, { Name: "ssh_allowed_ips", Type: TypeList, Default: `["0.0.0.0/0", "::/0"]`, Required: false, Sensitive: false, Description: "IPs allowed to connect via SSH", Group: GroupSSH, }, { Name: "admin_user", Type: TypeString, Default: "", Required: false, Sensitive: false, Description: "Admin username (defaults to framework name)", Group: GroupSSH, }, { Name: "admin_ssh_keys", Type: TypeList, Default: "[]", Required: false, Sensitive: false, Description: "Additional public SSH keys for admin user", Group: GroupSSH, }, // --- API Keys --- { Name: "venice_api_key", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Venice AI API key for inference", Group: GroupAPIKeys, }, { Name: "brave_search_api_key", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Brave Search API key", Group: GroupAPIKeys, }, // --- Model Configuration --- { Name: "primary_model", Type: TypeString, Default: "olafangensan-glm-4.7-flash-heretic", Required: false, Sensitive: false, Description: "Primary model for inference", Group: GroupModel, }, { Name: "primary_model_name", Type: TypeString, Default: "GLM 4.7 Flash Heretic", Required: false, Sensitive: false, Description: "Human-readable name for the primary model", Group: GroupModel, }, { Name: "fallback_models", Type: TypeList, Default: `["zai-org-glm-5"]`, Required: false, Sensitive: false, Description: "Fallback models in priority order", Group: GroupModel, }, { Name: "venice_base_url", Type: TypeString, Default: "https://api.venice.ai/api/v1", Required: false, Sensitive: false, Description: "Venice AI base URL", Group: GroupModel, }, // --- Discord --- { Name: "discord_bot_token", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Discord bot token", Group: GroupDiscord, }, { Name: "discord_server_id", Type: TypeString, Default: "", Required: false, Sensitive: false, Description: "Discord server/guild ID", Group: GroupDiscord, }, { Name: "discord_user_id", Type: TypeList, Default: "[]", Required: false, Sensitive: false, Description: "Discord user IDs for allowlist", Group: GroupDiscord, }, { Name: "discord_home_channel", Type: TypeString, Default: "", Required: false, Sensitive: false, Description: "Discord channel ID for home channel (Hermes)", Group: GroupDiscord, }, { Name: "discord_allowed_users", Type: TypeString, Default: "", Required: false, Sensitive: false, Description: "Comma-separated Discord user IDs allowed (Hermes)", Group: GroupDiscord, }, { Name: "discord_auto_thread", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Auto-create threads on @mention (Hermes)", Group: GroupDiscord, }, // --- Tailscale --- { Name: "enable_tailscale", Type: TypeBool, Default: "false", Required: false, Sensitive: false, Description: "Install Tailscale for secure remote access", Group: GroupTailscale, }, { Name: "tailscale_auth_key", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Tailscale auth key", Group: GroupTailscale, }, { Name: "tailscale_tailnet_domain", Type: TypeString, Default: "tailnet", Required: false, Sensitive: false, Description: "Tailscale tailnet domain (without .ts.net suffix)", Group: GroupTailscale, }, // --- Hermes-specific --- { Name: "agent_name", Type: TypeString, Default: "hermes", Required: false, Sensitive: false, Description: "Name for the agent (Hermes)", Group: GroupHermes, }, { Name: "docker_enabled", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Deploy in Docker (true) or install directly (false)", Group: GroupHermes, }, { Name: "gateway_token", Type: TypeString, Default: "", Required: false, Sensitive: true, Description: "Gateway authentication token (Hermes)", Group: GroupHermes, }, { Name: "gateway_allowed_users", Type: TypeString, Default: "", Required: false, Sensitive: false, Description: "Comma-separated list of allowed user IDs (Hermes gateway)", Group: GroupHermes, }, { Name: "gateway_allow_all_users", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Allow all users without allowlist (Hermes gateway)", Group: GroupHermes, }, { Name: "agent_timezone", Type: TypeString, Default: "UTC", Required: false, Sensitive: false, Description: "Timezone for the agent", Group: GroupHermes, }, // --- OpenClaw-specific --- { Name: "openclaw_version", Type: TypeString, Default: "lts", Required: false, Sensitive: false, Description: "OpenClaw version: 'latest', 'lts', or specific version", Group: GroupOpenClaw, }, { Name: "node_version", Type: TypeString, Default: "22", Required: false, Sensitive: false, Description: "Node.js major version (22 recommended)", Group: GroupOpenClaw, }, { Name: "enable_swap", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Create a swap file on the server", Group: GroupOpenClaw, }, { Name: "swap_size", Type: TypeNumber, Default: "2", Required: false, Sensitive: false, Description: "Switch file size in GB", Group: GroupOpenClaw, }, // --- Security --- { Name: "enable_fail2ban", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Install and configure fail2ban for SSH protection", Group: GroupSecurity, }, { Name: "enable_unattended_upgrades", Type: TypeBool, Default: "true", Required: false, Sensitive: false, Description: "Enable automatic security updates", Group: GroupSecurity, }, // --- Project Metadata --- { Name: "project_name", Type: TypeString, Default: "OpenBoatmobile", Required: false, Sensitive: false, Description: "Project name for tagging", Group: GroupProject, }, { Name: "environment", Type: TypeString, Default: "production", Required: false, Sensitive: false, Description: "Environment name (e.g., production, staging, development)", Group: GroupProject, }, } } // Schema returns the complete variable schema for OpenBoatmobile. // Order matters — this controls the output order in .env and tfvars. func Schema() []VarDef { return schemaCache } // SchemaMap returns a lookup map of variable name -> VarDef. func SchemaMap() map[string]VarDef { m := make(map[string]VarDef, len(schemaCache)) for _, v := range schemaCache { m[v.Name] = v } return m } // RequiredVars returns only the required variables. func RequiredVars() []VarDef { var out []VarDef for _, v := range schemaCache { if v.Required { out = append(out, v) } } return out } // SensitiveVars returns only the sensitive variables. func SensitiveVars() []VarDef { var out []VarDef for _, v := range schemaCache { if v.Sensitive { out = append(out, v) } } return out } // VarsByGroup returns variables organized by group, preserving order. func VarsByGroup() map[VarGroup][]VarDef { out := make(map[VarGroup][]VarDef) for _, v := range schemaCache { out[v.Group] = append(out[v.Group], v) } return out }