A distributed server-orchestration platform written in pure Dart. A central Hub manages a fleet of Node agents — a cloud-like view of every server's status, resources, capabilities, services and deployments, driven from one place.
Nodes dial the Hub outbound over WebSocket-on-TLS (wss) — the same
identity-centric, NAT-friendly model as its sibling
omnyshell: you address servers by node identity, not
host:port. A node behind NAT needs no inbound port, and the Hub needs exactly
one: the node channel and the REST API share a single TLS listener.
:8443 (TLS)
┌──────────────────┴──────────────────┐
│ /node → node control channel │
│ /shell → OmnyShell broker │
────►│ /api/v1 → REST API │◄──── CLI / REST client
│ /metrics → Prometheus │
└──────────────────┬──────────────────┘
HUB
▲ ▲ ▲
wss│ wss│ wss│ (outbound; NAT-friendly)
Node Node Node
omnyserver cert gen --out certs
omnyserver hub start --cert certs/server.crt --key certs/server.key \
--api-token api-secret --grant node-account:node-token:node
omnyserver node start --hub wss://hub:8443 --id worker-01 \
--principal node-account --token node-token --ca certs/ca.crt
omnyserver nodes list --api https://hub:8443 --ca certs/ca.crt --token api-secretAdd --shell and the Hub also brokers OmnyShell sessions — same
port, same certificate, same credentials. A node becomes shell-capable with
--with-shell, in the same process:
omnyserver hub start … --shell
omnyserver node start … --with-shell --shell-label allow-roles=admin
omnyshell exec worker-01 'uptime' --hub wss://hub:8443/shell \
--principal alice --token admin-token --ca certs/ca.crtA standalone OmnyShell node or service can attach to the same Hub just as well — point it at the shell mount:
omnyshell service install node --hub wss://hub:8443/shell --id worker-01 --token …Everything is available both as first-class Dart APIs and as the
omnyserver CLI and a versioned REST API (/api/v1) ready for a future
Web UI. Runs on Linux, macOS and Windows.
Built on omnyhub — the transport, node registry, heartbeat watchdog, RPC correlation and HTTP routing are the framework's, so OmnyServer is only what is actually its own.
See the API Documentation for the full list of classes and APIs.
- Hub orchestrator — node registration, discovery, authentication, live monitoring, audit and event aggregation, with a live model of every node.
- Node agents — connect, heartbeat, report status & capabilities, execute commands/formulas/presets, and reconnect automatically.
- Live monitoring — CPU, memory, storage, OS, processes and logs, designed for real-time dashboards.
- Capability detection — Docker, Podman, Dart, Python, Java, Node.js, Git, SSH, CUDA, Metal, OpenCL — detected dynamically.
- Formulas & presets — idempotent, cross-platform install/manage procedures, composed into presets, with desired-state reconciliation.
- Runs as an OS service —
omnyserver service install hub|nodeinstalls the Hub or an agent as a systemd unit, a launchd job or a Windows scheduled task, so it survives a reboot. See Run as an OS service. - Security — token & Ed25519 public-key auth, role-based authorization, content-derived identity, audit log; RBAC/multi-tenant ready.
- Persistence — repository abstractions with in-memory, JSON-directory and SQLite backends (PostgreSQL/distributed ready).
- HTTP API & metrics — versioned REST API with OpenAPI, structured errors,
and Prometheus/OpenTelemetry-ready
/metrics, served on the Hub's own TLS port beside the node channel — one port to open, one certificate to manage. - Events —
NodeConnected,HeartbeatReceived,FormulaFinished,PresetApplied, … with subscriptions and streaming. - Remote shell — the Hub can also broker OmnyShell sessions on
the same port and credentials (
--shell), and a node can be both an OmnyServer agent and an OmnyShell node in one process (--with-shell).
OmnyServer is a single package with role-based export libraries over a layered
lib/src (domain → application → infrastructure, plus protocol and shared).
lib/
omnyserver.dart core: models, protocol, contracts
omnyserver_hub.dart Hub runtime + server infra + persistence + HTTP API + metrics
omnyserver_node.dart Node agent + monitors + capabilities + formulas + services
omnyserver_cli.dart CLI as a library (buildRunner, HubApiClient)
src/
domain/ value objects, entities, Formula, auth & repository contracts, events
application/ OmnyServerHub, NodeAgent, NodeFormulaService, EventAggregator
infrastructure/ auth, monitors, capabilities, persistence, http, metrics
protocol/ handshake messages + codec, operation payloads
shared/ errors, json helpers, clock, ids
See doc/architecture.md, doc/protocol.md, doc/security.md, doc/formulas.md and doc/deployment.md.
dependencies:
omnyserver: ^0.3.0Or install the CLI:
dart pub global activate omnyserverSupported platforms: Linux, macOS, Windows. Requires the Dart SDK 3.12+.
The fastest way to see the whole stack work is the embedded example, which starts a Hub and a Node in one process and prints the node's live status:
dart run example/omnyserver_embedded_example.dartfinal hub = OmnyServerHub(HubConfig(
host: '127.0.0.1', port: 0,
securityContext: SecurityContext()
..useCertificateChain(certs.serverCert)
..usePrivateKey(certs.serverKey),
authenticator: TokenAuthenticator({
'node-token': TokenGrant(principal: PrincipalId('node-account'), roles: {'node'}),
}),
));
await hub.start();
final agent = NodeAgent(NodeAgentConfig(
hubUri: Uri.parse('wss://127.0.0.1:${hub.port}'),
nodeId: 'demo-node',
credentials: const TokenCredentialProvider(principal: 'node-account', token: 'node-token'),
securityContext: SecurityContext(withTrustedRoots: false)..setTrustedCertificates(certs.caCert),
statusProvider: const SystemMonitor().snapshot,
capabilityProvider: CapabilityScanner.standard().scan,
));
await agent.start();
// The node pushes a status snapshot as it registers, but it still has to reach
// the Hub — give it a moment before reading.
await Future<void>.delayed(const Duration(seconds: 1));
final status = hub.getStatus(NodeId('demo-node'));
print('${status?.cpu.coreCount} cores, ${status?.cpu.usagePercent}% used');dart run bin/omnyserver.dart cert gen --out certs
./run-hub.shThe Hub only speaks
wssand requires a certificate chain + key. Dart's TLS stack rejects a bare self-signed leaf, socert genbuilds a CA → leaf chain; nodes trust the CA with--ca.
With a real certificate (LetsEncrypt, cert-manager, a mounted secret), point the Hub at the directory instead of the two files:
omnyserver hub start --tls-dir /etc/letsencrypt/live/hub.example.com \
--api-token api-secret --grant node-account:node-token:node--tls-dir reads fullchain.pem + privkey.pem and re-checks them
periodically: when the certificate is renewed the Hub rebinds its listener with
the fresh one — no restart, and established connections drain on the old
listener. It replaces --cert/--key; passing both is an error.
dart run bin/omnyserver.dart node start \
--hub wss://hub:8443 --id worker-01 \
--principal node-account --token node-token --ca certs/ca.crthub start and node start run in the foreground and die with your terminal.
To keep one running, install it as a native service — a systemd unit on Linux, a
launchd job on macOS, a scheduled task on Windows:
omnyserver service install hub --tls-dir /etc/letsencrypt/live/hub.example.com \
--api-token api-secret \
--grant node-account:node-token:node
omnyserver service install node --hub wss://hub:8443 --id worker-01 \
--token node-token --ca certs/ca.crtThere is no separate daemon and no config file: service install takes the same
flags as hub start / node start, and bakes this executable plus those flags
into the service definition. Whatever omnyserver hub start … would have done in
your terminal is what the service does at boot. Relative paths are resolved
before they are baked in, so the unit does not depend on where you ran the
install from.
omnyserver service status hub # running
omnyserver service info hub # the parameters, and the command the OS runs
omnyserver service restart hub
omnyserver service stop hub
omnyserver service uninstall hubTwo more, for the day after: service reconfigure hub --cors-origin … re-applies
changed flags to the installed service, and service reinstall hub refreshes the
binary while keeping the config it was installed with — which is how a fleet
picks up a new release.
Use --dry-run to print the generated unit/plist without touching the system,
and --system to install machine-wide (sudo on Linux/macOS, Administrator on
Windows) rather than for your user alone.
Where it keeps its data. A service is supervised forever, so it persists by
default. --data-dir names one root holding everything — credentials and
identity at the top, and the Hub's fleet data (nodes, audit, metrics, desired
state, issued grants) under hub/:
/var/lib/omnyserver/ # --system default (~/.omnyserver otherwise)
hub/ # the Hub's fleet data
Pass --ephemeral for a Hub that keeps nothing. Note that omnyserver hub start
persists by default too, to ~/.omnyserver/hub — a Hub that forgot the fleet,
the audit trail and every credential grant add issued, on every restart, was a
footgun with no upside.
Linux, user scope. A user service runs under
systemctl --user, which stops at logout unless lingering is enabled.installtries to enable it for you and tells you the exact command (sudo loginctl enable-linger <user>) if it cannot.
Flags become part of the service definition, which means
--token,--api-tokenand--grantare written into a file readable by the user who installed it. Restrict it accordingly.
omnyserver nodes list --api https://hub:8443 --ca certs/ca.crt --token api-secret
omnyserver node status worker-01 --api https://hub:8443 --ca certs/ca.crt --token api-secret
omnyserver node restart worker-01 --api https://hub:8443 --ca certs/ca.crt --token api-secret
omnyserver formula run docker worker-01 --action verify --api https://hub:8443 --ca certs/ca.crt --token api-secret
omnyserver preset apply docker-host.json worker-01 --api https://hub:8443 --ca certs/ca.crt --token api-secretThe CLI's operational commands call the Hub's HTTP API — exactly the surface any other client uses.
Every one of them takes either credential the Hub knows:
# The Hub's master API token — one shared secret, audited as "api".
omnyserver node status worker-01 --api https://hub:8443 --token api-secret
# Your own grant (--grant alice:admin-token:admin) — an identity the Hub
# verifies, so the audit trail names you and your roles decide what you may do.
omnyserver node status worker-01 --api https://hub:8443 \
--principal alice --token admin-tokenA grant's roles are checked against the Hub's Authorizer before it may touch
the API at all, and the fail-closed default reserves it for admin — so
node-account's token connects nodes and nothing more, even if it leaks.
Ask the Hub what nodes can actually do, rather than guessing into a free-text box:
omnyserver formula list
# FORMULA NAME ACTIONS
# dart Dart SDK install, update, uninstall, verify
# docker Docker install, update, start, stop, restart, uninstall, verifySave a preset on the Hub once, and apply it by id everywhere:
omnyserver preset save docker-host.json
omnyserver preset apply docker-host --label env=prodA preset file is whatever copy you happen to have; a saved preset is the one
everybody agrees on. preset apply still accepts a file, for a one-off.
Label a node when it starts, then select on the label:
omnyserver node start --id web-01 --label env=prod --label role=web
omnyserver nodes list --label env=prod
omnyserver nodes list --offline # the ones wanting attention
omnyserver formula run docker --label env=prod --action verify # every prod node
omnyserver preset apply docker-host.json --allformula run and preset apply take one node, --node (repeatable), --label,
or --all, and report a result per node. A selector matching nothing is an
error, not a quiet success — "applied to 0 nodes" reads like it worked.
Grants can be baked into the command line (--grant alice:admin-token:admin), or
issued at runtime and revoked without restarting the Hub:
omnyserver hub start … --data-dir /var/lib/omnyserver # or the grants die with it
omnyserver grant add bob --role viewer --note 'read-only dashboard'
omnyserver grant list
omnyserver grant revoke <id> # the next request with that token failsThe Hub stores a hash, not the token. It is printed once, when issued, and cannot be shown again — so the Hub's storage is not a list of passwords, and a lost token is replaced rather than recovered. That is what the grant id is for.
Issuing and revoking are admin-only, so an operator can run the fleet but not
mint itself an admin token.
--data-diris not optional in production. Without it the Hub keeps nodes, the audit trail, metrics, declared state and issued credentials in memory only, and forgets all of it when it stops.
Declare what a node is supposed to be, then ask — at any point later — whether it still is:
omnyserver state set docker-host.json --label env=prod # declare; runs nothing
omnyserver state diff --label env=prod # has it drifted?
omnyserver state reconcile --label env=prod # make it true againDeclaring is not applying. preset apply runs steps and tells you they
succeeded; it cannot tell you anything a week later, after somebody logged into
the machine and changed something by hand. A declaration keeps answering:
state diff re-plans against what the node currently advertises, and an empty
plan means no drift.
state reconcile runs exactly what the plan says is outstanding, so a converged
node does nothing at all — idempotent, and safe on a timer. state diff exits
non-zero when anything has drifted, so it works as a check in a pipeline.
| Role | May |
|---|---|
viewer |
read the API: fleet, live status, history, events, audit |
operator |
also act: restart, shut down, update, formulas, presets |
admin |
everything |
node |
enrol a node — and nothing else; it cannot reach the API at all |
So a read-only dashboard link is a --grant bob:view-token:viewer, and a leaked
node credential still cannot operate the fleet.
omnyserver formula run docker worker-01 --action install --async
# worker-01 dispatched — ops show 2f6eadcd-…
omnyserver ops list
omnyserver ops show 2f6eadcd-… --waitformula run, preset apply and state reconcile answer synchronously, which is
right for a verify and wrong for an install: the Hub gives up after its request
timeout, the node carries on working, and you are told a failure that did not
happen. --async hands back an id instead. The work is the same — only who waits
for it changes.
omnyserver hub start … --alert 'disk>90' --alert 'cpu>95 for 5m' \
--alert 'offline for 2m'
omnyserver alerts # what is wrong right now; non-zero while anything isJudged on the heartbeats the Hub already receives. for 5m is what separates an
alert from a twitch: a node at 95% CPU for one heartbeat is a build running, and
one at 95% for five minutes is a problem. An alert is announced once and resolved
when it clears.
There are no default rules — a tool that invents its own thresholds is a tool that pages you at 3am about a disk it decided was too full.
Read a node's log without logging into it:
omnyserver node logs worker-01 -fThe Hub keeps a bounded tail (the last 500 lines per node, in memory) — for
looking at a machine that is misbehaving now. It is not the audit trail, and not
a substitute for shipping logs somewhere that keeps them. Nodes ship their log by
default (node start --no-ship-logs opts out).
Watch the fleet, and read a node's history:
omnyserver events --follow # tail -f for the fleet (SSE)
omnyserver node metrics worker-01 --since 1h # CPU / memory / disk over time
omnyserver audit # who did what, as the Hub verified itnode metrics reads history the Hub has been recording on every heartbeat
all along — no extra configuration, and it works for any node that has been
connected long enough to report twice.
omnyserver whoami answers what the Hub makes of your credentials:
omnyserver whoami --api https://hub:8443 --principal alice --token admin-token
# principal: alice
# roles: adminThe Hub's API is callable from a web app — that is what omnyserver_web, the
dashboard, is built on. Two things are needed, and both are needed:
omnyserver hub start --cert certs/server.crt --key certs/server.key \
--api-token api-secret --grant alice:admin-token:admin \
--cors-origin https://dashboard.example.com--cors-origin— a browser will not hand a page a cross-origin response unless the server says that origin may have it, and a dashboard is always a different origin than the Hub (in development too:webdevon:8080, Hub on:8443). Without it the app sees network errors and nothing else.- A publicly-trusted certificate, or one trusted at the OS/browser level. The
browser owns the TLS stack; there is no in-page
--insecureto offer, and a self-signed Hub simply will not load.
Client code imports the browser-safe barrel, and drives the very same
HubApiClient the CLI does:
import 'package:omnyserver/omnyserver_client_web.dart';
final client = HubApiClient(
Uri.parse('https://hub.example.com:8443'),
principal: 'alice',
token: 'admin-token',
);
final nodes = (await client.get('/nodes') as List)
.map((n) => NodeDescriptor.fromJson((n as Map).cast()))
.toList();Served over HTTPS on the Hub's own port, alongside the node control channel —
one TLS listener, two surfaces. Versioned under /api/v1:
| Method & path | Description |
|---|---|
GET /nodes |
list registered nodes |
GET /nodes/{id} |
node descriptor |
GET /nodes/{id}/status |
live status snapshot |
GET /nodes/{id}/capabilities |
advertised capabilities |
POST /nodes/{id}/restart · /shutdown · /update |
node control |
POST /nodes/{id}/formula |
run a formula action |
POST /presets/apply |
apply a preset to a node |
GET /events · /audit |
recent events / audit |
GET /openapi.json |
OpenAPI document |
GET /metrics (root) |
Prometheus exposition |
OmnyServer is built on omnyhub: the transport, node registry, heartbeat watchdog, RPC correlation and HTTP routing are the framework's. What OmnyServer adds is what is actually its own — identity, capability detection, formulas, presets, reconciliation, auditing and persistence.
- A Node dials the Hub over
wssat/nodeand sendsHello. - The Hub issues a challenge nonce; the Node answers with a token or a signed nonce. On success the Hub returns the resolved principal and roles.
- The Node registers its descriptor (identity, platform, capabilities, labels). The Hub authorizes the registration — a node credential may enrol a node and nothing else — then adds it to the live registry and begins receiving heartbeats, each carrying a live status snapshot.
- The Hub dispatches operations (formula, preset, restart, …) as RPCs over the same connection and correlates the replies.
All transport is TLS. Authentication is pluggable (token / Ed25519 public key); authorization is role-based and fail-closed; identity is content-derived; every sensitive action is audited. See doc/security.md.
The HTTP API is reached with the Hub's --api-token, or with a grant's
(principal, token) pair — --token plus --principal on the CLI, which the
Hub verifies against the same grant store the node channel uses, so the audit
trail names the operator and their roles decide what they may do. There is no
third way in, and no way in without one of them: a Hub started with neither
authenticates nobody, and says so. Only /healthz and /metrics are open, so a
load balancer and a Prometheus scraper — neither of which carries a token — can
still tell whether the Hub is alive.
OmnyServer is one of four packages sharing the same identity-centric,
NAT-friendly model — nodes dial a Hub outbound, and you address them by identity
rather than host:port.
| Package | What it does |
|---|---|
| omnyhub | The HUB framework everything below is built on: transport, routing, auth, node registry and control plane. |
| omnyserver (this) | Fleet orchestration — monitoring, capabilities, formulas, presets, desired-state reconciliation. |
| omnyshell | Remote shell — SSH-like sessions brokered to a node by identity. An OmnyServer Hub can host its broker (--shell). |
| omnydrive | File & git drive synchronization. |
- Remote agent self-update and OS-update orchestration.
- Additional transports (gRPC, QUIC, message bus) behind omnyhub's
Transport. - PostgreSQL and distributed persistence backends.
- Richer reconciliation (dependency ordering, version comparison).
- Web UI on top of the REST API; RBAC / multi-tenant authorization.
- Kubernetes orchestration and AI-workload scheduling.
dart pub get
dart analyze
dart test
dart run example/omnyserver_embedded_example.dartGraciliano M. Passos: gmpassos@GitHub.