Skip to main content

Serial Port

@shrug/serial-portv2026.07.28.1· 1mo agoMODELS
01README

USB-UART serial console + config management. The serial-port type is the raw console primitive (establish/send/read/exec/login) over a serial device (e.g. /dev/ttyUSB0); line config via stty(1), byte I/O via a direct Deno.open fd or a dd/cat subprocess fallback (auto), device paths allowlisted so login's vaulted password can't reach an arbitrary terminal. The serial-cfgmgmt/* types (node/exec/package/service) are the serial-console counterpart to SSH config management — for boards with no network — built on that same console primitive.

02Release Notes

Add uboot_setenv (bpi-console) for U-Boot env provisioning: setenv N vars (single-quoted so ${…} is stored literally and expanded by U-Boot at run time), optional saveenv to persist, and printenv read-back verification; writes a ubootEnv- resource and throws with the mismatches on failure. Solves BPI-F3 boot-from-NVMe (fdt_addr_r + bootcmd, saveenv). Fixes the step return to hand back writeResource's handle so the engine no longer schema-rejects a successful run. Adds no-op version-upgrade entries to all 7 models so existing instances migrate typeVersion. No globalArguments schema changes.

03Models7
@shrug/serial-portv2026.07.28.1serial_port.ts

Global Arguments

ArgumentTypeDescription
devicestringSerial device path, e.g. /dev/ttyUSB0.
baudnumberBaud rate. Default 115200.
framingstringData/parity/stop framing, e.g. 8N1 (8 data bits, no parity, 1 stop bit).
lineEndingstringLine terminator appended to sent lines. Default LF; use \\r for consoles that need carriage return.
transportenumByte-I/O transport: 'direct' (Deno.open raw fd), 'subprocess' (dd, needs only allow-run), or 'auto' (direct with subprocess fallback on a device-open permission denial).
username?stringDefault username for `login`.
password?stringDefault password for `login`, resolved from a vault reference. Never a literal.
fn establish()
Validate + configure the serial port (baud/framing/raw), probe it briefly, and record the `port` resource that later calls inherit config from. Fails loudly when the port is held by another process.
fn send(text: string, appendNewline: boolean, raw: boolean)
Write one line to the port (appends the line ending unless raw). Does not read a response.
ArgumentTypeDescription
textstringText to send.
appendNewlinebooleanAppend the configured line ending. Set false with `raw` to send exact bytes.
rawbooleanTreat `text` as an exact payload (implies appendNewline=false).
fn read()
Drain inbound bytes from the port until it goes idle or the cap is reached.
fn exec(command: string, prompt?: string, stripEcho: boolean)
Send a command line and capture the response until the prompt returns (if given) or the line goes idle. The console-shell primitive.
ArgumentTypeDescription
commandstringCommand line to send.
prompt?stringOptional regex; stop reading once the response's tail matches it (e.g. the shell prompt).
stripEchobooleanStrip the echoed command line from the captured output.
fn login(username?: string, promptAfter?: string)
Answer a getty login (login:/Password:) using the vaulted credential, then confirm the shell prompt. The password is never written to the recorded transcript.
ArgumentTypeDescription
username?stringUsername; defaults to the model's `username` global.
promptAfter?stringOptional regex for the shell prompt to expect after login (default matches a trailing $ , # , or > ).
fn uboot_setenv(vars: record, save: boolean, verify: boolean, prompt: string)
At the U-Boot prompt (`=> `, not a getty), set one or more environment variables with `setenv`, optionally `saveenv` to persist them, and read each back with `printenv` to verify. Values are single-quoted so spaces/`;`/`${…}` are stored literally (U-Boot expands `${…}` at run time). Idempotent and reusable across boards — pass board-specific paths (DTB, grub EFI, partitions) as the variable values. Run against a live U-Boot; not for a booted OS shell.
ArgumentTypeDescription
varsrecordU-Boot variables to set as name→value, applied in insertion order. Each becomes `setenv <name> '<value>'`. A value cannot contain a single quote or newline.
savebooleanRun `saveenv` after setting, persisting the env to the board's storage (eMMC/SPI).
verifybooleanRead each variable back with `printenv` and confirm the stored value matches. Robust even under capture (printenv output is newline-terminated).
promptstringRegex for the U-Boot prompt. Matching a no-newline prompt over a capture session is best-effort; verification does not rely on it.
fn session_start(capture: boolean, captureMaxBytes: number)
Start a persistent session holder: a detached `socat` opens the port once and bridges it to a PTY, so the logged-in shell survives across separate method runs. Later send/read/exec/login (and serial-cfgmgmt) calls automatically attach to the holder. With capture=true, also spawn a drainer that appends ALL console bytes to an on-disk ring, capturing output emitted while no client is attached (async printk, panic traces); read it with capture_read. Idempotency: errors if one is already live — stop
ArgumentTypeDescription
capturebooleanAlso spawn a drainer that records all console bytes to a ring (read with capture_read). Note: while capturing, exec/login read responses via the ring and reliably return command OUTPUT, but matching the bare shell prompt is best-effort (a no-newline prompt flushes to a serial console only on the next write) — prefer a newline-terminated command-emitted sentinel and generous idleMs. The async/unattended capture path (capture_read) is unaffected.
captureMaxBytesnumberRotate when the CURRENT ring file crosses this many bytes (append + rotate-on-restart; the retained .1+current window is up to ~2x this).
fn session_stop(keepCapture: boolean)
Stop the persistent session holder on the port (SIGTERM the capture drainer if any, then the socat holder, remove the PTY link). Removes the capture ring unless keepCapture=true. Safe to call when none is running.
ArgumentTypeDescription
keepCapturebooleanKeep the on-disk capture ring (and its .1) instead of removing it.
fn session_status()
Report whether a persistent session holder is running on the port. Recomputes liveness from the holder pid + PTY link (a dead holder reads as not-live and I/O methods fall back to open/close).
fn capture_read(sinceOffset?: number, maxBytes: number)
Return console bytes captured to the ring since an offset and advance the saved cursor. Serial-port-instance-only: the offset thresholds live in the `session` resource, which a serial-cfgmgmt/* instance cannot read. Output `data` is base64 (console bytes are binary). Pages: pass sinceOffset / read nextOffset; maxBytes never returns the whole ring. Bounds the ring by rotating when it crosses captureMaxBytes.
ArgumentTypeDescription
sinceOffset?numberStream offset to read from; defaults to the saved cursor. An explicit value overrides the cursor for re-read/seek.
maxBytesnumberMax bytes to return; page the rest with nextOffset.

Resources

port(infinite)— The configured serial port and its probe capture.
sent(infinite)— Record of a send() write.
captured(infinite)— Output captured by read().
execResult(infinite)— Result of an exec() command/response round-trip.
loginResult(infinite)— Result of a login() attempt (password scrubbed).
session(infinite)— A persistent socat session holder for the port (pid + PTY link).
captureRead(infinite)— Result of a capture_read (base64 bytes + offset cursor).
ubootEnv(infinite)— Result of a uboot_setenv run (vars set, saveenv, and read-back verification).
@shrug/serial-cfgmgmt/nodev2026.07.28.1serial_cfgmgmt_node.ts
fn gather()
Log in over the serial console (if credentials are set) and gather system facts (hostname, OS, arch, kernel, package managers). Writes the `info` resource.

Resources

info(infinite)— System facts gathered from the node over the serial console.
@shrug/serial-cfgmgmt/execv2026.07.28.1serial_cfgmgmt_exec.ts
fn run(command: string)
Run a single command line over the serial console and record its stdout and exit code.
ArgumentTypeDescription
commandstringCommand line to run.

Resources

result(infinite)— Result of a command run over the serial console.
@shrug/serial-cfgmgmt/packagev2026.07.28.1serial_cfgmgmt_package.ts

Global Arguments

ArgumentTypeDescription
becomebooleanEscalate privilege for every mutating command (install, upgrade) and the reboot probe (the login user is unprivileged, e.g. a `fedora` serial console with sudo). Leave false when the session is already root.
becomeMethodenumPrivilege-escalation command used when `become` is true. `sudo` runs as `sudo -n` (non-interactive).
fn query(name: string)
Check whether a package is installed on the target (read-only) and record its version.
ArgumentTypeDescription
namestringPackage name.
fn install(name: string)
Install a package on the target (MUTATING; needs root — set `become` when the login user is unprivileged). Idempotent: no-op when already installed.
ArgumentTypeDescription
namestringPackage name.
fn upgrade(refreshMetadata: boolean, securityOnly: boolean, dryRun: boolean, pollIntervalMs: number, maxWaitMs: number)
Bring all installed packages current within the running release (MUTATING; needs root — set `become` when the login user is unprivileged). Runs the transaction detached and polls to completion — never reboots. Idempotent: nothing-to-do => changed=false.
ArgumentTypeDescription
refreshMetadatabooleanRefresh repo metadata first (dnf/yum `--refresh`).
securityOnlybooleanApply only security errata (dnf/yum `--security`).
dryRunbooleanSolve and list the plan inline without installing anything.
pollIntervalMsnumberDelay between completion polls.
maxWaitMsnumberOverall bound; exceeding it fails loudly WITHOUT killing the running transaction.
fn system_upgrade(targetRelease: number, downloadOnly: boolean, confirm: boolean, pollIntervalMs: number, downloadMaxWaitMs: number, rebootMaxWaitMs: number)
Move the host to a newer Fedora release via the offline dnf system-upgrade transaction (MUTATING + IRREVERSIBLE; needs root — set `become` when the login user is unprivileged). dnf/Fedora ONLY. SAFE by default: confirm=false downloads and stages the transaction but NEVER reboots. Only confirm=true (and not downloadOnly) rides the reboots and verifies the new release.
ArgumentTypeDescription
targetReleasenumberTarget Fedora release, e.g. 43. Required; must be newer than the running release.
downloadOnlybooleanDownload and stage the offline transaction only; never reboot (same effect as leaving confirm false).
confirmbooleanHARD GATE for the irreversible reboot. false (default) => download + stage, then STOP before rebooting. Must be true (and downloadOnly false) to actually reboot into the new release.
pollIntervalMsnumberDelay between download-completion polls and reboot-barrier reads.
downloadMaxWaitMsnumberOverall bound on the offline download (90 min); exceeding it fails loudly WITHOUT killing the running transaction.
rebootMaxWaitMsnumberBound on the reboot barrier (2 h): how long to wait through the dark for the returning login prompt before failing loudly. The offline apply is the long pole — a full release jump takes 40-80 min on slow riscv (SpacemiT K1), and the barrier must span reboot → offline apply → reboot → getty. Too short reports a FALSE failure on a healthy upgrade (the transaction still completes; the method never assumes bricked). Raise further for larger jumps or slower boards.

Resources

package(infinite)— State of a package on the target.
upgrade(infinite)— Outcome of a whole-system upgrade on the target.
systemUpgrade(infinite)— Outcome of a Fedora release jump (offline dnf system-upgrade) on the target.
@shrug/serial-cfgmgmt/servicev2026.07.28.1serial_cfgmgmt_service.ts
fn status(name: string)
Read a systemd unit's active and enabled state (read-only).
ArgumentTypeDescription
namestringsystemd unit name, e.g. sshd.service.

Resources

service(infinite)— State of a systemd unit on the target.
@shrug/serial-cfgmgmt/filev2026.07.28.1serial_cfgmgmt_file.ts
fn push(localPath?: string, content?: string, chunkBytes: number, gzip: boolean, maxRetries: number, quietConsole: boolean)
Upload a local file (or inline content) to a remote path over the serial console (gzip+base64 chunked). MUTATING. Verifies sha256 both ends.
ArgumentTypeDescription
localPath?stringLocal file to upload (mutually exclusive with `content`).
content?stringInline content to upload (mutually exclusive with `localPath`).
chunkBytesnumberMax base64 line length per chunk (ceiling under the 4096-byte tty line buffer).
gzipbooleangzip the payload before transfer.
maxRetriesnumberPer-chunk retries on a non-zero exit before failing.
quietConsolebooleanBest-effort `dmesg -n 1` during transfer (restored after).
fn pull(localPath?: string, gzip: boolean, maxRetries: number, quietConsole: boolean)
Download a remote file over the serial console (base64, gzip-aware). Verifies sha256 against a remote sha256sum. Read-only on the target.
ArgumentTypeDescription
localPath?stringLocal path to write; if omitted the base64 content lands in the resource (avoid for secrets — resources persist).
gzipbooleanExpect a gzip'd stream (gunzip locally).
maxRetriesnumberWhole-pull retries on a corrupt stream (e.g. printk interleaving) before failing.
quietConsolebooleanBest-effort `dmesg -n 1` during the pull (restored after).
fn verify(localPath?: string, sha256?: string)
Compare a local file's sha256 (or a given sha256) against the remote file's sha256sum without transferring. Read-only.
ArgumentTypeDescription
localPath?stringLocal file whose sha256 to compare (mutually exclusive with `sha256`).
sha256?stringExpected sha256 hex (mutually exclusive with `localPath`).

Resources

file(infinite)— Result of a file transfer over the serial console.
@shrug/serial-cfgmgmt/storagev2026.07.28.1serial_cfgmgmt_storage.ts

Global Arguments

ArgumentTypeDescription
becomebooleanEscalate privilege for the btrfs probes and every mutating command (the login user is unprivileged, e.g. a `fedora` serial console with sudo). Leave false when the session is already root.
becomeMethodenumPrivilege-escalation command used when `become` is true. `sudo` runs as `sudo -n` (non-interactive).
fn disks()
Collect block devices, filesystems, mounts and btrfs subvolumes over the serial console (lsblk/findmnt/btrfs). Read-only. Writes the `storage` resource.
fn format_mount(device: string, partition: boolean, fstype: string, label?: string, mkfsArgs?: string, mountpoint: string, fstabOptions?: string, wipe: boolean, dryRun: boolean)
Format a device (optionally partition it first) and persist a nofail mount. MUTATING. Defaults to dryRun=true (writes a `plan`, executes nothing); a live run requires `confirmDevice` and refuses on any identity mismatch, a mounted target, or an existing filesystem signature (unless wipe=true).
ArgumentTypeDescription
devicestringTarget block device, e.g. /dev/mmcblk2.
partitionbooleanCreate a single GPT partition spanning the device, then format that partition.
fstypestringFilesystem type to create.
label?stringFilesystem label.
mkfsArgs?stringExtra args passed to mkfs.<fstype>, e.g. '-b 4096' (no shell metacharacters).
mountpointstringMount path, e.g. /mnt/data.
fstabOptions?stringfstab mount options; `nofail` and a device timeout are always forced on.
wipebooleanwipefs -a before mkfs. When false, a non-empty device (any existing fs or partition) is a hard refusal.
dryRunbooleanPreview only: write the plan, execute nothing. Default true.
fn relocate_subvol(sourceSubvol: string, targetMount: string, snapshotName?: string, repoint: boolean, finalMountpoint?: string, dryRun: boolean)
Copy a btrfs subvolume to another (already-formatted) btrfs via `btrfs send | receive`, verify the received copy, and optionally repoint fstab (nofail). MUTATING but ADDITIVE — never deletes the source. Defaults to dryRun=true; a live run requires `confirmDevice`.
ArgumentTypeDescription
sourceSubvolstringLive subvolume path to copy, e.g. /var.
targetMountstringMounted destination btrfs, e.g. /mnt/newdisk.
snapshotName?stringRead-only snapshot name to send (a leaf name, not a path).
repointbooleanRewrite fstab so finalMountpoint mounts the received subvol (nofail).
finalMountpoint?stringWhere the received subvol should mount after reboot (defaults to sourceSubvol).
dryRunbooleanPreview only: write the plan, execute nothing. Default true.

Resources

storage(infinite)— Block devices, filesystems, mounts and btrfs subvolumes gathered from the target over the serial console.
plan(infinite)— Dry-run preview of a mutating storage method: the exact ordered command plan and fstab line. Executes nothing.
mount(infinite)— Result of a live format_mount / relocate_subvol: the persisted mount, its UUID, and verification details.
04Previous Versions1
2026.07.22.2
05Stats
A
100 / 100
Downloads
4
Archive size
273.5 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit2/2earned
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License declared1/1earned
  • Verified public repository2/2earned
06Platforms
07Labels