added encryption and disk selection
This commit is contained in:
67
tui/model.go
67
tui/model.go
@@ -11,6 +11,10 @@ type State int
|
||||
const (
|
||||
StateWelcome State = iota
|
||||
StateDiskSelection
|
||||
StateEncryption
|
||||
StateHostname
|
||||
StateUser
|
||||
StateConfirm
|
||||
StateInstalling
|
||||
StateFinished
|
||||
)
|
||||
@@ -20,6 +24,12 @@ type RootModel struct {
|
||||
CurrentModel tea.Model
|
||||
Width int
|
||||
Height int
|
||||
SelectedDisk string
|
||||
EnableLUKS bool
|
||||
Hostname string
|
||||
Username string
|
||||
UserPassword string
|
||||
RootPassword string
|
||||
}
|
||||
|
||||
func NewModel() RootModel {
|
||||
@@ -29,6 +39,10 @@ func NewModel() RootModel {
|
||||
}
|
||||
}
|
||||
|
||||
func (m RootModel) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
@@ -54,13 +68,56 @@ func (m RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case steps.DiskSelModel:
|
||||
if m.CurrentModel.(steps.DiskSelModel).Finished {
|
||||
// need to update this to pass actual selected disk model to the installer
|
||||
selectedDisk := m.CurrentModel.(steps.DiskSelModel).SelectedDisk
|
||||
m.State = StateInstalling
|
||||
m.CurrentModel = steps.NewInstallModel(selectedDisk)
|
||||
m.SelectedDisk = m.CurrentModel.(steps.DiskSelModel).SelectedDisk
|
||||
m.State = StateEncryption
|
||||
m.CurrentModel = steps.NewEncryptionModel()
|
||||
return m, m.CurrentModel.Init()
|
||||
}
|
||||
// logic for StateInstalling -> StateFinished will go here
|
||||
|
||||
case steps.EncryptionModel:
|
||||
if m.CurrentModel.(steps.EncryptionModel).Finished {
|
||||
m.EnableLUKS = m.CurrentModel.(steps.EncryptionModel).EnableEncryption
|
||||
if m.EnableLUKS {
|
||||
m.RootPassword = m.CurrentModel.(steps.EncryptionModel).Password
|
||||
}
|
||||
m.State = StateHostname
|
||||
m.CurrentModel = steps.NewHostnameModel()
|
||||
return m, m.CurrentModel.Init()
|
||||
}
|
||||
|
||||
case steps.HostnameModel:
|
||||
if m.CurrentModel.(steps.HostnameModel).Finished {
|
||||
m.Hostname = m.CurrentModel.(steps.HostnameModel).Hostname
|
||||
m.State = StateUser
|
||||
m.CurrentModel = steps.NewUserModel()
|
||||
return m, m.CurrentModel.Init()
|
||||
}
|
||||
|
||||
case steps.UserModel:
|
||||
if m.CurrentModel.(steps.UserModel).Finished {
|
||||
m.Username = m.CurrentModel.(steps.UserModel).Username
|
||||
m.UserPassword = m.CurrentModel.(steps.UserModel).Password
|
||||
m.State = StateConfirm
|
||||
m.CurrentModel = steps.NewConfirmModel(m.SelectedDisk, m.EnableLUKS, m.Hostname, m.Username)
|
||||
return m, m.CurrentModel.Init()
|
||||
}
|
||||
|
||||
case steps.ConfirmModel:
|
||||
if m.CurrentModel.(steps.ConfirmModel).Finished {
|
||||
if m.CurrentModel.(steps.ConfirmModel).Confirmed {
|
||||
m.State = StateInstalling
|
||||
m.CurrentModel = steps.NewInstallModel(m.SelectedDisk, m.EnableLUKS, m.Hostname, m.Username, m.UserPassword, m.RootPassword)
|
||||
return m, m.CurrentModel.Init()
|
||||
} else {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
|
||||
case steps.InstallModel:
|
||||
if m.CurrentModel.(steps.InstallModel).Finished {
|
||||
m.State = StateFinished
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
@@ -19,16 +19,16 @@ func (i Disk) Description() string { return i.description }
|
||||
|
||||
type ItemDelegate struct{}
|
||||
|
||||
func (i ItemDelegate) Height() int { return 1 }
|
||||
func (i ItemDelegate) Width() int { return 0 }
|
||||
func (i ItemDelegate) Update(msg tea.Msg, m list.Model) tea.Cmd { return nil }
|
||||
func (i ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
||||
i, ok := item.(Disk)
|
||||
func (i ItemDelegate) Height() int { return 1 }
|
||||
func (i ItemDelegate) Spacing() int { return 0 }
|
||||
func (i ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
|
||||
disk, ok := item.(Disk)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
s := fmt.Sprintf("%d. %s - %s", index+1, i.Title(), i.Description())
|
||||
s := fmt.Sprintf("%d. %s - %s", index+1, disk.Title(), disk.Description())
|
||||
|
||||
// focus styles
|
||||
if index == m.Index() {
|
||||
@@ -53,12 +53,11 @@ func NewDiskSelModel(width, height int) DiskSelModel {
|
||||
Disk{name: "/dev/loop0", description: "2GB Fanxiang SSD"},
|
||||
}
|
||||
|
||||
l := list.New(disks, itemDelegate{}, width, height-5)
|
||||
l.Title = styles.TitleStyle.Render("Select Installation Disk")
|
||||
l := list.New(disks, ItemDelegate{}, width, height-5)
|
||||
l.Title = "Select Installation Disk"
|
||||
l.SetShowStatusBar(false)
|
||||
l.SetFilterEnabled(false)
|
||||
l.SetFilteringEnabled(false)
|
||||
l.Styles.Title = styles.TitleStyle
|
||||
l.KeyMap.Unbind()
|
||||
|
||||
return DiskSelModel{list: l}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user