site: give echo services its own page + navbar entry; frame it as a separate S2S-linked package, not native to the daemon

This commit is contained in:
Jean Chevronnet 2026-08-30 20:44:08 +00:00
parent 149ab69afe
commit c4836c7dd1
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
7 changed files with 104 additions and 20 deletions

View file

@ -1,15 +1,19 @@
# Introduction # Introduction
**echoIRCd** is a modern IRC daemon and a full services suite, written from scratch in Rust. It is **echoIRCd** is a modern IRC daemon, written from scratch in Rust. It is original code — not a fork
original code — not a fork of another ircd — and the entire tree compiles with of another ircd — and the entire tree compiles with `#![forbid(unsafe_code)]`.
`#![forbid(unsafe_code)]`.
Accounts and channels are handled by **echo services**, a *separate* companion package that links
to the daemon over the server-to-server protocol — the daemon itself is a pure ircd and does not
have services built in.
## What you get ## What you get
- **echoIRCd** — the server: a single-threaded core with an epoll reactor pool, the full channel - **echoIRCd** — the daemon: a single-threaded core with an epoll reactor pool, the full channel
and user mode set, host cloaking, connection classes, and an operator privilege model. and user mode set, host cloaking, connection classes, and an operator privilege model. It exposes
- **echo services** — NickServ, ChanServ, OperServ, MemoServ and more, linked over a standard the account and SASL *interface* that services plug into.
server-to-server protocol and backed by an event-sourced store. - **echo services** — a separate package (NickServ, ChanServ, OperServ, MemoServ and more), linked
over the server-to-server protocol and backed by an event-sourced store.
## Highlights ## Highlights

View file

