pushgo-gateway is the gateway service for PushGo, with three core capability groups:
- Public API: HTTP endpoints for devices, channels, messages, and events
- Private transport: real-time delivery over QUIC / Raw TCP / WSS / MQTT 5
- MCP gateway: MCP HTTP endpoint, OAuth flow, and channel-binding pages for MCP clients
- Gateway (this repository): https://github.com/AldenClark/pushgo-gateway
- Apple client: https://github.com/AldenClark/pushgo
- Android client: https://github.com/AldenClark/pushgo-android
- Global region
- token-service:
https://token.pushgo.dev/ - gateway:
https://gateway.pushgo.dev/ - Mainland China region
- token-service:
https://token.pushgo.cn/ - gateway:
https://gateway.pushgo.cn/
In production, explicitly set --token-service-url (or PUSHGO_TOKEN_SERVICE_URL) based on region.
Remote token-service URLs require PUSHGO_TOKEN_SERVICE_AUTH_TOKEN. This is a dedicated
service-to-service secret; do not reuse the public Gateway API token (PUSHGO_TOKEN). HTTP is
accepted only for literal loopback development endpoints, and token-service redirects may not
cross origins.
- QUIC: dedicated UDP listener (
--private-quic-bind) - Raw TCP: dedicated TCP listener (
--private-tcp-bind) - WSS: upgraded from HTTP at
/private/wswith subprotocolpushgo-private.v1 - MQTT 5: dedicated TCP listener (
--mqtt-bind) using QoS 1 only
--private-transportsis the master switch for private runtime. It supportstrue/falseand explicit sets likequic,tcp,wss,mqtt.- Private runtime has no implicit fallback: only transports listed in
--private-transportsare enabled. --private-*-bindalways means the local listener address owned by gateway.--private-*-portalways means the port advertised to app clients via/gateway/profile(transporthints).- If
quicis enabled,--private-tls-cert+--private-tls-keyare required. - Raw TCP is plain by default. Set
--private-tcp-tls-enabled=trueonly when gateway should terminate TLS itself. - WSS has no separate bind flag; it rides on
--http-addrand is typically exposed by edge TLS. - MQTT is plain by default. Set
--mqtt-tls-enabled=trueonly when gateway should terminate MQTT/TLS itself. - MQTT accepts only MQTT 5 and QoS 1. CONNECT must include MQTT 5 User Property
device_type=publishordevice_type=subscribe. device_type=publishcreates a temporary publish-only connection and is not persisted as a device route; any CONNECT client id on publish-only connections is ignored.device_type=subscribeis a persistent MQTT device identity; use an existingclient_id=<device_key>or leaveclient_idempty. If the supplied subscribe client id is missing, unknown, or replaced because it belongs to another platform, gateway issues a new device key and returns it in the MQTT 5 CONNACK Assigned Client Identifier; clients must persist that returned value as the nextclient_id.- MQTT does not expose broker-style broker-session persistence: CONNACK advertises
session_expiry_interval=0, no retained messages, no topic aliases, no subscription identifiers, no wildcard/shared subscriptions. PushGo channel subscriptions and application deliveries are persisted by gateway; a QoS 1 downlink remains in the application outbox until PUBACK and is replayed after reconnect. - MQTT topic is the raw
{channel_id}. Channel password is passed as MQTT 5 User Propertypushgo-password; gateway token, when configured, is passed as MQTT username. Each SUBSCRIBE packet may contain only one topic filter. - MQTT payload uses an envelope: publish
{"type":"message","data":{...}}, downlink{"schema":"pushgo.mqtt.delivery.v1","type":"message|event|thing","delivery_id":"...","channel_id":"...","data":{...}}. Topic identifies the channel; payloadtypeidentifies the business model. MQTT ingress currently accepts message publishes, including thing-scoped messages. MQTT downlink is a first-class outlet for message, event, and thing payloads; delivery is persisted before live send and cleared only after PUBACK. - MQTT Will Message is accepted only from
device_type=subscribedevices. Will Topic is raw{channel_id}and may target any channel. Will QoS must be 1, Will Retain must be false, Will Properties must include User Propertypushgo-password, and Will payload uses the same publish envelope. Gateway validates the Will at CONNECT, publishes it on abnormal connection close or MQTT 5DisconnectWithWillMessage, and suppresses it on normal DISCONNECT.
--mcp-enabled=truemounts/mcp,/oauth/*, and/.well-known/*endpoints on the same HTTP listener as the public API.--public-base-urlis recommended for reverse-proxy / container deployments so OAuth issuer URLs, bind URLs, and WSS hints point to the externally reachable HTTPS origin.--mcp-predefined-clientsacceptsclient_id:client_secretentries separated by semicolons or newlines.- If
--public-base-urlis omitted, gateway will bootstrap issuer URLs from the incoming HTTPS origin when possible; fixed public deployments should still set it explicitly.
Main options support both CLI flag and environment variable forms.
Advanced env-only runtime tunables are listed in a separate section below.
| CLI Flag | Env | Default | Required | Description |
|---|---|---|---|---|
--http-addr |
PUSHGO_HTTP_ADDR |
127.0.0.1:6666 |
No | HTTP API / WSS bind address |
--token |
PUSHGO_TOKEN |
None | No | Public API auth token (Authorization: Bearer <token> first; fallback ?token=<token> only when Authorization is absent) |
--sandbox-mode |
PUSHGO_SANDBOX_MODE |
false |
No | Sandbox mode (including APNS sandbox endpoint) |
--token-service-url |
PUSHGO_TOKEN_SERVICE_URL |
http://127.0.0.1:6766 |
No | token-service endpoint (set explicitly for remote deployments) |
| — | PUSHGO_TOKEN_SERVICE_AUTH_TOKEN |
None | For remote service | Env-only dedicated Bearer credential for token-service; never reuse PUSHGO_TOKEN |
--private-transports |
PUSHGO_PRIVATE_TRANSPORTS |
false |
No | Private transport switch (true/false or quic,tcp,wss,mqtt) |
--runtime-profile |
PUSHGO_RUNTIME_PROFILE |
small |
No | Resource/performance profile (small/public); never changes the database driver selected by --db-url |
--observability-log-level |
PUSHGO_OBSERVABILITY_LOG_LEVEL |
warn |
No | Native tracing log level (off/error/warn/info/debug/trace) |
--db-url |
PUSHGO_DB_URL |
None | Yes | Database URL (sqlite://, postgres://, postgresql://, pg://, mysql://) |
--public-base-url |
PUSHGO_PUBLIC_BASE_URL |
None | No | External HTTPS base URL used for MCP/OAuth issuer URLs and advertised WSS URL |
| CLI Flag | Env | Default | Required | Description |
|---|---|---|---|---|
--private-quic-bind |
PUSHGO_PRIVATE_QUIC_BIND |
127.0.0.1:5223 |
No | Local QUIC listener bind address (UDP) |
--private-quic-port |
PUSHGO_PRIVATE_QUIC_PORT |
5223 |
No | QUIC port advertised to app clients |
--private-tcp-bind |
PUSHGO_PRIVATE_TCP_BIND |
127.0.0.1:5223 |
No | Local Raw TCP listener bind address |
--private-tcp-port |
PUSHGO_PRIVATE_TCP_PORT |
5223 |
No | TCP port advertised to app clients |
--mqtt-bind |
PUSHGO_MQTT_BIND |
127.0.0.1:1883 |
No | Local MQTT 5 listener bind address |
--mqtt-port |
PUSHGO_MQTT_PORT |
1883 |
No | MQTT port advertised to app clients |
--mqtt-tls-enabled |
PUSHGO_MQTT_TLS_ENABLED |
false |
No | Terminate MQTT/TLS in gateway instead of accepting plain MQTT |
--mqtt-max-packet-bytes |
PUSHGO_MQTT_MAX_PACKET_BYTES |
32768 |
No | Maximum MQTT packet size accepted by gateway |
| CLI Flag | Env | Default | Required | Description |
|---|---|---|---|---|
--private-tls-cert |
PUSHGO_PRIVATE_TLS_CERT |
None | Conditional | TLS cert PEM required by quic, by tcp when private-tcp-tls-enabled=true, and by mqtt when mqtt-tls-enabled=true |
--private-tls-key |
PUSHGO_PRIVATE_TLS_KEY |
None | Conditional | TLS key PEM required by quic, by tcp when private-tcp-tls-enabled=true, and by mqtt when mqtt-tls-enabled=true |
--private-tcp-tls-enabled |
PUSHGO_PRIVATE_TCP_TLS_ENABLED |
false |
No | Terminate Raw TCP TLS in gateway instead of accepting plain TCP |
--private-tcp-proxy-protocol |
PUSHGO_PRIVATE_TCP_PROXY_PROTOCOL |
false |
No | Expect PROXY protocol v1 on Raw TCP ingress |
Fine-grained performance/resource knobs are internal profile defaults, not public CLI/env parameters.
| Profile | Intended deployment | Key defaults |
|---|---|---|
small |
Tiny/private SQLite deployment | Lower SQLite/cache/queue footprints, 5min maintenance tick, conservative cleanup defaults, provider in-flight caps 32/32/16 |
public |
Large external-DB gateway, primarily PostgreSQL/MySQL | Larger queue/pool limits, 1min maintenance tick, higher fanout budgets, external DB pool max 64/min 4, provider in-flight caps 128/256/128 |
Database driver selection is always based on --db-url; setting --runtime-profile=public with a SQLite URL still uses SQLite, and setting --runtime-profile=small with a PostgreSQL URL still uses PostgreSQL. If omitted, small is used.
| CLI Flag | Env | Default | Required | Description |
|---|---|---|---|---|
--mcp-enabled |
PUSHGO_MCP_ENABLED |
false |
No | Enable MCP HTTP endpoint (/mcp) and related OAuth / bind routes |
--mcp-dcr-enabled |
PUSHGO_MCP_DCR_ENABLED |
true |
No | Enable OAuth Dynamic Client Registration |
--mcp-predefined-clients |
PUSHGO_MCP_PREDEFINED_CLIENTS |
None | No | Predefined OAuth clients as client_id:client_secret joined by ; or newlines |
| Env | Default | Description |
|---|---|---|
PUSHGO_OBSERVABILITY_LOG_LEVEL |
warn |
Optional override for native tracing log level |
RUST_LOG |
None | Optional full EnvFilter directive override (higher priority than profile/level) |
- New writes use
blake3 + saltwith a PHC-like string format:$pushgo-blake3$v=1$<salt_base64url_nopad>$<digest_base64url_nopad>
- Legacy
argon2hashes remain readable. - On successful legacy verification, gateway upgrades that row in-place to the new
blake3format immediately (no offline migration required).
This keeps private deployment CPU cost low while maintaining non-plaintext storage.
Gateway no longer writes audit/statistics tables on the main delivery path. Operational troubleshooting is based on opt-in redacted tracing output.
Deprecated observability tables such as delivery_audit, subscription_audit, device_route_audit, channel_stats_daily, device_stats_daily, gateway_stats_hourly, and ops_stats_hourly are dropped during schema initialization or migration. Functional state such as MCP OAuth/session state is preserved separately.
Gateway now uses one native tracing pipeline for both spans and events.
Default output level is warn; use --observability-log-level (or PUSHGO_OBSERVABILITY_LOG_LEVEL) to raise/lower verbosity, and use RUST_LOG when full EnvFilter routing is needed.
Each trace event contains fixed envelope fields (ts_ms, component, event) and a whitelist of typed fields.
Potentially sensitive identifiers are emitted through redacted fields.
Example:
{"ts_ms":1713750000000,"component":"gateway","event":"dispatch.provider_send_failed","provider":"fcm","status_code":503,"invalid_token":false}Use compile-time symbol/stack support, external profilers, and opt-in tracing on the same timeline. The gateway does not expose private runtime memory or metrics diagnostic HTTP endpoints in the default product surface.
RUSTFLAGS="-C force-frame-pointers=yes" cargo build --profile profilingprofiling profile keeps release optimizations but preserves better debug attribution.
Sample OS process data outside the gateway process, for example /proc/<pid>/smaps_rollup and /proc/<pid>/status on Linux. Enable tracing only when you need a gateway event timeline to correlate with profiler output.
heaptrack:
heaptrack --output heaptrack.gateway.gz \
target/profiling/pushgo-gateway <gateway args...>valgrind massif:
valgrind --tool=massif --time-unit=ms --stacks=yes \
--massif-out-file=massif.out.gateway \
target/profiling/pushgo-gateway <gateway args...>Then correlate profiler hotspots with the same test window in redacted tracing output.
server {
listen 443 ssl http2;
server_name gateway.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Forwarded "for=$remote_addr;proto=$scheme;host=$host";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:6666;
}
}Default plain Raw TCP:
stream {
upstream pushgo_private_tcp_plain {
server 127.0.0.1:5223;
}
server {
listen 5223;
proxy_pass pushgo_private_tcp_plain;
proxy_protocol on;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}Gateway-terminated TLS (--private-tcp-tls-enabled=true):
stream {
upstream pushgo_private_tcp_tls {
server 127.0.0.1:55223;
}
server {
listen 5223;
proxy_pass pushgo_private_tcp_tls;
proxy_protocol on;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}stream {
upstream pushgo_quic_udp {
server 127.0.0.1:5223;
}
server {
listen 5223 udp;
proxy_pass pushgo_quic_udp;
proxy_timeout 600s;
}
}Default plain MQTT:
stream {
upstream pushgo_mqtt_plain {
server 127.0.0.1:1883;
}
server {
listen 8883 ssl;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
proxy_pass pushgo_mqtt_plain;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}MQTT clients must use MQTT 5 and QoS 1. CONNECT must include User Property device_type=publish for temporary publish-only devices, or device_type=subscribe for persistent devices that may SUBSCRIBE and receive messages. Publish-only client ids are ignored and never persisted. Subscribe devices may pass client_id=<device_key> or an empty client_id; when the supplied client id is empty, unknown, or replaced because it belongs to another platform, gateway returns the newly assigned device key in CONNACK Assigned Client Identifier and clients must use that value as the next client id. SUBSCRIBE/PUBLISH use topic {channel_id} and MQTT 5 User Property pushgo-password=<channel password>. MQTT publish payload is an envelope: {"type":"message","data":{...}} for messages, or {"type":"event|thing","action":"create|update|close|archive|delete","data":{...}} for entity actions. Topic/password are the trusted channel identity; payloads do not carry channel_id or password. MQTT downlink payload is {"schema":"pushgo.mqtt.delivery.v1","type":"message|event|thing","delivery_id":"...","channel_id":"...","data":{...}}; downlink is realtime and is not persisted through private outbox for offline MQTT receivers. Each SUBSCRIBE packet may contain only one topic filter. Gateway advertises no MQTT broker session persistence, retained messages, topic aliases, subscription identifiers, wildcard subscriptions, or shared subscriptions; PushGo channel subscriptions are the persisted subscription state. MQTT Will Message is available only to device_type=subscribe; Will Topic is {channel_id} and may target any channel, Will QoS must be 1, Will Retain must be false, Will Properties must include User Property pushgo-password, and Will payload uses the same publish envelope. Gateway publishes the Will on abnormal close or MQTT 5 DisconnectWithWillMessage, but not on normal DISCONNECT. If --mqtt-tls-enabled=false, clients connect with plain MQTT to gateway; if true, clients connect with MQTT/TLS directly to gateway.
PushGo QUIC uses a custom ALPN (pushgo-quic), not HTTP/3.
If the same Nginx instance already serves HTTP/3 on 443/udp, private QUIC cannot share that same UDP socket.
Recommended patterns:
- Use a dedicated UDP port for private QUIC (for example
5223/udp) and keep HTTP/3 on443/udp. - Use a dedicated LB/public IP for private QUIC (you can still expose external
443/udpthere).
PushGo now defaults to loopback-only private listeners (127.0.0.1:5223 for both QUIC and Raw TCP) and separates advertised app ports from local bind ports via /gateway/profile.
Download prebuilt binary (example):
curl -fL -o pushgo-gateway \
https://github.com/<owner>/<repo>/releases/download/<tag>/pushgo-gateway-amd64-musl
chmod +x pushgo-gatewayBuild from source:
cargo build --release -p pushgo-gateway
./target/release/pushgo-gateway --db-url <DB_URL>On Linux, systemd is recommended:
[Unit]
Description=PushGo Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pushgo
Group=pushgo
WorkingDirectory=/opt/pushgo-gateway
ExecStart=/opt/pushgo-gateway/pushgo-gateway \
--http-addr 0.0.0.0:6666 \
--private-transports quic,tcp,wss \
--runtime-profile public \
--private-quic-bind 127.0.0.1:5223 \
--private-quic-port 443 \
--private-tcp-bind 127.0.0.1:5223 \
--private-tcp-port 5223 \
--db-url ${PUSHGO_DB_URL} \
--token-service-url https://token.pushgo.dev
Environment=PUSHGO_DB_URL=postgres://user:pass@127.0.0.1:5432/pushgo
Environment=PUSHGO_PRIVATE_TLS_CERT=/etc/pushgo/certs/fullchain.pem
Environment=PUSHGO_PRIVATE_TLS_KEY=/etc/pushgo/certs/privkey.pem
Environment=PUSHGO_TOKEN=<gateway-bearer-token>
EnvironmentFile=/etc/pushgo/token-service.env
Restart=always
RestartSec=2
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.targetCreate /etc/pushgo/token-service.env with mode 0600 and the single entry
PUSHGO_TOKEN_SERVICE_AUTH_TOKEN=<dedicated-token-service-secret>.
Docker image files:
Dockerfile.gha: release/GitHub Actions image assembly from prebuiltdist/*-gnubinaries.Dockerfile.local: local source build (multi-stage) for developer machines.
Published images (for example ghcr.io/<owner>/pushgo-gateway:latest) are built from Dockerfile.gha.
Build locally from source:
docker build -f Dockerfile.local -t pushgo-gateway:local .On macOS with Apple container:
container system start
container build -f Dockerfile.local -t pushgo-gateway:local .Image ports:
6666/tcp: HTTP API + WSS5223/tcp: Raw TCP5223/udp: QUIC1883/tcpor edge8883/tcp: MQTT 5
MCP/OAuth routes (/mcp, /oauth/*, /.well-known/*) also use 6666/tcp; no extra container port is required.
Example:
docker run -d --name pushgo-gateway \
--env-file /etc/pushgo/token-service.env \
-p 6666:6666 \
-p 5223:5223/tcp \
-p 5223:5223/udp \
-p 1883:1883/tcp \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='postgres://user:pass@db:5432/pushgo' \
-e PUSHGO_TOKEN_SERVICE_URL='https://token.pushgo.dev' \
-e PUSHGO_RUNTIME_PROFILE=public \
-e PUSHGO_PRIVATE_TRANSPORTS=quic,tcp,wss,mqtt \
-e PUSHGO_MCP_ENABLED=true \
-e PUSHGO_PUBLIC_BASE_URL='https://gateway.example.com' \
-e PUSHGO_MCP_PREDEFINED_CLIENTS='chatgpt-prod:replace-me' \
-e PUSHGO_PRIVATE_QUIC_BIND=0.0.0.0:5223 \
-e PUSHGO_PRIVATE_QUIC_PORT=443 \
-e PUSHGO_PRIVATE_TCP_BIND=0.0.0.0:5223 \
-e PUSHGO_PRIVATE_TCP_PORT=5223 \
-e PUSHGO_MQTT_BIND=0.0.0.0:1883 \
-e PUSHGO_MQTT_PORT=1883 \
-e PUSHGO_PRIVATE_TLS_CERT=/certs/fullchain.pem \
-e PUSHGO_PRIVATE_TLS_KEY=/certs/privkey.pem \
-v /etc/pushgo/certs:/certs:ro \
ghcr.io/<owner>/pushgo-gateway:latestThe same image can be run locally with Apple container:
container run -d --name pushgo-gateway \
-p 6666:6666 \
-p 5223:5223/tcp \
-p 5223:5223/udp \
-p 1883:1883/tcp \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='sqlite:///data/pushgo-gateway.sqlite' \
-e PUSHGO_RUNTIME_PROFILE=small \
-e PUSHGO_PRIVATE_TRANSPORTS=wss,mqtt \
-e PUSHGO_MQTT_BIND=0.0.0.0:1883 \
-v pushgo-gateway-data:/data \
pushgo-gateway:localIf you rely on Dynamic Client Registration, you can omit PUSHGO_MCP_PREDEFINED_CLIENTS. For fixed clients, keep PUSHGO_PUBLIC_BASE_URL on the public HTTPS origin exposed by your reverse proxy or LB.
POST /messages/pullis the beta-compatible destructive pull route. Returned rows are removed immediately and clients must not create ACK work for them.POST /v2/messages/pullis non-destructive and returns at most 200 valid items plushas_more. Clients must keep pulling whilehas_more=true; an empty page can still havehas_more=truewhen corrupt/unsupported rows were silently deleted.- The outer
items[].delivery_idis authoritative. Missing or conflicting embedded IDs are corrupt data and are silently deleted by the outer ID. POST /messages/ackremains the legacy single-item contract{device_key, delivery_id}.POST /v2/messages/ackis the separate batch contract{device_key, delivery_ids}with at most 200 unique IDs and returns bothrequested_countandremoved_count.provider_queuedmeans the operation has entered this Gateway process's in-memory Provider worker queue; it is not Provider success.sent/provider_successorpartially_failed/provider_failedis persisted only after the worker receives the actual Provider result.
- Back up the database before the first start after upgrade. Legacy runtime schema versions may trigger a runtime-table hard reset; channel/base data is preserved by migration tests, but runtime queues and deprecated observability rows can be rebuilt or dropped.
- Replace legacy private-channel switches with
PUSHGO_PRIVATE_TRANSPORTS/--private-transports. Usenone,wss,quic,tcp,wss, orquic,tcp,wss,mqttexplicitly. - Replace removed per-queue private tuning environment variables with
PUSHGO_RUNTIME_PROFILE=small|public. The profile controls queue, cache, dispatch, and DB-pool defaults. - Sender clients may provide a globally unique
op_idfor payload-bound idempotent retry. Reusing it for the same operation returns the original delivery; reusing it with a different payload or operation scope returns409. If omitted, the gateway generates one. Save the returnedop_idand use/send_status/{op_id}for sender-facing status. - MQTT deployments must publish
1883/tcpfor plain MQTT or terminate TLS at the edge on8883/tcp. If gateway terminates MQTT/TLS directly, setPUSHGO_MQTT_TLS_ENABLED=trueand providePUSHGO_PRIVATE_TLS_CERT/PUSHGO_PRIVATE_TLS_KEY. - For cross-database upgrade validation, run
scripts/storage_crossdb_parity.sh. The script uses Docker when available and falls back to Apple container whenCONTAINER_CLI=containeror Docker is absent.
- Enable QUIC + Raw TCP together, and keep WSS as a compatibility path for restricted networks.
- Keep local private listeners on loopback and let the edge own public exposure.
- Plan private QUIC and HTTP/3 with separate
443/udpownership to avoid socket conflicts.
pushgo-gateway 是 PushGo 的网关服务,主要包含三类能力:
- 公共 API:设备、频道、消息、事件等 HTTP 接口
- 私有传输层:基于 QUIC / Raw TCP / WSS / MQTT 5 的实时收发
- MCP 网关:面向 MCP 客户端的 MCP HTTP 入口、OAuth 流程与频道绑定页面
- 网关(本仓库):https://github.com/AldenClark/pushgo-gateway
- Apple 客户端:https://github.com/AldenClark/pushgo
- Android 客户端:https://github.com/AldenClark/pushgo-android
- 全球区域
- token-service:
https://token.pushgo.dev/ - gateway:
https://gateway.pushgo.dev/ - 中国大陆区域
- token-service:
https://token.pushgo.cn/ - gateway:
https://gateway.pushgo.cn/
生产环境建议根据部署地域显式设置 --token-service-url(或 PUSHGO_TOKEN_SERVICE_URL)。
远端 token-service 必须配置 PUSHGO_TOKEN_SERVICE_AUTH_TOKEN。它是独立的服务间密钥,禁止复用
Gateway 公共 API token(PUSHGO_TOKEN)。HTTP 只允许字面 loopback 开发地址,token-service
重定向不得跨 origin。
- QUIC:独立 UDP 监听(
--private-quic-bind) - Raw TCP:独立 TCP 监听(
--private-tcp-bind) - WSS:复用 HTTP 入口,通过
/private/ws升级,要求 subprotocol 为pushgo-private.v1 - MQTT 5:独立 TCP 监听(
--mqtt-bind),仅支持 QoS 1
--private-transports是私有传输总开关,支持true/false与显式集合(例如quic,tcp,wss,mqtt)。- 私有传输不做隐式回退:只有
--private-transports显式列出的传输会启用。 --private-*-bind一律表示 gateway 本机监听地址。--private-*-port一律表示通过/gateway/profile(transport提示)对 app 下发的对外端口。- 启用
quic时,必须配置--private-tls-cert+--private-tls-key。 - Raw TCP 默认明文监听。只有需要 gateway 自己终止 TLS 时,才设置
--private-tcp-tls-enabled=true。 - WSS 没有单独 bind 参数,始终复用
--http-addr对应的 HTTP 入口。 - MQTT 默认明文监听。只有需要 gateway 自己终止 MQTT/TLS 时,才设置
--mqtt-tls-enabled=true。 - MQTT 仅接受 MQTT 5 和 QoS 1。CONNECT 必须携带 MQTT 5 User Property
device_type=publish或device_type=subscribe。 device_type=publish是连接级临时发送设备,不注册入库且不能订阅;publish-only 连接即使传入 client id 也会被忽略。device_type=subscribe是持久 MQTT 设备,可使用已有client_id=<device_key>,也可以留空client_id。如果 subscribe 连接传入的 client id 为空、未知,或者因为属于其他 platform 而被替换,gateway 会分配新的 device key,并通过 MQTT 5 CONNACK Assigned Client Identifier 返回;客户端必须保存这个返回值,并在下次连接时作为client_id使用。- MQTT 不提供 broker 风格的 session 持久化:CONNACK 会声明
session_expiry_interval=0,不支持 retained message、topic alias、subscription identifier、通配符订阅或 shared subscription。PushGo 频道订阅由 gateway 持久化,独立于当前 TCP 连接生命周期。 - MQTT topic 直接使用原始
{channel_id}。频道密码通过 MQTT 5 User Propertypushgo-password传递;配置了 gateway token 时,token 通过 MQTT username 传递。每个 SUBSCRIBE packet 只允许包含一个 topic filter。 - MQTT payload 使用 envelope:message publish 为
{"type":"message","data":{...}},event/thing publish 为{"type":"event|thing","action":"create|update|close|archive|delete","data":{...}};下行为{"schema":"pushgo.mqtt.delivery.v1","type":"message|event|thing","delivery_id":"...","channel_id":"...","data":{...}}。Topic 表示频道路由,payloadtype表示业务模型。MQTT 上行和下行都支持message、event、thing;topic/password 是可信通道身份,payload 不携带channel_id或password。MQTT 下行是实时出口,离线 MQTT receiver 不进入 private outbox。 - MQTT 遗嘱消息只允许
device_type=subscribe设备设置。Will Topic 直接使用{channel_id},可发送到任意频道;Will QoS 必须为 1,Will Retain 必须为 false;Will Properties 必须携带 User Propertypushgo-password;Will payload 使用同一套 publish envelope。Gateway 在 CONNECT 阶段校验遗嘱,在异常断开或 MQTT 5DisconnectWithWillMessage时发送,正常 DISCONNECT 不发送。
--mcp-enabled=true后,会在同一个 HTTP 监听器上挂载/mcp、/oauth/*与/.well-known/*。- 容器部署或反向代理部署时,建议显式设置
--public-base-url,让 OAuth issuer、绑定页面 URL、WSS 对外提示都指向真实可访问的 HTTPS 域名。 --mcp-predefined-clients使用client_id:client_secret格式,多个条目之间用分号或换行分隔。- 如果不传
--public-base-url,gateway 会尽量从入站 HTTPS Origin 推导 issuer;固定公网部署仍建议显式配置。
主参数同时支持 CLI 与环境变量两种方式。
仅环境变量可配置的高级运行时参数,见后续“高级环境变量(仅 env)”章节。
| CLI Flag | Env | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
--http-addr |
PUSHGO_HTTP_ADDR |
127.0.0.1:6666 |
否 | HTTP API / WSS 监听地址 |
--token |
PUSHGO_TOKEN |
无 | 否 | 公共 API 鉴权 token(优先 Authorization: Bearer <token>;仅当 Authorization 缺失时回退 ?token=<token>) |
--sandbox-mode |
PUSHGO_SANDBOX_MODE |
false |
否 | 沙盒模式(含 APNS sandbox) |
--token-service-url |
PUSHGO_TOKEN_SERVICE_URL |
http://127.0.0.1:6766 |
否 | token-service 地址(远端部署必须显式设置) |
| — | PUSHGO_TOKEN_SERVICE_AUTH_TOKEN |
无 | 远端服务必填 | 仅环境变量配置的 token-service 专用 Bearer 凭据;禁止复用 PUSHGO_TOKEN |
--private-transports |
PUSHGO_PRIVATE_TRANSPORTS |
false |
否 | 私有传输开关(true/false 或 quic,tcp,wss,mqtt) |
--runtime-profile |
PUSHGO_RUNTIME_PROFILE |
small |
否 | 资源/性能档位(small/public);不会改变 --db-url 选择的数据库驱动 |
--observability-log-level |
PUSHGO_OBSERVABILITY_LOG_LEVEL |
warn |
否 | 原生 tracing 日志级别(off/error/warn/info/debug/trace) |
--db-url |
PUSHGO_DB_URL |
无 | 是 | 数据库 URL(sqlite://、postgres://、postgresql://、pg://、mysql://) |
--public-base-url |
PUSHGO_PUBLIC_BASE_URL |
无 | 否 | MCP/OAuth issuer URL 与 WSS 对外提示使用的外部 HTTPS 基准地址 |
| CLI Flag | Env | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
--private-quic-bind |
PUSHGO_PRIVATE_QUIC_BIND |
127.0.0.1:5223 |
否 | QUIC 本机监听地址(UDP) |
--private-quic-port |
PUSHGO_PRIVATE_QUIC_PORT |
5223 |
否 | 对 app 下发的 QUIC 端口 |
--private-tcp-bind |
PUSHGO_PRIVATE_TCP_BIND |
127.0.0.1:5223 |
否 | Raw TCP 本机监听地址 |
--private-tcp-port |
PUSHGO_PRIVATE_TCP_PORT |
5223 |
否 | 对 app 下发的 TCP 端口 |
--mqtt-bind |
PUSHGO_MQTT_BIND |
127.0.0.1:1883 |
否 | MQTT 5 本机监听地址 |
--mqtt-port |
PUSHGO_MQTT_PORT |
1883 |
否 | 对 app 下发的 MQTT 端口 |
--mqtt-tls-enabled |
PUSHGO_MQTT_TLS_ENABLED |
false |
否 | gateway 终止 MQTT/TLS,而不是接收明文 MQTT |
--mqtt-max-packet-bytes |
PUSHGO_MQTT_MAX_PACKET_BYTES |
32768 |
否 | gateway 接受的最大 MQTT packet 大小 |
| CLI Flag | Env | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
--private-tls-cert |
PUSHGO_PRIVATE_TLS_CERT |
无 | 条件必填 | quic 必需;tcp 在 private-tcp-tls-enabled=true 时必需;mqtt 在 mqtt-tls-enabled=true 时必需 |
--private-tls-key |
PUSHGO_PRIVATE_TLS_KEY |
无 | 条件必填 | quic 必需;tcp 在 private-tcp-tls-enabled=true 时必需;mqtt 在 mqtt-tls-enabled=true 时必需 |
--private-tcp-tls-enabled |
PUSHGO_PRIVATE_TCP_TLS_ENABLED |
false |
否 | gateway 终止 Raw TCP TLS,而不是接收明文 TCP |
--private-tcp-proxy-protocol |
PUSHGO_PRIVATE_TCP_PROXY_PROTOCOL |
false |
否 | Raw TCP 入站是否要求 PROXY protocol v1 |
细粒度性能/资源旋钮现在是内部 profile 默认值,不再作为公共 CLI/env 参数暴露。
| Profile | 适用部署 | 关键默认值 |
|---|---|---|
small |
极小规模私有 SQLite 部署 | 更低 SQLite/cache/队列占用,maintenance 5 分钟 tick,保守清理默认值,provider 并发 32/32/16 |
public |
大规模外部 DB 网关,主要是 PostgreSQL/MySQL | 更大的队列/pool 限制,maintenance 1 分钟 tick,更高 fanout 预算,外部 DB pool max 64/min 4,provider 并发 128/256/128 |
数据库驱动始终由 --db-url 决定;设置 --runtime-profile=public 加 SQLite URL 仍然使用 SQLite,设置 --runtime-profile=small 加 PostgreSQL URL 仍然使用 PostgreSQL。不传时默认使用 small。
| CLI Flag | Env | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
--mcp-enabled |
PUSHGO_MCP_ENABLED |
false |
否 | 开启 MCP HTTP 入口(/mcp)及相关 OAuth / 绑定路由 |
--mcp-dcr-enabled |
PUSHGO_MCP_DCR_ENABLED |
true |
否 | 是否开启 OAuth Dynamic Client Registration |
--mcp-predefined-clients |
PUSHGO_MCP_PREDEFINED_CLIENTS |
无 | 否 | 预置 OAuth 客户端,格式为 client_id:client_secret,用 ; 或换行分隔 |
| Env | 默认值 | 说明 |
|---|---|---|
PUSHGO_OBSERVABILITY_LOG_LEVEL |
warn |
可选覆盖原生 tracing 日志级别 |
RUST_LOG |
无 | 可选覆盖完整 EnvFilter 指令(优先级高于 log level) |
gateway 不再在主投递链路写入审计/统计表。运行排错依赖用户主动开启的脱敏 tracing 输出。
delivery_audit、subscription_audit、device_route_audit、channel_stats_daily、device_stats_daily、gateway_stats_hourly、ops_stats_hourly 等旧观测表会在 schema 初始化或迁移时清理。MCP OAuth/session 等功能状态会独立保留。
gateway 已统一为一条原生 tracing 链路(span + event)。
默认输出级别为 warn;可通过 --observability-log-level(或 PUSHGO_OBSERVABILITY_LOG_LEVEL)调节,若需要完整路由规则可使用 RUST_LOG 覆盖。
每条事件固定包含 ts_ms、component、event,并附带白名单字段。
可能涉及敏感标识的字段会走脱敏输出。
示例:
{"ts_ms":1713750000000,"component":"gateway","event":"dispatch.provider_send_failed","provider":"fcm","status_code":503,"invalid_token":false}server {
listen 443 ssl http2;
server_name gateway.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Forwarded "for=$remote_addr;proto=$scheme;host=$host";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:6666;
}
}默认明文 Raw TCP:
stream {
upstream pushgo_private_tcp_plain {
server 127.0.0.1:5223;
}
server {
listen 5223;
proxy_pass pushgo_private_tcp_plain;
proxy_protocol on;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}网关终止 TLS(--private-tcp-tls-enabled=true):
stream {
upstream pushgo_private_tcp_tls {
server 127.0.0.1:55223;
}
server {
listen 5223;
proxy_pass pushgo_private_tcp_tls;
proxy_protocol on;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}stream {
upstream pushgo_quic_udp {
server 127.0.0.1:5223;
}
server {
listen 5223 udp;
proxy_pass pushgo_quic_udp;
proxy_timeout 600s;
}
}默认明文 MQTT:
stream {
upstream pushgo_mqtt_plain {
server 127.0.0.1:1883;
}
server {
listen 8883 ssl;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
proxy_pass pushgo_mqtt_plain;
proxy_connect_timeout 3s;
proxy_timeout 600s;
}
}MQTT 客户端必须使用 MQTT 5 和 QoS 1。CONNECT 必须携带 User Property device_type=publish 表示临时只发送设备,或 device_type=subscribe 表示可订阅接收的持久设备。Publish-only 连接的 client id 会被忽略且不会持久化。Subscribe 设备可传 client_id=<device_key>,也可传空 client_id;当 client id 为空、未知,或因为属于其他 platform 而被替换时,gateway 会通过 CONNACK Assigned Client Identifier 返回新分配的 device key,客户端必须将其保存为下次连接使用的 client id。SUBSCRIBE/PUBLISH 使用 topic {channel_id},并通过 MQTT 5 User Property pushgo-password=<channel password> 传递频道密码。MQTT publish payload 使用 envelope:{"type":"message","data":{...}} 发送 message;{"type":"event|thing","action":"create|update|close|archive|delete","data":{...}} 发送 event/thing 动作。Topic/password 是可信通道身份,payload 不携带 channel_id 或 password。MQTT 下行 payload 为 {"schema":"pushgo.mqtt.delivery.v1","type":"message|event|thing","delivery_id":"...","channel_id":"...","data":{...}},下行是实时出口,离线 MQTT receiver 不进入 private outbox。每个 SUBSCRIBE packet 只允许包含一个 topic filter。Gateway 不提供 MQTT broker session 持久化、retained message、topic alias、subscription identifier、通配符订阅或 shared subscription;PushGo 频道订阅才是持久订阅状态。MQTT 遗嘱消息只允许 device_type=subscribe 设备设置;Will Topic 为 {channel_id} 且可发送到任意频道,Will QoS 必须为 1,Will Retain 必须为 false,Will Properties 必须携带 User Property pushgo-password,Will payload 使用同一套 publish envelope。Gateway 会在异常断开或 MQTT 5 DisconnectWithWillMessage 时发送遗嘱,正常 DISCONNECT 不发送。--mqtt-tls-enabled=false 时客户端以明文 MQTT 连接 gateway;设置为 true 时客户端直接以 MQTT/TLS 连接 gateway。
PushGo QUIC 使用自定义 ALPN(pushgo-quic),不是 HTTP/3。
如果同一 Nginx 实例已经在 443/udp 提供 HTTP/3,则私有 QUIC 不能复用同一个 UDP socket。
推荐方案:
- 私有 QUIC 使用独立 UDP 端口(例如
5223/udp),HTTP/3 保持在443/udp。 - 为私有 QUIC 配置独立 LB/独立公网 IP(可继续对外暴露
443/udp)。
PushGo 现在默认把私有 QUIC / Raw TCP 都监听在本机回环地址 127.0.0.1:5223,并通过 /gateway/profile 将客户端应使用的对外端口单独下发。
下载预编译二进制(示例):
curl -fL -o pushgo-gateway \
https://github.com/<owner>/<repo>/releases/download/<tag>/pushgo-gateway-amd64-musl
chmod +x pushgo-gateway源码编译:
cargo build --release -p pushgo-gateway
./target/release/pushgo-gateway --db-url <DB_URL>Linux 建议通过 systemd 托管:
[Unit]
Description=PushGo Gateway
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=pushgo
Group=pushgo
WorkingDirectory=/opt/pushgo-gateway
ExecStart=/opt/pushgo-gateway/pushgo-gateway \
--http-addr 0.0.0.0:6666 \
--private-transports quic,tcp,wss \
--runtime-profile public \
--private-quic-bind 127.0.0.1:5223 \
--private-quic-port 443 \
--private-tcp-bind 127.0.0.1:5223 \
--private-tcp-port 5223 \
--db-url ${PUSHGO_DB_URL} \
--token-service-url https://token.pushgo.dev
Environment=PUSHGO_DB_URL=postgres://user:pass@127.0.0.1:5432/pushgo
Environment=PUSHGO_PRIVATE_TLS_CERT=/etc/pushgo/certs/fullchain.pem
Environment=PUSHGO_PRIVATE_TLS_KEY=/etc/pushgo/certs/privkey.pem
Environment=PUSHGO_TOKEN=<gateway-bearer-token>
EnvironmentFile=/etc/pushgo/token-service.env
Restart=always
RestartSec=2
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target创建权限为 0600 的 /etc/pushgo/token-service.env,文件只包含
PUSHGO_TOKEN_SERVICE_AUTH_TOKEN=<dedicated-token-service-secret>。
Docker 镜像文件说明:
Dockerfile.gha:用于 Release/GitHub Actions,基于预编译dist/*-gnu二进制组装镜像。Dockerfile.local:用于本地开发机,直接从源码多阶段构建镜像。
已发布镜像(例如 ghcr.io/<owner>/pushgo-gateway:latest)由 Dockerfile.gha 产出。
本地源码构建示例:
docker build -f Dockerfile.local -t pushgo-gateway:local .macOS 使用 Apple container:
container system start
container build -f Dockerfile.local -t pushgo-gateway:local .镜像默认暴露端口:
6666/tcp:HTTP API + WSS5223/tcp:Raw TCP5223/udp:QUIC1883/tcp或边缘8883/tcp:MQTT 5
MCP/OAuth 路由(/mcp、/oauth/*、/.well-known/*)同样复用 6666/tcp,不需要额外容器端口。
示例:
docker run -d --name pushgo-gateway \
--env-file /etc/pushgo/token-service.env \
-p 6666:6666 \
-p 5223:5223/tcp \
-p 5223:5223/udp \
-p 1883:1883/tcp \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='postgres://user:pass@db:5432/pushgo' \
-e PUSHGO_TOKEN_SERVICE_URL='https://token.pushgo.dev' \
-e PUSHGO_RUNTIME_PROFILE=public \
-e PUSHGO_PRIVATE_TRANSPORTS=quic,tcp,wss,mqtt \
-e PUSHGO_MCP_ENABLED=true \
-e PUSHGO_PUBLIC_BASE_URL='https://gateway.example.com' \
-e PUSHGO_MCP_PREDEFINED_CLIENTS='chatgpt-prod:replace-me' \
-e PUSHGO_PRIVATE_QUIC_BIND=0.0.0.0:5223 \
-e PUSHGO_PRIVATE_QUIC_PORT=443 \
-e PUSHGO_PRIVATE_TCP_BIND=0.0.0.0:5223 \
-e PUSHGO_PRIVATE_TCP_PORT=5223 \
-e PUSHGO_MQTT_BIND=0.0.0.0:1883 \
-e PUSHGO_MQTT_PORT=1883 \
-e PUSHGO_PRIVATE_TLS_CERT=/certs/fullchain.pem \
-e PUSHGO_PRIVATE_TLS_KEY=/certs/privkey.pem \
-v /etc/pushgo/certs:/certs:ro \
ghcr.io/<owner>/pushgo-gateway:latest同一镜像也可以用 Apple container 本地运行:
container run -d --name pushgo-gateway \
-p 6666:6666 \
-p 5223:5223/tcp \
-p 5223:5223/udp \
-p 1883:1883/tcp \
-e PUSHGO_HTTP_ADDR=0.0.0.0:6666 \
-e PUSHGO_DB_URL='sqlite:///data/pushgo-gateway.sqlite' \
-e PUSHGO_RUNTIME_PROFILE=small \
-e PUSHGO_PRIVATE_TRANSPORTS=wss,mqtt \
-e PUSHGO_MQTT_BIND=0.0.0.0:1883 \
-v pushgo-gateway-data:/data \
pushgo-gateway:local如果使用 Dynamic Client Registration,可以不传 PUSHGO_MCP_PREDEFINED_CLIENTS。如果是固定客户端,建议把 PUSHGO_PUBLIC_BASE_URL 设为反向代理或 LB 对外暴露的 HTTPS 域名。
POST /messages/pull是兼容 beta 客户端的破坏性 Pull;返回即删除,客户端不得为其创建 ACK 任务。POST /v2/messages/pull是非破坏性 Pull,每页最多返回 200 条有效数据和has_more。客户端必须在has_more=true时继续拉取;当损坏/不支持数据被静默删除时,空页也可能返回has_more=true。items[].delivery_id外层字段是唯一权威 ID。缺失或冲突的内层 ID 视为损坏数据,只按外层 ID 静默删除。POST /messages/ack保持 legacy 单条合同{device_key, delivery_id};独立的POST /v2/messages/ack才接受{device_key, delivery_ids},最多 200 个去重 ID,并同时返回requested_count与removed_count。provider_queued只表示操作已进入当前 Gateway 进程的 Provider worker 内存队列,不代表 Provider 成功;worker 获得真实结果后才持久化sent/provider_success或partially_failed/provider_failed。
- 首次启动新版本前先备份数据库。旧 runtime schema 可能触发 runtime 表 hard reset;迁移测试覆盖了频道等基础数据保留,但 runtime 队列和废弃观测表可能被重建或清理。
- 将旧私有通道开关替换为
PUSHGO_PRIVATE_TRANSPORTS/--private-transports,显式使用none、wss、quic,tcp,wss或quic,tcp,wss,mqtt。 - 移除旧的私有队列调参环境变量,改用
PUSHGO_RUNTIME_PROFILE=small|public。队列、缓存、dispatch 和 DB pool 默认值由 profile 统一控制。 - 发送端可以提供全局唯一的
op_id用于与 payload 绑定的幂等重试;同一操作重试会复用原投递,不同 payload 或不同操作范围复用会返回409。省略时由 gateway 生成。发送端应保存响应里的op_id,并通过/send_status/{op_id}查询发送状态。 - MQTT 部署需要发布
1883/tcp明文端口,或在边缘层终止8883/tcpTLS。如果由 gateway 直接终止 MQTT/TLS,需要设置PUSHGO_MQTT_TLS_ENABLED=true并提供PUSHGO_PRIVATE_TLS_CERT/PUSHGO_PRIVATE_TLS_KEY。 - 跨库升级验证可运行
scripts/storage_crossdb_parity.sh。脚本优先使用 Docker;没有 Docker 时可使用 Apple container,也可以显式设置CONTAINER_CLI=container。
- 建议同时启用 QUIC + Raw TCP,并保留 WSS 作为受限网络下的兼容路径。
- 建议本机私有监听保持 loopback,仅由边缘层对外暴露。
- 私有 QUIC 与 HTTP/3 请分离
443/udp归属,避免端口冲突。