initial echo project website (rust + axum + askama)

This commit is contained in:
Jean Chevronnet 2026-08-30 18:21:49 +00:00
commit 899040a319
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
11 changed files with 1480 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

912
Cargo.lock generated Normal file
View file

@ -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"

18
Cargo.toml Normal file
View file

@ -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

22
README.md Normal file
View file

@ -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 <https://echoircd.org>.
## 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).

122
src/main.rs Normal file
View file

@ -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: Template>(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("<h1>404</h1><p>Not found. <a href=\"/\">Back home</a>.</p>"),
)
.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() => {}
}
}

5
static/favicon.svg Normal file
View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#0b0e14"/>
<text x="50%" y="55%" font-family="ui-monospace,monospace" font-size="34" font-weight="700"
fill="#e6edf3" text-anchor="middle" dominant-baseline="middle">e<tspan fill="#3fb950">.</tspan></text>
</svg>

After

Width:  |  Height:  |  Size: 329 B

124
static/style.css Normal file
View file

@ -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}
}

40
templates/base.html Normal file
View file

@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}echoIRCd{% endblock %}</title>
<meta name="description" content="echoIRCd — a modern IRC network built from scratch in Rust: TLS 1.3, full IRCv3, SASL, and native services.">
<meta property="og:title" content="echoIRCd">
<meta property="og:description" content="A modern IRC network, built from scratch in Rust.">
<meta property="og:type" content="website">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header class="nav">
<a class="brand" href="/">echo<span class="dot">IRCd</span></a>
<nav class="navlinks">
<a href="/"{% if active == "home" %} class="on"{% endif %}>Home</a>
<a href="/features"{% if active == "features" %} class="on"{% endif %}>Features</a>
<a href="/connect"{% if active == "connect" %} class="on"{% endif %}>Connect</a>
<a href="https://git.devtronic.pro/echo/echoIRCd" class="ext">Source&nbsp;&#8599;</a>
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer class="foot">
<div class="foot-in">
<div>echoIRCd — native Rust IRC daemon &amp; services · <span class="mono">#![forbid(unsafe_code)]</span></div>
<div class="foot-links">
<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/website">website</a>
</div>
</div>
</footer>
</body>
</html>

58
templates/connect.html Normal file
View file

@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Connect — echoIRCd{% endblock %}
{% block content %}
<section class="page-head">
<h1>Connect</h1>
<p class="lede">Point any IRC client at the network — or open it in your browser.</p>
</section>
<section class="wrap">
<table class="detail">
<tr><th>Server</th><td class="mono">irc.echoircd.org</td></tr>
<tr><th>TLS (recommended)</th><td class="mono">6697</td></tr>
<tr><th>Plaintext</th><td class="mono">6667</td></tr>
<tr><th>Network</th><td class="mono">echoiRCd</td></tr>
</table>
<div class="webchat">
<span>Prefer the browser?</span>
<a class="btn" href="https://orbit.devtronic.pro">Open the web client &#8599;</a>
</div>
<h2 class="section-title">Client quick-start</h2>
<div class="block">
<p>Most clients accept a one-line server string. The leading <span class="mono">+</span> on the
port means TLS.</p>
<pre class="code">/server irc.echoircd.org +6697
/join #echoircd</pre>
<table class="detail">
<tr><th>HexChat</th><td class="mono">Network list → Add → irc.echoircd.org/+6697 → tick "Use SSL"</td></tr>
<tr><th>WeeChat</th><td class="mono">/server add echo irc.echoircd.org/6697 -tls</td></tr>
<tr><th>irssi</th><td class="mono">/connect -tls irc.echoircd.org 6697</td></tr>
<tr><th>mIRC</th><td class="mono">/server irc.echoircd.org +6697</td></tr>
</table>
</div>
<h2 class="section-title">Register your nick</h2>
<div class="block">
<p>Registering lets you keep your nickname, found channels, and log in with SASL.</p>
<pre class="code">/msg NickServ REGISTER &lt;password&gt; &lt;email&gt;
/msg NickServ IDENTIFY &lt;password&gt;</pre>
</div>
<h2 class="section-title">Log in with a key (ECDSA)</h2>
<div class="block">
<p>Instead of a password you can authenticate with a NIST&nbsp;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.</p>
<pre class="code"># 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 &lt;printed-public-key&gt;</pre>
<p class="hint">Point your client's SASL settings at the same key file and pick the
<span class="mono">ECDSA-NIST256P-CHALLENGE</span> mechanism.</p>
</div>
</section>
{% endblock %}

