Server & CLI

The TopGun server is a Rust binary built on axum and tokio. It manages client WebSocket connections, CRDT synchronization, Merkle delta sync, full-text and vector search, clustering, and durable storage (embedded redb by default, optional PostgreSQL for production).

This page documents the user-facing surface: how to run it, what flags and environment variables it reads, the HTTP and WebSocket endpoints, and the Rust embed API for callers that want to host the server in-process. For operator-oriented setup with Docker Compose, Kubernetes, and tuning advice, see Deploy.

Running the server

The repository entrypoint is pnpm start:server, which wraps cargo run --bin topgun-server --release. The binary is named topgun-server because the build was originally produced for the integration test suite; it is the same binary used in production-style demos like demo.topgun.build.

# From the repo root
pnpm start:server

# Or directly with cargo
cargo run --bin topgun-server --release

# Pick a port
PORT=8080 cargo run --bin topgun-server --release

# Increase log verbosity
RUST_LOG=topgun_server=debug cargo run --bin topgun-server --release

By default the server uses the embedded redb backend (no Postgres, no Docker required). It binds to 0.0.0.0:8080 and opens ./topgun.redb in the working directory. Set STORAGE_BACKEND=postgres and DATABASE_URL=... to use Postgres instead.

CLI flags

The topgun-server binary exposes five clap-derived flags (defined in packages/server-rust/src/bin/topgun_server.rs):

FlagTypeDefaultDescription
--node-id <ID>string"topgun-server-node"Unique identifier for this node in a cluster.
--host <ADDR>string"127.0.0.1"Host name or IP that peers use to reach this node’s cluster port. Also used as MemberInfo.host in join handshakes.
--port <PORT>u168080 (or env PORT)Client WebSocket port. 0 means OS-assigned. Also reads the PORT environment variable.
--cluster-port <PORT>u160 (OS-assigned)Inter-node cluster TCP port.
--seed-nodes <LIST>string"" (single-node)Comma-separated seed node addresses (e.g., "127.0.0.1:11001,127.0.0.1:11002"). Empty string means single-node mode.

Every other knob (storage backend, auth, search indexes, admin UI, memory limits) is configured via environment variables, listed in the next section.

Environment variables

All variables below correspond to direct std::env::var(...) reads or tracing-subscriber::EnvFilter::try_from_default_env() consumers in packages/server-rust/src/. Variables not in this table are not read by the binary.

Storage backend

VariableTypeDefaultConsumerNotes
STORAGE_BACKENDstring"redb"bin/topgun_server.rs:109One of redb, postgres, null. null is in-memory ephemeral (test only).
TOPGUN_REDB_PATHpath./topgun.redbbin/topgun_server.rs:115File path for the embedded redb database (used when STORAGE_BACKEND=redb).
DATABASE_URLstringnonebin/topgun_server.rs:126, storage/datastores/postgres.rs:693Postgres connection string (required when STORAGE_BACKEND=postgres).

Networking

