No description
  • Python 48.2%
  • Shell 36.9%
  • HTML 13.1%
  • Dockerfile 1.8%
Find a file
Chip Wasson 1f0d0d72fe Add curl to builder image; download logcurse at build time
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 22:19:17 +00:00
build Add curl to builder image; download logcurse at build time 2026-03-31 22:19:17 +00:00
.gitignore Remove PLAN.md and build logs from tracking 2026-03-31 14:23:59 +00:00
README.md Initial commit — Nimitz bootable Alpine Linux agent node 2026-03-30 22:39:37 +00:00

Nimitz

A bootable USB drive that loads a minimal Alpine Linux OS entirely into RAM, connects to an orchestrator, and makes the host machine available to an AI agent for remote task execution.

Drop a PC on a workbench, plug in the USB, boot it, walk away — the agent handles the rest from a desk.


How it works

[USB Boot] → [Diskless boot into RAM] → [DHCP] → [Orchestrator registration]
                                                          ↑
                                                [Beacon :2026]  ←  subnet sweep
                                                [SSH    :22  ]  ←  agent connects
                                                [HTTP   :80  ]  ←  GET /llms.txt

The ISO boots Alpine in diskless mode: packages are installed from the on-media APK cache into a tmpfs root, the apkovl overlay is applied on top, and OpenRC starts services. The kernel module squashfs (modloop) is loop-mounted from the USB and must stay plugged in.

At boot, the node:

  1. Derives a stable identity from the primary NIC MAC address (agent-xxxx)
  2. Generates a fresh SSH host key
  3. Starts a TCP beacon on port 2026 that announces its identity as JSON
  4. Registers with the Nimitz orchestrator over HTTP
  5. Serves a self-description file at http://<node>/llms.txt

Once up, an AI agent can SSH in, run commands, and interact with any attached serial devices.


Nomenclature

Name Role
Nimitz Project name
Bulwark Orchestrator — registers, tracks, and brokers access to nodes
Nimitz Node Individual USB-booted agent host

Prerequisites

To build:

  • Docker

To run tests:

  • qemu-system-x86_64

Configuration

Before building, edit the files in build/config/:

build/config/authorized_keys (required)

SSH public keys baked into every node image. Add at least one key — the build will fail without it.

ssh-keygen -t ed25519 -f ~/.ssh/nimitz -C "nimitz"
cat ~/.ssh/nimitz.pub >> build/config/authorized_keys

build/config/orchestrator_url

URL of the Bulwark orchestrator. Nodes POST their identity here at boot.

http://casadejerry.chip.wasson.tech:2026

Build

docker build -t nimitz-builder build/
docker run --rm --privileged \
  -v "$(pwd)/build/output:/build/output" \
  nimitz-builder

The ISO is written to build/output/nimitz-agent-boot.iso.


Deploy

Push to a machine with a USB drive attached

rsync -ah --progress build/output/nimitz-agent-boot.iso chip@chipnixos.chip.wasson-ece.dev:~/

Write to USB (run on the target machine)

dd if=~/nimitz-agent-boot.iso of=/dev/sdX bs=4M status=progress

Replace /dev/sdX with your USB device (lsblk to find it).

Note: Leave the USB plugged in while the node is running. The kernel module squashfs (modloop) is loop-mounted from the USB — ejecting it makes modprobe stop working.


Node services

Port Service Description
22 SSH Key-auth only, root login
80 HTTP Serves /llms.txt — node self-description
2026 Beacon TCP: connect and receive JSON identity blob

Discover a node

nc <host> 2026
# → {"hostname":"agent-ee3f","mac":"aa:bb:cc:dd:ee:ff","uptime":42}

Or fetch the full self-description:

curl http://<host>/llms.txt

SSH access

ssh root@agent-ee3f   # hostname resolves if your network has mDNS/DNS
ssh root@<ip>

The SSH host key is generated fresh each boot — accept the new fingerprint (TOFU).

Serial ports

List attached serial devices:

ssh root@<host> serial-list
ssh root@<host> serial-list --json

Interact with a serial device:

ssh root@<host> "python3 -c \"
import serial, time
s = serial.Serial('/dev/ttyUSB0', 9600, timeout=2)
s.write(b'YOUR_COMMAND\r')
time.sleep(0.5)
print(s.read(256))
\""

Supported USB-serial drivers (loaded at boot): ch341, cp210x, ftdi_sio, cdc_acm.


Integration tests

The test suite boots the ISO in QEMU and checks the beacon, SSH, and HTTP server.

./build/test.sh

To also run SSH exec tests, set up a test keypair first:

ssh-keygen -t ed25519 -f build/config/test_key -N ""
cat build/config/test_key.pub >> build/config/authorized_keys
# rebuild the ISO, then:
./build/test.sh --ssh-key build/config/test_key

Options:

--iso PATH       path to ISO (default: newest in build/output/)
--ssh-key PATH   private key for SSH exec tests
--timeout SECS   boot timeout, default 180s (increase if no KVM)

The QEMU serial console is captured to build/output/qemu-serial.log for debugging.


Project structure

build/
├── Dockerfile              Build environment (Alpine 3.21)
├── build.sh                Image build orchestrator
├── mkimg.nimitz.sh         Alpine mkimage profile
├── test.py                 Integration test suite
├── test.sh                 Test runner wrapper
├── config/
│   ├── authorized_keys     SSH public keys (add yours before building)
│   ├── orchestrator_url    Bulwark orchestrator URL
│   ├── sshd_config         Hardened SSH server config
│   ├── modules             Kernel modules to load at boot
│   ├── llms.txt            Node self-description served at port 80
│   └── package_list        Package list reference
├── scripts/                Executables installed to /usr/local/bin/
│   ├── agent-identity      Identity derivation (MAC → hostname)
│   ├── agent-beacon        TCP discovery beacon daemon
│   ├── agent-register      Orchestrator phone-home script
│   ├── generate-host-keys  SSH host key generation (first boot)
│   ├── serial-list         Serial port discovery utility
│   └── update-kernel       Builds initramfs + modloop (replaces removed mkinitfs script)
├── units/                  OpenRC service units → /etc/init.d/
│   ├── agent-identity      Runs before net; sets hostname
│   ├── agent-beacon        Beacon daemon (port 2026)
│   ├── agent-register      One-shot registration at boot
│   ├── agent-httpd         Serves /www/llms.txt on port 80
│   └── generate-host-keys  Generates SSH keys before sshd starts
└── output/                 Build artifacts (gitignored)
    ├── nimitz-agent-boot.iso
    └── qemu-serial.log

Design notes

  • Zero config per node — identity is derived from hardware (MAC address), same image on every USB
  • Diskless boot — packages install from the ISO's APK cache into a tmpfs root at boot; no writes to host disk
  • Modloop stays mounted — kernel modules live in a squashfs loop-mounted from the USB; leave the drive plugged in
  • Fresh SSH host keys — generated on each boot; orchestrator uses TOFU on first contact
  • Beacon runs indefinitely — orchestrator can rediscover nodes after a restart by sweeping port 2026
  • Registration failure is non-fatal — node remains reachable via beacon and SSH if orchestrator is down at boot