VivaHX
[ General News | Software | Archive | Discord ]
hxd-ng
[ Servers ] | 3 releases | Rust

A next-generation Hotline server.

A Hotline server, written from scratch in Rust, that serves the Hotline clients of 1997 and the phones of today from one shared state — and a new protocol, Hotline-ng, for the clients that want more than 1997 offered.

Hotline was a Mac-first chat, file-sharing and bulletin-board system from the late 1990s: you ran a server, people connected to it directly, and a tracker told them where servers were. Nothing about it was centralized, which is why it still exists. A small community still runs servers, writes clients, and extends the protocol; hlwiki.com keeps the list.

From the project's README.

Source code

( All servers | Edit this page | Edit on GitHub )
What's new: hxd-ng 0.3.0
Released 2026-11-06
Upgrading
  • No schema migration and no config change.
  • On the ng wire, the msg reply to a message that went as an instant message carries im: true and the message's guid, the one it was sent with or the server's own, so a client names it by guid for replies and reactions rather than as a stored message. A client that ignores unknown fields sees no other change.
Fixed
  • A private message waiting for its recipient, retried by login under its guid once they were back, never reaching their attached session.

  • A retry by login of a private message read and pruned since answered as an instant message.

Image: ghcr.io/mishan/hxd-ng:0.3.0

( Release page )
News about hxd-ng
3 posts
( All news )
hxd-ng 0.2.0
Released 2026-11-06
Upgrading
  • The main SQLite store migrates from schema 29 to 36 on first start: portable friendships, a private message's reference and per-mailbox cursor, replies, reactions on chat lines and on stored private messages, and stubs of read messages. Forward-only, as before: take a backup first (docs/operating.md, "Backups"). The registrar's database is unchanged.

  • A new secret, messaging-requests.key beside [inbox] db ([messaging] request_secret), is made owner-only on first start wherever [messaging] and [inbox] are configured, and one that cannot be read refuses the start. Back it up with the other keys. Each address's count of requests sent today starts over once.

  • [messaging] max_message_bytes is now at most 65505, and twice request_messages messages of that size must fit a page of the classic wire's Get Message Requests; a config past either is refused at start. The defaults are well inside both.

  • Get User Info shows a user's login and address only to that user and to a session holding disconnect_users; everyone else, the bootstrap guest included, reads - in their place.

  • A private message to a login now reaches every attached session of the account but the sender's, rather than one of them.

  • To an account taking part in messaging, a guest's message by login is now a message request, or refused where the account takes none.

  • On the ng wire, a stored private message is named by ref, and id is now its place in the reader's own mailbox; the msg reply carries ref in place of id. An id a client kept from 0.1.0 is not a cursor, so a client should read inbox afresh; a cursor its mailbox never issued is refused bad_request by inbox, msg_read and report.

  • Get Chat History sends a line in the session's own line endings, its nick cut to the user list's length.

  • Account and roles files and a self-signed TLS certificate are written owner-only, and an account file loosened by hand is made owner-only again by its next edit. A key or secret readable by group or others is still used, with a warning naming it.

  • A read message pruned inside its sender's [inbox] retain_unread window leaves a stub until the window ends; raising retain_unread can show a sender which of its messages already past the old window were read (docs/operating.md, [inbox]).

  • The Docker image's latest tag follows main alone; a release tag publishes X.Y.Z and X.Y.

Known limitations

Built since 0.1.0: instant messaging on the classic wire, and portable friendships on both wires.

  • Signed and sealed messages on the classic wire (capability bit 13 is never confirmed).
  • Replies on signed lines and messages.
  • hx-ng reacts to public lines only: no client reacts to instant or stored private messages yet.
Legacy Hotline wire
  • Structured Chat, capability bit 17: each chat line also sent as its Chat History entry, to a client that asks ([structured_chat], HXD_STRUCTURED_CHAT, docs/chat-history.md section 12).
  • Classic FILP uploads, and their resumes, from a session that negotiated Large Files, as GtkHx sends them; a refused HTXF transfer is logged at INFO with its peer and reason.
