Compare commits

..

No commits in common. "4e5e257f24a0197925fdd1a18cd63e80b76fd019" and "e89ee20d38aae593626691b97e43f66f5460f8b8" have entirely different histories.

37 changed files with 180 additions and 1387 deletions

8
.env
View File

@ -1,11 +1,5 @@
HOSTNAME=localhost:8079
PORT=8079
HTTPS=false
DEBUG=true
RESTRICTED_MODE=true
VALIDATE_SIGNATURES=false
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>"
API_TOKEN=somesecretpassword
# OPENTELEMETRY_URL=http://localhost:4317

361
Cargo.lock generated
View File

@ -63,17 +63,14 @@ 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",
@ -195,7 +192,6 @@ dependencies = [
"actix-rt",
"actix-server",
"actix-service",
"actix-tls",
"actix-utils",
"ahash",
"bytes",
@ -255,28 +251,13 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
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"
@ -298,7 +279,7 @@ checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "ap-relay"
version = "0.3.66"
version = "0.3.50"
dependencies = [
"activitystreams",
"activitystreams-ext",
@ -319,20 +300,13 @@ 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",
@ -372,12 +346,6 @@ 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"
@ -517,9 +485,9 @@ dependencies = [
[[package]]
name = "background-jobs"
version = "0.14.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62dc7cfc967d6714768097a876ca2941a54a26976c6d3c95ea6da48974890970"
checksum = "793a813f9145c5f3a27b8dcd834c0927de68bbd60d53a369e5894f3cc5759020"
dependencies = [
"background-jobs-actix",
"background-jobs-core",
@ -527,16 +495,15 @@ dependencies = [
[[package]]
name = "background-jobs-actix"
version = "0.14.2"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99f8bfe0a984c8d0bc7e67b376cc05e0b9015fdd3ee878900046120ef781c47e"
checksum = "47263ad9c5679419347dae655c2fa2cba078b0eaa51ac758d4f0e9690c06910b"
dependencies = [
"actix-rt",
"anyhow",
"async-mutex",
"async-trait",
"background-jobs-core",
"metrics",
"serde",
"serde_json",
"thiserror",
@ -548,15 +515,14 @@ dependencies = [
[[package]]
name = "background-jobs-core"
version = "0.14.1"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1274e49ae8eff1fc6b4943660e59ce2f2e13e65a23a707924a50a40c7b94fc4d"
checksum = "48e78e842fe2ae461319e3d1843c12e301630e65650332b02032ac70b0dfc66f"
dependencies = [
"actix-rt",
"anyhow",
"async-trait",
"event-listener",
"metrics",
"serde",
"serde_json",
"thiserror",
@ -596,18 +562,6 @@ 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"
@ -627,27 +581,6 @@ 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"
@ -668,9 +601,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.3.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "bytestring"
@ -683,9 +616,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.77"
version = "1.0.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
[[package]]
name = "cfg-if"
@ -759,7 +692,7 @@ dependencies = [
"async-trait",
"json5",
"lazy_static",
"nom 7.1.1",
"nom",
"pathdiff",
"ron",
"rust-ini",
@ -847,9 +780,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
dependencies = [
"autocfg",
"cfg-if",
@ -860,9 +793,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
dependencies = [
"cfg-if",
]
@ -877,17 +810,6 @@ 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"
@ -1007,12 +929,6 @@ 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"
@ -1079,12 +995,6 @@ 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"
@ -1211,7 +1121,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi",
]
[[package]]
@ -1251,7 +1161,7 @@ dependencies = [
"base64",
"byteorder",
"flate2",
"nom 7.1.1",
"nom",
"num-traits",
]
@ -1323,9 +1233,9 @@ dependencies = [
[[package]]
name = "http-signature-normalization-actix"
version = "0.6.2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7483d0ee4d093fa4bfe5956cd405492c07808a5064a29cfe3960d474f21f39c2"
checksum = "86dfd54a1764ad79376b8dbf29e5bf918a463eb5ec66c90cd0388508289af6f0"
dependencies = [
"actix-http",
"actix-rt",
@ -1527,19 +1437,6 @@ 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"
@ -1610,15 +1507,6 @@ 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"
@ -1668,56 +1556,13 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
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"
@ -1734,29 +1579,6 @@ 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"
@ -1780,7 +1602,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [
"libc",
"log",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasi",
"windows-sys",
]
@ -1802,28 +1624,6 @@ 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"
@ -1842,7 +1642,7 @@ checksum = "37794436ca3029a3089e0b95d42da1f0b565ad271e4d3bb4bad0c7bb70b10605"
dependencies = [
"bytecount",
"memchr",
"nom 7.1.1",
"nom",
]
[[package]]
@ -2022,15 +1822,6 @@ 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"
@ -2043,9 +1834,9 @@ dependencies = [
[[package]]
name = "os_str_bytes"
version = "6.4.1"
version = "6.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e"
[[package]]
name = "overload"
@ -2101,17 +1892,6 @@ 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"
@ -2285,12 +2065,6 @@ 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"
@ -2401,22 +2175,6 @@ 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"
@ -2426,22 +2184,6 @@ 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"
@ -2472,15 +2214,6 @@ 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"
@ -2643,7 +2376,7 @@ dependencies = [
"arc-swap",
"fastrand",
"lazy_static",
"nom 7.1.1",
"nom",
"nom_locate",
"num-bigint",
"num-integer",
@ -2663,7 +2396,7 @@ dependencies = [
"itertools 0.10.5",
"md5",
"mime",
"nom 7.1.1",
"nom",
"rsass",
]
@ -2757,9 +2490,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.89"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db"
checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7"
dependencies = [
"itoa",
"ryu",
@ -2846,12 +2579,6 @@ 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"
@ -2909,12 +2636,6 @@ 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"
@ -2982,12 +2703,6 @@ 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"
@ -3572,12 +3287,6 @@ 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"
@ -3787,12 +3496,6 @@ 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"

View File

@ -1,7 +1,7 @@
[package]
name = "ap-relay"
description = "A simple activitypub relay"
version = "0.3.66"
version = "0.3.50"
authors = ["asonix <asonix@asonix.dog>"]
license = "AGPL-3.0"
readme = "README.md"
@ -23,11 +23,7 @@ default = []
[dependencies]
anyhow = "1.0"
actix-rt = "2.7.0"
actix-web = { version = "4.0.1", default-features = false, features = [
"rustls",
"compress-brotli",
"compress-gzip",
] }
actix-web = { version = "4.0.1", default-features = false }
actix-webfinger = "0.4.0"
activitystreams = "0.7.0-alpha.19"
activitystreams-ext = "0.1.0-alpha.2"
@ -42,19 +38,12 @@ 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"] }
@ -81,7 +70,7 @@ tokio = { version = "1", features = ["macros", "sync"] }
uuid = { version = "1", features = ["v4", "serde"] }
[dependencies.background-jobs]
version = "0.14.0"
version = "0.13.0"
default-features = false
features = ["background-jobs-actix", "error-logging"]

View File

@ -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.52
asonix/relay:0.3.23
```
This will launch the relay with the database stored in "./sled/db-0.34" and listening on port 8080
#### Cargo
@ -98,11 +98,6 @@ 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 <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
@ -117,15 +112,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 `true`
This setting enforces checking HTTP signatures on incoming activities. It defaults to `false` but should be set to `true` in production scenarios
##### `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 `true`
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.
##### `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`. This defaults to `warn`
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`
##### `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`
@ -136,16 +131,6 @@ 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.

View File

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

View File

@ -1,6 +1,5 @@
use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
collector::Snapshot,
config::{AdminUrlKind, Config},
error::{Error, ErrorKind},
};
@ -51,10 +50,6 @@ pub(crate) async fn connected(client: &Client, config: &Config) -> Result<Connec
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>(
client: &Client,
config: &Config,

View File

@ -1,13 +1,9 @@
use crate::{
admin::{AllowedDomains, BlockedDomains, ConnectedActors, Domains},
collector::{MemoryCollector, Snapshot},
error::Error,
extractors::Admin,
};
use actix_web::{
web::{Data, Json},
HttpResponse,
};
use actix_web::{web::Json, HttpResponse};
pub(crate) async fn allow(
admin: Admin,
@ -62,10 +58,3 @@ pub(crate) async fn connected(admin: Admin) -> Result<Json<ConnectedActors>, Err
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,14 +14,11 @@ 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.stats
!self.blocks.is_empty() || !self.allowed.is_empty() || self.list
}
pub(crate) fn new() -> Self {
@ -43,8 +40,4 @@ impl Args {
pub(crate) fn list(&self) -> bool {
self.list
}
pub(crate) fn stats(&self) -> bool {
self.stats
}
}

View File

@ -1,414 +0,0 @@
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,9 +14,8 @@ use activitystreams::{
};
use config::Environment;
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
use rustls::{Certificate, PrivateKey};
use sha2::{Digest, Sha256};
use std::{io::BufReader, net::IpAddr, path::PathBuf};
use std::{net::IpAddr, path::PathBuf};
use uuid::Uuid;
#[derive(Clone, Debug, serde::Deserialize)]
@ -35,11 +34,6 @@ pub(crate) struct ParsedConfig {
telegram_token: Option<String>,
telegram_admin_handle: 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)]
@ -58,16 +52,6 @@ pub struct Config {
telegram_token: Option<String>,
telegram_admin_handle: 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)]
@ -93,7 +77,6 @@ pub enum AdminUrlKind {
Allowed,
Blocked,
Connected,
Stats,
}
impl std::fmt::Debug for Config {
@ -116,11 +99,6 @@ 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()
}
}
@ -133,8 +111,8 @@ impl Config {
.set_default("port", 8080u64)?
.set_default("debug", true)?
.set_default("restricted_mode", false)?
.set_default("validate_signatures", true)?
.set_default("https", true)?
.set_default("validate_signatures", false)?
.set_default("https", false)?
.set_default("publish_blocks", false)?
.set_default("sled_path", "./sled/db-0-34")?
.set_default("source_repo", "https://git.asonix.dog/asonix/relay")?
@ -142,11 +120,6 @@ 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()?;
@ -155,26 +128,6 @@ 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,
@ -190,76 +143,9 @@ 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<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 {
&self.sled_path
}
@ -452,8 +338,6 @@ impl Config {
.try_resolve(IriRelativeStr::new("api/v1/admin/blocked")?.as_ref())?,
AdminUrlKind::Connected => FixedBaseResolver::new(self.base_uri.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)

View File

@ -100,12 +100,6 @@ pub(crate) enum ErrorKind {
#[error("Couldn't sign digest")]
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")]
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,13 +2,11 @@
#![allow(clippy::needless_borrow)]
use activitystreams::iri_string::types::IriString;
use actix_web::{middleware::Compress, web, App, HttpServer};
use collector::MemoryCollector;
use actix_web::{web, App, HttpServer};
#[cfg(feature = "console")]
use console_subscriber::ConsoleLayer;
use opentelemetry::{sdk::Resource, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use rustls::ServerConfig;
use tracing_actix_web::TracingLogger;
use tracing_error::ErrorLayer;
use tracing_log::LogTracer;
@ -17,7 +15,6 @@ use tracing_subscriber::{filter::Targets, fmt::format::FmtSpan, layer::Subscribe
mod admin;
mod apub;
mod args;
mod collector;
mod config;
mod data;
mod db;
@ -35,7 +32,7 @@ use self::{
data::{ActorCache, MediaCache, State},
db::Db,
jobs::create_workers,
middleware::{DebugPayload, RelayResolver, Timings},
middleware::{DebugPayload, RelayResolver},
routes::{actor, inbox, index, nodeinfo, nodeinfo_meta, statics},
};
@ -46,7 +43,7 @@ fn init_subscriber(
LogTracer::init()?;
let targets: Targets = std::env::var("RUST_LOG")
.unwrap_or_else(|_| "warn,actix_web=debug,actix_server=debug,tracing_actix_web=info".into())
.unwrap_or_else(|_| "info".into())
.parse()?;
let format_layer = tracing_subscriber::fmt::layer()
@ -94,120 +91,72 @@ fn init_subscriber(
Ok(())
}
fn main() -> Result<(), anyhow::Error> {
#[actix_rt::main]
async fn main() -> Result<(), anyhow::Error> {
dotenv::dotenv().ok();
let config = Config::build()?;
init_subscriber(Config::software_name(), config.opentelemetry_url())?;
let collector = MemoryCollector::new();
collector.install()?;
let args = Args::new();
if args.any() {
return client_main(config, args);
let client = requests::build_client(&config.user_agent());
if !args.blocks().is_empty() || !args.allowed().is_empty() {
if args.undo() {
admin::client::unblock(&client, &config, args.blocks().to_vec()).await?;
admin::client::disallow(&client, &config, args.allowed().to_vec()).await?;
} else {
admin::client::block(&client, &config, args.blocks().to_vec()).await?;
admin::client::allow(&client, &config, args.allowed().to_vec()).await?;
}
println!("Updated lists");
}
if args.list() {
let (blocked, allowed, connected) = tokio::try_join!(
admin::client::blocked(&client, &config),
admin::client::allowed(&client, &config),
admin::client::connected(&client, &config)
)?;
let mut report = String::from("Report:\n");
if !allowed.allowed_domains.is_empty() {
report += "\nAllowed\n\t";
report += &allowed.allowed_domains.join("\n\t");
}
if !blocked.blocked_domains.is_empty() {
report += "\n\nBlocked\n\t";
report += &blocked.blocked_domains.join("\n\t");
}
if !connected.connected_actors.is_empty() {
report += "\n\nConnected\n\t";
report += &connected.connected_actors.join("\n\t");
}
report += "\n";
println!("{report}");
}
return Ok(());
}
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());
if !args.blocks().is_empty() || !args.allowed().is_empty() {
if args.undo() {
admin::client::unblock(&client, &config, args.blocks().to_vec()).await?;
admin::client::disallow(&client, &config, args.allowed().to_vec()).await?;
} else {
admin::client::block(&client, &config, args.blocks().to_vec()).await?;
admin::client::allow(&client, &config, args.allowed().to_vec()).await?;
}
println!("Updated lists");
}
if args.list() {
let (blocked, allowed, connected) = tokio::try_join!(
admin::client::blocked(&client, &config),
admin::client::allowed(&client, &config),
admin::client::connected(&client, &config)
)?;
let mut report = String::from("Report:\n");
if !allowed.allowed_domains.is_empty() {
report += "\nAllowed\n\t";
report += &allowed.allowed_domains.join("\n\t");
}
if !blocked.blocked_domains.is_empty() {
report += "\n\nBlocked\n\t";
report += &blocked.blocked_domains.join("\n\t");
}
if !connected.connected_actors.is_empty() {
report += "\n\nConnected\n\t";
report += &connected.connected_actors.join("\n\t");
}
report += "\n";
println!("{report}");
}
if args.stats() {
let stats = admin::client::stats(&client, &config).await?;
stats.present();
}
Ok(())
}
#[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 actors = ActorCache::new(db.clone());
tracing::warn!("Creating workers");
let (manager, job_server) =
create_workers(state.clone(), actors.clone(), media.clone(), config.clone());
if let Some((token, admin_handle)) = config.telegram_info() {
tracing::warn!("Creating telegram handler");
telegram::start(admin_handle.to_owned(), db.clone(), token);
}
let keys = config.open_keys()?;
let bind_address = config.bind_address();
let server = HttpServer::new(move || {
HttpServer::new(move || {
let app = App::new()
.app_data(web::Data::new(db.clone()))
.app_data(web::Data::new(state.clone()))
@ -215,8 +164,7 @@ async fn do_server_main(
.app_data(web::Data::new(actors.clone()))
.app_data(web::Data::new(config.clone()))
.app_data(web::Data::new(job_server.clone()))
.app_data(web::Data::new(media.clone()))
.app_data(web::Data::new(collector.clone()));
.app_data(web::Data::new(media.clone()));
let app = if let Some(data) = config.admin_config() {
app.app_data(data)
@ -224,9 +172,7 @@ async fn do_server_main(
app
};
app.wrap(Compress::default())
.wrap(TracingLogger::default())
.wrap(Timings)
app.wrap(TracingLogger::default())
.service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/media/{path}").route(web::get().to(routes::media)))
.service(
@ -257,35 +203,16 @@ async fn do_server_main(
.route("/unblock", web::post().to(admin::routes::unblock))
.route("/allowed", web::get().to(admin::routes::allowed))
.route("/blocked", web::get().to(admin::routes::blocked))
.route("/connected", web::get().to(admin::routes::connected))
.route("/stats", web::get().to(admin::routes::stats)),
.route("/connected", web::get().to(admin::routes::connected)),
),
)
});
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()
.await?;
} else {
tracing::warn!("Binding to {}:{}", bind_address.0, bind_address.1);
server.bind(bind_address)?.run().await?;
}
tracing::warn!("Server closed");
})
.bind(bind_address)?
.run()
.await?;
drop(manager);
tracing::warn!("Main complete");
Ok(())
}

View File

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

View File

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

View File

@ -1,143 +0,0 @@
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);
impl MyVerify {
#[tracing::instrument("Verify request", skip(self, signature, signing_string))]
#[tracing::instrument("Verify signature", skip(self, signature))]
async fn verify(
&self,
algorithm: Option<Algorithm>,
@ -106,7 +106,6 @@ impl PublicKeyResponse {
}
}
#[tracing::instrument("Verify signature")]
async fn do_verify(
public_key: &str,
signature: String,
@ -114,20 +113,15 @@ async fn do_verify(
) -> Result<(), Error> {
let public_key = RsaPublicKey::from_public_key_pem(public_key.trim())?;
let span = tracing::Span::current();
web::block(move || {
span.in_scope(|| {
let decoded = base64::decode(signature)?;
let signature = Signature::from_bytes(&decoded).map_err(ErrorKind::ReadSignature)?;
let hashed = Sha256::new_with_prefix(signing_string.as_bytes());
let decoded = base64::decode(signature)?;
let signature = Signature::from_bytes(&decoded)?;
let hashed = Sha256::new_with_prefix(signing_string.as_bytes());
let verifying_key = VerifyingKey::new_with_prefix(public_key);
verifying_key
.verify_digest(hashed, &signature)
.map_err(ErrorKind::VerifySignature)?;
let verifying_key = VerifyingKey::new_with_prefix(public_key);
verifying_key.verify_digest(hashed, &signature)?;
Ok(()) as Result<(), Error>
})
Ok(()) as Result<(), Error>
})
.await??;

View File

@ -7,19 +7,6 @@ use actix_web::{web, HttpResponse};
use rand::{seq::SliceRandom, thread_rng};
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 {
node.instance
.as_ref()
@ -33,28 +20,7 @@ pub(crate) async fn route(
state: web::Data<State>,
config: web::Data<Config>,
) -> Result<HttpResponse, Error> {
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);
}
}
let mut nodes = state.node_cache().nodes().await?;
nodes.sort_by(|lhs, rhs| match (open_reg(lhs), open_reg(rhs)) {
(true, true) | (false, false) => std::cmp::Ordering::Equal,
@ -71,13 +37,11 @@ pub(crate) async fn route(
let mut buf = BufWriter::new(Vec::new());
crate::templates::index(&mut buf, &local, &nodes, &config)?;
let html = buf.into_inner().map_err(|e| {
crate::templates::index(&mut buf, &nodes, &config)?;
let buf = buf.into_inner().map_err(|e| {
tracing::error!("Error rendering template, {}", e.error());
ErrorKind::FlushBuffer
})?;
let html = minify_html::minify(&html, &MINIFY_CONFIG);
Ok(HttpResponse::Ok().content_type("text/html").body(html))
Ok(HttpResponse::Ok().content_type("text/html").body(buf))
}

View File

@ -4,15 +4,15 @@
@(contact: &Contact, base: &IriString)
<div class="admin">
<div class="left">
<figure class="avatar">
<img src="@contact.avatar" alt="@contact.display_name's avatar">
</figure>
</div>
<div class="right">
<p class="display-name"><a href="@contact.url">@contact.display_name</a></p>
<p class="username">
@@@contact.username@if let Some(authority) = base.authority_str() {@@@authority}
</p>
</div>
<div class="left">
<figure class="avatar">
<img src="@contact.avatar" alt="@contact.display_name's avatar">
</figure>
</div>
<div class="right">
<p class="display-name"><a href="@contact.url">@contact.display_name</a></p>
<p class="username">
@@@contact.username@if let Some(authority) = base.authority_str() {@@@authority}
</p>
</div>
</div>

View File

@ -4,7 +4,7 @@ data::Node,
templates::{info, instance, statics::index_css},
};
@(local: &[Node], nodes: &[Node], config: &Config)
@(nodes: &[Node], config: &Config)
<!doctype html>
<html>
@ -12,7 +12,7 @@ templates::{info, instance, statics::index_css},
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>@config.hostname() | Relais ActivityPub</title>
<title>@config.hostname() | ActivityPub Relay</title>
<link rel="stylesheet" href="/static/@index_css.name" type="text/css" />
</head>
@ -24,23 +24,17 @@ templates::{info, instance, statics::index_css},
</div>
</header>
<main>
@if !local.is_empty() || config.local_blurb().is_some() {
<article>
<h3>A Propos</h3>
<section class="local-explainer">
@if let Some(blurb) = config.local_blurb() {
@blurb
} else {
<p>Ces domaines sont administrés par la même équipe que ce relais.</p>
}
</section>
@if !local.is_empty() {
<section>
<h3>@nodes.len() instances fédérées</h3>
<h3>@nodes.len() Connected Servers</h3>
@if nodes.is_empty() {
<p>Aucune instance fédérée en ce moment.</p>
} else {
<ul>
@for node in local {
@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)
@: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() {
@ -52,11 +46,10 @@ templates::{info, instance, statics::index_css},
}
</ul>
}
</article>
}
<article>
<a name="#joining"><h3>Rejoindre</h3></a>
<section class="joining">
</section>
<section>
<h3>Rejoindre</h3>
<article class="joining">
@if config.restricted_mode() {
<h4>
Ce relais est restreint.
@ -85,34 +78,10 @@ templates::{info, instance, statics::index_css},
<p>
Vérifiez la documentation de votre installation, qui suit probablement la convention de Mastodon ou de Pleroma.
</p>
</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>
}
</article>
</section>
</main>
<footer>
@if let Some(blurb) = config.footer_blurb() {
<div>@blurb</div>
}
<p>
Code source de l'application disponible ici:
<a href="@config.source_code()">@config.source_code()</a>

View File

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

View File

@ -3,24 +3,24 @@
@(instance: &Instance, software: Option<&str>, contact: Option<&Contact>, base: &IriString)
<section class="instance">
<h4 class="padded"><a href="@base">@instance.title</a></h4>
<p class="padded">
<article class="instance">
<h4 class="padded"><a href="@base">@instance.title</a></h4>
<p class="padded">
@if let Some(software) = software {
Utilise @software, version @instance.version.
Utilise @software, version @instance.version.
}
<br>
@if instance.reg {
@if instance.requires_approval {
<span class="moderated">Inscriptions soumises à approbation.</span>
} else {
} else{
<span class="open">Inscriptions ouvertes.</span>
}
} else {
} else{
<span class="closed">Inscriptions fermées.</span>
}
</p>
@if !instance.description.trim().is_empty() || contact.is_some() {
</p>
@if !instance.description.trim().is_empty() || contact.is_some() {
<div class="instance-info">
@if !instance.description.trim().is_empty() {
<h5 class="instance-description">Description:</h5>
@ -30,10 +30,10 @@
</div>
</div>
}
@if let Some(contact) = contact {
<h5 class="instance-admin">Administré par:</h5>
@:admin(contact, base)
}
</div>
@if let Some(contact) = contact {
<h5 class="instance-admin">Administré par:</h5>
@:admin(contact, base)
}
}
</section>
}
</article>