Stage 6: server image with a console installer (0.4.0)

- server/ profile and build-iso.sh [desktop|server]; make iso-server,
  make qemu-server, run-qemu.sh --serial; login on tty and ttyS0
- das-server: SSH, chrony, NetworkManager/dhcpcd, Docker with Compose,
  nftables (closed except SSH, own table), fail2ban reading socklog,
  sshd without root passwords, Docker log rotation, tools
- das-server-install: dialog installer in Ukrainian and English, or
  unattended with --config; GPT + Btrfs subvolumes, GRUB for UEFI and BIOS,
  serial console, SSH key with optional keys-only logins
- das-install-common: mount-source and postinstall shared with Calamares
- dsv: runit service manager (list, enable, disable, start, stop,
  restart, status, logs) with bash completion, also on the desktop
- das-update split into a subpackage without GTK; the window and tray
  icon move to /usr/libexec/das/das-update-gui
- das-snapper-setup enables dbus, which snapper needs
- TODO: ISO size review with measured numbers and options

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
das
2026-10-19 21:28:26 +03:00
co-authored by Claude Opus 5.5
parent 5349a028c8
commit eba4516998
42 changed files with 1454 additions and 114 deletions
@@ -1,10 +1,13 @@
// Command das-update updates Das OS with a snapper snapshot taken before
// the update, so a broken update can be rolled back from the boot menu.
//
// das-update window that lists and installs updates
// das-update the update window on a desktop, otherwise the same as --cli
// das-update --tray background check, shows a tray icon when updates are available
// das-update --cli the same in a terminal (servers, ssh); -y skips the question
// das-update --cli update in a terminal (servers, ssh); -y skips the question
// das-update --apply the privileged part, started by the window through pkexec
//
// The window and the tray icon are a separate program from das-tools
// (guiPath), so this one has no GTK dependency.
package main
import (
@@ -42,27 +45,42 @@ func main() {
case *cli:
os.Exit(runCLI(*yes))
case *tray:
os.Exit(runTray())
// Nothing to show without the desktop part
if haveGUI() {
execGUI()
}
default:
os.Exit(runWindow())
if haveGUI() && haveDisplay() {
execGUI()
}
os.Exit(runCLI(*yes))
}
}
// Machine-readable lines that --apply prints for the window, among the
// plain output of xbps.
const (
markStep = "@@step "
markSnapshot = "@@snapshot "
markReboot = "@@reboot"
markUpToDate = "@@uptodate"
markError = "@@error "
)
// guiPath is the window and tray icon program from das-tools.
const guiPath = "/usr/libexec/das/das-update-gui"
func haveGUI() bool {
_, err := os.Stat(guiPath)
return err == nil
}
func haveDisplay() bool {
return os.Getenv("DISPLAY") != "" || os.Getenv("WAYLAND_DISPLAY") != ""
}
// execGUI replaces this process with the graphical part, passing the
// arguments on. It returns only when that fails.
func execGUI() {
err := syscall.Exec(guiPath, append([]string{guiPath}, os.Args[1:]...), os.Environ())
fmt.Fprintln(os.Stderr, "das-update:", err)
}
type markReporter struct{ w io.Writer }
func (r markReporter) Step(title string) { fmt.Fprintln(r.w, markStep+title) }
func (r markReporter) Step(title string) { fmt.Fprintln(r.w, update.MarkStep+title) }
func (r markReporter) Output() io.Writer { return r.w }
func (r markReporter) Snapshot(n int) { fmt.Fprintf(r.w, "%s%d\n", markSnapshot, n) }
func (r markReporter) Snapshot(n int) { fmt.Fprintf(r.w, "%s%d\n", update.MarkSnapshot, n) }
func runApply() int {
if os.Geteuid() != 0 {
@@ -74,13 +92,13 @@ func runApply() int {
r := markReporter{os.Stdout}
res, err := update.System().Apply(context.Background(), r)
if err != nil {
fmt.Println(markError + err.Error())
fmt.Println(update.MarkError + err.Error())
return 1
}
if len(res.Updates) == 0 {
fmt.Println(markUpToDate)
fmt.Println(update.MarkUpToDate)
} else if res.Reboot {
fmt.Println(markReboot)
fmt.Println(update.MarkReboot)
}
return 0
}