Files
edr-platform/apps/edr-freight-api/src/modules/gps-tracking
Nathnael da08a9b085 fix(auth): list every route key on the class-level guard
Nest runs class and method guards together, so a class gate naming only
the view key silently required view AND action. Staff granted just an
action were denied before their key was checked. Each class gate now
names every key its routes use, and FleetView accepts an array so the
fleet controllers keep their coarse fallback.

Drops the one-off grant mapping SQL with it: already applied to dev, and
this fix removes the companion-view rule that was its recurring part.
2026-08-07 08:43:20 +00:00
..
2026-07-07 06:39:02 +00:00
2026-07-07 06:39:02 +00:00
gps
2026-07-10 15:23:49 +00:00
gps
2026-07-10 15:23:49 +00:00
2026-07-07 06:39:02 +00:00
2026-07-07 06:39:02 +00:00
2026-07-09 14:01:59 +00:00

GPS Tracking (GT06) — Operations & Device Configuration

GT06 trackers speak a raw TCP binary protocol, not HTTP/HTTPS. This shapes everything about how the service is deployed and how devices are pointed at it.


1. Why GPS needs its own dedicated TCP port

  • Not HTTP. GT06 devices send binary frames (0x78 0x78 | len | protocol | payload | serial | CRC16 | 0x0D 0x0A). An HTTP server receiving these answers 400 Bad Request and closes.
  • Dedicated port required. A listening socket is keyed on (IP, port); two listeners on the same pair collide (EADDRINUSE). The REST API already owns its port, so GPS traffic needs a separate one.
  • No hostname routing. GT06 frames carry no Host header and no TLS SNI, so L7 proxies (Nginx http, AWS ALB, Cloudflare proxy) cannot route them by domain. Routing must happen at Layer 4 (TCP) by port.
  • DNS carries no port. An A record maps a name to an IP only. The tracker config must state the port explicitly (e.g. gps.example.com:5023).

Operational requirements

Item Value
Protocol Raw TCP (not HTTP, not TLS)
Default port 5023 (configurable via GT06_TCP_PORT)
Listener bind 0.0.0.0 inside the freight-gps container
Edge terminator L4 — AWS NLB or Nginx stream {}. Not ALB / Cloudflare proxy.

2. Port configuration

5023 is only this project's default — not a GT06 protocol requirement. The listener binds whatever GT06_TCP_PORT says, as long as trackers are configured with the same number.

Host and container ports are decoupled in docker-compose.yaml:

freight-gps:
  ports:
    - "${GT06_TCP_PORT:-5023}:5023"   # host is configurable; container fixed
  environment:
    GT06_TCP_PORT: "5023"             # pinned inside the container
  • The container always listens on 5023.
  • The host/public port is configurable (443, 5023, 9000, …) via the root .env's GT06_TCP_PORT.
  • This split is required because the image runs as a non-root user (nestjs, uid 1001), which cannot bind ports <1024. Docker (root) binds the host port and forwards to 5023 inside.
  • Running outside Docker (pnpm dev:gps, systemd), GT06_TCP_PORT is the actual bind port, so <1024 needs root or CAP_NET_BIND_SERVICE.
  • 443 is allowed but risky: GT06 stays raw TCP, not TLS. Middleboxes that expect a TLS handshake on 443 may drop the connection.

3. Deployment topology

The GT06 listener runs as its own process (dist/main.gps.js, module GpsIngestModule) — DB + GPS only, no HTTP server. It shares the edr_freight DB with the API; the DB is the seam (ingester writes gps_devices / gps_positions, API reads them).

freight-api    HTTP :3001   GT06_TCP_PORT=0        (listener off, applies migrations)
freight-gps    TCP  :5023   DB_MIGRATIONS_RUN=false (owns the tracker socket)

DB_MIGRATIONS_RUN=false keeps the second process from racing migrations.

Horizontal scale: each tracker holds one long-lived TCP connection with per-socket session state, so N freight-gps replicas can run behind an L4 LB — each device sticks to one replica. ensureDevice is safe under concurrency (unique IMEI).


4. Device configuration (GT06 side)

Config is done by SMS to the tracker's SIM. Commands below are the canonical Concox/GT06 set — verify against your unit's sheet, syntax varies by firmware. Default command password is usually 123456.

Prep: data-enabled SIM, SMS on, SIM PIN off, know your carrier APN.

STATUS#                          # 1. sanity check — returns GSM/GPS/batt/GPRS
APN,<apn>#                       # 2. carrier data APN (add ,user,pass if needed)
SERVER,1,gps.example.com,5023,0# # 3. point at server (1=domain). Port MUST match GT06_TCP_PORT
GPRSON,1#                        # 4. enable data
GPSON,1#                         #    enable GPS
TIMER,10#                        # 5. upload interval, seconds (some use UPLOAD,10#)
RESET#                           # 6. reboot so it reconnects (many cache DNS until reboot)

Raw-IP variant of step 3: SERVER,0,203.0.113.50,5023,0# Custom host port (e.g. 443): SERVER,1,gps.example.com,443,0#

Verify from the server

docker compose logs -f freight-gps | grep -Ei "login|Auto-registering|ingester up"
nc -vz gps.example.com 5023
curl -H "Authorization: Bearer <token>" https://api.example.com/api/gps/positions/latest

First login packet auto-registers the IMEI (no manual step). online:true only when lastSeenAt < 5 min (computed at read time).

Auto-register leaves vehicleId null. Attach it (needs tracking.manage):

PATCH /api/gps/devices/:id   { "vehicleId": "<uuid>", "name": "Truck 03-ET" }

Failure map

Symptom Cause
No SMS reply SIM PIN on / no signal / wrong number
Replies but never connects APN wrong, or SERVER port ≠ GT06_TCP_PORT
Connects then drops server not ACKing, or middlebox on 443 expecting TLS
Registered but online:false packets blocked by firewall — open inbound TCP
Wrong location / positioned:false no GPS fix yet — open sky, cold start ~12 min

5. Security

  • GT06 authenticates with IMEI only, which is spoofable. Anyone who can reach the port can inject fake positions.
  • Do not expose the port to 0.0.0.0/0. Restrict at the firewall / security group to the SIM provider's APN / IP range.
  • Trackers must use the same host+port as the server: SERVER,1,gps.example.com,<port>,0#.

6. Edge (L4) termination

See infrastructure/nginx/gps-stream.conf for an Nginx stream {} example, and the AWS NLB notes in the same file. Reminder: L4 only — an HTTP proxy cannot route GT06.