Hotline-ng wire
  • Replies: re on chat and msg, naming a line, a stored private message, an instant message or a signed line, relayed as sent to sessions that declare replies; history by ids (docs/hotline-ng.md section 7.3).
  • Reactions: an account's emoji on lobby and room lines, instant messages and stored private messages (react), counted on history and inbox, told live as reactions_changed batched per room, and read again with reactions, naming who reacted by nick; off by default ([reactions], needs [history]; HXD_REACTIONS).
  • An instant message read as mail carries im: true and its guid.
Identity and registrar
  • Device proof on the classic wire: Get Device Challenge and Prove Device, capability bit 14, for a classic client holding its own device key ([identity] classic_hosts, classic_proof; HXD_IDENTITY_CLASSIC_HOSTS, HXD_IDENTITY_CLASSIC_PROOF; docs/proposals/portable-friends.md section 3).
Instant messaging
  • fogWraith's Instant Messaging extension and the message requests draft on the classic wire, capability bits 6 and 8, for accounts taking part, sharing friends, presence, IMs and requests with ng (docs/instant-messaging.md section 7).
  • Portable friendships: a friendship signed by both parties' devices, presented on another server and in place there with no new request, and ended everywhere by a signed withdrawal; on ng (friendships) and on the classic wire (capability bit 15), from a proved device (docs/proposals/portable-friends.md, docs/hotline-ng.md section 7.5).
  • Requests from guests: the plain guests as one group, an identity guest by its fingerprint to sessions that can name it; an approved identity guest's messages wait for an absent recipient only with [messaging] queue_identity_guests.
  • Listing message requests renews an account's opt-in, and Set Presence without a state leaves presence, and Invisible, as it was.
Moderation and bans
  • A moderator's clear of a line's reactions, recorded with its reason.
  • A purge (purge, a kick with purge, hxd purge) takes the person's reactions in its window, on lines and stored private messages; hxd inbox purge takes and counts an account's.
  • The moderation log shows an act kind it does not know as unknown (N) rather than failing.
Operations
  • Keys, secrets, account files and the self-signed certificate are written whole through a temporary file beside them, so a crash never leaves a partial key that refuses the next start; hlid's keys too.
  • HXD_SYSTEM_ICON for the system account's icon in the Docker image.
Fixed
  • Chat history's hourly retention failing with "database is locked" beside the inbox, and pruning nothing.
  • A session left on the roster, and online to friends, after its connection task panicked; a link or dialer that panics is unregistered and dialed again.
  • Status text set while Invisible reaching friends once the account's last session left.
  • A released message request counted twice toward the sender's daily quota.
  • A message to a session that had just logged in arriving marked queued.
  • A retry of a read and pruned private message storing a second copy, which told its sender it had been read.

Image: ghcr.io/mishan/hxd-ng:0.2.0

( Release page )
hxd-ng 0.1.0
Released 2026-11-06

The first release.

