diff --git a/cmd/imageOutdated.go b/cmd/imageOutdated.go new file mode 100644 index 0000000..7220564 --- /dev/null +++ b/cmd/imageOutdated.go @@ -0,0 +1,16 @@ +package cmd + +import ( + "log" + + "github.com/gerblesh/update-next/drv" + "github.com/spf13/cobra" +) + +func ImageOutdated(cmd *cobra.Command, args []string) { + outdated, err := drv.IsImageOutdated() + if err != nil { + log.Fatalf("Cannot determine if image is outdated: %v", err) + } + log.Printf("%t", outdated) +} diff --git a/cmd/root.go b/cmd/root.go index c493f39..1adb4b7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -30,6 +30,12 @@ var ( Short: "Run hardware checks", Run: HwCheck, } + + imageOutdatedCmd = &cobra.Command{ + Use: "is-img-outdated", + Short: "Print 'true' or 'false' based on if the current booted image is over 1 month old", + Run: ImageOutdated, + } ) func Execute() { @@ -43,5 +49,6 @@ func init() { rootCmd.AddCommand(waitCmd) rootCmd.AddCommand(updateCheckCmd) rootCmd.AddCommand(hardwareCheckCmd) + rootCmd.AddCommand(imageOutdatedCmd) rootCmd.Flags().BoolP("hw-check", "c", false, "run hardware check before running updates") } diff --git a/cmd/update.go b/cmd/update.go index b9efa12..eba5695 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -18,6 +18,14 @@ type Failure struct { } func Update(cmd *cobra.Command, args []string) { + outdated, err := drv.IsImageOutdated() + if err != nil { + log.Fatalf("Unable to determine if image is outdated: %v", err) + } + if outdated { + lib.Notify("System Warning", "Your current image is over 1 month old, run `ujust update`") + } + hwCheck, err := cmd.Flags().GetBool("hw-check") if err != nil { log.Fatalf("Failed to get hw-check flag: %v", err) @@ -35,8 +43,9 @@ func Update(cmd *cobra.Command, args []string) { if err != nil { log.Fatalf("Failed to list users") } + // Check if bootc update is available - updateAvailable, err := drv.CheckForUpdate() + updateAvailable, err := drv.CheckForImageUpdate() bootcUpdate := 0 if updateAvailable { bootcUpdate = 1 @@ -93,7 +102,7 @@ func Update(cmd *cobra.Command, args []string) { } for _, user := range users { currentUpdate++ - log.Printf("[%d/%d] Updating User Apps for user: %s (Flatpak)", currentUpdate, totalUpdates, user.Name) + log.Printf("[%d/%d] Updating Apps for User: %s (Flatpak)", currentUpdate, totalUpdates, user.Name) out, err := lib.RunUID(user.UID, []string{"/usr/bin/flatpak", "update", "-y"}, nil) if err != nil { failures[fmt.Sprintf("Flatpak User: %s", user.Name)] = Failure{ @@ -116,7 +125,7 @@ func Update(cmd *cobra.Command, args []string) { } for _, user := range users { currentUpdate++ - log.Printf("[%d/%d] Updating User Distroboxes: %s", currentUpdate, totalUpdates, user.Name) + log.Printf("[%d/%d] Updating Distroboxes for User: %s", currentUpdate, totalUpdates, user.Name) out, err := lib.RunUID(user.UID, []string{"/usr/bin/distrobox", "upgrade", "-a"}, nil) if err != nil { failures[fmt.Sprintf("Distrobox User: %s", user.Name)] = Failure{ @@ -134,7 +143,7 @@ func Update(cmd *cobra.Command, args []string) { failedSystemsStr := strings.Join(failedSystemsList, ", ") lib.Notify("Updates failed", fmt.Sprintf("ublue-upd failed to update: %s", failedSystemsStr)) - log.Printf("Update Failures:") + log.Printf("Updates Completed with Failures:") for name, fail := range failures { indentedOutput := "\t | " lines := strings.Split(fail.Output, "\n") @@ -146,5 +155,7 @@ func Update(cmd *cobra.Command, args []string) { } log.Printf("---> %s \n\t | Failure error: %v \n\t | Command Output: \n%s", name, fail.Err, indentedOutput) } + return } + log.Printf("Updates Completed") } diff --git a/cmd/updateCheck.go b/cmd/updateCheck.go index 46df558..019e69a 100644 --- a/cmd/updateCheck.go +++ b/cmd/updateCheck.go @@ -7,7 +7,7 @@ import ( ) func UpdateCheck(cmd *cobra.Command, args []string) { - update, err := drv.CheckForUpdate() + update, err := drv.CheckForImageUpdate() if err != nil { log.Fatalf("Failed to check for updates: %v", err) } diff --git a/drv/bootc.go b/drv/bootc.go index a8df3b2..2eeb1f1 100644 --- a/drv/bootc.go +++ b/drv/bootc.go @@ -1,10 +1,43 @@ package drv import ( + "encoding/json" + "log" "os/exec" "strings" + "time" ) +type bootcStatus struct { + Status struct { + Booted struct { + Image struct { + Timestamp string `json:"timestamp"` + } `json:"image"` + } `json:"booted"` + } `json:"status"` +} + +func IsImageOutdated() (bool, error) { + cmd := exec.Command("bootc", "status", "--format=json") + out, err := cmd.CombinedOutput() + if err != nil { + return false, nil + } + var status bootcStatus + err = json.Unmarshal(out, &status) + if err != nil { + return false, nil + } + timestamp, err := time.Parse(time.RFC3339Nano, status.Status.Booted.Image.Timestamp) + if err != nil { + return false, nil + } + oneMonthAgo := time.Now().AddDate(0, -1, 0) + + return timestamp.Before(oneMonthAgo), nil +} + func BootcUpdate() ([]byte, error) { cmd := exec.Command("/usr/bin/bootc", "upgrade") out, err := cmd.CombinedOutput() @@ -14,9 +47,9 @@ func BootcUpdate() ([]byte, error) { return out, nil } -func CheckForUpdate() (bool, error) { +func CheckForImageUpdate() (bool, error) { cmd := exec.Command("/usr/bin/bootc", "upgrade", "--check") - out, err := cmd.Output() + out, err := cmd.CombinedOutput() if err != nil { return true, err }