detached b39b9c4

fix(cachyos): add pacman-key --init and MkdirAll before host/chroot CachyOS repo setup

commit
b39b9c4754e5ec9fbd0f0d5152f8067ef75a4a2a
author
brice · · 2026-02-27 18:20 UTC
parent
ea4c673
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +79 -44
M internal/application/handlers/bootloader_handler_test.go +2 -2
@@ -28,8 +28,8 @@ func TestBootloaderHandler_Handle_Limine(t *testing.T) {
2828 mockFS.EXPECT().Exists(gomock.Any()).Return(true, nil).AnyTimes()
2929 mockExec.EXPECT().Execute(gomock.Any(), "blkid", "-s", "UUID", "-o", "value", gomock.Any()).Return([]byte("uuid"), nil).AnyTimes()
3030 mockExec.EXPECT().Execute(gomock.Any(), "cp", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
31- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "mkinitcpio", "-P").Return([]byte{}, nil).AnyTimes()
32- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "efibootmgr", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
31+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
32+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
3333
3434 handler := NewBootloaderHandler(mockFS, mockExec, mockChrExec, mockLogger)
3535
@@ -28,8 +28,8 @@ func TestBootloaderHandler_Handle_Limine(t *testing.T) {
28 mockFS.EXPECT().Exists(gomock.Any()).Return(true, nil).AnyTimes()28 mockFS.EXPECT().Exists(gomock.Any()).Return(true, nil).AnyTimes()
29 mockExec.EXPECT().Execute(gomock.Any(), "blkid", "-s", "UUID", "-o", "value", gomock.Any()).Return([]byte("uuid"), nil).AnyTimes()29 mockExec.EXPECT().Execute(gomock.Any(), "blkid", "-s", "UUID", "-o", "value", gomock.Any()).Return([]byte("uuid"), nil).AnyTimes()
30 mockExec.EXPECT().Execute(gomock.Any(), "cp", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()30 mockExec.EXPECT().Execute(gomock.Any(), "cp", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
31- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "mkinitcpio", "-P").Return([]byte{}, nil).AnyTimes()31+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
32- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "efibootmgr", gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()32+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
33 33
34 handler := NewBootloaderHandler(mockFS, mockExec, mockChrExec, mockLogger)34 handler := NewBootloaderHandler(mockFS, mockExec, mockChrExec, mockLogger)
35 35
M internal/application/handlers/configure_system_handler_test.go +1 -0
@@ -21,6 +21,7 @@ func TestConfigureSystemHandler_Handle_Success(t *testing.T) {
2121 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
2222 mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes()
2323 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
24+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
2425 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
2526 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
2627 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
@@ -21,6 +21,7 @@ func TestConfigureSystemHandler_Handle_Success(t *testing.T) {
21 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()21 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
22 mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes()22 mockLogger.EXPECT().LogPath().Return("/var/log/archup-install.log").AnyTimes()
23 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()23 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
24+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
24 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()25 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
25 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()26 mockChrExec.EXPECT().ExecuteInChrootWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
26 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()27 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
M internal/application/handlers/install_base_handler.go +11 -0
@@ -129,10 +129,16 @@ func (h *InstallBaseHandler) setupCachyOSOnHost(ctx context.Context) error {
129129 cachyOSKeyID = "F3B607488DB35A47"
130130 keyserver = "keyserver.ubuntu.com"
131131 hostPacmanConf = "/etc/pacman.conf"
132+ cachyOSMirrorlistDir = "/etc/pacman.d"
132133 cachyOSMirrorlistPath = "/etc/pacman.d/cachyos-mirrorlist"
133134 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"
134135 )
135136
137+ // Ensure the pacman keyring is initialized before any key operations.
138+ if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--init"); err != nil {
139+ return fmt.Errorf("failed to init pacman keyring on host: %w", err)
140+ }
141+
136142 // Only fetch from keyserver if key is not already present.
137143 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {
138144 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--recv-keys",
@@ -146,6 +152,11 @@ func (h *InstallBaseHandler) setupCachyOSOnHost(ctx context.Context) error {
146152 return fmt.Errorf("failed to sign CachyOS key on host: %w", err)
147153 }
148154
155+ // Ensure /etc/pacman.d exists before writing the mirrorlist.
156+ if err := h.fs.MkdirAll(cachyOSMirrorlistDir, 0755); err != nil {
157+ return fmt.Errorf("failed to create pacman.d directory on host: %w", err)
158+ }
159+
149160 // Write static mirrorlist — no versioned package URLs that can go stale.
150161 if err := h.fs.WriteFile(cachyOSMirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {
151162 return fmt.Errorf("failed to write host CachyOS mirrorlist: %w", err)
@@ -129,10 +129,16 @@ func (h *InstallBaseHandler) setupCachyOSOnHost(ctx context.Context) error {
129 cachyOSKeyID = "F3B607488DB35A47"129 cachyOSKeyID = "F3B607488DB35A47"
130 keyserver = "keyserver.ubuntu.com"130 keyserver = "keyserver.ubuntu.com"
131 hostPacmanConf = "/etc/pacman.conf"131 hostPacmanConf = "/etc/pacman.conf"
132+ cachyOSMirrorlistDir = "/etc/pacman.d"
132 cachyOSMirrorlistPath = "/etc/pacman.d/cachyos-mirrorlist"133 cachyOSMirrorlistPath = "/etc/pacman.d/cachyos-mirrorlist"
133 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"134 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"
134 )135 )
135 136
137+ // Ensure the pacman keyring is initialized before any key operations.
138+ if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--init"); err != nil {
139+ return fmt.Errorf("failed to init pacman keyring on host: %w", err)
140+ }
141+
136 // Only fetch from keyserver if key is not already present.142 // Only fetch from keyserver if key is not already present.
137 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {143 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {
138 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--recv-keys",144 if _, err := h.cmdExec.Execute(ctx, "pacman-key", "--recv-keys",
@@ -146,6 +152,11 @@ func (h *InstallBaseHandler) setupCachyOSOnHost(ctx context.Context) error {
146 return fmt.Errorf("failed to sign CachyOS key on host: %w", err)152 return fmt.Errorf("failed to sign CachyOS key on host: %w", err)
147 }153 }
148 154
155+ // Ensure /etc/pacman.d exists before writing the mirrorlist.
156+ if err := h.fs.MkdirAll(cachyOSMirrorlistDir, 0755); err != nil {
157+ return fmt.Errorf("failed to create pacman.d directory on host: %w", err)
158+ }
159+
149 // Write static mirrorlist — no versioned package URLs that can go stale.160 // Write static mirrorlist — no versioned package URLs that can go stale.
150 if err := h.fs.WriteFile(cachyOSMirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {161 if err := h.fs.WriteFile(cachyOSMirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {
151 return fmt.Errorf("failed to write host CachyOS mirrorlist: %w", err)162 return fmt.Errorf("failed to write host CachyOS mirrorlist: %w", err)
M internal/application/handlers/repos_handler.go +11 -0
@@ -213,6 +213,11 @@ func (h *ReposHandler) setupCachyOSRepo(ctx context.Context, mountPoint string)
213213 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"
214214 )
215215
216+ // Ensure keyring is initialized inside the chroot.
217+ if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--init"); err != nil {
218+ return fmt.Errorf("failed to init pacman keyring in chroot: %w", err)
219+ }
220+
216221 // Only fetch from keyserver if key is not already present in chroot.
217222 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {
218223 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--recv-keys",
@@ -225,6 +230,12 @@ func (h *ReposHandler) setupCachyOSRepo(ctx context.Context, mountPoint string)
225230 return fmt.Errorf("failed to sign CachyOS key: %w", err)
226231 }
227232
233+ // Ensure /etc/pacman.d exists inside the chroot before writing mirrorlist.
234+ mirrorlistDir := filepath.Join(mountPoint, "etc", "pacman.d")
235+ if err := h.fs.MkdirAll(mirrorlistDir, 0755); err != nil {
236+ return fmt.Errorf("failed to create pacman.d in chroot: %w", err)
237+ }
238+
228239 // Write static mirrorlist — no versioned package URLs that can go stale.
229240 mirrorlistPath := filepath.Join(mountPoint, "etc", "pacman.d", "cachyos-mirrorlist")
230241 if err := h.fs.WriteFile(mirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {
@@ -213,6 +213,11 @@ func (h *ReposHandler) setupCachyOSRepo(ctx context.Context, mountPoint string)
213 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"213 cachyOSMirrorlist = "## CachyOS mirrorlist\nServer = https://mirror.cachyos.org/repo/$arch/$repo\n"
214 )214 )
215 215
216+ // Ensure keyring is initialized inside the chroot.
217+ if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--init"); err != nil {
218+ return fmt.Errorf("failed to init pacman keyring in chroot: %w", err)
219+ }
220+
216 // Only fetch from keyserver if key is not already present in chroot.221 // Only fetch from keyserver if key is not already present in chroot.
217 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {222 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--list-keys", cachyOSKeyID); err != nil {
218 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--recv-keys",223 if _, err := h.chrExec.ExecuteInChroot(ctx, mountPoint, "pacman-key", "--recv-keys",
@@ -225,6 +230,12 @@ func (h *ReposHandler) setupCachyOSRepo(ctx context.Context, mountPoint string)
225 return fmt.Errorf("failed to sign CachyOS key: %w", err)230 return fmt.Errorf("failed to sign CachyOS key: %w", err)
226 }231 }
227 232
233+ // Ensure /etc/pacman.d exists inside the chroot before writing mirrorlist.
234+ mirrorlistDir := filepath.Join(mountPoint, "etc", "pacman.d")
235+ if err := h.fs.MkdirAll(mirrorlistDir, 0755); err != nil {
236+ return fmt.Errorf("failed to create pacman.d in chroot: %w", err)
237+ }
238+
228 // Write static mirrorlist — no versioned package URLs that can go stale.239 // Write static mirrorlist — no versioned package URLs that can go stale.
229 mirrorlistPath := filepath.Join(mountPoint, "etc", "pacman.d", "cachyos-mirrorlist")240 mirrorlistPath := filepath.Join(mountPoint, "etc", "pacman.d", "cachyos-mirrorlist")
230 if err := h.fs.WriteFile(mirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {241 if err := h.fs.WriteFile(mirrorlistPath, []byte(cachyOSMirrorlist), 0644); err != nil {
M internal/application/handlers/repos_handler_test.go +53 -42
@@ -12,6 +12,10 @@ import (
1212 "go.uber.org/mock/gomock"
1313 )
1414
15+func errNotFound(name string) error {
16+ return fmt.Errorf("%s: not found", name)
17+}
18+
1519 func TestReposHandler_Handle_Minimal(t *testing.T) {
1620 ctrl := gomock.NewController(t)
1721 defer ctrl.Finish()
@@ -21,6 +25,10 @@ func TestReposHandler_Handle_Minimal(t *testing.T) {
2125 mockLogger := mocks.NewMockLogger(ctrl)
2226
2327 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
28+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
29+
30+ // extra.packages read fails gracefully (no file present)
31+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
2432
2533 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
2634
@@ -37,19 +45,15 @@ func TestReposHandler_Handle_Minimal(t *testing.T) {
3745 if err != nil {
3846 t.Fatalf("expected no error, got %v", err)
3947 }
40-
4148 if !result.Success {
4249 t.Error("expected success")
4350 }
44-
4551 if result.Multilib {
4652 t.Error("expected multilib to be disabled")
4753 }
48-
4954 if result.Chaotic {
5055 t.Error("expected chaotic to be disabled")
5156 }
52-
5357 if result.AURHelper != "paru" {
5458 t.Errorf("expected paru helper, got %s", result.AURHelper)
5559 }
@@ -64,13 +68,21 @@ func TestReposHandler_Handle_AllEnabled(t *testing.T) {
6468 mockLogger := mocks.NewMockLogger(ctrl)
6569
6670 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
67- mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("[core]\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist\n"), nil).AnyTimes()
71+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
72+
73+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(
74+ []byte("[core]\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist\n"), nil,
75+ ).AnyTimes()
6876 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
77+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
78+
79+ // Chaotic keyring key ops (chaotic uses --recv-key singular, pacman -U for keyring)
6980 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--recv-key", gomock.Any(), "--keyserver", gomock.Any()).Return([]byte{}, nil).AnyTimes()
7081 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--lsign-key", gomock.Any()).Return([]byte{}, nil).AnyTimes()
7182 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-U", "--noconfirm", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
7283 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-Sy", "--noconfirm").Return([]byte{}, nil).AnyTimes()
7384 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", gomock.Any()).Return([]byte{}, nil).AnyTimes()
85+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", "--needed", gomock.Any()).Return([]byte{}, nil).AnyTimes()
7486
7587 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
7688
@@ -87,19 +99,15 @@ func TestReposHandler_Handle_AllEnabled(t *testing.T) {
8799 if err != nil {
88100 t.Fatalf("expected no error, got %v", err)
89101 }
90-
91102 if !result.Success {
92103 t.Error("expected success")
93104 }
94-
95105 if !result.Multilib {
96106 t.Error("expected multilib to be enabled")
97107 }
98-
99108 if !result.Chaotic {
100109 t.Error("expected chaotic to be enabled")
101110 }
102-
103111 if result.AURHelper != "yay" {
104112 t.Errorf("expected yay helper, got %s", result.AURHelper)
105113 }
@@ -115,36 +123,40 @@ func TestReposHandler_Handle_CachyOSKernel_AddsCachyOSRepo(t *testing.T) {
115123
116124 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
117125 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
118- // extra.packages read will fail gracefully
119- mockFS.EXPECT().ReadFile(gomock.Not("/mnt/etc/pacman.conf")).Return(nil, fmt.Errorf("not found")).AnyTimes()
120-
121- // Expect: recv CachyOS key
122- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--recv-key",
123- "F3B607488DB35A47", "--keyserver", "keyserver.ubuntu.com").Return([]byte{}, nil)
124-
125- // Expect: lsign CachyOS key
126- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--lsign-key",
127- "F3B607488DB35A47").Return([]byte{}, nil)
128126
129- // Expect: pacman -U for CachyOS packages (4 URLs)
130- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman", "-U", "--noconfirm",
131- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-keyring-20240331-1-any.pkg.tar.zst",
132- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-mirrorlist-18-1-any.pkg.tar.zst",
133- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-v3-mirrorlist-18-1-any.pkg.tar.zst",
134- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-v4-mirrorlist-6-1-any.pkg.tar.zst",
135- ).Return([]byte{}, nil)
127+ // extra.packages read fails gracefully; pacman.conf read succeeds
128+ mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return(
129+ []byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil,
130+ )
131+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
136132
137- // Expect: ReadFile for pacman.conf
138- mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return([]byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil)
133+ // MkdirAll for /mnt/etc/pacman.d
134+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil)
139135
140- // Expect: WriteFile with content containing [cachyos]
136+ // WriteFile: mirrorlist + pacman.conf
137+ mockFS.EXPECT().WriteFile("/mnt/etc/pacman.d/cachyos-mirrorlist", gomock.Any(), gomock.Any()).Return(nil)
141138 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.conf", gomock.Any(), gomock.Any()).DoAndReturn(
142139 func(path string, data []byte, perm interface{}) error {
143140 if !strings.Contains(string(data), "[cachyos]") {
144141 t.Errorf("expected pacman.conf to contain [cachyos], got:\n%s", string(data))
145142 }
146143 return nil
147- })
144+ },
145+ )
146+
147+ // pacman-key --init
148+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--init").Return([]byte{}, nil)
149+ // --list-keys: key not present → triggers --recv-keys
150+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--list-keys", "F3B607488DB35A47").Return(nil, errNotFound("key"))
151+ // --recv-keys
152+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--recv-keys",
153+ "F3B607488DB35A47", "--keyserver", "keyserver.ubuntu.com").Return([]byte{}, nil)
154+ // --lsign-key
155+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--lsign-key",
156+ "F3B607488DB35A47").Return([]byte{}, nil)
157+
158+ // No pacman -Sy here: CachyOS-only (no multilib, no chaotic) — sync is skipped in repos phase
159+ // (chroot sync only runs when multilib or chaotic are enabled)
148160
149161 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
150162
@@ -161,7 +173,6 @@ func TestReposHandler_Handle_CachyOSKernel_AddsCachyOSRepo(t *testing.T) {
161173 if err != nil {
162174 t.Fatalf("expected no error, got %v", err)
163175 }
164-
165176 if !result.Success {
166177 t.Error("expected success")
167178 }
@@ -176,20 +187,21 @@ func TestReposHandler_Handle_WithAdditionalRepos(t *testing.T) {
176187 mockLogger := mocks.NewMockLogger(ctrl)
177188
178189 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
190+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
179191
180- handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
192+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
181193
182- additionalRepos := []string{
183- "https://example.com/archup",
184- "https://custom.repo/packages",
185- }
194+ handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
186195
187196 cmd := commands.SetupRepositoriesCommand{
188- MountPoint: "/mnt",
189- EnableMultilib: false,
190- EnableChaotic: false,
191- AURHelper: packages.AURHelperParu,
192- AdditionalRepos: additionalRepos,
197+ MountPoint: "/mnt",
198+ EnableMultilib: false,
199+ EnableChaotic: false,
200+ AURHelper: packages.AURHelperParu,
201+ AdditionalRepos: []string{
202+ "https://example.com/archup",
203+ "https://custom.repo/packages",
204+ },
193205 }
194206
195207 result, err := handler.Handle(context.Background(), cmd)
@@ -197,7 +209,6 @@ func TestReposHandler_Handle_WithAdditionalRepos(t *testing.T) {
197209 if err != nil {
198210 t.Fatalf("expected no error, got %v", err)
199211 }
200-
201212 if !result.Success {
202213 t.Error("expected success")
203214 }
@@ -12,6 +12,10 @@ import (
12 "go.uber.org/mock/gomock"12 "go.uber.org/mock/gomock"
13 )13 )
14 14
15+func errNotFound(name string) error {
16+ return fmt.Errorf("%s: not found", name)
17+}
18+
15 func TestReposHandler_Handle_Minimal(t *testing.T) {19 func TestReposHandler_Handle_Minimal(t *testing.T) {
16 ctrl := gomock.NewController(t)20 ctrl := gomock.NewController(t)
17 defer ctrl.Finish()21 defer ctrl.Finish()
@@ -21,6 +25,10 @@ func TestReposHandler_Handle_Minimal(t *testing.T) {
21 mockLogger := mocks.NewMockLogger(ctrl)25 mockLogger := mocks.NewMockLogger(ctrl)
22 26
23 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()27 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
28+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
29+
30+ // extra.packages read fails gracefully (no file present)
31+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
24 32
25 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)33 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
26 34
@@ -37,19 +45,15 @@ func TestReposHandler_Handle_Minimal(t *testing.T) {
37 if err != nil {45 if err != nil {
38 t.Fatalf("expected no error, got %v", err)46 t.Fatalf("expected no error, got %v", err)
39 }47 }
40-
41 if !result.Success {48 if !result.Success {
42 t.Error("expected success")49 t.Error("expected success")
43 }50 }
44-
45 if result.Multilib {51 if result.Multilib {
46 t.Error("expected multilib to be disabled")52 t.Error("expected multilib to be disabled")
47 }53 }
48-
49 if result.Chaotic {54 if result.Chaotic {
50 t.Error("expected chaotic to be disabled")55 t.Error("expected chaotic to be disabled")
51 }56 }
52-
53 if result.AURHelper != "paru" {57 if result.AURHelper != "paru" {
54 t.Errorf("expected paru helper, got %s", result.AURHelper)58 t.Errorf("expected paru helper, got %s", result.AURHelper)
55 }59 }
@@ -64,13 +68,21 @@ func TestReposHandler_Handle_AllEnabled(t *testing.T) {
64 mockLogger := mocks.NewMockLogger(ctrl)68 mockLogger := mocks.NewMockLogger(ctrl)
65 69
66 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()70 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
67- mockFS.EXPECT().ReadFile(gomock.Any()).Return([]byte("[core]\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist\n"), nil).AnyTimes()71+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
72+
73+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(
74+ []byte("[core]\n#[multilib]\n#Include = /etc/pacman.d/mirrorlist\n"), nil,
75+ ).AnyTimes()
68 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()76 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
77+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
78+
79+ // Chaotic keyring key ops (chaotic uses --recv-key singular, pacman -U for keyring)
69 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--recv-key", gomock.Any(), "--keyserver", gomock.Any()).Return([]byte{}, nil).AnyTimes()80 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--recv-key", gomock.Any(), "--keyserver", gomock.Any()).Return([]byte{}, nil).AnyTimes()
70 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--lsign-key", gomock.Any()).Return([]byte{}, nil).AnyTimes()81 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman-key", "--lsign-key", gomock.Any()).Return([]byte{}, nil).AnyTimes()
71 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-U", "--noconfirm", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()82 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-U", "--noconfirm", gomock.Any(), gomock.Any()).Return([]byte{}, nil).AnyTimes()
72 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-Sy", "--noconfirm").Return([]byte{}, nil).AnyTimes()83 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-Sy", "--noconfirm").Return([]byte{}, nil).AnyTimes()
73 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", gomock.Any()).Return([]byte{}, nil).AnyTimes()84 mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", gomock.Any()).Return([]byte{}, nil).AnyTimes()
85+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), gomock.Any(), "pacman", "-S", "--noconfirm", "--needed", gomock.Any()).Return([]byte{}, nil).AnyTimes()
74 86
75 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)87 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
76 88
@@ -87,19 +99,15 @@ func TestReposHandler_Handle_AllEnabled(t *testing.T) {
87 if err != nil {99 if err != nil {
88 t.Fatalf("expected no error, got %v", err)100 t.Fatalf("expected no error, got %v", err)
89 }101 }
90-
91 if !result.Success {102 if !result.Success {
92 t.Error("expected success")103 t.Error("expected success")
93 }104 }
94-
95 if !result.Multilib {105 if !result.Multilib {
96 t.Error("expected multilib to be enabled")106 t.Error("expected multilib to be enabled")
97 }107 }
98-
99 if !result.Chaotic {108 if !result.Chaotic {
100 t.Error("expected chaotic to be enabled")109 t.Error("expected chaotic to be enabled")
101 }110 }
102-
103 if result.AURHelper != "yay" {111 if result.AURHelper != "yay" {
104 t.Errorf("expected yay helper, got %s", result.AURHelper)112 t.Errorf("expected yay helper, got %s", result.AURHelper)
105 }113 }
@@ -115,36 +123,40 @@ func TestReposHandler_Handle_CachyOSKernel_AddsCachyOSRepo(t *testing.T) {
115 123
116 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()124 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
117 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()125 mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
118- // extra.packages read will fail gracefully
119- mockFS.EXPECT().ReadFile(gomock.Not("/mnt/etc/pacman.conf")).Return(nil, fmt.Errorf("not found")).AnyTimes()
120-
121- // Expect: recv CachyOS key
122- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--recv-key",
123- "F3B607488DB35A47", "--keyserver", "keyserver.ubuntu.com").Return([]byte{}, nil)
124-
125- // Expect: lsign CachyOS key
126- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--lsign-key",
127- "F3B607488DB35A47").Return([]byte{}, nil)
128 126
129- // Expect: pacman -U for CachyOS packages (4 URLs)127+ // extra.packages read fails gracefully; pacman.conf read succeeds
130- mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman", "-U", "--noconfirm",128+ mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return(
131- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-keyring-20240331-1-any.pkg.tar.zst",129+ []byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil,
132- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-mirrorlist-18-1-any.pkg.tar.zst",130+ )
133- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-v3-mirrorlist-18-1-any.pkg.tar.zst",131+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
134- "https://mirror.cachyos.org/repo/x86_64/cachyos/cachyos-v4-mirrorlist-6-1-any.pkg.tar.zst",
135- ).Return([]byte{}, nil)
136 132
137- // Expect: ReadFile for pacman.conf133+ // MkdirAll for /mnt/etc/pacman.d
138- mockFS.EXPECT().ReadFile("/mnt/etc/pacman.conf").Return([]byte("[core]\nInclude = /etc/pacman.d/mirrorlist\n"), nil)134+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil)
139 135
140- // Expect: WriteFile with content containing [cachyos]136+ // WriteFile: mirrorlist + pacman.conf
137+ mockFS.EXPECT().WriteFile("/mnt/etc/pacman.d/cachyos-mirrorlist", gomock.Any(), gomock.Any()).Return(nil)
141 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.conf", gomock.Any(), gomock.Any()).DoAndReturn(138 mockFS.EXPECT().WriteFile("/mnt/etc/pacman.conf", gomock.Any(), gomock.Any()).DoAndReturn(
142 func(path string, data []byte, perm interface{}) error {139 func(path string, data []byte, perm interface{}) error {
143 if !strings.Contains(string(data), "[cachyos]") {140 if !strings.Contains(string(data), "[cachyos]") {
144 t.Errorf("expected pacman.conf to contain [cachyos], got:\n%s", string(data))141 t.Errorf("expected pacman.conf to contain [cachyos], got:\n%s", string(data))
145 }142 }
146 return nil143 return nil
147- })144+ },
145+ )
146+
147+ // pacman-key --init
148+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--init").Return([]byte{}, nil)
149+ // --list-keys: key not present → triggers --recv-keys
150+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--list-keys", "F3B607488DB35A47").Return(nil, errNotFound("key"))
151+ // --recv-keys
152+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--recv-keys",
153+ "F3B607488DB35A47", "--keyserver", "keyserver.ubuntu.com").Return([]byte{}, nil)
154+ // --lsign-key
155+ mockChrExec.EXPECT().ExecuteInChroot(gomock.Any(), "/mnt", "pacman-key", "--lsign-key",
156+ "F3B607488DB35A47").Return([]byte{}, nil)
157+
158+ // No pacman -Sy here: CachyOS-only (no multilib, no chaotic) — sync is skipped in repos phase
159+ // (chroot sync only runs when multilib or chaotic are enabled)
148 160
149 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)161 handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
150 162
@@ -161,7 +173,6 @@ func TestReposHandler_Handle_CachyOSKernel_AddsCachyOSRepo(t *testing.T) {
161 if err != nil {173 if err != nil {
162 t.Fatalf("expected no error, got %v", err)174 t.Fatalf("expected no error, got %v", err)
163 }175 }
164-
165 if !result.Success {176 if !result.Success {
166 t.Error("expected success")177 t.Error("expected success")
167 }178 }
@@ -176,20 +187,21 @@ func TestReposHandler_Handle_WithAdditionalRepos(t *testing.T) {
176 mockLogger := mocks.NewMockLogger(ctrl)187 mockLogger := mocks.NewMockLogger(ctrl)
177 188
178 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()189 mockLogger.EXPECT().Info(gomock.Any(), gomock.Any()).AnyTimes()
190+ mockLogger.EXPECT().Warn(gomock.Any(), gomock.Any()).AnyTimes()
179 191
180- handler := NewReposHandler(mockFS, mockChrExec, mockLogger)192+ mockFS.EXPECT().ReadFile(gomock.Any()).Return(nil, errNotFound("extra.packages")).AnyTimes()
181 193
182- additionalRepos := []string{194+ handler := NewReposHandler(mockFS, mockChrExec, mockLogger)
183- "https://example.com/archup",
184- "https://custom.repo/packages",
185- }
186 195
187 cmd := commands.SetupRepositoriesCommand{196 cmd := commands.SetupRepositoriesCommand{
188- MountPoint: "/mnt",197+ MountPoint: "/mnt",
189- EnableMultilib: false,198+ EnableMultilib: false,
190- EnableChaotic: false,199+ EnableChaotic: false,
191- AURHelper: packages.AURHelperParu,200+ AURHelper: packages.AURHelperParu,
192- AdditionalRepos: additionalRepos,201+ AdditionalRepos: []string{
202+ "https://example.com/archup",
203+ "https://custom.repo/packages",
204+ },
193 }205 }
194 206
195 result, err := handler.Handle(context.Background(), cmd)207 result, err := handler.Handle(context.Background(), cmd)
@@ -197,7 +209,6 @@ func TestReposHandler_Handle_WithAdditionalRepos(t *testing.T) {
197 if err != nil {209 if err != nil {
198 t.Fatalf("expected no error, got %v", err)210 t.Fatalf("expected no error, got %v", err)
199 }211 }
200-
201 if !result.Success {212 if !result.Success {
202 t.Error("expected success")213 t.Error("expected success")
203 }214 }
M internal/phases/systemconfig_test.go +1 -0
@@ -229,6 +229,7 @@ func TestConfigPhaseExecute(t *testing.T) {
229229 mockChrExec.EXPECT().ChrootExecWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
230230 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
231231 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
232+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
232233
233234 tmpDir := t.TempDir()
234235 logPath := filepath.Join(tmpDir, "test.log")
@@ -229,6 +229,7 @@ func TestConfigPhaseExecute(t *testing.T) {
229 mockChrExec.EXPECT().ChrootExecWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()229 mockChrExec.EXPECT().ChrootExecWithStdin(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
230 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()230 mockChrExec.EXPECT().ChrootSystemctl(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
231 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()231 mockFS.EXPECT().WriteFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
232+ mockFS.EXPECT().MkdirAll(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
232 233
233 tmpDir := t.TempDir()234 tmpDir := t.TempDir()
234 logPath := filepath.Join(tmpDir, "test.log")235 logPath := filepath.Join(tmpDir, "test.log")

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