obm/internal/config/deployment.go
MermaidMan 33d9a2cb2e deploy walkthrough, API validation, inference client, Hetzner provider
- Interactive deploy command with 8-step walkthrough:
  framework → provider → token → SSH → server → inference → tailscale → discord
- .env file generation from walkthrough config
- DeploymentConfig struct with framework-aware defaults
- Inference API client with validation for Venice, OpenRouter, OpenAI, Anthropic
- Hetzner Cloud provider: token validation, SSH key listing
- DotEnv parser/writer with schema validation
- Destroy command with confirmation prompt
- Validation subcommand for checking existing .env files
- All tests passing, go vet clean
2026-05-22 15:29:27 +00:00

166 lines
3.8 KiB
Go

// Package config defines the deployment configuration for OpenBoatmobile.
package config
import (
"strings"
)
// DeploymentConfig holds all configuration gathered during the walkthrough.
type DeploymentConfig struct {
// Framework selection
Framework string // "hermes" or "openclaw"
// Cloud provider
CloudProvider string // "hetzner" or "digitalocean"
// Provider tokens (sensitive)
HetznerToken string
DOToken string
// SSH configuration
SSHKeyNames []string
SSHKeyFingerprints []string
// Server configuration
ServerName string
Location string // hetzner: ash, fsn1, nbg1, hel1
Region string // DO: nyc3, sfo2, ams3, etc.
ServerType string // hetzner: cpx21, cx23, etc.
DropletSize string // DO: s-2vcpu-4gb, etc.
AgentName string
AgentTimezone string
// Inference
InferenceProvider string // "venice", "openrouter", "openai", "anthropic", "custom"
InferenceAPIKey string
InferenceBaseURL string
// Fallback inference
FallbackProviders []InferenceProviderConfig
// Model
PrimaryModel string
PrimaryModelName string
FallbackModels []string
VeniceBaseURL string
// Docker (Hermes only)
DockerEnabled bool
// OpenClaw-specific
OpenClawVersion string
NodeVersion string
EnableSwap bool
SwapSizeGB int
EnableFail2ban bool
EnableUnattendedUpgrades bool
// Tailscale
EnableTailscale bool
TailscaleAuthKey string
TailnetDomain string
// Discord
EnableDiscord bool
DiscordBotToken string
DiscordServerID string
DiscordUserIDs []string
// Hermes-specific Discord
DiscordHomeChannel string
DiscordAllowedUsers string
DiscordAutoThread bool
// Gateway (Hermes)
GatewayToken string
GatewayAllowedUsers string
GatewayAllowAllUsers bool
// Optional integrations
BraveSearchAPIKey string
}
// InferenceProviderConfig holds a single inference provider's config.
type InferenceProviderConfig struct {
Provider string
APIKey string
BaseURL string
}
// AdminUser returns the admin username based on framework selection.
func (c *DeploymentConfig) AdminUser() string {
return c.Framework // "hermes" or "openclaw"
}
// MonthlyCostEstimate returns an estimated monthly cost string.
func (c *DeploymentConfig) MonthlyCostEstimate() string {
switch c.CloudProvider {
case "hetzner":
return hetznerPrice(c.ServerType)
case "digitalocean":
return doPrice(c.DropletSize)
default:
return "unknown"
}
}
func hetznerPrice(serverType string) string {
prices := map[string]string{
"cx22": "€3.79/mo",
"cx23": "€5.83/mo",
"cpx21": "€4.49/mo",
"cpx31": "€8.98/mo",
"cpx41": "€17.96/mo",
}
if p, ok := prices[serverType]; ok {
return p
}
return "see Hetzner pricing"
}
func doPrice(size string) string {
prices := map[string]string{
"s-1vcpu-1gb": "$6/mo",
"s-1vcpu-2gb": "$12/mo",
"s-2vcpu-4gb": "$24/mo",
"s-4vcpu-8gb": "$48/mo",
"g-2vcpu-8gb": "$63/mo",
}
if p, ok := prices[size]; ok {
return p
}
return "see DO pricing"
}
// LocationOrRegion returns the location (Hetzner) or region (DO) string.
func LocationOrRegion(c *DeploymentConfig) string {
if c.CloudProvider == "hetzner" {
return c.Location
}
return c.Region
}
// ServerTypeOrDroplet returns the server type or droplet size string.
func ServerTypeOrDroplet(c *DeploymentConfig) string {
if c.CloudProvider == "hetzner" {
return c.ServerType
}
return c.DropletSize
}
// SSHKeySummary returns a human-readable summary of the SSH key config.
func SSHKeySummary(c *DeploymentConfig) string {
if len(c.SSHKeyNames) > 0 {
return strings.Join(c.SSHKeyNames, ", ")
}
if len(c.SSHKeyFingerprints) > 0 {
return "****" + c.SSHKeyFingerprints[0][max(0, len(c.SSHKeyFingerprints[0])-4):]
}
return "(none)"
}
func max(a, b int) int {
if a > b {
return a
}
return b
}