feat(bus): land the single-host loopback arm of the zenohd profile
Phase 5.1 of `.settings/features/feature-zenoh-transport.md`, split per the
Security & Identity council's chair call on meridian-nok3. The held commit
060b755c5 carried Compose *and* Helm; this lands only the arm the relay can
actually boot against, and holds the rest.
WHAT LANDS: the root `docker-compose.yml` `bus` profile (opt-in, default off),
`deploy/compose/zenoh/zenohd.json5` as the one router config both stacks will
mount read-only, `docker-compose.override.yml.example`, `.env.example`,
`.env.local.example`, `run.sh` with `zenoh` added to `PORT_KEYS`,
`.settings/run-profiles/local.yaml`, and the `env-doctor` bus report. Every
endpoint it renders is `tcp/localhost:<port>`.
WHAT IS HELD: `deploy/charts/**`, `deploy/compose/compose.yml`,
`compose.dev.yml`, `deploy/compose/.env.example` and
`deploy/compose/README.md`. Those render `tcp/zenohd:7447` and
`tcp/<pod>.<service>:7447`, and `ZenohBusConfig::validate` refuses a reachable
endpoint while no lane floor exceeds the authenticated link profile — which
under the in-deployment floors is unconditional, and which `from_env` cannot
raise by design. Confirmed against the binary built from this tree, not only
against `a_reachable_endpoint_is_refused_while_the_link_profile_meets_every_floor`:
MERIDIAN_BUS=zenoh MERIDIAN_ZENOH_ENDPOINTS=tcp/zenohd:7447 -> exit 1,
"endpoint `tcp/zenohd:7447` is reachable beyond this host ... an
event-injection primitive"
The held chart went further and made that state the documented default:
`_validate.tpl` failed the render unless endpoints were supplied whenever
`bus.mode != redis`. Shipping it would have pushed operators into a guaranteed
crash rather than merely allowing one.
Three findings from zenohd's own source shape the router config, none visible
from its documented surface. zenohd runs `config.adminspace.set_enabled(true)`
and `config.plugins_loading.set_enabled(true)` unconditionally after loading
the file, so the levers that hold are `--adminspace-permissions none`,
`plugins_loading.search_dirs: []`, an empty `plugins: {}`, and never `-P`.
Multicast scouting is force-ENABLED when the key is unset, so
`--no-multicast-scouting` is what survives a config swap, while gossip has no
CLI flag at all — which is why mounting the file is mandatory rather than a
convenience. And `transport.shared_memory.enabled` defaults TRUE in Zenoh 1.x,
so it is set explicitly false; Phase 6 is unscheduled and gated on a measured
crossover plus memlock/CAP_IPC_LOCK.
`run.sh`, `PORT_KEYS` and `local.yaml` move in this commit and not before: a
generated `zenohd:` override block against a base file with no `zenohd` service
makes `docker compose config` fail outright. The port is allocated whether or
not the profile is on, so enabling it later moves nothing.
Two fixes on top of the held commit. Its `.env.local` and override heredocs are
unquoted and its new comments carried raw backticks, so every `./run.sh`
invocation ran `bus` as a command — three `bus: command not found` lines on
stderr and the word deleted from the generated comment. Backticks are escaped
the way the surrounding lines already do it. And its `.env.example` block
re-documented `MERIDIAN_BUS` a second time, claiming `redis` is the only
shippable value and that the other modes exist "not in a deployable artifact";
that was already only half true and is now false, so the block points at the
canonical one and states the real boundary, which is loopback.
Also carried over: `env-doctor` exited 1 on any tree without a `.env`, because
the recipe exports `COMPOSE_ENV_FILES=.env` while `.env` is gitignored — the
tool you reach for BECAUSE the layers are confusing died before printing a
port. It now reports what was asked for, then drops the names that do not
exist.
Gates, each read as an exit code rather than from its output:
just compose-check 0 with COMPOSE_PROFILES=...,bus and 0 without
(63 assertions, 0 failed, both ways)
just env-doctor 0 both ways, including on a tree with no .env
docker compose --profile bus config 0; `published: "7447"` appears once,
so `ports: !override` replaced rather than
appended
just launcher-check 0
./run.sh ports --fresh-ports 0, and reproduces the saved allocation exactly
across three runs — every port equals its
profile starting point, zenoh included
just zenoh-check 0
just check-docker-context 0
Signed-off-by: Joshua Belke <admin@aipowergrid.io>
Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
This commit is contained in:
parent
c99ed00692
commit
6893ecfbd5
10 changed files with 346 additions and 8 deletions
55
.env.example
55
.env.example
|
|
@ -42,10 +42,15 @@
|
|||
# Compose profiles — which optional services start
|
||||
# -----------------------------------------------------------------------------
|
||||
# Core services (postgres, dragonfly, minio*) have no profile and
|
||||
# always start. The three below are opt-in. Kept enabled here so a fresh
|
||||
# `cp .env.example .env` brings up the same stack as before profiles existed;
|
||||
# trim or comment out for a leaner core-only stack.
|
||||
# always start. The extras below are opt-in. tools/auth/observability stay
|
||||
# enabled here so a fresh `cp .env.example .env` brings up the same stack as
|
||||
# before profiles existed; trim or comment out for a leaner core-only stack.
|
||||
# tools → adminer (:8082) · auth → keycloak (:8180) · observability → prometheus (:9090)
|
||||
# bus → zenohd (:7447) — DEFAULT OFF, do not add it here
|
||||
#
|
||||
# `bus` is deliberately absent from this line. zenohd carries events only, and
|
||||
# only once MERIDIAN_BUS names a Zenoh mode; starting a router that nothing
|
||||
# talks to is inventory, and the profile is how that stays a choice.
|
||||
COMPOSE_PROFILES=tools,auth,observability
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
@ -105,7 +110,11 @@ REDIS_URL=redis://localhost:6379
|
|||
#
|
||||
# `zenoh` and `shadow` need a relay BUILT with the `zenoh` feature
|
||||
# (`cargo build -p meridian-relay --features zenoh`). A build without it
|
||||
# refuses them by name rather than falling back to Redis.
|
||||
# refuses them by name rather than falling back to Redis. The published relay
|
||||
# image carries the feature — `Dockerfile` and `REMAPPING/cicd/Dockerfile` both
|
||||
# build with it — so these names are readable by the artifact, not only by a
|
||||
# local build. Carrying the feature is not the same as being reachable: the
|
||||
# endpoint rule below still confines this to one host.
|
||||
# MERIDIAN_BUS=redis
|
||||
|
||||
# Comma-separated Zenoh endpoints this pod connects to. REQUIRED whenever
|
||||
|
|
@ -356,6 +365,44 @@ MERIDIAN_S3_ADDRESSING_STYLE=path
|
|||
# REDUCTSTORE_SYNC_INTERVAL=60
|
||||
# REDUCTSTORE_LICENSE_DIR=./.secrets
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Event bus — zenohd router (COMPOSE_PROFILES=bus, default off)
|
||||
# -----------------------------------------------------------------------------
|
||||
# The bus is the pipe, not the plumbing. Postgres stays the system of record;
|
||||
# Dragonfly keeps presence TTL, rate limits, NIP-98 replay and the fenced
|
||||
# generation. zenohd routes events and stores nothing — no storage-manager
|
||||
# plugin, no REST plugin, no dynamic plugin loading, no multicast scouting and
|
||||
# no gossip scouting. The full contract lives in
|
||||
# deploy/compose/zenoh/zenohd.json5 and is asserted by `just compose-check`.
|
||||
#
|
||||
# MERIDIAN_BUS and MERIDIAN_ZENOH_* are documented once, in the `Cross-pod
|
||||
# event bus` block near the top of this file. That block owns the relay side;
|
||||
# this one owns the router and the host port it is published on.
|
||||
#
|
||||
# WHAT THIS PROFILE IS FOR: one host. The relay refuses any non-loopback
|
||||
# endpoint while the link profile cannot rise above the lane floors, so
|
||||
# `tcp/localhost:<port>` against a router on this machine is the whole of what
|
||||
# a Zenoh mode can reach today. A multi-pod endpoint (`tcp/zenohd:7447`,
|
||||
# `tcp/<pod>.<svc>:7447`) is refused by the relay at startup — it is not a
|
||||
# configuration this repo ships, and the adapter's own tests assert the refusal.
|
||||
#
|
||||
# THESE THREE MOVE TOGETHER. ZENOH_HOST_PORT is what Compose publishes;
|
||||
# MERIDIAN_ZENOH_ENDPOINTS is what the relay dials; docker-compose.override.yml
|
||||
# is what survives a bare `docker compose up -d`. Move one without the others
|
||||
# and the relay reports a connect timeout while the container is healthy —
|
||||
# the REDIS_HOST_PORT/REDIS_URL failure in a new costume. Discovery cannot
|
||||
# paper over it: multicast and gossip are both off by design, so an endpoint
|
||||
# the relay was not told about does not exist.
|
||||
# ZENOH_HOST_PORT=7447
|
||||
# MERIDIAN_ZENOH_ENDPOINTS=tcp/localhost:7447
|
||||
#
|
||||
# Image tag. Pinned in ONE place so the `zenoh` crate and the router image are
|
||||
# the same release — the charter refuses a 1.8-library/1.9-router pair. Tags
|
||||
# that exist on Docker Hub: 1.8.0, 1.9.0, 1.10.0 (also 0.11.0 and 1.0.x–1.7.x).
|
||||
# Never :latest or :nightly, both of which exist and both of which float.
|
||||
# ZENOH_IMAGE_TAG=1.8.0
|
||||
# ZENOH_MEMORY_LIMIT=128m
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Media Upload Admission
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -45,6 +45,14 @@
|
|||
# (os error 61)` from the relay with everything else looking healthy.
|
||||
# REDIS_HOST_PORT=6390
|
||||
# REDIS_URL=redis://localhost:6390
|
||||
#
|
||||
# zenohd (COMPOSE_PROFILES=bus). Same rule, same failure: ZENOH_HOST_PORT is
|
||||
# what Compose publishes and MERIDIAN_ZENOH_ENDPOINTS is what the relay dials.
|
||||
# Mirror any relocation in docker-compose.override.yml with `ports: !override`.
|
||||
# Neither multicast nor gossip scouting is enabled, so an endpoint the relay
|
||||
# was not explicitly given is unreachable — there is no discovery fallback.
|
||||
# ZENOH_HOST_PORT=7448
|
||||
# MERIDIAN_ZENOH_ENDPOINTS=tcp/localhost:7448
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Compose profiles — which optional services start
|
||||
|
|
@ -54,6 +62,7 @@
|
|||
# tools → adminer (DB browser, :8082)
|
||||
# auth → keycloak (local OAuth, :8180)
|
||||
# observability → prometheus (:9090)
|
||||
# bus → zenohd (event-bus router, :7447 — default off)
|
||||
# artifacts → reductstore (artifact/blob REST tier, :8383 — local disk)
|
||||
# artifacts-s3 → reductstore (same tier, MinIO behind it — needs a licence)
|
||||
#
|
||||
|
|
|
|||
|
|
@ -1730,6 +1730,33 @@ window after the last Zenoh-only pod becomes healthy.
|
|||
|
||||
### Phase 5 — Deploy the production `zenohd` topology
|
||||
|
||||
**Prerequisite — the relay image must build the adapter, and now does.**
|
||||
`Dockerfile` and `REMAPPING/cicd/Dockerfile` pass
|
||||
`--features meridian-relay/zenoh` to both `cargo chef cook` and `cargo build`.
|
||||
Before that every image this repo could build answered `MERIDIAN_BUS=zenoh` and
|
||||
`MERIDIAN_BUS=shadow` with `ConfigError::InvalidValue`, so a deploy artifact
|
||||
naming the variable produced a crash loop rather than a bus (`meridian-nok3`).
|
||||
Keep the two feature lists identical: cooking without the feature leaves the
|
||||
~77 extra packages to the source layer, where any edit under `crates/`
|
||||
recompiles them.
|
||||
|
||||
**Item 1 lands in two arms, and only the loopback arm is landable today.** The
|
||||
loopback arm is the root `docker-compose.yml` `bus` profile,
|
||||
`docker-compose.override.yml.example`, `.env.example`, `.env.local.example`,
|
||||
`run.sh` with its `PORT_KEYS` list, `.settings/run-profiles/local.yaml`,
|
||||
`Justfile`, and `deploy/compose/zenoh/zenohd.json5`; it renders
|
||||
`tcp/localhost:<port>`, which the relay accepts, and it is what makes a live
|
||||
probe possible at all. The multi-host arm is `deploy/compose/compose.yml`,
|
||||
`compose.dev.yml`, `deploy/compose/.env.example`, `deploy/compose/README.md`
|
||||
and the whole of item 2; it renders `tcp/zenohd:7447` and
|
||||
`tcp/<pod>.<service>:7447`, which `ZenohBusConfig::validate` refuses at startup
|
||||
while no lane floor exceeds the authenticated link profile. Confirmed against
|
||||
the built binary, not only against the unit test: `MERIDIAN_BUS=zenoh
|
||||
MERIDIAN_ZENOH_ENDPOINTS=tcp/zenohd:7447` exits non-zero naming the endpoint.
|
||||
The multi-host arm stays held until a mutually authenticated link profile
|
||||
exists, because shipping it earlier publishes a documented happy path that
|
||||
cannot boot.
|
||||
|
||||
1. Port the opt-in `bus` profile prototype to `deploy/compose/compose.yml`,
|
||||
`deploy/compose/compose.dev.yml`, `deploy/compose/.env.example`,
|
||||
`deploy/compose/README.md`, the **root `docker-compose.yml` local dev stack**
|
||||
|
|
@ -1896,8 +1923,10 @@ cargo test -p meridian-core kind_to_lane
|
|||
cargo test -p meridian-pubsub qos
|
||||
|
||||
# Phase 5 — deployment contract
|
||||
just compose-check
|
||||
just compose-check # run it with and without COMPOSE_PROFILES=bus
|
||||
just env-doctor
|
||||
just launcher-check
|
||||
./run.sh ports --fresh-ports # must reproduce the saved allocation
|
||||
# Run the existing tests under deploy/charts/meridian/tests/ through the chart's
|
||||
# documented test entry point when the Phase 5 slice begins.
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,12 @@ compose:
|
|||
# Opt-in service profiles. Core services (postgres, dragonfly, minio) have no
|
||||
# profile and always start; do not list them here.
|
||||
# tools → adminer auth → keycloak observability → prometheus
|
||||
# bus → zenohd (event-bus router) — deliberately NOT listed below
|
||||
#
|
||||
# The relay image now builds the adapter, so `bus` is off by choice rather
|
||||
# than by inability: MERIDIAN_BUS defaults to `redis`, and a router nothing
|
||||
# dials is inventory. Its port is still allocated below so enabling it later
|
||||
# does not move anything.
|
||||
profiles:
|
||||
- tools
|
||||
- observability
|
||||
|
|
@ -52,6 +58,10 @@ ports:
|
|||
adminer: 8083 # tools profile; upstream 8082 — held by codebase-chat adminer
|
||||
keycloak: 8182 # auth profile; upstream 8180 — held by codebase-chat Keycloak
|
||||
prometheus: 9091 # observability; upstream 9090 — held by codebase-chat Prom
|
||||
# `bus` profile (zenohd), default off. Allocated regardless: a port resolved
|
||||
# only when someone happens to enable the profile is a port that relocates
|
||||
# under a running stack the first time they do.
|
||||
zenoh: 7447 # upstream 7447 — free on this host today
|
||||
|
||||
# --- Relay host process ----------------------------------------------------
|
||||
relay: 3003 # upstream 3000 — 3002 held by the CoCo stack's relay
|
||||
|
|
|
|||
37
Justfile
37
Justfile
|
|
@ -127,10 +127,47 @@ env-doctor:
|
|||
# reporting, or the value shown contradicts the service list below.
|
||||
if [[ -f .env.local ]]; then set -a; . ./.env.local; set +a; fi
|
||||
echo "COMPOSE_ENV_FILES = ${COMPOSE_ENV_FILES:-<unset>}"
|
||||
# This recipe exports COMPOSE_ENV_FILES=.env, but .env is gitignored — so
|
||||
# on a fresh clone Compose fails closed with "couldn't find env file" and
|
||||
# env-doctor, the tool you reach for BECAUSE the layers are confusing,
|
||||
# dies before printing a single resolved port. Report what was asked for
|
||||
# on the line above, then drop the names that do not exist.
|
||||
_cef_keep=""
|
||||
if [[ -n "${COMPOSE_ENV_FILES:-}" ]]; then
|
||||
IFS=',' read -ra _cef_parts <<<"$COMPOSE_ENV_FILES"
|
||||
for _cef in "${_cef_parts[@]}"; do
|
||||
[[ -f "$_cef" ]] && _cef_keep="${_cef_keep:+${_cef_keep},}${_cef}"
|
||||
done
|
||||
if [[ "$_cef_keep" != "$COMPOSE_ENV_FILES" ]]; then
|
||||
echo " (using ${_cef_keep:-<none>} — the rest do not exist)"
|
||||
fi
|
||||
if [[ -n "$_cef_keep" ]]; then export COMPOSE_ENV_FILES="$_cef_keep"; else unset COMPOSE_ENV_FILES; fi
|
||||
fi
|
||||
echo "COMPOSE_PROFILES = ${COMPOSE_PROFILES:-<unset> (core services only)}"
|
||||
for f in .env .env.local docker-compose.override.yml; do
|
||||
[[ -f "$f" ]] && echo "present: $f" || echo "absent : $f"
|
||||
done
|
||||
# The bus is reported separately from the port table because its failure is
|
||||
# a MISMATCH, not a missing value: a relay on a Zenoh mode with no endpoint
|
||||
# list, or an endpoint list pointing at a port Compose is not publishing,
|
||||
# both look completely healthy from `docker compose ps`. Neither multicast
|
||||
# nor gossip scouting is enabled, so nothing discovers its way out of it.
|
||||
echo "--- event bus ---"
|
||||
bus_mode="${MERIDIAN_BUS:-redis (default)}"
|
||||
bus_peers="${MERIDIAN_ZENOH_ENDPOINTS:-<none>}"
|
||||
echo " MERIDIAN_BUS ${bus_mode}"
|
||||
echo " MERIDIAN_ZENOH_ENDPOINTS ${bus_peers}"
|
||||
if [[ "${MERIDIAN_BUS:-redis}" != "redis" && "${MERIDIAN_ZENOH_ENDPOINTS:-}" == "" ]]; then
|
||||
echo " !! MERIDIAN_BUS=${MERIDIAN_BUS} with no endpoints: discovery is off on both"
|
||||
echo " mechanisms, so the relay has no way to reach a router."
|
||||
fi
|
||||
if [[ ",${COMPOSE_PROFILES:-}," == *,bus,* ]]; then
|
||||
echo " profile 'bus' is ON → zenohd on ${ZENOH_HOST_PORT:-7447}, image eclipse/zenoh:${ZENOH_IMAGE_TAG:-1.8.0}"
|
||||
[[ "${MERIDIAN_BUS:-redis}" == "redis" ]] && \
|
||||
echo " note: MERIDIAN_BUS=redis — the router will start with nothing dialing it."
|
||||
else
|
||||
echo " profile 'bus' is OFF → no zenohd router in this stack"
|
||||
fi
|
||||
echo "--- resolved services and host ports ---"
|
||||
docker compose config --format json > /tmp/meridian-env-doctor.json
|
||||
python3 - /tmp/meridian-env-doctor.json <<'PY'
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ for a kind cluster.
|
|||
| `charts/meridian/` | The relay chart — `templates/` (deployment, service, ingress, httproute, hpa, pdb, servicemonitor, pvc-git, pairing-relay, bundled quickstart MinIO, secrets, `_helpers.tpl`, `_validate.tpl`, `NOTES.txt`), `values.yaml`, `values.schema.json`, `Chart.yaml`/`Chart.lock`, `examples/`, `ci/`, `tests/` |
|
||||
| `charts/meridian-push-gateway/` | The APNs push-gateway chart, `values-production.yaml`, `tests/render.sh` |
|
||||
| `charts/meridian-control-plane/` | The control-plane chart, `values.schema.json` (pins `identityProvider` to `oidc`), `tests/render.sh` |
|
||||
| `compose/` | `compose.yml` + `compose.dev.yml` + `compose.caddy.yml` + `compose.control-plane.yml` + `compose.dokploy.yml`, `Caddyfile`, `.env.example`, `run.sh`, `README.md` |
|
||||
| `compose/` | `compose.yml` + `compose.dev.yml` + `compose.caddy.yml` + `compose.control-plane.yml` + `compose.dokploy.yml`, `Caddyfile`, `.env.example`, `run.sh`, `README.md`, `zenoh/zenohd.json5` |
|
||||
| `local/` | `quickstart-ha-values.yaml`, `build-and-deploy.sh` for local kind clusters |
|
||||
| `../ct.yaml` | chart-testing config (chart dir, target branch, extra args) |
|
||||
|
||||
|
|
@ -45,6 +45,19 @@ repo between a chart change and production. Images come from
|
|||
come from the deployment repo.
|
||||
- Compose is the self-host path and must keep working standalone — do not make it
|
||||
depend on cluster-only resources.
|
||||
- **`compose/zenoh/zenohd.json5` is the one router config, mounted read-only by
|
||||
every stack that runs a router** — the root `docker-compose.yml` `bus` profile
|
||||
today, `compose.yml` when the multi-host arm lands. Do not copy it per stack:
|
||||
two copies of a security-relevant config drift and only one gets read.
|
||||
`just compose-check` parses it and asserts the Phase 5.3 contract (no REST, no
|
||||
storage-manager, no plugin loading, no multicast, no gossip, QoS on,
|
||||
lowlatency off, batching on, shared memory explicitly off).
|
||||
- **A relay env var is only usable if the image builds the code that reads it.**
|
||||
`MERIDIAN_BUS=zenoh|shadow` is a hard startup error unless the binary was built
|
||||
`--features meridian-relay/zenoh`, so `Dockerfile` and
|
||||
`REMAPPING/cicd/Dockerfile` carry that flag on both `cargo chef cook` and
|
||||
`cargo build`. A chart or Compose file that renders a variable the image cannot
|
||||
read is a crash loop, not a feature flag (`meridian-nok3`).
|
||||
- **The proxy overlays are mutually exclusive and opposite.** `compose.caddy.yml`
|
||||
adds a Caddy container and terminates TLS itself; `compose.dokploy.yml` assumes
|
||||
Dokploy's Traefik already exists and only unpublishes ports plus adds routing
|
||||
|
|
|
|||
100
deploy/compose/zenoh/zenohd.json5
Normal file
100
deploy/compose/zenoh/zenohd.json5
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// =============================================================================
|
||||
// zenohd.json5 — the ONE Meridian router configuration
|
||||
// =============================================================================
|
||||
// Mounted read-only by every stack that runs a router: the local dev backing
|
||||
// stack (../../../docker-compose.yml, `bus` profile) and the self-host
|
||||
// production stack (../compose.yml, `bus` profile). One file, because two
|
||||
// copies of a security-relevant config drift and only one of them is read.
|
||||
//
|
||||
// Charter: .settings/features/feature-zenoh-transport.md, Phase 5.3 —
|
||||
// "Do not enable REST, storage-manager, dynamic plugins, multicast, or gossip.
|
||||
// Routers route; Postgres stores; the relay authorizes."
|
||||
//
|
||||
// -----------------------------------------------------------------------------
|
||||
// What zenohd overrides no matter what this file says
|
||||
// -----------------------------------------------------------------------------
|
||||
// `zenohd/src/main.rs` runs these two lines unconditionally, after loading the
|
||||
// config and before opening the session:
|
||||
//
|
||||
// config.adminspace.set_enabled(true).unwrap();
|
||||
// config.plugins_loading.set_enabled(true).unwrap();
|
||||
//
|
||||
// So `adminspace.enabled: false` and `plugins_loading.enabled: false` below are
|
||||
// DOCUMENTATION OF INTENT, not enforcement — both render as `true` in zenohd's
|
||||
// own "Initial conf" log line. The levers that actually hold are:
|
||||
//
|
||||
// * `plugins_loading.search_dirs: []` — nothing is findable by name
|
||||
// * `plugins: {}` — nothing is required by path
|
||||
// * `--adminspace-permissions none` on the command line (compose `command:`)
|
||||
// * never passing `-P/--plugin` or `--plugin-search-dir`
|
||||
//
|
||||
// Multicast scouting has the inverse trap: zenohd force-ENABLES it when the
|
||||
// config leaves it unset, so `--no-multicast-scouting` on the command line is
|
||||
// what survives a config swap. Gossip scouting has no CLI flag at all — this
|
||||
// file is the only place it can be closed, which is why the compose services
|
||||
// mount it rather than running a bare `zenohd`.
|
||||
//
|
||||
// Verified against eclipse/zenoh 1.8.0, 1.9.0 and 1.10.0: parses clean, router
|
||||
// reaches "Zenoh can be reached at: tcp/…:7447", and every key below is echoed
|
||||
// with the intended value in the resolved config.
|
||||
// =============================================================================
|
||||
{
|
||||
mode: "router",
|
||||
metadata: { name: "meridian-zenohd" },
|
||||
|
||||
// Explicit endpoints only — both ambient discovery mechanisms are off below.
|
||||
// A router that discovers its own peers is a federation primitive; this
|
||||
// feature deploys routers INSIDE one Meridian deployment (§ Security).
|
||||
listen: { endpoints: ["tcp/0.0.0.0:7447"] },
|
||||
connect: { endpoints: [] },
|
||||
|
||||
scouting: {
|
||||
// Docker multicast between host and container does not work anyway, but
|
||||
// that is not why this is off: ambient discovery is refused on principle.
|
||||
multicast: { enabled: false },
|
||||
// No CLI equivalent. Removing this line silently re-enables gossip.
|
||||
gossip: { enabled: false },
|
||||
},
|
||||
|
||||
transport: {
|
||||
unicast: {
|
||||
// QoS and lowlatency are mutually exclusive in Zenoh: the LowLatency
|
||||
// transport does not preserve QoS prioritization. Q0–Q7 lanes need QoS,
|
||||
// so lowlatency stays off. Enabling both is rejected by the library.
|
||||
lowlatency: false,
|
||||
qos: { enabled: true },
|
||||
},
|
||||
link: {
|
||||
tx: {
|
||||
queue: {
|
||||
// Adaptive batching under back-pressure. This is the mechanism the
|
||||
// ≥5× gate depends on for small payloads; without it every small
|
||||
// message pays full per-message framing.
|
||||
batching: { enabled: true, time_limit: 1 },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Zenoh 1.x defaults shared memory to ENABLED. Phase 6 is unscheduled and
|
||||
// gated on a measured crossover plus `ulimit -l unlimited`/CAP_IPC_LOCK,
|
||||
// so leaving this unset silently activates a phase that has not been
|
||||
// decided. Explicit false, and Phase 6 flips it behind its own flag.
|
||||
shared_memory: { enabled: false },
|
||||
},
|
||||
|
||||
// Routers stamp an HLC timestamp on data that arrives without one. Peers and
|
||||
// clients do not — the relay is the authority for event time.
|
||||
timestamping: { enabled: { router: true, peer: false, client: false } },
|
||||
|
||||
// Overridden to `true` by zenohd (see header). `--adminspace-permissions
|
||||
// none` in the compose `command:` is what closes it; these values keep the
|
||||
// file honest about the intent and hold if the binary ever stops forcing it.
|
||||
adminspace: { enabled: false, permissions: { read: false, write: false } },
|
||||
|
||||
// No storage-manager, no REST, no anything. Loom's reference config
|
||||
// (.settings/reference-code/Loom/deployment/compose/zenoh/zenoh.json5)
|
||||
// enables a RocksDB storage_manager over `loom/**` and a REST admin API on
|
||||
// :8000. Both are refused here: a router that stores is a second durable log
|
||||
// (DIAGRAM.md § Scaling axes R2), and Postgres is the system of record.
|
||||
plugins: {},
|
||||
plugins_loading: { enabled: false, search_dirs: [] },
|
||||
}
|
||||
|
|
@ -44,4 +44,13 @@ services:
|
|||
# - "--proactor_threads=8"
|
||||
# - "--dbfilename="
|
||||
|
||||
# --- Relocate zenohd's host port (COMPOSE_PROFILES=bus) ---------------------
|
||||
# Only the host side moves; the container still listens on 7447, so the
|
||||
# in-network `tcp/zenohd:7447` endpoint keeps working for other containers.
|
||||
# Mirror this in .env.local as ZENOH_HOST_PORT + MERIDIAN_ZENOH_ENDPOINTS.
|
||||
# There is no REST port to relocate — the plugin is refused.
|
||||
# zenohd:
|
||||
# ports: !override
|
||||
# - "7448:7447"
|
||||
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,17 @@ name: meridian
|
|||
# tools → adminer (DB browser, :8082)
|
||||
# auth → keycloak (local OAuth, :8180)
|
||||
# observability → prometheus (:9090)
|
||||
# bus → zenohd (event-bus router, :7447 — default OFF)
|
||||
# artifacts → reductstore (artifact/blob REST tier, :8383 — local disk)
|
||||
# artifacts-s3 → reductstore (same tier, MinIO behind it — needs a licence)
|
||||
#
|
||||
# COMPOSE_PROFILES=tools,auth,observability docker compose up -d
|
||||
# COMPOSE_PROFILES=bus docker compose up -d # router too; core still starts
|
||||
#
|
||||
# `bus` is off in .env.example on purpose. The relay image builds the Zenoh
|
||||
# adapter, so the router is reachable by a binary that exists — but MERIDIAN_BUS
|
||||
# defaults to `redis`, and a router nothing dials is inventory, not readiness.
|
||||
# Turning the profile on is one half; naming a Zenoh mode is the other.
|
||||
#
|
||||
# `artifacts` and `artifacts-s3` are mutually exclusive — they bind the same
|
||||
# host port and are two backings of one tier, not two tiers. Pick one.
|
||||
|
|
@ -452,6 +459,66 @@ services:
|
|||
com.meridian.env: "dev"
|
||||
restart: unless-stopped
|
||||
|
||||
# ── zenohd — opt-in event-bus router (COMPOSE_PROFILES=bus, default OFF) ───
|
||||
# Router-mediated topology for the Zenoh transport track. It is the BUS, not
|
||||
# a second durable log: Postgres stays the system of record, Dragonfly keeps
|
||||
# presence TTL, rate limits, NIP-98 replay and the fenced generation. Routers
|
||||
# route; Postgres stores; the relay authorizes.
|
||||
#
|
||||
# Default off, and it must stay that way: `just _ensure-services` blocks on
|
||||
# postgres/dragonfly health for 120s, so a core service behind a profile —
|
||||
# or this one added to that gate — hangs every dev command.
|
||||
#
|
||||
# `command:` carries three settings the config file cannot enforce, because
|
||||
# zenohd overrides them after loading it (see deploy/compose/zenoh/zenohd.json5):
|
||||
# --no-multicast-scouting config value is force-ENABLED when unset
|
||||
# --adminspace-permissions `adminspace.enabled` is force-set to true
|
||||
# (never -P/--plugin) `plugins_loading.enabled` is force-set to true
|
||||
# Gossip scouting has no flag at all and is closed only by the mounted file,
|
||||
# which is why the mount is not optional.
|
||||
zenohd:
|
||||
# Tag is parameterized so the Rust crate pin and the router image are set
|
||||
# to the same release in ONE place. The default must stay a semver literal:
|
||||
# an unset variable resolving to :latest is how a router silently changes
|
||||
# protocol version under a pinned client.
|
||||
image: eclipse/zenoh:${ZENOH_IMAGE_TAG:-1.8.0}
|
||||
container_name: meridian-zenohd
|
||||
# Opt-in: COMPOSE_PROFILES=bus (see .env.local.example).
|
||||
profiles: ["bus"]
|
||||
init: true
|
||||
command:
|
||||
- "--config"
|
||||
- "/etc/zenoh/zenohd.json5"
|
||||
- "--no-multicast-scouting"
|
||||
- "--adminspace-permissions"
|
||||
- "none"
|
||||
volumes:
|
||||
- ./deploy/compose/zenoh/zenohd.json5:/etc/zenoh/zenohd.json5:ro
|
||||
ports:
|
||||
# Host port is overridable like every other one here; 7447 is Zenoh's
|
||||
# well-known port and collides with any other Zenoh stack on the host.
|
||||
# Relocating it means moving MERIDIAN_ZENOH_ENDPOINTS with it.
|
||||
- "${ZENOH_HOST_PORT:-7447}:7447"
|
||||
networks:
|
||||
- meridian-net
|
||||
healthcheck:
|
||||
# TCP 7447 only. The image is Alpine, so this is busybox nc (verified
|
||||
# `nc -z` works), not reductstore's bash /dev/tcp. Never probe :8000 —
|
||||
# the REST plugin is refused, so a REST probe would fail a healthy router.
|
||||
test: ["CMD-SHELL", "nc -z 127.0.0.1 7447"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${ZENOH_MEMORY_LIMIT:-128m}
|
||||
labels:
|
||||
com.meridian.service: "zenohd"
|
||||
com.meridian.env: "dev"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
name: meridian-postgres-data
|
||||
|
|
|
|||
21
run.sh
21
run.sh
|
|
@ -46,7 +46,7 @@ BLOCK_BEGIN="# >>> meridian run.sh managed block >>>"
|
|||
BLOCK_END="# <<< meridian run.sh managed block <<<"
|
||||
|
||||
# Logical port name -> environment variable it is published as.
|
||||
PORT_KEYS="postgres redis minio minio_console adminer keycloak prometheus relay health metrics control_plane control_plane_health desktop_vite desktop_hmr web"
|
||||
PORT_KEYS="postgres redis minio minio_console adminer keycloak prometheus zenoh relay health metrics control_plane control_plane_health desktop_vite desktop_hmr web"
|
||||
|
||||
# ── Options ──────────────────────────────────────────────────────────────────
|
||||
PROFILE="${MERIDIAN_RUN_PROFILE:-local}"
|
||||
|
|
@ -291,6 +291,7 @@ var_for_port() {
|
|||
adminer) printf 'MERIDIAN_ADMINER_PORT' ;;
|
||||
keycloak) printf 'MERIDIAN_KEYCLOAK_PORT' ;;
|
||||
prometheus) printf 'MERIDIAN_PROMETHEUS_PORT' ;;
|
||||
zenoh) printf 'ZENOH_HOST_PORT' ;;
|
||||
relay) printf 'MERIDIAN_RELAY_PORT' ;;
|
||||
health) printf 'MERIDIAN_HEALTH_PORT' ;;
|
||||
metrics) printf 'MERIDIAN_METRICS_PORT' ;;
|
||||
|
|
@ -584,6 +585,10 @@ MERIDIAN_MINIO_CONSOLE_PORT=${PORT_MINIO_CONSOLE}
|
|||
MERIDIAN_ADMINER_PORT=${PORT_ADMINER}
|
||||
MERIDIAN_KEYCLOAK_PORT=${PORT_KEYCLOAK}
|
||||
MERIDIAN_PROMETHEUS_PORT=${PORT_PROMETHEUS}
|
||||
# \`bus\` profile only. Allocated whether or not the profile is on, because a
|
||||
# port that is only resolved when a profile happens to be enabled is a port
|
||||
# that moves under you the first time someone enables it.
|
||||
ZENOH_HOST_PORT=${PORT_ZENOH}
|
||||
|
||||
# --- Connection strings, paired with the ports above -------------------------
|
||||
# Moving a port without its URL yields "Connection refused" from the relay with
|
||||
|
|
@ -591,6 +596,11 @@ MERIDIAN_PROMETHEUS_PORT=${PORT_PROMETHEUS}
|
|||
DATABASE_URL=postgres://${DB_USER}:${DB_PASS}@localhost:${PORT_POSTGRES}/${DB_NAME}
|
||||
REDIS_URL=redis://localhost:${PORT_REDIS}
|
||||
MERIDIAN_S3_ENDPOINT=http://localhost:${PORT_MINIO}
|
||||
# The bus has no discovery fallback — multicast and gossip scouting are both
|
||||
# off in the router config — so this endpoint list is the ONLY way a relay
|
||||
# finds the router. It is written unconditionally for the same reason
|
||||
# REDIS_URL is: the pair must never be half-applied.
|
||||
MERIDIAN_ZENOH_ENDPOINTS=tcp/localhost:${PORT_ZENOH}
|
||||
|
||||
# libpq variables for the psql-based scripts. Defaults resolve to localhost:5432,
|
||||
# which on a busy host is another project's database — the seed then reports
|
||||
|
|
@ -696,6 +706,13 @@ services:
|
|||
prometheus:
|
||||
ports: !override
|
||||
- "${PORT_PROMETHEUS}:9090"
|
||||
|
||||
# \`bus\` profile only. Compose accepts an override for a profiled service and
|
||||
# simply filters it out when the profile is off, so this block costs nothing
|
||||
# until COMPOSE_PROFILES includes \`bus\`.
|
||||
zenohd:
|
||||
ports: !override
|
||||
- "${PORT_ZENOH}:7447"
|
||||
EOF
|
||||
dbg "wrote docker-compose.override.yml"
|
||||
}
|
||||
|
|
@ -859,7 +876,7 @@ cmd_ports() {
|
|||
cmd_env() {
|
||||
prepare >/dev/null
|
||||
local v
|
||||
for v in MERIDIAN_RUN_PROFILE MERIDIAN_PG_HOST_PORT REDIS_HOST_PORT \
|
||||
for v in MERIDIAN_RUN_PROFILE MERIDIAN_PG_HOST_PORT REDIS_HOST_PORT ZENOH_HOST_PORT \
|
||||
MERIDIAN_MINIO_PORT MERIDIAN_MINIO_CONSOLE_PORT MERIDIAN_ADMINER_PORT \
|
||||
MERIDIAN_KEYCLOAK_PORT MERIDIAN_PROMETHEUS_PORT MERIDIAN_RELAY_PORT \
|
||||
MERIDIAN_HEALTH_PORT MERIDIAN_METRICS_PORT MERIDIAN_BIND_ADDR \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue