refactor(system): add retry logic and logging to timezone detection
- commit
- f981a2c911582804181acba76ad878255c235a47
- author
- bnema · · 2025-10-27 22:38 UTC
- committer
- bnema · · 2025-10-27 20:11 UTC
- parent
- 9700fec
- signature
- unsigned
5 files · +93 -37
M internal/config/config.go +2 -6
| @@ -49,11 +49,6 @@ const ( | |||
| 49 | 49 | KernelLinuxCachyOS = "linux-cachyos" | |
| 50 | 50 | ) | |
| 51 | 51 | ||
| 52 | - | // AUR helpers | |
| 53 | - | const ( | |
| 54 | - | AURHelperParu = "paru" | |
| 55 | - | AURHelperYay = "yay" | |
| 56 | - | ) | |
| 57 | 52 | ||
| 58 | 53 | // System paths | |
| 59 | 54 | const ( | |
| @@ -96,7 +91,7 @@ const ( | |||
| 96 | 91 | const ( | |
| 97 | 92 | LocaleDefault = "en_US.UTF-8" | |
| 98 | 93 | LocaleDefaultGen = "en_US.UTF-8 UTF-8" | |
| 99 | - | TimezoneDefault = "UTC" | |
| 94 | + | TimezoneDefault = "" | |
| 100 | 95 | KeymapDefault = "us" | |
| 101 | 96 | ) | |
| 102 | 97 | ||
| @@ -160,6 +155,7 @@ const ( | |||
| 160 | 155 | PathMntEtcPacmanConf = "/mnt/etc/pacman.conf" | |
| 161 | 156 | ) | |
| 162 | 157 | ||
| 158 | + | ||
| 163 | 159 | // PostInstall paths | |
| 164 | 160 | const ( | |
| 165 | 161 | PathMntBootLogo = "/mnt/boot/arch-logo.png" | |
| @@ -49,11 +49,6 @@ const ( | |||||
| 49 | KernelLinuxCachyOS = "linux-cachyos" | 49 | KernelLinuxCachyOS = "linux-cachyos" | ||
| 50 | ) | 50 | ) | ||
| 51 | 51 | ||||
| 52 | - | // AUR helpers | |||
| 53 | - | const ( | |||
| 54 | - | AURHelperParu = "paru" | |||
| 55 | - | AURHelperYay = "yay" | |||
| 56 | - | ) | |||
| 57 | 52 | ||||
| 58 | // System paths | 53 | // System paths | ||
| 59 | const ( | 54 | const ( | ||
| @@ -96,7 +91,7 @@ const ( | |||||
| 96 | const ( | 91 | const ( | ||
| 97 | LocaleDefault = "en_US.UTF-8" | 92 | LocaleDefault = "en_US.UTF-8" | ||
| 98 | LocaleDefaultGen = "en_US.UTF-8 UTF-8" | 93 | LocaleDefaultGen = "en_US.UTF-8 UTF-8" | ||
| 99 | - | TimezoneDefault = "UTC" | 94 | + | TimezoneDefault = "" |
| 100 | KeymapDefault = "us" | 95 | KeymapDefault = "us" | ||
| 101 | ) | 96 | ) | ||
| 102 | 97 | ||||
| @@ -160,6 +155,7 @@ const ( | |||||
| 160 | PathMntEtcPacmanConf = "/mnt/etc/pacman.conf" | 155 | PathMntEtcPacmanConf = "/mnt/etc/pacman.conf" | ||
| 161 | ) | 156 | ) | ||
| 162 | 157 | ||||
| 158 | + | ||||
| 163 | // PostInstall paths | 159 | // PostInstall paths | ||
| 164 | const ( | 160 | const ( | ||
| 165 | PathMntBootLogo = "/mnt/boot/arch-logo.png" | 161 | PathMntBootLogo = "/mnt/boot/arch-logo.png" | ||
A internal/system/system.go +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | package system | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "log/slog" | |
| 5 | + | ) | |
| 6 | + | ||
| 7 | + | // System provides system-level operations with logging support | |
| 8 | + | type System struct { | |
| 9 | + | log *slog.Logger | |
| 10 | + | } | |
| 11 | + | ||
| 12 | + | // NewSystem creates a new System instance with the provided logger | |
| 13 | + | func NewSystem(log *slog.Logger) *System { | |
| 14 | + | return &System{ | |
| 15 | + | log: log, | |
| 16 | + | } | |
| 17 | + | } | |
| @@ -0,0 +1,17 @@ | |||||
| 1 | + | package system | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "log/slog" | |||
| 5 | + | ) | |||
| 6 | + | ||||
| 7 | + | // System provides system-level operations with logging support | |||
| 8 | + | type System struct { | |||
| 9 | + | log *slog.Logger | |||
| 10 | + | } | |||
| 11 | + | ||||
| 12 | + | // NewSystem creates a new System instance with the provided logger | |||
| 13 | + | func NewSystem(log *slog.Logger) *System { | |||
| 14 | + | return &System{ | |||
| 15 | + | log: log, | |||
| 16 | + | } | |||
| 17 | + | } | |||
M internal/system/timezone.go +65 -25
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package system | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "io" | |
| 5 | 6 | "net/http" | |
| 6 | 7 | "strings" | |
| @@ -8,44 +9,83 @@ import ( | |||
| 8 | 9 | ) | |
| 9 | 10 | ||
| 10 | 11 | const ( | |
| 11 | - | timezoneAPIURL = "https://ipapi.co/timezone/" | |
| 12 | - | timezoneAPITimeout = 5 * time.Second | |
| 12 | + | timezoneAPIURL = "https://ipapi.co/timezone/" | |
| 13 | + | timezoneAPITimeout = 5 * time.Second | |
| 14 | + | timezoneRetryAttempt = 3 | |
| 15 | + | timezoneRetryDelay = 1 * time.Second | |
| 13 | 16 | ) | |
| 14 | 17 | ||
| 15 | - | // DetectTimezone fetches timezone from ipapi.co API | |
| 18 | + | // DetectTimezone fetches timezone from ipapi.co API with retry logic | |
| 19 | + | // Attempts up to 3 times with exponential backoff before falling back | |
| 16 | 20 | // Returns detected timezone string or empty string on failure | |
| 17 | - | func DetectTimezone() string { | |
| 18 | - | client := &http.Client{ | |
| 19 | - | Timeout: timezoneAPITimeout, | |
| 20 | - | } | |
| 21 | + | // Logs details to the system logger | |
| 22 | + | func (s *System) DetectTimezone() string { | |
| 23 | + | var lastErr error | |
| 21 | 24 | ||
| 22 | - | resp, err := client.Get(timezoneAPIURL) | |
| 23 | - | switch { | |
| 24 | - | case err != nil: | |
| 25 | - | return "" | |
| 26 | - | } | |
| 27 | - | defer resp.Body.Close() | |
| 25 | + | for attempt := 1; attempt <= timezoneRetryAttempt; attempt++ { | |
| 26 | + | if attempt > 1 { | |
| 27 | + | // Log retry attempt | |
| 28 | + | s.log.Debug("Timezone detection retry", | |
| 29 | + | "attempt", attempt, | |
| 30 | + | "max_attempts", timezoneRetryAttempt, | |
| 31 | + | "delay", timezoneRetryDelay.String()) | |
| 32 | + | time.Sleep(timezoneRetryDelay) | |
| 33 | + | } | |
| 28 | 34 | ||
| 29 | - | switch resp.StatusCode { | |
| 30 | - | case http.StatusOK: | |
| 35 | + | client := &http.Client{ | |
| 36 | + | Timeout: timezoneAPITimeout, | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | resp, err := client.Get(timezoneAPIURL) | |
| 40 | + | if err != nil { | |
| 41 | + | lastErr = fmt.Errorf("http request failed: %w", err) | |
| 42 | + | s.log.Debug("Timezone detection failed", | |
| 43 | + | "attempt", attempt, | |
| 44 | + | "error", lastErr) | |
| 45 | + | continue | |
| 46 | + | } | |
| 47 | + | defer resp.Body.Close() | |
| 48 | + | ||
| 49 | + | // Check HTTP status code | |
| 50 | + | if resp.StatusCode != http.StatusOK { | |
| 51 | + | lastErr = fmt.Errorf("API returned status %d", resp.StatusCode) | |
| 52 | + | s.log.Debug("Timezone detection failed", | |
| 53 | + | "attempt", attempt, | |
| 54 | + | "error", lastErr) | |
| 55 | + | continue | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | // Read response body | |
| 31 | 59 | body, err := io.ReadAll(resp.Body) | |
| 32 | - | switch { | |
| 33 | - | case err != nil: | |
| 34 | - | return "" | |
| 60 | + | if err != nil { | |
| 61 | + | lastErr = fmt.Errorf("failed to read response: %w", err) | |
| 62 | + | s.log.Debug("Timezone detection failed", | |
| 63 | + | "attempt", attempt, | |
| 64 | + | "error", lastErr) | |
| 65 | + | continue | |
| 35 | 66 | } | |
| 36 | 67 | ||
| 37 | 68 | timezone := strings.TrimSpace(string(body)) | |
| 38 | 69 | ||
| 39 | 70 | // Validate timezone format (should be like "America/New_York") | |
| 40 | - | switch { | |
| 41 | - | case strings.Contains(timezone, "/"): | |
| 42 | - | return timezone | |
| 43 | - | default: | |
| 44 | - | return "" | |
| 71 | + | if !strings.Contains(timezone, "/") { | |
| 72 | + | lastErr = fmt.Errorf("invalid timezone format: %q (expected format like America/New_York)", timezone) | |
| 73 | + | s.log.Debug("Timezone detection failed", | |
| 74 | + | "attempt", attempt, | |
| 75 | + | "error", lastErr) | |
| 76 | + | continue | |
| 45 | 77 | } | |
| 46 | - | default: | |
| 47 | - | return "" | |
| 78 | + | ||
| 79 | + | // Success - log and return | |
| 80 | + | s.log.Info("Timezone detected successfully", "timezone", timezone) | |
| 81 | + | return timezone | |
| 48 | 82 | } | |
| 83 | + | ||
| 84 | + | // All retries exhausted | |
| 85 | + | s.log.Warn("Timezone detection failed after all retries", | |
| 86 | + | "attempts", timezoneRetryAttempt, | |
| 87 | + | "last_error", lastErr) | |
| 88 | + | return "" | |
| 49 | 89 | } | |
| 50 | 90 | ||
| 51 | 91 | // ValidateTimezone checks if a timezone string is valid | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package system | 1 | package system | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "io" | 5 | "io" | ||
| 5 | "net/http" | 6 | "net/http" | ||
| 6 | "strings" | 7 | "strings" | ||
| @@ -8,44 +9,83 @@ import ( | |||||
| 8 | ) | 9 | ) | ||
| 9 | 10 | ||||
| 10 | const ( | 11 | const ( | ||
| 11 | - | timezoneAPIURL = "https://ipapi.co/timezone/" | 12 | + | timezoneAPIURL = "https://ipapi.co/timezone/" |
| 12 | - | timezoneAPITimeout = 5 * time.Second | 13 | + | timezoneAPITimeout = 5 * time.Second |
| 14 | + | timezoneRetryAttempt = 3 | |||
| 15 | + | timezoneRetryDelay = 1 * time.Second | |||
| 13 | ) | 16 | ) | ||
| 14 | 17 | ||||
| 15 | - | // DetectTimezone fetches timezone from ipapi.co API | 18 | + | // DetectTimezone fetches timezone from ipapi.co API with retry logic |
| 19 | + | // Attempts up to 3 times with exponential backoff before falling back | |||
| 16 | // Returns detected timezone string or empty string on failure | 20 | // Returns detected timezone string or empty string on failure | ||
| 17 | - | func DetectTimezone() string { | 21 | + | // Logs details to the system logger |
| 18 | - | client := &http.Client{ | 22 | + | func (s *System) DetectTimezone() string { |
| 19 | - | Timeout: timezoneAPITimeout, | 23 | + | var lastErr error |
| 20 | - | } | |||
| 21 | 24 | ||||
| 22 | - | resp, err := client.Get(timezoneAPIURL) | 25 | + | for attempt := 1; attempt <= timezoneRetryAttempt; attempt++ { |
| 23 | - | switch { | 26 | + | if attempt > 1 { |
| 24 | - | case err != nil: | 27 | + | // Log retry attempt |
| 25 | - | return "" | 28 | + | s.log.Debug("Timezone detection retry", |
| 26 | - | } | 29 | + | "attempt", attempt, |
| 27 | - | defer resp.Body.Close() | 30 | + | "max_attempts", timezoneRetryAttempt, |
| 31 | + | "delay", timezoneRetryDelay.String()) | |||
| 32 | + | time.Sleep(timezoneRetryDelay) | |||
| 33 | + | } | |||
| 28 | 34 | ||||
| 29 | - | switch resp.StatusCode { | 35 | + | client := &http.Client{ |
| 30 | - | case http.StatusOK: | 36 | + | Timeout: timezoneAPITimeout, |
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | resp, err := client.Get(timezoneAPIURL) | |||
| 40 | + | if err != nil { | |||
| 41 | + | lastErr = fmt.Errorf("http request failed: %w", err) | |||
| 42 | + | s.log.Debug("Timezone detection failed", | |||
| 43 | + | "attempt", attempt, | |||
| 44 | + | "error", lastErr) | |||
| 45 | + | continue | |||
| 46 | + | } | |||
| 47 | + | defer resp.Body.Close() | |||
| 48 | + | ||||
| 49 | + | // Check HTTP status code | |||
| 50 | + | if resp.StatusCode != http.StatusOK { | |||
| 51 | + | lastErr = fmt.Errorf("API returned status %d", resp.StatusCode) | |||
| 52 | + | s.log.Debug("Timezone detection failed", | |||
| 53 | + | "attempt", attempt, | |||
| 54 | + | "error", lastErr) | |||
| 55 | + | continue | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | // Read response body | |||
| 31 | body, err := io.ReadAll(resp.Body) | 59 | body, err := io.ReadAll(resp.Body) | ||
| 32 | - | switch { | 60 | + | if err != nil { |
| 33 | - | case err != nil: | 61 | + | lastErr = fmt.Errorf("failed to read response: %w", err) |
| 34 | - | return "" | 62 | + | s.log.Debug("Timezone detection failed", |
| 63 | + | "attempt", attempt, | |||
| 64 | + | "error", lastErr) | |||
| 65 | + | continue | |||
| 35 | } | 66 | } | ||
| 36 | 67 | ||||
| 37 | timezone := strings.TrimSpace(string(body)) | 68 | timezone := strings.TrimSpace(string(body)) | ||
| 38 | 69 | ||||
| 39 | // Validate timezone format (should be like "America/New_York") | 70 | // Validate timezone format (should be like "America/New_York") | ||
| 40 | - | switch { | 71 | + | if !strings.Contains(timezone, "/") { |
| 41 | - | case strings.Contains(timezone, "/"): | 72 | + | lastErr = fmt.Errorf("invalid timezone format: %q (expected format like America/New_York)", timezone) |
| 42 | - | return timezone | 73 | + | s.log.Debug("Timezone detection failed", |
| 43 | - | default: | 74 | + | "attempt", attempt, |
| 44 | - | return "" | 75 | + | "error", lastErr) |
| 76 | + | continue | |||
| 45 | } | 77 | } | ||
| 46 | - | default: | 78 | + | |
| 47 | - | return "" | 79 | + | // Success - log and return |
| 80 | + | s.log.Info("Timezone detected successfully", "timezone", timezone) | |||
| 81 | + | return timezone | |||
| 48 | } | 82 | } | ||
| 83 | + | ||||
| 84 | + | // All retries exhausted | |||
| 85 | + | s.log.Warn("Timezone detection failed after all retries", | |||
| 86 | + | "attempts", timezoneRetryAttempt, | |||
| 87 | + | "last_error", lastErr) | |||
| 88 | + | return "" | |||
| 49 | } | 89 | } | ||
| 50 | 90 | ||||
| 51 | // ValidateTimezone checks if a timezone string is valid | 91 | // ValidateTimezone checks if a timezone string is valid | ||
M internal/ui/installer_forms.go +5 -4
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | 4 | "fmt" | |
| 5 | 5 | ||
| 6 | 6 | "github.com/bnema/archup/internal/config" | |
| 7 | + | "github.com/bnema/archup/internal/phases" | |
| 7 | 8 | "github.com/bnema/archup/internal/system" | |
| 8 | 9 | "github.com/bnema/archup/internal/ui/components" | |
| 9 | 10 | "github.com/bnema/archup/internal/validation" | |
| @@ -11,10 +12,10 @@ import ( | |||
| 11 | 12 | ) | |
| 12 | 13 | ||
| 13 | 14 | // CreatePreflightForm creates the initial configuration form with all user identity fields | |
| 14 | - | func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder) *huh.Form { | |
| 15 | + | func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System) *huh.Form { | |
| 15 | 16 | ||
| 16 | 17 | // Auto-detect timezone from API, fallback to default if detection fails | |
| 17 | - | detectedTimezone := system.DetectTimezone() | |
| 18 | + | detectedTimezone := sys.DetectTimezone() | |
| 18 | 19 | switch { | |
| 19 | 20 | case detectedTimezone != "": | |
| 20 | 21 | cfg.Timezone = detectedTimezone | |
| @@ -140,8 +141,8 @@ func CreateOptionsForm(cfg *config.Config, fb *components.FormBuilder) *huh.Form | |||
| 140 | 141 | // Page 2: Repository and package management options | |
| 141 | 142 | aurOptions := []huh.Option[string]{ | |
| 142 | 143 | huh.NewOption("None", ""), | |
| 143 | - | components.CreateOption("Paru", DescAURHelperParu, config.AURHelperParu), | |
| 144 | - | components.CreateOption("Yay", DescAURHelperYay, config.AURHelperYay), | |
| 144 | + | components.CreateOption("Paru", DescAURHelperParu, phases.AURHelperParu), | |
| 145 | + | components.CreateOption("Yay", DescAURHelperYay, phases.AURHelperYay), | |
| 145 | 146 | } | |
| 146 | 147 | ||
| 147 | 148 | packageManagementGroup := huh.NewGroup( | |
| @@ -4,6 +4,7 @@ import ( | |||||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | 5 | ||||
| 6 | "github.com/bnema/archup/internal/config" | 6 | "github.com/bnema/archup/internal/config" | ||
| 7 | + | "github.com/bnema/archup/internal/phases" | |||
| 7 | "github.com/bnema/archup/internal/system" | 8 | "github.com/bnema/archup/internal/system" | ||
| 8 | "github.com/bnema/archup/internal/ui/components" | 9 | "github.com/bnema/archup/internal/ui/components" | ||
| 9 | "github.com/bnema/archup/internal/validation" | 10 | "github.com/bnema/archup/internal/validation" | ||
| @@ -11,10 +12,10 @@ import ( | |||||
| 11 | ) | 12 | ) | ||
| 12 | 13 | ||||
| 13 | // CreatePreflightForm creates the initial configuration form with all user identity fields | 14 | // CreatePreflightForm creates the initial configuration form with all user identity fields | ||
| 14 | - | func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder) *huh.Form { | 15 | + | func CreatePreflightForm(cfg *config.Config, fb *components.FormBuilder, sys *system.System) *huh.Form { |
| 15 | 16 | ||||
| 16 | // Auto-detect timezone from API, fallback to default if detection fails | 17 | // Auto-detect timezone from API, fallback to default if detection fails | ||
| 17 | - | detectedTimezone := system.DetectTimezone() | 18 | + | detectedTimezone := sys.DetectTimezone() |
| 18 | switch { | 19 | switch { | ||
| 19 | case detectedTimezone != "": | 20 | case detectedTimezone != "": | ||
| 20 | cfg.Timezone = detectedTimezone | 21 | cfg.Timezone = detectedTimezone | ||
| @@ -140,8 +141,8 @@ func CreateOptionsForm(cfg *config.Config, fb *components.FormBuilder) *huh.Form | |||||
| 140 | // Page 2: Repository and package management options | 141 | // Page 2: Repository and package management options | ||
| 141 | aurOptions := []huh.Option[string]{ | 142 | aurOptions := []huh.Option[string]{ | ||
| 142 | huh.NewOption("None", ""), | 143 | huh.NewOption("None", ""), | ||
| 143 | - | components.CreateOption("Paru", DescAURHelperParu, config.AURHelperParu), | 144 | + | components.CreateOption("Paru", DescAURHelperParu, phases.AURHelperParu), |
| 144 | - | components.CreateOption("Yay", DescAURHelperYay, config.AURHelperYay), | 145 | + | components.CreateOption("Yay", DescAURHelperYay, phases.AURHelperYay), |
| 145 | } | 146 | } | ||
| 146 | 147 | ||||
| 147 | packageManagementGroup := huh.NewGroup( | 148 | packageManagementGroup := huh.NewGroup( | ||
M internal/ui/model.go +4 -2
| @@ -42,6 +42,7 @@ type Model struct { | |||
| 42 | 42 | formBuilder *components.FormBuilder | |
| 43 | 43 | output *components.OutputViewer | |
| 44 | 44 | logger *logger.Logger | |
| 45 | + | system *system.System | |
| 45 | 46 | spinner spinner.Model | |
| 46 | 47 | version string | |
| 47 | 48 | width int | |
| @@ -66,6 +67,7 @@ func NewModel(orchestrator *phases.Orchestrator, cfg *config.Config, log *logger | |||
| 66 | 67 | config: cfg, | |
| 67 | 68 | formBuilder: components.NewFormBuilder(false, 80), // Initial width, will update on WindowSizeMsg | |
| 68 | 69 | logger: log, | |
| 70 | + | system: system.NewSystem(log.Slog()), | |
| 69 | 71 | spinner: s, | |
| 70 | 72 | version: version, | |
| 71 | 73 | width: 80, | |
| @@ -103,7 +105,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |||
| 103 | 105 | case StateShowLogo: | |
| 104 | 106 | m.logger.Info("Moving to preflight form") | |
| 105 | 107 | m.state = StatePreflightForm | |
| 106 | - | m.currentForm = CreatePreflightForm(m.config, m.formBuilder) | |
| 108 | + | m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system) | |
| 107 | 109 | return m, m.currentForm.Init() | |
| 108 | 110 | ||
| 109 | 111 | case StateConfirmation: | |
| @@ -117,7 +119,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |||
| 117 | 119 | switch m.state { | |
| 118 | 120 | case StateDiskForm: | |
| 119 | 121 | m.state = StatePreflightForm | |
| 120 | - | m.currentForm = CreatePreflightForm(m.config, m.formBuilder) | |
| 122 | + | m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system) | |
| 121 | 123 | return m, m.currentForm.Init() | |
| 122 | 124 | ||
| 123 | 125 | case StateOptionsForm: | |
| @@ -42,6 +42,7 @@ type Model struct { | |||||
| 42 | formBuilder *components.FormBuilder | 42 | formBuilder *components.FormBuilder | ||
| 43 | output *components.OutputViewer | 43 | output *components.OutputViewer | ||
| 44 | logger *logger.Logger | 44 | logger *logger.Logger | ||
| 45 | + | system *system.System | |||
| 45 | spinner spinner.Model | 46 | spinner spinner.Model | ||
| 46 | version string | 47 | version string | ||
| 47 | width int | 48 | width int | ||
| @@ -66,6 +67,7 @@ func NewModel(orchestrator *phases.Orchestrator, cfg *config.Config, log *logger | |||||
| 66 | config: cfg, | 67 | config: cfg, | ||
| 67 | formBuilder: components.NewFormBuilder(false, 80), // Initial width, will update on WindowSizeMsg | 68 | formBuilder: components.NewFormBuilder(false, 80), // Initial width, will update on WindowSizeMsg | ||
| 68 | logger: log, | 69 | logger: log, | ||
| 70 | + | system: system.NewSystem(log.Slog()), | |||
| 69 | spinner: s, | 71 | spinner: s, | ||
| 70 | version: version, | 72 | version: version, | ||
| 71 | width: 80, | 73 | width: 80, | ||
| @@ -103,7 +105,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |||||
| 103 | case StateShowLogo: | 105 | case StateShowLogo: | ||
| 104 | m.logger.Info("Moving to preflight form") | 106 | m.logger.Info("Moving to preflight form") | ||
| 105 | m.state = StatePreflightForm | 107 | m.state = StatePreflightForm | ||
| 106 | - | m.currentForm = CreatePreflightForm(m.config, m.formBuilder) | 108 | + | m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system) |
| 107 | return m, m.currentForm.Init() | 109 | return m, m.currentForm.Init() | ||
| 108 | 110 | ||||
| 109 | case StateConfirmation: | 111 | case StateConfirmation: | ||
| @@ -117,7 +119,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |||||
| 117 | switch m.state { | 119 | switch m.state { | ||
| 118 | case StateDiskForm: | 120 | case StateDiskForm: | ||
| 119 | m.state = StatePreflightForm | 121 | m.state = StatePreflightForm | ||
| 120 | - | m.currentForm = CreatePreflightForm(m.config, m.formBuilder) | 122 | + | m.currentForm = CreatePreflightForm(m.config, m.formBuilder, m.system) |
| 121 | return m, m.currentForm.Init() | 123 | return m, m.currentForm.Init() | ||
| 122 | 124 | ||||
| 123 | case StateOptionsForm: | 125 | case StateOptionsForm: | ||