Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Realms

This document explains what a realm is in Yuneta, how the agent stores and manages them, how they partition the on-disk layout, and what is not scoped per realm (which surprises people).

Sibling to YUNO_LIFECYCLE.md, DEBUGGING.md, IPC.md. Those documents cover the unit of deployment, which is the yuno. This document covers the unit of tenancy, which is the realm.


1. Mental model

A realm is a logical partition of a Yuneta host:

                  ┌─────────────────────────────────┐
                  │           Yuneta host           │
                  │     (one yuno_agent process)    │
                  └────────────────┬────────────────┘
                                   │ owns
                                   ▼
        ┌──────────────────────────────────────────────────┐
        │                  realms topic                    │
        │  id = "<name>.<role>.<env>"  (the composed URL)  │
        │  realm_owner  │  realm_role  │  realm_name  │ env│
        └──────────────────────────────────────────────────┘
                ▲                                     │
                │  parent_realm_id                    │  hook: yunos
                │  (self-fkey, hierarchical)          ▼
        ┌──────────────────────────────────────────────────┐
        │                   yunos topic                    │
        │  realm_id  ───►  (the realm above)               │
        └──────────────────────────────────────────────────┘

Three things live inside a realm: yunos, their working directories, and a bind_ip. Three things look per-realm but are not: the port pool, the cert sync directory, and most authzs. Section 7 has the full list.

The classic deployment pattern: one realm per project/customer (wattyzer, estadodelaire…), all sharing the same agent on a host.


2. Data model — the realms topic

Defined in treedb_schema_yuneta_agent.c. Required fields (all strings unless noted):

FieldNotes
idpkey. Format: <realm_name>.<realm_role>.<realm_env> — composed at create time, never edited.
realm_ownerTenant identifier. Used in the on-disk path. Immutable.
realm_roleLogical “role” of the realm (for example prod, api). Immutable.
realm_nameHuman-readable name. Immutable.
realm_envEnvironment label (for example prod, staging, dev). Immutable.

Optional fields:

FieldNotes
bind_ipIP the realm’s yunos listen on. Default "127.0.0.1".
realm_disabledBoolean, persistent. Currently advisory — see §10.5.
parent_realm_idfkey → another realm. Enables hierarchical realms.

Two fields kept in the schema for historical reasons but flagged deprecated (treedb_schema_yuneta_agent.c around the realms topic):

These now live on the agent, not on the realm — see §7.

Hooks (relations to other topics):

HookJoined topicInverse fkey on the other side
realmsrealmsparent_realm_id (parent → children)
yunosyunosrealm_id (realm → its yunos, 1:N)

3. The composed URL is the identity

The id is more than a label. The agent constructs it at create time as

<realm_name>.<realm_role>.<realm_env>

and uses it as the URL of the realm. You see it in these places:

Because the four parts (name, role, env, plus owner) are immutable and effectively make up the identity, the only mutable field on a realm is bind_ip (see §5).


4. On-disk layout

Built by build_yuno_private_domain() at c_agent.c:

/yuneta/realms/<realm_owner>/<realm_name>.<realm_role>.<realm_env>/<yuno_role>_<yuno_name>/
  ├── bin/     ← materialised configuration JSON files (see YUNO_LIFECYCLE.md §2.2)
  └── logs/    ← per-yuno log files (see DEBUGGING.md §5.1)

The realm owns the middle segment. Each yuno gets its own <yuno_role>_<yuno_name>/ subdirectory.

Important nuance: create-realm does not create /yuneta/realms/<owner>/<url>/. It only writes the treedb record. The directory is materialised by mkrdir() inside build_yuno_private_domain() the first time a yuno of that realm is started. Empty realms therefore leave no on-disk trace.

The binary repository at /yuneta/repos/<tags>/<role>/<version>/<role> (see YUNO_LIFECYCLE.md §2.1) is realm-agnostic. A binary is shared across all realms on the host.


5. CRUD commands

Registered in the agent’s command table at c_agent.c. Same patterns as the other agent commands (pm_<name> permission schemas, gobj_create_node / gobj_update_node / gobj_delete_node against the treedb).

5.1 create-realm

Handler at c_agent.c. Schema pm_create_realm.

Required: realm_owner, realm_role, realm_name, realm_env. Optional: bind_ip (default 127.0.0.1).

Flow:

  1. Validate that none of the four required fields is empty.

  2. Compose the id (<name>.<role>.<env>).

  3. Check if a row with that id already exists. If yes, return “Realm already exists”.

  4. gobj_create_node() — write the row.

