CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Implements PLAN.md Phase 2: the library every later phase is built on, as pure code under server/internal/matrix with no HTTP and no storage. canonicaljson: the canonical JSON encoding, hand-written because encoding/json escapes <, > and & and accepts floats. Integers only, in [-(2^53)+1, 2^53-1]; lone surrogate escapes rejected rather than turned into U+FFFD. signing: the Signing JSON algorithm alongside the existing key handling, and one lenient base64 decoder shared by the packages above it. id: identifier grammars as they appear inside events, deliberately laxer than userapi's account rules because rooms carry historical user IDs. roomversion: the registry. Versions 3 to 12 are supported, each written out in full as a Rules value with the flags the other packages read. Versions 1 and 2 are known but refused: their event format and state resolution v1 wait for federation. createRoom will default to 11, as the spec asks for a while after 1.16, with 12 available. event: PDU parsing and validation, content and reference hashes, event IDs, the redaction algorithm for every supported version, signing and verification, and a Builder. auth: the authorisation rules in spec order, power levels with their defaults, v12 creators with infinite power, the auth events selection algorithm, rule 3's auth_events checks, and the separate test for whether a redaction applies. Comments use the room version 12 rule numbering, with the version 11 numbers given where they differ. stateres: state resolution v2, and version 12's amendment (an empty starting state and the conflicted state subgraph). Tested against the spec's own vectors byte for byte: the canonical JSON examples, both JSON signing vectors and both event signing vectors. State resolution ports scenarios from Synapse's suite. canonicaljson and event.Parse have fuzz targets. The capabilities endpoint now advertises the registry instead of a hard-coded "11". docs/event-core.md describes the packages and the known gaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
6.4 KiB
Markdown
167 lines
6.4 KiB
Markdown
# Development
|
|
|
|
## Prerequisites
|
|
|
|
- Go 1.26+. Nothing else for the default build: the SQLite driver is pure
|
|
Go and `CGO_ENABLED=0` works everywhere.
|
|
- A C toolchain only if you want `go test -race`.
|
|
- PostgreSQL only to run the storage tests against it.
|
|
- `golangci-lint` v2 for `make lint`:
|
|
`go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest`
|
|
|
|
## Everyday commands
|
|
|
|
```sh
|
|
make build # bin/warp
|
|
make test # go test ./... in server/
|
|
make check # vet + test
|
|
make lint
|
|
make cover # coverage.html
|
|
make clean
|
|
```
|
|
|
|
Each one is a two-line shell command if you would rather not use `make`;
|
|
look at the [Makefile](../Makefile).
|
|
|
|
## Running a server locally
|
|
|
|
```sh
|
|
cp warp.yaml.example warp.yaml # set global.server_name
|
|
make build
|
|
./bin/warp generate-keys
|
|
./bin/warp serve
|
|
```
|
|
|
|
The SQLite file, the signing key and (later) the media store are written
|
|
relative to the working directory. They are in `.gitignore`.
|
|
|
|
For a throwaway server with no config file at all:
|
|
|
|
```sh
|
|
WARP_SERVER_NAME=dev.local WARP_PRIVATE_KEY=./keys/dev.key ./bin/warp generate-keys
|
|
WARP_SERVER_NAME=dev.local WARP_PRIVATE_KEY=./keys/dev.key ./bin/warp serve
|
|
```
|
|
|
|
## Adding a database migration
|
|
|
|
1. Write the same change twice, once per SQL dialect:
|
|
`server/internal/storage/sqlite/migrations/NNNN_name.sql` and
|
|
`server/internal/storage/postgres/migrations/NNNN_name.sql`.
|
|
Use the next free number; `NNNN` is zero-padded to four digits.
|
|
2. The files are embedded with `embed.FS`, so a rebuild picks them up.
|
|
3. `./bin/warp migrate --status` lists what would run, `./bin/warp migrate`
|
|
applies it. A server started with `database.auto_migrate: true` (the
|
|
default) runs them itself.
|
|
|
|
Rules the runner enforces:
|
|
|
|
- One transaction per migration. A failure rolls that file back entirely
|
|
and leaves the following ones unapplied.
|
|
- A migration that has already been applied and whose file has since
|
|
changed is an error. Write a new migration instead of editing an old
|
|
one; the checksum of every applied file is recorded.
|
|
- A database that has migrations this binary does not know about refuses
|
|
to start, so an old binary cannot run against a newer schema.
|
|
|
|
## Adding an endpoint
|
|
|
|
Routes are registered in `server/internal/setup/routes.go` on an
|
|
`httputil.Router`, which is `http.ServeMux` plus per-route metrics:
|
|
|
|
```go
|
|
r.HandleFunc("GET /_matrix/client/v3/profile/{userID}/displayname", s.handleGetDisplayName)
|
|
```
|
|
|
|
Handlers have the signature `func(http.ResponseWriter, *http.Request) error`
|
|
and return Matrix errors directly:
|
|
|
|
```go
|
|
func (s *Server) handleThing(w http.ResponseWriter, r *http.Request) error {
|
|
if somethingWrong {
|
|
return httputil.Forbidden("You are not allowed to do that")
|
|
}
|
|
httputil.WriteJSON(w, http.StatusOK, response{})
|
|
return nil
|
|
}
|
|
```
|
|
|
|
Anything returned that is not a `*httputil.MatrixError` is logged in full
|
|
and reported to the client as `M_UNKNOWN`, so internal detail never leaks.
|
|
|
|
Endpoints that belong to the federation surface are registered through the
|
|
gate instead, so they disappear when federation is switched off:
|
|
|
|
```go
|
|
r.Handle("GET /_matrix/federation/v1/version", s.federation.ProtectFunc(s.handleFederationVersion))
|
|
```
|
|
|
|
A Client-Server endpoint that would need federation to answer returns
|
|
`federation.RejectClientRequest("...")`, which is an `M_FORBIDDEN` that
|
|
says why.
|
|
|
|
## Tests
|
|
|
|
- Unit tests live next to the code. Each API package holds its own
|
|
HTTP-level tests, built on a throwaway SQLite database with the real
|
|
components behind them rather than fakes; `internal/setup` holds the
|
|
smaller set that goes through the whole wrapped handler, to catch a route
|
|
that was written but never registered.
|
|
- Storage tests run against every driver that is available:
|
|
SQLite always, PostgreSQL when `WARP_TEST_POSTGRES_DSN` is set.
|
|
- The event core under `internal/matrix` is tested against the spec's own
|
|
cryptographic and canonical JSON test vectors, and has fuzz targets for
|
|
the two functions that read bytes straight off the network. `make test`
|
|
runs the unit tests; the fuzzers are opt-in. See
|
|
[event-core.md](event-core.md).
|
|
- CI runs `gofmt -l`, `go mod tidy` (as a diff check), `go vet`, the tests
|
|
on Linux and Windows, the race detector on Linux, `golangci-lint`, and a
|
|
smoke test that boots the real binary and curls it.
|
|
|
|
## Complement
|
|
|
|
[Complement](https://github.com/matrix-org/complement) is the official Matrix
|
|
integration suite. It runs a container per homeserver per test and drives it
|
|
over the real HTTP API, which makes its pass count the honest measure of how
|
|
far along the server is — PLAN.md section 9 treats it as the primary progress
|
|
metric.
|
|
|
|
```sh
|
|
make complement # the current target set
|
|
server/test/complement/run.sh TestLogin # one test
|
|
COMPLEMENT_DEBUG=1 make complement # with the container logs
|
|
```
|
|
|
|
It needs Docker. The script builds the image, clones Complement into
|
|
`.complement/` (ignored by git) and runs the subset for the phases that are
|
|
done — currently `TestRegistration`, `TestLogin` and `TestDeviceManagement`,
|
|
with federation off. Extend `COMPLEMENT_TESTS` in
|
|
[run.sh](../server/test/complement/run.sh) as each phase lands, rather than
|
|
switching to the whole suite: a pass count that drops because a later phase
|
|
is missing is noise.
|
|
|
|
The image is [server/test/complement/Dockerfile](../server/test/complement/Dockerfile)
|
|
and the deployment is entirely `WARP_*` environment variables — see
|
|
[entrypoint.sh](../server/test/complement/entrypoint.sh). Two things there
|
|
are worth knowing: rate limiting is off, because the whole suite comes from
|
|
one address and would otherwise spend its time reading `M_LIMIT_EXCEEDED`;
|
|
and port 8448 is exposed but nothing listens on it yet. The federation
|
|
profile needs a TLS listener signed by the CA that Complement mounts at
|
|
`/complement/ca`, which arrives with federation in Phase 5.
|
|
|
|
## Creating the first account
|
|
|
|
A new install has registration off, so the first account is made from the
|
|
command line against the database directly — no server needs to be running:
|
|
|
|
```sh
|
|
./bin/warp create-account --username you --admin
|
|
```
|
|
|
|
It prompts for the password twice. `--password-file` reads it from a file and
|
|
`--password` takes it inline, for scripts that have already thought about the
|
|
shell history. From there, the admin API can turn registration on:
|
|
|
|
```sh
|
|
curl -X PUT http://localhost:8008/_warp/admin/v1/registration -H "Authorization: Bearer $TOKEN" -d '{"enabled": true}'
|
|
```
|