kavilo-hive — operator guide
This is the server guide. If you’re an end user driving the kavilo-hive
CLI against a hub someone else runs, see user-guide.md.
What you’re running
kavilo-hived is a single Go binary that hosts one or more isolated
hives under /h/<hive-ref> on the same HTTPS listener. It does:
- HTTPS + WSS terminus (self-signed CA built in; replaceable).
- An embedded admin web UI for the hub and per-hive.
- One-time 6-digit bootstrap codes for actor onboarding.
- Ed25519 actor authentication, optional per-key IP allowlists.
- Signed-envelope relay between actors (direct + group fan-out).
- A prekey-bundle directory for Signal-protocol session establishment.
- Sealed-sender certificate minting (via a paired
kavilo-cert-tool
sidecar binary shipped in the same Docker image).
The server never sees plaintext message content. The
Ciphertext field on every relayed envelope is opaque bytes — the Rust
client encrypts with libsignal before signing the envelope.
For protocol-level detail and the threat model, read
docs/adr/0001-signal-level-e2e.md
in the source repo.
Install
Docker image (recommended)
docker pull ghcr.io/kavilo-bot/kavilo-hived:0.1.2
Multi-arch (linux/amd64, linux/arm64), cosign-keyless signed
under the homebrew-tap workflow’s OIDC identity, with an in-toto SPDX
SBOM attestation. Verify before deploying:
cosign verify \
--certificate-identity-regexp '^https://github.com/kavilo-bot/homebrew-tap/.github/workflows/build-kavilo-hive-release.yml@.*$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/kavilo-bot/kavilo-hived:0.1.2
The image ships both /app/kavilo-hived (the server) and
/app/kavilo-cert-tool (the sealed-sender certificate minter the
server shells out to). /app is on $PATH.
Direct tarball
Versioned assets on the
Releases page
under the kavilo-hive-vX.Y.Z tag:
kavilo-hived_X.Y.Z_linux_amd64.tar.gz
kavilo-hived_X.Y.Z_linux_arm64.tar.gz
kavilo-hived_X.Y.Z_darwin_arm64.tar.gz
kavilo-hive_X.Y.Z_checksums.txt
KH_VER=0.1.2
curl -fsSL -o /tmp/kavilo-hived.tgz \
https://github.com/kavilo-bot/homebrew-tap/releases/download/kavilo-hive-v${KH_VER}/kavilo-hived_${KH_VER}_linux_amd64.tar.gz
sudo tar -xzf /tmp/kavilo-hived.tgz -C /usr/local/bin/ kavilo-hived
The tarballs contain just the binary; you’ll want kavilo-cert-tool
alongside it for sealed sender (build it from source or pull from the
Docker image). The Docker route is easier.
From source
git clone https://github.com/kavilo-bot/kavilo-hive
cd kavilo-hive
go build -trimpath -ldflags="-s -w" -o /usr/local/bin/kavilo-hived ./cmd/kavilo-hived
Quick start
kavilo-hived keeps all state under ~/.kavilo-hive (the managed
home directory). Three bootstrap commands followed by serve:
kavilo-hived admin init -password "<admin-password>"
kavilo-hived hub init
kavilo-hived hive create -name "Team Alpha"
kavilo-hived serve
That gets you:
https://127.0.0.1:8443/admin — hub admin UI (login: admin /
<admin-password>).
https://127.0.0.1:8443/h/<hive-ref>/admin — per-hive admin UI for
managing actors, groups, and bootstrap codes.
https://127.0.0.1:8443/ca.crt — download the built-in CA cert (give
this file to clients to trust the self-signed server cert).
kavilo-hived health runs a TLS + admin-API round-trip; add `-hive
[` for per-hive health.
## Docker compose
The source repo ships a `docker-compose.yml` for a complete operator
setup. The pattern is: bootstrap inside a one-shot container, then
start the long-running `serve`:
```bash
docker compose run --rm kavilo-hived admin init -password ""
docker compose run --rm kavilo-hived hub init
docker compose run --rm kavilo-hived hive create -name "Team Alpha"
docker compose up -d
```
The compose file mounts `${HOME}/.kavilo-hive` from the host into
`/home/nonroot/.kavilo-hive` in the container — TLS assets, actor
metadata, admin state, and the bootstrap-code store all persist on the
host.
## Public exposure via kavilo-tunnel
If you don't want to give kavilo-hived a directly routable address on
the public internet, the compose stack ships an optional
[`kavilo-tunnel`](https://github.com/kavilo-bot/homebrew-tap#kavilo-tunnel-client-cli)
sidecar that connects to a tunnel edge you (or your operator) runs and
forwards a public HTTPS URL to the local hub. End users connect to
that URL; kavilo-hived itself only listens on the compose bridge
network.
The default compose setup uses HTTP on the agent-to-hived hop —
kavilo-tunnel's reqwest client uses rustls with bundled webpki-roots
and has no flag to trust a private CA for the local upstream, so
pointing it at kavilo-hived's self-signed HTTPS listener fails with
`HTTP 502 — error sending request for url`. Public-side TLS (Let's
Encrypt at the tunnel edge) is terminated regardless, so end-user
traffic stays HTTPS; the Signal-protocol end-to-end encryption between
clients is unaffected — the tunnel sees the same opaque ciphertext
kavilo-hived does.
### Setup
1. Get a `kavilo-tunnel` token and endpoint from your tunnel
operator. Drop both into a `.env` file alongside `docker-compose.yml`
(template at `.env.example`):
```bash
KAVILO_TUNNEL_TOKEN=…
KAVILO_TUNNEL_ENDPOINT=https://tunnel.example.com:7777
KAVILO_TUNNEL_NAME=myhive
# Default upstream is http://kavilo-hived:8443 — leave unset unless
# you're pointing the tunnel at a different in-network service.
```
2. Bootstrap the hub once (any sane `-public-url` placeholder works
on this first pass — we'll fix it in step 4 once we know the
actual public URL the tunnel assigns):
```bash
docker compose run --rm kavilo-hived admin init \
-password "" \
-public-url "https://placeholder.invalid"
docker compose run --rm kavilo-hived hub init
docker compose run --rm kavilo-hived hive create -name "Your Hive"
```
3. Edit `~/.kavilo-hive/config.yaml` and set:
```yaml
server:
require_tls: false
```
This switches kavilo-hived's listener to plain HTTP on `:8443`.
The tunnel agent connects to it cleartext over the compose bridge
(a private Docker network on your host); public-side traffic still
arrives over Let's Encrypt HTTPS at the tunnel edge.
4. Bring up the stack with the `tunnel` profile and watch the
sidecar log for the public URL the operator assigned:
```bash
docker compose --profile tunnel up -d
docker compose logs -f kavilo-tunnel
# …
# kavilo-tunnel ready
# public URL: https://myhive-./
# forwarding to: http://kavilo-hived:8443
```
The `` (e.g. `alice-7f0c`) is the tail your tunnel
operator assigns to your account; it's stable across reconnects
once you've claimed a `--name`. The `` is the
operator-configured public hostname.
5. Patch the now-known public URL back into config so claim bundles
end users redeem reference the public hostname rather than the
placeholder, then restart hived:
```yaml
server:
public_url: https://myhive-.
```
```bash
docker compose restart kavilo-hived
```
Without the `tunnel` profile, `docker compose up` runs only
`kavilo-hived` — the default standalone behaviour is unchanged. The
sidecar persists its login state in a named volume
(`kavilo-tunnel-state`) so restarts don't re-auth.
### Verifying it works
From outside the host:
```bash
PUB=https://myhive-.
curl -sI "${PUB}/" # → 302 redirect to /admin
curl -s "${PUB}/ca.crt" # → kavilo-hived's CA PEM (served unauth)
```
A `HTTP 502 error sending request for url (https://...)` from the
tunnel agent means kavilo-tunnel hit the HTTPS upstream — double-check
that `require_tls: false` is set in `config.yaml` and that
`KAVILO_TUNNEL_URL` (env or compose default) is `http://`, not
`https://`.
## The managed home directory
```
~/.kavilo-hive/
├── config.yaml # root admin config
├── hub/
│ ├── config.yaml # hub registry
│ └── hives//config.yaml # per-hive config
├── tls/ # generated CA + server cert
└── registrations/ # short-lived bootstrap codes
```
`config.yaml` is intended to be edited by hand for things the CLI
doesn't expose. Restart `serve` after edits.
## Actor onboarding
End users need (a) credentials to register and (b) a one-time
bootstrap code to claim their long-term key. From the hive admin CLI:
```bash
# 1. Create the actor record (slot for someone named "alice").
kavilo-hived hive actor create -hive team-alpha \
-id alice -role user
# 2. Mint a one-shot 6-digit bootstrap code (valid for a short window;
# re-issue freely if it expires).
kavilo-hived hive actor bootstrap -hive team-alpha -id alice
# → 123456
```
Hand `alice` the server URL, the hive ref, her actor id+role, and the
6-digit code. She runs `kavilo-hive config init …` followed by
`kavilo-hive claim --code 123456` and is now wired up. See the
[user guide](/homebrew-tap/docs/kavilo-hive/user-guide.html) for the client side.
For bot actors (CI, automation), use `-role bot`. Bot creds skip the
interactive REPL but otherwise behave identically.
## Hub admin vs hive admin
| | Hub admin | Hive admin |
|---|---|---|
| Path | `/admin` | `/h//admin` |
| Scope | All hives, server config, federation | One hive's actors / groups / bootstrap codes |
| Creds | Single `admin` user with a password set by `admin init` | Per-hive `admin` with credentials minted by `hive create` |
| API token | Long-lived bearer token | Per-hive bearer (shown once at `hive create`) |
Hive-admin tokens are intentionally per-hive so you can give a team
operator the keys to *their* hive without exposing the whole hub.
## TLS
Out of the box the server generates a self-signed CA and a server
cert under `~/.kavilo-hive/tls/`. Two ways to use this:
- **Closed deployments** (Tailscale, VPN, lab): keep the built-in CA;
ship `ca.crt` to every client via `kavilo-hive config init --ca-file`.
- **Public deployments**: drop a Let's Encrypt cert (or your own PKI) into
`~/.kavilo-hive/tls/`; the server picks up whatever's in there. The
built-in CA is then irrelevant.
In both cases the WSS endpoint uses the same cert as the HTTPS admin
UI — there's only one listener.
## Federation (briefly)
A hive on server A can route to actors on server B once the operators
mutually pin each other's signing keys. The plumbing — discovery
document at `.well-known/kavilo-hive/v1`, mTLS server-to-server
transport at `/_federation/v1`, pinned `previous_signing_keys` chain
— is implemented through ADR-0002 Phase 14c. End-user flow is
identical to single-server (`kavilo-hive send --to alice@hive-b`); the
trust setup happens once between operators.
See
[`docs/adr/0002-multi-server-federation.md`](https://github.com/kavilo-bot/kavilo-hive/blob/main/docs/adr/0002-multi-server-federation.md).
## Image verification & SBOM
Every released image is cosign-keyless-signed and ships an SPDX SBOM
as an in-toto attestation. The release pipeline self-verifies before
declaring victory, but you should re-verify on each pull:
```bash
TAG=0.1.2
DIGEST=$(crane digest ghcr.io/kavilo-bot/kavilo-hived:${TAG})
# Signature
cosign verify \
--certificate-identity-regexp '^https://github.com/kavilo-bot/homebrew-tap/.github/workflows/build-kavilo-hive-release.yml@.*$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/kavilo-bot/kavilo-hived@${DIGEST}
# SBOM attestation
cosign verify-attestation --type spdxjson \
--certificate-identity-regexp '^https://github.com/kavilo-bot/homebrew-tap/.github/workflows/build-kavilo-hive-release.yml@.*$' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
ghcr.io/kavilo-bot/kavilo-hived@${DIGEST}
```
The recipe and digest are also printed in each release's job-summary
on the tap workflow run. The tap additionally runs
`verify-kavilo-hive-image.yml` on a weekly cron to catch Sigstore-side
drift before it bites operators.
## Production notes
- **Private keys**: actor private keys never live on the server. The
server only stores actor *public* keys and metadata. Hub admin
credentials and hive admin tokens *do* live in `~/.kavilo-hive/` —
back them up the way you'd back up SSH keys, and treat the directory
as sensitive.
- **TLS replacement**: the built-in CA is fine for controlled
deployments. For anything public-facing, drop your own cert in
`~/.kavilo-hive/tls/` (or terminate TLS at a reverse proxy and
point `kavilo-hived` at an internal-only listener).
- **Disk**: `~/.kavilo-hive/` must be writable. Mount the directory
read-write into the container if you're running Docker.
- **Backup**: the directory is the entire server state. `tar -czf
kavilo-hive-backup.tgz ~/.kavilo-hive` while the server is paused
gives you a clean snapshot. Phase 13's `kavilo-hived backup` /
`restore` subcommands automate this with encrypted bundles.
- **Upgrades**: stop the binary, replace it, restart. State is
forward-compatible across minor versions; major-version bumps will
call out any migration steps in their release notes.
## Where to go next
- Client side / how end users talk to your server →
[`user-guide.md`](/homebrew-tap/docs/kavilo-hive/user-guide.html)
- Protocol design + threat model →
[`docs/adr/0001-signal-level-e2e.md`](https://github.com/kavilo-bot/kavilo-hive/blob/main/docs/adr/0001-signal-level-e2e.md)
- Source +
bug reports → [`kavilo-bot/kavilo-hive`](https://github.com/kavilo-bot/kavilo-hive)
]