# 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. `internal/setup` holds the HTTP-level tests; they build a whole server on an in-memory SQLite database. - Storage tests run against every driver that is available: SQLite always, PostgreSQL when `WARP_TEST_POSTGRES_DSN` is set. - 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, the official integration suite, is wired up in Phase 1 — there is nothing it can test before registration exists.