Files
tuio/internal/tui/model.go
T
teste.inabaandClaude Sonnet 5 2fecad021b fix: persist host port and compat mode in config.toml
The host-side port mapping and seccomp compatibility-mode selections
were only used in-memory for docker run args and never written to
config.toml, so a re-run of the installer couldn't recover them as
form defaults (worse, the host port field silently defaulted to the
container's internal port). Add host_port and seccomp_unconfined to
[server] and decode them back via AppConfig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 15:08:24 -03:00

303 lines
7.4 KiB
Go

package tui
import (
"fmt"
"os"
"strconv"
"charm.land/bubbles/v2/spinner"
tea "charm.land/bubbletea/v2"
"github.com/BurntSushi/toml"
)
type Model struct {
currentStep step
history []step // stack of input steps visited, for back navigation
cursor int
width int
height int
spinner spinner.Model
loading bool
dockerInstalled bool
checkDockerDone bool
checkProgress float64
downloadDone bool
downloadMessage string
downloadError error
loginData DockerLoginData
isPublicIP bool
loginForm FormStep
wireguardForm FormStep
appForm FormStep
serverForm FormStep
dbForm FormStep
certForm FormStep
configValues ConfigValues
finishedFile bool
configFileError error
finishedDockerRun bool
dockerRunError error
err error
}
type DockerLoginData struct {
Login string
Password string
}
type ConfigValues struct {
Login map[string]string
Wireguard map[string]string
Server map[string]string
Database map[string]string
Cert map[string]string
Application map[string]string
}
type AppConfig struct {
Server struct {
Port int64 `toml:"port"`
HostPort int64 `toml:"host_port"`
Timeout int64 `toml:"timeout_seconds"`
Environment string `toml:"environment"`
SeccompUnconfined bool `toml:"seccomp_unconfined"`
} `toml:"server"`
Database struct {
Type string `toml:"type"`
URL string `toml:"url"`
MaxConns int64 `toml:"max_conns"`
MinConns int64 `toml:"min_conns"`
} `toml:"database"`
Certificates struct {
DirPath string `toml:"mapped_dir"`
} `toml:"certificate"`
Application struct {
CentralServerURL string `toml:"central_server_url"`
EnrollmentToken string `toml:"enrollment_token"`
} `toml:"application"`
}
func loadConfig() AppConfig {
var config AppConfig
config.Server.Port = containerAppPort
config.Server.HostPort = 8081
config.Server.Timeout = 30
config.Server.Environment = "production"
config.Database.Type = "postgres"
config.Database.MaxConns = 10
config.Database.MinConns = 2
// Database.URL, Certificates.DirPath and Application.CentralServerURL are
// intentionally left empty so the fields show their placeholder instead of a
// pre-filled dummy. A real config.toml (re-run) still seeds them through the
// decode below.
_, err := os.Stat("config.toml")
if err == nil {
if _, err := toml.DecodeFile("config.toml", &config); err != nil {
fmt.Printf("Error loading config: %v\n", err)
}
}
return config
}
func InitialModel() Model {
cfg := loadConfig()
s := spinner.New()
s.Spinner = spinner.Dot
s.Style = SpinnerStyle
seccompDefault := "Não"
if cfg.Server.SeccompUnconfined {
seccompDefault = "Sim"
}
return Model{
currentStep: StepCheckDocker,
loginForm: NewFormStep("Login no Repositório Docker", []FormField{
{
Id: "user",
Label: "Usuário",
Placeholder: "usuario",
Type: FieldTypeText,
},
{
Id: "password",
Label: "Senha",
Placeholder: "senhaForte123",
Type: FieldTypePassword,
},
}),
wireguardForm: NewFormStep("Configurações vproxy", []FormField{
{
Id: "privkey",
Label: "Chave Privada",
Placeholder: "",
Type: FieldTypeText,
},
{
Id: "vip",
Label: "IP Virtual",
Default: "127.0.0.1",
Placeholder: "127.0.0.1",
Type: FieldTypeText,
},
{
Id: "psk",
Label: "Pre-Shared Key",
Placeholder: "",
Type: FieldTypeText,
},
{
Id: "proxy_edps",
Label: "Proxy EDPS",
Placeholder: "22:127.0.0.1:22",
Type: FieldTypeText,
},
{
Id: "mtu",
Label: "MTU",
Default: "1380",
Placeholder: "1380",
Type: FieldTypeNumber,
},
{
Id: "proto",
Label: "Protocolo",
Default: "UDP",
Type: FieldTypeSelect,
Options: []string{"UDP", "TCP"},
},
}),
serverForm: NewFormStep("Servidor", []FormField{
{
Id: "port",
Label: "Porta (host)",
Placeholder: "8081",
Default: strconv.FormatInt(cfg.Server.HostPort, 10),
Type: FieldTypeNumber,
CharLimit: 4,
},
{
Id: "timeout",
Label: "Timeout (Segundos)",
Placeholder: "30",
Default: strconv.FormatInt(cfg.Server.Timeout, 10),
Type: FieldTypeNumber,
CharLimit: 3,
},
{
Id: "environment",
Label: "Ambiente",
Default: cfg.Server.Environment,
Type: FieldTypeSelect,
Options: []string{"development", "production"},
},
{
Id: "seccomp_unconfined",
Label: "Modo Compatibilidade (máquinas antigas)",
Default: seccompDefault,
Type: FieldTypeSelect,
Options: []string{"Não", "Sim"},
},
}),
dbForm: NewFormStep("Banco de Dados", []FormField{
{
Id: "database_type",
Label: "Tipo do Banco",
Default: cfg.Database.Type,
Type: FieldTypeSelect,
Options: []string{"postgres", "oracle"},
},
{
Id: "database_url",
Label: "URL de acesso",
Placeholder: "postgres://usuario:senha@banco:5432/app_dono_db",
Default: cfg.Database.URL,
Type: FieldTypeText,
},
{
Id: "max_conns",
Label: "Conexões ativas (máximo)",
Placeholder: "10",
Default: strconv.FormatInt(cfg.Database.MaxConns, 10),
Type: FieldTypeNumber,
},
{
Id: "min_conns",
Label: "Conexões ativas (mínimo)",
Placeholder: "2",
Default: strconv.FormatInt(cfg.Database.MinConns, 10),
Type: FieldTypeNumber,
},
}),
appForm: NewFormStep("Aplicação", []FormField{
{
Id: "central_server_url",
Label: "URL do Servidor Central",
Placeholder: "https://app-dono-api.vitruvio.com.br:8443",
Default: cfg.Application.CentralServerURL,
Type: FieldTypeText,
},
{
Id: "enrollment_token",
Label: "Token de Inscrição",
Placeholder: "token gerado no painel web",
Default: cfg.Application.EnrollmentToken,
Type: FieldTypeText,
},
}),
certForm: NewFormStep("Certificado", []FormField{
{
Id: "cert_dir_path",
Label: "Diretório para armazenar certificados",
Placeholder: "/caminho/para/diretorio",
Default: cfg.Certificates.DirPath,
Type: FieldTypeText,
},
}),
spinner: s,
}
}
func (m Model) Init() tea.Cmd {
return tea.Batch(CheckDockerCmd(), TickCmd(), m.spinner.Tick)
}
// advance moves to the next step, recording the current step on the history stack when
// it is an input step so that Esc can return to it. Consecutive duplicates are skipped.
func (m *Model) advance(next step) {
if isInputStep(m.currentStep) {
if n := len(m.history); n == 0 || m.history[n-1] != m.currentStep {
m.history = append(m.history, m.currentStep)
}
}
m.currentStep = next
}
// goBack returns to the previous input step on the history stack, if any. It reports
// whether the step changed.
func (m *Model) goBack() bool {
if len(m.history) == 0 {
return false
}
last := m.history[len(m.history)-1]
m.history = m.history[:len(m.history)-1]
m.currentStep = last
return true
}
// canGoBack reports whether Esc would return to a previous step from the current one.
func (m Model) canGoBack() bool {
return isInputStep(m.currentStep) && len(m.history) > 0
}