Files
Warp/.github/workflows/ci.yml
T
tajniak81andClaude Opus 5.5 829b236efe
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
CI / Admin panel (push) Canceled after 0s
CI / Web client (push) Canceled after 0s
CI / Desktop client (ubuntu-24.04) (push) Canceled after 0s
CI / Desktop client (windows-latest) (push) Canceled after 0s
Desktop: notifications, tray, auto-update, installer; web: sliding sync, voice messages, notifications
Web client (also what the desktop app runs):

- Simplified Sliding Sync (MSC4186) when the homeserver advertises
  org.matrix.simplified_msc3575. src/matrix/sliding.ts answers the SDK's
  GET /sync with a sliding sync request and rewrites the response into
  the /sync shape, as the Android app does; the SDK's own sliding sync
  mode doesn't sort thread replies into threads. 20 rooms first, then
  every room (up to 1000); to_device, e2ee, account data, receipts and
  typing extensions; M_UNKNOWN_POS starts a new connection. The SDK's
  /members?at=<sync token> drops the token, which here is ours. Settings
  can switch back to /sync.
- Notifications for new messages and incoming calls while Warp isn't in
  front, by the account's push rules, evaluated after decryption. The
  browser's Notification API once turned on in Settings, or the desktop
  app's bridge (window.warpDesktop). One per room; a click opens it.
- Voice messages: record from the composer, sent as m.audio with
  MSC3245's voice marker and MSC1767's duration and waveform (encrypted
  in encrypted rooms); a waveform player in the timeline.
- @warp/ui: a slot for extra composer buttons; bell-off, play and pause
  icons.

Desktop app:

- System notifications through Wails' notifications service, fed by
  /_desktop/bridge.js (/_desktop/notify, JSON only); clicking one shows
  the window and opens the room.
- Tray icon with Open and Quit; closing the window hides it (a one-time
  notification says so), File > Quit ends it. The page title goes to
  the window and the tray tooltip, the unread count to the taskbar or
  Dock badge.
- Auto-update from a Wails update manifest at DESKTOP_UPDATE_URL: a quiet
  check a minute after start and every six hours, a notification when a
  newer version exists, Help > Check for updates opens the update window.
  Optional signature key (DESKTOP_UPDATE_KEY). Linux package installs
  leave updates to the package manager. build/manifest writes the
  manifest.
- Per-user NSIS installer (make desktop-windows-installer), the macOS
  bundle zipped for the updater, a desktop CI job, and a Desktop release
  workflow that builds all three platforms and the manifest.

Tested on Windows against a local homeserver: sliding sync with E2EE,
threads and a server restart; notifications (toast history), close to
tray, relaunch restoring the window; voice messages recorded, sent and
played, encrypted too; the update check finding a newer version. Not
tested: installing an update, the NSIS installer, CI, macOS and Linux.
Code signing needs certificates and isn't done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:01:02 +02:00