85
templates/features.html Normal file
View file

@ -0,0 +1,85 @@
{% extends "base.html" %}
{% block title %}Features — echoIRCd{% endblock %}
{% block content %}
<section class="page-head">
<h1>Features</h1>
<p class="lede">Everything below is original, native Rust — the InspIRCd protocol and API are a
reference for wire compatibility, never a source of code.</p>
</section>
<section class="wrap prose">
<div class="block">
<h2>The daemon</h2>
<ul>
<li><strong>Safe by construction</strong><span class="mono">#![forbid(unsafe_code)]</span> across the whole tree, enforced in CI.</li>
<li><strong>Reactor core</strong> — a single core thread with a mio/epoll reactor pool; zero-copy broadcast lines and parallel large-channel fan-out.</li>
<li><strong>Full mode set</strong> — every standard channel and user mode, host cloaking, and custom prefixes.</li>
<li><strong>Operators</strong> — an InspIRCd-style privilege model: command grants, named privileges, and a usermode/chanmode allow-list per oper type.</li>
<li><strong>Edge</strong> — connection classes, per-class flood / fakelag, RFC&nbsp;1413 ident, and PROXY protocol v1/v2 (with TLS TLVs).</li>
<li><strong>Observability</strong> — a metrics endpoint, structured JSON logging, and native syslog.</li>
</ul>
</div>
<div class="block">
<h2>Transport &amp; TLS</h2>
<ul>
<li><strong>6697</strong> direct TLS, <strong>7799</strong> WebSocket-over-TLS (wss), <strong>6667</strong> plaintext.</li>
<li>Two interchangeable backends — <strong>OpenSSL</strong> and <strong>rustls</strong> — both serving TLS&nbsp;1.3.</li>
<li>With OpenSSL&nbsp;3.5 the handshake negotiates post-quantum <span class="mono">X25519MLKEM768</span> key exchange.</li>
<li>Per-host <strong>SNI</strong> certificates, reloaded live on rehash.</li>
</ul>
</div>
<div class="block">
<h2>IRCv3</h2>
<p>Capabilities advertised to clients:</p>
<div class="chips">
<span>server-time</span><span>message-tags</span><span>account-tag</span><span>account-notify</span>
<span>extended-join</span><span>chghost</span><span>multi-prefix</span><span>away-notify</span>
<span>invite-notify</span><span>setname</span><span>echo-message</span><span>userhost-in-names</span>
<span>batch</span><span>labeled-response</span><span>standard-replies</span><span>extended-monitor</span>
<span>draft/chathistory</span><span>draft/event-playback</span><span>draft/message-redaction</span>
<span>draft/multiline</span><span>draft/metadata-2</span><span>draft/read-marker</span>
<span>draft/webpush</span><span>draft/account-registration</span><span>sts</span>
</div>
</div>
<div class="block">
<h2>SASL</h2>
<ul>
<li><strong>PLAIN</strong> — classic username/password.</li>
<li><strong>EXTERNAL</strong> — authenticate by TLS client-certificate fingerprint.</li>
<li><strong>SCRAM-SHA-256</strong> — challenge/response, no password on the wire.</li>
<li><strong>ECDSA-NIST256P-CHALLENGE</strong> — sign a server challenge with a NIST&nbsp;P-256 key; the private key never leaves your client.</li>
</ul>
</div>
<div class="block">
<h2>Services</h2>
<ul>
<li><strong>NickServ</strong> — registration, grouped nicks, certificates, public keys, vhosts, profile metadata.</li>
<li><strong>ChanServ</strong> — founder/access, auto-op, akick, topic and mode locks.</li>
<li><strong>OperServ / MemoServ / more</strong> — network administration, offline messaging, and games.</li>
<li><strong>Event-sourced</strong> — every change is an appended event, replayed to rebuild state.</li>
<li><strong>SASL over S2S</strong> — the daemon relays mechanisms mechanism-agnostically; the services layer holds the credentials.</li>
</ul>
</div>
<div class="block">
<h2>Security</h2>
<ul>
<li>A native anti-abuse engine in the core — not a bolt-on module.</li>
<li>Connection-flood and nick-flood detection, mass-join screening.</li>
<li>Behavioral and content heuristics with computed-pattern mining.</li>
<li>A DEFCON state machine and policy, plus DNSBL / MX-blacklist screening via a native async resolver.</li>
</ul>
</div>
</section>
<section class="connect-strip">
<div class="cs-in">
<div><h2>Ready?</h2><p>See how to connect and register.</p></div>
<a class="btn primary" href="/connect">Connect &amp; register</a>
</div>
</section>
{% endblock %}

