gateway.v1¶
Device-facing and internal RPCs, plus the JSON WebSocket frames exchanged
between the agent and the realtime Durable Object. Source:
platform/nexus/proto/gateway/v1/.
| File | Contents |
|---|---|
device.proto |
GatewayEnrollmentService, GatewayDeviceService |
internal.proto |
GatewayInternalService |
realtime.proto |
RealtimePushService |
types.proto |
Shared messages: DeviceSummary, StateReportRequest and its parts, HeartbeatRequest, log entries |
Services¶
| Service | Caller → server | Auth |
|---|---|---|
GatewayEnrollmentService |
agent → nexus | none; the bootstrap token in the request is the credential |
GatewayDeviceService |
agent → nexus | Authorization: Bearer <api_token> (DeviceTokenInterceptor) |
GatewayInternalService |
realtime DO (and infrastructure) → nexus | X-Internal-Key (InternalKeyInterceptor, constant-time compare against NEXUS_INTERNAL_API_KEY) |
RealtimePushService |
nexus → realtime Worker | X-Internal-Key |
GatewayEnrollmentService¶
| RPC | Purpose |
|---|---|
Enroll |
First boot. Presents a bootstrap token and hardware facts; nexus creates a holding device row and returns {gateway_id, api_token}. The token is stored hashed. |
GatewayDeviceService¶
The interceptor resolves the bearer token to a device; every RPC acts on that device only, never on an id from the request.
| RPC | Purpose |
|---|---|
GetDesiredState |
Desired config version and desired agent version in one call. The agent reconciles both on connect; pushes only make it reconcile sooner. See OTA. |
GetConfig |
Fetch the desired config. |
ReportConfigStatus |
Outcome of an apply: applied, failed, rolled_back. |
UploadLogs |
Batched log lines (pf, system, daemon, …). |
ReportCommandReply |
The private reply to a command pushed with a reply id (access-code issue/revoke). Travels this channel so it is never mirrored to viewers or logged by the DO. |
WhoAmI |
Resolve the device id from the token. Used after a claim/replace has moved the token onto another device row. |
RequestCertificate |
Send a CSR for the gateway's own names; nexus proves the names over DNS (ACME) and returns the chain. The private key stays on the device. |
RequestRelay |
Ask for a relay assignment (ordered list, tunnel address, peer details). Answered from the authenticated identity. |
GatewayInternalService¶
| RPC | Called when |
|---|---|
ValidateGatewayToken |
Gateway WebSocket upgrade (?token=). Binds the socket to a device id. |
ValidateViewerTicket |
Viewer upgrade (?role=viewer&ticket=). Redeems a ticket from IssueViewerTicket; binds the socket to a user and org. |
ValidateShellTicket |
Operator upgrade (?role=operator&ticket=). Redeems a shell ticket; a viewer ticket never validates here. |
ReportShellSessionEnd |
An operator socket closed; closes the shell_sessions row. |
Heartbeat |
heartbeat frame (every 30 s). Updates devices.last_seen_at, appends a gateway_metrics row. |
StateReport |
state_report frame (every 5 min and on carrier change). Upserts gateway_states, appends a gateway_block_samples row. |
ConfigReport |
config_report frame. Same meaning as ReportConfigStatus, arriving via the DO. |
ReportAgentUpdate |
agent_update frame. Upserts agent_update_status. |
UploadInternalLogs |
log_batch frame. |
DeviceOffline |
The gateway socket closed. Marks the device offline at once. |
RegisterRelayToken |
Infrastructure registers a relay's bootstrap token before the relay first enrols. |
RealtimePushService¶
| RPC | Purpose |
|---|---|
Push |
Deliver an ApplyConfigPayload or a CommandPayload to one device's DO, which writes it onto the gateway socket. PushResponse.status is ok (command acknowledged), sent (written, nothing to acknowledge), no_ack (command not acknowledged within 3 s) or offline (no gateway socket). |
See Nexus → Realtime integration.
WebSocket frames¶
Frames on wss://{realtime}/gateway/{device_id} are JSON, not protobuf. The
TypeScript definitions are platform/realtime/src/messages.ts; the agent's Go
structs are in edge/gateway/internal/connector/messages.go.
Field names are camelCase
uptimeSeconds, not uptime_s; exitCode, not exit_code. The JSON tags
in the agent's structs and messages.ts are authoritative, not the proto
field names.
Agent → DO¶
type GatewayClientMessage =
| { type: "heartbeat"; uptimeSeconds?, loadAverage1m?, memoryFreeBytes?,
memoryTotalBytes?, cpuUsagePercent?,
cpuTemperatureCelsius?, timestamp? }
| { type: "state_report"; /* fields directly on the message, no envelope */ }
| { type: "config_report"; version?, status?, configHash?, error?, appliedAt? }
| { type: "agent_update"; phase?, targetVersion?, fromVersion?, error? }
| { type: "log_batch"; source?, entries?: { timestamp?, line }[] }
| { type: "command_result"; commandId?, exitCode?, stdout?, stderr? }
- Every frame except the shell frames is broadcast verbatim to viewers.
heartbeat,state_report,config_report,agent_updateandlog_batchbecome the matchingGatewayInternalServiceRPC. Thestate_reportmapping isplatform/realtime/src/state-report-mapping.ts; a field it does not name is dropped. See State reporting.command_resultis not forwarded to nexus. Its arrival acknowledges the pendingPushwith the samecommandId; viewers receive it in the broadcast.agent_update.phaseis one ofchecking,downloading,verifying,installing,restarting,confirmed,failed.
DO → Agent¶
type GatewayServerMessage =
| { type: "apply_config"; version: number; configHash: string; config: string /* base64 JSON */ }
| { type: "command"; id: string; commandType: string; payload?: string /* base64 */ }
Produced from the PushRequest oneof in platform/realtime/src/realtime-push.ts.
Shell frames¶
shell_input, shell_resize, shell_output, shell_close (base64 payloads
inside JSON) are relayed between the operator socket and the gateway socket
only. They never reach nexus or viewers. An operator socket that sends any
other frame type is closed.
Command kinds¶
commandType |
Notes |
|---|---|
ping, traceroute |
Diagnostics; payload carries the target. Result as command_result. |
reload, sync |
Acknowledged only. |
confirm_config |
Cancels the rollback timer. The version is in the command id, parsed as an integer. |
set_hostname |
Persists to /etc/myname and applies it. |
reboot, restart_agent |
Asynchronous: acknowledged, then acted on. |
update_agent |
Asynchronous; progress arrives as agent_update frames. |
reenroll |
Enrol against the target environment first; drop the current identity only on success. |
relay_reassess |
Make the relay keeper ask for an assignment again. |
issue_code, revoke_code |
Published-service access codes. The reply goes over ReportCommandReply; the socket only gets a bodiless acknowledgement. |
StateReport payload¶
The messages are in types.proto. What the .proto does not tell you:
- On the wire the JSON is camelCase (see above).
PFStatuscarries block counters (loggingBlockRules,loggingBlockPackets,loggingBlockEvaluations,silentBlockRules,silentBlockPackets), so "nothing was blocked" can be told apart from "blocks happen but their log is not arriving".NetworkInterfacecarries WireGuard and uplink state: public key, listen port, per-peer detail, anduplinkState(carrying/standby/down).- Some fields encode an absence on purpose; see State reporting.
int64fields arrive as strings once the report has been through Connect-JSON. nexus decodes the stored blob with that in mind (portal_state_int64_test.go).