Two questions that had been answered by one capability. graphical-session asks whether a session is running now; seat asks whether one could ever run here. Assignment needs the second -- a headless server can never have a display server, a workstation with nothing installed yet can, and until now those looked identical. The mesh would have assigned xorg to the server and found out at apply time. "seat" rather than "display", and the distinction matters most on a phone. An Android device plainly has a screen and has no seat: nothing there is going to take a DRM device and present an X or Wayland session on it. A capability called display would answer yes and be useless. This one answers no, which is true and useful. Read from what the kernel reports about its connectors, which distinguishes the two ways of not having one: a machine with a graphics card and nothing plugged in is a different thing from a machine with no graphics at all, and somebody deciding where a desktop goes wants to know which they are looking at. Verified against this workstation: it reports card1-DP-1 and card1-DP-2, which are the two monitors actually connected, and ignores the DisplayPort and HDMI that are not -- and the writeback connector reporting "unknown", which counting would have handed a seat to machines that have none.
211 lines
7.5 KiB
Go
211 lines
7.5 KiB
Go
package profile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Named capabilities. Constants rather than strings at the call site, because a capability
|
|
// nothing declares is a capability nothing can require, and a typo would produce exactly that.
|
|
const (
|
|
CapContainerRuntime = "container-runtime"
|
|
CapPackageManager = "package-manager"
|
|
CapServiceManager = "service-manager"
|
|
CapFirewall = "firewall"
|
|
CapOverlay = "overlay"
|
|
CapGraphicalSession = "graphical-session"
|
|
// CapSeat is hardware: somewhere a display server COULD run. CapGraphicalSession above is
|
|
// state: whether one IS running. Assignment needs the first.
|
|
CapSeat = "seat"
|
|
CapPrivileged = "privileged"
|
|
)
|
|
|
|
// commandCapability is the shape most detectors take: run something, and treat a working
|
|
// invocation as evidence.
|
|
//
|
|
// It runs a command that only succeeds if the thing is FUNCTIONING, never `--version` alone.
|
|
// A version string proves a binary is on disk, which is the assumption 04-ISSUES/007 records
|
|
// as false: the package was installed and the daemon was not running.
|
|
type commandCapability struct {
|
|
name string
|
|
command string
|
|
args []string
|
|
// why describes what a success actually proves, and is reported as the detector's `How`.
|
|
why string
|
|
// interpret decides the verdict from what the command said and how it exited.
|
|
//
|
|
// Exists because "exit zero" is not a universal answer. A degraded service manager reports
|
|
// its state on stdout and exits non-zero — it is running, and reading only the exit code
|
|
// declared no service manager on a machine whose init it was. That is 04-ISSUES/007 in the
|
|
// mirror: 007 is installed-but-broken reported present; this is working-but-imperfect
|
|
// reported absent. Both place work wrongly, and this one was only visible by running
|
|
// against a real machine.
|
|
//
|
|
// nil means the ordinary rule: success is exit zero.
|
|
interpret func(stdout string, err error) (present bool, detail string)
|
|
runner Runner
|
|
}
|
|
|
|
func (c commandCapability) Name() string { return c.name }
|
|
|
|
func (c commandCapability) Detect(ctx context.Context) Verdict {
|
|
out, err := c.runner(ctx, c.command, c.args...)
|
|
|
|
interpret := c.interpret
|
|
if interpret == nil {
|
|
interpret = exitZero
|
|
}
|
|
present, detail := interpret(out, err)
|
|
|
|
if strings.TrimSpace(detail) == "" {
|
|
// A verdict with no reason is the fault in a new place: something nobody can act on.
|
|
// Reached when a command fails silently, which systemctl does.
|
|
if present {
|
|
detail = "responded"
|
|
} else {
|
|
detail = fmt.Sprintf("%s gave no reason", c.command)
|
|
}
|
|
}
|
|
return Verdict{Name: c.name, Present: present, Detail: firstLine(detail), How: c.why}
|
|
}
|
|
|
|
// exitZero is the ordinary rule: the command worked, so the capability is there.
|
|
func exitZero(stdout string, err error) (bool, string) {
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
return true, stdout
|
|
}
|
|
|
|
// systemRunning reads what an init system says about itself rather than how it exited.
|
|
//
|
|
// `is-system-running` exits non-zero for every state except `running` — including `degraded`,
|
|
// which means units failed and the init is emphatically present. Treating that as absent made
|
|
// a machine running systemd report no service manager.
|
|
func systemRunning(stdout string, err error) (bool, string) {
|
|
state := strings.TrimSpace(firstLine(stdout))
|
|
switch state {
|
|
case "running", "degraded", "starting", "maintenance", "stopping":
|
|
return true, state
|
|
case "":
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
return false, "said nothing"
|
|
default:
|
|
// `offline` and `unknown` mean it is not managing this machine.
|
|
return false, state
|
|
}
|
|
}
|
|
|
|
func firstLine(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
s = s[:i]
|
|
}
|
|
if len(s) > 200 {
|
|
s = s[:200] + "…"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// privileged reports whether the host can change this machine at all.
|
|
//
|
|
// Reported as a capability rather than checked at startup on purpose: a host that cannot act
|
|
// is still a host that can report, and novox/hq ADR 0004 says what varies between nodes lives
|
|
// here rather than in the definition of a node.
|
|
type privileged struct{}
|
|
|
|
func (privileged) Name() string { return CapPrivileged }
|
|
|
|
func (privileged) Detect(context.Context) Verdict {
|
|
uid := os.Geteuid()
|
|
if uid == 0 {
|
|
return Verdict{
|
|
Name: CapPrivileged, Present: true,
|
|
Detail: "effective uid 0",
|
|
How: "effective uid — the host changes a machine, which needs root",
|
|
}
|
|
}
|
|
return Verdict{
|
|
Name: CapPrivileged, Present: false,
|
|
Detail: fmt.Sprintf("effective uid %d, not 0", uid),
|
|
How: "effective uid — the host changes a machine, which needs root",
|
|
}
|
|
}
|
|
|
|
// graphicalSession reports whether anything could display a window here.
|
|
//
|
|
// Environment rather than a probe, because a display server is reachable through a socket a
|
|
// detector would have to guess at, and the variables are what an application would use anyway.
|
|
// Stated so the limit is visible: this detects that a session is ADVERTISED, which is weaker
|
|
// than the other detectors here.
|
|
type graphicalSession struct{}
|
|
|
|
func (graphicalSession) Name() string { return CapGraphicalSession }
|
|
|
|
func (graphicalSession) Detect(context.Context) Verdict {
|
|
const how = "DISPLAY / WAYLAND_DISPLAY — weaker than the other checks: advertised, not probed"
|
|
if d := os.Getenv("WAYLAND_DISPLAY"); d != "" {
|
|
return Verdict{Name: CapGraphicalSession, Present: true, Detail: "wayland: " + d, How: how}
|
|
}
|
|
if d := os.Getenv("DISPLAY"); d != "" {
|
|
return Verdict{Name: CapGraphicalSession, Present: true, Detail: "x11: " + d, How: how}
|
|
}
|
|
return Verdict{
|
|
Name: CapGraphicalSession, Present: false,
|
|
Detail: "neither DISPLAY nor WAYLAND_DISPLAY is set",
|
|
How: how,
|
|
}
|
|
}
|
|
|
|
// Default returns the detectors the host runs when nobody says otherwise.
|
|
//
|
|
// Each command is chosen to prove the thing WORKS rather than exists:
|
|
// - the container runtime is asked for server-side information, which fails when the daemon
|
|
// is down even though the client is installed — the exact shape of 04-ISSUES/007;
|
|
// - the service manager is asked whether it is the running init, not whether it is present;
|
|
// - the firewall is asked to list a ruleset, which needs both the tool and the permission.
|
|
func Default(runner Runner) []Detector {
|
|
if runner == nil {
|
|
runner = ExecRunner
|
|
}
|
|
return []Detector{
|
|
privileged{},
|
|
graphicalSession{},
|
|
seat{},
|
|
commandCapability{
|
|
name: CapContainerRuntime, command: "docker", args: []string{"info", "--format", "{{.ServerVersion}}"},
|
|
why: "asks the daemon for its version — a running daemon, not an installed client",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapPackageManager, command: "pacman", args: []string{"-Q", "pacman"},
|
|
why: "queries the package database — a working database, not a binary on disk",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapServiceManager, command: "systemctl", args: []string{"is-system-running"},
|
|
why: "reads the init's own account of its state — degraded is still running",
|
|
interpret: systemRunning,
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapFirewall, command: "nft", args: []string{"list", "ruleset"},
|
|
why: "lists the ruleset — needs the tool AND the privilege to use it",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapOverlay, command: "wg", args: []string{"show", "interfaces"},
|
|
why: "asks the kernel for interfaces — needs the module, not just the tool",
|
|
runner: runner,
|
|
},
|
|
}
|
|
}
|
|
|
|
// isRoot is the same question `privileged` answers, exposed for tests that must check the
|
|
// detector against something other than itself.
|
|
func isRoot() bool { return os.Geteuid() == 0 }
|