@ -54,6 +54,12 @@ struct ConnectTemplate {
active: &'static str, active: &'static str,
} }
#[derive(Template)]
#[template(path = "services.html")]
struct ServicesTemplate {
active: &'static str,
}
struct SidePage { struct SidePage {
slug: &'static str, slug: &'static str,
title: &'static str, title: &'static str,
@ -95,6 +101,9 @@ async fn features() -> Response {
async fn connect() -> Response { async fn connect() -> Response {
page(ConnectTemplate { active: "connect" }) page(ConnectTemplate { active: "connect" })
} }
async fn services() -> Response {
page(ServicesTemplate { active: "services" })
}
async fn docs_index() -> Response { async fn docs_index() -> Response {
Redirect::permanent(&format!("/docs/{}", docs::first_slug())).into_response() Redirect::permanent(&format!("/docs/{}", docs::first_slug())).into_response()
@ -177,6 +186,7 @@ async fn main() {
.route("/", get(index)) .route("/", get(index))
.route("/features", get(features)) .route("/features", get(features))
.route("/connect", get(connect)) .route("/connect", get(connect))
.route("/services", get(services))
.route("/docs", get(docs_index)) .route("/docs", get(docs_index))
.route("/docs/search.json", get(docs_search)) .route("/docs/search.json", get(docs_search))
.route("/docs/:slug", get(docs_page)) .route("/docs/:slug", get(docs_page))

View file

@ -127,6 +127,13 @@ code{font-family:var(--mono);font-size:.9em;background:rgba(18,165,148,.09);colo
.foot-links a{color:#aeb8c0;font-size:.9rem} .foot-links a{color:#aeb8c0;font-size:.9rem}
.foot-links a:hover{color:#fff} .foot-links a:hover{color:#fff}
/* services flow diagram */
.flow{display:flex;align-items:center;gap:.55rem;flex-wrap:wrap;margin:1.3rem 0 .3rem}
.flow .node{background:#fff;border:1px solid var(--line2);border-radius:9px;padding:.45rem .85rem;font-weight:500;font-size:.92rem}
.flow .node.accent{border-color:var(--teal);color:var(--teal-dd);background:var(--teal-t)}
.flow .node.accent2{border-color:#c6b6ec;color:#5a3fb0;background:#f3edfd}
.flow .arrow{color:var(--faint);font-family:var(--mono);font-size:.82rem}
@media(max-width:820px){ @media(max-width:820px){
.hero-in{grid-template-columns:1fr;gap:1.6rem} .hero-in{grid-template-columns:1fr;gap:1.6rem}
.statuscard{max-width:26rem} .statuscard{max-width:26rem}

View file

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}echoIRCd — a from-scratch IRC server in Rust{% endblock %}</title> <title>{% block title %}echoIRCd — a from-scratch IRC server in Rust{% endblock %}</title>
<meta name="description" content="echoIRCd is a modern IRC daemon and services suite, written from scratch in Rust — full IRCv3, modern TLS, SASL."> <meta name="description" content="echoIRCd is a modern IRC daemon written from scratch in Rust — full IRCv3, modern TLS, SASL. Pairs with echo services, a separate package linked over S2S.">
<meta property="og:title" content="echoIRCd"> <meta property="og:title" content="echoIRCd">
<meta property="og:description" content="A from-scratch IRC server, written in Rust."> <meta property="og:description" content="A from-scratch IRC server, written in Rust.">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
@ -29,6 +29,7 @@
<nav class="mainnav"> <nav class="mainnav">
<a href="/"{% if active == "home" %} class="on"{% endif %}>Home</a> <a href="/"{% if active == "home" %} class="on"{% endif %}>Home</a>
<a href="/features"{% if active == "features" %} class="on"{% endif %}>Features</a> <a href="/features"{% if active == "features" %} class="on"{% endif %}>Features</a>
<a href="/services"{% if active == "services" %} class="on"{% endif %}>Services</a>
<a href="/connect"{% if active == "connect" %} class="on"{% endif %}>Connect</a> <a href="/connect"{% if active == "connect" %} class="on"{% endif %}>Connect</a>
<a href="/docs"{% if active == "docs" %} class="on"{% endif %}>Docs</a> <a href="/docs"{% if active == "docs" %} class="on"{% endif %}>Docs</a>
<a href="https://orbit.devtronic.pro">Web&nbsp;client</a> <a href="https://orbit.devtronic.pro">Web&nbsp;client</a>
@ -51,7 +52,7 @@
</svg> </svg>
<span>echoIRCd</span> <span>echoIRCd</span>
</div> </div>
<p class="foot-tag">A native-Rust IRC daemon &amp; services. No forks, no C, no <code>unsafe</code>.</p> <p class="foot-tag">A native-Rust IRC daemon, with <a href="/services">echo services</a> linked over S2S. No forks, no C, no <code>unsafe</code>.</p>
<nav class="foot-links"> <nav class="foot-links">
<a href="https://git.devtronic.pro/echo/echoIRCd">echoIRCd</a> <a href="https://git.devtronic.pro/echo/echoIRCd">echoIRCd</a>
<a href="https://git.devtronic.pro/echo/echo">services</a> <a href="https://git.devtronic.pro/echo/echo">services</a>

View file

@ -57,13 +57,11 @@
</div> </div>
<div class="doc-block"> <div class="doc-block">
<h2>Services</h2> <h2>Accounts &amp; services</h2>
<ul> <p>echoIRCd itself is only a daemon — it provides the account and SASL <em>interface</em> (the
<li><b>NickServ</b> — registration, grouped nicks, certificates, public keys, vhosts, profiles.</li> SASL relay, the account extension, and U-lined service commands). Nickname registration,
<li><b>ChanServ</b> — founder/access, auto-op, akick, topic and mode locks.</li> channels and the rest are handled by <a href="/services">echo&nbsp;services</a>, a separate
<li><b>OperServ · MemoServ · more</b> — network administration, offline messaging, and games.</li> package linked over S2S — not built into the daemon.</p>
<li><b>Event-sourced</b> store; SASL relayed to services mechanism-agnostically.</li>
</ul>
</div> </div>
<div class="doc-block"> <div class="doc-block">

View file

@ -4,10 +4,11 @@
<section class="hero"> <section class="hero">
<div class="hero-in"> <div class="hero-in">
<div class="hero-main"> <div class="hero-main">
<p class="eyebrow">IRC daemon &amp; services · written in Rust</p> <p class="eyebrow">IRC daemon · written in Rust</p>
<h1>The from-scratch<br>IRC server.</h1> <h1>The from-scratch<br>IRC server.</h1>
<p class="hero-sub">echoIRCd is a modern IRC daemon and a full services suite, built from the <p class="hero-sub">echoIRCd is a modern IRC daemon, built from the ground up in Rust — full
ground up in Rust — full IRCv3, modern TLS, and SASL. No forks. No C. No <code>unsafe</code>.</p> IRCv3, modern TLS, and SASL. No forks. No C. No <code>unsafe</code>. Pair it with
<a href="/services">echo services</a>, a separate package linked over S2S.</p>
<div class="cta"> <div class="cta">
<a class="btn btn-primary" href="/connect">Connect to the network</a> <a class="btn btn-primary" href="/connect">Connect to the network</a>
<a class="btn btn-ghost" href="/features">See the features</a> <a class="btn btn-ghost" href="/features">See the features</a>
@ -39,7 +40,7 @@
<div class="fcard"><h3>Full IRCv3</h3><p>server-time, message-tags, account-tag, batch, labeled-response, chathistory, multiline, standard-replies and more.</p></div> <div class="fcard"><h3>Full IRCv3</h3><p>server-time, message-tags, account-tag, batch, labeled-response, chathistory, multiline, standard-replies and more.</p></div>
<div class="fcard"><h3>Modern TLS</h3><p>Direct TLS and WebSocket, OpenSSL &amp; rustls backends, TLS&nbsp;1.3 with post-quantum X25519MLKEM768, per-host SNI.</p></div> <div class="fcard"><h3>Modern TLS</h3><p>Direct TLS and WebSocket, OpenSSL &amp; rustls backends, TLS&nbsp;1.3 with post-quantum X25519MLKEM768, per-host SNI.</p></div>
<div class="fcard"><h3>SASL, incl. ECDSA</h3><p>PLAIN, EXTERNAL, SCRAM-SHA-256, and ECDSA-NIST256P-CHALLENGE — sign a challenge, no password on the wire.</p></div> <div class="fcard"><h3>SASL, incl. ECDSA</h3><p>PLAIN, EXTERNAL, SCRAM-SHA-256, and ECDSA-NIST256P-CHALLENGE — sign a challenge, no password on the wire.</p></div>
<div class="fcard"><h3>Native services</h3><p>NickServ, ChanServ, OperServ, MemoServ and more over S2S — accounts, channels, vhosts, an event-sourced store.</p></div> <div class="fcard"><h3>Companion services</h3><p><a href="/services">echo services</a> is a <b>separate</b> package — NickServ, ChanServ and more — that links to the daemon over S2S. It is not built in.</p></div>
<div class="fcard"><h3>Anti-abuse built in</h3><p>A native security engine: connection &amp; nick-flood detection, behavioral/content heuristics, DEFCON, DNSBL/MX screening.</p></div> <div class="fcard"><h3>Anti-abuse built in</h3><p>A native security engine: connection &amp; nick-flood detection, behavioral/content heuristics, DEFCON, DNSBL/MX screening.</p></div>
</div> </div>
<p class="h-center more"><a href="/features">Everything that's inside &#8594;</a></p> <p class="h-center more"><a href="/features">Everything that's inside &#8594;</a></p>

63
templates/services.html Normal file
View file

@ -0,0 +1,63 @@
{% extends "base.html" %}
{% block title %}Services — echoIRCd{% endblock %}
{% block content %}
<section class="pagehead">
<div class="wrap">
<h1>echo services</h1>
<p>A companion services package that <b>links to echoIRCd over S2S</b>. It is <b>not</b> part of
the daemon — echoIRCd is a pure ircd; the services run as their own program.</p>
</div>
</section>
<section class="wrap doc">
<div class="doc-block">
<h2>Linked, not built in</h2>
<p>echoIRCd is an IRC <em>daemon</em> and nothing more. Accounts, nickname registration and
channel ownership are handled by <b>echo services</b> — a separate Rust program, from its own
repository, that connects to the network as a <b>linked services server</b>.</p>
<p>The split is deliberate: the daemon carries only the account and SASL <em>interface</em> (the
SASL relay, the account extension, and U-lined service commands); the services package holds
the accounts, the logic, and the data.</p>
<div class="flow">
<span class="node">IRC client</span><span class="arrow"></span>
<span class="node accent">echoIRCd</span><span class="arrow">&nbsp;S2S&nbsp;</span>
<span class="node accent2">echo services</span>
</div>
</div>
<div class="doc-block">
<h2>The services</h2>
<div class="cards">
<div class="fcard"><h3>NickServ</h3><p>Register and protect your nick — grouped nicks, certificates, public keys, vhosts, and profiles.</p></div>
<div class="fcard"><h3>ChanServ</h3><p>Channel founders and access lists, auto-op, akick, and topic &amp; mode locks.</p></div>
<div class="fcard"><h3>OperServ</h3><p>Network administration — akills, sessions, and staff tooling.</p></div>
<div class="fcard"><h3>MemoServ</h3><p>Leave messages for accounts while they're offline.</p></div>
</div>
</div>
<div class="doc-block">
<h2>How it fits together</h2>
<ul>
<li>echo services connects to echoIRCd over the <b>server link</b> — the same server-to-server protocol servers use between each other.</li>
<li>The daemon <b>U-lines</b> the services server and names it as the SASL server, so its privileged commands and SASL relay are trusted.</li>
<li>SASL is relayed from the daemon to services <b>mechanism-agnostically</b>, so new mechanisms (like ECDSA) work with no daemon changes.</li>
<li>State is <b>event-sourced</b>: every change is an appended event, replayed to rebuild the database.</li>
</ul>
</div>
<div class="doc-block">
<h2>Run &amp; source</h2>
<p>echo services is its own binary, with its own configuration:</p>
<pre class="code">echo config.toml</pre>
<p>Source: <a href="https://git.devtronic.pro/echo/echo">git.devtronic.pro/echo/echo</a>. See the
docs on <a href="/docs/services">Services</a> and <a href="/docs/linking">Server links</a>.</p>
</div>
</section>
<section class="ctaband">
<div class="wrap ctaband-in">
<div><h2>Register your nick</h2><p class="mono-line">/msg NickServ REGISTER &lt;password&gt; &lt;email&gt;</p></div>
<a class="btn btn-primary" href="/docs/accounts">Accounts &amp; SASL</a>
</div>
</section>
{% endblock %}