Compare commits
1 commit
main
...
migrate-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab1de96168 |
6 changed files with 64 additions and 70 deletions
22
DETAILS.md
22
DETAILS.md
|
|
@ -53,7 +53,7 @@ obm/
|
|||
│ │ │ └── hetzner_test.go
|
||||
│ │ └── provider_test.go
|
||||
│ ├── terraform/
|
||||
│ │ └── terraform.go # Runner: Init, Plan, Apply, Destroy wrappers
|
||||
│ │ └── terraform.go # Runner: Init, Plan, Apply, Destroy (OpenTofu/Terraform)
|
||||
│ └── validation/
|
||||
│ ├── validation.go # Check interface, Runner, CheckResult, Status enum
|
||||
│ └── validation_test.go
|
||||
|
|
@ -82,7 +82,7 @@ obm/
|
|||
| `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 destroy` | — | Confirmation prompt → `tofu destroy` → state cleanup |
|
||||
| `obm version` | — | Print version with commit hash and build time |
|
||||
| `obm help` | — | Print usage |
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ The `obm deploy` interactive flow runs 8 steps in sequence:
|
|||
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`.
|
||||
Final step: summary display with cost estimate → confirm → write `.env` → optionally run `tofu init && tofu apply` (or `terraform` if installed).
|
||||
|
||||
### Framework-specific defaults
|
||||
|
||||
|
|
@ -355,15 +355,15 @@ DigitalOcean prices:
|
|||
|
||||
---
|
||||
|
||||
## Terraform Integration
|
||||
## Terraform / OpenTofu 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.
|
||||
`obm` generates the `.env` file that OpenTofu (or Terraform) expects. The actual infrastructure 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`
|
||||
The `internal/terraform/terraform.go` wrapper provides (auto-detects OpenTofu first, Terraform fallback):
|
||||
- `Runner.Init()` — `tofu init -input=false`
|
||||
- `Runner.Plan(destroy bool)` — `tofu plan` (with optional `-destroy` flag)
|
||||
- `Runner.Apply()` — `tofu apply -auto-approve`
|
||||
- `Runner.Destroy()` — `tofu destroy -auto-approve`
|
||||
|
||||
All commands run in the `WorkDir` and capture combined output.
|
||||
|
||||
|
|
@ -391,7 +391,7 @@ User sets `TF_VAR_*` env vars → sourced from `.env` → Terraform reads them
|
|||
|
||||
## 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.
|
||||
`obm destroy` reads `terraform.tfstate` to list resources, shows them, asks for confirmation, runs `tofu destroy`, then cleans up state files unless `--keep-state` is set.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
4
LLMs.md
4
LLMs.md
|
|
@ -85,7 +85,7 @@ When your human runs `obm deploy`, they'll go through these in order:
|
|||
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`.
|
||||
After all 8 steps: summary + cost estimate → confirm → `.env` written → optional `tofu init && tofu apply` (or `terraform` if installed).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ This loads `.env`, checks required variables, and validates inference API keys a
|
|||
obm destroy
|
||||
```
|
||||
|
||||
It asks for confirmation. It reads `terraform.tfstate` to show what will be destroyed.
|
||||
It asks for confirmation. It reads the state file (`terraform.tfstate`) to show what will be destroyed.
|
||||
|
||||
### "Set up CI/CD deployment"
|
||||
|
||||
|
|
|
|||
60
Makefile
60
Makefile
|
|
@ -20,60 +20,32 @@ GOVET := $(GOCMD) vet
|
|||
# Cross-compilation targets
|
||||
TARGETS := linux-amd64 linux-arm64 darwin-arm64 windows-amd64 windows-arm64
|
||||
|
||||
# Platform detection (noiseless fallback — true check happens in check-go target)
|
||||
GOOS := $(shell go env GOOS 2>/dev/null || echo "unknown")
|
||||
GOARCH := $(shell go env GOARCH 2>/dev/null || echo "unknown")
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Friendly Go-not-found guard
|
||||
check-go:
|
||||
@if ! command -v go >/dev/null 2>&1; then \
|
||||
echo ""; \
|
||||
echo " ╭──────────────────────────────────────────────╮"; \
|
||||
echo " │ ⚠ Go is not installed on this machine! │"; \
|
||||
echo " ├──────────────────────────────────────────────┤"; \
|
||||
echo " │ │"; \
|
||||
echo " │ This is a Go project. You need the │"; \
|
||||
echo " │ Go language toolchain to build from source. │"; \
|
||||
echo " │ │"; \
|
||||
echo " │ Install Go: │"; \
|
||||
echo " │ Linux: your package manager │"; \
|
||||
echo " │ or https://go.dev/dl/ │"; \
|
||||
echo " │ │"; \
|
||||
echo " │ macOS: brew install go │"; \
|
||||
echo " │ │"; \
|
||||
echo " │ Windows: https://go.dev/dl/ │"; \
|
||||
echo " │ │"; \
|
||||
echo " │ Or use the obm full install script: │"; \
|
||||
echo " │ ./scripts/install.sh │"; \
|
||||
echo " │ │"; \
|
||||
echo " ╰──────────────────────────────────────────────╯"; \
|
||||
echo ""; \
|
||||
exit 1; \
|
||||
fi
|
||||
# Platform detection
|
||||
GOOS := $(shell go env GOOS)
|
||||
GOARCH := $(shell go env GOARCH)
|
||||
|
||||
all: lint test build
|
||||
|
||||
build: check-go
|
||||
build:
|
||||
@echo "Building $(BINARY) v$(VERSION) for $(GOOS)/$(GOARCH)..."
|
||||
$(GOBUILD) $(LDFLAGS) -o $(BINARY) ./cmd/obm
|
||||
|
||||
test: check-go
|
||||
test:
|
||||
@echo "Running tests..."
|
||||
$(GOTEST) -v -race -coverprofile=coverage.out ./...
|
||||
|
||||
lint: vet fmt
|
||||
@echo "✓ Lint pass"
|
||||
|
||||
vet: check-go
|
||||
vet:
|
||||
@echo "Running go vet..."
|
||||
$(GOVET) ./...
|
||||
|
||||
fmt: check-go
|
||||
fmt:
|
||||
@echo "Formatting code..."
|
||||
gofmt -l -s -w .
|
||||
|
||||
clean: check-go
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
rm -f $(BINARY)
|
||||
rm -f coverage.out
|
||||
|
|
@ -95,7 +67,7 @@ version:
|
|||
@echo "$(VERSION)"
|
||||
|
||||
# Cross-compilation
|
||||
cross-compile: check-go
|
||||
cross-compile:
|
||||
@echo "Cross-compiling for all platforms..."
|
||||
@mkdir -p bin
|
||||
@for target in $(TARGETS); do \
|
||||
|
|
@ -112,23 +84,23 @@ cross-compile: check-go
|
|||
@echo "✓ Cross-compilation complete"
|
||||
|
||||
# Build specific platform
|
||||
build-linux-amd64: check-go
|
||||
build-linux-amd64:
|
||||
@mkdir -p bin
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GOBUILD) $(LDFLAGS) -o bin/linux-amd64/$(BINARY) ./cmd/obm
|
||||
|
||||
build-linux-arm64: check-go
|
||||
build-linux-arm64:
|
||||
@mkdir -p bin
|
||||
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 $(GOBUILD) $(LDFLAGS) -o bin/linux-arm64/$(BINARY) ./cmd/obm
|
||||
|
||||
build-darwin-arm64: check-go
|
||||
build-darwin-arm64:
|
||||
@mkdir -p bin
|
||||
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 $(GOBUILD) $(LDFLAGS) -o bin/darwin-arm64/$(BINARY) ./cmd/obm
|
||||
|
||||
build-windows-amd64: check-go
|
||||
build-windows-amd64:
|
||||
@mkdir -p bin
|
||||
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 $(GOBUILD) $(LDFLAGS) -o bin/windows-amd64/$(BINARY).exe ./cmd/obm
|
||||
|
||||
build-windows-arm64: check-go
|
||||
build-windows-arm64:
|
||||
@mkdir -p bin
|
||||
GOOS=windows GOARCH=arm64 CGO_ENABLED=0 $(GOBUILD) $(LDFLAGS) -o bin/windows-arm64/$(BINARY).exe ./cmd/obm
|
||||
|
||||
|
|
@ -168,10 +140,10 @@ dev: build test
|
|||
@echo "✓ Development build complete"
|
||||
|
||||
# Quick test during development
|
||||
quick-test: check-go
|
||||
quick-test:
|
||||
$(GOTEST) -race ./...
|
||||
|
||||
# Coverage report
|
||||
coverage: check-go test
|
||||
coverage: test
|
||||
$(GOCMD) tool cover -html=coverage.out -o coverage.html
|
||||
@echo "Coverage report: coverage.html"
|
||||
|
|
@ -115,9 +115,9 @@ The AI model API costs are separate, and depend on your usage. Generally that's
|
|||
|
||||
## 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.
|
||||
`obm` generates a `.env` file that [OpenTofu](https://opentofu.org) (or [Terraform](https://terraform.io) as a fallback) reads to provision your server, install the agent software, and configure everything. You don't need to know either — `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.
|
||||
The infrastructure configs live in the [openboatmobile-ai](https://github.com/openboatmobile/openboatmobile-ai) repo. `obm` is the friendly CLI wrapper around them.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@ import (
|
|||
"github.com/openboatmobile/obm/internal/prompt"
|
||||
)
|
||||
|
||||
// tfBinary returns the path to the IaC binary: tofu preferred, terraform fallback.
|
||||
func tfBinary() string {
|
||||
for _, name := range []string{"tofu", "terraform"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "tofu"
|
||||
}
|
||||
|
||||
// Options configures the destroy operation.
|
||||
type Options struct {
|
||||
WorkDir string // Working directory (default: current)
|
||||
|
|
@ -198,7 +208,7 @@ func displayDestroyPlan(resources []Resource) {
|
|||
fmt.Println()
|
||||
}
|
||||
|
||||
// runTerraformDestroy executes terraform destroy.
|
||||
// runTerraformDestroy executes tofu/terraform destroy.
|
||||
func runTerraformDestroy(workDir string, opts *Options) error {
|
||||
args := []string{"destroy", "-auto-approve"}
|
||||
|
||||
|
|
@ -207,7 +217,7 @@ func runTerraformDestroy(workDir string, opts *Options) error {
|
|||
args = append(args, "-var-file", vf)
|
||||
}
|
||||
|
||||
cmd := exec.Command("terraform", args...)
|
||||
cmd := exec.Command(tfBinary(), args...)
|
||||
cmd.Dir = workDir
|
||||
|
||||
// Stream output to stdout/stderr
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
// Package terraform wraps Terraform operations (init, plan, apply, destroy).
|
||||
// Package terraform wraps IaC operations (init, plan, apply, destroy).
|
||||
// Supports OpenTofu (preferred) and Terraform (fallback).
|
||||
package terraform
|
||||
|
||||
import (
|
||||
|
|
@ -6,22 +7,32 @@ import (
|
|||
"os/exec"
|
||||
)
|
||||
|
||||
// Runner executes Terraform commands.
|
||||
// binary returns the path to the IaC binary: tofu preferred, terraform fallback.
|
||||
func binary() string {
|
||||
for _, name := range []string{"tofu", "terraform"} {
|
||||
if path, err := exec.LookPath(name); err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
return "tofu" // fallback — will produce a useful error at runtime
|
||||
}
|
||||
|
||||
// Runner executes infrastructure-as-code commands.
|
||||
type Runner struct {
|
||||
WorkDir string
|
||||
}
|
||||
|
||||
// NewRunner creates a Terraform runner for the given working directory.
|
||||
// NewRunner creates a runner for the given working directory.
|
||||
func NewRunner(workDir string) *Runner {
|
||||
return &Runner{WorkDir: workDir}
|
||||
}
|
||||
|
||||
// Init runs terraform init.
|
||||
// Init runs init.
|
||||
func (r *Runner) Init() error {
|
||||
return r.run("init", "-input=false")
|
||||
}
|
||||
|
||||
// Plan runs terraform plan.
|
||||
// Plan runs plan.
|
||||
func (r *Runner) Plan(destroy bool) error {
|
||||
args := []string{"plan"}
|
||||
if destroy {
|
||||
|
|
@ -30,22 +41,23 @@ func (r *Runner) Plan(destroy bool) error {
|
|||
return r.run(args...)
|
||||
}
|
||||
|
||||
// Apply runs terraform apply.
|
||||
// Apply runs apply.
|
||||
func (r *Runner) Apply() error {
|
||||
return r.run("apply", "-auto-approve")
|
||||
}
|
||||
|
||||
// Destroy runs terraform destroy.
|
||||
// Destroy runs destroy.
|
||||
func (r *Runner) Destroy() error {
|
||||
return r.run("destroy", "-auto-approve")
|
||||
}
|
||||
|
||||
func (r *Runner) run(args ...string) error {
|
||||
cmd := exec.Command("terraform", args...)
|
||||
bin := binary()
|
||||
cmd := exec.Command(bin, args...)
|
||||
cmd.Dir = r.WorkDir
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("terraform %v: %w\n%s", args, err, output)
|
||||
return fmt.Errorf("%s %v: %w\n%s", bin, args, err, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue