- Python 48.2%
- Shell 36.9%
- HTML 13.1%
- Dockerfile 1.8%
|
|
||
|---|---|---|
| build | ||
| .gitignore | ||
| README.md | ||
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:
- Derives a stable identity from the primary NIC MAC address (
agent-xxxx) - Generates a fresh SSH host key
- Starts a TCP beacon on port 2026 that announces its identity as JSON
- Registers with the Nimitz orchestrator over HTTP
- 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
modprobestop 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