93
templates/index.html Normal file
View file

@ -0,0 +1,93 @@
{% extends "base.html" %}
{% block title %}echoIRCd — a modern IRC network in Rust{% endblock %}
{% block content %}
<section class="hero">
<div class="badge-row">
<span class="badge">100% safe Rust</span>
<span class="badge">TLS 1.3 · post-quantum</span>
<span class="badge">Full IRCv3</span>
<span class="badge">SASL</span>
</div>
<h1>A modern IRC network,<br>built from scratch in <span class="accent">Rust</span>.</h1>
<p class="lede">echoIRCd is an original IRC daemon and services suite — no forks, no C, no
<span class="mono">unsafe</span>. Full IRCv3, modern TLS, server-to-server linking, and a
complete services stack with accounts, channels, and SASL.</p>
<div class="cta">
<a class="btn primary" href="/connect">Connect now</a>
<a class="btn" href="/features">Explore features</a>
</div>
<div class="connectline mono">/server irc.echoircd.org +6697</div>
</section>
<section class="split">
<div class="card">
<h3>echoIRCd <span class="tag">the daemon</span></h3>
<p>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.</p>
</div>
<div class="card">
<h3>echo <span class="tag">the services</span></h3>
<p>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.</p>
</div>
</section>
<section class="wrap">
<h2 class="section-title">What's inside</h2>
<div class="grid">
<div class="feat">
<div class="ico">&#128274;</div>
<h4>Modern TLS</h4>
<p>Direct TLS on 6697 and WebSocket (wss) on 7799. OpenSSL <em>and</em> rustls backends,
TLS&nbsp;1.3 with post-quantum X25519MLKEM768, and per-host SNI certificates.</p>
</div>
<div class="feat">
<div class="ico">&#9889;</div>
<h4>Full IRCv3</h4>
<p>server-time, message-tags, account-tag, extended-join, batch, labeled-response,
chathistory, multiline, setname, standard-replies, metadata and more.</p>
</div>
<div class="feat">
<div class="ico">&#128273;</div>
<h4>SASL</h4>
<p>PLAIN, EXTERNAL (certificate fingerprint), SCRAM-SHA-256, and
ECDSA-NIST256P-CHALLENGE — sign a challenge with your key, no password on the wire.</p>
</div>
<div class="feat">
<div class="ico">&#129302;</div>
<h4>Services</h4>
<p>Accounts, channels, vhosts, memos and games over a standard S2S link, with SASL relayed
mechanism-agnostically to the services layer.</p>
</div>
<div class="feat">
<div class="ico">&#128737;</div>
<h4>Anti-abuse</h4>
<p>A native security subsystem: connection and nick-flood detection, behavioral and content
heuristics, DEFCON states, and DNSBL / MX screening.</p>
</div>
<div class="feat">
<div class="ico">&#127760;</div>
<h4>Localized</h4>
<p>Server-wide locale catalogs translate numerics and service replies at a single chokepoint.
English stays zero-cost; switch locales on rehash.</p>
</div>
</div>
</section>
<section class="connect-strip">
<div class="cs-in">
<div>
<h2>Jump in.</h2>
<p>Point any IRC client at the network and say hello.</p>
</div>
<table class="mini">
<tr><td>Server</td><td class="mono">irc.echoircd.org</td></tr>
<tr><td>TLS</td><td class="mono">6697</td></tr>
<tr><td>WebSocket</td><td class="mono">7799 (wss)</td></tr>
<tr><td>Network</td><td class="mono">echoiRCd</td></tr>
</table>
</div>
</section>
{% endblock %}