Parley: Federated, decentralised chat that speaks plain IRC
People set their own picture under Settings in the web interface, or it
以下正文同步自 Hacker News,版权归原站所有,已转换为易读排版。
People set their own picture under Settings in the web interface, or it comes from their identity provider's picture claim at login and is re-hosted here. It is published to IRC clients as the IRCv3 avatar metadata key and to other instances in the well-known user document; see docs/PROTOCOL.md.
The instance's logo is not in this table, because it is not text: upload one PNG of at least 512 pixels along its longest side under Settings -> Logo in the admin page and Parley derives the favicon, the home-screen icon and the square an IRC client shows beside the network. A square is ideal, and a wordmark is fine -- anything up to three times as long as it is tall is centred on a transparent square rather than refused. Until then every instance shows Parley's own icon, which is why two of them look alike in a client's network list.
Chat help. /help (or /quote HELP <topic>) serves the pages in help/*.txt, which are compiled into the binary. Edit a page and rebuild to change it; the first line is its title. A new file is a new topic -- list it in help/index.txt, which make test checks.
Endpoints: /healthz for liveness, /api/v1/status for a JSON status of peers and channels, /metrics for Prometheus, and the API in docs/API.md.
Upgrading from a config file. parleyd -config config.json no longer reads the file: it prints, for every key in it, the flag, variable or setting that key has become, and exits. Move the process-level keys to your unit file or compose file, start the instance, then parleyctl settings import config.json applies the rest in one go and names what it skipped.
With TLS terminated in front of the IRC listener, every client arrives from the proxy: the logs name the wrong host, and per-address limits either do nothing or lock everybody out at once. List the proxy in irc_proxies and it must then prepend a PROXY protocol header (v1 or v2) to each connection.
This is two changes, and neither works alone. Listing a proxy that does not send a header drops every connection from it; sending a header from an address that is not listed feeds it to the IRC parser as garbage. There is no safe order, so change both together and be ready to put both back.
The default is empty, which is the whole thing switched off. 127.0.0.1/32 below is an example, not a default -- substitute the address your own connections actually arrive from:
parleyd -irc-proxy 127.0.0.1/32 # repeatable PARLEY_IRC_PROXIES=127.0.0.1/32 # comma- or space-separated
Note the name: PARLEY_TRUSTED_PROXIES is the web listener's equivalent and a different decision entirely.
To find the address to list, connect once and read the log. Every client address is reported the first time it is seen:
level=INFO msg="irc: new client address" addr=203.0.113.9 trusted_proxy=false
Behind a proxy every client shares one address, so that is a single line naming exactly what belongs in irc_proxies. It is always the address on the socket, never the one a header carries -- the header address could never match the list, so reporting it would hand you a value guaranteed to fail. Once the proxy is listed and sending headers, the line reappears for it with trusted_proxy=true, which is the confirmation that the two halves now agree.
Only listed addresses are believed, and a connection from one of them that does not carry a header is dropped rather than treated as a direct connection -- accepting both shapes from the same place hands back the address forgery the header exists to stop. Those drops are counted in parley_irc_proxy_rejected_total, which is the metric to watch while making the change.
The web listener has the same blindness and its own setting. Behind a reverse proxy every request arrives from that proxy, so the per-address gates on web login, the federation inbox and feed reads all share one bucket -- a global rate limit wearing a per-address name. List the proxy in http_proxies and X-Forwarded-For is believed from it, and nothing else changes.
Use that rather than auth.trusted_headers.proxies, which looks like it would do the job and does far more: a proxy on that list may assert who the user is, so a request to /login carrying Remote-User is logged in without a password. Fixing a rate limit is no reason to turn on passwordless login. The identity list does imply the address one, since a proxy trusted that far is not one to doubt about an address.
The step-by-step version, including the order that costs one outage instead of two, is in docs/PROXY.md.
With real addresses in hand, max_conns_per_addr becomes safe to turn on, and turning it on also applies the per-address login bucket to IRC. Leave it at 0 while the listener sits behind an unlisted proxy: every client shares one address there, so the cap would not limit anybody in particular, it would lock out everybody at once. It is a setting, so turning it on is parleyctl settings set max_conns_per_addr 8 and needs no restart. The per-account login backoff applies either way -- see docs/AUTH.md.
/metrics serves the Prometheus text format: connections, accounts online, channels, peers linked, and counters for pushes, inbox events, feed reads, tag messages and what was refused. It needs an admin token by default, since how many people are on an instance is the operator's business; set the metrics_public setting to serve it openly. Every sample is a count -- no nicks, no channel names, no peer domains.
<data_dir>/identity.key is the one file that cannot be regenerated. The domain's identity is the keypair: lose it and every peer that has cached the old public key refuses the instance, and the only fix is a new key that everyone has to re-trust. Back it up on the host, off the host:
parleyctl key show # public key and fingerprint parleyctl key export -y > instance.key # the secret; keep it somewhere safe parleyctl key import < instance.key # restore into an empty data dir
If the key ever leaks, replacing it is a supported operation rather than a disaster:
parleyctl key stage # generate the next key and publish it parleyctl key promote -y # ...then, once peers have re-read it, sign with it parleyctl key rotate -now -y # a leaked key: replace it at once, no grace period
Peers recover by themselves. A signature they cannot verify makes them re-read the instance document once, which is where they find the new public key, so the cost is one failed request each -- and if that request was a hello, a few minutes of backoff before they try again. The old key is kept as identity.key.<fingerprint>.bak because the only unrecoverable mistake here is replacing a key you still needed. Restart parleyd afterwards to serve the new one.
These read and write the data directory directly rather than going through the API, because an admin token must never be able to fetch the private key over the network. Run them on the instance host, with -data-dir or PARLEY_DATA_DIR pointing at the data directory.
Nothing to run: the database gains its new tables on first start and the old ones are untouched. What follows is the handful of things that changed under you, newest first.
How much one account may say is now bounded. message_rate (1 a second) and message_burst (20 at once) apply to everybody but bots, keyed by the account, so a person's clients share one budget. A refused message is answered 439 naming the target and is delivered nowhere. Every instance before this had no bound at all, so if yours has a room busier than that, raise the numbers or set message_rate to 0, which is the old behaviour:
parleyctl settings set message_rate 0
It is a setting, so it lands without a restart.
Somebody on another instance is alice:foo.com, not alice/foo.com. The separator was borrowed from draft/relaymsg, and it was the wrong half of the convention to copy: a strict client guards against mistaking a server name for a nick by requiring both a . and a : or neither, and a name with a domain has a dot and no colon. Such a client read the whole prefix as a server name and dropped every JOIN, PART, QUIT, NICK and AWAY from a remote user, while PRIVMSG degraded quietly -- which is why chat looked fine and no roster ever updated. PARLEYSEP in ISUPPORT says which character is in use, so a bot should read it from there rather than hardcode one.
The trap while a network is mid-upgrade: mention text crosses federation as bytes. Somebody on a peer that is still on / types alice/foo.com, and it does not match anything here until they upgrade. Tell your peers rather than letting them find it.
A reaction sent to a peer older than v0.5.0 is lost, not queued. Those versions answer an event type they do not implement with a 422, and a refusal is final to the sender's outbox: the event is dropped and the sender is told the send failed. From v0.5.0 on, an unimplemented type is answered 200 {"unsupported": true} instead, so this is the last change that has to wait for the whole mesh. Reactions between people here, and between upgraded instances, are unaffected.
GET /api/v1/status no longer publishes channel rosters. It is the only unauthenticated endpoint, and it was naming every member of every global channel -- our peers' users included, on their behalf, when their own landing page publishes counts and never names. A channel still carries name, a members count and on, the other domains with members in it. Nothing in federation read it: peers exchange members in signed hello snapshots.
history_replay now defaults to 50, up from 20. Only settings you have never set follow a default, so an instance that has chosen a number keeps it.
Those are gone. Start the new version, then import the old list once:
echo "alice:s3cret,bob:hunter2" | parleyctl -token <admin token> import
Project layout
cmd/parleyd the daemon cmd/parleyctl command-line client for the HTTP API internal/accounts accounts, IRC tokens and web sessions (SQLite) internal/auth identity providers: OpenID Connect, trusted headers internal/config configuration internal/homecloud Home Cloud app integration (household sync) internal/identity ed25519 instance key, request signing/verification internal/discovery DNS SRV + well-known resolution with caching internal/protocol wire types: documents, events, hashing internal/store append-only channel logs, peer cache internal/server IRC front end, federation inbox/outbox, channel state, HTTP API, web UI internal/web HTML templates (HTMX + DaisyUI + Tailwind) demo/ CoreDNS + Docker Compose demonstration homecloud/ Home Cloud catalog manifest help/ the chat HELP pages, embedded at build time docs/PROTOCOL.md the federation protocol docs/API.md the HTTP API docs/AUTH.md login, single sign-on and tokens
make test runs in-process end-to-end scenarios covering global and local channels, direct messages with automatic peering, peer exchange, catch-up after downtime, signature rejection and IRC basics.
Known gaps, roughly in the order they should be closed:
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。