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"` Timeout int64 `toml:"timeout_seconds"` Environment string `toml:"environment"` } `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 = 8081 config.Server.Timeout = 30 config.Server.Environment = "production" config.Database.Type = "postgres" config.Database.MaxConns = 10 config.Database.MinConns = 2 config.Certificates.DirPath = "/caminho/para/diretorio" // Database.URL 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 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.Port, 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: "Não", 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 }