# Running membershipd as a systemd user service `membershipd` is the unibus control plane (rooms, members, sealed keys, blob store) and, unless you point it at an external NATS with `--nats-url`, it also runs the embedded NATS + JetStream data plane. Running it as a **systemd user service** keeps it alive across logout/reboot and restarts it if it crashes. The unit (`unibus-membershipd.service`) binds both planes to `0.0.0.0`: | Plane | Port | Reachable from | |--------------|-------|----------------| | HTTP control | 8470 | LAN (`http://:8470/healthz`) | | NATS data | 4250 | LAN (`nats://:4250`) | ## Install (idempotent) ```bash cd ~/fn_registry/projects/message_bus/apps/unibus ./deploy/install.sh ``` This builds the binary, symlinks the unit into `~/.config/systemd/user/`, reloads systemd, and enables + starts the service. ## Manual steps (what install.sh does) ```bash cd ~/fn_registry/projects/message_bus/apps/unibus # 1. Build the pure-Go binary (no CGO). CGO_ENABLED=0 go build -o membershipd ./cmd/membershipd # 2. Link the unit into the systemd user directory. mkdir -p ~/.config/systemd/user ln -sf "$PWD/deploy/unibus-membershipd.service" ~/.config/systemd/user/unibus-membershipd.service # 3. Reload, enable (start on login) and start now. systemctl --user daemon-reload systemctl --user enable --now unibus-membershipd.service # (optional) survive logout without an active session: # sudo loginctl enable-linger "$USER" ``` ## Operate ```bash systemctl --user status unibus-membershipd.service # is it active? systemctl --user restart unibus-membershipd.service # after a rebuild systemctl --user stop unibus-membershipd.service systemctl --user disable unibus-membershipd.service # stop starting on login journalctl --user -u unibus-membershipd.service -f # follow logs # Health (local and from another LAN host): curl -fsS http://127.0.0.1:8470/healthz curl -fsS http://:8470/healthz ``` ## Notes - Writable state (SQLite DB, JetStream store, blobs) lives under `local_files/` relative to `WorkingDirectory`, which the unit sets to the app directory. - After editing the app code, rebuild (`CGO_ENABLED=0 go build -o membershipd ./cmd/membershipd`) and `systemctl --user restart unibus-membershipd.service`. - To run against an external NATS instead of the embedded one, append `--nats-url nats://:4222` to `ExecStart` and re-run `daemon-reload` + `restart`. ## Clustering (HA) — see `deploy/cluster/` The single-node service above is secure on its own. Running unibus as a multi-node **cluster** has extra hardening rules (issues 0006a–0006f); the full runbook and the generated material live in `deploy/cluster/`. Key points an operator must know: - **Homogeneous posture (0006d).** Every node MUST run `--bus-auth enforce` (the binary refuses to join a cluster otherwise) and present mutual route TLS on a public bind. `/healthz` publishes each node's `posture` so a monitor can flag a node that is not `enforce`+`acl`+`tls`. - **Separate route CA (0006f).** The cluster route layer authenticates *nodes*, not bus users — sign the route certs with a **dedicated cluster CA** (`--route-tls-ca`), NOT the client data-plane CA (`--tls-cert`'s CA). Keeping the two trust roots separate means a client cert can never be presented to the route port. `deploy/cluster/generate-cluster-certs.sh` builds this CA. - **Secret out of argv (0006f).** Pass the route password via `--cluster-pass-file` or the `UNIBUS_CLUSTER_PASS` env var, NOT `--cluster-pass` or a `nats://user:pass@host` in `--routes` (both are visible in `ps`/journald). When the secret comes from a file/env, list peers as bare `--routes nats://:6250` and the binary injects the credentials. - **`migrate-to-kv` confidentiality (0006f).** The migration writes the allowlist (handles/roles/sign pubs) into KV. Run it only against a **loopback** nats-url, or pin TLS with `--ca` for a remote target — otherwise that metadata travels in cleartext. The binary refuses a remote target without `--ca`. - **R1 is NOT HA (0006a/N3-DoS).** With `--kv-replicas 1` the control plane (including the nonce bucket) is a single point of failure: if the node owning the stream dies, every authenticated request fails closed (auth DoS). Real HA needs **R3** (quorum 2/3): raise replicas in place with `nats stream update --replicas 3` once the third node has joined. Do not advertise R1 as HA.