add documentation trio: README (casual), DETAILS (technical), LLMs (agent reference)
Some checks failed
CI / Test (pull_request) Failing after 56s
CI / Build (pull_request) Has been skipped
CI / Build-1 (pull_request) Has been skipped
CI / Build-2 (pull_request) Has been skipped
CI / Build-3 (pull_request) Has been skipped
CI / Build-4 (pull_request) Has been skipped
Some checks failed
CI / Test (pull_request) Failing after 56s
CI / Build (pull_request) Has been skipped
CI / Build-1 (pull_request) Has been skipped
CI / Build-2 (pull_request) Has been skipped
CI / Build-3 (pull_request) Has been skipped
CI / Build-4 (pull_request) Has been skipped
This commit is contained in:
parent
21f2bd3a9d
commit
bf9499bac0
3 changed files with 821 additions and 131 deletions
418
DETAILS.md
Normal file
418
DETAILS.md
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
# DETAILS.md — Technical Reference
|
||||
|
||||
Companion to [README.md](README.md). This file contains the full technical details that would normally live in a professional README — architecture, API surface, configuration schema, build system, and development workflows. Intended for developers, contributors, and automated tooling.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
`obm` is a Go CLI that generates Terraform-compatible `.env` files through an interactive walkthrough or a YAML config file. It validates API credentials against live endpoints before writing config, and wraps Terraform lifecycle commands (init, apply, destroy).
|
||||
|
||||
- **Language:** Go 1.22
|
||||
- **Module:** `github.com/openboatmobile/obm`
|
||||
- **Dependencies:** `gopkg.in/yaml.v3` (sole external dependency)
|
||||
- **Binary:** Single statically-linked binary, zero runtime dependencies
|
||||
- **Current version:** See `VERSION` file (0.1.0 at time of writing)
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
obm/
|
||||
├── cmd/obm/main.go # CLI entry point, subcommand routing
|
||||
├── internal/
|
||||
│ ├── config/
|
||||
│ │ ├── config.go # Config struct, GetValue/SetValue
|
||||
│ │ ├── deployment.go # DeploymentConfig, AdminUser(), MonthlyCostEstimate()
|
||||
│ │ ├── dotenv.go # DotEnvFile parser (round-trip .env read)
|
||||
│ │ ├── dotenv_writer.go # WriteDotEnv — grouped, commented .env output
|
||||
│ │ ├── schema.go # VarDef schema (all TF_VAR_ variables), VarGroup enum
|
||||
│ │ ├── tfvars.go # WriteTfVars — HCL-format tfvars output
|
||||
│ │ ├── yaml.go # YAMLConfig struct, LoadYAMLConfig(), ToDeploymentConfig()
|
||||
│ │ ├── config_test.go
|
||||
│ │ └── yaml_test.go
|
||||
│ ├── deploy/
|
||||
│ │ └── deploy.go # Walkthrough orchestrator (Run, RunFromFile, RunWithConfig)
|
||||
│ ├── destroy/
|
||||
│ │ ├── destroy.go # Terraform destroy with state parsing, confirmation
|
||||
│ │ └── destroy_test.go
|
||||
│ ├── inference/
|
||||
│ │ ├── client.go # HTTP client, ValidateAPIKey(), ValidationResult
|
||||
│ │ ├── inference.go # Provider enum, ProviderConfig, FallbackChain, DefaultGLMConfig
|
||||
│ │ ├── client_test.go
|
||||
│ │ └── inference_test.go
|
||||
│ ├── prompt/
|
||||
│ │ ├── prompt.go # Terminal I/O: Select, Confirm, Input, Password, color helpers
|
||||
│ │ └── prompt_test.go
|
||||
│ ├── provider/
|
||||
│ │ ├── provider.go # Provider interface, BaseProvider, Registry, Register/Get
|
||||
│ │ ├── import.go # Provider registration (blank import guidance)
|
||||
│ │ ├── hetzner/
|
||||
│ │ │ ├── hetzner.go # HetznerProvider: API validation, SSH key listing
|
||||
│ │ │ └── hetzner_test.go
|
||||
│ │ └── provider_test.go
|
||||
│ ├── terraform/
|
||||
│ │ └── terraform.go # Runner: Init, Plan, Apply, Destroy wrappers
|
||||
│ └── validation/
|
||||
│ ├── validation.go # Check interface, Runner, CheckResult, Status enum
|
||||
│ └── validation_test.go
|
||||
├── scripts/
|
||||
│ ├── install.sh # curl | sh installer
|
||||
│ └── release.sh # Tag + push release automation
|
||||
├── .github/workflows/
|
||||
│ ├── ci.yml # Test + build on push/PR
|
||||
│ └── release.yml # Cross-compile + GitHub Release on tag
|
||||
├── Makefile # Build, test, lint, cross-compile targets
|
||||
├── Dockerfile # Multi-stage: golang:1.22-alpine → alpine:3.20
|
||||
├── deploy.yaml.example # Full YAML config reference
|
||||
├── CHANGELOG.md
|
||||
├── CONTRIBUTING.md
|
||||
└── VERSION # Single line, e.g. "0.1.0"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Interface
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Command | Flags | Description |
|
||||
|---------|-------|-------------|
|
||||
| `obm deploy` | `--config <path>` | Interactive walkthrough (default) or non-interactive from YAML |
|
||||
| `obm validate` | `--env-file <path>` | Load `.env`, check required vars, validate API keys |
|
||||
| `obm status` | — | Show deployment state (not yet implemented) |
|
||||
| `obm destroy` | — | Confirmation prompt → `terraform destroy` → state cleanup |
|
||||
| `obm version` | — | Print version with commit hash and build time |
|
||||
| `obm help` | — | Print usage |
|
||||
|
||||
### Build-time variables
|
||||
|
||||
Injected via `-ldflags` at build time:
|
||||
|
||||
| Variable | Flag | Example |
|
||||
|----------|------|---------|
|
||||
| `main.version` | `-X main.version=0.1.0` | Semver from `VERSION` file |
|
||||
| `main.gitCommit` | `-X main.gitCommit=abc1234` | Short commit SHA |
|
||||
| `main.buildTime` | `-X main.buildTime=2026-05-22T15:30:00Z` | UTC ISO timestamp |
|
||||
|
||||
---
|
||||
|
||||
## Deploy Walkthrough Flow
|
||||
|
||||
The `obm deploy` interactive flow runs 8 steps in sequence:
|
||||
|
||||
1. **Framework** — Select Hermes or OpenClaw. Sets framework-specific defaults on `DeploymentConfig`.
|
||||
2. **Cloud Provider** — Hetzner or DigitalOcean.
|
||||
3. **Provider Token** — Enter API token. For Hetzner, validates against `/server_types` endpoint and lists SSH keys.
|
||||
4. **SSH Key** — Select from keys found on the provider, or enter manually.
|
||||
5. **Server Config** — Name, location/region, server type/droplet size.
|
||||
6. **Inference Provider** — ZAI, Venice, OpenRouter. Enter API key. Validates against `/models` endpoint.
|
||||
7. **Tailscale** — Optional VPN setup. Auth key and tailnet domain.
|
||||
8. **Discord** — Optional bot integration. Bot token, server ID, user IDs.
|
||||
|
||||
Final step: summary display with cost estimate → confirm → write `.env` → optionally run `terraform init && terraform apply`.
|
||||
|
||||
### Framework-specific defaults
|
||||
|
||||
**Hermes:**
|
||||
- `DockerEnabled = true`
|
||||
- `VeniceBaseURL = "https://api.venice.ai/api/v1"`
|
||||
- `GatewayAllowAllUsers = true`
|
||||
- `DiscordAutoThread = true`
|
||||
|
||||
**OpenClaw:**
|
||||
- `OpenClawVersion = "lts"`
|
||||
- `NodeVersion = "22"`
|
||||
- `EnableSwap = true`, `SwapSizeGB = 2`
|
||||
- `EnableFail2ban = true`, `EnableUnattendedUpgrades = true`
|
||||
|
||||
---
|
||||
|
||||
## Key Types
|
||||
|
||||
### DeploymentConfig (`internal/config/deployment.go`)
|
||||
|
||||
Central struct holding all walkthrough choices. 30+ fields covering framework, provider, server, inference, Tailscale, Discord, and gateway configuration.
|
||||
|
||||
Key methods:
|
||||
- `AdminUser() string` — returns framework name ("hermes" or "openclaw")
|
||||
- `MonthlyCostEstimate() string` — returns price string based on server type/droplet size
|
||||
|
||||
Package-level helper functions (not methods, because `DeploymentConfig` is in `config` but called from `deploy`):
|
||||
- `config.LocationOrRegion(cfg)` — Hetzner location or DO region
|
||||
- `config.ServerTypeOrDroplet(cfg)` — server type or droplet size
|
||||
- `config.SSHKeySummary(cfg)` — masked SSH key display
|
||||
|
||||
### DotEnvFile (`internal/config/dotenv.go`)
|
||||
|
||||
Round-trip parser for `.env` files:
|
||||
- `ParseDotEnv(path)` — parse `TF_VAR_`-prefixed env file
|
||||
- `env.GetVar(name)` — lookup with and without `TF_VAR_` prefix
|
||||
- `env.Values` — raw `map[string]string`
|
||||
|
||||
### VarDef Schema (`internal/config/schema.go`)
|
||||
|
||||
Complete schema of all Terraform variables with metadata:
|
||||
|
||||
```go
|
||||
type VarDef struct {
|
||||
Name string // TF variable name (e.g. "cloud_provider")
|
||||
Type ValueType // string, number, bool, list
|
||||
Default string
|
||||
Required bool
|
||||
Sensitive bool
|
||||
Description string
|
||||
Group VarGroup // Section for organized output
|
||||
EnvComment string // Additional .env hint
|
||||
}
|
||||
```
|
||||
|
||||
Variables are grouped: `PROVIDER`, `PROVIDER — Hetzner`, `PROVIDER — DigitalOcean`, `SERVER CONFIGURATION`, `SSH CONFIGURATION`, `API KEYS`, `MODEL CONFIGURATION`, `DISCORD`, `TAILSCALE`, `HERMES-SPECIFIC`, `OPENCLAW-SPECIFIC`, `SECURITY`, `PROJECT METADATA`.
|
||||
|
||||
### InferenceClient (`internal/inference/client.go`)
|
||||
|
||||
HTTP client for validating inference API keys:
|
||||
- `ValidateAPIKey(ctx, provider, apiKey)` — hits `/models` endpoint, checks for HTTP 200
|
||||
- Returns `ValidationResult{Valid, ErrorMessage, ModelCount, Latency}`
|
||||
- 30-second default timeout
|
||||
|
||||
### Provider Interface (`internal/provider/provider.go`)
|
||||
|
||||
```go
|
||||
type Provider interface {
|
||||
Name() string
|
||||
ProviderName() string
|
||||
Validate(ctx context.Context) error
|
||||
Checks(ctx context.Context) []validation.Check
|
||||
TokenEnvKey() string
|
||||
SetToken(token string)
|
||||
GetToken() string
|
||||
}
|
||||
```
|
||||
|
||||
Provider registry pattern: `Register(name, factory)` / `Get(name)`. Hetzner implementation at `internal/provider/hetzner/`.
|
||||
|
||||
### Validation Framework (`internal/validation/validation.go`)
|
||||
|
||||
Structured check system with `Runner`:
|
||||
|
||||
```go
|
||||
type Check interface {
|
||||
Name() string
|
||||
Category() CheckCategory
|
||||
Run(ctx context.Context) CheckResult
|
||||
}
|
||||
```
|
||||
|
||||
Status values: `PASS`, `FAIL`, `WARN`, `SKIP`, `ERROR`.
|
||||
Categories: `Credentials`, `Connectivity`, `SSH Keys`, `Server Config`, `Quotas`, `Account`.
|
||||
|
||||
---
|
||||
|
||||
## Inference Providers
|
||||
|
||||
Currently supported:
|
||||
|
||||
| Provider | Enum | Base URL | Auth |
|
||||
|----------|------|----------|------|
|
||||
| Z.ai | `ProviderZAI` | `https://api.z.ai/api/coding/paas/v4` | `GLM_API_KEY` env |
|
||||
| Venice.ai | `ProviderVenice` | `https://api.venice.ai/api/v1` | `VENICE_API_KEY` env |
|
||||
| OpenRouter | `ProviderOpenRouter` | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` env |
|
||||
|
||||
Fallback chains: ZAI → Venice → OpenRouter (for GLM models); Venice → OpenRouter.
|
||||
|
||||
`DefaultGLMConfig()` sets `MaxTokens=16384` to prevent the over-compression bug where Venice defaults to 131K.
|
||||
|
||||
---
|
||||
|
||||
## .env Generation
|
||||
|
||||
`WriteDotEnv()` in `internal/config/dotenv_writer.go` generates the `.env` file from a `Config`. Output format:
|
||||
|
||||
- Header comment with usage instructions
|
||||
- Variables grouped by `VarGroup`, each with description comment
|
||||
- `TF_VAR_` prefix on all variable names
|
||||
- JSON arrays for SSH keys: `TF_VAR_ssh_key_names='["key-name"]'` (single-quoted shell string containing JSON)
|
||||
- Sensitive values get `YOUR_..._HERE` placeholders if empty
|
||||
- `WriteTfVars()` generates HCL-format `terraform.tfvars` as an alternative
|
||||
|
||||
### Variable flow
|
||||
|
||||
User input → `DeploymentConfig` → `.env` (TF_VAR_ prefixed) → `source .env` → Terraform reads env vars → `templatefile()` → cloud-init → server provisioning.
|
||||
|
||||
---
|
||||
|
||||
## YAML Config (Non-interactive Mode)
|
||||
|
||||
`LoadYAMLConfig(path)` parses a YAML file into `YAMLConfig`, then `ToDeploymentConfig()` converts to `DeploymentConfig`. Schema:
|
||||
|
||||
```yaml
|
||||
framework: hermes | openclaw
|
||||
provider:
|
||||
name: hetzner | digitalocean
|
||||
token: "..."
|
||||
ssh:
|
||||
names: [...]
|
||||
fingerprints: [...]
|
||||
server:
|
||||
name: "..."
|
||||
location: "ash" | "fsn1" | "nbg1" | "hel1"
|
||||
type: "cpx21" | ...
|
||||
inference:
|
||||
provider: venice | openrouter | openai | anthropic | custom
|
||||
api_key: "..."
|
||||
primary_model: "..."
|
||||
tailscale:
|
||||
enabled: true
|
||||
auth_key: "..."
|
||||
tailnet: "..."
|
||||
discord:
|
||||
enabled: true
|
||||
bot_token: "..."
|
||||
server_id: "..."
|
||||
```
|
||||
|
||||
Full example: [`deploy.yaml.example`](deploy.yaml.example).
|
||||
|
||||
---
|
||||
|
||||
## Build System
|
||||
|
||||
### Makefile targets
|
||||
|
||||
| Target | Description |
|
||||
|--------|-------------|
|
||||
| `make build` | Build binary for current platform |
|
||||
| `make test` | Run tests with race detection and coverage |
|
||||
| `make lint` | `go vet` + `gofmt` |
|
||||
| `make vet` | Run `go vet` |
|
||||
| `make fmt` | Format with `gofmt` |
|
||||
| `make clean` | Remove binary and coverage files |
|
||||
| `make cross-compile` | Build for linux-amd64, linux-arm64, darwin-arm64, windows-amd64, windows-arm64 |
|
||||
| `make version` | Print VERSION file contents |
|
||||
|
||||
### Version injection
|
||||
|
||||
```bash
|
||||
VERSION=$(cat VERSION)
|
||||
GIT_COMMIT=$(git rev-parse --short HEAD)
|
||||
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
LDFLAGS="-s -w -X main.version=$VERSION -X main.gitCommit=$GIT_COMMIT -X main.buildTime=$BUILD_TIME"
|
||||
go build $LDFLAGS -o obm ./cmd/obm
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Multi-stage Dockerfile: `golang:1.22-alpine` (build) → `alpine:3.20` (runtime). Non-root user `obm:1000`. Entry point: `obm --help`.
|
||||
|
||||
---
|
||||
|
||||
## CI/CD
|
||||
|
||||
### CI (`ci.yml`)
|
||||
|
||||
Triggers on push/PR to main. Runs: `go vet` → `go test` → `gofmt` check → build. Build matrix: linux/darwin/windows × amd64/arm64 (excludes darwin/amd64).
|
||||
|
||||
### Release (`release.yml`)
|
||||
|
||||
Triggers on tag push (`v*`). Builds cross-compiled binaries, creates archives (.tar.gz for Unix, .zip for Windows), generates SHA256 checksums, creates GitHub Release with upload.
|
||||
|
||||
### Release process
|
||||
|
||||
```bash
|
||||
./scripts/release.sh v0.2.0
|
||||
# This: validates version → runs tests → updates VERSION → commits → tags → pushes tag
|
||||
```
|
||||
|
||||
Pre-release versions (containing hyphen, e.g. `v1.0.0-beta.1`) are marked as pre-release on GitHub.
|
||||
|
||||
---
|
||||
|
||||
## Cost Estimation
|
||||
|
||||
`DeploymentConfig.MonthlyCostEstimate()` maps server types to price strings.
|
||||
|
||||
Hetzner prices (current):
|
||||
|
||||
| Type | Price |
|
||||
|------|-------|
|
||||
| cx22 | €3.79/mo |
|
||||
| cx23 | €5.83/mo |
|
||||
| cpx21 | €4.49/mo |
|
||||
| cpx31 | €8.98/mo |
|
||||
| cpx41 | €17.96/mo |
|
||||
|
||||
DigitalOcean prices:
|
||||
|
||||
| Size | Price |
|
||||
|------|-------|
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## Terraform Integration
|
||||
|
||||
`obm` generates the `.env` file that Terraform expects. The actual Terraform configs live in the separate [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo.
|
||||
|
||||
The `internal/terraform/terraform.go` wrapper provides:
|
||||
- `Runner.Init()` — `terraform init -input=false`
|
||||
- `Runner.Plan(destroy bool)` — `terraform plan` (with optional `-destroy` flag)
|
||||
- `Runner.Apply()` — `terraform apply -auto-approve`
|
||||
- `Runner.Destroy()` — `terraform destroy -auto-approve`
|
||||
|
||||
All commands run in the `WorkDir` and capture combined output.
|
||||
|
||||
### Variable flow to cloud-init
|
||||
|
||||
User sets `TF_VAR_*` env vars → sourced from `.env` → Terraform reads them → injected into cloud-init templates via `templatefile()` → written to server during provisioning.
|
||||
|
||||
### Cloud-init outputs
|
||||
|
||||
**Hermes** (`userdata-hermes.tpl`):
|
||||
- `/home/<admin_user>/.hermes/.env` — API keys, Discord token, gateway token
|
||||
- `/home/<admin_user>/.hermes/config.yaml` — model config, Discord channels
|
||||
- `/home/<admin_user>/.hermes/SOUL.md` — agent personality template
|
||||
- `/home/<admin_user>/docker-compose.yml` — Docker mode only
|
||||
- `/etc/systemd/system/hermes.service` — systemd unit
|
||||
- `/usr/local/bin/hermes-health-check.sh` — diagnostic script
|
||||
|
||||
**OpenClaw** (`userdata-openclaw.tpl`):
|
||||
- `/etc/openclaw.env` — secrets (0600, root-owned)
|
||||
- `/home/<admin_user>/.openclaw/openclaw.json` — full config
|
||||
- `/etc/systemd/system/openclaw-gateway.service`
|
||||
- `/usr/local/bin/openclaw-health-check.sh`
|
||||
|
||||
---
|
||||
|
||||
## Destroy Flow
|
||||
|
||||
`obm destroy` reads `terraform.tfstate` to list resources, shows them, asks for confirmation, runs `terraform destroy`, then cleans up state files unless `--keep-state` is set.
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
make test # Full suite with race detection + coverage
|
||||
go test ./... # Without make
|
||||
go test -v -race -coverprofile=coverage.out ./...
|
||||
```
|
||||
|
||||
Test coverage includes: config parsing, dotenv round-tripping, YAML loading, inference client validation, Hetzner provider validation, destroy workflow, prompt helpers.
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide including development setup, PR checklist, and release process.
|
||||
|
||||
Key points:
|
||||
- Go 1.22+ required
|
||||
- Run `make lint test` before pushing
|
||||
- Never push directly to `main`
|
||||
- Feature branches: `git checkout -b <task-name>`
|
||||
272
LLMs.md
Normal file
272
LLMs.md
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
# LLMs.md — Agent Reference for obm
|
||||
|
||||
This file is written for AI agents (LLMs, coding assistants, autonomous agents) who need to understand the `obm` project and work with their human to deploy, configure, or develop it. If you're reading this, you're probably an agent trying to help someone. Here's what you need to know.
|
||||
|
||||
---
|
||||
|
||||
## What obm Is
|
||||
|
||||
`obm` (OpenBoatMobile) is a Go CLI tool that deploys AI agents to cloud infrastructure. It generates Terraform-compatible `.env` files through an interactive walkthrough or a YAML config, validates API credentials against live endpoints, and wraps Terraform lifecycle commands.
|
||||
|
||||
Think of it as: "Terraform for AI agents, with a friendly wizard instead of a 94-line `.env` template."
|
||||
|
||||
---
|
||||
|
||||
## Quick Orientation
|
||||
|
||||
| Fact | Value |
|
||||
|------|-------|
|
||||
| Language | Go 1.22 |
|
||||
| Module path | `github.com/openboatmobile/obm` |
|
||||
| External deps | `gopkg.in/yaml.v3` only |
|
||||
| Binary type | Static, zero runtime deps |
|
||||
| Version file | `VERSION` (single line, e.g. `0.1.0`) |
|
||||
| Current version | 0.1.0 |
|
||||
| License | MIT |
|
||||
|
||||
**Repos:**
|
||||
- `obm` (this repo) — the Go CLI
|
||||
- `openboatmobile-ai` — the Terraform configs that `obm` generates `.env` files for
|
||||
- `openboatmobile` (private) — live deployment with keys, do NOT touch
|
||||
|
||||
---
|
||||
|
||||
## Package Map
|
||||
|
||||
Use this to find what you need:
|
||||
|
||||
```
|
||||
cmd/obm/main.go → Entry point, subcommand routing, build-time version vars
|
||||
internal/config/ → All configuration types and I/O:
|
||||
config.go → Config struct, GetValue/SetValue
|
||||
deployment.go → DeploymentConfig (30+ fields), cost estimation
|
||||
schema.go → VarDef schema (all Terraform variables), VarGroup enum
|
||||
dotenv.go → DotEnvFile round-trip parser
|
||||
dotenv_writer.go → WriteDotEnv — grouped .env output
|
||||
tfvars.go → WriteTfVars — HCL-format output
|
||||
yaml.go → YAMLConfig, LoadYAMLConfig, ToDeploymentConfig
|
||||
internal/deploy/deploy.go → Walkthrough orchestrator (Run, RunFromFile, RunWithConfig)
|
||||
internal/destroy/destroy.go → Terraform destroy with state parsing
|
||||
internal/inference/ → Inference provider validation:
|
||||
client.go → HTTP client, ValidateAPIKey, ValidationResult
|
||||
inference.go → Provider enum, ProviderConfig, FallbackChain
|
||||
internal/prompt/prompt.go → Terminal I/O: Select, Confirm, Input, Password, colors
|
||||
internal/provider/ → Cloud provider abstraction:
|
||||
provider.go → Provider interface, BaseProvider, Registry
|
||||
hetzner/hetzner.go → Hetzner Cloud API: token validation, SSH key listing
|
||||
internal/terraform/terraform.go → Runner: Init, Plan, Apply, Destroy
|
||||
internal/validation/validation.go → Check interface, Runner, CheckResult, Status enum
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CLI Commands Your Human Might Ask About
|
||||
|
||||
| Command | What it does | When they'd use it |
|
||||
|---------|-------------|-------------------|
|
||||
| `obm deploy` | Interactive 8-step walkthrough | First-time setup or redeploy |
|
||||
| `obm deploy --config deploy.yaml` | Non-interactive from YAML | CI/CD, automation, repeat deploys |
|
||||
| `obm validate` | Check `.env` + validate API keys | "Did I configure this right?" |
|
||||
| `obm destroy` | Tear down infrastructure | "I'm done with this server" |
|
||||
| `obm version` | Print version with commit/build info | Debugging |
|
||||
|
||||
---
|
||||
|
||||
## Deploy Walkthrough Steps
|
||||
|
||||
When your human runs `obm deploy`, they'll go through these in order:
|
||||
|
||||
1. **Framework** — Hermes or OpenClaw
|
||||
2. **Cloud Provider** — Hetzner or DigitalOcean
|
||||
3. **Provider Token** — API key for their cloud provider (validated live)
|
||||
4. **SSH Key** — Select from provider or enter manually
|
||||
5. **Server Config** — Name, location/region, size
|
||||
6. **Inference Provider** — ZAI, Venice, or OpenRouter (API key validated live)
|
||||
7. **Tailscale** — Optional VPN (auth key + tailnet)
|
||||
8. **Discord** — Optional bot integration (token, server ID, user IDs)
|
||||
|
||||
After all 8 steps: summary + cost estimate → confirm → `.env` written → optional `terraform init && apply`.
|
||||
|
||||
---
|
||||
|
||||
## What Your Human Needs Before Deploying
|
||||
|
||||
Tell them to have these ready:
|
||||
|
||||
1. **Cloud provider account** with API token (Hetzner: console.hetzner.cloud → Security → API Tokens; DO: cloud.digitalocean.com → API)
|
||||
2. **SSH public key** uploaded to their cloud provider
|
||||
3. **Inference provider API key** (Venice, OpenRouter, or ZAI)
|
||||
4. *(Optional)* Tailscale auth key
|
||||
5. *(Optional)* Discord bot token + server ID
|
||||
|
||||
---
|
||||
|
||||
## Framework Differences
|
||||
|
||||
When your human asks "Hermes or OpenClaw?":
|
||||
|
||||
**Hermes Agent** — if they want:
|
||||
- Discord chat, voice, web search, many integrations
|
||||
- Python-based, Docker or direct install
|
||||
- More configurable (gateway, allowed users, auto-threading)
|
||||
|
||||
**OpenClaw** — if they want:
|
||||
- Simpler setup, fewer moving parts
|
||||
- Node.js-based
|
||||
- Built-in security (fail2ban, unattended upgrades, swap)
|
||||
|
||||
---
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### `.env` (Terraform input)
|
||||
|
||||
All variables prefixed with `TF_VAR_`. Generated by `obm deploy` or `WriteDotEnv()`. This is the primary output of the tool. Groups: PROVIDER, SERVER, SSH, API KEYS, MODEL, DISCORD, TAILSCALE, HERMES-SPECIFIC, OPENCLAW-SPECIFIC, SECURITY, PROJECT.
|
||||
|
||||
Special format for SSH keys: `TF_VAR_ssh_key_names='["key-name"]'` — single-quoted shell string containing JSON array.
|
||||
|
||||
### `deploy.yaml` (Non-interactive input)
|
||||
|
||||
Full schema in `deploy.yaml.example`. Key sections:
|
||||
|
||||
```yaml
|
||||
framework: hermes
|
||||
provider:
|
||||
name: hetzner
|
||||
token: "..."
|
||||
ssh:
|
||||
names: ["my-key"]
|
||||
server:
|
||||
name: "agent"
|
||||
location: "ash"
|
||||
type: "cpx21"
|
||||
inference:
|
||||
provider: venice
|
||||
api_key: "..."
|
||||
primary_model: "zai-org-glm-5"
|
||||
tailscale:
|
||||
enabled: true
|
||||
auth_key: "..."
|
||||
discord:
|
||||
enabled: false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
make build # Build for current platform
|
||||
make test # Test with race detection + coverage
|
||||
make lint # go vet + gofmt
|
||||
make cross-compile # All platforms: linux-{amd64,arm64}, darwin-arm64, windows-{amd64,arm64}
|
||||
make clean # Remove binary + coverage
|
||||
```
|
||||
|
||||
From source without make:
|
||||
```bash
|
||||
go build -o obm ./cmd/obm
|
||||
go test ./...
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
Version injection at build time:
|
||||
```bash
|
||||
go build -ldflags "-s -w -X main.version=$(cat VERSION) -X main.gitCommit=$(git rev-parse --short HEAD) -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -o obm ./cmd/obm
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks Your Human Might Ask For
|
||||
|
||||
### "Help me deploy an agent"
|
||||
|
||||
1. Make sure they have API keys ready (see "What Your Human Needs" above)
|
||||
2. Run `obm deploy`
|
||||
3. Walk them through the 8 steps if they have questions
|
||||
4. The cost estimate at the end is approximate — cloud providers may vary
|
||||
|
||||
### "Check if my config is valid"
|
||||
|
||||
```bash
|
||||
obm validate
|
||||
```
|
||||
|
||||
This loads `.env`, checks required variables, and validates inference API keys against live endpoints.
|
||||
|
||||
### "I want to tear down my server"
|
||||
|
||||
```bash
|
||||
obm destroy
|
||||
```
|
||||
|
||||
It asks for confirmation. It reads `terraform.tfstate` to show what will be destroyed.
|
||||
|
||||
### "Set up CI/CD deployment"
|
||||
|
||||
1. Create a `deploy.yaml` from `deploy.yaml.example`
|
||||
2. Store secrets in your CI secrets manager
|
||||
3. In pipeline: `obm deploy --config deploy.yaml`
|
||||
|
||||
### "Add a new cloud provider"
|
||||
|
||||
1. Create `internal/provider/<name>/<name>.go` implementing the `Provider` interface
|
||||
2. Register via `init()`: `provider.Register("<name>", func() provider.Provider { return New() })`
|
||||
3. Add provider-specific fields to `DeploymentConfig`
|
||||
4. Update `schema.go` with new `VarGroup` and `VarDef` entries
|
||||
5. Add step function in `deploy.go`
|
||||
|
||||
### "Add a new inference provider"
|
||||
|
||||
1. Add `Provider<Name>` constant to `internal/inference/inference.go`
|
||||
2. Add case to `Info()` method (name, env var, base URL)
|
||||
3. Add case to `UnmarshalText()` for YAML parsing
|
||||
4. Add case to `setAuthHeaders()` in `client.go` if auth differs from Bearer token
|
||||
5. Update `AllProviders()` return slice
|
||||
|
||||
---
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- `obm status` is stubbed — not yet functional
|
||||
- DigitalOcean provider validation not yet implemented (only Hetzner)
|
||||
- No resumable deploy — interrupted walkthrough = start over
|
||||
- No cost preview before the summary step
|
||||
- `openai` and `anthropic` inference providers listed in schema but not fully implemented in the inference client (only ZAI, Venice, OpenRouter have live validation)
|
||||
|
||||
---
|
||||
|
||||
## Relationship to Terraform Configs
|
||||
|
||||
`obm` generates the `.env` file. The Terraform configs that consume it live in the `openboatmobile-ai` repo. The variable schema in `internal/config/schema.go` must stay in sync with the Terraform variable definitions in `openboatmobile-ai/variables-*.tf`.
|
||||
|
||||
If you add a variable to the Terraform configs, you must also add a corresponding `VarDef` to `schema.go`.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Unit tests in each package (`*_test.go`)
|
||||
- Hetzner provider tested with mock HTTP servers (`WithHTTPClient` + `WithBaseURL` options)
|
||||
- Inference client tested with mock `/models` endpoints
|
||||
- Run with: `make test` or `go test -v -race -coverprofile=coverage.out ./...`
|
||||
|
||||
---
|
||||
|
||||
## File Locations Summary
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `VERSION` | Single-line semver string |
|
||||
| `Makefile` | Build, test, lint, cross-compile targets |
|
||||
| `Dockerfile` | Multi-stage build for containerized obm |
|
||||
| `deploy.yaml.example` | Full YAML config reference |
|
||||
| `scripts/install.sh` | `curl \| sh` installer |
|
||||
| `scripts/release.sh` | Tag + push release automation |
|
||||
| `.github/workflows/ci.yml` | Test + build on push/PR |
|
||||
| `.github/workflows/release.yml` | Cross-compile + GitHub Release on tag |
|
||||
| `CHANGELOG.md` | Version history |
|
||||
| `CONTRIBUTING.md` | Dev setup, PR checklist, release process |
|
||||
| `README.md` | User-facing overview (casual tone) |
|
||||
| `DETAILS.md` | Full technical reference |
|
||||
| `LLMs.md` | This file — agent-oriented reference |
|
||||
260
README.md
260
README.md
|
|
@ -1,166 +1,166 @@
|
|||
# obm - OpenBoatMobile Infrastructure CLI
|
||||
# obm 🚢
|
||||
|
||||
A CLI tool for deploying AI agents on cloud infrastructure with Terraform.
|
||||
**Deploy your own AI agent to the cloud in about five minutes.**
|
||||
|
||||
## Installation
|
||||
No YAML file editing. No guessing if your API keys work. No 94-line config templates.
|
||||
Just answer a few questions and your agent is live.
|
||||
|
||||
### Quick Install (Linux/macOS)
|
||||
---
|
||||
|
||||
## What is this?
|
||||
|
||||
`obm` is a command-line tool that walks you through setting up an AI agent on cloud infrastructure. It asks you things like "which cloud provider?" and "which AI model?" — then validates your answers on the spot, writes the config, and hands it off to Terraform to build everything.
|
||||
|
||||
It supports two agent frameworks:
|
||||
|
||||
- **Hermes Agent** (by Nous Research) — a Python-based agent with Discord chat, voice, web search, and a ton of integrations. Think of it as a personal AI assistant you can talk to.
|
||||
- **OpenClaw** — a Node.js-based agent with a simpler setup. Good if you want something lighter.
|
||||
|
||||
Both run on either **Hetzner Cloud** (cheap, EU-based) or **DigitalOcean** (more regions).
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
### Install
|
||||
|
||||
**Mac or Linux (one command):**
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh
|
||||
```
|
||||
|
||||
Install a specific version:
|
||||
**Or download a binary** from the [releases page](https://github.com/openboatmobile/obm/releases/latest) and put it somewhere on your PATH.
|
||||
|
||||
**Or build from source** (requires Go 1.22+):
|
||||
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/openboatmobile/obm/main/scripts/install.sh | sh -s -- v1.2.3
|
||||
git clone https://github.com/openboatmobile/obm.git
|
||||
cd obm
|
||||
make build
|
||||
```
|
||||
|
||||
### Manual Install
|
||||
|
||||
Download the latest release for your platform from [GitHub Releases](https://github.com/openboatmobile/obm/releases/latest):
|
||||
|
||||
| Platform | Architecture | Download |
|
||||
|----------|-------------|----------|
|
||||
| Linux | x86_64 (amd64) | `obm-linux-amd64.tar.gz` |
|
||||
| Linux | ARM64 | `obm-linux-arm64.tar.gz` |
|
||||
| macOS | Apple Silicon (arm64) | `obm-darwin-arm64.tar.gz` |
|
||||
| Windows | x86_64 (amd64) | `obm-windows-amd64.zip` |
|
||||
| Windows | ARM64 | `obm-windows-arm64.zip` |
|
||||
|
||||
**Linux/macOS:**
|
||||
```bash
|
||||
# Download and extract
|
||||
curl -sL https://github.com/openboatmobile/obm/releases/latest/download/obm-linux-amd64.tar.gz | tar xz
|
||||
# Or for ARM64:
|
||||
# curl -sL https://github.com/openboatmobile/obm/releases/latest/download/obm-linux-arm64.tar.gz | tar xz
|
||||
|
||||
chmod +x obm
|
||||
sudo mv obm /usr/local/bin/
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
# Download and extract
|
||||
Invoke-WebRequest -Uri https://github.com/openboatmobile/obm/releases/latest/download/obm-windows-amd64.zip -OutFile obm.zip
|
||||
Expand-Archive obm.zip
|
||||
# Add to PATH as needed
|
||||
```
|
||||
|
||||
### From Source
|
||||
### Deploy your agent
|
||||
|
||||
```bash
|
||||
go build -o obm ./cmd/obm
|
||||
obm deploy
|
||||
```
|
||||
|
||||
## Usage
|
||||
That's it. You'll get an interactive walkthrough that looks like this:
|
||||
|
||||
### Interactive Mode (Default)
|
||||
```
|
||||
🚢 OpenBoatmobile — Deploy your AI agent
|
||||
|
||||
Run the interactive wizard to configure your deployment:
|
||||
Step 1: Agent Framework
|
||||
[1] Hermes Agent (Nous Research) — Python-based, highly configurable
|
||||
[2] OpenClaw — Node.js-based, simpler setup
|
||||
|
||||
```bash
|
||||
./obm deploy
|
||||
Step 2: Cloud Provider
|
||||
[1] Hetzner Cloud — from €4.49/mo (recommended, ~70% cheaper)
|
||||
[2] DigitalOcean — from $6/mo (wider region availability)
|
||||
|
||||
Step 3: Provider API Token
|
||||
Get yours at: https://console.hetzner.cloud/ → Security → API Tokens
|
||||
Token: ********
|
||||
✓ Token validated
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
The wizard will guide you through:
|
||||
1. Agent framework selection (Hermes or OpenClaw)
|
||||
2. Cloud provider (Hetzner or DigitalOcean)
|
||||
3. Server configuration
|
||||
4. Inference provider (Venice, OpenRouter, OpenAI, Anthropic, or Custom)
|
||||
5. Optional: Tailscale VPN, Discord integration
|
||||
At the end, you'll see a summary with cost estimate and get asked to confirm. Say yes, and `obm` writes your config and kicks off Terraform.
|
||||
|
||||
### Non-Interactive Mode (CI/CD)
|
||||
### Other commands
|
||||
|
||||
For automated deployments, use a YAML configuration file:
|
||||
|
||||
```bash
|
||||
./obm deploy --config deploy.yaml
|
||||
```
|
||||
|
||||
See `deploy.yaml.example` for a complete configuration reference.
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
| Command | What it does |
|
||||
|---------|-------------|
|
||||
| `deploy` | Deploy an AI agent (interactive or --config for CI/CD) |
|
||||
| `validate` | Check configuration and API credentials |
|
||||
| `status` | Show current infrastructure state |
|
||||
| `destroy` | Tear down provisioned infrastructure |
|
||||
| `version` | Print version |
|
||||
| `help` | Show help message |
|
||||
| `obm deploy` | Interactive walkthrough to set up a new agent |
|
||||
| `obm validate` | Checks your existing config and API keys |
|
||||
| `obm status` | Shows the state of your current deployment |
|
||||
| `obm destroy` | Tears down your infrastructure (asks first, don't worry) |
|
||||
| `obm version` | Prints the version |
|
||||
|
||||
## Configuration File Format
|
||||
### Non-interactive mode (for automation)
|
||||
|
||||
The YAML configuration file supports the following structure:
|
||||
|
||||
```yaml
|
||||
# Required: Agent framework
|
||||
framework: hermes # or openclaw
|
||||
|
||||
# Required: Cloud provider
|
||||
provider:
|
||||
name: hetzner # or digitalocean
|
||||
token: "your-api-token"
|
||||
ssh:
|
||||
names: ["my-ssh-key"] # Hetzner
|
||||
# fingerprints: ["aa:bb:cc:dd"] # DigitalOcean
|
||||
|
||||
# Server configuration
|
||||
server:
|
||||
name: "my-agent"
|
||||
location: "ash" # Hetzner: ash, fsn1, nbg1, hel1
|
||||
type: "cpx21"
|
||||
|
||||
# Required: Inference provider
|
||||
inference:
|
||||
provider: venice # venice, openrouter, openai, anthropic, custom
|
||||
api_key: "your-api-key"
|
||||
primary_model: "zai-org-glm-5"
|
||||
|
||||
# Optional: Tailscale VPN
|
||||
tailscale:
|
||||
enabled: true
|
||||
auth_key: "tskey-auth-..."
|
||||
tailnet: "mytailnet"
|
||||
|
||||
# Optional: Discord integration
|
||||
discord:
|
||||
enabled: true
|
||||
bot_token: ""
|
||||
server_id: ""
|
||||
```
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Local Development
|
||||
If you're running this in CI/CD or just don't want the prompts:
|
||||
|
||||
```bash
|
||||
# Interactive setup
|
||||
./obm deploy
|
||||
|
||||
# Validate your .env file
|
||||
./obm validate --env-file .env
|
||||
obm deploy --config deploy.yaml
|
||||
```
|
||||
|
||||
### CI/CD Pipeline
|
||||
See [`deploy.yaml.example`](deploy.yaml.example) for the full config file format.
|
||||
|
||||
```bash
|
||||
# Create deploy.yaml from your secrets manager
|
||||
# Then run non-interactive deployment
|
||||
./obm deploy --config deploy.yaml
|
||||
```
|
||||
---
|
||||
|
||||
### GitOps Setup
|
||||
## What you'll need
|
||||
|
||||
1. Store `deploy.yaml` in your repository (use template with placeholders)
|
||||
2. Use a secrets manager for sensitive values
|
||||
3. In CI:
|
||||
```bash
|
||||
envsubst < deploy.yaml.template > deploy.yaml
|
||||
./obm deploy --config deploy.yaml
|
||||
```
|
||||
Before running `obm deploy`, have these ready:
|
||||
|
||||
1. **A cloud provider account** — [Hetzner Cloud](https://console.hetzner.cloud/) or [DigitalOcean](https://cloud.digitalocean.com/). Hetzner is cheaper; DigitalOcean has more data center locations.
|
||||
2. **An API token** from your cloud provider. You can generate one in their dashboard.
|
||||
3. **An AI model API key** — Venice AI, OpenRouter, OpenAI, or Anthropic. This is the "brain" your agent will use.
|
||||
4. **An SSH public key** uploaded to your cloud provider (so you can log into your server later).
|
||||
|
||||
Optional but recommended:
|
||||
- **Tailscale** account for VPN access to your server
|
||||
- **Discord bot token** if you want your agent to chat on Discord
|
||||
|
||||
---
|
||||
|
||||
## How much does it cost?
|
||||
|
||||
The server cost depends on your cloud provider and server size. `obm` shows you the estimated monthly cost before you commit.
|
||||
|
||||
**Rough starting points:**
|
||||
|
||||
| Provider | Smallest option | Good for |
|
||||
|----------|----------------|----------|
|
||||
| Hetzner | €4.49/mo (2 vCPU, 4 GB RAM) | Most agents |
|
||||
| DigitalOcean | $6/mo (1 vCPU, 1 GB RAM) | Light use |
|
||||
|
||||
The AI model API costs are separate and depend on your usage.
|
||||
|
||||
---
|
||||
|
||||
## What happens under the hood?
|
||||
|
||||
`obm` generates a `.env` file that [Terraform](https://terraform.io) reads to provision your server, install the agent software, and configure everything. You don't need to know Terraform — `obm` handles it.
|
||||
|
||||
The Terraform configs live in the [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo. `obm` is the friendly CLI wrapper around them.
|
||||
|
||||
---
|
||||
|
||||
## Project status
|
||||
|
||||
`obm` is actively developed and functional for the core deploy workflow. Here's what works and what's coming:
|
||||
|
||||
**Working now:**
|
||||
- Interactive deploy walkthrough (8 steps)
|
||||
- API key validation for cloud providers and inference providers
|
||||
- SSH key listing from Hetzner
|
||||
- `.env` file generation
|
||||
- Config validation (`obm validate`)
|
||||
- Infrastructure teardown (`obm destroy`)
|
||||
- Non-interactive mode with YAML config
|
||||
- Cross-compiled binaries (Linux, macOS, Windows)
|
||||
- `curl | sh` installer
|
||||
|
||||
**Coming soon:**
|
||||
- `obm status` — SSH health checks on your deployment
|
||||
- DigitalOcean provider validation
|
||||
- Cost estimation display
|
||||
- Resumable deploy (pick up where you left off)
|
||||
|
||||
---
|
||||
|
||||
## For developers
|
||||
|
||||
Building, testing, and contributing — see [DETAILS.md](DETAILS.md) for the full technical reference and [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guide.
|
||||
|
||||
## For AI agents
|
||||
|
||||
If you're an AI agent reading this to learn about the project, check out [LLMs.md](LLMs.md) — it's written specifically for you.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue