From 899040a319f33b8fb1b18e1bb74527a5911626d5 Mon Sep 17 00:00:00 2001 From: reverse Date: Sun, 30 Aug 2026 18:21:49 +0000 Subject: [PATCH] initial echo project website (rust + axum + askama) --- .gitignore | 1 + Cargo.lock | 912 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 + README.md | 22 + src/main.rs | 122 ++++++ static/favicon.svg | 5 + static/style.css | 124 ++++++ templates/base.html | 40 ++ templates/connect.html | 58 +++ templates/features.html | 85 ++++ templates/index.html | 93 ++++ 11 files changed, 1480 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs create mode 100644 static/favicon.svg create mode 100644 static/style.css create mode 100644 templates/base.html create mode 100644 templates/connect.html create mode 100644 templates/features.html create mode 100644 templates/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e088c5d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,912 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "askama" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28" +dependencies = [ + "askama_derive", + "askama_escape", + "humansize", + "num-traits", + "percent-encoding", +] + +[[package]] +name = "askama_derive" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83" +dependencies = [ + "askama_parser", + "basic-toml", + "mime", + "mime_guess", + "proc-macro2", + "quote", + "serde", + "syn 2.0.119", +] + +[[package]] +name = "askama_escape" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341" + +[[package]] +name = "askama_parser" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0" +dependencies = [ + "nom", +] + +[[package]] +name = "async-compression" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3976abdc8fe7d1133d43d304afd42abdf5bc3e1319d263d223bde07b5efc4be8" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compression-codecs" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" +dependencies = [ + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" + +[[package]] +name = "crc32fast" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "echo-website" +version = "0.1.0" +dependencies = [ + "askama", + "axum", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zlib-rs" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5b81ece --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "echo-website" +version = "0.1.0" +edition = "2021" +description = "The echoIRCd project website — Rust + Axum." +license = "MIT" + +[dependencies] +axum = "0.7" +tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "signal"] } +askama = "0.12" +tower-http = { version = "0.6", features = ["trace", "compression-gzip"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[profile.release] +opt-level = 2 +strip = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..276b7cc --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# echo website + +The [echoIRCd](https://git.devtronic.pro/echo/echoIRCd) project website — Rust + [Axum](https://github.com/tokio-rs/axum), with compile-time [Askama](https://github.com/rinja-rs/askama) templates. Templates and static assets are baked into the binary, so the deployed artifact is self-contained. + +Serves . + +## Run + +```sh +cargo run # http://127.0.0.1:8099 +ECHO_WEB_ADDR=127.0.0.1:9000 cargo run +``` + +## Layout + +- `src/main.rs` — Axum router (`/`, `/features`, `/connect`, `/static/style.css`, `/favicon.svg`, `/health`). +- `templates/` — Askama templates (`base.html` + one per page). +- `static/` — CSS and favicon, embedded via `include_str!`. + +## Deploy + +Built as a release binary and run under `echo-website.service`, reverse-proxied by nginx (TLS terminates at nginx). diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e8a922b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,122 @@ +//! The echoIRCd project website: a small Axum server that renders a handful of +//! compile-time Askama templates. Templates and static assets are baked into the +//! binary, so the deployed artifact is fully self-contained. + +use askama::Template; +use axum::{ + http::{header, StatusCode}, + response::{Html, IntoResponse, Response}, + routing::get, + Router, +}; +use std::net::SocketAddr; +use tower_http::{compression::CompressionLayer, trace::TraceLayer}; + +#[derive(Template)] +#[template(path = "index.html")] +struct IndexTemplate { + active: &'static str, +} + +#[derive(Template)] +#[template(path = "features.html")] +struct FeaturesTemplate { + active: &'static str, +} + +#[derive(Template)] +#[template(path = "connect.html")] +struct ConnectTemplate { + active: &'static str, +} + +/// Render a template to an HTML response, or a 500 if rendering fails. +fn page(t: T) -> Response { + match t.render() { + Ok(body) => Html(body).into_response(), + Err(e) => { + tracing::error!("template render error: {e}"); + (StatusCode::INTERNAL_SERVER_ERROR, "internal error").into_response() + } + } +} + +async fn index() -> Response { + page(IndexTemplate { active: "home" }) +} +async fn features() -> Response { + page(FeaturesTemplate { active: "features" }) +} +async fn connect() -> Response { + page(ConnectTemplate { active: "connect" }) +} + +async fn style() -> impl IntoResponse { + ( + [(header::CONTENT_TYPE, "text/css; charset=utf-8")], + include_str!("../static/style.css"), + ) +} +async fn favicon() -> impl IntoResponse { + ( + [(header::CONTENT_TYPE, "image/svg+xml")], + include_str!("../static/favicon.svg"), + ) +} +async fn health() -> &'static str { + "ok" +} + +async fn not_found() -> Response { + ( + StatusCode::NOT_FOUND, + Html("

404

Not found. Back home.

"), + ) + .into_response() +} + +#[tokio::main] +async fn main() { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "info".into()), + ) + .init(); + + let app = Router::new() + .route("/", get(index)) + .route("/features", get(features)) + .route("/connect", get(connect)) + .route("/static/style.css", get(style)) + .route("/favicon.svg", get(favicon)) + .route("/health", get(health)) + .fallback(not_found) + .layer(CompressionLayer::new()) + .layer(TraceLayer::new_for_http()); + + let addr: SocketAddr = std::env::var("ECHO_WEB_ADDR") + .unwrap_or_else(|_| "127.0.0.1:8099".into()) + .parse() + .expect("ECHO_WEB_ADDR must be a valid socket address"); + + let listener = tokio::net::TcpListener::bind(addr) + .await + .unwrap_or_else(|e| panic!("bind {addr}: {e}")); + tracing::info!("echo website listening on http://{addr}"); + + axum::serve(listener, app) + .with_graceful_shutdown(shutdown()) + .await + .expect("server error"); +} + +/// Resolve on SIGINT (Ctrl-C) or SIGTERM (systemd stop) for a clean shutdown. +async fn shutdown() { + use tokio::signal::unix::{signal, SignalKind}; + let mut term = signal(SignalKind::terminate()).expect("install SIGTERM handler"); + tokio::select! { + _ = tokio::signal::ctrl_c() => {} + _ = term.recv() => {} + } +} diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..7d0bcf3 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,5 @@ + + + e. + diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..2abe1c9 --- /dev/null +++ b/static/style.css @@ -0,0 +1,124 @@ +:root{ + --bg:#0b0e14; --bg2:#11161f; --bg3:#161c27; + --line:#222b38; --line2:#2b3646; + --fg:#c8d3e0; --fg2:#7d8896; --hi:#e6edf3; + --accent:#3fb950; --accent2:#2ea043; --link:#58a6ff; + --radius:12px; --wrap:1040px; + color-scheme:dark; +} +*{box-sizing:border-box} +html{scroll-behavior:smooth} +body{ + margin:0; background:var(--bg); color:var(--fg); + font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; + -webkit-font-smoothing:antialiased; +} +.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace} +a{color:var(--link);text-decoration:none} +a:hover{text-decoration:underline} +h1,h2,h3,h4{color:var(--hi);line-height:1.2} +.accent{color:var(--accent)} +main{min-height:60vh} +.wrap,.hero,.split,.page-head,.cs-in{max-width:var(--wrap);margin:0 auto;padding-left:1.25rem;padding-right:1.25rem} + +/* nav */ +.nav{ + display:flex;align-items:center;justify-content:space-between;gap:1rem; + max-width:var(--wrap);margin:0 auto;padding:1.1rem 1.25rem; +} +.brand{font-family:ui-monospace,monospace;font-weight:700;font-size:1.25rem;color:var(--hi)} +.brand:hover{text-decoration:none} +.brand .dot{color:var(--accent)} +.navlinks{display:flex;gap:1.4rem;align-items:center;flex-wrap:wrap} +.navlinks a{color:var(--fg2);font-size:.95rem} +.navlinks a:hover{color:var(--hi);text-decoration:none} +.navlinks a.on{color:var(--hi)} +.navlinks a.ext{color:var(--fg2)} + +/* hero */ +.hero{padding:3.5rem 1.25rem 2rem;text-align:center} +.badge-row{display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap;margin-bottom:1.6rem} +.badge{ + font-size:.78rem;color:var(--accent);background:rgba(63,185,80,.08); + border:1px solid rgba(63,185,80,.28);border-radius:999px;padding:.25rem .7rem; +} +.hero h1{font-size:clamp(2.1rem,5vw,3.4rem);letter-spacing:-1.5px;margin:.2rem 0 1rem} +.lede{color:var(--fg2);font-size:1.12rem;max-width:640px;margin:0 auto 1.8rem} +.cta{display:flex;gap:.8rem;justify-content:center;flex-wrap:wrap} +.btn{ + display:inline-block;padding:.65rem 1.2rem;border-radius:8px;font-weight:600; + border:1px solid var(--line2);color:var(--hi);background:var(--bg2); +} +.btn:hover{border-color:#3a475a;text-decoration:none} +.btn.primary{background:var(--accent2);border-color:var(--accent);color:#04120a} +.btn.primary:hover{background:var(--accent)} +.connectline{ + margin:1.8rem auto 0;max-width:max-content;color:var(--accent); + background:var(--bg2);border:1px solid var(--line);border-radius:8px;padding:.55rem 1rem;font-size:.95rem; +} + +/* split cards */ +.split{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-top:1.5rem} +.card{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius);padding:1.4rem 1.5rem} +.card h3{margin:0 0 .5rem;font-size:1.25rem} +.card p{margin:0;color:var(--fg2)} +.tag{font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:var(--fg2); + border:1px solid var(--line2);border-radius:6px;padding:.1rem .45rem;margin-left:.4rem;vertical-align:middle} + +/* feature grid */ +.wrap{padding-top:3.5rem;padding-bottom:1rem} +.section-title{text-align:center;font-size:1.6rem;margin:0 0 2rem} +.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:1rem} +.feat{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius);padding:1.3rem 1.4rem} +.feat .ico{font-size:1.5rem;margin-bottom:.5rem} +.feat h4{margin:.1rem 0 .5rem;font-size:1.08rem} +.feat p{margin:0;color:var(--fg2);font-size:.95rem} +.feat em{color:var(--fg);font-style:normal;font-weight:600} + +/* connect strip */ +.connect-strip{margin:4rem 0 0;background:linear-gradient(180deg,var(--bg2),var(--bg3));border-top:1px solid var(--line)} +.cs-in{display:flex;align-items:center;justify-content:space-between;gap:1.5rem;padding:2.4rem 1.25rem;flex-wrap:wrap} +.cs-in h2{margin:0 0 .2rem;font-size:1.5rem} +.cs-in p{margin:0;color:var(--fg2)} +.mini{border-collapse:collapse;font-size:.95rem} +.mini td{padding:.28rem .9rem;border-bottom:1px solid var(--line)} +.mini td:first-child{color:var(--fg2)} + +/* pages */ +.page-head{padding:3rem 1.25rem 1rem;text-align:center} +.page-head h1{font-size:2.4rem;letter-spacing:-1px;margin:0 0 .6rem} +.prose .block{margin:0 0 2.2rem} +.block h2{font-size:1.35rem;border-bottom:1px solid var(--line);padding-bottom:.5rem;margin:0 0 .9rem} +.block ul{margin:.4rem 0 0;padding-left:1.1rem} +.block li{margin:.35rem 0;color:var(--fg2)} +.block li strong{color:var(--fg)} + +/* chips */ +.chips{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:.6rem} +.chips span{ + font-family:ui-monospace,monospace;font-size:.8rem;color:var(--fg); + background:var(--bg3);border:1px solid var(--line);border-radius:6px;padding:.2rem .5rem; +} + +/* tables + code on connect page */ +.detail{width:100%;border-collapse:collapse;margin:.5rem 0 1rem} +.detail th{text-align:left;color:var(--fg2);font-weight:500;padding:.5rem .9rem;border-bottom:1px solid var(--line);white-space:nowrap;vertical-align:top} +.detail td{padding:.5rem .9rem;border-bottom:1px solid var(--line);color:var(--fg)} +.code{ + background:var(--bg2);border:1px solid var(--line);border-radius:8px; + padding:1rem 1.1rem;overflow-x:auto;color:var(--accent); + font-family:ui-monospace,monospace;font-size:.9rem;line-height:1.7;white-space:pre; +} +.hint{color:var(--fg2);font-size:.92rem} +.webchat{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;margin:1.5rem 0 0;color:var(--fg2)} + +/* footer */ +.foot{border-top:1px solid var(--line);margin-top:4rem} +.foot-in{max-width:var(--wrap);margin:0 auto;padding:1.6rem 1.25rem;display:flex;justify-content:space-between;gap:1rem;flex-wrap:wrap;color:var(--fg2);font-size:.9rem} +.foot .mono{color:var(--fg)} +.foot-links{display:flex;gap:1.1rem} + +@media(max-width:760px){ + .split{grid-template-columns:1fr} + .grid{grid-template-columns:1fr} +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..3e245c0 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,40 @@ + + + + + +{% block title %}echoIRCd{% endblock %} + + + + + + + + + + +
+{% block content %}{% endblock %} +
+ +
+
+
echoIRCd — native Rust IRC daemon & services · #![forbid(unsafe_code)]
+ +
+
+ + diff --git a/templates/connect.html b/templates/connect.html new file mode 100644 index 0000000..0efb687 --- /dev/null +++ b/templates/connect.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} +{% block title %}Connect — echoIRCd{% endblock %} +{% block content %} +
+

