Skip to main content
Omega is driven entirely by a single profile file (a YAML document). The profile is the source of truth for a device’s identity, how it reaches the platform, which capabilities it runs, and the security policy that governs it. This page is the complete reference: the mental model, every field, how the profile is validated, and a worked walkthrough for writing one.

The mental model: build vs. profile

Two independent things decide what a device can do. Confusing them is the most common source of “why isn’t my module running” questions.

The build decides what is POSSIBLE

Omega is compiled with a set of Cargo features (a “SKU”). A feature that is not compiled in simply does not exist in that binary. See Build Omega.

The profile decides what RUNS

The profile’s modules.required list names the modules to start, and security.capabilities grants the control-plane permissions. Nothing runs unless it is listed.
The two are checked against each other at load time. If the profile names a module or capability that is not compiled into the binary, Omega refuses to start with an exact error — it never runs half-configured. This guarantees that a device which is online is a device whose profile the binary can actually honor.

Where the profile lives and how it loads

Omega loads its profile from a file path passed on the command line:
On an installed device the installer generates this file (commonly config.yaml in the config directory) with the device’s identity and certificate paths filled in, and registers the service to run omega-agent --client <that path>. To regenerate it, delete the file and re-run the installer. There is no hidden fallback: the file named on the command line is the only profile Omega reads. A malformed or invalid profile is fatal (see Validation).

Top-level structure

Anything else at the top level is treated as a module section (e.g. sqlite-replication:) and is decoded by the module that owns it. Unknown sections are warned about (likely a typo or a section for a module you did not enable) but are not fatal.

connection

Everything Omega needs to establish an authenticated session with the platform.

connection.mqtt

connection.mqtt.tls

Each path field has a <field>_env twin (ca_cert_path_env, client_cert_path_env, client_key_path_env) that reads the path from an environment variable instead. See Environment overrides.

connection.jitr — enrollment

Present only on a device that has not yet enrolled. Just-in-time registration (JITR) exchanges a factory-provisioned bootstrap key/cert for the device’s own certificate, then this block is removed. Full walkthrough: Enrollment (JITR).
The enrollment proof carries a timestamp the platform accepts within a 5-minute window. A factory line with no NTP and a drifted real-time clock fails enrollment — and it fails looking exactly like a bad key. Set the device clock before flashing.

connection.cert_renewal

Controls automatic certificate renewal (a background thread; see How renewal works).

modules

The single list of modules Omega runs. There is exactly one list — required — and it means what it says.
  • A module in required must be compiled into this binary. If it is not (its build feature is disabled), Omega fails to start with a precise error naming the module — it is never silently skipped.
  • Nothing auto-runs. Only modules listed here start. The build deciding a module can exist does not make it run; the profile must name it.
  • Module order does not need to be managed by hand — dependencies are resolved automatically.
Earlier versions of Omega had a second optional list whose modules were skipped when unavailable. That is gone — silent skipping hid real build/config mismatches. A legacy profile that still carries an optional: key parses (the key is ignored), but those modules will not run. Move anything you actually want to required.
Because a required module that isn’t compiled is fatal, the profile’s modules.required is effectively a manifest that the binary must satisfy — which is exactly the guarantee you want: an online device is one whose declared modules are all present.

security

Capability grants, control-message authentication, and resource quotas.

security.capabilities

A map of capability → bool. A capability gates whether a module will accept control messages for its domain — a module can be running but reject every command if its capability is not granted (true). Two rules make this predictable:
  1. A granted capability must be available in this build. If you set some-cap: true but nothing in the binary provides some-cap, Omega fails to start with an exact error (security.capabilities enables "some-cap" but it is not available in this build…). Capabilities are validated against what is compiled, so there is no separate list to keep in sync with the build.
  2. A grant for a module that isn’t in modules.required is reported as inert (a warning) — the grant does nothing because the module isn’t running.
Most capability names are module names (ota, rpc, device-control, …). A few are provided internally by the ota module rather than being standalone modules — inventory and node — and are valid whenever ota is compiled.

Control-message signing — security.signed_control

Resource quotas

Shell command allowlist — security.rpc.allowed_commands

The rpc (and remote-command) capability runs only programs on this allowlist. An empty list denies everything. Keep it as narrow as the device actually needs — a broad allowlist is effectively remote shell.

Environment overrides

Almost every string field in connection has a <field>_env twin that reads the value from an environment variable instead of hard-coding it in the file. For example server_url / server_url_env, device_id / device_id_env, tls.client_key_path / tls.client_key_path_env. Prefer the _env form for secrets and per-device values so the profile itself stays free of machine-specific state. A few behaviors are driven by process environment variables rather than the profile:

Validation

Validate a profile without starting the device:
  • Exit 0 and ✓ config valid — the profile parses and every module and capability it names is present in this binary.
  • Exit 1 with one ERROR: line per problem — validation collects all errors, each naming the exact field and cause, e.g.:
The same validation runs at boot and when a new profile arrives over the air, so a config error is caught before it can affect a running fleet. There are no silent failures: a profile problem is always fatal and always names what to fix.

What happens on a bad profile at startup

If the profile is malformed or names something the binary can’t provide, Omega does not come up half-working. It:
  1. writes the exact reason to logs/startup-error.log (and, on Windows, the Application Event Log),
  2. exits with a distinct code (78), and
  3. the service supervisor stops the service cleanly with that reason surfaced — so an operator sees what is wrong instead of a generic “service failed to start.”
The rule across the board: bad config, an uncompiled module, an ungranted capability, and a broker-denied topic all fail fatally with the exact reason recorded durably. Transient conditions (broker unreachable) retry; anything a human must fix stops loudly.

Writing a profile from scratch

1

Start from identity

Set name, and the connection block: transport, server_url, device_id, root_topic, and cert_dir. These are the fields that make the device reachable.
2

Point TLS at the certificates

Fill connection.mqtt.tls with the CA bundle and the device cert/key paths — or, for a fresh device, add a connection.jitr block so it enrolls on first boot and writes its own cert into cert_dir.
3

List the modules you want to run

Add each module to modules.required. Every one must be compiled into the binary you deploy — check with omega-agent --validate.
4

Grant the matching capabilities

For each module that accepts control messages, add security.capabilities.<module>: true. Validation will tell you immediately if a grant references something the build doesn’t have.
5

Set quotas and signing

Set max_payload_bytes to fit your broker’s packet limit, and enable signed_control for authenticated control.
6

Validate before you ship

omega-agent --validate --client config.yaml — fix every ERROR: line until it prints ✓ config valid.

Minimal profile

The smallest profile that boots, enrolls, and stays managed:

Fuller profile

Adds enrollment, renewal, a capability grant, signed control, and quotas:

Common pitfalls

Fatal at startup with required module "X" is not available…. Either build a binary that includes that module’s feature, or remove it from modules.required. Run omega-agent --validate to catch it before deploying.
Fatal with security.capabilities enables "X" but it is not available in this build. Capabilities are validated against the build — remove the grant or add the feature.
Usually max_payload_bytes set larger than the broker or platform accepts, so large publishes are dropped in transit while small ones get through. Align it with the smallest packet-size limit on the path.
The module is running but its security.capabilities.<module> grant is missing or false. Grant it (true).

See also

  • Build Omega — how features/SKUs decide which modules a binary contains.
  • Connect — transport, broker, and the topic namespace in depth.
  • Enrollment (JITR) — provisioning a device its first certificate.