Skip to content

Latest commit

 

History

History
272 lines (204 loc) · 10.7 KB

File metadata and controls

272 lines (204 loc) · 10.7 KB

rust-linux-utils — Crate Scopes

Each crate has a single, clear focus. Cross-crate concerns (e.g. networking

  • firewall) live in the consuming application, not here.

Primary target hardware: Raspberry Pi CM4 on Ubuntu / Raspberry Pi OS. APIs aim to work on any Linux but are tuned for that deployment shape.


fs-bundle (existing)

Generic filesystem toolkit: tar+zstd pack/unpack, file/dir CRUD, declarative directory scaffolding, POSIX permission helpers. No domain knowledge.

net-info (existing)

Linux host network introspection (interfaces, IPs, MAC, MTU, IO counters, gateway, DNS, hostname, public IP) plus a Configurator trait with three backends: NetworkManager (nmcli), netplan, ifupdown.

systemd-units (existing)

Async wrapper around zbus_systemd for managing systemd units — start/stop/restart/reload/enable/disable/list/status. Pure-Rust D-Bus client (no libsystemd-dev). System bus and user session bus.

ufw (existing)

Manage Ubuntu's UFW firewall via the ufw CLI. Typed Rule builder, parsed Status snapshot, lifecycle (enable/disable/reload/reset), defaults, logging. UFW has no library API — shell-out is the only path.


pi-host (new)

Purpose. Read-only Raspberry Pi hardware introspection — model, revision, serial, firmware, thermal/throttling state, SoC clocks and voltages.

API surface.

  • model(), revision(), serial(), cpuinfo_summary()
  • soc_temp_c() -> f32
  • throttling() -> Throttling { under_voltage_now, freq_capped_now, throttled_now, soft_temp_limit_now, *_since_boot }
  • clocks() -> Clocks { arm_hz, core_hz, h264_hz, ... }
  • voltages() -> Voltages { core_v, sdram_c_v, sdram_i_v, sdram_p_v }
  • firmware_version() -> String

Underlying sources.

  • /proc/cpuinfo, /proc/device-tree/model, /proc/device-tree/serial-number
  • /sys/class/thermal/thermal_zone0/temp
  • vcgencmd shell-out for throttling, clocks, voltages, firmware

External crates. None — all data is in /proc, /sys, or vcgencmd output. Wrapping them adds dependency weight without payoff.

Out of scope. Hardware control (GPIO → gpio crate), generic CPU/mem stats (sysinfo), kernel logs (journald).


watchdog (new)

Purpose. Talk to the Linux watchdog driver (/dev/watchdog, /dev/watchdog0, ...) — set timeout, kick (keepalive), graceful close. On Pi this drives the BCM2835 hardware watchdog.

API surface.

  • Watchdog::open(path) / Watchdog::default() (opens /dev/watchdog)
  • set_timeout(secs: u32) -> Result<u32> — returns the value the kernel actually accepted (drivers clamp)
  • get_timeout() -> Result<u32>
  • keepalive() -> Result<()> — kick the dog
  • time_left() -> Result<u32>
  • info() -> Result<WatchdogInfo> — driver name, firmware version, options
  • magic_close() consumes the handle and writes 'V' so the kernel disables the watchdog instead of rebooting on close

Underlying sources. linux/watchdog.h ioctls (WDIOC_KEEPALIVE, WDIOC_SETTIMEOUT, WDIOC_GETTIMEOUT, WDIOC_GETTIMELEFT, WDIOC_GETSUPPORT).

External crates. nix for ergonomic, type-safe ioctl wrappers instead of raw libc::ioctl.

Out of scope. Software watchdog (systemd has WatchdogSec=), pet-it- from-a-thread daemonisation (the consumer wires that up).


storage (new)

Purpose. Disk and mount introspection — what's mounted where, how much space, what kind of device backs it (eMMC vs SD vs USB vs NVMe).

API surface.

  • mounts() -> Vec<Mount> — parsed /proc/mounts
  • disk_usage(path: &Path) -> DiskUsage { total, used, free, available } via statvfs
  • block_devices() -> Vec<BlockDevice> — name, size, rotational, removable, model, kind (Emmc/Sd/Usb/Nvme/Scsi/Other)
  • device_for_mount(mount: &Mount) -> Option<BlockDevice>

Underlying sources. /proc/mounts, /sys/block/<dev>/{size, removable, queue/rotational, device/type, device/model}, statvfs(2).

External crates. nix for statvfs. Avoiding sysinfo — overkill, drags in everything.

Out of scope. Mount/unmount (mount(2) requires root and is rarely useful from a service), partitioning, formatting, smartctl health.


gpio (new)

Purpose. GPIO, PWM, I²C, SPI, and UART access on the Pi. Thin re-export of rppal with a few ergonomic shortcuts and Pi-pin helpers.

API surface.

  • Re-export rppal::gpio, rppal::pwm, rppal::i2c, rppal::spi, rppal::uart under stable module names
  • pi_pin(bcm: u8) -> Result<rppal::gpio::Pin> — convenience wrapper
  • Re-export rppal::system::DeviceInfo so callers can branch on Pi model

External crates. rppal (the standard pure-Rust Pi peripheral crate). No system-lib dependency, builds anywhere.

Out of scope. High-level sensor drivers (DHT22, BME280, etc.) — those belong in their own crates that depend on embedded-hal traits, which rppal already implements. We don't reinvent that.


journald (new)