Connect

+

Point any IRC client at the network — or open it in your browser.

+
+ +
+ + + + + +
Serverirc.echoircd.org
TLS (recommended)6697
Plaintext6667
NetworkechoiRCd
+ +
+ Prefer the browser? + Open the web client ↗ +
+ +

Client quick-start

+
+

Most clients accept a one-line server string. The leading + on the + port means TLS.

+
/server irc.echoircd.org +6697
+/join #echoircd
+ + + + + +
HexChatNetwork list → Add → irc.echoircd.org/+6697 → tick "Use SSL"
WeeChat/server add echo irc.echoircd.org/6697 -tls
irssi/connect -tls irc.echoircd.org 6697
mIRC/server irc.echoircd.org +6697
+
+ +

Register your nick

+
+

Registering lets you keep your nickname, found channels, and log in with SASL.

+
/msg NickServ REGISTER <password> <email>
+/msg NickServ IDENTIFY <password>
+
+ +

Log in with a key (ECDSA)

+
+

Instead of a password you can authenticate with a NIST P-256 key. The server sends a + challenge, your client signs it, and the signature is checked against the key on your + account — nothing secret crosses the wire.

+
# generate a key and read its public half
+ecdsatool keygen ~/.ecdsa.pem
+ecdsatool pubkey ~/.ecdsa.pem
+
+# store it on your account, then use ECDSA-NIST256P-CHALLENGE for SASL
+/msg NickServ SET PUBKEY <printed-public-key>
+

