# dragoon
[](https://crates.io/crates/dragoon-proto)
[](#license)
> **Disconnected Rapid Guidance Once-deployed Operational Network** — a
> public-relay command executor for long-running experiments, named
> after the Gundam SEED 浮游炮 system since the architecture is
> isomorphic: a controller pilots remote autonomous nodes that execute
> tasks under cryptographically authenticated commands.
The model is **ctl → server → worker(s)** over HTTPS: every component
connects out, so no NAT/firewall holes are needed on the worker boxes.
Three binaries, one Cargo workspace:
| `dragoon-server` | Public-relay HTTP server (axum + rusqlite). The only host that needs an inbound port. |
| `dragoon-worker` | Execution agent. Polls the server, runs shell payloads, streams logs/artifacts back. |
| `dragoon-ctl` | Controller CLI. Submits commands, tails logs, drains the message inbox. |
The original Python implementation lives in git history (last commit:
`8cce0de` deletes it). Wire format, SQLite schema, canonical strings and
fingerprints are byte-for-byte unchanged — fixture parity tests in
`crates/dragoon-proto/tests/` lock that down.
See [`docs/plans/2026-04-27-remote-executor-design.md`](docs/plans/2026-04-27-remote-executor-design.md)
for the full design (auth model, protocol, state machine).
## Build
```bash
cargo build --release --workspace
# produces target/release/{dragoon-server, dragoon-worker, dragoon-ctl}
```
Tests: `cargo test --workspace` (~120 unit + integration tests, including
4 byte-for-byte parity fixtures and the auth_routes axum integration tests).
Lint: `cargo clippy --workspace --all-targets -- -D warnings` is clean.
## Quick start (loopback, no TLS)
```bash
DATA=/tmp/re-demo
mkdir -p "$DATA" && ssh-keygen -t ed25519 -f "$DATA/id" -N "" -q
cat > "$DATA/server.toml" <<EOF
[server]
data_dir = "$DATA/srv"
bind_host = "127.0.0.1"
bind_port = 8765
public_url = "http://127.0.0.1:8765"
EOF
# admin pipeline
./target/release/dragoon-server user pubkey add --user alice --pub "$DATA/id.pub" --data-dir "$DATA/srv"
CODE=$(./target/release/dragoon-server token issue --worker lab-w1 --data-dir "$DATA/srv" | awk '{print $NF}')
# server up
./target/release/dragoon-server run --config "$DATA/server.toml" &
# worker register + run (on the same or a different box)
./target/release/dragoon-worker init --server http://127.0.0.1:8765 --code "$CODE" \
--name lab-w1 --workdir "$DATA/wd" --state-file "$DATA/state.json"
./target/release/dragoon-worker run --name lab-w1 --state-file "$DATA/state.json" \
--workdir "$DATA/wd" --server http://127.0.0.1:8765 &
# ctl: log in, submit, watch
TOTP=$(oathtool --base32 --totp "$TOTP_SECRET")
printf 'hunter2\n%s\n' "$TOTP" | ./target/release/dragoon-ctl login \
--server http://127.0.0.1:8765 --username alice --ssh-key "$DATA/id"
./target/release/dragoon-ctl worker list
TID=$(./target/release/dragoon-ctl submit lab-w1 -c "echo hello-from-rust && hostname")
./target/release/dragoon-ctl tail "$TID"
./target/release/dragoon-ctl events
```
## Cross-compile a worker for another arch
The `xtask` cargo subcommand detects the target's arch over SSH and
builds a static musl binary against the right Rust target triple, then
scps it over and installs.
```bash
cargo xtask deploy-worker --host me@some-arm-box
# detects Linux aarch64 -> aarch64-unknown-linux-musl
# cargo build --release -p dragoon-worker --target aarch64-unknown-linux-musl
# scp -> /tmp/dragoon-worker.new.<pid> -> install -m 0755 to /usr/local/bin/dragoon-worker
```
Build all supported triples for an offline distribution:
```bash
cargo xtask release-all-triples
ls dist/ # dragoon-worker-x86_64-unknown-linux-musl
# dragoon-worker-aarch64-unknown-linux-musl
# dragoon-worker-armv7-unknown-linux-musleabihf
```
If a target's musl linker isn't on `PATH`, `cargo xtask doctor --triple T`
tells you what to install (`apt install musl-tools`,
`aarch64-linux-musl-cross`, etc.) — or fall back to `cargo install cross`
and use `cross build` directly.
macOS targets (`x86_64-apple-darwin`, `aarch64-apple-darwin`) only build
on a mac host — Linux→Mac cross-compilation is intentionally out of scope.
## Workspace layout
```
crates/
dragoon-proto/ shared protocol primitives (constants, wire format,
canonical strings, signers, models). Pure-Rust, no
tokio/reqwest/sqlite. The byte-parity fixture tests
live here.
dragoon-server/ axum app + rusqlite (bundled) + argon2id + totp-rs +
ed25519/RSA key handling. Routes:
/v1/auth/{challenge,login,logout}
/v1/workers, /v1/workers/{name}/fetch
/v1/tasks, /v1/tasks/{id}/{cancel,log,artifact}
/v1/messages, /v1/messages/ack
/v1/worker/{init,poll,log,finish,blob}
dragoon-worker/ reqwest + nix (setsid + killpg) + globset + walkdir.
Spawns bash -c in its own process group; threaded
stdout/stderr readers; SIGTERM→grace→SIGKILL on cancel.
dragoon-ctl/ reqwest signed-request client (KeyFileSigner or
AgentSigner over SSH_AUTH_SOCK) + clap CLI.
dragoon-testkit/ dev-only fixture loaders + harness.
xtask/ cross-compile orchestrator (see above).
fixtures/ JSON fixtures committed to repo; the source of truth
for canonical_request, canonical_task, fingerprints
and ed25519 signatures.
.claude/skills/ Project-level Claude Code skills (deploy-server,
deploy-worker, send-command).
```
## Authentication summary
### ctl → server
Every business request must carry **all** of:
- `Authorization: Bearer <session_token>`
- `X-RE-Timestamp` (±60 s window)
- `X-RE-Nonce` (5-min one-shot)
- `X-RE-Key-Fingerprint` (must match the session-bound fp)
- `X-RE-Signature` (SSH-wire ed25519 / rsa-sha2-512 signature over the
`RE-V1` canonical request bytes)
`KeyFileSigner` (loaded private key) and `AgentSigner` (talks the SSH
agent protocol over `SSH_AUTH_SOCK`) both produce the wire format the
server's `verify_ssh_wire_signature` accepts.
### server → worker
Every assigned task is signed by the per-server ed25519 task-signing
key. Worker pins the public key on `init` and rejects any
out-of-signature, mismatched-name, or out-of-order-seq task with
`error="bad_task_signature: <reason>"` *before* spawning a subprocess.
## License
Dual-licensed under either of:
- [Apache License, Version 2.0](LICENSE-APACHE)
- [MIT license](LICENSE-MIT)
at your option.