10.4 Editing config safely (PUT /services/raw, dry-build, hot-swap)
On a running node you edit a tenant through the services self-config API,
not by hand-editing files and restarting. It validates, dry-builds, and
hot-swaps atomically — an invalid edit never touches the running tenant.
The services mount
{ "path": "/services", "service": "services",
"config": { "access": { "read": "all", "write": "A" } } }
Protect it tightly — it's the tenant's control surface, and only operators
may change authorization config (5.0). Lock writes with write: "A" and set the
tenant's operatorRoles accordingly.
Reading and writing the config
| Request | Behavior |
|---|---|
GET /services/catalogue |
Available service types with their config schemas |
GET /services/catalogues |
Registered external catalogues and whether the operator permits their hosts |
GET /services/catalogue/available |
Built-in and registered services/adapters available to install |
POST /services/catalogue/install |
Fetch, hash-verify, compile-check, and deploy one catalogue item (8.11) |
GET /services/services |
Mount summary {mounts: [{path, service, access}]} |
GET /services/infras |
Operator-managed backends this tenant may reference, with values and secrets redacted (10.7) |
GET /services/raw |
The full tenant config + an ETag (version) |
PUT /services/raw |
Replace the config: parse → dry-build every mount → persist → hot-swap atomically → 204 + new ETag |
How a write is made safe
PUT /services/raw doesn't just save bytes. It:
- Parses the new config.
- Dry-builds the whole tenant — every mount is validated as if loaded.
- Only if that succeeds, persists and hot-swaps the live tenant atomically.
An invalid config is rejected with 400 and the reasons, and the running
tenant is left untouched. You cannot break a live tenant with a bad edit — the
worst case is the edit is refused.
Optimistic concurrency with If-Match
To avoid clobbering a concurrent change, send the ETag you read:
PUT /services/raw
If-Match: "<etag from GET /services/raw>"
A mismatch is 409 conflict — someone else changed the config since you read it.
Re-GET, reapply your change, retry. This is the standard read-modify-write loop.
Secrets are write-only
GET /services/raw reads back secrets as the marker "<secret>" —
auth.jwtSecret and everything under a top-level secrets block. On PUT, a
"<secret>" marker is replaced with the stored value, so a normal
GET → edit → PUT round-trip never destroys a secret. To rotate a secret,
supply a real value in its place. (A marker with no stored counterpart is a
400.) See 10.6.
File edits vs. API edits
- Editing
tenants/<name>.jsonon disk takes effect only on a reload (restart, or anyPUT /services/rawround-trip). - Editing through
PUT /services/rawvalidates and hot-swaps with no restart — the preferred path on a running server.
For a versioned or multi-file change, rs2 pull mirrors the tenant config and
every discovered spec subtree into a local rs2/ directory; edit it in git and
use rs2 push. Push uses the same If-Match/If-None-Match guarantees for
config and specs, aborting on a remote edit instead of clobbering it. Code bytes
and application data are intentionally outside that mirror.
Next: deploying and rotating custom code on a running node.
← Previous: 10.3 Logging & observability · Manual home · Next: 10.5 Deploying & rotating code →