Point your client's SASL settings at the same key file and pick the + ECDSA-NIST256P-CHALLENGE mechanism.

+
+
+{% endblock %} diff --git a/templates/features.html b/templates/features.html new file mode 100644 index 0000000..be9012d --- /dev/null +++ b/templates/features.html @@ -0,0 +1,85 @@ +{% extends "base.html" %} +{% block title %}Features — echoIRCd{% endblock %} +{% block content %} +
+

Features

+

Everything below is original, native Rust — the InspIRCd protocol and API are a + reference for wire compatibility, never a source of code.

+
+ +
+
+

The daemon

+
    +
  • Safe by construction#![forbid(unsafe_code)] across the whole tree, enforced in CI.
  • +
  • Reactor core — a single core thread with a mio/epoll reactor pool; zero-copy broadcast lines and parallel large-channel fan-out.
  • +
  • Full mode set — every standard channel and user mode, host cloaking, and custom prefixes.
  • +
  • Operators — an InspIRCd-style privilege model: command grants, named privileges, and a usermode/chanmode allow-list per oper type.
  • +
  • Edge — connection classes, per-class flood / fakelag, RFC 1413 ident, and PROXY protocol v1/v2 (with TLS TLVs).
  • +
  • Observability — a metrics endpoint, structured JSON logging, and native syslog.
  • +
