Summary
A device's remote_addr (the agent's public IP, from the X-Real-IP header) is set at authentication time but never persisted by the PostgreSQL store, so it always reads back as an empty string. The web UI device details, admin device details, and container details pages therefore never show the agent's remote address. This is a regression from the MongoDB store, which did persist it.
Root cause
AuthDevice sets device.RemoteAddr = req.RealIP (api/services/auth.go), but the PG layer has nowhere to store it:
- The bun
Device entity (api/store/pg/entity/device.go) has no remote_addr field.
DeviceFromModel (write path) never reads model.RemoteAddr, so the value is dropped before the INSERT.
DeviceToModel (read path) hardcodes RemoteAddr: "", so every read returns empty.
- No migration adds a
remote_addr column to the devices table.
The geolocation derived from the same X-Real-IP (latitude/longitude) does persist; only the address string is lost.
Regression
remote_addr was functional under MongoDB: the model carried a bson:"remote_addr" tag and the mongo store marshalled the full document, so reads returned it. It broke with the PostgreSQL store:
- Introduced by the initial PG store (
d29d4444b, 2026-02-02). The first version of the entity already lacked the field and hardcoded RemoteAddr: "".
- Cemented when the MongoDB store was removed (
bd56fb1d4, 2026-06-05), leaving PG as the sole store.
Impact
Display only. No security logic depends on it: SSH source IP, firewall rules, rate limiting, and session records use their own connection or X-Real-IP values.
openapi/spec/components/schemas/device.yaml advertises remote_addr but the API never populates it.
- Web UI device details (
ui/apps/console/src/pages/DeviceDetails.tsx) shows blank.
- Admin device details (
ui/apps/console/src/pages/admin/devices/AdminDeviceDetails.tsx) shows the placeholder.
- Container details (
ui/apps/console/src/pages/ContainerDetails.tsx); containers reuse the device model, so they break the same way.
Existing tests do not catch it because they assert on the service-layer model object and never round-trip through the real store.
Reproduction
- Connect an agent so it authenticates via
POST /api/devices/auth (the gateway forwards the public IP as X-Real-IP).
- Accept the device, then
GET /api/devices/{uid} (or open the device in the web UI).
- Observed:
remote_addr is "". Expected: the agent's public IP, as under MongoDB.
Suggested fix
Add a remote_addr column to the devices table (migration), add a RemoteAddr string field with a bun:"remote_addr" tag to the entity, assign it in DeviceFromModel, and return entity.RemoteAddr in DeviceToModel instead of the hardcoded "". Also set it on reconnect in AuthDevice so the stored address reflects the current connection, not only the first one.
Summary
A device's
remote_addr(the agent's public IP, from theX-Real-IPheader) is set at authentication time but never persisted by the PostgreSQL store, so it always reads back as an empty string. The web UI device details, admin device details, and container details pages therefore never show the agent's remote address. This is a regression from the MongoDB store, which did persist it.Root cause
AuthDevicesetsdevice.RemoteAddr = req.RealIP(api/services/auth.go), but the PG layer has nowhere to store it:Deviceentity (api/store/pg/entity/device.go) has noremote_addrfield.DeviceFromModel(write path) never readsmodel.RemoteAddr, so the value is dropped before the INSERT.DeviceToModel(read path) hardcodesRemoteAddr: "", so every read returns empty.remote_addrcolumn to thedevicestable.The geolocation derived from the same
X-Real-IP(latitude/longitude) does persist; only the address string is lost.Regression
remote_addrwas functional under MongoDB: the model carried abson:"remote_addr"tag and the mongo store marshalled the full document, so reads returned it. It broke with the PostgreSQL store:d29d4444b, 2026-02-02). The first version of the entity already lacked the field and hardcodedRemoteAddr: "".bd56fb1d4, 2026-06-05), leaving PG as the sole store.Impact
Display only. No security logic depends on it: SSH source IP, firewall rules, rate limiting, and session records use their own connection or
X-Real-IPvalues.openapi/spec/components/schemas/device.yamladvertisesremote_addrbut the API never populates it.ui/apps/console/src/pages/DeviceDetails.tsx) shows blank.ui/apps/console/src/pages/admin/devices/AdminDeviceDetails.tsx) shows the placeholder.ui/apps/console/src/pages/ContainerDetails.tsx); containers reuse the device model, so they break the same way.Existing tests do not catch it because they assert on the service-layer model object and never round-trip through the real store.
Reproduction
POST /api/devices/auth(the gateway forwards the public IP asX-Real-IP).GET /api/devices/{uid}(or open the device in the web UI).remote_addris"". Expected: the agent's public IP, as under MongoDB.Suggested fix
Add a
remote_addrcolumn to thedevicestable (migration), add aRemoteAddr stringfield with abun:"remote_addr"tag to the entity, assign it inDeviceFromModel, and returnentity.RemoteAddrinDeviceToModelinstead of the hardcoded"". Also set it on reconnect inAuthDeviceso the stored address reflects the current connection, not only the first one.