fix(boot): rebuild initramfs after Plymouth theme, mask NM-wait-online, add early KMS module
- commit
- b1298dd11f15126f188acbfe67735407d6ff67f4
- author
- brice · · 2026-02-27 18:42 UTC
- parent
- 058b81b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +44 -3
M internal/application/commands/install_bootloader.go +1 -0
| @@ -18,4 +18,5 @@ type InstallBootloaderCommand struct { | |||
| 18 | 18 | EFIPartition string // EFI partition device path | |
| 19 | 19 | TargetDisk string // Target disk device path | |
| 20 | 20 | KernelParamsExtra string // Additional kernel parameters | |
| 21 | + | GPUVendor string // "amd", "intel", "nvidia", "unknown" — used for early KMS module | |
| 21 | 22 | } | |
| @@ -18,4 +18,5 @@ type InstallBootloaderCommand struct { | |||||
| 18 | EFIPartition string // EFI partition device path | 18 | EFIPartition string // EFI partition device path | ||
| 19 | TargetDisk string // Target disk device path | 19 | TargetDisk string // Target disk device path | ||
| 20 | KernelParamsExtra string // Additional kernel parameters | 20 | KernelParamsExtra string // Additional kernel parameters | ||
| 21 | + | GPUVendor string // "amd", "intel", "nvidia", "unknown" — used for early KMS module | |||
| 21 | } | 22 | } | ||
M internal/application/handlers/bootloader_handler.go +31 -2
| @@ -63,7 +63,7 @@ func (h *BootloaderHandler) Handle(ctx context.Context, cmd commands.InstallBoot | |||
| 63 | 63 | return result, err | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | - | if err := h.configureMkinitcpio(ctx, cmd.MountPoint, cmd.EncryptionType); err != nil { | |
| 66 | + | if err := h.configureMkinitcpio(ctx, cmd.MountPoint, cmd.EncryptionType, cmd.GPUVendor); err != nil { | |
| 67 | 67 | result.ErrorDetail = err.Error() | |
| 68 | 68 | return result, err | |
| 69 | 69 | } | |
| @@ -91,7 +91,7 @@ func (h *BootloaderHandler) Handle(ctx context.Context, cmd commands.InstallBoot | |||
| 91 | 91 | return result, nil | |
| 92 | 92 | } | |
| 93 | 93 | ||
| 94 | - | func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint string, encType disk.EncryptionType) error { | |
| 94 | + | func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint string, encType disk.EncryptionType, gpuVendor string) error { | |
| 95 | 95 | confPath := filepath.Join(mountPoint, "etc", "mkinitcpio.conf") | |
| 96 | 96 | content, err := h.fs.ReadFile(confPath) | |
| 97 | 97 | if err != nil { | |
| @@ -105,6 +105,12 @@ func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint | |||
| 105 | 105 | } | |
| 106 | 106 | ||
| 107 | 107 | updated := replaceHooksLine(string(content), hooks) | |
| 108 | + | ||
| 109 | + | // Set the KMS module for early framebuffer so Plymouth loads cleanly. | |
| 110 | + | // Without this, the kms hook has nothing to load and Plymouth appears late. | |
| 111 | + | kmsModule := kmsModuleForGPU(gpuVendor) | |
| 112 | + | updated = replaceModulesLine(updated, kmsModule) | |
| 113 | + | ||
| 108 | 114 | if err := h.fs.WriteFile(confPath, []byte(updated), 0644); err != nil { | |
| 109 | 115 | h.logger.Error("Failed to write mkinitcpio.conf", "error", err) | |
| 110 | 116 | return fmt.Errorf("failed to write mkinitcpio.conf: %w", err) | |
| @@ -118,6 +124,29 @@ func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint | |||
| 118 | 124 | return nil | |
| 119 | 125 | } | |
| 120 | 126 | ||
| 127 | + | // kmsModuleForGPU returns the kernel module name required for early KMS on the given GPU vendor. | |
| 128 | + | // Returns an empty string for NVIDIA (uses proprietary driver, no early KMS) or unknown GPUs. | |
| 129 | + | func kmsModuleForGPU(vendor string) string { | |
| 130 | + | switch vendor { | |
| 131 | + | case "amd": | |
| 132 | + | return "amdgpu" | |
| 133 | + | case "intel": | |
| 134 | + | return "i915" | |
| 135 | + | default: | |
| 136 | + | return "" | |
| 137 | + | } | |
| 138 | + | } | |
| 139 | + | ||
| 140 | + | // replaceModulesLine replaces the MODULES=(...) line in mkinitcpio.conf. | |
| 141 | + | // If module is empty, MODULES=() is left unchanged (or set to empty). | |
| 142 | + | func replaceModulesLine(content, module string) string { | |
| 143 | + | re := regexp.MustCompile(`(?m)^MODULES=\(.*\)$`) | |
| 144 | + | if module == "" { | |
| 145 | + | return re.ReplaceAllString(content, "MODULES=()") | |
| 146 | + | } | |
| 147 | + | return re.ReplaceAllString(content, fmt.Sprintf("MODULES=(%s)", module)) | |
| 148 | + | } | |
| 149 | + | ||
| 121 | 150 | func (h *BootloaderHandler) installLimine(ctx context.Context, mountPoint string) error { | |
| 122 | 151 | limineDir := filepath.Join(mountPoint, "boot", "EFI", "limine") | |
| 123 | 152 | if err := h.fs.MkdirAll(limineDir, 0755); err != nil { | |
| @@ -63,7 +63,7 @@ func (h *BootloaderHandler) Handle(ctx context.Context, cmd commands.InstallBoot | |||||
| 63 | return result, err | 63 | return result, err | ||
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | - | if err := h.configureMkinitcpio(ctx, cmd.MountPoint, cmd.EncryptionType); err != nil { | 66 | + | if err := h.configureMkinitcpio(ctx, cmd.MountPoint, cmd.EncryptionType, cmd.GPUVendor); err != nil { |
| 67 | result.ErrorDetail = err.Error() | 67 | result.ErrorDetail = err.Error() | ||
| 68 | return result, err | 68 | return result, err | ||
| 69 | } | 69 | } | ||
| @@ -91,7 +91,7 @@ func (h *BootloaderHandler) Handle(ctx context.Context, cmd commands.InstallBoot | |||||
| 91 | return result, nil | 91 | return result, nil | ||
| 92 | } | 92 | } | ||
| 93 | 93 | ||||
| 94 | - | func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint string, encType disk.EncryptionType) error { | 94 | + | func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint string, encType disk.EncryptionType, gpuVendor string) error { |
| 95 | confPath := filepath.Join(mountPoint, "etc", "mkinitcpio.conf") | 95 | confPath := filepath.Join(mountPoint, "etc", "mkinitcpio.conf") | ||
| 96 | content, err := h.fs.ReadFile(confPath) | 96 | content, err := h.fs.ReadFile(confPath) | ||
| 97 | if err != nil { | 97 | if err != nil { | ||
| @@ -105,6 +105,12 @@ func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint | |||||
| 105 | } | 105 | } | ||
| 106 | 106 | ||||
| 107 | updated := replaceHooksLine(string(content), hooks) | 107 | updated := replaceHooksLine(string(content), hooks) | ||
| 108 | + | ||||
| 109 | + | // Set the KMS module for early framebuffer so Plymouth loads cleanly. | |||
| 110 | + | // Without this, the kms hook has nothing to load and Plymouth appears late. | |||
| 111 | + | kmsModule := kmsModuleForGPU(gpuVendor) | |||
| 112 | + | updated = replaceModulesLine(updated, kmsModule) | |||
| 113 | + | ||||
| 108 | if err := h.fs.WriteFile(confPath, []byte(updated), 0644); err != nil { | 114 | if err := h.fs.WriteFile(confPath, []byte(updated), 0644); err != nil { | ||
| 109 | h.logger.Error("Failed to write mkinitcpio.conf", "error", err) | 115 | h.logger.Error("Failed to write mkinitcpio.conf", "error", err) | ||
| 110 | return fmt.Errorf("failed to write mkinitcpio.conf: %w", err) | 116 | return fmt.Errorf("failed to write mkinitcpio.conf: %w", err) | ||
| @@ -118,6 +124,29 @@ func (h *BootloaderHandler) configureMkinitcpio(ctx context.Context, mountPoint | |||||
| 118 | return nil | 124 | return nil | ||
| 119 | } | 125 | } | ||
| 120 | 126 | ||||
| 127 | + | // kmsModuleForGPU returns the kernel module name required for early KMS on the given GPU vendor. | |||
| 128 | + | // Returns an empty string for NVIDIA (uses proprietary driver, no early KMS) or unknown GPUs. | |||
| 129 | + | func kmsModuleForGPU(vendor string) string { | |||
| 130 | + | switch vendor { | |||
| 131 | + | case "amd": | |||
| 132 | + | return "amdgpu" | |||
| 133 | + | case "intel": | |||
| 134 | + | return "i915" | |||
| 135 | + | default: | |||
| 136 | + | return "" | |||
| 137 | + | } | |||
| 138 | + | } | |||
| 139 | + | ||||
| 140 | + | // replaceModulesLine replaces the MODULES=(...) line in mkinitcpio.conf. | |||
| 141 | + | // If module is empty, MODULES=() is left unchanged (or set to empty). | |||
| 142 | + | func replaceModulesLine(content, module string) string { | |||
| 143 | + | re := regexp.MustCompile(`(?m)^MODULES=\(.*\)$`) | |||
| 144 | + | if module == "" { | |||
| 145 | + | return re.ReplaceAllString(content, "MODULES=()") | |||
| 146 | + | } | |||
| 147 | + | return re.ReplaceAllString(content, fmt.Sprintf("MODULES=(%s)", module)) | |||
| 148 | + | } | |||
| 149 | + | ||||
| 121 | func (h *BootloaderHandler) installLimine(ctx context.Context, mountPoint string) error { | 150 | func (h *BootloaderHandler) installLimine(ctx context.Context, mountPoint string) error { | ||
| 122 | limineDir := filepath.Join(mountPoint, "boot", "EFI", "limine") | 151 | limineDir := filepath.Join(mountPoint, "boot", "EFI", "limine") | ||
| 123 | if err := h.fs.MkdirAll(limineDir, 0755); err != nil { | 152 | if err := h.fs.MkdirAll(limineDir, 0755); err != nil { | ||
M internal/application/handlers/configure_system_handler.go +5 -0
| @@ -180,6 +180,11 @@ func (h *ConfigureSystemHandler) Handle(ctx context.Context, cmd commands.Config | |||
| 180 | 180 | return result, err | |
| 181 | 181 | } | |
| 182 | 182 | ||
| 183 | + | // Mask NetworkManager-wait-online to prevent boot delays when no network is immediately available. | |
| 184 | + | if err := h.chrExec.ChrootSystemctl(ctx, h.logger.LogPath(), cmd.MountPoint, "mask", "NetworkManager-wait-online.service"); err != nil { | |
| 185 | + | h.logger.Warn("Failed to mask NetworkManager-wait-online.service", "error", err) | |
| 186 | + | } | |
| 187 | + | ||
| 183 | 188 | // Migrate iwd WiFi credentials from live ISO to NetworkManager profiles in the installed system. | |
| 184 | 189 | if err := h.migrateIWDProfiles(cmd.MountPoint); err != nil { | |
| 185 | 190 | // Non-fatal: log and continue. User can configure WiFi manually after boot. | |
| @@ -180,6 +180,11 @@ func (h *ConfigureSystemHandler) Handle(ctx context.Context, cmd commands.Config | |||||
| 180 | return result, err | 180 | return result, err | ||
| 181 | } | 181 | } | ||
| 182 | 182 | ||||
| 183 | + | // Mask NetworkManager-wait-online to prevent boot delays when no network is immediately available. | |||
| 184 | + | if err := h.chrExec.ChrootSystemctl(ctx, h.logger.LogPath(), cmd.MountPoint, "mask", "NetworkManager-wait-online.service"); err != nil { | |||
| 185 | + | h.logger.Warn("Failed to mask NetworkManager-wait-online.service", "error", err) | |||
| 186 | + | } | |||
| 187 | + | ||||
| 183 | // Migrate iwd WiFi credentials from live ISO to NetworkManager profiles in the installed system. | 188 | // Migrate iwd WiFi credentials from live ISO to NetworkManager profiles in the installed system. | ||
| 184 | if err := h.migrateIWDProfiles(cmd.MountPoint); err != nil { | 189 | if err := h.migrateIWDProfiles(cmd.MountPoint); err != nil { | ||
| 185 | // Non-fatal: log and continue. User can configure WiFi manually after boot. | 190 | // Non-fatal: log and continue. User can configure WiFi manually after boot. | ||
M internal/application/handlers/post_install_handler.go +6 -1
| @@ -98,8 +98,13 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||
| 98 | 98 | return result, fmt.Errorf("failed to set Plymouth theme: %w", err) | |
| 99 | 99 | } | |
| 100 | 100 | ||
| 101 | + | // Rebuild initramfs so the Plymouth theme takes effect at boot | |
| 102 | + | if _, err := h.chrExec.ExecuteInChroot(ctx, cmd.MountPoint, "mkinitcpio", "-P"); err != nil { | |
| 103 | + | return result, fmt.Errorf("failed to rebuild initramfs after Plymouth theme: %w", err) | |
| 104 | + | } | |
| 105 | + | ||
| 101 | 106 | result.TasksRun = append(result.TasksRun, fmt.Sprintf("plymouth-theme-%s", cmd.PlymouthTheme)) | |
| 102 | - | h.logger.Info("Plymouth theme installed") | |
| 107 | + | h.logger.Info("Plymouth theme installed and initramfs rebuilt") | |
| 103 | 108 | } | |
| 104 | 109 | ||
| 105 | 110 | if err := h.installLimineLogo(cmd.MountPoint); err != nil { | |
| @@ -98,8 +98,13 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||||
| 98 | return result, fmt.Errorf("failed to set Plymouth theme: %w", err) | 98 | return result, fmt.Errorf("failed to set Plymouth theme: %w", err) | ||
| 99 | } | 99 | } | ||
| 100 | 100 | ||||
| 101 | + | // Rebuild initramfs so the Plymouth theme takes effect at boot | |||
| 102 | + | if _, err := h.chrExec.ExecuteInChroot(ctx, cmd.MountPoint, "mkinitcpio", "-P"); err != nil { | |||
| 103 | + | return result, fmt.Errorf("failed to rebuild initramfs after Plymouth theme: %w", err) | |||
| 104 | + | } | |||
| 105 | + | ||||
| 101 | result.TasksRun = append(result.TasksRun, fmt.Sprintf("plymouth-theme-%s", cmd.PlymouthTheme)) | 106 | result.TasksRun = append(result.TasksRun, fmt.Sprintf("plymouth-theme-%s", cmd.PlymouthTheme)) | ||
| 102 | - | h.logger.Info("Plymouth theme installed") | 107 | + | h.logger.Info("Plymouth theme installed and initramfs rebuilt") |
| 103 | } | 108 | } | ||
| 104 | 109 | ||||
| 105 | if err := h.installLimineLogo(cmd.MountPoint); err != nil { | 110 | if err := h.installLimineLogo(cmd.MountPoint); err != nil { | ||
M internal/interfaces/tui/handlers/install_handler.go +1 -0
| @@ -184,6 +184,7 @@ func CreateInstallationCommand(app AppContext, formData models.FormData) tea.Cmd | |||
| 184 | 184 | EFIPartition: partitionResult.EFIPartition, | |
| 185 | 185 | TargetDisk: formData.TargetDisk, | |
| 186 | 186 | KernelParamsExtra: formData.KernelParamsExtra, | |
| 187 | + | GPUVendor: formData.GPUVendor, | |
| 187 | 188 | } | |
| 188 | 189 | if _, err := svc.RunBootloaderSetup(ctx, bootCmd); err != nil { | |
| 189 | 190 | logger.Error("Bootloader setup failed", "error", err) | |
| @@ -184,6 +184,7 @@ func CreateInstallationCommand(app AppContext, formData models.FormData) tea.Cmd | |||||
| 184 | EFIPartition: partitionResult.EFIPartition, | 184 | EFIPartition: partitionResult.EFIPartition, | ||
| 185 | TargetDisk: formData.TargetDisk, | 185 | TargetDisk: formData.TargetDisk, | ||
| 186 | KernelParamsExtra: formData.KernelParamsExtra, | 186 | KernelParamsExtra: formData.KernelParamsExtra, | ||
| 187 | + | GPUVendor: formData.GPUVendor, | |||
| 187 | } | 188 | } | ||
| 188 | if _, err := svc.RunBootloaderSetup(ctx, bootCmd); err != nil { | 189 | if _, err := svc.RunBootloaderSetup(ctx, bootCmd); err != nil { | ||
| 189 | logger.Error("Bootloader setup failed", "error", err) | 190 | logger.Error("Bootloader setup failed", "error", err) | ||