feat(post-install): tune pacman.conf with Color, ParallelDownloads, ILoveCandy
- commit
- 5e43feca70aadc38cab33748587b1175945c5e10
- author
- brice · · 2026-02-27 05:29 UTC
- parent
- 5214853
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +87 -0
M internal/application/handlers/post_install_handler.go +21 -0
| @@ -100,6 +100,10 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||
| 100 | 100 | h.logger.Warn("Failed to install Limine logo", "error", err) | |
| 101 | 101 | } | |
| 102 | 102 | ||
| 103 | + | if err := h.tunePacmanConfig(cmd.MountPoint); err != nil { | |
| 104 | + | h.logger.Warn("Failed to tune pacman.conf", "error", err) | |
| 105 | + | } | |
| 106 | + | ||
| 103 | 107 | // Final cleanup and verification | |
| 104 | 108 | h.logger.Info("Post-installation tasks completed") | |
| 105 | 109 | result.Success = true | |
| @@ -226,6 +230,23 @@ func (h *PostInstallHandler) writeDankLinuxFlag(mountPoint string) error { | |||
| 226 | 230 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | |
| 227 | 231 | } | |
| 228 | 232 | ||
| 233 | + | func (h *PostInstallHandler) tunePacmanConfig(mountPoint string) error { | |
| 234 | + | confPath := filepath.Join(mountPoint, "etc", "pacman.conf") | |
| 235 | + | content, err := h.fs.ReadFile(confPath) | |
| 236 | + | if err != nil { | |
| 237 | + | return fmt.Errorf("failed to read pacman.conf: %w", err) | |
| 238 | + | } | |
| 239 | + | conf := string(content) | |
| 240 | + | conf = enablePacmanOption(conf, "#Color", "Color") | |
| 241 | + | conf = enablePacmanOption(conf, "#ParallelDownloads = 5", "ParallelDownloads = 5") | |
| 242 | + | conf = enablePacmanOption(conf, "#ILoveCandy", "ILoveCandy") | |
| 243 | + | return h.fs.WriteFile(confPath, []byte(conf), 0644) | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | func enablePacmanOption(conf, commented, replacement string) string { | |
| 247 | + | return strings.ReplaceAll(conf, commented, replacement) | |
| 248 | + | } | |
| 249 | + | ||
| 229 | 250 | func (h *PostInstallHandler) downloadTemplate(path string) ([]byte, error) { | |
| 230 | 251 | url := fmt.Sprintf("%s/%s", h.rawURL, path) | |
| 231 | 252 | h.logger.Info("Downloading template", "url", url) | |
| @@ -100,6 +100,10 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||||
| 100 | h.logger.Warn("Failed to install Limine logo", "error", err) | 100 | h.logger.Warn("Failed to install Limine logo", "error", err) | ||
| 101 | } | 101 | } | ||
| 102 | 102 | ||||
| 103 | + | if err := h.tunePacmanConfig(cmd.MountPoint); err != nil { | |||
| 104 | + | h.logger.Warn("Failed to tune pacman.conf", "error", err) | |||
| 105 | + | } | |||
| 106 | + | ||||
| 103 | // Final cleanup and verification | 107 | // Final cleanup and verification | ||
| 104 | h.logger.Info("Post-installation tasks completed") | 108 | h.logger.Info("Post-installation tasks completed") | ||
| 105 | result.Success = true | 109 | result.Success = true | ||
| @@ -226,6 +230,23 @@ func (h *PostInstallHandler) writeDankLinuxFlag(mountPoint string) error { | |||||
| 226 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | 230 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | ||
| 227 | } | 231 | } | ||
| 228 | 232 | ||||
| 233 | + | func (h *PostInstallHandler) tunePacmanConfig(mountPoint string) error { | |||
| 234 | + | confPath := filepath.Join(mountPoint, "etc", "pacman.conf") | |||
| 235 | + | content, err := h.fs.ReadFile(confPath) | |||
| 236 | + | if err != nil { | |||
| 237 | + | return fmt.Errorf("failed to read pacman.conf: %w", err) | |||
| 238 | + | } | |||
| 239 | + | conf := string(content) | |||
| 240 | + | conf = enablePacmanOption(conf, "#Color", "Color") | |||
| 241 | + | conf = enablePacmanOption(conf, "#ParallelDownloads = 5", "ParallelDownloads = 5") | |||
| 242 | + | conf = enablePacmanOption(conf, "#ILoveCandy", "ILoveCandy") | |||
| 243 | + | return h.fs.WriteFile(confPath, []byte(conf), 0644) | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | func enablePacmanOption(conf, commented, replacement string) string { | |||
| 247 | + | return strings.ReplaceAll(conf, commented, replacement) | |||
| 248 | + | } | |||
| 249 | + | ||||
| 229 | func (h *PostInstallHandler) downloadTemplate(path string) ([]byte, error) { | 250 | func (h *PostInstallHandler) downloadTemplate(path string) ([]byte, error) { | ||
| 230 | url := fmt.Sprintf("%s/%s", h.rawURL, path) | 251 | url := fmt.Sprintf("%s/%s", h.rawURL, path) | ||
| 231 | h.logger.Info("Downloading template", "url", url) | 252 | h.logger.Info("Downloading template", "url", url) | ||
M internal/application/handlers/post_install_handler_test.go +66 -0
| @@ -3,6 +3,7 @@ package handlers | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "net/http" | |
| 6 | + | "strings" | |
| 6 | 7 | "testing" | |
| 7 | 8 | ||
| 8 | 9 | "github.com/bnema/archup/internal/application/commands" | |
| @@ -212,6 +213,71 @@ func TestPostInstallHandler_Handle_WithDankLinux(t *testing.T) { | |||
| 212 | 213 | } | |
| 213 | 214 | } | |
| 214 | 215 | ||
| 216 | + | func TestPostInstallHandler_Handle_TunesPacman(t *testing.T) { | |
| 217 | + | ctrl := gomock.NewController(t) | |
| 218 | + | defer ctrl.Finish() | |
| 219 | + | ||
| 220 | + | mockFS := mocks.NewMockFileSystem(ctrl) | |
| 221 | + | mockHTTP := mocks.NewMockHTTPClient(ctrl) | |
| 222 | + | mockChrExec := mocks.NewMockChrootExecutor(ctrl) | |
| 223 | + | mockScriptExec := mocks.NewMockScriptExecutor(ctrl) | |
| 224 | + | mockLogger := mocks.NewMockLogger(ctrl) | |
| 225 | + | ||
| 226 | + | pacmanContent := "#Color\n#ParallelDownloads = 5\n#ILoveCandy\n" | |
| 227 | + | ||
| 228 | + | mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes() | |
| 229 | + | mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes() | |
| 230 | + | mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes() | |
| 231 | + | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | |
| 232 | + | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 233 | + | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |
| 234 | + | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 235 | + | ||
| 236 | + | // ReadFile for pacman.conf returns commented options; other reads return generic content | |
| 237 | + | mockFS.EXPECT().ReadFile(gomock.Eq("/mnt/etc/pacman.conf")).Return([]byte(pacmanContent), nil).AnyTimes() | |
| 238 | + | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | |
| 239 | + | ||
| 240 | + | // Capture the WriteFile call for pacman.conf and assert content | |
| 241 | + | mockFS.EXPECT().WriteFile(gomock.Eq("/mnt/etc/pacman.conf"), gomock.Any(), gomock.Any()).DoAndReturn( | |
| 242 | + | func(path string, data []byte, perm interface{}) error { | |
| 243 | + | content := string(data) | |
| 244 | + | if !strings.Contains(content, "Color") { | |
| 245 | + | t.Errorf("expected tuned pacman.conf to contain 'Color', got: %s", content) | |
| 246 | + | } | |
| 247 | + | if strings.Contains(content, "#Color") { | |
| 248 | + | t.Errorf("expected '#Color' to be uncommented in pacman.conf") | |
| 249 | + | } | |
| 250 | + | if !strings.Contains(content, "ParallelDownloads = 5") { | |
| 251 | + | t.Errorf("expected tuned pacman.conf to contain 'ParallelDownloads = 5'") | |
| 252 | + | } | |
| 253 | + | if !strings.Contains(content, "ILoveCandy") { | |
| 254 | + | t.Errorf("expected tuned pacman.conf to contain 'ILoveCandy'") | |
| 255 | + | } | |
| 256 | + | return nil | |
| 257 | + | }, | |
| 258 | + | ).AnyTimes() | |
| 259 | + | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 260 | + | ||
| 261 | + | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |
| 262 | + | ||
| 263 | + | cmd := commands.PostInstallCommand{ | |
| 264 | + | MountPoint: "/mnt", | |
| 265 | + | Username: "testuser", | |
| 266 | + | RunPostBootScripts: false, | |
| 267 | + | PlymouthTheme: "", | |
| 268 | + | } | |
| 269 | + | ||
| 270 | + | result, err := handler.Handle(context.Background(), cmd) | |
| 271 | + | ||
| 272 | + | if err != nil { | |
| 273 | + | t.Fatalf("expected no error, got %v", err) | |
| 274 | + | } | |
| 275 | + | ||
| 276 | + | if !result.Success { | |
| 277 | + | t.Error("expected success") | |
| 278 | + | } | |
| 279 | + | } | |
| 280 | + | ||
| 215 | 281 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | |
| 216 | 282 | ctrl := gomock.NewController(t) | |
| 217 | 283 | defer ctrl.Finish() | |
| @@ -3,6 +3,7 @@ package handlers | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "net/http" | 5 | "net/http" | ||
| 6 | + | "strings" | |||
| 6 | "testing" | 7 | "testing" | ||
| 7 | 8 | ||||
| 8 | "github.com/bnema/archup/internal/application/commands" | 9 | "github.com/bnema/archup/internal/application/commands" | ||
| @@ -212,6 +213,71 @@ func TestPostInstallHandler_Handle_WithDankLinux(t *testing.T) { | |||||
| 212 | } | 213 | } | ||
| 213 | } | 214 | } | ||
| 214 | 215 | ||||
| 216 | + | func TestPostInstallHandler_Handle_TunesPacman(t *testing.T) { | |||
| 217 | + | ctrl := gomock.NewController(t) | |||
| 218 | + | defer ctrl.Finish() | |||
| 219 | + | ||||
| 220 | + | mockFS := mocks.NewMockFileSystem(ctrl) | |||
| 221 | + | mockHTTP := mocks.NewMockHTTPClient(ctrl) | |||
| 222 | + | mockChrExec := mocks.NewMockChrootExecutor(ctrl) | |||
| 223 | + | mockScriptExec := mocks.NewMockScriptExecutor(ctrl) | |||
| 224 | + | mockLogger := mocks.NewMockLogger(ctrl) | |||
| 225 | + | ||||
| 226 | + | pacmanContent := "#Color\n#ParallelDownloads = 5\n#ILoveCandy\n" | |||
| 227 | + | ||||
| 228 | + | mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes() | |||
| 229 | + | mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes() | |||
| 230 | + | mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes() | |||
| 231 | + | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | |||
| 232 | + | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 233 | + | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |||
| 234 | + | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 235 | + | ||||
| 236 | + | // ReadFile for pacman.conf returns commented options; other reads return generic content | |||
| 237 | + | mockFS.EXPECT().ReadFile(gomock.Eq("/mnt/etc/pacman.conf")).Return([]byte(pacmanContent), nil).AnyTimes() | |||
| 238 | + | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | |||
| 239 | + | ||||
| 240 | + | // Capture the WriteFile call for pacman.conf and assert content | |||
| 241 | + | mockFS.EXPECT().WriteFile(gomock.Eq("/mnt/etc/pacman.conf"), gomock.Any(), gomock.Any()).DoAndReturn( | |||
| 242 | + | func(path string, data []byte, perm interface{}) error { | |||
| 243 | + | content := string(data) | |||
| 244 | + | if !strings.Contains(content, "Color") { | |||
| 245 | + | t.Errorf("expected tuned pacman.conf to contain 'Color', got: %s", content) | |||
| 246 | + | } | |||
| 247 | + | if strings.Contains(content, "#Color") { | |||
| 248 | + | t.Errorf("expected '#Color' to be uncommented in pacman.conf") | |||
| 249 | + | } | |||
| 250 | + | if !strings.Contains(content, "ParallelDownloads = 5") { | |||
| 251 | + | t.Errorf("expected tuned pacman.conf to contain 'ParallelDownloads = 5'") | |||
| 252 | + | } | |||
| 253 | + | if !strings.Contains(content, "ILoveCandy") { | |||
| 254 | + | t.Errorf("expected tuned pacman.conf to contain 'ILoveCandy'") | |||
| 255 | + | } | |||
| 256 | + | return nil | |||
| 257 | + | }, | |||
| 258 | + | ).AnyTimes() | |||
| 259 | + | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 260 | + | ||||
| 261 | + | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |||
| 262 | + | ||||
| 263 | + | cmd := commands.PostInstallCommand{ | |||
| 264 | + | MountPoint: "/mnt", | |||
| 265 | + | Username: "testuser", | |||
| 266 | + | RunPostBootScripts: false, | |||
| 267 | + | PlymouthTheme: "", | |||
| 268 | + | } | |||
| 269 | + | ||||
| 270 | + | result, err := handler.Handle(context.Background(), cmd) | |||
| 271 | + | ||||
| 272 | + | if err != nil { | |||
| 273 | + | t.Fatalf("expected no error, got %v", err) | |||
| 274 | + | } | |||
| 275 | + | ||||
| 276 | + | if !result.Success { | |||
| 277 | + | t.Error("expected success") | |||
| 278 | + | } | |||
| 279 | + | } | |||
| 280 | + | ||||
| 215 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | 281 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | ||
| 216 | ctrl := gomock.NewController(t) | 282 | ctrl := gomock.NewController(t) | ||
| 217 | defer ctrl.Finish() | 283 | defer ctrl.Finish() | ||