433 lines
14 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: "1.26"
jobs:
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- name: Verify formatting
if: runner.os == 'Linux'
working-directory: server
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "These files need gofmt:"
echo "$unformatted"
exit 1
fi
- name: Verify go.mod is tidy
if: runner.os == 'Linux'
working-directory: server
run: |
go mod tidy
git diff --exit-code -- go.mod go.sum
- name: Vet
working-directory: server
run: go vet ./...
- name: Test
working-directory: server
run: go test -count=1 ./...
- name: Test with the race detector
if: runner.os == 'Linux'
working-directory: server
run: go test -race -count=1 ./...
- name: Build
working-directory: server
run: go build ./...
postgres:
name: Storage tests on PostgreSQL
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: warp
POSTGRES_PASSWORD: warp
POSTGRES_DB: warp_test
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- name: Storage tests (sqlite + postgres)
working-directory: server
env:
WARP_TEST_POSTGRES_DSN: postgres://warp:warp@localhost:5432/warp_test?sslmode=disable
run: go test -count=1 ./internal/storage/...
complement:
name: Complement (federation off)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
# Complement is a separate Go module with its own dependencies; the
# runner script clones it into .complement/.
- name: Run the Complement subset for the phases that are done
run: bash server/test/complement/run.sh
lint:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- uses: golangci/golangci-lint-action@v8
with:
version: v2.14.0
working-directory: server
smoke:
name: Smoke test the binary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- name: Build
run: make build
- name: Generate a signing key and boot the server
run: |
set -euo pipefail
sed 's/^ server_name: .*/ server_name: ci.example/' warp.yaml.example > warp.yaml
./bin/warp version
./bin/warp generate-keys
./bin/warp migrate
./bin/warp serve &
server_pid=$!
trap 'kill "$server_pid" 2>/dev/null || true' EXIT
for _ in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8008/healthz >/dev/null 2>&1; then
break
fi
sleep 1
done
curl -fsS http://127.0.0.1:8008/healthz | tee /dev/stderr | grep -q '"status":"ok"'
curl -fsS http://127.0.0.1:8008/_matrix/client/versions | grep -q '"versions"'
curl -fsS http://127.0.0.1:8008/.well-known/matrix/client | grep -q 'm.homeserver'
curl -fsS http://127.0.0.1:8008/metrics | grep -q warp_build_info
# Federation is off by default, so its surface must be absent.
status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8008/.well-known/matrix/server)"
test "$status" = "404"
# Registration is off in a fresh install, so the first account is
# made from the command line. Everything from here on is the
# Phase 1 surface, through the real binary.
./bin/warp create-account --username ci-admin --password hunter2hunter2 --admin
token="$(curl -fsS http://127.0.0.1:8008/_matrix/client/v3/login -d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "ci-admin"},
"password": "hunter2hunter2"
}' | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
test -n "$token"
curl -fsS http://127.0.0.1:8008/_matrix/client/v3/account/whoami \
-H "Authorization: Bearer $token" | grep -q '@ci-admin:ci.example'
curl -fsS http://127.0.0.1:8008/_matrix/client/v3/capabilities \
-H "Authorization: Bearer $token" | grep -q 'm.room_versions'
curl -fsS http://127.0.0.1:8008/_warp/admin/v1/stats \
-H "Authorization: Bearer $token" | grep -q '"admins":1'
# An unauthenticated request must be refused, not served.
status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8008/_warp/admin/v1/stats)"
test "$status" = "401"
# Registration is refused while the switch is off, and accepted
# once the admin API flips it — with no restart.
status="$(curl -s -o /dev/null -w '%{http_code}' -X POST \
http://127.0.0.1:8008/_matrix/client/v3/register -d '{}')"
test "$status" = "403"
curl -fsS -X PUT http://127.0.0.1:8008/_warp/admin/v1/registration \
-H "Authorization: Bearer $token" -d '{"enabled":true}' | grep -q '"enabled":true'
status="$(curl -s -o /dev/null -w '%{http_code}' -X POST \
http://127.0.0.1:8008/_matrix/client/v3/register -d '{}')"
test "$status" = "401"
# Graceful shutdown: SIGTERM must stop the process cleanly.
kill -TERM "$server_pid"
wait "$server_pid"
admin:
name: Admin panel
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- name: Install
run: pnpm install --frozen-lockfile
- name: Typecheck and build the frontend
run: |
pnpm -r typecheck
pnpm --filter @warp/admin build
- name: Verify formatting
working-directory: apps/admin
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "These files need gofmt:"
echo "$unformatted"
exit 1
fi
- name: Vet and test warp-admin
working-directory: apps/admin
run: |
go vet ./...
go test -count=1 ./...
- name: Smoke test warp-admin against the homeserver
run: |
set -euo pipefail
make build
(cd apps/admin && go build -o ../../bin/warp-admin ./cmd/warp-admin)
sed 's/^ server_name: .*/ server_name: ci.example/' warp.yaml.example > warp.yaml
./bin/warp generate-keys
./bin/warp migrate
./bin/warp create-account --username ci-admin --password hunter2hunter2 --admin
./bin/warp serve &
./bin/warp-admin -homeserver http://127.0.0.1:8008 -listen 127.0.0.1:8009 &
trap 'kill $(jobs -p) 2>/dev/null || true' EXIT
for _ in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8008/healthz >/dev/null 2>&1 &&
curl -fsS http://127.0.0.1:8009/healthz >/dev/null 2>&1; then
break
fi
sleep 1
done
# The panel is embedded, and its routes fall back to index.html.
curl -fsS http://127.0.0.1:8009/ | grep -q '<div id="app">'
curl -fsS http://127.0.0.1:8009/users | grep -q '<div id="app">'
# Login and the admin API go through the proxy...
token="$(curl -fsS http://127.0.0.1:8009/_matrix/client/v3/login -d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "ci-admin"},
"password": "hunter2hunter2"
}' | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
test -n "$token"
curl -fsS http://127.0.0.1:8009/_warp/admin/v1/stats \
-H "Authorization: Bearer $token" | grep -q '"admins":1'
# ...and the rest of the homeserver does not.
status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8009/_matrix/client/v3/sync \
-H "Authorization: Bearer $token")"
test "$status" = "404"
web:
name: Web client
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: server/go.sum
- name: Install
run: pnpm install --frozen-lockfile
- name: Typecheck and build the frontend
run: |
pnpm --filter @warp/web typecheck
pnpm --filter @warp/web build
- name: Verify formatting
working-directory: apps/web
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "These files need gofmt:"
echo "$unformatted"
exit 1
fi
- name: Vet and test warp-web
working-directory: apps/web
run: |
go vet ./...
go test -count=1 ./...
- name: Smoke test warp-web against the homeserver
run: |
set -euo pipefail
make build
(cd apps/web && go build -o ../../bin/warp-web ./cmd/warp-web)
sed 's/^ server_name: .*/ server_name: ci.example/' warp.yaml.example > warp.yaml
./bin/warp generate-keys
./bin/warp migrate
./bin/warp create-account --username ci-user --password hunter2hunter2
./bin/warp serve &
./bin/warp-web -homeserver http://127.0.0.1:8008 -listen 127.0.0.1:8080 &
trap 'kill $(jobs -p) 2>/dev/null || true' EXIT
for _ in $(seq 1 30); do
if curl -fsS http://127.0.0.1:8008/healthz >/dev/null 2>&1 &&
curl -fsS http://127.0.0.1:8080/healthz >/dev/null 2>&1; then
break
fi
sleep 1
done
# The client is embedded, its routes fall back to index.html, and
# its runtime config says to use the proxy.
curl -fsS http://127.0.0.1:8080/ | grep -q '<div id="app">'
curl -fsS 'http://127.0.0.1:8080/room/!abc:ci.example' | grep -q '<div id="app">'
curl -fsS http://127.0.0.1:8080/config.json | grep -q '"homeserver_url":""'
# The client API goes through the proxy, trailing-slash routes too...
token="$(curl -fsS http://127.0.0.1:8080/_matrix/client/v3/login -d '{
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": "ci-user"},
"password": "hunter2hunter2"
}' | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')"
test -n "$token"
curl -fsS 'http://127.0.0.1:8080/_matrix/client/v3/sync?timeout=0' \
-H "Authorization: Bearer $token" | grep -q '"next_batch"'
curl -fsS http://127.0.0.1:8080/_matrix/client/v3/pushrules/ \
-H "Authorization: Bearer $token" | grep -q '"global"'
# ...and the admin API does not.
status="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/_warp/admin/v1/stats \
-H "Authorization: Bearer $token")"
test "$status" = "404"
desktop:
name: Desktop client (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: apps/desktop/go.sum
# Wails builds against GTK 4 and WebKitGTK 6.0 on Linux, with cgo.
- name: Install the Linux webview
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libgtk-4-dev libwebkitgtk-6.0-dev
- name: Install
run: pnpm install --frozen-lockfile
- name: Build the web client
run: pnpm --filter @warp/web build
- name: Verify formatting
if: runner.os == 'Linux'
working-directory: apps/desktop
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "These files need gofmt:"
echo "$unformatted"
exit 1
fi
- name: Vet and test
working-directory: apps/desktop
run: |
go vet ./...
go test -count=1 ./...
- name: Build
working-directory: apps/desktop
shell: bash
run: go build -tags production -o warp-desktop ./cmd/warp-desktop