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.
9.2 KiB
CLAUDE.md
Guidance for Claude Code when working in this repository.
User-facing docs are in Portuguese (BR): README.md and 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,spinnercomponents. - Lip Gloss v2 — styling.
- BurntSushi/toml — read existing
config.tomlfor 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) holds all state:
currentStep, progress/error flags, oneFormStepper form, andconfigValues(ConfigValues) which accumulates each form'smap[string]stringoutput by section. - Update (update.go) handles global messages
(
Ctrl+C, window size, spinner tick) then dispatches oncurrentStepto per-step handlers. Step transitions happen when a form returnsdone == trueor when an async completion message arrives. - View (view.go) renders per-step.
- Commands (cmds.go) wrap every blocking op (docker calls,
file writes) as a
tea.Cmdreturning aMsg:DockerCheckedMsg,ImageDownloadFinishedMsg,ConfigFileMsg,DockerRunMsg,TickMsg.
Wizard flow
steps.go defines the order. See docs/fluxo.md for full detail.
StepCheckDocker—exec.LookPath("docker"). Missing →StepDockerInstall(instructs manual install and exits; never auto-installs Docker).StepDockerLogin→StepDownloadImage—docker login+pullof the app image. These registry credentials are reused later for the vproxy image.StepIPQuestion— "public IP available?"- Yes → jump to
StepAppConfig. - No → vproxy block:
StepWireguardConfig→StepGenerateWireguardFile(writesenvs) →StepDownloadWireguard→StepRunWireguard.
- Yes → jump to
- Config forms:
StepAppConfig→StepServerConfig→StepDatabaseConfig→StepCertConfig. StepReview— shows all collected config; Enter confirms.StepGenerateFile— writesconfig.toml(validates numeric fields first).StepRunDocker— runsapp-dono-clientecontainer.StepRunUpdater— runsapp-dono-updater, which auto-updatesapp-dono-clientewhenever a new image is pushed to:latest.StepDone.
Navigation & error handling
- Back navigation:
Escreturns to the previous input step. The Model keeps ahistory []stepstack;advance(next)pushes the current input step,goBack()pops.isInputStep(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.Escis handled globally inUpdate; forms don't consume it. Form values survive going back because theFormSteps live on the Model. - Retry vs. fix: transient failures (image pull, container run) offer
rto 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:
imageName = hub.davinti.com.br:443/app-dono/app-cliente:latestwireguardImageName = hub.davinti.com.br:443/davinti-vproxy:latestupdaterImageName = docker:cliconfigPath = config.toml,wireguardConfigPath = envs
In 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 vialoadConfig. Generated byGenerateConfigTOML.envs— vproxy/WireGuard env vars, passed via--env-file. Generated only when there is no public IP. ByGenerateWireguardConfig.
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 pulls 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.tomlalways writesport = 8080(hardcoded inGenerateConfigTOML). The form's "Porta" value only sets the host-side port in thedocker runmapping (<port>:8080); inside the container the app always listens on 8080. - Network name is the
networkNameconstant (app-dono_app) in 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 inspectforrunning; on failure the last 20 log lines are surfaced.
Build & run
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)
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 (config.toml/envs generation + numeric
validation) and 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 inupdate.go, a render branch inview.go, and any newMsg/Cmdincmds.go.