No on-disk directory is created. No bootstrap of a “default” realm exists (see §10.4).

5.2 update-realm

Handler at c_agent.c. Schema pm_update_realm.

The only mutable field is bind_ip. The four pkey components are not in the param schema, so you cannot mutate them. To “rename” a realm, you must delete it and create it again. That is difficult, because the yunos refer to it by realm_id, the immutable composed URL.

5.3 delete-realm

Handler at c_agent.c.

Refusal conditions:

On success: removes the treedb row (gobj_delete_node()). The on-disk directory /yuneta/realms/<owner>/<url>/, if it ever was created, is not removed automatically — orphan directories survive realm deletion. Clean up by hand if it matters.


6. Realm ↔ yunos

The link is the yunos.realm_id fkey (treedb_schema_yuneta_agent.c). One realm has N yunos (realms.yunos hook), and one yuno belongs to exactly one realm.

At run-yuno time the agent reads the realm record to compute the working directory (c_agent.c):

json_t *realm   = get_yuno_realm(gobj, yuno);
realm_owner    = kw_get_str(gobj, realm, "realm_owner", …);
realm_role     = kw_get_str(gobj, realm, "realm_role",  …);
realm_name     = kw_get_str(gobj, realm, "realm_name",  …);
realm_env      = kw_get_str(gobj, realm, "realm_env",   …);
snprintf(url, …, "%s.%s.%s", realm_name, realm_role, realm_env);
build_path(bf, bfsize, "realms", realm_owner, url, role_plus_id, NULL);

Implication: update-realm cannot change the working-dir path because the four contributing fields are immutable. The path is stable for the realm’s lifetime.


7. What is not scoped per realm

This is where production gotchas live. The fact that some things look realm-scoped but is not, is the most common cause of surprises.

7.1 Port pool (range_ports / last_port)

Agent-level attributes (c_agent.c):

range_ports = "[[11100,11199]]"   ← default range
last_port   = 0

Allocation in get_new_service_port() at c_agent.c reads the agent’s range_ports, advances last_port globally, and hands out the next port. All realms on the host share the same pool. Two realms asking for an automatic port get adjacent ports from the same range, period.

The deprecated range_ports/last_port columns still listed in the realms schema (see §2) are vestigial, and the runtime ignores them. Do not write code that reads them.

7.2 Cert sync directory

Agent-level (c_agent.c): a single cert_sync_store_dir (default /yuneta/store/certs) is watched. Changes broadcast a reload-certs event to every yuno on the host, regardless of realm.

If two realms need disjoint cert sets, you cannot get that from the agent’s cert-sync mechanism — you have to ship the certs directly into each yuno’s config and skip cert-sync.

7.3 __username__ / authzs

The agent has one __username__ attribute (c_agent.c) with “permission for all” semantics. Per-command pm_<name> schemas gate access, but the gating is against the caller’s user, not against the realm. The agent itself has no built-in “user X can read realm R but not realm S” predicate. If you need that, enforce it in the auth_bff yuno (see the upcoming YUNO_AUTH.md).

7.4 The binary repository

/yuneta/repos/<tags>/<role>/<version>/<role> is host-global. A binary uploaded for one realm is visible to (and runnable by) yunos in every realm. The binaries topic does not have a realm fkey.

7.5 Audit log

use_audit_command_file + max_megas_audit_file are agent-level attributes — one audit file per host, not per realm.


8. bind_ip — the realm’s listen address

The one truly per-realm operational knob. Default 127.0.0.1 (loopback only). On production hosts you almost always want this changed to the public IP or the internal IP where the realm must listen.

