package steps import ( "encoding/json" "fmt" "miasma-installer/config" "miasma-installer/tui/styles" "os" "os/exec" "path/filepath" "strings" tea "github.com/charmbracelet/bubbletea" ) type InstallModel struct { config *config.InstallConfig Finished bool status string progress []string err error } func NewInstallModel(disk string, enableLUKS bool, hostname string, username string, userPassword string, rootPassword string) InstallModel { return InstallModel{ config: &config.InstallConfig{ Disk: disk, EnableLUKS: enableLUKS, Hostname: hostname, Username: username, UserPassword: userPassword, RootPassword: rootPassword, Timezone: "UTC", Locale: "en_US.UTF-8", }, status: "Preparing installation...", progress: []string{}, } } func (m InstallModel) Init() tea.Cmd { return m.startInstall() } type installCompleteMsg struct { err error } func (m InstallModel) startInstall() tea.Cmd { return func() tea.Msg { archConfig := m.config.ToArchInstall() configPath := "/tmp/miasma-install-config.json" data, err := json.MarshalIndent(archConfig, "", " ") if err != nil { return installCompleteMsg{err: fmt.Errorf("failed to marshal config: %w", err)} } if err := os.WriteFile(configPath, data, 0644); err != nil { return installCompleteMsg{err: fmt.Errorf("failed to write config: %w", err)} } cmd := exec.Command("archinstall", "--config", configPath) output, err := cmd.CombinedOutput() if err != nil { return installCompleteMsg{err: fmt.Errorf("archinstall failed: %w\nOutput: %s", err, string(output))} } if err := m.runPostInstallScripts(); err != nil { return installCompleteMsg{err: fmt.Errorf("post-install scripts failed: %w", err)} } return installCompleteMsg{err: nil} } } func (m InstallModel) runPostInstallScripts() error { scriptsDir := "./scripts" entries, err := os.ReadDir(scriptsDir) if err != nil { if os.IsNotExist(err) { return nil } return err } for _, entry := range entries { if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".sh") { continue } scriptPath := filepath.Join(scriptsDir, entry.Name()) cmd := exec.Command("/bin/bash", scriptPath) if output, err := cmd.CombinedOutput(); err != nil { return fmt.Errorf("script %s failed: %w\nOutput: %s", entry.Name(), err, string(output)) } } return nil } func (m InstallModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case installCompleteMsg: if msg.err != nil { m.err = msg.err m.status = "Installation failed" } else { m.status = "Installation complete!" } m.Finished = true return m, nil } return m, nil } func (m InstallModel) View() string { s := styles.TitleStyle.Render("Installing Miasma OS") s += "\n\n" s += m.status + "\n\n" if m.err != nil { s += styles.HelpStyle.Render(fmt.Sprintf("Error: %v\n", m.err)) } if m.Finished && m.err == nil { s += styles.HelpStyle.Render("Press Ctrl+C to exit") } return styles.MainStyle.Render(s) }