+
+ +
+

Transport & TLS

+
    +
  • 6697 direct TLS, 7799 WebSocket-over-TLS (wss), 6667 plaintext.
  • +
  • Two interchangeable backends — OpenSSL and rustls — both serving TLS 1.3.
  • +
  • With OpenSSL 3.5 the handshake negotiates post-quantum X25519MLKEM768 key exchange.
  • +
  • Per-host SNI certificates, reloaded live on rehash.
  • +
+
+ +
+

IRCv3

+

Capabilities advertised to clients:

+
+ server-timemessage-tagsaccount-tagaccount-notify + extended-joinchghostmulti-prefixaway-notify + invite-notifysetnameecho-messageuserhost-in-names + batchlabeled-responsestandard-repliesextended-monitor + draft/chathistorydraft/event-playbackdraft/message-redaction + draft/multilinedraft/metadata-2draft/read-marker + draft/webpushdraft/account-registrationsts +
+
+ +
+

SASL

+
    +
  • PLAIN — classic username/password.
  • +
  • EXTERNAL — authenticate by TLS client-certificate fingerprint.
  • +
  • SCRAM-SHA-256 — challenge/response, no password on the wire.
  • +
  • ECDSA-NIST256P-CHALLENGE — sign a server challenge with a NIST P-256 key; the private key never leaves your client.
  • +
