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:
das
2026-10-19 18:24:39 +03:00
co-authored by Claude Opus 5.5
parent 3b4527990e
commit d1d76c2bef
40 changed files with 2529 additions and 17 deletions
@@ -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")
}