Development
This document explains how to build, test, and run exchange locally in this repository. Deployment steps, protocol field tables, and platform installation details remain in the dedicated documents linked at the end.
Toolchain
The workspace uses Rust 2021 and requires Rust 1.88 or newer. The version is recorded in the rust-version field of the root Cargo.toml.
Workspace and binaries
The Rust workspace has nine members: eight under crates/ and the extension SDK under sdk/extend/rust. Protocol types are defined only in crates/proto; exchange, agent, and cli depend on this crate. Do not duplicate message structures in individual consumers.
| crate | Path | Output |
|---|---|---|
proto |
crates/proto |
Library; control messages, binary frames, and shared data structures live only here |
exchange |
crates/exchange |
exchange binary (same package and binary name) |
agent |
crates/agent |
remote-agent binary |
cli |
crates/cli |
remote-cli binary |
updater |
crates/updater |
Self-update library for remote-agent and remote-cli |
telemetry |
crates/telemetry |
Shared exception and log reporting library for all three components |
tls-client |
crates/tls-client |
Shared rustls client TLS policy and optional proxy connections |
worker-proto |
crates/worker-proto |
Protocol validation and credential hashing for Workers Exchange |
remote-extend-sdk |
sdk/extend/rust |
Library; extension provider framing and contracts |
The entry point for exchange is crates/exchange/src/main.rs, without a separate [[bin]] table, so cargo build -p exchange produces exchange. The agent and cli manifests (Cargo.toml) explicitly name their binaries remote-agent and remote-cli.
The connection tables are split by responsibility:
crates/exchange/src/hub.rsmanages online devices, reconnect replacement, and epochs.crates/exchange/src/hub/session_table.rstranslates operator-side and device-side IDs.crates/exchange/src/hub/outbound.rsmanages the bounded send queue for each connection.
Build
Run these commands from the repository root.
The release profile (opt-level = 2, thin LTO, unwind retained) builds the server, CLI, and Agent:
cargo build --release
Binaries are located at:
target/release/exchangetarget/release/remote-clitarget/release/remote-agent
release retains unwinding because exchange is a persistent process with multiple connections. If a connection task panics, unwinding lets tokio stop that task without taking down sessions for other devices in the same process.
The device Agent also has a size-optimized profile (opt-level = "z", LTO, panic = "abort", strip, codegen-units = 1):
cargo build -p agent --no-default-features --profile release-small
The output is target/release-small/remote-agent. Because procd, systemd, or Task Scheduler restarts the Agent after a crash, this profile can trade panic = "abort" for a smaller binary.
The database is not required for compilation. crates/exchange/src/db.rs uses sqlx runtime queries rather than query! macros, so cargo build works without a database.
Tests
cargo test --workspace
Windows skips tests requiring Unix /bin/sh (or /bin/ash on OpenWrt) and PTYs. The test module in crates/agent/src/pty.rs is guarded by #[cfg(all(test, unix))] and is not compiled on non-Unix platforms. The PTY implementation tries /bin/ash and then /bin/sh. Process-group and Unix signal paths in exec.rs also compile only on Unix; Windows command tests use PowerShell without invoking /bin/sh. After changing Unix paths in exec.rs or pty.rs, run cargo test --workspace on Linux as well; Windows results alone are insufficient.
Narrow the scope to one crate when useful, for example cargo test -p proto or cargo test -p exchange -- hub::.
Run exchange locally
Prepare PostgreSQL and put its connection string in DATABASE_URL. This variable maps to the exchange --database-url option (see crates/exchange/src/config.rs). At startup, db::connect runs migrations embedded at compile time:
sqlx::migrate!("../../migrations")
Migration files live in the repository-root migrations/ directory and run automatically at startup; sqlx-cli is unnecessary.
Audit operations also require an audit encryption key. The examples below generate a local test key. Store it securely and reuse the same key on subsequent starts; generating a new key each time prevents decryption of existing audit details.
export DATABASE_URL='postgres://luci:luci@127.0.0.1:5432/luci'
export AUDIT_ENCRYPTION_KEY="$(python -c 'import base64, secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())')"
cargo run -p exchange -- --listen 127.0.0.1:8080
In PowerShell, set the environment variable first:
$env:DATABASE_URL = "postgres://luci:luci@127.0.0.1:5432/luci"
$env:AUDIT_ENCRYPTION_KEY = python -c "import base64, secrets; print(base64.b64encode(secrets.token_bytes(32)).decode())"
cargo run -p exchange -- --listen 127.0.0.1:8080
If the release binary is already built, replace cargo run -p exchange -- above with ./target/release/exchange. The default listen address is 0.0.0.0:8080; explicitly bind to 127.0.0.1 for local debugging.
When the database has no enabled operator account, startup bootstraps an administrator and prints its token in that startup log only. The plaintext appears once. Complete deployment, reverse-proxy, and account instructions are in repository docs/deployment.md. That page contains server addresses and is not published on this site.
Pre-commit checks
Run these checks from the repository root. For Rust changes, start with focused crate tests, then complete workspace checks. If formatting checks find unrelated existing drift, format only changed files instead of reformatting other code. Android, the browser extension and the mobile console also need their own builds and tests; Rust tests do not cover those surfaces.
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace
Maintain documentation
Verify implementation behavior before updating repository guides in docs/. Website topics have a reference source at website/docs/<topic>.md and nine authored translations at website/docs/content/<locale>/<topic>.md. Keep commands, fields, code examples and heading levels aligned. Generate pages and search indexes with the script rather than editing HTML alone. Run the following from the repository root with Python 3.11+, markdown-it-py and Node.js installed:
python -m pip install markdown-it-py
python scripts/build-docs.py
python scripts/build-docs.py --check
python scripts/test-docs.py
python scripts/test-docs-markdown.py
node scripts/test-docs-locale.js
python scripts/test-build-docs.py
python scripts/test-site-seo.py
The generation check also compares every generated file byte for byte, failing on missing or stale output without writing files. CI runs it before any rebuild.
The checks validate translation completeness, code examples, heading structure, page links, anchors and language routing. Commit generated pages with their sources. Building documentation changes local files only; website deployment and download publication are separate operations.
Related documents
- Technical overview: outbound connections, session translation, epochs, bounded queues, and token storage
- Usage: login, adding devices, starting the Agent, commands, and shells
- Deployment guide: repository
docs/deployment.md(contains server addresses; not published on this site) - Wire protocol
- Multiplatform device deployment