detached 6495190

fix(base): inject cryptsetup for encrypted installs

commit
6495190c6f24238ed214e3b31a8fc47e2a0a3a63
author
brice · · 2026-02-27 05:25 UTC
parent
ef28457
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +61 -0
M internal/application/commands/install_base.go +1 -0
@@ -9,4 +9,5 @@ type InstallBaseCommand struct {
99 Packages []string // Additional packages to install (in addition to base)
1010 KernelVariant packages.KernelVariant // KernelStable, KernelZen, KernelLTS, KernelHardened, KernelCachyOS
1111 IncludeMicrocode bool // Whether to install CPU microcode
12+ Encrypted bool // true when disk encryption was chosen
1213 }
@@ -9,4 +9,5 @@ type InstallBaseCommand struct {
9 Packages []string // Additional packages to install (in addition to base)9 Packages []string // Additional packages to install (in addition to base)
10 KernelVariant packages.KernelVariant // KernelStable, KernelZen, KernelLTS, KernelHardened, KernelCachyOS10 KernelVariant packages.KernelVariant // KernelStable, KernelZen, KernelLTS, KernelHardened, KernelCachyOS
11 IncludeMicrocode bool // Whether to install CPU microcode11 IncludeMicrocode bool // Whether to install CPU microcode
12+ Encrypted bool // true when disk encryption was chosen
12 }13 }
M internal/application/handlers/install_base_handler.go +6 -0
@@ -68,6 +68,12 @@ func (h *InstallBaseHandler) Handle(ctx context.Context, cmd commands.InstallBas
6868 h.logger.Info("Adding CPU microcode packages")
6969 }
7070
71+ // Add cryptsetup for encrypted installs
72+ if cmd.Encrypted {
73+ basePackages = append(basePackages, "cryptsetup")
74+ h.logger.Info("Adding cryptsetup for encrypted install")
75+ }
76+
7177 // Add any additional packages from command
7278 if len(cmd.Packages) > 0 {
7379 basePackages = append(basePackages, cmd.Packages...)
@@ -68,6 +68,12 @@ func (h *InstallBaseHandler) Handle(ctx context.Context, cmd commands.InstallBas
68 h.logger.Info("Adding CPU microcode packages")68 h.logger.Info("Adding CPU microcode packages")
69 }69 }
70 70
71+ // Add cryptsetup for encrypted installs
72+ if cmd.Encrypted {
73+ basePackages = append(basePackages, "cryptsetup")
74+ h.logger.Info("Adding cryptsetup for encrypted install")
75+ }
76+
71 // Add any additional packages from command77 // Add any additional packages from command
72 if len(cmd.Packages) > 0 {78 if len(cmd.Packages) > 0 {
73 basePackages = append(basePackages, cmd.Packages...)79 basePackages = append(basePackages, cmd.Packages...)
M internal/application/handlers/install_base_handler_test.go +53 -0
@@ -10,6 +10,9 @@ import (
1010 "go.uber.org/mock/gomock"
1111 )
1212
13+// basePackagesContent is a minimal base.packages fixture used by all tests.
14+var basePackagesContent = []byte("base\nlinux-firmware\n")
15+
1316 func TestInstallBaseHandler_Handle_Success(t *testing.T) {
1417 ctrl := gomock.NewController(t)
1518 defer ctrl.Finish()
@@ -20,6 +23,7 @@ func TestInstallBaseHandler_Handle_Success(t *testing.T) {
2023 mockLogger := mocks.NewMockLogger(ctrl)
2124
2225 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
26+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
2327 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux-zen", "intel-ucode", "amd-ucode", "vim", "git").Return([]byte{}, nil).AnyTimes()
2428 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
2529 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
@@ -102,6 +106,7 @@ func TestInstallBaseHandler_Handle_WithCustomPackages(t *testing.T) {
102106 mockLogger := mocks.NewMockLogger(ctrl)
103107
104108 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
109+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
105110 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux", "neovim", "zsh", "tmux").Return([]byte{}, nil).AnyTimes()
106111 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
107112 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
@@ -141,3 +146,51 @@ func TestInstallBaseHandler_Handle_WithCustomPackages(t *testing.T) {
141146 }
142147 }
143148 }
149+
150+func TestInstallBaseHandler_Handle_WithEncryption_IncludesCryptsetup(t *testing.T) {
151+ ctrl := gomock.NewController(t)
152+ defer ctrl.Finish()
153+
154+ mockFS := mocks.NewMockFileSystem(ctrl)
155+ mockExec := mocks.NewMockCommandExecutor(ctrl)
156+ mockChrExec := mocks.NewMockChrootExecutor(ctrl)
157+ mockLogger := mocks.NewMockLogger(ctrl)
158+
159+ mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
160+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
161+ mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux", "cryptsetup").Return([]byte{}, nil).AnyTimes()
162+ mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
163+ mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
164+
165+ handler := NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
166+
167+ cmd := commands.InstallBaseCommand{
168+ TargetDisk: "/dev/sda",
169+ MountPoint: "/mnt",
170+ KernelVariant: packages.KernelStable,
171+ IncludeMicrocode: false,
172+ Encrypted: true,
173+ }
174+
175+ result, err := handler.Handle(context.Background(), cmd)
176+
177+ if err != nil {
178+ t.Fatalf("expected no error, got %v", err)
179+ }
180+
181+ if !result.Success {
182+ t.Error("expected success")
183+ }
184+
185+ // Assert cryptsetup is in PackagesInstalled
186+ found := false
187+ for _, pkg := range result.PackagesInstalled {
188+ if pkg == "cryptsetup" {
189+ found = true
190+ break
191+ }
192+ }
193+ if !found {
194+ t.Error("expected cryptsetup to be in PackagesInstalled for encrypted install")
195+ }
196+}
@@ -10,6 +10,9 @@ import (
10 "go.uber.org/mock/gomock"10 "go.uber.org/mock/gomock"
11 )11 )
12 12
13+// basePackagesContent is a minimal base.packages fixture used by all tests.
14+var basePackagesContent = []byte("base\nlinux-firmware\n")
15+
13 func TestInstallBaseHandler_Handle_Success(t *testing.T) {16 func TestInstallBaseHandler_Handle_Success(t *testing.T) {
14 ctrl := gomock.NewController(t)17 ctrl := gomock.NewController(t)
15 defer ctrl.Finish()18 defer ctrl.Finish()
@@ -20,6 +23,7 @@ func TestInstallBaseHandler_Handle_Success(t *testing.T) {
20 mockLogger := mocks.NewMockLogger(ctrl)23 mockLogger := mocks.NewMockLogger(ctrl)
21 24
22 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()25 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
26+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
23 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux-zen", "intel-ucode", "amd-ucode", "vim", "git").Return([]byte{}, nil).AnyTimes()27 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux-zen", "intel-ucode", "amd-ucode", "vim", "git").Return([]byte{}, nil).AnyTimes()
24 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()28 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
25 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()29 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
@@ -102,6 +106,7 @@ func TestInstallBaseHandler_Handle_WithCustomPackages(t *testing.T) {
102 mockLogger := mocks.NewMockLogger(ctrl)106 mockLogger := mocks.NewMockLogger(ctrl)
103 107
104 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()108 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
109+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
105 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux", "neovim", "zsh", "tmux").Return([]byte{}, nil).AnyTimes()110 mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux", "neovim", "zsh", "tmux").Return([]byte{}, nil).AnyTimes()
106 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()111 mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
107 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()112 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
@@ -141,3 +146,51 @@ func TestInstallBaseHandler_Handle_WithCustomPackages(t *testing.T) {
141 }146 }
142 }147 }
143 }148 }
149+
150+func TestInstallBaseHandler_Handle_WithEncryption_IncludesCryptsetup(t *testing.T) {
151+ ctrl := gomock.NewController(t)
152+ defer ctrl.Finish()
153+
154+ mockFS := mocks.NewMockFileSystem(ctrl)
155+ mockExec := mocks.NewMockCommandExecutor(ctrl)
156+ mockChrExec := mocks.NewMockChrootExecutor(ctrl)
157+ mockLogger := mocks.NewMockLogger(ctrl)
158+
159+ mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
160+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(basePackagesContent, nil).AnyTimes()
161+ mockExec.EXPECT().Execute(gomock.Any(), "pacstrap", "/mnt", "base", "linux-firmware", "linux", "cryptsetup").Return([]byte{}, nil).AnyTimes()
162+ mockExec.EXPECT().Execute(gomock.Any(), "genfstab", "-U", "/mnt").Return([]byte("# fstab"), nil).AnyTimes()
163+ mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
164+
165+ handler := NewInstallBaseHandler(mockFS, mockExec, mockChrExec, mockLogger)
166+
167+ cmd := commands.InstallBaseCommand{
168+ TargetDisk: "/dev/sda",
169+ MountPoint: "/mnt",
170+ KernelVariant: packages.KernelStable,
171+ IncludeMicrocode: false,
172+ Encrypted: true,
173+ }
174+
175+ result, err := handler.Handle(context.Background(), cmd)
176+
177+ if err != nil {
178+ t.Fatalf("expected no error, got %v", err)
179+ }
180+
181+ if !result.Success {
182+ t.Error("expected success")
183+ }
184+
185+ // Assert cryptsetup is in PackagesInstalled
186+ found := false
187+ for _, pkg := range result.PackagesInstalled {
188+ if pkg == "cryptsetup" {
189+ found = true
190+ break
191+ }
192+ }
193+ if !found {
194+ t.Error("expected cryptsetup to be in PackagesInstalled for encrypted install")
195+ }
196+}
M internal/interfaces/tui/handlers/install_handler.go +1 -0
@@ -141,6 +141,7 @@ func CreateInstallationCommand(app AppContext, formData models.FormData) tea.Cmd
141141 MountPoint: "/mnt",
142142 KernelVariant: parseKernelVariant(formData.KernelVariant),
143143 IncludeMicrocode: formData.Microcode,
144+ Encrypted: parseEncryptionType(formData.EncryptionType) != disk.EncryptionTypeNone,
144145 }
145146 if _, err := svc.RunBaseInstall(ctx, baseCmd); err != nil {
146147 logger.Error("Base installation failed", "error", err)
@@ -141,6 +141,7 @@ func CreateInstallationCommand(app AppContext, formData models.FormData) tea.Cmd
141 MountPoint: "/mnt",141 MountPoint: "/mnt",
142 KernelVariant: parseKernelVariant(formData.KernelVariant),142 KernelVariant: parseKernelVariant(formData.KernelVariant),
143 IncludeMicrocode: formData.Microcode,143 IncludeMicrocode: formData.Microcode,
144+ Encrypted: parseEncryptionType(formData.EncryptionType) != disk.EncryptionTypeNone,
144 }145 }
145 if _, err := svc.RunBaseInstall(ctx, baseCmd); err != nil {146 if _, err := svc.RunBaseInstall(ctx, baseCmd); err != nil {
146 logger.Error("Base installation failed", "error", err)147 logger.Error("Base installation failed", "error", err)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close