VariableTypeDefaultConsumerNotes
PORTu168080bin/topgun_server.rs:168 (via #[arg(env = "PORT")])Client WebSocket port. Equivalent to --port.
TOPGUN_BIND_ADDRstring"0.0.0.0"bin/topgun_server.rs:203Bind address for the client WebSocket listener. Override to 127.0.0.1 for loopback-only deployments.

Authentication

VariableTypeDefaultConsumerNotes
JWT_SECRETstringnonenetwork/module.rs:454HMAC secret for signing/verifying JWTs. Required for production auth.
TOPGUN_NO_AUTHtruthyunsetbin/topgun_server.rs:477If set ("true", "1"), disables auth. Test and demo use only.
INSECURE_FORWARD_AUTH_ERRORStruthyunsetbin/topgun_server.rs:470If set, forwards upstream auth errors verbatim in responses. Insecure; do not enable in production.
TOPGUN_ADMIN_USERNAMEstring"admin"network/handlers/admin.rs:111Username for the admin dashboard login.
TOPGUN_ADMIN_PASSWORDstringrequired if admin enablednetwork/handlers/admin.rs:112Password for the admin dashboard login.
TOPGUN_ADMIN_DIRpathbundled SPA (npm) / ./admin-dashboard/dist (monorepo)network/module.rs:478Static-file directory served at /admin/*. When unset, the npx @topgunbuild/server distribution auto-resolves the admin SPA bundled inside the package (the bin shim injects this path), and the monorepo falls back to ./admin-dashboard/dist. Set an explicit path to override — the shim never overwrites an operator-provided value.

Search indexes

VariableTypeDefaultConsumerNotes
TOPGUN_INDEX_PATHpathnonebin/topgun_server.rs:516, network/module.rs:316, network/handlers/admin.rs:1078Directory holding the BM25 full-text-search index.
TOPGUN_VECTOR_INDEX_PATHpathnonenetwork/module.rs:286, network/handlers/admin.rs:1139Directory holding the HNSW vector index.

Observability

VariableTypeDefaultConsumerNotes
RUST_LOGfilter expression"info"bin/topgun_server.rs:191 (via tracing_subscriber::EnvFilter::try_from_default_env())Standard tracing filter (e.g., topgun_server=debug,info). Not read via std::env::var directly.
TOPGUN_LOG_FORMATstringtextservice/middleware/observability.rs:130Set to "json" for structured JSON logs. Anything else uses the text formatter.

Memory and eviction

VariableTypeDefaultConsumerNotes
TOPGUN_MAX_RAM_MBu641024storage/eviction_config.rs:85RAM ceiling for the in-memory record cache.
TOPGUN_EVICTION_HIGH_PCTu885storage/eviction_config.rs:105High water mark (% of TOPGUN_MAX_RAM_MB). Eviction engages above this threshold.
TOPGUN_EVICTION_LOW_PCTu870storage/eviction_config.rs:125Low water mark. Eviction stops once usage falls below this.
TOPGUN_EVICTION_INTERVAL_MSu641000storage/eviction_config.rs:145Eviction orchestrator tick interval.

Write-behind buffer

VariableTypeDefaultConsumerNotes
TOPGUN_WRITEBEHIND_FLUSH_INTERVAL_MSu641000storage/datastores/write_behind.rs:79How often the write-behind buffer flushes to the durable backend.
TOPGUN_WRITEBEHIND_BATCH_SIZEusize100storage/datastores/write_behind.rs:99Maximum records flushed per write-behind tick.
TOPGUN_WRITEBEHIND_CAPACITYusize10000storage/datastores/write_behind.rs:119Bounded buffer size. Producers apply backpressure when full.

At startup the server emits a single tracing::info! line containing the effective max_ram_mb, water marks, eviction interval, and write_behind_enabled so operators can confirm the active configuration without reading source.

Endpoints

Health and observability

EndpointMethodDescription
/healthGETDetailed health JSON (server state, uptime, connection count).
/health/liveGETKubernetes liveness probe. Returns 200 if the process is alive.
/health/readyGETKubernetes readiness probe. Returns 200 once the server accepts connections.
/metricsGETPrometheus metrics endpoint (text/plain).

Client communication

EndpointMethodDescription
/wsGET (Upgrade)WebSocket upgrade. Primary real-time channel for CRDT sync, queries, and pub/sub. Sends AUTH_REQUIRED on connect when auth is enabled.
/syncPOSTStateless HTTP sync endpoint. Accepts MsgPack-encoded batched operations and returns deltas.

Admin API

EndpointMethodAuthDescription
/api/statusGETnoneServer status (version, uptime, node info).
/api/auth/loginPOSTnoneAdmin login. Returns JWT for admin API access.
/api/admin/cluster/statusGETadmin JWTCluster membership and partition status.
/api/admin/mapsGETadmin JWTList maps with record counts and partition info.
/api/admin/settingsGET, PUTadmin JWTRead or update server configuration at runtime.
/api/docsGETnoneSwagger UI for the admin API (OpenAPI spec at /api/openapi.json).
/admin/*GETnoneSPA admin dashboard. Serves static files from TOPGUN_ADMIN_DIR.

Rust embed API

For callers that want to host the server in-process (custom binaries, integration tests, embedded scenarios), the building blocks live in topgun_server::network.

NetworkConfig

struct NetworkConfig {
    host: String,                    // default "0.0.0.0"
    port: u16,                       // default 0 (OS-assigned)
    tls: Option<TlsConfig>,          // optional TLS (cert_path, key_path, ca_cert_path)
    connection: ConnectionConfig,    // per-connection settings
    cors_origins: Vec<String>,       // default []  (deny all cross-origin); set TOPGUN_CORS_ORIGINS env to a comma-separated list
    request_timeout: Duration,       // default 30s
}

ConnectionConfig fields: outbound_channel_capacity (256), send_timeout (5s), idle_timeout (60s), ws_write_buffer_size (128 KB), ws_max_write_buffer_size (512 KB).

ServerConfig

struct ServerConfig {
    node_id: String,
    default_operation_timeout_ms: u64,    // default 30000
    max_concurrent_operations: u32,       // default 1000
    gc_interval_ms: u64,                  // default 60000
    partition_count: u32,                 // default 271
    security: SecurityConfig,
}

Embedding the server

use topgun_server::network::config::NetworkConfig;
use topgun_server::network::module::NetworkModule;

let config = NetworkConfig {
    host: "0.0.0.0".to_string(),
    port: 8080,
    tls: None,
    ..Default::default()
};

let mut module = NetworkModule::new(config);
let port = module.start().await?;
println!("Server listening on port {port}");
module.serve(shutdown_signal()).await?;

PostgresDataStore

use sqlx::PgPool;
use topgun_server::storage::datastores::PostgresDataStore;

let pool = PgPool::connect("postgres://user:pass@localhost/topgun").await?;
let store = PostgresDataStore::new(pool, None)?;  // default table: "topgun_maps"
store.initialize().await?;                         // CREATE TABLE IF NOT EXISTS

PostgresDataStore::new(pool, table_name) takes an optional custom table name; if supplied, it is validated against ^[a-zA-Z_][a-zA-Z0-9_]*$. Call initialize() after construction to run the schema migration.

POST /sync wire format

The /sync endpoint is the stateless HTTP fallback for environments without WebSocket support (Cloudflare Workers, restrictive proxies, serverless). It accepts a batch of operations and returns deltas in a single request/response cycle.

  • URL: POST /sync
  • Content-Type: application/x-msgpack (binary MsgPack)
  • Response Content-Type: application/msgpack

HttpSyncRequest

interface HttpSyncRequest {
  clientId: string;
  clientHlc: Timestamp;                   // { millis, counter, nodeId }
  operations?: ClientOp[];                // mapName, key, record { value, timestamp }
  syncMaps?: SyncMapEntry[];              // { mapName, lastSyncTimestamp }
  queries?: HttpQueryRequest[];           // { queryId, mapName, filter, limit?, offset? }
  searches?: HttpSearchRequest[];
}

HttpSyncResponse

interface HttpSyncResponse {
  serverHlc: Timestamp;
  ack?: { lastId: string; results?: AckResult[] };
  deltas?: MapDelta[];                    // { mapName, records: DeltaRecord[], serverSyncTimestamp }
  queryResults?: HttpQueryResult[];
  searchResults?: HttpSearchResult[];
  errors?: HttpSyncError[];               // { code, message, context? }
}

Example request (shown as JSON for readability)

{
  "clientId": "client-1",
  "clientHlc": { "millis": 1706000000000, "counter": 0, "nodeId": "client-1" },
  "operations": [
    {
      "mapName": "todos",
      "key": "t1",
      "record": {
        "value": { "text": "Buy milk" },
        "timestamp": { "millis": 1706000000000, "counter": 1, "nodeId": "client-1" }
      }
    }
  ],
  "syncMaps": [
    {
      "mapName": "todos",
      "lastSyncTimestamp": { "millis": 1705999000000, "counter": 0, "nodeId": "" }
    }
  ]
}

Example response

{
  "serverHlc": { "millis": 1706000001000, "counter": 1, "nodeId": "server-1" },
  "ack": {
    "lastId": "http-op-0",
    "results": [{ "opId": "http-op-0", "success": true, "achievedLevel": "MEMORY" }]
  },
  "deltas": [
    {
      "mapName": "todos",
      "records": [
        {
          "key": "t2",
          "record": {
            "value": { "text": "Walk the dog" },
            "timestamp": { "millis": 1706000000500, "counter": 0, "nodeId": "client-2" }
          },
          "eventType": "PUT"
        }
      ],
      "serverSyncTimestamp": { "millis": 1706000001000, "counter": 2, "nodeId": "server-1" }
    }
  ],
  "queryResults": [],
  "searchResults": [],
  "errors": []
}

Graceful shutdown

On SIGTERM or SIGINT the server:

  1. Transitions health state to Draining.
  2. Sends a Close frame to every active WebSocket connection.
  3. Gives in-flight requests up to 30 seconds to complete.
  4. Transitions health state to Stopped.

The write-behind buffer flushes pending writes during the drain window; crash-safe shutdown drain plus WAL recovery is tracked as a follow-up item (see Concepts → Reliability for the current durability model).


← Adapters · MCP →