Purpose. Read systemd journal entries — filter by unit, priority, boot, time range; one-shot fetch or follow (tail).

API surface.

  • JournalReader builder: .unit("ssh.service"), .priority(Priority::Warning), .since(SystemTime), .boot(BootRef::Current), .lines(N)
  • .fetch() -> Result<Vec<Entry>> — one-shot
  • .follow() -> impl Stream<Item = Result<Entry>> — tail, async
  • Entry { timestamp, hostname, unit, syslog_identifier, priority, pid, message, fields: HashMap<String, String> }

Underlying source. Shell-out to journalctl --output=json. Parses each line as a JSON object. Same rationale as nmcli and ufw: keeps the dep tree clean, avoids needing libsystemd-dev at build time, and the output format is documented and stable.

External crates. serde, serde_json for line parsing. tokio for the async follow stream.

Out of scope. Writing to the journal — use tracing-journald or systemd-journal-logger directly from your app.


time-sync (new)

Purpose. Inspect and toggle system time-sync state — NTP enabled, synchronised, current timezone, RTC mode, system vs RTC time. Important on Pi because it has no built-in RTC and many CM4 carriers add one.

API surface.

  • TimeSync::system().await -> Result<TimeSync>
  • status() -> Result<TimeStatus { ntp_enabled, ntp_synchronized, timezone, local_rtc, time, rtc_time }>
  • set_ntp(enabled: bool) -> Result<()>
  • set_timezone(tz: &str) -> Result<()>
  • set_local_rtc(local: bool, adjust_system: bool) -> Result<()>

External crates. Reuses the zbus / zbus_systemd we already pulled in for systemd-units, with the timedate1 feature added. Async, pure D-Bus to systemd-timedated.

Out of scope. Choosing or configuring an NTP daemon (chrony / ntpsec / systemd-timesyncd) — that's deployment policy.


net-scan (new)

Purpose. Discover hosts on a network by TCP connect. Expand a target spec across a port spec, connect with bounded concurrency, report what answered. The motivating case is finding product devices on a LAN — but what counts as "yours" is a caller-supplied probe, not crate knowledge.

API surface.

  • parse_targets(&str) -> Result<Vec<Ipv4Addr>>10.0.0.5, 10.0.0.0/24, 10.0.0.5-40, 10.0.0.5-10.0.0.40, comma-mixed.
  • parse_ports(&str) -> Result<Vec<u16>>8099, 80,443, 8000-8100.
  • Scanner::new(targets, ports)? + .timeout() / .concurrency()? / .probe(); then .run().await -> Vec<Host> or .stream() for results as they land.
  • trait Probe — inspect an open socket, return Some(banner) to keep it or None to drop it. NoProbe (keep everything) and HttpProbe (GET <path>, require substrings) ship with the crate.

External crates. tokio for connect/timeout, futures for the bounded FuturesUnordered + Semaphore fan-out. No raw-socket or pcap dependency, so no root needed.

Out of scope. SYN/stealth scanning (raw sockets, root), UDP probing, service-version databases, OS fingerprinting, ARP sweeps — that is nmap's job. IPv4 only; sweeping an IPv6 subnet is not meaningful. TLS is not spoken, so HttpProbe is plaintext-only.


machine-id (new)

Purpose. Answer "which machine is this?" — and be honest about how much the answer can be trusted. No identifier is simultaneously universal, stable, and unique across Linux/Windows/macOS and x86/ARM, so this crate gathers the candidates that exist and tags each with a Confidence. The failure that motivates it is silent: an OS machine-id is baked into a disk image, so a whole fleet flashed from one image reports the same value.

API surface.

  • resolve(path) -> Result<MachineId> — best available identity, preferring one that survives fleet imaging. Never returns a ProvisionedOs id.
  • report(path) -> IdReport — every identifier this host can produce. Infallible; does not create a node id.
  • os::os_machine_id(), hardware::pi_serial(), node::node_id(path), node::read(path) for the individual sources.
  • Confidence::{Hardware, Assigned, ProvisionedOs} + Confidence::clonable().

External crates. machine-uid (zero deps on Linux; windows-sys + windows-registry on Windows, libc on macOS/illumos) for the OS id, uuid for the assigned node id. No root needed by any path.

Platform note. The one crate here that is not Linux-only — machine-uid covers Windows and macOS, node is pure filesystem, and hardware returns None off-Linux rather than failing to compile.

Out of scope. MAC addresses (randomised per-SSID, spoofable, several per host — net-info exposes them if wanted), DMI/SMBIOS UUIDs (root-only, absent on ARM, often zeroed or batch-duplicated), disk serials (identify a disk, not a machine), and hashing several weak signals into one fingerprint — any component changing silently changes the identity.


Cross-crate guidelines

  • No domain knowledge. A crate here knows about Linux primitives, not about agents/extensions/products.
  • Linux-only. No cfg(windows) or macOS branches; this is a CM4 target. Cross-platform crates (e.g. sysinfo, netdev) are fine as dependencies because they handle Linux gracefully.
  • Sync where it can be, async where the upstream forces it. Read-only filesystem and /proc access stays sync. D-Bus (systemd-units, time-sync) is async because zbus is async. Journal follow() is async because tailing is.
  • Shell-out is fine when the canonical interface is a CLI (ufw, nmcli, vcgencmd, journalctl). It's clearer, more debuggable, and avoids dragging in heavy bindings.
  • Errors are typed per-crate (UfwError, WatchdogError, ...). No shared error crate — each one's failure modes are different and callers usually handle one at a time.