Take a fresh box from git clone to a working three-process demo
(C2 server + agent + Tauri console) with mTLS in ~15 minutes.
Tested on Linux (Ubuntu 24.04 + Fedora 40). macOS and Windows work for the workspace build but the Tauri console section assumes Linux for the example commands.
| Component | Version | Why |
|---|---|---|
| Rust toolchain | stable (1.85+) | Workspace pin in rust-toolchain.toml |
openssl |
1.1.1+ | Used by scripts/gen-certs.sh to provision dev certs |
protoc (system) |
Optional | The workspace vendors protoc-bin-vendored; no manual install needed |
libwebkit2gtk-4.1-dev, libappindicator3-dev, librsvg2-dev, patchelf |
latest | Tauri webview prerequisites (Linux only) |
| Trunk | latest | Builds the Leptos WASM UI: cargo install trunk |
wasm32-unknown-unknown target |
— | rustup target add wasm32-unknown-unknown |
On Debian / Ubuntu:
sudo apt update
sudo apt install -y \
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf \
libssl-dev openssl build-essential pkg-config
cargo install trunk
rustup target add wasm32-unknown-unknowngit clone https://github.com/yourorg/rust-nexus.git
cd rust-nexusThe workspace has 9 active crates (trimmed from 14 in v3.8; 5 archived
to archive/). The ones you'll touch in this walkthrough:
nexus-common/ # NodeIdentity, sealed envelope, OsKind
nexus-a2a/ # A2A gRPC (cards, tls, capabilities, audit, interceptors)
nexus-infra/ # C2 server (A2A on :50052, NexusC2 on :50051)
nexus-agent/ # Cross-platform agent (PTY shell, transports/grpc.rs)
nexus-console/ # Operator desktop app
├── src-tauri/ # Tauri v2 wrapper (depends on Rust)
└── ui/ # Leptos WASM frontend (excluded from workspace; built by trunk)
integration-tests/ # Cross-crate tests
scripts/ # gen-certs.sh, demo.sh, build.sh
cargo check --workspace --exclude nexus-consoleExpected: clean build, ~20 warnings (mostly unused imports inherited from the overlay crates).
Run the full test suite:
cargo test --workspace --exclude nexus-consoleExpected: 170 / 170 pass. If anything fails, stop and triage — the deployment story assumes a green baseline.
./scripts/gen-certs.shThis writes a self-signed Ed25519 CA + server cert (CN=localhost) +
client cert (CN=operator-dev) into ./certs/:
certs/
├── ca.crt.pem # CA root certificate
├── ca.key.pem # CA private key (0o600; keep offline in prod)
├── server.crt.pem # Server cert (SAN=localhost,127.0.0.1)
├── server.key.pem # Server private key
├── client.crt.pem # Operator client cert
└── client.key.pem # Operator client private key
These certs are dev-only. They're self-signed by a CA that lives on the same box. For production, see
production.md§ CA strategy.
Export the canonical env vars (reserved names — never rename):
export NEXUS_CA_CERT="$(pwd)/certs/ca.crt.pem"
export NEXUS_SERVER_CERT="$(pwd)/certs/server.crt.pem"
export NEXUS_SERVER_KEY="$(pwd)/certs/server.key.pem"
export NEXUS_CLIENT_CERT="$(pwd)/certs/client.crt.pem"
export NEXUS_CLIENT_KEY="$(pwd)/certs/client.key.pem"cargo run --release --bin nexus-serverExpected log lines (look for these to confirm v1.2 features are on):
A2A server starting addr=127.0.0.1:50052 insecure_network=false mtls=true
A2A AgentCard signed with server identity
A2A agent registered peer_id=<32-byte hex> os=linux version=0.2.0
Without a NodeIdentity configured, the AgentCard ships unsigned and
you'll see ... ships unsigned (no server identity configured). That's
fine for local dev — see production.md § Server
provisioning for the signed-card setup.
In another terminal, with the same env vars exported:
cargo run --release -p nexus-agent -- \
--c2 https://localhost:50052 \
--transport grpcThe agent dials the C2's A2A port, sends an agent-register first
frame, and stays connected. You'll see on the server side:
A2A agent registered peer_id=<hex> os=linux version=...
In a third terminal:
# Build the Leptos WASM UI once
cd nexus-console/ui
trunk build --release
cd ..
# Run the Tauri shell in dev mode
cd src-tauri
cargo tauri devThe console window opens with 11 fixed tabs. Click Connect and fill in:
- C2 URL:
https://localhost:50052 - CA cert:
<repo>/certs/ca.crt.pem - Client cert:
<repo>/certs/client.crt.pem - Client key:
<repo>/certs/client.key.pem
The connection dialog calls GetAgentCard (verifies the Ed25519
signature if the server is configured with a NodeIdentity), then
ListRegisteredAgents. Your agent from step 6 should appear in the
list.
Tunnel badge connector (v4.4): To test the 6 new service tabs (Workbench, Portainer, Wiki, VS Code, Reports, Kasm) with real tunnel URLs, set
RTPI_SLUGandRTPI_DOMAINbefore launching:RTPI_SLUG=c3s RTPI_DOMAIN=onoiroi.us cargo tauri devWithout these variables, the new tabs render with localhost fallback URLs and tunnel badges are hidden.
Click the agent's row → Open shell. An xterm.js terminal opens.
Type whoami (or Get-Host on Windows agents) and confirm the output
round-trips.
Under the hood: the console sends a
shell-opencontrol frame targeting the agent's peer-id (hex). The C2'sOperatorRouterconsults the capability matrix, picks the agent's bidi back-channel fromAgentChannels, and proxies the operator's PTY input + agent's PTY output viaPart::filebyte frames.
If you don't have libwebkit2gtk available (e.g. CI container), skip the console and run the integration test loop:
./scripts/demo.sh
# [demo] PASS — v1.1 A2A loopback round-trip verifiedFor the v1.2 mTLS round-trip:
cargo test -p integration-tests --test a2a_mtls -- --ignored
# test mtls_round_trip ... okFor the v1.2 agent-side bidi round-trip (PTY echo):
cargo test -p integration-tests --test a2a_interactive_shell
# test agent_round_trip_via_a2a_bidi ... okThe console is dialing the C2 but doesn't trust the CA. Confirm
NEXUS_CA_CERT points at the same ca.crt.pem that signed
server.crt.pem. The TLS verifier compares the chain — if the server
cert's CN doesn't include localhost in its SAN, regenerate certs.
Another nexus-server is running, or a previous run didn't shut down cleanly. Find it:
ss -tlnp | grep 50052
kill <pid>Install the dev package:
sudo apt install libwebkit2gtk-4.1-devOn older Ubuntu (≤22.04), libwebkit2gtk-4.0-dev works too with a
Tauri config tweak — see Tauri's upstream docs.
The ListRegisteredAgents lister bridges from the legacy NexusC2
registry by default. v1.2 A2A-mode agents show up via AgentChannels
once they send their agent-register frame. Check:
- Server log has
A2A agent registered peer_id=…after the agent started. If not, the agent's first frame isn't reaching the C2 — inspect withRUST_LOG=trace. - The operator's
ListRegisteredAgentsresponse isRegisteredAgents { agents: [...] }. If empty, the lister is bridging only legacy agents — seenexus-infra/src/a2a_lister.rs.
You're on macOS without a signing cert. That's normal for local dev.
Use cargo tauri dev instead, or build unsigned with the warning.
The mTLS test is #[ignore] by default. Pass -- --ignored and the
five env vars (step 4) — without those env vars it bails out
correctly.