A-NET SHARED GAME LOBBY 1.0

DNS: chat.a-net.online must reach the existing nginx server. Use the same
public address as scores.a-net.online when nginx is on that host.
Public HTTPS port 443; chat backend 127.0.0.1:3021; scores stay on their port.
The backend is a separate Python stdlib service. Games require no Python.

1. Identify the account running your existing score Hub (inspect its systemd
   service with systemctl cat). Run chat as the SAME service account. This
   lets chat read existing private 0600 enrollment files after atomic updates
   without making credentials public or changing the score Hub.

2. Install files:
   sudo install -d -m 755 /opt/anet-chat /etc/anet-chat
   sudo install -m 644 chat_hub.py /opt/anet-chat/chat_hub.py
   sudo install -m 600 chat-hub.example.json /etc/anet-chat/chat-hub.json
   Set ownership of chat-hub.json to the score Hub's service account.

3. Edit /etc/anet-chat/chat-hub.json: score_config must point to the ACTUAL
   config used by the running score service. Do not replace that config or
   its peers. Chat reads enrolled-peers.json live. Relative enrollment paths
   resolve beside score_config; set an absolute enrollment_database in the
   score config if your score service uses a different working directory.
   Chat never writes the score config or enrollment file.

4. Edit User/Group in anet-chat.service to that existing service account.
   StateDirectory creates /var/lib/anet-chat owned by it automatically.
   sudo install -m 644 anet-chat.service /etc/systemd/system/anet-chat.service
   sudo systemctl daemon-reload
   sudo systemctl enable --now anet-chat
   curl --fail http://127.0.0.1:3021/health
   sudo journalctl -u anet-chat -n 30 --no-pager

5. Install nginx-chat.conf in /etc/nginx/conf.d/anet-chat.conf. The included
   rate-limit zone belongs in http{}, as is normal for conf.d/*.conf.
   Check that the zone name does not already exist. Run:
   sudo nginx -t
   sudo systemctl reload nginx
   sudo certbot --nginx -d chat.a-net.online
   Verify HTTPS: curl --fail https://chat.a-net.online/health
   Restrict /v1/chat to HTTPS after certificate issuance (redirect HTTP
   to HTTPS or remove the HTTP API location). Game clients use HTTPS only.
   Do not expose port 3021 publicly.

6. Enable chat in both games. For the SAME BBS, reuse the original
   PUMPKO hub/sync.json credential file when ANetARCADE asks for it.
   Automatic enrollment is only for NEW BBS identities; existing names
   never receive a replacement secret. Sharing scores may remain disabled.
   PUMPKO --chat-config / ANetARCADE --chat-config can toggle chat later.

Protocol: POST /v1/chat, HMAC-SHA256 X-A-Net-Signature over the exact JSON
body, timestamp within 60 seconds, random session ID and monotonic sequence.
Polls report playing/chat presence and retrieve messages after a cursor.
Chat polls every 2 seconds; gameplay presence every 10 seconds; disconnected
sessions expire after 45 seconds. Counts represent chat-enabled participants,
not all callers on a BBS. Same BBS+caller is counted once across games.
Queued messages are retried with a stable event ID to avoid duplicates.
The chat room accepts plain printable ASCII; terminal escape/control sequences
and OpenDoors backtick markup are rejected. Pipe colors are stripped from
handles. /quit or Esc returns to the game; /who refreshes displayed counts.
Messages expire after 24 hours by default. Rate limits and session caps apply.

Moderation: set chat_disabled:true on a peer in the score config or enrollment
store to deny chat for that BBS. Enrolled peers take precedence over config
peers. Keep a backup, edit atomically, and avoid racing automatic enrollment.
No web chat client, private messages, or IRC/MRC bridge is included.
