Scaffolds the server module and everything Phase 0 of PLAN.md calls for: a binary that boots, serves its discovery endpoints and nothing else yet. - config: one YAML file plus per-field WARP_* environment overrides, unknown fields rejected, validation that reports every problem at once - logging and metrics: slog, a Prometheus registry, per-route HTTP instrumentation labelled by pattern rather than path - httputil: the Matrix error envelope, error-returning handlers, CORS, request logging, panic recovery, and a router over http.ServeMux - endpoints: /_matrix/client/versions, /.well-known/matrix/client, /.well-known/matrix/server (gated), /healthz, /metrics, and M_UNRECOGNIZED for everything else - storage: driver registry, shared database/sql base, checksummed migration runner, sqlite and postgres drivers, pocketbase stub - settings: server_settings table behind a cached service, so the federation and registration switches change without a restart - federation: the gate that makes federation a runtime mode - cmd/warp: serve, migrate, generate-keys, version - Makefile, golangci-lint v2, GitHub Actions CI (Linux and Windows tests, race on Linux, storage tests against PostgreSQL, lint, boot smoke test), Dockerfile, example config and docs Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.5 KiB
Warp
A Matrix homeserver written from scratch in Go, plus a family of first-party clients. Federation is optional and can be switched on and off at runtime.
The full design and roadmap live in PLAN.md. This README covers what exists today.
Status
Phase 0 — bootstrap. Complete. There is a server binary that boots, serves its discovery endpoints and nothing else yet: no accounts, no rooms, no sync. Those are Phases 1 to 4.
What works now:
GET /_matrix/client/versions |
spec versions this build claims |
GET /.well-known/matrix/client |
client discovery |
GET /.well-known/matrix/server |
server discovery — only while federation is on |
GET /healthz |
liveness, including a database ping |
GET /metrics |
Prometheus |
anything else under /_matrix/ |
404 M_UNRECOGNIZED, as the spec requires |
Also in place: YAML + environment configuration, structured logging,
graceful shutdown, the storage interfaces with a working SQLite and
PostgreSQL driver (and a PocketBase stub), the embedded migration runner,
the server_settings table with the runtime federation and registration
switches, and warp generate-keys.
Quick start
Requires Go 1.26 or newer. No cgo, no C compiler, no database server: the default SQLite driver is pure Go.
cp warp.yaml.example warp.yaml
# edit global.server_name
make build
./bin/warp generate-keys
./bin/warp serve
Then:
curl localhost:8008/healthz
curl localhost:8008/_matrix/client/versions
Without make, the same from the server directory:
go build -o ../bin/warp ./cmd/warp
Configuration
One YAML file, every field overridable from the environment. See
warp.yaml.example — each setting lists its WARP_*
variable. With no config file at all the server runs on defaults plus the
environment, which is how the container image is meant to be used:
docker build -t warp .
docker run -p 8008:8008 -v warp-data:/data -e WARP_SERVER_NAME=warp.example warp
An unknown field in the config file is an error, not a warning: a typo in a security-relevant setting should never be silently ignored.
Commands
| Command | What it does |
|---|---|
warp serve |
run the homeserver |
warp migrate [--status] |
apply (or list) pending database migrations |
warp generate-keys [--output path] [--key-id id] |
create the ed25519 signing key |
warp version |
version and compiled-in storage drivers |
The signing key is written as a single line, the same format Synapse uses:
ed25519 <key-id> <unpadded-base64-seed>. generate-keys refuses to
overwrite an existing key — losing it would invalidate every event this
server has ever signed.
Federation switch
Federation is off in a fresh install. While it is off, the federation
surface does not answer at all; /.well-known/matrix/server returns
404 M_UNRECOGNIZED, exactly as it would on a server that has no
federation API.
The switch is stored in the server_settings table and cached in memory,
so flipping it takes effect on the next request without a restart. In
Phase 0 the only thing behind the gate is the delegation document; the
outbound queue, key fetcher and remote media fetch join it in Phase 5. Set
federation.allow_runtime_toggle: false to pin the switch to the config
file.
Development
make test # unit tests (SQLite)
make lint # golangci-lint
make check # vet + test, what CI runs
make cover # coverage.html
# the storage tests against PostgreSQL as well
WARP_TEST_POSTGRES_DSN='postgres://warp:warp@localhost/warp_test?sslmode=disable' make test-postgres
make test-race needs cgo and therefore a C toolchain; CI runs it on
Linux.
Layout
server/ the homeserver (Go module)
cmd/warp/ the binary: serve, migrate, generate-keys, version
internal/
config/ YAML + env configuration
logging/ metrics/ slog and Prometheus setup
httputil/ Matrix error envelope, CORS, router, middleware
federation/ the federation gate and allow/deny policy
settings/ runtime settings service (the switches)
setup/ wiring: storage, routes, graceful shutdown
matrix/signing/ ed25519 server keys
storage/ interfaces + driver registry
migrations/ embedded migration runner
sqlstore/ shared database/sql implementation
sqlite/ postgres/ pocketbase/
apps/ clients: ui, admin, web, desktop, mobile (Phase 1+)
docs/