Compare commits
751 commits
shigusegub
...
shigusegub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cafc59bf4f | ||
|
|
9edce3a0a6 | ||
|
|
256882f8fb | ||
|
|
3f139680a3 |
||
|
|
16d86dbaa9 |
||
|
|
e415eee9b6 |
||
|
|
6c5c5f2970 |
||
|
|
902fb8abc7 |
||
|
|
8a5512f69f |
||
|
|
932d1b6f94 |
||
|
|
256c493ec6 | ||
|
|
e085365d85 |
||
|
|
2982472d00 |
||
|
|
9ea8d37e9f |
||
|
|
532762bd9b |
||
|
|
b2e4580a81 |
||
|
|
1be50ed28b |
||
|
|
58d4f6aa2c |
||
|
|
de6ac2392c |
||
|
|
1f2179872a |
||
|
|
e52ba68303 |
||
|
|
4943e6a914 |
||
|
|
302039e437 |
||
|
|
8d8d9d3354 |
||
|
|
fa38d27b34 |
||
|
|
3faf7dd1c8 |
||
|
|
a03e5df39c |
||
|
|
04528f29d0 |
||
|
|
fc91131892 |
||
|
|
f4a3bc2300 |
||
|
|
accadd99f3 |
||
|
|
d698b96579 |
||
|
|
8880c502be |
||
|
|
e0ed6b5d39 |
||
|
|
52762f9770 |
||
|
|
65076aa2f8 |
||
|
|
92a112cf15 |
||
|
|
bbc53f0fef |
||
|
|
5810ecd16e |
||
|
|
e7b88cd652 |
||
|
|
b4a3e8436d |
||
|
|
235435fc9b |
||
|
|
f4ff7cb1ea |
||
|
|
830455d197 |
||
|
|
d8c880e494 |
||
|
|
6ac6cff5b5 |
||
|
|
bde6bc91a4 |
||
|
|
687fa6e1b2 | ||
|
|
0421e8b2e3 |
||
|
|
1659fd99d3 |
||
|
|
cfeca7f7af | ||
|
|
9c76e0686a |
||
|
|
1afc236468 | ||
|
|
62d4258d79 |
||
|
|
4515f9139c |
||
|
|
5a4cffbb62 | ||
|
|
62b6a24d5d | ||
|
|
0061f62355 | ||
|
|
775ae5d504 | ||
|
|
f61cf55676 |
||
|
|
b68f13b959 | ||
|
|
239ae3403b |
||
|
|
c023727ecd | ||
|
|
fd1b15dd58 |
||
|
|
d6a5dd2b76 |
||
|
|
72d45a9f8b |
||
|
|
760daad8ef |
||
|
|
47629351f0 |
||
|
|
bad04ab482 |
||
|
|
92b8fba3e3 |
||
|
|
770d827b50 |
||
|
|
b3f95adfb7 | ||
|
|
b3007be961 |
||
|
|
ef011bb8f9 |
||
|
|
5452e3f73e |
||
|
|
a881ffe8c2 |
||
|
|
c978fe0b7b |
||
|
|
a050b00898 |
||
|
|
d549b98f8b |
||
|
|
e35dacf9f9 |
||
|
|
71104db3f6 |
||
|
|
c596ace9df |
||
|
|
0e1315099f |
||
|
|
407db6c118 |
||
|
|
5a7635b943 |
||
|
|
21bb1a98a3 |
||
|
|
149236e468 | ||
|
|
820e80bdc7 |
||
|
|
fa920037f1 | ||
|
|
70d743bff9 | ||
|
|
635bc19ac8 |
||
|
|
9921e802c9 | ||
|
|
45459c8398 |
||
|
|
5709531197 |
||
|
|
c4ca9b41ad |
||
|
|
39d5648447 |
||
|
|
71cb70cd01 |
||
|
|
124f4ece1d | ||
|
|
59042e2793 |
||
|
|
0e642d4c90 | ||
|
|
16890c051a |
||
|
|
3ecfe034d7 | ||
|
|
6a5ca6213a |
||
|
|
48d6e9476b | ||
|
|
96d69bfa0a | ||
|
|
5ecc936444 |
||
|
|
2dda6cb52a |
||
|
|
f672ab337c |
||
|
|
656191bd1e |
||
|
|
ad7f68b924 | ||
|
|
2876fed876 | ||
|
|
f53342775d | ||
|
|
478b021fbe |
||
|
|
e1e853e8f7 |
||
|
|
2e59dcb9f6 |
||
|
|
e516c43411 |
||
|
|
253bbb80f5 |
||
|
|
e143e645d3 | ||
|
|
8943ab8140 |
||
|
|
1dd6a345bf | ||
|
|
4a47a756be |
||
|
|
1bb5eca063 | ||
|
|
5c85dadb29 | ||
|
|
a45eb365ed | ||
|
|
a22062a224 | ||
|
|
bc999b3309 |
||
|
|
16581a2f6d | ||
|
|
be5a3957b2 |
||
|
|
3b71770cfa |
||
|
|
030c7e929c |
||
|
|
d30d205f0b |
||
|
|
b4c624911c |
||
|
|
8ab0e046b1 | ||
|
|
ea49ad121e | ||
|
|
6132c9e88a |
||
|
|
2b32e3cf1f |
||
|
|
fd2074a30d | ||
|
|
03e75d3b3e |
||
|
|
b9ef1e2182 | ||
|
|
e210e16a37 | ||
|
|
842e6bcaff |
||
|
|
c591ca518c |
||
|
|
55c20c0fe8 |
||
|
|
2ce6e5f49d | ||
|
|
458a15bf9f |
||
|
|
7a741736ea |
||
|
|
3c916891f6 | ||
|
|
a5aad47cff | ||
|
|
d115b41848 | ||
|
|
34172673d8 | ||
|
|
6e73d043ef |
||
|
|
f5dd75b1a3 | ||
|
|
ce5c1b4c15 |
||
|
|
cece76e4cb |
||
|
|
9df50eea82 |
||
|
|
f90a52398b |
||
|
|
13c944a04b |
||
|
|
271c3a3dd8 |
||
|
|
acc44a5b6f |
||
|
|
51349f61f8 | ||
|
|
91089c93ad |
||
|
|
a9862dcaa0 |
||
|
|
498c9ad532 |
||
|
|
42c100605e |
||
|
|
0615025f92 |
||
|
|
7d1cc56a78 |
||
|
|
f985346fe8 |
||
|
|
24bdd9774a |
||
|
|
e3d15b1b53 | ||
|
|
99befcaf1b |
||
|
|
9a6c2f909d |
||
|
|
cc1b95c88e | ||
|
|
4fe567064a | ||
|
|
5ddd337045 |
||
|
|
d5f48101b8 |
||
|
|
bb4826d8ae |
||
|
|
465a81ecf2 |
||
|
|
d0b5a05075 |
||
|
|
836f4a2d69 |
||
|
|
3b3aad6065 |
||
|
|
d57fdcc7b0 |
||
|
|
34d827730a |
||
|
|
9b8aa2e2ee | ||
|
|
c2b8c67808 | ||
|
|
5d8b5a73d2 |
||
|
|
c5230d98c5 |
||
|
|
5f71532442 |
||
|
|
2d667e1d2a |
||
|
|
c518319349 |
||
|
|
ecd8a14435 |
||
|
|
d1a7e915ce |
||
|
|
96f471a291 |
||
|
|
d0c8ca92a5 |
||
|
|
85a802282e |
||
|
|
a9bf7212d4 |
||
|
|
a790e08a6c | ||
|
|
77b5f3f485 |
||
|
|
ce4e1d4287 |
||
|
|
b97092a97e |
||
|
|
ef2b374850 | ||
|
|
c20c0ee867 | ||
|
|
0070b13130 |
||
|
|
9b1721888e | ||
|
|
0862c8b88d |
||
|
|
1a48466b85 |
||
|
|
868723e371 |
||
|
|
3abe4ebfbd |
||
|
|
08c8571c59 |
||
|
|
c3fe61c2f5 |
||
|
|
97b94a2f43 | ||
|
|
6ffa90a42a | ||
|
|
efb43a02fa | ||
|
|
4aa0d5d709 | ||
|
|
bf477c1a53 |
||
|
|
d090aeb2bb |
||
|
|
77a7ba3a0e |
||
|
|
e63b7d6715 |
||
|
|
e6598ae5c7 |
||
|
|
2a56a02d9a | ||
|
|
59269cf07a | ||
|
|
c772f9b40a | ||
|
|
36bc7b116d | ||
|
|
0a076443ad | ||
|
|
1d6664b1f4 | ||
|
|
85a0f2e602 | ||
|
|
00ca971390 | ||
|
|
9129c65795 | ||
|
|
a30298c1eb | ||
|
|
103b724962 | ||
|
|
7ab1901a28 |
||
|
|
56105bc18d |
||
|
|
8c8c2de1e1 |
||
|
|
f7be7f8018 |
||
|
|
7d4b53bf48 |
||
|
|
631094666c |
||
|
|
6ec04af08c | ||
|
|
ac08f0487f |
||
|
|
bde0e3e353 |
||
|
|
a2eea6768c | ||
|
|
38bb47b615 | ||
|
|
3ba3319390 |
||
|
|
00c9059252 |
||
|
|
bbae94ddf3 |
||
|
|
0448ba5cd2 |
||
|
|
4c27a3c5a0 |
||
|
|
232cdab753 |
||
|
|
3ab8cc1eb9 |
||
|
|
70cb241173 |
||
|
|
a9aa19585b |
||
|
|
e2f744bd68 |
||
|
|
a89fe828d3 |
||
|
|
41ab347571 |
||
|
|
d383c453a1 |
||
|
|
c688d09478 |
||
|
|
2d307c1487 |
||
|
|
7de1affda8 | ||
|
|
ebd5596ba7 |
||
|
|
1d758dbbf5 |
||
|
|
b862254889 |
||
|
|
d0d6c986f4 |
||
|
|
f11ffca7f5 |
||
|
|
b11e8888d1 | ||
|
|
f4b415c54e | ||
|
|
dc348e8be8 |
||
|
|
9154f6fe3c |
||
|
|
a96fb31a89 |
||
|
|
28d7ac3a43 |
||
|
|
ab49f00090 |
||
|
|
604e2ad849 |
||
|
|
d964737d75 |
||
|
|
dc28528588 |
||
|
|
eae1311bc2 |
||
|
|
4d1044e2d1 |
||
|
|
dcc5a33f4d |
||
|
|
f6cc4c144b |
||
|
|
fa7986a267 |
||
|
|
7e54b0935b |
||
|
|
70c79ad512 |
||
|
|
7d9175350d |
||
|
|
a020b8a190 |
||
|
|
b0ae451942 | ||
|
|
cb0a1d1602 | ||
|
|
964feb1e47 |
||
|
|
dd07088377 |
||
|
|
3027052b0d |
||
|
|
d19a9f768c |
||
|
|
6e03f1d789 |
||
|
|
ef321846d4 |
||
|
|
6c79cc792f |
||
|
|
9912b2d16a |
||
|
|
93fe0f5ab5 |
||
|
|
6393fe0a9e |
||
|
|
0337bd0ae4 |
||
|
|
c290f022de |
||
|
|
e45dad2104 |
||
|
|
df67f21974 |
||
|
|
86725b5a00 |
||
|
|
7e9b8a7663 |
||
|
|
19a84baf82 |
||
|
|
c6d1cead83 | ||
|
|
9782bcf979 |
||
|
|
07c65adb0c |
||
|
|
6f5ea55613 | ||
|
|
98cd1bbb9a | ||
|
|
6165e2f342 | ||
|
|
86dd9663fc | ||
|
|
e676ec3507 |
||
|
|
fc9e6793ce |
||
|
|
ab634a4aa3 |
||
|
|
27090ad7e6 |
||
|
|
9061851fed |
||
|
|
719b5d65b5 |
||
|
|
e44d2329c8 |
||
|
|
f5f63bbc74 | ||
|
|
afe33bcc44 |
||
|
|
cd28c2089c |
||
|
|
a84d16831f |
||
|
|
ac05f65fbb | ||
|
|
aa16f40f09 | ||
|
|
f99ce5b2e6 | ||
|
|
e023ed7a5a |
||
|
|
54369c93d7 |
||
|
|
8a410bf49f |
||
|
|
e46365bf2a | ||
|
|
33fdc59bc1 | ||
|
|
b1d72e16a3 | ||
|
|
c8c1f7d38b | ||
|
|
cdb0f103a8 |
||
|
|
619db0adca | ||
|
|
4c9fc6287e |
||
|
|
a5e7145c97 | ||
|
|
0c0d687330 |
||
|
|
6ee40cb2eb |
||
|
|
ff7927e219 |
||
|
|
5f55c9653c |
||
|
|
678fe8a064 |
||
|
|
94a28d1286 |
||
|
|
a2a23b327b |
||
|
|
1db2b99e48 |
||
|
|
add3844c6d |
||
|
|
1a83b1d28d | ||
|
|
cda6738309 | ||
|
|
b054c2aa42 |
||
|
|
1e65c7a69e |
||
|
|
9dd02ecd50 |
||
|
|
49486a4e64 | ||
|
|
46a2808690 | ||
|
|
c428cf43e8 | ||
|
|
1a13ec539b | ||
|
|
7575b7abc2 |
||
|
|
57ab0a9302 | ||
|
|
4fe802b25a | ||
|
|
83dcc4ae36 | ||
|
|
be2a5056a3 | ||
|
|
093b156c65 | ||
|
|
c7c453ca21 | ||
|
|
0cf221ba13 | ||
|
|
c5737898f5 | ||
|
|
b90ac6b9c7 | ||
|
|
5b63307f85 | ||
|
|
512b0f67a4 |
||
|
|
784a319f2e |
||
|
|
7651490c50 |
||
|
|
d7a0d97c36 | ||
|
|
086c15b5cd | ||
|
|
4fef910303 | ||
|
|
526365364b | ||
|
|
7fff19cbe4 | ||
|
|
143f426e84 | ||
|
|
e4ad3ab322 | ||
|
|
9ae1249ccb |
||
|
|
7ab9e2c7ce |
||
|
|
c92d233233 |
||
|
|
2db3a9c04d | ||
|
|
d0c2d04356 |
||
|
|
d8e3ea69b1 |
||
|
|
9e16332d9d |
||
|
|
4810d2536e |
||
|
|
7e97216b52 | ||
|
|
47ca427497 | ||
|
|
ffff2098f0 | ||
|
|
e211b72924 | ||
|
|
68e4bb53a2 |
||
|
|
d8e9affded | ||
|
|
4d3aea1fce | ||
|
|
2b3ac2d7fe |
||
|
|
95eef879d7 |
||
|
|
c19bdf3814 |
||
|
|
95b15190de |
||
|
|
6c2d8209c9 |
||
|
|
6f415cf3fc |
||
|
|
0cf865f025 |
||
|
|
35b5447f3f |
||
|
|
90e390e45b |
||
|
|
d6d0ce7260 |
||
|
|
ea886dc36b |
||
|
|
9b331d648b | ||
|
|
71afba4825 |
||
|
|
bab24878c7 | ||
|
|
8a56cf5c0f |
||
|
|
8e72f4cd17 | ||
|
|
960c730706 | ||
|
|
ab9fd33762 |
||
|
|
216a00f73f |
||
|
|
7f4890b6a9 |
||
|
|
61feb3dfcd |
||
|
|
f579dc099c | ||
|
|
592be493c8 |
||
|
|
6ff1d10e22 | ||
|
|
e1b2e788d9 | ||
|
|
47021b5aba |
||
|
|
c780298ce7 |
||
|
|
6b86e31e5d |
||
|
|
ebcc7684c1 | ||
|
|
4873991983 | ||
|
|
2082bf729a | ||
|
|
133fc64472 | ||
|
|
c62d191986 |
||
|
|
f1249d830f | ||
|
|
727e9e7749 |
||
|
|
3c63877e61 |
||
|
|
86a5213523 | ||
|
|
aec0deef8b |
||
|
|
11281d740a | ||
|
|
394db0dce0 |
||
|
|
684e9ef247 |
||
|
|
ae763ff6cf | ||
|
|
af175fbdfc |
||
|
|
cd8816ecce | ||
|
|
4230887d7e | ||
|
|
8ccdd98914 |
||
|
|
78aef1b875 |
||
|
|
78a41dfdcd |
||
|
|
621d86a31d |
||
|
|
2c7095d300 | ||
|
|
6553ba24aa |
||
|
|
1a8d585cbf |
||
|
|
6ae02d71bd |
||
|
|
00dd1b5103 |
||
|
|
4acd8c4e72 |
||
|
|
50651284a2 |
||
|
|
9fdad779b5 |
||
|
|
a1f7413832 |
||
|
|
ee18feef7c |
||
|
|
93c155e4fa | ||
|
|
47e6dbfade |
||
|
|
da9cbc8e2f |
||
|
|
3dbc570471 |
||
|
|
a35aa6551e |
||
|
|
99b614a52e |
||
|
|
4337e0eb1b |
||
|
|
7756f491d5 |
||
|
|
bd45704dba |
||
|
|
9c540995b4 |
||
|
|
80e72b79f5 |
||
|
|
42683e79df |
||
|
|
da28a4c441 |
||
|
|
af6d12c0a5 |
||
|
|
cb2271978e |
||
|
|
db7bca8945 | ||
|
|
e4632eced3 |
||
|
|
a996d25b84 |
||
|
|
25e543d44d |
||
|
|
cafd75b072 |
||
|
|
95a33855d1 |
||
|
|
7f97e21910 |
||
|
|
209b9c0a1e |
||
|
|
16b7a95c48 |
||
|
|
2e968890de |
||
|
|
5229e8ae65 |
||
|
|
d8b8cbbb8d |
||
|
|
89a78d765c |
||
|
|
dd29b9c11b |
||
|
|
eea01b54b7 |
||
|
|
42eb9706a5 |
||
|
|
97a2e8c764 |
||
|
|
e002650e23 |
||
|
|
f00c13602d |
||
|
|
13d6246ed9 |
||
|
|
67e7f788c9 |
||
|
|
e2adc796c4 |
||
|
|
d2f7c9252f |
||
|
|
5351cd4ce9 |
||
|
|
fc5aea73ff |
||
|
|
c2fb145c5f | ||
|
|
683ab39160 | ||
|
|
7582b71f46 | ||
|
|
ebfa0d88df | ||
|
|
00265751cc | ||
|
|
3776f13f20 | ||
|
|
a3404e91bc | ||
|
|
5271876a5f | ||
|
|
01ced6bea2 | ||
|
|
1405f5dc8b | ||
|
|
fd7b809c54 | ||
|
|
096c4ea980 | ||
|
|
072dc39d83 | ||
|
|
7bba485397 | ||
|
|
1fe0970b64 | ||
|
|
08bf6c8fed | ||
|
|
cdcc432f31 | ||
|
|
b0de9bd3cd | ||
|
|
56a25202b9 | ||
|
|
265d3eeebc | ||
|
|
b224a2dacc | ||
|
|
8640fcef22 | ||
|
|
0fd544722f | ||
|
|
b67d7c1106 | ||
|
|
6f8233d780 | ||
|
|
2880aac617 | ||
|
|
4493d0d187 | ||
|
|
1a0af1c0c0 | ||
|
|
88a349f3ab | ||
|
|
a9fe2fe4d8 | ||
|
|
f138423814 | ||
|
|
eb69576154 |
||
|
|
c8baad165b |
||
|
|
799199f6b5 | ||
|
|
9fdc1a8d3b | ||
|
|
9db47790bb | ||
|
|
9e22baa66a | ||
|
|
5aa3c8a06e | ||
|
|
1e3456a160 | ||
|
|
8ac967518b | ||
|
|
9c8f653312 | ||
|
|
2cc7b8356c | ||
|
|
9af26e5fb5 | ||
|
|
f06a0eab50 | ||
|
|
ea78e76837 | ||
|
|
1d819195b6 | ||
|
|
711b33d81c | ||
|
|
7cc9ba6f06 | ||
|
|
63c9c7ea92 | ||
|
|
d1bd24ba64 | ||
|
|
106a52eb2e | ||
|
|
eabfb2bd47 | ||
|
|
876913d2af | ||
|
|
93d05efdb1 | ||
|
|
1701c343e1 | ||
|
|
85d311adcf | ||
|
|
cbb715b978 | ||
|
|
08ba0d46d7 | ||
|
|
01b139c633 | ||
|
|
a12f9777f8 | ||
|
|
f44774d387 | ||
|
|
1743a837dd | ||
|
|
0ac1b65ba9 | ||
|
|
b75f8f7c9a | ||
|
|
ad7ab7741b | ||
|
|
829a60ce93 | ||
|
|
02fc770e36 | ||
|
|
0f262bb2f3 | ||
|
|
9450fba83d | ||
|
|
bd0c5aa748 | ||
|
|
aed0eb3333 | ||
|
|
a5bd4e0d92 | ||
|
|
6ffd25682d | ||
|
|
a4137243b6 | ||
|
|
a4c929c527 | ||
|
|
c455e02255 | ||
|
|
2a23eacee8 | ||
|
|
17a563aeb4 | ||
|
|
6744a86fe3 | ||
|
|
39409a784d | ||
|
|
3ebf4b1056 | ||
|
|
eee54a9226 | ||
|
|
3790e6786c | ||
|
|
0393f0f92d | ||
|
|
926cad3a76 | ||
|
|
dd2f0a32d8 | ||
|
|
3c7cd8b043 | ||
|
|
cbacc0f146 | ||
|
|
94ea3dffd1 | ||
|
|
5777d8e8d3 | ||
|
|
d1ce6f9833 | ||
|
|
f3d9880f07 | ||
|
|
349d858fda | ||
|
|
9040434886 | ||
|
|
6a926bf605 | ||
|
|
7c79e4416f | ||
|
|
6f62cb0ed3 | ||
|
|
0d3085937c | ||
|
|
0fc046a75e | ||
|
|
a72f9cff23 | ||
|
|
dc7bd82968 | ||
|
|
e1a1e5c726 | ||
|
|
f3f72048ac | ||
|
|
2937bb68b1 | ||
|
|
750266f2e3 | ||
|
|
645211812e | ||
|
|
ee55764501 | ||
|
|
a9ad6297b7 | ||
|
|
6a3b5b3218 | ||
|
|
bf86768e88 | ||
|
|
531041041a | ||
|
|
f60a317c2f | ||
|
|
f4c28392e1 | ||
|
|
ec294b30c1 | ||
|
|
b8a66c22b3 | ||
|
|
93e8f9d7d1 | ||
|
|
8417629b4b | ||
|
|
958d250fe5 | ||
|
|
19e05b4a7b | ||
|
|
5b6af83e86 | ||
|
|
1b9cd83d88 | ||
|
|
dfaabb48ef | ||
|
|
6bbfba7f6e | ||
|
|
23cc812366 | ||
|
|
d0ef58a59d | ||
|
|
37cb2f9273 | ||
|
|
70de4491c2 | ||
|
|
d1787966a6 | ||
|
|
a0131ff733 |
||
|
|
bceb28b941 | ||
|
|
4e1ba489ec | ||
|
|
d95d7f6eba | ||
|
|
0592f111f6 | ||
|
|
40bc79e5ce | ||
|
|
87b4e3f3ff | ||
|
|
a1bb81bddb | ||
|
|
499b2ed118 | ||
|
|
8626a337f8 | ||
|
|
87e6cd3dc0 | ||
|
|
2c14121389 | ||
|
|
0e0931a3c2 | ||
|
|
3760480813 | ||
|
|
5f321b0b5b | ||
|
|
d0db1f00c3 | ||
|
|
662c9f36ac | ||
|
|
2388964b14 | ||
|
|
d03ae43ee0 | ||
|
|
8975129680 | ||
|
|
96f252023e | ||
|
|
0879dd3950 | ||
|
|
4bc0b26abe | ||
|
|
8abd25950a | ||
|
|
970e0f9044 | ||
|
|
848b3f5d5b | ||
|
|
865ddfdce0 | ||
|
|
cae3c9a980 |
||
|
|
4e65bbf134 |
||
|
|
7efd296afb |
||
|
|
74da3e01f7 |
||
|
|
dee7ac0415 |
||
|
|
222306ff27 | ||
|
|
ca38217898 |
||
|
|
19025563e2 | ||
|
|
65c7d0c7b9 | ||
|
|
490cd33bc9 | ||
|
|
8921dbfffd | ||
|
|
b645643cfb | ||
|
|
efb75488d9 |
||
|
|
911d84411d |
||
|
|
3a87250e15 |
||
|
|
68de463392 | ||
|
|
1b182b07dc | ||
|
|
2086561fbd | ||
|
|
3620726ff3 | ||
|
|
37041aae60 | ||
|
|
c3b779036d | ||
|
|
36a79ab58e | ||
|
|
d389359ec3 | ||
|
|
6405a2e682 | ||
|
|
38c30d50b4 | ||
|
|
9040f97cea | ||
|
|
e4f2f0994c | ||
|
|
8eca6ca87c | ||
|
|
120719f28c | ||
|
|
a9b5a28c26 | ||
|
|
938ee4cb01 | ||
|
|
c392b21db1 | ||
|
|
588bc656f2 | ||
|
|
ef7be0a1e5 | ||
|
|
e32ab8aef2 | ||
|
|
95c8b4732f |
||
|
|
cbc2ea3315 | ||
|
|
23a4d68c97 | ||
|
|
0b950f6253 | ||
|
|
699a7e57e8 |
||
|
|
3d9ac413af | ||
|
|
eed4f4bba8 |
||
|
|
f80c5744b1 |
||
|
|
1c685ea41a | ||
|
|
b798f7d6e9 | ||
|
|
2e80c786bb | ||
|
|
ec6ffa4fdf | ||
|
|
4693dc837b | ||
|
|
feda4d0718 | ||
|
|
cb78699a3b | ||
|
|
833e9829ba | ||
|
|
bd30d461b0 | ||
|
|
5001fb3a78 | ||
|
|
bc0c7fb310 | ||
|
|
c1e33bfadb | ||
|
|
e7a4d5ea66 | ||
|
|
6fac6ff7f1 | ||
|
|
055242f438 | ||
|
|
80ede85f75 | ||
|
|
26234c032d | ||
|
|
9901e6367e | ||
|
|
9b5afe9cd4 |
||
|
|
56b3db71ff |
||
|
|
ddc1a86f40 |
||
|
|
ce188baa92 |
||
|
|
177edbbb2c |
||
|
|
145b38b314 |
||
|
|
10ab71d157 |
||
|
|
6f86883cca |
||
|
|
be327ca982 |
||
|
|
a4fb651fac | ||
|
|
117b0bd79e | ||
|
|
49f9ab3034 | ||
|
|
0b871ff1f2 | ||
|
|
77a1d79f92 | ||
|
|
3b3e369f83 | ||
|
|
9912a9f603 | ||
|
|
49985b1614 | ||
|
|
92fd157cd8 | ||
|
|
b66b93a94a | ||
|
|
f0669997d3 | ||
|
|
57a3b1f6d0 | ||
|
|
1af8997462 | ||
|
|
e9e3d255e5 | ||
|
|
e781a7ef9b | ||
|
|
bff67650e1 | ||
|
|
6701f61990 | ||
|
|
39817fa547 | ||
|
|
0feb23a308 | ||
|
|
b9c281a0c3 | ||
|
|
bd61916270 | ||
|
|
47f4bde0ea | ||
|
|
958a4581d6 | ||
|
|
9ede9b92d3 | ||
|
|
3903f12c78 | ||
|
|
ee17d6413d | ||
|
|
71f5a493f3 | ||
|
|
6d46aefb7f | ||
|
|
9d7bdab7a2 | ||
|
|
c3c14f5860 | ||
|
|
9bbeeeb428 |
||
|
|
a1247220f3 |
||
|
|
9ce615d3d5 |
||
|
|
592955a895 | ||
|
|
7c09150cdb | ||
|
|
77436451ad | ||
|
|
a5c88eb39b | ||
|
|
627c944fec | ||
|
|
656dce96dc |
||
|
|
c7119d17d8 |
||
|
|
d860d50597 |
||
|
|
c44cf7d74f |
967 changed files with 27495 additions and 4048 deletions
25
.forgejo/issue_template/bug.yaml
Normal file
25
.forgejo/issue_template/bug.yaml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: 'Bug report'
|
||||||
|
about: 'Report a bug in Pleroma'
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
### Precheck
|
||||||
|
|
||||||
|
* For support use https://git.pleroma.social/pleroma/pleroma-support or [community channels](https://git.pleroma.social/pleroma/pleroma#community-channels).
|
||||||
|
* Please do a quick search to ensure no similar bug has been reported before. If the bug has not been addressed after 2 weeks, it's fine to bump it.
|
||||||
|
* Try to ensure that the bug is actually related to the Pleroma backend. For example, if a bug happens in Pleroma-FE but not in Mastodon-FE or mobile clients, it's likely that the bug should be filed in [Pleroma-FE](https://git.pleroma.social/pleroma/pleroma-fe/issues/new) repository.
|
||||||
|
- type: textarea
|
||||||
|
id: environment
|
||||||
|
attributes:
|
||||||
|
label: Environment
|
||||||
|
value: |
|
||||||
|
* Installation type (OTP or From Source):
|
||||||
|
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
||||||
|
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
||||||
|
* Operating system:
|
||||||
|
* PostgreSQL version (`psql -V`):
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
13
.forgejo/pull_request_template.md
Normal file
13
.forgejo/pull_request_template.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] Adding a changelog: In the `changelog.d` directory, create a file named `<code>.<type>`.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
`<code>` can be anything, but we recommend using a more or less unique identifier to avoid collisions, such as the branch name.
|
||||||
|
|
||||||
|
`<type>` can be `add`, `change`, `remove`, `fix`, `security` or `skip`. `skip` is only used if there is no user-visible change in the PR (for example, only editing comments in the code). Otherwise, choose a type that corresponds to your change.
|
||||||
|
|
||||||
|
In the file, write the changelog entry. For example, if a PR adds group functionality, we can create a file named `group.add` and write `Add group functionality` in it.
|
||||||
|
|
||||||
|
If one changelog entry is not enough, you may add more. But that might mean you can split it into two PRs. Only use more than one changelog entry if you really need to (for example, when one change in the code fix two different bugs, or when refactoring).
|
||||||
|
-->
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -56,6 +56,9 @@ pleroma.iml
|
||||||
# asdf
|
# asdf
|
||||||
.tool-versions
|
.tool-versions
|
||||||
|
|
||||||
|
# mise
|
||||||
|
mise.toml
|
||||||
|
|
||||||
# Editor temp files
|
# Editor temp files
|
||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
|
|
|
||||||
20
.woodpecker/changelog.yaml
Normal file
20
.woodpecker/changelog.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate"'
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
check-changelog:
|
||||||
|
image: docker.io/alpine:3.23
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache git
|
||||||
|
- sh ./tools/check-changelog
|
||||||
60
.woodpecker/docker-combine.yaml
Normal file
60
.woodpecker/docker-combine.yaml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
skip_clone: true
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
docker-develop-combine:
|
||||||
|
image: &docker_image git.pleroma.social/phnt/wpc-docker-tagger:0.9.1
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
settings: &docker_settings
|
||||||
|
registry: "git.pleroma.social"
|
||||||
|
image: "pleroma/pleroma"
|
||||||
|
architectures: [amd64, arm64, armv7]
|
||||||
|
tags:
|
||||||
|
- latest
|
||||||
|
- develop
|
||||||
|
- "${CI_COMMIT_SHA:0:8}"
|
||||||
|
username:
|
||||||
|
from_secret: pleroma-ci-user
|
||||||
|
password:
|
||||||
|
from_secret: pleroma-ci-password
|
||||||
|
|
||||||
|
docker-stable-combine:
|
||||||
|
image: *docker_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_settings
|
||||||
|
tags: &stable_docker_tags
|
||||||
|
- latest
|
||||||
|
- stable
|
||||||
|
- "${CI_COMMIT_SHA:0:8}"
|
||||||
|
|
||||||
|
docker-stable-tag-combine:
|
||||||
|
image: *docker_image
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
- evaluate: 'CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_settings
|
||||||
|
tags:
|
||||||
|
- <<: *stable_docker_tags
|
||||||
|
- "${CI_COMMIT_TAG}"
|
||||||
150
.woodpecker/docker.yaml
Normal file
150
.woodpecker/docker.yaml
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**", "Dockerfile" ]
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
- linux/arm
|
||||||
|
|
||||||
|
# This is needed for the when clauses below.
|
||||||
|
labels:
|
||||||
|
platform: ${platform}
|
||||||
|
memory: 'high'
|
||||||
|
|
||||||
|
variables:
|
||||||
|
docker_variables: &docker_variables
|
||||||
|
repo: pleroma/pleroma
|
||||||
|
registry: git.pleroma.social
|
||||||
|
username:
|
||||||
|
from_secret: pleroma-ci-user
|
||||||
|
password:
|
||||||
|
from_secret: pleroma-ci-password
|
||||||
|
armv7_docker_variables: &armv7_docker_variables
|
||||||
|
<<: *docker_variables
|
||||||
|
build_args:
|
||||||
|
- ELIXIR_IMG=docker.io/arm32v7/elixir
|
||||||
|
- ELIXIR_TAG=1.17.3-otp-27-alpine
|
||||||
|
- ALPINE_IMG=docker.io/arm32v7/alpine
|
||||||
|
- ALPINE_VER=3.20
|
||||||
|
extra_opts: --custom-platform=linux/arm/v7
|
||||||
|
kaniko_image: &kaniko_image docker.io/woodpeckerci/plugin-kaniko:2.3.2
|
||||||
|
|
||||||
|
steps:
|
||||||
|
docker-develop-amd64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags:
|
||||||
|
- latest-amd64
|
||||||
|
- develop-amd64
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-amd64
|
||||||
|
|
||||||
|
docker-develop-arm64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags:
|
||||||
|
- latest-arm64
|
||||||
|
- develop-arm64
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-arm64
|
||||||
|
|
||||||
|
docker-develop-armv7:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
settings:
|
||||||
|
<<: *armv7_docker_variables
|
||||||
|
tags:
|
||||||
|
- latest-armv7
|
||||||
|
- develop-armv7
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-armv7
|
||||||
|
|
||||||
|
docker-stable-amd64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags: &amd64_tags
|
||||||
|
- latest-amd64
|
||||||
|
- stable-amd64
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-amd64
|
||||||
|
|
||||||
|
# FE workflow runs only on linux/amd64
|
||||||
|
docker-stable-e2e-amd64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
dockerfile: Dockerfile-e2e
|
||||||
|
tags:
|
||||||
|
- stable-e2e
|
||||||
|
|
||||||
|
docker-stable-tag-amd64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags:
|
||||||
|
- <<: *amd64_tags
|
||||||
|
- ${CI_COMMIT_TAG}-amd64
|
||||||
|
|
||||||
|
docker-stable-arm64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags: &arm64_tags
|
||||||
|
- latest-arm64
|
||||||
|
- stable-arm64
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-arm64
|
||||||
|
|
||||||
|
docker-stable-tag-arm64:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
|
||||||
|
settings:
|
||||||
|
<<: *docker_variables
|
||||||
|
tags:
|
||||||
|
- <<: *arm64_tags
|
||||||
|
- ${CI_COMMIT_TAG}-arm64
|
||||||
|
|
||||||
|
docker-stable-armv7:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG == ""'
|
||||||
|
settings:
|
||||||
|
<<: *armv7_docker_variables
|
||||||
|
tags: &armv7_tags
|
||||||
|
- latest-armv7
|
||||||
|
- stable-armv7
|
||||||
|
- ${CI_COMMIT_SHA:0:8}-armv7
|
||||||
|
|
||||||
|
docker-stable-tag-armv7:
|
||||||
|
image: *kaniko_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable" && CI_COMMIT_TAG != ""'
|
||||||
|
settings:
|
||||||
|
<<: *armv7_docker_variables
|
||||||
|
tags:
|
||||||
|
- <<: *armv7_tags
|
||||||
|
- ${CI_COMMIT_TAG}-armv7
|
||||||
45
.woodpecker/docs.yaml
Normal file
45
.woodpecker/docs.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "docs/**" ]
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
evaluate: 'CI_COMMIT_BRANCH == "stable" || CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
skip_clone: true
|
||||||
|
|
||||||
|
variables:
|
||||||
|
trigger_settings: &trigger_settings
|
||||||
|
deploy: 'docs'
|
||||||
|
last-successful: true
|
||||||
|
token:
|
||||||
|
from_secret: pleroma-ci-woodpecker-token
|
||||||
|
repositories:
|
||||||
|
- pleroma/docs@master
|
||||||
|
docker_image: &docker_image docker.io/woodpeckerci/plugin-trigger:1.1.1
|
||||||
|
|
||||||
|
steps:
|
||||||
|
trigger-deploy-stable:
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
image: *docker_image
|
||||||
|
settings:
|
||||||
|
<<: *trigger_settings
|
||||||
|
params:
|
||||||
|
- BRANCH=stable
|
||||||
|
|
||||||
|
trigger-deploy-develop:
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
image: *docker_image
|
||||||
|
settings:
|
||||||
|
<<: *trigger_settings
|
||||||
|
params:
|
||||||
|
- BRANCH=develop
|
||||||
78
.woodpecker/lint.yaml
Normal file
78
.woodpecker/lint.yaml
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate"'
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
mix-format:
|
||||||
|
image: &elixir-image
|
||||||
|
docker.io/elixir:1.17-otp-27-alpine
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
failure: ignore
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
if ! mix format --check-formatted; then
|
||||||
|
touch fail.stamp
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
credo:
|
||||||
|
image: *elixir-image
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- |
|
||||||
|
if ! su testuser -c "HOME=/home/testuser mix analyze"; then
|
||||||
|
touch fail.stamp
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cycles:
|
||||||
|
# image: *elixir-image
|
||||||
|
# failure: ignore
|
||||||
|
# commands:
|
||||||
|
# - apk add --no-cache build-base cmake exiftool ffmpeg file-dev git openssl
|
||||||
|
# - adduser -D -h /home/testuser testuser
|
||||||
|
# - mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
# - chown -R testuser:testuser . /home/testuser
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
# - su testuser -c "HOME=/home/testuser mix compile"
|
||||||
|
# - |
|
||||||
|
# if ! su testuser -c "HOME=/home/testuser mix xref graph --format cycles --label compile | awk '{print $0} END{exit ($0 != \"No cycles found\")}'"; then
|
||||||
|
# touch fail.stamp
|
||||||
|
# exit 1
|
||||||
|
# fi
|
||||||
|
|
||||||
|
ensure-status:
|
||||||
|
image: *elixir-image
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
commands: |
|
||||||
|
if test -f fail.stamp; then
|
||||||
|
echo "One or more previous steps fails. Failing workflow..."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All steps passed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
292
.woodpecker/otp-musl.yaml
Normal file
292
.woodpecker/otp-musl.yaml
Normal file
|
|
@ -0,0 +1,292 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm
|
||||||
|
- linux/arm64
|
||||||
|
|
||||||
|
# This is needed for the when clauses below.
|
||||||
|
# When the platform clause is fixed, this might not be needed anymore
|
||||||
|
labels:
|
||||||
|
platform: ${platform}
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
build_cmds: &build_cmds
|
||||||
|
- apk add git build-base cmake file-dev openssl vips-dev zip
|
||||||
|
- echo "import Config" > config/prod.secret.exs
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get --only prod
|
||||||
|
- mkdir release
|
||||||
|
- export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH}
|
||||||
|
- mix release --path release
|
||||||
|
build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir:1.17.3-erlang-27.3.4.17-alpine-3.22.5
|
||||||
|
build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3-alpine
|
||||||
|
build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir:1.17.3-erlang-27.3.4.17-alpine-3.22.5
|
||||||
|
artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.1
|
||||||
|
artifacts_uploader_settings: &artifacts_uploader_settings
|
||||||
|
user:
|
||||||
|
from_secret: pleroma-ci-user
|
||||||
|
password:
|
||||||
|
from_secret: pleroma-ci-password
|
||||||
|
owner: 'pleroma'
|
||||||
|
env: &env
|
||||||
|
MIX_ENV: prod
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
|
||||||
|
steps:
|
||||||
|
otp-develop-amd64-musl:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &amd64_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release
|
||||||
|
|
||||||
|
otp-stable-amd64-musl:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *amd64_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-amd64-musl:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip release
|
||||||
|
|
||||||
|
otp-develop-arm-musl:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &arm_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip release
|
||||||
|
|
||||||
|
otp-stable-arm-musl:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *arm_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-arm-musl:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip release
|
||||||
|
|
||||||
|
otp-develop-arm64-musl:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &arm64_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release
|
||||||
|
|
||||||
|
otp-stable-arm64-musl:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *arm64_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-arm64-musl:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip release
|
||||||
|
|
||||||
|
upload-artifacts-amd64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-amd64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-amd64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-amd64-musl
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-amd64-musl
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-amd64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-amd64-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-artifacts-arm-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-arm-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-arm-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm-musl
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-arm-musl
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-arm-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-artifacts-arm64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-arm64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-arm64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm64-musl
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-arm64-musl
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-arm64-musl:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm64-musl
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64-musl.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
293
.woodpecker/otp.yaml
Normal file
293
.woodpecker/otp.yaml
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm
|
||||||
|
- linux/arm64
|
||||||
|
|
||||||
|
# This is needed for the when clauses below.
|
||||||
|
# When the platform clause is fixed, this might not be needed anymore
|
||||||
|
labels:
|
||||||
|
platform: ${platform}
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
build_cmds: &build_cmds
|
||||||
|
- apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git build-essential zip
|
||||||
|
- echo "import Config" > config/prod.secret.exs
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get --only prod
|
||||||
|
- mkdir release
|
||||||
|
- export PLEROMA_BUILD_BRANCH=${CI_COMMIT_BRANCH}
|
||||||
|
- mix release --path release
|
||||||
|
build_image_amd64: &build_image_amd64 docker.io/hexpm/elixir:1.17.3-erlang-27.3.4.17-ubuntu-noble-20260810
|
||||||
|
build_image_arm: &build_image_arm docker.io/arm32v7/elixir:1.17.3
|
||||||
|
build_image_arm64: &build_image_arm64 docker.io/hexpm/elixir:1.17.3-erlang-27.3.4.17-ubuntu-noble-20260810
|
||||||
|
artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.1
|
||||||
|
artifacts_uploader_settings: &artifacts_uploader_settings
|
||||||
|
user:
|
||||||
|
from_secret: pleroma-ci-user
|
||||||
|
password:
|
||||||
|
from_secret: pleroma-ci-password
|
||||||
|
owner: 'pleroma'
|
||||||
|
env: &env
|
||||||
|
MIX_ENV: prod
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
|
||||||
|
steps:
|
||||||
|
otp-develop-amd64:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &amd64_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip release
|
||||||
|
|
||||||
|
otp-stable-amd64:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *amd64_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-amd64:
|
||||||
|
image: *build_image_amd64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-amd64.zip release
|
||||||
|
|
||||||
|
otp-develop-arm:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &arm_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip release
|
||||||
|
|
||||||
|
otp-stable-arm:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *arm_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-arm:
|
||||||
|
image: *build_image_arm
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm.zip release
|
||||||
|
|
||||||
|
otp-develop-arm64:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
environment: *env
|
||||||
|
commands: &arm64_build
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip release
|
||||||
|
|
||||||
|
otp-stable-arm64:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual" && CI_COMMIT_BRANCH == "stable"'
|
||||||
|
environment: *env
|
||||||
|
commands: *arm64_build
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
otp-stable-tag-arm64:
|
||||||
|
image: *build_image_arm64
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
environment: *env
|
||||||
|
commands:
|
||||||
|
- <<: *build_cmds
|
||||||
|
- zip -9rq stable-${CI_COMMIT_SHA:0:8}-arm64.zip release
|
||||||
|
|
||||||
|
upload-artifacts-amd64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-amd64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-amd64
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-amd64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-amd64
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-amd64
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-amd64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/amd64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-amd64
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-amd64.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-artifacts-arm:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-arm:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-arm:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-arm
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-arm:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-artifacts-arm64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64
|
||||||
|
package_version: ${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
file_name: pleroma-${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
upload-latest-arm64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "push" && CI_COMMIT_BRANCH == "${CI_REPO_DEFAULT_BRANCH}"'
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "manual"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-${CI_COMMIT_BRANCH}-arm64
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./${CI_COMMIT_BRANCH}-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-artifacts-tag-arm64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm64
|
||||||
|
package_version: stable-${CI_COMMIT_SHA:0:8}-arm64
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
file_name: pleroma-stable-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
update: 'true'
|
||||||
|
|
||||||
|
# Tag events don't have CI_COMMIT_BRANCH set, hardcode stable
|
||||||
|
upload-latest-tag-arm64:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
when:
|
||||||
|
- evaluate: 'platform == "linux/arm64" && CI_PIPELINE_EVENT == "tag"'
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_name: pleroma-otp-stable-arm64
|
||||||
|
package_version: latest
|
||||||
|
file_source: ./stable-${CI_COMMIT_SHA:0:8}-arm64.zip
|
||||||
|
file_name: pleroma.zip
|
||||||
|
update: 'true'
|
||||||
66
.woodpecker/paradedb-integration.yaml
Normal file
66
.woodpecker/paradedb-integration.yaml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path:
|
||||||
|
- ".woodpecker/**"
|
||||||
|
- "lib/pleroma/search/**"
|
||||||
|
- "test/pleroma/search/**"
|
||||||
|
- "mix.exs"
|
||||||
|
- "mix.lock"
|
||||||
|
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate"'
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path:
|
||||||
|
- ".woodpecker/**"
|
||||||
|
- "lib/pleroma/search/**"
|
||||||
|
- "test/pleroma/search/**"
|
||||||
|
- "mix.exs"
|
||||||
|
- "mix.lock"
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
paradedb-integration:
|
||||||
|
image: docker.io/elixir:1.19-otp-28-alpine
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
PARADEDB_TEST_URL: postgres://postgres:postgres@paradedb:5432/paradedb
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg vips-dev file-dev git openssl postgresql-client
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get
|
||||||
|
- timeout 60 sh -c 'until pg_isready -h postgres -U postgres -d pleroma_test; do sleep 1; done' || { echo "PostgreSQL did not become ready" >&2; exit 1; }
|
||||||
|
- timeout 60 sh -c 'until pg_isready -h paradedb -U postgres -d paradedb; do sleep 1; done' || { echo "ParadeDB did not become ready" >&2; exit 1; }
|
||||||
|
- mix ecto.create
|
||||||
|
- mix ecto.migrate
|
||||||
|
- mix test --include integration test/pleroma/search/parade_db_integration_test.exs
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: docker.io/postgres:13-alpine
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
paradedb:
|
||||||
|
image: docker.io/paradedb/paradedb:0.24.3-pg15@sha256:997d62f6c2605becb7c45878af3f8c8c698f355006b675b3ce212d4eb2d14f2e
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: paradedb
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
103
.woodpecker/spec-build.yaml
Normal file
103
.woodpecker/spec-build.yaml
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path:
|
||||||
|
- ".woodpecker/spec-build.yaml"
|
||||||
|
- "mix.exs"
|
||||||
|
- "mix.lock"
|
||||||
|
- "lib/pleroma/web.ex"
|
||||||
|
- "lib/pleroma/web/**/*.ex"
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
evaluate: 'CI_COMMIT_BRANCH == "stable" || CI_COMMIT_BRANCH == CI_REPO_DEFAULT_BRANCH'
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
concurrency: 1
|
||||||
|
|
||||||
|
variables:
|
||||||
|
artifacts_uploader_image: &artifacts_uploader_image docker.io/woodpeckercommunity/plugin-gitea-package:0.5.1
|
||||||
|
artifacts_uploader_settings: &artifacts_uploader_settings
|
||||||
|
user:
|
||||||
|
from_secret: pleroma-ci-user
|
||||||
|
password:
|
||||||
|
from_secret: pleroma-ci-password
|
||||||
|
owner: 'pleroma'
|
||||||
|
package_name: api-docs-spec
|
||||||
|
file_source: ./spec.json
|
||||||
|
file_name: spec.json
|
||||||
|
update: 'true'
|
||||||
|
trigger_image: &trigger_image docker.io/woodpeckerci/plugin-trigger:1.1.1
|
||||||
|
trigger_settings: &trigger_settings
|
||||||
|
deploy: 'api-docs'
|
||||||
|
last-successful: true
|
||||||
|
token:
|
||||||
|
from_secret: pleroma-ci-woodpecker-token
|
||||||
|
repositories:
|
||||||
|
- pleroma/api-docs@master
|
||||||
|
|
||||||
|
steps:
|
||||||
|
build-spec:
|
||||||
|
image: docker.io/hexpm/elixir:1.17.3-erlang-27.3.4.17-alpine-3.22.5
|
||||||
|
environment:
|
||||||
|
MIX_ENV: prod
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
commands:
|
||||||
|
- apk update --no-cache
|
||||||
|
- apk add git build-base cmake file-dev openssl vips-dev zip
|
||||||
|
- mix local.hex --force
|
||||||
|
- mix local.rebar --force
|
||||||
|
- mix deps.get --only prod
|
||||||
|
- mix pleroma.openapi_spec spec.json
|
||||||
|
- |
|
||||||
|
printf 'BRANCH=stable\nSPEC_VERSION=%s\n' "$${CI_COMMIT_SHA}" > trigger-deploy-stable.params
|
||||||
|
printf 'BRANCH=develop\nSPEC_VERSION=%s\n' "$${CI_COMMIT_SHA}" > trigger-deploy-develop.params
|
||||||
|
|
||||||
|
upload-spec:
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_version: ${CI_COMMIT_SHA}
|
||||||
|
|
||||||
|
upload-spec-stable:
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_version: stable
|
||||||
|
|
||||||
|
upload-spec-develop:
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
image: *artifacts_uploader_image
|
||||||
|
settings:
|
||||||
|
<<: *artifacts_uploader_settings
|
||||||
|
package_version: develop
|
||||||
|
|
||||||
|
trigger-deploy-stable:
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
- event: manual
|
||||||
|
branch: stable
|
||||||
|
image: *trigger_image
|
||||||
|
settings:
|
||||||
|
<<: *trigger_settings
|
||||||
|
params:
|
||||||
|
- trigger-deploy-stable.params
|
||||||
|
|
||||||
|
trigger-deploy-develop:
|
||||||
|
when:
|
||||||
|
- event: push
|
||||||
|
- event: manual
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
image: *trigger_image
|
||||||
|
settings:
|
||||||
|
<<: *trigger_settings
|
||||||
|
params:
|
||||||
|
- trigger-deploy-develop.params
|
||||||
47
.woodpecker/unit-testing-elixir-1.17.yaml
Normal file
47
.woodpecker/unit-testing-elixir-1.17.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate"'
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
unit-testing-elixir-1.17:
|
||||||
|
image: elixir:1.17-otp-27-alpine
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg vips-dev file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
47
.woodpecker/unit-testing-elixir-1.19.yaml
Normal file
47
.woodpecker/unit-testing-elixir-1.19.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
when:
|
||||||
|
- event: pull_request
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
evaluate: 'CI_COMMIT_SOURCE_BRANCH != "weblate"'
|
||||||
|
- event: push
|
||||||
|
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
path: [ "**/*.ex", "**/*.eex", "**/*.exs", "mix.lock", ".woodpecker/**" ]
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- lint
|
||||||
|
|
||||||
|
labels:
|
||||||
|
platform: linux/amd64
|
||||||
|
|
||||||
|
variables:
|
||||||
|
script_file_entrypoint: &script_file_entrypoint
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
unit-testing-elixir-1.19:
|
||||||
|
image: elixir:1.19-otp-28-alpine
|
||||||
|
entrypoint: *script_file_entrypoint
|
||||||
|
environment:
|
||||||
|
MIX_ENV: test
|
||||||
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
commands:
|
||||||
|
- apk add --no-cache build-base cmake exiftool ffmpeg vips-dev file-dev git openssl
|
||||||
|
- adduser -D -h /home/testuser testuser
|
||||||
|
- mkdir -p /home/testuser/.mix /home/testuser/.hex
|
||||||
|
- chown -R testuser:testuser . /home/testuser
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.hex --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix local.rebar --force"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix deps.get"
|
||||||
|
- su testuser -c "HOME=/home/testuser mix pleroma.test_runner --preload-modules"
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13-alpine
|
||||||
|
failure: ignore
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: pleroma_test
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
163
CHANGELOG.md
163
CHANGELOG.md
|
|
@ -4,6 +4,169 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## 2.11.0
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update Bandit to address HTTP request handling vulnerabilities.
|
||||||
|
- Update bandit to version 1.12.4 to fix a DoS vulnerability
|
||||||
|
- Update bandit to version 1.12.5 to fix an HTTP/2 DoS vulnerability
|
||||||
|
- Update bandit to version 1.13.1 resolving a stuck process and unvalidated HTTP/1 headers vulnerabilities
|
||||||
|
- Updated CAStore to version 1.0.22 with updated Mozilla certificate bundle after eMudhra Intermediate CA Compromise
|
||||||
|
- Update certifi to version 2.18.0, which removes the distrusted emSign roots
|
||||||
|
- Update plug_crypto to version 2.2.1, so a malformed shout socket token no longer writes the secret key base to the error log
|
||||||
|
- Update Cowboy to address multipart header parsing denial-of-service vulnerabilities.
|
||||||
|
- Update cowboy to version 2.19.0 which fixes a memory exhaustion DoS
|
||||||
|
- Update Cowlib to address HTTP parsing resource exhaustion and injection vulnerabilities.
|
||||||
|
- Update cowlib to version 2.20.0 which fixes HTTP splitting and header smuggling/injection vulnerabilities
|
||||||
|
- Update gun to version 2.7.0 and cowlib to version 2.21.0 resolving a header smuggling vulnerability
|
||||||
|
- Update Decimal and compatible dependencies to address a rounding vulnerability.
|
||||||
|
- Update Decimal to version 3.2.0 resolving an unbounded allocation vulnerability
|
||||||
|
- Replace retired Earmark rendering with the maintained EarmarkParser and an attribute-escaping renderer.
|
||||||
|
- Update gun to version 2.6.0 which fixes an HTTP request/response smuggling vulnerability
|
||||||
|
- Update Hackney and its HTTPoison integration to address SSRF, timeout, and injection vulnerabilities.
|
||||||
|
- Hackney, Finch, Mua and the Swoosh HTTP mail adapters now trust only the CAStore certificate bundle, which rejects the compromised eMudhra intermediate CAs
|
||||||
|
- Ensure Host header is present and matches instance URI
|
||||||
|
- Update mint to version 1.10.0 which fixes multiple DoS vulnerabilities
|
||||||
|
- Update mint to version 1.11.0 resolving multiple vulnerabilities
|
||||||
|
- Update Phoenix LiveView to version 1.2.9 to fix an open redirect vulnerability
|
||||||
|
- Update Phoenix to version 1.8.11 resolving a process-exhaustion DoS vulnerability
|
||||||
|
- Update Phoenix to address long-poll NDJSON request denial-of-service vulnerabilities.
|
||||||
|
- Update Plug to address multipart header parsing denial-of-service vulnerabilities.
|
||||||
|
- Update Plug.Cowboy to address HTTP/2 scheme atom exhaustion denial-of-service vulnerabilities.
|
||||||
|
- Update Postgrex to address notification channel-name SQL injection vulnerabilities.
|
||||||
|
- Update postgrex to version 0.22.4 to fix an SQL injection vulnerability
|
||||||
|
- Remove unused QUIC lockfile entry with vulnerable TLS verification behavior.
|
||||||
|
- Update swoosh to version 1.28.0 which fixes a url injection vulnerability for the Microsoft Graph adapter
|
||||||
|
- Scrubbers: remove links and images whose scheme is hidden with tabs, newlines or leading control characters (for example java<TAB>script:), which browsers still run as javascript:. Custom scrubbers should use Pleroma.HTML.URIAttributes.allow_tag_with_uri_attributes instead of the FastSanitize macro
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update bundled Pleroma-FE to 2.11.4
|
||||||
|
- Allow disabling Chats feature
|
||||||
|
- Pleroma now requires Elixir 1.17 or newer and Erlang/OTP 27 or newer
|
||||||
|
- Update Debian and Ubuntu installation requirements and OTP dependencies
|
||||||
|
- Update Jason to support Decimal 3 dependency resolution.
|
||||||
|
- Changed how Logger is configured due to changes in Elixir and added deprecation warnings for the old configuration format
|
||||||
|
- Update majic to 1.2.0.
|
||||||
|
- Avoid a duplicate user query during OAuth authentication.
|
||||||
|
- Use the Hex package for oban_plugins_lazarus.
|
||||||
|
- Docs OpenBSD: Increased recommended `datasize` from 1536M to 4096M to avoid Pleroma crashes
|
||||||
|
- OpenRC: Automatically run migrations before starting pleroma
|
||||||
|
- Optimize orphaned OAuth apps cleanup by deleting records in batches instead of one by one.
|
||||||
|
- Switch patched Phoenix 1.7.14 back to upstream with Phoenix 1.8.0+
|
||||||
|
- Add optional migrations for rollback from Rebased
|
||||||
|
- Use upstream Hex releases for the `remote_ip` dependency and expose client IP ranges for remote IP resolution.
|
||||||
|
- Improve user search / autocompletion ordering.
|
||||||
|
- Gun is now the default HTTP client instead of Hackney
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow creating accounts without a password in the AdminAPI
|
||||||
|
- Implemented ConfigDB configuration blacklist and accompanying Mix task
|
||||||
|
- Accept more real-world ActivityPub object shapes collected by fedidev.fun
|
||||||
|
- Add Mastodon-compatible grouped notifications API endpoints under `/api/v2/notifications` for newly-created notifications
|
||||||
|
- Docs: Added documentation for installing newer version of Elixir/Erlang than what distro ships
|
||||||
|
- Allow attaching emojis to user lists
|
||||||
|
- Implement basics of Mastodon admin API
|
||||||
|
- Add backend support for Misskey Markdown (MFM) posts
|
||||||
|
- Add mix_audit for dependency vulnerability checks.
|
||||||
|
- Oban: Enabled the Reindexer plugin to reduce oban_jobs index DB bloat
|
||||||
|
- Add ParadeDB (pg_search) as an optional external search backend, indexing status content, content warnings, and attachment descriptions.
|
||||||
|
- Add support for Mastodon-compatible webhooks
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `/api/v1/pleroma/accounts/:id/favourites` ordering and pagination to use the favourite date.
|
||||||
|
- Reject unsupported or ambiguous inbox signatures instead of queueing a doomed failed-signature retry, allowing clients to fall back to legacy HTTP Signatures.
|
||||||
|
- Switch native captcha to the published pleroma_captcha Hex package.
|
||||||
|
- Updated the captcha package to fix compilation failure on BSD-based systems
|
||||||
|
- Dockerfile: update base images to fix problems with SSL handshakes
|
||||||
|
- Docs: Fixed OTP install links in documentation, broke up wall of install commands into smaller chunks, fixed sudo commands execution
|
||||||
|
- Fix notification and timeline queries for users with domain blocks choosing a plan that scans every active user per row; the followed-accounts exception is now an `ARRAY(SELECT ...)` InitPlan that the planner costs once.
|
||||||
|
- Fixed Elixir 1.19 warnings
|
||||||
|
- Support fetching featured collection items from the first collection page
|
||||||
|
- Fix preferred frontend not working due to InstanceStatic plug prematurely returning HTTP 404
|
||||||
|
- Fixed Gun HTTP/1 concurrency, forwarding and authenticated proxy handling, tunnel requests, and streamed connection cleanup. Updated Gun and Cowlib for HTTP client security fixes.
|
||||||
|
- Stop leaking a Hackney connection process, and the response bytes it read, for every unpooled request that ends without a reusable socket
|
||||||
|
- Fix hashtag timeline query for additional tag handling
|
||||||
|
- Fix compatibility with timestamped HTTP Signatures used by GoToSocial
|
||||||
|
- Handle reports with just actor ap id as the object
|
||||||
|
- Fix Swoosh HTTP mail adapters failing with `cacerts: :undefined` on OTP 26+
|
||||||
|
- Echo Mastodon-style `Sec-WebSocket-Protocol` tokens in streaming WebSocket handshakes.
|
||||||
|
- Enforce the max_body HTTP option again, which Hackney 4 silently stopped honouring because it always returns a fully read body
|
||||||
|
- Abort incomplete media proxy responses instead of caching partial bodies.
|
||||||
|
- Updated Pleroma-FE build URL after Forgejo migration
|
||||||
|
- Fix votersCount inflation when same voter picks multiple options
|
||||||
|
- Status: Fill all MastoAPI required values in preview cards
|
||||||
|
- Avoid creating atoms from serialized PublisherWorker parameters.
|
||||||
|
- Reject incoming reports when both the reporter and reported account are remote
|
||||||
|
- Fixed account status and media views exposing activities restricted from unauthenticated users.
|
||||||
|
- Stop leaking a Hackney connection process for every media proxy response that was streamed to the end
|
||||||
|
- Fix RSS feeds to be W3C compliant and render better in RSS readers
|
||||||
|
- Keep followed users visible in streams when their domain is blocked.
|
||||||
|
- Fix intermittent failure to compile syslog package
|
||||||
|
- Fix inconsistent User cache keys and invalidate numeric ID aliases.
|
||||||
|
- Include arm32v7 in multi-arch Docker images
|
||||||
|
- RichMedia: Fix backfill causing old posts to show up on timelines by disabling it in MastoAPI StatusView
|
||||||
|
- Fix Admin-FE installation by downloading builds from the package registry instead of GitLab job artifacts.
|
||||||
|
- Hackney: use HTTP/1.1 only, so closing one media proxy response no longer breaks other downloads from the same host
|
||||||
|
- Hackney: use the configured proxy when its host is an IP address; Hackney 4 ignored such proxies and connected directly
|
||||||
|
|
||||||
|
## 2.10.2
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- ActivityPub: Fixed failed-signature inbox retry handling and signer identity checks to prevent spoofed remote activities from being processed
|
||||||
|
|
||||||
|
## 2.10.1
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Move avatar_description and header_description fields to the account object
|
||||||
|
- Update Bandit to 1.10.4
|
||||||
|
- No-op code correctness improvements detected by Elixir 1.19 compiler
|
||||||
|
- Downgrade Hackney to 1.20.1
|
||||||
|
- Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney
|
||||||
|
- Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation
|
||||||
|
- Paginate follow requests
|
||||||
|
- Moved Phoenix LiveDashboard to /pleroma/live_dashboard
|
||||||
|
- Add mute/block expiry to the relationship object
|
||||||
|
- Filter indexable activities before inserting indexing jobs into the queue.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Allow assigning users to reports
|
||||||
|
- Allow fine-grained announce visibilities
|
||||||
|
- Add immutable tag on cache-control header for several endpoints that's serving the same exact things.
|
||||||
|
- Add reasonable defaults for :database_config_whitelist
|
||||||
|
- Support lists `exclusive` param
|
||||||
|
- Add v1/instance/domain_blocks endpoint
|
||||||
|
- Add /api/v2/instance profile fields limits info used by Mastodon
|
||||||
|
- Added Oban Web dashboard located at /pleroma/oban
|
||||||
|
- Add instructions on how to run a release in docker, to make it easier to run on older distros.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix the daily email digest job which was not executing
|
||||||
|
- Encode custom emoji URLs in EmojiReact activity tags.
|
||||||
|
- Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled
|
||||||
|
- Fix fetching Hubzilla Actors with alsoKnownAs as string
|
||||||
|
- Fix /phoenix/live_dashboard redirect not working when user added a path segment
|
||||||
|
- Fix 404 error codes for missing static files
|
||||||
|
- Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input.
|
||||||
|
- Correct old migrations for expiring activities and user access tokens.
|
||||||
|
- Federate `votersCount` correctly
|
||||||
|
- DB prune: Check if user follows hashtag with no objects before deletion
|
||||||
|
- Stop the rate limiter from crashing when run with wrong settings.
|
||||||
|
- Restore embeds route
|
||||||
|
- ReverseProxy: Recursively follow redirects until redirect_limit is reached
|
||||||
|
- Fix compilation with vips-8.18.0 with bumping to vix 0.36.0
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)
|
||||||
|
|
||||||
## 2.10
|
## 2.10
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -1,10 +1,12 @@
|
||||||
# https://hub.docker.com/r/hexpm/elixir/tags
|
# https://hub.docker.com/r/hexpm/elixir/tags
|
||||||
ARG ELIXIR_IMG=hexpm/elixir
|
ARG ELIXIR_IMG=hexpm/elixir
|
||||||
ARG ELIXIR_VER=1.17.3
|
ARG ELIXIR_VER=1.17.3
|
||||||
ARG ERLANG_VER=26.2.5.6
|
ARG ERLANG_VER=27.3.4.17
|
||||||
ARG ALPINE_VER=3.17.9
|
ARG ALPINE_VER=3.23.5
|
||||||
|
ARG ELIXIR_TAG=${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER}
|
||||||
|
ARG ALPINE_IMG=alpine
|
||||||
|
|
||||||
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} AS build
|
FROM ${ELIXIR_IMG}:${ELIXIR_TAG} AS build
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
@ -20,7 +22,7 @@ RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
|
||||||
mkdir release &&\
|
mkdir release &&\
|
||||||
mix release --path release
|
mix release --path release
|
||||||
|
|
||||||
FROM alpine:${ALPINE_VER}
|
FROM ${ALPINE_IMG}:${ALPINE_VER}
|
||||||
|
|
||||||
ARG BUILD_DATE
|
ARG BUILD_DATE
|
||||||
ARG VCS_REF
|
ARG VCS_REF
|
||||||
|
|
|
||||||
61
Dockerfile-e2e
Normal file
61
Dockerfile-e2e
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# https://hub.docker.com/r/hexpm/elixir/tags
|
||||||
|
ARG ELIXIR_IMG=docker.io/hexpm/elixir
|
||||||
|
ARG ALPINE_IMG=docker.io/alpine
|
||||||
|
ARG ELIXIR_VER=1.18.4
|
||||||
|
ARG ERLANG_VER=27.3.4.11
|
||||||
|
ARG ALPINE_VER=3.23.4
|
||||||
|
|
||||||
|
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} AS build
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV MIX_ENV=prod
|
||||||
|
ENV VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
|
||||||
|
RUN apk add git gcc g++ musl-dev make cmake file-dev vips-dev &&\
|
||||||
|
echo "import Config" > config/prod.secret.exs &&\
|
||||||
|
mix local.hex --force &&\
|
||||||
|
mix local.rebar --force &&\
|
||||||
|
mix deps.clean --all &&\
|
||||||
|
mix deps.get --only prod &&\
|
||||||
|
mkdir release &&\
|
||||||
|
mix release --path release
|
||||||
|
|
||||||
|
FROM ${ALPINE_IMG}:${ALPINE_VER}
|
||||||
|
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG VCS_REF
|
||||||
|
|
||||||
|
LABEL maintainer="ops@pleroma.social" \
|
||||||
|
org.opencontainers.image.title="pleroma" \
|
||||||
|
org.opencontainers.image.description="Pleroma FE E2E test image" \
|
||||||
|
org.opencontainers.image.authors="ops@pleroma.social" \
|
||||||
|
org.opencontainers.image.vendor="pleroma.social" \
|
||||||
|
org.opencontainers.image.documentation="https://git.pleroma.social/pleroma/pleroma" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0" \
|
||||||
|
org.opencontainers.image.url="https://pleroma.social" \
|
||||||
|
org.opencontainers.image.revision=$VCS_REF \
|
||||||
|
org.opencontainers.image.created=$BUILD_DATE
|
||||||
|
|
||||||
|
ARG HOME=/opt/pleroma
|
||||||
|
ARG DATA=/var/lib/pleroma
|
||||||
|
|
||||||
|
RUN apk update &&\
|
||||||
|
apk add exiftool ffmpeg vips libmagic ncurses postgresql-client &&\
|
||||||
|
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
|
||||||
|
mkdir -p ${DATA}/uploads &&\
|
||||||
|
mkdir -p ${DATA}/static &&\
|
||||||
|
chown -R pleroma ${DATA} &&\
|
||||||
|
mkdir -p /etc/pleroma &&\
|
||||||
|
chown -R pleroma /etc/pleroma
|
||||||
|
|
||||||
|
USER pleroma
|
||||||
|
|
||||||
|
COPY --from=build --chown=pleroma:0 /release ${HOME}
|
||||||
|
|
||||||
|
COPY --chown=pleroma --chmod=640 ./config/docker.exs /etc/pleroma/config.exs
|
||||||
|
COPY ./docker-entrypoint-e2e.sh ${HOME}
|
||||||
|
|
||||||
|
EXPOSE 4000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/pleroma/docker-entrypoint-e2e.sh"]
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<img src="https://git.pleroma.social/pleroma/pleroma/uploads/8cec84f5a084d887339f57deeb8a293e/pleroma-banner-vector-nopad-notext.svg" width="300px" />
|
<img src="https://git.pleroma.social/pleroma/pleroma/raw/branch/develop/logo-with-name.png" width="300px" />
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Encode custom emoji URLs in EmojiReact activity tags.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Use a custom redirect handler to ensure MediaProxy redirects are followed with Hackney
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Update Hackney, the default HTTP client, to the latest release which supports Happy Eyeballs for improved IPv6 federation
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Docs: Removed outdated, incorrect, unmaintained and inappropriate installation documentation (Arch, NetBSD, NixOS)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Add v1/instance/domain_blocks endpoint
|
|
||||||
1
changelog.d/legacy-sequences.fix
Normal file
1
changelog.d/legacy-sequences.fix
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Delete legacy Postgres sequences for activities, chats, and users which were converted to Flake IDs
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix OAuth app registration to accept `redirect_uris` as an array of strings (RFC 7591), while keeping backwards compatibility with string input.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Added Oban Web dashboard located at /pleroma/oban
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Paginate follow requests
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Moved Phoenix LiveDashboard to /pleroma/live_dashboard
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Reduce the number of flaky tests by making them sync if they affect the global state, and silence noisy test output.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Add instructions on how to run a release in docker, to make it easier to run on older distros.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix compilation with vips-8.18.0 with bumping to vix 0.36.0
|
|
||||||
|
|
@ -64,9 +64,9 @@ config :pleroma, Pleroma.Upload,
|
||||||
link_name: false,
|
link_name: false,
|
||||||
proxy_remote: false,
|
proxy_remote: false,
|
||||||
filename_display_max_length: 30,
|
filename_display_max_length: 30,
|
||||||
default_description: :filename,
|
default_description: nil,
|
||||||
base_url: nil,
|
base_url: nil,
|
||||||
allowed_mime_types: ["image", "audio", "video", "application"]
|
allowed_mime_types: ["image", "audio", "video"]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
|
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
|
||||||
|
|
||||||
|
|
@ -133,13 +133,17 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Configures Elixir's Logger
|
# Configures Elixir's Logger
|
||||||
config :logger, backends: [:console]
|
# Primary config
|
||||||
|
config :logger, level: :debug
|
||||||
|
|
||||||
config :logger, :console,
|
# Console config
|
||||||
level: :debug,
|
config :logger, :default_handler, level: :debug
|
||||||
|
|
||||||
|
config :logger, :default_formatter,
|
||||||
format: "\n$time $metadata[$level] $message\n",
|
format: "\n$time $metadata[$level] $message\n",
|
||||||
metadata: [:actor, :path, :type, :user]
|
metadata: [:actor, :path, :type, :user]
|
||||||
|
|
||||||
|
# Syslog config
|
||||||
config :logger, :ex_syslogger,
|
config :logger, :ex_syslogger,
|
||||||
level: :debug,
|
level: :debug,
|
||||||
ident: "pleroma",
|
ident: "pleroma",
|
||||||
|
|
@ -157,7 +161,11 @@ config :mime, :types, %{
|
||||||
"image/apng" => ["apng"]
|
"image/apng" => ["apng"]
|
||||||
}
|
}
|
||||||
|
|
||||||
config :tesla, adapter: Tesla.Adapter.Hackney
|
config :tesla, adapter: Tesla.Adapter.Gun
|
||||||
|
|
||||||
|
# Hackney 4 shares one HTTP/2 connection per host, and closing one response
|
||||||
|
# (as the media proxy does) stops it for every other stream on it.
|
||||||
|
config :hackney, default_protocols: [:http1]
|
||||||
|
|
||||||
# Configures http settings, upstream proxy etc.
|
# Configures http settings, upstream proxy etc.
|
||||||
config :pleroma, :http,
|
config :pleroma, :http,
|
||||||
|
|
@ -167,18 +175,18 @@ config :pleroma, :http,
|
||||||
adapter: []
|
adapter: []
|
||||||
|
|
||||||
config :pleroma, :instance,
|
config :pleroma, :instance,
|
||||||
name: "Shigusegubu",
|
name: "Pleroma",
|
||||||
email: "pleroma@hjkos.com",
|
email: "example@example.com",
|
||||||
notify_email: "pleroma@hjkos.com",
|
notify_email: "noreply@example.com",
|
||||||
description: "SigSegV, a pleroma instance",
|
description: "Pleroma: An efficient and flexible fediverse server",
|
||||||
short_description: "HJ's semi-personal instance",
|
short_description: "",
|
||||||
background_image: "/images/city.jpg",
|
background_image: "/images/city.jpg",
|
||||||
instance_thumbnail: "/instance/thumbnail.jpeg",
|
instance_thumbnail: "/instance/thumbnail.jpeg",
|
||||||
favicon: "/favicon.png",
|
favicon: "/favicon.png",
|
||||||
limit: 5_000,
|
limit: 5_000,
|
||||||
description_limit: 5_000,
|
description_limit: 5_000,
|
||||||
remote_limit: 100_000,
|
remote_limit: 100_000,
|
||||||
upload_limit: 200_000_000,
|
upload_limit: 16_000_000,
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
background_upload_limit: 4_000_000,
|
background_upload_limit: 4_000_000,
|
||||||
banner_upload_limit: 4_000_000,
|
banner_upload_limit: 4_000_000,
|
||||||
|
|
@ -196,17 +204,20 @@ config :pleroma, :instance,
|
||||||
federation_incoming_replies_max_depth: 100,
|
federation_incoming_replies_max_depth: 100,
|
||||||
allow_relay: true,
|
allow_relay: true,
|
||||||
public: true,
|
public: true,
|
||||||
quarantined_instances: [{ "pleroma.rareome.ga", "leaks private posts or sumshit i dont rember" }],
|
quarantined_instances: [],
|
||||||
rejected_instances: [],
|
rejected_instances: [],
|
||||||
static_dir: "instance/static/",
|
static_dir: "instance/static/",
|
||||||
allowed_post_formats: [
|
allowed_post_formats: [
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/bbcode"
|
"text/html",
|
||||||
|
"text/markdown",
|
||||||
|
"text/bbcode",
|
||||||
|
"text/x.misskeymarkdown"
|
||||||
],
|
],
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
autofollowing_nicknames: [],
|
autofollowing_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
attachment_links: true,
|
attachment_links: false,
|
||||||
max_report_comment_size: 1000,
|
max_report_comment_size: 1000,
|
||||||
report_strip_status: true,
|
report_strip_status: true,
|
||||||
safe_dm_mentions: false,
|
safe_dm_mentions: false,
|
||||||
|
|
@ -299,8 +310,8 @@ config :pleroma, :markup,
|
||||||
|
|
||||||
config :pleroma, :frontend_configurations,
|
config :pleroma, :frontend_configurations,
|
||||||
pleroma_fe: %{
|
pleroma_fe: %{
|
||||||
alwaysShowSubjectInput: false,
|
alwaysShowSubjectInput: true,
|
||||||
background: "/static/sigsegv_s.png",
|
background: "/images/city.jpg",
|
||||||
collapseMessageWithSubject: false,
|
collapseMessageWithSubject: false,
|
||||||
disableChat: false,
|
disableChat: false,
|
||||||
greentext: false,
|
greentext: false,
|
||||||
|
|
@ -314,18 +325,18 @@ config :pleroma, :frontend_configurations,
|
||||||
logo: "/static/logo.svg",
|
logo: "/static/logo.svg",
|
||||||
logoMargin: ".1em",
|
logoMargin: ".1em",
|
||||||
logoMask: true,
|
logoMask: true,
|
||||||
minimalScopesMode: true,
|
minimalScopesMode: false,
|
||||||
noAttachmentLinks: false,
|
noAttachmentLinks: false,
|
||||||
nsfwCensorImage: "",
|
nsfwCensorImage: "",
|
||||||
postContentType: "text/plain",
|
postContentType: "text/plain",
|
||||||
redirectRootLogin: "/main/friends",
|
redirectRootLogin: "/main/friends",
|
||||||
redirectRootNoLogin: "/main/all",
|
redirectRootNoLogin: "/main/all",
|
||||||
scopeCopy: false,
|
scopeCopy: true,
|
||||||
sidebarRight: false,
|
sidebarRight: false,
|
||||||
showFeaturesPanel: true,
|
showFeaturesPanel: true,
|
||||||
showInstanceSpecificPanel: true,
|
showInstanceSpecificPanel: false,
|
||||||
subjectLineBehavior: "noop",
|
subjectLineBehavior: "email",
|
||||||
theme: "sigsegv2",
|
theme: "pleroma-dark",
|
||||||
webPushNotifications: false
|
webPushNotifications: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -384,23 +395,8 @@ config :pleroma, :mrf_hellthread,
|
||||||
|
|
||||||
config :pleroma, :mrf_simple,
|
config :pleroma, :mrf_simple,
|
||||||
media_removal: [],
|
media_removal: [],
|
||||||
media_nsfw: [
|
media_nsfw: [],
|
||||||
{ "preteengirls.biz", "pron or even cp" },
|
federated_timeline_removal: [],
|
||||||
{ "melalandia.tk", "i don't rember" },
|
|
||||||
{ "pl.smuglo.li", "instance is dead but i still remember, rip smuglo" },
|
|
||||||
{ "baraag.net", "pron" },
|
|
||||||
{ "humblr.social", "3dpd pron" },
|
|
||||||
{ "pawoo.net", "tasteful pron, mostly." },
|
|
||||||
{ "sinblr.com", "3dpd pron" }
|
|
||||||
],
|
|
||||||
federated_timeline_removal: [
|
|
||||||
{ "preteengirls.biz", "pron or even cp" },
|
|
||||||
{ "melalandia.tk", "i don't rember" },
|
|
||||||
{ "baraag.net", "pron" },
|
|
||||||
{ "humblr.social", "3dpd pron" },
|
|
||||||
{ "pawoo.net", "tasteful pron, mostly." },
|
|
||||||
{ "sinblr.com", "3dpd pron" }
|
|
||||||
],
|
|
||||||
report_removal: [],
|
report_removal: [],
|
||||||
reject: [],
|
reject: [],
|
||||||
followers_only: [],
|
followers_only: [],
|
||||||
|
|
@ -511,7 +507,7 @@ config :pleroma, :media_preview_proxy,
|
||||||
min_content_length: 100 * 1024
|
min_content_length: 100 * 1024
|
||||||
|
|
||||||
config :pleroma, :shout,
|
config :pleroma, :shout,
|
||||||
enabled: false,
|
enabled: true,
|
||||||
limit: 5_000
|
limit: 5_000
|
||||||
|
|
||||||
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
|
config :phoenix, :format_encoders, json: Jason, "activity+json": Jason
|
||||||
|
|
@ -615,7 +611,11 @@ config :pleroma, Oban,
|
||||||
search_indexing: [limit: 10, paused: true],
|
search_indexing: [limit: 10, paused: true],
|
||||||
slow: 5
|
slow: 5
|
||||||
],
|
],
|
||||||
plugins: [Oban.Plugins.Lazarus, {Oban.Plugins.Pruner, max_age: 900}],
|
plugins: [
|
||||||
|
Oban.Plugins.Lazarus,
|
||||||
|
{Oban.Plugins.Pruner, max_age: 900},
|
||||||
|
{Oban.Plugins.Reindexer, schedule: "@weekly"}
|
||||||
|
],
|
||||||
crontab: [
|
crontab: [
|
||||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
|
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
|
||||||
|
|
@ -750,6 +750,7 @@ config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifeti
|
||||||
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
|
config :pleroma, Pleroma.Web.Plugs.RemoteIp,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
headers: ["x-forwarded-for"],
|
headers: ["x-forwarded-for"],
|
||||||
|
clients: [],
|
||||||
proxies: [],
|
proxies: [],
|
||||||
reserved: [
|
reserved: [
|
||||||
"127.0.0.0/8",
|
"127.0.0.0/8",
|
||||||
|
|
@ -788,7 +789,7 @@ config :pleroma, :frontends,
|
||||||
"name" => "pleroma-fe",
|
"name" => "pleroma-fe",
|
||||||
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
|
"git" => "https://git.pleroma.social/pleroma/pleroma-fe",
|
||||||
"build_url" =>
|
"build_url" =>
|
||||||
"https://git.pleroma.social/pleroma/pleroma-fe/-/jobs/artifacts/${ref}/download?job=build",
|
"https://git.pleroma.social/api/packages/pleroma/generic/pleroma-fe-builds/${ref}/latest.zip",
|
||||||
"ref" => "develop"
|
"ref" => "develop"
|
||||||
},
|
},
|
||||||
"fedi-fe" => %{
|
"fedi-fe" => %{
|
||||||
|
|
@ -805,7 +806,7 @@ config :pleroma, :frontends,
|
||||||
"name" => "admin-fe",
|
"name" => "admin-fe",
|
||||||
"git" => "https://git.pleroma.social/pleroma/admin-fe",
|
"git" => "https://git.pleroma.social/pleroma/admin-fe",
|
||||||
"build_url" =>
|
"build_url" =>
|
||||||
"https://git.pleroma.social/pleroma/admin-fe/-/jobs/artifacts/${ref}/download?job=build",
|
"https://git.pleroma.social/api/packages/pleroma/generic/admin-fe-builds/${ref}/latest.zip",
|
||||||
"ref" => "develop"
|
"ref" => "develop"
|
||||||
},
|
},
|
||||||
"soapbox" => %{
|
"soapbox" => %{
|
||||||
|
|
@ -839,7 +840,7 @@ config :pleroma, :web_cache_ttl,
|
||||||
|
|
||||||
config :pleroma, :modules, runtime_dir: "instance/modules"
|
config :pleroma, :modules, runtime_dir: "instance/modules"
|
||||||
|
|
||||||
config :pleroma, configurable_from_database: true
|
config :pleroma, configurable_from_database: false
|
||||||
|
|
||||||
config :pleroma, Pleroma.Repo,
|
config :pleroma, Pleroma.Repo,
|
||||||
parameters: [gin_fuzzy_search_limit: "500", jit: "off"],
|
parameters: [gin_fuzzy_search_limit: "500", jit: "off"],
|
||||||
|
|
@ -912,13 +913,9 @@ config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
||||||
|
|
||||||
config :pleroma, :mrf,
|
config :pleroma, :mrf,
|
||||||
policies: [
|
policies: [
|
||||||
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
|
|
||||||
Pleroma.Web.ActivityPub.MRF.HellthreadPolicy,
|
|
||||||
Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy,
|
|
||||||
Pleroma.Web.ActivityPub.MRF.TagPolicy,
|
|
||||||
Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
|
Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
|
||||||
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy,
|
Pleroma.Web.ActivityPub.MRF.TagPolicy,
|
||||||
Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy
|
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy
|
||||||
],
|
],
|
||||||
transparency: true,
|
transparency: true,
|
||||||
transparency_exclusions: []
|
transparency_exclusions: []
|
||||||
|
|
@ -943,7 +940,8 @@ config :pleroma, Pleroma.User.Backup,
|
||||||
timeout: :timer.minutes(30)
|
timeout: :timer.minutes(30)
|
||||||
|
|
||||||
config :pleroma, ConcurrentLimiter, [
|
config :pleroma, ConcurrentLimiter, [
|
||||||
{Pleroma.Search, [max_running: 30, max_waiting: 50]}
|
{Pleroma.Search, [max_running: 30, max_waiting: 50]},
|
||||||
|
{Pleroma.Webhook.Notify, [max_running: 5, max_waiting: 200]}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
|
config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fetch: true
|
||||||
|
|
@ -955,6 +953,15 @@ config :pleroma, Pleroma.Search.Meilisearch,
|
||||||
private_key: nil,
|
private_key: nil,
|
||||||
initial_indexing_chunk_size: 100_000
|
initial_indexing_chunk_size: 100_000
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Search.ParadeDB,
|
||||||
|
url: nil,
|
||||||
|
table: "pleroma_search_documents",
|
||||||
|
fuzzy_distance: 0
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Search.ParadeDB.Repo,
|
||||||
|
pool_size: 2,
|
||||||
|
prepare: :unnamed
|
||||||
|
|
||||||
config :pleroma, Pleroma.Application,
|
config :pleroma, Pleroma.Application,
|
||||||
background_migrators: true,
|
background_migrators: true,
|
||||||
internal_fetch: true,
|
internal_fetch: true,
|
||||||
|
|
@ -977,6 +984,21 @@ config :pleroma, Pleroma.Search.QdrantSearch,
|
||||||
vectors: %{size: 384, distance: "Cosine"}
|
vectors: %{size: 384, distance: "Cosine"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config :pleroma, :database_config_blacklist, [
|
||||||
|
{:pleroma, :logger}
|
||||||
|
]
|
||||||
|
|
||||||
|
config :pleroma, :database_config_whitelist, [
|
||||||
|
{:pleroma},
|
||||||
|
{:cors_plug},
|
||||||
|
{:ex_aws, :s3},
|
||||||
|
{:mime},
|
||||||
|
{:prometheus, Pleroma.Web.Endpoint.MetricsExporter},
|
||||||
|
{:web_push_encryption, :vapid_details}
|
||||||
|
]
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Chat, enabled: true
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
||||||
|
|
@ -815,7 +815,8 @@ config :pleroma, :config_description, [
|
||||||
"text/plain",
|
"text/plain",
|
||||||
"text/html",
|
"text/html",
|
||||||
"text/markdown",
|
"text/markdown",
|
||||||
"text/bbcode"
|
"text/bbcode",
|
||||||
|
"text/x.misskeymarkdown"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
|
@ -1394,7 +1395,13 @@ config :pleroma, :config_description, [
|
||||||
label: "Post Content Type",
|
label: "Post Content Type",
|
||||||
type: {:dropdown, :atom},
|
type: {:dropdown, :atom},
|
||||||
description: "Default post formatting option",
|
description: "Default post formatting option",
|
||||||
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
|
suggestions: [
|
||||||
|
"text/plain",
|
||||||
|
"text/html",
|
||||||
|
"text/markdown",
|
||||||
|
"text/bbcode",
|
||||||
|
"text/x.misskeymarkdown"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :redirectRootNoLogin,
|
key: :redirectRootNoLogin,
|
||||||
|
|
@ -2719,6 +2726,19 @@ config :pleroma, :config_description, [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Chat,
|
||||||
|
type: :group,
|
||||||
|
description: "Pleroma Chat settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :enabled,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Enables the chats API."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :http,
|
key: :http,
|
||||||
|
|
@ -2903,7 +2923,7 @@ config :pleroma, :config_description, [
|
||||||
key: Pleroma.Web.Plugs.RemoteIp,
|
key: Pleroma.Web.Plugs.RemoteIp,
|
||||||
type: :group,
|
type: :group,
|
||||||
description: """
|
description: """
|
||||||
`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
|
`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://hex.pm/packages/remote_ip) but with runtime configuration.
|
||||||
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
|
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
|
||||||
""",
|
""",
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -2919,6 +2939,12 @@ config :pleroma, :config_description, [
|
||||||
A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`.
|
A list of strings naming the HTTP headers to use when deriving the true client IP. Default: `["x-forwarded-for"]`.
|
||||||
"""
|
"""
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :clients,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of client IPs or subnets in CIDR notation. These will not be treated as proxies or reserved ranges. Defaults to `[]`. IPv4 entries without a bitmask will be assumed to be /32 and IPv6 /128."
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :proxies,
|
key: :proxies,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
|
|
@ -3072,9 +3098,16 @@ config :pleroma, :config_description, [
|
||||||
%{
|
%{
|
||||||
key: :max_connections,
|
key: :max_connections,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Maximum number of connections in the pool. Default: 250 connections.",
|
description:
|
||||||
|
"Maximum total number of connections in the pool. HTTP/1 origins may use multiple connections within this limit, while HTTP/2 connections are multiplexed. Default: 250 connections.",
|
||||||
suggestions: [250]
|
suggestions: [250]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :max_idle_time,
|
||||||
|
type: :integer,
|
||||||
|
description: "Time before an unused connection is closed. Default: 30000ms.",
|
||||||
|
suggestions: [30_000]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :connect_timeout,
|
key: :connect_timeout,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
|
|
@ -3490,6 +3523,26 @@ config :pleroma, :config_description, [
|
||||||
suggestion: [5]
|
suggestion: [5]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: Pleroma.Webhook.Notify,
|
||||||
|
type: :keyword,
|
||||||
|
description: "Concurrent limits configuration for webhooks.",
|
||||||
|
suggestions: [max_running: 5, max_waiting: 200],
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :max_running,
|
||||||
|
type: :integer,
|
||||||
|
description: "Max running concurrently jobs.",
|
||||||
|
suggestion: [5]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :max_waiting,
|
||||||
|
type: :integer,
|
||||||
|
description: "Max waiting jobs.",
|
||||||
|
suggestion: [200]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -3503,7 +3556,12 @@ config :pleroma, :config_description, [
|
||||||
key: :module,
|
key: :module,
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "Selected search module.",
|
description: "Selected search module.",
|
||||||
suggestion: [Pleroma.Search.DatabaseSearch, Pleroma.Search.Meilisearch]
|
suggestion: [
|
||||||
|
Pleroma.Search.DatabaseSearch,
|
||||||
|
Pleroma.Search.Meilisearch,
|
||||||
|
Pleroma.Search.QdrantSearch,
|
||||||
|
Pleroma.Search.ParadeDB
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -3536,6 +3594,34 @@ config :pleroma, :config_description, [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: Pleroma.Search.ParadeDB,
|
||||||
|
type: :group,
|
||||||
|
description: "ParadeDB settings.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :url,
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"ParadeDB database URL (separate Postgres instance). " <>
|
||||||
|
"Can also be set via `PARADEDB_DATABASE_URL`.",
|
||||||
|
suggestion: ["postgres://postgres:postgres@127.0.0.1:5432/paradedb"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :table,
|
||||||
|
type: :string,
|
||||||
|
description: "Table name used for search documents.",
|
||||||
|
suggestion: ["pleroma_search_documents"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :fuzzy_distance,
|
||||||
|
type: :integer,
|
||||||
|
description: "Edit-distance used for fuzzy token matching (0 disables fuzziness, max 2).",
|
||||||
|
suggestion: [0, 1, 2]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: Pleroma.Language.LanguageDetector,
|
key: Pleroma.Language.LanguageDetector,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ config :pleroma, Pleroma.Emails.Mailer, adapter: Swoosh.Adapters.Local
|
||||||
# different ports.
|
# different ports.
|
||||||
|
|
||||||
# Do not include timestamps in development logs
|
# Do not include timestamps in development logs
|
||||||
config :logger, Logger.Backends.Console, format: "$metadata[$level] $message\n"
|
config :logger, :default_formatter, format: "$metadata[$level] $message\n"
|
||||||
|
|
||||||
# Set a higher stacktrace during development. Avoid configuring such
|
# Set a higher stacktrace during development. Avoid configuring such
|
||||||
# in production as building large stacktraces may be expensive.
|
# in production as building large stacktraces may be expensive.
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ config :pleroma, Pleroma.Web.Endpoint,
|
||||||
config :phoenix, serve_endpoints: true
|
config :phoenix, serve_endpoints: true
|
||||||
|
|
||||||
# Do not print debug messages in production
|
# Do not print debug messages in production
|
||||||
config :logger, Logger.Backends.Console, level: :info
|
config :logger, :default_handler, level: :info
|
||||||
config :logger, :console, level: :info
|
|
||||||
config :logger, :ex_syslogger, level: :info
|
config :logger, :ex_syslogger, level: :info
|
||||||
|
|
||||||
# ## SSL Support
|
# ## SSL Support
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ config :pleroma, Pleroma.Captcha,
|
||||||
method: Pleroma.Captcha.Mock
|
method: Pleroma.Captcha.Mock
|
||||||
|
|
||||||
# Print only warnings and errors during test
|
# Print only warnings and errors during test
|
||||||
config :logger, :console,
|
config :logger, :default_handler, level: :warning
|
||||||
level: :warning,
|
|
||||||
format: "\n[$level] $message\n"
|
config :logger, :default_formatter,
|
||||||
|
format: "\n[$level] $message\n",
|
||||||
|
colors: [enabled: false]
|
||||||
|
|
||||||
config :pleroma, :auth, oauth_consumer_strategies: []
|
config :pleroma, :auth, oauth_consumer_strategies: []
|
||||||
|
|
||||||
|
|
|
||||||
70
docker-entrypoint-e2e.sh
Executable file
70
docker-entrypoint-e2e.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/bin/ash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
|
||||||
|
CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
|
||||||
|
|
||||||
|
echo '-- Waiting for database...'
|
||||||
|
while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
echo '-- Writing E2E config overrides...'
|
||||||
|
cat > "$CONFIG_OVERRIDE_PATH" <<EOF
|
||||||
|
import Config
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Captcha,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :instance,
|
||||||
|
registrations_open: true,
|
||||||
|
account_activation_required: false,
|
||||||
|
account_approval_required: false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo '-- Running migrations...'
|
||||||
|
/opt/pleroma/bin/pleroma_ctl migrate
|
||||||
|
|
||||||
|
if [ ! -f "$SEED_SENTINEL_PATH" ]; then
|
||||||
|
if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
|
||||||
|
echo '-- Seeding admin user' "$E2E_ADMIN_USERNAME" '...'
|
||||||
|
if ! PLEROMA_CTL_RPC_DISABLED=true /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
|
||||||
|
echo '-- User already exists or creation failed, ensuring admin + confirmed...'
|
||||||
|
PLEROMA_CTL_RPC_DISABLED=true /opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$SEED_SENTINEL_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '-- Starting!'
|
||||||
|
/opt/pleroma/bin/pleroma start &
|
||||||
|
PLEROMA_PID=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if kill -0 "$PLEROMA_PID" 2>/dev/null; then
|
||||||
|
kill -TERM "$PLEROMA_PID"
|
||||||
|
wait "$PLEROMA_PID" || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM
|
||||||
|
|
||||||
|
echo '-- Waiting for API...'
|
||||||
|
api_ok=false
|
||||||
|
for _i in $(seq 1 120); do
|
||||||
|
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
|
||||||
|
api_ok=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$api_ok" != true ]; then
|
||||||
|
echo 'Timed out waiting for Pleroma API to become available'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$PLEROMA_PID"
|
||||||
|
|
@ -170,3 +170,17 @@ This forcibly removes any enabled MRF that does not exist and will fix the abili
|
||||||
```sh
|
```sh
|
||||||
mix pleroma.config fix_mrf_policies
|
mix pleroma.config fix_mrf_policies
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Remove non-whitelisted configs from the database
|
||||||
|
|
||||||
|
This removes any configuration value that is not explicitly whitelisted by `:pleroma, :database_config_whitelist`. Might be useful after updating the whitelist.
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl config filter_whitelisted
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
mix pleroma.config filter_whitelisted
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,22 @@ The resulting frontend will always be installed into a folder of this template:
|
||||||
|
|
||||||
Careful: This folder will be completely replaced on installation.
|
Careful: This folder will be completely replaced on installation.
|
||||||
|
|
||||||
|
## Installing Admin-FE
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl frontend install admin-fe --ref stable
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mix pleroma.frontend install admin-fe --ref stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds come from the package registry: `https://git.pleroma.social/api/packages/pleroma/generic/admin-fe-builds/${ref}/latest.zip`. `stable` and `develop` are published; `stable` is the better choice for production. A release tag works as `--ref` once a build for it is published.
|
||||||
|
|
||||||
## Example installation for an unknown frontend
|
## Example installation for an unknown frontend
|
||||||
|
|
||||||
The installation process is the same, but you will have to give all the needed options on the command line. For example:
|
The installation process is the same, but you will have to give all the needed options on the command line. For example:
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ Due to aforementioned bug, preferred way of installing frontends in AdminFE is b
|
||||||

|

|
||||||
and filling in the fields. Unfortunately AdminFE does not provide the raw data necessary for you to fill those fields, so your best bet is to see what backend returns in browser's devtools or refer to the [source code](https://git.pleroma.social/pleroma/pleroma/-/blob/develop/config/config.exs?ref_type=heads#L742-791). For the most part, only **Name**, **Ref** (i.e. version) and **Build URL** fields are required, although some frontends might also require **Build Directory** to work.
|
and filling in the fields. Unfortunately AdminFE does not provide the raw data necessary for you to fill those fields, so your best bet is to see what backend returns in browser's devtools or refer to the [source code](https://git.pleroma.social/pleroma/pleroma/-/blob/develop/config/config.exs?ref_type=heads#L742-791). For the most part, only **Name**, **Ref** (i.e. version) and **Build URL** fields are required, although some frontends might also require **Build Directory** to work.
|
||||||
|
|
||||||
For pleroma-fe you can use either `master` or `develop` refs, or potentially any ref in GitLab that has artifacts for `build` job, but that's outside scope of this document.
|
Pleroma-FE and Admin-FE builds come from the package registry, not from GitLab job artifacts. For Admin-FE, set **Name** to `admin-fe`, **Ref** to `stable` (or `develop`) and **Build URL** to `https://git.pleroma.social/api/packages/pleroma/generic/admin-fe-builds/${ref}/latest.zip`. Only refs with a published build can be installed: `stable`, `develop`, and release tags once they are published.
|
||||||
|
|
||||||
### Selecting default frontend
|
### Selecting default frontend
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -541,6 +541,15 @@ Available caches:
|
||||||
|
|
||||||
## HTTP client
|
## HTTP client
|
||||||
|
|
||||||
|
Pleroma uses [Gun](https://github.com/ninenines/gun) for outgoing HTTP requests. To use Hackney instead:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :tesla, adapter: Tesla.Adapter.Hackney
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
With Hackney, size limits for fetched responses (for example for link previews) are checked only after Hackney has read the whole response. They limit what Pleroma keeps, not how much it downloads or holds in memory. Gun stops reading at the limit.
|
||||||
|
|
||||||
### :http
|
### :http
|
||||||
|
|
||||||
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
|
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
|
||||||
|
|
@ -550,6 +559,8 @@ Available caches:
|
||||||
|
|
||||||
### :hackney_pools
|
### :hackney_pools
|
||||||
|
|
||||||
|
*For `hackney` adapter*
|
||||||
|
|
||||||
Advanced. Tweaks Hackney (http client) connections pools.
|
Advanced. Tweaks Hackney (http client) connections pools.
|
||||||
|
|
||||||
There's three pools used:
|
There's three pools used:
|
||||||
|
|
@ -573,7 +584,8 @@ Settings for HTTP connection pool.
|
||||||
|
|
||||||
* `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries.
|
* `:connection_acquisition_wait` - Timeout to acquire a connection from pool.The total max time is this value multiplied by the number of retries.
|
||||||
* `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart.
|
* `connection_acquisition_retries` - Number of attempts to acquire the connection from the pool if it is overloaded. Each attempt is timed `:connection_acquisition_wait` apart.
|
||||||
* `:max_connections` - Maximum number of connections in the pool.
|
* `:max_connections` - Maximum total number of connections in the pool. HTTP/1 origins may use multiple connections within this limit, while HTTP/2 connections are multiplexed.
|
||||||
|
* `:max_idle_time` - Time before an unused connection is closed.
|
||||||
* `:connect_timeout` - Timeout to connect to the host.
|
* `:connect_timeout` - Timeout to connect to the host.
|
||||||
* `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded.
|
* `:reclaim_multiplier` - Multiplied by `:max_connections` this will be the maximum number of idle connections that will be reclaimed in case the pool is overloaded.
|
||||||
|
|
||||||
|
|
@ -858,21 +870,23 @@ Web Push Notifications configuration. You can use the mix task `mix web_push.gen
|
||||||
* ``private_key``: VAPID private key
|
* ``private_key``: VAPID private key
|
||||||
|
|
||||||
## :logger
|
## :logger
|
||||||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
|
* Logging to console/stdout is done by default, use `{ExSyslogger, :ex_syslogger}` to log to syslog
|
||||||
|
|
||||||
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
|
An example to enable ONLY ExSyslogger (f/ex in ``prod.secret.exs``) with info and debug suppressed:
|
||||||
```elixir
|
```elixir
|
||||||
config :logger,
|
config :pleroma, :logger,
|
||||||
backends: [{ExSyslogger, :ex_syslogger}]
|
backends: [{ExSyslogger, :ex_syslogger}]
|
||||||
|
|
||||||
|
config :logger, default_handler: false
|
||||||
|
|
||||||
config :logger, :ex_syslogger,
|
config :logger, :ex_syslogger,
|
||||||
level: :warning
|
level: :warning
|
||||||
```
|
```
|
||||||
|
|
||||||
Another example, keeping console output and adding the pid to syslog output:
|
Another example, keeping console output and adding the pid to syslog output:
|
||||||
```elixir
|
```elixir
|
||||||
config :logger,
|
config :pleroma, :logger,
|
||||||
backends: [:console, {ExSyslogger, :ex_syslogger}]
|
backends: [{ExSyslogger, :ex_syslogger}]
|
||||||
|
|
||||||
config :logger, :ex_syslogger,
|
config :logger, :ex_syslogger,
|
||||||
level: :warning,
|
level: :warning,
|
||||||
|
|
@ -883,23 +897,22 @@ See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_s
|
||||||
|
|
||||||
An example of logging info to local syslog, but debug to console:
|
An example of logging info to local syslog, but debug to console:
|
||||||
```elixir
|
```elixir
|
||||||
config :logger,
|
config :pleroma, :logger,
|
||||||
backends: [ {ExSyslogger, :ex_syslogger}, :console ],
|
backends: [{ExSyslogger, :ex_syslogger}]
|
||||||
level: :info
|
|
||||||
|
|
||||||
config :logger, :ex_syslogger,
|
config :logger, :ex_syslogger,
|
||||||
level: :info,
|
level: :info,
|
||||||
ident: "pleroma",
|
ident: "pleroma",
|
||||||
format: "$metadata[$level] $message"
|
format: "$metadata[$level] $message"
|
||||||
|
|
||||||
config :logger, :console,
|
config :logger, :default_handler,
|
||||||
level: :debug,
|
level: :debug
|
||||||
|
|
||||||
|
config :logger, :default_formatter,
|
||||||
format: "\n$time $metadata[$level] $message\n",
|
format: "\n$time $metadata[$level] $message\n",
|
||||||
metadata: [:request_id]
|
metadata: [:request_id]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Database options
|
## Database options
|
||||||
|
|
||||||
### RUM indexing for full text search
|
### RUM indexing for full text search
|
||||||
|
|
@ -1132,8 +1145,9 @@ Boolean, enables/disables in-database configuration. Read [Transferring the conf
|
||||||
|
|
||||||
List of valid configuration sections which are allowed to be configured from the
|
List of valid configuration sections which are allowed to be configured from the
|
||||||
database. Settings stored in the database before the whitelist is configured are
|
database. Settings stored in the database before the whitelist is configured are
|
||||||
still applied, so it is suggested to only use the whitelist on instances that
|
still applied. Consider running the `mix pleroma.config filter_whitelisted` task
|
||||||
have not migrated the config to the database.
|
after updating the whitelist. Read [Remove non-whitelisted configs from the database](../administration/CLI_tasks/config.md#remove-non-whitelisted-configs-from-the-database)
|
||||||
|
for more information.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```elixir
|
```elixir
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,6 @@ The other way to do it, for example, with Tor you would most likely add somethin
|
||||||
```
|
```
|
||||||
config :pleroma, :http, proxy_url: {:socks5, :localhost, 9050}
|
config :pleroma, :http, proxy_url: {:socks5, :localhost, 9050}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The Gun HTTP adapter supports SOCKS5. SOCKS4 proxies require another HTTP adapter.
|
||||||
|
For regular HTTP proxies, Gun forwards plain HTTP requests directly and uses CONNECT tunnels for HTTPS requests.
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ You will see a "Default Admin API Key", this is the key you actually put into yo
|
||||||
|
|
||||||
### Initial indexing
|
### Initial indexing
|
||||||
|
|
||||||
After setting up the configuration, you'll want to index all of your already existing posts. Only public posts are indexed. You'll only
|
After setting up the configuration, you'll want to index all of your already existing posts. Only public/unlisted posts are indexed. You'll only
|
||||||
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM
|
have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. This is also a fairly RAM
|
||||||
consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2
|
consuming process for `meilisearch`, and it will take a lot of RAM when running if you have a lot of posts (seems to be around 5G for ~1.2
|
||||||
million posts while idle and up to 7G while indexing initially, but your experience may be different).
|
million posts while idle and up to 7G while indexing initially, but your experience may be different).
|
||||||
|
|
@ -145,3 +145,155 @@ This will clear **all** the posts from the search index. Note, that deleted post
|
||||||
there is no need to actually clear the whole index, unless you want **all** of it gone. That said, the index does not hold any information
|
there is no need to actually clear the whole index, unless you want **all** of it gone. That said, the index does not hold any information
|
||||||
that cannot be re-created from the database, it should also generally be a lot smaller than the size of your database. Still, the size
|
that cannot be re-created from the database, it should also generally be a lot smaller than the size of your database. Still, the size
|
||||||
depends on the amount of text in posts.
|
depends on the amount of text in posts.
|
||||||
|
|
||||||
|
## ParadeDB
|
||||||
|
|
||||||
|
[ParadeDB](https://www.paradedb.com/) is a Postgres extension that provides BM25 full-text search. In Pleroma, it can be used as an
|
||||||
|
external search backend by pointing Pleroma at a separate Postgres instance with the `pg_search` extension installed.
|
||||||
|
Pleroma's integration suite tests `pg_search` 0.24.3 on PostgreSQL 15.
|
||||||
|
|
||||||
|
Pleroma will maintain a small `pleroma_search_documents` table in that database (via the existing search indexing queue) and run search
|
||||||
|
queries against it.
|
||||||
|
The indexed text includes status content, content warnings, and attachment descriptions.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Set the search module to `Pleroma.Search.ParadeDB` and configure the ParadeDB database URL. These settings must be in static/runtime config
|
||||||
|
(or `PARADEDB_DATABASE_URL`) so the dedicated ParadeDB Repo is started with the application. They cannot be migrated to or changed through
|
||||||
|
ConfigDB.
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search, module: Pleroma.Search.ParadeDB
|
||||||
|
>
|
||||||
|
> config :pleroma, Pleroma.Search.ParadeDB,
|
||||||
|
> url: System.get_env("PARADEDB_DATABASE_URL"),
|
||||||
|
> table: "pleroma_search_documents",
|
||||||
|
> fuzzy_distance: 0
|
||||||
|
>
|
||||||
|
> config :pleroma, Pleroma.Search.ParadeDB.Repo,
|
||||||
|
> pool_size: 2,
|
||||||
|
> prepare: :unnamed
|
||||||
|
|
||||||
|
When connecting over an untrusted network, enable certificate verification in the Repo configuration. For example:
|
||||||
|
|
||||||
|
> config :pleroma, Pleroma.Search.ParadeDB.Repo,
|
||||||
|
> ssl: [
|
||||||
|
> verify: :verify_peer,
|
||||||
|
> cacertfile: "/etc/pleroma/paradedb-ca.pem",
|
||||||
|
> server_name_indication: ~c"paradedb.example.com"
|
||||||
|
> ]
|
||||||
|
|
||||||
|
### Initial rollout
|
||||||
|
|
||||||
|
The `pg_search` extension must already be available in the database. Creating the extension normally requires an administrative database
|
||||||
|
role. The Pleroma runtime role requires `CONNECT` on the database, `USAGE` on the table's schema and the `pdb` schema, and `SELECT`, `INSERT`, `UPDATE`,
|
||||||
|
and `DELETE` on the generated table. For example, after creating the table as its owner:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
GRANT CONNECT ON DATABASE paradedb TO pleroma_search;
|
||||||
|
GRANT USAGE ON SCHEMA public, pdb TO pleroma_search;
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE pleroma_search_documents TO pleroma_search;
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this rollout order to prevent jobs from being handled by the previous backend:
|
||||||
|
|
||||||
|
1. Install `pg_search` 0.24.3 on PostgreSQL 15 or another deliberately tested combination.
|
||||||
|
2. Stop Pleroma so no search indexing jobs are running.
|
||||||
|
3. Put the ParadeDB backend in static configuration.
|
||||||
|
4. Create the table and BM25 index in a one-shot offline process using `PARADEDB_DATABASE_URL` with the table owner/setup credential.
|
||||||
|
5. Apply the runtime grants shown above, set the service's `PARADEDB_DATABASE_URL` to the restricted runtime credential, and start Pleroma.
|
||||||
|
6. Start the initial backfill and monitor the `search_indexing` queue.
|
||||||
|
|
||||||
|
Create the table and BM25 index once with:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
PARADEDB_DATABASE_URL='OWNER_URL' PLEROMA_CTL_RPC_DISABLED=true \
|
||||||
|
./bin/pleroma_ctl search.indexer create_index
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
PARADEDB_DATABASE_URL='OWNER_URL' mix pleroma.search.indexer create_index
|
||||||
|
```
|
||||||
|
|
||||||
|
The task currently prints an error without returning a failing process status. Verify that it prints `Index created` before continuing.
|
||||||
|
|
||||||
|
### Backfill
|
||||||
|
|
||||||
|
Run an initial indexing pass to enqueue existing posts:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
./bin/pleroma_ctl search.indexer index
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
PARADEDB_DATABASE_URL='RUNTIME_URL' mix pleroma.search.indexer index
|
||||||
|
```
|
||||||
|
|
||||||
|
The command enqueues up to 100,000 Create activities by default. It uses keyset pagination, so new activities arriving during the backfill
|
||||||
|
do not shift the remaining pages. The defaults and accepted options are:
|
||||||
|
|
||||||
|
* `--limit`: maximum activities to enqueue in this run; defaults to `100000` and must be positive
|
||||||
|
* `--step`: activities fetched from Pleroma's database per page; defaults to `1000` and must be positive
|
||||||
|
* `--chunk`: jobs inserted into Oban per batch; defaults to `100` and must be positive
|
||||||
|
* `--before`: resume before the checkpoint activity ID printed by an earlier run; the cursor is exclusive
|
||||||
|
|
||||||
|
The checkpoint is the last activity enqueued, not confirmation that its job succeeded. Repeat the command with its last `--before`
|
||||||
|
checkpoint only when it reports that more activities remain. Inspect failed or discarded Oban jobs as well as waiting jobs before declaring
|
||||||
|
the backfill complete.
|
||||||
|
|
||||||
|
The same operation can be run from an attached console while Pleroma is online:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
Pleroma.Search.Backfill.run(limit: 10_000)
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned `next_cursor` can be supplied as `before` when `exhausted` is `false`. The attached API also accepts `limit: :infinity`, but this
|
||||||
|
can create an unbounded number of Oban rows; bounded runs are safer on large instances. An `on_page` callback may be supplied for progress
|
||||||
|
reporting.
|
||||||
|
|
||||||
|
Note: Like the other external search backends, only public/unlisted Notes are indexed.
|
||||||
|
|
||||||
|
Set `fuzzy_distance` to `1` or `2` to allow typo-tolerant matching (`2` is the maximum ParadeDB supports).
|
||||||
|
|
||||||
|
Indexing runs via the Oban `search_indexing` queue. Its stock configuration starts paused so the search health monitor can control it; verify
|
||||||
|
that the queue is running before backfilling. A manually managed queue can be enabled with `search_indexing: 10` in the Oban `queues`
|
||||||
|
configuration instead of a `paused: true` entry.
|
||||||
|
Monitor that queue until the backfill jobs have drained before evaluating search completeness.
|
||||||
|
|
||||||
|
### Rebuilding
|
||||||
|
|
||||||
|
Stop Pleroma, or otherwise ensure no search indexing jobs are running, before rebuilding. While ParadeDB is still the configured backend,
|
||||||
|
run `drop_index`, then `create_index` with the table owner/setup credential. Reapply runtime grants after recreating the table, restart
|
||||||
|
Pleroma, and begin a new backfill. The DDL tasks print failures without returning a failing process status, so verify the exact `Index
|
||||||
|
dropped` and `Index created` success messages. Dropping the table removes only derived search data; Pleroma activities remain in the primary
|
||||||
|
database.
|
||||||
|
|
||||||
|
### Rolling back
|
||||||
|
|
||||||
|
To retain the ParadeDB table for a later retry, configure `Pleroma.Search.DatabaseSearch` (or the previous external backend) in static config
|
||||||
|
and restart Pleroma.
|
||||||
|
|
||||||
|
Before switching backends, inspect the `search_indexing` queue. Drain or cancel pending backfill jobs, or explicitly accept that they will run
|
||||||
|
against the newly configured backend after restart.
|
||||||
|
|
||||||
|
To remove the table during rollback, first stop Pleroma and run `drop_index` with the table owner/setup credential while ParadeDB is still
|
||||||
|
configured:
|
||||||
|
|
||||||
|
=== "OTP"
|
||||||
|
```sh
|
||||||
|
PARADEDB_DATABASE_URL='OWNER_URL' PLEROMA_CTL_RPC_DISABLED=true \
|
||||||
|
./bin/pleroma_ctl search.indexer drop_index
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "From Source"
|
||||||
|
```sh
|
||||||
|
PARADEDB_DATABASE_URL='OWNER_URL' mix pleroma.search.indexer drop_index
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify that the task prints `Index dropped`. Then change the backend in static config and restart. The ParadeDB environment variable may be
|
||||||
|
removed after removing the ParadeDB stanza or switching to a backend that does not start its Repo. After switching away from ParadeDB, the
|
||||||
|
generic `drop_index` task targets the newly configured backend and will not remove the ParadeDB table; remove it directly with the database
|
||||||
|
owner instead.
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,24 @@ The `/api/v1/pleroma/admin/*` path is backwards compatible with `/api/pleroma/ad
|
||||||
{
|
{
|
||||||
`nickname`,
|
`nickname`,
|
||||||
`email`,
|
`email`,
|
||||||
`password`
|
`password` (optional; when provided, must not be empty)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
- Response: Array of user objects
|
- Response: Array of user objects. When a user is created without a `password`, `data`
|
||||||
|
object contains a `password_reset_link` the user can follow to set their own password.
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"nickname": string,
|
||||||
|
"email": string,
|
||||||
|
"password_reset_link": string (optional)
|
||||||
|
},
|
||||||
|
"type": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
## `POST /api/v1/pleroma/admin/users/follow`
|
## `POST /api/v1/pleroma/admin/users/follow`
|
||||||
|
|
||||||
|
|
@ -665,6 +679,7 @@ Status: 404
|
||||||
- *optional* `limit`: **integer** the number of records to retrieve
|
- *optional* `limit`: **integer** the number of records to retrieve
|
||||||
- *optional* `page`: **integer** page number
|
- *optional* `page`: **integer** page number
|
||||||
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
|
- *optional* `page_size`: **integer** number of log entries per page (default is `50`)
|
||||||
|
- *optional* `assigned_account`: **string** assigned account ID
|
||||||
- Response:
|
- Response:
|
||||||
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
- On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin
|
||||||
- On success: JSON, returns a list of reports, where:
|
- On success: JSON, returns a list of reports, where:
|
||||||
|
|
@ -749,6 +764,7 @@ Status: 404
|
||||||
"url": "https://pleroma.example.org/users/lain",
|
"url": "https://pleroma.example.org/users/lain",
|
||||||
"username": "lain"
|
"username": "lain"
|
||||||
},
|
},
|
||||||
|
"assigned_account": null,
|
||||||
"content": "Please delete it",
|
"content": "Please delete it",
|
||||||
"created_at": "2019-04-29T19:48:15.000Z",
|
"created_at": "2019-04-29T19:48:15.000Z",
|
||||||
"id": "9iJGOv1j8hxuw19bcm",
|
"id": "9iJGOv1j8hxuw19bcm",
|
||||||
|
|
@ -868,6 +884,37 @@ Status: 404
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- Response:
|
||||||
|
- On failure:
|
||||||
|
- 400 Bad Request, JSON:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
`id`, // report id
|
||||||
|
`error` // error message
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- On success: `204`, empty response
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/reports/assign_account`
|
||||||
|
|
||||||
|
### Assign account to one or multiple reports
|
||||||
|
|
||||||
|
- Params:
|
||||||
|
|
||||||
|
```json
|
||||||
|
`reports`: [
|
||||||
|
{
|
||||||
|
`id`, // required, report id
|
||||||
|
`nickname` // account nickname, use null to unassign account
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
- On failure:
|
- On failure:
|
||||||
- 400 Bad Request, JSON:
|
- 400 Bad Request, JSON:
|
||||||
|
|
@ -1806,3 +1853,92 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns
|
||||||
```json
|
```json
|
||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/admin/webhooks`
|
||||||
|
|
||||||
|
### List webhooks
|
||||||
|
|
||||||
|
- Method: `GET`
|
||||||
|
- Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"id": "2",
|
||||||
|
"events": ["account.created"],
|
||||||
|
"url": "https://webhook.example/",
|
||||||
|
"secret": "eb85d4ccd8510e78f912743949dc354e8146987d",
|
||||||
|
"updated_at": "2022-10-29T17:44:16.000Z",
|
||||||
|
"created_at": "2022-10-29T17:44:13.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## `GET /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Get an individual webhook
|
||||||
|
|
||||||
|
- Method: `GET`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks`
|
||||||
|
|
||||||
|
### Create a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `url`: **string** Webhook URL
|
||||||
|
- `events`: **[string]** Types of events to trigger on (`account.created`, `report.created`)
|
||||||
|
- *optional* `enabled`: **boolean** Whether webhook is enabled
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `PATCH /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Update a webhook
|
||||||
|
|
||||||
|
- Method: `PATCH`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- *optional* `url`: **string** Webhook URL
|
||||||
|
- *optional* `events`: **[string]** Types of events to trigger on (`account.created`, `report.created`)
|
||||||
|
- *optional* `enabled`: **boolean** Whether webhook is enabled
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `DELETE /api/v1/pleroma/admin/webhooks/:id`
|
||||||
|
|
||||||
|
### Delete a webhook
|
||||||
|
|
||||||
|
- Method: `DELETE`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/enable`
|
||||||
|
|
||||||
|
### Activate a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/disable`
|
||||||
|
|
||||||
|
### Deactivate a webhook
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
||||||
|
## `POST /api/v1/pleroma/admin/webhooks/:id/rotate_secret`
|
||||||
|
|
||||||
|
### Rotate webhook signing secret
|
||||||
|
|
||||||
|
- Method: `POST`
|
||||||
|
- Params:
|
||||||
|
- `id`: **string** Webhook ID
|
||||||
|
- Response: A webhook
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ Pleroma does not process remote images and therefore cannot include fields such
|
||||||
|
|
||||||
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
||||||
|
|
||||||
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
|
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. Bookmarking an already bookmarked post will update the folder association, or remove it if `folder_id` is omitted or `null`.
|
||||||
|
|
||||||
## Accounts
|
## Accounts
|
||||||
|
|
||||||
|
|
@ -127,8 +127,6 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||||
- `avatar_description`: string, image description for user avatar, defaults to empty string
|
|
||||||
- `header_description`: string, image description for user banner, defaults to empty string
|
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -690,6 +690,7 @@ Audio scrobbling in Pleroma is **deprecated**.
|
||||||
* `album`: the album of the media playing [optional]
|
* `album`: the album of the media playing [optional]
|
||||||
* `artist`: the artist of the media playing [optional]
|
* `artist`: the artist of the media playing [optional]
|
||||||
* `length`: the length of the media playing [optional]
|
* `length`: the length of the media playing [optional]
|
||||||
|
* `external_link`: a URL referencing the media playing [optional]
|
||||||
* Response: the newly created media metadata entity representing the Listen activity
|
* Response: the newly created media metadata entity representing the Listen activity
|
||||||
|
|
||||||
# Emoji Reactions
|
# Emoji Reactions
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
This guide will assume you are on Debian 12 (“bookworm”) or later. This guide should also work with Ubuntu 22.04 (“jammy”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
This guide will assume you are on Debian 13 (“Trixie”) or later. This guide should also work with Ubuntu 26.04 (“resolute”) and later. It also assumes that you have administrative rights, either as root or a user with [sudo permissions](https://www.digitalocean.com/community/tutorials/how-to-add-delete-and-grant-sudo-privileges-to-users-on-a-debian-vps). If you want to run this guide with root, ignore the `sudo` at the beginning of the lines, unless it calls a user like `sudo -Hu pleroma`; in this case, use `su <username> -s $SHELL -c 'command'` instead.
|
||||||
|
|
||||||
{! backend/installation/generic_dependencies.include !}
|
{! backend/installation/generic_dependencies.include !}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ Note: This article is potentially outdated because at this time we may not have
|
||||||
|
|
||||||
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
||||||
- `postgresql-contrib` 11.0以上 (同上)
|
- `postgresql-contrib` 11.0以上 (同上)
|
||||||
- Elixir 1.15 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
- Elixir 1.17 以上 (Erlang/OTP 27 以上) ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
||||||
- `erlang-dev`
|
- `erlang-dev`
|
||||||
- `erlang-nox`
|
- `erlang-nox`
|
||||||
- `git`
|
- `git`
|
||||||
|
|
|
||||||
2
docs/installation/erlang_elixir_requirements.include
Normal file
2
docs/installation/erlang_elixir_requirements.include
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
* Elixir >=1.17.0, <1.20
|
||||||
|
* Erlang OTP >=27.0.0 (supported: <29)
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
## Required dependencies
|
## Required dependencies
|
||||||
|
|
||||||
|
{! backend/installation/erlang_elixir_requirements.include !}
|
||||||
* PostgreSQL >=11.0
|
* PostgreSQL >=11.0
|
||||||
* Elixir >=1.15.0 <1.19
|
|
||||||
* Erlang OTP >=23.0.0 (supported: <28)
|
|
||||||
* git
|
* git
|
||||||
* file / libmagic
|
* file / libmagic
|
||||||
* gcc or clang
|
* gcc or clang
|
||||||
|
|
@ -14,3 +13,7 @@
|
||||||
* ImageMagick
|
* ImageMagick
|
||||||
* FFmpeg
|
* FFmpeg
|
||||||
* exiftool
|
* exiftool
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
**Source installs only**
|
||||||
|
If your operating system repositories don't meet the Elixir/Erlang requirements, check [Installing newer versions of Elixir and Erlang](./installing_newer_elixir_erlang.md).
|
||||||
|
|
|
||||||
333
docs/installation/installing_newer_elixir_erlang.md
Normal file
333
docs/installation/installing_newer_elixir_erlang.md
Normal file
|
|
@ -0,0 +1,333 @@
|
||||||
|
# Installing newer versions of Elixir and Erlang
|
||||||
|
|
||||||
|
This guide is for installing newer versions of Elixir and/or Erlang on your system when installing Pleroma from source and your operating system repositories don't meet the minimum requirements listed below.
|
||||||
|
This guide assumes you are using Debian or Ubuntu.
|
||||||
|
**Using the system repositories is recommended if possible.**
|
||||||
|
**If a newer release of your operating system would meet the requirements, consider upgrading instead.**
|
||||||
|
|
||||||
|
Shell commands prepended with `#` in this page are supposed to be ran with root privileges, commands prepended with `$` are supposed to be ran as the Pleroma user unless noted otherwise.
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
If you are, or will be, following the source install guide, make sure to switch to the Pleroma user before running any of the `mix` commands using `sudo -Hu pleroma /bin/bash`.
|
||||||
|
Not doing so will result in `command not found` errors.
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
Most of this guide can be used to install Elixir and/or Erlang on operating systems that don't package either one.
|
||||||
|
Installing Pleroma on such systems is largely untested.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
{! backend/installation/erlang_elixir_requirements.include !}
|
||||||
|
|
||||||
|
## Install methods
|
||||||
|
|
||||||
|
Choose one of the following install methods:
|
||||||
|
|
||||||
|
* User level:
|
||||||
|
* mise (Erlang and Elixir install methods are built-in[^1])
|
||||||
|
* asdf (plugin-based, plugins are shells scripts; builds Erlang from source)
|
||||||
|
* pkgsrc (for advanced users, harder to install and maintain; builds Elixir and Erlang from source)
|
||||||
|
* System-wide:
|
||||||
|
* pkgsrc (for advanced users, harder to install and maintain; builds Elixir and Erlang from source)
|
||||||
|
|
||||||
|
[^1]: On Ubuntu downloads binaries both for Elixir and Erlang. On Debian builds Erlang from source and downloads binaries for Elixir.
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
**Ignore if using mise on Ubuntu.**
|
||||||
|
You are about to compile Erlang on your server.
|
||||||
|
If you are planning to run Pleroma on a VPS virtual machine, make sure you have enough disk space and system resources available.
|
||||||
|
At least 2GB of RAM and 8GB of extra free disk space is required to continue in this guide.
|
||||||
|
If your system does not meet these requirements, use [OTP install](./otp_en.md).
|
||||||
|
**This will take a considerable amount of time.**
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
Before proceeding, stop Pleroma if already running and uninstall both Elixir and Erlang.
|
||||||
|
Keeping Elixir will likely result in a non-functional Elixir install and keeping Erlang can result in various failures to compile Pleroma and its dependencies.
|
||||||
|
|
||||||
|
## mise
|
||||||
|
|
||||||
|
To start, create the Pleroma user according to the [source install guide](./debian_based_en.md#install-pleromabe).
|
||||||
|
|
||||||
|
### Debian-only preparation
|
||||||
|
|
||||||
|
If you are using Ubuntu, skip to the [next section](./installing_newer_elixir_erlang.md#elixirerlang-installation).
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
```
|
||||||
|
# apt install build-essential libssl-dev libncurses-dev git unzip
|
||||||
|
```
|
||||||
|
|
||||||
|
Add Erlang autotools configure options to Pleroma user's shell config (`.bashrc` for bash):
|
||||||
|
```sh
|
||||||
|
export KERL_CONFIGURE_OPTIONS="--without-javac --without-odbc --without-wx --without-ssh"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Elixir/Erlang installation
|
||||||
|
|
||||||
|
Install curl (for the mise installer):
|
||||||
|
```
|
||||||
|
# apt install curl
|
||||||
|
```
|
||||||
|
|
||||||
|
Switch to the Pleroma user using `sudo -Hu pleroma /bin/bash` and follow the install steps from mise documentation and install it for the user Pleroma: [https://mise.jdx.dev/getting-started.html](https://mise.jdx.dev/getting-started.html)
|
||||||
|
|
||||||
|
Add the following to activate mise in the Pleroma user's shell configuration (`.bashrc` for bash):
|
||||||
|
```sh
|
||||||
|
# Modify to match the mise installation location
|
||||||
|
eval "$($HOME/.local/bin/mise activate bash)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Source new profile settings:
|
||||||
|
```sh
|
||||||
|
$ source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
If installing Pleroma, follow the [Pleroma source installation instructions](./debian_based_en.md) up to the point before downloading mix dependencies for Pleroma, ignoring the instructions to download Elixir/Erlang.
|
||||||
|
|
||||||
|
Install Erlang (replace `<version>` with a supported release meeting Pleroma's requirements, OTP 27 is recommended):
|
||||||
|
```sh
|
||||||
|
$ mise install erlang@<version>
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
To get a list of available versions, use `mise ls-remote <language/tool>`.
|
||||||
|
|
||||||
|
Then run the following in the Pleroma source code repository:
|
||||||
|
```sh
|
||||||
|
$ mise use erlang@<version>
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Elixir (replace `<version>` with a supported release and `<OTP version>` with the mise-installed Erlang/OTP version, Elixir 1.18 is recommended):
|
||||||
|
```sh
|
||||||
|
$ mise install elixir@<version>-otp-<OTP version>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the following in the Pleroma source code directory:
|
||||||
|
```sh
|
||||||
|
$ mise use elixir@<Elixir version>
|
||||||
|
```
|
||||||
|
|
||||||
|
If installing Pleroma, continue with the source install guide up to the point of installing the systemd Pleroma service.
|
||||||
|
Don't attempt to start the Pleroma service yet.
|
||||||
|
|
||||||
|
Create a `pleroma.sh` startup script owned by the Pleroma user in Pleroma user's home directory:
|
||||||
|
```sh
|
||||||
|
#!/bin/bash
|
||||||
|
# Modify to match the mise installation location
|
||||||
|
eval "$($HOME/.local/bin/mise activate bash)"
|
||||||
|
|
||||||
|
exec mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the startup script executable:
|
||||||
|
```sh
|
||||||
|
chmod u+x pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Override the Pleroma service file to point to the new start up script, run `# systemctl edit pleroma` and add the following:
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/var/lib/pleroma/pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now start the Pleroma service.
|
||||||
|
If you are installing Pleroma, continue with creating your first user according to the install guide.
|
||||||
|
|
||||||
|
## asdf
|
||||||
|
|
||||||
|
To start, create the Pleroma user according to the [source install guide](./debian_based_en.md#install-pleromabe) and switch to the user using `sudo -Hu pleroma /bin/bash`.
|
||||||
|
Then follow the install and configure steps from asdf documentation and install it for the user Pleroma: [https://asdf-vm.com/guide/getting-started.html](https://asdf-vm.com/guide/getting-started.html)
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
```
|
||||||
|
# apt install build-essential libssl-dev libncurses-dev git unzip curl
|
||||||
|
```
|
||||||
|
|
||||||
|
Add Erlang autotools configure options to Pleroma user's shell config (`.bashrc` for bash):
|
||||||
|
```sh
|
||||||
|
export KERL_CONFIGURE_OPTIONS="--without-javac --without-odbc --without-wx --without-ssh"
|
||||||
|
```
|
||||||
|
|
||||||
|
Source new profile settings:
|
||||||
|
```sh
|
||||||
|
$ source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the Erlang plugin:
|
||||||
|
```sh
|
||||||
|
$ asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Build and install Erlang (replace `<version>` with a supported release meeting Pleroma's requirements, OTP 27 is recommended):
|
||||||
|
```sh
|
||||||
|
$ asdf install erlang <version>
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
To get a list of available versions, use `asdf list all <plugin>`.
|
||||||
|
|
||||||
|
Install the Elixir plugin:
|
||||||
|
```sh
|
||||||
|
$ asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Elixir (replace `<version>` with a supported release and `<OTP version>` with the asdf-installed Erlang/OTP version, Elixir 1.18 is recommended):
|
||||||
|
```sh
|
||||||
|
$ asdf install elixir <version>-otp-<OTP version>
|
||||||
|
```
|
||||||
|
|
||||||
|
If installing Pleroma, follow the [Pleroma source installation instructions](./debian_based_en.md) up to the point before downloading mix dependencies for Pleroma, ignoring the instructions to download Elixir/Erlang.
|
||||||
|
|
||||||
|
Then run the following in the Pleroma source code directory:
|
||||||
|
```sh
|
||||||
|
$ asdf set erlang <OTP version>
|
||||||
|
$ asdf set elixir <Elixir version>
|
||||||
|
```
|
||||||
|
|
||||||
|
If installing Pleroma, continue with the source install guide up to the point of installing the systemd Pleroma service.
|
||||||
|
Don't attempt to start the Pleroma service yet.
|
||||||
|
|
||||||
|
Create a `pleroma.sh` startup script owned by the Pleroma user in Pleroma user's home directory:
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
# Modify to match your .bashrc settings used when installing asdf
|
||||||
|
export ASDF_DATA_DIR="/your/custom/data/dir"
|
||||||
|
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
|
||||||
|
|
||||||
|
exec mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the startup script executable:
|
||||||
|
```sh
|
||||||
|
chmod u+x pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Override the Pleroma service file to point to the new start up script, run `# systemctl edit pleroma` and add the following:
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/var/lib/pleroma/pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now start the Pleroma service.
|
||||||
|
If you are installing Pleroma, continue with creating your first user according to the install guide.
|
||||||
|
|
||||||
|
## pkgsrc
|
||||||
|
|
||||||
|
pkgsrc is NetBSD's portable package building framework also supporting Linux.
|
||||||
|
Its use is discouraged for novice users as it is harder to maintain and keep up-to-date compared to other options in this guide.
|
||||||
|
|
||||||
|
Before installing pkgsrc, choose which install method (tarball or CVS) you want to use.
|
||||||
|
Both update differently and have their caveats, check NetBSD documentation for more info: [https://www.netbsd.org/docs/pkgsrc/getting.html#uptodate](https://www.netbsd.org/docs/pkgsrc/getting.html#uptodate)
|
||||||
|
Using the stable branch is recommended.
|
||||||
|
|
||||||
|
!!!warning
|
||||||
|
For CVS install, `cvs` might not be installed on your system, in which case it can be installed with `# apt install cvs`.
|
||||||
|
|
||||||
|
pkgsrc supports both user-level and system-wide package installation, hence why most commands below don't have a `#`/`$` prepended to them.
|
||||||
|
For user-level installs, use the Pleroma user created in the [source install guide](./debian_based_en.md#install-pleromabe).
|
||||||
|
To start, follow the `quickstart -> install source packages` portion of the guide to acquire pkgsrc: [https://pkgsrc.org](https://pkgsrc.org)
|
||||||
|
|
||||||
|
### Bootstrap
|
||||||
|
|
||||||
|
Install build dependencies for bootstrap:
|
||||||
|
```
|
||||||
|
# apt install build-essential
|
||||||
|
```
|
||||||
|
|
||||||
|
For user-level installation run:
|
||||||
|
```sh
|
||||||
|
$ cd pkgsrc/bootstrap
|
||||||
|
$ ./bootstrap --prefer-pkgsrc yes --unprivileged --make-jobs <number of CPU cores - 1>
|
||||||
|
```
|
||||||
|
|
||||||
|
For system-wide installation run:
|
||||||
|
```
|
||||||
|
# cd pkgsrc/bootstrap
|
||||||
|
# ./bootstrap --prefer-pkgsrc yes --make-jobs <number of CPU cores - 1>
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
For more bootstrap options, run `./bootstrap --help`
|
||||||
|
|
||||||
|
Add pgksrc packages to `$PATH` in shell configuration (`.bashrc` for bash), default prefix is `$HOME/pkg` for user-level install and `/usr/pkg` for system-wide install:
|
||||||
|
```sh
|
||||||
|
export PATH="$PATH:<prefix for pkgsrc install>/bin"
|
||||||
|
```
|
||||||
|
|
||||||
|
Source new profile settings:
|
||||||
|
```sh
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
!!!info
|
||||||
|
For system-wide installs, both the root user and Pleroma user need to have the `PATH` set accordingly.
|
||||||
|
|
||||||
|
### Package installation
|
||||||
|
|
||||||
|
Erlang:
|
||||||
|
```sh
|
||||||
|
cd pkgsrc/lang/erlang
|
||||||
|
bmake install clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Elixir:
|
||||||
|
```sh
|
||||||
|
cd ../elixir
|
||||||
|
bmake install clean
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pleroma startup script
|
||||||
|
|
||||||
|
If installing Pleroma, first follow the [Pleroma source installation instructions](./debian_based_en.md) up to the point of installing the systemd Pleroma service, ignoring the instruction to install Elixir/Erlang. Don't attempt to start the service yet.
|
||||||
|
|
||||||
|
Create a `pleroma.sh` startup script owned by the Pleroma user in Pleroma user's home directory:
|
||||||
|
```sh
|
||||||
|
#!/bin/sh
|
||||||
|
# Modify to match your .bashrc settings used when bootsrapping pkgsrc
|
||||||
|
export PATH="$PATH:<prefix for pkgsrc install>/bin"
|
||||||
|
|
||||||
|
exec mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
Make the startup script executable:
|
||||||
|
```sh
|
||||||
|
chmod u+x pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Override the Pleroma service file to point to the new start up script, run `# systemctl edit pleroma` and add the following:
|
||||||
|
```ini
|
||||||
|
[Service]
|
||||||
|
ExecStart=
|
||||||
|
ExecStart=/var/lib/pleroma/pleroma.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now start the Pleroma service.
|
||||||
|
If you are installing Pleroma, continue with creating your first user according to the install guide.
|
||||||
|
|
||||||
|
## Maintenance
|
||||||
|
|
||||||
|
Since Elixir and Erlang is no longer managed by the system package manager after following this guide, it is recommended to periodically check their respective websites/repositories for newer versions with security bug fixes.
|
||||||
|
When updating Elixir, Erlang or both, stop Pleroma before proceeding with the update.
|
||||||
|
|
||||||
|
Pleroma will likely need to be recompiled after the update by running the following command in the Pleroma source code directory as the Pleroma user:
|
||||||
|
```sh
|
||||||
|
MIX_ENV=prod mix compile
|
||||||
|
```
|
||||||
|
|
||||||
|
If Pleroma fails to compile or start after upgrading Elixir/Erlang, run the following as a troubleshooting step in the Pleroma source code directory as the Pleroma user:
|
||||||
|
```sh
|
||||||
|
mix clean
|
||||||
|
mix deps.clean --all
|
||||||
|
mix deps.get
|
||||||
|
MIX_ENV=prod mix compile
|
||||||
|
```
|
||||||
|
|
||||||
|
Then try to start Pleroma again. If that doesn't help, join the Matrix room or IRC channel mentioned below, or make an issue on [git.pleroma.social/pleroma/pleroma](https://git.pleroma.social/pleroma/pleroma/issues/new/choose) explaining the problem.
|
||||||
|
|
||||||
|
## Questions
|
||||||
|
|
||||||
|
Questions about the guide or it didn’t work as it should, ask in [#pleroma:libera.chat](https://matrix.to/#/#pleroma:libera.chat) via Matrix or **#pleroma** on **libera.chat** via IRC.
|
||||||
|
|
||||||
|
<!-- vim ts=4:sw=4 -->
|
||||||
130
docs/installation/migrating_from_rebased.md
Normal file
130
docs/installation/migrating_from_rebased.md
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
# Migrating from Rebased
|
||||||
|
|
||||||
|
Rebased and Pleroma currently have compatible database schemas, so removing
|
||||||
|
Rebased-specific database objects is optional. The cleanup described below is
|
||||||
|
destructive and can take a long time on large databases.
|
||||||
|
|
||||||
|
This guide requires a Pleroma release that contains this document and native
|
||||||
|
webhook support. If you are reading the development version of this guide
|
||||||
|
before those changes reach `stable`, wait for the corresponding stable release.
|
||||||
|
|
||||||
|
## Prepare and back up
|
||||||
|
|
||||||
|
Plan a maintenance window and read the [backup guide](../administration/backup.md)
|
||||||
|
before starting.
|
||||||
|
|
||||||
|
Stop Rebased and confirm it is stopped before making the backup. Omit the
|
||||||
|
backup guide's final restart step and leave Rebased stopped:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl stop pleroma
|
||||||
|
sudo systemctl is-active pleroma
|
||||||
|
```
|
||||||
|
|
||||||
|
Make a verified database backup and a copy of the Rebased installation,
|
||||||
|
including its configuration, uploads, and `instance` directory. Also save the
|
||||||
|
current systemd unit if you plan to replace it.
|
||||||
|
|
||||||
|
## Install Pleroma
|
||||||
|
|
||||||
|
Do not use `git pull --rebase` to change an existing Rebased checkout into a
|
||||||
|
Pleroma checkout. The forks use different branches, and rebasing can replay
|
||||||
|
Rebased-specific commits over Pleroma. Move the old checkout aside and clone a
|
||||||
|
clean copy of Pleroma instead. The paths below assume the default source
|
||||||
|
installation in `/opt/pleroma`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mv /opt/pleroma /opt/pleroma.rebased
|
||||||
|
sudo mkdir /opt/pleroma
|
||||||
|
sudo chown pleroma:pleroma /opt/pleroma
|
||||||
|
sudo -Hu pleroma git clone -b stable https://git.pleroma.social/pleroma/pleroma /opt/pleroma
|
||||||
|
```
|
||||||
|
|
||||||
|
Restore the instance-specific files from the old checkout. This usually
|
||||||
|
includes `config/prod.secret.exs`, `config/prod.exported_from_db.secret.exs` if
|
||||||
|
present, `uploads`, and `instance`. Diff and restore any other non-stock
|
||||||
|
configuration files, preserve their ownership, and review them before starting
|
||||||
|
Pleroma.
|
||||||
|
|
||||||
|
Pleroma requires Elixir 1.17 or newer and Erlang/OTP 27 or newer. Install supported Erlang and Elixir
|
||||||
|
versions as described in the installation guide for your distribution before
|
||||||
|
running any Mix command. Do not reuse Rebased's `.tool-versions`. If you keep
|
||||||
|
using [`asdf`](https://asdf-vm.com/), select supported versions in the
|
||||||
|
`pleroma` user's home and use the asdf Mix shim for every command below.
|
||||||
|
|
||||||
|
Set `MIX` to the appropriate binary, verify the selected runtime, then install
|
||||||
|
the dependencies and compile Pleroma:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/pleroma
|
||||||
|
MIX=/usr/bin/mix
|
||||||
|
# If using asdf instead:
|
||||||
|
# MIX=/var/lib/pleroma/.asdf/shims/mix
|
||||||
|
sudo -Hu pleroma "$MIX" --version
|
||||||
|
sudo -Hu pleroma MIX_ENV=prod "$MIX" deps.get
|
||||||
|
sudo -Hu pleroma MIX_ENV=prod "$MIX" compile
|
||||||
|
```
|
||||||
|
|
||||||
|
Compare your existing systemd unit with `installation/pleroma.service`. If you
|
||||||
|
replace it, review its user, paths, and environment first. Reload systemd after
|
||||||
|
making changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
Rebased does not bundle a frontend, so `instance/static` may contain an
|
||||||
|
installed frontend. Pleroma includes a bundled frontend. You can remove the
|
||||||
|
separately installed frontend after the migration, but do not remove other
|
||||||
|
instance files such as custom emojis.
|
||||||
|
|
||||||
|
## Database migration
|
||||||
|
|
||||||
|
Rebased's extra columns, index, and enum labels do not prevent Pleroma from
|
||||||
|
starting. Existing notifications with Rebased-only event types are not
|
||||||
|
supported by Pleroma, however, and can cause notification API errors. Run the
|
||||||
|
cleanup below, or archive and remove those notification rows separately before
|
||||||
|
starting Pleroma. Skipping the cleanup preserves them if you need to restore
|
||||||
|
Rebased.
|
||||||
|
|
||||||
|
Pleroma supports Rebased's webhook schema. The cleanup does not roll back the
|
||||||
|
shared webhook migrations, so webhook configuration is preserved.
|
||||||
|
|
||||||
|
To perform the optional cleanup, run the rollback while the service is stopped:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MIX=/usr/bin/mix
|
||||||
|
# If using asdf instead:
|
||||||
|
# MIX=/var/lib/pleroma/.asdf/shims/mix
|
||||||
|
sudo -Hu pleroma MIX_ENV=prod "$MIX" ecto.rollback --migrations-path priv/repo/optional_migrations/rebased_rollbacks --all
|
||||||
|
```
|
||||||
|
|
||||||
|
This deletes the Rebased-only `accepts_email_list`, `location`, and
|
||||||
|
`last_move_at` user fields and the `pleroma:participation_accepted`,
|
||||||
|
`pleroma:participation_request`, `pleroma:event_reminder`, and
|
||||||
|
`pleroma:event_update` notifications. It also rewrites the notifications table
|
||||||
|
and can require substantial time, free disk space, and write-ahead log capacity
|
||||||
|
on a large instance.
|
||||||
|
|
||||||
|
Apply all pending Pleroma migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -Hu pleroma MIX_ENV=prod "$MIX" ecto.migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start and verify Pleroma
|
||||||
|
|
||||||
|
Start Pleroma and inspect its status and logs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start pleroma
|
||||||
|
sudo systemctl status pleroma
|
||||||
|
sudo journalctl -u pleroma -n 100 --no-pager
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify that the instance, uploads, frontend, and webhook configuration work as
|
||||||
|
expected before deleting `/opt/pleroma.rebased` or the database backup. If the
|
||||||
|
migration fails, keep the service stopped and restore both the database backup
|
||||||
|
and the previous checkout before restarting Rebased. If you changed the
|
||||||
|
systemd unit or runtime setup, restore those too and run
|
||||||
|
`systemctl daemon-reload` before restarting the service.
|
||||||
|
|
@ -76,35 +76,51 @@ $EDITOR /etc/pleroma/config.exs
|
||||||
```
|
```
|
||||||
## Installing the release
|
## Installing the release
|
||||||
Before proceeding, get the flavour from [Detecting flavour](otp_en.md#detecting-flavour) section in OTP installation guide.
|
Before proceeding, get the flavour from [Detecting flavour](otp_en.md#detecting-flavour) section in OTP installation guide.
|
||||||
|
|
||||||
|
|
||||||
|
Delete all files in pleroma user's directory:
|
||||||
```sh
|
```sh
|
||||||
# Delete all files in pleroma user's directory
|
|
||||||
rm -r ~pleroma/*
|
rm -r ~pleroma/*
|
||||||
|
```
|
||||||
|
|
||||||
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
Set the flavour environment variable to the string you got in Detecting flavour section.
|
||||||
# For example if the flavour is `amd64-musl` the command will be
|
For example if the flavour is `amd64-musl` the command will be:
|
||||||
|
|
||||||
|
```sh
|
||||||
export FLAVOUR="amd64-musl"
|
export FLAVOUR="amd64-musl"
|
||||||
|
```
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
Clone the release build into a temporary directory and unpack it.
|
||||||
# Replace `stable` with `unstable` if you want to run the unstable branch
|
Replace `stable` with `develop`, if you want to run the develop branch.
|
||||||
sudo -Hu pleroma "
|
|
||||||
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
|
||||||
unzip /tmp/pleroma.zip -d /tmp/
|
|
||||||
"
|
|
||||||
|
|
||||||
# Move the release to the home directory and delete temporary files
|
```sh
|
||||||
sudo -Hu pleroma "
|
sudo -Hu pleroma curl "https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-stable-$FLAVOUR/latest/pleroma.zip" -o /tmp/pleroma.zip
|
||||||
mv /tmp/release/* ~pleroma/
|
sudo -Hu pleroma unzip /tmp/pleroma.zip -d /tmp/
|
||||||
rmdir /tmp/release
|
```
|
||||||
rm /tmp/pleroma.zip
|
|
||||||
"
|
|
||||||
|
|
||||||
# Start the instance to verify that everything is working as expected
|
Move the release to the home directory and delete temporary files:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo -Hu pleroma mv /tmp/release/* ~pleroma/
|
||||||
|
sudo -Hu pleroma rmdir /tmp/release
|
||||||
|
sudo -Hu pleroma rm /tmp/pleroma.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the instance to verify that everything is working as expected:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -Hu pleroma "./bin/pleroma daemon"
|
sudo -Hu pleroma "./bin/pleroma daemon"
|
||||||
|
```
|
||||||
|
|
||||||
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
|
Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly:
|
||||||
|
|
||||||
|
```sh
|
||||||
sleep 20 && curl http://localhost:4000/api/v1/instance
|
sleep 20 && curl http://localhost:4000/api/v1/instance
|
||||||
|
```
|
||||||
|
|
||||||
# Stop the instance
|
Stop the instance:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -Hu pleroma "./bin/pleroma stop"
|
sudo -Hu pleroma "./bin/pleroma stop"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,13 +67,19 @@ Pleroma will be run by a dedicated \_pleroma user. Before creating it, insert th
|
||||||
|
|
||||||
```
|
```
|
||||||
pleroma:\
|
pleroma:\
|
||||||
:datasize=1536M:\
|
:datasize=4096M:\
|
||||||
:openfiles-max=4096:\
|
:openfiles-max=4096:\
|
||||||
:openfiles-cur=1024:\
|
:openfiles-cur=1024:\
|
||||||
:setenv=LC_ALL=en_US.UTF-8,VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS,MIX_ENV=prod:\
|
:setenv=LC_ALL=en_US.UTF-8,VIX_COMPILATION_MODE=PLATFORM_PROVIDED_LIBVIPS,MIX_ENV=prod:\
|
||||||
:tc=daemon:
|
:tc=daemon:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```
|
||||||
|
# cap_mkdb /etc/login.conf
|
||||||
|
```
|
||||||
|
|
||||||
This creates a "pleroma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having Pleroma crash some time after starting.
|
This creates a "pleroma" login class and sets higher values than default for datasize and openfiles (see [login.conf(5)](https://man.openbsd.org/login.conf)), this is required to avoid having Pleroma crash some time after starting.
|
||||||
|
|
||||||
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/):
|
Create the \_pleroma user, assign it the pleroma login class and create its home directory (/home/\_pleroma/):
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ Other than things bundled in the OTP release Pleroma depends on:
|
||||||
* curl (to download the release build)
|
* curl (to download the release build)
|
||||||
* unzip (needed to unpack release builds)
|
* unzip (needed to unpack release builds)
|
||||||
* ncurses (ERTS won't run without it)
|
* ncurses (ERTS won't run without it)
|
||||||
|
* libvips (for image processing)
|
||||||
* PostgreSQL (also utilizes extensions in postgresql-contrib)
|
* PostgreSQL (also utilizes extensions in postgresql-contrib)
|
||||||
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
* nginx (could be swapped with another reverse proxy but this guide covers only it)
|
||||||
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
* certbot (for Let's Encrypt certificates, could be swapped with another ACME client, but this guide covers only it)
|
||||||
|
|
@ -46,7 +47,13 @@ Other than things bundled in the OTP release Pleroma depends on:
|
||||||
|
|
||||||
=== "Debian/Ubuntu"
|
=== "Debian/Ubuntu"
|
||||||
```
|
```
|
||||||
apt install curl unzip libncurses5 postgresql postgresql-contrib nginx certbot libmagic-dev
|
apt install curl unzip libncurses6 libvips42t64 postgresql postgresql-contrib nginx certbot libmagic-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
On Debian 12 and Ubuntu 22.04, replace `libvips42t64` with `libvips42`:
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install curl unzip libncurses6 libvips42 postgresql postgresql-contrib nginx certbot libmagic-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installing optional packages
|
### Installing optional packages
|
||||||
|
|
@ -88,9 +95,23 @@ RUM indexes are an alternative indexing scheme that is not included in PostgreSQ
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Debian/Ubuntu"
|
=== "Debian/Ubuntu"
|
||||||
|
Install the package matching your distribution's PostgreSQL version:
|
||||||
|
|
||||||
```
|
```
|
||||||
# Available only on Buster/19.04
|
# Debian 13
|
||||||
apt install postgresql-11-rum
|
apt install postgresql-17-rum
|
||||||
|
|
||||||
|
# Debian 12
|
||||||
|
apt install postgresql-15-rum
|
||||||
|
|
||||||
|
# Ubuntu 26.04
|
||||||
|
apt install postgresql-18-rum
|
||||||
|
|
||||||
|
# Ubuntu 24.04
|
||||||
|
apt install postgresql-16-rum
|
||||||
|
|
||||||
|
# Ubuntu 22.04
|
||||||
|
apt install postgresql-14-rum
|
||||||
```
|
```
|
||||||
|
|
||||||
#### (Optional) Performance configuration
|
#### (Optional) Performance configuration
|
||||||
|
|
@ -109,60 +130,90 @@ Restart PostgreSQL to apply configuration changes:
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installing Pleroma
|
### Installing Pleroma
|
||||||
|
Create a Pleroma user:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Create a Pleroma user
|
|
||||||
adduser --system --shell /bin/false --home /opt/pleroma pleroma
|
adduser --system --shell /bin/false --home /opt/pleroma pleroma
|
||||||
|
```
|
||||||
|
|
||||||
# Set the flavour environment variable to the string you got in Detecting flavour section.
|
Set the flavour environment variable to the string you got in Detecting flavour section. For example if the flavour is `amd64-musl` the command will be:
|
||||||
# For example if the flavour is `amd64-musl` the command will be
|
|
||||||
|
```sh
|
||||||
export FLAVOUR="amd64-musl"
|
export FLAVOUR="amd64-musl"
|
||||||
|
```
|
||||||
|
|
||||||
# Clone the release build into a temporary directory and unpack it
|
Clone the release build into a temporary directory and unpack it:
|
||||||
sudo -Hu pleroma "
|
|
||||||
curl 'https://git.pleroma.social/api/v4/projects/2/jobs/artifacts/stable/download?job=$FLAVOUR' -o /tmp/pleroma.zip
|
|
||||||
unzip /tmp/pleroma.zip -d /tmp/
|
|
||||||
"
|
|
||||||
|
|
||||||
# Move the release to the home directory and delete temporary files
|
```sh
|
||||||
sudo -Hu pleroma "
|
sudo -Hu pleroma curl "https://git.pleroma.social/api/packages/pleroma/generic/pleroma-otp-stable-$FLAVOUR/latest/pleroma.zip" -o /tmp/pleroma.zip
|
||||||
mv /tmp/release/* /opt/pleroma
|
sudo -Hu pleroma unzip /tmp/pleroma.zip -d /tmp/
|
||||||
rmdir /tmp/release
|
```
|
||||||
rm /tmp/pleroma.zip
|
|
||||||
"
|
|
||||||
# Create uploads directory and set proper permissions (skip if planning to use a remote uploader)
|
|
||||||
# Note: It does not have to be `/var/lib/pleroma/uploads`, the config generator will ask about the upload directory later
|
|
||||||
|
|
||||||
|
Move the release to the home directory and delete temporary files:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo -Hu pleroma mv /tmp/release/* /opt/pleroma
|
||||||
|
sudo -Hu pleroma rmdir /tmp/release
|
||||||
|
sudo -Hu pleroma rm /tmp/pleroma.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Create uploads directory and set proper permissions (skip if planning to use a remote uploader).
|
||||||
|
Note: It does not have to be `/var/lib/pleroma/uploads`, the config generator will ask about the upload directory later.
|
||||||
|
|
||||||
|
```sh
|
||||||
mkdir -p /var/lib/pleroma/uploads
|
mkdir -p /var/lib/pleroma/uploads
|
||||||
chown -R pleroma /var/lib/pleroma
|
chown -R pleroma /var/lib/pleroma
|
||||||
|
```
|
||||||
|
|
||||||
# Create custom public files directory (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
Create custom public files directory (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
||||||
# Note: It does not have to be `/var/lib/pleroma/static`, the config generator will ask about the custom public files directory later
|
Note: It does not have to be `/var/lib/pleroma/static`, the config generator will ask about the custom public files directory later.
|
||||||
|
|
||||||
|
```sh
|
||||||
mkdir -p /var/lib/pleroma/static
|
mkdir -p /var/lib/pleroma/static
|
||||||
chown -R pleroma /var/lib/pleroma
|
chown -R pleroma /var/lib/pleroma
|
||||||
|
```
|
||||||
|
|
||||||
# Create a config directory
|
Create a config directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
mkdir -p /etc/pleroma
|
mkdir -p /etc/pleroma
|
||||||
chown -R pleroma /etc/pleroma
|
chown -R pleroma /etc/pleroma
|
||||||
|
```
|
||||||
|
|
||||||
# Run the config generator
|
Run the config generator:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
sudo -Hu pleroma "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
||||||
|
```
|
||||||
|
|
||||||
# Create the postgres database
|
Create the postgres database and schema:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
sudo -u postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
||||||
|
|
||||||
# Create the database schema
|
|
||||||
sudo -Hu pleroma "./bin/pleroma_ctl migrate"
|
sudo -Hu pleroma "./bin/pleroma_ctl migrate"
|
||||||
|
```
|
||||||
|
|
||||||
# If you have installed RUM indexes uncommend and run
|
(Optional) If you have installed RUM indexes:
|
||||||
# sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
|
||||||
|
|
||||||
# Start the instance to verify that everything is working as expected
|
```sh
|
||||||
|
sudo -Hu pleroma "./bin/pleroma_ctl migrate --migrations-path priv/repo/optional_migrations/rum_indexing/"
|
||||||
|
```
|
||||||
|
|
||||||
|
Start the instance to verify that everything is working as expected:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -Hu pleroma "./bin/pleroma daemon"
|
sudo -Hu pleroma "./bin/pleroma daemon"
|
||||||
|
```
|
||||||
|
|
||||||
# Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly
|
Wait for about 20 seconds and query the instance endpoint, if it shows your uri, name and email correctly, you are configured correctly:
|
||||||
|
|
||||||
|
```sh
|
||||||
sleep 20 && curl http://localhost:4000/api/v1/instance
|
sleep 20 && curl http://localhost:4000/api/v1/instance
|
||||||
|
```
|
||||||
|
|
||||||
# Stop the instance
|
Stop the instance:
|
||||||
|
|
||||||
|
```sh
|
||||||
sudo -Hu pleroma "./bin/pleroma stop"
|
sudo -Hu pleroma "./bin/pleroma stop"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ depend() {
|
||||||
need nginx postgresql
|
need nginx postgresql
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
ebegin "running migrations if any"
|
||||||
|
su -s /bin/sh -c "cd \"${directory}\" && exec /usr/bin/mix ecto.migrate" \
|
||||||
|
"${command_user%%:*}"
|
||||||
|
eend "$?"
|
||||||
|
}
|
||||||
|
|
||||||
healthcheck() {
|
healthcheck() {
|
||||||
# put pleroma_health=YES in /etc/conf.d/pleroma if you want healthchecking
|
# put pleroma_health=YES in /etc/conf.d/pleroma if you want healthchecking
|
||||||
# and make sure you have curl installed
|
# and make sure you have curl installed
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ After=network.target postgresql.service
|
||||||
ExecReload=/bin/kill $MAINPID
|
ExecReload=/bin/kill $MAINPID
|
||||||
KillMode=process
|
KillMode=process
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
StandardOutput=journal
|
|
||||||
|
|
||||||
; Name of the user that runs the Pleroma service.
|
; Name of the user that runs the Pleroma service.
|
||||||
User=pleroma
|
User=pleroma
|
||||||
|
|
@ -15,11 +14,9 @@ Environment="MIX_ENV=prod"
|
||||||
|
|
||||||
; Make sure that all paths fit your installation.
|
; Make sure that all paths fit your installation.
|
||||||
; Path to the home directory of the user running the Pleroma service.
|
; Path to the home directory of the user running the Pleroma service.
|
||||||
Environment="HOME=/home/pleroma"
|
Environment="HOME=/var/lib/pleroma"
|
||||||
; Path to the folder containing the Pleroma installation.
|
; Path to the folder containing the Pleroma installation.
|
||||||
WorkingDirectory=/home/pleroma/pleroma
|
WorkingDirectory=/opt/pleroma
|
||||||
; Path to the environment file. the file contains RELEASE_COOKIE and etc
|
|
||||||
;EnvironmentFile=/opt/pleroma/config/pleroma.env
|
|
||||||
; Path to the Mix binary.
|
; Path to the Mix binary.
|
||||||
ExecStart=/usr/bin/mix phx.server
|
ExecStart=/usr/bin/mix phx.server
|
||||||
|
|
||||||
|
|
@ -27,7 +24,7 @@ ExecStart=/usr/bin/mix phx.server
|
||||||
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
|
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
|
||||||
ProtectHome=false
|
ProtectHome=true
|
||||||
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,8 @@ defmodule Mix.Pleroma do
|
||||||
{Majic.Pool,
|
{Majic.Pool,
|
||||||
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}
|
||||||
] ++
|
] ++
|
||||||
http_children(adapter)
|
http_children(adapter) ++
|
||||||
|
search_children()
|
||||||
|
|
||||||
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
|
||||||
|
|
||||||
|
|
@ -141,4 +142,14 @@ defmodule Mix.Pleroma do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp http_children(_), do: []
|
defp http_children(_), do: []
|
||||||
|
|
||||||
|
defp search_children do
|
||||||
|
case Pleroma.Config.get([Pleroma.Search, :module]) do
|
||||||
|
Pleroma.Search.ParadeDB ->
|
||||||
|
[Pleroma.Search.ParadeDB.Repo]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -234,6 +234,64 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Removes non-whitelisted configuration sections
|
||||||
|
def run(["filter_whitelisted" | rest]) do
|
||||||
|
{options, [], []} =
|
||||||
|
OptionParser.parse(
|
||||||
|
rest,
|
||||||
|
strict: [force: :boolean],
|
||||||
|
aliases: [f: :force]
|
||||||
|
)
|
||||||
|
|
||||||
|
force = Keyword.get(options, :force, false)
|
||||||
|
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
whitelisted_configs = Pleroma.Config.get(:database_config_whitelist)
|
||||||
|
|
||||||
|
if whitelisted_configs in [nil, false] do
|
||||||
|
shell_error("No unwanted settings in ConfigDB. No changes made.")
|
||||||
|
else
|
||||||
|
{whitelisted_groups, whitelisted_keys} = get_filters(whitelisted_configs)
|
||||||
|
|
||||||
|
filtered =
|
||||||
|
from(c in ConfigDB)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(¬_whitelisted?(&1, whitelisted_groups, whitelisted_keys))
|
||||||
|
|
||||||
|
do_config_filter(filtered, force)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Removes blacklisted configuration sections
|
||||||
|
def run(["filter_blacklisted" | rest]) do
|
||||||
|
{options, [], []} =
|
||||||
|
OptionParser.parse(
|
||||||
|
rest,
|
||||||
|
strict: [force: :boolean],
|
||||||
|
aliases: [f: :force]
|
||||||
|
)
|
||||||
|
|
||||||
|
force = Keyword.get(options, :force, false)
|
||||||
|
|
||||||
|
start_pleroma()
|
||||||
|
|
||||||
|
blacklisted_configs = Pleroma.Config.get(:database_config_blacklist)
|
||||||
|
|
||||||
|
if blacklisted_configs in [nil, false] do
|
||||||
|
shell_error("No unwanted settings in ConfigDB. No changes made.")
|
||||||
|
else
|
||||||
|
{blacklisted_groups, blacklisted_keys} = get_filters(blacklisted_configs)
|
||||||
|
|
||||||
|
filtered =
|
||||||
|
from(c in ConfigDB)
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.filter(&blacklisted?(&1, blacklisted_groups, blacklisted_keys))
|
||||||
|
|
||||||
|
do_config_filter(filtered, force)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec migrate_to_db(Path.t() | nil) :: any()
|
@spec migrate_to_db(Path.t() | nil) :: any()
|
||||||
def migrate_to_db(file_path \\ nil) do
|
def migrate_to_db(file_path \\ nil) do
|
||||||
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
with :ok <- Pleroma.Config.DeprecationWarnings.warn() do
|
||||||
|
|
@ -277,9 +335,12 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
group
|
group
|
||||||
|> Pleroma.Config.Loader.filter_group(settings)
|
|> Pleroma.Config.Loader.filter_group(settings)
|
||||||
|> Enum.each(fn {key, value} ->
|
|> Enum.each(fn {key, value} ->
|
||||||
|
if ConfigDB.static?(group, key) do
|
||||||
|
shell_info("Settings for key #{key} must remain in static configuration; skipping.")
|
||||||
|
else
|
||||||
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
{:ok, _} = ConfigDB.update_or_create(%{group: group, key: key, value: value})
|
||||||
|
|
||||||
shell_info("Settings for key #{key} migrated.")
|
shell_info("Settings for key #{key} migrated.")
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
shell_info("Settings for group #{inspect(group)} migrated.")
|
shell_info("Settings for group #{inspect(group)} migrated.")
|
||||||
|
|
@ -434,4 +495,50 @@ defmodule Mix.Tasks.Pleroma.Config do
|
||||||
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;")
|
||||||
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_filters(config) do
|
||||||
|
groups =
|
||||||
|
config
|
||||||
|
|> Enum.filter(fn
|
||||||
|
{_group} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|> Enum.map(fn {group} -> group end)
|
||||||
|
|
||||||
|
keys =
|
||||||
|
config
|
||||||
|
|> Enum.filter(fn
|
||||||
|
{_group, _key} -> true
|
||||||
|
_ -> false
|
||||||
|
end)
|
||||||
|
|
||||||
|
{groups, keys}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp not_whitelisted?(%{group: group, key: key}, whitelisted_groups, whitelisted_keys) do
|
||||||
|
not Enum.member?(whitelisted_groups, group) and
|
||||||
|
not Enum.member?(whitelisted_keys, {group, key})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp blacklisted?(%{group: group, key: key}, blacklisted_groups, blacklisted_keys) do
|
||||||
|
Enum.member?(blacklisted_groups, group) or
|
||||||
|
Enum.member?(blacklisted_keys, {group, key})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_config_filter(filtered, _) when length(filtered) == 0 do
|
||||||
|
shell_error("No unwanted settings in ConfigDB. No changes made.")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_config_filter(filtered, force) do
|
||||||
|
shell_info("The following settings will be removed from ConfigDB:\n")
|
||||||
|
Enum.each(filtered, &dump(&1))
|
||||||
|
|
||||||
|
if force or shell_prompt("Are you sure you want to continue?", "n") in ~w(Yn Y y) do
|
||||||
|
filtered_ids = Enum.map(filtered, fn %{id: id} -> id end)
|
||||||
|
|
||||||
|
Repo.delete_all(from(c in ConfigDB, where: c.id in ^filtered_ids))
|
||||||
|
else
|
||||||
|
shell_error("No changes made.")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||||
DELETE FROM hashtags AS ht
|
DELETE FROM hashtags AS ht
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
SELECT 1 FROM hashtags_objects hto
|
SELECT 1 FROM hashtags_objects hto
|
||||||
WHERE ht.id = hto.hashtag_id)
|
WHERE ht.id = hto.hashtag_id
|
||||||
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM user_follows_hashtag ufh
|
||||||
|
WHERE ht.id = ufh.hashtag_id
|
||||||
|
)
|
||||||
"""
|
"""
|
||||||
|> Repo.query()
|
|> Repo.query()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ defmodule Mix.Tasks.Pleroma.OpenapiSpec do
|
||||||
else
|
else
|
||||||
{_, errors} ->
|
{_, errors} ->
|
||||||
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
IO.puts(IO.ANSI.format([:red, :bright, "Spec check failed, errors:"]))
|
||||||
Enum.map(errors, &IO.puts/1)
|
Enum.each(errors, &IO.puts/1)
|
||||||
|
|
||||||
raise "Spec check failed"
|
raise "Spec check failed"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.Search.Indexer do
|
defmodule Mix.Tasks.Pleroma.Search.Indexer do
|
||||||
import Mix.Pleroma
|
import Mix.Pleroma
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Pleroma.Workers.SearchIndexingWorker
|
|
||||||
|
|
||||||
def run(["create_index"]) do
|
def run(["create_index"]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
@ -35,49 +32,30 @@ defmodule Mix.Tasks.Pleroma.Search.Indexer do
|
||||||
strict: [
|
strict: [
|
||||||
chunk: :integer,
|
chunk: :integer,
|
||||||
limit: :integer,
|
limit: :integer,
|
||||||
step: :integer
|
step: :integer,
|
||||||
|
before: :string
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
chunk_size = Keyword.get(options, :chunk, 100)
|
result =
|
||||||
limit = Keyword.get(options, :limit, 100_000)
|
Pleroma.Search.Backfill.run(
|
||||||
per_step = Keyword.get(options, :step, 1000)
|
options ++
|
||||||
|
[
|
||||||
chunks = max(div(limit, per_step), 1)
|
on_page: fn page ->
|
||||||
|
IO.puts(
|
||||||
1..chunks
|
"Queued #{page.count} activities (#{page.enqueued} total), " <>
|
||||||
|> Enum.each(fn step ->
|
"checkpoint: #{page.next_cursor}"
|
||||||
q =
|
)
|
||||||
from(a in Pleroma.Activity,
|
end
|
||||||
limit: ^per_step,
|
]
|
||||||
offset: ^per_step * (^step - 1),
|
|
||||||
select: [:id],
|
|
||||||
order_by: [desc: :id]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, ids} =
|
IO.puts("Queued #{result.enqueued} activities")
|
||||||
Pleroma.Repo.transaction(fn ->
|
|
||||||
Pleroma.Repo.stream(q, timeout: :infinity)
|
|
||||||
|> Enum.map(fn a ->
|
|
||||||
a.id
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
IO.puts("Got #{length(ids)} activities, adding to indexer")
|
unless result.exhausted do
|
||||||
|
IO.puts("Resume with --before #{result.next_cursor}")
|
||||||
ids
|
end
|
||||||
|> Enum.chunk_every(chunk_size)
|
|
||||||
|> Enum.each(fn chunk ->
|
|
||||||
IO.puts("Adding #{length(chunk)} activities to indexing queue")
|
|
||||||
|
|
||||||
chunk
|
|
||||||
|> Enum.map(fn id ->
|
|
||||||
SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => id})
|
|
||||||
end)
|
|
||||||
|> Oban.insert_all()
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
query,
|
query,
|
||||||
timeout: :infinity
|
timeout: :infinity
|
||||||
)
|
)
|
||||||
|> Stream.map(&Pleroma.Search.Meilisearch.object_to_search_data/1)
|
|> Stream.map(&Pleroma.Search.object_to_search_data/1)
|
||||||
|> Stream.filter(fn o -> not is_nil(o) end)
|
|> Stream.filter(fn o -> not is_nil(o) end)
|
||||||
|> Stream.chunk_every(chunk_size)
|
|> Stream.chunk_every(chunk_size)
|
||||||
|> Stream.transform(0, fn objects, acc ->
|
|> Stream.transform(0, fn objects, acc ->
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Activity.HTML do
|
||||||
|
|
||||||
def invalidate_cache_for(activity_id) do
|
def invalidate_cache_for(activity_id) do
|
||||||
keys = get_cache_keys_for(activity_id)
|
keys = get_cache_keys_for(activity_id)
|
||||||
Enum.map(keys, &@cachex.del(:scrubber_cache, &1))
|
Enum.each(keys, &@cachex.del(:scrubber_cache, &1))
|
||||||
@cachex.del(:scrubber_management_cache, activity_id)
|
@cachex.del(:scrubber_management_cache, activity_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ defmodule Pleroma.Application do
|
||||||
# every time the application is restarted, so we disable module
|
# every time the application is restarted, so we disable module
|
||||||
# conflicts at runtime
|
# conflicts at runtime
|
||||||
Code.compiler_options(ignore_module_conflict: true)
|
Code.compiler_options(ignore_module_conflict: true)
|
||||||
|
configure_logger()
|
||||||
Pleroma.Telemetry.Logger.attach()
|
Pleroma.Telemetry.Logger.attach()
|
||||||
Config.Holder.save_default()
|
Config.Holder.save_default()
|
||||||
Pleroma.HTML.compile_scrubbers()
|
Pleroma.HTML.compile_scrubbers()
|
||||||
|
|
@ -65,6 +66,7 @@ defmodule Pleroma.Application do
|
||||||
|
|
||||||
if match?({Tesla.Adapter.Finch, _}, adapter) do
|
if match?({Tesla.Adapter.Finch, _}, adapter) do
|
||||||
Logger.info("Starting Finch")
|
Logger.info("Starting Finch")
|
||||||
|
use_castore_for_otp_cacerts()
|
||||||
Finch.start_link(name: MyFinch)
|
Finch.start_link(name: MyFinch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -80,7 +82,10 @@ defmodule Pleroma.Application do
|
||||||
[
|
[
|
||||||
Pleroma.PromEx,
|
Pleroma.PromEx,
|
||||||
Pleroma.LDAP,
|
Pleroma.LDAP,
|
||||||
Pleroma.Repo,
|
Pleroma.Repo
|
||||||
|
] ++
|
||||||
|
search_children() ++
|
||||||
|
[
|
||||||
Config.TransferTask,
|
Config.TransferTask,
|
||||||
Pleroma.Emoji,
|
Pleroma.Emoji,
|
||||||
Pleroma.Web.Plugs.RateLimiter.Supervisor,
|
Pleroma.Web.Plugs.RateLimiter.Supervisor,
|
||||||
|
|
@ -113,6 +118,44 @@ defmodule Pleroma.Application do
|
||||||
Supervisor.start_link(children, opts)
|
Supervisor.start_link(children, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def configure_logger do
|
||||||
|
Config.get([:logger, :backends], [])
|
||||||
|
|> Enum.each(fn backend ->
|
||||||
|
case backend_to_logger(backend) do
|
||||||
|
{:ok, logger} ->
|
||||||
|
add_logger(logger)
|
||||||
|
|
||||||
|
{:error, :console_backend} ->
|
||||||
|
Logger.warning(":console is no longer considered a backend and is enabled by default")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_logger(backend) do
|
||||||
|
case LoggerBackends.add(backend) do
|
||||||
|
{:ok, _} ->
|
||||||
|
Logger.debug("Successfully added logger backend: #{inspect(backend)}")
|
||||||
|
|
||||||
|
{:error, :already_present} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Logger.error("Failed to add logger backend #{inspect(backend)}: #{inspect(reason)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp backend_to_logger(:console), do: {:error, :console_backend}
|
||||||
|
|
||||||
|
defp backend_to_logger({:ex_syslogger = backend, name}) do
|
||||||
|
Logger.warning(
|
||||||
|
"Configuration {:#{backend}, :#{name}} is incorrect. Use {ExSyslogger, :#{name}} instead!"
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, {ExSyslogger, name}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp backend_to_logger(backend), do: {:ok, backend}
|
||||||
|
|
||||||
def load_custom_modules do
|
def load_custom_modules do
|
||||||
dir = Config.get([:modules, :runtime_dir])
|
dir = Config.get([:modules, :runtime_dir])
|
||||||
|
|
||||||
|
|
@ -159,6 +202,16 @@ defmodule Pleroma.Application do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp search_children do
|
||||||
|
case Config.get([Pleroma.Search, :module]) do
|
||||||
|
Pleroma.Search.ParadeDB ->
|
||||||
|
[Pleroma.Search.ParadeDB.Repo]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp emoji_packs_expiration,
|
defp emoji_packs_expiration,
|
||||||
do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60))
|
do: expiration(default: :timer.seconds(5 * 60), interval: :timer.seconds(60))
|
||||||
|
|
||||||
|
|
@ -252,6 +305,15 @@ defmodule Pleroma.Application do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Mint trusts the OTP CA store, which comes from the OS. Make it the CAStore
|
||||||
|
# bundle that the other adapters trust. OTP 24 has no such store and Mint
|
||||||
|
# uses CAStore there anyway.
|
||||||
|
defp use_castore_for_otp_cacerts do
|
||||||
|
if Code.ensure_loaded?(:public_key) and function_exported?(:public_key, :cacerts_load, 1) do
|
||||||
|
:ok = :public_key.cacerts_load(CAStore.file_path())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp http_children_hackney do
|
defp http_children_hackney do
|
||||||
pools = [:federation, :media]
|
pools = [:federation, :media]
|
||||||
|
|
||||||
|
|
@ -278,7 +340,8 @@ defmodule Pleroma.Application do
|
||||||
config = Config.get(ConcurrentLimiter, [])
|
config = Config.get(ConcurrentLimiter, [])
|
||||||
|
|
||||||
[
|
[
|
||||||
Pleroma.Search
|
Pleroma.Search,
|
||||||
|
Pleroma.Webhook.Notify
|
||||||
]
|
]
|
||||||
|> Enum.each(fn module ->
|
|> Enum.each(fn module ->
|
||||||
mod_config = Keyword.get(config, module, [])
|
mod_config = Keyword.get(config, module, [])
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ defmodule Pleroma.Bookmark do
|
||||||
schema "bookmarks" do
|
schema "bookmarks" do
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType)
|
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.Type)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Bookmark do
|
||||||
|> validate_required([:user_id, :activity_id])
|
|> validate_required([:user_id, :activity_id])
|
||||||
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||||
|> Repo.insert(
|
|> Repo.insert(
|
||||||
on_conflict: [set: [folder_id: folder_id]],
|
on_conflict: [set: [folder_id: folder_id, updated_at: NaiveDateTime.utc_now()]],
|
||||||
conflict_target: [:user_id, :activity_id]
|
conflict_target: [:user_id, :activity_id]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -76,11 +76,4 @@ defmodule Pleroma.Bookmark do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
|> Repo.delete()
|
|> Repo.delete()
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_folder(bookmark, folder_id) do
|
|
||||||
bookmark
|
|
||||||
|> cast(%{folder_id: folder_id}, [:folder_id])
|
|
||||||
|> validate_required([:folder_id])
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.BookmarkFolder do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
|
@primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
|
||||||
|
|
||||||
schema "bookmark_folders" do
|
schema "bookmark_folders" do
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
|
|
|
||||||
|
|
@ -108,4 +108,6 @@ defmodule Pleroma.Chat do
|
||||||
|> cast(%{pinned: false}, [:pinned])
|
|> cast(%{pinned: false}, [:pinned])
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], true)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@logger_formatter_config_knobs [:colors, :format, :metadata, :truncate, :utc_log]
|
||||||
|
|
||||||
def check_exiftool_filter do
|
def check_exiftool_filter do
|
||||||
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
|
filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
|
||||||
|
|
||||||
|
|
@ -218,7 +220,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
check_quarantined_instances_tuples(),
|
check_quarantined_instances_tuples(),
|
||||||
check_transparency_exclusions_tuples(),
|
check_transparency_exclusions_tuples(),
|
||||||
check_simple_policy_tuples(),
|
check_simple_policy_tuples(),
|
||||||
check_exiftool_filter()
|
check_exiftool_filter(),
|
||||||
|
check_deprecated_logger_config()
|
||||||
]
|
]
|
||||||
|> Enum.reduce(:ok, fn
|
|> Enum.reduce(:ok, fn
|
||||||
:ok, :ok -> :ok
|
:ok, :ok -> :ok
|
||||||
|
|
@ -415,4 +418,72 @@ defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp merge_deprecated_logger_config(config) do
|
||||||
|
formatter_config = Enum.filter(config, fn {k, _} -> k in @logger_formatter_config_knobs end)
|
||||||
|
formatter = Logger.default_formatter(formatter_config)
|
||||||
|
log_level = Keyword.get(config, :level, nil)
|
||||||
|
|
||||||
|
Logger.debug("""
|
||||||
|
Reconfiguring console Logger with deprecated configuration syntax.
|
||||||
|
Handler configuration:
|
||||||
|
log_level: #{inspect(log_level)}
|
||||||
|
|
||||||
|
Formatter:
|
||||||
|
#{inspect(formatter)}
|
||||||
|
""")
|
||||||
|
|
||||||
|
if Config.get(:env) != :test do
|
||||||
|
if log_level, do: :logger.update_handler_config(:default, :level, log_level)
|
||||||
|
:logger.set_handler_config(:default, :formatter, formatter)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec check_deprecated_logger_config() :: :ok | :error
|
||||||
|
def check_deprecated_logger_config do
|
||||||
|
backends_config = Application.get_env(:logger, :backends)
|
||||||
|
console_config = Application.get_env(:logger, :console)
|
||||||
|
|
||||||
|
# NOTE: No need to merge the old backends config since it still works.
|
||||||
|
# And new configuration will just add new Logger backends.
|
||||||
|
backend =
|
||||||
|
if backends_config do
|
||||||
|
Logger.warning("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your configuration is using deprecated syntax for configuring backends of Elixir's logger.
|
||||||
|
`config :logger, backends: [...]` is deprecated syntax due to changes in Elixir.
|
||||||
|
Please update your configuration at your earliest convenience to use:
|
||||||
|
`config :pleroma, :logger, backends: [...]`
|
||||||
|
|
||||||
|
Note: `:console` is no longer considered a backend and is used by default, you can disable it using:
|
||||||
|
`config :logger, :default_handler: false`
|
||||||
|
""")
|
||||||
|
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
console =
|
||||||
|
if console_config do
|
||||||
|
Logger.warning("""
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your configuration is using deprecated syntax for configuring logging to console.
|
||||||
|
`config :logger, :console` is deprecated syntax due to changes in Elixir.
|
||||||
|
Please update your configuration at your earliest convenience to use
|
||||||
|
`config :logger, default_handler` and `config :logger, :default_formatter`.
|
||||||
|
|
||||||
|
Note: `:default_handler` is used only for the `level` setting. All other configurations go under
|
||||||
|
`:default_formatter`. For more info visit: https://hexdocs.pm/logger/Logger.html#module-backends-and-backwards-compatibility
|
||||||
|
""")
|
||||||
|
|
||||||
|
merge_deprecated_logger_config(console_config)
|
||||||
|
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
if backend or console, do: :error, else: :ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,11 +47,12 @@ defmodule Pleroma.Config.TransferTask do
|
||||||
|
|
||||||
settings =
|
settings =
|
||||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||||
|
|> Enum.reject(&ConfigDB.static?/1)
|
||||||
|> Enum.map(&merge_with_default/1)
|
|> Enum.map(&merge_with_default/1)
|
||||||
|
|
||||||
started_applications = Application.started_applications()
|
started_applications = Application.started_applications()
|
||||||
|
|
||||||
reject = [nil, :postgrex]
|
reject = [nil, :postgrex, :logger]
|
||||||
|
|
||||||
reject =
|
reject =
|
||||||
if restart_pleroma? do
|
if restart_pleroma? do
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.ConfigDB do
|
||||||
import Pleroma.Web.Gettext
|
import Pleroma.Web.Gettext
|
||||||
|
|
||||||
alias __MODULE__
|
alias __MODULE__
|
||||||
|
alias Pleroma.EctoType.Config.RateLimit
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
@ -22,6 +23,12 @@ defmodule Pleroma.ConfigDB do
|
||||||
{:pleroma, :mrf_keyword, :replace}
|
{:pleroma, :mrf_keyword, :replace}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@static_config [
|
||||||
|
{:pleroma, Pleroma.Search},
|
||||||
|
{:pleroma, Pleroma.Search.ParadeDB},
|
||||||
|
{:pleroma, Pleroma.Search.ParadeDB.Repo}
|
||||||
|
]
|
||||||
|
|
||||||
schema "config" do
|
schema "config" do
|
||||||
field(:key, Pleroma.EctoType.Config.Atom)
|
field(:key, Pleroma.EctoType.Config.Atom)
|
||||||
field(:group, Pleroma.EctoType.Config.Atom)
|
field(:group, Pleroma.EctoType.Config.Atom)
|
||||||
|
|
@ -31,16 +38,32 @@ defmodule Pleroma.ConfigDB do
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_all_as_keyword() :: keyword()
|
@spec get_all_as_keyword(keyword()) :: keyword()
|
||||||
def get_all_as_keyword do
|
def get_all_as_keyword(options \\ []) do
|
||||||
ConfigDB
|
ConfigDB
|
||||||
|> select([c], {c.group, c.key, c.value})
|
|> select([c], {c.group, c.key, c.value})
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|> maybe_reject_static(options)
|
||||||
|> Enum.reduce([], fn {group, key, value}, acc ->
|
|> Enum.reduce([], fn {group, key, value}, acc ->
|
||||||
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
Keyword.update(acc, group, [{key, value}], &Keyword.merge(&1, [{key, value}]))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def static?(%{group: group, key: key}), do: static?(group, key)
|
||||||
|
def static?(_config), do: false
|
||||||
|
|
||||||
|
def static?(group, key) do
|
||||||
|
Enum.any?(@static_config, fn {static_group, static_key} ->
|
||||||
|
group in [static_group, inspect(static_group)] and key in [static_key, inspect(static_key)]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_static(configs, exclude_static: true) do
|
||||||
|
Enum.reject(configs, fn {group, key, _value} -> static?(group, key) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_static(configs, _options), do: configs
|
||||||
|
|
||||||
@spec get_all_by_group(atom() | String.t()) :: [t()]
|
@spec get_all_by_group(atom() | String.t()) :: [t()]
|
||||||
def get_all_by_group(group) do
|
def get_all_by_group(group) do
|
||||||
from(c in ConfigDB, where: c.group == ^group) |> Repo.all()
|
from(c in ConfigDB, where: c.group == ^group) |> Repo.all()
|
||||||
|
|
@ -60,8 +83,59 @@ defmodule Pleroma.ConfigDB do
|
||||||
|> cast(params, [:key, :group, :value])
|
|> cast(params, [:key, :group, :value])
|
||||||
|> validate_required([:key, :group, :value])
|
|> validate_required([:key, :group, :value])
|
||||||
|> unique_constraint(:key, name: :config_group_key_index)
|
|> unique_constraint(:key, name: :config_group_key_index)
|
||||||
|
|> validate_rate_limit()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_rate_limit(changeset) do
|
||||||
|
group = get_field(changeset, :group)
|
||||||
|
key = get_field(changeset, :key)
|
||||||
|
|
||||||
|
if group == :pleroma and key == :rate_limit do
|
||||||
|
value = get_field(changeset, :value)
|
||||||
|
|
||||||
|
case normalize_rate_limit(value) do
|
||||||
|
{:ok, normalized_value} ->
|
||||||
|
put_change(changeset, :value, normalized_value)
|
||||||
|
|
||||||
|
{:error, {limiter_name, reason}} ->
|
||||||
|
add_error(
|
||||||
|
changeset,
|
||||||
|
:value,
|
||||||
|
"invalid :rate_limit value for #{inspect(limiter_name)}: #{reason}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_rate_limit(nil), do: {:ok, nil}
|
||||||
|
|
||||||
|
defp normalize_rate_limit(%{} = value), do: normalize_rate_limit(Map.to_list(value))
|
||||||
|
|
||||||
|
defp normalize_rate_limit(value) when is_list(value) do
|
||||||
|
if Keyword.keyword?(value) do
|
||||||
|
value
|
||||||
|
|> Enum.reduce_while({:ok, []}, fn {limiter_name, limiter_value}, {:ok, acc} ->
|
||||||
|
case RateLimit.cast_with_error(limiter_value) do
|
||||||
|
{:ok, normalized_limiter_value} ->
|
||||||
|
{:cont, {:ok, [{limiter_name, normalized_limiter_value} | acc]}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:halt, {:error, {limiter_name, reason}}}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> case do
|
||||||
|
{:ok, acc} -> {:ok, Enum.reverse(acc)}
|
||||||
|
{:error, _} = error -> error
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:error, {:rate_limit, "must be a keyword list"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_rate_limit(_), do: {:error, {:rate_limit, "must be a keyword list"}}
|
||||||
|
|
||||||
defp create(params) do
|
defp create(params) do
|
||||||
%ConfigDB{}
|
%ConfigDB{}
|
||||||
|> changeset(params)
|
|> changeset(params)
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,14 @@ defmodule Pleroma.Constants do
|
||||||
"generator",
|
"generator",
|
||||||
"rules",
|
"rules",
|
||||||
"language",
|
"language",
|
||||||
"voters"
|
"voters",
|
||||||
|
"assigned_account"
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
const(static_only_files,
|
const(static_only_files,
|
||||||
do:
|
do:
|
||||||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
|
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js schemas doc embed.js embed.css)
|
||||||
)
|
)
|
||||||
|
|
||||||
const(status_updatable_fields,
|
const(status_updatable_fields,
|
||||||
|
|
@ -133,15 +134,11 @@ defmodule Pleroma.Constants do
|
||||||
do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
|
do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
|
||||||
)
|
)
|
||||||
|
|
||||||
const(activity_json_canonical_mime_type,
|
# List of allowed chars in the path segment of a URI
|
||||||
do: "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
# unreserved, sub-delims, ":", "@" and "/" allowed as the separator in path
|
||||||
)
|
# https://datatracker.ietf.org/doc/html/rfc3986
|
||||||
|
const(uri_path_allowed_reserved_chars,
|
||||||
const(activity_json_mime_types,
|
do: ~c"!$&'()*+,;=/:@"
|
||||||
do: [
|
|
||||||
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
|
||||||
"application/activity+json"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const(upload_object_types, do: ["Document", "Image"])
|
const(upload_object_types, do: ["Document", "Image"])
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,27 @@ defmodule Pleroma.Docs.Generator do
|
||||||
|
|
||||||
@spec list_behaviour_implementations(behaviour :: module()) :: [module()]
|
@spec list_behaviour_implementations(behaviour :: module()) :: [module()]
|
||||||
def list_behaviour_implementations(behaviour) do
|
def list_behaviour_implementations(behaviour) do
|
||||||
:code.all_loaded()
|
# :code.all_loaded() no longer works on Elixir 1.19 and newer since modules are lazily loaded.
|
||||||
|> Enum.filter(fn {module, _} ->
|
# We can get a complete list of modules by interating over Application.loaded_applications/0,
|
||||||
|
# but that includes every module which would get loaded below, which is very slow.
|
||||||
|
# Hardcode the Pleroma application instead, which is good enough for now.
|
||||||
|
application_modules =
|
||||||
|
case :application.get_key(:pleroma, :modules) do
|
||||||
|
{:ok, modules} -> modules
|
||||||
|
:undefined -> []
|
||||||
|
end
|
||||||
|
|
||||||
|
# At compile time or in tests some modules will be outside of the Pleroma application.
|
||||||
|
loaded_modules = Enum.map(:code.all_loaded(), fn {module, _} -> module end)
|
||||||
|
|
||||||
|
(application_modules ++ loaded_modules)
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.filter(fn module ->
|
||||||
|
Code.ensure_loaded(module)
|
||||||
|
|
||||||
# This shouldn't be needed as all modules are expected to have module_info/1,
|
# This shouldn't be needed as all modules are expected to have module_info/1,
|
||||||
# but in test environments some transient modules `:elixir_compiler_XX`
|
# but in test environments some transient modules `:elixir_compiler_XX`
|
||||||
# are loaded for some reason (where XX is a random integer).
|
# are loaded for some reason (where XX is a random integer).
|
||||||
Code.ensure_loaded(module)
|
|
||||||
|
|
||||||
if function_exported?(module, :module_info, 1) do
|
if function_exported?(module, :module_info, 1) do
|
||||||
module.module_info(:attributes)
|
module.module_info(:attributes)
|
||||||
|> Keyword.get_values(:behaviour)
|
|> Keyword.get_values(:behaviour)
|
||||||
|
|
@ -26,7 +40,6 @@ defmodule Pleroma.Docs.Generator do
|
||||||
|> Enum.member?(behaviour)
|
|> Enum.member?(behaviour)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|> Enum.map(fn {module, _} -> module end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
||||||
71
lib/pleroma/ecto_type/config/rate_limit.ex
Normal file
71
lib/pleroma/ecto_type/config/rate_limit.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.EctoType.Config.RateLimit do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
@type t ::
|
||||||
|
nil
|
||||||
|
| {non_neg_integer(), non_neg_integer()}
|
||||||
|
| [{non_neg_integer(), non_neg_integer()}]
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def type, do: :term
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def cast(value) do
|
||||||
|
case cast_with_error(value) do
|
||||||
|
{:ok, normalized} -> {:ok, normalized}
|
||||||
|
{:error, _reason} -> :error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def load(value), do: cast(value)
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def dump(value), do: cast(value)
|
||||||
|
|
||||||
|
@spec cast_with_error(term()) :: {:ok, t()} | {:error, String.t()}
|
||||||
|
def cast_with_error(nil), do: {:ok, nil}
|
||||||
|
|
||||||
|
def cast_with_error({scale, limit}) do
|
||||||
|
with {:ok, scale} <- parse_integer(scale, "scale"),
|
||||||
|
{:ok, limit} <- parse_integer(limit, "limit"),
|
||||||
|
true <- scale >= 1 and limit >= 1 do
|
||||||
|
{:ok, {scale, limit}}
|
||||||
|
else
|
||||||
|
false -> {:error, "scale and limit must be >= 1"}
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_with_error([{_, _} = unauth, {_, _} = auth]) do
|
||||||
|
with {:ok, unauth} <- cast_with_error(unauth),
|
||||||
|
{:ok, auth} <- cast_with_error(auth) do
|
||||||
|
{:ok, [unauth, auth]}
|
||||||
|
else
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_with_error(_),
|
||||||
|
do:
|
||||||
|
{:error, "must be a {scale, limit} tuple, a [{scale, limit}, {scale, limit}] list, or nil"}
|
||||||
|
|
||||||
|
defp parse_integer(value, _label) when is_integer(value), do: {:ok, value}
|
||||||
|
|
||||||
|
defp parse_integer(value, label) when is_binary(value) do
|
||||||
|
value = String.trim(value)
|
||||||
|
|
||||||
|
case Integer.parse(value) do
|
||||||
|
{number, ""} -> {:ok, number}
|
||||||
|
_ -> {:error, "#{label} must be an integer"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_integer(_value, label), do: {:error, "#{label} must be an integer"}
|
||||||
|
end
|
||||||
|
|
@ -36,10 +36,11 @@ defmodule Pleroma.Emails.Mailer do
|
||||||
def deliver(email, config \\ [])
|
def deliver(email, config \\ [])
|
||||||
|
|
||||||
def deliver(email, config) do
|
def deliver(email, config) do
|
||||||
# temporary hackney fix until hackney max_connections bug is fixed
|
# Swoosh HTTP adapters use Hackney, which otherwise trusts its certifi bundle
|
||||||
# https://git.pleroma.social/pleroma/pleroma/-/issues/2101
|
|
||||||
email =
|
email =
|
||||||
Swoosh.Email.put_private(email, :hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|
Swoosh.Email.put_private(email, :hackney_options,
|
||||||
|
ssl_options: Pleroma.HTTP.TLS.ssl_options()
|
||||||
|
)
|
||||||
|
|
||||||
case enabled?() do
|
case enabled?() do
|
||||||
true -> Swoosh.Mailer.deliver(email, parse_config(config))
|
true -> Swoosh.Mailer.deliver(email, parse_config(config))
|
||||||
|
|
@ -67,6 +68,21 @@ defmodule Pleroma.Emails.Mailer do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_config(config) do
|
defp parse_config(config) do
|
||||||
Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config)
|
@otp_app
|
||||||
|
|> Swoosh.Mailer.parse_config(__MODULE__, @mailer_config, config)
|
||||||
|
|> put_mua_cacertfile()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
# Mua otherwise trusts the OS CA store. Keep a CA that the admin set.
|
||||||
|
def put_mua_cacertfile(config) do
|
||||||
|
ssl = Keyword.get(config, :ssl, [])
|
||||||
|
|
||||||
|
if config[:adapter] == Swoosh.Adapters.Mua and
|
||||||
|
not Keyword.has_key?(ssl, :cacertfile) and not Keyword.has_key?(ssl, :cacerts) do
|
||||||
|
Keyword.put(config, :ssl, [{:cacertfile, CAStore.file_path()} | ssl])
|
||||||
|
else
|
||||||
|
config
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -258,10 +258,16 @@ defmodule Pleroma.FollowingRelationship do
|
||||||
[_, user_actor: user_actor],
|
[_, user_actor: user_actor],
|
||||||
fragment(
|
fragment(
|
||||||
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
|
# "(actor's domain NOT in domain_blocks) OR (actor IS in followed AP IDs)"
|
||||||
|
#
|
||||||
|
# ARRAY(SELECT ...) makes the followed-ids lookup an InitPlan that is
|
||||||
|
# evaluated and costed once. As a plain subselect, the planner charges
|
||||||
|
# its startup cost on every rescan of the actor lookup, which inflates
|
||||||
|
# the cost of the index plan enough for it to lose to a plan that
|
||||||
|
# materialises every active user for every candidate row.
|
||||||
"""
|
"""
|
||||||
NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR
|
NOT (substring(? from '.*://([^/]*)') = ANY(?)) OR
|
||||||
? = ANY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
|
? = ANY(ARRAY(SELECT ap_id FROM users AS u INNER JOIN following_relationships AS fr
|
||||||
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?)
|
ON u.id = fr.following_id WHERE fr.follower_id = ? AND fr.state = ?))
|
||||||
""",
|
""",
|
||||||
user_actor.ap_id,
|
user_actor.ap_id,
|
||||||
^user.domain_blocks,
|
^user.domain_blocks,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,15 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Formatter do
|
defmodule Pleroma.Formatter do
|
||||||
|
require Logger
|
||||||
|
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
@safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
|
@safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
|
||||||
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
|
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
|
||||||
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
|
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
|
||||||
|
@markdown_void_elements ~w[area base br col command embed hr img input keygen link meta param source track wbr]
|
||||||
|
|
||||||
defp linkify_opts do
|
defp linkify_opts do
|
||||||
Pleroma.Config.get(Pleroma.Formatter) ++
|
Pleroma.Config.get(Pleroma.Formatter) ++
|
||||||
|
|
@ -124,7 +127,157 @@ defmodule Pleroma.Formatter do
|
||||||
end
|
end
|
||||||
|
|
||||||
def markdown_to_html(text) do
|
def markdown_to_html(text) do
|
||||||
Earmark.as_html!(text, %Earmark.Options{compact_output: true, smartypants: false})
|
markdown_to_html(text, %{})
|
||||||
|
end
|
||||||
|
|
||||||
|
def markdown_to_html(text, opts) do
|
||||||
|
breaks = Map.get(opts, :breaks, false)
|
||||||
|
|
||||||
|
{_status, ast, _messages} = EarmarkParser.as_ast(text, breaks: breaks, gfm: true)
|
||||||
|
|
||||||
|
ast
|
||||||
|
|> maybe_strip_email_autolinks(text, Map.get(opts, :strip_email_autolinks, false))
|
||||||
|
|> render_markdown_nodes(false)
|
||||||
|
|> IO.iodata_to_binary()
|
||||||
|
rescue
|
||||||
|
exception ->
|
||||||
|
Logger.warning("Markdown rendering failed in #{inspect(exception.__struct__)}")
|
||||||
|
markdown_fallback(text, Map.get(opts, :breaks, false))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp markdown_fallback(text, breaks) do
|
||||||
|
text = text |> escape_html() |> IO.iodata_to_binary() |> maybe_render_markdown_breaks(breaks)
|
||||||
|
"<p>" <> text <> "</p>"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_render_markdown_breaks(text, true), do: String.replace(text, ~r/\r?\n/, "<br>")
|
||||||
|
defp maybe_render_markdown_breaks(text, false), do: text
|
||||||
|
|
||||||
|
defp maybe_strip_email_autolinks(ast, _text, false), do: ast
|
||||||
|
|
||||||
|
defp maybe_strip_email_autolinks(ast, text, true) do
|
||||||
|
emails =
|
||||||
|
~r/<([^ >]+@[^ >]+)>/
|
||||||
|
|> Regex.scan(text, capture: :all_but_first)
|
||||||
|
|> Enum.map(fn [email] -> String.replace_prefix(email, "mailto:", "") end)
|
||||||
|
|> MapSet.new()
|
||||||
|
|
||||||
|
strip_email_autolinks(ast, emails)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_email_autolinks(nodes, emails) when is_list(nodes) do
|
||||||
|
Enum.map(nodes, &strip_email_autolinks(&1, emails))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_email_autolinks({"a", attrs, [email], _meta} = node, emails)
|
||||||
|
when is_binary(email) do
|
||||||
|
if List.keyfind(attrs, "href", 0) == {"href", "mailto:" <> email} and
|
||||||
|
MapSet.member?(emails, email) do
|
||||||
|
email
|
||||||
|
else
|
||||||
|
node
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_email_autolinks({tag, attrs, children, meta}, emails) do
|
||||||
|
{tag, attrs, strip_email_autolinks(children, emails), meta}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_email_autolinks(text, _emails) when is_binary(text), do: text
|
||||||
|
|
||||||
|
defp render_markdown_nodes(nodes, verbatim) when is_list(nodes) do
|
||||||
|
Enum.map(nodes, &render_markdown_node(&1, verbatim))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_node(text, true) when is_binary(text),
|
||||||
|
do: strip_dangerous_markdown_html(text)
|
||||||
|
|
||||||
|
defp render_markdown_node(text, false) when is_binary(text) do
|
||||||
|
text
|
||||||
|
|> strip_dangerous_markdown_html()
|
||||||
|
|> escape_markdown_html()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_node({:comment, _attrs, _children, _meta}, _verbatim), do: []
|
||||||
|
|
||||||
|
defp render_markdown_node({tag, attrs, children, meta}, verbatim) do
|
||||||
|
normalized_tag = String.downcase(tag)
|
||||||
|
|
||||||
|
if normalized_tag in ["script", "style"] do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
render_markdown_element(tag, normalized_tag, attrs, children, meta, verbatim)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_element("code", _normalized_tag, attrs, children, _meta, _verbatim) do
|
||||||
|
["<code", render_markdown_attrs(attrs), ">", render_markdown_code(children), "</code>"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_element(tag, "hr", attrs, _children, _meta, _verbatim) do
|
||||||
|
attrs = Enum.reject(attrs, &match?({"class", class} when class in ~w[thin medium thick], &1))
|
||||||
|
["<", tag, render_markdown_attrs(attrs), "/>"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_element(tag, normalized_tag, attrs, _children, _meta, _verbatim)
|
||||||
|
when normalized_tag in @markdown_void_elements do
|
||||||
|
["<", tag, render_markdown_attrs(attrs), ">"]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_element(tag, _normalized_tag, attrs, children, meta, verbatim) do
|
||||||
|
children_verbatim = verbatim or Map.get(meta, :verbatim, false)
|
||||||
|
|
||||||
|
[
|
||||||
|
"<",
|
||||||
|
tag,
|
||||||
|
render_markdown_attrs(attrs),
|
||||||
|
">",
|
||||||
|
render_markdown_nodes(children, children_verbatim),
|
||||||
|
"</",
|
||||||
|
tag,
|
||||||
|
">"
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_attrs(attrs) do
|
||||||
|
Enum.map(attrs, fn {name, value} ->
|
||||||
|
value = value |> to_string() |> HtmlEntities.decode() |> sanitize_markdown_url(name)
|
||||||
|
[" ", to_string(name), "=\"", escape_html(value), "\""]
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_markdown_code(children) do
|
||||||
|
Enum.map(children, &escape_html/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sanitize_markdown_url(value, name) do
|
||||||
|
if String.downcase(to_string(name)) in ["href", "src"] do
|
||||||
|
# Percent-encode instead of cutting, so the URL stays complete but can
|
||||||
|
# never close the attribute or open a tag.
|
||||||
|
String.replace(value, ~r/["<>]/, fn
|
||||||
|
"\"" -> "%22"
|
||||||
|
"<" -> "%3C"
|
||||||
|
">" -> "%3E"
|
||||||
|
end)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp strip_dangerous_markdown_html(text) do
|
||||||
|
text
|
||||||
|
|> String.replace(~r/<(script|style)\b[^>]*>.*?<\/\1>/is, "")
|
||||||
|
|> String.replace(~r/<(script|style)\b[^>]*>.*\z/is, "")
|
||||||
|
|> String.replace(~r/<\/(script|style)\s*>/i, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp escape_markdown_html(text) do
|
||||||
|
text |> HtmlEntities.decode() |> escape_html()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp escape_html(text) do
|
||||||
|
{:safe, escaped} = Phoenix.HTML.html_escape(text)
|
||||||
|
escaped
|
||||||
end
|
end
|
||||||
|
|
||||||
def html_escape({text, mentions, hashtags}, type) do
|
def html_escape({text, mentions, hashtags}, type) do
|
||||||
|
|
@ -135,6 +288,10 @@ defmodule Pleroma.Formatter do
|
||||||
HTML.filter_tags(text)
|
HTML.filter_tags(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def html_escape(text, "text/x.misskeymarkdown") do
|
||||||
|
HTML.filter_tags(text)
|
||||||
|
end
|
||||||
|
|
||||||
def html_escape(text, "text/plain") do
|
def html_escape(text, "text/plain") do
|
||||||
Regex.split(@link_regex, text, include_captures: true)
|
Regex.split(@link_regex, text, include_captures: true)
|
||||||
|> Enum.map_every(2, fn chunk ->
|
|> Enum.map_every(2, fn chunk ->
|
||||||
|
|
|
||||||
|
|
@ -75,8 +75,8 @@ defmodule Pleroma.Frontend do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp download_build(frontend_info, dest) do
|
defp download_build(frontend_info, dest) do
|
||||||
Logger.info("Downloading pre-built bundle for #{frontend_info["name"]}")
|
|
||||||
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
||||||
|
Logger.info("Downloading pre-built bundle for #{frontend_info["name"]} from #{url}")
|
||||||
|
|
||||||
with {:ok, %{status: 200, body: zip_body}} <-
|
with {:ok, %{status: 200, body: zip_body}} <-
|
||||||
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
Pleroma.HTTP.get(url, [], pool: :media, recv_timeout: 120_000) do
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,13 @@ defmodule Pleroma.Gopher.Server do
|
||||||
|
|
||||||
def init([ip, port]) do
|
def init([ip, port]) do
|
||||||
Logger.info("Starting gopher server on #{port}")
|
Logger.info("Starting gopher server on #{port}")
|
||||||
|
Process.flag(:trap_exit, true)
|
||||||
|
|
||||||
|
listener = :gopher
|
||||||
|
|
||||||
{:ok, _pid} =
|
{:ok, _pid} =
|
||||||
:ranch.start_listener(
|
:ranch.start_listener(
|
||||||
:gopher,
|
listener,
|
||||||
:ranch_tcp,
|
:ranch_tcp,
|
||||||
%{
|
%{
|
||||||
num_acceptors: 100,
|
num_acceptors: 100,
|
||||||
|
|
@ -35,7 +38,11 @@ defmodule Pleroma.Gopher.Server do
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, %{ip: ip, port: port}}
|
{:ok, %{ip: ip, port: port, listener: listener}}
|
||||||
|
end
|
||||||
|
|
||||||
|
def terminate(_reason, state) do
|
||||||
|
:ranch.stop_listener(state.listener)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@ defmodule Pleroma.Gun do
|
||||||
@callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
|
@callback open(charlist(), pos_integer(), map()) :: {:ok, pid()}
|
||||||
@callback info(pid()) :: map()
|
@callback info(pid()) :: map()
|
||||||
@callback close(pid()) :: :ok
|
@callback close(pid()) :: :ok
|
||||||
|
@callback cancel(pid(), reference() | [reference()]) :: :ok
|
||||||
@callback await_up(pid, pos_integer()) :: {:ok, atom()} | {:error, atom()}
|
@callback await_up(pid, pos_integer()) :: {:ok, atom()} | {:error, atom()}
|
||||||
@callback connect(pid(), map()) :: reference()
|
@callback connect(pid(), map()) :: reference()
|
||||||
@callback await(pid(), reference()) :: {:response, :fin, 200, []}
|
@callback await(pid(), reference()) :: tuple()
|
||||||
|
@callback await_tunnel_up(pid(), reference() | :undefined, timeout()) ::
|
||||||
|
{:ok, atom()} | {:error, term()}
|
||||||
@callback set_owner(pid(), pid()) :: :ok
|
@callback set_owner(pid(), pid()) :: :ok
|
||||||
|
|
||||||
defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
|
defp api, do: Pleroma.Config.get([Pleroma.Gun], Pleroma.Gun.API)
|
||||||
|
|
@ -19,11 +22,15 @@ defmodule Pleroma.Gun do
|
||||||
|
|
||||||
def close(pid), do: api().close(pid)
|
def close(pid), do: api().close(pid)
|
||||||
|
|
||||||
|
def cancel(pid, stream), do: api().cancel(pid, stream)
|
||||||
|
|
||||||
def await_up(pid, timeout \\ 5_000), do: api().await_up(pid, timeout)
|
def await_up(pid, timeout \\ 5_000), do: api().await_up(pid, timeout)
|
||||||
|
|
||||||
def connect(pid, opts), do: api().connect(pid, opts)
|
def connect(pid, opts), do: api().connect(pid, opts)
|
||||||
|
|
||||||
def await(pid, ref), do: api().await(pid, ref)
|
def await(pid, ref), do: api().await(pid, ref)
|
||||||
|
|
||||||
|
def await_tunnel_up(pid, ref, timeout), do: api().await_tunnel_up(pid, ref, timeout)
|
||||||
|
|
||||||
def set_owner(pid, owner), do: api().set_owner(pid, owner)
|
def set_owner(pid, owner), do: api().set_owner(pid, owner)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ defmodule Pleroma.Gun.API do
|
||||||
@impl Gun
|
@impl Gun
|
||||||
defdelegate close(pid), to: :gun
|
defdelegate close(pid), to: :gun
|
||||||
|
|
||||||
|
@impl Gun
|
||||||
|
defdelegate cancel(pid, stream), to: :gun
|
||||||
|
|
||||||
@impl Gun
|
@impl Gun
|
||||||
defdelegate await_up(pid, timeout \\ 5_000), to: :gun
|
defdelegate await_up(pid, timeout \\ 5_000), to: :gun
|
||||||
|
|
||||||
|
|
@ -41,6 +44,28 @@ defmodule Pleroma.Gun.API do
|
||||||
@impl Gun
|
@impl Gun
|
||||||
defdelegate await(pid, ref), to: :gun
|
defdelegate await(pid, ref), to: :gun
|
||||||
|
|
||||||
|
@impl Gun
|
||||||
|
def await_tunnel_up(pid, stream_ref, timeout) do
|
||||||
|
monitor_ref = Process.monitor(pid)
|
||||||
|
|
||||||
|
result =
|
||||||
|
receive do
|
||||||
|
{:gun_tunnel_up, ^pid, ^stream_ref, protocol} ->
|
||||||
|
{:ok, protocol}
|
||||||
|
|
||||||
|
{:gun_down, ^pid, _protocol, reason, _killed_streams} ->
|
||||||
|
{:error, {:down, reason}}
|
||||||
|
|
||||||
|
{:DOWN, ^monitor_ref, :process, ^pid, reason} ->
|
||||||
|
{:error, {:down, reason}}
|
||||||
|
after
|
||||||
|
timeout -> {:error, :timeout}
|
||||||
|
end
|
||||||
|
|
||||||
|
Process.demonitor(monitor_ref, [:flush])
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
@impl Gun
|
@impl Gun
|
||||||
defdelegate set_owner(pid, owner), to: :gun
|
defdelegate set_owner(pid, owner), to: :gun
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,34 @@ defmodule Pleroma.Gun.Conn do
|
||||||
Map.put(opts, :tls_opts, tls_opts)
|
Map.put(opts, :tls_opts, tls_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_open(%URI{scheme: "http"} = uri, %{proxy: {proxy_host, proxy_port}} = opts) do
|
||||||
|
with open_opts <- opts |> proxy_open_opts() |> Map.put(:protocols, [:http]),
|
||||||
|
{:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
|
||||||
|
{:ok, protocol} <- await_up(conn, opts[:connect_timeout]) do
|
||||||
|
{:ok, conn, protocol, :forward_proxy}
|
||||||
|
else
|
||||||
|
error ->
|
||||||
|
Logger.warning(
|
||||||
|
"Opening forward proxy connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
|
defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
|
||||||
connect_opts =
|
connect_opts =
|
||||||
uri
|
uri
|
||||||
|> destination_opts()
|
|> destination_opts()
|
||||||
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
|
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
|
||||||
|
|> add_proxy_auth(opts)
|
||||||
|
|
||||||
with open_opts <- Map.delete(opts, :tls_opts),
|
with open_opts <- proxy_open_opts(opts),
|
||||||
{:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
|
{:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
|
||||||
{:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]),
|
{:ok, _proxy_protocol} <- await_up(conn, opts[:connect_timeout]) do
|
||||||
stream <- Gun.connect(conn, connect_opts),
|
conn
|
||||||
{:response, :fin, 200, _} <- Gun.await(conn, stream) do
|
|> Gun.connect(connect_opts)
|
||||||
{:ok, conn, protocol}
|
|> await_proxy_connect(conn, opts[:connect_timeout])
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
Logger.warning(
|
Logger.warning(
|
||||||
|
|
@ -64,30 +80,29 @@ defmodule Pleroma.Gun.Conn do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
|
defp do_open(_uri, %{proxy: {:socks4, _proxy_host, _proxy_port}}) do
|
||||||
version =
|
{:error, :socks4_unsupported}
|
||||||
proxy_type
|
|
||||||
|> to_string()
|
|
||||||
|> String.last()
|
|
||||||
|> case do
|
|
||||||
"4" -> 4
|
|
||||||
_ -> 5
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts)
|
||||||
|
when proxy_type in [:socks, :socks5] do
|
||||||
socks_opts =
|
socks_opts =
|
||||||
uri
|
uri
|
||||||
|> destination_opts()
|
|> destination_opts()
|
||||||
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
|
|> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
|
||||||
|> Map.put(:version, version)
|
|> Map.put(:version, 5)
|
||||||
|
|> add_socks_proxy_auth(opts)
|
||||||
|
|
||||||
opts =
|
open_opts =
|
||||||
opts
|
opts
|
||||||
|
|> proxy_open_opts()
|
||||||
|> Map.put(:protocols, [:socks])
|
|> Map.put(:protocols, [:socks])
|
||||||
|> Map.put(:socks_opts, socks_opts)
|
|> Map.put(:socks_opts, socks_opts)
|
||||||
|
|
||||||
with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
|
with {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
|
||||||
{:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
|
{:ok, :socks} <- await_up(conn, opts[:connect_timeout]),
|
||||||
{:ok, conn, protocol}
|
{:ok, protocol} <- await_tunnel_up(conn, :undefined, opts[:connect_timeout]) do
|
||||||
|
{:ok, conn, protocol, nil}
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
Logger.warning(
|
Logger.warning(
|
||||||
|
|
@ -100,10 +115,11 @@ defmodule Pleroma.Gun.Conn do
|
||||||
|
|
||||||
defp do_open(%URI{host: host, port: port} = uri, opts) do
|
defp do_open(%URI{host: host, port: port} = uri, opts) do
|
||||||
host = Pleroma.HTTP.AdapterHelper.parse_host(host)
|
host = Pleroma.HTTP.AdapterHelper.parse_host(host)
|
||||||
|
opts = Map.put(opts, :transport, transport(uri.scheme))
|
||||||
|
|
||||||
with {:ok, conn} <- Gun.open(host, port, opts),
|
with {:ok, conn} <- Gun.open(host, port, opts),
|
||||||
{:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
|
{:ok, protocol} <- await_up(conn, opts[:connect_timeout]) do
|
||||||
{:ok, conn, protocol}
|
{:ok, conn, protocol, nil}
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
Logger.warning(
|
Logger.warning(
|
||||||
|
|
@ -120,11 +136,90 @@ defmodule Pleroma.Gun.Conn do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_http2_opts(opts, "https", tls_opts) do
|
defp add_http2_opts(opts, "https", tls_opts) do
|
||||||
Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
|
Map.merge(opts, %{protocols: [:http2, :http], transport: :tls, tls_opts: tls_opts})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_http2_opts(opts, _, _), do: opts
|
defp add_http2_opts(opts, _, _), do: opts
|
||||||
|
|
||||||
|
defp add_proxy_auth(connect_opts, %{proxy_auth: {username, password}})
|
||||||
|
when is_binary(username) and is_binary(password) do
|
||||||
|
Map.merge(connect_opts, %{username: username, password: password})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_proxy_auth(connect_opts, _), do: connect_opts
|
||||||
|
|
||||||
|
defp add_socks_proxy_auth(socks_opts, %{proxy_auth: {username, password}})
|
||||||
|
when is_binary(username) and is_binary(password) do
|
||||||
|
Map.put(socks_opts, :auth, [{:username_password, username, password}])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_socks_proxy_auth(socks_opts, _), do: socks_opts
|
||||||
|
|
||||||
|
defp proxy_open_opts(opts) do
|
||||||
|
proxy_tls_opts = Map.get(opts, :proxy_tls_opts, [])
|
||||||
|
|
||||||
|
opts =
|
||||||
|
opts
|
||||||
|
|> Map.drop([:proxy, :proxy_auth, :proxy_tls_opts, :tls_opts])
|
||||||
|
|> Map.put_new(:transport, :tcp)
|
||||||
|
|
||||||
|
if opts.transport == :tls do
|
||||||
|
Map.put(opts, :tls_opts, proxy_tls_opts)
|
||||||
|
else
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp await_up(conn, timeout) do
|
||||||
|
case Gun.await_up(conn, timeout) do
|
||||||
|
{:ok, protocol} ->
|
||||||
|
{:ok, protocol}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Gun.close(conn)
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp await_proxy_connect(stream, conn, timeout) do
|
||||||
|
case Gun.await(conn, stream) do
|
||||||
|
{:response, :fin, 200, _headers} ->
|
||||||
|
case await_tunnel_up(conn, stream, timeout) do
|
||||||
|
{:ok, protocol} -> {:ok, conn, protocol, stream}
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
|
||||||
|
{:response, _fin, 407, _headers} ->
|
||||||
|
close_with_error(conn, :proxy_auth_failed)
|
||||||
|
|
||||||
|
{:response, _fin, status, _headers} when is_integer(status) ->
|
||||||
|
close_with_error(conn, {:proxy_connect_failed, status})
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Gun.close(conn)
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp close_with_error(conn, reason) do
|
||||||
|
Gun.close(conn)
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp transport("https"), do: :tls
|
||||||
|
defp transport(_), do: :tcp
|
||||||
|
|
||||||
|
defp await_tunnel_up(conn, stream_ref, timeout) do
|
||||||
|
case Gun.await_tunnel_up(conn, stream_ref, timeout) do
|
||||||
|
{:ok, protocol} ->
|
||||||
|
{:ok, protocol}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Gun.close(conn)
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
|
def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
|
||||||
"#{scheme}://#{host}#{path}"
|
"#{scheme}://#{host}#{path}"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,53 +4,132 @@
|
||||||
|
|
||||||
defmodule Pleroma.Gun.ConnectionPool do
|
defmodule Pleroma.Gun.ConnectionPool do
|
||||||
@registry __MODULE__
|
@registry __MODULE__
|
||||||
|
@locks Module.concat(__MODULE__, Locks)
|
||||||
|
|
||||||
alias Pleroma.Gun.ConnectionPool.WorkerSupervisor
|
alias Pleroma.Gun.ConnectionPool.WorkerSupervisor
|
||||||
|
|
||||||
|
@connection_option_keys [
|
||||||
|
:http_opts,
|
||||||
|
:http2_opts,
|
||||||
|
:protocols,
|
||||||
|
:proxy,
|
||||||
|
:proxy_auth,
|
||||||
|
:proxy_tls_opts,
|
||||||
|
:retry,
|
||||||
|
:retry_timeout,
|
||||||
|
:socks_opts,
|
||||||
|
:tcp_opts,
|
||||||
|
:tls_opts,
|
||||||
|
:transport,
|
||||||
|
:ws_opts
|
||||||
|
]
|
||||||
|
|
||||||
def children do
|
def children do
|
||||||
[
|
[
|
||||||
{Registry, keys: :unique, name: @registry},
|
{Registry, keys: :duplicate, name: @registry},
|
||||||
|
{Registry, keys: :unique, name: @locks},
|
||||||
Pleroma.Gun.ConnectionPool.WorkerSupervisor
|
Pleroma.Gun.ConnectionPool.WorkerSupervisor
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_conn(URI.t(), keyword()) :: {:ok, pid()} | {:error, term()}
|
@spec get_conn(URI.t(), keyword()) :: {:ok, pid()} | {:error, term()}
|
||||||
def get_conn(uri, opts) do
|
def get_conn(uri, opts) do
|
||||||
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
|
key = connection_key(uri, opts)
|
||||||
|
|
||||||
case Registry.lookup(@registry, key) do
|
case checkout_existing(Registry.lookup(@registry, key)) do
|
||||||
# The key has already been registered, but connection is not up yet
|
{:ok, gun_pid} ->
|
||||||
[{worker_pid, nil}] ->
|
|
||||||
get_gun_pid_from_worker(worker_pid, true)
|
|
||||||
|
|
||||||
[{worker_pid, {gun_pid, _used_by, _crf, _last_reference}}] ->
|
|
||||||
GenServer.call(worker_pid, :add_client)
|
|
||||||
{:ok, gun_pid}
|
{:ok, gun_pid}
|
||||||
|
|
||||||
[] ->
|
:not_available ->
|
||||||
# :gun.set_owner fails in :connected state for whatevever reason,
|
provision_or_wait(key, uri, opts)
|
||||||
# so we open the connection in the process directly and send it's pid back
|
end
|
||||||
# We trust gun to handle timeouts by itself
|
end
|
||||||
|
|
||||||
|
defp checkout_existing([]), do: :not_available
|
||||||
|
|
||||||
|
defp checkout_existing([{_worker_pid, nil} | workers]), do: checkout_existing(workers)
|
||||||
|
|
||||||
|
defp checkout_existing([{worker_pid, _value} | workers]) do
|
||||||
|
case checkout_worker(worker_pid) do
|
||||||
|
{:ok, gun_pid} -> {:ok, gun_pid}
|
||||||
|
_ -> checkout_existing(workers)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp provision_or_wait(key, uri, opts) do
|
||||||
|
result = with_route_lock(key, fn -> provision(key, uri, opts) end)
|
||||||
|
|
||||||
|
case result do
|
||||||
|
{:ok, gun_pid} ->
|
||||||
|
{:ok, gun_pid}
|
||||||
|
|
||||||
|
{:new, worker_pid} ->
|
||||||
|
get_gun_pid_from_worker(worker_pid)
|
||||||
|
|
||||||
|
{:wait, worker_pid} ->
|
||||||
|
case checkout_worker(worker_pid, :infinity) do
|
||||||
|
{:ok, gun_pid} -> {:ok, gun_pid}
|
||||||
|
:busy -> get_conn(uri, opts)
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp provision(key, uri, opts) do
|
||||||
|
workers = Registry.lookup(@registry, key)
|
||||||
|
|
||||||
|
case checkout_existing(workers) do
|
||||||
|
{:ok, gun_pid} ->
|
||||||
|
{:ok, gun_pid}
|
||||||
|
|
||||||
|
:not_available ->
|
||||||
|
case Enum.find(workers, fn {_worker_pid, value} -> is_nil(value) end) do
|
||||||
|
{worker_pid, nil} -> {:wait, worker_pid}
|
||||||
|
nil -> start_worker(key, uri, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp with_route_lock(key, function) do
|
||||||
|
case Registry.register(@locks, key, nil) do
|
||||||
|
{:ok, _} ->
|
||||||
|
try do
|
||||||
|
function.()
|
||||||
|
after
|
||||||
|
Registry.unregister(@locks, key)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, {:already_registered, _owner}} ->
|
||||||
|
Process.sleep(1)
|
||||||
|
with_route_lock(key, function)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp start_worker(key, uri, opts) do
|
||||||
|
# :gun.set_owner fails in :connected state, so the worker opens the connection itself.
|
||||||
case WorkerSupervisor.start_worker([key, uri, opts, self()]) do
|
case WorkerSupervisor.start_worker([key, uri, opts, self()]) do
|
||||||
{:ok, worker_pid} ->
|
{:ok, worker_pid} -> {:new, worker_pid}
|
||||||
get_gun_pid_from_worker(worker_pid, false)
|
error -> error
|
||||||
|
|
||||||
{:error, {:already_started, worker_pid}} ->
|
|
||||||
get_gun_pid_from_worker(worker_pid, true)
|
|
||||||
|
|
||||||
err ->
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_gun_pid_from_worker(worker_pid, register) do
|
defp checkout_worker(worker_pid, timeout \\ 5_000) do
|
||||||
|
try do
|
||||||
|
GenServer.call(worker_pid, :checkout, timeout)
|
||||||
|
catch
|
||||||
|
:exit, reason -> {:error, {:connection_worker_exit, reason}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_gun_pid_from_worker(worker_pid) do
|
||||||
# GenServer.call will block the process for timeout length if
|
# GenServer.call will block the process for timeout length if
|
||||||
# the server crashes on startup (which will happen if gun fails to connect)
|
# the server crashes on startup (which will happen if gun fails to connect)
|
||||||
# so instead we use cast + monitor
|
# so instead we use cast + monitor
|
||||||
|
|
||||||
ref = Process.monitor(worker_pid)
|
ref = Process.monitor(worker_pid)
|
||||||
if register, do: GenServer.cast(worker_pid, {:add_client, self()})
|
|
||||||
|
|
||||||
receive do
|
receive do
|
||||||
{:conn_pid, pid} ->
|
{:conn_pid, pid} ->
|
||||||
|
|
@ -68,19 +147,115 @@ defmodule Pleroma.Gun.ConnectionPool do
|
||||||
|
|
||||||
@spec release_conn(pid()) :: :ok
|
@spec release_conn(pid()) :: :ok
|
||||||
def release_conn(conn_pid) do
|
def release_conn(conn_pid) do
|
||||||
# :ets.fun2ms(fn {_, {worker_pid, {gun_pid, _, _, _}}} when gun_pid == conn_pid ->
|
# :ets.fun2ms(fn {_, {worker_pid, {gun_pid, _tunnel}}} when gun_pid == conn_pid ->
|
||||||
# worker_pid end)
|
# worker_pid end)
|
||||||
query_result =
|
query_result =
|
||||||
Registry.select(@registry, [
|
Registry.select(@registry, [
|
||||||
{{:_, :"$1", {:"$2", :_, :_, :_}}, [{:==, :"$2", conn_pid}], [:"$1"]}
|
{{:_, :"$1", {:"$2", :_}}, [{:==, :"$2", conn_pid}], [:"$1"]}
|
||||||
])
|
])
|
||||||
|
|
||||||
case query_result do
|
case query_result do
|
||||||
[worker_pid] ->
|
[worker_pid] ->
|
||||||
|
try do
|
||||||
GenServer.call(worker_pid, :remove_client)
|
GenServer.call(worker_pid, :remove_client)
|
||||||
|
catch
|
||||||
|
:exit, _ -> :ok
|
||||||
|
end
|
||||||
|
|
||||||
[] ->
|
[] ->
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec discard_conn(pid()) :: :ok
|
||||||
|
def discard_conn(conn_pid) do
|
||||||
|
case worker_for_conn(conn_pid) do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
worker_pid ->
|
||||||
|
DynamicSupervisor.terminate_child(WorkerSupervisor, worker_pid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec cancel_stream(pid(), reference() | [reference()]) :: :ok
|
||||||
|
def cancel_stream(conn_pid, stream) do
|
||||||
|
case worker_for_conn(conn_pid) do
|
||||||
|
nil ->
|
||||||
|
Pleroma.Gun.cancel(conn_pid, stream)
|
||||||
|
|
||||||
|
worker_pid ->
|
||||||
|
try do
|
||||||
|
GenServer.call(worker_pid, {:cancel_stream, stream})
|
||||||
|
catch
|
||||||
|
:exit, _ -> :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec register_stream(pid(), reference() | [reference()]) :: :ok
|
||||||
|
def register_stream(conn_pid, stream) do
|
||||||
|
call_worker(conn_pid, {:register_stream, stream})
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec finish_stream(pid(), reference() | [reference()]) :: :ok
|
||||||
|
def finish_stream(conn_pid, stream) do
|
||||||
|
call_worker(conn_pid, {:finish_stream, stream})
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec release_stream(pid(), reference() | [reference()]) :: :ok
|
||||||
|
def release_stream(conn_pid, stream) do
|
||||||
|
call_worker(conn_pid, {:release_stream, stream})
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec tunnel_ref(pid()) :: reference() | :forward_proxy | nil
|
||||||
|
def tunnel_ref(conn_pid) do
|
||||||
|
case Registry.select(@registry, [
|
||||||
|
{{:_, :_, {:"$1", :"$2"}}, [{:==, :"$1", conn_pid}], [:"$2"]}
|
||||||
|
]) do
|
||||||
|
[tunnel] -> tunnel
|
||||||
|
[] -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp worker_for_conn(conn_pid) do
|
||||||
|
case Registry.select(@registry, [
|
||||||
|
{{:_, :"$1", {:"$2", :_}}, [{:==, :"$2", conn_pid}], [:"$1"]}
|
||||||
|
]) do
|
||||||
|
[worker_pid] -> worker_pid
|
||||||
|
[] -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp call_worker(conn_pid, message) do
|
||||||
|
case worker_for_conn(conn_pid) do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
worker_pid ->
|
||||||
|
try do
|
||||||
|
GenServer.call(worker_pid, message)
|
||||||
|
catch
|
||||||
|
:exit, _ -> :ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp connection_key(uri, opts) do
|
||||||
|
origin = {
|
||||||
|
String.downcase(uri.scheme),
|
||||||
|
String.downcase(uri.host),
|
||||||
|
uri.port
|
||||||
|
}
|
||||||
|
|
||||||
|
profile =
|
||||||
|
opts
|
||||||
|
|> Map.new()
|
||||||
|
|> Map.take(@connection_option_keys)
|
||||||
|
|> :erlang.term_to_binary()
|
||||||
|
|> then(&:crypto.hash(:sha256, &1))
|
||||||
|
|
||||||
|
fingerprint = Base.encode16(profile, case: :lower)
|
||||||
|
Enum.join(Tuple.to_list(origin) ++ [fingerprint], ":")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|
||||||
|
|
||||||
def start_monitor do
|
def start_monitor do
|
||||||
pid =
|
pid =
|
||||||
case GenServer.start(__MODULE__, [], name: {:via, Registry, {registry(), "reclaimer"}}) do
|
case GenServer.start(__MODULE__, [], name: __MODULE__) do
|
||||||
{:ok, pid} ->
|
{:ok, pid} ->
|
||||||
pid
|
pid
|
||||||
|
|
||||||
{:error, {:already_registered, pid}} ->
|
{:error, {:already_started, pid}} ->
|
||||||
pid
|
pid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -41,17 +41,16 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|
||||||
reclaim_max: reclaim_max
|
reclaim_max: reclaim_max
|
||||||
})
|
})
|
||||||
|
|
||||||
# :ets.fun2ms(
|
workers =
|
||||||
# fn {_, {worker_pid, {_, used_by, crf, last_reference}}} when used_by == [] ->
|
|
||||||
# {worker_pid, crf, last_reference} end)
|
|
||||||
unused_conns =
|
|
||||||
Registry.select(
|
Registry.select(
|
||||||
registry(),
|
registry(),
|
||||||
[
|
[
|
||||||
{{:_, :"$1", {:_, :"$2", :"$3", :"$4"}}, [{:==, :"$2", []}], [{{:"$1", :"$3", :"$4"}}]}
|
{{:_, :"$1", {:_, :_}}, [], [:"$1"]}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unused_conns = Enum.flat_map(workers, &reclaim_info/1)
|
||||||
|
|
||||||
case unused_conns do
|
case unused_conns do
|
||||||
[] ->
|
[] ->
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
|
|
@ -65,25 +64,43 @@ defmodule Pleroma.Gun.ConnectionPool.Reclaimer do
|
||||||
{:stop, :no_unused_conns, nil}
|
{:stop, :no_unused_conns, nil}
|
||||||
|
|
||||||
unused_conns ->
|
unused_conns ->
|
||||||
reclaimed =
|
reclaimed_count =
|
||||||
unused_conns
|
unused_conns
|
||||||
|> Enum.sort(fn {_pid1, crf1, last_reference1}, {_pid2, crf2, last_reference2} ->
|
|> Enum.sort_by(fn {_pid, crf, last_reference} -> {crf, last_reference} end)
|
||||||
crf1 <= crf2 and last_reference1 <= last_reference2
|
|> Enum.reduce_while(0, fn
|
||||||
end)
|
_worker, count when count == reclaim_max ->
|
||||||
|> Enum.take(reclaim_max)
|
{:halt, count}
|
||||||
|
|
||||||
reclaimed
|
{worker_pid, _crf, _last_reference}, count ->
|
||||||
|> Enum.each(fn {pid, _, _} ->
|
if reclaim(worker_pid), do: {:cont, count + 1}, else: {:cont, count}
|
||||||
DynamicSupervisor.terminate_child(Pleroma.Gun.ConnectionPool.WorkerSupervisor, pid)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
[:pleroma, :connection_pool, :reclaim, :stop],
|
[:pleroma, :connection_pool, :reclaim, :stop],
|
||||||
%{reclaimed_count: Enum.count(reclaimed)},
|
%{reclaimed_count: reclaimed_count},
|
||||||
%{max_connections: max_connections}
|
%{max_connections: max_connections}
|
||||||
)
|
)
|
||||||
|
|
||||||
{:stop, :normal, nil}
|
{:stop, :normal, nil}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp reclaim_info(worker_pid) do
|
||||||
|
try do
|
||||||
|
case GenServer.call(worker_pid, :reclaim_info) do
|
||||||
|
{:idle, crf, last_reference} -> [{worker_pid, crf, last_reference}]
|
||||||
|
:in_use -> []
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
:exit, _ -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp reclaim(worker_pid) do
|
||||||
|
try do
|
||||||
|
GenServer.call(worker_pid, :reclaim) == :reclaimed
|
||||||
|
catch
|
||||||
|
:exit, _ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,25 +8,22 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
|
||||||
|
|
||||||
defp registry, do: Pleroma.Gun.ConnectionPool
|
defp registry, do: Pleroma.Gun.ConnectionPool
|
||||||
|
|
||||||
def start_link([key | _] = opts) do
|
def start_link(opts), do: GenServer.start_link(__MODULE__, opts)
|
||||||
GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {registry(), key}})
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init([_key, _uri, _opts, _client_pid] = opts) do
|
def init([key, _uri, _opts, _client_pid] = opts) do
|
||||||
|
{:ok, _} = Registry.register(registry(), key, nil)
|
||||||
{:ok, nil, {:continue, {:connect, opts}}}
|
{:ok, nil, {:continue, {:connect, opts}}}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
|
def handle_continue({:connect, [key, uri, opts, client_pid]}, _) do
|
||||||
with {:ok, conn_pid, protocol} <- Gun.Conn.open(uri, opts),
|
with {:ok, conn_pid, protocol, tunnel} <- Gun.Conn.open(uri, opts),
|
||||||
Process.link(conn_pid) do
|
Process.link(conn_pid) do
|
||||||
time = :erlang.monotonic_time(:millisecond)
|
time = :erlang.monotonic_time(:millisecond)
|
||||||
|
|
||||||
{_, _} =
|
Registry.unregister(registry(), key)
|
||||||
Registry.update_value(registry(), key, fn _ ->
|
{:ok, _} = Registry.register(registry(), key, {conn_pid, tunnel})
|
||||||
{conn_pid, [client_pid], 1, time}
|
|
||||||
end)
|
|
||||||
|
|
||||||
send(client_pid, {:conn_pid, conn_pid})
|
send(client_pid, {:conn_pid, conn_pid})
|
||||||
|
|
||||||
|
|
@ -34,8 +31,14 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
|
||||||
%{
|
%{
|
||||||
key: key,
|
key: key,
|
||||||
timer: nil,
|
timer: nil,
|
||||||
client_monitors: %{client_pid => Process.monitor(client_pid)},
|
clients: %{
|
||||||
protocol: protocol
|
client_pid => %{count: 1, monitor: Process.monitor(client_pid), streams: MapSet.new()}
|
||||||
|
},
|
||||||
|
conn_pid: conn_pid,
|
||||||
|
protocol: protocol,
|
||||||
|
proxied: Keyword.has_key?(opts, :proxy),
|
||||||
|
crf: 1,
|
||||||
|
last_reference: time
|
||||||
}, :hibernate}
|
}, :hibernate}
|
||||||
else
|
else
|
||||||
err ->
|
err ->
|
||||||
|
|
@ -44,106 +47,225 @@ defmodule Pleroma.Gun.ConnectionPool.Worker do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_cast({:add_client, client_pid}, state) do
|
def handle_call(:checkout, _from, %{protocol: protocol, clients: clients} = state)
|
||||||
case handle_call(:add_client, {client_pid, nil}, state) do
|
when protocol != :http2 and map_size(clients) > 0 do
|
||||||
{:reply, conn_pid, state, :hibernate} ->
|
{:reply, :busy, state, :hibernate}
|
||||||
send(client_pid, {:conn_pid, conn_pid})
|
|
||||||
{:noreply, state, :hibernate}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
def handle_call(:checkout, {client_pid, _}, %{key: key, protocol: protocol} = state) do
|
||||||
def handle_cast({:remove_client, client_pid}, state) do
|
|
||||||
case handle_call(:remove_client, {client_pid, nil}, state) do
|
|
||||||
{:reply, _, state, :hibernate} ->
|
|
||||||
{:noreply, state, :hibernate}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_call(:add_client, {client_pid, _}, %{key: key, protocol: protocol} = state) do
|
|
||||||
time = :erlang.monotonic_time(:millisecond)
|
time = :erlang.monotonic_time(:millisecond)
|
||||||
|
|
||||||
{{conn_pid, used_by, _, _}, _} =
|
[{_, {conn_pid, _tunnel}}] =
|
||||||
Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
|
Registry.lookup(registry(), key) |> Enum.filter(&(elem(&1, 0) == self()))
|
||||||
{conn_pid, [client_pid | used_by], crf(time - last_reference, crf), time}
|
|
||||||
end)
|
state =
|
||||||
|
state
|
||||||
|
|> cancel_idle_timer()
|
||||||
|
|> add_client(client_pid)
|
||||||
|
|> Map.put(:crf, crf(time - state.last_reference, state.crf))
|
||||||
|
|> Map.put(:last_reference, time)
|
||||||
|
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
[:pleroma, :connection_pool, :client, :add],
|
[:pleroma, :connection_pool, :client, :add],
|
||||||
%{client_pid: client_pid, clients: used_by},
|
%{client_pid: client_pid, clients: Map.keys(state.clients)},
|
||||||
%{key: state.key, protocol: protocol}
|
%{key: state.key, protocol: protocol}
|
||||||
)
|
)
|
||||||
|
|
||||||
state =
|
{:reply, {:ok, conn_pid}, state, :hibernate}
|
||||||
if state.timer != nil do
|
|
||||||
Process.cancel_timer(state[:timer])
|
|
||||||
%{state | timer: nil}
|
|
||||||
else
|
|
||||||
state
|
|
||||||
end
|
|
||||||
|
|
||||||
ref = Process.monitor(client_pid)
|
|
||||||
|
|
||||||
state = put_in(state.client_monitors[client_pid], ref)
|
|
||||||
{:reply, conn_pid, state, :hibernate}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call(:remove_client, {client_pid, _}, %{key: key} = state) do
|
def handle_call(:remove_client, {client_pid, _}, state) do
|
||||||
{{_conn_pid, used_by, _crf, _last_reference}, _} =
|
case active_streams(state, client_pid) do
|
||||||
Registry.update_value(registry(), key, fn {conn_pid, used_by, crf, last_reference} ->
|
[] ->
|
||||||
{conn_pid, List.delete(used_by, client_pid), crf, last_reference}
|
{:reply, :ok, remove_client(state, client_pid, true), :hibernate}
|
||||||
end)
|
|
||||||
|
|
||||||
{ref, state} = pop_in(state.client_monitors[client_pid])
|
streams when state.protocol == :http2 ->
|
||||||
|
Enum.each(streams, &(:ok = Gun.cancel(state.conn_pid, &1)))
|
||||||
|
{:reply, :ok, remove_client(state, client_pid, true), :hibernate}
|
||||||
|
|
||||||
Process.demonitor(ref, [:flush])
|
_streams ->
|
||||||
|
{:stop, :normal, :ok, state}
|
||||||
timer =
|
end
|
||||||
if used_by == [] do
|
|
||||||
max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
|
|
||||||
Process.send_after(self(), :idle_close, max_idle)
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
{:reply, :ok, %{state | timer: timer}, :hibernate}
|
def handle_call({:register_stream, stream}, {client_pid, _}, state) do
|
||||||
|
{:reply, :ok, update_stream(state, client_pid, &MapSet.put(&1, stream)), :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:finish_stream, stream}, {client_pid, _}, state) do
|
||||||
|
{:reply, :ok, update_stream(state, client_pid, &MapSet.delete(&1, stream)), :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:release_stream, stream}, {client_pid, _}, state) do
|
||||||
|
state = update_stream(state, client_pid, &MapSet.delete(&1, stream))
|
||||||
|
{:reply, :ok, remove_client(state, client_pid, true), :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:cancel_stream, stream}, {client_pid, _}, %{protocol: :http2} = state) do
|
||||||
|
:ok = Gun.cancel(state.conn_pid, stream)
|
||||||
|
state = update_stream(state, client_pid, &MapSet.delete(&1, stream))
|
||||||
|
{:reply, :ok, remove_client(state, client_pid, true), :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:cancel_stream, _stream}, _from, state) do
|
||||||
|
{:stop, :normal, :ok, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call(:reclaim_info, _from, %{clients: clients} = state)
|
||||||
|
when map_size(clients) == 0 do
|
||||||
|
{:reply, {:idle, state.crf, state.last_reference}, state, :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call(:reclaim_info, _from, state) do
|
||||||
|
{:reply, :in_use, state, :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call(:reclaim, _from, %{clients: clients} = state) when map_size(clients) == 0 do
|
||||||
|
{:stop, :normal, :reclaimed, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call(:reclaim, _from, state) do
|
||||||
|
{:reply, :in_use, state, :hibernate}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(:idle_close, state) do
|
def handle_info({:idle_close, token}, %{timer: {_timer_ref, token}, clients: clients} = state)
|
||||||
# Gun monitors the owner process, and will close the connection automatically
|
when map_size(clients) == 0 do
|
||||||
# when it's terminated
|
# Gun monitors the owner process, and will close the connection automatically.
|
||||||
{:stop, :normal, state}
|
{:stop, :normal, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_info({:idle_close, _token}, state), do: {:noreply, state, :hibernate}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:gun_up, _pid, _protocol}, state) do
|
def handle_info({:gun_up, _pid, protocol}, state) do
|
||||||
{:noreply, state, :hibernate}
|
{:noreply, %{state | protocol: protocol}, :hibernate}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gracefully shutdown if the connection got closed without any streams left
|
@impl true
|
||||||
|
def handle_info({:gun_error, conn_pid, stream, _reason}, %{conn_pid: conn_pid} = state) do
|
||||||
|
{:noreply, delete_stream(state, stream), :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info({:gun_down, _pid, _protocol, _reason, _streams}, %{proxied: true} = state) do
|
||||||
|
{:stop, :normal, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Gracefully shutdown if the connection got closed without any streams left.
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do
|
def handle_info({:gun_down, _pid, _protocol, _reason, []}, state) do
|
||||||
{:stop, :normal, state}
|
{:stop, :normal, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Otherwise, wait for retry
|
# Otherwise, wait for retry.
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do
|
def handle_info({:gun_down, _pid, _protocol, _reason, _killed_streams}, state) do
|
||||||
{:noreply, state, :hibernate}
|
{:noreply, state, :hibernate}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info({:DOWN, _ref, :process, pid, reason}, state) do
|
def handle_info({:DOWN, ref, :process, pid, reason}, state) do
|
||||||
|
case state.clients[pid] do
|
||||||
|
%{monitor: ^ref} ->
|
||||||
:telemetry.execute(
|
:telemetry.execute(
|
||||||
[:pleroma, :connection_pool, :client, :dead],
|
[:pleroma, :connection_pool, :client, :dead],
|
||||||
%{client_pid: pid, reason: reason},
|
%{client_pid: pid, reason: reason},
|
||||||
%{key: state.key}
|
%{key: state.key}
|
||||||
)
|
)
|
||||||
|
|
||||||
handle_cast({:remove_client, pid}, state)
|
handle_dead_client(state, pid)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:noreply, state, :hibernate}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp add_client(state, client_pid) do
|
||||||
|
update_in(state.clients, fn clients ->
|
||||||
|
case clients[client_pid] do
|
||||||
|
nil ->
|
||||||
|
Map.put(clients, client_pid, %{
|
||||||
|
count: 1,
|
||||||
|
monitor: Process.monitor(client_pid),
|
||||||
|
streams: MapSet.new()
|
||||||
|
})
|
||||||
|
|
||||||
|
client ->
|
||||||
|
Map.put(clients, client_pid, %{client | count: client.count + 1})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_dead_client(state, client_pid) do
|
||||||
|
streams = active_streams(state, client_pid)
|
||||||
|
|
||||||
|
cond do
|
||||||
|
streams == [] ->
|
||||||
|
{:noreply, remove_client(state, client_pid, false, true), :hibernate}
|
||||||
|
|
||||||
|
state.protocol == :http2 ->
|
||||||
|
Enum.each(streams, &(:ok = Gun.cancel(state.conn_pid, &1)))
|
||||||
|
{:noreply, remove_client(state, client_pid, false, true), :hibernate}
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:stop, :normal, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp active_streams(state, client_pid) do
|
||||||
|
case state.clients[client_pid] do
|
||||||
|
%{streams: streams} -> MapSet.to_list(streams)
|
||||||
|
nil -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp update_stream(state, client_pid, update) do
|
||||||
|
update_in(state.clients, fn clients ->
|
||||||
|
case clients[client_pid] do
|
||||||
|
nil -> clients
|
||||||
|
client -> Map.put(clients, client_pid, %{client | streams: update.(client.streams)})
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp delete_stream(state, stream) do
|
||||||
|
update_in(state.clients, fn clients ->
|
||||||
|
Map.new(clients, fn {pid, client} ->
|
||||||
|
{pid, %{client | streams: MapSet.delete(client.streams, stream)}}
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp remove_client(state, client_pid, demonitor, all \\ false) do
|
||||||
|
case state.clients[client_pid] do
|
||||||
|
nil ->
|
||||||
|
state
|
||||||
|
|
||||||
|
%{count: count} = client when count > 1 and not all ->
|
||||||
|
put_in(state.clients[client_pid], %{client | count: count - 1})
|
||||||
|
|
||||||
|
client ->
|
||||||
|
if demonitor, do: Process.demonitor(client.monitor, [:flush])
|
||||||
|
state = update_in(state.clients, &Map.delete(&1, client_pid))
|
||||||
|
|
||||||
|
if map_size(state.clients) == 0, do: schedule_idle_close(state), else: state
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cancel_idle_timer(%{timer: nil} = state), do: state
|
||||||
|
|
||||||
|
defp cancel_idle_timer(%{timer: {timer_ref, _token}} = state) do
|
||||||
|
Process.cancel_timer(timer_ref)
|
||||||
|
%{state | timer: nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp schedule_idle_close(state) do
|
||||||
|
max_idle = Pleroma.Config.get([:connections_pool, :max_idle_time], 30_000)
|
||||||
|
token = make_ref()
|
||||||
|
timer_ref = Process.send_after(self(), {:idle_close, token}, max_idle)
|
||||||
|
%{state | timer: {timer_ref, token}}
|
||||||
end
|
end
|
||||||
|
|
||||||
# LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
|
# LRFU policy: https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.55.1478
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,14 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do
|
||||||
def start_worker(opts, true) do
|
def start_worker(opts, true) do
|
||||||
case DynamicSupervisor.start_child(__MODULE__, {Worker, opts}) do
|
case DynamicSupervisor.start_child(__MODULE__, {Worker, opts}) do
|
||||||
{:error, :max_children} ->
|
{:error, :max_children} ->
|
||||||
:telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts})
|
[key | _] = opts
|
||||||
|
|
||||||
|
:telemetry.execute(
|
||||||
|
[:pleroma, :connection_pool, :provision_failure],
|
||||||
|
%{},
|
||||||
|
%{key: key}
|
||||||
|
)
|
||||||
|
|
||||||
{:error, :pool_full}
|
{:error, :pool_full}
|
||||||
|
|
||||||
res ->
|
res ->
|
||||||
|
|
|
||||||
40
lib/pleroma/html/uri_attributes.ex
Normal file
40
lib/pleroma/html/uri_attributes.ex
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTML.URIAttributes do
|
||||||
|
@moduledoc """
|
||||||
|
URI attribute rules for the scrubbers.
|
||||||
|
|
||||||
|
Works like `FastSanitize.Sanitizer.Meta.allow_tag_with_uri_attributes/3`, but
|
||||||
|
checks the scheme the way a browser reads the URL. Browsers remove tabs and
|
||||||
|
newlines anywhere and control characters and spaces at the start, so
|
||||||
|
`java\\tscript:` is a `javascript:` link to them, while `URI.parse/1` finds no
|
||||||
|
scheme and FastSanitize lets it through.
|
||||||
|
"""
|
||||||
|
|
||||||
|
defmacro allow_tag_with_uri_attributes(tag, attrs, valid_schemes) do
|
||||||
|
for attr <- attrs do
|
||||||
|
quote do
|
||||||
|
def scrub_attribute(unquote(tag), {unquote(attr), "&" <> _}), do: nil
|
||||||
|
|
||||||
|
def scrub_attribute(unquote(tag), {unquote(attr), uri} = attribute) do
|
||||||
|
if Pleroma.HTML.URIAttributes.valid_scheme?(uri, unquote(valid_schemes)),
|
||||||
|
do: attribute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec valid_scheme?(String.t(), [String.t()]) :: boolean()
|
||||||
|
def valid_scheme?(uri, valid_schemes) do
|
||||||
|
scheme =
|
||||||
|
uri
|
||||||
|
|> String.replace(~r/[\t\n\r]/, "")
|
||||||
|
|> String.replace(~r/\A[\x00-\x20]+/, "")
|
||||||
|
|> URI.parse()
|
||||||
|
|> Map.get(:scheme)
|
||||||
|
|
||||||
|
is_nil(scheme) or scheme in valid_schemes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -130,5 +130,9 @@ defmodule Pleroma.HTTP do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp default_middleware,
|
defp default_middleware,
|
||||||
do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
|
do: [
|
||||||
|
Tesla.Middleware.FollowRedirects,
|
||||||
|
Pleroma.Tesla.Middleware.EncodeUrl,
|
||||||
|
Pleroma.Tesla.Middleware.MaxBody
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
|
||||||
253
lib/pleroma/http/adapter/gun.ex
Normal file
253
lib/pleroma/http/adapter/gun.ex
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.Adapter.Gun do
|
||||||
|
@behaviour Tesla.Adapter
|
||||||
|
|
||||||
|
alias Pleroma.Gun.ConnectionPool
|
||||||
|
alias Tesla.Multipart
|
||||||
|
|
||||||
|
@default_timeout 1_000
|
||||||
|
|
||||||
|
@impl Tesla.Adapter
|
||||||
|
def call(env, opts) do
|
||||||
|
adapter_opts =
|
||||||
|
Tesla.Adapter.opts(
|
||||||
|
[close_conn: true, body_as: :plain, send_body: :at_once, receive: true],
|
||||||
|
env,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
|> Map.new()
|
||||||
|
|
||||||
|
with {:ok, status, headers, body} <- request(env, adapter_opts) do
|
||||||
|
env = %{env | status: status, headers: format_headers(headers), body: body}
|
||||||
|
{:ok, put_stream_ref(env, body)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_stream_ref(env, %{stream: stream}) do
|
||||||
|
put_in(env.opts[:adapter][:stream], stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_stream_ref(env, _body), do: env
|
||||||
|
|
||||||
|
defp request(env, %{conn: conn, tunnel: tunnel} = opts) do
|
||||||
|
uri = URI.parse(Tesla.build_url(env))
|
||||||
|
path = request_path(uri, tunnel)
|
||||||
|
method = Tesla.Adapter.Shared.format_method(env.method)
|
||||||
|
{headers, body, send_body} = prepare_body(env.headers, env.body, opts[:send_body])
|
||||||
|
headers = maybe_add_forward_proxy_headers(headers, uri, opts, tunnel)
|
||||||
|
request_opts = request_opts(opts[:reply_to], tunnel)
|
||||||
|
|
||||||
|
stream =
|
||||||
|
try do
|
||||||
|
open_stream(conn, method, path, headers, body, request_opts, send_body)
|
||||||
|
catch
|
||||||
|
kind, reason ->
|
||||||
|
ConnectionPool.release_conn(conn)
|
||||||
|
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok = ConnectionPool.register_stream(conn, stream)
|
||||||
|
:ok = send_stream_body(conn, stream, body, send_body)
|
||||||
|
response = read_response(conn, stream, opts)
|
||||||
|
|
||||||
|
case response do
|
||||||
|
{:error, reason} ->
|
||||||
|
ConnectionPool.cancel_stream(conn, stream)
|
||||||
|
{:error, {:gun_stream_error, reason}}
|
||||||
|
|
||||||
|
response ->
|
||||||
|
unless opts[:body_as] in [:chunks, :stream] and stream_body?(response) do
|
||||||
|
:ok = ConnectionPool.finish_stream(conn, stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[:close_conn] and opts[:body_as] not in [:stream, :chunks] do
|
||||||
|
:gun.close(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
response
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp stream_body?({:ok, _status, _headers, %{stream: _stream}}), do: true
|
||||||
|
defp stream_body?({:ok, _status, _headers, %Stream{}}), do: true
|
||||||
|
defp stream_body?(_response), do: false
|
||||||
|
|
||||||
|
defp request_opts(reply_to, nil), do: %{reply_to: reply_to || self()}
|
||||||
|
|
||||||
|
defp request_opts(reply_to, :forward_proxy), do: %{reply_to: reply_to || self()}
|
||||||
|
|
||||||
|
defp request_opts(reply_to, tunnel) do
|
||||||
|
%{reply_to: reply_to || self(), tunnel: tunnel}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp request_path(uri, :forward_proxy) do
|
||||||
|
path = Tesla.Adapter.Shared.prepare_path(uri.path, uri.query)
|
||||||
|
"#{uri.scheme}://#{authority(uri)}#{path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp request_path(uri, _tunnel) do
|
||||||
|
Tesla.Adapter.Shared.prepare_path(uri.path, uri.query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_add_forward_proxy_headers(headers, uri, opts, :forward_proxy) do
|
||||||
|
headers
|
||||||
|
|> put_header_new("host", authority(uri))
|
||||||
|
|> maybe_put_proxy_authorization(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_add_forward_proxy_headers(headers, _uri, _opts, _tunnel), do: headers
|
||||||
|
|
||||||
|
defp maybe_put_proxy_authorization(headers, %{proxy_auth: {username, password}})
|
||||||
|
when is_binary(username) and is_binary(password) do
|
||||||
|
value = "Basic " <> Base.encode64(username <> ":" <> password)
|
||||||
|
List.keystore(headers, "proxy-authorization", 0, {"proxy-authorization", value})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_proxy_authorization(headers, _opts), do: headers
|
||||||
|
|
||||||
|
defp put_header_new(headers, name, value) do
|
||||||
|
if List.keymember?(headers, name, 0), do: headers, else: [{name, value} | headers]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp authority(%URI{scheme: scheme, host: host, port: port}) do
|
||||||
|
host = if String.contains?(host, ":"), do: "[#{host}]", else: host
|
||||||
|
if port == URI.default_port(scheme), do: host, else: "#{host}:#{port}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_body(headers, %Multipart{} = multipart, _send_body) do
|
||||||
|
{format_headers(headers ++ Multipart.headers(multipart)), Multipart.body(multipart), :stream}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_body(headers, body, _send_body)
|
||||||
|
when is_struct(body, Stream) or is_function(body) do
|
||||||
|
{format_headers(headers), body, :stream}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp prepare_body(headers, body, send_body) do
|
||||||
|
{format_headers(headers), body || "", send_body}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp open_stream(conn, method, path, headers, _body, request_opts, :stream) do
|
||||||
|
:gun.headers(conn, method, path, headers, request_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp open_stream(conn, method, path, headers, body, request_opts, :at_once) do
|
||||||
|
:gun.request(conn, method, path, headers, body, request_opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp send_stream_body(conn, stream, body, :stream) do
|
||||||
|
try do
|
||||||
|
for data <- body, do: :ok = :gun.data(conn, stream, :nofin, data)
|
||||||
|
:gun.data(conn, stream, :fin, "")
|
||||||
|
catch
|
||||||
|
kind, reason ->
|
||||||
|
ConnectionPool.cancel_stream(conn, stream)
|
||||||
|
:erlang.raise(kind, reason, __STACKTRACE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp send_stream_body(_conn, _stream, _body, :at_once), do: :ok
|
||||||
|
|
||||||
|
defp read_response(conn, stream, opts) do
|
||||||
|
receive? = opts[:receive]
|
||||||
|
|
||||||
|
receive do
|
||||||
|
{:gun_response, ^conn, ^stream, :fin, status, headers} ->
|
||||||
|
{:ok, status, headers, ""}
|
||||||
|
|
||||||
|
{:gun_response, ^conn, ^stream, :nofin, status, headers} ->
|
||||||
|
format_response(conn, stream, opts, status, headers, opts[:body_as])
|
||||||
|
|
||||||
|
{:gun_up, ^conn, _protocol} when receive? ->
|
||||||
|
read_response(conn, stream, opts)
|
||||||
|
|
||||||
|
{:gun_error, ^conn, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
{:gun_error, ^conn, ^stream, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
{:gun_down, ^conn, _protocol, _reason, _killed_streams} when receive? ->
|
||||||
|
read_response(conn, stream, opts)
|
||||||
|
|
||||||
|
{:DOWN, _ref, :process, ^conn, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
after
|
||||||
|
opts[:timeout] || @default_timeout -> {:error, :recv_response_timeout}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_response(conn, stream, opts, status, headers, :plain) do
|
||||||
|
case read_body(conn, stream, opts) do
|
||||||
|
{:ok, body} ->
|
||||||
|
{:ok, status, headers, body}
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
:ok = :gun.flush(stream)
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_response(conn, stream, opts, status, headers, :stream) do
|
||||||
|
body =
|
||||||
|
Stream.resource(
|
||||||
|
fn -> :reading end,
|
||||||
|
fn
|
||||||
|
:reading ->
|
||||||
|
case Tesla.Adapter.Gun.read_chunk(conn, stream, opts) do
|
||||||
|
{:nofin, part} -> {[part], :reading}
|
||||||
|
{:fin, part} -> {[part], :done}
|
||||||
|
{:error, reason} -> raise "Gun stream failed: #{inspect(reason)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
:done ->
|
||||||
|
{:halt, :done}
|
||||||
|
end,
|
||||||
|
fn _state ->
|
||||||
|
if opts[:close_conn], do: :gun.close(conn)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, status, headers, body}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_response(conn, stream, opts, status, headers, :chunks) do
|
||||||
|
{:ok, status, headers, %{pid: conn, stream: stream, opts: Map.to_list(opts)}}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp read_body(conn, stream, opts, acc \\ "") do
|
||||||
|
receive do
|
||||||
|
{:gun_data, ^conn, ^stream, :fin, body} ->
|
||||||
|
append_body(acc, body, opts[:max_body])
|
||||||
|
|
||||||
|
{:gun_data, ^conn, ^stream, :nofin, body} ->
|
||||||
|
with {:ok, acc} <- append_body(acc, body, opts[:max_body]) do
|
||||||
|
read_body(conn, stream, opts, acc)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:gun_error, ^conn, ^stream, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
|
||||||
|
{:DOWN, _ref, :process, ^conn, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
after
|
||||||
|
opts[:timeout] || @default_timeout -> {:error, :recv_body_timeout}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp append_body(acc, part, nil), do: {:ok, acc <> part}
|
||||||
|
|
||||||
|
defp append_body(acc, part, limit) do
|
||||||
|
body = acc <> part
|
||||||
|
if byte_size(body) <= limit, do: {:ok, body}, else: {:error, :body_too_large}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_headers(headers) do
|
||||||
|
Enum.map(headers, fn {key, value} ->
|
||||||
|
{String.downcase(to_string(key)), to_string(value)}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -46,6 +46,24 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
||||||
|> adapter_helper().options(uri)
|
|> adapter_helper().options(uri)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
The configured Tesla adapter with TLS options, for clients that do not go
|
||||||
|
through `Pleroma.HTTP`.
|
||||||
|
"""
|
||||||
|
@spec tesla_adapter() :: module() | {module(), keyword()}
|
||||||
|
def tesla_adapter do
|
||||||
|
case adapter() do
|
||||||
|
Tesla.Adapter.Hackney ->
|
||||||
|
{Tesla.Adapter.Hackney, Pleroma.HTTP.TLS.put_hackney_ssl_options([])}
|
||||||
|
|
||||||
|
Tesla.Adapter.Gun ->
|
||||||
|
{Tesla.Adapter.Gun, certificates_verification: true}
|
||||||
|
|
||||||
|
adapter ->
|
||||||
|
adapter
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp adapter, do: Application.get_env(:tesla, :adapter)
|
defp adapter, do: Application.get_env(:tesla, :adapter)
|
||||||
|
|
||||||
defp adapter_helper do
|
defp adapter_helper do
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
||||||
|
|
||||||
@defaults
|
@defaults
|
||||||
|> Keyword.merge(config_opts)
|
|> Keyword.merge(config_opts)
|
||||||
|
|> ssl_options_to_tls_opts()
|
||||||
|> add_scheme_opts(uri)
|
|> add_scheme_opts(uri)
|
||||||
|> AdapterHelper.maybe_add_proxy(proxy)
|
|> AdapterHelper.maybe_add_proxy(proxy)
|
||||||
|> Keyword.merge(incoming_opts)
|
|> Keyword.merge(incoming_opts)
|
||||||
|
|
@ -35,6 +36,15 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
||||||
|> maybe_stream()
|
|> maybe_stream()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# [:http, :adapter, :ssl_options] is Hackney's name for the TLS options,
|
||||||
|
# and AdminFE sets it. Keep those settings when Gun is the adapter.
|
||||||
|
defp ssl_options_to_tls_opts(opts) do
|
||||||
|
case Keyword.pop(opts, :ssl_options) do
|
||||||
|
{nil, opts} -> opts
|
||||||
|
{ssl_options, opts} -> Keyword.put_new(opts, :tls_opts, ssl_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
|
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
|
||||||
|
|
||||||
defp add_scheme_opts(opts, %{scheme: "https"}) do
|
defp add_scheme_opts(opts, %{scheme: "https"}) do
|
||||||
|
|
@ -48,10 +58,10 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
||||||
Keyword.put(opts, :timeout, recv_timeout)
|
Keyword.put(opts, :timeout, recv_timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gun uses [body_as: :stream]
|
# Keep the pool lease until the caller consumes or cancels the body.
|
||||||
defp maybe_stream(opts) do
|
defp maybe_stream(opts) do
|
||||||
case Keyword.pop(opts, :stream, nil) do
|
case Keyword.pop(opts, :stream, nil) do
|
||||||
{true, opts} -> Keyword.put(opts, :body_as, :stream)
|
{true, opts} -> Keyword.put(opts, :body_as, :chunks)
|
||||||
{_, opts} -> opts
|
{_, opts} -> opts
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,25 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do
|
||||||
|> add_scheme_opts(uri)
|
|> add_scheme_opts(uri)
|
||||||
|> maybe_add_with_body()
|
|> maybe_add_with_body()
|
||||||
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
|
||||||
|
|> Keyword.put(:follow_redirect, false)
|
||||||
|
|> Keyword.put(:force_redirect, false)
|
||||||
|
|> normalize_proxy()
|
||||||
|
|> Pleroma.HTTP.TLS.put_hackney_ssl_options()
|
||||||
|
end
|
||||||
|
|
||||||
|
# Hackney 4 ignores a proxy whose host is an IP tuple and connects directly.
|
||||||
|
# ConfigDB turns a numeric proxy host into such a tuple.
|
||||||
|
defp normalize_proxy(opts) do
|
||||||
|
case Keyword.fetch(opts, :proxy) do
|
||||||
|
{:ok, {type, host, port}} when is_tuple(host) ->
|
||||||
|
Keyword.put(opts, :proxy, {type, :inet.ntoa(host), port})
|
||||||
|
|
||||||
|
{:ok, {host, port}} when is_tuple(host) ->
|
||||||
|
Keyword.put(opts, :proxy, {:inet.ntoa(host), port})
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
opts
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_scheme_opts(opts, _), do: opts
|
defp add_scheme_opts(opts, _), do: opts
|
||||||
|
|
|
||||||
49
lib/pleroma/http/tls.ex
Normal file
49
lib/pleroma/http/tls.ex
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.TLS do
|
||||||
|
@moduledoc """
|
||||||
|
TLS client options that trust the CAStore bundle.
|
||||||
|
|
||||||
|
Hackney otherwise trusts its own certifi bundle. Hackney 1 drops all of its
|
||||||
|
default TLS options, including certificate checks, as soon as any
|
||||||
|
`ssl_options` are given. Hackney 4 merges them, so `partial_chain` must be
|
||||||
|
set too: Hackney's own one trusts any certificate in the peer chain that is
|
||||||
|
in certifi.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@spec ssl_options(keyword()) :: keyword()
|
||||||
|
def ssl_options(overrides \\ []) do
|
||||||
|
defaults = [
|
||||||
|
verify: :verify_peer,
|
||||||
|
cacertfile: CAStore.file_path(),
|
||||||
|
depth: 20,
|
||||||
|
customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)],
|
||||||
|
partial_chain: &__MODULE__.reject_partial_chain/1
|
||||||
|
]
|
||||||
|
|
||||||
|
# Hackney drops cacerts when cacertfile is set too.
|
||||||
|
defaults =
|
||||||
|
if Keyword.has_key?(overrides, :cacerts),
|
||||||
|
do: Keyword.delete(defaults, :cacertfile),
|
||||||
|
else: defaults
|
||||||
|
|
||||||
|
Keyword.merge(defaults, overrides)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def reject_partial_chain(_certs), do: :unknown_ca
|
||||||
|
|
||||||
|
@spec put_hackney_ssl_options(keyword()) :: keyword()
|
||||||
|
def put_hackney_ssl_options(opts) do
|
||||||
|
# Hackney 4 ignores the top-level insecure option and falls back to certifi.
|
||||||
|
if opts[:insecure] do
|
||||||
|
Keyword.put(opts, :ssl_options, verify: :verify_none)
|
||||||
|
else
|
||||||
|
opts
|
||||||
|
|> Keyword.put(:ssl_options, ssl_options(Keyword.get(opts, :ssl_options, [])))
|
||||||
|
|> Keyword.put(:proxy_ssl_options, ssl_options(Keyword.get(opts, :proxy_ssl_options, [])))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.List do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Emoji
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
|
|
@ -17,14 +18,54 @@ defmodule Pleroma.List do
|
||||||
field(:title, :string)
|
field(:title, :string)
|
||||||
field(:following, {:array, :string}, default: [])
|
field(:following, {:array, :string}, default: [])
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
|
field(:exclusive, :boolean, default: false)
|
||||||
|
field(:emoji, :string)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
def title_changeset(list, attrs \\ %{}) do
|
def update_changeset(list, attrs \\ %{}) do
|
||||||
list
|
list
|
||||||
|> cast(attrs, [:title])
|
|> cast(attrs, [:title, :exclusive, :emoji])
|
||||||
|> validate_required([:title])
|
|> validate_required([:title])
|
||||||
|
|> fix_emoji()
|
||||||
|
|> validate_emoji()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fix_emoji(changeset) do
|
||||||
|
with {:emoji_field, emoji} when is_binary(emoji) <-
|
||||||
|
{:emoji_field, get_field(changeset, :emoji)},
|
||||||
|
{:fixed_emoji, emoji} <-
|
||||||
|
{:fixed_emoji,
|
||||||
|
emoji
|
||||||
|
|> Emoji.fully_qualify_emoji()
|
||||||
|
|> Emoji.maybe_quote()} do
|
||||||
|
put_change(changeset, :emoji, emoji)
|
||||||
|
else
|
||||||
|
{:emoji_field, _} -> changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_emoji(changeset) do
|
||||||
|
validate_change(changeset, :emoji, fn
|
||||||
|
:emoji, nil ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
:emoji, emoji ->
|
||||||
|
if Emoji.unicode?(emoji) or valid_local_custom_emoji?(emoji) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[emoji: "Invalid emoji"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_local_custom_emoji?(emoji) do
|
||||||
|
with %{file: _path} <- Emoji.get(emoji) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_changeset(list, attrs \\ %{}) do
|
def follow_changeset(list, attrs \\ %{}) do
|
||||||
|
|
@ -91,14 +132,14 @@ defmodule Pleroma.List do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
def rename(%Pleroma.List{} = list, title) do
|
def update(%Pleroma.List{} = list, params) do
|
||||||
list
|
list
|
||||||
|> title_changeset(%{title: title})
|
|> update_changeset(params)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(title, %User{} = creator) do
|
def create(params, %User{} = creator) do
|
||||||
changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title})
|
changeset = update_changeset(%Pleroma.List{user_id: creator.id}, params)
|
||||||
|
|
||||||
if changeset.valid? do
|
if changeset.valid? do
|
||||||
Repo.transaction(fn ->
|
Repo.transaction(fn ->
|
||||||
|
|
@ -149,4 +190,14 @@ defmodule Pleroma.List do
|
||||||
end
|
end
|
||||||
|
|
||||||
def member?(_, _), do: false
|
def member?(_, _), do: false
|
||||||
|
|
||||||
|
def get_exclusive_list_members(%User{id: user_id}) do
|
||||||
|
Pleroma.List
|
||||||
|
|> where([l], l.user_id == ^user_id)
|
||||||
|
|> where([l], l.exclusive == true)
|
||||||
|
|> select([l], l.following)
|
||||||
|
|> Repo.all()
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ defmodule Pleroma.Marker do
|
||||||
|
|
||||||
defp get_marker(user, timeline) do
|
defp get_marker(user, timeline) do
|
||||||
case Repo.find_resource(get_query(user, timeline)) do
|
case Repo.find_resource(get_query(user, timeline)) do
|
||||||
{:ok, marker} -> %__MODULE__{marker | user: user}
|
{:ok, %__MODULE__{} = marker} -> %{marker | user: user}
|
||||||
_ -> %__MODULE__{timeline: timeline, user_id: user.id}
|
_ -> %__MODULE__{timeline: timeline, user_id: user.id}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def disable(%Ecto.Changeset{} = changeset, force \\ false) do
|
def disable(%Ecto.Changeset{} = changeset, force \\ false) do
|
||||||
|
%Settings{} =
|
||||||
settings =
|
settings =
|
||||||
changeset
|
changeset
|
||||||
|> Ecto.Changeset.apply_changes()
|
|> Ecto.Changeset.apply_changes()
|
||||||
|
|
@ -20,20 +21,20 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do
|
def disable_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{settings | totp: %Settings.TOTP{}})
|
|> put_change(%Settings{settings | totp: %Settings.TOTP{}})
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do
|
def confirm_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
|
||||||
totp_settings = %Settings.TOTP{settings.totp | confirmed: true}
|
totp_settings = %Settings.TOTP{(%Settings.TOTP{} = settings.totp) | confirmed: true}
|
||||||
|
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{settings | totp: totp_settings, enabled: true})
|
|> put_change(%Settings{settings | totp: totp_settings, enabled: true})
|
||||||
end
|
end
|
||||||
|
|
||||||
def setup_totp(%User{} = user, attrs) do
|
def setup_totp(%User{} = user, attrs) do
|
||||||
mfa_settings = MFA.fetch_settings(user)
|
%Settings{} = mfa_settings = MFA.fetch_settings(user)
|
||||||
|
|
||||||
totp_settings =
|
totp_settings =
|
||||||
%Settings.TOTP{}
|
%Settings.TOTP{}
|
||||||
|
|
@ -46,7 +47,7 @@ defmodule Pleroma.MFA.Changeset do
|
||||||
def cast_backup_codes(%User{} = user, codes) do
|
def cast_backup_codes(%User{} = user, codes) do
|
||||||
user
|
user
|
||||||
|> put_change(%Settings{
|
|> put_change(%Settings{
|
||||||
user.multi_factor_authentication_settings
|
(%Settings{} = user.multi_factor_authentication_settings)
|
||||||
| backup_codes: codes
|
| backup_codes: codes
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -132,11 +132,18 @@ defmodule Pleroma.ModerationLog do
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
def insert_log(%{actor: %User{}, action: action, subject: %Activity{} = subject} = attrs)
|
||||||
when action in ["report_note_delete", "report_update", "report_note"] do
|
when action in [
|
||||||
|
"report_note_delete",
|
||||||
|
"report_update",
|
||||||
|
"report_note",
|
||||||
|
"report_unassigned",
|
||||||
|
"report_assigned"
|
||||||
|
] do
|
||||||
data =
|
data =
|
||||||
attrs
|
attrs
|
||||||
|> prepare_log_data
|
|> prepare_log_data
|
||||||
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
|> Pleroma.Maps.put_if_present("text", attrs[:text])
|
||||||
|
|> Pleroma.Maps.put_if_present("assigned_account", attrs[:assigned_account])
|
||||||
|> Map.merge(%{"subject" => report_to_map(subject)})
|
|> Map.merge(%{"subject" => report_to_map(subject)})
|
||||||
|
|
||||||
insert_log_entry_with_message(%ModerationLog{data: data})
|
insert_log_entry_with_message(%ModerationLog{data: data})
|
||||||
|
|
@ -339,6 +346,16 @@ defmodule Pleroma.ModerationLog do
|
||||||
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
"@#{actor_nickname} approved users: #{users_to_nicknames_string(users)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "reject",
|
||||||
|
"subject" => users
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
"@#{actor_nickname} rejected users: #{users_to_nicknames_string(users)}"
|
||||||
|
end
|
||||||
|
|
||||||
def get_log_entry_message(%ModerationLog{
|
def get_log_entry_message(%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
"actor" => %{"nickname" => actor_nickname},
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
|
@ -441,6 +458,35 @@ defmodule Pleroma.ModerationLog do
|
||||||
" with '#{state}' state"
|
" with '#{state}' state"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "report_assigned",
|
||||||
|
"subject" => %{"id" => subject_id, "type" => "report"},
|
||||||
|
"assigned_account" => assigned_account
|
||||||
|
}
|
||||||
|
} = log
|
||||||
|
) do
|
||||||
|
"@#{actor_nickname} assigned report ##{subject_id}" <>
|
||||||
|
subject_actor_nickname(log, " (on user ", ")") <>
|
||||||
|
" to user #{assigned_account}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_log_entry_message(
|
||||||
|
%ModerationLog{
|
||||||
|
data: %{
|
||||||
|
"actor" => %{"nickname" => actor_nickname},
|
||||||
|
"action" => "report_unassigned",
|
||||||
|
"subject" => %{"id" => subject_id, "type" => "report"}
|
||||||
|
}
|
||||||
|
} = log
|
||||||
|
) do
|
||||||
|
"@#{actor_nickname} unassigned report ##{subject_id}" <>
|
||||||
|
subject_actor_nickname(log, " (on user ", ")") <>
|
||||||
|
" from a user"
|
||||||
|
end
|
||||||
|
|
||||||
def get_log_entry_message(
|
def get_log_entry_message(
|
||||||
%ModerationLog{
|
%ModerationLog{
|
||||||
data: %{
|
data: %{
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.Notification do
|
||||||
# remember to add a migration to add it to the `notifications_type` enum
|
# remember to add a migration to add it to the `notifications_type` enum
|
||||||
# as well.
|
# as well.
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
|
field(:group_key, :string)
|
||||||
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
|
||||||
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ defmodule Pleroma.Notification do
|
||||||
|> type_from_activity()
|
|> type_from_activity()
|
||||||
|
|
||||||
notification
|
notification
|
||||||
|> changeset(%{type: type})
|
|> changeset(%{type: type, group_key: grouped_notification_key(type, activity)})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -62,6 +63,99 @@ defmodule Pleroma.Notification do
|
||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@groupable_notification_types ~w{favourite follow reblog}
|
||||||
|
@group_bucket_seconds 12 * 60 * 60
|
||||||
|
|
||||||
|
def groupable_notification_types, do: @groupable_notification_types
|
||||||
|
|
||||||
|
def group_notifications(notifications, grouped_types \\ nil) do
|
||||||
|
grouped_types = normalize_grouped_types(grouped_types)
|
||||||
|
|
||||||
|
{group_keys, grouped_notifications} =
|
||||||
|
Enum.reduce(notifications, {[], %{}}, fn notification,
|
||||||
|
{group_keys, grouped_notifications} ->
|
||||||
|
group_key = group_key(notification, grouped_types)
|
||||||
|
|
||||||
|
if Map.has_key?(grouped_notifications, group_key) do
|
||||||
|
{group_keys, Map.update!(grouped_notifications, group_key, &[notification | &1])}
|
||||||
|
else
|
||||||
|
{[group_key | group_keys], Map.put(grouped_notifications, group_key, [notification])}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
group_keys
|
||||||
|
|> Enum.reverse()
|
||||||
|
|> Enum.map(fn group_key ->
|
||||||
|
grouped_notifications
|
||||||
|
|> Map.fetch!(group_key)
|
||||||
|
|> Enum.reverse()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_key(notification, grouped_types \\ nil)
|
||||||
|
|
||||||
|
def group_key(%Notification{} = notification, nil) do
|
||||||
|
group_key(notification, @groupable_notification_types)
|
||||||
|
end
|
||||||
|
|
||||||
|
def group_key(%Notification{type: type, group_key: group_key} = notification, grouped_types) do
|
||||||
|
grouped_types = normalize_grouped_types(grouped_types)
|
||||||
|
|
||||||
|
if type in @groupable_notification_types and type in grouped_types and is_binary(group_key) do
|
||||||
|
group_key
|
||||||
|
else
|
||||||
|
ungrouped_group_key(notification)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_grouped_types(nil), do: @groupable_notification_types
|
||||||
|
def normalize_grouped_types(types) when is_list(types), do: types
|
||||||
|
def normalize_grouped_types(type), do: [type]
|
||||||
|
|
||||||
|
defp ungrouped_group_key(%Notification{id: id}), do: "ungrouped-#{id}"
|
||||||
|
|
||||||
|
defp grouped_notification_key(type, activity) when type in @groupable_notification_types do
|
||||||
|
with target_id when is_binary(target_id) <- group_target_id(type, activity) do
|
||||||
|
# Mastodon uses Redis to reuse a recent bucket for rolling 12h windows. Pleroma keeps keys
|
||||||
|
# deterministic and self-contained; clients must treat group_key as opaque either way.
|
||||||
|
"#{type}-#{target_id}-#{group_time_bucket(activity)}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp grouped_notification_key(_type, _activity), do: nil
|
||||||
|
|
||||||
|
defp group_time_bucket(%Activity{inserted_at: inserted_at}) when not is_nil(inserted_at) do
|
||||||
|
inserted_at
|
||||||
|
|> NaiveDateTime.to_erl()
|
||||||
|
|> :calendar.datetime_to_gregorian_seconds()
|
||||||
|
|> div(@group_bucket_seconds)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp group_time_bucket(_),
|
||||||
|
do: group_time_bucket(%Activity{inserted_at: NaiveDateTime.utc_now()})
|
||||||
|
|
||||||
|
defp group_target_id(type, activity) when type in ["favourite", "reblog"] do
|
||||||
|
with object_id when is_binary(object_id) <- object_id_for(activity),
|
||||||
|
%Activity{id: id} <- Activity.get_create_by_object_ap_id(object_id) do
|
||||||
|
to_string(id)
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp group_target_id("follow", %{data: %{"object" => ap_id}}) do
|
||||||
|
case User.get_cached_by_ap_id(ap_id) do
|
||||||
|
%User{id: id} -> to_string(id)
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp group_target_id(_, _), do: nil
|
||||||
|
|
||||||
|
defp object_id_for(%{data: %{"object" => %{"id" => id}}}) when is_binary(id), do: id
|
||||||
|
defp object_id_for(%{data: %{"object" => id}}) when is_binary(id), do: id
|
||||||
|
defp object_id_for(_), do: nil
|
||||||
|
|
||||||
@notification_types ~w{
|
@notification_types ~w{
|
||||||
favourite
|
favourite
|
||||||
follow
|
follow
|
||||||
|
|
@ -79,7 +173,7 @@ defmodule Pleroma.Notification do
|
||||||
|
|
||||||
def changeset(%Notification{} = notification, attrs) do
|
def changeset(%Notification{} = notification, attrs) do
|
||||||
notification
|
notification
|
||||||
|> cast(attrs, [:seen, :type])
|
|> cast(attrs, [:seen, :type, :group_key])
|
||||||
|> validate_inclusion(:type, @notification_types)
|
|> validate_inclusion(:type, @notification_types)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -458,7 +552,8 @@ defmodule Pleroma.Notification do
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
activity: activity,
|
activity: activity,
|
||||||
seen: mark_as_read?(activity, user),
|
seen: mark_as_read?(activity, user),
|
||||||
type: type
|
type: type,
|
||||||
|
group_key: grouped_notification_key(type, activity)
|
||||||
})
|
})
|
||||||
|> Marker.multi_set_last_read_id(user, "notifications")
|
|> Marker.multi_set_last_read_id(user, "notifications")
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
|
|
||||||
|
|
@ -372,12 +372,28 @@ defmodule Pleroma.Object do
|
||||||
option
|
option
|
||||||
end)
|
end)
|
||||||
|
|
||||||
voters = [actor | object.data["voters"] || []] |> Enum.uniq()
|
existing_voters = object.data["voters"] || []
|
||||||
|
voters = [actor | existing_voters] |> Enum.uniq()
|
||||||
|
new_voter? = actor not in existing_voters
|
||||||
|
existing_voters_count = object.data["votersCount"]
|
||||||
|
|
||||||
|
voters_count =
|
||||||
|
cond do
|
||||||
|
is_integer(existing_voters_count) and new_voter? ->
|
||||||
|
existing_voters_count + 1
|
||||||
|
|
||||||
|
is_integer(existing_voters_count) ->
|
||||||
|
existing_voters_count
|
||||||
|
|
||||||
|
true ->
|
||||||
|
length(voters)
|
||||||
|
end
|
||||||
|
|
||||||
data =
|
data =
|
||||||
object.data
|
object.data
|
||||||
|> Map.put(key, options)
|
|> Map.put(key, options)
|
||||||
|> Map.put("voters", voters)
|
|> Map.put("voters", voters)
|
||||||
|
|> Map.put("votersCount", voters_count)
|
||||||
|
|
||||||
object
|
object
|
||||||
|> Object.change(%{data: data})
|
|> Object.change(%{data: data})
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue