Files
tuio/CLAUDE.md
T
teste.inaba a05b98fdf5 feat: auto-update app-dono-cliente when a new image is pushed to :latest
Adds a StepRunUpdater step that starts app-dono-updater after the app
container comes up: a poll loop (docker pull, compare image IDs,
recreate on change) baked into the official docker:cli image via
`sh -c`, reusing the same docker run argv as the initial container
start so the two can't drift.

Not built on Watchtower: containrrr/watchtower was archived upstream
in Dec 2025 with no maintained successor recommended for production
use, so this avoids taking on that dependency.
2026-08-26 12:30:44 -03:00

174 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. `StepReview` — shows all collected config; Enter confirms.
6. `StepGenerateFile` — writes `config.toml` (validates numeric fields first).
7. `StepRunDocker` — runs `app-dono-cliente` container.
8. `StepRunUpdater` — runs `app-dono-updater`, which auto-updates `app-dono-cliente`
whenever a new image is pushed to `:latest`.
9. `StepDone`.
### Navigation & error handling
- **Back navigation:** `Esc` returns to the previous *input* step. The Model keeps a
`history []step` stack; `advance(next)` pushes the current input step, `goBack()`
pops. `isInputStep` ([steps.go](internal/tui/steps.go)) gates which steps participate
— action/wait steps (downloads, file gen, container runs) are excluded so back never
re-enters a side-effecting step. `Esc` is handled globally in `Update`; forms don't
consume it. Form values survive going back because the `FormStep`s live on the Model.
- **Retry vs. fix:** transient failures (image pull, container run) offer `r` to re-run
just that command (`q`/other quits). Validation failures (file generation) route back
to the relevant form to correct the value instead of quitting.
## 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`
- `updaterImageName = docker:cli`
- `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. |
| `app-dono-updater` | docker:cli | Not a third-party updater — a poll loop (`sh -c`) baked into the official `docker:cli` image, since Watchtower was archived upstream in Dec 2025 with no maintained successor recommended for production. Every `updaterPollIntervalSeconds` (300s) it `docker pull`s `app-dono-cliente`'s image, compares image IDs, and if changed, stops/removes/recreates the container using the exact same `docker run` argv as the original start (`appClienteRunArgs` in `docker.go`, shared by both call sites so they can't drift). Mounts `/var/run/docker.sock` and the host's `~/.docker/config.json` (written by the earlier `docker login`) for private-registry auth. Not on `app-dono_app` — only talks to the Docker daemon. |
`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
```
**Versioning:** `tui.Version` ([internal/tui/version.go](internal/tui/version.go))
defaults to `"dev"` and is stamped at build time via `VERSION_LDFLAG`
(`-X .../internal/tui.Version=$(VERSION)`) in the Makefile. `installer --version`
(or `-v`) prints it and exits before the TUI starts; it is also shown in the TUI
header. A plain `go build` leaves it as `"dev"`. The Makefile build rules have no
source deps, so `make build` will not rebuild existing `dist/` artifacts — run
`make clean` first when re-stamping a new VERSION.
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`.