Skip to content

Install

Omniglass ships as a single self-contained binary with the operator console compiled in, so one file is the whole app: every run mode (server, node run, migrate, bootstrap, token) is the same binary with a different first argument. You can download that binary directly, or run the container image instead.

Each GitHub Release carries an archive per platform:

OSArchitecturesArchive
Linuxamd64, arm64.tar.gz
macOSamd64 (Intel), arm64 (Apple silicon).tar.gz
Windowsamd64.zip

Download the archive for your platform, verify it against checksums.txt, extract the omniglass binary, and put it on your PATH:

Terminal window
VERSION=0.1.0 # the release tag, without the leading v
BASE=https://github.com/hyperscaleav/omniglass/releases/download/v$VERSION
curl -sSLO $BASE/omniglass_${VERSION}_linux_amd64.tar.gz
curl -sSLO $BASE/checksums.txt
sha256sum --check --ignore-missing checksums.txt
tar xzf omniglass_${VERSION}_linux_amd64.tar.gz
sudo install omniglass /usr/local/bin/
omniglass --version

Each archive also ships an SBOM (.sbom.json) alongside its checksum.

The macOS binaries are not yet notarized (#58), so Gatekeeper quarantines a freshly downloaded binary. Clear the quarantine attribute once:

Terminal window
xattr -d com.apple.quarantine omniglass

The binary is the server, the collection node, and the CLI in one. To stand up the API and console you need Postgres and the same environment the image uses; see Container image for the OMNIGLASS_DSN and migrate/server/bootstrap/token flow, which is identical for the binary. To drive a running server from the command line, see the CLI.

The same binary simulates devices: omniglass sim run, beside a collection node, serves every component placed on that node whose product it has a profile for, each on its own loopback address (simulation, and the operator’s walk-through in simulate a fleet). It must share the node’s loopback: the same host, or the same container or pod network namespace. With Docker, run it in the node container’s namespace:

Terminal window
docker run -d --name omniglass-sim --network container:omniglass-node \
-e OMNIGLASS_SERVER -e OMNIGLASS_NODE_NAME -e OMNIGLASS_NODE_TOKEN \
ghcr.io/hyperscaleav/omniglass sim run

A sidecar in the node’s pod is the Helm chart’s to offer (#892). It runs unprivileged, and each privilege it is given buys one thing:

  • Ports below 1024 (SNMP 161, HTTP 80, SSH 22). In a container, Docker already lets any user bind them; in Kubernetes, set the safe sysctl net.ipv4.ip_unprivileged_port_start=0 on the pod. A bare binary needs sudo setcap cap_net_bind_service=+ep ./omniglass. Without any of these, lift the ports with --port-offset 10000 and aim the endpoints at the lifted ports, as the dev fleet does.
  • Ping. The node’s pinger is unprivileged: the kernel must let its group send ICMP echo through net.ipv4.ping_group_range (with Docker, --sysctl net.ipv4.ping_group_range="0 2147483647").
  • Impairments and truthful ping need --impairments and NET_ADMIN (docker run --cap-add NET_ADMIN), because they rewrite the firewall and traffic shaping of the network namespace the simulator runs in. Grant it only in a namespace of the simulator’s own (a container or pod), never on a workstation’s host namespace: there the same rules would apply to the whole machine’s loopback. Without it, every loopback address answers ping and impairments are reported unavailable; collection still works.

On macOS, loopback answers only 127.0.0.1 until an address is aliased, so a bare binary needs an alias for each simulated address it serves (the dev fleet uses 127.60.0.0/16), for example for i in $(seq 1 20); do sudo ifconfig lo0 alias 127.60.0.$i up; done. Impairments are Linux only.

make dev and the Docker stack below seed the example fleet, but omniglass seed-dev --scenario <name> can stand up other fleets: a fresh install with nothing in it, the example fleet having a bad day, or the example fleet at enterprise size. The list, with what each one holds, is the command’s own help, rendered in the CLI reference. Every scenario except the fresh install is the example fleet plus a generated expansion of it, so a scenario never drifts from the fleet it grows from, and like the example fleet every one is idempotent. Pick one with SEED_SCENARIO for make dev, or STACK_SCENARIO for the Docker stack (on a fresh install the stack is the server and its owner alone, since its node and simulated devices come from the example fleet):

Terminal window
SEED_SCENARIO=degraded make dev
STACK_SCENARIO=large make dev-sim-container

The large fleet copies the example fleet’s self-contained rooms (a room whose devices serve only the systems in it) under replica sites, and simulates only the example fleet’s own devices, so the collector is not load-tested by accident (a device the example fleet does not declare is never wired by the seed, whatever the scenario). It takes a couple of minutes to seed.

From a checkout, with nothing but Docker, make dev-sim-container runs every piece of the above as containers on a private network of their own: Postgres, the server (seeded with the example fleet and its simulated components), the node edge-hq enrolled with a one-time token, and the simulator with --impairments in the node container’s network namespace. Only the simulator holds NET_ADMIN, and it reaches only that namespace; only the node takes the ping sysctl. In a container the simulator binds the real ports, so the fleet is seeded unlifted (omniglass seed-dev --sim-port-offset 0). It prints the console’s address, on a 127.0.0.1 port Docker picks so two copies never collide (sign in as dev/dev), and tears everything down on Ctrl-C. Breaking a component on purpose works there as in simulate a fleet.

It does not attach to a make dev already running on the host: that server’s bus listens on the host’s loopback only, so a containerized node could reach it only on the host’s network, where the simulator’s NET_ADMIN would rewrite the host’s own firewall. The stack carries its own server instead.

The same stack is what demos are recorded against, the terminal demos and the console walkthroughs alike: make walkthrough-stack holds it up without publishing anything and prints how a Playwright container on its network reaches it, and make test-walkthrough-stack proves it (the node collects, and an impairment set through the API takes a component’s reachability down and its repair brings it back). The recipe is stack_walkthrough_up in web/e2e/lib/stack.sh.

A demo is a recording of a feature working, by one of two recorders: a terminal demo for CLI and API work, a walkthrough for a console flow. make demo ID=<id> records one demo of either kind (ID="<a> <b>" several, no ID every demo), each recorder on a stack of its own; when a PR needs which, and how to write and review each, are in demos.

A terminal demo is a VHS tape, scripts/demo/<id>.tape. VHS runs from its own image, ghcr.io/charmbracelet/vhs at v0.12.1, pinned by digest in web/e2e/tape/Dockerfile; the image bundles what VHS drives (ttyd for the terminal, chromium to render it, ffmpeg to encode the GIF and MP4) and the fonts the house settings name, and publishes a multi-platform index, so it runs on amd64 and arm64 hosts alike. The recorder adds jq, the prelude every tape sources, and this tree’s omniglass binary, copied out of the stack’s image, so a tape types the CLI the stack serves. Nothing is installed on the host. make demo writes scripts/demo/out/<id>/: the GIF, the MP4 and demo.json, the manifest a passing recording carries. A terminal demo is a test: a command it types that exits non-zero, a wait in it that runs out, or a recording over its budget (MAX_GIF_BYTES and MAX_SECONDS in web/e2e/tapelib.mjs) fails the run, naming the tape and the cause. make test-demos proves each of those failures.

A walkthrough is a script, web/e2e/walkthroughs/<id>.yaml (the id is lowercase letters, digits and hyphens): a title, an optional description, and steps, each one kind of action or a wait for a visible result, with a caption shown while it runs. The step kinds, their fields and the rules are defined, with an annotated example, at the top of web/e2e/walkthroughlib.mjs. The format is validated strictly: an unknown key or kind fails, naming the file and the step, and so does a script that ends on an action rather than a wait for its result.

title: Read a simulated component's status
steps:
- goto: /simulation
caption: The Simulation page shows the simulated fleet, live.
- click: '[data-testid="sim-row"] >> nth=0'
caption: Open a simulated component.
- wait: { selector: '[data-testid="sim-status"]' }
caption: Its status says where it is served, and how often collection has reached it.
poster: true

The recording’s ffmpeg image is pinned by digest to an amd64-only image (its tag publishes no multi-platform index), so recording on an arm64 host needs amd64 emulation registered with Docker.

make walkthroughs records every script on the walkthrough stack (make walkthroughs ID=<id> records one) into web/e2e/walkthroughs/out/<id>/: an mp4 opening on a title card, with a cursor that glides to each target and ripples where it clicks, a highlight on the target and a caption bar; preview.webp, an animated preview encoded from the mp4 by the same pinned ffmpeg (a lower frame rate and width, looping), which is what the PR comment and a docs page play inline; poster.png; and a still per step. A walkthrough is a test: a step whose result never appears fails the run, naming the step, its kind and what it waited for, and keeps the stills taken so far; so does a recording over the budget, whose limits on length, mp4 size and preview size are MAX_SECONDS, MAX_BYTES and MAX_PREVIEW_BYTES in web/e2e/walkthroughlib.mjs. make test-walkthroughs proves both failures, an over-budget preview among them. When a PR needs a walkthrough, how to review its stills, and how a docs page embeds one are in demos.