+
+ +
+

Services

+
    +
  • NickServ — registration, grouped nicks, certificates, public keys, vhosts, profile metadata.
  • +
  • ChanServ — founder/access, auto-op, akick, topic and mode locks.
  • +
  • OperServ / MemoServ / more — network administration, offline messaging, and games.
  • +
  • Event-sourced — every change is an appended event, replayed to rebuild state.
  • +
  • SASL over S2S — the daemon relays mechanisms mechanism-agnostically; the services layer holds the credentials.
  • +
+
+ +
+

Security

+
    +
  • A native anti-abuse engine in the core — not a bolt-on module.
  • +
  • Connection-flood and nick-flood detection, mass-join screening.
  • +
  • Behavioral and content heuristics with computed-pattern mining.
  • +
  • A DEFCON state machine and policy, plus DNSBL / MX-blacklist screening via a native async resolver.
  • +
+
+
+ +
+
+

Ready?

See how to connect and register.

+ Connect & register +
+
+{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..30a6b36 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} +{% block title %}echoIRCd — a modern IRC network in Rust{% endblock %} +{% block content %} +
+
+ 100% safe Rust + TLS 1.3 · post-quantum + Full IRCv3 + SASL +
+

A modern IRC network,
built from scratch in Rust.

+

echoIRCd is an original IRC daemon and services suite — no forks, no C, no + unsafe. Full IRCv3, modern TLS, server-to-server linking, and a + complete services stack with accounts, channels, and SASL.

+ +
/server irc.echoircd.org +6697
+
+ +
+
+

echoIRCd the daemon

+

A single-threaded core with an epoll reactor pool and a module-per-file architecture. + The full channel and user mode set, host cloaking, connection classes, PROXY protocol, + ident, and an InspIRCd-style operator privilege system.

+
+
+

echo the services

+

NickServ, ChanServ, OperServ, MemoServ and more, linked over a standard server-to-server + protocol. Account registration, vhosts, grouped nicks, memos and SASL — all backed by an + event-sourced store.

+
+
+ +
+

What's inside

+
+
+
🔒
+

Modern TLS

+

Direct TLS on 6697 and WebSocket (wss) on 7799. OpenSSL and rustls backends, + TLS 1.3 with post-quantum X25519MLKEM768, and per-host SNI certificates.

+
+
+
+

Full IRCv3

+

server-time, message-tags, account-tag, extended-join, batch, labeled-response, + chathistory, multiline, setname, standard-replies, metadata and more.

+
+
+
🔑
+

SASL

+

PLAIN, EXTERNAL (certificate fingerprint), SCRAM-SHA-256, and + ECDSA-NIST256P-CHALLENGE — sign a challenge with your key, no password on the wire.

+
+
+
🤖
+

Services

+

Accounts, channels, vhosts, memos and games over a standard S2S link, with SASL relayed + mechanism-agnostically to the services layer.

+
+
+
🛡
+

Anti-abuse

+

A native security subsystem: connection and nick-flood detection, behavioral and content + heuristics, DEFCON states, and DNSBL / MX screening.

+
+
+
🌐
+

Localized

+

Server-wide locale catalogs translate numerics and service replies at a single chokepoint. + English stays zero-cost; switch locales on rehash.

+
+
+
+ +
+
+
+

Jump in.

+

Point any IRC client at the network and say hello.

+
+ + + + + +
Serverirc.echoircd.org
TLS6697
WebSocket7799 (wss)
NetworkechoiRCd
+
+
+{% endblock %}