mirror of
https://forgejo.ellis.link/continuwuation/continuwuity/
synced 2026-04-02 10:25:43 +00:00
Compare commits
950 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fde835673 | ||
|
|
bf9d498621 | ||
|
|
f8cf57c1fb | ||
|
|
26a6d349dc | ||
|
|
77c0c13a83 | ||
|
|
36b8de1339 | ||
|
|
29a6065bae | ||
|
|
f4c8b77377 | ||
|
|
85400d15bc | ||
|
|
13334a88ca | ||
|
|
351062eb82 | ||
|
|
001523ec81 | ||
|
|
0a5f18ed83 | ||
|
|
16ce62302a | ||
|
|
0068624cd6 | ||
|
|
2837151dba | ||
|
|
3d8757c896 | ||
|
|
b175892178 | ||
|
|
d8a1996e24 | ||
|
|
a934a7f687 | ||
|
|
1638be0339 | ||
|
|
ea2343850f | ||
|
|
fcfb323cd5 | ||
|
|
84a8e36120 | ||
|
|
47f9c69eff | ||
|
|
73af171830 | ||
|
|
9654dd9218 | ||
|
|
dea5fee6a3 | ||
|
|
4d42a29c51 | ||
|
|
7b0e830f4c | ||
|
|
e7b2c14280 | ||
|
|
bb5f2556c3 | ||
|
|
f047675a63 | ||
|
|
f4db6292b3 | ||
|
|
fcb9d04d9e | ||
|
|
ffc41cb01f | ||
|
|
e4bcbb8088 | ||
|
|
e4ba34a638 | ||
|
|
d5e7befaa9 | ||
|
|
1157140657 | ||
|
|
fb49e37067 | ||
|
|
8923c9a227 | ||
|
|
2b7e0dcb80 | ||
|
|
e790785db8 | ||
|
|
6b65a8fc86 | ||
|
|
a79ae7d1a2 | ||
|
|
156199a218 | ||
|
|
c16e5b50eb | ||
|
|
47ca835c20 | ||
|
|
dc3d9ebbf1 | ||
|
|
c1bcdeaf01 | ||
|
|
08efebd5af | ||
|
|
ddacda9ecf | ||
|
|
1e4b1e516e | ||
|
|
fadb2de5bf | ||
|
|
edd42974e5 | ||
|
|
e0fd84149d | ||
|
|
23690fd837 | ||
|
|
867050df9e | ||
|
|
50e66a2976 | ||
|
|
4d0fc41222 | ||
|
|
152ae705a0 | ||
|
|
54e6a41404 | ||
|
|
c3f00f4d15 | ||
|
|
43300ea41e | ||
|
|
17a54bc4f8 | ||
|
|
f540bed61e | ||
|
|
2cf472a69b | ||
|
|
b2d8da489c | ||
|
|
025afb61cb | ||
|
|
40d53563fc | ||
|
|
5db816277b | ||
|
|
efa70b9e78 | ||
|
|
bac795de5f | ||
|
|
f69f4c479c | ||
|
|
992f725362 | ||
|
|
0e56d1c7a2 | ||
|
|
b4d5282123 | ||
|
|
15ad7523aa | ||
|
|
eb6e509ad8 | ||
|
|
4432c06c86 | ||
|
|
bc5ecb0c72 | ||
|
|
f1b41ce6e3 | ||
|
|
826b1d1d62 | ||
|
|
c40ed0c98e | ||
|
|
2d9aab2e79 | ||
|
|
66679c7522 | ||
|
|
a2afdbe1fa | ||
|
|
f86ab0cd03 | ||
|
|
7b8ef3b070 | ||
|
|
83ef4eecc7 | ||
|
|
52ccad04a6 | ||
|
|
8fe19a6ef0 | ||
|
|
fd96f597cd | ||
|
|
982f9eb780 | ||
|
|
aeb6fbe70f | ||
|
|
d264935625 | ||
|
|
28cd784972 | ||
|
|
6662aa21ef | ||
|
|
9b9a91f6ef | ||
|
|
94b805de0b | ||
|
|
eded585f79 | ||
|
|
a552321287 | ||
|
|
f98bfbbdcd | ||
|
|
e9ab548445 | ||
|
|
15184d1a79 | ||
|
|
0c6bbde25f | ||
|
|
13aa353765 | ||
|
|
ac941a801a | ||
|
|
f261d44edb | ||
|
|
849cfdcdfa | ||
|
|
1470331f7e | ||
|
|
dcaa96a79f | ||
|
|
101a966922 | ||
|
|
57940f38ea | ||
|
|
22f8c339fc | ||
|
|
c896acb729 | ||
|
|
7f1bc490d6 | ||
|
|
1091c30845 | ||
|
|
12d9e454db | ||
|
|
82b973a8b1 | ||
|
|
71ee31a014 | ||
|
|
69b4ec81b6 | ||
|
|
fe0969e7ca | ||
|
|
2e3e14b045 | ||
|
|
f51d4237c7 | ||
|
|
d8a87258d6 | ||
|
|
3da2242730 | ||
|
|
5add9a8c34 | ||
|
|
5b1642f641 | ||
|
|
eb94aa7079 | ||
|
|
24884c3b9b | ||
|
|
a0a002b17f | ||
|
|
f632b06e6d | ||
|
|
1f88866612 | ||
|
|
ccef1a4c8b | ||
|
|
5c258f41c8 | ||
|
|
15126ee1b2 | ||
|
|
e37ac56dba | ||
|
|
a74613be96 | ||
|
|
a95ded167f | ||
|
|
95121b6e05 | ||
|
|
e6884ef13a | ||
|
|
49033db2b8 | ||
|
|
ddc8c3b46c | ||
|
|
ea95627dce | ||
|
|
c3e751542d | ||
|
|
4b765aa17c | ||
|
|
954cfc6bb7 | ||
|
|
2f85a5c1ac | ||
|
|
7e50db4193 | ||
|
|
7a3cc3941e | ||
|
|
ca82b59c6f | ||
|
|
3b5607ecdc | ||
|
|
96f6a75bc8 | ||
|
|
68f42baf73 | ||
|
|
271959ee27 | ||
|
|
c423a83656 | ||
|
|
4e975887cf | ||
|
|
8bb69eb81d | ||
|
|
2100618d47 | ||
|
|
936d2915e2 | ||
|
|
f014231644 | ||
|
|
7d487d53d8 | ||
|
|
d7d874f88d | ||
|
|
ee864bcd9e | ||
|
|
111cbea6fc | ||
|
|
85f734ec74 | ||
|
|
2468e0c3de | ||
|
|
f841c2356d | ||
|
|
c64adbec0e | ||
|
|
4458efa2b2 | ||
|
|
5c0bf29122 | ||
|
|
91b49a7786 | ||
|
|
5a17fbccf5 | ||
|
|
5ed95ea357 | ||
|
|
ccfa939bd3 | ||
|
|
263e338088 | ||
|
|
2fb43dd38d | ||
|
|
59efabbbc2 | ||
|
|
010e4ee35a | ||
|
|
992c0a1e58 | ||
|
|
9b20c6918f | ||
|
|
3dc91525ce | ||
|
|
a88f913a17 | ||
|
|
4343218957 | ||
|
|
b116984e46 | ||
|
|
b0ac5255c8 | ||
|
|
29fc5b9b52 | ||
|
|
3ccd9ea326 | ||
|
|
8a2ae401df | ||
|
|
f465d77ad3 | ||
|
|
2fd6f6b0ff | ||
|
|
8b6018d77d | ||
|
|
463f1a1287 | ||
|
|
04fc87ad07 | ||
|
|
dc573f4562 | ||
|
|
74826dcb94 | ||
|
|
b20c4e0539 | ||
|
|
fb8a2846df | ||
|
|
04971d0430 | ||
|
|
f098532b09 | ||
|
|
60927c1c72 | ||
|
|
03296412ab | ||
|
|
934ab6a4fe | ||
|
|
1378399f9d | ||
|
|
09680f653f | ||
|
|
39f19c483a | ||
|
|
39c0f4ea3d | ||
|
|
405167fc3f | ||
|
|
b13ea4ff45 | ||
|
|
100fc5e0f7 | ||
|
|
96f9d64111 | ||
|
|
30ad5da5f1 | ||
|
|
ce0ca96df4 | ||
|
|
4851ad26e2 | ||
|
|
328759a60f | ||
|
|
463fa4fd53 | ||
|
|
db494e0d68 | ||
|
|
4788040458 | ||
|
|
06531993f6 | ||
|
|
c29197b3f4 | ||
|
|
739eab46d5 | ||
|
|
923a98eb66 | ||
|
|
4430e4dee0 | ||
|
|
d67f19a55d | ||
|
|
b903b46d16 | ||
|
|
167559bb27 | ||
|
|
838e4b9d8d | ||
|
|
038b71fc9d | ||
|
|
720fbd09c2 | ||
|
|
c42cb90dd3 | ||
|
|
5950355348 | ||
|
|
f79bd2ac72 | ||
|
|
80ec0e31b1 | ||
|
|
bda44b16b1 | ||
|
|
e2280aa1a5 | ||
|
|
bdf2de076a | ||
|
|
1797fec3c9 | ||
|
|
188fa5a073 | ||
|
|
f0c63c539b | ||
|
|
649e9da1f8 | ||
|
|
df28359a19 | ||
|
|
9370e93a8d | ||
|
|
bdd5845490 | ||
|
|
bacffd6174 | ||
|
|
a1bfd7a018 | ||
|
|
7009f56a7a | ||
|
|
2c0bfac43e | ||
|
|
fcb6c8a113 | ||
|
|
1ab77aeb91 | ||
|
|
3d73b53136 | ||
|
|
101fdbc9b1 | ||
|
|
e53c2fbc5a | ||
|
|
95006f7e46 | ||
|
|
cce270d938 | ||
|
|
5ec49b3f62 | ||
|
|
e4dc4a1ba5 | ||
|
|
d2fb6d04c9 | ||
|
|
05efd9b044 | ||
|
|
b3f2288d07 | ||
|
|
084751ae38 | ||
|
|
aa7a310200 | ||
|
|
38c989a07e | ||
|
|
5be679e17b | ||
|
|
b8baa1223d | ||
|
|
b87f1649d4 | ||
|
|
4a6f089b23 | ||
|
|
4600c7f32d | ||
|
|
4cc92dd175 | ||
|
|
93ec4e579b | ||
|
|
c111d2e395 | ||
|
|
03d890cd49 | ||
|
|
a35b6cbfdd | ||
|
|
5570220c89 | ||
|
|
899b79873e | ||
|
|
57969f9480 | ||
|
|
d88ab37120 | ||
|
|
bc58e5002d | ||
|
|
160f48043e | ||
|
|
0023b09f5b | ||
|
|
db3c718ddc | ||
|
|
e73aa2aa21 | ||
|
|
61f2a3c68b | ||
|
|
0627b46f40 | ||
|
|
f10f5319db | ||
|
|
16e76d45cb | ||
|
|
2a304c2b6c | ||
|
|
271f720286 | ||
|
|
2d251eb19c | ||
|
|
01b2928d55 | ||
|
|
50c2d2b801 | ||
|
|
5dcdafe207 | ||
|
|
c62d653989 | ||
|
|
454dd43d4c | ||
|
|
8077e910f6 | ||
|
|
b5d4a1c1b0 | ||
|
|
aa9540af21 | ||
|
|
080975ab0e | ||
|
|
f129d90900 | ||
|
|
53223a4d5f | ||
|
|
05befa4ba2 | ||
|
|
a8e690f22b | ||
|
|
192c1e08da | ||
|
|
0fa6976d86 | ||
|
|
93e7cf461d | ||
|
|
d036d8adcb | ||
|
|
7a4bbe2ff6 | ||
|
|
438911c18d | ||
|
|
bd71435a22 | ||
|
|
53fa7c3729 | ||
|
|
d2facaee0b | ||
|
|
0dae9280d9 | ||
|
|
35336eb686 | ||
|
|
efea13a675 | ||
|
|
b5ee15a216 | ||
|
|
0873e18e14 | ||
|
|
6abc4ad798 | ||
|
|
373991a8d6 | ||
|
|
68ad351f84 | ||
|
|
83e853e7a3 | ||
|
|
1c453b1b55 | ||
|
|
60141950f7 | ||
|
|
391bfd986e | ||
|
|
da03de1d32 | ||
|
|
e54f4d4397 | ||
|
|
daa5c34ea3 | ||
|
|
3259ea08b5 | ||
|
|
158de10fe6 | ||
|
|
4718387dbe | ||
|
|
a43c78e55f | ||
|
|
7ba0777bd3 | ||
|
|
59d86d3641 | ||
|
|
51df946911 | ||
|
|
23a9055199 | ||
|
|
100c6f572b | ||
|
|
eb5556e74e | ||
|
|
8296e0ed67 | ||
|
|
8e3be6feb0 | ||
|
|
59c4062305 | ||
|
|
113a27c1d5 | ||
|
|
5af880e4f4 | ||
|
|
56a1b0e761 | ||
|
|
5722c4ae39 | ||
|
|
dfd13780df | ||
|
|
dcd7422c45 | ||
|
|
7397064edd | ||
|
|
52a561ff9e | ||
|
|
5e72d36800 | ||
|
|
611f09829e | ||
|
|
a388c2e06e | ||
|
|
24b37e03a0 | ||
|
|
a309ef55c9 | ||
|
|
c1c084dda1 | ||
|
|
72d9e8ed2b | ||
|
|
a3638dbb15 | ||
|
|
5254eb4f72 | ||
|
|
b31e81a469 | ||
|
|
0e580292a6 | ||
|
|
38a24e0170 | ||
|
|
8350aced39 | ||
|
|
04e3de08eb | ||
|
|
2bc53139fa | ||
|
|
8691141237 | ||
|
|
17a3ed4c56 | ||
|
|
2dd68d3fa5 | ||
|
|
dc18f89c0b | ||
|
|
eaf1cf38a5 | ||
|
|
eeda96d94a | ||
|
|
1e8b8cce0f | ||
|
|
db46d6dd6b | ||
|
|
08274150e5 | ||
|
|
e125af620e | ||
|
|
177c9e8bfa | ||
|
|
4f5c6de853 | ||
|
|
25c004f08c | ||
|
|
f104ced55d | ||
|
|
0cea64309a | ||
|
|
229f2fde7a | ||
|
|
9a0ffd55da | ||
|
|
d1b3faf4bd | ||
|
|
4014cb4413 | ||
|
|
b2dbc3bc10 | ||
|
|
e2033fb6c1 | ||
|
|
6b433392a4 | ||
|
|
0832c28a40 | ||
|
|
b0eec60724 | ||
|
|
82acc7c0b0 | ||
|
|
c9384e9c78 | ||
|
|
3502e6ef87 | ||
|
|
deca2c52c6 | ||
|
|
daa46a0dc4 | ||
|
|
74791642c1 | ||
|
|
1a64e42cfe | ||
|
|
5edd391e83 | ||
|
|
7658387a74 | ||
|
|
be2d1c722b | ||
|
|
8cf55c702f | ||
|
|
3b9fba233c | ||
|
|
af81baae44 | ||
|
|
52d470058a | ||
|
|
df0328f43f | ||
|
|
473b29d524 | ||
|
|
e1d1dac95e | ||
|
|
a2d25215a3 | ||
|
|
46423cab4f | ||
|
|
2e2cf08bb2 | ||
|
|
ee64fb149c | ||
|
|
0522fe7d92 | ||
|
|
6dd6e4bfaf | ||
|
|
a1ced0a56f | ||
|
|
0171262581 | ||
|
|
972037dcd9 | ||
|
|
0613140130 | ||
|
|
f43c09b05d | ||
|
|
b035cd7cbe | ||
|
|
b7aa79884d | ||
|
|
9c6cc44abb | ||
|
|
9c0d7b1da0 | ||
|
|
ed3a464722 | ||
|
|
28ac3790c2 | ||
|
|
9115901c66 | ||
|
|
652faafe6e | ||
|
|
8deba94a12 | ||
|
|
bb27f21ac1 | ||
|
|
339a1fc4e8 | ||
|
|
75731671d1 | ||
|
|
20aab1792b | ||
|
|
e9f4cd58a9 | ||
|
|
6bae136720 | ||
|
|
39a1e994eb | ||
|
|
c2c36197e9 | ||
|
|
c952db72e7 | ||
|
|
1ca484f90d | ||
|
|
b1150cded9 | ||
|
|
2b35f83089 | ||
|
|
792d4aa6ce | ||
|
|
6c461025e5 | ||
|
|
9d1db7d171 | ||
|
|
2387f7f955 | ||
|
|
5805394ca5 | ||
|
|
3301cde8e7 | ||
|
|
b28d216e00 | ||
|
|
f127987c7a | ||
|
|
fa02d7b7e3 | ||
|
|
96a16190c5 | ||
|
|
d9054f5364 | ||
|
|
db288b169e | ||
|
|
bc50034173 | ||
|
|
20fe84379c | ||
|
|
06f01d384f | ||
|
|
bd19fac4c5 | ||
|
|
703c275266 | ||
|
|
c05f00661b | ||
|
|
0e46b750a0 | ||
|
|
3a51e18ce6 | ||
|
|
7638bbc49c | ||
|
|
35049c94de | ||
|
|
7db8478dc0 | ||
|
|
df7a1dee15 | ||
|
|
dc633100da | ||
|
|
0f1432f448 | ||
|
|
30b5ad3870 | ||
|
|
cb48e25783 | ||
|
|
57acc4f655 | ||
|
|
875d9e8b07 | ||
|
|
de21f7442a | ||
|
|
7ad7badd60 | ||
|
|
a6edaad6fc | ||
|
|
fbcdb3860a | ||
|
|
01f4455ceb | ||
|
|
c1712d4d8b | ||
|
|
3480074f61 | ||
|
|
efbdced535 | ||
|
|
19d8f0b27e | ||
|
|
8d5305c769 | ||
|
|
73b85372d6 | ||
|
|
5e6b9ec1cf | ||
|
|
e24b7d2f3e | ||
|
|
c61258fa0d | ||
|
|
65177d237a | ||
|
|
325f1c0bd2 | ||
|
|
68f1b4789a | ||
|
|
624cd2acfa | ||
|
|
fb758f5f4b | ||
|
|
5d138a7e5e | ||
|
|
563618cc9e | ||
|
|
0a28d80cf2 | ||
|
|
6f9d7b5667 | ||
|
|
d875e0c1c0 | ||
|
|
7a71012589 | ||
|
|
189688994b | ||
|
|
bc093e9544 | ||
|
|
2dae3052e2 | ||
|
|
0f07b86159 | ||
|
|
2f84bc895d | ||
|
|
3b34e72456 | ||
|
|
7eee88160a | ||
|
|
5f46623371 | ||
|
|
cbb97b4fdf | ||
|
|
14dcc8db15 | ||
|
|
213a6d441f | ||
|
|
98d96b89a5 | ||
|
|
1c0ed91f6f | ||
|
|
64705fa27d | ||
|
|
8affdc43a6 | ||
|
|
1ccdba8921 | ||
|
|
50ce87161b | ||
|
|
c6f4b20e17 | ||
|
|
2cb31275f0 | ||
|
|
5aee03d14a | ||
|
|
959fd2e6c4 | ||
|
|
f40a3ea4a6 | ||
|
|
aa963c61da | ||
|
|
c98cf13010 | ||
|
|
73dd4b1f8f | ||
|
|
c921deaee2 | ||
|
|
a5de4d30bb | ||
|
|
9e9b256b83 | ||
|
|
ddf327e8b6 | ||
|
|
3341a8e56e | ||
|
|
4942d684ea | ||
|
|
f8f6dba674 | ||
|
|
e5c9bf1992 | ||
|
|
502856a4a2 | ||
|
|
2be2a0dc91 | ||
|
|
cd3e7394bf | ||
|
|
a543bec1a5 | ||
|
|
a589a34d15 | ||
|
|
4312ac91c1 | ||
|
|
e83b426caf | ||
|
|
cad1d34611 | ||
|
|
ce4517269b | ||
|
|
169617ab87 | ||
|
|
3596fe0e18 | ||
|
|
a22524496d | ||
|
|
1d1b1644e9 | ||
|
|
483f0a9c86 | ||
|
|
3d3d63fdf4 | ||
|
|
08f2b8579c | ||
|
|
30e7298dd7 | ||
|
|
83565007bb | ||
|
|
3872ae80f7 | ||
|
|
0923b6f428 | ||
|
|
48d9677959 | ||
|
|
02bd67dc4b | ||
|
|
3813628acd | ||
|
|
7b7593a6f5 | ||
|
|
1323506c88 | ||
|
|
332fc74fb8 | ||
|
|
03c04ce0a1 | ||
|
|
08bf074cbb | ||
|
|
539aa27815 | ||
|
|
22272bdc16 | ||
|
|
8b68d6306c | ||
|
|
d4775f0763 | ||
|
|
571ab6ac2b | ||
|
|
5df7443437 | ||
|
|
e76e604771 | ||
|
|
1f9225e4d1 | ||
|
|
c914a4fd91 | ||
|
|
aa34021b27 | ||
|
|
1bb4021b90 | ||
|
|
877c04de52 | ||
|
|
029e1c630a | ||
|
|
39110ebc3c | ||
|
|
2ab3231ea6 | ||
|
|
d0069cc100 | ||
|
|
556e78214a | ||
|
|
8fff7ea706 | ||
|
|
f712c0cefb | ||
|
|
26d103d314 | ||
|
|
0688a96c37 | ||
|
|
eb73d8c669 | ||
|
|
20a54aacd6 | ||
|
|
81cd677b4e | ||
|
|
73da353e52 | ||
|
|
d5677b6ae7 | ||
|
|
01a77f8a71 | ||
|
|
ea03a50e21 | ||
|
|
25d44cad31 | ||
|
|
91519959ed | ||
|
|
2e31bcc213 | ||
|
|
305dfc3b42 | ||
|
|
65fbb80145 | ||
|
|
f1d90e5df6 | ||
|
|
74b29ce067 | ||
|
|
0e7c3cb338 | ||
|
|
14a3471fcb | ||
|
|
c834e86e67 | ||
|
|
b4f0a8a8b5 | ||
|
|
9bb90213e1 | ||
|
|
fcdf1463ef | ||
|
|
88d038ffec | ||
|
|
4b4c0952a2 | ||
|
|
016270b33b | ||
|
|
d2063013b4 | ||
|
|
03ba9bde29 | ||
|
|
1287a86c05 | ||
|
|
8210e8c42e | ||
|
|
adf0bfd894 | ||
|
|
6b843ec4dd | ||
|
|
ac02078395 | ||
|
|
b9d38fd3ba | ||
|
|
1b2c8236fb | ||
|
|
d7b8af627c | ||
|
|
130aae8758 | ||
|
|
4741a76896 | ||
|
|
5bfb62e979 | ||
|
|
cb03654dc1 | ||
|
|
f0557e3303 | ||
|
|
f52acd9cdf | ||
|
|
eae41fc411 | ||
|
|
c3c91e9d80 | ||
|
|
a8de5d1e60 | ||
|
|
7688d67870 | ||
|
|
89d7d48324 | ||
|
|
b525031a25 | ||
|
|
67f4285504 | ||
|
|
b65f05ce19 | ||
|
|
db2c9f28b6 | ||
|
|
fc1b8326e6 | ||
|
|
6e50b07bf5 | ||
|
|
9a3c52aa75 | ||
|
|
ccf9f95cc9 | ||
|
|
0524e6ed52 | ||
|
|
7f5b59afbb | ||
|
|
ab5db37851 | ||
|
|
c0c7f23a05 | ||
|
|
14ec41c211 | ||
|
|
2230bc7339 | ||
|
|
0ebabba971 | ||
|
|
3ed561cb31 | ||
|
|
a061644b2d | ||
|
|
82ac6b01b2 | ||
|
|
97ddb2ce87 | ||
|
|
a04ff7d4af | ||
|
|
10dfbf6420 | ||
|
|
d10bc67c9d | ||
|
|
197a02bf8d | ||
|
|
8103bd7310 | ||
|
|
81487e3f07 | ||
|
|
bfbb29dded | ||
|
|
1cc7cf54a7 | ||
|
|
40e4019f7f | ||
|
|
176d95c2a8 | ||
|
|
8d32fb1445 | ||
|
|
82a3b73774 | ||
|
|
1f19356693 | ||
|
|
3ada847570 | ||
|
|
0bade5317f | ||
|
|
c2267d4c03 | ||
|
|
aebae11c82 | ||
|
|
f871d8fd4e | ||
|
|
4a68e28c71 | ||
|
|
6e59135a7d | ||
|
|
0e74ade7d7 | ||
|
|
e4aa20ebeb | ||
|
|
427aa4645c | ||
|
|
73718a1208 | ||
|
|
0e3d192ad2 | ||
|
|
76a4d8aa4c | ||
|
|
9bb52cb3ec | ||
|
|
dd49b3c3a1 | ||
|
|
b2e56777af | ||
|
|
f32380772f | ||
|
|
8428f43c78 | ||
|
|
3af153f5ae | ||
|
|
38238c309f | ||
|
|
0857fe7907 | ||
|
|
c738c119f8 | ||
|
|
c1227340b3 | ||
|
|
bf10ff65a4 | ||
|
|
b781771a9b | ||
|
|
df8ba04e31 | ||
|
|
19926ba00d | ||
|
|
893cc50570 | ||
|
|
c9fbbdce1c | ||
|
|
732e8b82aa | ||
|
|
282c2feca8 | ||
|
|
919735b4ce | ||
|
|
2e83e56a07 | ||
|
|
ff7dfec74c | ||
|
|
84290bd668 | ||
|
|
b29a8791de | ||
|
|
83220b43a2 | ||
|
|
4ea7af5780 | ||
|
|
79fb8091dc | ||
|
|
f6fa2a4f65 | ||
|
|
b63937af0b | ||
|
|
3c4e325036 | ||
|
|
023fb41c49 | ||
|
|
9a5f1dac57 | ||
|
|
173ff26eb6 | ||
|
|
45e3fdba69 | ||
|
|
9f359e0550 | ||
|
|
ffdf47d1ea | ||
|
|
1af65e695d | ||
|
|
b1886583d9 | ||
|
|
f11103b43b | ||
|
|
9b096cc67b | ||
|
|
1ac72ab914 | ||
|
|
f0533e07ef | ||
|
|
68f42f5a2f | ||
|
|
884cbab135 | ||
|
|
4aead5de7a | ||
|
|
aef25ea1f7 | ||
|
|
1a4736d40b | ||
|
|
f09e0dc137 | ||
|
|
de79b66cea | ||
|
|
95ca9d00a2 | ||
|
|
887496d040 | ||
|
|
c2586737ae | ||
|
|
7d2f510cc3 | ||
|
|
102bd1b4a6 | ||
|
|
89ab687f16 | ||
|
|
1108235c63 | ||
|
|
90d9a997a5 | ||
|
|
5fe5ab279c | ||
|
|
f1d1366129 | ||
|
|
ba48758b89 | ||
|
|
9df5265c00 | ||
|
|
ee52d2f751 | ||
|
|
53fe2362fc | ||
|
|
38ab1083e3 | ||
|
|
050841a871 | ||
|
|
4521e93d04 | ||
|
|
0f3d43153b | ||
|
|
e5eccb3a0c | ||
|
|
68cbf19154 | ||
|
|
2ab427fe99 | ||
|
|
02081b66c4 | ||
|
|
b3fc8516ed | ||
|
|
9e51525c25 | ||
|
|
14039d9df4 | ||
|
|
eed8a2a801 | ||
|
|
c3a0d28309 | ||
|
|
6d1144bb69 | ||
|
|
2e45cb281a | ||
|
|
0baa57f5d9 | ||
|
|
faa2b95c84 | ||
|
|
dd1d8fa760 | ||
|
|
f4cfc77a57 | ||
|
|
b8b93a2e86 | ||
|
|
29d69b7688 | ||
|
|
bd07fb61e0 | ||
|
|
a41a60ef07 | ||
|
|
ec7a9ab726 | ||
|
|
25f598ce6c | ||
|
|
dbcb3be0ab | ||
|
|
a537462d51 | ||
|
|
d2aef071bc | ||
|
|
d68b11e8ff | ||
|
|
9cf5b0926e | ||
|
|
ff0b57c89c | ||
|
|
b94045a468 | ||
|
|
3122648767 | ||
|
|
3f5349ad76 | ||
|
|
27dcf213f1 | ||
|
|
a1b526b3b7 | ||
|
|
dc614e11d6 | ||
|
|
c5569b4c6e | ||
|
|
71a1285c7b | ||
|
|
abdda6cf32 | ||
|
|
4d21f9d962 | ||
|
|
1013fe5a42 | ||
|
|
f31b7b9420 | ||
|
|
e5e358cc68 | ||
|
|
50bc7cc005 | ||
|
|
445015e9ea | ||
|
|
7a38c12e5d | ||
|
|
2a77951152 | ||
|
|
0256c27363 | ||
|
|
826edc0a3a | ||
|
|
a5043a38e1 | ||
|
|
bfd471a863 | ||
|
|
3981e77ec6 | ||
|
|
81bf4b7150 | ||
|
|
b8ec763a7c | ||
|
|
003d4edbfa | ||
|
|
4f0006d18a | ||
|
|
b822e3a94c | ||
|
|
68fffe8e96 | ||
|
|
7328ed7509 | ||
|
|
6ccf578437 | ||
|
|
8a1848a814 | ||
|
|
b4cd8e9140 | ||
|
|
a08f90b161 | ||
|
|
207979579c | ||
|
|
68b96026ec | ||
|
|
30beb20230 | ||
|
|
19e7779693 | ||
|
|
6269822613 | ||
|
|
0877ee6191 | ||
|
|
a37b2b9e64 | ||
|
|
29fe960efa | ||
|
|
6bf2e73830 | ||
|
|
630760b5da | ||
|
|
61e7f1e614 | ||
|
|
7ebed7aa3e | ||
|
|
ad3eeaf4c1 | ||
|
|
5215fbe695 | ||
|
|
dc9fe657d5 | ||
|
|
1c7c5bc09c | ||
|
|
32161801ed | ||
|
|
71bdcb958a | ||
|
|
d3db0ad4e2 | ||
|
|
e098448b9d | ||
|
|
d49507bc21 | ||
|
|
cb73ae3732 | ||
|
|
06bec40591 | ||
|
|
9a7ba94ccf | ||
|
|
2990c30ac9 | ||
|
|
d9c575d96f | ||
|
|
c32406aa0e | ||
|
|
03d12cb44e | ||
|
|
bef7dbd1cb | ||
|
|
08577873b4 | ||
|
|
a3931b0f1f | ||
|
|
ba2f22b5d3 | ||
|
|
0914aaa1b6 | ||
|
|
f3427afc7f | ||
|
|
9aa372d83b | ||
|
|
5893901a75 | ||
|
|
8ba9b33a95 | ||
|
|
70047ff26d | ||
|
|
1d57e14dc0 | ||
|
|
5d81203277 | ||
|
|
ad39a34c16 | ||
|
|
a007338b34 | ||
|
|
3d1507e6dd | ||
|
|
4cb7c0b982 | ||
|
|
0c34cf95ce | ||
|
|
17cc02ff99 | ||
|
|
c0f8253fc5 | ||
|
|
0fd0a5d73c | ||
|
|
4e6fc2f2df | ||
|
|
a6742ce8a7 | ||
|
|
188dea13e0 | ||
|
|
a7fe434086 | ||
|
|
eb8dd9cb44 | ||
|
|
474d50d10c | ||
|
|
2e732c711c | ||
|
|
981ec51ec0 | ||
|
|
2dd5cf8c68 | ||
|
|
74832bdc47 | ||
|
|
fdc9a9a1b8 | ||
|
|
1f3a9a40e5 | ||
|
|
362649ff87 | ||
|
|
4aeec78ab4 | ||
|
|
9bfa89a555 | ||
|
|
6c1434c165 | ||
|
|
ae1a4fd283 | ||
|
|
9eb0784f6f | ||
|
|
8bffcfe82b | ||
|
|
6ef4781050 | ||
|
|
302592f219 | ||
|
|
7cd72d8447 | ||
|
|
4389e08686 | ||
|
|
91064fe873 | ||
|
|
004354353a | ||
|
|
c64a507691 | ||
|
|
81d2078cdb | ||
|
|
f5864afb52 | ||
|
|
9a63e7cc9b | ||
|
|
296d7c58ee | ||
|
|
a8446f910a | ||
|
|
a063a6d088 | ||
|
|
5069c88f77 | ||
|
|
53974320e5 | ||
|
|
1c6ef66e3e | ||
|
|
ffb63c9c8d | ||
|
|
de6b296eb5 | ||
|
|
4c11c9f048 | ||
|
|
6074298426 | ||
|
|
6e9f68bf81 | ||
|
|
edd67a102a | ||
|
|
434b5118cc | ||
|
|
4185a33747 | ||
|
|
829307c83b | ||
|
|
2bd7a92256 | ||
|
|
bfa33f8713 | ||
|
|
040cf29051 | ||
|
|
80bc1cd78a | ||
|
|
78994deb1e | ||
|
|
714b3e7144 | ||
|
|
1cd57f40f6 | ||
|
|
da9a0eb77b | ||
|
|
37b2c90e62 | ||
|
|
ba150a1185 | ||
|
|
ddce9496f2 | ||
|
|
fe637f481d | ||
|
|
18e43e1d35 | ||
|
|
09fca89ac5 | ||
|
|
9f19a2025d | ||
|
|
6b918966d4 | ||
|
|
328502c1cd | ||
|
|
d15e461303 | ||
|
|
6946eead28 | ||
|
|
09d3240365 | ||
|
|
653ec3799e | ||
|
|
6de9f52d5a | ||
|
|
484e7d1d2a | ||
|
|
dfa01541b3 | ||
|
|
adbe9268ce | ||
|
|
3504e6e724 | ||
|
|
154b2ab490 | ||
|
|
2231ccf118 | ||
|
|
d4d9f92ade | ||
|
|
e4e1636da8 | ||
|
|
e99aac9550 | ||
|
|
ddb87168ed | ||
|
|
245c34e659 | ||
|
|
43b07be3fc | ||
|
|
99d98efeb1 | ||
|
|
7b25ef2e6c | ||
|
|
1f8a7a707c | ||
|
|
86ec20e787 | ||
|
|
8c21388f01 | ||
|
|
d657fa32e9 | ||
|
|
321e197d8c | ||
|
|
16a98b0683 | ||
|
|
9e1bbc1650 | ||
|
|
91ff6a36a4 | ||
|
|
56f1d8be1f | ||
|
|
ed60f189cc | ||
|
|
cabf4362be | ||
|
|
2472c7c47a | ||
|
|
136cb038cf | ||
|
|
8f89be0fbd | ||
|
|
bbdced9c90 | ||
|
|
a6f4dc2b74 | ||
|
|
df203fa244 | ||
|
|
c6e6eb0af3 | ||
|
|
29babebc4d | ||
|
|
2f3194840c | ||
|
|
0ebb323490 | ||
|
|
f8e1255994 | ||
|
|
b5c0c30a5e | ||
|
|
ac4590952b | ||
|
|
67569cb9c8 | ||
|
|
11ec0dff4f | ||
|
|
a198f0481a | ||
|
|
6266e0ab5e | ||
|
|
9ee1485960 | ||
|
|
05314ec46c | ||
|
|
b66d2d44d0 | ||
|
|
3b2db9027a | ||
|
|
97e81885db | ||
|
|
706c1c993b |
@@ -11,5 +11,13 @@ indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.{md,txt}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 80
|
||||
|
||||
[*.nix]
|
||||
indent_size = 2
|
||||
indent_size = 2
|
||||
|
||||
[*.rs]
|
||||
indent_style = tab
|
||||
|
||||
4
.envrc
4
.envrc
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
use flake
|
||||
dotenv_if_exists
|
||||
|
||||
use flake ".#${DIRENV_DEVSHELL:-default}"
|
||||
|
||||
PATH_add bin
|
||||
|
||||
264
.gitea/workflows/ci.yml
Normal file
264
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,264 @@
|
||||
name: CI and Artifacts
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
# documentation workflow deals with this or is not relevant for this workflow
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'conduwuit-example.toml'
|
||||
- 'book.toml'
|
||||
- '.gitlab-ci.yml'
|
||||
- '.gitignore'
|
||||
- 'renovate.json'
|
||||
- 'docs/**'
|
||||
- 'debian/**'
|
||||
- 'docker/**'
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
#workflow_dispatch:
|
||||
|
||||
#concurrency:
|
||||
# group: ${{ gitea.head_ref || gitea.ref_name }}
|
||||
# cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Required to make some things output color
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# conduwuit.cachix.org
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# Just in case incremental is still being set to true, speeds up CI
|
||||
CARGO_INCREMENTAL: 0
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
# Get error output from nix that we can actually use
|
||||
NIX_CONFIG: show-trace = true
|
||||
|
||||
#permissions:
|
||||
# packages: write
|
||||
# contents: read
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Tag comparison check
|
||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||
run: |
|
||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
if [ $LATEST_TAG != ${{ gitea.ref_name }} ]; then
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Install Nix
|
||||
uses: https://github.com/DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
extra-conf: |
|
||||
experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
|
||||
- name: Prepare build environment
|
||||
run: |
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
bin/nix-build-and-cache ci
|
||||
|
||||
- name: Run CI tests
|
||||
run: |
|
||||
direnv exec . engage > >(tee -a test_output.log)
|
||||
|
||||
- name: Sync Complement repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
with:
|
||||
repository: 'matrix-org/complement'
|
||||
path: complement_src
|
||||
|
||||
- name: Run Complement tests
|
||||
run: |
|
||||
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
|
||||
cp -v -f result complement_oci_image.tar.gz
|
||||
|
||||
- name: Upload Complement OCI image
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_oci_image.tar.gz
|
||||
path: complement_oci_image.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement logs
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_test_logs.jsonl
|
||||
path: complement_test_logs.jsonl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement results
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_test_results.jsonl
|
||||
path: complement_test_results.jsonl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Diff Complement results with checked-in repo results
|
||||
run: |
|
||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_test_output.log)
|
||||
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 100 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Update Job Summary
|
||||
if: success() || failure()
|
||||
run: |
|
||||
if [ ${{ job.status }} == 'success' ]; then
|
||||
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
- target: x86_64-unknown-linux-musl
|
||||
steps:
|
||||
- name: Sync repository
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: https://github.com/DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
extra-conf: |
|
||||
experimental-features = nix-command flakes
|
||||
accept-flake-config = true
|
||||
|
||||
- name: Install and enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: https://github.com/DeterminateSystems/magic-nix-cache-action@main
|
||||
with:
|
||||
diagnostic-endpoint: ""
|
||||
upstream-cache: "https://attic.kennel.juneis.dog/conduwuit"
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
|
||||
- name: Prepare build environment
|
||||
run: |
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop .#all-features --command true
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||
|
||||
- name: Upload static-${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-${{ matrix.target }}
|
||||
path: static-${{ matrix.target }}
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload deb ${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}
|
||||
path: ${{ matrix.target }}.deb
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
|
||||
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}
|
||||
uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-${{ matrix.target }}
|
||||
path: oci-image-${{ matrix.target }}.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
438
.github/workflows/ci.yml
vendored
438
.github/workflows/ci.yml
vendored
@@ -14,10 +14,11 @@ on:
|
||||
- 'docs/**'
|
||||
- 'debian/**'
|
||||
- 'docker/**'
|
||||
- 'test_results/**'
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- change-ci-cache
|
||||
tags:
|
||||
- '*'
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -26,15 +27,34 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# sccache only on main repo
|
||||
SCCACHE_GHA_ENABLED: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'true' || 'false' }}"
|
||||
RUSTC_WRAPPER: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
|
||||
SCCACHE_BUCKET: "${{ (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]') && 'sccache' || '' }}"
|
||||
SCCACHE_S3_USE_SSL: ${{ vars.SCCACHE_S3_USE_SSL }}
|
||||
SCCACHE_REGION: ${{ vars.SCCACHE_REGION }}
|
||||
SCCACHE_ENDPOINT: ${{ vars.SCCACHE_ENDPOINT }}
|
||||
SCCACHE_CACHE_MULTIARCH: ${{ vars.SCCACHE_CACHE_MULTIARCH }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# Required to make some things output color
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# conduwuit.cachix.org
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# Just in case incremental is still being set to true, speeds up CI
|
||||
CARGO_INCREMENTAL: 0
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
|
||||
NIX_CONFIG: |
|
||||
show-trace = true
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
# complement uses libolm
|
||||
NIXPKGS_ALLOW_INSECURE: 1
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
@@ -44,12 +64,28 @@ jobs:
|
||||
tests:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CARGO_PROFILE: "test"
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
- name: Tag comparison check
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') }}
|
||||
run: |
|
||||
# Tag mismatch with latest repo tag check to prevent potential downgrades
|
||||
LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
if [ $LATEST_TAG != ${{ github.ref_name }} ]; then
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.'
|
||||
echo '# WARNING: Attempting to run this workflow for a tag that is not the latest repo tag. Aborting.' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
@@ -57,20 +93,38 @@ jobs:
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# collect garbage until Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 2073741824
|
||||
# do purge caches
|
||||
purge: true
|
||||
# purge all versions of the cache
|
||||
purge-prefixes: nix-${{ runner.os }}-
|
||||
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||
purge-last-accessed: 86400
|
||||
# except the version with the `primary-key`, if it exists
|
||||
purge-primary-key: never
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
@@ -80,21 +134,54 @@ jobs:
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop --command true
|
||||
nix develop .#all-features --command true --impure
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache ci
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# use sccache for Rust
|
||||
- name: Run sccache-cache
|
||||
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Run CI tests
|
||||
run: |
|
||||
direnv exec . engage > >(tee -a test_output.log)
|
||||
|
||||
- name: Sync Complement repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'matrix-org/complement'
|
||||
path: complement_src
|
||||
|
||||
- name: Run Complement tests
|
||||
run: |
|
||||
direnv exec . bin/complement 'complement_src' 'complement_test_logs.jsonl' 'complement_test_results.jsonl'
|
||||
# the nix devshell sets $COMPLEMENT_SRC, so "/dev/null" is no-op
|
||||
direnv exec . bin/complement "/dev/null" complement_test_logs.jsonl complement_test_results.jsonl > >(tee -a test_output.log)
|
||||
cp -v -f result complement_oci_image.tar.gz
|
||||
|
||||
- name: Upload Complement OCI image
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: complement_oci_image.tar.gz
|
||||
path: complement_oci_image.tar.gz
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Complement logs
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -111,17 +198,8 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Diff Complement results with checked-in repo results
|
||||
# TODO: figure out why our complement results are not 100% consistent so we don't need to allow failures
|
||||
continue-on-error: true
|
||||
run: |
|
||||
diff -u --color=always complement_test_results.jsonl tests/test_results/complement/test_results.jsonl > >(tee -a complement_test_output.log)
|
||||
|
||||
- name: Add Complement diff result to Job Summary
|
||||
run: |
|
||||
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 50 complement_test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
diff -u --color=always tests/test_results/complement/test_results.jsonl complement_test_results.jsonl > >(tee -a complement_diff_output.log)
|
||||
|
||||
- name: Update Job Summary
|
||||
if: success() || failure()
|
||||
@@ -129,50 +207,77 @@ jobs:
|
||||
if [ ${{ job.status }} == 'success' ]; then
|
||||
echo '# ✅ completed suwuccessfully' >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo '# CI failure' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 20 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 40 test_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo '# Complement diff results' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```diff' >> $GITHUB_STEP_SUMMARY
|
||||
tail -n 100 complement_diff_output.log | sed 's/\x1b\[[0-9;]*m//g' >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Run cargo clean test artifacts
|
||||
run: |
|
||||
cargo clean --profile test
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
needs: tests
|
||||
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: aarch64-unknown-linux-musl
|
||||
- target: aarch64-unknown-linux-musl-jemalloc
|
||||
- target: x86_64-unknown-linux-musl
|
||||
- target: x86_64-unknown-linux-musl-jemalloc
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Install and enable Cachix binary cache
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Configure Magic Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
# restore and save a cache using this key
|
||||
primary-key: nix-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# collect garbage until Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 2073741824
|
||||
# do purge caches
|
||||
purge: true
|
||||
# purge all versions of the cache
|
||||
purge-prefixes: nix-${{ runner.os }}-
|
||||
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||
purge-last-accessed: 86400
|
||||
# except the version with the `primary-key`, if it exists
|
||||
purge-primary-key: never
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg= conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
EOF
|
||||
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
sudo tee -a /etc/nix/nix.conf > /dev/null <<EOF
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
@@ -182,15 +287,109 @@ jobs:
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --impure --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop --command true
|
||||
nix develop .#all-features --command true --impure
|
||||
|
||||
# use sccache for Rust
|
||||
- name: Run sccache-cache
|
||||
if: (github.event.pull_request.draft != true) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && (vars.SCCACHE_ENDPOINT != '') && (github.event.pull_request.user.login != 'renovate[bot]')
|
||||
uses: mozilla-actions/sccache-action@main
|
||||
|
||||
# use rust-cache
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-all-crates: "true"
|
||||
|
||||
- name: Build static ${{ matrix.target }}
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}
|
||||
mkdir -p target/release
|
||||
cp -v -f result/bin/conduit target/release/
|
||||
direnv exec . cargo deb --no-build --no-strip --output target/debian/${{ matrix.target }}.deb
|
||||
mv target/release/conduit static-${{ matrix.target }}
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}
|
||||
mv -v target/release/${{ matrix.target }}.deb ${{ matrix.target }}.deb
|
||||
|
||||
# quick smoke test of the x86_64 static release binary
|
||||
- name: Run x86_64 static release binary
|
||||
run: |
|
||||
# GH actions default runners are x86_64 only
|
||||
if file result/bin/conduit | grep x86-64; then
|
||||
result/bin/conduit --version
|
||||
fi
|
||||
|
||||
- name: Build static debug ${{ matrix.target }}
|
||||
run: |
|
||||
CARGO_DEB_TARGET_TUPLE=$(echo ${{ matrix.target }} | grep -o -E '^([^-]*-){3}[^-]*')
|
||||
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
|
||||
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#static-${{ matrix.target }}-all-features-debug
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# > warning: dev profile is not supported and will be a hard error in the future. cargo-deb is for making releases, and it doesn't make sense to use it with dev profiles.
|
||||
# so we need to coerce cargo-deb into thinking this is a release binary
|
||||
mkdir -v -p target/release/
|
||||
mkdir -v -p target/$CARGO_DEB_TARGET_TUPLE/release/
|
||||
cp -v -f result/bin/conduit target/release/conduwuit
|
||||
cp -v -f result/bin/conduit target/$CARGO_DEB_TARGET_TUPLE/release/conduwuit
|
||||
# -p conduit is the main crate name
|
||||
direnv exec . cargo deb --verbose --no-build --no-strip -p conduit --target=$CARGO_DEB_TARGET_TUPLE --output target/release/${{ matrix.target }}-debug.deb
|
||||
mv -v target/release/conduwuit static-${{ matrix.target }}-debug
|
||||
mv -v target/release/${{ matrix.target }}-debug.deb ${{ matrix.target }}-debug.deb
|
||||
|
||||
# quick smoke test of the x86_64 static debug binary
|
||||
- name: Run x86_64 static debug binary
|
||||
run: |
|
||||
# GH actions default runners are x86_64 only
|
||||
if file result/bin/conduit | grep x86-64; then
|
||||
result/bin/conduit --version
|
||||
fi
|
||||
|
||||
# check validity of produced deb package, invalid debs will error on these commands
|
||||
- name: Validate produced deb package
|
||||
run: |
|
||||
# List contents
|
||||
dpkg-deb --contents ${{ matrix.target }}.deb
|
||||
dpkg-deb --contents ${{ matrix.target }}-debug.deb
|
||||
# List info
|
||||
dpkg-deb --info ${{ matrix.target }}.deb
|
||||
dpkg-deb --info ${{ matrix.target }}-debug.deb
|
||||
|
||||
- name: Upload static-${{ matrix.target }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -203,14 +402,69 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}
|
||||
path: target/debian/${{ matrix.target }}.deb
|
||||
path: ${{ matrix.target }}.deb
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload static-${{ matrix.target }}-debug
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-${{ matrix.target }}-debug
|
||||
path: static-${{ matrix.target }}-debug
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload deb ${{ matrix.target }}-debug
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: deb-${{ matrix.target }}-debug
|
||||
path: ${{ matrix.target }}-debug.deb
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Build OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -v -f result oci-image-${{ matrix.target }}.tar.gz
|
||||
|
||||
- name: Build debug OCI image ${{ matrix.target }}
|
||||
run: |
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#oci-image-${{ matrix.target }}-all-features-debug
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -v -f result oci-image-${{ matrix.target }}-debug.tar.gz
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -219,24 +473,33 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Upload OCI image ${{ matrix.target }}-debug
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: oci-image-${{ matrix.target }}-debug
|
||||
path: oci-image-${{ matrix.target }}-debug.tar.gz
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
docker:
|
||||
name: Docker publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name != 'pull_request'
|
||||
if: (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main' || (github.event.pull_request.draft != true)) && (vars.DOCKER_USERNAME != '') && (vars.GITLAB_USERNAME != '') && github.event.pull_request.user.login != 'renovate[bot]'
|
||||
env:
|
||||
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
DOCKER_TAG: docker.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
GLCR_ARM64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GLCR_AMD64: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}-amd64
|
||||
GLCR_TAG: registry.gitlab.com/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
|
||||
GLCR_BRANCH: registry.gitlab.com/${{ github.repository }}:${{ (github.ref == 'refs/heads/main' && 'latest') || github.ref_name }}
|
||||
DOCKER_ARM64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
DOCKER_AMD64: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
DOCKER_TAG: docker.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
DOCKER_BRANCH: docker.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GHCR_ARM64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GHCR_AMD64: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GHCR_TAG: ghcr.io/${{ github.repository }}:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GHCR_BRANCH: ghcr.io/${{ github.repository }}:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
GLCR_ARM64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-arm64v8
|
||||
GLCR_AMD64: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}-amd64
|
||||
GLCR_TAG: registry.gitlab.com/conduwuit/conduwuit:${{ (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}-${{ github.sha }}
|
||||
GLCR_BRANCH: registry.gitlab.com/conduwuit/conduwuit:${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-rc') && 'latest') || (github.head_ref != '' && format('merge-{0}-{1}', github.event.number, github.event.pull_request.user.login)) || github.ref_name }}
|
||||
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
|
||||
steps:
|
||||
@@ -268,8 +531,10 @@ jobs:
|
||||
|
||||
- name: Move OCI images into position
|
||||
run: |
|
||||
mv oci-image-x86_64-*-jemalloc/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv oci-image-aarch64-*-jemalloc/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
mv -v oci-image-x86_64-unknown-linux-musl/*.tar.gz oci-image-amd64.tar.gz
|
||||
mv -v oci-image-aarch64-unknown-linux-musl/*.tar.gz oci-image-arm64v8.tar.gz
|
||||
mv -v oci-image-x86_64-unknown-linux-musl-debug/*.tar.gz oci-image-amd64-debug.tar.gz
|
||||
mv -v oci-image-aarch64-unknown-linux-musl-debug/*.tar.gz oci-image-arm64v8-debug.tar.gz
|
||||
|
||||
- name: Load and push amd64 image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
@@ -293,6 +558,28 @@ jobs:
|
||||
docker push ${{ env.GHCR_ARM64 }}
|
||||
docker push ${{ env.GLCR_ARM64 }}
|
||||
|
||||
- name: Load and push amd64 debug image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-amd64-debug.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_AMD64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_AMD64 }}-debug
|
||||
docker push ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker push ${{ env.GHCR_AMD64 }}-debug
|
||||
docker push ${{ env.GLCR_AMD64 }}-debug
|
||||
|
||||
- name: Load and push arm64 debug image
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
docker load -i oci-image-arm64v8-debug.tar.gz
|
||||
docker tag $(docker images -q conduit:main) ${{ env.DOCKER_ARM64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GHCR_ARM64 }}-debug
|
||||
docker tag $(docker images -q conduit:main) ${{ env.GLCR_ARM64 }}-debug
|
||||
docker push ${{ env.DOCKER_ARM64 }}-debug
|
||||
docker push ${{ env.GHCR_ARM64 }}-debug
|
||||
docker push ${{ env.GLCR_ARM64 }}-debug
|
||||
|
||||
- name: Create Docker combined manifests
|
||||
run: |
|
||||
# Dockerhub Container Registry
|
||||
@@ -302,9 +589,21 @@ jobs:
|
||||
docker manifest create ${{ env.GHCR_TAG }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
||||
docker manifest create ${{ env.GHCR_BRANCH }} --amend ${{ env.GHCR_ARM64 }} --amend ${{ env.GHCR_AMD64 }}
|
||||
# GitLab Container Registry
|
||||
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GCCR_AMD64 }}
|
||||
docker manifest create ${{ env.GLCR_TAG }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
|
||||
docker manifest create ${{ env.GLCR_BRANCH }} --amend ${{ env.GLCR_ARM64 }} --amend ${{ env.GLCR_AMD64 }}
|
||||
|
||||
- name: Create Docker combined debug manifests
|
||||
run: |
|
||||
# Dockerhub Container Registry
|
||||
docker manifest create ${{ env.DOCKER_TAG }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
|
||||
docker manifest create ${{ env.DOCKER_BRANCH }}-debug --amend ${{ env.DOCKER_ARM64 }}-debug --amend ${{ env.DOCKER_AMD64 }}-debug
|
||||
# GitHub Container Registry
|
||||
docker manifest create ${{ env.GHCR_TAG }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
|
||||
docker manifest create ${{ env.GHCR_BRANCH }}-debug --amend ${{ env.GHCR_ARM64 }}-debug --amend ${{ env.GHCR_AMD64 }}-debug
|
||||
# GitLab Container Registry
|
||||
docker manifest create ${{ env.GLCR_TAG }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
|
||||
docker manifest create ${{ env.GLCR_BRANCH }}-debug --amend ${{ env.GLCR_ARM64 }}-debug --amend ${{ env.GLCR_AMD64 }}-debug
|
||||
|
||||
- name: Push manifests to Docker registries
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
run: |
|
||||
@@ -314,6 +613,12 @@ jobs:
|
||||
docker manifest push ${{ env.GHCR_BRANCH }}
|
||||
docker manifest push ${{ env.GLCR_TAG }}
|
||||
docker manifest push ${{ env.GLCR_BRANCH }}
|
||||
docker manifest push ${{ env.DOCKER_TAG }}-debug
|
||||
docker manifest push ${{ env.DOCKER_BRANCH }}-debug
|
||||
docker manifest push ${{ env.GHCR_TAG }}-debug
|
||||
docker manifest push ${{ env.GHCR_BRANCH }}-debug
|
||||
docker manifest push ${{ env.GLCR_TAG }}-debug
|
||||
docker manifest push ${{ env.GLCR_BRANCH }}-debug
|
||||
|
||||
- name: Add Image Links to Job Summary
|
||||
if: ${{ (vars.DOCKER_USERNAME != '') && (env.DOCKERHUB_TOKEN != '') }}
|
||||
@@ -321,3 +626,6 @@ jobs:
|
||||
echo "- \`docker pull ${{ env.DOCKER_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GHCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GLCR_TAG }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.DOCKER_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GHCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`docker pull ${{ env.GLCR_TAG }}-debug\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
133
.github/workflows/documentation.yml
vendored
133
.github/workflows/documentation.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
@@ -14,9 +16,16 @@ env:
|
||||
TERM: ansi
|
||||
# Publishing to my nix binary cache
|
||||
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN }}
|
||||
# conduwuit.cachix.org
|
||||
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
|
||||
# Custom nix binary cache if fork is being used
|
||||
ATTIC_ENDPOINT: ${{ vars.ATTIC_ENDPOINT }}
|
||||
ATTIC_PUBLIC_KEY: ${{ vars.ATTIC_PUBLIC_KEY }}
|
||||
# Get error output from nix that we can actually use, and use our binary caches for the earlier CI steps
|
||||
NIX_CONFIG: |
|
||||
show-trace = true
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
@@ -27,7 +36,6 @@ concurrency:
|
||||
jobs:
|
||||
docs:
|
||||
name: Documentation and GitHub Pages
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
@@ -39,6 +47,9 @@ jobs:
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
|
||||
- name: Sync repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -46,54 +57,104 @@ jobs:
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Install Nix (with flakes and nix-command enabled)
|
||||
uses: cachix/install-nix-action@v26
|
||||
- uses: nixbuild/nix-quick-install-action@v28
|
||||
|
||||
- name: Enable Cachix binary cache
|
||||
run: |
|
||||
nix profile install nixpkgs#cachix
|
||||
cachix use crane
|
||||
cachix use nix-community
|
||||
|
||||
- name: Restore and cache Nix store
|
||||
uses: nix-community/cache-nix-action@v5.1.0
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
# restore and save a cache using this key
|
||||
primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/.lock') }}
|
||||
# if there's no cache hit, restore a cache by this prefix
|
||||
restore-prefixes-first-match: nix-${{ runner.os }}-
|
||||
# collect garbage until Nix store size (in bytes) is at most this number
|
||||
# before trying to save a new cache
|
||||
gc-max-store-size-linux: 2073741824
|
||||
# do purge caches
|
||||
purge: true
|
||||
# purge all versions of the cache
|
||||
purge-prefixes: nix-${{ runner.os }}-
|
||||
# created more than this number of seconds ago relative to the start of the `Post Restore` phase
|
||||
purge-last-accessed: 86400
|
||||
# except the version with the `primary-key`, if it exists
|
||||
purge-primary-key: never
|
||||
# always save the cache
|
||||
save-always: true
|
||||
|
||||
# Add `nix-community`, Crane, upstream Conduit, and conduwuit binary caches
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
extra-substituters = https://nix-community.cachix.org
|
||||
extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=
|
||||
extra-substituters = https://crane.cachix.org
|
||||
extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit
|
||||
extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduwuit
|
||||
extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
- name: Apply Nix binary cache configuration
|
||||
run: |
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = https://attic.kennel.juneis.dog/conduit https://attic.kennel.juneis.dog/conduwuit https://cache.lix.systems https://conduwuit.cachix.org
|
||||
extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk= conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o= conduwuit.cachix.org-1:MFRm6jcnfTf0jSAbmvLfhO3KBMt4px+1xaereWXp8Xg=
|
||||
EOF
|
||||
|
||||
- name: Add alternative Nix binary caches if specified
|
||||
- name: Use alternative Nix binary caches if specified
|
||||
if: ${{ (env.ATTIC_ENDPOINT != '') && (env.ATTIC_PUBLIC_KEY != '') }}
|
||||
run: |
|
||||
echo "extra-substituters = ${{ env.ATTIC_ENDPOINT }}" >> /etc/nix/nix.conf
|
||||
echo "extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}" >> /etc/nix/nix.conf
|
||||
sudo tee -a "${XDG_CONFIG_HOME:-$HOME/.config}/nix/nix.conf" > /dev/null <<EOF
|
||||
extra-substituters = ${{ env.ATTIC_ENDPOINT }}
|
||||
extra-trusted-public-keys = ${{ env.ATTIC_PUBLIC_KEY }}
|
||||
EOF
|
||||
|
||||
- name: Pop/push Magic Nix Cache
|
||||
uses: DeterminateSystems/magic-nix-cache-action@main
|
||||
|
||||
- name: Configure `nix-direnv`
|
||||
- name: Prepare build environment
|
||||
run: |
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
|
||||
- name: Install `direnv` and `nix-direnv`
|
||||
run: nix-env -f "<nixpkgs>" -iA direnv -iA nix-direnv
|
||||
|
||||
# Do this to shorten the logs for the real CI step
|
||||
- name: Populate `/nix/store`
|
||||
run: nix develop --command true
|
||||
|
||||
- name: Allow direnv
|
||||
run: direnv allow
|
||||
echo 'source $HOME/.nix-profile/share/nix-direnv/direnvrc' > "$HOME/.direnvrc"
|
||||
nix profile install --inputs-from . nixpkgs#direnv nixpkgs#nix-direnv
|
||||
direnv allow
|
||||
nix develop --command true
|
||||
|
||||
- name: Cache CI dependencies
|
||||
run: |
|
||||
./bin/nix-build-and-cache ci
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache ci
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run lychee and markdownlint
|
||||
run: |
|
||||
direnv exec . engage just lints lychee
|
||||
direnv exec . engage just lints markdownlint
|
||||
|
||||
- name: Build documentation (book)
|
||||
run: |
|
||||
./bin/nix-build-and-cache just .#book
|
||||
cp -r --dereference result public
|
||||
# attic nix binary cache server is very, very terribly flakey. nothing i can do to fix it other than retry multiple times here
|
||||
ATTEMPTS=3
|
||||
SUCCESS=false
|
||||
while (( ATTEMPTS-- > 0 ))
|
||||
do
|
||||
bin/nix-build-and-cache just .#book
|
||||
if [[ $? == 0 ]]; then
|
||||
SUCCESS=true
|
||||
break
|
||||
else
|
||||
sleep 3
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $SUCCESS == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -r --dereference result public
|
||||
|
||||
- name: Upload generated documentation (book) as normal artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
||||
8
.github/workflows/trivy.yml
vendored
8
.github/workflows/trivy.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
schedule:
|
||||
- cron: '00 12 * * *'
|
||||
|
||||
@@ -24,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on repo
|
||||
uses: aquasecurity/trivy-action@0.19.0
|
||||
uses: aquasecurity/trivy-action@0.24.0
|
||||
with:
|
||||
scan-type: repo
|
||||
format: sarif
|
||||
@@ -32,9 +34,9 @@ jobs:
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
|
||||
- name: Run Trivy code and vulnerability scanner on filesystem
|
||||
uses: aquasecurity/trivy-action@0.19.0
|
||||
uses: aquasecurity/trivy-action@0.24.0
|
||||
with:
|
||||
scan-type: fs
|
||||
format: sarif
|
||||
output: trivy-results.sarif
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
severity: CRITICAL,HIGH,MEDIUM,LOW
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
||||
# Local environment overrides
|
||||
/.env
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
|
||||
@@ -26,15 +26,19 @@ before_script:
|
||||
|
||||
# Add conduwuit binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduwuit" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://attic.kennel.juneis.dog/conduit" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add alternate binary cache
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add Lix binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://cache.lix.systems" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = cache.lix.systems:aBnZUw8zA7H35Cz2RyKFVs3H4PlGTLawyY5KRbvJR8o=" >> /etc/nix/nix.conf; fi
|
||||
|
||||
# Add crane binary cache
|
||||
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
|
||||
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
|
||||
@@ -54,7 +58,7 @@ before_script:
|
||||
|
||||
ci:
|
||||
stage: ci
|
||||
image: nixos/nix:2.22.0
|
||||
image: nixos/nix:2.23.3
|
||||
script:
|
||||
# Cache CI dependencies
|
||||
- ./bin/nix-build-and-cache ci
|
||||
@@ -79,7 +83,7 @@ ci:
|
||||
|
||||
artifacts:
|
||||
stage: artifacts
|
||||
image: nixos/nix:2.22.0
|
||||
image: nixos/nix:2.23.3
|
||||
script:
|
||||
- ./bin/nix-build-and-cache just .#static-x86_64-unknown-linux-musl
|
||||
- cp result/bin/conduit x86_64-unknown-linux-musl
|
||||
|
||||
1
.markdownlintignore
Symbolic link
1
.markdownlintignore
Symbolic link
@@ -0,0 +1 @@
|
||||
.gitignore
|
||||
@@ -61,7 +61,7 @@ ## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement over email at
|
||||
strawberry@puppygock.gay or over Matrix at @strawberry:puppygock.gay.
|
||||
<strawberry@puppygock.gay> or over Matrix at @strawberry:puppygock.gay.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@@ -131,4 +131,3 @@ ## Attribution
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
||||
|
||||
145
CONTRIBUTING.md
Normal file
145
CONTRIBUTING.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Contributing guide
|
||||
|
||||
This page is for about contributing to conduwuit. The
|
||||
[development](development.md) page may be of interest for you as well.
|
||||
|
||||
If you would like to work on an [issue][issues] that is not assigned, preferably
|
||||
ask in the Matrix room first at [#conduwuit:puppygock.gay][conduwuit-matrix],
|
||||
and comment on it.
|
||||
|
||||
### Linting and Formatting
|
||||
|
||||
It is mandatory all your changes satisfy the lints (clippy, rustc, rustdoc, etc)
|
||||
and your code is formatted via the **nightly** `cargo fmt`. A lot of the
|
||||
`rustfmt.toml` features depend on nightly toolchain. It would be ideal if they
|
||||
weren't nightly-exclusive features, but they currently still are. CI's rustfmt
|
||||
uses nightly.
|
||||
|
||||
If you need to allow a lint, please make sure it's either obvious as to why
|
||||
(e.g. clippy saying redundant clone but it's actually required) or it has a
|
||||
comment saying why. Do not write inefficient code for the sake of satisfying
|
||||
lints. If a lint is wrong and provides a more inefficient solution or
|
||||
suggestion, allow the lint and mention that in a comment.
|
||||
|
||||
### Running CI tests locally
|
||||
|
||||
conduwuit's CI for tests, linting, formatting, audit, etc use
|
||||
[`engage`][engage]. engage can be installed from nixpkgs or `cargo install
|
||||
engage`. conduwuit's Nix flake devshell has the nixpkgs engage with `direnv`.
|
||||
Use `engage --help` for more usage details.
|
||||
|
||||
To test, format, lint, etc that CI would do, install engage, allow the `.envrc`
|
||||
file using `direnv allow`, and run `engage`.
|
||||
|
||||
All of the tasks are defined at the [engage.toml][engage.toml] file. You can
|
||||
view all of them neatly by running `engage list`
|
||||
|
||||
If you would like to run only a specific engage task group, use `just`:
|
||||
|
||||
- `engage just <group>`
|
||||
- Example: `engage just lints`
|
||||
|
||||
If you would like to run a specific engage task in a specific group, use `just
|
||||
<GROUP> [TASK]`: `engage just lints cargo-fmt`
|
||||
|
||||
The following binaries are used in [`engage.toml`][engage.toml]:
|
||||
|
||||
- [`engage`][engage]
|
||||
- `nix`
|
||||
- [`direnv`][direnv]
|
||||
- `rustc`
|
||||
- `cargo`
|
||||
- `cargo-fmt`
|
||||
- `rustdoc`
|
||||
- `cargo-clippy`
|
||||
- [`cargo-audit`][cargo-audit]
|
||||
- [`cargo-deb`][cargo-deb]
|
||||
- [`lychee`][lychee]
|
||||
- [`markdownlint-cli`][markdownlint-cli]
|
||||
- `dpkg`
|
||||
|
||||
### Matrix tests
|
||||
|
||||
CI runs [Complement][complement], but currently does not fail if results from
|
||||
the checked-in results differ with the new results. If your changes are done to
|
||||
fix Matrix tests, note that in your pull request. If more Complement tests start
|
||||
failing from your changes, please review the logs (they are uploaded as
|
||||
artifacts) and determine if they're intended or not.
|
||||
|
||||
If you'd like to run Complement locally using Nix, see the
|
||||
[testing](docs/development/testing.md) page.
|
||||
|
||||
[Sytest][sytest] support will come soon.
|
||||
|
||||
### Writing documentation
|
||||
|
||||
conduwuit's website uses [`mdbook`][mdbook] and deployed via CI using GitHub
|
||||
Pages in the [`documentation.yml`][documentation.yml] workflow file with Nix's
|
||||
mdbook in the devshell. All documentation is in the `docs/` directory at the top
|
||||
level. The compiled mdbook website is also uploaded as an artifact.
|
||||
|
||||
To build the documentation using Nix, run: `bin/nix-build-and-cache just .#book`
|
||||
|
||||
The output of the mdbook generation is in `result/`. mdbooks can be opened in
|
||||
your browser from the individual HTML files without any web server needed.
|
||||
|
||||
### Inclusivity and Diversity
|
||||
|
||||
All **MUST** code and write with inclusivity and diversity in mind. See the
|
||||
[following page by Google on writing inclusive code and
|
||||
documentation](https://developers.google.com/style/inclusive-documentation).
|
||||
|
||||
This **EXPLICITLY** forbids usage of terms like "blacklist"/"whitelist" and
|
||||
"master"/"slave", [forbids gender-specific words and
|
||||
phrases](https://developers.google.com/style/pronouns#gender-neutral-pronouns),
|
||||
forbids ableist language like "sanity-check", "cripple", or "insane", and
|
||||
forbids culture-specific language (e.g. US-only holidays or cultures).
|
||||
|
||||
No exceptions are allowed. Dependencies that may use these terms are allowed but
|
||||
[do not replicate the name in your functions or
|
||||
variables](https://developers.google.com/style/inclusive-documentation#write-around).
|
||||
|
||||
In addition to language, write and code with the user experience in mind. This
|
||||
is software that intends to be used by everyone, so make it easy and comfortable
|
||||
for everyone to use. 🏳️⚧️
|
||||
|
||||
### Variable, comment, function, etc standards
|
||||
|
||||
Rust's default style and standards with regards to [function names, variable
|
||||
names, comments](https://rust-lang.github.io/api-guidelines/naming.html), etc
|
||||
applies here.
|
||||
|
||||
### Creating pull requests
|
||||
|
||||
Please try to keep contributions to the GitHub. While the mirrors of conduwuit
|
||||
allow for pull/merge requests, there is no guarantee I will see them in a timely
|
||||
manner. Additionally, please mark WIP or unfinished or incomplete PRs as drafts.
|
||||
This prevents me from having to ping once in a while to double check the status
|
||||
of it, especially when the CI completed successfully and everything so it
|
||||
*looks* done.
|
||||
|
||||
If you open a pull request on one of the mirrors, it is your responsibility to
|
||||
inform me about its existence. In the future I may try to solve this with more
|
||||
repo bots in the conduwuit Matrix room. There is no mailing list or email-patch
|
||||
support on the sr.ht mirror, but if you'd like to email me a git patch you can
|
||||
do so at `strawberry@puppygock.gay`.
|
||||
|
||||
Direct all PRs/MRs to the `main` branch.
|
||||
|
||||
By sending a pull request or patch, you are agreeing that your changes are
|
||||
allowed to be licenced under the Apache-2.0 licence and all of your conduct is
|
||||
in line with the Contributor's Covenant.
|
||||
|
||||
[issues]: https://github.com/girlbossceo/conduwuit/issues
|
||||
[conduwuit-matrix]: https://matrix.to/#/#conduwuit:puppygock.gay
|
||||
[complement]: https://github.com/matrix-org/complement/
|
||||
[engage.toml]: https://github.com/girlbossceo/conduwuit/blob/main/engage.toml
|
||||
[engage]: https://charles.page.computer.surgery/engage/
|
||||
[sytest]: https://github.com/matrix-org/sytest/
|
||||
[cargo-deb]: https://github.com/kornelski/cargo-deb
|
||||
[lychee]: https://github.com/lycheeverse/lychee
|
||||
[markdownlint-cli]: https://github.com/igorshubovych/markdownlint-cli
|
||||
[cargo-audit]: https://github.com/RustSec/rustsec/tree/main/cargo-audit
|
||||
[direnv]: https://direnv.net/
|
||||
[mdbook]: https://rust-lang.github.io/mdBook/
|
||||
[documentation.yml]: https://github.com/girlbossceo/conduwuit/blob/main/.github/workflows/documentation.yml
|
||||
2273
Cargo.lock
generated
2273
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1164
Cargo.toml
1164
Cargo.toml
File diff suppressed because it is too large
Load Diff
63
README.md
63
README.md
@@ -1,17 +1,15 @@
|
||||
# conduwuit
|
||||
|
||||
`main` / stable: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
`main` / stable: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
|
||||
`dev`: [](https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml)
|
||||
<!-- ANCHOR: catchphrase --> ### a very cool, featureful fork of
|
||||
[Conduit](https://conduit.rs/) <!-- ANCHOR_END: catchphrase -->
|
||||
|
||||
<!-- ANCHOR: catchphrase -->
|
||||
### a very cool, featureful fork of [Conduit](https://conduit.rs/)
|
||||
<!-- ANCHOR_END: catchphrase -->
|
||||
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more
|
||||
information.
|
||||
|
||||
Visit the [Conduwuit documentation](https://conduwuit.puppyirl.gay/) for more information.
|
||||
|
||||
<!-- ANCHOR: body -->
|
||||
#### What is Matrix?
|
||||
<!-- ANCHOR: body --> #### What is Matrix?
|
||||
|
||||
[Matrix](https://matrix.org) is an open network for secure and decentralized
|
||||
communication. Users from every Matrix homeserver can chat with users from all
|
||||
@@ -20,35 +18,35 @@ #### What is Matrix?
|
||||
|
||||
#### What is the goal?
|
||||
|
||||
An efficient Matrix homeserver that's easy to set up and just works. You can install
|
||||
it on a mini-computer like the Raspberry Pi to host Matrix for your family,
|
||||
friends or company.
|
||||
An efficient Matrix homeserver that's easy to set up and just works. You can
|
||||
install it on a mini-computer like the Raspberry Pi to host Matrix for your
|
||||
family, friends or company.
|
||||
|
||||
#### Can I try it out?
|
||||
|
||||
An official conduwuit server ran by me is available at transfem.dev ([element.transfem.dev](https://element.transfem.dev) / [cinny.transfem.dev](https://cinny.transfem.dev))
|
||||
An official conduwuit server ran by me is available at transfem.dev
|
||||
([element.transfem.dev](https://element.transfem.dev) /
|
||||
[cinny.transfem.dev](https://cinny.transfem.dev))
|
||||
|
||||
transfem.dev is a public homeserver that can be used, it is not a "test only
|
||||
homeserver". This means there are rules, so please read the rules:
|
||||
[https://transfem.dev/homeserver_rules.txt](https://transfem.dev/homeserver_rules.txt)
|
||||
|
||||
transfem.dev is also listed at
|
||||
[servers.joinmatrix.org](https://servers.joinmatrix.org/)
|
||||
|
||||
#### What is the current status?
|
||||
|
||||
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and participate in most
|
||||
Matrix rooms, but not all features are supported and you might run into bugs
|
||||
from time to time.
|
||||
conduwuit is a hard fork of Conduit which is in beta, meaning you can join and
|
||||
participate in most Matrix rooms, but not all features are supported and you
|
||||
might run into bugs from time to time.
|
||||
|
||||
<!-- ANCHOR_END: body -->
|
||||
|
||||
<!-- ANCHOR: footer -->
|
||||
#### How can I contribute?
|
||||
|
||||
1. Look for an issue you would like to work on and make sure it's not assigned
|
||||
to other users
|
||||
2. Ask someone to assign the issue to you (comment on the issue or chat in
|
||||
[#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay))
|
||||
3. Fork the repo and work on the issue.
|
||||
4. Submit a PR (please keep contributions to the GitHub repo, main development is done here, not the GitLab repo which exists just as a mirror. If you are avoiding GitHub, feel free to join our Matrix chat to get your patch in.)
|
||||
|
||||
#### Contact
|
||||
<!-- ANCHOR: footer --> #### Contact
|
||||
|
||||
If you run into any question, feel free to
|
||||
|
||||
- Ask us in `#conduwuit:puppygock.gay` on Matrix
|
||||
- [Open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new)
|
||||
|
||||
@@ -60,7 +58,9 @@ #### Donate
|
||||
|
||||
#### Logo
|
||||
|
||||
Original repo and Matrix room picture was from bran (<3). Current banner image and logo is directly from [this cohost post](https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
|
||||
Original repo and Matrix room picture was from bran (<3). Current banner image
|
||||
and logo is directly from [this cohost
|
||||
post](https://cohost.org/RatBaby/post/1028290-finally-a-flag-for).
|
||||
|
||||
#### Is it conduwuit or Conduwuit?
|
||||
|
||||
@@ -69,8 +69,9 @@ #### Is it conduwuit or Conduwuit?
|
||||
#### Mirrors of conduwuit
|
||||
|
||||
- GitHub: <https://github.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/girlbossceo/conduwuit>
|
||||
- GitLab: <https://gitlab.com/conduwuit/conduwuit>
|
||||
- git.girlcock.ceo: <https://git.girlcock.ceo/strawberry/conduwuit>
|
||||
- git.gay: <https://git.gay/june/conduwuit>
|
||||
- Codeberg: <https://codeberg.org/girlbossceo/conduwuit>
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit>
|
||||
<!-- ANCHOR_END: footer -->
|
||||
- sourcehut: <https://git.sr.ht/~girlbossceo/conduwuit> <!-- ANCHOR_END: footer
|
||||
-->
|
||||
|
||||
62
arch/conduwuit.service
Normal file
62
arch/conduwuit.service
Normal file
@@ -0,0 +1,62 @@
|
||||
[Unit]
|
||||
Description=conduwuit Matrix homeserver
|
||||
After=network.target
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
RequiresMountsFor=/var/lib/private/conduwuit
|
||||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
Type=notify
|
||||
|
||||
AmbientCapabilities=
|
||||
CapabilityBoundingSet=
|
||||
|
||||
DevicePolicy=closed
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
ProcSubset=pid
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelModules=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectProc=invisible
|
||||
ProtectSystem=strict
|
||||
PrivateDevices=yes
|
||||
PrivateMounts=yes
|
||||
PrivateTmp=yes
|
||||
PrivateUsers=yes
|
||||
PrivateIPC=yes
|
||||
RemoveIPC=yes
|
||||
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service @resources
|
||||
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
|
||||
SystemCallErrorNumber=EPERM
|
||||
StateDirectory=conduwuit
|
||||
|
||||
RuntimeDirectory=conduwuit
|
||||
RuntimeDirectoryMode=0750
|
||||
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
BindPaths=/var/lib/private/conduwuit:/var/lib/matrix-conduit
|
||||
BindPaths=/var/lib/private/conduwuit:/var/lib/private/matrix-conduit
|
||||
|
||||
ExecStart=/usr/bin/conduwuit
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
TimeoutStopSec=4m
|
||||
TimeoutStartSec=4m
|
||||
|
||||
StartLimitInterval=1m
|
||||
StartLimitBurst=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -1,2 +0,0 @@
|
||||
[advisories]
|
||||
ignore = ["RUSTSEC-2020-0016"]
|
||||
@@ -7,7 +7,7 @@ set -euo pipefail
|
||||
# The `COMPLEMENT_SRC` environment variable is set in the Nix dev shell, which
|
||||
# points to a store path containing the Complement source code. It's likely you
|
||||
# want to just pass that as the first argument to use it here.
|
||||
COMPLEMENT_SRC="$1"
|
||||
COMPLEMENT_SRC="${COMPLEMENT_SRC:-$1}"
|
||||
|
||||
# A `.jsonl` file to write test logs to
|
||||
LOG_FILE="$2"
|
||||
@@ -15,17 +15,27 @@ LOG_FILE="$2"
|
||||
# A `.jsonl` file to write test results to
|
||||
RESULTS_FILE="$3"
|
||||
|
||||
OCI_IMAGE="complement-conduit:dev"
|
||||
OCI_IMAGE="complement-conduit:main"
|
||||
|
||||
# Complement tests that are skipped due to flakiness/reliability issues
|
||||
SKIPPED_COMPLEMENT_TESTS='-skip=TestClientSpacesSummary.*|TestJoinFederatedRoomFromApplicationServiceBridgeUser.*|TestJumpToDateEndpoint.*'
|
||||
|
||||
# $COMPLEMENT_SRC needs to be a directory to Complement source code
|
||||
if [ -f "$COMPLEMENT_SRC" ]; then
|
||||
echo "\$COMPLEMENT_SRC must be a directory/path to Complement source code"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# quick test to make sure we can actually write to $LOG_FILE and $RESULTS_FILE
|
||||
touch $LOG_FILE && rm -v $LOG_FILE
|
||||
touch $RESULTS_FILE && rm -v $RESULTS_FILE
|
||||
|
||||
toplevel="$(git rev-parse --show-toplevel)"
|
||||
|
||||
pushd "$toplevel" > /dev/null
|
||||
# uses nix-output-monitor (nom) if available
|
||||
if command -v nom &> /dev/null; then
|
||||
nom build .#complement
|
||||
else
|
||||
nix build -L .#complement
|
||||
fi
|
||||
|
||||
bin/nix-build-and-cache just .#static-complement
|
||||
|
||||
docker load < result
|
||||
popd > /dev/null
|
||||
|
||||
@@ -34,7 +44,7 @@ set +o pipefail
|
||||
env \
|
||||
-C "$COMPLEMENT_SRC" \
|
||||
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
|
||||
go test -vet=off -timeout 1h -json ./tests | tee "$LOG_FILE"
|
||||
go test -tags="conduwuit_blacklist" "$SKIPPED_COMPLEMENT_TESTS" -v -timeout 1h -json ./tests | tee "$LOG_FILE"
|
||||
set -o pipefail
|
||||
|
||||
# Post-process the results into an easy-to-compare format, sorted by Test name for reproducible results
|
||||
|
||||
@@ -13,41 +13,61 @@ just() {
|
||||
nix build -L "$@"
|
||||
fi
|
||||
|
||||
if [ ! -z "$ATTIC_TOKEN" ]; then
|
||||
# historical "conduit" store for compatibility purposes, same as conduwuit
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
readarray -t outputs < <(nix path-info "$@")
|
||||
readarray -t derivations < <(nix path-info "$@" --derivation)
|
||||
|
||||
# Push the target installable and its build dependencies
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
push \
|
||||
conduit \
|
||||
"${outputs[@]}" \
|
||||
"${derivations[@]}"
|
||||
|
||||
# main "conduwuit" store
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduwuit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Push the target installable and its build dependencies
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
push \
|
||||
conduwuit \
|
||||
"${outputs[@]}" \
|
||||
"${derivations[@]}"
|
||||
else
|
||||
if [ -z "$ATTIC_TOKEN" ]; then
|
||||
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
|
||||
return
|
||||
fi
|
||||
|
||||
# historical "conduit" store for compatibility purposes, same as conduwuit
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Find all output paths of the installables and their build dependencies
|
||||
readarray -t derivations < <(nix path-info --derivation "$@")
|
||||
cache=()
|
||||
for derivation in "${derivations[@]}"; do
|
||||
cache+=(
|
||||
"$(nix-store --query --requisites --include-outputs "$derivation")"
|
||||
)
|
||||
done
|
||||
|
||||
# Upload them to Attic (conduit store)
|
||||
#
|
||||
# Use `xargs` and a here-string because something would probably explode if
|
||||
# several thousand arguments got passed to a command at once. Hopefully no
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduit <<< "${cache[*]}"
|
||||
)
|
||||
|
||||
# main "conduwuit" store
|
||||
nix run --inputs-from "$toplevel" attic -- \
|
||||
login \
|
||||
conduwuit \
|
||||
"${ATTIC_ENDPOINT:-https://attic.kennel.juneis.dog/conduwuit}" \
|
||||
"$ATTIC_TOKEN"
|
||||
|
||||
# Upload them to Attic (conduwuit store) and Cachix
|
||||
#
|
||||
# Use `xargs` and a here-string because something would probably explode if
|
||||
# several thousand arguments got passed to a command at once. Hopefully no
|
||||
# store paths include a newline in them.
|
||||
(
|
||||
IFS=$'\n'
|
||||
nix shell --inputs-from "$toplevel" attic -c xargs \
|
||||
attic push conduwuit <<< "${cache[*]}"
|
||||
|
||||
# push to cachix if available
|
||||
if [ "$CACHIX_AUTH_TOKEN" ]; then
|
||||
nix shell --inputs-from "$toplevel" cachix -c xargs \
|
||||
cachix push conduwuit <<< "${cache[*]}"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
# Build and cache things needed for CI
|
||||
@@ -56,8 +76,10 @@ ci() {
|
||||
--inputs-from "$toplevel"
|
||||
|
||||
# Keep sorted
|
||||
"$toplevel#devShells.x86_64-linux.default.inputDerivation"
|
||||
"$toplevel#devShells.x86_64-linux.default"
|
||||
"$toplevel#devShells.x86_64-linux.all-features"
|
||||
attic#default
|
||||
cachix#default
|
||||
nixpkgs#direnv
|
||||
nixpkgs#jq
|
||||
nixpkgs#nix-direnv
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
[book]
|
||||
title = "conduwuit"
|
||||
title = "conduwuit 🏳️⚧️ 💜 🦴"
|
||||
description = "conduwuit, which is a well-maintained fork of Conduit, is a simple, fast and reliable chat server for the Matrix protocol"
|
||||
language = "en"
|
||||
authors = ["strawberry (June)"]
|
||||
text-direction = "ltr"
|
||||
multilingual = false
|
||||
src = "docs"
|
||||
|
||||
[build]
|
||||
build-dir = "public"
|
||||
create-missing = true
|
||||
extra-watch-dirs = ["debian", "docs"]
|
||||
|
||||
[rust]
|
||||
edition = "2021"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/girlbossceo/conduwuit"
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
too-many-lines-threshold = 700
|
||||
array-size-threshold = 4096
|
||||
cognitive-complexity-threshold = 94 # TODO reduce me ALARA
|
||||
excessive-nesting-threshold = 11 # TODO reduce me to 4 or 5
|
||||
future-size-threshold = 7745 # TODO reduce me ALARA
|
||||
stack-size-threshold = 144000 # reduce me ALARA
|
||||
too-many-lines-threshold = 700 # TODO reduce me to <= 100
|
||||
type-complexity-threshold = 250 # reduce me to ~200
|
||||
|
||||
@@ -52,20 +52,30 @@
|
||||
|
||||
# Performance monitoring/tracing sample rate for Sentry.io
|
||||
#
|
||||
# Note that too high values may impact performance, and can be disabled by setting it to 0.0
|
||||
# Note that too high values may impact performance, and can be disabled by setting it to 0.0 (0%)
|
||||
# This value is read as a percentage to Sentry, represented as a decimal
|
||||
#
|
||||
# Defaults to 0.15
|
||||
# Defaults to 15% of traces (0.15)
|
||||
#sentry_traces_sample_rate = 0.15
|
||||
|
||||
# Whether to attach a stacktrace to Sentry reports.
|
||||
#sentry_attach_stacktrace = false
|
||||
|
||||
# Send panics to sentry. This is true by default, but sentry has to be enabled.
|
||||
#sentry_send_panic = true
|
||||
|
||||
# Send errors to sentry. This is true by default, but sentry has to be enabled. This option is
|
||||
# only effective in release-mode; forced to false in debug-mode.
|
||||
#sentry_send_error = true
|
||||
|
||||
|
||||
### Database configuration
|
||||
|
||||
# This is the only directory where conduwuit will save its data, including media
|
||||
database_path = "/var/lib/matrix-conduit/"
|
||||
# This is the only directory where conduwuit will save its data, including media.
|
||||
# Note: this was previously "/var/lib/matrix-conduit"
|
||||
database_path = "/var/lib/conduwuit"
|
||||
|
||||
# Database backend: Only rocksdb and sqlite are supported. Please note that sqlite
|
||||
# will perform significantly worse than rocksdb as it is not intended to be used the
|
||||
# way it is by conduwuit. sqlite only exists for historical reasons.
|
||||
# Database backend: Only rocksdb is supported.
|
||||
database_backend = "rocksdb"
|
||||
|
||||
|
||||
@@ -76,11 +86,16 @@ database_backend = "rocksdb"
|
||||
# forwarded to the conduwuit instance running on this port
|
||||
# Docker users: Don't change this, you'll need to map an external port to this.
|
||||
# To listen on multiple ports, specify a vector e.g. [8080, 8448]
|
||||
#
|
||||
# default if unspecified is 8008
|
||||
port = 6167
|
||||
|
||||
# default address (IPv4 or IPv6) conduwuit will listen on. Generally you want this to be
|
||||
# localhost (127.0.0.1 / ::1). If you are using Docker or a container NAT networking setup, you
|
||||
# likely need this to be 0.0.0.0.
|
||||
# To listen multiple addresses, specify a vector e.g. ["127.0.0.1", "::1"]
|
||||
#
|
||||
# default if unspecified is both IPv4 and IPv6 localhost: ["127.0.0.1", "::1"]
|
||||
address = "127.0.0.1"
|
||||
|
||||
# Max request size for file uploads
|
||||
@@ -192,6 +207,11 @@ registration_token = "change this token for something specific to your server"
|
||||
# defaults to false
|
||||
# block_non_admin_invites = false
|
||||
|
||||
# Allows admins to enter commands in rooms other than #admins by prefixing with \!admin. The reply
|
||||
# will be publicly visible to the room, originating from the sender.
|
||||
# defaults to true
|
||||
#admin_escape_commands = true
|
||||
|
||||
# List of forbidden username patterns/strings. Values in this list are matched as *contains*.
|
||||
# This is checked upon username availability check, registration, and startup as warnings if any local users in your database
|
||||
# have a forbidden username.
|
||||
@@ -204,9 +224,11 @@ registration_token = "change this token for something specific to your server"
|
||||
# No default.
|
||||
# forbidden_alias_names = []
|
||||
|
||||
# List of forbidden server names that we will block all client room joins, incoming federated room directory requests, incoming federated invites for, and incoming federated joins. This check is applied on the room ID, room alias, sender server name, and sender user's server name.
|
||||
# Basically "global" ACLs. For our user (client) checks, admin users are allowed.
|
||||
# No default.
|
||||
# List of forbidden server names that we will block incoming AND outgoing federation with, and block client room joins / remote user invites.
|
||||
#
|
||||
# This check is applied on the room ID, room alias, sender server name, sender user's server name, inbound federation X-Matrix origin, and outbound federation handler.
|
||||
#
|
||||
# Basically "global" ACLs. No default.
|
||||
# forbidden_remote_server_names = []
|
||||
|
||||
# List of forbidden server names that we will block all outgoing federated room directory requests for. Useful for preventing our users from wandering into bad servers or spaces.
|
||||
@@ -269,6 +291,19 @@ url_preview_check_root_domain = false
|
||||
# Defaults to true
|
||||
allow_profile_lookup_federation_requests = true
|
||||
|
||||
# Config option to automatically deactivate the account of any user who attempts to join a:
|
||||
# - banned room
|
||||
# - forbidden room alias
|
||||
# - room alias or ID with a forbidden server name
|
||||
#
|
||||
# This may be useful if all your banned lists consist of toxic rooms or servers that no good faith user would ever attempt to join, and
|
||||
# to automatically remediate the problem without any admin user intervention.
|
||||
#
|
||||
# This will also make the user leave all rooms. Federation (e.g. remote room invites) are ignored here.
|
||||
#
|
||||
# Defaults to false as rooms can be banned for non-moderation-related reasons
|
||||
#auto_deactivate_banned_room_attempts = false
|
||||
|
||||
|
||||
### Misc
|
||||
|
||||
@@ -349,27 +384,68 @@ allow_profile_lookup_federation_requests = true
|
||||
# If 'tracing_flame' is enabled, set the path to write the generated profile.
|
||||
# tracing_flame_output_path = "./tracing.folded"
|
||||
|
||||
# Enable the tokio-console. This option is only relevant to developers.
|
||||
# See: docs/development.md#debugging-with-tokio-console for more information.
|
||||
#tokio_console = false
|
||||
|
||||
# Enable backward-compatibility with Conduit's media directory by creating symlinks of media. This
|
||||
# option is only necessary if you plan on using Conduit again. Otherwise setting this to false
|
||||
# reduces filesystem clutter and overhead for managing these symlinks in the directory. This is now
|
||||
# disabled by default. You may still return to upstream Conduit but you have to run Conduwuit at
|
||||
# least once with this set to true and allow the media_startup_check to take place before shutting
|
||||
# down to return to Conduit.
|
||||
#
|
||||
# Disabled by default.
|
||||
#media_compat_file_link = false
|
||||
|
||||
# Prunes missing media from the database as part of the media startup checks. This means if you
|
||||
# delete files from the media directory the corresponding entries will be removed from the
|
||||
# database. This is disabled by default because if the media directory is accidentally moved or
|
||||
# inaccessible the metadata entries in the database will be lost with sadness.
|
||||
#
|
||||
# Disabled by default.
|
||||
#prune_missing_media = false
|
||||
|
||||
# Checks consistency of the media directory at startup:
|
||||
# 1. When `media_compat_file_link` is enbled, this check will upgrade media when switching back
|
||||
# and forth between Conduit and Conduwuit. Both options must be enabled to handle this.
|
||||
# 2. When media is deleted from the directory, this check will also delete its database entry.
|
||||
#
|
||||
# If none of these checks apply to your use cases, and your media directory is significantly large
|
||||
# setting this to false may reduce startup time.
|
||||
#
|
||||
# Enabled by default.
|
||||
#media_startup_check = true
|
||||
|
||||
# OpenID token expiration/TTL in seconds
|
||||
#
|
||||
# These are the OpenID tokens that are primarily used for Matrix account integrations, *not* OIDC/OpenID Connect/etc
|
||||
#
|
||||
# Defaults to 3600 (1 hour)
|
||||
#openid_token_ttl = 3600
|
||||
|
||||
# Emergency password feature. This password set here will let you login to the server service account (e.g. `@conduit`)
|
||||
# and let you run admin commands, invite yourself to the admin room, etc.
|
||||
#
|
||||
# no default.
|
||||
#emergency_password = ""
|
||||
|
||||
|
||||
### Generic database options
|
||||
|
||||
# Set this to any float value to multiply conduwuit's in-memory LRU caches with.
|
||||
# May be useful if you have significant memory to spare to increase performance.
|
||||
#
|
||||
# This was previously called `conduit_cache_capacity_modifier`
|
||||
#
|
||||
# Defaults to 1.0.
|
||||
#conduit_cache_capacity_modifier = 1.0
|
||||
#cache_capacity_modifier = 1.0
|
||||
|
||||
# Set this to any float value in megabytes for conduwuit to tell the database engine that this much memory is available for database-related caches.
|
||||
# May be useful if you have significant memory to spare to increase performance.
|
||||
# Defaults to 256.0
|
||||
#db_cache_capacity_mb = 256.0
|
||||
|
||||
# Interval in seconds when conduwuit will run database cleanup operations.
|
||||
#
|
||||
# For SQLite: this will flush the WAL by executing `PRAGMA wal_checkpoint(RESTART)` (https://www.sqlite.org/pragma.html#pragma_wal_checkpoint)
|
||||
# For RocksDB: this will run `flush_opt` to flush database memtables to SST files on disk (https://docs.rs/rocksdb/latest/rocksdb/struct.DBCommon.html#method.flush_opt)
|
||||
# These operations always run on shutdown.
|
||||
#
|
||||
# Defaults to 30 minutes (1800 seconds) to avoid IO amplification from too frequent cleanups
|
||||
#cleanup_second_interval = 1800
|
||||
|
||||
|
||||
### RocksDB options
|
||||
|
||||
@@ -451,6 +527,31 @@ allow_profile_lookup_federation_requests = true
|
||||
# Defaults to false as this uses more CPU when compressing.
|
||||
#rocksdb_bottommost_compression = false
|
||||
|
||||
# Level of statistics collection. Some admin commands to display database statistics may require
|
||||
# this option to be set. Database performance may be impacted by higher settings.
|
||||
#
|
||||
# Option is a number ranging from 0 to 6:
|
||||
# 0 = No statistics.
|
||||
# 1 = No statistics in release mode (default).
|
||||
# 2 to 3 = Statistics with no performance impact.
|
||||
# 3 to 5 = Statistics with possible performance impact.
|
||||
# 6 = All statistics.
|
||||
#
|
||||
# Defaults to 1 (No statistics, except in debug-mode)
|
||||
#rocksdb_stats_level = 1
|
||||
|
||||
# Database repair mode (for RocksDB SST corruption)
|
||||
#
|
||||
# Use this option when the server reports corruption while running or panics. If the server refuses
|
||||
# to start use the recovery mode options first. Corruption errors containing the acronym 'SST' which
|
||||
# occur after startup will likely require this option.
|
||||
#
|
||||
# - Backing up your database directory is recommended prior to running the repair.
|
||||
# - Disabling repair mode and restarting the server is recommended after running the repair.
|
||||
#
|
||||
# Defaults to false
|
||||
#rocksdb_repair = false
|
||||
|
||||
# Database recovery mode (for RocksDB WAL corruption)
|
||||
#
|
||||
# Use this option when the server reports corruption and refuses to start. Set mode 2 (PointInTime)
|
||||
@@ -478,11 +579,6 @@ allow_profile_lookup_federation_requests = true
|
||||
# Defaults to 1 (TolerateCorruptedTailRecords)
|
||||
#rocksdb_recovery_mode = 1
|
||||
|
||||
# Controls whether memory buffers are written to storage at the fixed interval set by `cleanup_period_interval`
|
||||
# even when they are not full. Setting this will increase load on the storage backplane and is never advised
|
||||
# under normal circumstances.
|
||||
#rocksdb_periodic_cleanup = false
|
||||
|
||||
|
||||
### Domain Name Resolution and Caching
|
||||
|
||||
@@ -697,6 +793,44 @@ allow_profile_lookup_federation_requests = true
|
||||
#typing_client_timeout_max_s = 45
|
||||
|
||||
|
||||
### TURN / VoIP
|
||||
|
||||
# vector list of TURN URIs/servers to use
|
||||
#
|
||||
# No default
|
||||
#turn_uris = ["turn:example.turn.uri?transport=udp", "turn:example.turn.uri?transport=tcp"]
|
||||
|
||||
# TURN secret to use for generating the HMAC-SHA1 hash apart of username and password generation
|
||||
#
|
||||
# this is more secure, but if needed you can use traditional username/password below.
|
||||
#
|
||||
# no default
|
||||
#turn_secret = ""
|
||||
|
||||
# TURN username to provide the client
|
||||
#
|
||||
# no default
|
||||
#turn_username = ""
|
||||
|
||||
# TURN password to provide the client
|
||||
#
|
||||
# no default
|
||||
#turn_password = ""
|
||||
|
||||
# TURN TTL
|
||||
#
|
||||
# Default is 86400 seconds
|
||||
#turn_ttl = 86400
|
||||
|
||||
# allow guests/unauthenticated users to access TURN credentials
|
||||
#
|
||||
# this is the equivalent of Synapse's `turn_allow_guests` config option. this allows
|
||||
# any unauthenticated user to call `/_matrix/client/v3/voip/turnServer`.
|
||||
#
|
||||
# defaults to false
|
||||
#turn_allow_guests = false
|
||||
|
||||
|
||||
# Other options not in [global]:
|
||||
#
|
||||
#
|
||||
|
||||
40
debian/README.md
vendored
40
debian/README.md
vendored
@@ -1,34 +1,22 @@
|
||||
conduwuit for Debian
|
||||
==================
|
||||
# conduwuit for Debian
|
||||
|
||||
Installation
|
||||
------------
|
||||
Information about downloading and deploying the Debian package. This may also be referenced for other `apt`-based distros such as Ubuntu.
|
||||
|
||||
Information about downloading, building and deploying the Debian package, see
|
||||
the "Installing conduwuit" section in the Deploying docs.
|
||||
All following sections until "Setting up the Reverse Proxy" be ignored because
|
||||
this is handled automatically by the packaging.
|
||||
### Installation
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
It is recommended to see the [generic deployment guide](../deploying/generic.md) for further information if needed as usage of the Debian package is generally related.
|
||||
|
||||
When installed, Debconf generates the configuration of the homeserver
|
||||
(host)name, the address and port it listens on. This configuration ends up in
|
||||
`/etc/conduwuit/conduwuit.toml`.
|
||||
### Configuration
|
||||
|
||||
You can tweak more detailed settings by uncommenting and setting the variables
|
||||
in `/etc/conduwuit/conduwuit.toml`. This involves settings such as the maximum
|
||||
file size for download/upload, enabling federation, etc.
|
||||
When installed, the example config is placed at `/etc/conduwuit/conduwuit.toml` as the default config. At the minimum, you will need to change your `server_name` here.
|
||||
|
||||
Running
|
||||
-------
|
||||
You can tweak more detailed settings by uncommenting and setting the config options
|
||||
in `/etc/conduwuit/conduwuit.toml`.
|
||||
|
||||
The package uses the `conduwuit.service` systemd unit file to start and
|
||||
stop conduwuit. It loads the configuration file mentioned above to set up the
|
||||
environment before running the server.
|
||||
### Running
|
||||
|
||||
This package assumes by default that conduwuit will be placed behind a reverse
|
||||
proxy. This default deployment entails just listening
|
||||
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
|
||||
<http://localhost:6167>. Matrix federation requires TLS, so you will need to set up
|
||||
some certificates and renewal, for it to work properly.
|
||||
The package uses the [`conduwuit.service`](../configuration/examples.md#example-systemd-unit-file) systemd unit file to start and stop conduwuit. The binary is installed at `/usr/sbin/conduwuit`.
|
||||
|
||||
This package assumes by default that conduwuit will be placed behind a reverse proxy. The default config options apply (listening on `localhost` and TCP port `6167`). Matrix federation requires a valid domain name and TLS, so you will need to set up TLS certificates and renewal for it to work properly if you intend to federate.
|
||||
|
||||
Consult various online documentation and guides on setting up a reverse proxy and TLS. Caddy is documented at the [generic deployment guide](../deploying/generic.md#setting-up-the-reverse-proxy) as it's the easiest and most user friendly.
|
||||
|
||||
22
debian/conduwuit.service
vendored
22
debian/conduwuit.service
vendored
@@ -1,13 +1,20 @@
|
||||
[Unit]
|
||||
Description=conduwuit Matrix homeserver
|
||||
Documentation=https://conduwuit.puppyirl.gay/
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
DynamicUser=yes
|
||||
User=_conduwuit
|
||||
Group=_conduwuit
|
||||
User=conduwuit
|
||||
Group=conduwuit
|
||||
Type=notify
|
||||
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
|
||||
ExecStart=/usr/sbin/conduwuit
|
||||
|
||||
ReadWritePaths=/var/lib/conduwuit /etc/conduwuit
|
||||
|
||||
AmbientCapabilities=
|
||||
CapabilityBoundingSet=
|
||||
|
||||
@@ -39,19 +46,16 @@ SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service @resources
|
||||
SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring @ipc
|
||||
SystemCallErrorNumber=EPERM
|
||||
StateDirectory=matrix-conduit
|
||||
#StateDirectory=conduwuit
|
||||
|
||||
RuntimeDirectory=conduit
|
||||
RuntimeDirectory=conduwuit
|
||||
RuntimeDirectoryMode=0750
|
||||
|
||||
Environment="CONDUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
|
||||
ExecStart=/usr/sbin/conduwuit
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
TimeoutStopSec=4m
|
||||
TimeoutStartSec=4m
|
||||
TimeoutStopSec=2m
|
||||
TimeoutStartSec=2m
|
||||
|
||||
StartLimitInterval=1m
|
||||
StartLimitBurst=5
|
||||
|
||||
23
debian/config
vendored
23
debian/config
vendored
@@ -1,17 +1,18 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# TODO: implement debconf support that is maintainable without duplicating the config
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# Ask for the Matrix homeserver name, address and port.
|
||||
db_input high conduwuit/hostname || true
|
||||
db_go
|
||||
|
||||
db_input low conduwuit/address || true
|
||||
db_go
|
||||
|
||||
db_input medium conduwuit/port || true
|
||||
db_go
|
||||
#. /usr/share/debconf/confmodule
|
||||
#
|
||||
## Ask for the Matrix homeserver name, address and port.
|
||||
#db_input high conduwuit/hostname || true
|
||||
#db_go
|
||||
#
|
||||
#db_input low conduwuit/address || true
|
||||
#db_go
|
||||
#
|
||||
#db_input medium conduwuit/port || true
|
||||
#db_go
|
||||
|
||||
exit 0
|
||||
|
||||
35
debian/postinst
vendored
35
debian/postinst
vendored
@@ -1,28 +1,43 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
# TODO: implement debconf support that is maintainable without duplicating the config
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit/
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
||||
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
# Create the `_conduwuit` user if it does not exist yet.
|
||||
if ! getent passwd _conduwuit > /dev/null ; then
|
||||
# Create the `conduwuit` user if it does not exist yet.
|
||||
if ! getent passwd conduwuit > /dev/null ; then
|
||||
echo 'Adding system user for the conduwuit Matrix homeserver' 1>&2
|
||||
adduser --system --group --quiet \
|
||||
--home "$CONDUWUIT_DATABASE_PATH" \
|
||||
--disabled-login \
|
||||
--shell "/usr/sbin/nologin" \
|
||||
--force-badname \
|
||||
_conduwuit
|
||||
conduwuit
|
||||
fi
|
||||
|
||||
# Create the database path if it does not exist yet and fix up ownership
|
||||
# and permissions.
|
||||
mkdir -p "$CONDUWUIT_DATABASE_PATH"
|
||||
chown _conduwuit:_conduwuit -R "$CONDUWUIT_DATABASE_PATH"
|
||||
chmod 700 "$CONDUWUIT_DATABASE_PATH"
|
||||
# and permissions for the config.
|
||||
mkdir -v -p "$CONDUWUIT_DATABASE_PATH"
|
||||
|
||||
# symlink the previous location for compatibility if it does not exist yet.
|
||||
if ! test -L "/var/lib/matrix-conduit" ; then
|
||||
ln -s -v "$CONDUWUIT_DATABASE_PATH" "/var/lib/matrix-conduit"
|
||||
fi
|
||||
|
||||
chown -v conduwuit:conduwuit -R "$CONDUWUIT_DATABASE_PATH"
|
||||
chown -v conduwuit:conduwuit -R "$CONDUWUIT_CONFIG_PATH"
|
||||
|
||||
chmod -v 740 "$CONDUWUIT_DATABASE_PATH"
|
||||
|
||||
echo ''
|
||||
echo 'Make sure you edit the example config at /etc/conduwuit/conduwuit.toml before starting!'
|
||||
echo 'To start the server, run: systemctl start conduwuit.service'
|
||||
echo ''
|
||||
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
11
debian/postrm
vendored
11
debian/postrm
vendored
@@ -1,10 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
. /usr/share/debconf/confmodule
|
||||
#. /usr/share/debconf/confmodule
|
||||
|
||||
CONDUWUIT_CONFIG_PATH=/etc/conduwuit
|
||||
CONDUWUIT_DATABASE_PATH=/var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_PATH_SYMLINK=/var/lib/matrix-conduit
|
||||
|
||||
case $1 in
|
||||
purge)
|
||||
@@ -15,11 +16,15 @@ case $1 in
|
||||
# "configuration files must be preserved when the package is removed, and
|
||||
# only deleted when the package is purged."
|
||||
if [ -d "$CONDUWUIT_CONFIG_PATH" ]; then
|
||||
rm -r "$CONDUWUIT_CONFIG_PATH"
|
||||
rm -v -r "$CONDUWUIT_CONFIG_PATH"
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH" ]; then
|
||||
rm -r "$CONDUWUIT_DATABASE_PATH"
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH"
|
||||
fi
|
||||
|
||||
if [ -d "$CONDUWUIT_DATABASE_PATH_SYMLINK" ]; then
|
||||
rm -v -r "$CONDUWUIT_DATABASE_PATH_SYMLINK"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
21
debian/templates
vendored
21
debian/templates
vendored
@@ -1,21 +0,0 @@
|
||||
Template: conduwuit/hostname
|
||||
Type: string
|
||||
Default: localhost
|
||||
Description: The server (host)name of the Matrix homeserver
|
||||
This is the hostname the homeserver will be reachable at via a client.
|
||||
.
|
||||
If set to "localhost", you can connect with a client locally and clients
|
||||
from other hosts and also other homeservers will not be able to reach you!
|
||||
|
||||
Template: conduwuit/address
|
||||
Type: string
|
||||
Default: 127.0.0.1
|
||||
Description: The listen address of the Matrix homeserver
|
||||
This is the address the homeserver will listen on. Leave it set to 127.0.0.1
|
||||
when using a reverse proxy.
|
||||
|
||||
Template: conduwuit/port
|
||||
Type: string
|
||||
Default: 6167
|
||||
Description: The port of the Matrix homeserver
|
||||
This port is most often just accessed by a reverse proxy.
|
||||
42
deps/rust-rocksdb/Cargo.toml
vendored
Normal file
42
deps/rust-rocksdb/Cargo.toml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "rust-rocksdb-uwu"
|
||||
categories.workspace = true
|
||||
description = "dylib wrapper for rust-rocksdb"
|
||||
edition = "2021"
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.0.1"
|
||||
|
||||
[features]
|
||||
default = ["lz4", "zstd", "zlib", "bzip2"]
|
||||
jemalloc = ["rust-rocksdb/jemalloc"]
|
||||
io-uring = ["rust-rocksdb/io-uring"]
|
||||
valgrind = ["rust-rocksdb/valgrind"]
|
||||
snappy = ["rust-rocksdb/snappy"]
|
||||
lz4 = ["rust-rocksdb/lz4"]
|
||||
zstd = ["rust-rocksdb/zstd"]
|
||||
zlib = ["rust-rocksdb/zlib"]
|
||||
bzip2 = ["rust-rocksdb/bzip2"]
|
||||
rtti = ["rust-rocksdb/rtti"]
|
||||
mt_static = ["rust-rocksdb/mt_static"]
|
||||
multi-threaded-cf = ["rust-rocksdb/multi-threaded-cf"]
|
||||
serde1 = ["rust-rocksdb/serde1"]
|
||||
malloc-usable-size = ["rust-rocksdb/malloc-usable-size"]
|
||||
|
||||
[dependencies.rust-rocksdb]
|
||||
git = "https://github.com/girlbossceo/rust-rocksdb-zaidoon1"
|
||||
rev = "5383ca8173299066b516406e3a2cf945ead891cb"
|
||||
#branch = "master"
|
||||
default-features = false
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib"
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
61
deps/rust-rocksdb/lib.rs
vendored
Normal file
61
deps/rust-rocksdb/lib.rs
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
pub use rust_rocksdb::*;
|
||||
|
||||
#[cfg_attr(not(conduit_mods), link(name = "rocksdb"))]
|
||||
#[cfg_attr(conduit_mods, link(name = "rocksdb", kind = "static"))]
|
||||
extern "C" {
|
||||
pub fn rocksdb_list_column_families();
|
||||
pub fn rocksdb_logger_create_stderr_logger();
|
||||
pub fn rocksdb_options_set_info_log();
|
||||
pub fn rocksdb_get_options_from_string();
|
||||
pub fn rocksdb_writebatch_create();
|
||||
pub fn rocksdb_writebatch_destroy();
|
||||
pub fn rocksdb_writebatch_put_cf();
|
||||
pub fn rocksdb_writebatch_delete_cf();
|
||||
pub fn rocksdb_iter_value();
|
||||
pub fn rocksdb_iter_seek_to_last();
|
||||
pub fn rocksdb_iter_seek_for_prev();
|
||||
pub fn rocksdb_iter_seek_to_first();
|
||||
pub fn rocksdb_iter_next();
|
||||
pub fn rocksdb_iter_prev();
|
||||
pub fn rocksdb_iter_seek();
|
||||
pub fn rocksdb_iter_valid();
|
||||
pub fn rocksdb_iter_get_error();
|
||||
pub fn rocksdb_iter_key();
|
||||
pub fn rocksdb_iter_destroy();
|
||||
pub fn rocksdb_livefiles();
|
||||
pub fn rocksdb_livefiles_count();
|
||||
pub fn rocksdb_livefiles_destroy();
|
||||
pub fn rocksdb_livefiles_column_family_name();
|
||||
pub fn rocksdb_livefiles_name();
|
||||
pub fn rocksdb_livefiles_size();
|
||||
pub fn rocksdb_livefiles_level();
|
||||
pub fn rocksdb_livefiles_smallestkey();
|
||||
pub fn rocksdb_livefiles_largestkey();
|
||||
pub fn rocksdb_livefiles_entries();
|
||||
pub fn rocksdb_livefiles_deletions();
|
||||
pub fn rocksdb_put_cf();
|
||||
pub fn rocksdb_delete_cf();
|
||||
pub fn rocksdb_get_pinned_cf();
|
||||
pub fn rocksdb_create_column_family();
|
||||
pub fn rocksdb_get_latest_sequence_number();
|
||||
pub fn rocksdb_batched_multi_get_cf();
|
||||
pub fn rocksdb_cancel_all_background_work();
|
||||
pub fn rocksdb_repair_db();
|
||||
pub fn rocksdb_list_column_families_destroy();
|
||||
pub fn rocksdb_flush();
|
||||
pub fn rocksdb_flush_wal();
|
||||
pub fn rocksdb_open_column_families();
|
||||
pub fn rocksdb_open_for_read_only_column_families();
|
||||
pub fn rocksdb_open_as_secondary_column_families();
|
||||
pub fn rocksdb_open_column_families_with_ttl();
|
||||
pub fn rocksdb_open();
|
||||
pub fn rocksdb_open_for_read_only();
|
||||
pub fn rocksdb_open_with_ttl();
|
||||
pub fn rocksdb_open_as_secondary();
|
||||
pub fn rocksdb_write();
|
||||
pub fn rocksdb_create_iterator_cf();
|
||||
pub fn rocksdb_backup_engine_create_new_backup_flush();
|
||||
pub fn rocksdb_backup_engine_options_create();
|
||||
pub fn rocksdb_write_buffer_manager_destroy();
|
||||
pub fn rocksdb_options_set_ttl();
|
||||
}
|
||||
@@ -2,14 +2,21 @@ # Summary
|
||||
|
||||
- [Introduction](introduction.md)
|
||||
- [Differences from upstream Conduit](differences.md)
|
||||
|
||||
- [Example configuration](configuration.md)
|
||||
- [Configuration](configuration.md)
|
||||
- [Examples](configuration/examples.md)
|
||||
- [Deploying](deploying.md)
|
||||
- [Generic](deploying/generic.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [NixOS](deploying/nixos.md)
|
||||
- [Generic](deploying/generic.md)
|
||||
- [NixOS](deploying/nixos.md)
|
||||
- [Docker](deploying/docker.md)
|
||||
- [Arch Linux](deploying/arch-linux.md)
|
||||
- [Debian](deploying/debian.md)
|
||||
- [FreeBSD](deploying/freebsd.md)
|
||||
- [TURN](turn.md)
|
||||
- [Appservices](appservices.md)
|
||||
- [Maintenance](maintenance.md)
|
||||
- [Troubleshooting](troubleshooting.md)
|
||||
- [Development](development.md)
|
||||
- [Testing](development/testing.md)
|
||||
- [Contributing](contributing.md)
|
||||
- [Testing](development/testing.md)
|
||||
- [Hot Reloading ("Live" Development)](development/hot_reload.md)
|
||||
- [conduwuit Community Code of Conduct](conduwuit_coc.md)
|
||||
|
||||
@@ -2,23 +2,25 @@ # Setting up Appservices
|
||||
|
||||
## Getting help
|
||||
|
||||
If you run into any problems while setting up an Appservice: ask us in [#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay) or [open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new).
|
||||
If you run into any problems while setting up an Appservice: ask us in
|
||||
[#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay) or
|
||||
[open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new).
|
||||
|
||||
## Set up the appservice - general instructions
|
||||
|
||||
Follow whatever instructions are given by the appservice. This usually includes
|
||||
downloading, changing its config (setting domain, homeserver url, port etc.)
|
||||
and later starting it.
|
||||
downloading, changing its config (setting domain, homeserver url, port etc.) and
|
||||
later starting it.
|
||||
|
||||
At some point the appservice guide should ask you to add a registration yaml
|
||||
file to the homeserver. In Synapse you would do this by adding the path to the
|
||||
homeserver.yaml, but in Conduit you can do this from within Matrix:
|
||||
homeserver.yaml, but in conduwuit you can do this from within Matrix:
|
||||
|
||||
First, go into the #admins room of your homeserver. The first person that
|
||||
First, go into the `#admins` room of your homeserver. The first person that
|
||||
registered on the homeserver automatically joins it. Then send a message into
|
||||
the room like this:
|
||||
|
||||
@conduit:your.server.name: register-appservice
|
||||
!admin appservices register
|
||||
```
|
||||
paste
|
||||
the
|
||||
@@ -31,13 +33,13 @@ ## Set up the appservice - general instructions
|
||||
```
|
||||
|
||||
You can confirm it worked by sending a message like this:
|
||||
`@conduit:your.server.name: appservices list`
|
||||
`!admin appservices list`
|
||||
|
||||
The `@conduit` bot should answer with `Appservices (1): your-bridge`
|
||||
The server bot should answer with `Appservices (1): your-bridge`
|
||||
|
||||
Then you are done. Conduit will send messages to the appservices and the
|
||||
Then you are done. conduwuit will send messages to the appservices and the
|
||||
appservice can send requests to the homeserver. You don't need to restart
|
||||
Conduit, but if it doesn't work, restarting while the appservice is running
|
||||
conduwuit, but if it doesn't work, restarting while the appservice is running
|
||||
could help.
|
||||
|
||||
## Appservice-specific instructions
|
||||
@@ -46,16 +48,6 @@ ### Remove an appservice
|
||||
|
||||
To remove an appservice go to your admin room and execute
|
||||
|
||||
`@conduit:your.server.name: appservices unregister <name>`
|
||||
`!admin appservices unregister <name>`
|
||||
|
||||
where `<name>` one of the output of `appservices list`.
|
||||
|
||||
### Tested appservices
|
||||
|
||||
These appservices have been tested and work with Conduit without any extra steps:
|
||||
|
||||
- [matrix-appservice-discord](https://github.com/Half-Shot/matrix-appservice-discord)
|
||||
- [mautrix-hangouts](https://github.com/mautrix/hangouts/)
|
||||
- [mautrix-telegram](https://github.com/mautrix/telegram/)
|
||||
- [mautrix-signal](https://github.com/mautrix/signal/) from version `0.2.2` forward.
|
||||
- [heisenbridge](https://github.com/hifi/heisenbridge/)
|
||||
|
||||
93
docs/conduwuit_coc.md
Normal file
93
docs/conduwuit_coc.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# conduwuit Community Code of Conduct
|
||||
|
||||
Welcome to the conduwuit community! We’re excited to have you here. conduwuit is
|
||||
a hard-fork of the Conduit homeserver, aimed at making Matrix more accessible
|
||||
and inclusive for everyone.
|
||||
|
||||
This space is dedicated to fostering a positive, supportive, and inclusive
|
||||
environment for everyone. This Code of Conduct applies to all conduwuit spaces,
|
||||
including any further community rooms that reference this CoC. Here are our
|
||||
guidelines to help maintain the welcoming atmosphere that sets conduwuit apart.
|
||||
|
||||
For the general foundational rules, please refer to the [Contributor's
|
||||
Covenant](https://github.com/girlbossceo/conduwuit/blob/main/CODE_OF_CONDUCT.md).
|
||||
Below are additional guidelines specific to the conduwuit community.
|
||||
|
||||
## Our Values and Guidelines
|
||||
|
||||
1. **Respect and Inclusivity**: We are committed to maintaining a community
|
||||
where everyone feels safe and respected. Discrimination, harassment, or hate
|
||||
speech of any kind will not be tolerated. Recognise that each community member
|
||||
experiences the world differently based on their past experiences, background,
|
||||
and identity. Share your own experiences and be open to learning about others'
|
||||
diverse perspectives.
|
||||
|
||||
2. **Positivity and Constructiveness**: Engage in constructive discussions and
|
||||
support each other. If you feel angry, negative, or aggressive, take a break
|
||||
until you can participate in a positive and constructive manner. Process intense
|
||||
feelings with a friend or in a private setting before engaging in community
|
||||
conversations to help maintain a supportive and focused environment.
|
||||
|
||||
3. **Clarity and Understanding**: Our community includes neurodivergent
|
||||
individuals and those who may not appreciate sarcasm or subtlety. Communicate
|
||||
clearly and kindly, avoiding sarcasm and ensuring your messages are easily
|
||||
understood by all. Additionally, avoid putting the burden of education on
|
||||
marginalized groups by doing your own research before asking for explanations.
|
||||
|
||||
4. **Be Open to Inclusivity**: Actively engage in conversations about making our
|
||||
community more inclusive. Report discriminatory behavior to the moderators
|
||||
and be open to constructive feedback that aims to improve our community.
|
||||
Understand that discussing discrimination and negative experiences can be
|
||||
emotionally taxing, so focus on the message rather than critiquing the tone
|
||||
used.
|
||||
|
||||
5. **Commit to Inclusivity**: Building an inclusive community requires time,
|
||||
energy, and resources. Recognise that addressing discrimination and bias is
|
||||
an ongoing process that necessitates commitment and action from all community
|
||||
members.
|
||||
|
||||
## Matrix Community
|
||||
|
||||
This Code of Conduct applies to the entire [conduwuit Matrix
|
||||
Space](https://matrix.to/#/#conduwuit-space:puppygock.gay) and its rooms,
|
||||
including:
|
||||
|
||||
### [#conduwuit:puppygock.gay](https://matrix.to/#/#conduwuit:puppygock.gay)
|
||||
|
||||
This room is for support and discussions about conduwuit. Ask questions, share
|
||||
insights, and help each other out.
|
||||
|
||||
### [#conduwuit-offtopic:girlboss.ceo](https://matrix.to/#/#conduwuit-offtopic:girlboss.ceo)
|
||||
|
||||
For off-topic community conversations about any subject. While this room allows
|
||||
for a wide range of topics, the same CoC applies. Keep discussions respectful
|
||||
and inclusive, and avoid divisive subjects like country/world politics. General
|
||||
topics, such as world events, are welcome as long as they follow the CoC.
|
||||
|
||||
### [#conduwuit-dev:puppygock.gay](https://matrix.to/#/#conduwuit-dev:puppygock.gay)
|
||||
|
||||
This room is dedicated to discussing active development of conduwuit. Posting
|
||||
requires an elevated power level, which can be requested in one of the other
|
||||
rooms. Use this space to collaborate and innovate.
|
||||
|
||||
## Enforcement
|
||||
|
||||
We have a zero-tolerance policy for violations of this Code of Conduct. If
|
||||
someone’s behavior makes you uncomfortable, please report it to the moderators.
|
||||
Actions we may take include:
|
||||
|
||||
1. **Warning**: A warning given directly in the room or via a private message
|
||||
from the moderators, identifying the violation and requesting corrective
|
||||
action.
|
||||
2. **Temporary Mute**: Temporary restriction from participating in discussions
|
||||
for a specified period to allow for reflection and cooling off.
|
||||
3. **Kick or Ban**: Egregious behavior may result in an immediate kick or ban to
|
||||
protect other community members. Bans are considered permanent and will only
|
||||
be reversed in exceptional circumstances after proven good behavior.
|
||||
|
||||
Please highlight issues directly in rooms when possible, but if you don't feel
|
||||
comfortable doing that, then please send a DM to one of the moderators directly.
|
||||
|
||||
Together, let’s build a community where everyone feels valued and respected.
|
||||
|
||||
- The conduwuit Moderation Team
|
||||
@@ -1,5 +1,48 @@
|
||||
# Example configuration
|
||||
# Configuration
|
||||
|
||||
``` toml
|
||||
{{#include ../conduwuit-example.toml}}
|
||||
```
|
||||
This chapter describes various ways to configure conduwuit.
|
||||
|
||||
## Basics
|
||||
|
||||
conduwuit uses a config file for the majority of the settings, but also supports
|
||||
setting individual config options via commandline.
|
||||
|
||||
Please refer to the [example config
|
||||
file](./configuration/examples.md#example-configuration) for all of those
|
||||
settings.
|
||||
|
||||
The config file to use can be specified on the commandline when running
|
||||
conduwuit by specifying the `-c`, `--config` flag. Alternatively, you can use
|
||||
the environment variable `CONDUWUIT_CONFIG` to specify the config file to used.
|
||||
Conduit's environment variables are supported for backwards compatibility.
|
||||
|
||||
## Option commandline flag
|
||||
|
||||
conduwuit supports setting individual config options in TOML format from the
|
||||
`-O` / `--option` flag. For example, you can set your server name via `-O
|
||||
server_name=\"example.com\"`.
|
||||
|
||||
Note that the config is parsed as TOML, and shells like bash will remove quotes.
|
||||
So unfortunately it is required to escape quotes if the config option takes a
|
||||
string. This does not apply to options that take booleans or numbers:
|
||||
- `--option allow_registration=true` works ✅
|
||||
- `-O max_request_size=99999999` works ✅
|
||||
- `-O server_name=example.com` does not work ❌
|
||||
- `--option log=\"debug\"` works ✅
|
||||
- `--option server_name='"example.com'"` works ✅
|
||||
|
||||
|
||||
## Environment variables
|
||||
|
||||
All of the settings that are found in the config file can be specified by using
|
||||
environment variables. The environment variable names should be all caps and
|
||||
prefixed with `CONDUWUIT_`.
|
||||
|
||||
For example, if the setting you are changing is `max_request_size`, then the
|
||||
environment variable to set is `CONDUWUIT_MAX_REQUEST_SIZE`.
|
||||
|
||||
To modify config options not in the `[global]` context such as
|
||||
`[global.well_known]`, use the `__` suffix split: `CONDUWUIT_WELL_KNOWN__SERVER`
|
||||
|
||||
Conduit's environment variables are supported for backwards compatibility (e.g.
|
||||
`CONDUIT_SERVER_NAME`).
|
||||
|
||||
32
docs/configuration/examples.md
Normal file
32
docs/configuration/examples.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Example configuration
|
||||
|
||||
<details>
|
||||
<summary>Example configuration</summary>
|
||||
|
||||
```toml
|
||||
{{#include ../../conduwuit-example.toml}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Debian systemd unit file
|
||||
|
||||
<details>
|
||||
<summary>Debian systemd unit file</summary>
|
||||
|
||||
```
|
||||
{{#include ../../debian/conduwuit.service}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Arch Linux systemd unit file
|
||||
|
||||
<details>
|
||||
<summary>Arch Linux systemd unit file</summary>
|
||||
|
||||
```
|
||||
{{#include ../../arch/conduwuit.service}}
|
||||
```
|
||||
|
||||
</details>
|
||||
1
docs/contributing.md
Normal file
1
docs/contributing.md
Normal file
@@ -0,0 +1 @@
|
||||
{{#include ../CONTRIBUTING.md}}
|
||||
@@ -1,3 +1,3 @@
|
||||
# Deploying
|
||||
|
||||
This chapter describes various ways to deploy Conduwuit.
|
||||
This chapter describes various ways to deploy conduwuit.
|
||||
|
||||
15
docs/deploying/arch-linux.md
Normal file
15
docs/deploying/arch-linux.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# conduwuit for Arch Linux
|
||||
|
||||
Currently conduwuit is only on the Arch User Repository (AUR).
|
||||
|
||||
The conduwuit AUR packages are community maintained and are not maintained by
|
||||
conduwuit development team, but the AUR package maintainers are in the Matrix
|
||||
room. Please attempt to verify your AUR package's PKGBUILD file looks fine
|
||||
before asking for support.
|
||||
|
||||
- [conduwuit](https://aur.archlinux.org/packages/conduwuit) - latest tagged
|
||||
conduwuit
|
||||
- [conduwuit-git](https://aur.archlinux.org/packages/conduwuit-git) - latest git
|
||||
conduwuit from `main` branch
|
||||
- [conduwuit-bin](https://aur.archlinux.org/packages/conduwuit-bin) - latest
|
||||
tagged conduwuit static binary
|
||||
@@ -1,40 +1,29 @@
|
||||
# Conduit - Behind Traefik Reverse Proxy
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
|
||||
### If you already built the conduduwit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_LOG: warn,state_res=warn
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
@@ -48,7 +37,7 @@ services:
|
||||
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Conduit - Traefik Reverse Proxy Labels
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
# conduwuit - Traefik Reverse Proxy Labels
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
@@ -7,10 +6,10 @@ services:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
|
||||
|
||||
- "traefik.http.routers.to-conduit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Conduit is hosted
|
||||
- "traefik.http.routers.to-conduit.tls=true"
|
||||
- "traefik.http.routers.to-conduit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduit.middlewares=cors-headers@docker"
|
||||
- "traefik.http.routers.to-conduwuit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which conduwuit is hosted
|
||||
- "traefik.http.routers.to-conduwuit.tls=true"
|
||||
- "traefik.http.routers.to-conduwuit.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.routers.to-conduwuit.middlewares=cors-headers@docker"
|
||||
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
|
||||
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
||||
|
||||
54
docs/deploying/docker-compose.with-caddy.yml
Normal file
54
docs/deploying/docker-compose.with-caddy.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
services:
|
||||
caddy:
|
||||
# This compose file uses caddy-docker-proxy as the reverse proxy for conduwuit!
|
||||
# For more info, visit https://github.com/lucaslorentz/caddy-docker-proxy
|
||||
image: lucaslorentz/caddy-docker-proxy:ci-alpine
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
environment:
|
||||
- CADDY_INGRESS_NETWORKS=caddy
|
||||
networks:
|
||||
- caddy
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./data:/data
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
caddy: example.com
|
||||
caddy.0_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
|
||||
caddy.1_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"}}
|
||||
|
||||
homeserver:
|
||||
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
environment:
|
||||
CONDUWUIT_SERVER_NAME: example.com # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
networks:
|
||||
- caddy
|
||||
labels:
|
||||
caddy: matrix.example.com
|
||||
caddy.reverse_proxy: "{{upstreams 6167}}"
|
||||
|
||||
volumes:
|
||||
db:
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
@@ -1,45 +1,32 @@
|
||||
# Conduit - Behind Traefik Reverse Proxy
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
# conduwuit - Behind Traefik Reverse Proxy
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
|
||||
### If you already built the conduwuit image with 'docker build' or want to use the Docker Hub image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- db:/srv/conduit/.local/share/conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/srv/conduwuit/.local/share/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
networks:
|
||||
- proxy
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUIT_ALLOW_REGISTRATION : 'true'
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
CONDUWUIT_ALLOW_REGISTRATION : 'true'
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
### Uncomment and change values as desired
|
||||
# CONDUIT_ADDRESS: 0.0.0.0
|
||||
# CONDUIT_PORT: 6167
|
||||
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
|
||||
# CONDUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
|
||||
# CONDUIT_WORKERS: 10
|
||||
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
# CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
# CONDUWUIT_PORT: 6167
|
||||
# CONDUWUIT_LOG: info # default is: "warn,state_res=warn"
|
||||
# CONDUWUIT_ALLOW_JAEGER: 'false'
|
||||
# CONDUWUIT_ALLOW_ENCRYPTION: 'true'
|
||||
# CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
# CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
# CONDUWUIT_DATABASE_PATH: /srv/conduwuit/.local/share/conduwuit
|
||||
# CONDUWUIT_WORKERS: 10
|
||||
# CONDUWUIT_MAX_REQUEST_SIZE: 20000000 # in bytes, ~20 MB
|
||||
|
||||
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
|
||||
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
|
||||
@@ -53,7 +40,7 @@ services:
|
||||
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -1,45 +1,33 @@
|
||||
# Conduit
|
||||
version: '2.4' # uses '2.4' for cpuset
|
||||
# conduwuit
|
||||
|
||||
services:
|
||||
homeserver:
|
||||
### If you already built the Conduit image with 'docker build' or want to use a registry image,
|
||||
### If you already built the conduwuit image with 'docker build' or want to use a registry image,
|
||||
### then you are ready to go.
|
||||
image: girlbossceo/conduwuit:latest
|
||||
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
|
||||
### build lines. If you want meaningful labels in your built Conduit image, you should run docker compose like this:
|
||||
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker compose up -d
|
||||
# build:
|
||||
# context: .
|
||||
# args:
|
||||
# CREATED: '2021-03-16T08:18:27Z'
|
||||
# VERSION: '0.1.0'
|
||||
# LOCAL: 'false'
|
||||
# GIT_REF: origin/master
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8448:6167
|
||||
volumes:
|
||||
- db:/var/lib/matrix-conduit
|
||||
#- ./conduwuit.toml:/etc/conduit.toml
|
||||
- db:/var/lib/conduwuit
|
||||
#- ./conduwuit.toml:/etc/conduwuit.toml
|
||||
environment:
|
||||
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit
|
||||
CONDUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUIT_PORT: 6167
|
||||
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUIT_LOG: warn,state_res=warn
|
||||
CONDUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUIT_CONFIG: './conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#cpuset: "0-4" # Uncomment to limit to specific CPU cores
|
||||
CONDUWUIT_SERVER_NAME: your.server.name # EDIT THIS
|
||||
CONDUWUIT_DATABASE_PATH: /var/lib/conduwuit
|
||||
CONDUWUIT_DATABASE_BACKEND: rocksdb
|
||||
CONDUWUIT_PORT: 6167
|
||||
CONDUWUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
|
||||
CONDUWUIT_ALLOW_REGISTRATION: 'true'
|
||||
CONDUWUIT_ALLOW_FEDERATION: 'true'
|
||||
CONDUWUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
|
||||
CONDUWUIT_TRUSTED_SERVERS: '["matrix.org"]'
|
||||
#CONDUWUIT_LOG: warn,state_res=warn
|
||||
CONDUWUIT_ADDRESS: 0.0.0.0
|
||||
#CONDUWUIT_CONFIG: '/etc/conduwuit.toml' # Uncomment if you mapped config toml above
|
||||
#
|
||||
### Uncomment if you want to use your own Element-Web App.
|
||||
### Note: You need to provide a config.json for Element and you also need a second
|
||||
### Domain or Subdomain for the communication between Element and Conduit
|
||||
### Domain or Subdomain for the communication between Element and conduwuit
|
||||
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
|
||||
# element-web:
|
||||
# image: vectorim/element-web:latest
|
||||
|
||||
@@ -2,94 +2,117 @@ # conduwuit for Docker
|
||||
|
||||
## Docker
|
||||
|
||||
To run conduwuit with Docker you can either build the image yourself or pull it from a registry.
|
||||
|
||||
To run conduwuit with Docker you can either build the image yourself or pull it
|
||||
from a registry.
|
||||
|
||||
### Use a registry
|
||||
|
||||
OCI images for conduwuit are available in the registries listed below.
|
||||
|
||||
| Registry | Image | Size | Notes |
|
||||
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:latest][gl] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:latest][dh] | ![Image Size][shield-latest] | Stable tagged image. |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:main][gl] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:main][dh] | ![Image Size][shield-main] | Stable main branch. |
|
||||
| GitHub Registry | [ghcr.io/girlbossceo/conduwuit:dev][gh] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
| GitLab Registry | [registry.gitlab.com/girlbossceo/conduwuit:dev][gl] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
| Docker Hub | [docker.io/girlbossceo/conduwuit:dev][dh] | ![Image Size][shield-dev] | Development version/branch. |
|
||||
| Registry | Image
|
||||
| Size | Notes | | --------------- |
|
||||
--------------------------------------------------------------- |
|
||||
----------------------------- | ---------------------- | | GitHub Registry |
|
||||
[ghcr.io/girlbossceo/conduwuit:latest][gh] | ![Image Size][shield-latest] |
|
||||
Stable tagged image. | | GitLab Registry |
|
||||
[registry.gitlab.com/conduwuit/conduwuit:latest][gl] | ![Image
|
||||
Size][shield-latest] | Stable tagged image. | | Docker Hub |
|
||||
[docker.io/girlbossceo/conduwuit:latest][dh] | ![Image
|
||||
Size][shield-latest] | Stable tagged image. | | GitHub Registry |
|
||||
[ghcr.io/girlbossceo/conduwuit:main][gh] | ![Image Size][shield-main] |
|
||||
Stable main branch. | | GitLab Registry |
|
||||
[registry.gitlab.com/conduwuit/conduwuit:main][gl] | ![Image
|
||||
Size][shield-main] | Stable main branch. | | Docker Hub |
|
||||
[docker.io/girlbossceo/conduwuit:main][dh] | ![Image
|
||||
Size][shield-main] | Stable main branch. |
|
||||
|
||||
|
||||
[dh]: https://hub.docker.com/repository/docker/girlbossceo/conduwuit
|
||||
[dh]: https://hub.docker.com/r/girlbossceo/conduwuit
|
||||
[gh]: https://github.com/girlbossceo/conduwuit/pkgs/container/conduwuit
|
||||
[gl]: https://gitlab.com/girlbossceo/conduwuit/container_registry/6351657
|
||||
[gl]: https://gitlab.com/conduwuit/conduwuit/container_registry/6369729
|
||||
[shield-latest]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/latest
|
||||
[shield-main]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/main
|
||||
[shield-dev]: https://img.shields.io/docker/image-size/girlbossceo/conduwuit/dev
|
||||
|
||||
|
||||
Use
|
||||
```bash
|
||||
docker image pull <link>
|
||||
```
|
||||
to pull it to your machine.
|
||||
|
||||
```bash docker image pull <link> ```
|
||||
|
||||
to pull it to your machine.
|
||||
|
||||
### Run
|
||||
|
||||
When you have the image you can simply run it with
|
||||
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/conduwuit/ \
|
||||
-e CONDUIT_SERVER_NAME="your.server.name" \
|
||||
-e CONDUIT_DATABASE_BACKEND="rocksdb" \
|
||||
-e CONDUIT_ALLOW_REGISTRATION=false \
|
||||
-e CONDUIT_ALLOW_FEDERATION=true \
|
||||
-e CONDUIT_MAX_REQUEST_SIZE="40000000" \
|
||||
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
|
||||
-e CONDUIT_LOG="warn,ruma_state_res=warn" \
|
||||
--name conduit <link>
|
||||
```bash
|
||||
docker run -d -p 8448:6167 \
|
||||
-v db:/var/lib/conduwuit/ \
|
||||
-e CONDUWUIT_SERVER_NAME="your.server.name" \
|
||||
-e CONDUWUIT_DATABASE_BACKEND="rocksdb" \
|
||||
-e CONDUWUIT_ALLOW_REGISTRATION=false \
|
||||
--name conduit <link>
|
||||
```
|
||||
|
||||
or you can use [docker compose](#docker-compose).
|
||||
|
||||
The `-d` flag lets the container run in detached mode. You may supply an optional `conduwuit.toml` config file, an example can be found [here](../configuration.md).
|
||||
You can pass in different env vars to change config values on the fly. You can even configure conduwuit completely by using env vars. For an overview of possible
|
||||
values, please take a look at the `docker-compose.yml` file.
|
||||
The `-d` flag lets the container run in detached mode. You may supply an
|
||||
optional `conduwuit.toml` config file, the example config can be found
|
||||
[here](../configuration/examples.md). You can pass in different env vars to
|
||||
change config values on the fly. You can even configure conduwuit completely by
|
||||
using env vars. For an overview of possible values, please take a look at the
|
||||
[`docker-compose.yml`](docker-compose.yml) file.
|
||||
|
||||
If you just want to test conduwuit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
|
||||
If you just want to test conduwuit for a short time, you can use the `--rm`
|
||||
flag, which will clean up everything related to your container after you stop
|
||||
it.
|
||||
|
||||
### Docker-compose
|
||||
|
||||
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
|
||||
If the `docker run` command is not for you or your setup, you can also use one
|
||||
of the provided `docker-compose` files.
|
||||
|
||||
Depending on your proxy setup, you can use one of the following files;
|
||||
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
|
||||
- If you don't have a `traefik` instance set up (or any other reverse proxy), use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
|
||||
|
||||
- If you already have a `traefik` instance set up, use
|
||||
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
|
||||
- If you don't have a `traefik` instance set up and would like to use it, use
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
|
||||
- If you want a setup that works out of the box with `caddy-docker-proxy`, use
|
||||
[`docker-compose.with-caddy.yml`](docker-compose.with-caddy.yml) and replace all
|
||||
`example.com` placeholders with your own domain
|
||||
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
|
||||
|
||||
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
|
||||
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
|
||||
for your server.
|
||||
When picking the traefik-related compose file, rename it so it matches
|
||||
`docker-compose.yml`, and rename the override file to
|
||||
`docker-compose.override.yml`. Edit the latter with the values you want for your
|
||||
server.
|
||||
|
||||
When picking the `caddy-docker-proxy` compose file, it's important to first
|
||||
create the `caddy` network before spinning up the containers:
|
||||
|
||||
```bash docker network create caddy ```
|
||||
|
||||
After that, you can rename it so it matches `docker-compose.yml` and spin up the
|
||||
containers!
|
||||
|
||||
Additional info about deploying conduwuit can be found [here](generic.md).
|
||||
|
||||
### Build
|
||||
|
||||
To build the conduwuit image with docker-compose, you first need to open and modify the `docker-compose.yml` file. There you need to comment the `image:` option and uncomment the `build:` option. Then call docker compose with:
|
||||
To build the conduwuit image with docker-compose, you first need to open and
|
||||
modify the `docker-compose.yml` file. There you need to comment the `image:`
|
||||
option and uncomment the `build:` option. Then call docker compose with:
|
||||
|
||||
```bash
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
|
||||
This will also start the container right afterwards, so if want it to run in detached mode, you also should use the `-d` flag.
|
||||
This will also start the container right afterwards, so if want it to run in
|
||||
detached mode, you also should use the `-d` flag.
|
||||
|
||||
### Run
|
||||
|
||||
If you already have built the image or want to use one from the registries, you can just start the container and everything else in the compose file in detached mode with:
|
||||
If you already have built the image or want to use one from the registries, you
|
||||
can just start the container and everything else in the compose file in detached
|
||||
mode with:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
@@ -99,105 +122,22 @@ ### Run
|
||||
|
||||
### Use Traefik as Proxy
|
||||
|
||||
As a container user, you probably know about Traefik. It is a easy to use reverse proxy for making
|
||||
containerized app and services available through the web. With the two provided files,
|
||||
As a container user, you probably know about Traefik. It is a easy to use
|
||||
reverse proxy for making containerized app and services available through the
|
||||
web. With the two provided files,
|
||||
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and
|
||||
[`docker-compose.override.yml`](docker-compose.override.yml), it is equally easy to deploy
|
||||
and use conduwuit, with a little caveat. If you already took a look at the files, then you should have
|
||||
seen the `well-known` service, and that is the little caveat. Traefik is simply a proxy and
|
||||
loadbalancer and is not able to serve any kind of content, but for conduwuit to federate, we need to
|
||||
either expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/client` and
|
||||
`.well-known/matrix/server`.
|
||||
|
||||
With the service `well-known` we use a single `nginx` container that will serve those two files.
|
||||
|
||||
So...step by step:
|
||||
|
||||
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
|
||||
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
|
||||
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
|
||||
3. Create the `conduwuit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure conduwuit per env vars.
|
||||
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
|
||||
5. Create the files needed by the `well-known` service.
|
||||
|
||||
- `./nginx/matrix.conf` (relative to the compose file, you can change this, but then also need to change the volume mapping)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name <SUBDOMAIN>.<DOMAIN>;
|
||||
listen 80 default_server;
|
||||
|
||||
location /.well-known/matrix/server {
|
||||
return 200 '{"m.server": "<SUBDOMAIN>.<DOMAIN>:443"}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
}
|
||||
|
||||
location /.well-known/matrix/client {
|
||||
return 200 '{"m.homeserver": {"base_url": "https://<SUBDOMAIN>.<DOMAIN>"}}';
|
||||
types { } default_type "application/json; charset=utf-8";
|
||||
add_header "Access-Control-Allow-Origin" *;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
6. Run `docker compose up -d`
|
||||
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
|
||||
|
||||
|
||||
[`docker-compose.override.yml`](docker-compose.override.yml), it is equally easy
|
||||
to deploy and use conduwuit, with a little caveat. If you already took a look at
|
||||
the files, then you should have seen the `well-known` service, and that is the
|
||||
little caveat. Traefik is simply a proxy and loadbalancer and is not able to
|
||||
serve any kind of content, but for conduwuit to federate, we need to either
|
||||
expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/client`
|
||||
and `.well-known/matrix/server`.
|
||||
|
||||
With the service `well-known` we use a single `nginx` container that will serve
|
||||
those two files.
|
||||
|
||||
## Voice communication
|
||||
|
||||
In order to make or receive calls, a TURN server is required. conduwuit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
|
||||
|
||||
### Configuration
|
||||
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```conf
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
|
||||
|
||||
These same values need to be set in conduwuit. You can either modify conduwuit.toml to include these lines:
|
||||
```
|
||||
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
|
||||
turn_secret = "<secret key from coturn configuration>"
|
||||
```
|
||||
or append the following to the docker environment variables dependig on which configuration method you used earlier:
|
||||
```yml
|
||||
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
|
||||
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
|
||||
```
|
||||
Restart Conduit to apply these changes.
|
||||
|
||||
### Run
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
```bash
|
||||
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
|
||||
and run `docker compose up -d` in the same directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
|
||||
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).
|
||||
See the [TURN](../turn.md) page.
|
||||
|
||||
11
docs/deploying/freebsd.md
Normal file
11
docs/deploying/freebsd.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# conduwuit for FreeBSD
|
||||
|
||||
conduwuit at the moment does not provide FreeBSD builds. Building conduwuit on
|
||||
FreeBSD requires a specific environment variable to use the system prebuilt
|
||||
RocksDB library instead of rust-rocksdb / rust-librocksdb-sys which does *not*
|
||||
work and will cause a build error or coredump.
|
||||
|
||||
Use the following environment variable: `ROCKSDB_LIB_DIR=/usr/local/lib`
|
||||
|
||||
Such example commandline with it can be: `ROCKSDB_LIB_DIR=/usr/local/lib cargo
|
||||
build --release`
|
||||
@@ -1,158 +1,141 @@
|
||||
# Generic deployment documentation
|
||||
|
||||
### Please note that this documentation is not fully representative of conduwuit at the moment. Assume majority of it is outdated.
|
||||
|
||||
> ## Getting help
|
||||
>
|
||||
> If you run into any problems while setting up conduwuit, ask us
|
||||
> in `#conduwuit:puppygock.gay` or [open an issue on GitHub](https://github.com/girlbossceo/conduwuit/issues/new).
|
||||
> If you run into any problems while setting up conduwuit, ask us in
|
||||
> `#conduwuit:puppygock.gay` or [open an issue on
|
||||
> GitHub](https://github.com/girlbossceo/conduwuit/issues/new).
|
||||
|
||||
## Installing conduwuit
|
||||
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see what you need.
|
||||
You may simply download the binary that fits your machine. Run `uname -m` to see
|
||||
what you need.
|
||||
|
||||
Prebuilt binaries can be downloaded from the latest tagged release [here](https://github.com/girlbossceo/conduwuit/releases/latest).
|
||||
Prebuilt fully static musl binaries can be downloaded from the latest tagged
|
||||
release [here](https://github.com/girlbossceo/conduwuit/releases/latest) or
|
||||
`main` CI branch workflow artifact output. These also include Debian packages.
|
||||
These binaries have jemalloc and io_uring statically linked and included with
|
||||
them.
|
||||
|
||||
Alternatively, you may compile the binary yourself. First, install any dependencies:
|
||||
Alternatively, you may compile the binary yourself. We recommend using
|
||||
[Lix](https://lix.systems) to build conduwuit as this has the most guaranteed
|
||||
reproducibiltiy and easiest to get a build environment and output going.
|
||||
|
||||
```bash
|
||||
# Debian
|
||||
$ sudo apt install libclang-dev build-essential
|
||||
|
||||
# RHEL
|
||||
$ sudo dnf install clang
|
||||
```
|
||||
Then, `cd` into the source tree of conduwuit and run:
|
||||
```bash
|
||||
$ cargo build --release
|
||||
```
|
||||
Otherwise, follow standard Rust project build guides (installing git and cloning
|
||||
the repo, getting the Rust toolchain via rustup, installing LLVM toolchain +
|
||||
libclang for RocksDB, installing liburing for io_uring and RocksDB, etc).
|
||||
|
||||
## Adding a conduwuit user
|
||||
|
||||
While conduwuit can run as any user it is usually better to use dedicated users for different services. This also allows
|
||||
you to make sure that the file permissions are correctly set up.
|
||||
While conduwuit can run as any user it is better to use dedicated users for
|
||||
different services. This also allows you to make sure that the file permissions
|
||||
are correctly set up.
|
||||
|
||||
In Debian or RHEL, you can use this command to create a conduwuit user:
|
||||
In Debian or Fedora/RHEL, you can use this command to create a conduwuit user:
|
||||
|
||||
```bash
|
||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||
sudo adduser --system conduwuit --group --disabled-login --no-create-home
|
||||
```
|
||||
|
||||
For distros without `adduser`:
|
||||
|
||||
```bash sudo useradd -r --shell /usr/bin/nologin --no-create-home conduwuit ```
|
||||
|
||||
## Forwarding ports in the firewall or the router
|
||||
|
||||
conduwuit uses the ports 443 and 8448 both of which need to be open in the firewall.
|
||||
conduwuit uses the ports 443 and 8448 both of which need to be open in the
|
||||
firewall.
|
||||
|
||||
If conduwuit runs behind a router or in a container and has a different public IP address than the host system these public ports need to be forwarded directly or indirectly to the port mentioned in the config.
|
||||
If conduwuit runs behind a router or in a container and has a different public
|
||||
IP address than the host system these public ports need to be forwarded directly
|
||||
or indirectly to the port mentioned in the config.
|
||||
|
||||
## Setting up a systemd service
|
||||
|
||||
Now we'll set up a systemd service for conduwuit, so it's easy to start/stop conduwuit and set it to autostart when your
|
||||
server reboots. Simply paste the default systemd service you can find below into
|
||||
`/etc/systemd/system/conduwuit.service`.
|
||||
|
||||
```systemd
|
||||
[Unit]
|
||||
Description=conduwuit Matrix Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Environment="CONDUWUIT_CONFIG=/etc/conduwuit/conduwuit.toml"
|
||||
User=conduwuit
|
||||
Group=conduwuit
|
||||
RuntimeDirectory=conduwuit
|
||||
RuntimeDirectoryMode=0750
|
||||
Restart=always
|
||||
ExecStart=/usr/local/bin/conduwuit
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
Finally, run
|
||||
|
||||
```bash
|
||||
$ sudo systemctl daemon-reload
|
||||
```
|
||||
The systemd unit for conduwuit can be found
|
||||
[here](../configuration/examples.md#example-systemd-unit-file). You may need to
|
||||
change the `ExecStart=` path to where you placed the conduwuit binary.
|
||||
|
||||
## Creating the conduwuit configuration file
|
||||
|
||||
Now we need to create the conduwuit's config file in `/etc/conduwuit/conduwuit.toml`. Paste this in **and take a moment
|
||||
to read it. You need to change at least the server name.**
|
||||
RocksDB (`rocksdb`) is the only supported database backend. SQLite only exists for historical reasons and is not recommended. Any performance issues, storage issues, database issues, etc will not be assisted if using SQLite and you will be asked to migrate to RocksDB first.
|
||||
Now we need to create the conduwuit's config file in
|
||||
`/etc/conduwuit/conduwuit.toml`. The example config can be found at
|
||||
[conduwuit-example.toml](../configuration/examples.md).**Please take a moment to
|
||||
read it. You need to change at least the server name.**
|
||||
|
||||
See the following example config at [conduwuit-example.toml](../configuration.md)
|
||||
RocksDB is the only supported database backend.
|
||||
|
||||
## Setting the correct file permissions
|
||||
|
||||
As we are using a conduwuit specific user we need to allow it to read the config. To do that you can run this command on
|
||||
Debian or RHEL:
|
||||
If you are using a dedicated user for conduwuit, you will need to allow it to
|
||||
read the config. To do that you can run this:
|
||||
|
||||
```bash
|
||||
sudo chown -R root:root /etc/conduwuit
|
||||
sudo chmod 755 /etc/conduwuit
|
||||
sudo chown -R root:root /etc/conduwuit sudo chmod -R 755 /etc/conduwuit
|
||||
```
|
||||
|
||||
If you use the default database path you also need to run this:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/conduwuit/
|
||||
sudo chown -R conduwuit:conduwuit /var/lib/conduwuit/
|
||||
sudo chmod 700 /var/lib/conduwuit/
|
||||
```bash
|
||||
sudo mkdir -p /var/lib/conduwuit/ sudo chown -R conduwuit:conduwuit
|
||||
/var/lib/conduwuit/ sudo chmod 700 /var/lib/conduwuit/
|
||||
```
|
||||
|
||||
## Setting up the Reverse Proxy
|
||||
|
||||
Refer to the documentation or various guides online of your chosen reverse proxy software. A Caddy example will be provided as this is the recommended reverse proxy for new users and is very trivial.
|
||||
Refer to the documentation or various guides online of your chosen reverse proxy
|
||||
software. A [Caddy](https://caddyserver.com/) example will be provided as this
|
||||
is the recommended reverse proxy for new users and is very trivial to use
|
||||
(handles TLS, reverse proxy headers, etc transparently with proper defaults).
|
||||
|
||||
Lighttpd is not supported as it seems to mess with the `X-Matrix` Authorization
|
||||
header, making federation non-functional. If using Apache, you need to use
|
||||
`nocanon` to prevent this.
|
||||
|
||||
### Caddy
|
||||
|
||||
Create `/etc/caddy/conf.d/conduwuit_caddyfile` and enter this (substitute for your server name).
|
||||
Create `/etc/caddy/conf.d/conduwuit_caddyfile` and enter this (substitute for
|
||||
your server name).
|
||||
|
||||
```caddy
|
||||
your.server.name, your.server.name:8448 {
|
||||
# TCP
|
||||
reverse_proxy 127.0.0.1:6167
|
||||
|
||||
# UNIX socket
|
||||
#reverse_proxy unix//run/conduwuit/conduwuit.sock
|
||||
your.server.name, your.server.name:8448 { # TCP reverse_proxy
|
||||
127.0.0.1:6167
|
||||
# UNIX socket
|
||||
#reverse_proxy unix//run/conduwuit/conduwuit.sock
|
||||
}
|
||||
```
|
||||
|
||||
That's it! Just start or enable the service and you're set.
|
||||
That's it! Just start and enable the service and you're set.
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable caddy
|
||||
```
|
||||
```bash sudo systemctl enable --now caddy ```
|
||||
|
||||
## You're done!
|
||||
## You're done
|
||||
|
||||
Now you can start conduwuit with:
|
||||
|
||||
```bash
|
||||
$ sudo systemctl start conduwuit
|
||||
```
|
||||
```bash sudo systemctl start conduwuit ```
|
||||
|
||||
Set it to start automatically when your system boots with:
|
||||
|
||||
```bash
|
||||
$ sudo systemctl enable conduwuit
|
||||
```
|
||||
```bash sudo systemctl enable conduwuit ```
|
||||
|
||||
## How do I know it works?
|
||||
|
||||
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register.
|
||||
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your
|
||||
homeserver and try to register.
|
||||
|
||||
You can also use these commands as a quick health check.
|
||||
You can also use these commands as a quick health check (replace
|
||||
`your.server.name`).
|
||||
|
||||
```bash
|
||||
$ curl https://your.server.name/_conduwuit/server_version
|
||||
```bash $ curl https://your.server.name/_conduwuit/server_version
|
||||
|
||||
# If using port 8448
|
||||
$ curl https://your.server.name:8448/_conduwuit/server_version
|
||||
```
|
||||
# If using port 8448 $ curl
|
||||
https://your.server.name:8448/_conduwuit/server_version ```
|
||||
|
||||
- To check if your server can talk with other homeservers, you can use the [Matrix Federation Tester](https://federationtester.matrix.org/).
|
||||
If you can register but cannot join federated rooms check your config again and also check if the port 8448 is open and forwarded correctly.
|
||||
- To check if your server can talk with other homeservers, you can use the
|
||||
[Matrix Federation Tester](https://federationtester.matrix.org/). If you can
|
||||
register but cannot join federated rooms check your config again and also check
|
||||
if the port 8448 is open and forwarded correctly.
|
||||
|
||||
# What's next?
|
||||
|
||||
@@ -162,4 +145,5 @@ ## Audio/Video calls
|
||||
|
||||
## Appservices
|
||||
|
||||
If you want to set up an appservice, take a look at the [Appservice Guide](../appservices.md).
|
||||
If you want to set up an appservice, take a look at the [Appservice
|
||||
Guide](../appservices.md).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# conduwuit for NixOS
|
||||
|
||||
conduwuit can be acquired by Nix from various places:
|
||||
conduwuit can be acquired by [Lix][lix] from various places:
|
||||
|
||||
* The `flake.nix` at the root of the repo
|
||||
* The `default.nix` at the root of the repo
|
||||
@@ -8,23 +8,31 @@ # conduwuit for NixOS
|
||||
|
||||
A binary cache for conduwuit that the CI/CD publishes to is available at the
|
||||
following places (both are the same just different names):
|
||||
```
|
||||
https://attic.kennel.juneis.dog/conduit
|
||||
conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
|
||||
``` https://attic.kennel.juneis.dog/conduit
|
||||
conduit:eEKoUwlQGDdYmAI/Q/0slVlegqh/QmAvQd7HBSm21Wk=
|
||||
|
||||
https://attic.kennel.juneis.dog/conduwuit
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw=
|
||||
```
|
||||
conduwuit:BbycGUgTISsltcmH0qNjFR9dbrQNYgdIAcmViSGoVTE= ```
|
||||
|
||||
If specifying a URL in your flake, please use the GitHub remote: `github:girlbossceo/conduwuit`
|
||||
The binary caches have been recreated recently due to attic issues. The old
|
||||
public keys were:
|
||||
|
||||
``` conduit:Isq8FGyEC6FOXH6nD+BOeAA+bKp6X6UIbupSlGEPuOg=
|
||||
|
||||
conduwuit:lYPVh7o1hLu1idH4Xt2QHaRa49WRGSAqzcfFd94aOTw= ```
|
||||
|
||||
If specifying a URL in your flake, please use the GitHub remote:
|
||||
`github:girlbossceo/conduwuit`
|
||||
|
||||
The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so
|
||||
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to
|
||||
configure conduwuit.
|
||||
|
||||
If you want to run the latest code, you should get Conduwuit from the `flake.nix`
|
||||
or `default.nix` and set [`services.matrix-conduit.package`][package]
|
||||
appropriately.
|
||||
If you want to run the latest code, you should get conduwuit from the
|
||||
`flake.nix` or `default.nix` and set
|
||||
[`services.matrix-conduit.package`][package] appropriately.
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
|
||||
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package
|
||||
|
||||
@@ -1,22 +1,53 @@
|
||||
# Development
|
||||
|
||||
Information about developing the project. If you are only interested in using
|
||||
it, you can safely ignore this section.
|
||||
it, you can safely ignore this section. If you plan on contributing, see the
|
||||
[contributor's guide](contributing.md).
|
||||
|
||||
## List of forked dependencies During conduwuit development, we have had to fork
|
||||
some dependencies to support our use-cases in some areas. This ranges from
|
||||
things said upstream project won't accept for any reason, faster-paced
|
||||
development (unresponsive or slow upstream), conduwuit-specific usecases, or
|
||||
lack of time to upstream some things.
|
||||
|
||||
- [ruma/ruma][1]: <https://github.com/girlbossceo/ruwuma> - various performance
|
||||
improvements, more features, faster-paced development, client/server interop
|
||||
hacks upstream won't accept, etc
|
||||
- [facebook/rocksdb][2]: <https://github.com/girlbossceo/rocksdb> - liburing
|
||||
build fixes, GCC build fix, and logging callback C API for Rust tracing
|
||||
integration
|
||||
- [tikv/jemallocator][3]: <https://github.com/girlbossceo/jemallocator> - musl
|
||||
builds seem to be broken on upstream
|
||||
- [zyansheep/rustyline-async][4]:
|
||||
<https://github.com/girlbossceo/rustyline-async> - tab completion callback and
|
||||
`CTRL+\` signal quit event for CLI
|
||||
- [rust-rocksdb/rust-rocksdb][5]:
|
||||
<https://github.com/girlbossceo/rust-rocksdb-zaidoon1> - [`@zaidoon1`'s][8] fork
|
||||
has quicker updates, more up to date dependencies. Our changes fix musl build
|
||||
issues, Rust part of the logging callback C API, removes unnecessary `gtest`
|
||||
include, and uses our RocksDB and jemallocator
|
||||
- [tokio-rs/tracing][6]: <https://github.com/girlbossceo/tracing> - Implements
|
||||
`Clone` for `EnvFilter` to support dynamically changing tracing envfilter's
|
||||
alongside other logging/metrics things
|
||||
|
||||
## Debugging with `tokio-console`
|
||||
|
||||
[`tokio-console`][1] can be a useful tool for debugging and profiling. To make
|
||||
a `tokio-console`-enabled build of Conduwuit, enable the `tokio_console` feature,
|
||||
disable the default `release_max_log_level` feature, and set the
|
||||
`--cfg tokio_unstable` flag to enable experimental tokio APIs. A build might
|
||||
look like this:
|
||||
[`tokio-console`][7] can be a useful tool for debugging and profiling. To make a
|
||||
`tokio-console`-enabled build of conduwuit, enable the `tokio_console` feature,
|
||||
disable the default `release_max_log_level` feature, and set the `--cfg
|
||||
tokio_unstable` flag to enable experimental tokio APIs. A build might look like
|
||||
this:
|
||||
|
||||
```bash
|
||||
RUSTFLAGS="--cfg tokio_unstable" cargo build \
|
||||
--release \
|
||||
--no-default-features \
|
||||
--features
|
||||
backend_rocksdb,systemd,element_hacks,sentry_telemetry,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```bash RUSTFLAGS="--cfg tokio_unstable" cargo build \ --release \
|
||||
--no-default-features \
|
||||
--features=systemd,element_hacks,gzip_compression,brotli_compression,zstd_compression,tokio_console
|
||||
```
|
||||
|
||||
[1]: https://docs.rs/tokio-console/latest/tokio_console/
|
||||
[1]: https://github.com/ruma/ruma/
|
||||
[2]: https://github.com/facebook/rocksdb/
|
||||
[3]: https://github.com/tikv/jemallocator/
|
||||
[4]: https://github.com/zyansheep/rustyline-async/
|
||||
[5]: https://github.com/rust-rocksdb/rust-rocksdb/
|
||||
[6]: https://github.com/tokio-rs/tracing/
|
||||
[7]: https://docs.rs/tokio-console/latest/tokio_console/
|
||||
[8]: https://github.com/zaidoon1/
|
||||
|
||||
BIN
docs/development/assets/libraries.png
Normal file
BIN
docs/development/assets/libraries.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
docs/development/assets/reload_order.png
Normal file
BIN
docs/development/assets/reload_order.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
197
docs/development/hot_reload.md
Normal file
197
docs/development/hot_reload.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Hot Reloading ("Live" Development)
|
||||
|
||||
### Summary
|
||||
|
||||
When developing in debug-builds with the nightly toolchain, conduwuit is modular
|
||||
using dynamic libraries and various parts of the application are hot-reloadable
|
||||
while the server is running: http api handlers, admin commands, services,
|
||||
database, etc. These are all split up into individual workspace crates as seen
|
||||
in the `src/` directory. Changes to sourcecode in a crate rebuild that crate and
|
||||
subsequent crates depending on it. Reloading then occurs for the changed crates.
|
||||
|
||||
Release builds still produce static binaries which are unaffected. Rust's
|
||||
soundness guarantees are in full force. Thus you cannot hot-reload release
|
||||
binaries.
|
||||
|
||||
### Requirements
|
||||
|
||||
Currently, this development setup only works on x86_64 and aarch64 Linux glibc.
|
||||
[musl explicitly does not support hot reloadable libraries, and does not
|
||||
implement `dlclose`][2]. macOS does not fully support our usage of `RTLD_GLOBAL`
|
||||
possibly due to some thread-local issues. [This Rust issue][3] may be of
|
||||
relevance, specifically [this comment][4]. It may be possible to get it working
|
||||
on only very modern macOS versions such as at least Sonoma, as currently loading
|
||||
dylibs is supported, but not unloading them in our setup, and the cited comment
|
||||
mentions an Apple WWDC confirming there have been TLS changes to somewhat make
|
||||
this possible.
|
||||
|
||||
As mentioned above this requires the nightly toolchain. This is due to reliance
|
||||
on various Cargo.toml features that are only available on nightly, most
|
||||
specifically `RUSTFLAGS` in Cargo.toml. Some of the implementation could also be
|
||||
simpler based on other various nightly features. We hope lots of nightly
|
||||
features start making it out of nightly sooner as there have been dozens of very
|
||||
helpful features that have been stuck in nightly ("unstable") for at least 5+
|
||||
years that would make this simpler. We encourage greater community consensus to
|
||||
move these features into stability.
|
||||
|
||||
This currently only works on x86_64/aarch64 Linux with a glibc C library. musl C
|
||||
library, macOS, and likely other host architectures are not supported (if other
|
||||
architectures work, feel free to let us know and/or make a PR updating this).
|
||||
This should work on GNU ld and lld (rust-lld) and gcc/clang, however if you
|
||||
happen to have linker issues it's recommended to try using `mold` or `gold`
|
||||
linkers, and please let us know in the [conduwuit Matrix room][7] the linker
|
||||
error and what linker solved this issue so we can figure out a solution. Ideally
|
||||
there should be minimal friction to using this, and in the future a build script
|
||||
(`build.rs`) may be suitable to making this easier to use if the capabilities
|
||||
allow us.
|
||||
|
||||
### Usage
|
||||
|
||||
As of 19 May 2024, the instructions for using this are:
|
||||
|
||||
0. Have patience. Don't hesitate to join the [conduwuit Matrix room][7] to
|
||||
receive help using this. As indicated by the various rustflags used and some
|
||||
of the interesting issues linked at the bottom, this is definitely not something
|
||||
the Rust ecosystem or toolchain is used to doing.
|
||||
|
||||
1. Install the nightly toolchain using rustup. You may need to use `rustup
|
||||
override set nightly` in your local conduwuit directory, or use `cargo
|
||||
+nightly` for all actions.
|
||||
|
||||
2. Uncomment `cargo-features` at the top level / root Cargo.toml
|
||||
|
||||
3. Scroll down to the `# Developer profile` section and uncomment ALL the
|
||||
rustflags for each dev profile and their respective packages.
|
||||
|
||||
4. In each workspace crate's Cargo.toml (everything under `src/*` AND
|
||||
`deps/rust-rocksdb/Cargo.toml`), uncomment the `dylib` crate type under
|
||||
`[lib]`.
|
||||
|
||||
5. Due to [this rpath issue][5], you must export the `LD_LIBRARY_PATH`
|
||||
environment variable to your nightly Rust toolchain library directory. If
|
||||
using rustup (hopefully), use this: `export
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$HOME/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/`
|
||||
|
||||
6. Start the server. You can use `cargo +nightly run` for this along with the
|
||||
standard.
|
||||
|
||||
7. Make some changes where you need to.
|
||||
|
||||
8. In a separate terminal window in the same directory (or using a terminal
|
||||
multiplexer like tmux), run the *build* Cargo command `cargo +nightly build`.
|
||||
Cargo should only rebuild what was changed / what's necessary, so it should
|
||||
not be rebuilding all the crates.
|
||||
|
||||
9. In your conduwuit server terminal, hit/send `CTRL+C` signal. This will tell
|
||||
conduwuit to find which libraries need to be reloaded, and reloads them as
|
||||
necessary.
|
||||
|
||||
10. If there were no errors, it will tell you it successfully reloaded `#`
|
||||
modules, and your changes should now be visible. Repeat 7 - 9 as needed.
|
||||
|
||||
To shutdown conduwuit in this setup, hit/send `CTRL+\`. Normal builds still
|
||||
shutdown with `CTRL+C` as usual.
|
||||
|
||||
Steps 1 - 5 are the initial first-time steps for using this. To remove the hot
|
||||
reload setup, revert/comment all the Cargo.toml changes.
|
||||
|
||||
As mentioned in the requirements section, if you happen to have some linker
|
||||
issues, try using the `-fuse-ld=` rustflag and specify mold or gold in all the
|
||||
`rustflags` definitions in the top level Cargo.toml, and please let us know in
|
||||
the [conduwuit Matrix room][7] the problem. mold can be installed typically
|
||||
through your distro, and gold is provided by the binutils package.
|
||||
|
||||
It's possible a helper script can be made to do all of this, or most preferably
|
||||
a specially made build script (build.rs). `cargo watch` support will be
|
||||
implemented soon which will eliminate the need to manually run `cargo build` all
|
||||
together.
|
||||
|
||||
### Addendum
|
||||
|
||||
Conduit was inherited as a single crate without modularity or reloading in its
|
||||
design. Reasonable partitioning and abstraction allowed a split into several
|
||||
crates, though many circular dependencies had to be corrected. The resulting
|
||||
crates now form a directed graph as depicted in figures below. The interfacing
|
||||
between these crates is still extremely broad which is not mitigable.
|
||||
|
||||
Initially [hot_lib_reload][6] was investigated but found appropriate for a
|
||||
project designed with modularity through limited interfaces, not a large and
|
||||
complex existing codebase. Instead a bespoke solution built directly on
|
||||
[libloading][8] satisfied our constraints. This required relatively minimal
|
||||
modifications and zero maintenance burden compared to what would be required
|
||||
otherwise. The technical difference lies with relocation processing: we leverage
|
||||
global bindings (`RTLD_GLOBAL`) in a very intentional way. Most libraries and
|
||||
off-the-shelf module systems (such as [hot_lib_reload][6]) restrict themselves
|
||||
to local bindings (`RTLD_LOCAL`). This allows them to release software to
|
||||
multiple platforms with much greater consistency, but at the cost of burdening
|
||||
applications to explicitly manage these bindings. In our case with an optional
|
||||
feature for developers, we shrug any such requirement to enjoy the cost/benefit
|
||||
on platforms where global relocations are properly cooperative.
|
||||
|
||||
To make use of `RTLD_GLOBAL` the application has to be oriented as a directed
|
||||
acyclic graph. The primary rule is simple and illustrated in the figure below:
|
||||
**no crate is allowed to call a function or use a variable from a crate below
|
||||
it.**
|
||||
|
||||

|
||||
|
||||
When a symbol is referenced between crates they become bound: **crates cannot be
|
||||
unloaded until their calling crates are first unloaded.** Thus we start the
|
||||
reloading process from the crate which has no callers. There is a small problem
|
||||
though: the first crate is called by the base executable itself! This is solved
|
||||
by using an `RTLD_LOCAL` binding for just one link between the main executable
|
||||
and the first crate, freeing the executable from all modules as no global
|
||||
binding ever occurs between them.
|
||||
|
||||

|
||||
|
||||
Proper resource management is essential for reliable reloading to occur. This is
|
||||
a very basic ask in RAII-idiomatic Rust and the exposure to reloading hazards is
|
||||
remarkably low, generally stemming from poor patterns and practices.
|
||||
Unfortunately static analysis doesn't enforce reload-safety programmatically
|
||||
(though it could one day), for now hazards can be avoided by knowing a few basic
|
||||
do's and dont's:
|
||||
|
||||
1. Understand that code is memory. Just like one is forbidden from referencing
|
||||
free'd memory, one must not transfer control to free'd code. Exposure to this
|
||||
is primarily from two things:
|
||||
|
||||
- Callbacks, which this project makes very little use of.
|
||||
- Async tasks, which are addressed below.
|
||||
|
||||
2. Tie all resources to a scope or object lifetime with greatest possible
|
||||
symmetry (locality). For our purposes this applies to code resources, which
|
||||
means async blocks and tokio tasks.
|
||||
|
||||
- **Never spawn a task without receiving and storing its JoinHandle**.
|
||||
- **Always wait on join handles** before leaving a scope or in another cleanup
|
||||
function called by an owning scope.
|
||||
|
||||
3. Know any minor specific quirks documented in code or here:
|
||||
|
||||
- Don't use `tokio::spawn`, instead use our `Handle` in `core/server.rs`, which
|
||||
is reachable in most of the codebase via `services()` or other state. This is
|
||||
due to some bugs or assumptions made in tokio, as it happens in `unsafe {}`
|
||||
blocks, which are mitigated by circumventing some thread-local variables. Using
|
||||
runtime handles is good practice in any case.
|
||||
|
||||
The initial implementation PR is available [here][1].
|
||||
|
||||
### Interesting related issues/bugs
|
||||
|
||||
- [DT_RUNPATH produced in binary with rpath = true is wrong (cargo)][5]
|
||||
- [Disabling MIR Optimization in Rust Compilation
|
||||
(cargo)](https://internals.rust-lang.org/t/disabling-mir-optimization-in-rust-compilation/19066/5)
|
||||
- [Workspace-level metadata
|
||||
(cargo-deb)](https://github.com/kornelski/cargo-deb/issues/68)
|
||||
|
||||
[1]: https://github.com/girlbossceo/conduwuit/pull/387
|
||||
[2]: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Unloading-libraries
|
||||
[3]: https://github.com/rust-lang/rust/issues/28794
|
||||
[4]: https://github.com/rust-lang/rust/issues/28794#issuecomment-368693049
|
||||
[5]: https://github.com/rust-lang/cargo/issues/12746
|
||||
[6]: https://crates.io/crates/hot-lib-reloader/
|
||||
[7]: https://matrix.to/#/#conduwuit:puppygock.gay
|
||||
[8]: https://crates.io/crates/libloading
|
||||
@@ -5,13 +5,19 @@ ## Complement
|
||||
Have a look at [Complement's repository][complement] for an explanation of what
|
||||
it is.
|
||||
|
||||
To test against Complement, with Nix and direnv installed and set up, you can
|
||||
either:
|
||||
To test against Complement, with [Lix][lix] and direnv installed and set up, you
|
||||
can:
|
||||
|
||||
* Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl ./path/to/results.jsonl`
|
||||
to build a Complement image, run the tests, and output the logs and results
|
||||
to the specified paths
|
||||
* Run `./bin/complement "$COMPLEMENT_SRC" ./path/to/logs.jsonl
|
||||
./path/to/results.jsonl` to build a Complement image, run the tests, and output
|
||||
the logs and results to the specified paths. This will also output the OCI image
|
||||
at `result`
|
||||
* Run `nix build .#complement` from the root of the repository to just build a
|
||||
Complement image
|
||||
Complement OCI image outputted to `result` (it's a `.tar.gz` file)
|
||||
* Or download the latest Complement OCI image from the CI workflow artifacts
|
||||
output from the commit/revision you want to test (e.g. from main)
|
||||
[here][ci-workflows]
|
||||
|
||||
[lix]: https://lix.systems/
|
||||
[ci-workflows]: https://github.com/girlbossceo/conduwuit/actions/workflows/ci.yml?query=event%3Apush+is%3Asuccess+actor%3Agirlbossceo
|
||||
[complement]: https://github.com/matrix-org/complement
|
||||
|
||||
@@ -1,152 +1,380 @@
|
||||
#### **Note: This list may not up to date. There are rapidly more and more improvements, fixes, changes, etc being made that it is becoming more difficult to maintain this list. I recommend that you give conduwuit a try and see the differences for yourself. If you have any concerns, feel free to join the conduwuit Matrix room and ask any pre-usage questions.**
|
||||
#### **Note: This list may not up to date. There are rapidly more and more
|
||||
improvements, fixes, changes, etc being made that it is becoming more difficult
|
||||
to maintain this list. I recommend that you give conduwuit a try and see the
|
||||
differences for yourself. If you have any concerns, feel free to join the
|
||||
conduwuit Matrix room and ask any pre-usage questions.**
|
||||
|
||||
### list of features, bug fixes, etc that conduwuit does that Conduit does not:
|
||||
### list of features, bug fixes, etc that conduwuit does that Conduit does not
|
||||
|
||||
Outgoing typing indicators, outgoing read receipts, **and** outgoing presence!
|
||||
|
||||
## Performance:
|
||||
- Concurrency support for key fetching for faster remote room joins and room joins that will error less frequently
|
||||
- Send `Cache-Control` response header with `immutable` and 1 year cache length for all media requests to instruct clients to cache media, and reduce server load from media requests that could be otherwise cached
|
||||
- Add feature flags and config options to enable/build with zstd, brotli, and/or gzip HTTP body compression (response and request)
|
||||
- Eliminate all usage of the thread-blocking `getaddrinfo(3)` call upon DNS queries, significantly improving federation latency/ping and cache DNS results (NXDOMAINs, successful queries, etc) using hickory-dns / hickory-resolver
|
||||
- Vastly improve RocksDB default settings to use new features that help with performance significantly, uses settings tailored to SSDs, various ways to tweak RocksDB, and a conduwuit setting to tell RocksDB to use settings that are tailored to HDDs or slow spinning rust storage or buggy filesystems.
|
||||
- Add a Cargo build profile for aggressive build-time performance optimisations for release builds (1 codegen unit, no debug, fat LTO, etc, and optimise all crates with same)
|
||||
## Performance
|
||||
|
||||
- Concurrency support for individual homeserver key fetching for faster remote
|
||||
room joins and room joins that will error less frequently
|
||||
- Send `Cache-Control` response header with `immutable` and 1 year cache length
|
||||
for all media requests (download and thumbnail) to instruct clients to cache
|
||||
media, and reduce server load from media requests that could be otherwise cached
|
||||
- Add feature flags and config options to enable/build with zstd, brotli, and/or
|
||||
gzip HTTP body compression (response and request)
|
||||
- Eliminate all usage of the thread-blocking `getaddrinfo(3)` call upon DNS
|
||||
queries, significantly improving federation latency/ping and cache DNS results
|
||||
(NXDOMAINs, successful queries, etc) using hickory-dns / hickory-resolver
|
||||
- Enable HTTP/2 support on all requests
|
||||
- Vastly improve RocksDB default settings to use new features that help with
|
||||
performance significantly, uses settings tailored to SSDs, various ways to tweak
|
||||
RocksDB, and a conduwuit setting to tell RocksDB to use settings that are
|
||||
tailored to HDDs or slow spinning rust storage or buggy filesystems.
|
||||
- Implement database flush and cleanup conduwuit operations when using RocksDB
|
||||
- Implement RocksDB write buffer corking and coalescing in database write-heavy areas
|
||||
- Perform connection pooling and keepalives where necessary to significantly improve federation performance and latency
|
||||
- Various config options to tweak connection pooling, request timeouts, connection timeouts, DNS timeouts and settings, etc with good defaults which also help huge with performance via reusing connections and retrying where needed
|
||||
- Implement building conduwuit with jemalloc (which extends to the RocksDB jemalloc feature for maximum gains) or hardened_malloc light variant, and produce CI builds with jemalloc for performance (Nix doesn't seem to build [hardened_malloc-rs](https://github.com/girlbossceo/hardened_malloc-rs) properly)
|
||||
- Add support for caching DNS results with hickory-dns / hickory-resolver in conduwuit (not a replacement for a proper resolver cache, but still far better than nothing)
|
||||
- Add config option for using DNS over TCP, and config option for controlling A/AAAA record lookup strategy (e.g. don't query AAAA records if you don't have IPv6 connectivity)
|
||||
- Overall significant database, Client-Server, and federation performance and latency improvements (check out the ping room leaderboards if you don't believe me :>)
|
||||
- Add config options for RocksDB compression and bottommost compression, including choosing the algorithm and compression level
|
||||
- Use [loole](https://github.com/mahdi-shojaee/loole) MPSC channels instead of tokio MPSC channels for huge performance boosts in sending channels (mainly relevant for federation) and presence channels
|
||||
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance, build speeds, binary size, and CPU usage in release builds by avoid compiling debug/trace log level macros that users will generally never use (can be disabled with a build-time feature flag)
|
||||
- Implement RocksDB write buffer corking and coalescing in database write-heavy
|
||||
areas
|
||||
- Perform connection pooling and keepalives where necessary to significantly
|
||||
improve federation performance and latency
|
||||
- Various config options to tweak connection pooling, request timeouts,
|
||||
connection timeouts, DNS timeouts and settings, etc with good defaults which
|
||||
also help huge with performance via reusing connections and retrying where
|
||||
needed
|
||||
- Properly get and use the amount of parallelism / tokio workers
|
||||
- Implement building conduwuit with jemalloc (which extends to the RocksDB
|
||||
jemalloc feature for maximum gains) or hardened_malloc light variant, and
|
||||
io_uring support, and produce CI builds with jemalloc and io_uring by default
|
||||
for performance (Nix doesn't seem to build
|
||||
[hardened_malloc-rs](https://github.com/girlbossceo/hardened_malloc-rs)
|
||||
properly)
|
||||
- Add support for caching DNS results with hickory-dns / hickory-resolver in
|
||||
conduwuit (not a replacement for a proper resolver cache, but still far better
|
||||
than nothing), also properly falls back on TCP for UDP errors or if a SRV
|
||||
response is too large
|
||||
- Add config option for using DNS over TCP, and config option for controlling
|
||||
A/AAAA record lookup strategy (e.g. don't query AAAA records if you don't have
|
||||
IPv6 connectivity)
|
||||
- Overall significant database, Client-Server, and federation performance and
|
||||
latency improvements (check out the ping room leaderboards if you don't believe
|
||||
me :>)
|
||||
- Add config options for RocksDB compression and bottommost compression,
|
||||
including choosing the algorithm and compression level
|
||||
- Use [loole](https://github.com/mahdi-shojaee/loole) MPSC channels instead of
|
||||
tokio MPSC channels for huge performance boosts in sending channels (mainly
|
||||
relevant for federation) and presence channels
|
||||
- Use `tracing`/`log`'s `release_max_level_info` feature to improve performance,
|
||||
build speeds, binary size, and CPU usage in release builds by avoid compiling
|
||||
debug/trace log level macros that users will generally never use (can be
|
||||
disabled with a build-time feature flag)
|
||||
- Remove some unnecessary checks on EDU handling for incoming transactions,
|
||||
effectively speeding them up
|
||||
- Simplify, dedupe, etc huge chunks of the codebase, including some that were
|
||||
unnecessary overhead, binary bloats, or preventing compiler/linker optimisations
|
||||
- Implement zero-copy RocksDB database accessors, substantially improving
|
||||
performance caused by unnecessary memory allocations
|
||||
|
||||
## General Fixes/Features
|
||||
|
||||
## General Fixes:
|
||||
- Raise and improve all the various request timeouts making some things like room joins and client bugs error less or none at all than they should, and make them all user configurable
|
||||
- Add legacy Element client hack fixing password changes and deactivations on
|
||||
legacy Element Android/iOS due to usage of an unspecced `user` field for UIAA
|
||||
- Raise and improve all the various request timeouts making some things like
|
||||
room joins and client bugs error less or none at all than they should, and make
|
||||
them all user configurable
|
||||
- Add missing `reason` field to user ban events (`/ban`)
|
||||
- Fixed spec compliance issue with room version 8 - 11 joins (https://github.com/matrix-org/synapse/issues/16717 / https://github.com/matrix-org/matrix-spec/issues/1708)
|
||||
- Safer and cleaner shutdowns on both database side as we run cleanup on shutdown and exits database loop better (no potential hanging issues in database loop), overall cleaner shutdown logic
|
||||
- Stop sending `make_join` requests on room joins if 15 servers respond with `M_UNSUPPORTED_ROOM_VERSION` or `M_INVALID_ROOM_VERSION`
|
||||
- Stop sending `make_join` requests if 50 servers cannot provide `make_join` for us
|
||||
- Respect *most* client parameters for `/media/` requests (`allow_redirect` still needs work)
|
||||
- Increased graceful shutdown timeout from a low 60 seconds to 180 seconds to avoid killing connections and let the remaining ones finish processing
|
||||
- Return joined member count of rooms for push rules/conditions instead of a hardcoded value of 10
|
||||
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
|
||||
- Allow HEAD HTTP requests in CORS for clients (despite not being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need to behave the same as GET requests, Synapse supports HEAD requests)
|
||||
- Resolve and remove some "features" from upstream that result in concurrency hazards, exponential backoff issues, or arbitrary performance limiters
|
||||
- Find more servers for outbound federation `/hierarchy` requests instead of just the room ID server name
|
||||
- Support for suggesting servers to join through at `/_matrix/client/v3/directory/room/{roomAlias}`
|
||||
- Support for suggesting servers to join through us at `/_matrix/federation/v1/query/directory`
|
||||
- Add workaround for [Out Of Your Element](https://gitdab.com/cadence/out-of-your-element) appservice bridge to make it functional on conduwuit (bug has already been reported)
|
||||
- Safer and cleaner shutdowns across incoming/outgoing requests (graceful
|
||||
shutdown) and the database
|
||||
- Stop sending `make_join` requests on room joins if 15 servers respond with
|
||||
`M_UNSUPPORTED_ROOM_VERSION` or `M_INVALID_ROOM_VERSION`
|
||||
- Stop sending `make_join` requests if 50 servers cannot provide `make_join` for
|
||||
us
|
||||
- Respect *most* client parameters for `/media/` requests (`allow_redirect`
|
||||
still needs work)
|
||||
- Return joined member count of rooms for push rules/conditions instead of a
|
||||
hardcoded value of 10
|
||||
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure
|
||||
only by environment variables and no longer need to set `CONDUIT_CONFIG` to an
|
||||
empty string.
|
||||
- Allow HEAD and PATCH (MSC4138) HTTP requests in CORS for clients (despite not
|
||||
being explicity mentioned in Matrix spec, HTTP spec says all HEAD requests need
|
||||
to behave the same as GET requests, Synapse supports HEAD requests)
|
||||
- Fix using conduwuit with flake-compat on NixOS
|
||||
- Resolve and remove some "features" from upstream that result in concurrency
|
||||
hazards, exponential backoff issues, or arbitrary performance limiters
|
||||
- Find more servers for outbound federation `/hierarchy` requests instead of
|
||||
just the room ID server name
|
||||
- Support for suggesting servers to join through at
|
||||
`/_matrix/client/v3/directory/room/{roomAlias}`
|
||||
- Support for suggesting servers to join through us at
|
||||
`/_matrix/federation/v1/query/directory`
|
||||
- Misc edge-case search fixes (e.g. potentially missing some events)
|
||||
- Misc `/sync` fixes (e.g. returning unnecessary data or incorrect/invalid
|
||||
responses)
|
||||
- Add `replaces_state` and `prev_sender` in `unsigned` for state event changes
|
||||
which primarily makes Element's "See history" button on a state event functional
|
||||
- Fix Conduit not allowing incoming federation requests for various world
|
||||
readable rooms
|
||||
- Fix Conduit not respecting the client-requested file name on media requests
|
||||
- Prevent sending junk / non-membership events to `/send_join` and `/send_leave`
|
||||
endpoints
|
||||
- Only allow the requested membership type on `/send_join` and `/send_leave`
|
||||
endpoints (e.g. don't allow leave memberships on join endpoints)
|
||||
- Prevent state key impersonation on `/send_join` and `/send_leave` endpoints
|
||||
- Validate `X-Matrix` origin and request body `"origin"` field on incoming
|
||||
transactions
|
||||
- Add `GET /_matrix/client/v1/register/m.login.registration_token/validity`
|
||||
endpoint
|
||||
- Explicitly define support for sliding sync at `/_matrix/client/versions`
|
||||
(`org.matrix.msc3575`)
|
||||
- Fix seeing empty status messages on user presences
|
||||
|
||||
## Moderation
|
||||
|
||||
## Moderation:
|
||||
- (Also see [Admin Room](#admin-room) for all the admin commands pertaining to moderation, there's a lot!)
|
||||
- (Also see [Admin Room](#admin-room) for all the admin commands pertaining to
|
||||
moderation, there's a lot!)
|
||||
- Add support for room banning/blocking by ID using admin command
|
||||
- Add support for serving `support` well-known from `[well_known.support]` (MSC1929)
|
||||
- Config option to forbid publishing rooms to the room directory (`lockdown_public_room_directory`) except for admins
|
||||
- Admin commands to delete room aliases and unpublish rooms from our room directory
|
||||
- For all [`/report`](https://spec.matrix.org/v1.9/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
|
||||
- Add support for serving `support` well-known from `[global.well_known]`
|
||||
(MSC1929) (`/.well-known/matrix/support`)
|
||||
- Config option to forbid publishing rooms to the room directory
|
||||
(`lockdown_public_room_directory`) except for admins
|
||||
- Admin commands to delete room aliases and unpublish rooms from our room
|
||||
directory
|
||||
- For all
|
||||
[`/report`](https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3roomsroomidreporteventid)
|
||||
requests: check if the reported event ID belongs to the reported room ID, raise
|
||||
report reasoning character limit to 750, fix broken formatting, make a small
|
||||
delayed random response per spec suggestion on privacy, and check if the sender
|
||||
user is in the reported room.
|
||||
- Support blocking servers from downloading remote media from, returning a 404
|
||||
- Don't allow `m.call.invite` events to be sent in public rooms (prevents calling the entire room)
|
||||
- On new public room creations, only allow moderators to send `m.call.invite`, `org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events
|
||||
- Add support for a "global ACLs" feature (`forbidden_remote_server_names`) that blocks inbound remote room invites, room joins by room ID on server name, room joins by room alias on server name, incoming federated joins, and incoming federated room directory requests. This is very helpful for blocking servers that are purely toxic/bad and serve no value in allowing our users to suffer from things like room invite spam or such. Please note that this is not a substitute for room ACLs.
|
||||
- Add support for a config option to forbid our local users from sending federated room directory requests for (`forbidden_remote_room_directory_server_names`). Similar to above, useful for blocking servers that help prevent our users from wandering into bad areas of Matrix via room directories of those malicious servers.
|
||||
- Don't allow `m.call.invite` events to be sent in public rooms (prevents
|
||||
calling the entire room)
|
||||
- On new public room creations, only allow moderators to send `m.call.invite`,
|
||||
`org.matrix.msc3401.call`, and `org.matrix.msc3401.call.member` events to
|
||||
prevent unprivileged users from calling the entire room
|
||||
- Add support for a "global ACLs" feature (`forbidden_remote_server_names`) that
|
||||
blocks inbound remote room invites, room joins by room ID on server name, room
|
||||
joins by room alias on server name, incoming federated joins, and incoming
|
||||
federated room directory requests. This is very helpful for blocking servers
|
||||
that are purely toxic/bad and serve no value in allowing our users to suffer
|
||||
from things like room invite spam or such. Please note that this is not a
|
||||
substitute for room ACLs.
|
||||
- Add support for a config option to forbid our local users from sending
|
||||
federated room directory requests for
|
||||
(`forbidden_remote_room_directory_server_names`). Similar to above, useful for
|
||||
blocking servers that help prevent our users from wandering into bad areas of
|
||||
Matrix via room directories of those malicious servers.
|
||||
- Add config option for auto remediating/deactivating local non-admin users who
|
||||
attempt to join bad/forbidden rooms (`auto_deactivate_banned_room_attempts`)
|
||||
- Deactivating users will remove their profile picture, blurhash, display name,
|
||||
and leave all rooms by default just like Synapse and for additional privacy
|
||||
- Reject some EDUs from ACL'd users such as read receipts and typing indicators
|
||||
|
||||
## Privacy/Security
|
||||
|
||||
## Privacy/Security:
|
||||
- Add config option for device name federation with a privacy-friendly default (disabled)
|
||||
- Add config option for requiring authentication to the `/publicRooms` endpoint (room directory) with a default enabled for privacy
|
||||
- Add config option for federating `/publicRooms` endpoint (room directory) to other servers with a default disabled for privacy
|
||||
- Uses proper `argon2` crate by RustCrypto instead of questionable `rust-argon2` crate
|
||||
- Add config option for device name federation with a privacy-friendly default
|
||||
(disabled)
|
||||
- Add config option for requiring authentication to the `/publicRooms` endpoint
|
||||
(room directory) with a default enabled for privacy
|
||||
- Add config option for federating `/publicRooms` endpoint (room directory) to
|
||||
other servers with a default disabled for privacy
|
||||
- Uses proper `argon2` crate by RustCrypto instead of questionable `rust-argon2`
|
||||
crate
|
||||
- Generate passwords with 25 characters instead of 15
|
||||
- Config option `ip_range_denylist` to support refusing to send requests (typically federation) to specific IP ranges, typically RFC 1918, non-routable, testnet, etc addresses like Synapse for security (note: this is not a guaranteed protection, and you should be using a firewall with zones if you want guaranteed protection as doing this on the application level is prone to bypasses).
|
||||
- Config option to block non-admin users from sending room invites or receiving remote room invites. Admin users are still allowed.
|
||||
- Config option `ip_range_denylist` to support refusing to send requests
|
||||
(typically federation) to specific IP ranges, typically RFC 1918, non-routable,
|
||||
testnet, etc addresses like Synapse for security (note: this is not a guaranteed
|
||||
protection, and you should be using a firewall with zones if you want guaranteed
|
||||
protection as doing this on the application level is prone to bypasses).
|
||||
- Config option to block non-admin users from sending room invites or receiving
|
||||
remote room invites. Admin users are still allowed.
|
||||
- Config option to disable incoming and/or outgoing remote read receipts
|
||||
- Config option to disable incoming and/or outgoing remote typing indicators
|
||||
- Config option to disable incoming, outgoing, and/or local presence
|
||||
- Config option to disable incoming, outgoing, and/or local presence and for
|
||||
timing out remote users
|
||||
- Sanitise file names for the `Content-Disposition` header for all media
|
||||
requests (thumbnails, downloads, uploads)
|
||||
- Media repository on handling `Content-Disposition` and `Content-Type` is fully
|
||||
spec compliant and secured
|
||||
- Send secure default HTTP headers such as a strong restrictive CSP (see
|
||||
MSC4149), deny iframes, disable `X-XSS-Protection`, disable interest cohort in
|
||||
`Permission-Policy`, etc to mitigate any potential attack surface such as from
|
||||
untrusted media
|
||||
|
||||
## Administration/Logging
|
||||
|
||||
## Administration/Logging:
|
||||
- Commandline argument to specify the path to a config file instead of relying on `CONDUIT_CONFIG`
|
||||
- Commandline argument to specify the path to a config file instead of relying
|
||||
on `CONDUIT_CONFIG`
|
||||
- Revamped admin room infrastructure and commands
|
||||
- Substantially clean up, improve, and fix logging (less noisy dead server logging, registration attempts, more useful troubleshooting logging, proper error propagation, etc)
|
||||
- Configurable RocksDB logging (`LOG` files) with proper defaults (rotate, max size, verbosity, etc) to stop LOG files from accumulating so much
|
||||
- Explicit startup error if your configuration allows open registration without a token or such like Synapse with a way to bypass it if needed
|
||||
- Replace the lightning bolt emoji option with support for setting any arbitrary text (e.g. another emoji) to suffix to all new user registrations, with a conduwuit default of 🏳️⚧️
|
||||
- Substantially clean up, improve, and fix logging (less noisy dead server
|
||||
logging, registration attempts, more useful troubleshooting logging, proper
|
||||
error propagation, etc)
|
||||
- Configurable RocksDB logging (`LOG` files) with proper defaults (rotate, max
|
||||
size, verbosity, etc) to stop LOG files from accumulating so much
|
||||
- Explicit startup error if your configuration allows open registration without
|
||||
a token or such like Synapse with a way to bypass it if needed
|
||||
- Replace the lightning bolt emoji option with support for setting any arbitrary
|
||||
text (e.g. another emoji) to suffix to all new user registrations, with a
|
||||
conduwuit default of "🏳️⚧️"
|
||||
- Implement config option to auto join rooms upon registration
|
||||
- Warn on unknown config options specified
|
||||
- Add `/_conduwuit/server_version` route to return the version of conduwuit without relying on the federation API `/_matrix/federation/v1/version`
|
||||
- Add configurable RocksDB recovery modes to aid in recovering corrupted RocksDB databases
|
||||
- Support config options via `CONDUWUIT_` prefix
|
||||
- Add support for listening on multiple TCP ports
|
||||
- Disable update check by default as it's not useful for conduwuit
|
||||
- Add `/_conduwuit/server_version` route to return the version of conduwuit
|
||||
without relying on the federation API `/_matrix/federation/v1/version`
|
||||
- Add `/_conduwuit/local_user_count` route to return the amount of registered
|
||||
active local users on your homeserver *if federation is enabled*
|
||||
- Add configurable RocksDB recovery modes to aid in recovering corrupted RocksDB
|
||||
databases
|
||||
- Support config options via `CONDUWUIT_` prefix and accessing non-global struct
|
||||
config options with the `__` split (e.g. `CONDUWUIT_WELL_KNOWN__SERVER`)
|
||||
- Add support for listening on multiple TCP ports and multiple addresses
|
||||
- **Opt-in** Sentry.io telemetry and metrics, mainly used for crash reporting
|
||||
- Log the client IP on various requests such as registrations, banned room join
|
||||
attempts, logins, deactivations, federation transactions, etc
|
||||
- Fix Conduit dropping some remote server federation response errors
|
||||
|
||||
## Maintenance/Stability
|
||||
|
||||
## Maintenance/Stability:
|
||||
- GitLab CI ported to GitHub Actions
|
||||
- Repo is mirrored to GitHub, GitLab, git.gay, sourcehut, and Codeberg (see README.md for their links)
|
||||
- Extensively revamp the example config to be extremely helpful and useful to both new users and power users
|
||||
- Fixed every single clippy (default lints) and rustc warnings, including some that were performance related or potential safety issues / unsoundness
|
||||
- Add support for the Matrix spec compliance test suite
|
||||
[Complement](https://github.com/matrix-org/complement/) via the Nix flake and
|
||||
various other fixes for it
|
||||
- Implement running and diff'ing Complement results in CI and error if any
|
||||
mismatch occurs to prevent large cases of conduwuit regressions
|
||||
- Repo is (officially) mirrored to GitHub, GitLab, git.gay, git.girlcock.ceo,
|
||||
sourcehut, and Codeberg (see README.md for their links)
|
||||
- Docker container images published to GitLab Container Registry, GitHub
|
||||
Container Registry, and Dockerhub
|
||||
- Extensively revamp the example config to be extremely helpful and useful to
|
||||
both new users and power users
|
||||
- Fixed every single clippy (default lints) and rustc warnings, including some
|
||||
that were performance related or potential safety issues / unsoundness
|
||||
- Add a **lot** of other clippy and rustc lints and a rustfmt.toml file
|
||||
- Has [Renovate](https://docs.renovatebot.com/), [Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL dependencies as up to date as possible
|
||||
- Attempts and interest in removing extreme and unnecessary panics/unwraps/expects that can lead to denial of service or such (upstream and upstream contributors want this unusual behaviour for some reason)
|
||||
- Purge unmaintained/irrelevant/broken database backends (heed, sled, persy) and other unnecessary code or overhead
|
||||
- Repo uses [Renovate](https://docs.renovatebot.com/),
|
||||
[Trivy](https://github.com/aquasecurity/trivy-action), and keeps ALL
|
||||
dependencies as up to date as possible
|
||||
- Purge unmaintained/irrelevant/broken database backends (heed, sled, persy) and
|
||||
other unnecessary code or overhead
|
||||
- webp support for images
|
||||
- Add cargo audit support to CI
|
||||
- CI tests with all features
|
||||
- Add timestamp by commit date support to building OCI images for keeping image build reproducibility and still have a meaningful "last modified date" for OCI image metadata
|
||||
- Update rusqlite/sqlite (not that you should be using it)
|
||||
- Startup check if conduwuit running in a container and is listening on 127.0.0.1 (generally containers are using NAT networking and 0.0.0.0 is the intended listening address)
|
||||
- Add documentation lints via lychee and markdownlint-cli to CI
|
||||
- CI tests for all sorts of feature matrixes (jemalloc, non-defaullt, all
|
||||
features, etc)
|
||||
- Add static and dynamic linking smoke tests in CI to prevent any potential
|
||||
linking regressions for Complement, static binaries, Nix devshells, etc
|
||||
- Add timestamp by commit date when building OCI images for keeping image build
|
||||
reproducibility and still have a meaningful "last modified date" for OCI image
|
||||
- Add timestamp by commit date via `SOURCE_DATE_EPOCH` for Debian packages
|
||||
- Startup check if conduwuit running in a container and is listening on
|
||||
127.0.0.1 (generally containers are using NAT networking and 0.0.0.0 is the
|
||||
intended listening address)
|
||||
- Add a panic catcher layer to return panic messages in HTTP responses if a
|
||||
panic occurs
|
||||
- Add full compatibility support for SHA256 media file names instead of base64
|
||||
file names to overcome filesystem file name length limitations (OS error file
|
||||
name too long) while still retaining upstream database compatibility
|
||||
- Remove SQLite support due to being very poor performance, difficult to
|
||||
maintain against RocksDB, and is a blocker to significantly improved database
|
||||
code
|
||||
|
||||
## Admin Room
|
||||
|
||||
## Admin Room:
|
||||
- Fix admin room handler to not panic/crash if the admin room command response fails (e.g. too large message)
|
||||
- Add command to dynamically change conduwuit's tracing log level filter on the fly
|
||||
- Add support for a console CLI interface that can issue admin commands and
|
||||
output them in your terminal
|
||||
- Add support for an admin-user-only commandline admin room interface that can
|
||||
be issued in any room with the `\\!admin` or `\!admin` prefix and returns the
|
||||
response as yourself in the same room
|
||||
- Add admin commands for uptime, server startup, server shutdown, and server
|
||||
restart
|
||||
- Fix admin room handler to not panic/crash if the admin room command response
|
||||
fails (e.g. too large message)
|
||||
- Add command to dynamically change conduwuit's tracing log level filter on the
|
||||
fly
|
||||
- Add admin command to fetch a server's `/.well-known/matrix/support` file
|
||||
- Add debug admin command to force update user device lists (could potentially resolve some E2EE flukes)
|
||||
- Implement **RocksDB online backups**, listing RocksDB backups, and listing database file counts all via admin commands
|
||||
- Add various database visibility commands such as being able to query the getters and iterators used in conduwuit, a very helpful online debugging utility
|
||||
- Add debug admin command to force update user device lists (could potentially
|
||||
resolve some E2EE flukes)
|
||||
- Implement **RocksDB online backups**, listing RocksDB backups, and listing
|
||||
database file counts all via admin commands
|
||||
- Add various database visibility commands such as being able to query the
|
||||
getters and iterators used in conduwuit, a very helpful online debugging utility
|
||||
- Forbid the admin room from being made public or world readable history
|
||||
- Add `!admin` as a way to call the admin bot
|
||||
- Extend clear cache admin command to support clearing more caches such as DNS and TLS name overrides
|
||||
- Admin debug command to send a federation request/ping to a server's `/_matrix/federation/v1/version` endpoint and measures the latency it took
|
||||
- Extend clear cache admin command to support clearing more caches such as DNS
|
||||
and TLS name overrides
|
||||
- Admin debug command to send a federation request/ping to a server's
|
||||
`/_matrix/federation/v1/version` endpoint and measures the latency it took
|
||||
- Add admin command to bulk delete media via a codeblock list of MXC URLs.
|
||||
- Add admin command to delete both the thumbnail and media MXC URLs from an event ID (e.g. from an abuse report)
|
||||
- Add admin command to delete both the thumbnail and media MXC URLs from an
|
||||
event ID (e.g. from an abuse report)
|
||||
- Add admin command to list all the rooms a local user is joined in
|
||||
- Add admin command to delete all remote media in the past X minutes as a form of deleting media that you don't want on your server that a remote user posted in a room
|
||||
- Add admin command to return a room's state
|
||||
- Admin debug command to fetch a PDU from a remote server and inserts it into our database/timeline as backfill
|
||||
- Add admin command to delete media via a specific MXC. This deletes the MXC from our database, and the file locally.
|
||||
- Add admin commands for banning (blocking) room IDs from our local users joining (admins are always allowed) and evicts all our local users from that room, in addition to bulk room banning support, and blocks room invites (remote and local) to the banned room, as a moderation feature
|
||||
- Add admin command to list joined members in a room
|
||||
- Add admin command to view the room topic of a room
|
||||
- Add admin command to delete all remote media in the past X minutes as a form
|
||||
of deleting media that you don't want on your server that a remote user posted
|
||||
in a room, a `--force` flag to ignore errors, and support for reading `last
|
||||
modified time` instead of `creation time` for filesystems that don't support
|
||||
file created metadata
|
||||
- Add admin command to return a room's full/complete state
|
||||
- Admin debug command to fetch a PDU from a remote server and inserts it into
|
||||
our database/timeline as backfill
|
||||
- Add admin command to delete media via a specific MXC. This deletes the MXC
|
||||
from our database, and the file locally.
|
||||
- Add admin commands for banning (blocking) room IDs from our local users
|
||||
joining (admins are always allowed) and evicts all our local users from that
|
||||
room, in addition to bulk room banning support, and blocks room invites (remote
|
||||
and local) to the banned room, as a moderation feature
|
||||
- Add admin commands to output jemalloc memory stats and memory usage
|
||||
- Add admin command to get conduwuit's uptime
|
||||
- Add admin command to get rooms a *remote* user shares with us
|
||||
- Add debug admin commands to get the earliest and latest PDU in a room
|
||||
- Add debug admin command to echo a message
|
||||
- Add admin command to insert rooms tags for a user, most useful for inserting
|
||||
the `m.server_notice` tag on your admin room to make it "persistent" in the
|
||||
"System Alerts" section of Element
|
||||
- Add experimental admin debug command for Dendrite's `AdminDownloadState`
|
||||
(`/admin/downloadState/{serverName}/{roomID}`) admin API endpoint to download
|
||||
and use a remote server's room state in the room
|
||||
- Disable URL previews by default in the admin room due to various command
|
||||
outputs having "URLs" in them that clients may needlessly render/request
|
||||
- Extend memory usage admin server command to support showing memory allocator
|
||||
stats such as jemalloc's
|
||||
- Add admin debug command to see memory allocator's full extended debug
|
||||
statistics such as jemalloc's
|
||||
|
||||
## Misc
|
||||
|
||||
## Misc:
|
||||
- Support for creating rooms with custom room IDs like Maunium Synapse (`room_id` request body field to `/createRoom`)
|
||||
- Query parameter `?format=event|content` for returning either the room state event's content (default) for the full room state event on ` /_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see https://github.com/matrix-org/matrix-spec/issues/1047)
|
||||
- Add **optional** feature flag to use SHA256 key names for media instead of base64 to overcome filesystem file name length limitations (OS error file name too long)
|
||||
- Add guest support for accessing TURN servers via `turn_allow_guests` like
|
||||
Synapse
|
||||
- Support for creating rooms with custom room IDs like Maunium Synapse
|
||||
(`room_id` request body field to `/createRoom`)
|
||||
- Query parameter `?format=event|content` for returning either the room state
|
||||
event's content (default) for the full room state event on
|
||||
`/_matrix/client/v3/rooms/{roomId}/state/{eventType}[/{stateKey}]` requests (see
|
||||
<https://github.com/matrix-org/matrix-spec/issues/1047>)
|
||||
- Send a User-Agent on all of our requests
|
||||
- Send `avatar_url` on invite room membership events/changes
|
||||
- Support sending [`well_known` response to client login responses](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3login) if using config option `[well_known.client]`
|
||||
- Implement `include_state` search criteria support for `/search` requests (response now can include room states)
|
||||
- Declare various missing Matrix versions and features at `/_matrix/client/versions`
|
||||
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers may still call
|
||||
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa which may help in joining certain rooms.
|
||||
- Support sending [`well_known` response to client login
|
||||
responses](https://spec.matrix.org/v1.10/client-server-api/#post_matrixclientv3login)
|
||||
if using config option `[well_known.client]`
|
||||
- Implement `include_state` search criteria support for `/search` requests
|
||||
(response now can include room states)
|
||||
- Declare various missing Matrix versions and features at
|
||||
`/_matrix/client/versions`
|
||||
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers
|
||||
may still call
|
||||
- Config option to change Conduit's behaviour of homeserver key fetching
|
||||
(`query_trusted_key_servers_first`). This option sets whether conduwuit will
|
||||
query trusted notary key servers first before the individual homeserver(s), or
|
||||
vice versa which may help in joining certain rooms.
|
||||
- Implement unstable MSC2666 support for querying mutual rooms with a user
|
||||
- Assume well-knowns are broken if they exceed past 10000 characters.
|
||||
- Add support for the Matrix spec compliance test suite [Complement](https://github.com/matrix-org/complement/) via the Nix flake and various other fixes for it
|
||||
- Add support for listening on both HTTP and HTTPS if using direct TLS with conduwuit for usecases such as Complement
|
||||
- Implement running and diff'ing Complement results in CI
|
||||
- Interest in supporting other operating systems such as macOS, BSDs, and Windows, and getting them added into CI and doing builds for them
|
||||
- Implement unstable MSC3266 room summary API support
|
||||
- Implement unstable MSC4125 support for specifying servers to join via on
|
||||
federated invites
|
||||
- Make conduwuit build and be functional under Nix + macOS
|
||||
- Log out all sessions after unsetting the emergency password
|
||||
- Assume well-knowns are broken if they exceed past 12288 characters.
|
||||
- Add support for listening on both HTTP and HTTPS if using direct TLS with
|
||||
conduwuit for usecases such as Complement
|
||||
- Add config option for disabling RocksDB Direct IO if needed
|
||||
- Add various documentation on maintaining conduwuit, using RocksDB online
|
||||
backups, some troubleshooting, using admin commands, moderation documentation,
|
||||
etc
|
||||
- (Developers): Add support for [hot reloadable/"live" modular
|
||||
development](development/hot_reload.md)
|
||||
- (Developers): Add support for tokio-console
|
||||
- (Developers): Add support for tracing flame graphs
|
||||
- Add `release-debuginfo` Cargo build profile
|
||||
- No cryptocurrency donations allowed, conduwuit is fully maintained by independent queer maintainers, and with a strong priority on inclusitivity and comfort for protected groups 🏳️⚧️
|
||||
- No cryptocurrency donations allowed, conduwuit is fully maintained by
|
||||
independent queer maintainers, and with a strong priority on inclusitivity and
|
||||
comfort for protected groups 🏳️⚧️
|
||||
- [Add a community Code of Conduct for all conduwuit community spaces, primarily
|
||||
the Matrix space](https://conduwuit.puppyirl.gay/conduwuit_coc.html)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Conduwuit
|
||||
# conduwuit
|
||||
|
||||
{{#include ../README.md:catchphrase}}
|
||||
|
||||
@@ -6,12 +6,17 @@ # Conduwuit
|
||||
|
||||
#### What's different about your fork than upstream Conduit?
|
||||
|
||||
See [differences.md](differences.md)
|
||||
See the [differences](differences.md) page
|
||||
|
||||
#### How can I deploy my own?
|
||||
|
||||
- [Deployment options](deploying.md)
|
||||
|
||||
If you want to connect an Appservice to Conduwuit, take a look at the [appservices documentation](appservices.md).
|
||||
If you want to connect an appservice to conduwuit, take a look at the
|
||||
[appservices documentation](appservices.md).
|
||||
|
||||
#### How can I contribute?
|
||||
|
||||
See the [contributor's guide](contributing.md)
|
||||
|
||||
{{#include ../README.md:footer}}
|
||||
|
||||
97
docs/maintenance.md
Normal file
97
docs/maintenance.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Maintaining your conduwuit setup
|
||||
|
||||
## Moderation
|
||||
|
||||
conduwuit has moderation through admin room commands. "binary commands" (medium
|
||||
priority) and an admin API (low priority) is planned. Some moderation-related
|
||||
config options are available in the example config such as "global ACLs" and
|
||||
blocking media requests to certain servers. See the example config for the
|
||||
moderation config options under the "Moderation / Privacy / Security" section.
|
||||
|
||||
conduwuit has moderation admin commands for:
|
||||
|
||||
- managing room aliases (`!admin rooms alias`)
|
||||
- managing room directory (`!admin rooms directory`)
|
||||
- managing room banning/blocking and user removal (`!admin rooms moderation`)
|
||||
- managing user accounts (`!admin users`)
|
||||
- fetching `/.well-known/matrix/support` from servers (`!admin federation`)
|
||||
- blocking incoming federation for certain rooms (not the same as room banning)
|
||||
(`!admin federation`)
|
||||
- deleting media (see [the media section](#media))
|
||||
|
||||
Any commands with `-list` in them will require a codeblock in the message with
|
||||
each object being newline delimited. An example of doing this is:
|
||||
|
||||
```` !admin rooms moderation ban-list-of-rooms ``` !roomid1:server.name
|
||||
!roomid2:server.name !roomid3:server.name ``` ````
|
||||
|
||||
## Database
|
||||
|
||||
If using RocksDB, there's very little you need to do. Compaction is ran
|
||||
automatically based on various defined thresholds tuned for conduwuit to be high
|
||||
performance with the least I/O amplifcation or overhead. Manually running
|
||||
compaction is not recommended, or compaction via a timer. RocksDB is built with
|
||||
io_uring support via liburing for async read I/O.
|
||||
|
||||
Some RocksDB settings can be adjusted such as the compression method chosen. See
|
||||
the RocksDB section in the [example config](configuration/examples.md). btrfs
|
||||
users may benefit from disabling compression on RocksDB if CoW is in use.
|
||||
|
||||
RocksDB troubleshooting can be found [in the RocksDB section of
|
||||
troubleshooting](troubleshooting.md).
|
||||
|
||||
## Backups
|
||||
|
||||
Currently only RocksDB supports online backups. If you'd like to backup your
|
||||
database online without any downtime, see the `!admin server` command for the
|
||||
backup commands and the `database_backup_path` config options in the example
|
||||
config. Please note that the format of the database backup is not the exact
|
||||
same. This is unfortunately a bad design choice by Facebook as we are using the
|
||||
database backup engine API from RocksDB, however the data is still there and can
|
||||
still be joined together.
|
||||
|
||||
To restore a backup from an online RocksDB backup:
|
||||
|
||||
- shutdown conduwuit
|
||||
- create a new directory for merging together the data
|
||||
- in the online backup created, copy all `.sst` files in
|
||||
`$DATABASE_BACKUP_PATH/shared_checksum` to your new directory
|
||||
- trim all the strings so instead of `######_sxxxxxxxxx.sst`, it reads
|
||||
`######.sst`. A way of doing this with sed and bash is `for file in *.sst; do mv
|
||||
"$file" "$(echo "$file" | sed 's/_s.*/.sst/')"; done`
|
||||
- copy all the files in `$DATABASE_BACKUP_PATH/1` (or the latest backup number
|
||||
if you have multiple) to your new directory
|
||||
- set your `database_path` config option to your new directory, or replace your
|
||||
old one with the new one you crafted
|
||||
- start up conduwuit again and it should open as normal
|
||||
|
||||
If you'd like to do an offline backup, shutdown conduwuit and copy your
|
||||
`database_path` directory elsewhere. This can be restored with no modifications
|
||||
needed.
|
||||
|
||||
Backing up media is also just copying the `media/` directory from your database
|
||||
directory.
|
||||
|
||||
## Media
|
||||
|
||||
Media still needs various work, however conduwuit implements media deletion via:
|
||||
|
||||
- MXC URI or Event ID (unencrypted and attempts to find the MXC URI in the
|
||||
event)
|
||||
- Delete list of MXC URIs
|
||||
- Delete remote media in the past `N` seconds/minutes via filesystem metadata on
|
||||
the file created time (`btime`) or file modified time (`mtime`)
|
||||
|
||||
See the `!admin media` command for further information. All media in conduwuit
|
||||
is stored at `$DATABASE_DIR/media`. This will be configurable soon.
|
||||
|
||||
If you are finding yourself needing extensive granular control over media, we
|
||||
recommend looking into [Matrix Media
|
||||
Repo](https://github.com/t2bot/matrix-media-repo). conduwuit intends to
|
||||
implement various utilities for media, but MMR is dedicated to extensive media
|
||||
management.
|
||||
|
||||
Built-in S3 support is also planned, but for now using a "S3 filesystem" on
|
||||
`media/` works. conduwuit also sends a `Cache-Control` header of 1 year and
|
||||
immutable for all media requests (download and thumbnail) to reduce unnecessary
|
||||
media requests from browsers, reduce bandwidth usage, and reduce load.
|
||||
117
docs/troubleshooting.md
Normal file
117
docs/troubleshooting.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Troubleshooting conduwuit
|
||||
|
||||
> ## Docker users ⚠️
|
||||
>
|
||||
> Docker is extremely UX unfriendly. Because of this, a ton of issues or support
|
||||
> is actually Docker support, not conduwuit support. We also cannot document the
|
||||
> ever-growing list of Docker issues here.
|
||||
>
|
||||
> If you intend on asking for support and you are using Docker, **PLEASE**
|
||||
> triple validate your issues are **NOT** because you have a misconfiguration in
|
||||
> your Docker setup.
|
||||
>
|
||||
> If there are things like Compose file issues or Dockerhub image issues, those
|
||||
> can still be mentioned as long as they're something we can fix.
|
||||
|
||||
## General potential issues
|
||||
|
||||
#### Potential DNS issues when using Docker
|
||||
|
||||
Docker has issues with its default DNS setup that may cause DNS to not be
|
||||
properly functional when running conduwuit, resulting in federation issues. The
|
||||
symptoms of this have shown in excessively long room joins (30+ minutes) from
|
||||
very long DNS timeouts, log entries of "mismatching responding nameservers",
|
||||
and/or partial or non-functional inbound/outbound federation.
|
||||
|
||||
This is **not** a conduwuit issue, and is purely a Docker issue. It is not
|
||||
sustainable for heavy DNS activity which is normal for Matrix federation. The
|
||||
workarounds for this are:
|
||||
- Use DNS over TCP via the config option `query_over_tcp_only = true`
|
||||
- Don't use Docker's default DNS setup and instead allow the container to use
|
||||
and communicate with your host's DNS servers (host's `/etc/resolv.conf`)
|
||||
|
||||
## Rocksdb / database issues
|
||||
|
||||
#### Direct IO
|
||||
|
||||
Some filesystems may not like RocksDB using [Direct
|
||||
IO](https://github.com/facebook/rocksdb/wiki/Direct-IO). Direct IO is for
|
||||
non-buffered I/O which improves conduwuit performance, but at least FUSE is a
|
||||
filesystem potentially known to not like this. See the [example
|
||||
config](configuration/examples.md) for disabling it if needed. Issues from
|
||||
Direct IO on unsupported filesystems are usually shown as startup errors.
|
||||
|
||||
#### Database corruption
|
||||
|
||||
If your database is corrupted *and* is failing to start (e.g. checksum
|
||||
mismatch), it may be recoverable but careful steps must be taken, and there is
|
||||
no guarantee it may be recoverable.
|
||||
|
||||
The first thing that can be done is launching conduwuit with the
|
||||
`rocksdb_repair` config option set to true. This will tell RocksDB to attempt to
|
||||
repair itself at launch. If this does not work, disable the option and continue
|
||||
reading.
|
||||
|
||||
RocksDB has the following recovery modes:
|
||||
|
||||
- `TolerateCorruptedTailRecords`
|
||||
- `AbsoluteConsistency`
|
||||
- `PointInTime`
|
||||
- `SkipAnyCorruptedRecord`
|
||||
|
||||
By default, conduwuit uses `TolerateCorruptedTailRecords` as generally these may
|
||||
be due to bad federation and we can re-fetch the correct data over federation.
|
||||
The RocksDB default is `PointInTime` which will attempt to restore a "snapshot"
|
||||
of the data when it was last known to be good. This data can be either a few
|
||||
seconds old, or multiple minutes prior. `PointInTime` may not be suitable for
|
||||
default usage due to clients and servers possibly not being able to handle
|
||||
sudden "backwards time travels", and `AbsoluteConsistency` may be too strict.
|
||||
|
||||
`AbsoluteConsistency` will fail to start the database if any sign of corruption
|
||||
is detected. `SkipAnyCorruptedRecord` will skip all forms of corruption unless
|
||||
it forbids the database from opening (e.g. too severe). Usage of
|
||||
`SkipAnyCorruptedRecord` voids any support as this may cause more damage and/or
|
||||
leave your database in a permanently inconsistent state, but it may do something
|
||||
if `PointInTime` does not work as a last ditch effort.
|
||||
|
||||
With this in mind:
|
||||
|
||||
- First start conduwuit with the `PointInTime` recovery method. See the [example
|
||||
config](configuration/examples.md) for how to do this using
|
||||
`rocksdb_recovery_mode`
|
||||
- If your database successfully opens, clients are recommended to clear their
|
||||
client cache to account for the rollback
|
||||
- Leave your conduwuit running in `PointInTime` for at least 30-60 minutes so as
|
||||
much possible corruption is restored
|
||||
- If all goes will, you should be able to restore back to using
|
||||
`TolerateCorruptedTailRecords` and you have successfully recovered your database
|
||||
|
||||
## Debugging
|
||||
|
||||
Note that users should not really be debugging things. If you find yourself
|
||||
debugging and find the issue, please let us know and/or how we can fix it.
|
||||
Various debug commands can be found in `!admin debug`.
|
||||
|
||||
#### Debug/Trace log level
|
||||
|
||||
conduwuit builds without debug or trace log levels by default for at least
|
||||
performance reasons. This may change in the future and/or binaries providing
|
||||
such configurations may be provided. If you need to access debug/trace log
|
||||
levels, you will need to build without the `release_max_log_level` feature.
|
||||
|
||||
#### Changing log level dynamically
|
||||
|
||||
conduwuit supports changing the tracing log environment filter on-the-fly using
|
||||
the admin command `!admin debug change-log-level`. This accepts a string
|
||||
**without quotes** the same format as the `log` config option.
|
||||
|
||||
#### Pinging servers
|
||||
|
||||
conduwuit can ping other servers using `!admin debug ping`. This takes a server
|
||||
name and goes through the server discovery process and queries
|
||||
`/_matrix/federation/v1/version`. Errors are outputted.
|
||||
|
||||
#### Allocator memory stats
|
||||
|
||||
When using jemalloc with jemallocator's `stats` feature, you can see conduwuit's
|
||||
jemalloc memory stats by using `!admin debug memory-stats`
|
||||
63
docs/turn.md
63
docs/turn.md
@@ -1,25 +1,54 @@
|
||||
# Setting up TURN/STURN
|
||||
|
||||
## General instructions
|
||||
In order to make or receive calls, a TURN server is required. conduwuit suggests
|
||||
using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also
|
||||
available as a Docker image.
|
||||
|
||||
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md).
|
||||
### Configuration
|
||||
|
||||
## Edit/Add a few settings to your existing conduit.toml
|
||||
Create a configuration file called `coturn.conf` containing:
|
||||
|
||||
```
|
||||
# Refer to your Coturn settings.
|
||||
# `your.turn.url` has to match the REALM setting of your Coturn as well as `transport`.
|
||||
turn_uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"]
|
||||
|
||||
# static-auth-secret of your turnserver
|
||||
turn_secret = "ADD SECRET HERE"
|
||||
|
||||
# If you have your TURN server configured to use a username and password
|
||||
# you can provide these information too. In this case comment out `turn_secret above`!
|
||||
#turn_username = ""
|
||||
#turn_password = ""
|
||||
```conf
|
||||
use-auth-secret
|
||||
static-auth-secret=<a secret key>
|
||||
realm=<your server domain>
|
||||
```
|
||||
|
||||
## Apply settings
|
||||
A common way to generate a suitable alphanumeric secret key is by using `pwgen
|
||||
-s 64 1`.
|
||||
|
||||
Restart Conduit.
|
||||
These same values need to be set in conduwuit. See the [example
|
||||
config](configuration/examples.md) in the TURN section for configuring these and
|
||||
restart conduwuit after.
|
||||
|
||||
### Run
|
||||
|
||||
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
|
||||
|
||||
```bash
|
||||
docker run -d --network=host -v
|
||||
$(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
|
||||
```
|
||||
|
||||
or docker-compose. For the latter, paste the following section into a file
|
||||
called `docker-compose.yml` and run `docker compose up -d` in the same
|
||||
directory.
|
||||
|
||||
```yml
|
||||
version: 3
|
||||
services:
|
||||
turn:
|
||||
container_name: coturn-server
|
||||
image: docker.io/coturn/coturn
|
||||
restart: unless-stopped
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
- ./coturn.conf:/etc/coturn/turnserver.conf
|
||||
```
|
||||
|
||||
To understand why the host networking mode is used and explore alternative
|
||||
configuration options, please visit [Coturn's Docker
|
||||
documentation](https://github.com/coturn/coturn/blob/master/docker/coturn/README.md).
|
||||
|
||||
For security recommendations see Synapse's [Coturn
|
||||
documentation](https://element-hq.github.io/synapse/latest/turn-howto.html).
|
||||
|
||||
121
engage.toml
121
engage.toml
@@ -55,26 +55,42 @@ name = "lychee"
|
||||
group = "versions"
|
||||
script = "lychee --version"
|
||||
|
||||
[[task]]
|
||||
name = "markdownlint"
|
||||
group = "versions"
|
||||
script = "markdownlint --version"
|
||||
|
||||
[[task]]
|
||||
name = "dpkg"
|
||||
group = "versions"
|
||||
script = "dpkg --version"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-audit"
|
||||
group = "security"
|
||||
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked --ignore RUSTSEC-2020-0016"
|
||||
script = "cargo audit -D warnings -D unmaintained -D unsound -D yanked"
|
||||
|
||||
[[task]]
|
||||
name = "cargo-fmt"
|
||||
group = "lints"
|
||||
script = "cargo fmt --check -- --color=always"
|
||||
script = """
|
||||
cargo fmt --check -- --color=always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "cargo-doc"
|
||||
group = "lints"
|
||||
script = """
|
||||
RUSTDOCFLAGS="-D warnings" cargo doc \
|
||||
--workspace \
|
||||
--all-features \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--color always
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
RUSTDOCFLAGS="-D warnings" \
|
||||
direnv exec . \
|
||||
cargo doc \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-features \
|
||||
--no-deps \
|
||||
--document-private-items \
|
||||
--color always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
@@ -83,6 +99,7 @@ group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
@@ -93,13 +110,16 @@ cargo clippy \
|
||||
name = "clippy/all"
|
||||
group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
@@ -108,6 +128,7 @@ group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--features jemalloc \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
@@ -115,33 +136,73 @@ cargo clippy \
|
||||
-D warnings
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "clippy/hardened_malloc"
|
||||
group = "lints"
|
||||
script = """
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--features hardened_malloc \
|
||||
--all-targets \
|
||||
--color=always \
|
||||
-- \
|
||||
-D warnings
|
||||
"""
|
||||
#[[task]]
|
||||
#name = "clippy/hardened_malloc"
|
||||
#group = "lints"
|
||||
#script = """
|
||||
#cargo clippy \
|
||||
# --workspace \
|
||||
# --features hardened_malloc \
|
||||
# --all-targets \
|
||||
# --color=always \
|
||||
# -- \
|
||||
# -D warnings
|
||||
#"""
|
||||
|
||||
[[task]]
|
||||
name = "lychee"
|
||||
group = "lints"
|
||||
script = "lychee --offline docs"
|
||||
script = "lychee --verbose --offline docs *.md --exclude development.md"
|
||||
|
||||
[[task]]
|
||||
name = "cargo"
|
||||
name = "markdownlint"
|
||||
group = "lints"
|
||||
script = "markdownlint docs *.md || true" # TODO: fix the ton of markdown lints so we can drop `|| true`
|
||||
|
||||
[[task]]
|
||||
name = "cargo/all"
|
||||
group = "tests"
|
||||
script = """
|
||||
env DIRENV_DEVSHELL=all-features \
|
||||
direnv exec . \
|
||||
cargo test \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
[[task]]
|
||||
name = "cargo/default"
|
||||
group = "tests"
|
||||
script = """
|
||||
cargo test \
|
||||
--workspace \
|
||||
--profile test \
|
||||
--all-targets \
|
||||
--all-features \
|
||||
--color=always \
|
||||
-- \
|
||||
--color=always
|
||||
"""
|
||||
|
||||
# Ensure that the flake's default output can build and run without crashing
|
||||
#
|
||||
# This is a dynamically-linked jemalloc build, which is a case not covered by
|
||||
# our other tests. We've had linking problems in the past with dynamic
|
||||
# jemalloc builds that usually show up as an immediate segfault or "invalid free"
|
||||
[[task]]
|
||||
name = "nix-default"
|
||||
group = "tests"
|
||||
script = """
|
||||
env DIRENV_DEVSHELL=dynamic \
|
||||
CARGO_PROFILE="test" \
|
||||
direnv exec . \
|
||||
bin/nix-build-and-cache just .#default-test
|
||||
env DIRENV_DEVSHELL=dynamic \
|
||||
CARGO_PROFILE="test" \
|
||||
direnv exec . \
|
||||
nix run -L .#default-test -- --help && nix run -L .#default-test -- --version
|
||||
"""
|
||||
|
||||
571
flake.lock
generated
571
flake.lock
generated
@@ -9,11 +9,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1711742460,
|
||||
"narHash": "sha256-0O4v6e4a1toxXZ2gf5INhg4WPE5C5T+SVvsBt+45Mcc=",
|
||||
"lastModified": 1724226964,
|
||||
"narHash": "sha256-cltFh4su2vcFidxKp7LuEgX3ZGLfPy0DCdrQZ/QTe68=",
|
||||
"owner": "zhaofengli",
|
||||
"repo": "attic",
|
||||
"rev": "4dbdbee45728d8ce5788db6461aaaa89d98081f0",
|
||||
"rev": "6d9aeaef0a067d664cb11bb7704f7ec373d47fb2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -23,14 +23,69 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"flake-compat": "flake-compat_3",
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724232775,
|
||||
"narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "master",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cachix_2": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_2",
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712055811,
|
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"complement": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1714472853,
|
||||
"narHash": "sha256-CNRHSZe3TE+3tFj2dHNyxTMjDqL0MKY3P/3jqUgA7YE=",
|
||||
"lastModified": 1722323564,
|
||||
"narHash": "sha256-6w6/N8walz4Ayc9zu7iySqJRmGFukhkaICLn4dweAcA=",
|
||||
"owner": "matrix-org",
|
||||
"repo": "complement",
|
||||
"rev": "891d18872c153d39a9ce63b545045efddb845738",
|
||||
"rev": "6e4426a9e63233f9821a4d2382bfed145244183f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -48,11 +103,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1702918879,
|
||||
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
|
||||
"lastModified": 1722960479,
|
||||
"narHash": "sha256-NhCkJJQhD5GUib8zN9JrmYGMwt4lCRp6ZVNzIiYCl0Y=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
|
||||
"rev": "4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -68,11 +123,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713738183,
|
||||
"narHash": "sha256-qd/MuLm7OfKQKyd4FAMqV4H6zYyOfef5lLzRrmXwKJM=",
|
||||
"lastModified": 1724006180,
|
||||
"narHash": "sha256-PVxPj0Ga2fMYMtcT9ARCthF+4U71YkOT7ZjgD/vf1Aw=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "f6c6a2fb1b8bd9b65d65ca9342dd0eb180a63f11",
|
||||
"rev": "7ce92819802bc583b7e82ebc08013a530f22209f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -82,6 +137,70 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix_2",
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix_2",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"git-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723156315,
|
||||
"narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708704632,
|
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "python-rewrite",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -90,11 +209,11 @@
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1714544767,
|
||||
"narHash": "sha256-kF1bX+YFMedf1g0PAJYwGUkzh22JmULtj8Rm4IXAQKs=",
|
||||
"lastModified": 1724221791,
|
||||
"narHash": "sha256-mKX67QPnUybOopVph/LhOV1G/H4EvPxDIfSmbufrVdA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "73124e1356bde9411b163d636b39fe4804b7ca45",
|
||||
"rev": "e88b38a5a3834e039d413a88f8150a75ef6453ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -105,6 +224,22 @@
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
@@ -120,7 +255,23 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake-compat_3": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_4": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
@@ -138,21 +289,6 @@
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
@@ -164,6 +300,42 @@
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
@@ -171,6 +343,99 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1723202784,
|
||||
"narHash": "sha256-qbhjc/NEGaDbyy0ucycubq4N3//gDFFH3DOmp1D3u1Q=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "c7012d0c18567c889b948781bc74a501e92275d1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"liburing": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724199144,
|
||||
"narHash": "sha256-MVjnwO6EbKzzSrU51dSseLarZ1fRp+6SagAf/nE/XZU=",
|
||||
"owner": "axboe",
|
||||
"repo": "liburing",
|
||||
"rev": "2d4e799017d64cd2f8304503eef9064931bb3fbd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "axboe",
|
||||
"ref": "master",
|
||||
"repo": "liburing",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-filter": {
|
||||
"locked": {
|
||||
"lastModified": 1710156097,
|
||||
@@ -187,13 +452,67 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1711401922,
|
||||
"narHash": "sha256-QoQqXoj8ClGo0sqD/qWKFWezgEwUL0SUh37/vY2jNhc=",
|
||||
"lastModified": 1723827930,
|
||||
"narHash": "sha256-EU+W5F6y2CVNxGrGIMpY7nSVYq72WRChYxF4zpjx0y4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "07262b18b97000d16a4bdb003418bd2fb067a932",
|
||||
"rev": "d4a7a4d0e066278bfb0d77bd2a7adde1c0ec9e3d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -203,13 +522,45 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1711460390,
|
||||
"narHash": "sha256-akSgjDZL6pVHEfSE6sz1DNSXuYX6hq+P/1Z5IoYWs7E=",
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "44733514b72e732bd49f5511bd0203dea9b9a434",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_2": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1720535198,
|
||||
"narHash": "sha256-zwVvxrdIzralnSbcpghA92tWu2DV2lwv89xZc8MTrbg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "205fd4226592cc83fd4c0885a3e4c9c400efabb5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -219,13 +570,45 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"nixpkgs-stable_2": {
|
||||
"locked": {
|
||||
"lastModified": 1713537308,
|
||||
"narHash": "sha256-XtTSSIB2DA6tOv+l0FhvfDMiyCmhoRbNB+0SeInZkbk=",
|
||||
"lastModified": 1720386169,
|
||||
"narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5c24cf2f0a12ad855f444c30b2421d044120c66f",
|
||||
"rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1692808169,
|
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1722813957,
|
||||
"narHash": "sha256-IAoYyYnED7P8zrBFMnmp7ydaJfwTnwcnqxUElC1I26Y=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cb9a96f23c491c081b38eab96d22fa958043c9fa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -235,19 +618,61 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rocksdb": {
|
||||
"flake": false,
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1713810944,
|
||||
"narHash": "sha256-/Xf0bzNJPclH9IP80QNaABfhj4IAR5LycYET18VFCXc=",
|
||||
"owner": "facebook",
|
||||
"repo": "rocksdb",
|
||||
"rev": "6f7cabeac80a3a6150be2c8a8369fcecb107bf43",
|
||||
"lastModified": 1724271409,
|
||||
"narHash": "sha256-z4nw9HxkaXEn+5OT8ljLVL2oataHvAzUQ1LEi8Fp+SY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "36a9aeaaa17a2d4348498275f9fe530cd4f9e519",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "facebook",
|
||||
"ref": "v9.1.1",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"cachix",
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692876271,
|
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rocksdb": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724285323,
|
||||
"narHash": "sha256-k60kreKQ0v+bQ16yBd2SfLYpuNjMw2qoRmZL/S3k6CU=",
|
||||
"owner": "girlbossceo",
|
||||
"repo": "rocksdb",
|
||||
"rev": "5a67ad7ce46328578ee5587fb0c23faa03d14e67",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "girlbossceo",
|
||||
"ref": "v9.5.2",
|
||||
"repo": "rocksdb",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -255,24 +680,26 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"attic": "attic",
|
||||
"cachix": "cachix",
|
||||
"complement": "complement",
|
||||
"crane": "crane_2",
|
||||
"fenix": "fenix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"flake-compat": "flake-compat_4",
|
||||
"flake-utils": "flake-utils_3",
|
||||
"liburing": "liburing",
|
||||
"nix-filter": "nix-filter",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"nixpkgs": "nixpkgs_4",
|
||||
"rocksdb": "rocksdb"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1713628977,
|
||||
"narHash": "sha256-iN5QUlUq527lswmBC+RopfXdu6Xx7mmTaBSH2l59FtM=",
|
||||
"lastModified": 1724153119,
|
||||
"narHash": "sha256-WxpvDJDttkINkXmUA/W5o11lwLPYhATAgu0QUAacZ2g=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "55d9a533b309119c8acd13061581b43ae8840823",
|
||||
"rev": "3723e5910c14f0ffbd13de474b8a8fcc74db04ce",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -296,6 +723,36 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
334
flake.nix
334
flake.nix
@@ -1,6 +1,7 @@
|
||||
{
|
||||
inputs = {
|
||||
attic.url = "github:zhaofengli/attic?ref=main";
|
||||
cachix.url = "github:cachix/cachix?ref=master";
|
||||
complement = { url = "github:matrix-org/complement?ref=main"; flake = false; };
|
||||
crane = { url = "github:ipetkov/crane?ref=master"; inputs.nixpkgs.follows = "nixpkgs"; };
|
||||
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
|
||||
@@ -8,26 +9,32 @@
|
||||
flake-utils.url = "github:numtide/flake-utils?ref=main";
|
||||
nix-filter.url = "github:numtide/nix-filter?ref=main";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
|
||||
rocksdb = { url = "github:facebook/rocksdb?ref=v9.1.1"; flake = false; };
|
||||
rocksdb = { url = "github:girlbossceo/rocksdb?ref=v9.5.2"; flake = false; };
|
||||
liburing = { url = "github:axboe/liburing?ref=master"; flake = false; };
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
inputs.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgsHost = inputs.nixpkgs.legacyPackages.${system};
|
||||
pkgsHost = import inputs.nixpkgs{
|
||||
inherit system;
|
||||
config.permittedInsecurePackages = [ "olm-3.2.16" ];
|
||||
};
|
||||
pkgsHostStatic = pkgsHost.pkgsStatic;
|
||||
|
||||
# The Rust toolchain to use
|
||||
toolchain = inputs.fenix.packages.${system}.fromToolchainFile {
|
||||
file = ./rust-toolchain.toml;
|
||||
|
||||
# See also `rust-toolchain.toml`
|
||||
sha256 = "sha256-e4mlaJehWBymYxJGgnbuCObVlqMlQSilZ8FljG9zPHY=";
|
||||
sha256 = "sha256-3jVIIf5XPnUU1CRaTyAiO0XHVbJl12MSx3eucTXCjtE=";
|
||||
};
|
||||
|
||||
scope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
|
||||
inherit pkgs;
|
||||
book = self.callPackage ./nix/pkgs/book {};
|
||||
complement = self.callPackage ./nix/pkgs/complement {};
|
||||
craneLib = ((inputs.crane.mkLib pkgs).overrideToolchain toolchain);
|
||||
craneLib = ((inputs.crane.mkLib pkgs).overrideToolchain (_: toolchain));
|
||||
inherit inputs;
|
||||
main = self.callPackage ./nix/pkgs/main {};
|
||||
oci-image = self.callPackage ./nix/pkgs/oci-image {};
|
||||
@@ -37,21 +44,174 @@
|
||||
"v"
|
||||
(builtins.fromJSON (builtins.readFile ./flake.lock))
|
||||
.nodes.rocksdb.original.ref;
|
||||
# we have this already at https://github.com/girlbossceo/rocksdb/commit/a935c0273e1ba44eacf88ce3685a9b9831486155
|
||||
# unsetting this so i don't have to revert it and make this nix exclusive
|
||||
patches = [];
|
||||
cmakeFlags = pkgs.lib.subtractLists
|
||||
[
|
||||
# no real reason to have snappy, no one uses this
|
||||
"-DWITH_SNAPPY=1"
|
||||
# we dont need to use ldb or sst_dump (core_tools)
|
||||
"-DWITH_CORE_TOOLS=1"
|
||||
# we dont need to build rocksdb tests
|
||||
"-DWITH_TESTS=1"
|
||||
# we use rust-rocksdb via C interface and dont need C++ RTTI
|
||||
"-DUSE_RTTI=1"
|
||||
]
|
||||
old.cmakeFlags
|
||||
++ [
|
||||
# we dont need to use ldb or sst_dump (core_tools)
|
||||
"-DWITH_CORE_TOOLS=0"
|
||||
# we dont need trace tools
|
||||
"-DWITH_TRACE_TOOLS=0"
|
||||
# we dont need to build rocksdb tests
|
||||
"-DWITH_TESTS=0"
|
||||
# we use rust-rocksdb via C interface and dont need C++ RTTI
|
||||
"-DUSE_RTTI=0"
|
||||
];
|
||||
|
||||
# outputs has "tools" which we dont need or use
|
||||
outputs = [ "out" ];
|
||||
|
||||
# preInstall hooks has stuff for messing with ldb/sst_dump which we dont need or use
|
||||
preInstall = "";
|
||||
});
|
||||
# TODO: remove once https://github.com/NixOS/nixpkgs/pull/314945 is available
|
||||
liburing = pkgs.liburing.overrideAttrs (old: {
|
||||
# the configure script doesn't support these, and unconditionally
|
||||
# builds both static and dynamic libraries.
|
||||
configureFlags = pkgs.lib.subtractLists
|
||||
[ "--enable-static" "--disable-shared" ]
|
||||
old.configureFlags;
|
||||
});
|
||||
});
|
||||
|
||||
scopeHost = (scope pkgsHost);
|
||||
scopeHost = mkScope pkgsHost;
|
||||
scopeHostStatic = mkScope pkgsHostStatic;
|
||||
|
||||
mkDevShell = scope: scope.pkgs.mkShell {
|
||||
env = scope.main.env // {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so. The
|
||||
# `rust-src` component is required in order for this to work.
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
|
||||
# Convenient way to access a pinned version of Complement's source
|
||||
# code.
|
||||
COMPLEMENT_SRC = inputs.complement.outPath;
|
||||
|
||||
# Needed for Complement
|
||||
CGO_CFLAGS = "-I${scope.pkgs.olm}/include";
|
||||
CGO_LDFLAGS = "-L${scope.pkgs.olm}/lib";
|
||||
};
|
||||
|
||||
# Development tools
|
||||
packages = [
|
||||
# Always use nightly rustfmt because most of its options are unstable
|
||||
#
|
||||
# This needs to come before `toolchain` in this list, otherwise
|
||||
# `$PATH` will have stable rustfmt instead.
|
||||
inputs.fenix.packages.${system}.latest.rustfmt
|
||||
|
||||
toolchain
|
||||
]
|
||||
++ (with pkgsHost.pkgs; [
|
||||
engage
|
||||
cargo-audit
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
# Needed for CI to check validity of produced Debian packages (dpkg-deb)
|
||||
dpkg
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
|
||||
# Needed for linting markdown files
|
||||
markdownlint-cli
|
||||
|
||||
# Useful for editing the book locally
|
||||
mdbook
|
||||
|
||||
# used for rust caching in CI to speed it up
|
||||
sccache
|
||||
|
||||
# needed so we can get rid of gcc and other unused deps that bloat OCI images
|
||||
removeReferencesTo
|
||||
])
|
||||
++ scope.main.buildInputs
|
||||
++ scope.main.propagatedBuildInputs
|
||||
++ scope.main.nativeBuildInputs;
|
||||
|
||||
meta.broken = scope.main.meta.broken;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
default = scopeHost.main;
|
||||
jemalloc = scopeHost.main.override { features = ["jemalloc"]; };
|
||||
default-debug = scopeHost.main.override {
|
||||
profile = "dev";
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
};
|
||||
default-test = scopeHost.main.override {
|
||||
profile = "test";
|
||||
disable_release_max_log_level = true;
|
||||
};
|
||||
all-features = scopeHost.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
all-features-debug = scopeHost.main.override {
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
hmalloc = scopeHost.main.override { features = ["hardened_malloc"]; };
|
||||
|
||||
oci-image = scopeHost.oci-image;
|
||||
oci-image-jemalloc = scopeHost.oci-image.override {
|
||||
oci-image-all-features = scopeHost.oci-image.override {
|
||||
main = scopeHost.main.override {
|
||||
features = ["jemalloc"];
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
};
|
||||
oci-image-all-features-debug = scopeHost.oci-image.override {
|
||||
main = scopeHost.main.override {
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
};
|
||||
oci-image-hmalloc = scopeHost.oci-image.override {
|
||||
@@ -63,6 +223,7 @@
|
||||
book = scopeHost.book;
|
||||
|
||||
complement = scopeHost.complement;
|
||||
static-complement = scopeHostStatic.complement;
|
||||
}
|
||||
//
|
||||
builtins.listToAttrs
|
||||
@@ -78,7 +239,7 @@
|
||||
config = crossSystem;
|
||||
};
|
||||
}).pkgsStatic;
|
||||
scopeCrossStatic = scope pkgsCrossStatic;
|
||||
scopeCrossStatic = mkScope pkgsCrossStatic;
|
||||
in
|
||||
[
|
||||
# An output for a statically-linked binary
|
||||
@@ -87,11 +248,54 @@
|
||||
value = scopeCrossStatic.main;
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with jemalloc
|
||||
# An output for a statically-linked unstripped debug ("dev") binary
|
||||
{
|
||||
name = "${binaryName}-jemalloc";
|
||||
name = "${binaryName}-debug";
|
||||
value = scopeCrossStatic.main.override {
|
||||
features = ["jemalloc"];
|
||||
profile = "dev";
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked unstripped debug binary with the
|
||||
# "test" profile (for CI usage only)
|
||||
{
|
||||
name = "${binaryName}-test";
|
||||
value = scopeCrossStatic.main.override {
|
||||
profile = "test";
|
||||
disable_release_max_log_level = true;
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked binary with `--all-features`
|
||||
{
|
||||
name = "${binaryName}-all-features";
|
||||
value = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
# An output for a statically-linked unstripped debug ("dev") binary with `--all-features`
|
||||
{
|
||||
name = "${binaryName}-all-features-debug";
|
||||
value = scopeCrossStatic.main.override {
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,12 +313,49 @@
|
||||
value = scopeCrossStatic.oci-image;
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with jemalloc
|
||||
# An output for an OCI image based on that unstripped debug ("dev") binary
|
||||
{
|
||||
name = "oci-image-${crossSystem}-jemalloc";
|
||||
name = "oci-image-${crossSystem}-debug";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
features = ["jemalloc"];
|
||||
profile = "dev";
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that binary with `--all-features`
|
||||
{
|
||||
name = "oci-image-${crossSystem}-all-features";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
# An output for an OCI image based on that unstripped debug ("dev") binary with `--all-features`
|
||||
{
|
||||
name = "oci-image-${crossSystem}-all-features-debug";
|
||||
value = scopeCrossStatic.oci-image.override {
|
||||
main = scopeCrossStatic.main.override {
|
||||
profile = "dev";
|
||||
all_features = true;
|
||||
# debug build users expect full logs
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -137,50 +378,23 @@
|
||||
)
|
||||
);
|
||||
|
||||
devShells.default = pkgsHost.mkShell {
|
||||
env = scopeHost.main.env // {
|
||||
# Rust Analyzer needs to be able to find the path to default crate
|
||||
# sources, and it can read this environment variable to do so. The
|
||||
# `rust-src` component is required in order for this to work.
|
||||
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
|
||||
|
||||
# Convenient way to access a pinned version of Complement's source
|
||||
# code.
|
||||
COMPLEMENT_SRC = inputs.complement.outPath;
|
||||
devShells.default = mkDevShell scopeHostStatic;
|
||||
devShells.all-features = mkDevShell
|
||||
(scopeHostStatic.overrideScope (final: prev: {
|
||||
main = prev.main.override {
|
||||
all_features = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
|
||||
# Development tools
|
||||
packages = [
|
||||
# Always use nightly rustfmt because most of its options are unstable
|
||||
#
|
||||
# This needs to come before `toolchain` in this list, otherwise
|
||||
# `$PATH` will have stable rustfmt instead.
|
||||
inputs.fenix.packages.${system}.latest.rustfmt
|
||||
|
||||
toolchain
|
||||
]
|
||||
++ (with pkgsHost; [
|
||||
engage
|
||||
cargo-audit
|
||||
|
||||
# Needed for producing Debian packages
|
||||
cargo-deb
|
||||
|
||||
# Needed for Complement
|
||||
go
|
||||
olm
|
||||
|
||||
# Needed for our script for Complement
|
||||
jq
|
||||
|
||||
# Needed for finding broken markdown links
|
||||
lychee
|
||||
|
||||
# Useful for editing the book locally
|
||||
mdbook
|
||||
])
|
||||
++
|
||||
scopeHost.main.nativeBuildInputs;
|
||||
};
|
||||
}));
|
||||
devShells.no-features = mkDevShell
|
||||
(scopeHostStatic.overrideScope (final: prev: {
|
||||
main = prev.main.override { default_features = false; };
|
||||
}));
|
||||
devShells.dynamic = mkDevShell scopeHost;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,9 +14,13 @@ stdenv.mkDerivation {
|
||||
include = [
|
||||
"book.toml"
|
||||
"conduwuit-example.toml"
|
||||
"CONTRIBUTING.md"
|
||||
"README.md"
|
||||
"debian/conduwuit.service"
|
||||
"debian/README.md"
|
||||
"arch/conduwuit.service"
|
||||
"docs"
|
||||
"theme"
|
||||
];
|
||||
};
|
||||
|
||||
@@ -25,7 +29,6 @@ stdenv.mkDerivation {
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
mdbook build
|
||||
mv public $out
|
||||
mdbook build -d $out
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -5,13 +5,17 @@ allow_guest_registration = true
|
||||
allow_public_room_directory_over_federation = true
|
||||
allow_public_room_directory_without_auth = true
|
||||
allow_registration = true
|
||||
allow_unstable_room_versions = true
|
||||
database_backend = "rocksdb"
|
||||
database_path = "/database"
|
||||
log = "trace"
|
||||
log = "trace,h2=warn,hyper=warn"
|
||||
port = [8008, 8448]
|
||||
trusted_servers = []
|
||||
query_trusted_key_servers_first = false
|
||||
yes_i_am_very_very_sure_i_want_an_open_registration_server_prone_to_abuse = true
|
||||
ip_range_denylist = []
|
||||
url_preview_domain_contains_allowlist = ["*"]
|
||||
media_compat_file_link = false
|
||||
media_startup_check = false
|
||||
rocksdb_direct_io = false
|
||||
|
||||
[global.tls]
|
||||
certs = "/certificate.crt"
|
||||
|
||||
@@ -14,8 +14,15 @@
|
||||
|
||||
let
|
||||
main' = main.override {
|
||||
profile = "dev";
|
||||
features = ["axum_dual_protocol"];
|
||||
profile = "test";
|
||||
all_features = true;
|
||||
disable_release_max_log_level = true;
|
||||
disable_features = [
|
||||
# this is non-functional on nix for some reason
|
||||
"hardened_malloc"
|
||||
# dont include experimental features
|
||||
"experimental"
|
||||
];
|
||||
};
|
||||
|
||||
start = writeShellScriptBin "start" ''
|
||||
@@ -44,16 +51,14 @@ let
|
||||
-sha256
|
||||
|
||||
${lib.getExe' coreutils "env"} \
|
||||
CONDUIT_SERVER_NAME="$SERVER_NAME" \
|
||||
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8448" \
|
||||
CONDUIT_WELL_KNOWN_SERVER="$SERVER_NAME:8008" \
|
||||
CONDUWUIT_SERVER_NAME="$SERVER_NAME" \
|
||||
${lib.getExe main'}
|
||||
'';
|
||||
in
|
||||
|
||||
dockerTools.buildImage {
|
||||
name = "complement-${main.pname}";
|
||||
tag = "dev";
|
||||
tag = "main";
|
||||
|
||||
copyToRoot = buildEnv {
|
||||
name = "root";
|
||||
@@ -81,7 +86,7 @@ dockerTools.buildImage {
|
||||
|
||||
Env = [
|
||||
"SSL_CERT_FILE=/complement/ca/ca.crt"
|
||||
"CONDUIT_CONFIG=${./config.toml}"
|
||||
"CONDUWUIT_CONFIG=${./config.toml}"
|
||||
];
|
||||
|
||||
ExposedPorts = {
|
||||
|
||||
@@ -13,6 +13,12 @@ lib.optionalAttrs stdenv.hostPlatform.isStatic {
|
||||
lib.concatStringsSep
|
||||
" "
|
||||
([]
|
||||
++ lib.optionals
|
||||
stdenv.targetPlatform.isx86_64
|
||||
[ "-C" "target-cpu=x86-64-v2" ]
|
||||
++ lib.optionals
|
||||
stdenv.targetPlatform.isAarch64
|
||||
[ "-C" "target-cpu=cortex-a55" ] # cortex-a55 == ARMv8.2-a
|
||||
# This disables PIE for static builds, which isn't great in terms
|
||||
# of security. Unfortunately, my hand is forced because nixpkgs'
|
||||
# `libstdc++.a` is built without `-fPIE`, which precludes us from
|
||||
|
||||
@@ -1,27 +1,112 @@
|
||||
# Dependencies (keep sorted)
|
||||
{ craneLib
|
||||
, inputs
|
||||
, jq
|
||||
, lib
|
||||
, libiconv
|
||||
, liburing
|
||||
, pkgsBuildHost
|
||||
, rocksdb
|
||||
, removeReferencesTo
|
||||
, rust
|
||||
, rust-jemalloc-sys
|
||||
, stdenv
|
||||
|
||||
# Options (keep sorted)
|
||||
, default_features ? true
|
||||
, disable_release_max_log_level ? false
|
||||
, all_features ? false
|
||||
, disable_features ? []
|
||||
, features ? []
|
||||
, profile ? "release"
|
||||
}:
|
||||
|
||||
let
|
||||
# We perform default-feature unification in nix, because some of the dependencies
|
||||
# on the nix side depend on feature values.
|
||||
crateFeatures = path:
|
||||
let manifest = lib.importTOML "${path}/Cargo.toml"; in
|
||||
lib.remove "default" (lib.attrNames manifest.features);
|
||||
crateDefaultFeatures = path:
|
||||
(lib.importTOML "${path}/Cargo.toml").features.default;
|
||||
allDefaultFeatures = crateDefaultFeatures "${inputs.self}/src/main";
|
||||
allFeatures = crateFeatures "${inputs.self}/src/main";
|
||||
features' = lib.unique
|
||||
(features ++
|
||||
lib.optionals default_features allDefaultFeatures ++
|
||||
lib.optionals all_features allFeatures);
|
||||
disable_features' = disable_features ++ lib.optionals disable_release_max_log_level ["release_max_log_level"];
|
||||
features'' = lib.subtractLists disable_features' features';
|
||||
|
||||
featureEnabled = feature : builtins.elem feature features'';
|
||||
|
||||
enableLiburing = featureEnabled "io_uring" && !stdenv.isDarwin;
|
||||
|
||||
# This derivation will set the JEMALLOC_OVERRIDE variable, causing the
|
||||
# tikv-jemalloc-sys crate to use the nixpkgs jemalloc instead of building it's
|
||||
# own. In order for this to work, we need to set flags on the build that match
|
||||
# whatever flags tikv-jemalloc-sys was going to use. These are dependent on
|
||||
# which features we enable in tikv-jemalloc-sys.
|
||||
rust-jemalloc-sys' = (rust-jemalloc-sys.override {
|
||||
# tikv-jemalloc-sys/unprefixed_malloc_on_supported_platforms feature
|
||||
unprefixed = true;
|
||||
}).overrideAttrs (old: {
|
||||
configureFlags = old.configureFlags ++
|
||||
# we dont need docs
|
||||
[ "--disable-doc" ] ++
|
||||
# we dont need cxx/C++ integration
|
||||
[ "--disable-cxx" ] ++
|
||||
# tikv-jemalloc-sys/profiling feature
|
||||
lib.optional (featureEnabled "jemalloc_prof") "--enable-prof" ++
|
||||
# tikv-jemalloc-sys/stats feature
|
||||
(if (featureEnabled "jemalloc_stats") then [ "--enable-stats" ] else [ "--disable-stats" ]);
|
||||
});
|
||||
|
||||
buildDepsOnlyEnv =
|
||||
let
|
||||
rocksdb' = rocksdb.override {
|
||||
enableJemalloc = builtins.elem "jemalloc" features;
|
||||
};
|
||||
rocksdb' = (rocksdb.override {
|
||||
jemalloc = rust-jemalloc-sys';
|
||||
# rocksdb fails to build with prefixed jemalloc, which is required on
|
||||
# darwin due to [1]. In this case, fall back to building rocksdb with
|
||||
# libc malloc. This should not cause conflicts, because all of the
|
||||
# jemalloc symbols are prefixed.
|
||||
#
|
||||
# [1]: https://github.com/tikv/jemallocator/blob/ab0676d77e81268cd09b059260c75b38dbef2d51/jemalloc-sys/src/env.rs#L17
|
||||
enableJemalloc = featureEnabled "jemalloc" && !stdenv.isDarwin;
|
||||
|
||||
# for some reason enableLiburing in nixpkgs rocksdb is default true
|
||||
# which breaks Darwin entirely
|
||||
enableLiburing = enableLiburing;
|
||||
}).overrideAttrs (old: {
|
||||
# TODO: static rocksdb fails to build on darwin, also see <https://github.com/NixOS/nixpkgs/issues/320448>
|
||||
# build log at <https://girlboss.ceo/~strawberry/pb/JjGH>
|
||||
meta.broken = stdenv.hostPlatform.isStatic && stdenv.isDarwin;
|
||||
|
||||
enableLiburing = enableLiburing;
|
||||
|
||||
sse42Support = stdenv.targetPlatform.isx86_64;
|
||||
|
||||
cmakeFlags = if stdenv.targetPlatform.isx86_64
|
||||
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||
++ lib.optionals stdenv.targetPlatform.isx86_64 [
|
||||
"-DPORTABLE=x86-64-v2"
|
||||
"-DUSE_SSE=1"
|
||||
"-DHAVE_SSE=1"
|
||||
"-DHAVE_SSE42=1"
|
||||
]
|
||||
else if stdenv.targetPlatform.isAarch64
|
||||
then lib.subtractLists [ "-DPORTABLE=1" ] old.cmakeFlags
|
||||
++ lib.optionals stdenv.targetPlatform.isAarch64 [
|
||||
# cortex-a73 == ARMv8-A
|
||||
"-DPORTABLE=armv8-a"
|
||||
]
|
||||
else old.cmakeFlags;
|
||||
});
|
||||
in
|
||||
{
|
||||
# https://crane.dev/faq/rebuilds-bindgen.html
|
||||
NIX_OUTPATH_USED_AS_RANDOM_SEED = "aaaaaaaaaa";
|
||||
|
||||
CARGO_PROFILE = profile;
|
||||
ROCKSDB_INCLUDE_DIR = "${rocksdb'}/include";
|
||||
ROCKSDB_LIB_DIR = "${rocksdb'}/lib";
|
||||
@@ -37,8 +122,19 @@ buildDepsOnlyEnv =
|
||||
});
|
||||
|
||||
buildPackageEnv = {
|
||||
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev;
|
||||
} // buildDepsOnlyEnv;
|
||||
CONDUWUIT_VERSION_EXTRA = inputs.self.shortRev or inputs.self.dirtyShortRev or "";
|
||||
} // buildDepsOnlyEnv // {
|
||||
# Only needed in static stdenv because these are transitive dependencies of rocksdb
|
||||
CARGO_BUILD_RUSTFLAGS = buildDepsOnlyEnv.CARGO_BUILD_RUSTFLAGS
|
||||
+ lib.optionalString (enableLiburing && stdenv.hostPlatform.isStatic)
|
||||
" -L${lib.getLib liburing}/lib -luring"
|
||||
+ lib.optionalString stdenv.targetPlatform.isx86_64
|
||||
" -Ctarget-cpu=x86-64-v2"
|
||||
+ lib.optionalString stdenv.targetPlatform.isAarch64
|
||||
" -Ctarget-cpu=cortex-a73"; # cortex-a73 == ARMv8-A
|
||||
};
|
||||
|
||||
|
||||
|
||||
commonAttrs = {
|
||||
inherit
|
||||
@@ -55,18 +151,46 @@ commonAttrs = {
|
||||
include = [
|
||||
"Cargo.lock"
|
||||
"Cargo.toml"
|
||||
"hot_lib"
|
||||
"deps"
|
||||
"src"
|
||||
];
|
||||
};
|
||||
|
||||
dontStrip = profile == "dev" || profile == "test";
|
||||
dontPatchELF = profile == "dev" || profile == "test";
|
||||
|
||||
buildInputs = lib.optional (featureEnabled "jemalloc") rust-jemalloc-sys';
|
||||
|
||||
nativeBuildInputs = [
|
||||
# bindgen needs the build platform's libclang. Apparently due to "splicing
|
||||
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
|
||||
# right thing here.
|
||||
pkgsBuildHost.rustPlatform.bindgenHook
|
||||
|
||||
# We don't actually depend on `jq`, but crane's `buildPackage` does, but
|
||||
# its `buildDepsOnly` doesn't. This causes those two derivations to have
|
||||
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
|
||||
# rebuilds of bindgen and its depedents.
|
||||
jq
|
||||
|
||||
# needed so we can get rid of gcc and other unused deps that bloat OCI images
|
||||
removeReferencesTo
|
||||
]
|
||||
++ lib.optionals stdenv.isDarwin [ libiconv ];
|
||||
++ lib.optionals stdenv.isDarwin [
|
||||
# https://github.com/NixOS/nixpkgs/issues/206242
|
||||
libiconv
|
||||
|
||||
# https://stackoverflow.com/questions/69869574/properly-adding-darwin-apple-sdk-to-a-nix-shell
|
||||
# https://discourse.nixos.org/t/compile-a-rust-binary-on-macos-dbcrossbar/8612
|
||||
pkgsBuildHost.darwin.apple_sdk.frameworks.Security
|
||||
];
|
||||
|
||||
# for some reason gcc and other weird deps are added to OCI images and bloats it up
|
||||
#
|
||||
# <https://github.com/input-output-hk/haskell.nix/issues/829>
|
||||
postInstall = with pkgsBuildHost; ''
|
||||
find "$out" -type f -exec remove-references-to -t ${stdenv.cc} -t ${gcc} -t ${libgcc} -t ${linuxHeaders} -t ${libidn2} -t ${libunistring} '{}' +
|
||||
'';
|
||||
};
|
||||
in
|
||||
|
||||
@@ -75,15 +199,14 @@ craneLib.buildPackage ( commonAttrs // {
|
||||
env = buildDepsOnlyEnv;
|
||||
});
|
||||
|
||||
cargoExtraArgs = ""
|
||||
cargoExtraArgs = "--no-default-features "
|
||||
+ lib.optionalString
|
||||
(!default_features)
|
||||
"--no-default-features "
|
||||
+ lib.optionalString
|
||||
(features != [])
|
||||
"--features " + (builtins.concatStringsSep "," features);
|
||||
(features'' != [])
|
||||
"--features " + (builtins.concatStringsSep "," features'');
|
||||
|
||||
# This is redundant with CI
|
||||
cargoTestCommand = "";
|
||||
cargoCheckCommand = "";
|
||||
doCheck = false;
|
||||
|
||||
env = buildPackageEnv;
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
},
|
||||
"nix": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"labels": ["dependencies", "github_actions"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# If you're having trouble making the relevant changes, bug a maintainer.
|
||||
|
||||
[toolchain]
|
||||
channel = "1.76.0"
|
||||
channel = "1.80.1"
|
||||
components = [
|
||||
# For rust-analyzer
|
||||
"rust-src",
|
||||
|
||||
45
src/admin/Cargo.toml
Normal file
45
src/admin/Cargo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "conduit_admin"
|
||||
categories.workspace = true
|
||||
description.workspace = true
|
||||
edition.workspace = true
|
||||
keywords.workspace = true
|
||||
license.workspace = true
|
||||
readme.workspace = true
|
||||
repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "mod.rs"
|
||||
crate-type = [
|
||||
"rlib",
|
||||
# "dylib",
|
||||
]
|
||||
|
||||
[features]
|
||||
#dev_release_log_level = []
|
||||
release_max_log_level = [
|
||||
"tracing/max_level_trace",
|
||||
"tracing/release_max_level_info",
|
||||
"log/max_level_trace",
|
||||
"log/release_max_level_info",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
conduit-api.workspace = true
|
||||
conduit-core.workspace = true
|
||||
conduit-macros.workspace = true
|
||||
conduit-service.workspace = true
|
||||
const-str.workspace = true
|
||||
futures-util.workspace = true
|
||||
log.workspace = true
|
||||
ruma.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
tokio.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
66
src/admin/admin.rs
Normal file
66
src/admin/admin.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use clap::Parser;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{
|
||||
appservice, appservice::AppserviceCommand, check, check::CheckCommand, command::Command, debug,
|
||||
debug::DebugCommand, federation, federation::FederationCommand, media, media::MediaCommand, query,
|
||||
query::QueryCommand, room, room::RoomCommand, server, server::ServerCommand, user, user::UserCommand,
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "admin", version = env!("CARGO_PKG_VERSION"))]
|
||||
pub(super) enum AdminCommand {
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing appservices
|
||||
Appservices(AppserviceCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing local users
|
||||
Users(UserCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing rooms
|
||||
Rooms(RoomCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing federation
|
||||
Federation(FederationCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing the server
|
||||
Server(ServerCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for managing media
|
||||
Media(MediaCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for checking integrity
|
||||
Check(CheckCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Commands for debugging things
|
||||
Debug(DebugCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Low-level queries for database getters and iterators
|
||||
Query(QueryCommand),
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "command")]
|
||||
pub(super) async fn process(command: AdminCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
use AdminCommand::*;
|
||||
|
||||
Ok(match command {
|
||||
Appservices(command) => appservice::process(command, context).await?,
|
||||
Media(command) => media::process(command, context).await?,
|
||||
Users(command) => user::process(command, context).await?,
|
||||
Rooms(command) => room::process(command, context).await?,
|
||||
Federation(command) => federation::process(command, context).await?,
|
||||
Server(command) => server::process(command, context).await?,
|
||||
Debug(command) => debug::process(command, context).await?,
|
||||
Query(command) => query::process(command, context).await?,
|
||||
Check(command) => check::process(command, context).await?,
|
||||
})
|
||||
}
|
||||
68
src/admin/appservice/commands.rs
Normal file
68
src/admin/appservice/commands.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use ruma::{api::appservice::Registration, events::room::message::RoomMessageEventContent};
|
||||
|
||||
use crate::{admin_command, Result};
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn register(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let appservice_config = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
|
||||
match parsed_config {
|
||||
Ok(yaml) => match self.services.appservice.register_appservice(yaml).await {
|
||||
Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Appservice registered with ID: {id}."
|
||||
))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to register appservice: {e}"
|
||||
))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Could not parse appservice config: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn unregister(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||
match self
|
||||
.services
|
||||
.appservice
|
||||
.unregister_appservice(&appservice_identifier)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Appservice unregistered.")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to unregister appservice: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_appservice_config(&self, appservice_identifier: String) -> Result<RoomMessageEventContent> {
|
||||
match self
|
||||
.services
|
||||
.appservice
|
||||
.get_registration(&appservice_identifier)
|
||||
.await
|
||||
{
|
||||
Some(config) => {
|
||||
let config_str = serde_yaml::to_string(&config).expect("config should've been validated on register");
|
||||
let output = format!("Config for {appservice_identifier}:\n\n```yaml\n{config_str}\n```",);
|
||||
Ok(RoomMessageEventContent::notice_markdown(output))
|
||||
},
|
||||
None => Ok(RoomMessageEventContent::text_plain("Appservice does not exist.")),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_registered(&self) -> Result<RoomMessageEventContent> {
|
||||
let appservices = self.services.appservice.iter_ids().await;
|
||||
let output = format!("Appservices ({}): {}", appservices.len(), appservices.join(", "));
|
||||
Ok(RoomMessageEventContent::text_plain(output))
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use conduit::Result;
|
||||
|
||||
use self::appservice_command::{list, register, show, unregister};
|
||||
use crate::Result;
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
pub(crate) mod appservice_command;
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum AppserviceCommand {
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[admin_command_dispatch]
|
||||
pub(super) enum AppserviceCommand {
|
||||
/// - Register an appservice using its registration YAML
|
||||
///
|
||||
/// This command needs a YAML generated by an appservice (such as a bridge),
|
||||
@@ -29,24 +28,13 @@ pub(crate) enum AppserviceCommand {
|
||||
/// - Show an appservice's config using its ID
|
||||
///
|
||||
/// You can find the ID using the `list-appservices` command.
|
||||
Show {
|
||||
#[clap(alias("show"))]
|
||||
ShowAppserviceConfig {
|
||||
/// The appservice to show
|
||||
appservice_identifier: String,
|
||||
},
|
||||
|
||||
/// - List all the currently registered appservices
|
||||
List,
|
||||
}
|
||||
|
||||
pub(crate) async fn process(command: AppserviceCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
AppserviceCommand::Register => register(body).await?,
|
||||
AppserviceCommand::Unregister {
|
||||
appservice_identifier,
|
||||
} => unregister(body, appservice_identifier).await?,
|
||||
AppserviceCommand::Show {
|
||||
appservice_identifier,
|
||||
} => show(body, appservice_identifier).await?,
|
||||
AppserviceCommand::List => list(body).await?,
|
||||
})
|
||||
#[clap(alias("list"))]
|
||||
ListRegistered,
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
use conduit::Result;
|
||||
use conduit_macros::implement;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{services, Result};
|
||||
use crate::Command;
|
||||
|
||||
/// Uses the iterator in `src/database/key_value/users.rs` to iterator over
|
||||
/// every user in our database (remote and local). Reports total count, any
|
||||
/// errors if there were any, etc
|
||||
pub(crate) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
#[implement(Command, params = "<'_>")]
|
||||
pub(super) async fn check_all_users(&self) -> Result<RoomMessageEventContent> {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services().users.db.iter();
|
||||
let results = self.services.users.db.iter();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
let users = results.collect::<Vec<_>>();
|
||||
@@ -17,10 +20,9 @@ pub(crate) async fn check_all_users(_body: Vec<&str>) -> Result<RoomMessageEvent
|
||||
let ok_count = users.iter().filter(|user| user.is_ok()).count();
|
||||
|
||||
let message = format!(
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {:?}\nFailure/Invalid user count: \
|
||||
{:?}\nSuccess/Valid user count: {:?}```",
|
||||
total, err_count, ok_count
|
||||
"Database query completed in {query_time:?}:\n\n```\nTotal entries: {total:?}\nFailure/Invalid user count: \
|
||||
{err_count:?}\nSuccess/Valid user count: {ok_count:?}\n```"
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_html(message, String::new()))
|
||||
Ok(RoomMessageEventContent::notice_markdown(message))
|
||||
}
|
||||
18
src/admin/check/mod.rs
Normal file
18
src/admin/check/mod.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum CheckCommand {
|
||||
AllUsers,
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: CheckCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
CheckCommand::AllUsers => context.check_all_users().await?,
|
||||
})
|
||||
}
|
||||
11
src/admin/command.rs
Normal file
11
src/admin/command.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use conduit_service::Services;
|
||||
use ruma::EventId;
|
||||
|
||||
pub(crate) struct Command<'a> {
|
||||
pub(crate) services: &'a Services,
|
||||
pub(crate) body: &'a [&'a str],
|
||||
pub(crate) timer: SystemTime,
|
||||
pub(crate) reply_id: Option<&'a EventId>,
|
||||
}
|
||||
831
src/admin/debug/commands.rs
Normal file
831
src/admin/debug/commands.rs
Normal file
@@ -0,0 +1,831 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
fmt::Write,
|
||||
sync::Arc,
|
||||
time::{Instant, SystemTime},
|
||||
};
|
||||
|
||||
use api::client::validate_and_add_event_id;
|
||||
use conduit::{debug, debug_error, err, info, trace, utils, warn, Error, PduEvent, Result};
|
||||
use ruma::{
|
||||
api::{client::error::ErrorKind, federation::event::get_room_state},
|
||||
events::room::message::RoomMessageEventContent,
|
||||
CanonicalJsonObject, EventId, OwnedRoomOrAliasId, RoomId, RoomVersionId, ServerName,
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::admin_command;
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn echo(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
||||
let message = message.join(" ");
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain(message))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_auth_chain(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||
let event_id = Arc::<EventId>::from(event_id);
|
||||
if let Some(event) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
|
||||
let room_id_str = event
|
||||
.get("room_id")
|
||||
.and_then(|val| val.as_str())
|
||||
.ok_or_else(|| Error::bad_database("Invalid event in database"))?;
|
||||
|
||||
let room_id = <&RoomId>::try_from(room_id_str)
|
||||
.map_err(|_| Error::bad_database("Invalid room id field in event in database"))?;
|
||||
|
||||
let start = Instant::now();
|
||||
let count = self
|
||||
.services
|
||||
.rooms
|
||||
.auth_chain
|
||||
.event_ids_iter(room_id, vec![event_id])
|
||||
.await?
|
||||
.count();
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Loaded auth chain with length {count} in {elapsed:?}"
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain("Event not found."))
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn parse_pdu(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().saturating_sub(1)].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
|
||||
Ok(hash) => {
|
||||
let event_id = EventId::parse(format!("${hash}"));
|
||||
|
||||
match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
|
||||
Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"EventId: {event_id:?}\nCould not parse event: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid json in command body: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_pdu(&self, event_id: Box<EventId>) -> Result<RoomMessageEventContent> {
|
||||
let mut outlier = false;
|
||||
let mut pdu_json = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.get_non_outlier_pdu_json(&event_id)?;
|
||||
if pdu_json.is_none() {
|
||||
outlier = true;
|
||||
pdu_json = self.services.rooms.timeline.get_pdu_json(&event_id)?;
|
||||
}
|
||||
match pdu_json {
|
||||
Some(json) => {
|
||||
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{}\n```json\n{}\n```",
|
||||
if outlier {
|
||||
"Outlier PDU found in our database"
|
||||
} else {
|
||||
"PDU found in our database"
|
||||
},
|
||||
json_text
|
||||
)))
|
||||
},
|
||||
None => Ok(RoomMessageEventContent::text_plain("PDU not found locally.")),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_pdu_list(
|
||||
&self, server: Box<ServerName>, force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
}
|
||||
|
||||
if server == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
|
||||
the database.",
|
||||
));
|
||||
}
|
||||
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let list = self
|
||||
.body
|
||||
.iter()
|
||||
.collect::<Vec<_>>()
|
||||
.drain(1..self.body.len().saturating_sub(1))
|
||||
.filter_map(|pdu| EventId::parse(pdu).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut failed_count: usize = 0;
|
||||
let mut success_count: usize = 0;
|
||||
|
||||
for pdu in list {
|
||||
if force {
|
||||
if let Err(e) = self.get_remote_pdu(Box::from(pdu), server.clone()).await {
|
||||
failed_count = failed_count.saturating_add(1);
|
||||
self.services
|
||||
.admin
|
||||
.send_message(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to get remote PDU, ignoring error: {e}"
|
||||
)))
|
||||
.await;
|
||||
warn!("Failed to get remote PDU, ignoring error: {e}");
|
||||
} else {
|
||||
success_count = success_count.saturating_add(1);
|
||||
}
|
||||
} else {
|
||||
self.get_remote_pdu(Box::from(pdu), server.clone()).await?;
|
||||
success_count = success_count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Fetched {success_count} remote PDUs successfully with {failed_count} failures"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_pdu(
|
||||
&self, event_id: Box<EventId>, server: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
}
|
||||
|
||||
if server == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
|
||||
));
|
||||
}
|
||||
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server,
|
||||
ruma::api::federation::event::get_event::v1::Request {
|
||||
event_id: event_id.clone().into(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let json: CanonicalJsonObject = serde_json::from_str(response.pdu.get()).map_err(|e| {
|
||||
warn!(
|
||||
"Requested event ID {event_id} from server but failed to convert from RawValue to \
|
||||
CanonicalJsonObject (malformed event/response?): {e}"
|
||||
);
|
||||
Error::BadRequest(ErrorKind::Unknown, "Received response from server but failed to parse PDU")
|
||||
})?;
|
||||
|
||||
trace!("Attempting to parse PDU: {:?}", &response.pdu);
|
||||
let parsed_pdu = {
|
||||
let parsed_result = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.parse_incoming_pdu(&response.pdu);
|
||||
let (event_id, value, room_id) = match parsed_result {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
warn!("Failed to parse PDU: {e}");
|
||||
info!("Full PDU: {:?}", &response.pdu);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse PDU remote server {server} sent us: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
vec![(event_id, value, room_id)]
|
||||
};
|
||||
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
debug!("Attempting to fetch homeserver signing keys for {server}");
|
||||
self.services
|
||||
.server_keys
|
||||
.fetch_required_signing_keys(parsed_pdu.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Could not fetch all signatures for PDUs from {server}: {e:?}");
|
||||
});
|
||||
|
||||
info!("Attempting to handle event ID {event_id} as backfilled PDU");
|
||||
self.services
|
||||
.rooms
|
||||
.timeline
|
||||
.backfill_pdu(&server, response.pdu, &pub_key_map)
|
||||
.await?;
|
||||
|
||||
let json_text = serde_json::to_string_pretty(&json).expect("canonical json is valid json");
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"{}\n```json\n{}\n```",
|
||||
"Got PDU from specified server and handled as backfilled PDU successfully. Event body:", json_text
|
||||
)))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Remote server did not have PDU or failed sending request to remote server: {e}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result<RoomMessageEventContent> {
|
||||
let room_id = self.services.rooms.alias.resolve(&room).await?;
|
||||
let room_state = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.room_state_full(&room_id)
|
||||
.await?
|
||||
.values()
|
||||
.map(|pdu| pdu.to_state_event())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if room_state.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Unable to find room state in our database (vector is empty)",
|
||||
));
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&room_state).map_err(|e| {
|
||||
warn!("Failed converting room state vector in our database to pretty JSON: {e}");
|
||||
Error::bad_database(
|
||||
"Failed to convert room state events to pretty JSON, possible invalid room state events in our database",
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("```json\n{json}\n```")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn ping(&self, server: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
||||
if server == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves.",
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
|
||||
match self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(&server, ruma::api::federation::discovery::get_server_version::v1::Request {})
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
let ping_time = timer.elapsed();
|
||||
|
||||
let json_text_res = serde_json::to_string_pretty(&response.server);
|
||||
|
||||
if let Ok(json) = json_text_res {
|
||||
return Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Got response which took {ping_time:?} time:\n```json\n{json}\n```"
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Got non-JSON response which took {ping_time:?} time:\n{response:?}"
|
||||
)))
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Failed sending federation request to specified server from ping debug command: {e}");
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed sending federation request to specified server:\n\n{e}",
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn force_device_list_updates(&self) -> Result<RoomMessageEventContent> {
|
||||
// Force E2EE device list updates for all users
|
||||
for user_id in self.services.users.iter().filter_map(Result::ok) {
|
||||
self.services.users.mark_device_key_update(&user_id)?;
|
||||
}
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Marked all devices for all users as having new keys to update",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn change_log_level(&self, filter: Option<String>, reset: bool) -> Result<RoomMessageEventContent> {
|
||||
let handles = &["console"];
|
||||
|
||||
if reset {
|
||||
let old_filter_layer = match EnvFilter::try_new(&self.services.globals.config.log) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Log level from config appears to be invalid now: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
match self
|
||||
.services
|
||||
.server
|
||||
.log
|
||||
.reload
|
||||
.reload(&old_filter_layer, Some(handles))
|
||||
{
|
||||
Ok(()) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully changed log level back to config value {}",
|
||||
self.services.globals.config.log
|
||||
)));
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to modify and reload the global tracing log level: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(filter) = filter {
|
||||
let new_filter_layer = match EnvFilter::try_new(filter) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Invalid log level filter specified: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
match self
|
||||
.services
|
||||
.server
|
||||
.log
|
||||
.reload
|
||||
.reload(&new_filter_layer, Some(handles))
|
||||
{
|
||||
Ok(()) => {
|
||||
return Ok(RoomMessageEventContent::text_plain("Successfully changed log level"));
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to modify and reload the global tracing log level: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain("No log level was specified."))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn sign_json(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(mut value) => {
|
||||
ruma::signatures::sign_json(
|
||||
self.services.globals.server_name().as_str(),
|
||||
self.services.globals.keypair(),
|
||||
&mut value,
|
||||
)
|
||||
.expect("our request json is what ruma expects");
|
||||
let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
|
||||
Ok(RoomMessageEventContent::text_plain(json_text))
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn verify_json(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let string = self.body[1..self.body.len().checked_sub(1).unwrap()].join("\n");
|
||||
match serde_json::from_str(&string) {
|
||||
Ok(value) => {
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
self.services
|
||||
.server_keys
|
||||
.fetch_required_signing_keys([&value], &pub_key_map)
|
||||
.await?;
|
||||
|
||||
let pub_key_map = pub_key_map.read().await;
|
||||
match ruma::signatures::verify_json(&pub_key_map, &value) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Signature verification failed: {e}"
|
||||
))),
|
||||
}
|
||||
},
|
||||
Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn first_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let first_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.first_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the first PDU in database"))?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!("{first_pdu:?}")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn latest_pdu_in_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let latest_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub(super) async fn force_set_room_state_from_server(
|
||||
&self, room_id: Box<RoomId>, server_name: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.server_in_room(&self.services.globals.config.server_name, &room_id)?
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"We are not participating in the room / we don't know about the room ID.",
|
||||
));
|
||||
}
|
||||
|
||||
let first_pdu = self
|
||||
.services
|
||||
.rooms
|
||||
.timeline
|
||||
.latest_pdu_in_room(&room_id)?
|
||||
.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
|
||||
|
||||
let room_version = self.services.rooms.state.get_room_version(&room_id)?;
|
||||
|
||||
let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
|
||||
let pub_key_map = RwLock::new(BTreeMap::new());
|
||||
|
||||
let remote_state_response = self
|
||||
.services
|
||||
.sending
|
||||
.send_federation_request(
|
||||
&server_name,
|
||||
get_room_state::v1::Request {
|
||||
room_id: room_id.clone().into(),
|
||||
event_id: first_pdu.event_id.clone().into(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut events = Vec::with_capacity(remote_state_response.pdus.len());
|
||||
|
||||
for pdu in remote_state_response.pdus.clone() {
|
||||
events.push(match self.services.rooms.event_handler.parse_incoming_pdu(&pdu) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
warn!("Could not parse PDU, ignoring: {e}");
|
||||
continue;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
info!("Fetching required signing keys for all the state events we got");
|
||||
self.services
|
||||
.server_keys
|
||||
.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
|
||||
.await?;
|
||||
|
||||
info!("Going through room_state response PDUs");
|
||||
for result in remote_state_response
|
||||
.pdus
|
||||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
|
||||
{
|
||||
let Ok((event_id, value)) = result.await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
|
||||
debug_error!("Invalid PDU in fetching remote room state PDUs response: {value:#?}");
|
||||
err!(BadServerResponse(debug_error!("Invalid PDU in send_join response: {e:?}")))
|
||||
})?;
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&event_id, &value)?;
|
||||
if let Some(state_key) = &pdu.state_key {
|
||||
let shortstatekey = self
|
||||
.services
|
||||
.rooms
|
||||
.short
|
||||
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
|
||||
state.insert(shortstatekey, pdu.event_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
info!("Going through auth_chain response");
|
||||
for result in remote_state_response
|
||||
.auth_chain
|
||||
.iter()
|
||||
.map(|pdu| validate_and_add_event_id(self.services, pdu, &room_version, &pub_key_map))
|
||||
{
|
||||
let Ok((event_id, value)) = result.await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.services
|
||||
.rooms
|
||||
.outlier
|
||||
.add_pdu_outlier(&event_id, &value)?;
|
||||
}
|
||||
|
||||
let new_room_state = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.resolve_state(room_id.clone().as_ref(), &room_version, state)
|
||||
.await?;
|
||||
|
||||
info!("Forcing new room state");
|
||||
let (short_state_hash, new, removed) = self
|
||||
.services
|
||||
.rooms
|
||||
.state_compressor
|
||||
.save_state(room_id.clone().as_ref(), new_room_state)?;
|
||||
|
||||
let state_lock = self.services.rooms.state.mutex.lock(&room_id).await;
|
||||
self.services
|
||||
.rooms
|
||||
.state
|
||||
.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
|
||||
.await?;
|
||||
|
||||
info!(
|
||||
"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
|
||||
m.room.member state"
|
||||
);
|
||||
self.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.update_joined_count(&room_id)?;
|
||||
|
||||
drop(state_lock);
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Successfully forced the room state from the requested remote server.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_signing_keys(
|
||||
&self, server_name: Option<Box<ServerName>>, _cached: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||
let signing_keys = self.services.globals.signing_keys_for(&server_name)?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"```rs\n{signing_keys:#?}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[allow(dead_code)]
|
||||
pub(super) async fn get_verify_keys(
|
||||
&self, server_name: Option<Box<ServerName>>, cached: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let server_name = server_name.unwrap_or_else(|| self.services.server.config.server_name.clone().into());
|
||||
let mut out = String::new();
|
||||
|
||||
if cached {
|
||||
writeln!(out, "| Key ID | VerifyKey |")?;
|
||||
writeln!(out, "| --- | --- |")?;
|
||||
for (key_id, verify_key) in self.services.globals.verify_keys_for(&server_name)? {
|
||||
writeln!(out, "| {key_id} | {verify_key:?} |")?;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::notice_markdown(out));
|
||||
}
|
||||
|
||||
let signature_ids: Vec<String> = Vec::new();
|
||||
let keys = self
|
||||
.services
|
||||
.server_keys
|
||||
.fetch_signing_keys_for_server(&server_name, signature_ids)
|
||||
.await?;
|
||||
|
||||
writeln!(out, "| Key ID | Public Key |")?;
|
||||
writeln!(out, "| --- | --- |")?;
|
||||
for (key_id, key) in keys {
|
||||
writeln!(out, "| {key_id} | {key} |")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn resolve_true_destination(
|
||||
&self, server_name: Box<ServerName>, no_cache: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if !self.services.globals.config.allow_federation {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Federation is disabled on this homeserver.",
|
||||
));
|
||||
}
|
||||
|
||||
if server_name == self.services.globals.config.server_name {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
|
||||
));
|
||||
}
|
||||
|
||||
let actual = self
|
||||
.services
|
||||
.resolver
|
||||
.resolve_actual_dest(&server_name, !no_cache)
|
||||
.await?;
|
||||
|
||||
let msg = format!("Destination: {}\nHostname URI: {}", actual.dest, actual.host,);
|
||||
|
||||
Ok(RoomMessageEventContent::text_markdown(msg))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn memory_stats(&self) -> Result<RoomMessageEventContent> {
|
||||
let html_body = conduit::alloc::memory_stats();
|
||||
|
||||
if html_body.is_none() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"malloc stats are not supported on your compiled malloc.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
"This command's output can only be viewed by clients that render HTML.".to_owned(),
|
||||
html_body.expect("string result"),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[admin_command]
|
||||
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
|
||||
let out = self.services.server.metrics.runtime_metrics().map_or_else(
|
||||
|| "Runtime metrics are not available.".to_owned(),
|
||||
|metrics| format!("```rs\n{metrics:#?}\n```"),
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_markdown(out))
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_unstable))]
|
||||
#[admin_command]
|
||||
pub(super) async fn runtime_metrics(&self) -> Result<RoomMessageEventContent> {
|
||||
Ok(RoomMessageEventContent::text_markdown(
|
||||
"Runtime metrics require building with `tokio_unstable`.",
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(tokio_unstable)]
|
||||
#[admin_command]
|
||||
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
|
||||
let out = self.services.server.metrics.runtime_interval().map_or_else(
|
||||
|| "Runtime metrics are not available.".to_owned(),
|
||||
|metrics| format!("```rs\n{metrics:#?}\n```"),
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::text_markdown(out))
|
||||
}
|
||||
|
||||
#[cfg(not(tokio_unstable))]
|
||||
#[admin_command]
|
||||
pub(super) async fn runtime_interval(&self) -> Result<RoomMessageEventContent> {
|
||||
Ok(RoomMessageEventContent::text_markdown(
|
||||
"Runtime metrics require building with `tokio_unstable`.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn time(&self) -> Result<RoomMessageEventContent> {
|
||||
let now = SystemTime::now();
|
||||
Ok(RoomMessageEventContent::text_markdown(utils::time::format(now, "%+")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_dependencies(&self, names: bool) -> Result<RoomMessageEventContent> {
|
||||
if names {
|
||||
let out = info::cargo::dependencies_names().join(" ");
|
||||
return Ok(RoomMessageEventContent::notice_markdown(out));
|
||||
}
|
||||
|
||||
let deps = info::cargo::dependencies();
|
||||
let mut out = String::new();
|
||||
writeln!(out, "| name | version | features |")?;
|
||||
writeln!(out, "| ---- | ------- | -------- |")?;
|
||||
for (name, dep) in deps {
|
||||
let version = dep.try_req().unwrap_or("*");
|
||||
let feats = dep.req_features();
|
||||
let feats = if !feats.is_empty() {
|
||||
feats.join(" ")
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
writeln!(out, "{name} | {version} | {feats}")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn database_stats(
|
||||
&self, property: Option<String>, map: Option<String>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let property = property.unwrap_or_else(|| "rocksdb.stats".to_owned());
|
||||
let map_name = map.as_ref().map_or(utils::string::EMPTY, String::as_str);
|
||||
|
||||
let mut out = String::new();
|
||||
for (name, map) in self.services.db.iter_maps() {
|
||||
if !map_name.is_empty() && *map_name != *name {
|
||||
continue;
|
||||
}
|
||||
|
||||
let res = map.property(&property)?;
|
||||
let res = res.trim();
|
||||
writeln!(out, "##### {name}:\n```\n{res}\n```")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
mod commands;
|
||||
pub(crate) mod tester;
|
||||
|
||||
use clap::Subcommand;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, ServerName};
|
||||
use conduit::Result;
|
||||
use ruma::{EventId, OwnedRoomOrAliasId, RoomId, ServerName};
|
||||
|
||||
use self::debug_commands::{
|
||||
change_log_level, force_device_list_updates, get_auth_chain, get_pdu, get_remote_pdu, get_remote_pdu_list,
|
||||
get_room_state, memory_stats, parse_pdu, ping, resolve_true_destination, sign_json, verify_json,
|
||||
};
|
||||
use crate::Result;
|
||||
use self::tester::TesterCommand;
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
pub(crate) mod debug_commands;
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum DebugCommand {
|
||||
/// - Echo input of admin command
|
||||
Echo {
|
||||
message: Vec<String>,
|
||||
},
|
||||
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum DebugCommand {
|
||||
/// - Get the auth_chain of a PDU
|
||||
GetAuthChain {
|
||||
/// An event ID (the $ character followed by the base64 reference hash)
|
||||
@@ -34,8 +38,8 @@ pub(crate) enum DebugCommand {
|
||||
},
|
||||
|
||||
/// - Attempts to retrieve a PDU from a remote server. Inserts it into our
|
||||
/// database/timeline if found and we do not have this PDU already
|
||||
/// (following normal event auth rules, handles it as an incoming PDU).
|
||||
/// database/timeline if found and we do not have this PDU already
|
||||
/// (following normal event auth rules, handles it as an incoming PDU).
|
||||
GetRemotePdu {
|
||||
/// An event ID (a $ followed by the base64 reference hash)
|
||||
event_id: Box<EventId>,
|
||||
@@ -45,8 +49,8 @@ pub(crate) enum DebugCommand {
|
||||
server: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// Same as `get-remote-pdu` but accepts a codeblock newline delimited list
|
||||
/// of PDUs and a single server to fetch from
|
||||
/// - Same as `get-remote-pdu` but accepts a codeblock newline delimited
|
||||
/// list of PDUs and a single server to fetch from
|
||||
GetRemotePduList {
|
||||
/// Argument for us to attempt to fetch all the events from the
|
||||
/// specified remote server.
|
||||
@@ -69,7 +73,15 @@ pub(crate) enum DebugCommand {
|
||||
/// Of course the check is still done on the actual client API.
|
||||
GetRoomState {
|
||||
/// Room ID
|
||||
room_id: Box<RoomId>,
|
||||
room_id: OwnedRoomOrAliasId,
|
||||
},
|
||||
|
||||
/// - Get and display signing keys from local cache or remote server.
|
||||
GetSigningKeys {
|
||||
server_name: Option<Box<ServerName>>,
|
||||
|
||||
#[arg(short, long)]
|
||||
cached: bool,
|
||||
},
|
||||
|
||||
/// - Sends a federation request to the remote server's
|
||||
@@ -107,6 +119,41 @@ pub(crate) enum DebugCommand {
|
||||
/// the command.
|
||||
VerifyJson,
|
||||
|
||||
/// - Prints the very first PDU in the specified room (typically
|
||||
/// m.room.create)
|
||||
FirstPduInRoom {
|
||||
/// The room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Prints the latest ("last") PDU in the specified room (typically a
|
||||
/// message)
|
||||
LatestPduInRoom {
|
||||
/// The room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Forcefully replaces the room state of our local copy of the specified
|
||||
/// room, with the copy (auth chain and room state events) the specified
|
||||
/// remote server says.
|
||||
///
|
||||
/// A common desire for room deletion is to simply "reset" our copy of the
|
||||
/// room. While this admin command is not a replacement for that, if you
|
||||
/// know you have split/broken room state and you know another server in the
|
||||
/// room that has the best/working room state, this command can let you use
|
||||
/// their room state. Such example is your server saying users are in a
|
||||
/// room, but other servers are saying they're not in the room in question.
|
||||
///
|
||||
/// This command will get the latest PDU in the room we know about, and
|
||||
/// request the room state at that point in time via
|
||||
/// `/_matrix/federation/v1/state/{roomId}`.
|
||||
ForceSetRoomStateFromServer {
|
||||
/// The impacted room ID
|
||||
room_id: Box<RoomId>,
|
||||
/// The server we will use to query the room state for
|
||||
server_name: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// - Runs a server name through conduwuit's true destination resolution
|
||||
/// process
|
||||
///
|
||||
@@ -120,42 +167,34 @@ pub(crate) enum DebugCommand {
|
||||
|
||||
/// - Print extended memory usage
|
||||
MemoryStats,
|
||||
}
|
||||
|
||||
pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
Ok(match command {
|
||||
DebugCommand::GetAuthChain {
|
||||
event_id,
|
||||
} => get_auth_chain(body, event_id).await?,
|
||||
DebugCommand::ParsePdu => parse_pdu(body).await?,
|
||||
DebugCommand::GetPdu {
|
||||
event_id,
|
||||
} => get_pdu(body, event_id).await?,
|
||||
DebugCommand::GetRemotePdu {
|
||||
event_id,
|
||||
server,
|
||||
} => get_remote_pdu(body, event_id, server).await?,
|
||||
DebugCommand::GetRoomState {
|
||||
room_id,
|
||||
} => get_room_state(body, room_id).await?,
|
||||
DebugCommand::Ping {
|
||||
server,
|
||||
} => ping(body, server).await?,
|
||||
DebugCommand::ForceDeviceListUpdates => force_device_list_updates(body).await?,
|
||||
DebugCommand::ChangeLogLevel {
|
||||
filter,
|
||||
reset,
|
||||
} => change_log_level(body, filter, reset).await?,
|
||||
DebugCommand::SignJson => sign_json(body).await?,
|
||||
DebugCommand::VerifyJson => verify_json(body).await?,
|
||||
DebugCommand::GetRemotePduList {
|
||||
server,
|
||||
force,
|
||||
} => get_remote_pdu_list(body, server, force).await?,
|
||||
DebugCommand::ResolveTrueDestination {
|
||||
server_name,
|
||||
no_cache,
|
||||
} => resolve_true_destination(body, server_name, no_cache).await?,
|
||||
DebugCommand::MemoryStats => memory_stats(),
|
||||
})
|
||||
/// - Print general tokio runtime metric totals.
|
||||
RuntimeMetrics,
|
||||
|
||||
/// - Print detailed tokio runtime metrics accumulated since last command
|
||||
/// invocation.
|
||||
RuntimeInterval,
|
||||
|
||||
/// - Print the current time
|
||||
Time,
|
||||
|
||||
/// - List dependencies
|
||||
ListDependencies {
|
||||
#[arg(short, long)]
|
||||
names: bool,
|
||||
},
|
||||
|
||||
/// - Get database statistics
|
||||
DatabaseStats {
|
||||
property: Option<String>,
|
||||
|
||||
#[arg(short, long, alias("column"))]
|
||||
map: Option<String>,
|
||||
},
|
||||
|
||||
/// - Developer test stubs
|
||||
#[command(subcommand)]
|
||||
#[allow(non_snake_case)]
|
||||
#[clap(hide(true))]
|
||||
Tester(TesterCommand),
|
||||
}
|
||||
53
src/admin/debug/tester.rs
Normal file
53
src/admin/debug/tester.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use conduit::Err;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch, Result};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub(crate) enum TesterCommand {
|
||||
Panic,
|
||||
Failure,
|
||||
Tester,
|
||||
Timer,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[admin_command]
|
||||
async fn panic(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
panic!("panicked")
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[admin_command]
|
||||
async fn failure(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
Err!("failed")
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[rustfmt::skip]
|
||||
#[admin_command]
|
||||
async fn tester(&self) -> Result<RoomMessageEventContent> {
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("completed"))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[rustfmt::skip]
|
||||
#[admin_command]
|
||||
async fn timer(&self) -> Result<RoomMessageEventContent> {
|
||||
let started = std::time::Instant::now();
|
||||
timed(self.body);
|
||||
|
||||
let elapsed = started.elapsed();
|
||||
Ok(RoomMessageEventContent::notice_plain(format!("completed in {elapsed:#?}")))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[rustfmt::skip]
|
||||
#[allow(unused_variables)]
|
||||
fn timed(body: &[&str]) {
|
||||
|
||||
}
|
||||
@@ -1,40 +1,45 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId, ServerName, UserId};
|
||||
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info},
|
||||
services,
|
||||
utils::HtmlEscape,
|
||||
Result,
|
||||
};
|
||||
use crate::{admin_command, escape_html, get_room_info};
|
||||
|
||||
pub(crate) async fn disable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
services().rooms.metadata.disable_room(&room_id, true)?;
|
||||
#[admin_command]
|
||||
pub(super) async fn disable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||
Ok(RoomMessageEventContent::text_plain("Room disabled."))
|
||||
}
|
||||
|
||||
pub(crate) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
services().rooms.metadata.disable_room(&room_id, false)?;
|
||||
#[admin_command]
|
||||
pub(super) async fn enable_room(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
self.services.rooms.metadata.disable_room(&room_id, false)?;
|
||||
Ok(RoomMessageEventContent::text_plain("Room enabled."))
|
||||
}
|
||||
|
||||
pub(crate) async fn incoming_federeation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
let map = services().globals.roomid_federationhandletime.read().await;
|
||||
#[admin_command]
|
||||
pub(super) async fn incoming_federation(&self) -> Result<RoomMessageEventContent> {
|
||||
let map = self
|
||||
.services
|
||||
.rooms
|
||||
.event_handler
|
||||
.federation_handletime
|
||||
.read()
|
||||
.expect("locked");
|
||||
let mut msg = format!("Handling {} incoming pdus:\n", map.len());
|
||||
|
||||
for (r, (e, i)) in map.iter() {
|
||||
let elapsed = i.elapsed();
|
||||
let _ = writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60);
|
||||
writeln!(msg, "{} {}: {}m{}s", r, e, elapsed.as_secs() / 60, elapsed.as_secs() % 60)?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(&msg))
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_support_well_known(
|
||||
_body: Vec<&str>, server_name: Box<ServerName>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let response = services()
|
||||
.globals
|
||||
#[admin_command]
|
||||
pub(super) async fn fetch_support_well_known(&self, server_name: Box<ServerName>) -> Result<RoomMessageEventContent> {
|
||||
let response = self
|
||||
.services
|
||||
.client
|
||||
.default
|
||||
.get(format!("https://{server_name}/.well-known/matrix/support"))
|
||||
@@ -67,34 +72,32 @@ pub(crate) async fn fetch_support_well_known(
|
||||
},
|
||||
};
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Got JSON response:\n\n```json\n{pretty_json}\n```"),
|
||||
format!(
|
||||
"<p>Got JSON response:</p>\n<pre><code class=\"language-json\">{}\n</code></pre>\n",
|
||||
HtmlEscape(&pretty_json)
|
||||
),
|
||||
))
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Got JSON response:\n\n```json\n{pretty_json}\n```"
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
|
||||
if user_id.server_name() == services().globals.config.server_name {
|
||||
#[admin_command]
|
||||
pub(super) async fn remote_user_in_rooms(&self, user_id: Box<UserId>) -> Result<RoomMessageEventContent> {
|
||||
if user_id.server_name() == self.services.globals.config.server_name {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"User belongs to our server, please use `list-joined-rooms` user admin command instead.",
|
||||
));
|
||||
}
|
||||
|
||||
if !services().users.exists(&user_id)? {
|
||||
if !self.services.users.exists(&user_id)? {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Remote user does not exist in our database.",
|
||||
));
|
||||
}
|
||||
|
||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = services()
|
||||
let mut rooms: Vec<(OwnedRoomId, u64, String)> = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.rooms_joined(&user_id)
|
||||
.filter_map(Result::ok)
|
||||
.map(|room_id| get_room_info(&room_id))
|
||||
.map(|room_id| get_room_info(self.services, &room_id))
|
||||
.collect();
|
||||
|
||||
if rooms.is_empty() {
|
||||
@@ -105,7 +108,8 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms {user_id} shares with us:\n{}",
|
||||
"Rooms {user_id} shares with us ({}):\n{}",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
|
||||
@@ -113,19 +117,20 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
|
||||
.join("\n")
|
||||
);
|
||||
let output_html = format!(
|
||||
"<table><caption>Rooms {user_id} shares with \
|
||||
us</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
"<table><caption>Rooms {user_id} shares with us \
|
||||
({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (id, members, name)| {
|
||||
writeln!(
|
||||
output,
|
||||
"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
|
||||
escape_html(id.as_ref()),
|
||||
id,
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
42
src/admin/federation/mod.rs
Normal file
42
src/admin/federation/mod.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{RoomId, ServerName, UserId};
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum FederationCommand {
|
||||
/// - List all rooms we are currently handling an incoming pdu from
|
||||
IncomingFederation,
|
||||
|
||||
/// - Disables incoming federation handling for a room.
|
||||
DisableRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Enables incoming federation handling for a room again.
|
||||
EnableRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Fetch `/.well-known/matrix/support` from the specified server
|
||||
///
|
||||
/// Despite the name, this is not a federation endpoint and does not go
|
||||
/// through the federation / server resolution process as per-spec this is
|
||||
/// supposed to be served at the server_name.
|
||||
///
|
||||
/// Respecting homeservers put this file here for listing administration,
|
||||
/// moderation, and security inquiries. This command provides a way to
|
||||
/// easily fetch that information.
|
||||
FetchSupportWellKnown {
|
||||
server_name: Box<ServerName>,
|
||||
},
|
||||
|
||||
/// - Lists all the rooms we share/track with the specified *remote* user
|
||||
RemoteUserInRooms {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
}
|
||||
314
src/admin/media/commands.rs
Normal file
314
src/admin/media/commands.rs
Normal file
@@ -0,0 +1,314 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use conduit::{debug, info, trace, utils::time::parse_timepoint_ago, warn, Result};
|
||||
use conduit_service::media::Dim;
|
||||
use ruma::{
|
||||
events::room::message::RoomMessageEventContent, EventId, Mxc, MxcUri, OwnedMxcUri, OwnedServerName, ServerName,
|
||||
};
|
||||
|
||||
use crate::{admin_command, utils::parse_local_user_id};
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete(
|
||||
&self, mxc: Option<Box<MxcUri>>, event_id: Option<Box<EventId>>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if event_id.is_some() && mxc.is_some() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Please specify either an MXC or an event ID, not both.",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(mxc) = mxc {
|
||||
debug!("Got MXC URL: {mxc}");
|
||||
self.services
|
||||
.media
|
||||
.delete(&mxc.as_str().try_into()?)
|
||||
.await?;
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Deleted the MXC from our database and on our filesystem.",
|
||||
));
|
||||
} else if let Some(event_id) = event_id {
|
||||
debug!("Got event ID to delete media from: {event_id}");
|
||||
|
||||
let mut mxc_urls = vec![];
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
// parsing the PDU for any MXC URLs begins here
|
||||
if let Some(event_json) = self.services.rooms.timeline.get_pdu_json(&event_id)? {
|
||||
if let Some(content_key) = event_json.get("content") {
|
||||
debug!("Event ID has \"content\".");
|
||||
let content_obj = content_key.as_object();
|
||||
|
||||
if let Some(content) = content_obj {
|
||||
// 1. attempts to parse the "url" key
|
||||
debug!("Attempting to go into \"url\" key for main media file");
|
||||
if let Some(url) = content.get("url") {
|
||||
debug!("Got a URL in the event ID {event_id}: {url}");
|
||||
|
||||
if url.to_string().starts_with("\"mxc://") {
|
||||
debug!("Pushing URL {url} to list of MXCs to delete");
|
||||
let final_url = url.to_string().replace('"', "");
|
||||
mxc_urls.push(final_url);
|
||||
} else {
|
||||
info!("Found a URL in the event ID {event_id} but did not start with mxc://, ignoring");
|
||||
}
|
||||
}
|
||||
|
||||
// 2. attempts to parse the "info" key
|
||||
debug!("Attempting to go into \"info\" key for thumbnails");
|
||||
if let Some(info_key) = content.get("info") {
|
||||
debug!("Event ID has \"info\".");
|
||||
let info_obj = info_key.as_object();
|
||||
|
||||
if let Some(info) = info_obj {
|
||||
if let Some(thumbnail_url) = info.get("thumbnail_url") {
|
||||
debug!("Found a thumbnail_url in info key: {thumbnail_url}");
|
||||
|
||||
if thumbnail_url.to_string().starts_with("\"mxc://") {
|
||||
debug!("Pushing thumbnail URL {thumbnail_url} to list of MXCs to delete");
|
||||
let final_thumbnail_url = thumbnail_url.to_string().replace('"', "");
|
||||
mxc_urls.push(final_thumbnail_url);
|
||||
} else {
|
||||
info!(
|
||||
"Found a thumbnail URL in the event ID {event_id} but did not start with \
|
||||
mxc://, ignoring"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!("No \"thumbnail_url\" key in \"info\" key, assuming no thumbnails.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. attempts to parse the "file" key
|
||||
debug!("Attempting to go into \"file\" key");
|
||||
if let Some(file_key) = content.get("file") {
|
||||
debug!("Event ID has \"file\".");
|
||||
let file_obj = file_key.as_object();
|
||||
|
||||
if let Some(file) = file_obj {
|
||||
if let Some(url) = file.get("url") {
|
||||
debug!("Found url in file key: {url}");
|
||||
|
||||
if url.to_string().starts_with("\"mxc://") {
|
||||
debug!("Pushing URL {url} to list of MXCs to delete");
|
||||
let final_url = url.to_string().replace('"', "");
|
||||
mxc_urls.push(final_url);
|
||||
} else {
|
||||
info!(
|
||||
"Found a URL in the event ID {event_id} but did not start with mxc://, \
|
||||
ignoring"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
info!("No \"url\" key in \"file\" key.");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Event ID does not have a \"content\" key or failed parsing the event ID JSON.",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Event ID does not have a \"content\" key, this is not a message or an event type that contains \
|
||||
media.",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Event ID does not exist or is not known to us.",
|
||||
));
|
||||
}
|
||||
|
||||
if mxc_urls.is_empty() {
|
||||
// we shouldn't get here (should have errored earlier) but just in case for
|
||||
// whatever reason we do...
|
||||
info!("Parsed event ID {event_id} but did not contain any MXC URLs.");
|
||||
return Ok(RoomMessageEventContent::text_plain("Parsed event ID but found no MXC URLs."));
|
||||
}
|
||||
|
||||
for mxc_url in mxc_urls {
|
||||
self.services
|
||||
.media
|
||||
.delete(&mxc_url.as_str().try_into()?)
|
||||
.await?;
|
||||
mxc_deletion_count = mxc_deletion_count.saturating_add(1);
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {mxc_deletion_count} total MXCs from our database and the filesystem from event ID {event_id}."
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Please specify either an MXC using --mxc or an event ID using --event-id of the message containing an image. \
|
||||
See --help for details.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_list(&self) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let mxc_list = self
|
||||
.body
|
||||
.to_vec()
|
||||
.drain(1..self.body.len().checked_sub(1).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut mxc_deletion_count: usize = 0;
|
||||
|
||||
for mxc in mxc_list {
|
||||
debug!("Deleting MXC {mxc} in bulk");
|
||||
self.services.media.delete(&mxc.try_into()?).await?;
|
||||
mxc_deletion_count = mxc_deletion_count
|
||||
.checked_add(1)
|
||||
.expect("mxc_deletion_count should not get this high");
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_past_remote_media(&self, duration: String, force: bool) -> Result<RoomMessageEventContent> {
|
||||
let duration = parse_timepoint_ago(&duration)?;
|
||||
let deleted_count = self
|
||||
.services
|
||||
.media
|
||||
.delete_all_remote_media_at_after_time(duration, force)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {deleted_count} total files.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_user(&self, username: String, force: bool) -> Result<RoomMessageEventContent> {
|
||||
let user_id = parse_local_user_id(self.services, &username)?;
|
||||
|
||||
let deleted_count = self
|
||||
.services
|
||||
.media
|
||||
.delete_from_user(&user_id, force)
|
||||
.await?;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {deleted_count} total files.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn delete_all_from_server(
|
||||
&self, server_name: Box<ServerName>, force: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
if server_name == self.services.globals.server_name() {
|
||||
return Ok(RoomMessageEventContent::text_plain("This command only works for remote media."));
|
||||
}
|
||||
|
||||
let Ok(all_mxcs) = self.services.media.get_all_mxcs().await else {
|
||||
return Ok(RoomMessageEventContent::text_plain("Failed to get MXC URIs from our database"));
|
||||
};
|
||||
|
||||
let mut deleted_count: usize = 0;
|
||||
|
||||
for mxc in all_mxcs {
|
||||
let mxc_server_name = match mxc.server_name() {
|
||||
Ok(server_name) => server_name,
|
||||
Err(e) => {
|
||||
if force {
|
||||
warn!("Failed to parse MXC {mxc} server name from database, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse MXC {mxc} server name from database: {e}",
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
if mxc_server_name != server_name || self.services.globals.server_is_ours(mxc_server_name) {
|
||||
trace!("skipping MXC URI {mxc}");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
|
||||
match self.services.media.delete(&mxc).await {
|
||||
Ok(()) => {
|
||||
deleted_count = deleted_count.saturating_add(1);
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
warn!("Failed to delete {mxc}, ignoring error and skipping: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!("Failed to delete MXC {mxc}: {e}")));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Deleted {deleted_count} total files.",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_file_info(&self, mxc: OwnedMxcUri) -> Result<RoomMessageEventContent> {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let metadata = self.services.media.get_metadata(&mxc);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!("```\n{metadata:#?}\n```")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_file(
|
||||
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let timeout = Duration::from_millis(timeout.into());
|
||||
let mut result = self
|
||||
.services
|
||||
.media
|
||||
.fetch_remote_content(&mxc, None, server.as_deref(), timeout)
|
||||
.await?;
|
||||
|
||||
// Grab the length of the content before clearing it to not flood the output
|
||||
let len = result.content.as_ref().expect("content").len();
|
||||
result.content.as_mut().expect("content").clear();
|
||||
|
||||
let out = format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```");
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn get_remote_thumbnail(
|
||||
&self, mxc: OwnedMxcUri, server: Option<OwnedServerName>, timeout: u32, width: u32, height: u32,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let mxc: Mxc<'_> = mxc.as_str().try_into()?;
|
||||
let timeout = Duration::from_millis(timeout.into());
|
||||
let dim = Dim::new(width, height, None);
|
||||
let mut result = self
|
||||
.services
|
||||
.media
|
||||
.fetch_remote_thumbnail(&mxc, None, server.as_deref(), timeout, &dim)
|
||||
.await?;
|
||||
|
||||
// Grab the length of the content before clearing it to not flood the output
|
||||
let len = result.content.as_ref().expect("content").len();
|
||||
result.content.as_mut().expect("content").clear();
|
||||
|
||||
let out = format!("```\n{result:#?}\nreceived {len} bytes for file content.\n```");
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
91
src/admin/media/mod.rs
Normal file
91
src/admin/media/mod.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{EventId, MxcUri, OwnedMxcUri, OwnedServerName, ServerName};
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum MediaCommand {
|
||||
/// - Deletes a single media file from our database and on the filesystem
|
||||
/// via a single MXC URL
|
||||
Delete {
|
||||
/// The MXC URL to delete
|
||||
#[arg(long)]
|
||||
mxc: Option<Box<MxcUri>>,
|
||||
|
||||
/// - The message event ID which contains the media and thumbnail MXC
|
||||
/// URLs
|
||||
#[arg(long)]
|
||||
event_id: Option<Box<EventId>>,
|
||||
},
|
||||
|
||||
/// - Deletes a codeblock list of MXC URLs from our database and on the
|
||||
/// filesystem
|
||||
DeleteList,
|
||||
|
||||
/// - Deletes all remote media in the last X amount of time using filesystem
|
||||
/// metadata first created at date.
|
||||
DeletePastRemoteMedia {
|
||||
/// - The duration (at or after), e.g. "5m" to delete all media in the
|
||||
/// past 5 minutes
|
||||
duration: String,
|
||||
|
||||
/// Continues deleting remote media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// - Deletes all the local media from a local user on our server
|
||||
DeleteAllFromUser {
|
||||
username: String,
|
||||
|
||||
/// Continues deleting media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// - Deletes all remote media from the specified remote server
|
||||
DeleteAllFromServer {
|
||||
server_name: Box<ServerName>,
|
||||
|
||||
/// Continues deleting media if an undeletable object is found
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
GetFileInfo {
|
||||
/// The MXC URL to lookup info for.
|
||||
mxc: OwnedMxcUri,
|
||||
},
|
||||
|
||||
GetRemoteFile {
|
||||
/// The MXC URL to fetch
|
||||
mxc: OwnedMxcUri,
|
||||
|
||||
#[arg(short, long)]
|
||||
server: Option<OwnedServerName>,
|
||||
|
||||
#[arg(short, long, default_value("10000"))]
|
||||
timeout: u32,
|
||||
},
|
||||
|
||||
GetRemoteThumbnail {
|
||||
/// The MXC URL to fetch
|
||||
mxc: OwnedMxcUri,
|
||||
|
||||
#[arg(short, long)]
|
||||
server: Option<OwnedServerName>,
|
||||
|
||||
#[arg(short, long, default_value("10000"))]
|
||||
timeout: u32,
|
||||
|
||||
#[arg(short, long)]
|
||||
width: u32,
|
||||
|
||||
#[arg(short, long)]
|
||||
height: u32,
|
||||
},
|
||||
}
|
||||
61
src/admin/mod.rs
Normal file
61
src/admin/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
#![recursion_limit = "192"]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
#![allow(clippy::enum_glob_use)]
|
||||
|
||||
pub(crate) mod admin;
|
||||
pub(crate) mod command;
|
||||
pub(crate) mod processor;
|
||||
mod tests;
|
||||
pub(crate) mod utils;
|
||||
|
||||
pub(crate) mod appservice;
|
||||
pub(crate) mod check;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod federation;
|
||||
pub(crate) mod media;
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod room;
|
||||
pub(crate) mod server;
|
||||
pub(crate) mod user;
|
||||
|
||||
extern crate conduit_api as api;
|
||||
extern crate conduit_core as conduit;
|
||||
extern crate conduit_service as service;
|
||||
|
||||
pub(crate) use conduit::Result;
|
||||
pub(crate) use conduit_macros::{admin_command, admin_command_dispatch};
|
||||
|
||||
pub(crate) use crate::{
|
||||
command::Command,
|
||||
utils::{escape_html, get_room_info},
|
||||
};
|
||||
|
||||
pub(crate) const PAGE_SIZE: usize = 100;
|
||||
|
||||
conduit::mod_ctor! {}
|
||||
conduit::mod_dtor! {}
|
||||
conduit::rustc_flags_capture! {}
|
||||
|
||||
/// Install the admin command processor
|
||||
pub async fn init(admin_service: &service::admin::Service) {
|
||||
_ = admin_service
|
||||
.complete
|
||||
.write()
|
||||
.expect("locked for writing")
|
||||
.insert(processor::complete);
|
||||
_ = admin_service
|
||||
.handle
|
||||
.write()
|
||||
.await
|
||||
.insert(processor::dispatch);
|
||||
}
|
||||
|
||||
/// Uninstall the admin command handler
|
||||
pub async fn fini(admin_service: &service::admin::Service) {
|
||||
_ = admin_service.handle.write().await.take();
|
||||
_ = admin_service
|
||||
.complete
|
||||
.write()
|
||||
.expect("locked for writing")
|
||||
.take();
|
||||
}
|
||||
261
src/admin/processor.rs
Normal file
261
src/admin/processor.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
use std::{
|
||||
fmt::Write,
|
||||
panic::AssertUnwindSafe,
|
||||
sync::{Arc, Mutex},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use clap::{CommandFactory, Parser};
|
||||
use conduit::{
|
||||
debug, error,
|
||||
log::{
|
||||
capture,
|
||||
capture::Capture,
|
||||
fmt::{markdown_table, markdown_table_head},
|
||||
},
|
||||
trace,
|
||||
utils::string::{collect_stream, common_prefix},
|
||||
Error, Result,
|
||||
};
|
||||
use futures_util::future::FutureExt;
|
||||
use ruma::{
|
||||
events::{
|
||||
relation::InReplyTo,
|
||||
room::message::{Relation::Reply, RoomMessageEventContent},
|
||||
},
|
||||
EventId,
|
||||
};
|
||||
use service::{
|
||||
admin::{CommandInput, CommandOutput, ProcessorFuture, ProcessorResult},
|
||||
Services,
|
||||
};
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
||||
|
||||
use crate::{admin, admin::AdminCommand, Command};
|
||||
|
||||
#[must_use]
|
||||
pub(super) fn complete(line: &str) -> String { complete_command(AdminCommand::command(), line) }
|
||||
|
||||
#[must_use]
|
||||
pub(super) fn dispatch(services: Arc<Services>, command: CommandInput) -> ProcessorFuture {
|
||||
Box::pin(handle_command(services, command))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, name = "admin")]
|
||||
async fn handle_command(services: Arc<Services>, command: CommandInput) -> ProcessorResult {
|
||||
AssertUnwindSafe(Box::pin(process_command(services, &command)))
|
||||
.catch_unwind()
|
||||
.await
|
||||
.map_err(Error::from_panic)
|
||||
.unwrap_or_else(|error| handle_panic(&error, &command))
|
||||
}
|
||||
|
||||
async fn process_command(services: Arc<Services>, input: &CommandInput) -> ProcessorResult {
|
||||
let (command, args, body) = match parse(&services, input) {
|
||||
Err(error) => return Err(error),
|
||||
Ok(parsed) => parsed,
|
||||
};
|
||||
|
||||
let context = Command {
|
||||
services: &services,
|
||||
body: &body,
|
||||
timer: SystemTime::now(),
|
||||
reply_id: input.reply_id.as_deref(),
|
||||
};
|
||||
|
||||
process(&context, command, &args).await
|
||||
}
|
||||
|
||||
fn handle_panic(error: &Error, command: &CommandInput) -> ProcessorResult {
|
||||
let link = "Please submit a [bug report](https://github.com/girlbossceo/conduwuit/issues/new). 🥺";
|
||||
let msg = format!("Panic occurred while processing command:\n```\n{error:#?}\n```\n{link}");
|
||||
let content = RoomMessageEventContent::notice_markdown(msg);
|
||||
error!("Panic while processing command: {error:?}");
|
||||
Err(reply(content, command.reply_id.as_deref()))
|
||||
}
|
||||
|
||||
// Parse and process a message from the admin room
|
||||
async fn process(context: &Command<'_>, command: AdminCommand, args: &[String]) -> ProcessorResult {
|
||||
let (capture, logs) = capture_create(context);
|
||||
|
||||
let capture_scope = capture.start();
|
||||
let result = Box::pin(admin::process(command, context)).await;
|
||||
drop(capture_scope);
|
||||
|
||||
debug!(
|
||||
ok = result.is_ok(),
|
||||
elapsed = ?context.timer.elapsed(),
|
||||
command = ?args,
|
||||
"command processed"
|
||||
);
|
||||
|
||||
let mut output = String::new();
|
||||
|
||||
// Prepend the logs only if any were captured
|
||||
let logs = logs.lock().expect("locked");
|
||||
if logs.lines().count() > 2 {
|
||||
writeln!(&mut output, "{logs}").expect("failed to format logs to command output");
|
||||
}
|
||||
drop(logs);
|
||||
|
||||
match result {
|
||||
Ok(content) => {
|
||||
write!(&mut output, "{0}", content.body()).expect("failed to format command result to output buffer");
|
||||
Ok(Some(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id)))
|
||||
},
|
||||
Err(error) => {
|
||||
write!(&mut output, "Command failed with error:\n```\n{error:#?}\n```")
|
||||
.expect("failed to format command result to output");
|
||||
Err(reply(RoomMessageEventContent::notice_markdown(output), context.reply_id))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn capture_create(context: &Command<'_>) -> (Arc<Capture>, Arc<Mutex<String>>) {
|
||||
let env_config = &context.services.server.config.admin_log_capture;
|
||||
let env_filter = EnvFilter::try_new(env_config).unwrap_or_else(|_| "debug".into());
|
||||
let log_level = env_filter
|
||||
.max_level_hint()
|
||||
.and_then(LevelFilter::into_level)
|
||||
.unwrap_or(Level::DEBUG);
|
||||
|
||||
let filter =
|
||||
move |data: capture::Data<'_>| data.level() <= log_level && data.our_modules() && data.scope.contains(&"admin");
|
||||
|
||||
let logs = Arc::new(Mutex::new(
|
||||
collect_stream(|s| markdown_table_head(s)).expect("markdown table header"),
|
||||
));
|
||||
|
||||
let capture = Capture::new(
|
||||
&context.services.server.log.capture,
|
||||
Some(filter),
|
||||
capture::fmt(markdown_table, logs.clone()),
|
||||
);
|
||||
|
||||
(capture, logs)
|
||||
}
|
||||
|
||||
// Parse chat messages from the admin room into an AdminCommand object
|
||||
fn parse<'a>(
|
||||
services: &Arc<Services>, input: &'a CommandInput,
|
||||
) -> Result<(AdminCommand, Vec<String>, Vec<&'a str>), CommandOutput> {
|
||||
let lines = input.command.lines().filter(|line| !line.trim().is_empty());
|
||||
let command_line = lines.clone().next().expect("command missing first line");
|
||||
let body = lines.skip(1).collect();
|
||||
match parse_command(command_line) {
|
||||
Ok((command, args)) => Ok((command, args, body)),
|
||||
Err(error) => {
|
||||
let message = error
|
||||
.to_string()
|
||||
.replace("server.name", services.globals.server_name().as_str());
|
||||
Err(reply(
|
||||
RoomMessageEventContent::notice_markdown(message),
|
||||
input.reply_id.as_deref(),
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_command(line: &str) -> Result<(AdminCommand, Vec<String>)> {
|
||||
let argv = parse_line(line);
|
||||
let command = AdminCommand::try_parse_from(&argv)?;
|
||||
Ok((command, argv))
|
||||
}
|
||||
|
||||
fn complete_command(mut cmd: clap::Command, line: &str) -> String {
|
||||
let argv = parse_line(line);
|
||||
let mut ret = Vec::<String>::with_capacity(argv.len().saturating_add(1));
|
||||
|
||||
'token: for token in argv.into_iter().skip(1) {
|
||||
let cmd_ = cmd.clone();
|
||||
let mut choice = Vec::new();
|
||||
|
||||
for sub in cmd_.get_subcommands() {
|
||||
let name = sub.get_name();
|
||||
if *name == token {
|
||||
// token already complete; recurse to subcommand
|
||||
ret.push(token);
|
||||
cmd.clone_from(sub);
|
||||
continue 'token;
|
||||
} else if name.starts_with(&token) {
|
||||
// partial match; add to choices
|
||||
choice.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if choice.len() == 1 {
|
||||
// One choice. Add extra space because it's complete
|
||||
let choice = *choice.first().expect("only choice");
|
||||
ret.push(choice.to_owned());
|
||||
ret.push(String::new());
|
||||
} else if choice.is_empty() {
|
||||
// Nothing found, return original string
|
||||
ret.push(token);
|
||||
} else {
|
||||
// Find the common prefix
|
||||
ret.push(common_prefix(&choice).into());
|
||||
}
|
||||
|
||||
// Return from completion
|
||||
return ret.join(" ");
|
||||
}
|
||||
|
||||
// Return from no completion. Needs a space though.
|
||||
ret.push(String::new());
|
||||
ret.join(" ")
|
||||
}
|
||||
|
||||
// Parse chat messages from the admin room into an AdminCommand object
|
||||
fn parse_line(command_line: &str) -> Vec<String> {
|
||||
let mut argv = command_line
|
||||
.split_whitespace()
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// Remove any escapes that came with a server-side escape command
|
||||
if !argv.is_empty() && argv[0].ends_with("admin") {
|
||||
argv[0] = argv[0].trim_start_matches('\\').into();
|
||||
}
|
||||
|
||||
// First indice has to be "admin" but for console convenience we add it here
|
||||
if !argv.is_empty() && !argv[0].ends_with("admin") && !argv[0].starts_with('@') {
|
||||
argv.insert(0, "admin".to_owned());
|
||||
}
|
||||
|
||||
// Replace `help command` with `command --help`
|
||||
// Clap has a help subcommand, but it omits the long help description.
|
||||
if argv.len() > 1 && argv[1] == "help" {
|
||||
argv.remove(1);
|
||||
argv.push("--help".to_owned());
|
||||
}
|
||||
|
||||
// Backwards compatibility with `register_appservice`-style commands
|
||||
if argv.len() > 1 && argv[1].contains('_') {
|
||||
argv[1] = argv[1].replace('_', "-");
|
||||
}
|
||||
|
||||
// Backwards compatibility with `register_appservice`-style commands
|
||||
if argv.len() > 2 && argv[2].contains('_') {
|
||||
argv[2] = argv[2].replace('_', "-");
|
||||
}
|
||||
|
||||
// if the user is using the `query` command (argv[1]), replace the database
|
||||
// function/table calls with underscores to match the codebase
|
||||
if argv.len() > 3 && argv[1].eq("query") {
|
||||
argv[3] = argv[3].replace('_', "-");
|
||||
}
|
||||
|
||||
trace!(?command_line, ?argv, "parse");
|
||||
argv
|
||||
}
|
||||
|
||||
fn reply(mut content: RoomMessageEventContent, reply_id: Option<&EventId>) -> RoomMessageEventContent {
|
||||
content.relates_to = reply_id.map(|event_id| Reply {
|
||||
in_reply_to: InReplyTo {
|
||||
event_id: event_id.to_owned(),
|
||||
},
|
||||
});
|
||||
|
||||
content
|
||||
}
|
||||
70
src/admin/query/account_data.rs
Normal file
70
src/admin/query/account_data.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{
|
||||
events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
|
||||
RoomId, UserId,
|
||||
};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||
pub(crate) enum AccountDataCommand {
|
||||
/// - Returns all changes to the account data that happened after `since`.
|
||||
ChangesSince {
|
||||
/// Full user ID
|
||||
user_id: Box<UserId>,
|
||||
/// UNIX timestamp since (u64)
|
||||
since: u64,
|
||||
/// Optional room ID of the account data
|
||||
room_id: Option<Box<RoomId>>,
|
||||
},
|
||||
|
||||
/// - Searches the account data for a specific kind.
|
||||
Get {
|
||||
/// Full user ID
|
||||
user_id: Box<UserId>,
|
||||
/// Account data event type
|
||||
kind: RoomAccountDataEventType,
|
||||
/// Optional room ID of the account data
|
||||
room_id: Option<Box<RoomId>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/account_data.rs
|
||||
pub(super) async fn process(subcommand: AccountDataCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
AccountDataCommand::ChangesSince {
|
||||
user_id,
|
||||
since,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.account_data
|
||||
.changes_since(room_id.as_deref(), &user_id, since)?;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
AccountDataCommand::Get {
|
||||
user_id,
|
||||
kind,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.account_data
|
||||
.get(room_id.as_deref(), &user_id, kind)?;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
49
src/admin/query/appservice.rs
Normal file
49
src/admin/query/appservice.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/appservice.rs
|
||||
pub(crate) enum AppserviceCommand {
|
||||
/// - Gets the appservice registration info/details from the ID as a string
|
||||
GetRegistration {
|
||||
/// Appservice registration ID
|
||||
appservice_id: Box<str>,
|
||||
},
|
||||
|
||||
/// - Gets all appservice registrations with their ID and registration info
|
||||
All,
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/appservice.rs
|
||||
pub(super) async fn process(subcommand: AppserviceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
AppserviceCommand::GetRegistration {
|
||||
appservice_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.appservice
|
||||
.db
|
||||
.get_registration(appservice_id.as_ref());
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
AppserviceCommand::All => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.appservice.all();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
78
src/admin/query/globals.rs
Normal file
78
src/admin/query/globals.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, ServerName};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||
pub(crate) enum GlobalsCommand {
|
||||
DatabaseVersion,
|
||||
|
||||
CurrentCount,
|
||||
|
||||
LastCheckForUpdatesId,
|
||||
|
||||
LoadKeypair,
|
||||
|
||||
/// - This returns an empty `Ok(BTreeMap<..>)` when there are no keys found
|
||||
/// for the server.
|
||||
SigningKeysFor {
|
||||
origin: Box<ServerName>,
|
||||
},
|
||||
}
|
||||
|
||||
/// All the getters and iterators from src/database/key_value/globals.rs
|
||||
pub(super) async fn process(subcommand: GlobalsCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
GlobalsCommand::DatabaseVersion => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.database_version();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
GlobalsCommand::CurrentCount => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.current_count();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
GlobalsCommand::LastCheckForUpdatesId => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.updates.last_check_for_updates_id();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
GlobalsCommand::LoadKeypair => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.load_keypair();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
GlobalsCommand::SigningKeysFor {
|
||||
origin,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.globals.db.verify_keys_for(&origin);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
60
src/admin/query/mod.rs
Normal file
60
src/admin/query/mod.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
mod account_data;
|
||||
mod appservice;
|
||||
mod globals;
|
||||
mod presence;
|
||||
mod resolver;
|
||||
mod room_alias;
|
||||
mod room_state_cache;
|
||||
mod sending;
|
||||
mod users;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
|
||||
use self::{
|
||||
account_data::AccountDataCommand, appservice::AppserviceCommand, globals::GlobalsCommand,
|
||||
presence::PresenceCommand, resolver::ResolverCommand, room_alias::RoomAliasCommand,
|
||||
room_state_cache::RoomStateCacheCommand, sending::SendingCommand, users::UsersCommand,
|
||||
};
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// Query tables from database
|
||||
pub(super) enum QueryCommand {
|
||||
/// - account_data.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
AccountData(AccountDataCommand),
|
||||
|
||||
/// - appservice.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Appservice(AppserviceCommand),
|
||||
|
||||
/// - presence.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Presence(PresenceCommand),
|
||||
|
||||
/// - rooms/alias.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
RoomAlias(RoomAliasCommand),
|
||||
|
||||
/// - rooms/state_cache iterators and getters
|
||||
#[command(subcommand)]
|
||||
RoomStateCache(RoomStateCacheCommand),
|
||||
|
||||
/// - globals.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Globals(GlobalsCommand),
|
||||
|
||||
/// - sending.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Sending(SendingCommand),
|
||||
|
||||
/// - users.rs iterators and getters
|
||||
#[command(subcommand)]
|
||||
Users(UsersCommand),
|
||||
|
||||
/// - resolver service
|
||||
#[command(subcommand)]
|
||||
Resolver(ResolverCommand),
|
||||
}
|
||||
53
src/admin/query/presence.rs
Normal file
53
src/admin/query/presence.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, UserId};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/presence.rs
|
||||
pub(crate) enum PresenceCommand {
|
||||
/// - Returns the latest presence event for the given user.
|
||||
GetPresence {
|
||||
/// Full user ID
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
/// - Iterator of the most recent presence updates that happened after the
|
||||
/// event with id `since`.
|
||||
PresenceSince {
|
||||
/// UNIX timestamp since (u64)
|
||||
since: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/presence.rs
|
||||
pub(super) async fn process(subcommand: PresenceCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
PresenceCommand::GetPresence {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.presence.db.get_presence(&user_id)?;
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
PresenceCommand::PresenceSince {
|
||||
since,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.presence.db.presence_since(since);
|
||||
let presence_since: Vec<(_, _, _)> = results.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{presence_since:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
94
src/admin/query/resolver.rs
Normal file
94
src/admin/query/resolver.rs
Normal file
@@ -0,0 +1,94 @@
|
||||
use std::fmt::Write;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::{utils::time, Result};
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedServerName};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// Resolver service and caches
|
||||
pub(crate) enum ResolverCommand {
|
||||
/// Query the destinations cache
|
||||
DestinationsCache {
|
||||
server_name: Option<OwnedServerName>,
|
||||
},
|
||||
|
||||
/// Query the overrides cache
|
||||
OverridesCache {
|
||||
name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn destinations_cache(&self, server_name: Option<OwnedServerName>) -> Result<RoomMessageEventContent> {
|
||||
use service::resolver::cache::CachedDest;
|
||||
|
||||
let mut out = String::new();
|
||||
writeln!(out, "| Server Name | Destination | Hostname | Expires |")?;
|
||||
writeln!(out, "| ----------- | ----------- | -------- | ------- |")?;
|
||||
let row = |(
|
||||
name,
|
||||
&CachedDest {
|
||||
ref dest,
|
||||
ref host,
|
||||
expire,
|
||||
},
|
||||
)| {
|
||||
let expire = time::format(expire, "%+");
|
||||
writeln!(out, "| {name} | {dest} | {host} | {expire} |").expect("wrote line");
|
||||
};
|
||||
|
||||
let map = self
|
||||
.services
|
||||
.resolver
|
||||
.cache
|
||||
.destinations
|
||||
.read()
|
||||
.expect("locked");
|
||||
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
map.get_key_value(server_name).map(row);
|
||||
} else {
|
||||
map.iter().for_each(row);
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn overrides_cache(&self, server_name: Option<String>) -> Result<RoomMessageEventContent> {
|
||||
use service::resolver::cache::CachedOverride;
|
||||
|
||||
let mut out = String::new();
|
||||
writeln!(out, "| Server Name | IP | Port | Expires |")?;
|
||||
writeln!(out, "| ----------- | --- | ----:| ------- |")?;
|
||||
let row = |(
|
||||
name,
|
||||
&CachedOverride {
|
||||
ref ips,
|
||||
port,
|
||||
expire,
|
||||
},
|
||||
)| {
|
||||
let expire = time::format(expire, "%+");
|
||||
writeln!(out, "| {name} | {ips:?} | {port} | {expire} |").expect("wrote line");
|
||||
};
|
||||
|
||||
let map = self
|
||||
.services
|
||||
.resolver
|
||||
.cache
|
||||
.overrides
|
||||
.read()
|
||||
.expect("locked");
|
||||
|
||||
if let Some(server_name) = server_name.as_ref() {
|
||||
map.get_key_value(server_name).map(row);
|
||||
} else {
|
||||
map.iter().for_each(row);
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(out))
|
||||
}
|
||||
64
src/admin/query/room_alias.rs
Normal file
64
src/admin/query/room_alias.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/rooms/alias.rs
|
||||
pub(crate) enum RoomAliasCommand {
|
||||
ResolveLocalAlias {
|
||||
/// Full room alias
|
||||
alias: Box<RoomAliasId>,
|
||||
},
|
||||
|
||||
/// - Iterator of all our local room aliases for the room ID
|
||||
LocalAliasesForRoom {
|
||||
/// Full room ID
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Iterator of all our local aliases in our database with their room IDs
|
||||
AllLocalAliases,
|
||||
}
|
||||
|
||||
/// All the getters and iterators in src/database/key_value/rooms/alias.rs
|
||||
pub(super) async fn process(subcommand: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
RoomAliasCommand::ResolveLocalAlias {
|
||||
alias,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.alias.resolve_local_alias(&alias);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomAliasCommand::LocalAliasesForRoom {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.alias.local_aliases_for_room(&room_id);
|
||||
let aliases: Vec<_> = results.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomAliasCommand::AllLocalAliases => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.alias.all_local_aliases();
|
||||
let aliases: Vec<_> = results.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{aliases:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
287
src/admin/query/room_state_cache.rs
Normal file
287
src/admin/query/room_state_cache.rs
Normal file
@@ -0,0 +1,287 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum RoomStateCacheCommand {
|
||||
ServerInRoom {
|
||||
server: Box<ServerName>,
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomServers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
ServerRooms {
|
||||
server: Box<ServerName>,
|
||||
},
|
||||
|
||||
RoomMembers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
LocalUsersInRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
ActiveLocalUsersInRoom {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomJoinedCount {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomInvitedCount {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomUserOnceJoined {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
RoomMembersInvited {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
GetInviteCount {
|
||||
room_id: Box<RoomId>,
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
GetLeftCount {
|
||||
room_id: Box<RoomId>,
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsJoined {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsLeft {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
RoomsInvited {
|
||||
user_id: Box<UserId>,
|
||||
},
|
||||
|
||||
InviteState {
|
||||
user_id: Box<UserId>,
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(
|
||||
subcommand: RoomStateCacheCommand, context: &Command<'_>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
RoomStateCacheCommand::ServerInRoom {
|
||||
server,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let result = services.rooms.state_cache.server_in_room(&server, &room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{result:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomServers {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.room_servers(&room_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::ServerRooms {
|
||||
server,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.server_rooms(&server).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomMembers {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.room_members(&room_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::LocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.local_users_in_room(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::ActiveLocalUsersInRoom {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Vec<_> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.active_local_users_in_room(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomJoinedCount {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.state_cache.room_joined_count(&room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomInvitedCount {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.state_cache.room_invited_count(&room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomUserOnceJoined {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_useroncejoined(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomMembersInvited {
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members_invited(&room_id)
|
||||
.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::GetInviteCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_invite_count(&room_id, &user_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::GetLeftCount {
|
||||
room_id,
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services
|
||||
.rooms
|
||||
.state_cache
|
||||
.get_left_count(&room_id, &user_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsJoined {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_joined(&user_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsInvited {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_invited(&user_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::RoomsLeft {
|
||||
user_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results: Result<Vec<_>> = services.rooms.state_cache.rooms_left(&user_id).collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
RoomStateCacheCommand::InviteState {
|
||||
user_id,
|
||||
room_id,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.rooms.state_cache.invite_state(&user_id, &room_id);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,81 @@
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, ServerName, UserId};
|
||||
use service::sending::Destination;
|
||||
|
||||
use super::Sending;
|
||||
use crate::{service::sending::Destination, services, Result};
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/sending.rs
|
||||
pub(crate) enum SendingCommand {
|
||||
/// - Queries database for all `servercurrentevent_data`
|
||||
ActiveRequests,
|
||||
|
||||
/// - Queries database for `servercurrentevent_data` but for a specific
|
||||
/// destination
|
||||
///
|
||||
/// This command takes only *one* format of these arguments:
|
||||
///
|
||||
/// appservice_id
|
||||
/// server_name
|
||||
/// user_id AND push_key
|
||||
///
|
||||
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
||||
/// enum
|
||||
ActiveRequestsFor {
|
||||
#[arg(short, long)]
|
||||
appservice_id: Option<String>,
|
||||
#[arg(short, long)]
|
||||
server_name: Option<Box<ServerName>>,
|
||||
#[arg(short, long)]
|
||||
user_id: Option<Box<UserId>>,
|
||||
#[arg(short, long)]
|
||||
push_key: Option<String>,
|
||||
},
|
||||
|
||||
/// - Queries database for `servernameevent_data` which are the queued up
|
||||
/// requests that will eventually be sent
|
||||
///
|
||||
/// This command takes only *one* format of these arguments:
|
||||
///
|
||||
/// appservice_id
|
||||
/// server_name
|
||||
/// user_id AND push_key
|
||||
///
|
||||
/// See src/service/sending/mod.rs for the definition of the `Destination`
|
||||
/// enum
|
||||
QueuedRequests {
|
||||
#[arg(short, long)]
|
||||
appservice_id: Option<String>,
|
||||
#[arg(short, long)]
|
||||
server_name: Option<Box<ServerName>>,
|
||||
#[arg(short, long)]
|
||||
user_id: Option<Box<UserId>>,
|
||||
#[arg(short, long)]
|
||||
push_key: Option<String>,
|
||||
},
|
||||
|
||||
GetLatestEduCount {
|
||||
server_name: Box<ServerName>,
|
||||
},
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/sending.rs
|
||||
pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventContent> {
|
||||
pub(super) async fn process(subcommand: SendingCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
Sending::ActiveRequests => {
|
||||
SendingCommand::ActiveRequests => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services().sending.db.active_requests();
|
||||
let results = services.sending.db.active_requests();
|
||||
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
let active_requests: Result<Vec<(_, _, _)>> = results.collect();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", active_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
active_requests
|
||||
),
|
||||
))
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
Sending::QueuedRequests {
|
||||
SendingCommand::QueuedRequests {
|
||||
appservice_id,
|
||||
server_name,
|
||||
user_id,
|
||||
@@ -33,8 +87,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
--help for more details.",
|
||||
));
|
||||
}
|
||||
|
||||
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = match (appservice_id, server_name, user_id, push_key) {
|
||||
(Some(appservice_id), None, None, None) => {
|
||||
if appservice_id.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -43,25 +97,15 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
services
|
||||
.sending
|
||||
.db
|
||||
.queued_requests(&Destination::Appservice(appservice_id));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
},
|
||||
(None, Some(server_name), None, None) => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
.sending
|
||||
.db
|
||||
.queued_requests(&Destination::Normal(server_name.into()));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
.queued_requests(&Destination::Appservice(appservice_id))
|
||||
},
|
||||
(None, Some(server_name), None, None) => services
|
||||
.sending
|
||||
.db
|
||||
.queued_requests(&Destination::Normal(server_name.into())),
|
||||
(None, None, Some(user_id), Some(push_key)) => {
|
||||
if push_key.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -70,14 +114,10 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
services
|
||||
.sending
|
||||
.db
|
||||
.queued_requests(&Destination::Push(user_id.into(), push_key));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
.queued_requests(&Destination::Push(user_id.into(), push_key))
|
||||
},
|
||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -94,16 +134,13 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
};
|
||||
|
||||
let queued_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", queued_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
queued_requests
|
||||
),
|
||||
))
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{queued_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
Sending::ActiveRequestsFor {
|
||||
SendingCommand::ActiveRequestsFor {
|
||||
appservice_id,
|
||||
server_name,
|
||||
user_id,
|
||||
@@ -116,7 +153,8 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
));
|
||||
}
|
||||
|
||||
let (results, query_time) = match (appservice_id, server_name, user_id, push_key) {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = match (appservice_id, server_name, user_id, push_key) {
|
||||
(Some(appservice_id), None, None, None) => {
|
||||
if appservice_id.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -125,25 +163,15 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
services
|
||||
.sending
|
||||
.db
|
||||
.active_requests_for(&Destination::Appservice(appservice_id));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
},
|
||||
(None, Some(server_name), None, None) => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
.sending
|
||||
.db
|
||||
.active_requests_for(&Destination::Normal(server_name.into()));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
.active_requests_for(&Destination::Appservice(appservice_id))
|
||||
},
|
||||
(None, Some(server_name), None, None) => services
|
||||
.sending
|
||||
.db
|
||||
.active_requests_for(&Destination::Normal(server_name.into())),
|
||||
(None, None, Some(user_id), Some(push_key)) => {
|
||||
if push_key.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -152,14 +180,10 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
));
|
||||
}
|
||||
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services()
|
||||
services
|
||||
.sending
|
||||
.db
|
||||
.active_requests_for(&Destination::Push(user_id.into(), push_key));
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
(results, query_time)
|
||||
.active_requests_for(&Destination::Push(user_id.into(), push_key))
|
||||
},
|
||||
(Some(_), Some(_), Some(_), Some(_)) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
@@ -176,29 +200,22 @@ pub(crate) async fn sending(subcommand: Sending) -> Result<RoomMessageEventConte
|
||||
};
|
||||
|
||||
let active_requests = results.collect::<Result<Vec<(_, _)>>>();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", active_requests),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
active_requests
|
||||
),
|
||||
))
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{active_requests:#?}\n```"
|
||||
)))
|
||||
},
|
||||
Sending::GetLatestEduCount {
|
||||
SendingCommand::GetLatestEduCount {
|
||||
server_name,
|
||||
} => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services().sending.db.get_latest_educount(&server_name);
|
||||
let results = services.sending.db.get_latest_educount(&server_name);
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::text_html(
|
||||
format!("Query completed in {query_time:?}:\n\n```\n{:?}```", results),
|
||||
format!(
|
||||
"<p>Query completed in {query_time:?}:</p>\n<pre><code>{:?}\n</code></pre>",
|
||||
results
|
||||
),
|
||||
))
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
29
src/admin/query/users.rs
Normal file
29
src/admin/query/users.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::Command;
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
/// All the getters and iterators from src/database/key_value/users.rs
|
||||
pub(crate) enum UsersCommand {
|
||||
Iter,
|
||||
}
|
||||
|
||||
/// All the getters and iterators in key_value/users.rs
|
||||
pub(super) async fn process(subcommand: UsersCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
|
||||
match subcommand {
|
||||
UsersCommand::Iter => {
|
||||
let timer = tokio::time::Instant::now();
|
||||
let results = services.users.db.iter();
|
||||
let users = results.collect::<Vec<_>>();
|
||||
let query_time = timer.elapsed();
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Query completed in {query_time:?}:\n\n```rs\n{users:#?}\n```"
|
||||
)))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,50 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId};
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomAliasId, RoomId};
|
||||
|
||||
use super::RoomAliasCommand;
|
||||
use crate::{service::admin::escape_html, services, Result};
|
||||
use crate::{escape_html, Command};
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum RoomAliasCommand {
|
||||
/// - Make an alias point to a room.
|
||||
Set {
|
||||
#[arg(short, long)]
|
||||
/// Set the alias even if a room is already using it
|
||||
force: bool,
|
||||
|
||||
/// The room id to set the alias on
|
||||
room_id: Box<RoomId>,
|
||||
|
||||
/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
|
||||
room_alias_localpart: String,
|
||||
},
|
||||
|
||||
/// - Remove a local alias
|
||||
Remove {
|
||||
/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
|
||||
room_alias_localpart: String,
|
||||
},
|
||||
|
||||
/// - Show which room is using an alias
|
||||
Which {
|
||||
/// The alias localpart to look up (`alias`, not
|
||||
/// `#alias:servername.tld`)
|
||||
room_alias_localpart: String,
|
||||
},
|
||||
|
||||
/// - List aliases currently being used
|
||||
List {
|
||||
/// If set, only list the aliases for this room
|
||||
room_id: Option<Box<RoomId>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: RoomAliasCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
let server_user = &services.globals.server_user;
|
||||
|
||||
pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
ref room_alias_localpart,
|
||||
@@ -17,29 +56,35 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
| RoomAliasCommand::Which {
|
||||
ref room_alias_localpart,
|
||||
} => {
|
||||
let room_alias_str = format!("#{}:{}", room_alias_localpart, services().globals.server_name());
|
||||
let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
|
||||
let room_alias = match RoomAliasId::parse_box(room_alias_str) {
|
||||
Ok(alias) => alias,
|
||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {}", err))),
|
||||
Err(err) => return Ok(RoomMessageEventContent::text_plain(format!("Failed to parse alias: {err}"))),
|
||||
};
|
||||
match command {
|
||||
RoomAliasCommand::Set {
|
||||
force,
|
||||
room_id,
|
||||
..
|
||||
} => match (force, services().rooms.alias.resolve_local_alias(&room_alias)) {
|
||||
(true, Ok(Some(id))) => match services().rooms.alias.set_alias(&room_alias, &room_id) {
|
||||
} => match (force, services.rooms.alias.resolve_local_alias(&room_alias)) {
|
||||
(true, Ok(Some(id))) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Successfully overwrote alias (formerly {})",
|
||||
id
|
||||
"Successfully overwrote alias (formerly {id})"
|
||||
))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
(false, Ok(Some(id))) => Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Refusing to overwrite in use alias for {}, use -f or --force to overwrite",
|
||||
id
|
||||
"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
|
||||
))),
|
||||
(_, Ok(None)) => match services().rooms.alias.set_alias(&room_alias, &room_id) {
|
||||
(_, Ok(None)) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.set_alias(&room_alias, &room_id, server_user)
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Successfully set alias")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
@@ -47,20 +92,25 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
},
|
||||
RoomAliasCommand::Remove {
|
||||
..
|
||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => match services().rooms.alias.remove_alias(&room_alias) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {}", id))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {}", err))),
|
||||
} => match services.rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => match services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(&room_alias, server_user)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain(format!("Removed alias from {id}"))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Failed to remove alias: {err}"))),
|
||||
},
|
||||
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
||||
},
|
||||
RoomAliasCommand::Which {
|
||||
..
|
||||
} => match services().rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {}", id))),
|
||||
} => match services.rooms.alias.resolve_local_alias(&room_alias) {
|
||||
Ok(Some(id)) => Ok(RoomMessageEventContent::text_plain(format!("Alias resolves to {id}"))),
|
||||
Ok(None) => Ok(RoomMessageEventContent::text_plain("Alias isn't in use.")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to lookup alias: {err}"))),
|
||||
},
|
||||
RoomAliasCommand::List {
|
||||
..
|
||||
@@ -71,7 +121,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
room_id,
|
||||
} => {
|
||||
if let Some(room_id) = room_id {
|
||||
let aliases = services()
|
||||
let aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
@@ -79,12 +129,13 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
match aliases {
|
||||
Ok(aliases) => {
|
||||
let plain_list = aliases.iter().fold(String::new(), |mut output, alias| {
|
||||
writeln!(output, "- {alias}").unwrap();
|
||||
writeln!(output, "- {alias}").expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
let html_list = aliases.iter().fold(String::new(), |mut output, alias| {
|
||||
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref())).unwrap();
|
||||
writeln!(output, "<li>{}</li>", escape_html(alias.as_ref()))
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -92,21 +143,22 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
let html = format!("Aliases for {room_id}:\n<ul>{html_list}</ul>");
|
||||
Ok(RoomMessageEventContent::text_html(plain, html))
|
||||
},
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {}", err))),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to list aliases: {err}"))),
|
||||
}
|
||||
} else {
|
||||
let aliases = services()
|
||||
let aliases = services
|
||||
.rooms
|
||||
.alias
|
||||
.all_local_aliases()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
match aliases {
|
||||
Ok(aliases) => {
|
||||
let server_name = services().globals.server_name();
|
||||
let server_name = services.globals.server_name();
|
||||
let plain_list = aliases
|
||||
.iter()
|
||||
.fold(String::new(), |mut output, (alias, id)| {
|
||||
writeln!(output, "- `{alias}` -> #{id}:{server_name}").unwrap();
|
||||
writeln!(output, "- `{alias}` -> #{id}:{server_name}")
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -120,7 +172,7 @@ pub(crate) async fn process(command: RoomAliasCommand, _body: Vec<&str>) -> Resu
|
||||
escape_html(id.as_ref()),
|
||||
server_name
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
});
|
||||
|
||||
@@ -1,28 +1,58 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
||||
use conduit::Result;
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info, PAGE_SIZE},
|
||||
services, Result,
|
||||
};
|
||||
use crate::{admin_command, escape_html, get_room_info, PAGE_SIZE};
|
||||
|
||||
pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMessageEventContent> {
|
||||
#[admin_command]
|
||||
pub(super) async fn list_rooms(
|
||||
&self, page: Option<usize>, exclude_disabled: bool, exclude_banned: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
||||
let page = page.unwrap_or(1);
|
||||
let mut rooms = services()
|
||||
let mut rooms = self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.iter_ids()
|
||||
.filter_map(Result::ok)
|
||||
.map(|id: OwnedRoomId| get_room_info(&id))
|
||||
.filter_map(|room_id| {
|
||||
room_id
|
||||
.ok()
|
||||
.filter(|room_id| {
|
||||
if exclude_disabled
|
||||
&& self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.is_disabled(room_id)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if exclude_banned
|
||||
&& self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.is_banned(room_id)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
})
|
||||
.map(|room_id| get_room_info(self.services, &room_id))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
rooms.sort_by_key(|r| r.1);
|
||||
rooms.reverse();
|
||||
|
||||
let rooms = rooms
|
||||
.into_iter()
|
||||
.skip(page.saturating_sub(1) * PAGE_SIZE)
|
||||
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
|
||||
.take(PAGE_SIZE)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -51,7 +81,7 @@ pub(crate) async fn list(_body: Vec<&str>, page: Option<usize>) -> Result<RoomMe
|
||||
members,
|
||||
escape_html(name)
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
@@ -1,24 +1,43 @@
|
||||
use std::fmt::Write as _;
|
||||
use std::fmt::Write;
|
||||
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId};
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomId};
|
||||
|
||||
use super::RoomDirectoryCommand;
|
||||
use crate::{
|
||||
service::admin::{escape_html, get_room_info, PAGE_SIZE},
|
||||
services, Result,
|
||||
};
|
||||
use crate::{escape_html, get_room_info, Command, PAGE_SIZE};
|
||||
|
||||
pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) -> Result<RoomMessageEventContent> {
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum RoomDirectoryCommand {
|
||||
/// - Publish a room to the room directory
|
||||
Publish {
|
||||
/// The room id of the room to publish
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Unpublish a room to the room directory
|
||||
Unpublish {
|
||||
/// The room id of the room to unpublish
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - List rooms that are published
|
||||
List {
|
||||
page: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
pub(super) async fn process(command: RoomDirectoryCommand, context: &Command<'_>) -> Result<RoomMessageEventContent> {
|
||||
let services = context.services;
|
||||
match command {
|
||||
RoomDirectoryCommand::Publish {
|
||||
room_id,
|
||||
} => match services().rooms.directory.set_public(&room_id) {
|
||||
} => match services.rooms.directory.set_public(&room_id) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room published")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
||||
},
|
||||
RoomDirectoryCommand::Unpublish {
|
||||
room_id,
|
||||
} => match services().rooms.directory.set_not_public(&room_id) {
|
||||
} => match services.rooms.directory.set_not_public(&room_id) {
|
||||
Ok(()) => Ok(RoomMessageEventContent::text_plain("Room unpublished")),
|
||||
Err(err) => Ok(RoomMessageEventContent::text_plain(format!("Unable to update room: {err}"))),
|
||||
},
|
||||
@@ -27,19 +46,19 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
|
||||
} => {
|
||||
// TODO: i know there's a way to do this with clap, but i can't seem to find it
|
||||
let page = page.unwrap_or(1);
|
||||
let mut rooms = services()
|
||||
let mut rooms = services
|
||||
.rooms
|
||||
.directory
|
||||
.public_rooms()
|
||||
.filter_map(Result::ok)
|
||||
.map(|id: OwnedRoomId| get_room_info(&id))
|
||||
.map(|id: OwnedRoomId| get_room_info(services, &id))
|
||||
.collect::<Vec<_>>();
|
||||
rooms.sort_by_key(|r| r.1);
|
||||
rooms.reverse();
|
||||
|
||||
let rooms = rooms
|
||||
.into_iter()
|
||||
.skip(page.saturating_sub(1) * PAGE_SIZE)
|
||||
.skip(page.saturating_sub(1).saturating_mul(PAGE_SIZE))
|
||||
.take(PAGE_SIZE)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -68,7 +87,7 @@ pub(crate) async fn process(command: RoomDirectoryCommand, _body: Vec<&str>) ->
|
||||
members,
|
||||
escape_html(name.as_ref())
|
||||
)
|
||||
.unwrap();
|
||||
.expect("should be able to write to string buffer");
|
||||
output
|
||||
})
|
||||
);
|
||||
84
src/admin/room/info.rs
Normal file
84
src/admin/room/info.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
use ruma::{events::room::message::RoomMessageEventContent, RoomId};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum RoomInfoCommand {
|
||||
/// - List joined members in a room
|
||||
ListJoinedMembers {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
|
||||
/// - Displays room topic
|
||||
///
|
||||
/// Room topics can be huge, so this is in its
|
||||
/// own separate command
|
||||
ViewRoomTopic {
|
||||
room_id: Box<RoomId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn list_joined_members(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
let room_name = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_name(&room_id)
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_else(|| room_id.to_string());
|
||||
|
||||
let members = self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(Result::ok);
|
||||
|
||||
let member_info = members
|
||||
.into_iter()
|
||||
.map(|user_id| {
|
||||
(
|
||||
user_id.clone(),
|
||||
self.services
|
||||
.users
|
||||
.displayname(&user_id)
|
||||
.unwrap_or(None)
|
||||
.unwrap_or_else(|| user_id.to_string()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let output_plain = format!(
|
||||
"{} Members in Room \"{}\":\n```\n{}\n```",
|
||||
member_info.len(),
|
||||
room_name,
|
||||
member_info
|
||||
.iter()
|
||||
.map(|(mxid, displayname)| format!("{mxid} | {displayname}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn view_room_topic(&self, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
|
||||
let Some(room_topic) = self
|
||||
.services
|
||||
.rooms
|
||||
.state_accessor
|
||||
.get_room_topic(&room_id)?
|
||||
else {
|
||||
return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
|
||||
};
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(format!(
|
||||
"Room topic:\n```\n{room_topic}\n```"
|
||||
)))
|
||||
}
|
||||
47
src/admin/room/mod.rs
Normal file
47
src/admin/room/mod.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
mod alias;
|
||||
mod commands;
|
||||
mod directory;
|
||||
mod info;
|
||||
mod moderation;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
|
||||
use self::{
|
||||
alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, moderation::RoomModerationCommand,
|
||||
};
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum RoomCommand {
|
||||
/// - List all rooms the server knows about
|
||||
#[clap(alias = "list")]
|
||||
ListRooms {
|
||||
page: Option<usize>,
|
||||
|
||||
/// Excludes rooms that we have federation disabled with
|
||||
#[arg(long)]
|
||||
exclude_disabled: bool,
|
||||
|
||||
/// Excludes rooms that we have banned
|
||||
#[arg(long)]
|
||||
exclude_banned: bool,
|
||||
},
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - View information about a room we know about
|
||||
Info(RoomInfoCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Manage moderation of remote or local rooms
|
||||
Moderation(RoomModerationCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Manage rooms' aliases
|
||||
Alias(RoomAliasCommand),
|
||||
|
||||
#[command(subcommand)]
|
||||
/// - Manage the room directory
|
||||
Directory(RoomDirectoryCommand),
|
||||
}
|
||||
613
src/admin/room/moderation.rs
Normal file
613
src/admin/room/moderation.rs
Normal file
@@ -0,0 +1,613 @@
|
||||
use api::client::leave_room;
|
||||
use clap::Subcommand;
|
||||
use conduit::{debug, error, info, warn, Result};
|
||||
use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, RoomAliasId, RoomId, RoomOrAliasId};
|
||||
|
||||
use crate::{admin_command, admin_command_dispatch, get_room_info};
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(crate) enum RoomModerationCommand {
|
||||
/// - Bans a room from local users joining and evicts all our local users
|
||||
/// from the room. Also blocks any invites (local and remote) for the
|
||||
/// banned room.
|
||||
///
|
||||
/// Server admins (users in the conduwuit admin room) will not be evicted
|
||||
/// and server admins can still join the room. To evict admins too, use
|
||||
/// --force (also ignores errors) To disable incoming federation of the
|
||||
/// room, use --disable-federation
|
||||
BanRoom {
|
||||
#[arg(short, long)]
|
||||
/// Evicts admins out of the room and ignores any potential errors when
|
||||
/// making our local users leave the room
|
||||
force: bool,
|
||||
|
||||
#[arg(long)]
|
||||
/// Disables incoming federation of the room after banning and evicting
|
||||
/// users
|
||||
disable_federation: bool,
|
||||
|
||||
/// The room in the format of `!roomid:example.com` or a room alias in
|
||||
/// the format of `#roomalias:example.com`
|
||||
room: Box<RoomOrAliasId>,
|
||||
},
|
||||
|
||||
/// - Bans a list of rooms (room IDs and room aliases) from a newline
|
||||
/// delimited codeblock similar to `user deactivate-all`
|
||||
BanListOfRooms {
|
||||
#[arg(short, long)]
|
||||
/// Evicts admins out of the room and ignores any potential errors when
|
||||
/// making our local users leave the room
|
||||
force: bool,
|
||||
|
||||
#[arg(long)]
|
||||
/// Disables incoming federation of the room after banning and evicting
|
||||
/// users
|
||||
disable_federation: bool,
|
||||
},
|
||||
|
||||
/// - Unbans a room to allow local users to join again
|
||||
///
|
||||
/// To re-enable incoming federation of the room, use --enable-federation
|
||||
UnbanRoom {
|
||||
#[arg(long)]
|
||||
/// Enables incoming federation of the room after unbanning
|
||||
enable_federation: bool,
|
||||
|
||||
/// The room in the format of `!roomid:example.com` or a room alias in
|
||||
/// the format of `#roomalias:example.com`
|
||||
room: Box<RoomOrAliasId>,
|
||||
},
|
||||
|
||||
/// - List of all rooms we have banned
|
||||
ListBannedRooms {
|
||||
#[arg(long)]
|
||||
/// Whether to only output room IDs without supplementary room
|
||||
/// information
|
||||
no_details: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn ban_room(
|
||||
&self, force: bool, disable_federation: bool, room: Box<RoomOrAliasId>,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
debug!("Got room alias or ID: {}", room);
|
||||
|
||||
let admin_room_alias = &self.services.globals.admin_alias;
|
||||
|
||||
if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
|
||||
if room.to_string().eq(&admin_room_id) || room.to_string().eq(admin_room_alias) {
|
||||
return Ok(RoomMessageEventContent::text_plain("Not allowed to ban the admin room."));
|
||||
}
|
||||
}
|
||||
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, banning room ID");
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, true)?;
|
||||
|
||||
room_id
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
debug!(?room_id, ?servers, "Got federation response fetching room ID for {room}");
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::notice_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, true)?;
|
||||
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
debug!("Making all users leave the room {}", &room);
|
||||
if force {
|
||||
for local_user in self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
self.services.globals.user_is_local(local_user)
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would
|
||||
// fail auth check)
|
||||
&& (self.services.globals.user_is_local(local_user)
|
||||
// since this is a force operation, assume user is an admin
|
||||
// if somehow this fails
|
||||
&& self.services
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(true))
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||
&local_user, &room_id
|
||||
);
|
||||
|
||||
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||
warn!(%e, "Failed to leave room");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for local_user in self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == self.services.globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote users
|
||||
// who are in the admin room to the list of local users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== self.services.globals.server_name()
|
||||
&& !self.services
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(false))
|
||||
})
|
||||
}) {
|
||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {} leave room {} during room banning: {}",
|
||||
&local_user, &room_id, e
|
||||
);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still banned \
|
||||
but not removing any more users): {}\nIf you would like to ignore errors, use --force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any local aliases, ignore errors
|
||||
for ref local_alias in self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.filter_map(Result::ok)
|
||||
{
|
||||
_ = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(local_alias, &self.services.globals.server_user)
|
||||
.await;
|
||||
}
|
||||
|
||||
// unpublish from room directory, ignore errors
|
||||
_ = self.services.rooms.directory.set_not_public(&room_id);
|
||||
|
||||
if disable_federation {
|
||||
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned, removed all our local users, and disabled incoming federation with room.",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room banned and removed all our local users, use `!admin federation disable-room` to stop receiving new \
|
||||
inbound federation events as well if needed.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn ban_list_of_rooms(&self, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
|
||||
if self.body.len() < 2 || !self.body[0].trim().starts_with("```") || self.body.last().unwrap_or(&"").trim() != "```"
|
||||
{
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Expected code block in command body. Add --help for details.",
|
||||
));
|
||||
}
|
||||
|
||||
let rooms_s = self
|
||||
.body
|
||||
.to_vec()
|
||||
.drain(1..self.body.len().saturating_sub(1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let admin_room_alias = &self.services.globals.admin_alias;
|
||||
|
||||
let mut room_ban_count: usize = 0;
|
||||
let mut room_ids: Vec<OwnedRoomId> = Vec::new();
|
||||
|
||||
for &room in &rooms_s {
|
||||
match <&RoomOrAliasId>::try_from(room) {
|
||||
Ok(room_alias_or_id) => {
|
||||
if let Some(admin_room_id) = self.services.admin.get_admin_room()? {
|
||||
if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(admin_room_alias) {
|
||||
info!("User specified admin room in bulk ban list, ignoring");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if room_alias_or_id.is_room_id() {
|
||||
let room_id = match RoomId::parse(room_alias_or_id) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force banning
|
||||
warn!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
};
|
||||
|
||||
room_ids.push(room_id);
|
||||
}
|
||||
|
||||
if room_alias_or_id.is_room_alias_id() {
|
||||
match RoomAliasId::parse(room_alias_or_id) {
|
||||
Ok(room_alias) => {
|
||||
let room_id =
|
||||
if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!(
|
||||
"We don't have this room alias to a room ID locally, attempting to fetch room \
|
||||
ID over federation"
|
||||
);
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
debug!(
|
||||
?room_id,
|
||||
?servers,
|
||||
"Got federation response fetching room ID for {room}",
|
||||
);
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
// don't fail if force blocking
|
||||
if force {
|
||||
warn!("Failed to resolve room alias {room} to a room ID: {e}");
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
room_ids.push(room_id);
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
|
||||
logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if force {
|
||||
// ignore rooms we failed to parse if we're force deleting
|
||||
error!(
|
||||
"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for room_id in room_ids {
|
||||
if self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.ban_room(&room_id, true)
|
||||
.is_ok()
|
||||
{
|
||||
debug!("Banned {room_id} successfully");
|
||||
room_ban_count = room_ban_count.saturating_add(1);
|
||||
}
|
||||
|
||||
debug!("Making all users leave the room {}", &room_id);
|
||||
if force {
|
||||
for local_user in self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == self.services.globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote
|
||||
// users who are in the admin room to the list of local
|
||||
// users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== self.services.globals.server_name()
|
||||
// since this is a force operation, assume user is an
|
||||
// admin if somehow this fails
|
||||
&& self.services
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(true))
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
|
||||
&local_user, room_id
|
||||
);
|
||||
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||
warn!(%e, "Failed to leave room");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for local_user in self
|
||||
.services
|
||||
.rooms
|
||||
.state_cache
|
||||
.room_members(&room_id)
|
||||
.filter_map(|user| {
|
||||
user.ok().filter(|local_user| {
|
||||
local_user.server_name() == self.services.globals.server_name()
|
||||
// additional wrapped check here is to avoid adding remote
|
||||
// users who are in the admin room to the list of local
|
||||
// users (would fail auth check)
|
||||
&& (local_user.server_name()
|
||||
== self.services.globals.server_name()
|
||||
&& !self.services
|
||||
.users
|
||||
.is_admin(local_user)
|
||||
.unwrap_or(false))
|
||||
})
|
||||
}) {
|
||||
debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
|
||||
if let Err(e) = leave_room(self.services, &local_user, &room_id, None).await {
|
||||
error!(
|
||||
"Error attempting to make local user {} leave room {} during bulk room banning: {}",
|
||||
&local_user, &room_id, e
|
||||
);
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Error attempting to make local user {} leave room {} during room banning (room is still \
|
||||
banned but not removing any more users and not banning any more rooms): {}\nIf you would \
|
||||
like to ignore errors, use --force",
|
||||
&local_user, &room_id, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove any local aliases, ignore errors
|
||||
for ref local_alias in self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.local_aliases_for_room(&room_id)
|
||||
.filter_map(Result::ok)
|
||||
{
|
||||
_ = self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.remove_alias(local_alias, &self.services.globals.server_user)
|
||||
.await;
|
||||
}
|
||||
|
||||
// unpublish from room directory, ignore errors
|
||||
_ = self.services.rooms.directory.set_not_public(&room_id);
|
||||
|
||||
if disable_federation {
|
||||
self.services.rooms.metadata.disable_room(&room_id, true)?;
|
||||
}
|
||||
}
|
||||
|
||||
if disable_federation {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled incoming \
|
||||
federation with the room."
|
||||
)))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn unban_room(&self, enable_federation: bool, room: Box<RoomOrAliasId>) -> Result<RoomMessageEventContent> {
|
||||
let room_id = if room.is_room_id() {
|
||||
let room_id = match RoomId::parse(&room) {
|
||||
Ok(room_id) => room_id,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!("Room specified is a room ID, unbanning room ID");
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, false)?;
|
||||
|
||||
room_id
|
||||
} else if room.is_room_alias_id() {
|
||||
let room_alias = match RoomAliasId::parse(&room) {
|
||||
Ok(room_alias) => room_alias,
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to parse room ID {room}. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`): {e}"
|
||||
)))
|
||||
},
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Room specified is not a room ID, attempting to resolve room alias to a room ID locally, if not using \
|
||||
get_alias_helper to fetch room ID remotely"
|
||||
);
|
||||
|
||||
let room_id = if let Some(room_id) = self.services.rooms.alias.resolve_local_alias(&room_alias)? {
|
||||
room_id
|
||||
} else {
|
||||
debug!("We don't have this room alias to a room ID locally, attempting to fetch room ID over federation");
|
||||
|
||||
match self
|
||||
.services
|
||||
.rooms
|
||||
.alias
|
||||
.resolve_alias(&room_alias, None)
|
||||
.await
|
||||
{
|
||||
Ok((room_id, servers)) => {
|
||||
debug!(?room_id, ?servers, "Got federation response fetching room ID for room {room}");
|
||||
room_id
|
||||
},
|
||||
Err(e) => {
|
||||
return Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Failed to resolve room alias {room} to a room ID: {e}"
|
||||
)));
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
self.services.rooms.metadata.ban_room(&room_id, false)?;
|
||||
|
||||
room_id
|
||||
} else {
|
||||
return Ok(RoomMessageEventContent::text_plain(
|
||||
"Room specified is not a room ID or room alias. Please note that this requires a full room ID \
|
||||
(`!awIh6gGInaS5wLQJwa:example.com`) or a room alias (`#roomalias:example.com`)",
|
||||
));
|
||||
};
|
||||
|
||||
if enable_federation {
|
||||
self.services.rooms.metadata.disable_room(&room_id, false)?;
|
||||
return Ok(RoomMessageEventContent::text_plain("Room unbanned."));
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(
|
||||
"Room unbanned, you may need to re-enable federation with the room using enable-room if this is a remote room \
|
||||
to make it fully functional.",
|
||||
))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
async fn list_banned_rooms(&self, no_details: bool) -> Result<RoomMessageEventContent> {
|
||||
let rooms = self
|
||||
.services
|
||||
.rooms
|
||||
.metadata
|
||||
.list_banned_rooms()
|
||||
.collect::<Result<Vec<_>, _>>();
|
||||
|
||||
match rooms {
|
||||
Ok(room_ids) => {
|
||||
if room_ids.is_empty() {
|
||||
return Ok(RoomMessageEventContent::text_plain("No rooms are banned."));
|
||||
}
|
||||
|
||||
let mut rooms = room_ids
|
||||
.into_iter()
|
||||
.map(|room_id| get_room_info(self.services, &room_id))
|
||||
.collect::<Vec<_>>();
|
||||
rooms.sort_by_key(|r| r.1);
|
||||
rooms.reverse();
|
||||
|
||||
let output_plain = format!(
|
||||
"Rooms Banned ({}):\n```\n{}\n```",
|
||||
rooms.len(),
|
||||
rooms
|
||||
.iter()
|
||||
.map(|(id, members, name)| if no_details {
|
||||
format!("{id}")
|
||||
} else {
|
||||
format!("{id}\tMembers: {members}\tName: {name}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(output_plain))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to list banned rooms: {e}");
|
||||
Ok(RoomMessageEventContent::text_plain(format!("Unable to list banned rooms: {e}")))
|
||||
},
|
||||
}
|
||||
}
|
||||
163
src/admin/server/commands.rs
Normal file
163
src/admin/server/commands.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use std::{fmt::Write, sync::Arc};
|
||||
|
||||
use conduit::{info, utils::time, warn, Err, Result};
|
||||
use ruma::events::room::message::RoomMessageEventContent;
|
||||
|
||||
use crate::admin_command;
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn uptime(&self) -> Result<RoomMessageEventContent> {
|
||||
let elapsed = self
|
||||
.services
|
||||
.server
|
||||
.started
|
||||
.elapsed()
|
||||
.expect("standard duration");
|
||||
|
||||
let result = time::pretty(elapsed);
|
||||
Ok(RoomMessageEventContent::notice_plain(format!("{result}.")))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn show_config(&self) -> Result<RoomMessageEventContent> {
|
||||
// Construct and send the response
|
||||
Ok(RoomMessageEventContent::text_plain(format!("{}", self.services.globals.config)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_features(
|
||||
&self, available: bool, enabled: bool, comma: bool,
|
||||
) -> Result<RoomMessageEventContent> {
|
||||
let delim = if comma {
|
||||
","
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
if enabled && !available {
|
||||
let features = info::rustc::features().join(delim);
|
||||
let out = format!("`\n{features}\n`");
|
||||
return Ok(RoomMessageEventContent::text_markdown(out));
|
||||
}
|
||||
|
||||
if available && !enabled {
|
||||
let features = info::cargo::features().join(delim);
|
||||
let out = format!("`\n{features}\n`");
|
||||
return Ok(RoomMessageEventContent::text_markdown(out));
|
||||
}
|
||||
|
||||
let mut features = String::new();
|
||||
let enabled = info::rustc::features();
|
||||
let available = info::cargo::features();
|
||||
for feature in available {
|
||||
let active = enabled.contains(&feature.as_str());
|
||||
let emoji = if active {
|
||||
"✅"
|
||||
} else {
|
||||
"❌"
|
||||
};
|
||||
let remark = if active {
|
||||
"[enabled]"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
writeln!(features, "{emoji} {feature} {remark}")?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::text_markdown(features))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn memory_usage(&self) -> Result<RoomMessageEventContent> {
|
||||
let services_usage = self.services.memory_usage().await?;
|
||||
let database_usage = self.services.db.db.memory_usage()?;
|
||||
let allocator_usage = conduit::alloc::memory_usage().map_or(String::new(), |s| format!("\nAllocator:\n{s}"));
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain(format!(
|
||||
"Services:\n{services_usage}\nDatabase:\n{database_usage}{allocator_usage}",
|
||||
)))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn clear_caches(&self) -> Result<RoomMessageEventContent> {
|
||||
self.services.clear_cache().await;
|
||||
|
||||
Ok(RoomMessageEventContent::text_plain("Done."))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_backups(&self) -> Result<RoomMessageEventContent> {
|
||||
let result = self.services.globals.db.backup_list()?;
|
||||
|
||||
if result.is_empty() {
|
||||
Ok(RoomMessageEventContent::text_plain("No backups found."))
|
||||
} else {
|
||||
Ok(RoomMessageEventContent::text_plain(result))
|
||||
}
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn backup_database(&self) -> Result<RoomMessageEventContent> {
|
||||
let globals = Arc::clone(&self.services.globals);
|
||||
let mut result = self
|
||||
.services
|
||||
.server
|
||||
.runtime()
|
||||
.spawn_blocking(move || match globals.db.backup() {
|
||||
Ok(()) => String::new(),
|
||||
Err(e) => (*e).to_string(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
if result.is_empty() {
|
||||
result = self.services.globals.db.backup_list()?;
|
||||
}
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(result))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn list_database_files(&self) -> Result<RoomMessageEventContent> {
|
||||
let result = self.services.globals.db.file_list()?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_markdown(result))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn admin_notice(&self, message: Vec<String>) -> Result<RoomMessageEventContent> {
|
||||
let message = message.join(" ");
|
||||
self.services.admin.send_text(&message).await;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("Notice was sent to #admins"))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn reload_mods(&self) -> Result<RoomMessageEventContent> {
|
||||
self.services.server.reload()?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("Reloading server..."))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
#[cfg(unix)]
|
||||
pub(super) async fn restart(&self, force: bool) -> Result<RoomMessageEventContent> {
|
||||
use conduit::utils::sys::current_exe_deleted;
|
||||
|
||||
if !force && current_exe_deleted() {
|
||||
return Err!(
|
||||
"The server cannot be restarted because the executable changed. If this is expected use --force to \
|
||||
override."
|
||||
);
|
||||
}
|
||||
|
||||
self.services.server.restart()?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("Restarting server..."))
|
||||
}
|
||||
|
||||
#[admin_command]
|
||||
pub(super) async fn shutdown(&self) -> Result<RoomMessageEventContent> {
|
||||
warn!("shutdown command");
|
||||
self.services.server.shutdown()?;
|
||||
|
||||
Ok(RoomMessageEventContent::notice_plain("Shutting down server..."))
|
||||
}
|
||||
63
src/admin/server/mod.rs
Normal file
63
src/admin/server/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
mod commands;
|
||||
|
||||
use clap::Subcommand;
|
||||
use conduit::Result;
|
||||
|
||||
use crate::admin_command_dispatch;
|
||||
|
||||
#[admin_command_dispatch]
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub(super) enum ServerCommand {
|
||||
/// - Time elapsed since startup
|
||||
Uptime,
|
||||
|
||||
/// - Show configuration values
|
||||
ShowConfig,
|
||||
|
||||
/// - List the features built into the server
|
||||
ListFeatures {
|
||||
#[arg(short, long)]
|
||||
available: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
enabled: bool,
|
||||
|
||||
#[arg(short, long)]
|
||||
comma: bool,
|
||||
},
|
||||
|
||||
/// - Print database memory usage statistics
|
||||
MemoryUsage,
|
||||
|
||||
/// - Clears all of Conduwuit's caches
|
||||
ClearCaches,
|
||||
|
||||
/// - Performs an online backup of the database (only available for RocksDB
|
||||
/// at the moment)
|
||||
BackupDatabase,
|
||||
|
||||
/// - List database backups
|
||||
ListBackups,
|
||||
|
||||
/// - List database files
|
||||
ListDatabaseFiles,
|
||||
|
||||
/// - Send a message to the admin room.
|
||||
AdminNotice {
|
||||
message: Vec<String>,
|
||||
},
|
||||
|
||||
/// - Hot-reload the server
|
||||
#[clap(alias = "reload")]
|
||||
ReloadMods,
|
||||
|
||||
#[cfg(unix)]
|
||||
/// - Restart the server
|
||||
Restart {
|
||||
#[arg(short, long)]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// - Shutdown the server
|
||||
Shutdown,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user