diff --git a/pkg/action/bootentries.go b/pkg/action/bootentries.go index 2d0443c..8ea776f 100644 --- a/pkg/action/bootentries.go +++ b/pkg/action/bootentries.go @@ -2,8 +2,6 @@ package action import ( "fmt" - cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" - "github.com/kairos-io/kairos-agent/v2/pkg/elemental" "os" "path/filepath" "reflect" @@ -11,6 +9,9 @@ import ( "strings" "syscall" + cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" + "github.com/kairos-io/kairos-agent/v2/pkg/elemental" + "github.com/erikgeiser/promptkit/confirmation" "github.com/erikgeiser/promptkit/selection" "github.com/kairos-io/kairos-agent/v2/pkg/config" @@ -82,15 +83,15 @@ func selectBootEntrySystemd(cfg *config.Config, entry string) error { } originalEntries := entries - // when there are only 3 entries, we can assume they are active, passive and recovery + // when there are only 3 entries, we can assume they are either cos (which will be replaced eventually), fallback or recovery if len(entries) == 3 { entries = []string{"cos", "fallback", "recovery"} } - // when there are more than 3 entries, then we need to also extract the part between the first _ and the .conf in order to distinguish the entries // Check that entry exists in the entries list err = entryInList(cfg, entry, entries) - if err != nil { + // we also accept "active" as a selection so we can migrate eventually from cos + if err != nil && !strings.HasPrefix(entry, "active") { return err } @@ -113,7 +114,12 @@ func selectBootEntrySystemd(cfg *config.Config, entry string) error { cfg.Logger.Errorf("could not read loader.conf: %s", err) return err } + originalEntry := entry if !reflect.DeepEqual(originalEntries, entries) { + // since we temporarily allow also active, here we need to first set entry to "cos" so it will match with the originalEntries + if strings.HasPrefix(entry, "active") { + entry = "cos" + } for _, e := range originalEntries { if strings.HasPrefix(e, entry) { entry = e @@ -131,7 +137,7 @@ func selectBootEntrySystemd(cfg *config.Config, entry string) error { cfg.Logger.Errorf("could not write loader.conf: %s", err) return err } - cfg.Logger.Infof("Default boot entry set to %s", entry) + cfg.Logger.Infof("Default boot entry set to %s", originalEntry) return err } @@ -210,6 +216,13 @@ func bootNameToSystemdConf(name string) (string, error) { return "active" + differenciator + ".conf", nil } + if strings.HasPrefix(name, "active") { + if name != "active" { + differenciator = "_" + strings.TrimPrefix(name, "active ") + } + return "active" + differenciator + ".conf", nil + } + if strings.HasPrefix(name, "fallback") { if name != "fallback" { differenciator = "_" + strings.TrimPrefix(name, "fallback ") diff --git a/pkg/action/bootentries_test.go b/pkg/action/bootentries_test.go index 26143a8..68be92d 100644 --- a/pkg/action/bootentries_test.go +++ b/pkg/action/bootentries_test.go @@ -108,17 +108,19 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() { It("lists the boot entries if there is any", func() { err := fs.WriteFile("/efi/loader/loader.conf", []byte("timeout 5\ndefault kairos\nrecovery kairos2\n"), os.ModePerm) Expect(err).ToNot(HaveOccurred()) - err = fs.WriteFile("/efi/loader/entries/active.conf", []byte("title kairos\nlinux /vmlinuz\ninitrd /initrd\noptions root=LABEL=COS_GRUB\n"), os.ModePerm) + err = fs.WriteFile("/efi/loader/entries/active.conf", []byte("title kairos\nefi /EFI/kairos/active.efi\n"), os.ModePerm) Expect(err).ToNot(HaveOccurred()) - - err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title kairos2\nlinux /vmlinuz2\ninitrd /initrd2\noptions root=LABEL=COS_GRUB2\n"), os.ModePerm) + err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm) + Expect(err).ToNot(HaveOccurred()) + err = fs.WriteFile("/efi/loader/entries/recovery.conf", []byte("title kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm) Expect(err).ToNot(HaveOccurred()) entries, err := listSystemdEntries(config, &v1.Partition{MountPoint: "/efi"}) Expect(err).ToNot(HaveOccurred()) - Expect(entries).To(HaveLen(2)) + Expect(entries).To(HaveLen(3)) Expect(entries).To(ContainElement("cos")) Expect(entries).To(ContainElement("fallback")) + Expect(entries).To(ContainElement("recovery")) }) It("list empty boot entries if there is none", func() { @@ -185,7 +187,51 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() { "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")).To(BeTrue()) + + err = SelectBootEntry(config, "cos") + Expect(err).ToNot(HaveOccurred()) + Expect(memLog.String()).To(ContainSubstring("Default boot entry set to cos")) + reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf") + Expect(err).ToNot(HaveOccurred()) + Expect(reader["default"]).To(Equal("active.conf")) + // Should have called a remount to make it RW + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT, + "")).To(BeTrue()) + // Should have called a remount to make it RO + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT|syscall.MS_RDONLY, + "")).To(BeTrue()) + + // also works using active (we want to get rid of the word cos later but this also needs to be applied in GRUB) + err = SelectBootEntry(config, "active") + Expect(err).ToNot(HaveOccurred()) + Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active")) + reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf") + Expect(err).ToNot(HaveOccurred()) + Expect(reader["default"]).To(Equal("active.conf")) + // Should have called a remount to make it RW + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT, + "")).To(BeTrue()) + // Should have called a remount to make it RO + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT|syscall.MS_RDONLY, + "")).To(BeTrue()) }) + It("selects the boot entry in a extend-cmdline installation with boot branding", func() { err := fs.WriteFile("/efi/loader/entries/active_install-mode_awesomeos.conf", []byte("title awesomeos\nefi /EFI/kairos/active_install-mode_awesomeos.efi\n"), os.ModePerm) Expect(err).ToNot(HaveOccurred()) @@ -258,6 +304,28 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() { "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")).To(BeTrue()) + + // also works using active (we want to get rid of the word cos later but this also needs to be applied in GRUB) + err = SelectBootEntry(config, "active") + Expect(err).ToNot(HaveOccurred()) + Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active")) + reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf") + Expect(err).ToNot(HaveOccurred()) + Expect(reader["default"]).To(Equal("active_install-mode_awesomeos.conf")) + // Should have called a remount to make it RW + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT, + "")).To(BeTrue()) + // Should have called a remount to make it RO + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT|syscall.MS_RDONLY, + "")).To(BeTrue()) }) It("selects the boot entry in a extra-cmdline installation", func() { @@ -401,9 +469,52 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() { "", syscall.MS_REMOUNT|syscall.MS_RDONLY, "")).To(BeTrue()) + + // also works using active (we want to get rid of the word cos later but this also needs to be applied in GRUB) + err = SelectBootEntry(config, "active") + Expect(err).ToNot(HaveOccurred()) + Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active")) + reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf") + Expect(err).ToNot(HaveOccurred()) + Expect(reader["default"]).To(Equal("active.conf")) + // Should have called a remount to make it RW + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT, + "")).To(BeTrue()) + // Should have called a remount to make it RO + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT|syscall.MS_RDONLY, + "")).To(BeTrue()) + err = SelectBootEntry(config, "active foobar") + Expect(err).ToNot(HaveOccurred()) + Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active foobar")) + reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf") + Expect(err).ToNot(HaveOccurred()) + Expect(reader["default"]).To(Equal("active_foobar.conf")) + // Should have called a remount to make it RW + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT, + "")).To(BeTrue()) + // Should have called a remount to make it RO + Expect(syscallMock.WasMountCalledWith( + "", + "/efi", + "", + syscall.MS_REMOUNT|syscall.MS_RDONLY, + "")).To(BeTrue()) }) }) }) + Context("Under grub", func() { Context("ListBootEntries", func() { It("fails to list the boot entries when there is no grub files", func() { diff --git a/pkg/uki/install.go b/pkg/uki/install.go index e7f4c9e..d24e643 100644 --- a/pkg/uki/install.go +++ b/pkg/uki/install.go @@ -161,7 +161,7 @@ func (i *InstallAction) Run() (err error) { } // SelectBootEntry sets the default boot entry to the selected entry - err = action.SelectBootEntry(i.cfg, "active") + err = action.SelectBootEntry(i.cfg, "cos") if err != nil { i.cfg.Logger.Warnf("selecting active boot entry: %s", err.Error()) } diff --git a/pkg/uki/reset.go b/pkg/uki/reset.go index 73d1f01..57d8d7d 100644 --- a/pkg/uki/reset.go +++ b/pkg/uki/reset.go @@ -81,7 +81,7 @@ func (r *ResetAction) Run() (err error) { return fmt.Errorf("copying recovery to active: %w", err) } // SelectBootEntry sets the default boot entry to the selected entry - err = action.SelectBootEntry(r.cfg, "active") + err = action.SelectBootEntry(r.cfg, "cos") // Should we fail? Or warn? if err != nil { r.cfg.Logger.Errorf("selecting boot entry : %s", err.Error()) diff --git a/pkg/uki/upgrade.go b/pkg/uki/upgrade.go index 6d0a253..e2cd0cf 100644 --- a/pkg/uki/upgrade.go +++ b/pkg/uki/upgrade.go @@ -90,7 +90,7 @@ func (i *UpgradeAction) Run() (err error) { } // SelectBootEntry sets the default boot entry to the selected entry - err = action.SelectBootEntry(i.cfg, "active") + err = action.SelectBootEntry(i.cfg, "cos") // Should we fail? Or warn? if err != nil { i.cfg.Logger.Errorf("selecting boot entry: %s", err.Error())