R2D2-MERIDIAN/deploy/compose/README.md
Joshua Belke de4cfec61e feat: rebrand Codebase Chat to Meridian
Renames the product to Meridian across 1826 files: 24 crates
(codebase-chat-* -> meridian-*), the Flutter package, env vars
(CODEBASE_CHAT_* -> MERIDIAN_*), the deep-link scheme (meridian://),
Postgres GUCs, Helm charts, skills, and the agent surface.

White-labels every external identity onto self-hosted infrastructure:
hosts move from *.codebase.design to *.meridian.r2d2.office.ilab.zone,
images to registry.r2d2.office.ilab.zone/meridian-*, the repo slug to
r2d2/meridian, and bundle IDs to zone.ilab.office.r2d2.meridian.*.
The Block staging relay and the four Block-internal build repos are not
reachable from a self-hosted deployment and are no longer referenced.

The mark becomes a pixel M. It is 5x6 rather than a square 5x5 because
the avatar-pile mask asserts the hole clears the glyph's right edge:
at 5x5 that edge moves from 68.4% to 73% of the tile, which overruns the
56px team-card hole outright and leaves the other three piles under a
pixel. At 5x6 the aspect is 0.833 against the retired C's 0.800, so all
four masks clear it unchanged. All 59 materializations are regenerated
from the generators; `just check-brand` passes.

Four things are deliberately NOT renamed, because they match what was
*stored* rather than what now ships. Rewriting any of them makes a
migration no-op on exactly the installs it exists to repair:

- Frozen migrations 0001-0030. Their SHA-256 digests are pinned in
  n-minus-one-pins.json and embedded in the attested N-1 image. The new
  vocabulary lands as forward migration 0031, which dual-reads all three
  generations' GUCs, lock names, app profiles and mesh d_tags. The
  push-gateway's own 0001 is likewise restored byte-identical, with
  0002 widening its app_profile CHECK.
- Legacy namespace chains. xyz.block.codebasechat.app is *prepended* to
  LEGACY_RELEASE_IDENTIFIERS and its dev/localStorage twins, per the rule
  in legacy_dirs.rs that a previous rename already broke once.
- Bead IDs (codebaseChat-*), which are cited from commits and docs.
- CHANGELOG history and upstream issue links.

The Codebase-era persona ids are added to RETIRED_PERSONAS with their
prompts verbatim, but deliberately NOT to RETIRED_PERSONA_REPLACEMENTS:
that map drives migration::retire_agents, which deletes deployed
instances, and its safety argument is that the successor is already
deployed alongside. That held for Buzz->Codebase; nothing provisions a
Meridian agent on an install that already onboarded, so mapping these
would delete a working agent and leave nothing in its place.

The brand-guard self-test changes axis: the M is symmetric about its
vertical axis, so a mirrored M *is* the canonical M and asserting a
rejection there would assert a bug. It now flips top-to-bottom (the mark
reads as a W) and pins the horizontal symmetry so the coupling is visible
if the mark ever becomes asymmetric again.

Verified: cargo check --workspace --all-targets clean, just fix-all
clean, flutter analyze clean, just check-skills pass, check-brand 59/59,
brand-core 9/9, avatarPileMask 4/4, starter-avatar contrast 2/2.

Signed-off-by: Joshua Belke <joshua@innovationhub-act.org>
2026-08-04 14:06:04 -04:00

6.5 KiB

Meridian Docker Compose deployment

This is the single-node/VPS deployment bundle. It is intentionally separate from the root docker-compose.yml, which remains local development infrastructure.

Quick start

cd deploy/compose
cp .env.example .env
$EDITOR .env       # replace every CHANGE_ME value
./run.sh start

For a public VPS with automatic Let's Encrypt certificates:

cd deploy/compose
MERIDIAN_COMPOSE_TLS=true ./run.sh start

The bootstrap script should eventually replace manual .env editing for normal users. It is responsible for generating stable secrets and, optionally, an owner keypair.

Control plane (hosted communities)

compose.control-plane.yml adds the service that mints communities and backs the desktop app's "Create a new community" flow. It is optional — the relay runs without it; only provisioning needs it.

docker compose -f compose.yml -f compose.control-plane.yml up -d

Three settings are load-bearing, and each fails in a way that does not look like a configuration error:

  1. It gets its own database. The relay and the control plane share one .env, and a bare DATABASE_URL names the relay's database. Migrating into it interleaves two _sqlx_migrations histories and corrupts both, so control-plane-db-init creates meridian_control_plane separately.
  2. MERIDIAN_CONTROL_RELAY_OPERATOR_API_ORIGIN must equal the relay's RELAY_OPERATOR_API_ORIGIN byte-for-byte. The relay rebuilds the NIP-98 u tag as {origin}{path}{?query} and compares it to the signed value, so a trailing slash — or localhost on one side and 127.0.0.1 on the other — fails every operator call with a signature error.
  3. The relay must trust the operator key. Derive it with meridian-control-plane --print-operator-pubkey and add the result to the relay's RELAY_OPERATOR_PUBKEYS, or provisioning is rejected outright.

MERIDIAN_CONTROL_IDENTITY_PROVIDER=dev accepts any email address with no verification. It refuses to start unless MERIDIAN_CONTROL_ALLOW_DEV_LOGIN=1 is set as well — keep both off anything reachable by someone other than you.

Dokploy

compose.dokploy.yml is an alternative to the Helm/ArgoCD path in ../charts, for running the stack on a single server under Dokploy.

docker compose -f compose.yml -f compose.control-plane.yml -f compose.dokploy.yml up -d

Dokploy supplies the reverse proxy (Traefik) and certificates, so this overlay is the mirror image of compose.caddy.yml: it unpublishes host ports and routes by Traefik label instead. Do not add a ports: entry back — that bypasses the proxy and exposes the relay directly on the host.

Host Service
register.meridian.r2d2.office.ilab.zone control plane (:8090)
relay.meridian.r2d2.office.ilab.zone relay (:3000)
*.communities.meridian.r2d2.office.ilab.zone relay — community derived from the Host header

Two constraints worth knowing before the first deploy:

  • dokploy-network is external, created by Dokploy itself. Bringing this stack up outside Dokploy fails on the missing network. That is the intended signal, not something to work around by declaring the network locally.
  • The certificate resolver must use DNS-01. Community hosts are minted as <name>.communities.meridian.r2d2.office.ilab.zone, which needs a wildcard certificate, and Let's Encrypt only issues wildcards over DNS-01. With HTTP-01 the deploy succeeds and then every new community fails to get a certificate — a failure that surfaces per community rather than at deploy time. meridian.r2d2.office.ilab.zone is already on Cloudflare, so a scoped Cloudflare API token is the least-friction provider.

DNS: point register, relay, and a *.communities wildcard at the server.

Production notes

  • Requires Docker Compose v2.24.4 or newer; the TLS override uses Compose's !reset tag to remove the direct relay port when Caddy terminates HTTPS.
  • Default MERIDIAN_IMAGE tracks registry.r2d2.office.ilab.zone/meridian:main for early testing. Pin it to registry.r2d2.office.ilab.zone/meridian:sha-<7> or a semver release tag for production once available.
  • Keep MERIDIAN_RELAY_PRIVATE_KEY, MERIDIAN_GIT_HOOK_HMAC_SECRET, database/Redis, and S3 secrets stable across restarts.
  • RELAY_OWNER_PUBKEY is intentionally not prefixed with MERIDIAN_; it must be a 64-character hex Nostr pubkey when closed relay mode is enabled.
  • MERIDIAN_AUTO_MIGRATE is opt-in. Set MERIDIAN_AUTO_MIGRATE=true or run meridian-admin migrate before starting the relay when bootstrapping a fresh database. Auto-migration requires an image that includes embedded SQLx migrations.
  • The stack uses Postgres, Redis, MinIO, and a git data volume because those are real Meridian dependencies today. Minimal mode can simplify this later.
  • The bundled Compose stack fixes the relay endpoint to http://minio:9000 and MERIDIAN_S3_ADDRESSING_STYLE=path: Docker DNS resolves minio, not <bucket>.minio. It is not configurable for an external S3 provider through .env; use the Helm chart or a custom Compose configuration for providers such as new Railway Storage Buckets that require virtual addressing.

Run ./run.sh backup-hint for the checklist. ./run.sh backup OUTPUT_DIR pauses relay writes while it captures Postgres and MinIO into one consistency set; it always resumes the relay on failure. Set BACKUP_SIGNING_KEY to an external PEM private key and write directly to encrypted storage. The worker can read and exfiltrate that PEM; read-only mounting is not HSM custody. Keep the matching public key and four authenticated probes outside the artifact, then set BACKUP_VERIFY_KEY and RESTORE_PROBES_DIR before running ./run.sh restore-verify OUTPUT_DIR. The verifier restores the matching relay and exact recorded Redis-protocol images with auto-migration disabled into isolated containers. It proves identity, audit chains, event counts, empty-state rebuild/readiness, exhaustive database-to-Git/media object graph integrity, ACL denial, Git refs, media hashes, and workflow-secret decryption, and fails the configured artifact-age and technical restore-time gates. See docs/runbooks/relay-backup-restore.md.

Validation

Before sharing an install link publicly, verify a fresh install with:

cd deploy/compose
cp .env.example .env
$EDITOR .env
./run.sh config
./run.sh start
curl -fsS "http://127.0.0.1:$(grep -E '^MERIDIAN_HTTP_PORT=' .env | cut -d= -f2-)/_liveness"
./run.sh status