Cover the deploy-critical pure logic: - GenerateConfigTOML: round-trip decode of values + a guard asserting the config port is always containerAppPort (host port must not leak) - GenerateWireguardConfig: MTU emitted/commented, PROTO default UDP - WriteConfigFile / WriteWireguardConfigFile: numeric validation rejects bad input and does not write a file - FormStep.Values(): text/password/select resolution and select default fallback Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
149 lines
7.0 KiB
Markdown
149 lines
7.0 KiB
Markdown
# CLAUDE.md
|
||
|
||
Guidance for Claude Code when working in this repository.
|
||
|
||
> User-facing docs are in Portuguese (BR): [README.md](README.md) and [docs/](docs/).
|
||
> This file is in English and is the technical source of truth for how the code works.
|
||
|
||
## What this is
|
||
|
||
A terminal UI (TUI) **installer** for the "App do Dono" **client middleware**. The
|
||
middleware runs on a tenant's infrastructure and brokers communication between the
|
||
tenant's servers and davinTI's **central server**.
|
||
|
||
The installer is a guided wizard that: checks Docker → logs into the private registry
|
||
→ pulls images → (optionally) sets up a WireGuard tunnel (`vproxy`) when there is no
|
||
public IP → collects configuration via terminal forms → generates config files
|
||
(`config.toml`, `envs`) → runs the Docker containers.
|
||
|
||
It is **not** the middleware itself — it only provisions and launches it via `docker`.
|
||
|
||
## Tech stack
|
||
|
||
- **Go 1.25**
|
||
- **Bubble Tea v2** (`charm.land/bubbletea/v2`) — Elm-architecture TUI runtime.
|
||
- **Bubbles v2** — `textinput`, `spinner` components.
|
||
- **Lip Gloss v2** — styling.
|
||
- **BurntSushi/toml** — read existing `config.toml` for form defaults.
|
||
- Module path: `git.davinti.com.br/davinTI/app-dono/tui`.
|
||
|
||
## Layout
|
||
|
||
```
|
||
cmd/main.go Entry point; starts the Bubble Tea program.
|
||
internal/tui/
|
||
model.go Model struct, ConfigValues, loadConfig (defaults), InitialModel, Init.
|
||
steps.go step enum — the ordered wizard stages.
|
||
update.go Update: global keys + per-step dispatch (handlers).
|
||
view.go View: header + per-step body + help footer (AltScreen).
|
||
form.go Generic FormStep/FormField component (text/password/number/select).
|
||
cmds.go Msg types and async tea.Cmd wrappers around docker/file ops.
|
||
docker.go os/exec wrappers around the `docker` binary.
|
||
config.go Generates & writes config.toml and envs (+ numeric validation).
|
||
styles.go Lip Gloss palette/styles.
|
||
Makefile Multi-arch build + S3 publish.
|
||
```
|
||
|
||
## Architecture (Model–Update–View)
|
||
|
||
- **Model** ([model.go](internal/tui/model.go)) holds all state: `currentStep`,
|
||
progress/error flags, one `FormStep` per form, and `configValues` (`ConfigValues`)
|
||
which accumulates each form's `map[string]string` output by section.
|
||
- **Update** ([update.go](internal/tui/update.go)) handles global messages
|
||
(`Ctrl+C`, window size, spinner tick) then dispatches on `currentStep` to per-step
|
||
handlers. Step transitions happen when a form returns `done == true` or when an
|
||
async completion message arrives.
|
||
- **View** ([view.go](internal/tui/view.go)) renders per-step.
|
||
- **Commands** ([cmds.go](internal/tui/cmds.go)) wrap every blocking op (docker calls,
|
||
file writes) as a `tea.Cmd` returning a `Msg`: `DockerCheckedMsg`,
|
||
`ImageDownloadFinishedMsg`, `ConfigFileMsg`, `DockerRunMsg`, `TickMsg`.
|
||
|
||
## Wizard flow
|
||
|
||
`steps.go` defines the order. See [docs/fluxo.md](docs/fluxo.md) for full detail.
|
||
|
||
1. `StepCheckDocker` — `exec.LookPath("docker")`. Missing → `StepDockerInstall`
|
||
(instructs manual install and exits; **never auto-installs Docker**).
|
||
2. `StepDockerLogin` → `StepDownloadImage` — `docker login` + `pull` of the app image.
|
||
These registry credentials are **reused** later for the vproxy image.
|
||
3. `StepIPQuestion` — "public IP available?"
|
||
- **Yes** → jump to `StepAppConfig`.
|
||
- **No** → vproxy block: `StepWireguardConfig` → `StepGenerateWireguardFile`
|
||
(writes `envs`) → `StepDownloadWireguard` → `StepRunWireguard`.
|
||
4. Config forms: `StepAppConfig` → `StepServerConfig` → `StepDatabaseConfig` →
|
||
`StepCertConfig`.
|
||
5. `StepGenerateFile` — writes `config.toml` (validates numeric fields first).
|
||
6. `StepRunDocker` — runs `app-dono-cliente` container.
|
||
7. `StepDone`.
|
||
|
||
## Key constants
|
||
|
||
In [update.go](internal/tui/update.go):
|
||
|
||
- `imageName = hub.davinti.com.br:443/app-dono/app-cliente:latest`
|
||
- `wireguardImageName = hub.davinti.com.br:443/davinti-vproxy:latest`
|
||
- `configPath = config.toml`, `wireguardConfigPath = envs`
|
||
|
||
In [docker.go](internal/tui/docker.go): `networkName = app-dono_app` (all containers
|
||
join this network; created on demand).
|
||
|
||
## Generated files
|
||
|
||
- **`config.toml`** — app config, bind-mounted at `/app/config.toml`. If present at
|
||
startup it seeds form defaults via `loadConfig`. Generated by `GenerateConfigTOML`.
|
||
- **`envs`** — vproxy/WireGuard env vars, passed via `--env-file`. Generated only when
|
||
there is no public IP. By `GenerateWireguardConfig`.
|
||
|
||
Both are gitignored.
|
||
|
||
## Containers
|
||
|
||
| Container | Image | Notes |
|
||
| ------------------ | -------------------- | -------------------------------------------------------- |
|
||
| `app-dono-cliente` | app-cliente | `<host port>:8080`, mounts config.toml + cert dir, `--restart unless-stopped`, runs as host uid:gid. |
|
||
| `vproxy` | davinti-vproxy | `--cap-add=NET_ADMIN`, `/dev/net/tun`, `--env-file envs`, only without public IP. |
|
||
|
||
`seccomp=unconfined` is added to either container when the "Modo Compatibilidade"
|
||
(`seccomp_unconfined`) select is `"Sim"` — for old machines.
|
||
|
||
## Gotchas (read before editing)
|
||
|
||
- **Port:** `config.toml` always writes `port = 8080` (hardcoded in
|
||
`GenerateConfigTOML`). The form's "Porta" value only sets the **host-side** port in
|
||
the `docker run` mapping (`<port>:8080`); inside the container the app always listens
|
||
on 8080.
|
||
- **Network name** is the `networkName` constant (`app-dono_app`) in
|
||
[docker.go](internal/tui/docker.go); use it rather than re-typing the literal.
|
||
- **Registry credentials** are collected once (login form) and reused for the vproxy
|
||
pull — no second prompt.
|
||
- Containers run detached and are verified with `docker inspect` for `running`; on
|
||
failure the last 20 log lines are surfaced.
|
||
|
||
## Build & run
|
||
|
||
```bash
|
||
go run ./cmd # run from source
|
||
go build -o installer ./cmd # local binary
|
||
|
||
make build # multi-arch (linux/darwin/windows, amd64/arm64) → ./dist
|
||
make build VERSION=2.0.0
|
||
make clean
|
||
make push S3_BUCKET=<bucket> VERSION=<v> # sync dist/ to S3 (version + latest)
|
||
make release S3_BUCKET=<bucket> VERSION=<v> # per-binary copy with OS/arch naming
|
||
```
|
||
|
||
Tests (`go test ./...`) cover the pure logic in
|
||
[config_test.go](internal/tui/config_test.go) (config.toml/envs generation + numeric
|
||
validation) and [form_test.go](internal/tui/form_test.go) (`FormStep.Values()`). The
|
||
docker wrappers, Update and View are not unit-tested — verify those by running
|
||
`go build ./...` and exercising the TUI manually. Fully running the installer requires
|
||
a working Docker daemon and registry access.
|
||
|
||
## Conventions
|
||
|
||
- User-facing strings in the TUI are **Portuguese (BR)**; keep new ones consistent.
|
||
- Keep code comments and identifiers matching the surrounding style (mixed
|
||
English identifiers, Portuguese user messages).
|
||
- When adding a wizard step: add it to `steps.go`, a handler in `update.go`, a render
|
||
branch in `view.go`, and any new `Msg`/`Cmd` in `cmds.go`.
|