diff --git a/.env b/.env
index c7d64d4..a9465f8 100644
--- a/.env
+++ b/.env
@@ -1,5 +1,11 @@
HOSTNAME=localhost:8079
PORT=8079
+HTTPS=false
+DEBUG=true
RESTRICTED_MODE=true
+VALIDATE_SIGNATURES=false
API_TOKEN=kjsdhfkwjenrkajhsdakjsnd
+FOOTER_BLURB="Opéré par @max"
+LOCAL_DOMAINS="xolus.net"
+LOCAL_BLURB="
Relais ActivityPub francophone
"
# OPENTELEMETRY_URL=http://localhost:4317
diff --git a/Cargo.lock b/Cargo.lock
index e38d3f2..5bd9064 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -63,14 +63,17 @@ dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
+ "actix-tls",
"actix-utils",
"ahash",
"base64",
"bitflags",
+ "brotli",
"bytes",
"bytestring",
"derive_more",
"encoding_rs",
+ "flate2",
"futures-core",
"h2",
"http",
@@ -192,6 +195,7 @@ dependencies = [
"actix-rt",
"actix-server",
"actix-service",
+ "actix-tls",
"actix-utils",
"ahash",
"bytes",
@@ -251,13 +255,28 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "0.7.19"
+version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
[[package]]
name = "ammonia"
version = "3.2.1"
@@ -279,7 +298,7 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "ap-relay"
-version = "0.3.50"
+version = "0.3.66"
dependencies = [
"activitystreams",
"activitystreams-ext",
@@ -300,13 +319,20 @@ dependencies = [
"futures-util",
"http-signature-normalization-actix",
"lru",
+ "metrics",
+ "metrics-util",
"mime",
+ "minify-html",
"opentelemetry",
"opentelemetry-otlp",
+ "pin-project-lite",
+ "quanta",
"rand",
"rsa",
"rsa-magic-public-key",
"ructe",
+ "rustls",
+ "rustls-pemfile",
"serde",
"serde_json",
"sha2",
@@ -346,6 +372,12 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
+[[package]]
+name = "arrayvec"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+
[[package]]
name = "async-mutex"
version = "1.4.0"
@@ -485,9 +517,9 @@ dependencies = [
[[package]]
name = "background-jobs"
-version = "0.13.0"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "793a813f9145c5f3a27b8dcd834c0927de68bbd60d53a369e5894f3cc5759020"
+checksum = "62dc7cfc967d6714768097a876ca2941a54a26976c6d3c95ea6da48974890970"
dependencies = [
"background-jobs-actix",
"background-jobs-core",
@@ -495,15 +527,16 @@ dependencies = [
[[package]]
name = "background-jobs-actix"
-version = "0.13.1"
+version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47263ad9c5679419347dae655c2fa2cba078b0eaa51ac758d4f0e9690c06910b"
+checksum = "99f8bfe0a984c8d0bc7e67b376cc05e0b9015fdd3ee878900046120ef781c47e"
dependencies = [
"actix-rt",
"anyhow",
"async-mutex",
"async-trait",
"background-jobs-core",
+ "metrics",
"serde",
"serde_json",
"thiserror",
@@ -515,14 +548,15 @@ dependencies = [
[[package]]
name = "background-jobs-core"
-version = "0.13.0"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48e78e842fe2ae461319e3d1843c12e301630e65650332b02032ac70b0dfc66f"
+checksum = "1274e49ae8eff1fc6b4943660e59ce2f2e13e65a23a707924a50a40c7b94fc4d"
dependencies = [
"actix-rt",
"anyhow",
"async-trait",
"event-listener",
+ "metrics",
"serde",
"serde_json",
"thiserror",
@@ -562,6 +596,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+[[package]]
+name = "bitvec"
+version = "0.19.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
[[package]]
name = "block-buffer"
version = "0.10.3"
@@ -581,6 +627,27 @@ dependencies = [
"cipher",
]
+[[package]]
+name = "brotli"
+version = "3.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
[[package]]
name = "bumpalo"
version = "3.11.1"
@@ -601,9 +668,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
-version = "1.2.1"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "bytestring"
@@ -616,9 +683,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.76"
+version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
+checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cfg-if"
@@ -692,7 +759,7 @@ dependencies = [
"async-trait",
"json5",
"lazy_static",
- "nom",
+ "nom 7.1.1",
"pathdiff",
"ron",
"rust-ini",
@@ -780,9 +847,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.11"
+version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
+checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
@@ -793,9 +860,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.12"
+version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
@@ -810,6 +877,17 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "css-minify"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692b185e3b7c9af96b3195f3021f53a931d896968ed2ad3fb1cdb6558b30c9ab"
+dependencies = [
+ "derive_more",
+ "indexmap",
+ "nom 6.1.2",
+]
+
[[package]]
name = "darling"
version = "0.13.4"
@@ -929,6 +1007,12 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "endian-type"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
+
[[package]]
name = "erasable"
version = "1.2.1"
@@ -995,6 +1079,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "funty"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
+
[[package]]
name = "futf"
version = "0.1.5"
@@ -1121,7 +1211,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@@ -1161,7 +1251,7 @@ dependencies = [
"base64",
"byteorder",
"flate2",
- "nom",
+ "nom 7.1.1",
"num-traits",
]
@@ -1233,9 +1323,9 @@ dependencies = [
[[package]]
name = "http-signature-normalization-actix"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86dfd54a1764ad79376b8dbf29e5bf918a463eb5ec66c90cd0388508289af6f0"
+checksum = "7483d0ee4d093fa4bfe5956cd405492c07808a5064a29cfe3960d474f21f39c2"
dependencies = [
"actix-http",
"actix-rt",
@@ -1437,6 +1527,19 @@ dependencies = [
"spin",
]
+[[package]]
+name = "lexical-core"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
+dependencies = [
+ "arrayvec",
+ "bitflags",
+ "cfg-if",
+ "ryu",
+ "static_assertions",
+]
+
[[package]]
name = "libc"
version = "0.2.137"
@@ -1507,6 +1610,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
+[[package]]
+name = "mach"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "maplit"
version = "1.0.2"
@@ -1556,13 +1668,56 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
-version = "0.6.5"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
+[[package]]
+name = "metrics"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849"
+dependencies = [
+ "ahash",
+ "metrics-macros",
+ "portable-atomic",
+]
+
+[[package]]
+name = "metrics-macros"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "metrics-util"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d24dc2dbae22bff6f1f9326ffce828c9f07ef9cc1e8002e5279f845432a30a"
+dependencies = [
+ "aho-corasick",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "hashbrown",
+ "indexmap",
+ "metrics",
+ "num_cpus",
+ "ordered-float",
+ "parking_lot 0.12.1",
+ "portable-atomic",
+ "quanta",
+ "radix_trie",
+ "sketches-ddsketch",
+]
+
[[package]]
name = "mime"
version = "0.3.16"
@@ -1579,6 +1734,29 @@ dependencies = [
"unicase",
]
+[[package]]
+name = "minify-html"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58f84854d62363972a73c3d8331b85a479366a0871a83f2a01ac11b9ba787c10"
+dependencies = [
+ "aho-corasick",
+ "css-minify",
+ "lazy_static",
+ "memchr",
+ "minify-js",
+]
+
+[[package]]
+name = "minify-js"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe033709f5a1159736cf7e22748518ffb75af26f3a6264d52ecc8bb38c68c36"
+dependencies = [
+ "lazy_static",
+ "parse-js",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -1602,7 +1780,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
- "wasi",
+ "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
@@ -1624,6 +1802,28 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+[[package]]
+name = "nibble_vec"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
+dependencies = [
+ "smallvec",
+]
+
+[[package]]
+name = "nom"
+version = "6.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
+dependencies = [
+ "bitvec",
+ "funty",
+ "lexical-core",
+ "memchr",
+ "version_check",
+]
+
[[package]]
name = "nom"
version = "7.1.1"
@@ -1642,7 +1842,7 @@ checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605"
dependencies = [
"bytecount",
"memchr",
- "nom",
+ "nom 7.1.1",
]
[[package]]
@@ -1822,6 +2022,15 @@ dependencies = [
"tokio-stream",
]
+[[package]]
+name = "ordered-float"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ordered-multimap"
version = "0.4.3"
@@ -1834,9 +2043,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
-version = "6.4.0"
+version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "overload"
@@ -1892,6 +2101,17 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "parse-js"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66bb85ec60d22b9e6d4adac1e3dbdaf3903a4485f476c5f4dd7ed1285cbf4dad"
+dependencies = [
+ "aho-corasick",
+ "lazy_static",
+ "memchr",
+]
+
[[package]]
name = "paste"
version = "1.0.9"
@@ -2065,6 +2285,12 @@ dependencies = [
"spki",
]
+[[package]]
+name = "portable-atomic"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15eb2c6e362923af47e13c23ca5afb859e83d54452c55b0b9ac763b8f7c1ac16"
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -2175,6 +2401,22 @@ dependencies = [
"prost",
]
+[[package]]
+name = "quanta"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e31331286705f455e56cca62e0e717158474ff02b7936c1fa596d983f4ae27"
+dependencies = [
+ "crossbeam-utils",
+ "libc",
+ "mach",
+ "once_cell",
+ "raw-cpuid",
+ "wasi 0.10.2+wasi-snapshot-preview1",
+ "web-sys",
+ "winapi",
+]
+
[[package]]
name = "quote"
version = "1.0.21"
@@ -2184,6 +2426,22 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "radium"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
+
+[[package]]
+name = "radix_trie"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
+dependencies = [
+ "endian-type",
+ "nibble_vec",
+]
+
[[package]]
name = "rand"
version = "0.8.5"
@@ -2214,6 +2472,15 @@ dependencies = [
"getrandom",
]
+[[package]]
+name = "raw-cpuid"
+version = "10.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "rc-box"
version = "1.2.0"
@@ -2376,7 +2643,7 @@ dependencies = [
"arc-swap",
"fastrand",
"lazy_static",
- "nom",
+ "nom 7.1.1",
"nom_locate",
"num-bigint",
"num-integer",
@@ -2396,7 +2663,7 @@ dependencies = [
"itertools 0.10.5",
"md5",
"mime",
- "nom",
+ "nom 7.1.1",
"rsass",
]
@@ -2490,9 +2757,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.88"
+version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7"
+checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [
"itoa",
"ryu",
@@ -2579,6 +2846,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
+[[package]]
+name = "sketches-ddsketch"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceb945e54128e09c43d8e4f1277851bd5044c6fc540bbaa2ad888f60b3da9ae7"
+
[[package]]
name = "slab"
version = "0.4.7"
@@ -2636,6 +2909,12 @@ dependencies = [
"der",
]
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
[[package]]
name = "string_cache"
version = "0.8.4"
@@ -2703,6 +2982,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
[[package]]
name = "teloxide"
version = "0.11.2"
@@ -3287,6 +3572,12 @@ dependencies = [
"try-lock",
]
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -3496,6 +3787,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "wyz"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
+
[[package]]
name = "yaml-rust"
version = "0.4.5"
diff --git a/Cargo.toml b/Cargo.toml
index dcddf46..fbc9e5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,7 @@
[package]
name = "ap-relay"
description = "A simple activitypub relay"
-version = "0.3.50"
+version = "0.3.66"
authors = ["asonix "]
license = "AGPL-3.0"
readme = "README.md"
@@ -23,7 +23,11 @@ default = []
[dependencies]
anyhow = "1.0"
actix-rt = "2.7.0"
-actix-web = { version = "4.0.1", default-features = false }
+actix-web = { version = "4.0.1", default-features = false, features = [
+ "rustls",
+ "compress-brotli",
+ "compress-gzip",
+] }
actix-webfinger = "0.4.0"
activitystreams = "0.7.0-alpha.19"
activitystreams-ext = "0.1.0-alpha.2"
@@ -38,12 +42,19 @@ dashmap = "5.1.0"
dotenv = "0.15.0"
futures-util = "0.3.17"
lru = "0.8.0"
+metrics = "0.20.1"
+metrics-util = "0.14.0"
mime = "0.3.16"
+minify-html = "0.10.0"
opentelemetry = { version = "0.18", features = ["rt-tokio"] }
opentelemetry-otlp = "0.11"
+pin-project-lite = "0.2.9"
+quanta = "0.10.1"
rand = "0.8"
rsa = "0.7"
rsa-magic-public-key = "0.6.0"
+rustls = "0.20.7"
+rustls-pemfile = "1.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = { version = "0.10", features = ["oid"] }
@@ -70,7 +81,7 @@ tokio = { version = "1", features = ["macros", "sync"] }
uuid = { version = "1", features = ["v4", "serde"] }
[dependencies.background-jobs]
-version = "0.13.0"
+version = "0.14.0"
default-features = false
features = ["background-jobs-actix", "error-logging"]
diff --git a/README.md b/README.md
index 1506ca2..64d873e 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ $ sudo docker run --rm -it \
-e ADDR=0.0.0.0 \
-e SLED_PATH=/mnt/sled/db-0.34 \
-p 8080:8080 \
- asonix/relay:0.3.23
+ asonix/relay:0.3.52
```
This will launch the relay with the database stored in "./sled/db-0.34" and listening on port 8080
#### Cargo
@@ -98,6 +98,11 @@ API_TOKEN=somepasswordishtoken
OPENTELEMETRY_URL=localhost:4317
TELEGRAM_TOKEN=secret
TELEGRAM_ADMIN_HANDLE=your_handle
+TLS_KEY=/path/to/key
+TLS_CERT=/path/to/cert
+FOOTER_BLURB="Contact @asonix for inquiries"
+LOCAL_DOMAINS=masto.asonix.dog
+LOCAL_BLURB="Welcome to my cool relay where I have cool relay things happening. I hope you enjoy your stay!
"
```
#### Descriptions
@@ -112,15 +117,15 @@ Whether to print incoming activities to the console when requests hit the /inbox
##### `RESTRICTED_MODE`
This setting enables an 'allowlist' setup where only servers that have been explicitly enabled through the `relay -a` command can join the relay. This is `false` by default. If `RESTRICTED_MODE` is not enabled, then manually allowing domains with `relay -a` has no effect.
##### `VALIDATE_SIGNATURES`
-This setting enforces checking HTTP signatures on incoming activities. It defaults to `false` but should be set to `true` in production scenarios
+This setting enforces checking HTTP signatures on incoming activities. It defaults to `true`
##### `HTTPS`
-Whether the current server is running on an HTTPS port or not. This is used for generating URLs to the current running relay. By default it is set to `false`, but should be `true` in production scenarios.
+Whether the current server is running on an HTTPS port or not. This is used for generating URLs to the current running relay. By default it is set to `true`
##### `PUBLISH_BLOCKS`
Whether or not to publish a list of blocked domains in the `nodeinfo` metadata for the server. It defaults to `false`.
##### `SLED_PATH`
Where to store the on-disk database of connected servers. This defaults to `./sled/db-0.34`.
##### `RUST_LOG`
-The log level to print. Available levels are `ERROR`, `WARN`, `INFO`, `DEBUG`, and `TRACE`. You can also specify module paths to enable some logs but not others, such as `RUST_LOG=warn,tracing_actix_web=info,relay=info`
+The log level to print. Available levels are `ERROR`, `WARN`, `INFO`, `DEBUG`, and `TRACE`. You can also specify module paths to enable some logs but not others, such as `RUST_LOG=warn,tracing_actix_web=info,relay=info`. This defaults to `warn`
##### `SOURCE_REPO`
The URL to the source code for the relay. This defaults to `https://git.asonix.dog/asonix/relay`, but should be changed if you're running a fork hosted elsewhere.
##### `API_TOKEN`
@@ -131,6 +136,16 @@ A URL for exporting opentelemetry spans. This is mostly useful for debugging. Th
A Telegram Bot Token for running the relay administration bot. There is no default.
##### `TELEGRAM_ADMIN_HANDLE`
The handle of the telegram user allowed to administer the relay. There is no default.
+##### `TLS_KEY`
+Optional - This is specified if you are running the relay directly on the internet and have a TLS key to provide HTTPS for your relay
+##### `TLS_CERT`
+Optional - This is specified if you are running the relay directly on the internet and have a TLS certificate chain to provide HTTPS for your relay
+##### `FOOTER_BLURB`
+Optional - Add custom notes in the footer of the page
+##### `LOCAL_DOMAINS`
+Optional - domains of mastodon servers run by the same admin as the relay
+##### `LOCAL_BLURB`
+Optional - description for the relay
### Subscribing
Mastodon admins can subscribe to this relay by adding the `/inbox` route to their relay settings.
diff --git a/scss/index.scss b/scss/index.scss
index b9f7fcf..b7e85bc 100644
--- a/scss/index.scss
+++ b/scss/index.scss
@@ -41,7 +41,7 @@ header {
}
}
-section {
+article {
background-color: #fff;
color: #333;
border: 1px solid #e5e5e5;
@@ -51,8 +51,16 @@ section {
max-width: 700px;
padding-bottom: 32px;
- > p:first-child {
- margin-top: 0;
+ section {
+ border-bottom: 1px solid #e5e5e5;
+
+ > h4:first-child,
+ > p:first-child {
+ margin-top: 0;
+ }
+ > p:last-child {
+ margin-bottom: 0;
+ }
}
h3 {
@@ -67,13 +75,13 @@ section {
li {
padding-top: 36px;
- border-bottom: 1px solid #e5e5e5;
}
.padded {
padding: 0 24px;
}
+ .local-explainer,
.joining {
padding: 24px;
}
@@ -174,9 +182,11 @@ footer {
li {
padding: 0;
- border-bottom: none;
}
}
+ article section {
+ border-bottom: none;
+ }
}
}
@@ -241,7 +251,7 @@ footer {
padding: 24px;
}
- section {
+ article {
border-left: none;
border-right: none;
border-radius: 0;
diff --git a/src/admin/client.rs b/src/admin/client.rs
index f63151f..3602487 100644
--- a/src/admin/client.rs
+++ b/src/admin/client.rs
@@ -1,5 +1,6 @@
use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
+ collector::Snapshot,
config::{AdminUrlKind, Config},
error::{Error, ErrorKind},
};
@@ -50,6 +51,10 @@ pub(crate) async fn connected(client: &Client, config: &Config) -> Result Result {
+ get_results(client, config, AdminUrlKind::Stats).await
+}
+
async fn get_results(
client: &Client,
config: &Config,
diff --git a/src/admin/routes.rs b/src/admin/routes.rs
index c33efca..6578bfd 100644
--- a/src/admin/routes.rs
+++ b/src/admin/routes.rs
@@ -1,9 +1,13 @@
use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
+ collector::{MemoryCollector, Snapshot},
error::Error,
extractors::Admin,
};
-use actix_web::{web::Json, HttpResponse};
+use actix_web::{
+ web::{Data, Json},
+ HttpResponse,
+};
pub(crate) async fn allow(
admin: Admin,
@@ -58,3 +62,10 @@ pub(crate) async fn connected(admin: Admin) -> Result, Err
Ok(Json(ConnectedActors { connected_actors }))
}
+
+pub(crate) async fn stats(
+ _admin: Admin,
+ collector: Data,
+) -> Result, Error> {
+ Ok(Json(collector.snapshot()))
+}
diff --git a/src/args.rs b/src/args.rs
index 6b6c054..18a4059 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -14,11 +14,14 @@ pub(crate) struct Args {
#[arg(short, long, help = "List allowed and blocked domains")]
list: bool,
+
+ #[arg(short, long, help = "Get statistics from the server")]
+ stats: bool,
}
impl Args {
pub(crate) fn any(&self) -> bool {
- !self.blocks.is_empty() || !self.allowed.is_empty() || self.list
+ !self.blocks.is_empty() || !self.allowed.is_empty() || self.list || self.stats
}
pub(crate) fn new() -> Self {
@@ -40,4 +43,8 @@ impl Args {
pub(crate) fn list(&self) -> bool {
self.list
}
+
+ pub(crate) fn stats(&self) -> bool {
+ self.stats
+ }
}
diff --git a/src/collector.rs b/src/collector.rs
new file mode 100644
index 0000000..0d3536d
--- /dev/null
+++ b/src/collector.rs
@@ -0,0 +1,414 @@
+use metrics::{Key, Recorder, SetRecorderError};
+use metrics_util::{
+ registry::{AtomicStorage, GenerationalStorage, Recency, Registry},
+ MetricKindMask, Summary,
+};
+use quanta::Clock;
+use std::{
+ collections::{BTreeMap, HashMap},
+ sync::{atomic::Ordering, Arc, RwLock},
+ time::Duration,
+};
+
+const SECONDS: u64 = 1;
+const MINUTES: u64 = 60 * SECONDS;
+const HOURS: u64 = 60 * MINUTES;
+const DAYS: u64 = 24 * HOURS;
+
+type DistributionMap = BTreeMap, Summary>;
+
+#[derive(Clone)]
+pub struct MemoryCollector {
+ inner: Arc,
+}
+
+struct Inner {
+ descriptions: RwLock>,
+ distributions: RwLock>,
+ recency: Recency,
+ registry: Registry>,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+struct Counter {
+ labels: BTreeMap,
+ value: u64,
+}
+
+impl std::fmt::Display for Counter {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let labels = self
+ .labels
+ .iter()
+ .map(|(k, v)| format!("{}: {}", k, v))
+ .collect::>()
+ .join(", ");
+
+ write!(f, "{} - {}", labels, self.value)
+ }
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+struct Gauge {
+ labels: BTreeMap,
+ value: f64,
+}
+
+impl std::fmt::Display for Gauge {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let labels = self
+ .labels
+ .iter()
+ .map(|(k, v)| format!("{}: {}", k, v))
+ .collect::>()
+ .join(", ");
+
+ write!(f, "{} - {}", labels, self.value)
+ }
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+struct Histogram {
+ labels: BTreeMap,
+ value: Vec<(f64, Option)>,
+}
+
+impl std::fmt::Display for Histogram {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let labels = self
+ .labels
+ .iter()
+ .map(|(k, v)| format!("{}: {}", k, v))
+ .collect::>()
+ .join(", ");
+
+ let value = self
+ .value
+ .iter()
+ .map(|(k, v)| {
+ if let Some(v) = v {
+ format!("{}: {:.6}", k, v)
+ } else {
+ format!("{}: None,", k)
+ }
+ })
+ .collect::>()
+ .join(", ");
+
+ write!(f, "{} - {}", labels, value)
+ }
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub(crate) struct Snapshot {
+ counters: HashMap>,
+ gauges: HashMap>,
+ histograms: HashMap>,
+}
+
+const PAIRS: [((&str, &str), &str); 2] = [
+ (
+ (
+ "background-jobs.worker.started",
+ "background-jobs.worker.finished",
+ ),
+ "background-jobs.worker.running",
+ ),
+ (
+ (
+ "background-jobs.job.started",
+ "background-jobs.job.finished",
+ ),
+ "background-jobs.job.running",
+ ),
+];
+
+#[derive(Default)]
+struct MergeCounter {
+ start: Option,
+ finish: Option,
+}
+
+impl MergeCounter {
+ fn merge(self) -> Option {
+ match (self.start, self.finish) {
+ (Some(start), Some(end)) => Some(Counter {
+ labels: start.labels,
+ value: start.value.saturating_sub(end.value),
+ }),
+ (Some(only), None) => Some(only),
+ (None, Some(only)) => Some(Counter {
+ labels: only.labels,
+ value: 0,
+ }),
+ (None, None) => None,
+ }
+ }
+}
+
+impl Snapshot {
+ pub(crate) fn present(self) {
+ if !self.counters.is_empty() {
+ println!("Counters");
+ let mut merging = HashMap::new();
+ for (key, counters) in self.counters {
+ if let Some(((start, _), name)) = PAIRS
+ .iter()
+ .find(|((start, finish), _)| *start == key || *finish == key)
+ {
+ let entry = merging.entry(name).or_insert_with(HashMap::new);
+
+ for counter in counters {
+ let mut merge_counter = entry
+ .entry(counter.labels.clone())
+ .or_insert_with(MergeCounter::default);
+ if key == *start {
+ merge_counter.start = Some(counter);
+ } else {
+ merge_counter.finish = Some(counter);
+ }
+ }
+
+ continue;
+ }
+
+ println!("\t{}", key);
+ for counter in counters {
+ println!("\t\t{}", counter);
+ }
+ }
+
+ for (key, counters) in merging {
+ println!("\t{}", key);
+
+ for (_, counter) in counters {
+ if let Some(counter) = counter.merge() {
+ println!("\t\t{}", counter);
+ }
+ }
+ }
+ }
+
+ if !self.gauges.is_empty() {
+ println!("Gauges");
+ for (key, gauges) in self.gauges {
+ println!("\t{}", key);
+
+ for gauge in gauges {
+ println!("\t\t{}", gauge);
+ }
+ }
+ }
+
+ if !self.histograms.is_empty() {
+ println!("Histograms");
+ for (key, histograms) in self.histograms {
+ println!("\t{}", key);
+
+ for histogram in histograms {
+ println!("\t\t{}", histogram);
+ }
+ }
+ }
+ }
+}
+
+fn key_to_parts(key: &Key) -> (String, Vec<(String, String)>) {
+ let labels = key
+ .labels()
+ .into_iter()
+ .map(|label| (label.key().to_string(), label.value().to_string()))
+ .collect();
+ let name = key.name().to_string();
+ (name, labels)
+}
+
+impl Inner {
+ fn snapshot_counters(&self) -> HashMap> {
+ let mut counters = HashMap::new();
+
+ for (key, counter) in self.registry.get_counter_handles() {
+ let gen = counter.get_generation();
+ if !self.recency.should_store_counter(&key, gen, &self.registry) {
+ continue;
+ }
+
+ let (name, labels) = key_to_parts(&key);
+ let value = counter.get_inner().load(Ordering::Acquire);
+ counters.entry(name).or_insert_with(Vec::new).push(Counter {
+ labels: labels.into_iter().collect(),
+ value,
+ });
+ }
+
+ counters
+ }
+
+ fn snapshot_gauges(&self) -> HashMap> {
+ let mut gauges = HashMap::new();
+
+ for (key, gauge) in self.registry.get_gauge_handles() {
+ let gen = gauge.get_generation();
+ if !self.recency.should_store_gauge(&key, gen, &self.registry) {
+ continue;
+ }
+
+ let (name, labels) = key_to_parts(&key);
+ let value = f64::from_bits(gauge.get_inner().load(Ordering::Acquire));
+ gauges.entry(name).or_insert_with(Vec::new).push(Gauge {
+ labels: labels.into_iter().collect(),
+ value,
+ })
+ }
+
+ gauges
+ }
+
+ fn snapshot_histograms(&self) -> HashMap> {
+ for (key, histogram) in self.registry.get_histogram_handles() {
+ let gen = histogram.get_generation();
+ let (name, labels) = key_to_parts(&key);
+
+ if !self
+ .recency
+ .should_store_histogram(&key, gen, &self.registry)
+ {
+ let mut d = self.distributions.write().unwrap();
+ let delete_by_name = if let Some(by_name) = d.get_mut(&name) {
+ by_name.remove(&labels);
+ by_name.is_empty()
+ } else {
+ false
+ };
+ drop(d);
+
+ if delete_by_name {
+ self.descriptions.write().unwrap().remove(&name);
+ }
+
+ continue;
+ }
+
+ let mut d = self.distributions.write().unwrap();
+ let outer_entry = d.entry(name.clone()).or_insert_with(BTreeMap::new);
+
+ let entry = outer_entry
+ .entry(labels)
+ .or_insert_with(Summary::with_defaults);
+
+ histogram.get_inner().clear_with(|samples| {
+ for sample in samples {
+ entry.add(*sample);
+ }
+ })
+ }
+
+ let d = self.distributions.read().unwrap().clone();
+ d.into_iter()
+ .map(|(key, value)| {
+ (
+ key,
+ value
+ .into_iter()
+ .map(|(labels, summary)| Histogram {
+ labels: labels.into_iter().collect(),
+ value: [0.001, 0.01, 0.05, 0.1, 0.5, 0.9, 0.99, 1.0]
+ .into_iter()
+ .map(|q| (q, summary.quantile(q)))
+ .collect(),
+ })
+ .collect(),
+ )
+ })
+ .collect()
+ }
+
+ fn snapshot(&self) -> Snapshot {
+ Snapshot {
+ counters: self.snapshot_counters(),
+ gauges: self.snapshot_gauges(),
+ histograms: self.snapshot_histograms(),
+ }
+ }
+}
+
+impl MemoryCollector {
+ pub(crate) fn new() -> Self {
+ MemoryCollector {
+ inner: Arc::new(Inner {
+ descriptions: Default::default(),
+ distributions: Default::default(),
+ recency: Recency::new(
+ Clock::new(),
+ MetricKindMask::ALL,
+ Some(Duration::from_secs(5 * DAYS)),
+ ),
+ registry: Registry::new(GenerationalStorage::atomic()),
+ }),
+ }
+ }
+
+ pub(crate) fn install(&self) -> Result<(), SetRecorderError> {
+ metrics::set_boxed_recorder(Box::new(self.clone()))
+ }
+
+ pub(crate) fn snapshot(&self) -> Snapshot {
+ self.inner.snapshot()
+ }
+
+ fn add_description_if_missing(
+ &self,
+ key: &metrics::KeyName,
+ description: metrics::SharedString,
+ ) {
+ let mut d = self.inner.descriptions.write().unwrap();
+ d.entry(key.as_str().to_owned()).or_insert(description);
+ }
+}
+
+impl Recorder for MemoryCollector {
+ fn describe_counter(
+ &self,
+ key: metrics::KeyName,
+ _: Option,
+ description: metrics::SharedString,
+ ) {
+ self.add_description_if_missing(&key, description)
+ }
+
+ fn describe_gauge(
+ &self,
+ key: metrics::KeyName,
+ _: Option,
+ description: metrics::SharedString,
+ ) {
+ self.add_description_if_missing(&key, description)
+ }
+
+ fn describe_histogram(
+ &self,
+ key: metrics::KeyName,
+ _: Option,
+ description: metrics::SharedString,
+ ) {
+ self.add_description_if_missing(&key, description)
+ }
+
+ fn register_counter(&self, key: &Key) -> metrics::Counter {
+ self.inner
+ .registry
+ .get_or_create_counter(key, |c| c.clone().into())
+ }
+
+ fn register_gauge(&self, key: &Key) -> metrics::Gauge {
+ self.inner
+ .registry
+ .get_or_create_gauge(key, |c| c.clone().into())
+ }
+
+ fn register_histogram(&self, key: &Key) -> metrics::Histogram {
+ self.inner
+ .registry
+ .get_or_create_histogram(key, |c| c.clone().into())
+ }
+}
diff --git a/src/config.rs b/src/config.rs
index 284f807..1d4cf86 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -14,8 +14,9 @@ use activitystreams::{
};
use config::Environment;
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
+use rustls::{Certificate, PrivateKey};
use sha2::{Digest, Sha256};
-use std::{net::IpAddr, path::PathBuf};
+use std::{io::BufReader, net::IpAddr, path::PathBuf};
use uuid::Uuid;
#[derive(Clone, Debug, serde::Deserialize)]
@@ -34,6 +35,11 @@ pub(crate) struct ParsedConfig {
telegram_token: Option,
telegram_admin_handle: Option,
api_token: Option,
+ tls_key: Option,
+ tls_cert: Option,
+ footer_blurb: Option,
+ local_domains: Option,
+ local_blurb: Option,
}
#[derive(Clone)]
@@ -52,6 +58,16 @@ pub struct Config {
telegram_token: Option,
telegram_admin_handle: Option,
api_token: Option,
+ tls: Option,
+ footer_blurb: Option,
+ local_domains: Vec,
+ local_blurb: Option,
+}
+
+#[derive(Clone)]
+struct TlsConfig {
+ key: PathBuf,
+ cert: PathBuf,
}
#[derive(Debug)]
@@ -77,6 +93,7 @@ pub enum AdminUrlKind {
Allowed,
Blocked,
Connected,
+ Stats,
}
impl std::fmt::Debug for Config {
@@ -99,6 +116,11 @@ impl std::fmt::Debug for Config {
.field("telegram_token", &"[redacted]")
.field("telegram_admin_handle", &self.telegram_admin_handle)
.field("api_token", &"[redacted]")
+ .field("tls_key", &"[redacted]")
+ .field("tls_cert", &"[redacted]")
+ .field("footer_blurb", &self.footer_blurb)
+ .field("local_domains", &self.local_domains)
+ .field("local_blurb", &self.local_blurb)
.finish()
}
}
@@ -111,8 +133,8 @@ impl Config {
.set_default("port", 8080u64)?
.set_default("debug", true)?
.set_default("restricted_mode", false)?
- .set_default("validate_signatures", false)?
- .set_default("https", false)?
+ .set_default("validate_signatures", true)?
+ .set_default("https", true)?
.set_default("publish_blocks", false)?
.set_default("sled_path", "./sled/db-0-34")?
.set_default("source_repo", "https://git.asonix.dog/asonix/relay")?
@@ -120,6 +142,11 @@ impl Config {
.set_default("telegram_token", None as Option<&str>)?
.set_default("telegram_admin_handle", None as Option<&str>)?
.set_default("api_token", None as Option<&str>)?
+ .set_default("tls_key", None as Option<&str>)?
+ .set_default("tls_cert", None as Option<&str>)?
+ .set_default("footer_blurb", None as Option<&str>)?
+ .set_default("local_domains", None as Option<&str>)?
+ .set_default("local_blurb", None as Option<&str>)?
.add_source(Environment::default())
.build()?;
@@ -128,6 +155,26 @@ impl Config {
let scheme = if config.https { "https" } else { "http" };
let base_uri = iri!(format!("{}://{}", scheme, config.hostname)).into_absolute();
+ let tls = match (config.tls_key, config.tls_cert) {
+ (Some(key), Some(cert)) => Some(TlsConfig { key, cert }),
+ (Some(_), None) => {
+ tracing::warn!("TLS_KEY is set but TLS_CERT isn't , not building TLS config");
+ None
+ }
+ (None, Some(_)) => {
+ tracing::warn!("TLS_CERT is set but TLS_KEY isn't , not building TLS config");
+ None
+ }
+ (None, None) => None,
+ };
+
+ let local_domains = config
+ .local_domains
+ .iter()
+ .flat_map(|s| s.split(','))
+ .map(|d| d.to_string())
+ .collect();
+
Ok(Config {
hostname: config.hostname,
addr: config.addr,
@@ -143,9 +190,76 @@ impl Config {
telegram_token: config.telegram_token,
telegram_admin_handle: config.telegram_admin_handle,
api_token: config.api_token,
+ tls,
+ footer_blurb: config.footer_blurb,
+ local_domains,
+ local_blurb: config.local_blurb,
})
}
+ pub(crate) fn open_keys(&self) -> Result