Upgrading
  • From this release on, the SQLite databases (the main store and a registrar's) migrate their schema forward on start and cannot be downgraded: an older hxd refuses a database a newer one has migrated, so going back means restoring the backup taken before the upgrade (docs/operating.md, "Backups").
Known limitations

Designed but not built yet:

  • Vouching (docs/identity-vouch.md).
  • File attachments on chat lines and private messages (docs/attachments.md).
  • Importing mhxd's and Mobius's news.
  • Secure rooms, and rooms across links.
  • Bans placed from the config, and the ban list on the ng wire.
  • A registrar's standing and probation, and its key backup.
  • The delivered/read split for private messages: delivery to a classic client still marks a message read.
  • Instant messaging on the classic wire (the extension's 800-block for period clients), and portable friendships (docs/proposals/portable-friends.md).
  • Legacy text encodings other than Mac Roman, such as Shift-JIS or Latin-1.
  • The real-client exit test for rooms (period 1.2, 1.5 and 1.9 clients) has not been run.
Legacy Hotline wire
  • Hotline 1.2 and 1.5 clients (and 1.9 where its behavior is known) on TCP :5500: login, the agreement, user list, nick and icon changes, Get User Info, keepalive, with mhxd's observable behavior.
  • Public chat, /me, the chat subject, private chats and their invites, private messages, broadcast.
  • The access bitmap enforced on every request; the 1.5 user editor for accounts.
  • Text-Encoding: UTF-8 for a client that negotiates it, Mac Roman with ? for the rest.
  • HOPE secure login with ChaCha20-Poly1305 or Blowfish transport on the plain port ([hope]).
  • TLS on a port of its own with a TLS HTXF port beside it ([tls]); a CA's certificate or a self-signed one, reloaded on SIGHUP.
  • Tracker registration to HTRK v1 and v3 trackers ([tracker]).
  • The system account ([system]): /help, /msg, /report, /block, /rooms, /join, /role and the rest, for clients that only have private messages.
Hotline-ng wire
  • A JSON protocol over WebSocket on :5700 ([ng]), meant to sit behind a TLS-terminating proxy; spec in docs/hotline-ng.md.
  • Sessions that detach when the connection drops and resume with every event replayed by seq, or a resync.
  • Discovery, transport tokens, and, with [identity], the TRTP tunnel (/trtp, /htxf) that carries the classic wire over the ng port.
  • Trusted proxies and the client-address header they write ([ng] trusted_proxies).
  • A weighted request limit per session, answered rate_limited with retry_after.
  • The ng port's own HTTP limits: connections per address and in all, counted from accept, and rates on challenges and avatar fetches.
Identity and registrar
  • Portable identity: Ed25519 identity keys, device certificates, cards and login proofs bound to the server's host ([identity]).
  • Accounts linked to an identity, or made for one on first login.
  • Device enrollment through a mailbox and a code (/identity/enroll).
  • Server key handover to a pre-committed successor (hxd identity successor, hxd identity handover).
  • Revocation by hand (hxd identity revoke), and a registrar's revocations, rotations and freezes applied from the registrars the server trusts.
  • Identity policy: new_accounts = "create" opt-in (default guest), newcomer_delay, allow_list, banned_registrars.
  • An identity registrar on the ng port ([registrar]): handles, attestations, published records, and hxd registrar for freeze, revoke, recover, invites and inspect.
  • hlid: a whole identity in one command, certificates, enroll and agent, auth, register, revoke, rotate, and tunnel for a classic client.
  • Signed public lines, private messages and news articles on the ng wire; private messages sealed to a device ([sealed]).
Accounts and limits
  • Accounts as TOML files, with a guest made on first run and hxd account for editing; the ng accounts requests.
  • Connection caps and rates per address and per account, mhxd's chat-flood and spam-point limits, failed-login throttling ([limits]), and a bound on logins in progress ([server] logins_in_flight).
  • A client that will not drain is dropped as a slow consumer, its queues bounded per connection and by [server] queue_budget_mb in all.
Chat, rooms and roles
  • Chat history: server-held scrollback, fogWraith's Get Chat History on the classic wire and history on ng ([history]).
  • Curated, persistent rooms from a rooms file, gated by read and post lists ([rooms], hxd room); period clients join by /join.
  • Rooms that require signed lines (hxd room create --signed), read-only to classic clients.
  • Roles: named grants accounts hold, resolved to the access every wire sees ([roles], hxd role, /role).
  • Colored Nicknames on the classic wire, shown on ng.
Private messages and inbox
  • An offline inbox: a message to an account that is away waits and is delivered at login, on both wires ([inbox]).
  • Message guids for safe retry, read state, blocks, retention and a per-sender daily quota.
  • hxd inbox purge for a deleted account's mail, subscriptions and push devices.
Instant messaging
  • fogWraith's Instant Messaging extension on the ng wire, off by default ([messaging], HXD_MESSAGING), for accounts holding [access] messaging: friends, presence with Invisible, IMs through the offline inbox with delivered and read receipts and typing, and discovery that shows non-friends no presence (docs/instant-messaging.md, docs/hotline-ng.md).
  • Message requests: a stranger's messages held until approved, for an account that takes them, with approvals, holds and bounds per sender, address and recipient.
  • Blocks read as a missing account on every request that names one.
  • A classic /msg <login> to a friend arrives as an IM, and from a classic sender to someone who takes requests, as a request.
News and feeds
  • Threaded news with markdown bodies, references between articles, backlinks and full-text search ([news], hxd news-reindex).
  • Follows and mutes, and the notifications they earn: replies, references and posts in a followed thread ([news.notify]).
  • Image attachments on articles, content-addressed and quota-bound.
  • The 1.5 threaded-news transactions over the same tree, and 1.2 flat news as one category ([news] flat_category).
  • Ceilings on live articles and text, and a share of them per author.
  • RSS, RDF, Atom and JSON Feed polled into read-only categories ([[news.feed]], limits in [news.feeds], hxd news feeds).
Moderation and bans
  • Reports, redaction of a chat line, revocation of an image, and a purge of someone's last hour, with an audit trail ([moderation]).
  • Reports delivered to classic moderators as messages from the system account, and to ng moderators as events.
  • Durable bans on addresses, logins, identities and registrars, placed by a kick with ban or hxd ban add, listed and lifted with hxd ban list and hxd ban lift, applied on SIGHUP.
  • Moderation from the command line, against the database: hxd history redact, hxd media revoke, hxd purge, hxd reports, hxd moderation log.
Files
  • The file area: browse, Get Info, download and upload, folders either way, Large File with verified resume, drop boxes, and New Folder, Delete, rename, Move and comments on a local area ([files]).
  • The file area on the ng wire too: browse, download, previews (files_preview), uploads resumed across sessions (files_upload) and the same file management.
  • A transfer queue with per-account limits ([files] downloads_per_account, uploads_per_account, downloads, uploads, queue).
Media and avatars
  • Images in chat (fogWraith's Inline Media): decoded and re-encoded by the server, every byte of metadata stripped, served by handle ([media]).
  • Avatars on both wires: fogWraith's GIF Icons and the ng avatars capability, kept per account ([avatars]).
Voice and video
  • A voice SFU on one UDP port, one room shared by classic and ng clients ([voice]).
  • Camera and screen share on the voice connection, subscribed to per stream ([voice.video]).
Server linking
  • fogWraith's Server Linking by key mode over TLS ([link], hxd link): users, public chat, private messages, user info, kicks and bans across links, and relaying between them.
  • The user transport and user keys drafts: how a linked user is connected, and their signed and sealed messages.
Push
  • Web Push sent by the server itself, for a private message or a news reply while the app is closed ([push]).
  • A uniqush-push 2.11 sidecar as the second gateway ([push] gateway = "uniqush").
  • hxd push rekey to replace the VAPID key, dropping every registered device.
Operations
  • One TOML config file, every key optional; SIGHUP reloads accounts, roles, rooms, bans, revocations, the TLS certificate, link peers and the banner (docs/operating.md).
  • Optional subsystems behind Cargo features, a config section for a missing one refused at start.
  • [server] classic = false for an ng-only server.
  • The server banner, held or by URL, shown on both wires ([banner]).
  • The Docker image at ghcr.io/mishan/hxd-ng, configured from HXD_* variables, with HXD_MODE=relay for hlrelay (docs/docker.md).
  • hlrelay: discovery, /trtp and /htxf in front of a classic server that does not speak ng (docs/relay.md).
  • GET /metrics behind the metrics feature ([metrics], docs/metrics.md).
  • hxd-load: scenarios on both wires with the server's invariants checked under load (docs/load-testing.md).
  • HXD_DEBUG=proto wire trace, in GtkHx's format.
  • tools/ng-client.mjs, a terminal ng client, and tools/ng-voice.html, a one-file voice and video client.

Image: ghcr.io/mishan/hxd-ng:0.1.0

( Release page )
 Links
- Home
- Hotline HQ Discord
- Software
- News Archive
- Server Tracker
- Hotline Wiki
 Clients
- Hotline Navigator v0.3.1
- Heidrun 1.7.1
- GtkHx v1.6.2
- Hotline (SwiftUI) 1.0beta29
- Invigoration v2.3.3
 Servers
- hxd-ng v0.3.0
- Lemoniscate v0.1.8
- Heidrun Server 1.5.2
- Mobius v0.23.2
- Hotline Server 1.9.1
 Trackers
- Magnetron v0.4.0
- Argus
- hltracker (Visual Basic)
 Bots
- Heidrun Spirit 1.0.0
- Hotline Discord Bridge 1.0.0a
- Hotline2IRCRouter
 Misc
- Hotline IM (HIM) v0.2.0
- Heidrun protocol and CLI 1.0.0
- Hotline Modern v0.9.1
- Caps
- CreateList

 

Brought to you by BigRedH.com | HLWiki.com | stickytack.com