Stage 5: das-update and das-welcome (0.3.0)
das-tools (Go, GTK 3 via gotk3): - das-update: update window, tray icon when updates are available, --cli mode; xbps-install -Su between a snapper pre/post snapshot pair, privileged part through pkexec, rollback and reboot hints - das-welcome: first-login window with install (live), language, home folder renaming, updates, settings and help das-settings 0.3.0: create home folders with translated names. xdg-user-dirs 0.20 only looks for translations in the first XDG_DATA_DIRS entry with a locale directory (/usr/local/share/locale). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
// Package update runs a full system update wrapped in a snapper pre/post
|
||||
// snapshot pair, so a broken update can be rolled back from the boot menu.
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"dasos/tools/internal/i18n"
|
||||
"dasos/tools/internal/snapper"
|
||||
"dasos/tools/internal/xbps"
|
||||
)
|
||||
|
||||
// Description marks the snapshots in "snapper list".
|
||||
const Description = "das-update"
|
||||
|
||||
// Reporter receives the progress of Apply.
|
||||
type Reporter interface {
|
||||
Step(title string)
|
||||
// Output receives the output of the commands Apply runs.
|
||||
Output() io.Writer
|
||||
// Snapshot is called once the "pre" snapshot exists.
|
||||
Snapshot(n int)
|
||||
}
|
||||
|
||||
// Result describes a finished update.
|
||||
type Result struct {
|
||||
Updates []xbps.Update
|
||||
Snapshot int // number of the "pre" snapshot, 0 without snapshots
|
||||
Reboot bool // the kernel or core libraries changed
|
||||
}
|
||||
|
||||
// Updater holds the system operations, replaced in tests.
|
||||
type Updater struct {
|
||||
Run func(ctx context.Context, out io.Writer, name string, args ...string) error
|
||||
Pending func(ctx context.Context) ([]xbps.Update, error)
|
||||
Snapshots func() bool
|
||||
Pre func(ctx context.Context, description string) (int, error)
|
||||
Post func(ctx context.Context, pre int, description string) error
|
||||
}
|
||||
|
||||
// System returns the Updater for the running system. It needs root.
|
||||
func System() *Updater {
|
||||
return &Updater{
|
||||
Run: run,
|
||||
Pending: pending,
|
||||
Snapshots: snapper.Available,
|
||||
Pre: snapper.Pre,
|
||||
Post: snapper.Post,
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context, out io.Writer, name string, args ...string) error {
|
||||
cmd := exec.CommandContext(ctx, name, args...)
|
||||
cmd.Stdout = out
|
||||
cmd.Stderr = out
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("%s: %w", name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pending lists updates against the index synced by the first step.
|
||||
func pending(ctx context.Context) ([]xbps.Update, error) {
|
||||
out, err := exec.CommandContext(ctx, "xbps-install", "-un").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("xbps-install -un: %w: %s", err, out)
|
||||
}
|
||||
return xbps.ParseDryRun(string(out)), nil
|
||||
}
|
||||
|
||||
// Apply updates the whole system. Without pending updates it does nothing
|
||||
// and returns an empty Result. When snapshots are set up, a failure to take
|
||||
// the "pre" snapshot stops the update: updating without a way back is
|
||||
// exactly what das-update exists to prevent.
|
||||
func (u *Updater) Apply(ctx context.Context, r Reporter) (Result, error) {
|
||||
var res Result
|
||||
|
||||
r.Step(i18n.T("update.step.sync"))
|
||||
if err := u.Run(ctx, r.Output(), "xbps-install", "-S"); err != nil {
|
||||
return res, err
|
||||
}
|
||||
updates, err := u.Pending(ctx)
|
||||
if err != nil {
|
||||
// An outdated xbps refuses to plan the update; it is updated first anyway.
|
||||
updates = []xbps.Update{{Name: "xbps", Action: "update"}}
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return res, nil
|
||||
}
|
||||
res.Updates = updates
|
||||
res.Reboot = xbps.NeedsReboot(updates)
|
||||
|
||||
if u.Snapshots() {
|
||||
r.Step(i18n.T("update.step.pre"))
|
||||
n, err := u.Pre(ctx, Description)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.Snapshot = n
|
||||
r.Snapshot(n)
|
||||
} else {
|
||||
r.Step(i18n.T("update.nosnapshots"))
|
||||
}
|
||||
|
||||
err = u.install(ctx, r, updates)
|
||||
if res.Snapshot > 0 {
|
||||
// Taken after a failure too: the pair shows what the update changed.
|
||||
r.Step(i18n.T("update.step.post"))
|
||||
if perr := u.Post(ctx, res.Snapshot, Description); perr != nil && err == nil {
|
||||
err = perr
|
||||
}
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (u *Updater) install(ctx context.Context, r Reporter, updates []xbps.Update) error {
|
||||
if xbps.SelfUpdate(updates) {
|
||||
r.Step(i18n.T("update.step.xbps"))
|
||||
if err := u.Run(ctx, r.Output(), "xbps-install", "-yu", "xbps"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
r.Step(i18n.T("update.step.install"))
|
||||
return u.Run(ctx, r.Output(), "xbps-install", "-yu")
|
||||
}
|
||||
Reference in New Issue
Block a user