diff --git a/Configuration.md b/Configuration.md index 4fbae21..197f893 100644 --- a/Configuration.md +++ b/Configuration.md @@ -1,129 +1,399 @@ # Configuration -Echo reads a single TOML file. Pass its path as the first argument (default `config.toml`). Copy `config.example.toml` to start. `config.toml` is gitignored because it holds the link password. +echo reads one TOML file, passed as the first argument (`echo config.toml`). Copy `config.example.toml` to start. Keep `config.toml` out of version control; it holds the link password. -## [uplink] (required) +Two sections are required: `[uplink]` and `[server]`. Everything else is optional and off when omitted. A few settings reload on `OperServ REHASH` without a restart (noted below); the rest need a restart. -The ircd Echo links to. `password` must match the `` block on the uplink. +The smallest working config: - [uplink] - host = "127.0.0.1" - port = 7000 - password = "changeme" +```toml +[uplink] +host = "127.0.0.1" +port = 7000 +password = "changeme" -## [server] (required) +[server] +name = "services.example.net" +sid = "42S" +description = "Network Services" +``` -Echo's own identity on the network. `sid` is three characters, unique on the network. `protocol` is the InspIRCd link version (1206 for insp4, 1205 for insp3). +## Contents - [server] - name = "services.example.net" - sid = "42S" - description = "Federated Services" - protocol = 1206 +- **Required:** [`[uplink]`](#uplink), [`[server]`](#server) +- **Services and operators:** [`[modules]`](#modules), [`[[oper]]`](#oper), [`[log]`](#log) +- **Policy:** [`[register]`](#register), [`[extban]`](#extban), [`[auth]`](#auth) +- **Lifecycle and limits:** [`[expire]`](#expire), [`[session]`](#session) +- **User-facing:** [`[language]`](#language), [`[email]`](#email) +- **Integrations:** [`[health]`](#health), [`[grpc]`](#grpc), [`[jsonrpc]`](#jsonrpc), [`[keycard]`](#keycard), [`[dictserv]`](#dictserv) +- **Optional multi-node:** [`[gossip]`, `[[peer]]`](#running-more-than-one-node) +- [Full example](#full-example) -## [gossip], [gossip.tls], [[peer]] +--- -Node-to-node replication. Omit all three to run a single node. See [Federation](Federation). +## `[uplink]` - [gossip] - bind = "0.0.0.0:16700" - secret = "shared-secret" +Where echo connects, and how it authenticates the link. Add a matching `` block on the ircd for the same SID and password. - [gossip.tls] # omit for a plaintext link - cert = "certs/node.crt" - key = "certs/node.key" - ca = "certs/ca.crt" # a peer must present a cert signed by this CA +| Key | Default | Description | +| --- | --- | --- | +| `host` | required | Uplink address. | +| `port` | required | Uplink server-to-server port. | +| `password` | required | Link password; must match the ircd's `` block. | +| `tls` | `false` | Connect over TLS instead of plaintext. | +| `spki_fingerprint` | `""` | Required when `tls = true`. The server's SPKIFP, base64 SHA256 of its SubjectPublicKeyInfo. The link cert is pinned by this fingerprint rather than a CA, so a self-signed ircd cert is fine. | - [[peer]] # one block per other node - addr = "other-node:16700" - name = "other-node" # TLS name to expect; matches the peer certificate +```toml +[uplink] +host = "irc.example.net" +port = 7000 +password = "s3cret" +# tls = true +# spki_fingerprint = "Wh/ag/dtBCpXhiYPhRWV66kx3FutmTyNw1dysTbWvao=" +``` -Generate certificates with `scripts/gen-certs.sh`. +Get the fingerprint of a running ircd with: -## [grpc], [grpc.tls] +```sh +openssl s_client -connect irc.example.net:7000 /dev/null \ + | openssl x509 -pubkey -noout | openssl pkey -pubin -outform DER \ + | openssl dgst -sha256 -binary | base64 +``` -The account and channel directory over gRPC, for a website to mirror. Omit to disable. See [Directory API](Directory-API). +## `[server]` - [grpc] - bind = "127.0.0.1:50051" - token = "shared-secret" # every RPC sends `authorization: Bearer ` +The services server's own identity, and how its pseudo-clients present themselves. -## [modules] +| Key | Default | Description | +| --- | --- | --- | +| `name` | required | Services server name (e.g. `services.example.net`). | +| `sid` | required | Three-character server ID, unique on the network. | +| `description` | required | Server description shown in `/links` and `/map`. | +| `protocol` | `1206` | InspIRCd link protocol version (1206 = insp4, 1205 = insp3). | +| `scram_iterations` | high | PBKDF2 cost baked into new SCRAM verifiers at registration. Lower it only if registration latency matters more than verifier strength. | +| `guest_nick` | `"Guest"` | Prefix a user is renamed to on `LOGOUT` (the ircd appends a number). Must start with a letter. | +| `service_host` | server name | Hostname the pseudo-clients wear, so NickServ shows as `NickServ!services@`. | +| `service_modes` | `"iHkB"` | User modes the pseudo-clients are introduced with: invisible, hideoper, servprotect (unkillable, needs the services server U-lined), bot. Add `T` to have the ircd block all CTCP. | +| `service_oper_type` | `"Network Service"` | Oper type shown in `/whois` for the pseudo-clients. Empty leaves them non-opers. | +| `services_channel` | `"#services"` | Channel every pseudo-client joins at startup. Empty keeps them out of any channel. | +| `standard_replies` | `false` | Emit IRCv3 FAIL/WARN/NOTE standard replies instead of plain notices. Needs `m_services_stdrpl` on the ircd. | -Which services start. Omit for the full standard suite. List names to run a subset; add `"example"` for the template service. +## `[modules]` - [modules] - services = ["nickserv", "chanserv"] +Which services to start. Omit the section for the full suite; list names to run a subset. Add `"example"` to also start the template service. -## [[oper]] +| Key | Default | Description | +| --- | --- | --- | +| `services` | full suite | Lowercase service names to load. | -Services operators and their privileges: `auspex` (see hidden info), `suspend` (suspend/unsuspend), `admin` (modify or drop others' accounts and channels). +```toml +[modules] +services = ["nickserv", "chanserv", "memoserv", "operserv"] +``` - [[oper]] - account = "yournick" - privs = ["auspex", "suspend", "admin"] +The full suite is `nickserv`, `chanserv`, `botserv`, `hostserv`, `memoserv`, `operserv`, `statserv`, `groupserv`, `infoserv`, `reportserv`, `helpserv`, `chanfix`, `diceserv`, `gameserv`, `debugserv`. -## [log] +## `[[oper]]` -An audit channel for the incident feed. Notable actions are announced there; private material and cosmetic self-service tweaks are not. +Services operators. Repeat the block once per operator. Grant a tier with `type`, or list fine-grained privileges with `privs`. The three tiers stack in power: - [log] - channel = "#services" +- **operator** is day-to-day moderation: view hidden info, network bans, kick, kill, session limits, ignores, mode, spam filters, log search. +- **administrator** is that plus account and channel data: suspend, drop, set flags, forbid, global notices, defcon, memo/info/bot administration. +- **root** is that plus daemon control: add or remove opers, `SET`, `REHASH`, `RESTART`, `SHUTDOWN`, the activity feed. -`notify_exclude` is an optional list of masks [OperServ NOTIFY](Services#operserv-network-operator-tools) never announces, so they cannot flood the feed. It takes three kinds of entry: +| Key | Default | Description | +| --- | --- | --- | +| `account` | required | The account name that holds these privileges. | +| `type` | none | Tier shortcut: `operator`, `administrator`, or `root`. | +| `privs` | `[]` | Fine-grained privileges: `auspex`, `oper`, `suspend`, `admin`, `root`. | -- `#channel` — mute that channel (keep a broad `#*` watch out of `#staff`). -- `server:` (alias `via:`) — mute everyone on a matching server. This is the handle on a relay whose users have **clean nicks**; the server name is what prints after `via` in a feed line. -- anything else — mute a user: a nick glob, `user@host`, or extban. `"*/*"` catches PyLink relays that suffix nicks with `/network`. +```toml +[[oper]] +account = "alice" +type = "root" -Reloadable with `OperServ REHASH` (no restart). +[[oper]] +account = "bob" +privs = ["oper", "suspend"] +``` - [log] - channel = "#services" - notify_exclude = ["*/*", "server:chatnova.relay", "#staff"] +Runtime operators can also be added without editing the config, with `OperServ OPER ADD` (optionally time-limited). -## [expire] +## `[log]` -Inactivity expiry. Accounts not identified to, and channels not joined, past the threshold are dropped on a periodic pass. Opers, live sessions, occupied channels, and NOEXPIRE records are spared. A zero or omitted field leaves that kind never expiring. +The staff audit feed. Notable service actions (registrations, drops, vhosts, suspensions, akicks, access and bot changes) are announced to a channel so operators can see who did what. Memo bodies, credential material, and cosmetic self-service changes are never shown. - [expire] - accounts_days = 90 - channels_days = 30 - warn_days = 7 # heads-up email this many days before expiry +| Key | Default | Description | +| --- | --- | --- | +| `channel` | required | The log channel. Keep it operator-only; NOTIFY lines can carry real IPs. | +| `notify_exclude` | `[]` | Masks `OperServ NOTIFY` never announces. `#channel` mutes a channel, `server:` (or `via:`) mutes a server, anything else mutes a user (nick glob, user@host, or extban). Reloadable with `REHASH`. | -## [session] +```toml +[log] +channel = "#services" +notify_exclude = ["*/*", "server:relay.example.net", "#staff"] +``` -Per-IP session limiting. The connection that puts an IP over the limit is killed on connect; OperServ exceptions adjust it per mask. +## `[register]` - [session] - default_limit = 3 +Registration policy. Both settings reload with `REHASH`. -## [auth] +| Key | Default | Description | +| --- | --- | --- | +| `confusable_check` | `true` | Refuse look-alike or mixed-script registration names. Turn off for a community that legitimately uses non-Latin names. | +| `vouch` | `false` | Invite-only registration: a new account stays pending until an existing member runs `NickServ VOUCH`, instead of confirming by email. | -Account authority. Omitted, Echo owns accounts itself. With `external = true`, identity is handed to an outside authority (for example a website): IRC can only IDENTIFY, and the authority pushes accounts in via the gRPC Accounts API. Echo still owns channel, vhost, and ban data. +## `[extban]` - [auth] - external = true +Which extbans echo recognizes. Omit the section (or leave the list empty) to accept every extban the ircd advertises. -## [dictserv] (opt-in) +| Key | Default | Description | +| --- | --- | --- | +| `enabled` | all | Extban names to accept (`account`, `realname`, `country`, ...). | -Enables [DictServ](Services#dictserv-dictionary-lookups-opt-in): dictionary, thesaurus and reference lookups over the DICT protocol (RFC 2229). `server` is a `host:port` DICT endpoint; dict.org hosts all the standard databases. **Omit the section and echo makes no outbound lookup requests at all** — this is opt-in on purpose, since it's the one service that reaches off-network. Lookups run off the reactor, are globally rate-limited, and replies are truncated to one line. +## `[auth]` - [dictserv] - server = "dict.org:2628" +Who owns accounts. By default echo owns them and `REGISTER` / `IDENTIFY` / `SET PASSWORD` all work on their own. Set `external = true` to hand identity to an outside authority (a website): IRC can then only `IDENTIFY`, everything that changes an account is refused, and the authority pushes accounts in over the [gRPC Accounts API](#grpc). echo still owns all channel, vhost, and ban data, keyed by account name. -## [register] +| Key | Default | Description | +| --- | --- | --- | +| `external` | `false` | Delegate account identity to an external authority. | -Registration policy. `confusable_check` (default **true**) is the [look-alike name guard](Services#nickserv-accounts): it refuses REGISTER of a nick or channel that mixes alphabets, is built from homoglyphs or styled (fullwidth/math) letters imitating Latin, or hides invisible/bidi characters, while genuine monolingual text — including accented French — passes. Set it `false` for a community that legitimately uses mixed or non-Latin names. Reloadable with `OperServ REHASH`. +## `[expire]` - [register] - confusable_check = false # only if you want to allow look-alike names +Drop inactive accounts and channels on a periodic pass. Operators, live sessions, occupied channels, and `NOEXPIRE`-pinned records are spared. A zero or omitted field leaves that kind never expiring. Omit the section to disable expiry. -## [email] +| Key | Default | Description | +| --- | --- | --- | +| `accounts_days` | `0` (off) | Expire accounts not identified to for this many days. | +| `channels_days` | `0` (off) | Expire channels not joined for this many days. | +| `warn_days` | `0` (off) | Email the owner this many days before expiry (needs `[email]` and an address on file). | -Outbound email for registration confirmation and password recovery. Omit to run without email. `command` is a shell command fed the message on stdin (`msmtp -t`, `sendmail -t`, or a relay script). +## `[session]` - [email] - command = "msmtp -t" - from = "noreply@example.net" - brand = "Example Network" +Per-IP connection limiting. The connection that puts an IP over the limit is killed on connect. `OperServ EXCEPTION` raises or lowers it per IP-mask (a limit of 0 means unlimited). + +| Key | Default | Description | +| --- | --- | --- | +| `default_limit` | `0` (off) | Connections allowed per IP. | + +## `[language]` + +Reply language. echo ships English plus French, German, Spanish (with an Argentine variant), and Portuguese (Portugal and Brazil). + +| Key | Default | Description | +| --- | --- | --- | +| `default` | `"en"` | Language for users who haven't chosen one. | +| `dir` | `"lang"` | Directory of `.json` catalogs. | +| `available` | `[]` | Codes a user may pick with `NickServ SET LANGUAGE`. Empty allows only the default. | + +```toml +[language] +default = "en" +available = ["en", "fr", "de", "es", "es-ar", "pt", "pt-br"] +``` + +## `[email]` + +Outbound email for registration confirmation and password recovery. Without it, those features are off. + +| Key | Default | Description | +| --- | --- | --- | +| `from` | required | Sender address. | +| `command` | required | Shell command the message is piped to, e.g. `sendmail -t` or `msmtp -t`. Run via `sh -c`. | +| `brand` | `"Network Services"` | Display name in the email template. | +| `accent` | `"#4f46e5"` | Accent color (any CSS color). | +| `logo` | `""` | Hosted logo image URL (email clients don't render inline SVG or data URIs). | +| `confirm_url` | `""` | Base URL of a one-click confirmation endpoint. When set, confirmation emails include a `?account=&code=` link alongside the CONFIRM command. | + +```toml +[email] +from = "services@example.net" +command = "msmtp -t" +brand = "Example Network" +``` + +## `[health]` + +A read-only, unauthenticated HTTP endpoint for monitoring. `GET /health` is a JSON liveness probe; `GET /metrics` is a Prometheus scrape (account, channel, and oper totals, per-service counters, and a monotonic log-progress gauge). Bind it to localhost. + +| Key | Default | Description | +| --- | --- | --- | +| `bind` | required | HTTP listen address, e.g. `127.0.0.1:9099`. | + +## `[grpc]` + +A gRPC directory of accounts and channels for a website to mirror (identity and metadata only, never credentials), and, with the token, to register or confirm accounts. Schema in `proto/echo.proto`. Off unless configured. + +| Key | Default | Description | +| --- | --- | --- | +| `bind` | required | Listen address, e.g. `127.0.0.1:50051`. | +| `token` | required | Bearer token every RPC must send (`authorization: Bearer `). | +| `[grpc.tls]` `cert` / `key` | none | Optional server TLS (PEM chain and key). Omit on a private or loopback hop. | + +```toml +[grpc] +bind = "127.0.0.1:50051" +token = "a-long-shared-secret" +``` + +## `[jsonrpc]` + +An HTTP JSON-RPC endpoint (for a staff web panel). Keep it on localhost behind a reverse proxy, or terminate TLS here. + +| Key | Default | Description | +| --- | --- | --- | +| `bind` | required | HTTP listen address, e.g. `127.0.0.1:5601`. | +| `token` | required | Bearer token every request must send. | +| `origins` | `[]` | Browser origins allowed cross-site (CORS). Empty means no browser access. | +| `[jsonrpc.tls]` `cert` / `key` | none | Terminate TLS here (enables HTTP/2). Omit if a proxy does TLS. | + +## `[keycard]` + +Passwordless web login: a member already signed in on the website connects with a one-time `kc_...` token in place of a password. echo redeems it against the website's localhost login endpoint. + +| Key | Default | Description | +| --- | --- | --- | +| `url` | required | The website's login-token endpoint. | +| `api_key` | required | Matches the endpoint's `X-API-Key`. | + +## `[dictserv]` + +DictServ, dictionary and reference lookups over the DICT protocol (RFC 2229). Opt-in, because it makes outbound network requests. Lookups are globally rate-limited and truncated to one line. + +| Key | Default | Description | +| --- | --- | --- | +| `server` | `"dict.org:2628"` | DICT endpoint. | + +```toml +[dictserv] +server = "dict.org:2628" +``` + +## Running more than one node + +Optional. A single node is the usual setup. If you run several, they replicate their event logs and share one account and channel database. `[gossip]` turns it on, `[[peer]]` names the others. See the wiki's federation notes for the trust model and certificate setup. + +| Section | Key | Description | +| --- | --- | --- | +| `[gossip]` | `bind` | Where peers reach this node. Omit for a dial-only node. | +| `[gossip]` | `secret` | Shared secret every node presents. | +| `[gossip.tls]` | `cert` / `key` / `ca` | Mutual TLS; a peer must present a cert signed by `ca`. | +| `[[peer]]` | `addr` / `name` | One block per other node; `name` must match the peer's certificate. | + +--- + +## Full example + +Every section, with the optional ones commented out. Uncomment and edit what you need. + +```toml +# ---- required ---- + +[uplink] +host = "127.0.0.1" +port = 7000 +password = "changeme" # must match the ircd's block +# tls = false +# spki_fingerprint = "" # base64 SHA256 of the server's SPKI; required when tls = true + +[server] +name = "services.example.net" +sid = "42S" # three chars, unique on the network +description = "Network Services" +# protocol = 1206 # 1206 = insp4, 1205 = insp3 +# scram_iterations = 210000 +# guest_nick = "Guest" +# service_host = "" # empty = the server name +# service_modes = "iHkB" +# service_oper_type = "Network Service" +# services_channel = "#services" +# standard_replies = false # needs m_services_stdrpl on the ircd + +# ---- services and operators ---- + +# [modules] +# services = ["nickserv", "chanserv"] # omit for the full suite + +# [[oper]] +# account = "yournick" +# type = "root" # operator | administrator | root +# privs = [] # or fine-grained: auspex, oper, suspend, admin, root + +# [log] +# channel = "#services" +# notify_exclude = ["*/*", "server:relay.example.net", "#staff"] + +# ---- policy ---- + +# [register] +# confusable_check = true +# vouch = false + +# [extban] +# enabled = [] # empty = accept every extban the ircd offers + +# [auth] +# external = false # true = an outside authority owns accounts + +# ---- lifecycle and limits ---- + +# [expire] +# accounts_days = 90 +# channels_days = 30 +# warn_days = 7 + +# [session] +# default_limit = 3 + +# ---- user-facing ---- + +# [language] +# default = "en" +# available = ["en", "fr", "de", "es", "es-ar", "pt", "pt-br"] + +# [email] +# from = "services@example.net" +# command = "msmtp -t" +# brand = "Example Network" +# accent = "#4f46e5" +# logo = "" +# confirm_url = "" # e.g. https://example.net/confirm + +# ---- integrations ---- + +# [health] +# bind = "127.0.0.1:9099" + +# [grpc] +# bind = "127.0.0.1:50051" +# token = "a-long-shared-secret" +# [grpc.tls] +# cert = "certs/node.crt" +# key = "certs/node.key" + +# [jsonrpc] +# bind = "127.0.0.1:5601" +# token = "a-long-shared-secret" +# origins = ["https://example.net"] + +# [keycard] +# url = "http://127.0.0.1:8000/accounts/api/login-token" +# api_key = "shared-key" + +# [dictserv] +# server = "dict.org:2628" + +# ---- optional multi-node ---- + +# [gossip] +# bind = "0.0.0.0:16700" +# secret = "shared-secret" +# [gossip.tls] +# cert = "certs/node.crt" +# key = "certs/node.key" +# ca = "certs/ca.crt" +# [[peer]] +# addr = "other-node:16700" +# name = "other-node" +``` diff --git a/Home.md b/Home.md index 717764b..aede151 100644 --- a/Home.md +++ b/Home.md @@ -1,16 +1,18 @@ -# Echo +# echo -A federated IRC services daemon in Rust. This wiki covers how Echo is built, how the federation works, how to run it, and how to extend it. +Services for an IRC network, built for InspIRCd, in Rust. This wiki is the reference: the services and their commands, the full configuration, the design, and how to extend it. -## Contents +## Pages -- [Architecture](Architecture) — the engine, the event-sourced store, the module boundary -- [Federation](Federation) — gossip replication, the global/local scope model, conflict resolution -- [Services](Services) — the built-in service suite and their commands -- [Configuration](Configuration) — the `config.toml` reference -- [Directory API](Directory-API) — the gRPC account and channel directory for websites -- [Writing a Module](Writing-a-Module) — build a new service or ircd protocol link +- [Services](Services): every service and its commands, with examples +- [Configuration](Configuration): the complete `config.toml` reference +- [Architecture](Architecture): the engine, the event log, the module boundary +- [Directory API](Directory-API): the gRPC directory a website uses to mirror accounts and channels +- [Deployment](Deployment): running echo in production, with backups and durability +- [Writing a Module](Writing-a-Module): add a service or an ircd protocol -## In one paragraph +## What echo is -Echo links to an ircd over its server-to-server protocol and runs the network's services as pseudo-clients. Every change to an account or channel is an event appended to a per-node log, and state is the fold of that log. Nodes exchange logs by gossip and converge, so several Echo instances share one account and channel database with no central server. The core is one crate; each service and each ircd protocol is a separate crate built against the `echo-api` SDK. +echo links to an InspIRCd server over the server-to-server protocol and runs the network's services, NickServ, ChanServ, and about a dozen more, as pseudo-clients. There is no database: every change is one event appended to a log, and the running state is a fold over that log. The engine handles one event at a time, so there are no locks on the shared state. Each service is its own crate, walled off from the log and from anything that holds a password. + +echo can also run as more than one node sharing a database. That is optional, and most setups never need it. diff --git a/Services.md b/Services.md index 7efba84..9cc87f4 100644 --- a/Services.md +++ b/Services.md @@ -1,105 +1,299 @@ # Services -Every service is a first-class pseudo-client. Omitting the `[modules]` config section starts the full suite; list names there to run a subset. +echo runs each network service as its own pseudo-client. You talk to one by messaging it: -## NickServ, accounts +``` +/msg NickServ REGISTER hunter2 alice@example.net +``` -REGISTER, IDENTIFY, LOGOUT, GROUP / UNGROUP / GLIST (grouped nicks), GHOST / RECOVER, INFO, ALIST, CERT (SASL EXTERNAL fingerprints), SET PASSWORD / EMAIL, DROP, RESETPASS and CONFIRM by email, SUSPEND, NOEXPIRE, AJOIN. SASL PLAIN, SCRAM-SHA-256/512, and EXTERNAL. Login sets account metadata on the ircd, which drives the standard logged-in numerics. +Most clients also carry short aliases (`/ns`, `/cs`, `/os`, `/ms`, `/hs`, `/bs`, `/gs`), which echo answers over `SQUERY`. Every service has built-in help: `/msg HELP` lists its commands and `/msg HELP ` explains one. -## ChanServ, channels +The whole suite starts by default. To run a subset, list the ones you want under [`[modules]`](Configuration#modules). -REGISTER, INFO, LIST, DROP, SET (FOUNDER, DESC, channel toggles), ACCESS with AOP/SOP/VOP shortcuts, FLAGS (granular per-account flags), OP/DEOP/VOICE/DEVOICE, KICK, BAN/UNBAN, AKICK, ENFORCE, TOPIC, ENTRYMSG, INVITE, GETKEY, SEEN, CLONE, MODE and MLOCK, SUSPEND, NOEXPIRE. Registering a channel requires holding ops in it. +- [NickServ](#nickserv) accounts and login +- [ChanServ](#chanserv) channel registration and access +- [MemoServ](#memoserv) messages between accounts +- [HostServ](#hostserv) virtual hosts +- [BotServ](#botserv) per-channel bots +- [OperServ](#operserv) network operator tools +- [GroupServ](#groupserv), [StatServ](#statserv), [ChanFix](#chanfix), [HelpServ](#helpserv), [InfoServ](#infoserv), [ReportServ](#reportserv), [GameServ](#gameserv), [DiceServ](#diceserv), [DictServ](#dictserv), [DebugServ](#debugserv) -Registration (of nicks and channels) refuses **look-alike names** — a name that mixes two alphabets (the classic `аdmin` with a Cyrillic `а`), is built entirely of homoglyphs imitating Latin, uses styled fullwidth/math letters (`Admin`, `𝐚𝐝𝐦𝐢𝐧`), or hides invisible / text-direction characters. Genuine monolingual text passes, including accented French. This guards the registered-name namespace against impersonation, which a message-content filter on the ircd never sees. A blocked attempt is also announced to the staff log channel (who tried which name) and recorded for `OperServ LOGSEARCH`, so operators can spot and search for someone probing. It's on by default and can be turned off per network — see [`[register]`](Configuration#register). +--- -## BotServ, channel bots +## NickServ -Bot management, ASSIGN/UNASSIGN a bot to a channel, SAY/ACT, per-channel kickers (caps, flood, repeat, badwords), fantasy commands such as `!op`, and greet. +Your account and everything tied to your identity. Registering a nick claims it as an account; identifying to it proves you own it. -## HostServ, vhosts +| Command | Syntax | What it does | +| --- | --- | --- | +| REGISTER | `REGISTER [email]` | Register your current nick as an account. | +| IDENTIFY | `IDENTIFY [account] ` | Log in. | +| LOGIN | `LOGIN ` | Log in and take the nick back in one step. | +| LOGOUT | `LOGOUT` | Log out; your nick becomes a guest nick. | +| GHOST | `GHOST [password]` | Disconnect a stale session sitting on your nick. | +| RECOVER | `RECOVER [password]` | Reclaim your nick from another session. | +| INFO | `INFO [account]` | Show account details. | +| SET | `SET PASSWORD `, `SET EMAIL
`, `SET GREET [text]`, `SET AUTOOP {ON\|OFF}`, `SET KILL {ON\|OFF}`, `SET HIDE STATUS {ON\|OFF}` | Change your password, email, or preferences. | +| CERT | `CERT ADD [password] `, `CERT DEL [password] `, `CERT LIST [password]` | Manage the TLS fingerprints that log you in via SASL EXTERNAL. | +| AJOIN | `AJOIN ADD <#channel>`, `AJOIN DEL <#channel>`, `AJOIN LIST` | Channels to auto-join when you log in. | +| GROUP | `GROUP ` | Link this nick to an existing account. | +| GLIST / UNGROUP | `GLIST`, `UNGROUP [nick]` | List or drop grouped nicks. | +| ALIST | `ALIST` | List channels you hold access on. | +| RESETPASS | `RESETPASS `, then `RESETPASS ` | Reset a forgotten password by email. | +| CONFIRM | `CONFIRM ` | Confirm your email address. | +| VOUCH | `VOUCH ` | Confirm a pending member (invite-only registration). | +| DROP | `DROP ` | Delete your account. | -Self-serve REQUEST plus operator APPROVE/REJECT, ON/OFF, an offer menu, a forbidden-pattern list, and an auto-vhost template. +Operator: `LIST `, `GETEMAIL `, `SUSPEND [reason]`, `UNSUSPEND `, `NOEXPIRE {ON|OFF}`, `SASET PASSWORD|EMAIL|GREET ...`. -## MemoServ, offline messages +``` +/msg NickServ REGISTER hunter2 alice@example.net +/msg NickServ IDENTIFY hunter2 +/msg NickServ SET EMAIL alice@newmail.net +/msg NickServ CERT ADD a1b2c3... # log in without a password using your client cert +/msg NickServ AJOIN ADD #dev +/msg NickServ GHOST alice hunter2 # kick a ghost off your nick, then reclaim it +``` -SEND, LIST, READ, DEL. Memos are stored on the account and delivered whether or not the recipient is online. +Login (any mechanism) sets your account name on the ircd, so you show up as logged in to other clients and to bans that key on accounts. echo supports SASL **PLAIN**, **SCRAM-SHA-256**, **SCRAM-SHA-512**, and **EXTERNAL** (certificate). -## OperServ, network operator tools +**Look-alike names are refused.** REGISTER rejects a nick or channel that mixes two alphabets (the classic `аdmin` with a Cyrillic `а`), is built entirely of homoglyphs imitating Latin, uses styled fullwidth or math letters (`Admin`, `𝐚𝐝𝐦𝐢𝐧`), or hides invisible or text-direction characters. Genuine monolingual text passes, accented French included. Blocked attempts are announced to the staff log and recorded for `OperServ LOGSEARCH`. On by default; see [`[register]`](Configuration#register). -AKILL and other X-lines, FORBID, SPAMFILTER, NOTIFY, DEFCON, JUPE, SESSION limits and exceptions, IGNORE, GLOBAL, KILL, KICK, MODE, SVS tools, STATS, and LOGSEARCH over the incident log. Typed operator privileges gate each command. +--- -### NOTIFY, an operator watch list +## ChanServ -`NOTIFY ADD + ` watches a mask; matching users have their flagged events announced to the staff feed (as DebugServ, tagged `[NOTIFY]`). Also `DEL `, `LIST`/`VIEW [pattern]`, and `CLEAR`. Admin only. +Channel registration and everything that follows from owning a channel: who gets ops, which modes stay locked, the topic, bans. -Each line carries the forensic detail a cloak hides, in labelled fields — the real IP, its rdns (`DNS:`, shown only when it differs), the login, and the origin server. A services command reads `Service:verb`: +| Command | Syntax | What it does | +| --- | --- | --- | +| REGISTER | `REGISTER <#channel>` | Register a channel (you must be opped in it). | +| INFO | `INFO <#channel>` | Show registration, founder, settings, topic setter. | +| SET | `SET <#channel> FOUNDER `, `DESC `, `SUCCESSOR \|OFF`, `SIGNKICK\|PRIVATE\|PEACE\|SECUREOPS\|SECUREVOICES\|RESTRICTED\|AUTOOP\|KEEPTOPIC\|TOPICLOCK {ON\|OFF}` | Transfer the founder or flip a channel toggle. | +| ACCESS | `ACCESS <#channel> LIST`, `ADD `, `DEL ` | Manage the access list by tier. | +| SOP / AOP / HOP / VOP | `SOP\|AOP\|HOP\|VOP <#channel> ADD\|DEL\|LIST [account]` | Tiered access shortcuts. | +| FLAGS | `FLAGS <#channel> [account [+/-flags]]` | Granular per-account flags (see below). | +| LEVELS | `LEVELS <#channel> [SET \| RESET ]` | Tune which tier holds each capability. | +| STATUS | `STATUS <#channel> [nick]` | Show a user's effective access. | +| OP / DEOP / VOICE / DEVOICE | `OP <#channel> [nick]` (and the rest) | Give or take status. | +| OWNER / PROTECT / HALFOP | `OWNER\|PROTECT\|HALFOP <#channel> [nick]` (plus DE- forms) | Higher status modes. | +| UP / DOWN | `UP <#channel>`, `DOWN <#channel>` | Apply or drop your own auto-status. | +| TOPIC | `TOPIC <#channel> ` | Set the topic. | +| AKICK | `AKICK <#channel> ADD [reason]`, `DEL `, `LIST`, `CLEAR` | Auto-kick list. | +| BAN / UNBAN / KICK | `BAN <#channel> [reason]`, `UNBAN <#channel> [nick]`, `KICK <#channel> [reason]` | One-off moderation. | +| MODE / MLOCK | `MODE <#channel> `, `MLOCK <#channel> [modes]` | Set modes now, or lock them permanently. | +| ENTRYMSG | `ENTRYMSG <#channel> [CLEAR \| ]` | Message shown to users on join. | +| INVITE / GETKEY | `INVITE <#channel> [nick]`, `GETKEY <#channel>` | Get in, or read the key. | +| ENFORCE / SYNC | `ENFORCE <#channel>`, `SYNC <#channel>` | Re-apply the mode lock / re-apply access to current members. | +| CLONE | `CLONE ` | Copy settings from one channel to another. | +| DROP | `DROP <#channel>` | Delete the registration. | - [NOTIFY] reverse!reverse@be.yourself.4ever used OperServ:rehash · IP: 2001:41d0:701:1100::65d0 DNS: vps-9b04b3b4.vps.ovh.net · Account: reverse · Server: irc.tchatou.fr - [NOTIFY] TimeRider!swaygo@kmaclxbm.dvmin24u.po6mymze.IP joined #devs · IP: 81.185.143.211 · Account: swaygo · Server: irc.tchatou.fr +Operator: `LIST`, `SUSPEND <#channel> [+expiry] [reason]`, `UNSUSPEND`, `NOEXPIRE <#channel> {ON|OFF}`. -The displayed `nick!ident@host` keeps the cloak so it still correlates with what ordinary users see. Because these lines expose real IPs, keep the log channel **operator-only** (`+O`). The connection *port* is not shown — the ircd's server-to-server burst never sends it to services. +``` +/msg ChanServ REGISTER #dev +/msg ChanServ SOP #dev ADD alice +/msg ChanServ SET #dev SECUREOPS ON # only access-list users keep ops +/msg ChanServ MLOCK #dev +ntc-k # lock these modes on, that one off +/msg ChanServ AKICK #dev ADD *!*@spam.host Get out +/msg ChanServ TOPIC #dev Welcome to #dev +``` -- **Mask** — a `nick!user@host` pattern, a bare nick glob (`baddie*`), or a `#channel` (watches everyone who joins a matching channel). -- **Flags** — `c` connect, `d` disconnect, `o` oper-up, `j` join, `p` part, `k` kick, `m` channel mode, `t` topic, `n` nick change, `u` user mode, `s` services command, `S` services SET; `*` for all. Disconnects and parts carry their quit/part reason; a kick names the kicker and reason. -- **Expiry** — `+30d`, `+12h`, `+45m`, or `+0` for permanent. +**FLAGS letters:** `f` full, `o` auto-op, `O` op (no auto-op), `h` auto-halfop, `v` auto-voice, `t` topic, `i` invite, `a` manage access, `s` settings, `g` greet, and `d` **deny status** (bar a user from any op or voice, and strip it if someone hands it to them anyway). - /msg OperServ NOTIFY ADD +0 cdn baddie*!*@* known ban-evader - /msg OperServ NOTIFY ADD +7d j #recruiting watch who joins during the drive - /msg OperServ NOTIFY ADD +0 j #* every join, network-wide +``` +/msg ChanServ FLAGS #dev alice +ov # give alice op and voice +/msg ChanServ FLAGS #dev troublemaker +d # this user never gets status here +``` -A deliberately broad mask like `#*` (every channel) or `*` (every user) is allowed — NOTIFY only observes, so there is no too-wide guard. Scope such a watch with flags (here just `j`) and lean on `notify_exclude` to keep relay noise out. +--- -**Keeping relay clients out of the feed.** A broad watch — a channel watch especially — will match every PyLink-relayed user (their nicks carry a `/network` suffix) and flood the log. Set the exclude list once and reload it live, no restart: +## MemoServ - # in config.toml - [log] - channel = "#services" - notify_exclude = ["*/*"] # any nick with a "/" is a relay client +Messages to an account, delivered whether or not they're online. - # then, as an operator: - /msg OperServ REHASH - -OperServ- Configuration reloaded from /etc/echo/config.toml. +| Command | Syntax | What it does | +| --- | --- | --- | +| SEND | `SEND ` | Leave a memo. | +| RSEND | `RSEND ` | Leave a memo with a read receipt. | +| LIST / READ | `LIST`, `READ \|NEW\|ALL` | List or read memos. | +| DEL | `DEL \|ALL` | Delete memos. | +| CANCEL | `CANCEL ` | Recall a memo you sent, if unread. | +| CHECK | `CHECK ` | See whether a memo you sent was read. | +| INFO | `INFO` | Mailbox summary. | +| IGNORE | `IGNORE ADD `, `DEL `, `LIST` | Block memos from an account. | +| SET | `SET NOTIFY {ON\|OFF}`, `SET LIMIT \|NONE` | Preferences. | -Excluded masks are skipped by *every* watch, so genuine locals still surface while the relay noise is gone. An entry mutes a **user** (nick glob, `user@host`, extban), a **channel** (`#staff`), or a whole **server** (`server:`, alias `via:`). The server form is the one that catches a relay whose users have clean nicks — `via chatnova.relay` in the feed becomes `server:chatnova.relay` in the list: +Admin: `SENDALL ` (every account), `STAFF ` (every operator). - notify_exclude = ["*/*", "server:chatnova.relay", "#staff"] +``` +/msg MemoServ SEND alice back tomorrow, watch #dev +/msg MemoServ READ NEW +``` -So "every join except #staff, and never the relays" is `#*` watched with those three exclusions. See [Configuration → [log]](Configuration#log). +--- -## StatServ, statistics +## HostServ -Network and per-channel statistics. +Virtual hosts. A user takes one from an operator-offered menu or requests a custom one; operators approve and assign. -## GroupServ, account groups +| Command | Syntax | What it does | +| --- | --- | --- | +| ON / OFF | `ON`, `OFF` | Turn your vhost on, or drop back to your normal host. | +| REQUEST | `REQUEST ` | Ask for a custom vhost. | +| OFFERLIST / TAKE | `OFFERLIST`, `TAKE ` | Browse the menu and take one. | -REGISTER/DROP/INFO/LIST, ADD/DEL members, FLAGS. A channel access entry can name a group, and every member then inherits that channel access. +Operator: `SET [duration]`, `DEL `, `LIST`, `WAITING`, `ACTIVATE `, `REJECT `, `OFFER `, `OFFERDEL `, `FORBID `, `FORBIDDEL `. -## ChanFix, opless recovery +``` +/msg HostServ REQUEST alice.dev.example.net +/msg HostServ SET bob staff.example.net # operator assigns a vhost directly +/msg HostServ ON +``` -SCORES and CHANFIX. The engine scores op-time per identity from live network state; ChanFix reops a channel's trusted regulars once it is opless. It defers to ChanServ and will not touch a registered channel. +--- -## HelpServ, help desk +## BotServ -REQUEST to open a ticket, CANCEL your own, and the operator queue: LIST, VIEW, TAKE, NEXT, CLOSE. Tickets are event-sourced and show up in OperServ LOGSEARCH. +A services bot that sits in a channel, speaks for staff, runs fantasy commands, and enforces kickers. -## InfoServ, bulletins +| Command | Syntax | What it does | +| --- | --- | --- | +| ASSIGN / UNASSIGN | `ASSIGN <#channel> `, `UNASSIGN <#channel>` | Put a bot in a channel, or remove it. | +| SAY / ACT | `SAY <#channel> `, `ACT <#channel> ` | Speak (or /me) through the bot. | +| SET | `SET <#channel> `, `SET PRIVATE ` | Per-channel or per-bot settings. | +| KICK | `KICK <#channel> [params]` | Configure the message kickers. | +| BADWORDS | `BADWORDS <#channel> ADD\|DEL\|LIST\|CLEAR [pattern]` | The badword list. | +| TRIGGER | `TRIGGER <#channel> ADD \|[\|]`, `DEL `, `LIST` | Keyword auto-responses. | +| INFO / BOTLIST / COPY | `INFO `, `BOTLIST`, `COPY <#source> <#dest>` | Inspect, list, or clone. | -POST/LIST/DEL public bulletins shown on connect, and OPOST/OLIST/ODEL operator bulletins shown on login. +Operator: `BOT ADD|CHANGE|DEL|LIST`, `AUTOASSIGN |OFF`. -## ReportServ, abuse reports +Fantasy commands run in-channel through the assigned bot: `!op`, `!deop`, `!kick nick`, `!topic ...`, and (with the game and dice services) `!roll`, `!dict`. -REPORT a nick or channel (rate-limited), and the operator queue: LIST, VIEW, CLOSE, DEL. A filed report flows through the audit feed to the incident log and the staff channel. +``` +/msg BotServ ASSIGN #dev Robo +/msg BotServ KICK #dev FLOOD ON 5 3 # kick on 5 lines in 3 seconds +/msg BotServ SAY #dev meeting in 5 +``` -## DiceServ, dice and math +--- -ROLL, CALC, and the EX variants: dice notation, arithmetic, functions, constants, and repeats. +## OperServ -## DictServ, dictionary lookups (opt-in) +The network operator toolbox. Every command is gated by a typed privilege tier (operator, administrator, root); see [Operators](Configuration#oper). -Dictionary, thesaurus and reference lookups over the DICT protocol (RFC 2229) — dict.org by default. Every command maps to one DICT database: `dict` (WordNet), `define` (GCIDE), `definitions` (all), `thes` (thesaurus), `acronym`, `element`, `jargon`, `term` (computing), `bible`/`biblename`, `law`, `ciainfo`, `counties`/`places`/`zipcodes`; `LOOKUP` lists them all. Use it two ways: `/msg DictServ dict cat`, or the fantasy `!dict cat` in a channel with an assigned bot (the bot speaks the answer). +**Bans and enforcement** -DictServ is **off by default** and loads only when a [`[dictserv]`](Configuration#dictserv-opt-in) section is present — it's the one service that makes outbound requests, so it's a conscious opt-in. The DICT round trip runs off the reactor (it never touches the engine lock), lookups are globally rate-limited so a channel can't hammer the server, and each reply is truncated to one line. +| Command | Syntax | +| --- | --- | +| AKILL | `AKILL ADD [+expiry] `, `DEL `, `LIST [pattern]` | +| SQLINE / SNLINE | `SQLINE ADD [+expiry] `, `SNLINE ADD [+expiry] ` (plus DEL/LIST) | +| SHUN / CBAN | `SHUN ADD [+expiry] `, `CBAN ADD [+expiry] <#channel> ` (plus DEL/LIST) | +| FORBID | `FORBID ADD `, `DEL ...`, `LIST` | +| SPAMFILTER | `SPAMFILTER ADD [+expiry] `, `DEL `, `LIST` | +| SESSION / EXCEPTION | `SESSION LIST `, `SESSION VIEW `, `EXCEPTION ADD [reason]` | +| CHANKILL | `CHANKILL <#channel> [reason]` | -## Interconnection +**Watching and auditing** -The services share one identity model, one event-sourced store, and one audit trail. A group grants channel access. A report or a help ticket is an event that the engine's audit feed records to the searchable log and announces to staff. InfoServ and OperServ share one news store. ChanFix defers to ChanServ. They are not isolated islands. +| Command | Syntax | +| --- | --- | +| NOTIFY | `NOTIFY ADD + `, `DEL`, `LIST`, `VIEW`, `CLEAR` | +| INFO | `INFO `, `INFO ADD `, `INFO DEL ` | +| LOGSEARCH | `LOGSEARCH [pattern]` | +| STATS | `STATS` | +| REDACT | `REDACT <#channel\|nick> [reason]` | + +**Acting on users and channels** + +| Command | Syntax | +| --- | --- | +| KILL / KICK / MODE | `KILL [reason]`, `KICK <#channel> [reason]`, `MODE <#channel> [params]` | +| SVSNICK / SVSJOIN / SVSPART | `SVSNICK `, `SVSJOIN <#channel> [key]`, `SVSPART <#channel> [reason]` | +| IGNORE / GLOBAL | `IGNORE ADD [+expiry] [reason]`, `GLOBAL ` | +| PROTECT / SETALL | `PROTECT ALL\| [ON\|OFF]`, `SETALL ` | +| JUPE | `JUPE [reason]`, `JUPE DEL`, `JUPE LIST` | + +**Daemon control (root)** + +| Command | Syntax | +| --- | --- | +| DEFCON | `DEFCON [1-5]` | +| OPER | `OPER ADD [+duration]`, `OPER DEL `, `OPER LIST` | +| SET | `SET READONLY {ON\|OFF}` | +| REHASH / RESTART / SHUTDOWN | `REHASH`, `RESTART [reason]`, `SHUTDOWN [reason]` | + +``` +/msg OperServ AKILL ADD +7d *@evil.example.net repeated spam +/msg OperServ NOTIFY ADD +30d j #staff badnick* watching this pattern +/msg OperServ OPER ADD alice administrator +/msg OperServ LOGSEARCH alice +``` + +`REHASH` reloads the reloadable parts of the config (the log channel and its excludes, the registration and extban policy) without a restart. + +--- + +## The rest + +### GroupServ + +Account groups whose members inherit the group's channel access. A channel access entry of `!groupname` grants access to every member. + +`REGISTER `, `DROP `, `INFO `, `LIST`, `ADD `, `DEL `, `FLAGS [account [+/-flags]]` + +``` +/msg GroupServ REGISTER !devs +/msg GroupServ ADD !devs alice +/msg ChanServ ACCESS #dev ADD !devs op # the whole group gets op on #dev +``` + +### StatServ + +`SERVER` for network-wide counts, `/msg StatServ #channel` for a channel's activity. + +### ChanFix + +Reop a channel that lost all its ops, from op-time its regulars earned earlier. `SCORES <#channel>` shows the standings, `CHANFIX <#channel>` reops the trusted regulars. + +### HelpServ + +An operator help-desk queue. Users open a ticket, operators work it. + +`REQUEST `, `CANCEL`, `LIST [ALL]`, `VIEW `, `TAKE `, `NEXT`, `CLOSE ` + +### InfoServ + +Network bulletins shown on connect and at login. `POST `, `LIST`, `DEL ` for public notices; `OPOST`, `OLIST`, `ODEL` for operator-only ones. + +### ReportServ + +User abuse reports that land in the operator audit trail. `REPORT ` files one; operators `LIST`, `VIEW `, `CLOSE `, `DEL `. + +### GameServ + +A server-side referee for tic-tac-toe, Connect Four, and chess, with a ranked ladder. Moves are checked on the server, so a client can't cheat. + +`CHALLENGE `, `ACCEPT [id]`, `DECLINE [id]`, `MOVE `, `RESIGN `, `GAMES`, `STATS [nick]`, `TOP [ttt|c4|chess]` + +``` +/msg GameServ CHALLENGE bob chess +/msg GameServ MOVE 1 e2e4 +``` + +### DiceServ + +Dice and math for tabletop play. `ROLL ` (whole numbers), `CALC ` (decimals), `EXROLL`/`EXCALC` show each die. + +``` +/msg DiceServ ROLL 2d6+1 +``` + +### DictServ + +Dictionary and reference lookups over dict.org, off unless [configured](Configuration#dictserv). `DICT `, `DEFINE `, `THES `, and `LOOKUP` for the full list. Also answers `!dict`, `!define`, and friends through a channel bot. + +### DebugServ + +Streams live services activity (logins, session lifecycle, account and channel changes) to the staff log channel. `STATUS` reports what it's currently reporting. Read-only.