name: fermihdi-hd-sandbox description: Install and run the self-contained FermiHDI HD sandbox on a single host — a full end-to-end HD instance (DMS, SCC, storage nodes, filter node, proxy, the SQL/GraphQL/MCP query services and synthetic flow generation) plus an optional benchmark suite, from the published download at downloads.fermihdi.io with every image from cr.fermihdi.io. The agent probes the host, asks the user every deployment question up front (version, image variant, storage node count, which drives to assign, hugepages and boot-loader changes, NICs, filter node, AI services), then asks what to test (browser tour, MCP/GraphQL/SQL query smoke test, benchmark), and runs everything unattended through command-line switches. Customer-facing; one host, no source checkout, no external PKI.
Run the FermiHDI HD sandbox
The sandbox is a complete HD deployment on one host for evaluation, demos and benchmarking. It ships its own CA and a licence minted when the archive was packaged, so it needs no external PKI and no source checkout. For a production, multi-host deployment use fermihdi-hd-install.
- Archive:
https://downloads.fermihdi.io/sandbox-latest.tar.gz(pinned:https://downloads.fermihdi.io/<version>/fermihdi-hd-sandbox-<version>.tar.gz, each with a.sha256next to it). - Images:
cr.fermihdi.io/hd/<service>:<tag>. For 2.0.5:2.0.5is the AVX-512 Rock,2.0.5-avx2the AVX2 Rock, and-ub2604the Ubuntu 26.04 builds. The query helpers also pullalpineandpython:3-alpinefrom Docker Hub. - Licence: valid for 30 days from packaging. An archive older than that will not start; download it again.
How to run this skill
Work through the steps in order. Every script takes a command-line switch for every question it could ask, so run nothing that waits on a prompt, and never pipe answers into one.
- Probe the host. Read-only.
- Ask the deployment questions, all in one batch.
- Show the plan: the exact commands, which ones need
sudo, and what they change on the host. Wait for a yes. - Prepare the host and launch.
- Ask the test questions, all in one batch.
- Run the tests and report. Tear down as the user chose.
Hard rules:
- Never choose a drive for the user. Show the probe results and let them pick. An assigned drive's contents are overwritten.
- Never run
sudo, bind a device to vfio-pci, change the boot loader or reboot unless the user said yes to that specific action. The scripts never reboot on their own; tell the user when a reboot is needed and wait for them. - If the host runs other workloads, confirm before anything that stops containers. Launching the sandbox removes any earlier sandbox instance.
Use your question tool to ask when you have one; otherwise ask in one message and wait.
Step 1: probe (read-only)
Download and unpack first, because the probe uses the archive's own tools. Nothing here changes the host.
curl -fsSL https://downloads.fermihdi.io/sandbox-latest.tar.gz -o fermihdi-hd-sandbox.tar.gz
curl -fsSL https://downloads.fermihdi.io/fermihdi-hd-sandbox-2.0.5.tar.gz.sha256 # compare the hash
sha256sum fermihdi-hd-sandbox.tar.gz
tar -xzf fermihdi-hd-sandbox.tar.gz && cd fermihdi_sandbox
Then collect:
| What | Command |
|---|---|
| OS and init | cat /etc/os-release; ps -p 1 -o comm= |
| CPU, cores, NUMA nodes, RAM | lscpu | grep -E 'Model name|^CPU\(s\)|NUMA node\(s\)'; free -g |
| Image variant this CPU can run | bash -c 'source ./detect_variant.sh; fermihdi_detect_variant' (rock, avx2 or arm64) |
| Hugepages and their mounts | grep -i huge /proc/meminfo; grep hugetlbfs /proc/mounts; cat /proc/cmdline |
| Drives that can be assigned | python3 configure_sandbox.py --list-storage --json |
| NICs | ls /sys/class/net; ip route show default |
| Docker and Compose | docker version --format '{{.Server.Version}}'; docker compose version |
| Ports the sandbox publishes | ss -ltn '( sport = :80 or sport = :8080 or sport = :8081 or sport = :8082 or sport = :9089 )' |
| A sandbox already running | docker ps --filter label=com.docker.compose.project=fermihdi_sandbox --format '{{.Names}}' |
| GPU (only for AI services) | nvidia-smi -L |
| Registry access | docker pull cr.fermihdi.io/hd/proxy:2.0.5 |
--list-storage --json reports every NVMe and SATA device with its type (nvme_vfio already bound
to vfio-pci, nvme_kernel, sata), PCI address, block device, NUMA node, IOMMU group, size,
model, in_use reasons (mounted, swap, held by LVM/RAID/device-mapper) and the value to pass to
--sn-storage. Devices that are in use cannot be assigned.
Step 2: ask about the deployment
Ask all of these in one batch. Offer what the probe found as the default, and say why.
| # | Question | Choices (default) | Becomes |
|---|---|---|---|
| 1 | Which version? | latest 2.0.5, or a pinned version |
archive URL, --tag |
| 2 | Which image variant? | the detected one; rock (AVX-512), avx2, ubuntu (Ubuntu 26.04 builds), arm64 |
--type |
| 3 | How many storage nodes? | 1 below ~64 GB RAM, 2 or 3 above |
--num-sns |
| 4 | Storage for each node? Show the probe table. | tmpfs for every node (RAM-backed 2 GB file, data lost on stop); a listed NVMe drive through SPDK (bound to vfio-pci, removed from the OS); or a listed drive as a kernel block device |
--storage tmpfs, or --sn-storage sn-N=<value> per node, plus --bind-nvme <PCI,...> for SPDK |
| 5 | May I prepare the host with sudo? It reserves hugepages, mounts them, raises fs.aio-max-nr, and binds any drive or NIC chosen here. |
yes / no (without it the host needs hugepages and their mounts already in place) | sudo bash sandbox_host_setup.sh ... |
| 6 | Keep the hugepages and their mounts across reboots? | yes: a boot unit re-reserves the 2 MB pool, and /etc/fstab mounts /dev/hugepages (2 MB) and /dev/hugepages1G (1 GB) |
--persist yes|no |
| 7 | Change the kernel command line? Needed for NVMe through SPDK (1 GB pages), and for IOMMU and CPU isolation when those apply. A reboot follows, which the user does. | apply / print only / skip (tmpfs-only: skip, with --hugepages-1g 0) |
--grub apply|print|skip |
| 8 | Multi-NUMA hosts only: keep everything on one NUMA node? | confine (the node with the drives) / spread / a specific node |
--numa-mode, --numa-node N |
| 9 | Give a node a dedicated NIC for its DPDK data plane? | posix for all (default) / named NICs. The NIC with the default route or this session is refused. |
--bind-nic <PCI,...> and --nic <node>=<PCI> |
| 10 | Filter node (BloomRF)? | active (default) / passive / off |
--bloomrf |
| 11 | AI services (Ollama and an n8n chat workflow)? | none (default) / CPU / GPU | --ai, --ai-gpu |
| 12 | Registry mirror, and log level? | cr.fermihdi.io/hd, info |
--registry, --log-level |
If any sandbox port is taken, or a sandbox is already running, say so here and ask what to do.
Step 3: plan, prepare, launch
Show the commands built from the answers, mark the sudo ones, and wait for a yes.
Prepare the host (one command; it also writes sandbox_hw_config.json and the compose
override, so the launch asks nothing):
# tmpfs-only lab
sudo bash sandbox_host_setup.sh --num-sns 1 --storage tmpfs --hugepages-1g 0 \
--bind-nvme none --bind-nic none --grub skip --persist yes --yes
# NVMe through SPDK for two nodes (values from --list-storage)
sudo bash sandbox_host_setup.sh --num-sns 2 --bind-nvme 0000:41:00.0,0000:42:00.0 \
--sn-storage sn-1=0000:41:00.0 --sn-storage sn-2=0000:42:00.0 \
--bind-nic none --grub apply --persist yes --yes
Add --numa-mode/--numa-node, --nic, --bind-nic and --dedicated-interfaces from the
answers. If it prints "GRUB updated — please reboot" (exit status 0 after the boot-loader
change), stop and ask the user to reboot. After the reboot, run the same command again with
--grub print: it is idempotent, re-reserves the pools and re-checks the mounts.
What host setup does with hugepages:
- Sizes: 4 × 1 GB plus 512 × 2 MB per instance (every storage node and the SCC), split evenly
across NUMA nodes. Override with
--hugepages-1gand--hugepages-2m. - Now: reserves both pools per NUMA node, and mounts
/dev/hugepagesand/dev/hugepages/nodeNat 2 MB and/dev/hugepages1Gat 1 GB. - At boot (
--persist yes):fermihdi-sandbox-hugepages.servicere-reserves the pages and re-creates the mounts before Docker starts. A managed block in/etc/fstab(nofail) mounts the same three paths. The 1 GB pool is reserved by the kernel command line (default_hugepagesz=2M hugepagesz=1G hugepages=N), which--grub applywrites throughgrubbyon RHEL-family systems and through/etc/default/grubelsewhere. When a file in/etc/default/grub.d/overrides it (Ubuntu cloud images do), it also writes99-fermihdi-sandbox.cfgthere, and it checks the regeneratedgrub.cfgfor the arguments.default_hugepageszstays 2M so/dev/hugepagesremains the 2 MB pool. - Checks:
sudo bash sandbox_host_setup.sh --help; the summary at the end lists both pools, both mounts and what survives a reboot.
Verify before launching:
grep -E 'HugePages_(Total|Free)|Hugepagesize' /proc/meminfo
cat /sys/kernel/mm/hugepages/hugepages-1048576kB/nr_hugepages
grep hugetlbfs /proc/mounts # /dev/hugepages pagesize=2M, /dev/hugepages1G pagesize=1024M
Pull and launch (no sudo):
bash pull_sandbox_images.sh --tag 2.0.5 --type rock
bash start_sandbox.sh --tag 2.0.5 --type rock --num-sns 1 --bloomrf active --yes
Launching takes a few minutes. It is ready at "Sandbox is fully launched!", and ingesting at
"batches confirmed on sn-1". For NVMe storage nodes the launcher refuses to start if
/dev/hugepages1G is not a 1 GB mount or has no free pages, and prints the fix.
If sandbox_hw_config.json does not exist, start_sandbox.sh takes the same answers itself:
--storage, --sn-storage, --numa-mode, --nic, --dedicated-interfaces, --grub and --yes.
Rules while it runs:
- Relaunch the whole lab between runs. Restarting one service, the proxy especially, loses its DMS registration and everything behind it answers 502. Recovery is a full relaunch.
- Use
--num-sns 1to3for a lab that ingests.--standalonenever receives its targets from the DMS, and ingest hangs while every service looks healthy.
Step 4: ask about the test
Ask in one batch, once the lab is up.
| # | Question | Choices (default) |
|---|---|---|
| 1 | What should I run? | a browser tour / a query smoke test through MCP, GraphQL and SQL (default) / the benchmark suite / the smoke test, then the benchmark |
| 2 | For queries: which services, and any query of your own? | all three; the IPFLOW dataset; the default queries |
| 3 | For the benchmark: which phases? | all / 1 YCSB 150 B / 2 TSBS 512 B / 3 HD-Pipeline 128 B |
| 4 | Batches per phase, and records per batch? | 100 and 60000; suggest 10 batches on a tmpfs lab or a small host |
| 5 | Benchmark options? | jumbo frames (no), a query while ingest is still running (no), keep containers afterwards (no) |
| 6 | When it finishes? | leave the lab running / tear it down and keep the hardware config and reports / full reset |
Step 5: run and report
Browser tour. Replace HOST with the host's address. The lab's stand-in authentication lets
everyone in as admin, so keep these ports off untrusted networks.
| Page | URL |
|---|---|
| DMS WebUX | http://HOST:8081/ |
| Data Explore | http://HOST/hd_data_explore |
| GraphiQL (HD GraphQL / IP-flow GraphQL) | http://HOST/hd_graphiql, http://HOST/ipflow_graphiql |
Grafana (sign in as admin / admin; it asks for a new password) |
http://HOST/grafana |
| Traefik dashboards (query / DMS) | http://HOST:8080/, http://HOST:8082/ |
| Flow generator (Locust) | http://HOST:9089/ |
Query smoke test. Each helper talks to its service on the sandbox's front-end network and writes a JSON result beside itself.
bash query_mcp.sh # MCP execute_hd_query tool, 10 rows
bash query_ipflow.sh # HD GraphQL, 100 rows
bash query_ipflow.sh --sql 'SELECT ip_proto, SUM(bytes) AS bytes FROM IPFLOW GROUP BY ip_proto'
bash query_sql.sh # SQL service: rows per protocol
bash query_sql.sh --sql 'SELECT COUNT(*) AS n, MIN(timestamp), MAX(timestamp) FROM {table}'
A pass is rows from all three and no errors in the GraphQL result. Flows keep arriving, so run
the GraphQL and SQL aggregates back to back to compare their totals. --help on each helper lists
its options.
Benchmark. It stops the running lab and runs its own. Relaunch the lab afterwards if the user wants it back.
bash run_benchmark.sh --tag 2.0.5 --type rock --num-sns 1 --num-batches 10 [--phase 2] \
[--records-per-batch 60000] [--jumbo] [--concurrent] [--no-cleanup]
Report each phase's PASS or FAIL, "All batches confirmed", and the peak ingest and query rates. The
JSON report is in tests/test_logs/benchmark_*.json.
Health.
docker ps --format '{{.Names}} {{.Status}}' shows no restarting containers, and
bash sn_storage_status.sh shows each storage node's fill. For no rows, hangs, a storage node that
will not start, or Rock startup problems, use fermihdi-hd-troubleshoot. Most sandbox problems
are host prep (hugepages, their mounts, VFIO) or the relaunch rule, not the engine.
Tear down, as the user chose:
bash reset_sandbox.sh --yes --keep-hw --keep-logs # stop everything; keep hardware config and reports
bash reset_sandbox.sh --yes # full reset
Undo the host changes only if the user asks:
sudo systemctl disable --now fermihdi-sandbox-hugepages.service
sudo rm /etc/systemd/system/fermihdi-sandbox-hugepages.service /usr/local/sbin/fermihdi-sandbox-hugepages
sudo sed -i '/^# BEGIN FermiHDI sandbox hugetlbfs/,/^# END FermiHDI sandbox hugetlbfs/d' /etc/fstab
sudo rm /etc/sysctl.d/99-fermihdi-sandbox-aio.conf
# Kernel command line: /etc/default/grub.fermihdi-<timestamp>.bak holds the previous file,
# and remove /etc/default/grub.d/99-fermihdi-sandbox.cfg if it exists, then sudo update-grub;
# on RHEL-family systems: sudo grubby --update-kernel=ALL --remove-args="hugepagesz hugepages default_hugepagesz"
Drives bound to vfio-pci return to their kernel driver at the next reboot.