Merge tag 'v0.3.66' into max

This commit is contained in:
Maxime Augier 2022-11-28 09:54:00 +01:00
commit 112ed7cedd
37 changed files with 1383 additions and 171 deletions

6
.env
View File

@ -1,5 +1,11 @@
HOSTNAME=localhost:8079 HOSTNAME=localhost:8079
PORT=8079 PORT=8079
HTTPS=false
DEBUG=true
RESTRICTED_MODE=true RESTRICTED_MODE=true
VALIDATE_SIGNATURES=false
API_TOKEN=kjsdhfkwjenrkajhsdakjsnd API_TOKEN=kjsdhfkwjenrkajhsdakjsnd
FOOTER_BLURB="Opéré par <a href=\"https://mastodon.xolus.net/@max\">@max</a>"
LOCAL_DOMAINS="xolus.net"
LOCAL_BLURB="<p>Relais ActivityPub francophone</p>"
# OPENTELEMETRY_URL=http://localhost:4317 # OPENTELEMETRY_URL=http://localhost:4317

361
Cargo.lock generated
View File

@ -63,14 +63,17 @@ dependencies = [
"actix-codec", "actix-codec",
"actix-rt", "actix-rt",
"actix-service", "actix-service",
"actix-tls",
"actix-utils", "actix-utils",
"ahash", "ahash",
"base64", "base64",
"bitflags", "bitflags",
"brotli",
"bytes", "bytes",
"bytestring", "bytestring",
"derive_more", "derive_more",
"encoding_rs", "encoding_rs",
"flate2",
"futures-core", "futures-core",
"h2", "h2",
"http", "http",
@ -192,6 +195,7 @@ dependencies = [
"actix-rt", "actix-rt",
"actix-server", "actix-server",
"actix-service", "actix-service",
"actix-tls",
"actix-utils", "actix-utils",
"ahash", "ahash",
"bytes", "bytes",
@ -251,13 +255,28 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.19" version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [ dependencies = [
"memchr", "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]] [[package]]
name = "ammonia" name = "ammonia"
version = "3.2.1" version = "3.2.1"
@ -279,7 +298,7 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]] [[package]]
name = "ap-relay" name = "ap-relay"
version = "0.3.50" version = "0.3.66"
dependencies = [ dependencies = [
"activitystreams", "activitystreams",
"activitystreams-ext", "activitystreams-ext",
@ -300,13 +319,20 @@ dependencies = [
"futures-util", "futures-util",
"http-signature-normalization-actix", "http-signature-normalization-actix",
"lru", "lru",
"metrics",
"metrics-util",
"mime", "mime",
"minify-html",
"opentelemetry", "opentelemetry",
"opentelemetry-otlp", "opentelemetry-otlp",
"pin-project-lite",
"quanta",
"rand", "rand",
"rsa", "rsa",
"rsa-magic-public-key", "rsa-magic-public-key",
"ructe", "ructe",
"rustls",
"rustls-pemfile",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -346,6 +372,12 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164" checksum = "983cd8b9d4b02a6dc6ffa557262eb5858a27a0038ffffe21a0f133eaa819a164"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "async-mutex" name = "async-mutex"
version = "1.4.0" version = "1.4.0"
@ -485,9 +517,9 @@ dependencies = [
[[package]] [[package]]
name = "background-jobs" name = "background-jobs"
version = "0.13.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793a813f9145c5f3a27b8dcd834c0927de68bbd60d53a369e5894f3cc5759020" checksum = "62dc7cfc967d6714768097a876ca2941a54a26976c6d3c95ea6da48974890970"
dependencies = [ dependencies = [
"background-jobs-actix", "background-jobs-actix",
"background-jobs-core", "background-jobs-core",
@ -495,15 +527,16 @@ dependencies = [
[[package]] [[package]]
name = "background-jobs-actix" name = "background-jobs-actix"
version = "0.13.1" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47263ad9c5679419347dae655c2fa2cba078b0eaa51ac758d4f0e9690c06910b" checksum = "99f8bfe0a984c8d0bc7e67b376cc05e0b9015fdd3ee878900046120ef781c47e"
dependencies = [ dependencies = [
"actix-rt", "actix-rt",
"anyhow", "anyhow",
"async-mutex", "async-mutex",
"async-trait", "async-trait",
"background-jobs-core", "background-jobs-core",
"metrics",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -515,14 +548,15 @@ dependencies = [
[[package]] [[package]]
name = "background-jobs-core" name = "background-jobs-core"
version = "0.13.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e78e842fe2ae461319e3d1843c12e301630e65650332b02032ac70b0dfc66f" checksum = "1274e49ae8eff1fc6b4943660e59ce2f2e13e65a23a707924a50a40c7b94fc4d"
dependencies = [ dependencies = [
"actix-rt", "actix-rt",
"anyhow", "anyhow",
"async-trait", "async-trait",
"event-listener", "event-listener",
"metrics",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -562,6 +596,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.3" version = "0.10.3"
@ -581,6 +627,27 @@ dependencies = [
"cipher", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.11.1" version = "3.11.1"
@ -601,9 +668,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.2.1" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]] [[package]]
name = "bytestring" name = "bytestring"
@ -616,9 +683,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.76" version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -692,7 +759,7 @@ dependencies = [
"async-trait", "async-trait",
"json5", "json5",
"lazy_static", "lazy_static",
"nom", "nom 7.1.1",
"pathdiff", "pathdiff",
"ron", "ron",
"rust-ini", "rust-ini",
@ -780,9 +847,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-epoch" name = "crossbeam-epoch"
version = "0.9.11" version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if", "cfg-if",
@ -793,9 +860,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.12" version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -810,6 +877,17 @@ dependencies = [
"typenum", "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]] [[package]]
name = "darling" name = "darling"
version = "0.13.4" version = "0.13.4"
@ -929,6 +1007,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "erasable" name = "erasable"
version = "1.2.1" version = "1.2.1"
@ -995,6 +1079,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "funty"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]] [[package]]
name = "futf" name = "futf"
version = "0.1.5" version = "0.1.5"
@ -1121,7 +1211,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]] [[package]]
@ -1161,7 +1251,7 @@ dependencies = [
"base64", "base64",
"byteorder", "byteorder",
"flate2", "flate2",
"nom", "nom 7.1.1",
"num-traits", "num-traits",
] ]
@ -1233,9 +1323,9 @@ dependencies = [
[[package]] [[package]]
name = "http-signature-normalization-actix" name = "http-signature-normalization-actix"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86dfd54a1764ad79376b8dbf29e5bf918a463eb5ec66c90cd0388508289af6f0" checksum = "7483d0ee4d093fa4bfe5956cd405492c07808a5064a29cfe3960d474f21f39c2"
dependencies = [ dependencies = [
"actix-http", "actix-http",
"actix-rt", "actix-rt",
@ -1437,6 +1527,19 @@ dependencies = [
"spin", "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]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.137"
@ -1507,6 +1610,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
[[package]]
name = "mach"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "maplit" name = "maplit"
version = "1.0.2" version = "1.0.2"
@ -1556,13 +1668,56 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "memoffset" name = "memoffset"
version = "0.6.5" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [ dependencies = [
"autocfg", "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]] [[package]]
name = "mime" name = "mime"
version = "0.3.16" version = "0.3.16"
@ -1579,6 +1734,29 @@ dependencies = [
"unicase", "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]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -1602,7 +1780,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys",
] ]
@ -1624,6 +1802,28 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" 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]] [[package]]
name = "nom" name = "nom"
version = "7.1.1" version = "7.1.1"
@ -1642,7 +1842,7 @@ checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605"
dependencies = [ dependencies = [
"bytecount", "bytecount",
"memchr", "memchr",
"nom", "nom 7.1.1",
] ]
[[package]] [[package]]
@ -1822,6 +2022,15 @@ dependencies = [
"tokio-stream", "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]] [[package]]
name = "ordered-multimap" name = "ordered-multimap"
version = "0.4.3" version = "0.4.3"
@ -1834,9 +2043,9 @@ dependencies = [
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.4.0" version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]] [[package]]
name = "overload" name = "overload"
@ -1892,6 +2101,17 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "paste" name = "paste"
version = "1.0.9" version = "1.0.9"
@ -2065,6 +2285,12 @@ dependencies = [
"spki", "spki",
] ]
[[package]]
name = "portable-atomic"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15eb2c6e362923af47e13c23ca5afb859e83d54452c55b0b9ac763b8f7c1ac16"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -2175,6 +2401,22 @@ dependencies = [
"prost", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.21"
@ -2184,6 +2426,22 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -2214,6 +2472,15 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "raw-cpuid"
version = "10.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "rc-box" name = "rc-box"
version = "1.2.0" version = "1.2.0"
@ -2376,7 +2643,7 @@ dependencies = [
"arc-swap", "arc-swap",
"fastrand", "fastrand",
"lazy_static", "lazy_static",
"nom", "nom 7.1.1",
"nom_locate", "nom_locate",
"num-bigint", "num-bigint",
"num-integer", "num-integer",
@ -2396,7 +2663,7 @@ dependencies = [
"itertools 0.10.5", "itertools 0.10.5",
"md5", "md5",
"mime", "mime",
"nom", "nom 7.1.1",
"rsass", "rsass",
] ]
@ -2490,9 +2757,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.88" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
dependencies = [ dependencies = [
"itoa", "itoa",
"ryu", "ryu",
@ -2579,6 +2846,12 @@ version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "sketches-ddsketch"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ceb945e54128e09c43d8e4f1277851bd5044c6fc540bbaa2ad888f60b3da9ae7"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.7" version = "0.4.7"
@ -2636,6 +2909,12 @@ dependencies = [
"der", "der",
] ]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.8.4" version = "0.8.4"
@ -2703,6 +2982,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e" checksum = "20f34339676cdcab560c9a82300c4c2581f68b9369aedf0fae86f2ff9565ff3e"
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "teloxide" name = "teloxide"
version = "0.11.2" version = "0.11.2"
@ -3287,6 +3572,12 @@ dependencies = [
"try-lock", "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]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
@ -3496,6 +3787,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]] [[package]]
name = "yaml-rust" name = "yaml-rust"
version = "0.4.5" version = "0.4.5"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "ap-relay" name = "ap-relay"
description = "A simple activitypub relay" description = "A simple activitypub relay"
version = "0.3.50" version = "0.3.66"
authors = ["asonix <asonix@asonix.dog>"] authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0" license = "AGPL-3.0"
readme = "README.md" readme = "README.md"
@ -23,7 +23,11 @@ default = []
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
actix-rt = "2.7.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" actix-webfinger = "0.4.0"
activitystreams = "0.7.0-alpha.19" activitystreams = "0.7.0-alpha.19"
activitystreams-ext = "0.1.0-alpha.2" activitystreams-ext = "0.1.0-alpha.2"
@ -38,12 +42,19 @@ dashmap = "5.1.0"
dotenv = "0.15.0" dotenv = "0.15.0"
futures-util = "0.3.17" futures-util = "0.3.17"
lru = "0.8.0" lru = "0.8.0"
metrics = "0.20.1"
metrics-util = "0.14.0"
mime = "0.3.16" mime = "0.3.16"
minify-html = "0.10.0"
opentelemetry = { version = "0.18", features = ["rt-tokio"] } opentelemetry = { version = "0.18", features = ["rt-tokio"] }
opentelemetry-otlp = "0.11" opentelemetry-otlp = "0.11"
pin-project-lite = "0.2.9"
quanta = "0.10.1"
rand = "0.8" rand = "0.8"
rsa = "0.7" rsa = "0.7"
rsa-magic-public-key = "0.6.0" rsa-magic-public-key = "0.6.0"
rustls = "0.20.7"
rustls-pemfile = "1.0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
sha2 = { version = "0.10", features = ["oid"] } sha2 = { version = "0.10", features = ["oid"] }
@ -70,7 +81,7 @@ tokio = { version = "1", features = ["macros", "sync"] }
uuid = { version = "1", features = ["v4", "serde"] } uuid = { version = "1", features = ["v4", "serde"] }
[dependencies.background-jobs] [dependencies.background-jobs]
version = "0.13.0" version = "0.14.0"
default-features = false default-features = false
features = ["background-jobs-actix", "error-logging"] features = ["background-jobs-actix", "error-logging"]

View File

@ -10,7 +10,7 @@ $ sudo docker run --rm -it \
-e ADDR=0.0.0.0 \ -e ADDR=0.0.0.0 \
-e SLED_PATH=/mnt/sled/db-0.34 \ -e SLED_PATH=/mnt/sled/db-0.34 \
-p 8080:8080 \ -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 This will launch the relay with the database stored in "./sled/db-0.34" and listening on port 8080
#### Cargo #### Cargo
@ -98,6 +98,11 @@ API_TOKEN=somepasswordishtoken
OPENTELEMETRY_URL=localhost:4317 OPENTELEMETRY_URL=localhost:4317
TELEGRAM_TOKEN=secret TELEGRAM_TOKEN=secret
TELEGRAM_ADMIN_HANDLE=your_handle TELEGRAM_ADMIN_HANDLE=your_handle
TLS_KEY=/path/to/key
TLS_CERT=/path/to/cert
FOOTER_BLURB="Contact <a href=\"https://masto.asonix.dog/@asonix\">@asonix</a> for inquiries"
LOCAL_DOMAINS=masto.asonix.dog
LOCAL_BLURB="<p>Welcome to my cool relay where I have cool relay things happening. I hope you enjoy your stay!</p>"
``` ```
#### Descriptions #### Descriptions
@ -112,15 +117,15 @@ Whether to print incoming activities to the console when requests hit the /inbox
##### `RESTRICTED_MODE` ##### `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. 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` ##### `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` ##### `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` ##### `PUBLISH_BLOCKS`
Whether or not to publish a list of blocked domains in the `nodeinfo` metadata for the server. It defaults to `false`. Whether or not to publish a list of blocked domains in the `nodeinfo` metadata for the server. It defaults to `false`.
##### `SLED_PATH` ##### `SLED_PATH`
Where to store the on-disk database of connected servers. This defaults to `./sled/db-0.34`. Where to store the on-disk database of connected servers. This defaults to `./sled/db-0.34`.
##### `RUST_LOG` ##### `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` ##### `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. 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` ##### `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. A Telegram Bot Token for running the relay administration bot. There is no default.
##### `TELEGRAM_ADMIN_HANDLE` ##### `TELEGRAM_ADMIN_HANDLE`
The handle of the telegram user allowed to administer the relay. There is no default. 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 ### Subscribing
Mastodon admins can subscribe to this relay by adding the `/inbox` route to their relay settings. Mastodon admins can subscribe to this relay by adding the `/inbox` route to their relay settings.

View File

@ -41,7 +41,7 @@ header {
} }
} }
section { article {
background-color: #fff; background-color: #fff;
color: #333; color: #333;
border: 1px solid #e5e5e5; border: 1px solid #e5e5e5;
@ -51,9 +51,17 @@ section {
max-width: 700px; max-width: 700px;
padding-bottom: 32px; padding-bottom: 32px;
section {
border-bottom: 1px solid #e5e5e5;
> h4:first-child,
> p:first-child { > p:first-child {
margin-top: 0; margin-top: 0;
} }
> p:last-child {
margin-bottom: 0;
}
}
h3 { h3 {
padding: 24px; padding: 24px;
@ -67,13 +75,13 @@ section {
li { li {
padding-top: 36px; padding-top: 36px;
border-bottom: 1px solid #e5e5e5;
} }
.padded { .padded {
padding: 0 24px; padding: 0 24px;
} }
.local-explainer,
.joining { .joining {
padding: 24px; padding: 24px;
} }
@ -174,9 +182,11 @@ footer {
li { li {
padding: 0; padding: 0;
border-bottom: none;
} }
} }
article section {
border-bottom: none;
}
} }
} }
@ -241,7 +251,7 @@ footer {
padding: 24px; padding: 24px;
} }
section { article {
border-left: none; border-left: none;
border-right: none; border-right: none;
border-radius: 0; border-radius: 0;

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains}, admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
collector::Snapshot,
config::{AdminUrlKind, Config}, config::{AdminUrlKind, Config},
error::{Error, ErrorKind}, error::{Error, ErrorKind},
}; };
@ -50,6 +51,10 @@ pub(crate) async fn connected(client: &Client, config: &Config) -> Result<Connec
get_results(client, config, AdminUrlKind::Connected).await get_results(client, config, AdminUrlKind::Connected).await
} }
pub(crate) async fn stats(client: &Client, config: &Config) -> Result<Snapshot, Error> {
get_results(client, config, AdminUrlKind::Stats).await
}
async fn get_results<T: DeserializeOwned>( async fn get_results<T: DeserializeOwned>(
client: &Client, client: &Client,
config: &Config, config: &Config,

View File

@ -1,9 +1,13 @@
use crate::{ use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains}, admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
collector::{MemoryCollector, Snapshot},
error::Error, error::Error,
extractors::Admin, extractors::Admin,
}; };
use actix_web::{web::Json, HttpResponse}; use actix_web::{
web::{Data, Json},
HttpResponse,
};
pub(crate) async fn allow( pub(crate) async fn allow(
admin: Admin, admin: Admin,
@ -58,3 +62,10 @@ pub(crate) async fn connected(admin: Admin) -> Result<Json<ConnectedActors>, Err
Ok(Json(ConnectedActors { connected_actors })) Ok(Json(ConnectedActors { connected_actors }))
} }
pub(crate) async fn stats(
_admin: Admin,
collector: Data<MemoryCollector>,
) -> Result<Json<Snapshot>, Error> {
Ok(Json(collector.snapshot()))
}

View File

@ -14,11 +14,14 @@ pub(crate) struct Args {
#[arg(short, long, help = "List allowed and blocked domains")] #[arg(short, long, help = "List allowed and blocked domains")]
list: bool, list: bool,
#[arg(short, long, help = "Get statistics from the server")]
stats: bool,
} }
impl Args { impl Args {
pub(crate) fn any(&self) -> bool { 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 { pub(crate) fn new() -> Self {
@ -40,4 +43,8 @@ impl Args {
pub(crate) fn list(&self) -> bool { pub(crate) fn list(&self) -> bool {
self.list self.list
} }
pub(crate) fn stats(&self) -> bool {
self.stats
}
} }

414
src/collector.rs Normal file
View File

@ -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<Vec<(String, String)>, Summary>;
#[derive(Clone)]
pub struct MemoryCollector {
inner: Arc<Inner>,
}
struct Inner {
descriptions: RwLock<HashMap<String, metrics::SharedString>>,
distributions: RwLock<HashMap<String, DistributionMap>>,
recency: Recency<Key>,
registry: Registry<Key, GenerationalStorage<AtomicStorage>>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Counter {
labels: BTreeMap<String, String>,
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::<Vec<_>>()
.join(", ");
write!(f, "{} - {}", labels, self.value)
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Gauge {
labels: BTreeMap<String, String>,
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::<Vec<_>>()
.join(", ");
write!(f, "{} - {}", labels, self.value)
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct Histogram {
labels: BTreeMap<String, String>,
value: Vec<(f64, Option<f64>)>,
}
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::<Vec<_>>()
.join(", ");
let value = self
.value
.iter()
.map(|(k, v)| {
if let Some(v) = v {
format!("{}: {:.6}", k, v)
} else {
format!("{}: None,", k)
}
})
.collect::<Vec<_>>()
.join(", ");
write!(f, "{} - {}", labels, value)
}
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub(crate) struct Snapshot {
counters: HashMap<String, Vec<Counter>>,
gauges: HashMap<String, Vec<Gauge>>,
histograms: HashMap<String, Vec<Histogram>>,
}
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<Counter>,
finish: Option<Counter>,
}
impl MergeCounter {
fn merge(self) -> Option<Counter> {
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<String, Vec<Counter>> {
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<String, Vec<Gauge>> {
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<String, Vec<Histogram>> {
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<metrics::Unit>,
description: metrics::SharedString,
) {
self.add_description_if_missing(&key, description)
}
fn describe_gauge(
&self,
key: metrics::KeyName,
_: Option<metrics::Unit>,
description: metrics::SharedString,
) {
self.add_description_if_missing(&key, description)
}
fn describe_histogram(
&self,
key: metrics::KeyName,
_: Option<metrics::Unit>,
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())
}
}

View File

@ -14,8 +14,9 @@ use activitystreams::{
}; };
use config::Environment; use config::Environment;
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
use rustls::{Certificate, PrivateKey};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{net::IpAddr, path::PathBuf}; use std::{io::BufReader, net::IpAddr, path::PathBuf};
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone, Debug, serde::Deserialize)] #[derive(Clone, Debug, serde::Deserialize)]
@ -34,6 +35,11 @@ pub(crate) struct ParsedConfig {
telegram_token: Option<String>, telegram_token: Option<String>,
telegram_admin_handle: Option<String>, telegram_admin_handle: Option<String>,
api_token: Option<String>, api_token: Option<String>,
tls_key: Option<PathBuf>,
tls_cert: Option<PathBuf>,
footer_blurb: Option<String>,
local_domains: Option<String>,
local_blurb: Option<String>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -52,6 +58,16 @@ pub struct Config {
telegram_token: Option<String>, telegram_token: Option<String>,
telegram_admin_handle: Option<String>, telegram_admin_handle: Option<String>,
api_token: Option<String>, api_token: Option<String>,
tls: Option<TlsConfig>,
footer_blurb: Option<String>,
local_domains: Vec<String>,
local_blurb: Option<String>,
}
#[derive(Clone)]
struct TlsConfig {
key: PathBuf,
cert: PathBuf,
} }
#[derive(Debug)] #[derive(Debug)]
@ -77,6 +93,7 @@ pub enum AdminUrlKind {
Allowed, Allowed,
Blocked, Blocked,
Connected, Connected,
Stats,
} }
impl std::fmt::Debug for Config { impl std::fmt::Debug for Config {
@ -99,6 +116,11 @@ impl std::fmt::Debug for Config {
.field("telegram_token", &"[redacted]") .field("telegram_token", &"[redacted]")
.field("telegram_admin_handle", &self.telegram_admin_handle) .field("telegram_admin_handle", &self.telegram_admin_handle)
.field("api_token", &"[redacted]") .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() .finish()
} }
} }
@ -111,8 +133,8 @@ impl Config {
.set_default("port", 8080u64)? .set_default("port", 8080u64)?
.set_default("debug", true)? .set_default("debug", true)?
.set_default("restricted_mode", false)? .set_default("restricted_mode", false)?
.set_default("validate_signatures", false)? .set_default("validate_signatures", true)?
.set_default("https", false)? .set_default("https", true)?
.set_default("publish_blocks", false)? .set_default("publish_blocks", false)?
.set_default("sled_path", "./sled/db-0-34")? .set_default("sled_path", "./sled/db-0-34")?
.set_default("source_repo", "https://git.asonix.dog/asonix/relay")? .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_token", None as Option<&str>)?
.set_default("telegram_admin_handle", None as Option<&str>)? .set_default("telegram_admin_handle", None as Option<&str>)?
.set_default("api_token", 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()) .add_source(Environment::default())
.build()?; .build()?;
@ -128,6 +155,26 @@ impl Config {
let scheme = if config.https { "https" } else { "http" }; let scheme = if config.https { "https" } else { "http" };
let base_uri = iri!(format!("{}://{}", scheme, config.hostname)).into_absolute(); 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 { Ok(Config {
hostname: config.hostname, hostname: config.hostname,
addr: config.addr, addr: config.addr,
@ -143,9 +190,76 @@ impl Config {
telegram_token: config.telegram_token, telegram_token: config.telegram_token,
telegram_admin_handle: config.telegram_admin_handle, telegram_admin_handle: config.telegram_admin_handle,
api_token: config.api_token, 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<Option<(Vec<Certificate>, PrivateKey)>, Error> {
let tls = if let Some(tls) = &self.tls {
tls
} else {
tracing::warn!("No TLS config present");
return Ok(None);
};
let mut certs_reader = BufReader::new(std::fs::File::open(&tls.cert)?);
let certs = rustls_pemfile::certs(&mut certs_reader)?;
if certs.is_empty() {
tracing::warn!("No certs read from certificate file");
return Ok(None);
}
let mut key_reader = BufReader::new(std::fs::File::open(&tls.key)?);
let key = rustls_pemfile::read_one(&mut key_reader)?;
let certs = certs.into_iter().map(Certificate).collect();
let key = if let Some(key) = key {
match key {
rustls_pemfile::Item::RSAKey(der) => PrivateKey(der),
rustls_pemfile::Item::PKCS8Key(der) => PrivateKey(der),
rustls_pemfile::Item::ECKey(der) => PrivateKey(der),
_ => {
tracing::warn!("Unknown key format: {:?}", key);
return Ok(None);
}
}
} else {
tracing::warn!("Failed to read private key");
return Ok(None);
};
Ok(Some((certs, key)))
}
pub(crate) fn footer_blurb(&self) -> Option<crate::templates::Html<String>> {
if let Some(blurb) = &self.footer_blurb {
if !blurb.is_empty() {
return Some(crate::templates::Html(ammonia::clean(blurb)));
}
}
None
}
pub(crate) fn local_blurb(&self) -> Option<crate::templates::Html<String>> {
if let Some(blurb) = &self.local_blurb {
if !blurb.is_empty() {
return Some(crate::templates::Html(ammonia::clean(blurb)));
}
}
None
}
pub(crate) fn local_domains(&self) -> &[String] {
&self.local_domains
}
pub(crate) fn sled_path(&self) -> &PathBuf { pub(crate) fn sled_path(&self) -> &PathBuf {
&self.sled_path &self.sled_path
} }
@ -338,6 +452,8 @@ impl Config {
.try_resolve(IriRelativeStr::new("api/v1/admin/blocked")?.as_ref())?, .try_resolve(IriRelativeStr::new("api/v1/admin/blocked")?.as_ref())?,
AdminUrlKind::Connected => FixedBaseResolver::new(self.base_uri.as_ref()) AdminUrlKind::Connected => FixedBaseResolver::new(self.base_uri.as_ref())
.try_resolve(IriRelativeStr::new("api/v1/admin/connected")?.as_ref())?, .try_resolve(IriRelativeStr::new("api/v1/admin/connected")?.as_ref())?,
AdminUrlKind::Stats => FixedBaseResolver::new(self.base_uri.as_ref())
.try_resolve(IriRelativeStr::new("api/v1/admin/stats")?.as_ref())?,
}; };
Ok(iri) Ok(iri)

View File

@ -100,6 +100,12 @@ pub(crate) enum ErrorKind {
#[error("Couldn't sign digest")] #[error("Couldn't sign digest")]
Signature(#[from] signature::Error), Signature(#[from] signature::Error),
#[error("Couldn't read signature")]
ReadSignature(signature::Error),
#[error("Couldn't verify signature")]
VerifySignature(signature::Error),
#[error("Couldn't parse the signature header")] #[error("Couldn't parse the signature header")]
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue), HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),

View File

@ -11,7 +11,7 @@ use actix_web::{
use bcrypt::{BcryptError, DEFAULT_COST}; use bcrypt::{BcryptError, DEFAULT_COST};
use futures_util::future::LocalBoxFuture; use futures_util::future::LocalBoxFuture;
use http_signature_normalization_actix::prelude::InvalidHeaderValue; use http_signature_normalization_actix::prelude::InvalidHeaderValue;
use std::{convert::Infallible, str::FromStr}; use std::{convert::Infallible, str::FromStr, time::Instant};
use tracing_error::SpanTrace; use tracing_error::SpanTrace;
use crate::db::Db; use crate::db::Db;
@ -61,7 +61,8 @@ impl Admin {
hashed_api_token: Data<AdminConfig>, hashed_api_token: Data<AdminConfig>,
x_api_token: XApiToken, x_api_token: XApiToken,
) -> Result<(), Error> { ) -> Result<(), Error> {
if actix_web::web::block(move || hashed_api_token.verify(x_api_token)) let span = tracing::Span::current();
if actix_web::web::block(move || span.in_scope(|| hashed_api_token.verify(x_api_token)))
.await .await
.map_err(Error::canceled)?? .map_err(Error::canceled)??
{ {
@ -178,10 +179,15 @@ impl FromRequest for Admin {
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let now = Instant::now();
let res = Self::prepare_verify(req); let res = Self::prepare_verify(req);
Box::pin(async move { Box::pin(async move {
let (db, c, t) = res?; let (db, c, t) = res?;
Self::verify(c, t).await?; Self::verify(c, t).await?;
metrics::histogram!(
"relay.admin.verify",
now.elapsed().as_micros() as f64 / 1_000_000_f64
);
Ok(Admin { db }) Ok(Admin { db })
}) })
} }

View File

@ -7,8 +7,8 @@ mod nodeinfo;
mod process_listeners; mod process_listeners;
pub(crate) use self::{ pub(crate) use self::{
contact::QueryContact, deliver::Deliver, deliver_many::DeliverMany, contact::QueryContact, deliver::Deliver, deliver_many::DeliverMany, instance::QueryInstance,
instance::QueryInstance, nodeinfo::QueryNodeinfo, nodeinfo::QueryNodeinfo,
}; };
use crate::{ use crate::{
@ -22,7 +22,7 @@ use background_jobs::{
memory_storage::{ActixTimer, Storage}, memory_storage::{ActixTimer, Storage},
Job, Manager, QueueHandle, WorkerConfig, Job, Manager, QueueHandle, WorkerConfig,
}; };
use std::time::Duration; use std::{convert::TryFrom, num::NonZeroUsize, time::Duration};
fn debug_object(activity: &serde_json::Value) -> &serde_json::Value { fn debug_object(activity: &serde_json::Value) -> &serde_json::Value {
let mut object = &activity["object"]["type"]; let mut object = &activity["object"]["type"];
@ -44,6 +44,9 @@ pub(crate) fn create_workers(
media: MediaCache, media: MediaCache,
config: Config, config: Config,
) -> (Manager, JobServer) { ) -> (Manager, JobServer) {
let parallelism = std::thread::available_parallelism()
.unwrap_or_else(|_| NonZeroUsize::try_from(1).expect("nonzero"));
let shared = WorkerConfig::new_managed(Storage::new(ActixTimer), move |queue_handle| { let shared = WorkerConfig::new_managed(Storage::new(ActixTimer), move |queue_handle| {
JobState::new( JobState::new(
state.clone(), state.clone(),
@ -64,8 +67,10 @@ pub(crate) fn create_workers(
.register::<apub::Forward>() .register::<apub::Forward>()
.register::<apub::Reject>() .register::<apub::Reject>()
.register::<apub::Undo>() .register::<apub::Undo>()
.set_worker_count("default", 16) .set_worker_count("maintenance", 2)
.start(); .set_worker_count("apub", 2)
.set_worker_count("deliver", 8)
.start_with_threads(parallelism);
shared.every(Duration::from_secs(60 * 5), Listeners); shared.every(Duration::from_secs(60 * 5), Listeners);

View File

@ -67,6 +67,7 @@ impl ActixJob for Announce {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::apub::Announce"; const NAME: &'static str = "relay::jobs::apub::Announce";
const QUEUE: &'static str = "apub";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -116,6 +116,7 @@ impl ActixJob for Follow {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::apub::Follow"; const NAME: &'static str = "relay::jobs::apub::Follow";
const QUEUE: &'static str = "apub";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -52,6 +52,7 @@ impl ActixJob for Forward {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::apub::Forward"; const NAME: &'static str = "relay::jobs::apub::Forward";
const QUEUE: &'static str = "apub";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -38,6 +38,7 @@ impl ActixJob for Reject {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::apub::Reject"; const NAME: &'static str = "relay::jobs::apub::Reject";
const QUEUE: &'static str = "apub";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -53,6 +53,7 @@ impl ActixJob for Undo {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::apub::Undo"; const NAME: &'static str = "relay::jobs::apub::Undo";
const QUEUE: &'static str = "apub";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -86,6 +86,7 @@ impl ActixJob for QueryContact {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::QueryContact"; const NAME: &'static str = "relay::jobs::QueryContact";
const QUEUE: &'static str = "maintenance";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -55,6 +55,7 @@ impl ActixJob for Deliver {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::Deliver"; const NAME: &'static str = "relay::jobs::Deliver";
const QUEUE: &'static str = "deliver";
const BACKOFF: Backoff = Backoff::Exponential(8); const BACKOFF: Backoff = Backoff::Exponential(8);
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {

View File

@ -50,6 +50,7 @@ impl ActixJob for DeliverMany {
type Future = LocalBoxFuture<'static, Result<(), anyhow::Error>>; type Future = LocalBoxFuture<'static, Result<(), anyhow::Error>>;
const NAME: &'static str = "relay::jobs::DeliverMany"; const NAME: &'static str = "relay::jobs::DeliverMany";
const QUEUE: &'static str = "deliver";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -110,6 +110,7 @@ impl ActixJob for QueryInstance {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::QueryInstance"; const NAME: &'static str = "relay::jobs::QueryInstance";
const QUEUE: &'static str = "maintenance";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -100,6 +100,7 @@ impl ActixJob for QueryNodeinfo {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::QueryNodeinfo"; const NAME: &'static str = "relay::jobs::QueryNodeinfo";
const QUEUE: &'static str = "maintenance";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -28,6 +28,7 @@ impl ActixJob for Listeners {
type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>; type Future = Pin<Box<dyn Future<Output = Result<(), anyhow::Error>>>>;
const NAME: &'static str = "relay::jobs::Listeners"; const NAME: &'static str = "relay::jobs::Listeners";
const QUEUE: &'static str = "maintenance";
fn run(self, state: Self::State) -> Self::Future { fn run(self, state: Self::State) -> Self::Future {
Box::pin(async move { self.perform(state).await.map_err(Into::into) }) Box::pin(async move { self.perform(state).await.map_err(Into::into) })

View File

@ -2,11 +2,13 @@
#![allow(clippy::needless_borrow)] #![allow(clippy::needless_borrow)]
use activitystreams::iri_string::types::IriString; use activitystreams::iri_string::types::IriString;
use actix_web::{web, App, HttpServer}; use actix_web::{middleware::Compress, web, App, HttpServer};
use collector::MemoryCollector;
#[cfg(feature = "console")] #[cfg(feature = "console")]
use console_subscriber::ConsoleLayer; use console_subscriber::ConsoleLayer;
use opentelemetry::{sdk::Resource, KeyValue}; use opentelemetry::{sdk::Resource, KeyValue};
use opentelemetry_otlp::WithExportConfig; use opentelemetry_otlp::WithExportConfig;
use rustls::ServerConfig;
use tracing_actix_web::TracingLogger; use tracing_actix_web::TracingLogger;
use tracing_error::ErrorLayer; use tracing_error::ErrorLayer;
use tracing_log::LogTracer; use tracing_log::LogTracer;
@ -15,6 +17,7 @@ use tracing_subscriber::{filter::Targets, fmt::format::FmtSpan, layer::Subscribe
mod admin; mod admin;
mod apub; mod apub;
mod args; mod args;
mod collector;
mod config; mod config;
mod data; mod data;
mod db; mod db;
@ -32,7 +35,7 @@ use self::{
data::{ActorCache, MediaCache, State}, data::{ActorCache, MediaCache, State},
db::Db, db::Db,
jobs::create_workers, jobs::create_workers,
middleware::{DebugPayload, RelayResolver}, middleware::{DebugPayload, RelayResolver, Timings},
routes::{actor, inbox, index, nodeinfo, nodeinfo_meta, statics}, routes::{actor, inbox, index, nodeinfo, nodeinfo_meta, statics},
}; };
@ -43,7 +46,7 @@ fn init_subscriber(
LogTracer::init()?; LogTracer::init()?;
let targets: Targets = std::env::var("RUST_LOG") let targets: Targets = std::env::var("RUST_LOG")
.unwrap_or_else(|_| "info".into()) .unwrap_or_else(|_| "warn,actix_web=debug,actix_server=debug,tracing_actix_web=info".into())
.parse()?; .parse()?;
let format_layer = tracing_subscriber::fmt::layer() let format_layer = tracing_subscriber::fmt::layer()
@ -91,17 +94,41 @@ fn init_subscriber(
Ok(()) Ok(())
} }
#[actix_rt::main] fn main() -> Result<(), anyhow::Error> {
async fn main() -> Result<(), anyhow::Error> {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
let config = Config::build()?; let config = Config::build()?;
init_subscriber(Config::software_name(), config.opentelemetry_url())?; init_subscriber(Config::software_name(), config.opentelemetry_url())?;
let collector = MemoryCollector::new();
collector.install()?;
let args = Args::new(); let args = Args::new();
if args.any() { if args.any() {
return client_main(config, args);
}
tracing::warn!("Opening DB");
let db = Db::build(&config)?;
tracing::warn!("Building caches");
let actors = ActorCache::new(db.clone());
let media = MediaCache::new(db.clone());
server_main(db, actors, media, collector, config)?;
tracing::warn!("Application exit");
Ok(())
}
#[actix_rt::main]
async fn client_main(config: Config, args: Args) -> Result<(), anyhow::Error> {
actix_rt::spawn(do_client_main(config, args)).await?
}
async fn do_client_main(config: Config, args: Args) -> Result<(), anyhow::Error> {
let client = requests::build_client(&config.user_agent()); let client = requests::build_client(&config.user_agent());
if !args.blocks().is_empty() || !args.allowed().is_empty() { if !args.blocks().is_empty() || !args.allowed().is_empty() {
@ -139,24 +166,48 @@ async fn main() -> Result<(), anyhow::Error> {
println!("{report}"); println!("{report}");
} }
return Ok(()); if args.stats() {
let stats = admin::client::stats(&client, &config).await?;
stats.present();
} }
let db = Db::build(&config)?; Ok(())
}
let media = MediaCache::new(db.clone()); #[actix_rt::main]
async fn server_main(
db: Db,
actors: ActorCache,
media: MediaCache,
collector: MemoryCollector,
config: Config,
) -> Result<(), anyhow::Error> {
actix_rt::spawn(do_server_main(db, actors, media, collector, config)).await?
}
async fn do_server_main(
db: Db,
actors: ActorCache,
media: MediaCache,
collector: MemoryCollector,
config: Config,
) -> Result<(), anyhow::Error> {
tracing::warn!("Creating state");
let state = State::build(db.clone()).await?; let state = State::build(db.clone()).await?;
let actors = ActorCache::new(db.clone());
tracing::warn!("Creating workers");
let (manager, job_server) = let (manager, job_server) =
create_workers(state.clone(), actors.clone(), media.clone(), config.clone()); create_workers(state.clone(), actors.clone(), media.clone(), config.clone());
if let Some((token, admin_handle)) = config.telegram_info() { if let Some((token, admin_handle)) = config.telegram_info() {
tracing::warn!("Creating telegram handler");
telegram::start(admin_handle.to_owned(), db.clone(), token); telegram::start(admin_handle.to_owned(), db.clone(), token);
} }
let keys = config.open_keys()?;
let bind_address = config.bind_address(); let bind_address = config.bind_address();
HttpServer::new(move || { let server = HttpServer::new(move || {
let app = App::new() let app = App::new()
.app_data(web::Data::new(db.clone())) .app_data(web::Data::new(db.clone()))
.app_data(web::Data::new(state.clone())) .app_data(web::Data::new(state.clone()))
@ -164,7 +215,8 @@ async fn main() -> Result<(), anyhow::Error> {
.app_data(web::Data::new(actors.clone())) .app_data(web::Data::new(actors.clone()))
.app_data(web::Data::new(config.clone())) .app_data(web::Data::new(config.clone()))
.app_data(web::Data::new(job_server.clone())) .app_data(web::Data::new(job_server.clone()))
.app_data(web::Data::new(media.clone())); .app_data(web::Data::new(media.clone()))
.app_data(web::Data::new(collector.clone()));
let app = if let Some(data) = config.admin_config() { let app = if let Some(data) = config.admin_config() {
app.app_data(data) app.app_data(data)
@ -172,7 +224,9 @@ async fn main() -> Result<(), anyhow::Error> {
app app
}; };
app.wrap(TracingLogger::default()) app.wrap(Compress::default())
.wrap(TracingLogger::default())
.wrap(Timings)
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/media/{path}").route(web::get().to(routes::media))) .service(web::resource("/media/{path}").route(web::get().to(routes::media)))
.service( .service(
@ -203,16 +257,35 @@ async fn main() -> Result<(), anyhow::Error> {
.route("/unblock", web::post().to(admin::routes::unblock)) .route("/unblock", web::post().to(admin::routes::unblock))
.route("/allowed", web::get().to(admin::routes::allowed)) .route("/allowed", web::get().to(admin::routes::allowed))
.route("/blocked", web::get().to(admin::routes::blocked)) .route("/blocked", web::get().to(admin::routes::blocked))
.route("/connected", web::get().to(admin::routes::connected)), .route("/connected", web::get().to(admin::routes::connected))
.route("/stats", web::get().to(admin::routes::stats)),
), ),
) )
}) });
.bind(bind_address)?
if let Some((certs, key)) = keys {
tracing::warn!("Binding to {}:{} with TLS", bind_address.0, bind_address.1);
let server_config = ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_safe_default_protocol_versions()?
.with_no_client_auth()
.with_single_cert(certs, key)?;
server
.bind_rustls(bind_address, server_config)?
.run() .run()
.await?; .await?;
} else {
tracing::warn!("Binding to {}:{}", bind_address.0, bind_address.1);
server.bind(bind_address)?.run().await?;
}
tracing::warn!("Server closed");
drop(manager); drop(manager);
tracing::warn!("Main complete");
Ok(()) Ok(())
} }

View File

@ -1,7 +1,9 @@
mod payload; mod payload;
mod timings;
mod verifier; mod verifier;
mod webfinger; mod webfinger;
pub(crate) use payload::DebugPayload; pub(crate) use payload::DebugPayload;
pub(crate) use timings::Timings;
pub(crate) use verifier::MyVerify; pub(crate) use verifier::MyVerify;
pub(crate) use webfinger::RelayResolver; pub(crate) use webfinger::RelayResolver;

View File

@ -5,7 +5,7 @@ use actix_web::{
HttpMessage, HttpMessage,
}; };
use futures_util::{ use futures_util::{
future::{LocalBoxFuture, TryFutureExt}, future::TryFutureExt,
stream::{once, TryStreamExt}, stream::{once, TryStreamExt},
}; };
use std::{ use std::{
@ -45,7 +45,7 @@ where
{ {
type Response = S::Response; type Response = S::Response;
type Error = S::Error; type Error = S::Error;
type Future = LocalBoxFuture<'static, Result<S::Response, S::Error>>; type Future = S::Future;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.1.poll_ready(cx) self.1.poll_ready(cx)
@ -68,13 +68,9 @@ where
)), )),
}); });
let fut = self.1.call(req); self.1.call(req)
Box::pin(async move { fut.await })
} else { } else {
let fut = self.1.call(req); self.1.call(req)
Box::pin(async move { fut.await })
} }
} }
} }

143
src/middleware/timings.rs Normal file
View File

@ -0,0 +1,143 @@
use actix_web::{
body::MessageBody,
dev::{Service, ServiceRequest, ServiceResponse, Transform},
http::StatusCode,
};
use std::{
future::{ready, Future, Ready},
time::Instant,
};
pub(crate) struct Timings;
pub(crate) struct TimingsMiddleware<S>(S);
struct LogOnDrop {
begin: Instant,
path: String,
method: String,
arm: bool,
}
pin_project_lite::pin_project! {
pub(crate) struct TimingsFuture<F> {
#[pin]
future: F,
log_on_drop: Option<LogOnDrop>,
}
}
pin_project_lite::pin_project! {
pub(crate) struct TimingsBody<B> {
#[pin]
body: B,
log_on_drop: LogOnDrop,
}
}
impl Drop for LogOnDrop {
fn drop(&mut self) {
if self.arm {
let duration = self.begin.elapsed();
metrics::histogram!("relay.request.complete", duration, "path" => self.path.clone(), "method" => self.method.clone());
}
}
}
impl<S, B> Transform<S, ServiceRequest> for Timings
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
S::Future: 'static,
{
type Response = ServiceResponse<TimingsBody<B>>;
type Error = S::Error;
type InitError = ();
type Transform = TimingsMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(TimingsMiddleware(service)))
}
}
impl<S, B> Service<ServiceRequest> for TimingsMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
S::Future: 'static,
{
type Response = ServiceResponse<TimingsBody<B>>;
type Error = S::Error;
type Future = TimingsFuture<S::Future>;
fn poll_ready(
&self,
ctx: &mut core::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.0.poll_ready(ctx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
let log_on_drop = LogOnDrop {
begin: Instant::now(),
path: req.path().to_string(),
method: req.method().to_string(),
arm: false,
};
let future = self.0.call(req);
TimingsFuture {
future,
log_on_drop: Some(log_on_drop),
}
}
}
impl<F, B> Future for TimingsFuture<F>
where
F: Future<Output = Result<ServiceResponse<B>, actix_web::Error>>,
{
type Output = Result<ServiceResponse<TimingsBody<B>>, actix_web::Error>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let this = self.project();
let res = std::task::ready!(this.future.poll(cx));
let mut log_on_drop = this
.log_on_drop
.take()
.expect("TimingsFuture polled after completion");
let status = match &res {
Ok(res) => res.status(),
Err(e) => e.as_response_error().status_code(),
};
log_on_drop.arm =
status != StatusCode::NOT_FOUND && status != StatusCode::METHOD_NOT_ALLOWED;
let res = res.map(|r| r.map_body(|_, body| TimingsBody { body, log_on_drop }));
std::task::Poll::Ready(res)
}
}
impl<B: MessageBody> MessageBody for TimingsBody<B> {
type Error = B::Error;
fn size(&self) -> actix_web::body::BodySize {
self.body.size()
}
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<actix_web::web::Bytes, Self::Error>>> {
self.project().body.poll_next(cx)
}
}

View File

@ -16,7 +16,7 @@ use std::{future::Future, pin::Pin};
pub(crate) struct MyVerify(pub Requests, pub ActorCache, pub State); pub(crate) struct MyVerify(pub Requests, pub ActorCache, pub State);
impl MyVerify { impl MyVerify {
#[tracing::instrument("Verify signature", skip(self, signature))] #[tracing::instrument("Verify request", skip(self, signature, signing_string))]
async fn verify( async fn verify(
&self, &self,
algorithm: Option<Algorithm>, algorithm: Option<Algorithm>,
@ -106,6 +106,7 @@ impl PublicKeyResponse {
} }
} }
#[tracing::instrument("Verify signature")]
async fn do_verify( async fn do_verify(
public_key: &str, public_key: &str,
signature: String, signature: String,
@ -113,16 +114,21 @@ async fn do_verify(
) -> Result<(), Error> { ) -> Result<(), Error> {
let public_key = RsaPublicKey::from_public_key_pem(public_key.trim())?; let public_key = RsaPublicKey::from_public_key_pem(public_key.trim())?;
let span = tracing::Span::current();
web::block(move || { web::block(move || {
span.in_scope(|| {
let decoded = base64::decode(signature)?; let decoded = base64::decode(signature)?;
let signature = Signature::from_bytes(&decoded)?; let signature = Signature::from_bytes(&decoded).map_err(ErrorKind::ReadSignature)?;
let hashed = Sha256::new_with_prefix(signing_string.as_bytes()); let hashed = Sha256::new_with_prefix(signing_string.as_bytes());
let verifying_key = VerifyingKey::new_with_prefix(public_key); let verifying_key = VerifyingKey::new_with_prefix(public_key);
verifying_key.verify_digest(hashed, &signature)?; verifying_key
.verify_digest(hashed, &signature)
.map_err(ErrorKind::VerifySignature)?;
Ok(()) as Result<(), Error> Ok(()) as Result<(), Error>
}) })
})
.await??; .await??;
Ok(()) Ok(())

View File

@ -7,6 +7,19 @@ use actix_web::{web, HttpResponse};
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng};
use std::io::BufWriter; use std::io::BufWriter;
const MINIFY_CONFIG: minify_html::Cfg = minify_html::Cfg {
do_not_minify_doctype: true,
ensure_spec_compliant_unquoted_attribute_values: true,
keep_closing_tags: true,
keep_html_and_head_opening_tags: false,
keep_spaces_between_attributes: true,
keep_comments: false,
minify_js: true,
minify_css: true,
remove_bangs: true,
remove_processing_instructions: true,
};
fn open_reg(node: &Node) -> bool { fn open_reg(node: &Node) -> bool {
node.instance node.instance
.as_ref() .as_ref()
@ -20,7 +33,28 @@ pub(crate) async fn route(
state: web::Data<State>, state: web::Data<State>,
config: web::Data<Config>, config: web::Data<Config>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let mut nodes = state.node_cache().nodes().await?; let all_nodes = state.node_cache().nodes().await?;
let mut nodes = Vec::new();
let mut local = Vec::new();
for node in all_nodes {
if node
.base
.authority_str()
.map(|authority| {
config
.local_domains()
.iter()
.any(|domain| domain.as_str() == authority)
})
.unwrap_or(false)
{
local.push(node);
} else {
nodes.push(node);
}
}
nodes.sort_by(|lhs, rhs| match (open_reg(lhs), open_reg(rhs)) { nodes.sort_by(|lhs, rhs| match (open_reg(lhs), open_reg(rhs)) {
(true, true) | (false, false) => std::cmp::Ordering::Equal, (true, true) | (false, false) => std::cmp::Ordering::Equal,
@ -37,11 +71,13 @@ pub(crate) async fn route(
let mut buf = BufWriter::new(Vec::new()); let mut buf = BufWriter::new(Vec::new());
crate::templates::index(&mut buf, &nodes, &config)?; crate::templates::index(&mut buf, &local, &nodes, &config)?;
let buf = buf.into_inner().map_err(|e| { let html = buf.into_inner().map_err(|e| {
tracing::error!("Error rendering template, {}", e.error()); tracing::error!("Error rendering template, {}", e.error());
ErrorKind::FlushBuffer ErrorKind::FlushBuffer
})?; })?;
Ok(HttpResponse::Ok().content_type("text/html").body(buf)) let html = minify_html::minify(&html, &MINIFY_CONFIG);
Ok(HttpResponse::Ok().content_type("text/html").body(html))
} }

View File

@ -4,7 +4,7 @@ data::Node,
templates::{info, instance, statics::index_css}, templates::{info, instance, statics::index_css},
}; };
@(nodes: &[Node], config: &Config) @(local: &[Node], nodes: &[Node], config: &Config)
<!doctype html> <!doctype html>
<html> <html>
@ -24,16 +24,23 @@ templates::{info, instance, statics::index_css},
</div> </div>
</header> </header>
<main> <main>
<section> @if !local.is_empty() || config.local_blurb().is_some() {
<h3>@nodes.len() instances fédérées.</h3> <article>
@if nodes.is_empty() { <h3>About</h3>
<p>Aucune instance fédérée en ce moment.</p> <section class="local-explainer">
@if let Some(blurb) = config.local_blurb() {
@blurb
} else { } else {
<p>Ces domaines sont administrés par la même équipe que ce relais.</p>
}
</section>
@if !local.is_empty() {
<ul> <ul>
@for node in nodes { @for node in local {
@if let Some(inst) = node.instance.as_ref() { @if let Some(inst) = node.instance.as_ref() {
<li> <li>
@:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(), &node.base) @:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(),
&node.base)
</li> </li>
} else { } else {
@if let Some(inf) = node.info.as_ref() { @if let Some(inf) = node.info.as_ref() {
@ -45,10 +52,11 @@ templates::{info, instance, statics::index_css},
} }
</ul> </ul>
} }
</section> </article>
<section> }
<a name="#joining"><h3>Rejoindre</h3></a> <article>
<article class="joining"> <a name="#joining"><h3>Joining</h3></a>
<section class="joining">
@if config.restricted_mode() { @if config.restricted_mode() {
<h4> <h4>
Ce relais est restreint. Ce relais est restreint.
@ -77,10 +85,34 @@ templates::{info, instance, statics::index_css},
<p> <p>
Vérifiez la documentation de votre installation, qui suit probablement la convention de Mastodon ou de Pleroma. Vérifiez la documentation de votre installation, qui suit probablement la convention de Mastodon ou de Pleroma.
</p> </p>
</article>
</section> </section>
</article>
@if !nodes.is_empty() {
<article>
<h3>@nodes.len() Connected Servers</h3>
<ul>
@for node in nodes {
@if let Some(inst) = node.instance.as_ref() {
<li>
@:instance(inst, node.info.as_ref().map(|info| { info.software.as_ref() }), node.contact.as_ref(),
&node.base)
</li>
} else {
@if let Some(inf) = node.info.as_ref() {
<li>
@:info(inf, &node.base)
</li>
}
}
}
</ul>
</article>
}
</main> </main>
<footer> <footer>
@if let Some(blurb) = config.footer_blurb() {
<div>@blurb</div>
}
<p> <p>
Code source de l'application disponible ici: Code source de l'application disponible ici:
<a href="@config.source_code()">@config.source_code()</a> <a href="@config.source_code()">@config.source_code()</a>

View File

@ -3,14 +3,14 @@
@(info: &Info, base: &IriString) @(info: &Info, base: &IriString)
<article class="info"> <section class="info">
@if let Some(authority) = base.authority_str() { @if let Some(authority) = base.authority_str() {
<h4 class="padded"><a href="@base">@authority</a></h4> <h4 class="padded"><a href="@base">@authority</a></h4>
} }
<p class="padded"> <p class="padded">
Utilise @info.software, version @info.version. Utilise @info.software, version @info.version.
@if info.reg { @if info.reg {
Enregistrement ouvert. Inscriptions ouvertes
} }
</p> </p>
</article> </section>

View File

@ -3,7 +3,7 @@
@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &IriString) @(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &IriString)
<article class="instance"> <section class="instance">
<h4 class="padded"><a href="@base">@instance.title</a></h4> <h4 class="padded"><a href="@base">@instance.title</a></h4>
<p class="padded"> <p class="padded">
@if let Some(software) = software { @if let Some(software) = software {
@ -35,5 +35,9 @@
@:admin(contact, base) @:admin(contact, base)
} }
</div> </div>
@if let Some(contact) = contact {
<h5 class="instance-admin">@instance.title's admin:</h5>
@:admin(contact, base)
} }
</article> }
</section>