Configurer¶
The configurer is the persistence layer for component configurations and the only resolver of property references. On startup it loads saved configs from disk and publishes them over NATS. During operation it receives update, patch, commit, and delete commands and writes valid configs to disk.
Module id: cfg. The config types it owns are listed under Configuration. Every other writable type is ingested here and dispatched to the owning module.
See Configuration for the five stages (startup, intent published, intent status, effective published, effective status) and Messaging for the full request-reply catalog.
Configuration¶
| Config type | Component ref | Description |
|---|---|---|
bundleinfo |
cfg.bundleinfo.<bundleId> |
BundleInfoConfiguration is the read-only view of one configuration bundle (RFC-014), published by the configurer as cfg.bundleinfo.<bundleId> - the same pattern as file -> fileinfo and props -> propsinfo, except that here there is no writable half: a variant is a file, not a config, and bundle update is the only thing that writes one. |
props |
cfg.props.<id> |
PropertiesConfiguration is the BOUND form of a props resolution: a list of literal configuration values. |
propsinfo |
cfg.propsinfo.<id> |
Resolved property values, keyed by property name. Each value is the JSON the property resolved to, of whatever type that property holds. |
resolution |
cfg.resolution.<id> |
ResolutionConfiguration is the envelope that binds a property bag into another writable config's fields before the bound body is dispatched to the owning module (RFC-022): the target-shaped body under targetConfig, the bag under properties, and the ref and type of the target. |
role |
cfg.role.<name> |
RoleConfiguration defines a configured role, cfg.role.<name> (RFC-035 D2): what a kind of person holding it may read, patch, write and manage, as four lists of component filters, and which commands they may send, as a fifth. |
secret |
cfg.secret.<id> |
SecretConfiguration declares a secret, cfg.secret.<id>: what its value is and which config types may reference it. |
teapot |
cfg.teapot.<id> |
Mainly used for testing or probing. |
teapotinfo |
cfg.teapotinfo.<id> |
TeapotConfigurationInfo is the read-only mirror the configurer derives from a teapot config, produced for the same reason the teapot is: so the derived-companion path has a subject with nothing behind it. |
Persistence¶
One file per config component, stored under a configured directory (default /run/algol/config).
| Field | Description |
|---|---|
ref |
The component this config configures |
managedBy |
Where the config originated (cli, ext, module, ...) |
bundle |
Configuration-bundle attribution. Optional; absent means unattributed |
schemaVersion |
Schema generation of config |
config |
The configuration payload |
Update changes the in-memory running configuration. Commit writes a config's latest intent to disk, whether or not its module has answered it or its resolution is OK (store-managed, not read-only; a delete intent removes the file). Its reply names the refs whose committed intent is not in effect yet, with the stage that says so - intent status WAIT or ERROR, effective status WAIT or ERROR, or no answer to this generation - a report, never a refusal. On the next reboot the configurer re-publishes those files as startup configurations.
Configuration bundles¶
A bundle is the attribution key - the thing a config is stamped with and config commit --bundle persists by. A variant is one installed set of it, and it is what a file is: installed bundles live under the bundle directory (default /var/algol/cfg/bundles), one JSON file per variant, named <bundle>@<variant>.json. Several variants of one bundle may be installed side by side; activating one withdraws what another placed.
Bundles on disk are inert. Boot never replays them. Boot applies the startup set. Each bundle with a variant installed is published as read-only cfg.bundleinfo.<bundle>, one block per variant, with per-member drift (the variant's body hash vs running intent, plus the bundle holding the ref).
algol bundle ls
algol bundle info usbexport
algol bundle update usbexport@v1 --add sys.systemdmount.disk01 --add trigger.trigger.start_export
algol bundle activate usbexport
algol bundle delete usbexport
update edits the file, and is the only verb that requires the variant - it creates what it names, so there is nothing to resolve against. The others take a bare bundle: info shows every variant of it, delete removes every variant, and activate resolves it where exactly one is installed. activate is what applies a bundle to the device (and takes --commit, because what it changes is running configuration). --add captures the ref's operator intent, so property references stay unresolved and the bundle is portable. update --adopt makes the running configs follow the edit at once: a ref added is stamped with the document's bundle (taken over from another bundle only with --force), a ref removed loses its stamp and keeps running, unattributed - nothing is published, and config commit persists it. See RFC-014.
Manifests¶
The transport artifact: bundles plus an ordered activation intent, optionally with plain device config. algol manifest export / import are the backup and restore path.
Secrets¶
A secret declaration (cfg.secret.<id>, RFC-001) is a config like any other; its value is not. Values live under --secret-dir (default /var/algol/cfg/secrets), one file per secret named by its id, mode 0600 in a 0700 directory, written to a temp file and renamed into place with the directory fsynced. The directory is created at startup; one that other users can reach fails the boot rather than being narrowed. A value file whose id has no declaration is ignored, with one warning at startup.
The value goes in and out through the secret's own command subject (local.cfg.cmd.secret.<id>.set / .get), never through the configuration API. Who asked is the reply subject's inbox prefix (_INBOX.<moduleId>, _INBOX.userkey.<key>, RFC-034), and every set and get is logged with it. set validates the value against the declared kind and stores it; a value equal to the stored one is a no-op. get is answered for a person always, and for a module only when a config dispatched to that module references the secret from a type the declaration admits (RFC-001 D4) - decided from the same records config ls shows. Declaring or scoping a secret (config update, patch or delete on cfg.secret.*) needs a person's identity; a module is refused with 403.
A value set, rotated, or deleted with its declaration re-dispatches every config that references the secret, unchanged and with force, so the module's apply runs again and dereferences the new state (RFC-001 D5). Nothing else re-dispatches for a secret - the configurer's own restart republish in particular does not.
Events¶
The configurer publishes one event, config_changed, on local.cfg.event2.config_changed.<Base32(config ref)>, whenever a config's record moved - startup file written, intent published, intent status, the module's answer - or was removed. The payload is the record as config ls shows it after the change (three content digests, the two status axes with their messages), never a body - built from the same summary a listing carries, so the two never report different digests for one config.
A changed ref is held for 250 ms and the net change is published once, so one operator action is normally one event and a state that lasted only inside the hold is not reported. A configurer restart publishes nothing; a device boot publishes one event per config once its module has answered. Like every event2 it reaches the cloud only through a gate eventForwardFilter naming it (cfg.config_changed.*).
NATS endpoints¶
| Command | Request | Description |
|---|---|---|
cfg.bundle.<id>.activate |
JSON, signed | Applies one installed variant to the running store: every member is placed and stamped with the bundle, and every config carrying that bundle which the variant does not list is withdrawn. An omitted variant resolves only where exactly one is installed. Persisted by a bundle commit. |
cfg.bundle.<id>.delete |
JSON, signed | Deletes one installed variant's file, or every variant of the bundle when none is named, refused while a file still describes running state held by its own bundle unless forced. Running configs are never touched by it. |
cfg.bundle.<id>.update |
JSON, signed | Creates or edits one variant of a bundle from running configs: members added from their running intent or removed, and the description set. The variant is required - update creates what it names. Every invocation writes a whole new artifact. With adopt the running configs follow the edit at once - refs added are stamped with the bundle, refs removed lose their stamp - persisted by a commit. |
cfg.manifest.<id>.check |
bytes, application/octet-stream |
Says what importing the manifest would do, by the validation the import runs, writing nothing: the bundles installed, the activations, the configs applied, and with the replace header the running configs and bundle files converging to it would take off the device. |
cfg.manifest.<id>.export |
JSON or none | Answers a manifest describing this device, gzipped: with no body or an empty request the backup - every installed bundle plus the whole startup set - and with bundles named a rollout of those bundles, activated in the order given. A named bundle whose variant is omitted resolves only where exactly one is installed, and the manifest names the full address either way. |
cfg.manifest.<id>.import |
bytes, application/octet-stream, signed |
Installs a manifest: its bundles, the activations it lists, and its config section, then persists them; the body is the manifest, plain JSON or gzipped. Validation is all-or-nothing before anything is written; a failure after it comes back inside the result, on the item it belongs to. |
cfg.secret.<id>.get |
none | Reads a secret's value, answered only into the requester's own inbox and only to a requester a dispatched config admits to it. |
cfg.secret.<id>.set |
JSON, signed | Stores a declared secret's value, answered only into the requester's own inbox; the reply says when and how many bytes, never the value. |
cfg.store.<id>.commit |
JSON, signed | Persists the latest intent of the configs a filter matches to their startup files, so it survives a restart, whether or not the module has answered it yet; with a bundle, the members of that bundle only. The reply names what the requester's role left out. |
cfg.store.<id>.delete_bundle |
JSON, signed | Tears down a bundle: a delete for every running config carrying it, in reverse module order, best-effort, with each member's outcome in the reply. It needs no bundle file. |
cfg.store.<id>.list_config_status |
JSON | Lists the configs a filter matches as records of the stages a config travels - startup, intent published and its status, effective published and its status - each stage included on request. Records outside the requester's read set are left out silently. |
cfg.store.<id>.patch |
JSON, signed | Changes the enabled state, the property values or both of one running config, atomically; a field left out is left as it is. Properties can be set, never added, removed or retyped. |
cfg.store.<id>.revert |
JSON, signed | Returns the running configs a filter matches to the body persisted on disk - the inverse of a commit; with a bundle, the members of that bundle only. A config already at its committed body is left alone. |
cfg.store.<id>.update |
JSON, signed | Creates, edits or deletes one running config: the update envelope names the ref, the operation and the config body of the addressed type. The reply is the configurer's answer for the intent; the module's own answer follows on the config status subject. |
Plus app.main.status, the health probe every module answers, declared under the configurable module id. The CLI verbs behind these commands are on the CLI page.
Request and response shapes: Messaging.