Files
mesh-catalog/modules/mounts/cmd/mounts/machine.go
T
jochen 2b1b9b0235 Let mounts write each share's mount and automount, so no node depends on a hand-written fstab line (hq ADR 0263)
A machine mounts what its assignment names: a node's share from the
provision nfs-share, whose path it reads from nfs-server's state on the
bus, and occasional sources (an SMB device, a disk) whose absence is a
state, not a fault. apply writes and arms the unit pairs as root; watch
tries each source and judges the health; the operator's fstab lines are
taken over only through adopt, on their word, once the replacement is
armed. SMB logins are an outside party's secret, given with secret accept.
2026-10-08 18:36:01 +02:00

119 lines
4.2 KiB
Go

package main
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
// CommandTimeout bounds every command this module runs. No command it runs touches a mounted
// filesystem, so none should block on a server that is gone; the bound is there in case one does.
const CommandTimeout = 5 * time.Second
// Runner runs one command and answers its standard output. It is injected so that the tools are tested
// against recorded answers rather than this machine's service manager.
type Runner func(ctx context.Context, name string, args ...string) (string, error)
// ExecRunner runs a command on the machine, bounded by CommandTimeout.
func ExecRunner(ctx context.Context, name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, CommandTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &stdout, &stderr
if err := cmd.Run(); err != nil {
said := strings.TrimSpace(stderr.String())
if said != "" {
return stdout.String(), fmt.Errorf("%s: %w: %s", name, err, said)
}
return stdout.String(), fmt.Errorf("%s: %w", name, err)
}
return stdout.String(), nil
}
// Machine is what the module reads: a filesystem root (the real one, or a test's tree standing in for
// /etc, /proc and /var/lib), a way to run a command, and a way to look a command up.
type Machine struct {
Root string
Run Runner
LookPath func(name string) bool
// Escalate runs a command as root: as given where this process is root, else through `sudo -n`, as the
// bundle runs as the operator account (novox/hq ADR 0175 §4) and the sudo module lets it.
Escalate Runner
}
// Here is the machine this process runs on.
func Here() *Machine {
return &Machine{Root: "/", Run: ExecRunner, Escalate: EscalatedRunner,
LookPath: func(n string) bool { _, err := exec.LookPath(n); return err == nil }}
}
// read answers one file's text under the root. Only regular files the module names are read; a
// mountpoint is never opened, so a network filesystem whose server is gone cannot block a call.
func (m *Machine) read(path string) (string, error) {
b, err := os.ReadFile(filepath.Join(m.Root, path))
return string(b), err
}
// glob answers the files under the root matching a pattern, as paths seen from the machine's root.
func (m *Machine) glob(pattern string) []string {
found, _ := filepath.Glob(filepath.Join(m.Root, pattern))
out := make([]string, 0, len(found))
for _, f := range found {
rel, err := filepath.Rel(m.Root, f)
if err != nil {
continue
}
out = append(out, "/"+filepath.ToSlash(rel))
}
return out
}
// unreadable says why a file could not be read, in the module's words.
func unreadable(path string, err error) string {
switch {
case os.IsNotExist(err):
return path + ": not present"
case os.IsPermission(err):
return path + ": not readable by the tool runner's account"
default:
return fmt.Sprintf("%s: not read (%v)", path, err)
}
}
// EscalatedRunner runs a command as root, through sudo without a prompt where this process is not root.
// A refusal by sudo is said as one, never read as an empty answer.
func EscalatedRunner(ctx context.Context, name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, 25*time.Second)
defer cancel()
program, argv := name, args
if os.Getuid() != 0 {
program, argv = "sudo", append([]string{"-n", name}, args...)
}
cmd := exec.CommandContext(ctx, program, argv...)
var stdout, stderr bytes.Buffer
cmd.Stdout, cmd.Stderr = &stdout, &stderr
if err := cmd.Run(); err != nil {
said := strings.TrimSpace(stderr.String())
if program == "sudo" && strings.HasPrefix(said, "sudo:") {
return stdout.String(), fmt.Errorf("%s needs root, and the tool runner's account may not run it without a prompt: %s", name, said)
}
if said != "" {
return stdout.String(), fmt.Errorf("%s: %w: %s", name, err, said)
}
return stdout.String(), fmt.Errorf("%s: %w", name, err)
}
return stdout.String(), nil
}
// present says whether a device or a file is there. Never used on a mount point.
func (m *Machine) present(p string) bool {
_, err := os.Stat(filepath.Join(m.Root, p))
return err == nil
}