Skip to content
HN On Hacker News ↗

parley

▲ 330 points • 190 comments • by davidcollantes • 2w ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this entire text is AI.

97 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 1
SEGMENTS · AI 1 of 1
WORD COUNT 1,570
PEAK AI % 97% · §1
Analyzed
Sep 28
backend: pangram/v3.3
Segments scanned
1 windows
avg 1570 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,570 words · 1 segments analyzed

Human AI-generated
§1 AI · 97%

Federated, decentralised chat that speaks plain IRC. Parley is a chat network with no centre. Every person (or team) runs a small instance for their own domain. Instances find each other through DNS and well-known identity documents, exchange signed messages over HTTPS, and present the whole federated network to ordinary IRC clients such as irssi, WeeChat or Textual, with no plugins. Identities look like email: [email protected] runs on foo.com, [email protected] on bar.com. Bob types /msg [email protected] hi and it just works, even if the two instances have never heard of each other before. Status: working proof of concept. It demonstrates the design end to end and runs a real instance, but it is not hardened yet. See Limitations. It works with the client you already have Two instances (foo.com and bar.com), two stock irssi sessions. Alice connects to her instance; Bob connects to his: Bob opens a query with /msg [email protected] and the two instances federate on the spot. Alice sees Bob as bob on bar.com; Bob sees Alice as alice on foo.com: Both join #lobby, a global channel replicated across their instances: What it does Ordinary IRC in front. Connect with any IRC client. Log in with PASS or SASL PLAIN using your password or an IRC token. IRCv3 server-time, message-tags, echo-message, multi-prefix and setname are offered to clients that want them, and TAGMSG relays client-only tags such as typing indicators, across the federation as well as between clients here. Tags on a message itself are kept, so a +reply is still a reply when it comes back out of history, and draft/multiline makes a pasted paragraph one message rather than eight. Scrollback that follows you rather than your client. CHATHISTORY pages through channels and private messages alike, a join replays what you have not been shown, and draft/read-marker puts where you have read up to on the account, so marking a channel read on your phone clears it on your desktop and a client is told where to draw the line as soon as it joins. Accounts you can manage while it runs. Accounts live in the instance's data directory, not in a config file. Create them with parleyctl, the admin page, the HTTP API, or let people arrive through single sign-on: any OpenID Connect provider, or identity headers from a reverse proxy. People mint IRC tokens for their clients on their settings page; bots are accounts with the bot role. Discovery via DNS + WKID. _parley._tcp.<domain> SRV points at the instance; https://<host>/.well-known/parley/instance.json publishes its ed25519 public key and inbox; /.well-known/parley/<user>.json confirms a user exists. This mirrors how Salty IM finds people. Signed HTTP federation. Every event is a JSON document POSTed to the peer's /inbox, with a detached ed25519 signature in headers. Receivers verify against the key they discovered themselves. Federation is open: any instance whose signature checks out can talk to you. Automatic peering. Message someone on a new domain and the two instances link up on their own. Linked instances exchange the peers they know (gossip), so a mesh forms without configuration. Two kinds of channel. #dev is global, replicated across every linked instance with members in it. Nobody owns it, so it has no topic and no operators. &notes is local: it never leaves the instance, is invisible to peers, and is the one place a topic exists. Moderation without channel ownership. Nobody can be kicked out of a channel nobody owns, so /ban becomes a block list by mask: /ban [email protected] for one person, /ban *!*@example.com for a whole instance. Yours covers your account; an admin's covers the instance. Peering itself is controlled with parleyctl peers. Addresses map onto ordinary IRC identity. A user's nick is the bare local part and their instance is the host, so alice appears as [email protected] -- prefixes, NAMES, WHO and WHOIS all agree, and a nick you see in NAMES is one you can message. /whois [email protected] consults her instance's WKID document. History that survives downtime, and is searchable. Each instance keeps its channel history in SQLite with a full-text index (parleyctl search) and serves it at /channels/<name>/feed. When a peer comes back after an outage it pulls what it missed, and clients get recent history replayed on JOIN. Quick start (two instances on one machine, no DNS) Then, in two terminals: -insecure and -resolve exist only for local development. In production each instance has a real domain, a TLS certificate, an SRV record, and finds peers via DNS. The real thing: DNS + TLS demo demo/ brings up CoreDNS (authoritative for foo.com and bar.com, with _parley._tcp SRV records), a local demo CA, and both instances in Docker, then runs scripted IRC sessions and prints the evidence: See demo/README.md. Running your own instance The container image is prologic/parley, and docker-compose.example.yml is a starting point. An instance for example.com, reachable at chat.example.com, needs: A place to run it, with /data on persistent storage (it holds the instance key, the peer cache, the channel logs and the accounts): Then create accounts with parleyctl (it is in the image too) or the admin page: Without an admin token and with no accounts yet, parleyd logs a one-time /setup URL that creates the first admin in the browser. Single sign-on through OpenID Connect or a reverse proxy's identity headers is described in docs/AUTH.md; SSO users mint IRC tokens for their clients on their settings page. HTTPS in front of port 8443 on chat.example.com. Any reverse proxy that terminates TLS will do; parleyd itself serves plain HTTP unless you give it -tls-cert and -tls-key. An SRV record so other instances can find you: Without it, peers fall back to https://example.com/.well-known/parley/. IRC over TLS for your clients. parleyd's IRC listener is plaintext, so terminate TLS in front of it. With Caddy's layer4 module, for example: Then, in irssi: /connect -tls -tls_verify chat.example.com 6697 <password-or-token> alice. The port here is whatever your proxy listens on. If it is not 6697, start parleyd with -irc-port (and -irc-host, if IRC is on a different name to the endpoint), or set PARLEY_IRC_PORT / PARLEY_IRC_HOST: The landing page, the settings page, parleyctl and the instance document all print a connect line from it, and the settings page prints a live token on that line. A wrong port under a right hostname still passes certificate verification, so the token would go to whatever else is listening there. Check it from the outside. parleyctl check probes an instance the way a peer does — SRV record, well-known documents, the advertised endpoint, the inbox, and the IRC TLS port — and says what to fix: It needs no token and works against anyone's instance, so it is also how you tell a peer what is wrong with theirs. The common failure is a missing SRV record: the instance is perfectly reachable at its own host, but nobody resolving the identity domain can find it. Add -resolve https://chat.example.com to probe the host directly while DNS is still wrong, and -json for a machine-readable report. It exits non-zero if any check fails. How it fits together Bob's client sends PRIVMSG [email protected] :hi. bar.com looks up _parley._tcp.foo.com, fetches the instance document from the host it names, and caches the key. bar.com signs the event and POSTs it to foo.com's inbox. foo.com discovers bar.com the same way to verify the signature, delivers the message to Alice's clients, and since bar.com is a stranger, sends a hello back. Both sides now exchange channel rosters and peer lists. The wire format is documented in docs/PROTOCOL.md. Configuration There is no config file. Configuration is in two places and each thing is in exactly one of them. Flags, each with an environment variable, for what the process needs before it can open its database, what describes the machine and network it sits on, and the secrets and trust decisions about who may assert an identity. A flag wins over its variable. Only -domain is required. flag env meaning default -domain PARLEY_DOMAIN identity domain this instance serves required -data PARLEY_DATA_DIR identity key, databases, peer and membership state ./data -endpoint PARLEY_ENDPOINT advertised base URL https://<domain> -irc PARLEY_IRC_LISTEN IRC listener (plaintext; terminate TLS in front) :6667 -http PARLEY_HTTP_LISTEN HTTP(S) listener for the web UI, discovery, inbox and feeds :8443 -irc-host, -irc-port PARLEY_IRC_HOST, PARLEY_IRC_PORT where clients reach IRC over TLS endpoint host, 6697 -tls-cert, -tls-key PARLEY_TLS_CERT, PARLEY_TLS_KEY serve HTTPS directly off -admin-token PARLEY_ADMIN_TOKEN bearer token for the admin API and parleyctl off -admin (repeatable) PARLEY_ADMINS nicks that are admins regardless of their stored role none -seed (repeatable) PARLEY_SEEDS peer domains to link with at startup none -irc-proxy (repeatable) PARLEY_IRC_PROXIES CIDRs allowed to prepend a PROXY header to an IRC connection none -http-proxy (repeatable) PARLEY_HTTP_PROXIES CIDRs whose X-Forwarded-For the web listener believes none PARLEY_TRUSTED_PROXIES and PARLEY_TRUSTED_* identity headers from listed proxies (see docs/AUTH.md) off PARLEY_OIDC_* OpenID Connect provider (see docs/AUTH.md) off HAVEN_SOCKET Home Cloud app socket for household sync off -ca, -insecure, -resolve, -debug PARLEY_DEBUG local development off Settings, in the database, for everything an administrator might change while the instance runs. They take effect the moment they are saved, with no restart, and have no flag and no environment variable. Change them on the admin page, through PUT /api/v1/settings (docs/API.md), or with parleyctl: Only what differs from the defaults is stored, so an upgrade that changes a default changes it for every instance that never touched that key. setting meaning default motd message of the day (empty means the built-in text) built-in history_replay