feat(post-install): install and enable limine-snapper-sync
- commit
- 1befd70a866f741f480b8c5a4480d30b016c3510
- author
- brice · · 2026-02-27 05:33 UTC
- parent
- 9012e23
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +90 -0
M internal/application/handlers/post_install_handler.go +15 -0
| @@ -69,6 +69,11 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||
| 69 | 69 | h.logger.Info("Dank Linux flag file written") | |
| 70 | 70 | } | |
| 71 | 71 | ||
| 72 | + | // Setup limine-snapper-sync for btrfs snapshot bootability | |
| 73 | + | if err := h.setupSnapperSync(ctx, cmd.MountPoint); err != nil { | |
| 74 | + | h.logger.Warn("Failed to setup limine-snapper-sync", "error", err) | |
| 75 | + | } | |
| 76 | + | ||
| 72 | 77 | // Install Plymouth theme if specified | |
| 73 | 78 | if cmd.PlymouthTheme != "" { | |
| 74 | 79 | h.logger.Info("Installing Plymouth theme", "theme", cmd.PlymouthTheme) | |
| @@ -234,6 +239,16 @@ func (h *PostInstallHandler) writeDankLinuxFlag(mountPoint string) error { | |||
| 234 | 239 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | |
| 235 | 240 | } | |
| 236 | 241 | ||
| 242 | + | func (h *PostInstallHandler) setupSnapperSync(ctx context.Context, mountPoint string) error { | |
| 243 | + | if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman", "-S", "--noconfirm", "--needed", "limine-snapper-sync"); err != nil { | |
| 244 | + | return fmt.Errorf("failed to install limine-snapper-sync: %w", err) | |
| 245 | + | } | |
| 246 | + | if err := h.chrExec.ChrootSystemctl(ctx, h.logger.LogPath(), mountPoint, "enable", "limine-snapper-sync.timer"); err != nil { | |
| 247 | + | return fmt.Errorf("failed to enable limine-snapper-sync.timer: %w", err) | |
| 248 | + | } | |
| 249 | + | return nil | |
| 250 | + | } | |
| 251 | + | ||
| 237 | 252 | func (h *PostInstallHandler) installLimineHook(mountPoint, targetDisk string) error { | |
| 238 | 253 | hooksDir := filepath.Join(mountPoint, "etc", "pacman.d", "hooks") | |
| 239 | 254 | if err := h.fs.MkdirAll(hooksDir, 0755); err != nil { | |
| @@ -69,6 +69,11 @@ func (h *PostInstallHandler) Handle(ctx context.Context, cmd commands.PostInstal | |||||
| 69 | h.logger.Info("Dank Linux flag file written") | 69 | h.logger.Info("Dank Linux flag file written") | ||
| 70 | } | 70 | } | ||
| 71 | 71 | ||||
| 72 | + | // Setup limine-snapper-sync for btrfs snapshot bootability | |||
| 73 | + | if err := h.setupSnapperSync(ctx, cmd.MountPoint); err != nil { | |||
| 74 | + | h.logger.Warn("Failed to setup limine-snapper-sync", "error", err) | |||
| 75 | + | } | |||
| 76 | + | ||||
| 72 | // Install Plymouth theme if specified | 77 | // Install Plymouth theme if specified | ||
| 73 | if cmd.PlymouthTheme != "" { | 78 | if cmd.PlymouthTheme != "" { | ||
| 74 | h.logger.Info("Installing Plymouth theme", "theme", cmd.PlymouthTheme) | 79 | h.logger.Info("Installing Plymouth theme", "theme", cmd.PlymouthTheme) | ||
| @@ -234,6 +239,16 @@ func (h *PostInstallHandler) writeDankLinuxFlag(mountPoint string) error { | |||||
| 234 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | 239 | return h.fs.WriteFile(flagPath, []byte(""), 0644) | ||
| 235 | } | 240 | } | ||
| 236 | 241 | ||||
| 242 | + | func (h *PostInstallHandler) setupSnapperSync(ctx context.Context, mountPoint string) error { | |||
| 243 | + | if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman", "-S", "--noconfirm", "--needed", "limine-snapper-sync"); err != nil { | |||
| 244 | + | return fmt.Errorf("failed to install limine-snapper-sync: %w", err) | |||
| 245 | + | } | |||
| 246 | + | if err := h.chrExec.ChrootSystemctl(ctx, h.logger.LogPath(), mountPoint, "enable", "limine-snapper-sync.timer"); err != nil { | |||
| 247 | + | return fmt.Errorf("failed to enable limine-snapper-sync.timer: %w", err) | |||
| 248 | + | } | |||
| 249 | + | return nil | |||
| 250 | + | } | |||
| 251 | + | ||||
| 237 | func (h *PostInstallHandler) installLimineHook(mountPoint, targetDisk string) error { | 252 | func (h *PostInstallHandler) installLimineHook(mountPoint, targetDisk string) error { | ||
| 238 | hooksDir := filepath.Join(mountPoint, "etc", "pacman.d", "hooks") | 253 | hooksDir := filepath.Join(mountPoint, "etc", "pacman.d", "hooks") | ||
| 239 | if err := h.fs.MkdirAll(hooksDir, 0755); err != nil { | 254 | if err := h.fs.MkdirAll(hooksDir, 0755); err != nil { | ||
M internal/application/handlers/post_install_handler_test.go +75 -0
| @@ -38,6 +38,7 @@ func TestPostInstallHandler_Handle_NoScripts(t *testing.T) { | |||
| 38 | 38 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 39 | 39 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 40 | 40 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |
| 41 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |
| 41 | 42 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 42 | 43 | ||
| 43 | 44 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |
| @@ -182,6 +183,7 @@ func TestPostInstallHandler_Handle_WithDankLinux(t *testing.T) { | |||
| 182 | 183 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 183 | 184 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 184 | 185 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |
| 186 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |
| 185 | 187 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 186 | 188 | ||
| 187 | 189 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |
| @@ -231,6 +233,7 @@ func TestPostInstallHandler_Handle_TunesPacman(t *testing.T) { | |||
| 231 | 233 | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | |
| 232 | 234 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 233 | 235 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |
| 236 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |
| 234 | 237 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 235 | 238 | ||
| 236 | 239 | // ReadFile for pacman.conf returns commented options; other reads return generic content | |
| @@ -300,6 +303,7 @@ func TestPostInstallHandler_Handle_InstallsLimineHook(t *testing.T) { | |||
| 300 | 303 | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | |
| 301 | 304 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 302 | 305 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte(hookTemplate)), nil).AnyTimes() | |
| 306 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |
| 303 | 307 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 304 | 308 | ||
| 305 | 309 | hookWritten := false | |
| @@ -343,6 +347,77 @@ func TestPostInstallHandler_Handle_InstallsLimineHook(t *testing.T) { | |||
| 343 | 347 | } | |
| 344 | 348 | } | |
| 345 | 349 | ||
| 350 | + | func TestPostInstallHandler_Handle_EnablesSnapperSync(t *testing.T) { | |
| 351 | + | ctrl := gomock.NewController(t) | |
| 352 | + | defer ctrl.Finish() | |
| 353 | + | ||
| 354 | + | mockFS := mocks.NewMockFileSystem(ctrl) | |
| 355 | + | mockHTTP := mocks.NewMockHTTPClient(ctrl) | |
| 356 | + | mockChrExec := mocks.NewMockChrootExecutor(ctrl) | |
| 357 | + | mockScriptExec := mocks.NewMockScriptExecutor(ctrl) | |
| 358 | + | mockLogger := mocks.NewMockLogger(ctrl) | |
| 359 | + | ||
| 360 | + | pacmanInstalled := false | |
| 361 | + | snapperTimerEnabled := false | |
| 362 | + | ||
| 363 | + | mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes() | |
| 364 | + | mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes() | |
| 365 | + | mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes() | |
| 366 | + | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | |
| 367 | + | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | |
| 368 | + | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 369 | + | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 370 | + | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |
| 371 | + | ||
| 372 | + | // Track pacman install of limine-snapper-sync (specific expectation first) | |
| 373 | + | mockChrExec.EXPECT().ExecuteInChroot( | |
| 374 | + | gomock.Any(), gomock.Eq("/mnt"), gomock.Eq("pacman"), | |
| 375 | + | gomock.Eq("-S"), gomock.Eq("--noconfirm"), gomock.Eq("--needed"), gomock.Eq("limine-snapper-sync"), | |
| 376 | + | ).DoAndReturn(func(ctx context.Context, mountPoint, command string, args ...string) ([]byte, error) { | |
| 377 | + | pacmanInstalled = true | |
| 378 | + | return []byte{}, nil | |
| 379 | + | }).AnyTimes() | |
| 380 | + | // Catch-all for any other ExecuteInChroot calls | |
| 381 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |
| 382 | + | ||
| 383 | + | // Track systemctl enable for limine-snapper-sync.timer (specific expectation first) | |
| 384 | + | mockChrExec.EXPECT().ChrootSystemctl( | |
| 385 | + | gomock.Any(), gomock.Any(), gomock.Eq("/mnt"), gomock.Eq("enable"), gomock.Eq("limine-snapper-sync.timer"), | |
| 386 | + | ).DoAndReturn(func(ctx context.Context, logPath, chrootPath string, args ...string) error { | |
| 387 | + | snapperTimerEnabled = true | |
| 388 | + | return nil | |
| 389 | + | }).AnyTimes() | |
| 390 | + | // Catch-all for any other ChrootSystemctl calls | |
| 391 | + | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |
| 392 | + | ||
| 393 | + | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |
| 394 | + | ||
| 395 | + | cmd := commands.PostInstallCommand{ | |
| 396 | + | MountPoint: "/mnt", | |
| 397 | + | Username: "testuser", | |
| 398 | + | RunPostBootScripts: false, | |
| 399 | + | PlymouthTheme: "", | |
| 400 | + | } | |
| 401 | + | ||
| 402 | + | result, err := handler.Handle(context.Background(), cmd) | |
| 403 | + | ||
| 404 | + | if err != nil { | |
| 405 | + | t.Fatalf("expected no error, got %v", err) | |
| 406 | + | } | |
| 407 | + | ||
| 408 | + | if !result.Success { | |
| 409 | + | t.Error("expected success") | |
| 410 | + | } | |
| 411 | + | ||
| 412 | + | if !pacmanInstalled { | |
| 413 | + | t.Error("expected pacman to install limine-snapper-sync") | |
| 414 | + | } | |
| 415 | + | ||
| 416 | + | if !snapperTimerEnabled { | |
| 417 | + | t.Error("expected limine-snapper-sync.timer to be enabled via systemctl") | |
| 418 | + | } | |
| 419 | + | } | |
| 420 | + | ||
| 346 | 421 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | |
| 347 | 422 | ctrl := gomock.NewController(t) | |
| 348 | 423 | defer ctrl.Finish() | |
| @@ -38,6 +38,7 @@ func TestPostInstallHandler_Handle_NoScripts(t *testing.T) { | |||||
| 38 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 38 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 39 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 39 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 40 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | 40 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | ||
| 41 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |||
| 41 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 42 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 42 | 43 | ||||
| 43 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | 44 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | ||
| @@ -182,6 +183,7 @@ func TestPostInstallHandler_Handle_WithDankLinux(t *testing.T) { | |||||
| 182 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 183 | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 183 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 184 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 184 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | 185 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | ||
| 186 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |||
| 185 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 187 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 186 | 188 | ||||
| 187 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | 189 | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | ||
| @@ -231,6 +233,7 @@ func TestPostInstallHandler_Handle_TunesPacman(t *testing.T) { | |||||
| 231 | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | 233 | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | ||
| 232 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 234 | 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() | 235 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | ||
| 236 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |||
| 234 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 237 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 235 | 238 | ||||
| 236 | // ReadFile for pacman.conf returns commented options; other reads return generic content | 239 | // ReadFile for pacman.conf returns commented options; other reads return generic content | ||
| @@ -300,6 +303,7 @@ func TestPostInstallHandler_Handle_InstallsLimineHook(t *testing.T) { | |||||
| 300 | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | 303 | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | ||
| 301 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 304 | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 302 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte(hookTemplate)), nil).AnyTimes() | 305 | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte(hookTemplate)), nil).AnyTimes() | ||
| 306 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |||
| 303 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | 307 | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | ||
| 304 | 308 | ||||
| 305 | hookWritten := false | 309 | hookWritten := false | ||
| @@ -343,6 +347,77 @@ func TestPostInstallHandler_Handle_InstallsLimineHook(t *testing.T) { | |||||
| 343 | } | 347 | } | ||
| 344 | } | 348 | } | ||
| 345 | 349 | ||||
| 350 | + | func TestPostInstallHandler_Handle_EnablesSnapperSync(t *testing.T) { | |||
| 351 | + | ctrl := gomock.NewController(t) | |||
| 352 | + | defer ctrl.Finish() | |||
| 353 | + | ||||
| 354 | + | mockFS := mocks.NewMockFileSystem(ctrl) | |||
| 355 | + | mockHTTP := mocks.NewMockHTTPClient(ctrl) | |||
| 356 | + | mockChrExec := mocks.NewMockChrootExecutor(ctrl) | |||
| 357 | + | mockScriptExec := mocks.NewMockScriptExecutor(ctrl) | |||
| 358 | + | mockLogger := mocks.NewMockLogger(ctrl) | |||
| 359 | + | ||||
| 360 | + | pacmanInstalled := false | |||
| 361 | + | snapperTimerEnabled := false | |||
| 362 | + | ||||
| 363 | + | mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes() | |||
| 364 | + | mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes() | |||
| 365 | + | mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes() | |||
| 366 | + | mockFS.EXPECT().Exists(gomock.Any()).Return(false, nil).AnyTimes() | |||
| 367 | + | mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("graphics: yes"), nil).AnyTimes() | |||
| 368 | + | mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 369 | + | mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 370 | + | mockHTTP.EXPECT().Get(gomock.Any()).Return(newMockResponse(ctrl, http.StatusOK, []byte("content")), nil).AnyTimes() | |||
| 371 | + | ||||
| 372 | + | // Track pacman install of limine-snapper-sync (specific expectation first) | |||
| 373 | + | mockChrExec.EXPECT().ExecuteInChroot( | |||
| 374 | + | gomock.Any(), gomock.Eq("/mnt"), gomock.Eq("pacman"), | |||
| 375 | + | gomock.Eq("-S"), gomock.Eq("--noconfirm"), gomock.Eq("--needed"), gomock.Eq("limine-snapper-sync"), | |||
| 376 | + | ).DoAndReturn(func(ctx context.Context, mountPoint, command string, args ...string) ([]byte, error) { | |||
| 377 | + | pacmanInstalled = true | |||
| 378 | + | return []byte{}, nil | |||
| 379 | + | }).AnyTimes() | |||
| 380 | + | // Catch-all for any other ExecuteInChroot calls | |||
| 381 | + | mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes() | |||
| 382 | + | ||||
| 383 | + | // Track systemctl enable for limine-snapper-sync.timer (specific expectation first) | |||
| 384 | + | mockChrExec.EXPECT().ChrootSystemctl( | |||
| 385 | + | gomock.Any(), gomock.Any(), gomock.Eq("/mnt"), gomock.Eq("enable"), gomock.Eq("limine-snapper-sync.timer"), | |||
| 386 | + | ).DoAndReturn(func(ctx context.Context, logPath, chrootPath string, args ...string) error { | |||
| 387 | + | snapperTimerEnabled = true | |||
| 388 | + | return nil | |||
| 389 | + | }).AnyTimes() | |||
| 390 | + | // Catch-all for any other ChrootSystemctl calls | |||
| 391 | + | mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() | |||
| 392 | + | ||||
| 393 | + | handler := NewPostInstallHandler(mockFS, mockHTTP, mockChrExec, mockScriptExec, mockLogger, "https://raw.githubusercontent.com/bnema/archup/dev") | |||
| 394 | + | ||||
| 395 | + | cmd := commands.PostInstallCommand{ | |||
| 396 | + | MountPoint: "/mnt", | |||
| 397 | + | Username: "testuser", | |||
| 398 | + | RunPostBootScripts: false, | |||
| 399 | + | PlymouthTheme: "", | |||
| 400 | + | } | |||
| 401 | + | ||||
| 402 | + | result, err := handler.Handle(context.Background(), cmd) | |||
| 403 | + | ||||
| 404 | + | if err != nil { | |||
| 405 | + | t.Fatalf("expected no error, got %v", err) | |||
| 406 | + | } | |||
| 407 | + | ||||
| 408 | + | if !result.Success { | |||
| 409 | + | t.Error("expected success") | |||
| 410 | + | } | |||
| 411 | + | ||||
| 412 | + | if !pacmanInstalled { | |||
| 413 | + | t.Error("expected pacman to install limine-snapper-sync") | |||
| 414 | + | } | |||
| 415 | + | ||||
| 416 | + | if !snapperTimerEnabled { | |||
| 417 | + | t.Error("expected limine-snapper-sync.timer to be enabled via systemctl") | |||
| 418 | + | } | |||
| 419 | + | } | |||
| 420 | + | ||||
| 346 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | 421 | func TestPostInstallHandler_Handle_Everything(t *testing.T) { | ||
| 347 | ctrl := gomock.NewController(t) | 422 | ctrl := gomock.NewController(t) | ||
| 348 | defer ctrl.Finish() | 423 | defer ctrl.Finish() | ||