6.6 KiB
Self-service
Self-service lets a hotspot owner log in to the dashboard, edit their device options (static TG lists, default reflector, timer, language, etc.), and have those options pushed to the ADN DMR Peer Server without manually editing YAML on the server.
It involves four pieces: MySQL (Clients table), monitor API (FastAPI session + REST), React (/self-service page), and the integrated hotspot proxy in adn-server (periodic RPTO to the master). The peer server is the component that ultimately applies RPTO / OPTIONS to the running bridge and hotspot behaviour.
Prerequisites
SELF_SERVICEblock inadn-monitor.yamlwith valid MySQL credentials and PBKDF2 parameters matching your password tooling (same salt/iterations ashotspot_proxy_self_service.pywhen used).DASHBOARD.SELF_SERVICE: trueso the UI shows the Self-service menu entry (andmonitor.pyexposes/api/self-service/*when DB connects).Clientstable populated with rows:callsign,int_id(DMR ID),psswd(PBKDF2-SHA256 hex),options(semicolon-separatedKEY=value),logged_in,host,modified, etc. (see adn-monitor DB schema / migration scripts in the repo).- Hotspot traffic should pass through the proxy if you rely on
modifiedand RPTO push (see flow below).
Authentication
| Endpoint | Purpose |
|---|---|
POST /api/auth/login |
Body: callsign, password. Verifies PBKDF2 hash against Clients.psswd for rows with logged_in = 1. On success: session cookie with user_id, int_ids (all DMR IDs for that callsign). |
GET /api/auth/login-by-ip |
Optional: single user match for Clients.host = client IP (same session shape). |
POST /api/auth/logout |
Clears session. |
GET /api/auth/me |
Returns { callsign, int_ids, selected_int_id } for the React app. |
Session lifetime is extended on activity (SelfServiceController uses a long inactivity timeout).
Device API (after login)
| Method | Path | Purpose |
|---|---|---|
| GET | /api/self-service/device?int_id= |
Load Clients row for that int_id (must be in session int_ids). Returns JSON: int_id, callsign, mode, options parsed into TS1/TS2 lists, DIAL, VOICE, LANG, SINGLE, TIMER. |
| POST | /api/self-service/device/options |
Body: int_id, options string (Homebrew OPTIONS line). Must end with ;. Max length 4096. Updates DB: Clients.options, sets modified = 1. |
| GET | /api/self-service/device/modified?int_id= |
Returns **`{ modified: 0 |
| POST | /api/self-service/device/select |
Body: int_id — set session selected_int_id when the user has multiple devices. |
End-to-end flow (why options reach the hotspot)
sequenceDiagram
participant UI as React /self-service
participant API as monitor FastAPI
participant DB as MySQL Clients
participant SRV as adn-server PROXY
participant HS as Hotspot
UI->>API: POST /api/self-service/device/options
API->>DB: UPDATE options, modified = 1
Note over SRV: send_opts loop ~every 10 s
SRV->>DB: read rows with modified = 1
SRV->>SRV: RPTO on MASTER leg
SRV->>HS: OPTIONS via HBP path
SRV->>DB: clear modified
- User saves options in the web UI → monitor API writes
Clients.optionsandmodified = 1. - The hotspot proxy runs
send_optson a loop (~every 10 s). For rows withmodified = 1, it reads options from the DB, sends RPTO (options) to the peer server at(MASTER, assigned_dest_port), then clearsmodifiedin the DB. - The ADN DMR Peer Server receives RPTO on the MASTER leg and updates its OPTIONS / bridge state (same path as a normal hotspot registration refresh).
- The server pushes the appropriate signalling to the hotspot so static TG / reflector / timer settings take effect without a full hotspot restart (exact behaviour matches the HBP OPTIONS flow in the server).
Important: the proxy sends RPTO to the master only, not to the hotspot directly. If the proxy is not in the path, you must ensure another mechanism applies OPTIONS or you run the server without this proxy path.
Prerequisite: the hotspot must send
PASS=in its OPTIONS line for password login and bidirectional DB sync. If the hotspot sends explicit content (TGs, SINGLE, etc.) withoutPASS=, the server takes options directly from that line and the DB row is ignored. If the hotspot sends no RPTO at all (timer expires) or an empty OPTIONS, the server falls back to the database. See Hotspot proxy — OPTIONS line behaviour.
logged_in reconciliation
The logged_in flag gates both password and IP-based login: only
rows with logged_in = 1 can authenticate on the dashboard. The peer
server keeps this flag accurate by reconciling it against actually connected
peers every 120 s (the lst_seen loop):
- Peers currently connected to the inject-only MASTER get
logged_in = 1. - All other rows get
logged_in = 0. - The loop starts with
now=True, so the first tick at boot clears every stale flag immediately — after a server restart, hotspots that did not reconnect cannot authenticate via login-by-IP.
This replaced the legacy hourly clean_tbl (24 h idle sweep), which left
logged_in = 1 for peers no longer connected after a restart.
Password hashing
AuthenticateUser uses:
hash_pbkdf2('sha256', password, PBKDF2_SALT, PBKDF2_ITERATIONS)
stored as hex in Clients.psswd. The same parameters must be used wherever passwords are registered (e.g. hotspot_proxy_self_service.py in the adn-dmr-server / tooling repo).
UI
- Route
/self-servicein the React app (SelfService.tsx): loads/api/auth/me, then device details forselected_int_id, edit options string, save. - External SelfCare link (e.g.
selfcare.adn.systems) may appear in the nav as a separate product — not the same as this local self-service.
See also
- Documentation home
- Configuration —
SELF_SERVICE,DASHBOARD,PROXY - Architecture — proxy + report path
- Hotspot proxy details: adn-monitor
proxy/README.md(RPTO timing table)