Skip to content

Commit

Permalink
add rpm-ostree support
Browse files Browse the repository at this point in the history
  • Loading branch information
gerblesh committed Nov 30, 2024
1 parent d2b6d9e commit 4b515cb
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ $ journalctl -exu 'ublue-upd.service'
# How do I build this?

1. `just build` will build this project and place the binary in `output/ublue-upd`
1. `sudo ./output/ublue-upd` will run a system update
1. `sudo ./output/ublue-upd` will run an update
6 changes: 5 additions & 1 deletion cmd/imageOutdated.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import (
)

func ImageOutdated(cmd *cobra.Command, args []string) {
outdated, err := drv.IsImageOutdated()
systemDriver, err := drv.GetSystemUpdateDriver()
if err != nil {
log.Fatalf("Failed to get system update driver: %v", err)
}
outdated, err := systemDriver.ImageOutdated()
if err != nil {
log.Fatalf("Cannot determine if image is outdated: %v", err)
}
Expand Down
50 changes: 28 additions & 22 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ type Failure struct {
}

func Update(cmd *cobra.Command, args []string) {
outdated, err := drv.IsImageOutdated()
systemDriver, err := drv.GetSystemUpdateDriver()
if err != nil {
log.Fatalf("Failed to get system update driver")
}
outdated, err := systemDriver.ImageOutdated()
if err != nil {
log.Fatalf("Unable to determine if image is outdated: %v", err)
}
Expand Down Expand Up @@ -48,17 +52,19 @@ func Update(cmd *cobra.Command, args []string) {
log.Fatalf("Failed to list users")
}

// Check if bootc update is available
updateAvailable, err := drv.CheckForImageUpdate()
// Check if system update is available
log.Printf("Checking for system updates (%s)", systemDriver.Name)
updateAvailable, err := systemDriver.UpdateAvailable()
// ignore error on dry run
if err != nil && !dryRun {
log.Fatalf("Failed to check for image updates: %v", err)
}
// don't update bootc if there's a dry run
log.Printf("System updates available: %t (%s)", updateAvailable, systemDriver.Name)
// don't update system if there's a dry run
updateAvailable = updateAvailable && !dryRun
bootcUpdate := 0
systemUpdate := 0
if updateAvailable {
bootcUpdate = 1
systemUpdate = 1
}

// Check if brew is installed
Expand All @@ -68,25 +74,25 @@ func Update(cmd *cobra.Command, args []string) {
brewUpdate = 1
}

totalUpdates := brewUpdate + bootcUpdate + 1 + len(users) + 1 + len(users) // Bootc + Brew + Flatpak (users + root) + Distrobox (users + root)
currentUpdate := 0
totalSteps := brewUpdate + systemUpdate + 1 + len(users) + 1 + len(users) // system + Brew + Flatpak (users + root) + Distrobox (users + root)
currentStep := 0
failures := make(map[string]Failure)

if updateAvailable {
currentUpdate++
log.Printf("[%d/%d] Updating System (Bootc)", currentUpdate, totalUpdates)
out, err := drv.BootcUpdate()
currentStep++
log.Printf("[%d/%d] Updating System (%s)", currentStep, totalSteps, systemDriver.Name)
out, err := systemDriver.Update()
if err != nil {
failures["Bootc"] = Failure{
failures[systemDriver.Name] = Failure{
err,
string(out),
}
}
}

if brewUpdate == 1 {
currentUpdate++
log.Printf("[%d/%d] Updating CLI Apps (Brew)", currentUpdate, totalUpdates)
currentStep++
log.Printf("[%d/%d] Updating CLI Apps (Brew)", currentStep, totalSteps)
out, err := drv.BrewUpdate(brewUid)
if err != nil {
failures["Brew"] = Failure{
Expand All @@ -97,8 +103,8 @@ func Update(cmd *cobra.Command, args []string) {
}

// Run flatpak updates
currentUpdate++
log.Printf("[%d/%d] Updating System Apps (Flatpak)", currentUpdate, totalUpdates)
currentStep++
log.Printf("[%d/%d] Updating System Apps (Flatpak)", currentStep, totalSteps)
flatpakCmd := exec.Command("/usr/bin/flatpak", "update", "-y")
out, err := flatpakCmd.CombinedOutput()
if err != nil {
Expand All @@ -108,8 +114,8 @@ func Update(cmd *cobra.Command, args []string) {
}
}
for _, user := range users {
currentUpdate++
log.Printf("[%d/%d] Updating Apps for User: %s (Flatpak)", currentUpdate, totalUpdates, user.Name)
currentStep++
log.Printf("[%d/%d] Updating Apps for User: %s (Flatpak)", currentStep, totalSteps, 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{
Expand All @@ -120,8 +126,8 @@ func Update(cmd *cobra.Command, args []string) {
}

// Run distrobox updates
currentUpdate++
log.Printf("[%d/%d] Updating System Distroboxes", currentUpdate, totalUpdates)
currentStep++
log.Printf("[%d/%d] Updating System Distroboxes", currentStep, totalSteps)
// distrobox doesn't support sudo, run with systemd-run
out, err = lib.RunUID(0, []string{"/usr/bin/distrobox", "upgrade", "-a"}, nil)
if err != nil {
Expand All @@ -131,8 +137,8 @@ func Update(cmd *cobra.Command, args []string) {
}
}
for _, user := range users {
currentUpdate++
log.Printf("[%d/%d] Updating Distroboxes for User: %s", currentUpdate, totalUpdates, user.Name)
currentStep++
log.Printf("[%d/%d] Updating Distroboxes for User: %s", currentStep, totalSteps, 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{
Expand Down
6 changes: 5 additions & 1 deletion cmd/updateCheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import (
)

func UpdateCheck(cmd *cobra.Command, args []string) {
update, err := drv.CheckForImageUpdate()
systemDriver, err := drv.GetSystemUpdateDriver()
if err != nil {
log.Fatalf("Failed to get system update driver: %v", err)
}
update, err := systemDriver.UpdateAvailable()
if err != nil {
log.Fatalf("Failed to check for updates: %v", err)
}
Expand Down
98 changes: 94 additions & 4 deletions drv/bootc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,43 @@ import (
"time"
)

// implementation of bootc and rpm-ostree commands (rpm-ostree support will be removed in the future)

type bootcStatus struct {
Status struct {
Booted struct {
Image struct {
Incompatible bool `json:"incompatible"`
Image struct {
Timestamp string `json:"timestamp"`
} `json:"image"`
} `json:"booted"`
Staged struct {
Incompatible bool `json:"incompatible"`
}
} `json:"status"`
}

func IsImageOutdated() (bool, error) {
type rpmOstreeStatus struct {
Deployments []struct {
Timestamp int64 `json:"timestamp"`
} `json:"deployments"`
}

func BootcCompat() (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
}
return !(status.Status.Booted.Incompatible || status.Status.Staged.Incompatible), nil
}

func IsBootcImageOutdated() (bool, error) {
cmd := exec.Command("bootc", "status", "--format=json")
out, err := cmd.CombinedOutput()
if err != nil {
Expand All @@ -32,7 +58,7 @@ func IsImageOutdated() (bool, error) {
if err != nil {
return false, nil
}
oneMonthAgo := time.Now().AddDate(0, -1, 0)
oneMonthAgo := time.Now().UTC().AddDate(0, -1, 0)

return timestamp.Before(oneMonthAgo), nil
}
Expand All @@ -46,11 +72,75 @@ func BootcUpdate() ([]byte, error) {
return out, nil
}

func CheckForImageUpdate() (bool, error) {
func CheckForBootcImageUpdate() (bool, error) {
cmd := exec.Command("/usr/bin/bootc", "upgrade", "--check")
out, err := cmd.CombinedOutput()
if err != nil {
return true, err
}
return !strings.Contains(string(out), "No changes in:"), nil
}

func IsRpmOstreeImageOutdated() (bool, error) {
cmd := exec.Command("rpm-ostree", "status", "--json", "--booted")
out, err := cmd.CombinedOutput()
if err != nil {
return false, nil
}
var status rpmOstreeStatus
err = json.Unmarshal(out, &status)
if err != nil {
return false, nil
}
timestamp := time.Unix(status.Deployments[0].Timestamp, 0).UTC()
oneMonthAgo := time.Now().AddDate(0, -1, 0)

return timestamp.Before(oneMonthAgo), nil
}

func RpmOstreeUpdate() ([]byte, error) {
cmd := exec.Command("/usr/bin/rpm-ostree", "upgrade")
out, err := cmd.CombinedOutput()
if err != nil {
return out, err
}
return out, nil
}

func CheckForRpmOstreeImageUpdate() (bool, error) {
cmd := exec.Command("/usr/bin/rpm-ostree", "upgrade", "--check")
out, err := cmd.CombinedOutput()
if err != nil {
return true, err
}
return strings.Contains(string(out), "AvailableUpdate"), nil
}

// Generalize bootc and rpm-ostree drivers into this struct as system updaters
type SystemUpdateDriver struct {
ImageOutdated func() (bool, error)
Update func() ([]byte, error)
UpdateAvailable func() (bool, error)
Name string
}

func GetSystemUpdateDriver() (SystemUpdateDriver, error) {
useBootc, err := BootcCompat()
if err != nil {
return SystemUpdateDriver{}, err
}
if useBootc {
return SystemUpdateDriver{
IsBootcImageOutdated,
BootcUpdate,
CheckForBootcImageUpdate,
"Bootc",
}, nil
}
return SystemUpdateDriver{
IsRpmOstreeImageOutdated,
RpmOstreeUpdate,
CheckForRpmOstreeImageUpdate,
"rpm-ostree",
}, nil
}
1 change: 0 additions & 1 deletion drv/brew.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package drv
import (
"fmt"
"github.com/gerblesh/ublue-upd/lib"
"log"
"os"
"syscall"
)
Expand Down
2 changes: 1 addition & 1 deletion ublue-upd.service
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Description=Universal Blue Update Oneshot Service

[Service]
Type=oneshot
ExecStart=/usr/bin/ublue-update
ExecStart=/usr/bin/ublue-upd -c

0 comments on commit 4b515cb

Please sign in to comment.