bind_ip is the IP for the yunos in this realm to bind their listening sockets to, not a routing rule. Each yuno reads it from its realm record at start and uses it where its config refers to bind_ip (typically by templating a tcp://${bind_ip}:${port} URL).

Set at create time, mutable via update-realm:

ycommand -c 'update-realm id=<name>.<role>.<env> bind_ip=10.0.0.5'
ycommand -c 'kill-yuno yuno_role=<role>'   # for each affected yuno
ycommand -c 'run-yuno'                     # pick up the new bind_ip

A change to bind_ip does not restart anything automatically.


9. Hierarchical realms (parent_realm_id)

The realms topic has a self-fkey parent_realm_id and a hook back to the same topic. This permits a tree of realms (a parent realm with child realms). Current runtime usage is limited — the field is mainly informational (controlcenter displays the tree). Nothing in build_yuno_private_domain walks the parent chain when it computes paths. The working dir uses only the four immutable fields of the realm itself.

Treat it as metadata. Do not build infrastructure on the assumption that a child realm inherits anything operational from its parent.


10. Sharp edges

10.1 The four pkey components are immutable

realm_owner, realm_role, realm_name, realm_env. There is no rename. If you got one wrong, you must delete the realm (after deleting its yunos) and re-create it.

10.2 delete-realm only refuses on existence of yunos, not on

running state

It iterates the yunos hook and bails if any rows exist. If you disable-yuno everything and forget to delete-yuno first, delete-realm still refuses. The error message tells you, but it can be confusing the first time.

10.3 The on-disk realm directory is never cleaned up

Both at delete-realm and on a host migration, the directory under /yuneta/realms/<owner>/<url>/ is left behind. If you reuse the same realm coordinates later, you inherit the old content. When you retire a realm, clean the directory by hand.

10.4 No default realm bootstrap

The agent does not create a realm on first run. A brand-new agent with no realm cannot run any yuno (the yuno create requires realm_id). First action on a new host is always create-realm.

10.5 realm_disabled is advisory

The field exists in the schema and you can set it via the treedb tools. Nothing in c_agent.c’s start path checks it before running yunos of a disabled realm. Do not trust it as a kill switch. It is information for operators, not enforcement.

10.6 The deprecated range_ports and last_port on realms are traps

They are still in the schema. Code that “reads the port range of the realm” reads stale data. The runtime ignores those fields. The real port pool is on the agent (c_agent.c). If you find code that reads realm.range_ports, that code has a bug. Correct it to read the pool of the agent.

10.7 One agent per host means one port pool per host

If you need disjoint port pools for security or for audit, you need two agents on the host, or two hosts. Realms alone cannot give you this.

10.8 The cert sync reload-certs event goes to all yunos

The agent does not filter a cert change by realm. Every yuno receives the reload event and decides what to do with it. A yuno without TLS ignores the event. A yuno with TLS reloads. To scope a cert change to one realm, you must partition the yunos in another way, for example with cert paths in the per-yuno config, which does not use cert sync.


11. Recipes

11.1 Create a realm for a new project

# 1. create the realm
ycommand -c 'create-realm realm_owner=acme realm_role=prod realm_name=widgets realm_env=production bind_ip=10.0.0.7'

# 2. list to confirm
ycommand -c 'list-realms'

# 3. … now you can create yunos against it
ycommand -c 'create-yuno realm_id=widgets.prod.production yuno_role=my_service yuno_name=instance_01'

11.2 Move all yunos out of a realm before deleting it

REALM_ID=widgets.prod.production

# disable each yuno
for y in $(ycommand -j -c "list-yunos realm_id=$REALM_ID" | jq -r '.[].id'); do
    ycommand -c "kill-yuno    id=$y"
    ycommand -c "disable-yuno id=$y"
    ycommand -c "delete-yuno  id=$y whole=1"
done

# now the realm is empty
ycommand -c "delete-realm id=$REALM_ID"

# sweep the leftover directory by hand (the agent does not)
sudo rm -rf "/yuneta/realms/acme/$REALM_ID"

11.3 Change the bind_ip of a realm

REALM_ID=widgets.prod.production
ycommand -c "update-realm id=$REALM_ID bind_ip=10.0.0.99"

# update takes effect on next yuno start
for y in $(ycommand -j -c "list-yunos realm_id=$REALM_ID" | jq -r '.[].id'); do
    ycommand -c "kill-yuno id=$y"
done
ycommand -c "run-yuno"

11.4 List which yunos belong to which realm

ycommand -c 'list-realms'                                 # all realms
ycommand -c 'list-yunos realm_id=widgets.prod.production' # one realm
ycommand -c 'list-yunos'                                  # all yunos with their realm_id

12. Code pointers

WhatWhere
realms topic schematreedb_schema_yuneta_agent.c
create-realm handleryunos/c/yuno_agent/src/c_agent.c
update-realm handlerc_agent.c
delete-realm handlerc_agent.c
Realm URL composition (<name>.<role>.<env>)c_agent.c
Yuno working-dir from realmc_agent.c (build_yuno_private_domain)
Agent’s port poolc_agent.c
Agent’s cert sync attrsc_agent.c
bind_ip defaulttreedb_schema_yuneta_agent.c (realms topic, bind_ip field)
parent_realm_id self-fkeytreedb_schema_yuneta_agent.c
realms.yunos hooktreedb_schema_yuneta_agent.c