mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-22 07:24:30 +09:00
Compare commits
571
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f19660e5 | ||
|
|
d5ca5d8293 | ||
|
|
e403fb6cce | ||
|
|
e8dff6a8a1 | ||
|
|
2b656a1186 | ||
|
|
381699623e | ||
|
|
d7bbe933fa | ||
|
|
069b13c382 | ||
|
|
538b5d69bb | ||
|
|
3c8fe74660 | ||
|
|
c0f825ae2a | ||
|
|
8d8a29377c | ||
|
|
abcb5cb814 | ||
|
|
97b2ac91ca | ||
|
|
123c4d5bbf | ||
|
|
6e2bf3cbe6 | ||
|
|
f509bef1fb | ||
|
|
959b03b5f4 | ||
|
|
f501d34179 | ||
|
|
3657438467 | ||
|
|
2976b1842b | ||
|
|
8cac3b35c9 | ||
|
|
d514501d57 | ||
|
|
83bb9d89c8 | ||
|
|
0117605572 | ||
|
|
72e14c17ba | ||
|
|
aa0ebdb97e | ||
|
|
3d4b2933e1 | ||
|
|
922ff5ea64 | ||
|
|
fce3200265 | ||
|
|
3b35db2681 | ||
|
|
c7da0cf33d | ||
|
|
78272521bb | ||
|
|
8e39380ee6 | ||
|
|
fb58447ba1 | ||
|
|
2ffe8f3387 | ||
|
|
4194525933 | ||
|
|
44799dafa2 | ||
|
|
30b32c9a35 | ||
|
|
d1b4b82895 | ||
|
|
9bc43c0c08 | ||
|
|
13960a0bb3 | ||
|
|
64cb159d65 | ||
|
|
d2173fe879 | ||
|
|
49eb8cdd81 | ||
|
|
b4fc1241ae | ||
|
|
54f1b37666 | ||
|
|
8bda151596 | ||
|
|
ab19c0d1ac | ||
|
|
68cb2a4bbb | ||
|
|
17746fcdc4 | ||
|
|
c890956e4f | ||
|
|
8a5d4a8bdb | ||
|
|
0ba15ee46a | ||
|
|
85d9ef7386 | ||
|
|
f8d85f0e5e | ||
|
|
7f674474b5 | ||
|
|
c9fdf8fead | ||
|
|
c17091768c | ||
|
|
e0640d68ff | ||
|
|
3292dc5f89 | ||
|
|
86e29b61e3 | ||
|
|
f8d3c70a91 | ||
|
|
74c6fad260 | ||
|
|
bcb30eea88 | ||
|
|
d59ddc2214 | ||
|
|
e87b593f46 | ||
|
|
5a2d81ebbb | ||
|
|
50c28aa8fa | ||
|
|
af7724dbf6 | ||
|
|
be314788b3 | ||
|
|
5b3f69bf27 | ||
|
|
d7aa6ec2aa | ||
|
|
20e3474393 | ||
|
|
3694300d86 | ||
|
|
54ec74e4f9 | ||
|
|
aa3b058ce6 | ||
|
|
44bc29b101 | ||
|
|
b07dcc368d | ||
|
|
0967bf3136 | ||
|
|
edc913096a | ||
|
|
cca8227ca6 | ||
|
|
45b78a13c2 | ||
|
|
6acd7f3d22 | ||
|
|
7d2c51a57f | ||
|
|
575a5adbef | ||
|
|
5695510980 | ||
|
|
c16d414d93 | ||
|
|
e7b13fd19f | ||
|
|
f22261f218 | ||
|
|
901ed623f6 | ||
|
|
1198b61648 | ||
|
|
d564ca5691 | ||
|
|
b7e92cbfe0 | ||
|
|
1cd62eb661 | ||
|
|
7e177a1c90 | ||
|
|
78471d8a85 | ||
|
|
6b6ae13919 | ||
|
|
7197371123 | ||
|
|
70fd9e6331 | ||
|
|
c14b7d4cb3 | ||
|
|
0f0ff5f3df | ||
|
|
b4c614d21a | ||
|
|
bef5e33eec | ||
|
|
e5a568acca | ||
|
|
a208b56354 | ||
|
|
71be6c1de4 | ||
|
|
eae905b1da | ||
|
|
10cd4f96a4 | ||
|
|
9d74daf8ad | ||
|
|
6f344def93 | ||
|
|
72f9f9c396 | ||
|
|
c8755885d4 | ||
|
|
12d503d4c5 | ||
|
|
2f6201bcbd | ||
|
|
ec681e6061 | ||
|
|
0fc6fad11d | ||
|
|
eb329f2cd0 | ||
|
|
9a20821332 | ||
|
|
4749eb7f86 | ||
|
|
2d83fd2a93 | ||
|
|
d180af6ba2 | ||
|
|
4810eb3e24 | ||
|
|
48a19dedd8 | ||
|
|
cadf7ea532 | ||
|
|
fc60d05f17 | ||
|
|
5072488f1e | ||
|
|
7a5b9a0ded | ||
|
|
4ce4d4e09a | ||
|
|
82abd03a37 | ||
|
|
93d1f8af46 | ||
|
|
a19002652b | ||
|
|
6534ee4300 | ||
|
|
4db13b0375 | ||
|
|
a5c1362f4a | ||
|
|
454fefdb92 | ||
|
|
f390e610b9 | ||
|
|
ece622e800 | ||
|
|
bc17922b02 | ||
|
|
a32d8e4f52 | ||
|
|
12daeb758d | ||
|
|
fff67ed9af | ||
|
|
d0b4816a5f | ||
|
|
0febad324c | ||
|
|
dcc3273889 | ||
|
|
e9a4f33a7e | ||
|
|
ae820ba4ea | ||
|
|
94f1239050 | ||
|
|
acc1392a53 | ||
|
|
a6a9789091 | ||
|
|
fdb7410976 | ||
|
|
b146b8ee33 | ||
|
|
4b1afc953f | ||
|
|
4ecbe9603c | ||
|
|
847d449882 | ||
|
|
bef20cba85 | ||
|
|
ebf91ccc76 | ||
|
|
abfaff16c2 | ||
|
|
89fa895a0a | ||
|
|
334fef52e1 | ||
|
|
aa42bf13ce | ||
|
|
c486c2d3d0 | ||
|
|
0ec1cee99e | ||
|
|
ccbe4857a1 | ||
|
|
7f8b0afedb | ||
|
|
e94b7d7ad8 | ||
|
|
27baa38fd1 | ||
|
|
4af10dd3e3 | ||
|
|
269d33cab2 | ||
|
|
170e12595b | ||
|
|
b82bbaa2dd | ||
|
|
b8f3dbddbf | ||
|
|
663572fd10 | ||
|
|
5033cf7203 | ||
|
|
0fcab2f8f0 | ||
|
|
c3d790f664 | ||
|
|
0906a85d6f | ||
|
|
4da8dec4ac | ||
|
|
d6e3254a16 | ||
|
|
6dc698ff5e | ||
|
|
39e48458da | ||
|
|
c5453f83e8 | ||
|
|
510cc1b916 | ||
|
|
e7859fefb1 | ||
|
|
46a356cc40 | ||
|
|
bd56174870 | ||
|
|
965bb5f634 | ||
|
|
7937d5f802 | ||
|
|
f1373a63c3 | ||
|
|
e0d7625c39 | ||
|
|
f8505d19f7 | ||
|
|
1515a8487f | ||
|
|
7a4aa42d4b | ||
|
|
0ef1cd14c3 | ||
|
|
795e190bda | ||
|
|
d00389ab30 | ||
|
|
3071bc7525 | ||
|
|
6de6be2358 | ||
|
|
c51c222c47 | ||
|
|
e218ba21de | ||
|
|
de358c0def | ||
|
|
4b88d64b2d | ||
|
|
ee1c861eb2 | ||
|
|
6a24ac3f4e | ||
|
|
8b312c610e | ||
|
|
2e3e3a1536 | ||
|
|
5c63d81ffd | ||
|
|
c402c52a8a | ||
|
|
317fedeef9 | ||
|
|
002502a7fa | ||
|
|
1997850d55 | ||
|
|
d346eb0e88 | ||
|
|
7c5fa2180a | ||
|
|
c748c8af4e | ||
|
|
ba59a13149 | ||
|
|
3f4160b138 | ||
|
|
5f4295e399 | ||
|
|
4e730832c7 | ||
|
|
d7c00d4556 | ||
|
|
154b65afe5 | ||
|
|
fcc2a3f64b | ||
|
|
80456861ac | ||
|
|
0c4f016ba2 | ||
|
|
cc5545c333 | ||
|
|
6e28092cdc | ||
|
|
8b6910d505 | ||
|
|
d87e31efaf | ||
|
|
6618a6baf4 | ||
|
|
22b8f5454b | ||
|
|
801bd3f106 | ||
|
|
e26c8c870d | ||
|
|
f4e545e090 | ||
|
|
2006fc0d8d | ||
|
|
d456048e69 | ||
|
|
fd35b4da24 | ||
|
|
283d179b05 | ||
|
|
3093e7334b | ||
|
|
02c82f0038 | ||
|
|
e1eecc3b6c | ||
|
|
bf3d73a5f7 | ||
|
|
05257d6439 | ||
|
|
532e828fe6 | ||
|
|
12a407aca8 | ||
|
|
5ca458dada | ||
|
|
0aeff01c2d | ||
|
|
2ac164d5b8 | ||
|
|
14d475df9a | ||
|
|
f3c777b244 | ||
|
|
1544e58e76 | ||
|
|
5d0c9c7cbe | ||
|
|
8f58fcc4c4 | ||
|
|
5799ef705d | ||
|
|
0de7dde1ce | ||
|
|
7b39e5a79d | ||
|
|
583c791016 | ||
|
|
bc5dcdfe21 | ||
|
|
973aaced96 | ||
|
|
3e9ee908f8 | ||
|
|
e7347b582c | ||
|
|
71b7cffabc | ||
|
|
da9e9ff0be | ||
|
|
c6941d5905 | ||
|
|
a3cf960660 | ||
|
|
7eebfca20b | ||
|
|
e9167d96ec | ||
|
|
1664050ef7 | ||
|
|
7fa00c0e89 | ||
|
|
81d69c41f5 | ||
|
|
4e6b837ccc | ||
|
|
a9f7a23c0d | ||
|
|
07301adc6d | ||
|
|
c6630008b5 | ||
|
|
cdcaba34ce | ||
|
|
09b9a57e38 | ||
|
|
79d7c85832 | ||
|
|
eb0e8366bc | ||
|
|
cf9752db4f | ||
|
|
d6fb3b2c50 | ||
|
|
b04fdc68df | ||
|
|
706c41aad9 | ||
|
|
db9ec0605e | ||
|
|
a95172bf88 | ||
|
|
597116a0b4 | ||
|
|
811341bc2f | ||
|
|
98fa41dcf0 | ||
|
|
b52a0b5d5f | ||
|
|
effeaaa435 | ||
|
|
27fc634bc9 | ||
|
|
69d93f9fee | ||
|
|
00620715da | ||
|
|
1ec8f31253 | ||
|
|
1abde06824 | ||
|
|
b54016653b | ||
|
|
ee74d61f27 | ||
|
|
1f18d3262d | ||
|
|
8ea7707b37 | ||
|
|
7b40df5d6c | ||
|
|
6f98de33f7 | ||
|
|
efe94ed094 | ||
|
|
603b936536 | ||
|
|
d87351eefe | ||
|
|
76e6891f5b | ||
|
|
5040ae2c10 | ||
|
|
87df92e2c2 | ||
|
|
237aff666d | ||
|
|
11645cbf28 | ||
|
|
e297a6a653 | ||
|
|
1eed347ffb | ||
|
|
4aa7a3e181 | ||
|
|
69786d3b49 | ||
|
|
2fb5fb1abb | ||
|
|
a9265cbb39 | ||
|
|
ee2d11ee0a | ||
|
|
632067b552 | ||
|
|
840dc3dfa5 | ||
|
|
4e6f9b539c | ||
|
|
98cce4815d | ||
|
|
b375abc20a | ||
|
|
21cb7ba69c | ||
|
|
be69333eaf | ||
|
|
9a074adb11 | ||
|
|
2df82b2b5e | ||
|
|
d691047884 | ||
|
|
c2e7fde5bc | ||
|
|
7e4d5137f8 | ||
|
|
376afd2ad6 | ||
|
|
e3fcedbec5 | ||
|
|
7c9564bcad | ||
|
|
cfed6cc4e0 | ||
|
|
c7bd1be3e4 | ||
|
|
2161d84701 | ||
|
|
eaeeb3b502 | ||
|
|
dc32a7c70e | ||
|
|
ab0b483fbe | ||
|
|
6e2f90b03c | ||
|
|
5e0806f479 | ||
|
|
dfdfffe5de | ||
|
|
f5e32aed31 | ||
|
|
710c1aeaa8 | ||
|
|
af49cd6cc4 | ||
|
|
ca719e7b5e | ||
|
|
ab4069ed0e | ||
|
|
12bfaa83ba | ||
|
|
1076728241 | ||
|
|
360b984adc | ||
|
|
dcdf7e1d93 | ||
|
|
e8cb167dbf | ||
|
|
0b3418dcbe | ||
|
|
ec7649193c | ||
|
|
2b8a743dc5 | ||
|
|
39f9beda5a | ||
|
|
f0b3c82cfd | ||
|
|
2808edf6d0 | ||
|
|
071263188a | ||
|
|
f9108f24ce | ||
|
|
e98b77a54a | ||
|
|
2636e9cc13 | ||
|
|
944b586f22 | ||
|
|
4f968bbc47 | ||
|
|
d433a039b5 | ||
|
|
b30ea361d3 | ||
|
|
9908518f5b | ||
|
|
364084c819 | ||
|
|
c488906131 | ||
|
|
39c72f0fb0 | ||
|
|
3736d94d73 | ||
|
|
192cec689a | ||
|
|
e895c41bf0 | ||
|
|
997d98c65c | ||
|
|
c9ae5b6ee8 | ||
|
|
a728be4062 | ||
|
|
fce81367fb | ||
|
|
5a4edc0b59 | ||
|
|
713ae5f7f5 | ||
|
|
eaee820216 | ||
|
|
564c5ae164 | ||
|
|
dd8ed6f205 | ||
|
|
ed8c412415 | ||
|
|
12417a6942 | ||
|
|
d05f6c9aaa | ||
|
|
0ca035c547 | ||
|
|
dfd46ccc2c | ||
|
|
f6df3169ca | ||
|
|
5b280898c5 | ||
|
|
2a9e25c788 | ||
|
|
463c03fb6d | ||
|
|
153dad11e1 | ||
|
|
e2d05a44a8 | ||
|
|
30ba55bd4d | ||
|
|
9def9fbef6 | ||
|
|
fa3fd0027c | ||
|
|
7e1b934637 | ||
|
|
33a118d12a | ||
|
|
01f53a168d | ||
|
|
336b8b7dcd | ||
|
|
48d0034239 | ||
|
|
677ef8491e | ||
|
|
6a6119ed1e | ||
|
|
931327d1dc | ||
|
|
858a2d9e2b | ||
|
|
841fb7af41 | ||
|
|
08e65d41c0 | ||
|
|
f76c4dc041 | ||
|
|
f1f8ba2031 | ||
|
|
5ab8d745c0 | ||
|
|
ff62bc89a4 | ||
|
|
838bbdb5ec | ||
|
|
1c36a59b2c | ||
|
|
6730a242db | ||
|
|
e62ae77643 | ||
|
|
f4f39e6a89 | ||
|
|
00bf74cef5 | ||
|
|
c1c45d835f | ||
|
|
bbfe809bef | ||
|
|
e0843ac4f5 | ||
|
|
43741cdad8 | ||
|
|
b8e3807262 | ||
|
|
3304f01a84 | ||
|
|
2ba463235b | ||
|
|
15136fed59 | ||
|
|
6013581dd9 | ||
|
|
7a91f128e9 | ||
|
|
963ffc5550 | ||
|
|
a90991612c | ||
|
|
50ad23b760 | ||
|
|
425dab983b | ||
|
|
a0825e77c4 | ||
|
|
88038a1d5b | ||
|
|
c2c0fdb445 | ||
|
|
dcd5f09d6a | ||
|
|
590b1f36fd | ||
|
|
168ac727f1 | ||
|
|
deb86dd92e | ||
|
|
7ccec4d3b8 | ||
|
|
2f38bcdf26 | ||
|
|
f2785941aa | ||
|
|
ea9f83a443 | ||
|
|
bd6ca7290e | ||
|
|
b85e975fb5 | ||
|
|
b6e504f68c | ||
|
|
5fde6eb484 | ||
|
|
b16989d567 | ||
|
|
c9754ac11a | ||
|
|
f34e4a5115 | ||
|
|
44b3615298 | ||
|
|
211e98307d | ||
|
|
18c303abf6 | ||
|
|
7021a58090 | ||
|
|
320725a587 | ||
|
|
8450edc072 | ||
|
|
a1e2bf2c8d | ||
|
|
82b2f4ec5e | ||
|
|
c92e5d03a7 | ||
|
|
91340c5c84 | ||
|
|
045dd5d027 | ||
|
|
a21b9c4659 | ||
|
|
4b1b869802 | ||
|
|
1ab7e7dfcc | ||
|
|
31c53d2dff | ||
|
|
412a1ae79d | ||
|
|
0b2306ec3d | ||
|
|
242ed3a934 | ||
|
|
70e1ce682a | ||
|
|
7601bf98ee | ||
|
|
c7ec2a0f58 | ||
|
|
6a5e0056a8 | ||
|
|
78d105b46e | ||
|
|
c68d62b8a0 | ||
|
|
df58020f4c | ||
|
|
f052ce05aa | ||
|
|
eedfd9275f | ||
|
|
416af4bec4 | ||
|
|
108d282ddd | ||
|
|
a6103244b0 | ||
|
|
38935c83c5 | ||
|
|
c157ab5752 | ||
|
|
574a93257c | ||
|
|
ba7d8781cf | ||
|
|
3256af8d92 | ||
|
|
86043212f2 | ||
|
|
5d85e88532 | ||
|
|
e2abfd476a | ||
|
|
487febac8e | ||
|
|
a3454e8245 | ||
|
|
bf7567b768 | ||
|
|
ac3450ab32 | ||
|
|
5d034becb8 | ||
|
|
f9397d0db9 | ||
|
|
9005139dc8 | ||
|
|
d93604afa2 | ||
|
|
c4f0b2ece0 | ||
|
|
98a42f612b | ||
|
|
cc75e1318d | ||
|
|
9027cbdf3e | ||
|
|
2119e10ed5 | ||
|
|
87f3eb3c81 | ||
|
|
f9bb8bd585 | ||
|
|
bc47a724af | ||
|
|
f32356801d | ||
|
|
efd677f32b | ||
|
|
3cec27ba57 | ||
|
|
1f810ba04d | ||
|
|
522cf08e61 | ||
|
|
025c01ab13 | ||
|
|
dc41b53d60 | ||
|
|
3b552e00ef | ||
|
|
56e04e7b53 | ||
|
|
deac653a9e | ||
|
|
ed9528834d | ||
|
|
4cecbf1f43 | ||
|
|
b019f4a91f | ||
|
|
34b6ecfbd2 | ||
|
|
4ef9c4c65b | ||
|
|
3276039e41 | ||
|
|
03d1354562 | ||
|
|
964845d7a7 | ||
|
|
3bc5dd8e0f | ||
|
|
17292fd6a5 | ||
|
|
f753659899 | ||
|
|
7412ec3395 | ||
|
|
570c8776c4 | ||
|
|
910db6734b | ||
|
|
9e614026d7 | ||
|
|
b38e7c6433 | ||
|
|
83c8e91955 | ||
|
|
0532dd0440 | ||
|
|
a08615e306 | ||
|
|
f4c5fee17e | ||
|
|
c5aaf65a10 | ||
|
|
951e39da3d | ||
|
|
b693d84d2b | ||
|
|
9bbf6c513b | ||
|
|
50cec92738 | ||
|
|
c212d315f4 | ||
|
|
1861432a53 | ||
|
|
d0c6146429 | ||
|
|
550e6b05a1 | ||
|
|
5b6949170f | ||
|
|
f32bc37794 | ||
|
|
8ee2279b4b | ||
|
|
6339c3b8ad | ||
|
|
7c9274847f | ||
|
|
5d1dddc093 | ||
|
|
04481d7235 | ||
|
|
a3d6cf37cb | ||
|
|
ed10f9d323 | ||
|
|
4b278a0da8 | ||
|
|
1281045648 | ||
|
|
69c42cff90 | ||
|
|
7d56481aba | ||
|
|
91a2604e9e | ||
|
|
a9dc74a520 | ||
|
|
a9cc04d277 | ||
|
|
8cb097f954 | ||
|
|
78f783f0c4 | ||
|
|
d14998ff69 | ||
|
|
ca7ddd9272 | ||
|
|
84dcf6b8a8 | ||
|
|
a59b80ce11 | ||
|
|
007f338823 | ||
|
|
7d34d25497 | ||
|
|
7375ac9d80 | ||
|
|
6af33c7188 | ||
|
|
33737e0f79 | ||
|
|
5afbf67a70 | ||
|
|
580401d2dc | ||
|
|
38b7c63431 | ||
|
|
57d08cd091 | ||
|
|
91e2d31614 | ||
|
|
d375dc7946 | ||
|
|
3fffce2a4a |
No files matched your search
@@ -1,14 +1,14 @@
|
||||
FROM chrislusf/seaweedfs:4.31 AS seaweedfs
|
||||
FROM chrislusf/seaweedfs:4.47 AS seaweedfs
|
||||
|
||||
FROM erlang:28.5.0.1
|
||||
FROM erlang:28.5.0.6
|
||||
|
||||
ARG USERNAME=vscode
|
||||
ARG USER_UID=1000
|
||||
ARG USER_GID=1000
|
||||
ARG NODE_MAJOR=24
|
||||
ARG ELP_VERSION=2026-02-27
|
||||
ARG PNPM_VERSION=10.29.3
|
||||
ARG WASM_BINDGEN_VERSION=0.2.123
|
||||
ARG NODE_MAJOR=26
|
||||
ARG ELP_VERSION=2026-08-10
|
||||
ARG PNPM_VERSION=11.27.0
|
||||
ARG WASM_BINDGEN_VERSION=0.2.128
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
@@ -131,7 +131,8 @@ RUN apt-get update \
|
||||
RUN curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& corepack enable
|
||||
&& npm install -g "pnpm@${PNPM_VERSION}" \
|
||||
&& pnpm --version
|
||||
|
||||
RUN python3 -m pip install --break-system-packages --no-cache-dir awscli
|
||||
|
||||
@@ -167,7 +168,7 @@ RUN ARCH="$(dpkg --print-architecture)" \
|
||||
arm64) ELP_ARCH="aarch64" ;; \
|
||||
*) echo "Unsupported architecture for ELP: $ARCH" >&2; exit 1 ;; \
|
||||
esac \
|
||||
&& curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL "https://github.com/WhatsApp/erlang-language-platform/releases/download/${ELP_VERSION}/elp-linux-${ELP_ARCH}-unknown-linux-gnu-otp-28.tar.gz" -o /tmp/elp.tgz \
|
||||
&& curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL "https://github.com/WhatsApp/erlang-language-platform/releases/download/${ELP_VERSION}/elp-linux-${ELP_ARCH}-unknown-linux-gnu-otp-28.5.tar.gz" -o /tmp/elp.tgz \
|
||||
&& tar -C /usr/local/bin -xzf /tmp/elp.tgz elp \
|
||||
&& chmod +x /usr/local/bin/elp \
|
||||
&& rm /tmp/elp.tgz
|
||||
@@ -194,7 +195,4 @@ RUN curl --retry 5 --retry-delay 2 --retry-all-errors -fsSL https://sh.rustup.rs
|
||||
&& cargo install wasm-bindgen-cli --version "${WASM_BINDGEN_VERSION}" --locked \
|
||||
&& rm -rf "/home/${USERNAME}/.cargo/registry" "/home/${USERNAME}/.cargo/git"
|
||||
|
||||
RUN corepack prepare "pnpm@${PNPM_VERSION}" --activate \
|
||||
&& pnpm --version
|
||||
|
||||
WORKDIR /workspaces/fluxer
|
||||
@@ -14,7 +14,7 @@
|
||||
"DOCKER_HOST": "unix:///var/run/docker.sock"
|
||||
},
|
||||
"runServices": ["workspace", "postgres", "valkey", "nats", "livekit", "meilisearch", "mailpit"],
|
||||
"forwardPorts": [3000, 8088, 8080, 8771, 8082, 8773, 3010, 3020, 8333],
|
||||
"forwardPorts": [3000, 8088, 8080, 8771, 8082, 8773, 3020, 8333],
|
||||
"portsAttributes": {
|
||||
"8088": {
|
||||
"label": "Fluxer dev proxy",
|
||||
@@ -38,7 +38,11 @@
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"erlang.includePaths": ["."],
|
||||
"search.exclude": {
|
||||
"**/_build/default/lib/fluxer_gateway": true
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"biomejs.biome",
|
||||
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
init: true
|
||||
environment:
|
||||
DOCKER_HOST: unix:///var/run/docker.sock
|
||||
npm_config_store_dir: /home/vscode/.local/share/pnpm/store
|
||||
pnpm_config_store_dir: /home/vscode/.local/share/pnpm/store
|
||||
FLUXER_PUBLIC_PORT: "${FLUXER_DEV_PROXY_PORT:-8088}"
|
||||
FLUXER_PUBLIC_URL: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}"
|
||||
FLUXER_API_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/api"
|
||||
@@ -23,13 +23,11 @@ services:
|
||||
FLUXER_S3_PUBLIC_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}"
|
||||
FLUXER_LIVEKIT_URL: "ws://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/livekit"
|
||||
FLUXER_LIVEKIT_INTERNAL_URL: "http://livekit:7880"
|
||||
FLUXER_LIVEKIT_WEBHOOK_URL: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/api/webhooks/livekit"
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/media"
|
||||
FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/media"
|
||||
FLUXER_GATEWAY_STATIC_CDN_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}"
|
||||
FLUXER_ADMIN_OAUTH_REDIRECT_URI: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/admin/oauth2_callback"
|
||||
FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS: "http://localhost,http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}"
|
||||
PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT: "http://localhost:${FLUXER_DEV_PROXY_PORT:-8088}/api"
|
||||
volumes:
|
||||
- ..:/workspaces/fluxer:cached
|
||||
- type: volume
|
||||
@@ -87,11 +85,6 @@ services:
|
||||
target: /workspaces/fluxer/packages/hono/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: package-hono-types-node-modules
|
||||
target: /workspaces/fluxer/packages/hono_types/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: package-i18n-node-modules
|
||||
target: /workspaces/fluxer/packages/i18n/node_modules
|
||||
@@ -167,21 +160,11 @@ services:
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/http_client/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-initialization-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/initialization/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-kv-client-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/kv_client/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-locale-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/locale/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-media-proxy-utils-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/media_proxy_utils/node_modules
|
||||
@@ -202,11 +185,6 @@ services:
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/rate_limit/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-sms-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/sms/node_modules
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: volume
|
||||
source: fluxer-api-virus-scan-node-modules
|
||||
target: /workspaces/fluxer/fluxer_api/pkgs/virus_scan/node_modules
|
||||
@@ -256,7 +234,6 @@ services:
|
||||
- "127.0.0.1:${FLUXER_DEV_GATEWAY_PORT:-8771}:8771"
|
||||
- "127.0.0.1:${FLUXER_DEV_MEDIA_PROXY_PORT:-8082}:8082"
|
||||
- "127.0.0.1:${FLUXER_DEV_APP_PROXY_PORT:-8773}:8773"
|
||||
- "127.0.0.1:${FLUXER_DEV_MARKETING_PORT:-3010}:3010"
|
||||
- "127.0.0.1:${FLUXER_DEV_ADMIN_PORT:-3020}:3020"
|
||||
- "127.0.0.1:${FLUXER_DEV_SEAWEEDFS_S3_PORT:-3900}:8333"
|
||||
depends_on:
|
||||
@@ -293,13 +270,13 @@ services:
|
||||
start_period: 5s
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8.1.7-alpine
|
||||
image: valkey/valkey:9.1.2-alpine
|
||||
command: ["valkey-server", "--save", "", "--appendonly", "no"]
|
||||
ports:
|
||||
- "127.0.0.1:${FLUXER_DEV_VALKEY_PORT:-6379}:6379"
|
||||
|
||||
nats:
|
||||
image: nats:2.14.2-alpine
|
||||
image: nats:2.14.7-alpine
|
||||
command: ["-js", "-sd", "/data", "-m", "8222"]
|
||||
volumes:
|
||||
- nats-data:/data
|
||||
@@ -322,9 +299,10 @@ services:
|
||||
- "127.0.0.1:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}:${FLUXER_DEV_LIVEKIT_UDP_PORT:-7882}/udp"
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:v1.12
|
||||
image: getmeili/meilisearch:v1.53
|
||||
environment:
|
||||
MEILI_NO_ANALYTICS: "true"
|
||||
MEILI_UPGRADE_DB: "true"
|
||||
MEILI_MASTER_KEY: fluxer-dev-meilisearch
|
||||
volumes:
|
||||
- meilisearch-data:/meili_data
|
||||
@@ -338,7 +316,7 @@ services:
|
||||
start_period: 5s
|
||||
|
||||
mailpit:
|
||||
image: axllent/mailpit:v1.30
|
||||
image: axllent/mailpit:v1.31
|
||||
environment:
|
||||
MP_DATABASE: /data/mailpit.db
|
||||
MP_MAX_MESSAGES: 5000
|
||||
@@ -361,7 +339,6 @@ volumes:
|
||||
package-errors-node-modules:
|
||||
package-geo-utils-node-modules:
|
||||
package-hono-node-modules:
|
||||
package-hono-types-node-modules:
|
||||
package-i18n-node-modules:
|
||||
package-instance-bootstrap-node-modules:
|
||||
package-ip-utils-node-modules:
|
||||
@@ -377,14 +354,11 @@ volumes:
|
||||
fluxer-api-email-node-modules:
|
||||
fluxer-api-geoip-node-modules:
|
||||
fluxer-api-http-client-node-modules:
|
||||
fluxer-api-initialization-node-modules:
|
||||
fluxer-api-kv-client-node-modules:
|
||||
fluxer-api-locale-node-modules:
|
||||
fluxer-api-media-proxy-utils-node-modules:
|
||||
fluxer-api-mime-utils-node-modules:
|
||||
fluxer-api-nats-node-modules:
|
||||
fluxer-api-rate-limit-node-modules:
|
||||
fluxer-api-sms-node-modules:
|
||||
fluxer-api-virus-scan-node-modules:
|
||||
fluxer-api-worker-node-modules:
|
||||
fluxer-app-list-utils-node-modules:
|
||||
|
||||
+6
-4
@@ -9,7 +9,6 @@
|
||||
**/.git/**
|
||||
/.github/
|
||||
/.pnpm-store/
|
||||
/fluxer_marketing
|
||||
|
||||
**/.env
|
||||
**/.env.*.local
|
||||
@@ -33,11 +32,15 @@
|
||||
/fluxer_docs/.astro/
|
||||
/fluxer_app/.devserver-cache.json
|
||||
/fluxer_app/pkgs/libfluxcore/
|
||||
/fluxer_app/pkgs/libfluxwebp/
|
||||
/fluxer_app/src/features/i18n/locales/*/messages.mjs
|
||||
/fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
/fluxer_app/src/features/theme/styles/generated/
|
||||
/fluxer_app/src/features/ui/components/SVGMasks.tsx
|
||||
/fluxer_app/src/features/ui/constants/AvatarStatusGeometry.ts
|
||||
/fluxer_static/desktop/spellcheck/dictionaries/*/*
|
||||
!/fluxer_static/desktop/spellcheck/dictionaries/[email protected]/LICENSE
|
||||
!/fluxer_static/desktop/spellcheck/dictionaries/[email protected]/LICENSE
|
||||
/fluxer_gateway/priv/
|
||||
|
||||
/fluxer_media_proxy/fuzz/artifacts/
|
||||
@@ -45,11 +48,10 @@
|
||||
/packages/markdown_parser/rust/fuzz/artifacts/
|
||||
/packages/markdown_parser/rust/fuzz/corpus/
|
||||
|
||||
/fluxer_media_proxy/.benchmark-cache/
|
||||
/fluxer_media_proxy/bench-results/
|
||||
|
||||
/app-dist-output/
|
||||
/artifacts/
|
||||
/desktop-shared-assets/
|
||||
/desktop-modules/
|
||||
/s3_payload/
|
||||
/upload_staging/
|
||||
|
||||
|
||||
@@ -9,6 +9,33 @@ max_line_length = 120
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
|
||||
[*.rs]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 100
|
||||
|
||||
[*.{erl,hrl,app.src,escript}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 96
|
||||
|
||||
[fluxer_gateway/{rebar.config,elvis.config,.erlfmt}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
max_line_length = 96
|
||||
|
||||
[*.{c,h,py}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[fluxer_app/rust/libfluxwebp/**.{c,h}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{md,mdx,proto,tpl}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{yml,yaml,Dockerfile}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
* text=auto
|
||||
fluxer_static/** -text -diff
|
||||
fluxer_static/**/*.md text diff
|
||||
fluxer_static/*.sh text eol=lf diff
|
||||
fluxer_static/*.sha256 text eol=lf diff
|
||||
packages/fonts/files/** -text -diff
|
||||
fluxer_app/src/features/voice/utils/noise_suppression/deepfilternet3/*.wasm -text -diff
|
||||
fluxer_app/src/features/voice/utils/noise_suppression/deepfilternet3/*.tar.gz -text -diff
|
||||
fluxer_app/src/features/i18n/locales/** linguist-generated
|
||||
**/weblate/locales/** linguist-generated
|
||||
fluxer_api/src/api/openapi/openapi.json linguist-generated
|
||||
fluxer_admin/openapi-admin.json linguist-generated
|
||||
@@ -1,7 +1,2 @@
|
||||
/.github/CODEOWNERS @fluxerapp/developers
|
||||
/.github/workflows/ @fluxerapp/developers
|
||||
/fluxer_marketing @fluxerapp/developers
|
||||
/.gitmodules @fluxerapp/developers
|
||||
/.github/workflows/dispatch-private-marketing-build.yaml @fluxerapp/developers
|
||||
/packages/i18n/marketing/ @fluxerapp/developers
|
||||
/scripts/setup-private-marketing.sh @fluxerapp/developers
|
||||
+9
-27
@@ -1,24 +1,24 @@
|
||||
# Contributing to Fluxer
|
||||
|
||||
This policy applies to all issues, discussions, commits and pull requests.
|
||||
This policy applies to all commits and pull requests.
|
||||
|
||||
## Scope
|
||||
|
||||
To prevent spam, only approved contributors may submit pull requests.
|
||||
|
||||
To request approval, comment on an existing issue and ask to implement it. For work that extends beyond a defect fix, open a [discussion](https://github.com/orgs/fluxerapp/discussions) first.
|
||||
To request approval, comment on the [feedback.fluxer.com](https://feedback.fluxer.com) post you want to implement and ask to work on it. For work that extends beyond a defect fix, post a feature request there first.
|
||||
|
||||
Every pull request must:
|
||||
|
||||
- Target the repository's default branch.
|
||||
- Include a closing reference for each repository issue it resolves.
|
||||
- Link each feedback.fluxer.com post it resolves.
|
||||
- Receive approval from a maintainer before it is merged.
|
||||
|
||||
Place each closing reference on a separate line:
|
||||
Place each link on a separate line:
|
||||
|
||||
```text
|
||||
Closes #123
|
||||
Closes #456
|
||||
Resolves https://feedback.fluxer.com/p/123
|
||||
Resolves https://feedback.fluxer.com/p/456
|
||||
```
|
||||
|
||||
## Authorship
|
||||
@@ -78,32 +78,14 @@ Complete every section of the pull request template. Clearly describe:
|
||||
|
||||
## Reports and other contributions
|
||||
|
||||
Use the [bug report form](https://github.com/fluxerapp/fluxer/issues/new?template=bug-report.yaml) to report reproducible defects.
|
||||
Report bugs and request features at [feedback.fluxer.com](https://feedback.fluxer.com).
|
||||
|
||||
Report security vulnerabilities privately through the channels specified in the [security policy](https://github.com/fluxerapp/fluxer/blob/main/.github/SECURITY.md). Do not report vulnerabilities in public issues or discussions.
|
||||
Report security vulnerabilities privately through [fluxer.app/security](https://fluxer.app/security). Never post them publicly.
|
||||
|
||||
Use [discussions](https://github.com/orgs/fluxerapp/discussions) for feature proposals and self-hosting questions.
|
||||
Read the [operator documentation](https://fluxer.dev) for self-hosting questions.
|
||||
|
||||
Submit translations through [Weblate](https://weblate.fluxer.tools), not through pull requests.
|
||||
|
||||
All repository activity is governed by the [Code of Conduct](https://github.com/fluxerapp/fluxer/blob/main/.github/CODE_OF_CONDUCT.md).
|
||||
|
||||
Fluxer is distributed under the [GNU Affero General Public License, version 3.0 or later](https://github.com/fluxerapp/fluxer/blob/main/LICENSE). By adding a DCO sign-off, you certify that you have the right to submit the contribution under that licence.
|
||||
|
||||
## Private marketing project
|
||||
|
||||
The marketing implementation is maintained in a private repository at the `fluxer_marketing` submodule path. The public workspace, bootstrap, checks, and development stack work without initializing it.
|
||||
|
||||
Authorized maintainers can initialize only that submodule and install its independent dependencies:
|
||||
|
||||
```sh
|
||||
./scripts/setup-private-marketing.sh
|
||||
pnpm --dir fluxer_marketing install --frozen-lockfile
|
||||
cargo metadata --locked --manifest-path fluxer_marketing/Cargo.toml
|
||||
```
|
||||
|
||||
To run the private marketing service in the local development stack and direct application links to it, add this override to the ignored `config/env/local.env` file:
|
||||
|
||||
```sh
|
||||
FLUXER_MARKETING_ENDPOINT=http://localhost:8088/marketing
|
||||
```
|
||||
@@ -1,41 +0,0 @@
|
||||
# yaml-language-server: $schema=https://www.schemastore.org/github-discussion.json
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Search existing discussions before posting a feature proposal.
|
||||
|
||||
Report vulnerabilities through the [private form](https://github.com/fluxerapp/fluxer/security/advisories/new) or <[email protected]>.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Current problem
|
||||
description: State what you are trying to do and what prevents it.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed change
|
||||
description: State the expected behaviour.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: notes
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Optional. Include constraints, trade-offs, related discussions, screenshots or mockups.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I searched existing discussions.
|
||||
required: true
|
||||
@@ -16,4 +16,10 @@ Every commit made by a contributor must include the [Developer Certificate of Or
|
||||
|
||||
## Name and marks
|
||||
|
||||
The AGPL does not grant permission to use the Fluxer name, logo or other branding. Forks must use a distinct name and branding unless Fluxer Platform AB grants permission otherwise.
|
||||
Fluxer and the Fluxer logo are trademarks of Fluxer Platform AB. Neither the AGPL nor the CC BY-SA 4.0 licence on Fluxer artwork grants trademark rights. Fluxer Platform AB grants everyone the following permissions.
|
||||
|
||||
- You may distribute unmodified builds of Fluxer, or builds with light patches, under the Fluxer name and logo. Light patches are changes for packaging, portability, security and bug fixes, configuration defaults and translations. Linux distributions, nixpkgs, Flathub and container images are all covered.
|
||||
- A self-hosted instance running such a build may show the Fluxer name and logo under the instance's own name and domain, as long as it does not imply affiliation with or endorsement by Fluxer Platform AB.
|
||||
- You may refer to Fluxer by name to describe compatibility, for example "works with Fluxer".
|
||||
|
||||
Forks with substantive functional changes must use their own name and logo. Any other use needs permission from Fluxer Platform AB. Contact support@fluxer.com.
|
||||
@@ -1,83 +0,0 @@
|
||||
# yaml-language-server: $schema=https://www.schemastore.org/github-issue-forms.json
|
||||
name: Bug report
|
||||
description: Report a reproducible defect in Fluxer.
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Search [open and closed issues](https://github.com/fluxerapp/fluxer/issues?q=is%3Aissue) before filing a report.
|
||||
|
||||
Report vulnerabilities through the [private form](https://github.com/fluxerapp/fluxer/security/advisories/new) or <[email protected]>. Send account and billing requests to <[email protected]>.
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Observed behaviour
|
||||
description: State what happened and what you expected.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: Give numbered steps starting from a fresh app or session.
|
||||
placeholder: |
|
||||
1. Go to ...
|
||||
2. Select ...
|
||||
3. Observe ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: build
|
||||
attributes:
|
||||
label: Build information
|
||||
description: >-
|
||||
Open User Settings, scroll to the bottom of the left sidebar, and select
|
||||
the build information. Fluxer copies it to the clipboard.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: surface
|
||||
attributes:
|
||||
label: Affected surface
|
||||
multiple: true
|
||||
options:
|
||||
- Desktop app
|
||||
- Web app
|
||||
- Voice, video, or Go Live
|
||||
- Self-hosted instance
|
||||
- HTTP API or Gateway
|
||||
- Documentation site
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: instance
|
||||
attributes:
|
||||
label: Instance
|
||||
description: For a self-hosted instance, include the release tag and database backend.
|
||||
placeholder: fluxer.app
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: evidence
|
||||
attributes:
|
||||
label: Evidence
|
||||
description: Attach relevant logs, screenshots or recordings. Remove tokens, keys, private messages and other personal data. Configuration files may contain secrets.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I searched open and closed issues.
|
||||
required: true
|
||||
- label: I removed secrets and unrelated personal data from the report.
|
||||
required: true
|
||||
@@ -1,18 +0,0 @@
|
||||
# yaml-language-server: $schema=https://www.schemastore.org/github-issue-config.json
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Mobile client bugs
|
||||
url: https://github.com/fluxerapp/flutter_client#bug-reporting
|
||||
about: Read the reporting instructions for the Fluxer mobile client.
|
||||
- name: Account and billing support
|
||||
url: https://fluxer.app/help
|
||||
about: Find account help and support contact details.
|
||||
- name: Feature proposals
|
||||
url: https://github.com/orgs/fluxerapp/discussions
|
||||
about: Propose a feature in a discussion.
|
||||
- name: Translations
|
||||
url: https://weblate.fluxer.tools
|
||||
about: Improve an existing locale or start a new one.
|
||||
- name: Self-hosting support
|
||||
url: https://fluxer.dev
|
||||
about: Read the operator documentation, then open a discussion if the problem remains.
|
||||
@@ -1,44 +0,0 @@
|
||||
# yaml-language-server: $schema=https://www.schemastore.org/github-issue-forms.json
|
||||
name: Documentation
|
||||
description: Report incorrect, missing or unclear documentation.
|
||||
type: Task
|
||||
labels:
|
||||
- docs
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
This form covers <https://fluxer.dev> and operator documentation.
|
||||
|
||||
- type: textarea
|
||||
id: issue
|
||||
attributes:
|
||||
label: Documentation defect
|
||||
description: State what the page says and what is correct. For missing content, state what information you needed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: location
|
||||
attributes:
|
||||
label: Location
|
||||
description: Provide the page URL or file path and heading.
|
||||
placeholder: https://fluxer.dev/gateway/overview/
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: suggestion
|
||||
attributes:
|
||||
label: Proposed wording
|
||||
description: Optional.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
id: checks
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I searched open and closed issues.
|
||||
required: true
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
# Security policy
|
||||
|
||||
Do not report a vulnerability in an issue, pull request, or discussion.
|
||||
Do not report a vulnerability in a pull request, on feedback.fluxer.com, in a Fluxer community, or in a direct message to staff.
|
||||
|
||||
Submit a report through [GitHub private vulnerability reporting](https://github.com/fluxerapp/fluxer/security/advisories/new) or email <security@fluxer.com>. Include the affected component, impact, reproduction steps, and supporting evidence. Remove unrelated personal data and secrets.
|
||||
Submit a report through <https://fluxer.app/security> or email <security@fluxer.com>. Include the affected component, impact, reproduction steps, and supporting evidence. Remove unrelated personal data and secrets.
|
||||
|
||||
The programme scope, testing rules, safe harbour, disclosure process, and reward terms are published at <https://fluxer.app/security>. That page is authoritative.
|
||||
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: App Wasm cache
|
||||
description: Restore or save the shared application Wasm artifacts
|
||||
inputs:
|
||||
mode:
|
||||
description: Cache operation (restore or save)
|
||||
required: true
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether the restore found the exact artifact key
|
||||
value: ${{ steps.cache.outputs.cache-hit }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Resolve artifact key
|
||||
id: key
|
||||
if: always()
|
||||
shell: bash
|
||||
run: echo "key=app-wasm-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs', 'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock', 'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**', 'fluxer_app/rust/libfluxwebp/Cargo.toml', 'fluxer_app/rust/libfluxwebp/Cargo.lock', 'fluxer_app/rust/libfluxwebp/src/**', 'fluxer_app/rust/libfluxwebp/shim/**', 'fluxer_app/rust/libfluxwebp/simd/**', 'packages/markdown_parser/rust/Cargo.toml', 'packages/markdown_parser/rust/.cargo/config.toml', 'packages/markdown_parser/rust/src/**') }}" >> "$GITHUB_OUTPUT"
|
||||
- name: Restore artifacts
|
||||
id: cache
|
||||
if: inputs.mode == 'restore'
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/pkgs/libfluxwebp
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
- name: Save artifacts
|
||||
if: always() && inputs.mode == 'save'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/pkgs/libfluxwebp
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: ${{ steps.key.outputs.key }}
|
||||
@@ -0,0 +1,29 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: CI helper
|
||||
description: Restore or build the shared Rust CI helper
|
||||
inputs:
|
||||
save:
|
||||
description: Save a missing helper cache on main
|
||||
required: false
|
||||
default: 'false'
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Restore ci helper
|
||||
id: cache
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.98.1-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**') }}
|
||||
- name: Build ci helper
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: cargo build --locked --package fluxer-ci
|
||||
- name: Save ci helper
|
||||
if: inputs.save == 'true' && github.ref == 'refs/heads/main' && steps.cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: ${{ steps.cache.outputs.cache-primary-key }}
|
||||
+2
-10
@@ -22,20 +22,15 @@ f:docs:
|
||||
f:gateway:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: fluxer_gateway/**/*
|
||||
f:marketing:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- fluxer_marketing
|
||||
- packages/i18n/marketing/**/*
|
||||
f:media_proxy:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: fluxer_media_proxy/**/*
|
||||
f:messages:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: fluxer_messages/**/*
|
||||
f:recon:
|
||||
f:push:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: fluxer_recon/**/*
|
||||
- any-glob-to-any-file: fluxer_push/**/*
|
||||
f:snowflakes:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: fluxer_snowflakes/**/*
|
||||
@@ -72,9 +67,6 @@ p:geo-utils:
|
||||
p:hono:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: packages/hono/**/*
|
||||
p:hono-types:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: packages/hono_types/**/*
|
||||
p:i18n:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file: packages/i18n/**/*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Closes #
|
||||
Resolves https://feedback.fluxer.com/p/
|
||||
|
||||
<!-- Repeat this line for each resolved issue, up to 20. Remove the placeholder only if no issue is resolved and the approval gate does not apply. -->
|
||||
<!-- Repeat this line for each feedback.fluxer.com post this resolves, up to 20. Remove the placeholder only if no post is resolved and the approval gate does not apply. -->
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -12,26 +12,11 @@ on:
|
||||
description: "Path to the Dockerfile to build"
|
||||
type: string
|
||||
required: true
|
||||
context:
|
||||
description: "Docker build context"
|
||||
type: string
|
||||
required: false
|
||||
default: "."
|
||||
build-version:
|
||||
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
moving-tags:
|
||||
description: "Comma-separated moving tags to repoint at this build"
|
||||
type: string
|
||||
required: false
|
||||
default: "v1,latest"
|
||||
extra-build-args:
|
||||
description: "Additional Docker build args, one KEY=VALUE entry per line"
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
@@ -58,13 +43,13 @@ jobs:
|
||||
outputs:
|
||||
build_version: ${{ steps.vars.outputs.build_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
@@ -101,21 +86,21 @@ jobs:
|
||||
- platform: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: resolve source date
|
||||
id: source
|
||||
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
|
||||
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
push: true
|
||||
provenance: mode=min
|
||||
@@ -125,7 +110,6 @@ jobs:
|
||||
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
|
||||
SOURCE_SHA=${{ github.sha }}
|
||||
SOURCE_DATE=${{ steps.source.outputs.date }}
|
||||
${{ inputs.extra-build-args }}
|
||||
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_OWNER }}/${{ inputs.image }}:buildcache-${{ matrix.platform }}
|
||||
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_OWNER }}/${{ inputs.image }}:buildcache-${{ matrix.platform }},mode=max,image-manifest=true,oci-mediatypes=true,ignore-error=true
|
||||
env:
|
||||
@@ -141,15 +125,15 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
toolchain: "1.98.1"
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -190,7 +174,7 @@ jobs:
|
||||
|
||||
- name: Advance moving image tags
|
||||
env:
|
||||
MOVING_TAGS: ${{ inputs.moving-tags }}
|
||||
MOVING_TAGS: v1,latest
|
||||
VERSION: ${{ needs.meta.outputs.build_version }}
|
||||
run: >-
|
||||
tools/ci/run.sh image-set
|
||||
|
||||
@@ -43,13 +43,13 @@ jobs:
|
||||
outputs:
|
||||
build_version: ${{ steps.vars.outputs.build_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: set variables
|
||||
id: vars
|
||||
run: >-
|
||||
@@ -71,20 +71,19 @@ jobs:
|
||||
BUILD_VERSION: ${{ needs.meta.outputs.build_version }}
|
||||
PUBLIC_ASSET_BASE_URL: ""
|
||||
BUNDLE_LOCAL_ASSETS: "true"
|
||||
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED: "false"
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: prepare docker config
|
||||
run: >-
|
||||
tools/ci/run.sh build-app-proxy
|
||||
--step prepare_docker_config
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- name: configure ghcr auth
|
||||
env:
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
@@ -131,19 +130,19 @@ jobs:
|
||||
- platform: arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: resolve source date
|
||||
id: source
|
||||
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
|
||||
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
|
||||
with:
|
||||
context: .
|
||||
file: fluxer_app_proxy/Dockerfile
|
||||
@@ -155,7 +154,7 @@ jobs:
|
||||
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
|
||||
SOURCE_SHA=${{ github.sha }}
|
||||
SOURCE_DATE=${{ steps.source.outputs.date }}
|
||||
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED=false
|
||||
FLUXER_SELF_HOSTED=true
|
||||
APP_ASSETS_REF=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:${{ needs.meta.outputs.build_version }}-assets
|
||||
APP_ASSETS_PLATFORM=linux/amd64
|
||||
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:buildcache-${{ matrix.platform }}
|
||||
@@ -173,15 +172,15 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
toolchain: "1.98.1"
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -43,13 +43,13 @@ jobs:
|
||||
outputs:
|
||||
build_version: ${{ steps.vars.outputs.build_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: set variables
|
||||
id: vars
|
||||
run: >-
|
||||
@@ -67,18 +67,18 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: prepare docker config
|
||||
run: >-
|
||||
tools/ci/run.sh build-app-proxy
|
||||
--step prepare_docker_config
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- name: configure ghcr auth
|
||||
env:
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
@@ -131,13 +131,13 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
- name: resolve source date
|
||||
id: source
|
||||
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
run: >-
|
||||
tools/ci/run.sh build-app-proxy
|
||||
--step prepare_docker_config
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- name: configure ghcr auth
|
||||
env:
|
||||
GHCR_USERNAME: ${{ github.actor }}
|
||||
@@ -178,19 +178,19 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: resolve source date
|
||||
id: source
|
||||
run: echo "date=$(TZ=UTC git log -1 --no-show-signature --pretty=%cd --date=format-local:%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
|
||||
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
|
||||
with:
|
||||
context: .
|
||||
file: fluxer_app_proxy/Dockerfile
|
||||
@@ -200,6 +200,7 @@ jobs:
|
||||
tags: ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy:${{ needs.meta.outputs.build_version }}-arm64
|
||||
build-args: |
|
||||
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
|
||||
PUBLIC_ASSET_BASE_URL=https://fluxerstatic.com
|
||||
SOURCE_SHA=${{ github.sha }}
|
||||
SOURCE_DATE=${{ steps.source.outputs.date }}
|
||||
APP_ASSETS_REF=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy:${{ needs.meta.outputs.build_version }}-assets
|
||||
@@ -219,15 +220,15 @@ jobs:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
toolchain: "1.98.1"
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
name: build desktop modules
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
channel:
|
||||
description: Release channel to ship renderer modules on. The live shell on that channel stays as it is.
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- canary
|
||||
- stable
|
||||
default: canary
|
||||
build_version:
|
||||
description: Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation.
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
shell_contract:
|
||||
description: "Did the shell contract change since the oldest shell these modules still reach? unchanged refuses if it did. compatible keeps older shells on these modules because the renderer still works on them. raise stops sending these modules to shells without the new contract: Linux and managed shells keep their current renderer, Windows and macOS update their shell first."
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- unchanged
|
||||
- compatible
|
||||
- raise
|
||||
default: unchanged
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: desktop-${{ inputs.channel }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
BUILD_CHANNEL: ${{ inputs.channel == 'canary' && 'canary' || 'stable' }}
|
||||
|
||||
jobs:
|
||||
meta:
|
||||
name: Resolve build metadata
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 25
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
version: ${{ steps.meta.outputs.version }}
|
||||
pub_date: ${{ steps.meta.outputs.pub_date }}
|
||||
channel: ${{ steps.meta.outputs.channel }}
|
||||
build_channel: ${{ steps.meta.outputs.build_channel }}
|
||||
source_sha: ${{ steps.meta.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: fluxer
|
||||
permission-contents: read
|
||||
- name: Set metadata
|
||||
id: meta
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
FLUXER_BUILD_VERSION: ${{ inputs.build_version }}
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_metadata
|
||||
--channel "${{ inputs.channel }}"
|
||||
|
||||
shared_assets:
|
||||
name: Build shared renderer assets
|
||||
needs:
|
||||
- meta
|
||||
runs-on: ubuntu-24.04
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.channel }}
|
||||
BUILD_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
PUBLIC_RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout CI helpers
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: _ci
|
||||
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: source
|
||||
|
||||
- name: Set up Rust toolchain (renderer wasm)
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.98.1"
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Set workdir (Unix)
|
||||
env:
|
||||
SUBST_TARGET: ${{ github.workspace }}/source
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_workdir_unix
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 26
|
||||
|
||||
- name: Set up pnpm
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step setup_pnpm
|
||||
|
||||
- name: Resolve pnpm store path (Unix)
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step resolve_pnpm_store_unix
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: ${{ env.PNPM_STORE_PATH }}
|
||||
key: ${{ runner.os }}-shared-renderer-pnpm-store-${{ hashFiles('source/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-shared-renderer-pnpm-store-
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-shared-renderer-cargo-registry-${{ hashFiles('source/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-shared-renderer-cargo-registry-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step install_dependencies
|
||||
|
||||
- name: Update version
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step update_version
|
||||
|
||||
- name: Set build channel
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_build_channel
|
||||
|
||||
- name: Build shared renderer assets
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_shared_assets
|
||||
|
||||
- name: Prepare shared renderer artifact
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step prepare_shared_assets
|
||||
|
||||
- name: Upload shared renderer artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: desktop-shared-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: source/desktop-shared-assets
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
- name: Split renderer into desktop modules
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step split_modules
|
||||
|
||||
- name: Pack desktop modules
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step pack_modules
|
||||
|
||||
- name: Upload desktop module packages
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: |
|
||||
source/desktop-modules/classification.json
|
||||
source/desktop-modules/*/module.json
|
||||
source/desktop-modules/*/package.br
|
||||
source/desktop-modules/*/package.br.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
assemble:
|
||||
name: Assemble the modules-only release
|
||||
if: ${{ !cancelled() && needs.shared_assets.result == 'success' }}
|
||||
needs:
|
||||
- meta
|
||||
- shared_assets
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Download desktop module packages
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: desktop-modules
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: fluxer
|
||||
permission-contents: read
|
||||
- name: Check which shells run these modules
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
SHELL_CONTRACT: ${{ inputs.shell_contract }}
|
||||
DESKTOP_MODULES_ONLY_RELEASE: true
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step check_shell_contract
|
||||
|
||||
- name: Build the modules-only manifests
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_modules_only_manifest
|
||||
|
||||
- name: Prepare GitHub release assets
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step prepare_modules_release_assets
|
||||
|
||||
- name: Upload GitHub release assets
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: fluxer-desktop-modules-release-assets
|
||||
path: release_assets
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
publish_release:
|
||||
name: Publish GitHub modules-only release
|
||||
if: ${{ !cancelled() && needs.assemble.result == 'success' }}
|
||||
needs:
|
||||
- meta
|
||||
- assemble
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Download GitHub release assets
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: fluxer-desktop-modules-release-assets
|
||||
path: release_assets
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: fluxer
|
||||
permission-contents: write
|
||||
- name: Publish GitHub modules-only release
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
RELEASE_BASELINE_SHA: ${{ vars.RELEASE_BASELINE_SHA }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
release_args=(
|
||||
release publish
|
||||
--component "fluxer-desktop-${CHANNEL}"
|
||||
--build-version "${VERSION}"
|
||||
--source-sha "${SOURCE_SHA}"
|
||||
--previous-sha "${RELEASE_BASELINE_SHA}"
|
||||
--asset-dir release_assets
|
||||
)
|
||||
if [[ "${CHANNEL}" == "canary" ]]; then
|
||||
release_args+=(--prerelease)
|
||||
fi
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- "${release_args[@]}"
|
||||
@@ -11,11 +11,6 @@ on:
|
||||
- stable
|
||||
- canary
|
||||
default: stable
|
||||
test_build:
|
||||
description: Stash artifacts under desktop-test/ instead of desktop/ (API will not pick these up as a release).
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
build_version:
|
||||
description: Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation.
|
||||
required: false
|
||||
@@ -26,19 +21,27 @@ on:
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
shell_contract:
|
||||
description: "Did the shell contract change since the oldest shell these modules still reach? unchanged refuses if it did. compatible keeps older shells on these modules because the renderer still works on them. raise stops sending these modules to shells without the new contract: Linux and managed shells keep their current renderer, Windows and macOS update their shell first."
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- unchanged
|
||||
- compatible
|
||||
- raise
|
||||
default: unchanged
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: desktop-${{ inputs.channel }}-${{ inputs.test_build && 'test' || 'release' }}
|
||||
cancel-in-progress: true
|
||||
group: desktop-${{ inputs.channel }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
CHANNEL: ${{ inputs.channel }}
|
||||
BUILD_CHANNEL: ${{ inputs.channel == 'canary' && 'canary' || 'stable' }}
|
||||
TEST_BUILD: ${{ inputs.test_build && 'true' || 'false' }}
|
||||
|
||||
jobs:
|
||||
meta:
|
||||
@@ -53,19 +56,18 @@ jobs:
|
||||
pub_date: ${{ steps.meta.outputs.pub_date }}
|
||||
channel: ${{ steps.meta.outputs.channel }}
|
||||
build_channel: ${{ steps.meta.outputs.build_channel }}
|
||||
test_build: ${{ steps.meta.outputs.test_build }}
|
||||
s3_prefix: ${{ steps.meta.outputs.s3_prefix }}
|
||||
source_sha: ${{ steps.meta.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
@@ -85,7 +87,18 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_metadata
|
||||
--channel "${{ inputs.channel }}"
|
||||
--test-build "${{ inputs.test_build }}"
|
||||
|
||||
- name: Check which shells run these modules
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
CHANNEL: ${{ steps.meta.outputs.build_channel }}
|
||||
SOURCE_SHA: ${{ steps.meta.outputs.source_sha }}
|
||||
VERSION: ${{ steps.meta.outputs.version }}
|
||||
SHELL_CONTRACT: ${{ inputs.shell_contract }}
|
||||
DESKTOP_MODULES_ONLY_RELEASE: false
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step check_shell_contract
|
||||
|
||||
matrix:
|
||||
name: Resolve build matrix
|
||||
@@ -98,12 +111,12 @@ jobs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Build platform matrix
|
||||
id: set-matrix
|
||||
@@ -112,11 +125,150 @@ jobs:
|
||||
--step set_matrix
|
||||
--skip-targets "${{ inputs.skip_targets }}"
|
||||
|
||||
shared_assets:
|
||||
name: Build shared renderer assets
|
||||
needs:
|
||||
- meta
|
||||
runs-on: ubuntu-24.04
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.channel }}
|
||||
BUILD_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
PUBLIC_RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout CI helpers
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: _ci
|
||||
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: source
|
||||
|
||||
- name: Set up Rust toolchain (renderer wasm)
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.98.1"
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Set workdir (Unix)
|
||||
env:
|
||||
SUBST_TARGET: ${{ github.workspace }}/source
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_workdir_unix
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 26
|
||||
|
||||
- name: Set up pnpm
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step setup_pnpm
|
||||
|
||||
- name: Resolve pnpm store path (Unix)
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step resolve_pnpm_store_unix
|
||||
|
||||
- name: Cache pnpm store
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: ${{ env.PNPM_STORE_PATH }}
|
||||
key: ${{ runner.os }}-shared-renderer-pnpm-store-${{ hashFiles('source/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-shared-renderer-pnpm-store-
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-shared-renderer-cargo-registry-${{ hashFiles('source/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-shared-renderer-cargo-registry-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step install_dependencies
|
||||
|
||||
- name: Update version
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step update_version
|
||||
|
||||
- name: Set build channel
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_build_channel
|
||||
|
||||
- name: Build shared renderer assets
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_shared_assets
|
||||
|
||||
- name: Prepare shared renderer artifact
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step prepare_shared_assets
|
||||
|
||||
- name: Upload shared renderer artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: desktop-shared-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: source/desktop-shared-assets
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
- name: Split renderer into desktop modules
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step split_modules
|
||||
|
||||
- name: Pack desktop modules
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step pack_modules
|
||||
|
||||
- name: Upload desktop module packages
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: |
|
||||
source/desktop-modules/classification.json
|
||||
source/desktop-modules/*/module.json
|
||||
source/desktop-modules/*/package.br
|
||||
source/desktop-modules/*/package.br.sha256
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform }} (${{ matrix.arch }}, ${{ matrix.desktop_variant }})
|
||||
name: Build ${{ matrix.platform }} (${{ matrix.arch }})
|
||||
needs:
|
||||
- meta
|
||||
- matrix
|
||||
- shared_assets
|
||||
runs-on: ${{ matrix.os }}
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 180
|
||||
@@ -137,41 +289,35 @@ jobs:
|
||||
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
S3_DESKTOP_PREFIX: ${{ needs.meta.outputs.s3_prefix }}
|
||||
DESKTOP_HANDOFF_PREFIX: _handoff/desktop/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
S3_ENDPOINT: ${{ vars.DOWNLOADS_S3_ENDPOINT }}
|
||||
S3_BUCKET: ${{ vars.DOWNLOADS_S3_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.DOWNLOADS_AWS_ACCESS_KEY_ID || secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
DESKTOP_PLATFORM: ${{ matrix.platform }}
|
||||
DESKTOP_ARCH: ${{ matrix.arch }}
|
||||
DESKTOP_VARIANT: ${{ matrix.desktop_variant }}
|
||||
FLUXER_MODULES: "1"
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
ELECTRON_ARCH: ${{ matrix.electron_arch }}
|
||||
steps:
|
||||
- name: Checkout CI helpers
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: _ci
|
||||
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
path: source
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Set up Python (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Ensure python3 command (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@@ -196,14 +342,14 @@ jobs:
|
||||
--step set_workdir_unix
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
node-version: 26
|
||||
|
||||
- name: Set up pnpm via corepack
|
||||
- name: Set up pnpm
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step setup_pnpm_corepack
|
||||
--step setup_pnpm
|
||||
|
||||
- name: Resolve pnpm store path (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
@@ -247,9 +393,9 @@ jobs:
|
||||
|
||||
- name: Set up Rust toolchain (Unix)
|
||||
if: matrix.platform != 'windows'
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
targets: ${{ matrix.platform == 'macos' && 'aarch64-apple-darwin,x86_64-apple-darwin' || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }}
|
||||
|
||||
- name: Install MSVC ARM64 build tools
|
||||
@@ -260,7 +406,7 @@ jobs:
|
||||
|
||||
- name: Set up MSVC env (Windows)
|
||||
if: matrix.platform == 'windows'
|
||||
uses: TheMrMilchmann/setup-msvc-dev@79dac248aac9d0059f86eae9d8b5bfab4e95e97c
|
||||
uses: TheMrMilchmann/setup-msvc-dev@368ef7d1ee4d1171b31d4a7f67f4d954f903f5a9
|
||||
with:
|
||||
arch: ${{ matrix.arch == 'arm64' && 'amd64_arm64' || 'amd64' }}
|
||||
|
||||
@@ -292,6 +438,17 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step set_build_channel
|
||||
|
||||
- name: Download shared renderer artifact
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: desktop-shared-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: source/desktop-shared-assets
|
||||
|
||||
- name: Restore shared renderer assets
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step restore_shared_assets
|
||||
|
||||
- name: Build Electron main process
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
env:
|
||||
@@ -302,9 +459,9 @@ jobs:
|
||||
|
||||
- name: Set up .NET SDK (Windows)
|
||||
if: matrix.platform == 'windows'
|
||||
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1
|
||||
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
dotnet-version: "10.0.x"
|
||||
|
||||
- name: Install Velopack CLI
|
||||
if: matrix.platform == 'windows'
|
||||
@@ -363,7 +520,7 @@ jobs:
|
||||
|
||||
- name: Azure login for Artifact Signing
|
||||
if: matrix.platform == 'windows'
|
||||
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43
|
||||
uses: azure/login@a641126d1b8aa4d1fa005f4f92df94a3a4c4c906
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
@@ -416,6 +573,16 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step verify_windows_unpacked_signatures
|
||||
|
||||
- name: Verify the packaged shell starts and boots its bundled renderer (Windows)
|
||||
if: matrix.platform == 'windows' && matrix.arch == 'x64'
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
env:
|
||||
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
|
||||
BUILD_VERSION: ${{ env.VERSION }}
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step verify_bundled_renderer_windows
|
||||
|
||||
- name: Create portable ZIP (Windows)
|
||||
if: matrix.platform == 'windows'
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
@@ -456,6 +623,16 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_app_linux
|
||||
|
||||
- name: Verify the packaged shell boots its bundled renderer (Linux)
|
||||
if: matrix.platform == 'linux' && matrix.arch == 'x64'
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
env:
|
||||
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
|
||||
BUILD_VERSION: ${{ env.VERSION }}
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step verify_bundled_renderer_linux
|
||||
|
||||
- name: Verify signed Windows artifacts
|
||||
if: matrix.platform == 'windows'
|
||||
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
|
||||
@@ -477,6 +654,12 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step prepare_artifacts_unix
|
||||
|
||||
- name: Build AppImage update feed (Linux)
|
||||
if: matrix.platform == 'linux'
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_appimage_update_feed
|
||||
|
||||
- name: Normalize updater YAML (macOS)
|
||||
if: matrix.platform == 'macos'
|
||||
run: >-
|
||||
@@ -495,21 +678,79 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step generate_checksums_windows
|
||||
|
||||
- name: Upload artifacts to S3 handoff
|
||||
- name: Stage build artifacts
|
||||
id: handoff
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
|
||||
--step upload_handoff
|
||||
--step stage_handoff
|
||||
|
||||
upload:
|
||||
name: Upload to S3
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: ${{ steps.handoff.outputs.artifact_name }}
|
||||
path: upload_staging
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
verify_windows_arm64:
|
||||
name: Verify windows (arm64)
|
||||
if: ${{ !cancelled() && needs.build.result == 'success' }}
|
||||
needs:
|
||||
- meta
|
||||
- build
|
||||
runs-on: windows-11-arm
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
sparse-checkout: fluxer_desktop/scripts
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Download windows arm64 build artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: fluxer-desktop-${{ needs.meta.outputs.build_channel }}-windows-arm64*
|
||||
|
||||
- name: Verify the packaged shell starts and boots its bundled renderer
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$zip = Get-ChildItem -Path artifacts -Recurse -Filter "*-$env:VERSION-portable-win-arm64.zip" | Select-Object -First 1
|
||||
if ($null -eq $zip) { Write-Host 'This build has no windows arm64 portable zip'; exit 0 }
|
||||
$app = Join-Path $env:RUNNER_TEMP 'app'
|
||||
Expand-Archive -Path $zip.FullName -DestinationPath $app
|
||||
$exe = Get-ChildItem -Path $app -Recurse -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||
if ($null -eq $exe) { throw "$($zip.Name) holds no Fluxer executable" }
|
||||
node fluxer_desktop/scripts/release-check.mjs --app $exe.DirectoryName --channel $env:CHANNEL --expect-renderer $env:VERSION --expect-source bundled --package-origin http://127.0.0.1:9 --app-arg=--disable-gpu --workdir (Join-Path $env:RUNNER_TEMP 'runs') --timeout-seconds 240
|
||||
if ($LASTEXITCODE -ne 0) { throw "The windows arm64 build fails its release check" }
|
||||
|
||||
upload:
|
||||
name: Assemble desktop release assets
|
||||
if: ${{ !cancelled() && needs.build.result == 'success' && needs.verify_windows_arm64.result == 'success' }}
|
||||
needs:
|
||||
- meta
|
||||
- shared_assets
|
||||
- build
|
||||
- verify_windows_arm64
|
||||
runs-on: ubuntu-24.04-arm
|
||||
environment: desktop-releases
|
||||
timeout-minutes: 180
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
@@ -520,34 +761,26 @@ jobs:
|
||||
BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
|
||||
TEST_BUILD: ${{ needs.meta.outputs.test_build }}
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
S3_DESKTOP_PREFIX: ${{ needs.meta.outputs.s3_prefix }}
|
||||
DESKTOP_HANDOFF_PREFIX: _handoff/desktop/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
DESKTOP_RELEASE_ASSETS_PREFIX: _handoff/desktop-release-assets/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
DESKTOP_METADATA_PREFIX: _handoff/desktop-metadata/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
S3_ENDPOINT: ${{ vars.DOWNLOADS_S3_ENDPOINT }}
|
||||
S3_BUCKET: ${{ vars.DOWNLOADS_S3_BUCKET }}
|
||||
PUBLIC_DL_BASE: https://api.fluxer.app/dl
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.DOWNLOADS_AWS_ACCESS_KEY_ID || secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Download S3 handoff artifacts
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step download_handoff
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: fluxer-desktop-${{ needs.meta.outputs.build_channel }}-*
|
||||
|
||||
- name: Build S3 payload layout (+ manifest.json)
|
||||
- name: Build payload layout (+ manifest.json)
|
||||
env:
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
|
||||
@@ -555,43 +788,57 @@ jobs:
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_payload
|
||||
|
||||
- name: Download desktop module packages
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }}
|
||||
path: desktop-modules
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: fluxer
|
||||
permission-contents: read
|
||||
- name: Check which shells run these modules
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
SHELL_CONTRACT: ${{ inputs.shell_contract }}
|
||||
DESKTOP_MODULES_ONLY_RELEASE: false
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step check_shell_contract
|
||||
|
||||
- name: Build desktop module manifest
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_module_manifest
|
||||
|
||||
- name: Prepare GitHub release assets
|
||||
if: needs.meta.outputs.test_build != 'true'
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step prepare_release_assets
|
||||
|
||||
- name: Publish GitHub release descriptor
|
||||
if: needs.meta.outputs.test_build != 'true'
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step publish_release_descriptor
|
||||
|
||||
- name: Upload payload to S3
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step upload_payload
|
||||
|
||||
- name: Upload GitHub release asset handoff
|
||||
if: needs.meta.outputs.test_build != 'true'
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step upload_release_assets
|
||||
- name: Upload GitHub release assets
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: fluxer-desktop-release-assets
|
||||
path: release_assets
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
compression-level: 0
|
||||
|
||||
- name: Build summary
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step build_summary
|
||||
|
||||
- name: Cleanup S3 handoff
|
||||
if: ${{ success() }}
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step cleanup_handoff
|
||||
|
||||
publish_release:
|
||||
name: Publish GitHub desktop release
|
||||
if: ${{ !cancelled() && needs.upload.result == 'success' && needs.meta.outputs.test_build != 'true' }}
|
||||
if: ${{ !cancelled() && needs.upload.result == 'success' }}
|
||||
needs:
|
||||
- meta
|
||||
- upload
|
||||
@@ -603,28 +850,22 @@ jobs:
|
||||
env:
|
||||
CHANNEL: ${{ needs.meta.outputs.build_channel }}
|
||||
VERSION: ${{ needs.meta.outputs.version }}
|
||||
S3_DESKTOP_PREFIX: ${{ needs.meta.outputs.s3_prefix }}
|
||||
DESKTOP_RELEASE_ASSETS_PREFIX: _handoff/desktop-release-assets/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
DESKTOP_METADATA_PREFIX: _handoff/desktop-metadata/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
|
||||
S3_ENDPOINT: ${{ vars.DOWNLOADS_S3_ENDPOINT }}
|
||||
S3_BUCKET: ${{ vars.DOWNLOADS_S3_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.DOWNLOADS_AWS_ACCESS_KEY_ID || secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOWNLOADS_AWS_SECRET_ACCESS_KEY || secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
ref: ${{ needs.meta.outputs.source_sha }}
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Download GitHub release assets
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step download_release_assets
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||
with:
|
||||
name: fluxer-desktop-release-assets
|
||||
path: release_assets
|
||||
|
||||
- name: Create token
|
||||
id: create-token
|
||||
@@ -656,15 +897,3 @@ jobs:
|
||||
release_args+=(--prerelease)
|
||||
fi
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- "${release_args[@]}"
|
||||
|
||||
- name: Publish GitHub release readiness marker
|
||||
env:
|
||||
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step publish_release_marker
|
||||
|
||||
- name: Publish payload metadata to S3
|
||||
run: >-
|
||||
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
|
||||
--step publish_payload_metadata
|
||||
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: build push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-version:
|
||||
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
name: approve build release
|
||||
permissions: {}
|
||||
runs-on: ubuntu-24.04
|
||||
environment: builds
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: approved
|
||||
run: echo "Build release approved."
|
||||
|
||||
image:
|
||||
needs: approve
|
||||
uses: ./.github/workflows/_build-image.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image: fluxer-push
|
||||
dockerfile: fluxer_push/Dockerfile
|
||||
build-version: ${{ inputs['build-version'] }}
|
||||
@@ -1,36 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: build recon
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build-version:
|
||||
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
name: approve build release
|
||||
permissions: {}
|
||||
runs-on: ubuntu-24.04
|
||||
environment: builds
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: approved
|
||||
run: echo "Build release approved."
|
||||
|
||||
image:
|
||||
needs: approve
|
||||
uses: ./.github/workflows/_build-image.yaml
|
||||
secrets: inherit
|
||||
with:
|
||||
image: fluxer-recon
|
||||
dockerfile: fluxer_recon/Dockerfile
|
||||
build-version: ${{ inputs['build-version'] }}
|
||||
@@ -19,22 +19,22 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout fluxer
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
name: desktop windows release check
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
channel:
|
||||
description: Release channel
|
||||
type: choice
|
||||
options:
|
||||
- canary
|
||||
- stable
|
||||
default: canary
|
||||
version:
|
||||
description: Published version to check
|
||||
required: true
|
||||
type: string
|
||||
expect_failure:
|
||||
description: Pass only when the published build fails to start
|
||||
type: boolean
|
||||
default: false
|
||||
update_from:
|
||||
description: Comma-separated published versions to install with Setup and update to the version above from the live feed
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
stale_apply_record:
|
||||
description: Before each update, leave behind an hour-old record of an update that never installed (passes only when the installed versions retry it)
|
||||
type: boolean
|
||||
default: false
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/desktop-windows-release-check.yaml
|
||||
- fluxer_desktop/scripts/release-check.mjs
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: desktop-windows-release-check-${{ github.ref }}-${{ inputs.version || 'pr' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check windows (${{ matrix.arch }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- arch: x64
|
||||
os: windows-2025
|
||||
- arch: arm64
|
||||
os: windows-11-arm
|
||||
env:
|
||||
CHANNEL: ${{ inputs.channel || 'canary' }}
|
||||
VERSION: ${{ inputs.version || '2026.1009.20411' }}
|
||||
EXPECT_FAILURE: ${{ github.event_name == 'pull_request' && 'true' || inputs.expect_failure }}
|
||||
UPDATE_FROM: ${{ inputs.update_from || '' }}
|
||||
STALE_APPLY_RECORD: ${{ inputs.stale_apply_record && 'true' || 'false' }}
|
||||
ARCH: ${{ matrix.arch }}
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
sparse-checkout: fluxer_desktop/scripts
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Check the portable build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$work = Join-Path $env:RUNNER_TEMP 'portable'
|
||||
New-Item -ItemType Directory -Force -Path $work | Out-Null
|
||||
$zip = Join-Path $work 'portable.zip'
|
||||
Invoke-WebRequest -Uri "https://pkgs.fluxer.com/desktop/$env:CHANNEL/win32/$env:ARCH/$env:VERSION/portable" -OutFile $zip
|
||||
Expand-Archive -Path $zip -DestinationPath (Join-Path $work 'app')
|
||||
$exe = Get-ChildItem -Path (Join-Path $work 'app') -Recurse -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||
if ($null -eq $exe) { throw "The portable build holds no Fluxer executable" }
|
||||
$output = & node fluxer_desktop/scripts/release-check.mjs --app $exe.DirectoryName --channel $env:CHANNEL --expect-renderer $env:VERSION --expect-source bundled --app-arg=--disable-gpu --workdir (Join-Path $work 'runs') --timeout-seconds 240 2>&1
|
||||
$code = $LASTEXITCODE
|
||||
$output | ForEach-Object { Write-Host $_ }
|
||||
if ($env:EXPECT_FAILURE -eq 'true') {
|
||||
if ($code -eq 0) { throw "Expected $env:VERSION to fail its release check, it passed" }
|
||||
Write-Host "$env:VERSION fails its release check on windows $env:ARCH as expected"
|
||||
exit 0
|
||||
}
|
||||
if ($code -ne 0) { throw "The release check failed for $env:VERSION on windows $env:ARCH" }
|
||||
|
||||
- name: Update installed builds from the live feed
|
||||
if: env.UPDATE_FROM != ''
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$channelDirs = @{ canary = 'fluxer_desktop_canary'; stable = 'fluxer_desktop' }
|
||||
$dataDirs = @{ canary = 'fluxercanary'; stable = 'fluxer' }
|
||||
$installRoot = Join-Path $env:LOCALAPPDATA $channelDirs[$env:CHANNEL]
|
||||
$logPath = Join-Path (Join-Path $env:APPDATA $dataDirs[$env:CHANNEL]) 'logs\main.log'
|
||||
$failures = @()
|
||||
foreach ($from in ($env:UPDATE_FROM -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
|
||||
Write-Host "== install $from, then update to $env:VERSION"
|
||||
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $installRoot) { Remove-Item -Recurse -Force $installRoot }
|
||||
Remove-Item -Recurse -Force (Split-Path (Split-Path $logPath -Parent) -Parent) -ErrorAction SilentlyContinue
|
||||
$setup = Join-Path $env:RUNNER_TEMP "setup-$from.exe"
|
||||
Invoke-WebRequest -Uri "https://pkgs.fluxer.com/desktop/$env:CHANNEL/win32/$env:ARCH/$from/setup" -OutFile $setup
|
||||
Start-Process -FilePath $setup -ArgumentList '--silent' -Wait
|
||||
Start-Sleep -Seconds 5
|
||||
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
$exe = Get-ChildItem -Path (Join-Path $installRoot 'current') -Filter 'Fluxer*.exe' | Where-Object { $_.Name -notlike '*_ExecutionStub.exe' } | Select-Object -First 1
|
||||
if ($null -eq $exe) { $failures += "$from did not install"; continue }
|
||||
if ($env:STALE_APPLY_RECORD -eq 'true') {
|
||||
$dataRoot = Split-Path (Split-Path $logPath -Parent) -Parent
|
||||
New-Item -ItemType Directory -Force -Path $dataRoot | Out-Null
|
||||
$attemptedAt = [DateTimeOffset]::UtcNow.AddHours(-1).ToUnixTimeMilliseconds()
|
||||
Set-Content -Path (Join-Path $dataRoot 'update-apply-state.json') -Value "{`"version`":`"$env:VERSION`",`"attemptedAt`":$attemptedAt}" -NoNewline
|
||||
Write-Host "left a stale apply record for $env:VERSION"
|
||||
}
|
||||
Start-Process -FilePath $exe.FullName -ArgumentList '--disable-gpu'
|
||||
$deadline = (Get-Date).AddMinutes(8)
|
||||
$reported = $null
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
Start-Sleep -Seconds 10
|
||||
if (Test-Path $logPath) {
|
||||
$line = Select-String -Path $logPath -Pattern "The renderer reported its build: .*Desktop $([regex]::Escape($env:VERSION)), Web $([regex]::Escape($env:VERSION))" | Select-Object -Last 1
|
||||
if ($null -ne $line) { $reported = $line.Line; break }
|
||||
}
|
||||
}
|
||||
$installed = Get-Content (Join-Path $installRoot 'current\sq.version') -Raw -ErrorAction SilentlyContinue
|
||||
Write-Host "installed package after update: $installed"
|
||||
$logs = Join-Path $env:RUNNER_TEMP "logs\$from"
|
||||
New-Item -ItemType Directory -Force -Path $logs | Out-Null
|
||||
if (Test-Path $logPath) { Copy-Item $logPath $logs }
|
||||
Get-ChildItem -Path $installRoot -Filter '*.log' -ErrorAction SilentlyContinue | Copy-Item -Destination $logs
|
||||
if (Test-Path $logPath) { Get-Content $logPath | Select-String -Pattern 'Bootstrap|ShellSelfUpdate|Velopack|velopack|NativeModulePreflight|reported its build' | Select-Object -Last 40 | ForEach-Object { Write-Host $_.Line } }
|
||||
if ($null -eq $reported) { $failures += "$from did not reach Desktop and Web $env:VERSION" } else { Write-Host "$from updated: $reported" }
|
||||
Get-Process | Where-Object { $_.Path -like "$installRoot*" } | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($failures.Count -gt 0) { throw ($failures -join "`n") }
|
||||
|
||||
- name: Upload update logs
|
||||
if: always() && env.UPDATE_FROM != ''
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
with:
|
||||
name: update-logs-${{ matrix.arch }}
|
||||
path: ${{ runner.temp }}/logs
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
@@ -1,230 +0,0 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: Dispatch private marketing build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- fluxer_marketing
|
||||
- Cargo.toml
|
||||
- fluxer_common/**
|
||||
- packages/fonts/manifest.json
|
||||
- packages/fonts/NOTICE.md
|
||||
- packages/fonts/LICENSE-IBM-PLEX.txt
|
||||
- packages/fonts/css/locale-fallbacks.css
|
||||
- packages/fonts/files/FluxerSans/**
|
||||
- packages/fonts/files/FluxerMono/**
|
||||
- packages/fonts/marketing/**
|
||||
- packages/i18n/marketing/**
|
||||
- fluxer_static/marketing/branding/**
|
||||
- .github/workflows/dispatch-private-marketing-build.yaml
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: private-marketing-dispatch
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
metadata:
|
||||
name: resolve exact private build metadata
|
||||
if: github.repository == 'fluxerapp/fluxer'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
parent_sha: ${{ steps.inputs.outputs.parent_sha }}
|
||||
gitlink_sha: ${{ steps.inputs.outputs.gitlink_sha }}
|
||||
build_version: ${{ steps.inputs.outputs.build_version }}
|
||||
correlation_id: ${{ steps.inputs.outputs.correlation_id }}
|
||||
steps:
|
||||
- name: Create token
|
||||
id: create-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: fluxer
|
||||
permission-contents: read
|
||||
- name: Resolve trusted build inputs
|
||||
id: inputs
|
||||
env:
|
||||
EVENT_AFTER: ${{ github.event.after }}
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
PARENT_SHA: ${{ github.sha }}
|
||||
PUBLIC_REPOSITORY: ${{ github.repository }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
RUN_ATTEMPT: ${{ github.run_attempt }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$GITHUB_EVENT_NAME" == "push" ]]
|
||||
[[ "$GITHUB_REF" == "refs/heads/main" ]]
|
||||
[[ "$PUBLIC_REPOSITORY" == "fluxerapp/fluxer" ]]
|
||||
[[ "$PARENT_SHA" =~ ^[0-9a-f]{40}$ ]]
|
||||
[[ "$EVENT_AFTER" == "$PARENT_SHA" ]]
|
||||
[[ "$RUN_ID" =~ ^[1-9][0-9]*$ ]]
|
||||
[[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]]
|
||||
(( 10#$RUN_ATTEMPT <= 10 ))
|
||||
|
||||
main_sha="$(gh api "repos/$PUBLIC_REPOSITORY/git/ref/heads/main" --jq .object.sha)"
|
||||
[[ "$main_sha" =~ ^[0-9a-f]{40}$ ]]
|
||||
main_comparison="$(gh api "repos/$PUBLIC_REPOSITORY/compare/$PARENT_SHA...$main_sha")"
|
||||
main_status="$(jq -r .status <<<"$main_comparison")"
|
||||
[[ "$main_status" == "identical" || "$main_status" == "ahead" ]]
|
||||
[[ "$(jq -r .merge_base_commit.sha <<<"$main_comparison")" == "$PARENT_SHA" ]]
|
||||
|
||||
commit="$(gh api "repos/$PUBLIC_REPOSITORY/git/commits/$PARENT_SHA")"
|
||||
[[ "$(jq -r .sha <<<"$commit")" == "$PARENT_SHA" ]]
|
||||
tree_sha="$(jq -r .tree.sha <<<"$commit")"
|
||||
[[ "$tree_sha" =~ ^[0-9a-f]{40}$ ]]
|
||||
entry="$(
|
||||
gh api "repos/$PUBLIC_REPOSITORY/git/trees/$tree_sha" |
|
||||
jq -cer '[.tree[] | select(.path == "fluxer_marketing")] | if length == 1 then .[0] else error("expected exactly one marketing gitlink") end'
|
||||
)"
|
||||
mode="$(jq -r .mode <<<"$entry")"
|
||||
type="$(jq -r .type <<<"$entry")"
|
||||
gitlink_sha="$(jq -r .sha <<<"$entry")"
|
||||
path="$(jq -r .path <<<"$entry")"
|
||||
if [[ "$mode" != "160000" || "$type" != "commit" || "$path" != "fluxer_marketing" || ! "$gitlink_sha" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "::error::Public parent does not contain a valid fluxer_marketing gitlink."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
run="$(gh api "repos/$PUBLIC_REPOSITORY/actions/runs/$RUN_ID")"
|
||||
[[ "$(jq -r .id <<<"$run")" == "$RUN_ID" ]]
|
||||
[[ "$(jq -r .run_attempt <<<"$run")" == "$RUN_ATTEMPT" ]]
|
||||
[[ "$(jq -r .event <<<"$run")" == "push" ]]
|
||||
[[ "$(jq -r .head_sha <<<"$run")" == "$PARENT_SHA" ]]
|
||||
run_created_at="$(jq -r .created_at <<<"$run")"
|
||||
[[ "$run_created_at" =~ ^[1-9][0-9]{3}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]]
|
||||
run_created_epoch="$(date -u -d "$run_created_at" +%s)"
|
||||
[[ "$run_created_epoch" =~ ^[1-9][0-9]*$ ]]
|
||||
build_epoch=$((run_created_epoch + 10#$RUN_ATTEMPT - 1))
|
||||
read -r year month day time_segment <<<"$(date -u -d "@$build_epoch" '+%Y %m %d %H%M%S')"
|
||||
month="$((10#$month))"
|
||||
micro="$((10#$time_segment))"
|
||||
build_version="$year.$month$day.$micro"
|
||||
[[ "$build_version" =~ ^[1-9][0-9]{3}\.[1-9][0-9]{2,3}\.([0-9]|[1-9][0-9]{0,5})$ ]]
|
||||
correlation_id="public-${RUN_ID}-${RUN_ATTEMPT}"
|
||||
[[ "$correlation_id" =~ ^[A-Za-z0-9._:-]{1,64}$ ]]
|
||||
{
|
||||
echo "parent_sha=$PARENT_SHA"
|
||||
echo "gitlink_sha=$gitlink_sha"
|
||||
echo "build_version=$build_version"
|
||||
echo "correlation_id=$correlation_id"
|
||||
} >>"$GITHUB_OUTPUT"
|
||||
|
||||
dispatch:
|
||||
name: dispatch exact private build
|
||||
needs: metadata
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 65
|
||||
environment: private-marketing-dispatch
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Validate trusted build inputs
|
||||
env:
|
||||
DISPATCH_ENABLED: ${{ vars.MARKETING_DISPATCH_ENABLED }}
|
||||
EXPECTED_PARENT_SHA: ${{ github.sha }}
|
||||
EXPECTED_CORRELATION_ID: public-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
PARENT_SHA: ${{ needs.metadata.outputs.parent_sha }}
|
||||
GITLINK_SHA: ${{ needs.metadata.outputs.gitlink_sha }}
|
||||
BUILD_VERSION: ${{ needs.metadata.outputs.build_version }}
|
||||
CORRELATION_ID: ${{ needs.metadata.outputs.correlation_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ "$GITHUB_EVENT_NAME" == "push" ]]
|
||||
[[ "$GITHUB_REF" == "refs/heads/main" ]]
|
||||
[[ "$GITHUB_REPOSITORY" == "fluxerapp/fluxer" ]]
|
||||
[[ "$PARENT_SHA" == "$EXPECTED_PARENT_SHA" ]]
|
||||
[[ "$PARENT_SHA" =~ ^[0-9a-f]{40}$ ]]
|
||||
[[ "$GITLINK_SHA" =~ ^[0-9a-f]{40}$ ]]
|
||||
[[ "$BUILD_VERSION" =~ ^[1-9][0-9]{3}\.[1-9][0-9]{2,3}\.([0-9]|[1-9][0-9]{0,5})$ ]]
|
||||
[[ "$CORRELATION_ID" == "$EXPECTED_CORRELATION_ID" ]]
|
||||
[[ "$CORRELATION_ID" =~ ^[A-Za-z0-9._:-]{1,64}$ ]]
|
||||
if [[ "$DISPATCH_ENABLED" != "true" ]]; then
|
||||
echo "::error::Private marketing dispatch is intentionally disabled until the package cutover guard completes."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Create private dispatch token
|
||||
id: private-token
|
||||
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
|
||||
with:
|
||||
client-id: ${{ vars.FLUXER_CI_APP_ID }}
|
||||
private-key: ${{ secrets.FLUXER_CI_APP_KEY }}
|
||||
owner: fluxerapp
|
||||
repositories: marketing
|
||||
permission-actions: write
|
||||
|
||||
- name: Dispatch exact private build
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.private-token.outputs.token }}
|
||||
PARENT_SHA: ${{ needs.metadata.outputs.parent_sha }}
|
||||
GITLINK_SHA: ${{ needs.metadata.outputs.gitlink_sha }}
|
||||
BUILD_VERSION: ${{ needs.metadata.outputs.build_version }}
|
||||
CORRELATION_ID: ${{ needs.metadata.outputs.correlation_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh api --method POST repos/fluxerapp/marketing/actions/workflows/build-marketing.yaml/dispatches \
|
||||
--field ref=main \
|
||||
--field "inputs[parent_sha]=$PARENT_SHA" \
|
||||
--field "inputs[gitlink_sha]=$GITLINK_SHA" \
|
||||
--field "inputs[build_version]=$BUILD_VERSION" \
|
||||
--field "inputs[correlation_id]=$CORRELATION_ID"
|
||||
|
||||
- name: Wait for private build conclusion
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.private-token.outputs.token }}
|
||||
PARENT_SHA: ${{ needs.metadata.outputs.parent_sha }}
|
||||
GITLINK_SHA: ${{ needs.metadata.outputs.gitlink_sha }}
|
||||
BUILD_VERSION: ${{ needs.metadata.outputs.build_version }}
|
||||
CORRELATION_ID: ${{ needs.metadata.outputs.correlation_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expected_title="marketing-build correlation=$CORRELATION_ID parent=$PARENT_SHA gitlink=$GITLINK_SHA version=$BUILD_VERSION"
|
||||
deadline=$((SECONDS + 3600))
|
||||
run_id=""
|
||||
while (( SECONDS < deadline )); do
|
||||
runs="$(gh api "repos/fluxerapp/marketing/actions/workflows/build-marketing.yaml/runs?event=workflow_dispatch&per_page=100" --jq '[.workflow_runs[] | {id, event, display_title, status, conclusion}]')"
|
||||
matches="$(jq --arg title "$expected_title" '[.[] | select(.event == "workflow_dispatch" and .display_title == $title)]' <<<"$runs")"
|
||||
count="$(jq 'length' <<<"$matches")"
|
||||
if [[ "$count" == "1" ]]; then
|
||||
run_id="$(jq -r '.[0].id' <<<"$matches")"
|
||||
break
|
||||
fi
|
||||
if [[ "$count" != "0" ]]; then
|
||||
echo "::error::Private build correlation matched multiple workflow runs."
|
||||
exit 1
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
if [[ -z "$run_id" ]]; then
|
||||
echo "::error::Timed out waiting for the private build dispatch to appear."
|
||||
exit 1
|
||||
fi
|
||||
while (( SECONDS < deadline )); do
|
||||
runs="$(gh api "repos/fluxerapp/marketing/actions/workflows/build-marketing.yaml/runs?event=workflow_dispatch&per_page=100" --jq '[.workflow_runs[] | {id, event, display_title, status, conclusion}]')"
|
||||
matches="$(jq --arg title "$expected_title" '[.[] | select(.event == "workflow_dispatch" and .display_title == $title)]' <<<"$runs")"
|
||||
if [[ "$(jq 'length' <<<"$matches")" != "1" || "$(jq -r '.[0].id' <<<"$matches")" != "$run_id" ]]; then
|
||||
echo "::error::Private build correlation is missing or ambiguous."
|
||||
exit 1
|
||||
fi
|
||||
run="$(jq '.[0]' <<<"$matches")"
|
||||
status="$(jq -r '.status' <<<"$run")"
|
||||
conclusion="$(jq -r '.conclusion // empty' <<<"$run")"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
echo "::error::Private marketing build concluded with $conclusion."
|
||||
exit 1
|
||||
fi
|
||||
echo "Private marketing build completed successfully."
|
||||
exit 0
|
||||
fi
|
||||
sleep 15
|
||||
done
|
||||
echo "::error::Timed out waiting for the private marketing build."
|
||||
exit 1
|
||||
@@ -0,0 +1,183 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
name: Helm chart
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- deploy/helm/**
|
||||
- deploy/self-hosting/Caddyfile
|
||||
- .github/workflows/helm.yaml
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- deploy/helm/**
|
||||
- deploy/self-hosting/Caddyfile
|
||||
- .github/workflows/helm.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
render:
|
||||
name: Lint and render with Helm ${{ matrix.helm }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- helm: v3.19.0
|
||||
sha256: a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496
|
||||
- helm: v4.2.4
|
||||
sha256: c306b46f719b0a4da32d0f78ee21bf90ce8d602f15b22ab753f0674d1670a7f3
|
||||
env:
|
||||
CHART: deploy/helm/fluxer
|
||||
KUBECONFORM_VERSION: v0.7.0
|
||||
KUBECONFORM_SHA256: c31518ddd122663b3f3aa874cfe8178cb0988de944f29c74a0b9260920d115d3
|
||||
CRD_CATALOG: https://raw.githubusercontent.com/datreeio/CRDs-catalog/63669a570e231d4f1f8396d229a1de512bcf0a34/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json
|
||||
steps:
|
||||
- name: Checkout fluxer
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Helm and kubeconform
|
||||
env:
|
||||
HELM_VERSION: ${{ matrix.helm }}
|
||||
HELM_SHA256: ${{ matrix.sha256 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
cd "$RUNNER_TEMP"
|
||||
curl -fsSLo helm.tgz "https://get.helm.sh/helm-${HELM_VERSION}-linux-amd64.tar.gz"
|
||||
echo "${HELM_SHA256} helm.tgz" | sha256sum -c -
|
||||
tar -xzf helm.tgz linux-amd64/helm
|
||||
mv linux-amd64/helm bin/helm
|
||||
curl -fsSLo kubeconform.tgz "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz"
|
||||
echo "${KUBECONFORM_SHA256} kubeconform.tgz" | sha256sum -c -
|
||||
tar -xzf kubeconform.tgz kubeconform
|
||||
mv kubeconform bin/kubeconform
|
||||
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Lint, render and validate every CI values file
|
||||
run: |
|
||||
set -euo pipefail
|
||||
command -v helm kubeconform python3
|
||||
python3 -c 'import yaml'
|
||||
helm version --short
|
||||
shopt -s nullglob
|
||||
files=("$CHART"/ci/*.yaml)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "::error::no values files under $CHART/ci"
|
||||
exit 1
|
||||
fi
|
||||
for values in "${files[@]}"; do
|
||||
echo "::group::$values"
|
||||
helm lint --strict "$CHART" -f "$values"
|
||||
helm template fluxer "$CHART" -n fluxer -f "$values" \
|
||||
--api-versions cert-manager.io/v1 --api-versions gateway.networking.k8s.io/v1 > rendered.yaml
|
||||
kubeconform -strict -summary -kubernetes-version 1.29.0 \
|
||||
-schema-location default -schema-location "$CRD_CATALOG" rendered.yaml
|
||||
if grep -qi 'example\.com' rendered.yaml; then
|
||||
echo "::error::$values renders example.com"
|
||||
exit 1
|
||||
fi
|
||||
if grep -Eqi 'change_?me' rendered.yaml; then
|
||||
echo "::error::$values renders a CHANGE_ME placeholder"
|
||||
exit 1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Check the guards
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expect_failure() {
|
||||
local message=$1
|
||||
shift
|
||||
if output=$(helm template fluxer "$CHART" "$@" 2>&1); then
|
||||
echo "::error::rendered with $*"
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -qF "$message" <<<"$output"; then
|
||||
echo "::error::expected \"$message\" for $*, got: $output"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
d=domain=chat.example.test
|
||||
expect_failure "domain must be set"
|
||||
expect_failure "is a placeholder" --set $d --set secrets.values.FLUXER_SUDO_MODE_SECRET=CHANGE_ME
|
||||
expect_failure "is a placeholder" --set $d --set-string 'secrets.extra.FLUXER_STRIPE_SECRET_KEY=<change-me>'
|
||||
expect_failure "at least 32 characters" --set $d --set secrets.values.FLUXER_GATEWAY_RPC_AUTH_TOKEN=short
|
||||
expect_failure "letters and digits only" --set $d --set-string 'secrets.values.FLUXER_VALKEY_PASSWORD=0123456789abcdef0123456789abcd:@'
|
||||
expect_failure "worker.replicas must be 1" --set $d --set worker.replicas=2
|
||||
expect_failure "at least 32 bytes" --set $d --set secrets.values.FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=c2hvcnQ=
|
||||
expect_failure "set postgres.external.host" --set $d --set postgres.enabled=false
|
||||
expect_failure "set together" --set $d --set secrets.values.FLUXER_VAPID_PUBLIC_KEY=abc
|
||||
expect_failure "is a secret and would be stored in plain text" --set $d --set env.FLUXER_EMAIL_SMTP_PASSWORD=not-a-real-password
|
||||
expect_failure "is a secret and would be stored in plain text" --set $d --set api.env.FLUXER_STRIPE_SECRET_KEY=not-a-real-key
|
||||
expect_failure "holds a password" --set $d --set-string 'env.FLUXER_SEARCH_URL=https://user:[email protected]'
|
||||
expect_failure "Set livekit.nodeCIDRs" --set $d --set networkPolicy.ingressNamespaceSelector.matchLabels.name=ingress
|
||||
expect_failure "set livekit.external.publicUrl" --set $d --set livekit.enabled=false --set livekit.external.url=http://livekit.voice.svc:7880
|
||||
expect_failure "reserve a public address" --set $d --set livekit.expose=loadBalancer
|
||||
|
||||
- name: Check that a chart version bump restarts nothing
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cp -R "$CHART" "$RUNNER_TEMP/bumped"
|
||||
sed -e 's/^version: .*/version: 99.0.0/' -e 's/^appVersion: .*/appVersion: "bumped"/' "$CHART/Chart.yaml" > "$RUNNER_TEMP/bumped/Chart.yaml"
|
||||
helm template fluxer "$CHART" -n fluxer -f "$CHART/ci/existing-secret-values.yaml" > before.yaml
|
||||
helm template fluxer "$RUNNER_TEMP/bumped" -n fluxer -f "$CHART/ci/existing-secret-values.yaml" > after.yaml
|
||||
python3 - before.yaml after.yaml <<'PY'
|
||||
import sys, yaml
|
||||
def pods(path):
|
||||
out = {}
|
||||
for doc in yaml.safe_load_all(open(path)):
|
||||
if doc and doc.get('kind') in ('Deployment', 'StatefulSet'):
|
||||
spec = doc['spec']
|
||||
out[f"{doc['kind']}/{doc['metadata']['name']}"] = (spec['template'], spec['selector'], spec.get('volumeClaimTemplates'))
|
||||
return out
|
||||
before, after = pods(sys.argv[1]), pods(sys.argv[2])
|
||||
if len(before) < 20:
|
||||
sys.exit(f'only {len(before)} workloads rendered')
|
||||
changed = sorted(name for name in before.keys() | after.keys() if before.get(name) != after.get(name))
|
||||
if changed:
|
||||
sys.exit('a chart version bump changes the pod template of: ' + ', '.join(changed))
|
||||
print(f'{len(before)} workloads keep their pod template across a chart version bump')
|
||||
PY
|
||||
|
||||
- name: Check the default resource requests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
helm template fluxer "$CHART" -n fluxer -f "$CHART/ci/default-values.yaml" > rendered.yaml
|
||||
python3 - rendered.yaml <<'PY'
|
||||
import sys, yaml
|
||||
def cpu(v):
|
||||
v = str(v)
|
||||
return float(v[:-1]) if v.endswith('m') else float(v) * 1000
|
||||
units = {'Ki': 2**10, 'Mi': 2**20, 'Gi': 2**30}
|
||||
def mem(v):
|
||||
v = str(v)
|
||||
for unit, factor in units.items():
|
||||
if v.endswith(unit):
|
||||
return float(v[:-len(unit)]) * factor
|
||||
return float(v)
|
||||
total_cpu = total_mem = 0
|
||||
for doc in yaml.safe_load_all(open(sys.argv[1])):
|
||||
if not doc or doc.get('kind') not in ('Deployment', 'StatefulSet'):
|
||||
continue
|
||||
replicas = doc['spec'].get('replicas', 1)
|
||||
for container in doc['spec']['template']['spec']['containers']:
|
||||
requests = (container.get('resources') or {}).get('requests') or {}
|
||||
if 'cpu' not in requests or 'memory' not in requests:
|
||||
sys.exit(f"{doc['kind']}/{doc['metadata']['name']} has a container without cpu and memory requests")
|
||||
total_cpu += replicas * cpu(requests['cpu'])
|
||||
total_mem += replicas * mem(requests['memory'])
|
||||
print(f"requests: {total_cpu:.0f}m cpu, {total_mem / 2**30:.2f}Gi memory")
|
||||
if total_cpu > 1200 or total_mem > 4.5 * 2**30:
|
||||
sys.exit('the default install requests more than 1200m cpu or 4.5Gi memory')
|
||||
PY
|
||||
@@ -35,24 +35,24 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
token: ${{ steps.create-token.outputs.token }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: "24"
|
||||
node-version: "26"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -61,6 +61,8 @@ jobs:
|
||||
--step install_dependencies
|
||||
|
||||
- name: Refresh source catalogs
|
||||
env:
|
||||
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
|
||||
run: pnpm i18n:source-sync
|
||||
|
||||
- name: Format generated catalogs
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Checkout Weblate branch
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
with:
|
||||
token: ${{ steps.create-token.outputs.token }}
|
||||
ref: ${{ env.WEBLATE_BRANCH }}
|
||||
@@ -48,17 +48,17 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: "24"
|
||||
node-version: "26"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
--step install_dependencies
|
||||
|
||||
- name: Compile translated catalogs
|
||||
run: pnpm i18n:weblate-compile
|
||||
run: pnpm i18n:compile
|
||||
|
||||
- name: Format generated catalogs
|
||||
run: pnpm biome format --write package.json packages/i18n/package.json packages/i18n/scripts/SyncStaticI18nCatalogs.ts packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
|
||||
@@ -77,14 +77,14 @@ jobs:
|
||||
GH_TOKEN: ${{ steps.create-token.outputs.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "$(git status --porcelain -- fluxer_app/src/features/i18n/locales packages/i18n/marketing packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n)" ]]; then
|
||||
if [[ -z "$(git status --porcelain -- fluxer_app/src/features/i18n/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n)" ]]; then
|
||||
echo "No generated catalog changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "fluxer-ci[bot]"
|
||||
git config user.email "${{ vars.FLUXER_CI_APP_USER_ID }}+fluxer-ci[bot]@users.noreply.github.com"
|
||||
git add fluxer_app/src/features/i18n/locales packages/i18n/marketing packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
|
||||
git add fluxer_app/src/features/i18n/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
|
||||
git commit -m "i18n: compile Weblate catalogs"
|
||||
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
||||
git push origin "HEAD:$WEBLATE_BRANCH"
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
permission-pull-requests: write
|
||||
|
||||
- name: Label pull request
|
||||
uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213
|
||||
uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13
|
||||
with:
|
||||
repo-token: ${{ steps.create-token.outputs.token }}
|
||||
configuration-path: .github/labeller.yaml
|
||||
|
||||
@@ -56,15 +56,15 @@ jobs:
|
||||
contents: write
|
||||
packages: read
|
||||
steps:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
env:
|
||||
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5
|
||||
- uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
toolchain: "1.98.1"
|
||||
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069
|
||||
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
+86
-155
@@ -15,7 +15,6 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
GHCR_REGISTRY: ghcr.io/${{ github.repository_owner }}
|
||||
CARGO_PROFILE_DEV_DEBUG: none
|
||||
CARGO_PROFILE_TEST_DEBUG: none
|
||||
CARGO_INCREMENTAL: '0'
|
||||
@@ -28,43 +27,26 @@ jobs:
|
||||
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Restore ci helper
|
||||
id: ci-helper
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
- name: Set up ci helper
|
||||
uses: ./.github/actions/ci-helper
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
|
||||
|
||||
- name: Build ci helper
|
||||
if: steps.ci-helper.outputs.cache-hit != 'true'
|
||||
run: cargo build --locked --package fluxer-ci
|
||||
|
||||
- name: Save ci helper
|
||||
if: github.ref == 'refs/heads/main' && steps.ci-helper.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
|
||||
save: 'true'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -83,34 +65,24 @@ jobs:
|
||||
PNPM_TEST_WORKSPACE_CONCURRENCY: '2'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Restore ci helper
|
||||
id: ci-helper
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
|
||||
|
||||
- name: Build ci helper
|
||||
if: steps.ci-helper.outputs.cache-hit != 'true'
|
||||
run: cargo build --locked --package fluxer-ci
|
||||
- name: Set up ci helper
|
||||
uses: ./.github/actions/ci-helper
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -119,18 +91,9 @@ jobs:
|
||||
|
||||
- name: Restore fluxer_app wasm artifacts
|
||||
id: app-wasm
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
uses: ./.github/actions/app-wasm-cache
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: >-
|
||||
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
|
||||
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
|
||||
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
|
||||
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
|
||||
'packages/markdown_parser/rust/Cargo.toml', 'packages/markdown_parser/rust/.cargo/config.toml',
|
||||
'packages/markdown_parser/rust/src/**') }}
|
||||
mode: restore
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
@@ -138,39 +101,31 @@ jobs:
|
||||
|
||||
- name: Save fluxer_app wasm artifacts
|
||||
if: always() && github.ref == 'refs/heads/main' && steps.app-wasm.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
uses: ./.github/actions/app-wasm-cache
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: >-
|
||||
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
|
||||
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
|
||||
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
|
||||
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
|
||||
'packages/markdown_parser/rust/Cargo.toml', 'packages/markdown_parser/rust/.cargo/config.toml',
|
||||
'packages/markdown_parser/rust/src/**') }}
|
||||
mode: save
|
||||
|
||||
rust:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
components: clippy, rustfmt
|
||||
targets: x86_64-pc-windows-msvc
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Cache cargo
|
||||
@@ -185,11 +140,14 @@ jobs:
|
||||
rust-${{ runner.os }}-${{ hashFiles('fluxer_media_proxy/tools/install-native-deps.sh') }}-
|
||||
|
||||
- name: Install cargo-deny
|
||||
run: cargo install cargo-deny --version 0.19.6 --locked
|
||||
run: cargo install cargo-deny --version 0.20.2 --locked
|
||||
|
||||
- name: Check Rust dependencies
|
||||
run: cargo deny --locked check -D warnings
|
||||
|
||||
- name: Check libfluxwebp dependencies
|
||||
run: cargo deny --manifest-path fluxer_app/rust/libfluxwebp/Cargo.toml --config deny.toml --locked check licenses bans sources
|
||||
|
||||
- name: Check desktop native dependencies
|
||||
run: tools/ci/check-desktop-native-workspaces.sh dependencies
|
||||
|
||||
@@ -242,13 +200,16 @@ jobs:
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Check formatting (libfluxwebp)
|
||||
run: cargo fmt --manifest-path fluxer_app/rust/libfluxwebp/Cargo.toml -- --check
|
||||
|
||||
- name: Check formatting (desktop native workspaces)
|
||||
run: tools/ci/check-desktop-native-workspaces.sh fmt
|
||||
|
||||
- name: Clippy (warnings as errors)
|
||||
run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
|
||||
|
||||
- name: Clippy (desktop native workspaces on Linux, warnings as errors)
|
||||
- name: Clippy (desktop native workspaces on Linux and Windows backends, warnings as errors)
|
||||
run: tools/ci/check-desktop-native-workspaces.sh clippy
|
||||
|
||||
- name: Verify the source-built ffmpeg CLI is on PATH
|
||||
@@ -263,6 +224,9 @@ jobs:
|
||||
FLUXER_REQUIRE_MEDIA_FIXTURES: "1"
|
||||
run: cargo test --workspace --all-features --locked
|
||||
|
||||
- name: Run tests (libfluxcore)
|
||||
run: cargo test --locked --manifest-path fluxer_app/rust/libfluxcore/Cargo.toml
|
||||
|
||||
- name: Run desktop native workspace tests on Linux
|
||||
run: tools/ci/check-desktop-native-workspaces.sh test
|
||||
|
||||
@@ -273,12 +237,12 @@ jobs:
|
||||
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
|
||||
- name: Cache cargo (gateway NIFs)
|
||||
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
|
||||
@@ -288,24 +252,14 @@ jobs:
|
||||
fluxer_gateway/native/push_markdown_plaintext_nif -> target
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Restore ci helper
|
||||
id: ci-helper
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
|
||||
|
||||
- name: Build ci helper
|
||||
if: steps.ci-helper.outputs.cache-hit != 'true'
|
||||
run: cargo build --locked --package fluxer-ci
|
||||
- name: Set up ci helper
|
||||
uses: ./.github/actions/ci-helper
|
||||
|
||||
- name: Set up Erlang
|
||||
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124
|
||||
with:
|
||||
otp-version: '28'
|
||||
rebar3-version: '3.24.0'
|
||||
rebar3-version: '3.27.0'
|
||||
|
||||
- name: Restore rebar3 dependencies
|
||||
id: rebar3-cache
|
||||
@@ -317,10 +271,13 @@ jobs:
|
||||
!fluxer_gateway/_build/default/lib/fluxer_gateway/**
|
||||
!fluxer_gateway/_build/test/lib/fluxer_gateway/**
|
||||
key: >-
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.24.0-${{ hashFiles('fluxer_gateway/rebar.lock',
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.27.0-${{ hashFiles('fluxer_gateway/rebar.lock',
|
||||
'fluxer_gateway/rebar.config') }}
|
||||
restore-keys: |
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.24.0-
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.27.0-
|
||||
|
||||
- name: Drop restored gateway build output
|
||||
run: rm -rf fluxer_gateway/_build/default/lib/fluxer_gateway fluxer_gateway/_build/test/lib/fluxer_gateway
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
@@ -348,7 +305,7 @@ jobs:
|
||||
!fluxer_gateway/_build/default/lib/fluxer_gateway/**
|
||||
!fluxer_gateway/_build/test/lib/fluxer_gateway/**
|
||||
key: >-
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.24.0-${{ hashFiles('fluxer_gateway/rebar.lock',
|
||||
rebar3-${{ runner.os }}-otp28-rebar3.27.0-${{ hashFiles('fluxer_gateway/rebar.lock',
|
||||
'fluxer_gateway/rebar.config') }}
|
||||
|
||||
knip:
|
||||
@@ -358,34 +315,24 @@ jobs:
|
||||
FLUXER_CI_BIN: ${{ github.workspace }}/target/debug/fluxer-ci
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Rust toolchain (CI helpers)
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9
|
||||
uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de
|
||||
with:
|
||||
toolchain: "1.93.0"
|
||||
toolchain: "1.98.1"
|
||||
targets: wasm32-unknown-unknown
|
||||
|
||||
- name: Restore ci helper
|
||||
id: ci-helper
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
with:
|
||||
path: target/debug/fluxer-ci
|
||||
key: >-
|
||||
fluxer-ci-bin-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'Cargo.toml',
|
||||
'tools/ci/Cargo.toml', 'tools/ci/src/**', 'tools/ci/templates/**') }}
|
||||
|
||||
- name: Build ci helper
|
||||
if: steps.ci-helper.outputs.cache-hit != 'true'
|
||||
run: cargo build --locked --package fluxer-ci
|
||||
- name: Set up ci helper
|
||||
uses: ./.github/actions/ci-helper
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -394,18 +341,9 @@ jobs:
|
||||
|
||||
- name: Restore fluxer_app wasm artifacts
|
||||
id: app-wasm
|
||||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
uses: ./.github/actions/app-wasm-cache
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: >-
|
||||
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
|
||||
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
|
||||
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
|
||||
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
|
||||
'packages/markdown_parser/rust/Cargo.toml', 'packages/markdown_parser/rust/.cargo/config.toml',
|
||||
'packages/markdown_parser/rust/src/**') }}
|
||||
mode: restore
|
||||
|
||||
- name: Run knip
|
||||
run: |
|
||||
@@ -413,33 +351,24 @@ jobs:
|
||||
|
||||
- name: Save fluxer_app wasm artifacts
|
||||
if: always() && github.ref == 'refs/heads/main' && steps.app-wasm.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
||||
uses: ./.github/actions/app-wasm-cache
|
||||
with:
|
||||
path: |
|
||||
fluxer_app/pkgs/libfluxcore
|
||||
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
key: >-
|
||||
app-wasm-${{ runner.os }}-1.93.0-${{ hashFiles('Cargo.lock', 'tools/ci/src/app_wasm.rs',
|
||||
'tools/ci/templates/libfluxcore_wrapper.js', 'tools/ci/templates/libfluxcore_wrapper.d.ts',
|
||||
'fluxer_app/rust/libfluxcore/Cargo.toml', 'fluxer_app/rust/libfluxcore/Cargo.lock',
|
||||
'fluxer_app/rust/libfluxcore/.cargo/config.toml', 'fluxer_app/rust/libfluxcore/src/**',
|
||||
'packages/markdown_parser/rust/Cargo.toml', 'packages/markdown_parser/rust/.cargo/config.toml',
|
||||
'packages/markdown_parser/rust/src/**') }}
|
||||
mode: save
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -451,20 +380,23 @@ jobs:
|
||||
- name: Lint JSX for browser-translation safety
|
||||
run: pnpm exec eslint . --max-warnings 0
|
||||
|
||||
- name: Check data-flx attributes
|
||||
run: pnpm --filter fluxer_app theming:data-flx:check
|
||||
|
||||
i18n:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -473,15 +405,14 @@ jobs:
|
||||
- name: Compile locale catalogs
|
||||
run: pnpm i18n:compile
|
||||
|
||||
- name: Check drift of compiled locale modules
|
||||
- name: Check drift of synced catalogs
|
||||
run: |
|
||||
if ! git diff --exit-code -- \
|
||||
packages/errors/src/i18n/locales \
|
||||
packages/errors/src/i18n/ErrorI18nTypes.generated.ts \
|
||||
fluxer_api/pkgs/email/src/email_i18n/locales \
|
||||
packages/errors/src/i18n/weblate \
|
||||
fluxer_api/pkgs/email/src/email_i18n/weblate \
|
||||
fluxer_api/pkgs/email/src/email_i18n/EmailI18nTypes.generated.ts \
|
||||
fluxer_api/src/api/content_i18n/locales; then
|
||||
echo "::error::Compiled locale modules are outdated. Run 'pnpm i18n:compile' and commit the result. Translations belong in the weblate/ catalogs, not in the generated locales/ modules."
|
||||
fluxer_api/src/api/content_i18n/weblate; then
|
||||
echo "::error::Static catalogs are out of sync with their sources. Run 'pnpm i18n:compile' and commit the result."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -490,15 +421,15 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271
|
||||
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '26'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -515,12 +446,12 @@ jobs:
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Install font tooling
|
||||
run: python3 -m pip install -r tools/fonts/requirements.txt
|
||||
|
||||
+9
-5
@@ -10,6 +10,7 @@
|
||||
/.direnv/
|
||||
/.fluxer/
|
||||
/.pnpm-store/
|
||||
/.vscode/
|
||||
|
||||
**/*.css.d.ts
|
||||
**/*.tsbuildinfo
|
||||
@@ -25,23 +26,26 @@
|
||||
|
||||
/fluxer_app/.devserver-cache.json
|
||||
/fluxer_app/pkgs/libfluxcore/
|
||||
/fluxer_app/pkgs/libfluxwebp/
|
||||
/fluxer_app/src/features/i18n/locales/*/messages.mjs
|
||||
**/auto-i18n-reviewed-unchanged.json.lock
|
||||
**/weblate/locales/auto-i18n-reviewed-unchanged.json
|
||||
/fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
|
||||
/fluxer_app/src/features/theme/styles/generated/
|
||||
/fluxer_app/src/features/ui/components/SVGMasks.tsx
|
||||
/fluxer_app/src/features/ui/constants/AvatarStatusGeometry.ts
|
||||
/fluxer_gateway/priv/
|
||||
/fluxer_static/desktop/spellcheck/dictionaries/*/*
|
||||
!/fluxer_static/desktop/spellcheck/dictionaries/[email protected]/LICENSE
|
||||
!/fluxer_static/desktop/spellcheck/dictionaries/[email protected]/LICENSE
|
||||
|
||||
/fluxer_desktop/native/rust/fuzz/artifacts/
|
||||
/fluxer_desktop/native/rust/fuzz/corpus/
|
||||
/packages/markdown_parser/rust/fuzz/artifacts/
|
||||
/packages/markdown_parser/rust/fuzz/corpus/
|
||||
|
||||
/fluxer_media_proxy/.benchmark-cache/
|
||||
/fluxer_media_proxy/bench-results/
|
||||
|
||||
/app-dist-output/
|
||||
/artifacts/
|
||||
/desktop-shared-assets/
|
||||
/desktop-modules/
|
||||
/s3_payload/
|
||||
/upload_staging/
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[submodule "fluxer_marketing"]
|
||||
path = fluxer_marketing
|
||||
url = https://github.com/fluxerapp/marketing.git
|
||||
update = none
|
||||
Generated
+894
-1087
File diff suppressed because it is too large.
Load diff
+1
-3
@@ -7,18 +7,16 @@ members = [
|
||||
"fluxer_gifs",
|
||||
"fluxer_svc",
|
||||
"fluxer_messages",
|
||||
"fluxer_push",
|
||||
"fluxer_snowflakes",
|
||||
"tools/ci",
|
||||
"tools/content/update-frozen-snapshot",
|
||||
"tools/dev",
|
||||
"tools/i18n_auto",
|
||||
"fluxer_users",
|
||||
"fluxer_unfurl",
|
||||
"packages/markdown_parser/rust",
|
||||
"fluxer_recon",
|
||||
]
|
||||
exclude = [
|
||||
"fluxer_marketing",
|
||||
"packages/markdown_parser/rust/fuzz",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -6,18 +6,175 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://fluxer.app/donate">
|
||||
<img src="https://img.shields.io/badge/Donate-fluxer.app%2Fdonate-brightgreen" alt="Donate" /></a>
|
||||
<a href="https://fluxer.app/download">
|
||||
<img src="https://img.shields.io/badge/Download-fluxer.app-4641D9" alt="Download" /></a>
|
||||
<a href="https://docs.fluxer.app">
|
||||
<img src="https://img.shields.io/badge/Docs-docs.fluxer.app-blue" alt="Documentation" /></a>
|
||||
<a href="https://fluxer.app/donate">
|
||||
<img src="https://img.shields.io/badge/Donate-fluxer.app%2Fdonate-brightgreen" alt="Donate" /></a>
|
||||
<a href="./LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-AGPLv3-purple" alt="AGPLv3 License" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://flathub.org/apps/app.fluxer.Fluxer">
|
||||
<img src="https://dl.flathub.org/assets/badges/flathub-badge-en.svg" alt="Get it on Flathub" height="60" /></a>
|
||||
</p>
|
||||
|
||||
# Fluxer
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Bug reports and feature requests have moved to [feedback.fluxer.com](https://feedback.fluxer.com). Sign in with your Fluxer account to post, vote and follow updates. GitHub Issues and Discussions are closed. Report security vulnerabilities privately through [fluxer.app/security](https://fluxer.app/security).
|
||||
|
||||
Fluxer is a free and open source instant messaging and VoIP chat app built for friends, groups, and communities.
|
||||
|
||||
<p align="center">
|
||||
<img src="./fluxer_static/marketing/screenshots/desktop-readme-1920w.png" alt="Fluxer app showcase" width="900">
|
||||
<img src="https://fluxer.app/static/img/screenshots-desktop-readme-1920w.70cb6ce340007e0a.png" alt="Fluxer running side by side on a desktop monitor and a phone" width="640">
|
||||
</p>
|
||||
|
||||
## Download
|
||||
|
||||
| Windows | macOS | Linux | Android | iOS |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [Installer (x64)][win-setup-x64] | [Disk image][mac-dmg] | [Flathub][flathub] | [Google Play (beta)][android-play] | [TestFlight][ios-testflight] |
|
||||
| [Installer (ARM64)][win-setup-arm64] | | [deb (x64)][linux-deb-x64] | [APK (beta)][android-apk] | |
|
||||
| [Portable (x64)][win-portable-x64] | | [deb (ARM64)][linux-deb-arm64] | [Obtainium (beta)][obtainium] | |
|
||||
| [Portable (ARM64)][win-portable-arm64] | | [rpm (x64)][linux-rpm-x64] | | |
|
||||
| | | [rpm (ARM64)][linux-rpm-arm64] | | |
|
||||
| | | [AppImage (x64)][linux-appimage-x64] | | |
|
||||
| | | [AppImage (ARM64)][linux-appimage-arm64] | | |
|
||||
| | | [tar.gz (x64)][linux-targz-x64] | | |
|
||||
| | | [tar.gz (ARM64)][linux-targz-arm64] | | |
|
||||
|
||||
The macOS disk image runs on both Apple silicon and Intel. Windows and Linux need the build matching your processor.
|
||||
|
||||
On Linux, prefer a repository over a single file so Fluxer updates with the rest of your system.
|
||||
|
||||
## Linux package repositories
|
||||
|
||||
The package is `fluxer` for stable and `fluxer-canary` for canary. apt and dnf subscribe to one channel per entry file. pacman and Flatpak serve both from one repository.
|
||||
|
||||
### Flatpak
|
||||
|
||||
Stable is on [Flathub][flathub], the easiest route on most desktops:
|
||||
|
||||
```sh
|
||||
flatpak install flathub app.fluxer.Fluxer
|
||||
```
|
||||
|
||||
Flathub has stable only. To use Fluxer's own repository, open [the stable][flatpak-ref] or [the canary][flatpak-canary-ref] reference file and your software manager takes over. Some desktops also accept `flatpak+https://pkgs.fluxer.com/flatpak/fluxer.flatpakref` in the address bar.
|
||||
|
||||
From a terminal:
|
||||
|
||||
```sh
|
||||
flatpak install https://pkgs.fluxer.com/flatpak/fluxer.flatpakref
|
||||
```
|
||||
|
||||
### Debian and Ubuntu
|
||||
|
||||
```sh
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
sudo curl -fsSL -o /etc/apt/keyrings/fluxer-archive-keyring.gpg https://pkgs.fluxer.com/keys/fluxer-archive-keyring.gpg
|
||||
sudo curl -fsSL -o /etc/apt/sources.list.d/fluxer.sources https://pkgs.fluxer.com/deb/fluxer.sources
|
||||
sudo apt update && sudo apt install fluxer
|
||||
```
|
||||
|
||||
For canary, use the canary entry file and package.
|
||||
|
||||
```sh
|
||||
sudo curl -fsSL -o /etc/apt/sources.list.d/fluxer-canary.sources https://pkgs.fluxer.com/deb/fluxer-canary.sources
|
||||
sudo apt update && sudo apt install fluxer-canary
|
||||
```
|
||||
|
||||
A `.deb` installed from a download only updates once its channel's entry is added.
|
||||
|
||||
### Fedora and RHEL
|
||||
|
||||
```sh
|
||||
sudo curl -fsSL -o /etc/yum.repos.d/fluxer.repo https://pkgs.fluxer.com/rpm/fluxer.repo
|
||||
sudo dnf install fluxer
|
||||
```
|
||||
|
||||
For canary, use the canary entry file and package.
|
||||
|
||||
```sh
|
||||
sudo curl -fsSL -o /etc/yum.repos.d/fluxer-canary.repo https://pkgs.fluxer.com/rpm/fluxer-canary.repo
|
||||
sudo dnf install fluxer-canary
|
||||
```
|
||||
|
||||
RHEL, Rocky, Alma and CentOS Stream need `sudo dnf install epel-release` first, because their base repositories lack `libXScrnSaver`. Fedora does not.
|
||||
|
||||
### Arch Linux
|
||||
|
||||
The repository is signed, so pacman needs the key once:
|
||||
|
||||
```sh
|
||||
sudo pacman-key --init
|
||||
curl -fsSL -o /tmp/fluxer-archive-keyring.asc https://pkgs.fluxer.com/keys/fluxer-archive-keyring.asc
|
||||
sudo pacman-key --add /tmp/fluxer-archive-keyring.asc
|
||||
sudo pacman-key --lsign-key 09D01339EE128925F75E675C855C5BDE34D205D2
|
||||
```
|
||||
|
||||
`--lsign-key` is what makes pacman trust it. Then add the repository:
|
||||
|
||||
```sh
|
||||
sudo tee -a /etc/pacman.conf >/dev/null <<'REPO'
|
||||
|
||||
[fluxer]
|
||||
SigLevel = Required TrustedOnly
|
||||
Server = https://pkgs.fluxer.com/arch/$repo/os/$arch
|
||||
REPO
|
||||
sudo pacman -Syu fluxer
|
||||
```
|
||||
|
||||
Write `$repo` and `$arch` literally. Both are pacman variables, not shell ones, hence the quoted heredoc.
|
||||
|
||||
Full setup notes, including canary, are in the [Linux repositories documentation][docs-linux].
|
||||
|
||||
## Other ways to run it
|
||||
|
||||
- [Open Fluxer in a browser](https://web.fluxer.app), no install needed.
|
||||
- [Host your own instance][docs-selfhost] from this repository.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Documentation home][docs]
|
||||
- [Downloads][docs-downloads]
|
||||
- [Self-hosting][docs-selfhost]
|
||||
|
||||
## License
|
||||
|
||||
The source is licensed under the [AGPL-3.0-or-later](./LICENSE) license.
|
||||
|
||||
Fluxer artwork, such as the logo, icons, badges and default avatars, is
|
||||
licensed under [CC BY-SA 4.0](./fluxer_static/LICENSE). Third-party material
|
||||
keeps its own terms, listed in
|
||||
[fluxer_static/THIRD_PARTY_LICENSES.md](./fluxer_static/THIRD_PARTY_LICENSES.md).
|
||||
|
||||
Use of the Fluxer name and logo is covered by the
|
||||
[name and marks policy](./.github/GOVERNANCE.md#name-and-marks).
|
||||
|
||||
[win-setup-x64]: https://pkgs.fluxer.com/desktop/stable/win32/x64/latest/setup
|
||||
[win-setup-arm64]: https://pkgs.fluxer.com/desktop/stable/win32/arm64/latest/setup
|
||||
[win-portable-x64]: https://pkgs.fluxer.com/desktop/stable/win32/x64/latest/portable
|
||||
[win-portable-arm64]: https://pkgs.fluxer.com/desktop/stable/win32/arm64/latest/portable
|
||||
[mac-dmg]: https://pkgs.fluxer.com/desktop/stable/darwin/arm64/latest/dmg
|
||||
[linux-deb-x64]: https://pkgs.fluxer.com/desktop/stable/linux/x64/latest/deb
|
||||
[linux-deb-arm64]: https://pkgs.fluxer.com/desktop/stable/linux/arm64/latest/deb
|
||||
[linux-rpm-x64]: https://pkgs.fluxer.com/desktop/stable/linux/x64/latest/rpm
|
||||
[linux-rpm-arm64]: https://pkgs.fluxer.com/desktop/stable/linux/arm64/latest/rpm
|
||||
[linux-appimage-x64]: https://pkgs.fluxer.com/desktop/stable/linux/x64/latest/appimage
|
||||
[linux-appimage-arm64]: https://pkgs.fluxer.com/desktop/stable/linux/arm64/latest/appimage
|
||||
[linux-targz-x64]: https://pkgs.fluxer.com/desktop/stable/linux/x64/latest/tar_gz
|
||||
[linux-targz-arm64]: https://pkgs.fluxer.com/desktop/stable/linux/arm64/latest/tar_gz
|
||||
[flatpak-ref]: https://pkgs.fluxer.com/flatpak/fluxer.flatpakref
|
||||
[flatpak-canary-ref]: https://pkgs.fluxer.com/flatpak/fluxer-canary.flatpakref
|
||||
[flathub]: https://flathub.org/apps/app.fluxer.Fluxer
|
||||
[android-play]: https://play.google.com/store/apps/details?id=com.fluxer
|
||||
[android-apk]: https://github.com/fluxerapp/flutter_client/releases
|
||||
[obtainium]: https://obtainium.imranr.dev/
|
||||
[ios-testflight]: https://testflight.apple.com/join/PKZR6pK9
|
||||
[docs]: https://docs.fluxer.app
|
||||
[docs-downloads]: https://docs.fluxer.app/downloads/overview/
|
||||
[docs-linux]: https://docs.fluxer.app/downloads/linux-repositories/
|
||||
[docs-selfhost]: https://docs.fluxer.app/operator/get-started/
|
||||
+7
-6
@@ -48,7 +48,7 @@
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"preset": "recommended",
|
||||
"complexity": {
|
||||
"noForEach": "off",
|
||||
"noImportantStyles": "off",
|
||||
@@ -83,6 +83,7 @@
|
||||
}
|
||||
},
|
||||
"useConst": "error",
|
||||
"noDescendingSpecificity": "off",
|
||||
"noNonNullAssertion": "off",
|
||||
"noParameterAssign": "off",
|
||||
"noRestrictedImports": {
|
||||
@@ -98,7 +99,7 @@
|
||||
}
|
||||
},
|
||||
"a11y": {
|
||||
"recommended": true,
|
||||
"preset": "recommended",
|
||||
"useAriaPropsForRole": "error",
|
||||
"useValidAriaRole": "error",
|
||||
"useValidAriaValues": "error",
|
||||
@@ -142,6 +143,10 @@
|
||||
],
|
||||
"linter": {"rules": {"style": {"noRestrictedImports": "off"}}}
|
||||
},
|
||||
{
|
||||
"includes": ["fluxer_app/src/**/*.worklet.js"],
|
||||
"javascript": {"globals": ["AudioWorkletProcessor", "registerProcessor", "sampleRate", "currentTime"]}
|
||||
},
|
||||
{
|
||||
"includes": ["**/*.astro"],
|
||||
"linter": {"rules": {"correctness": {"noUnusedImports": "off", "noUnusedVariables": "off"}}},
|
||||
@@ -158,7 +163,6 @@
|
||||
"**",
|
||||
"!**/.git",
|
||||
"!**/app.css",
|
||||
"!fluxer_admin/public/static/app.css",
|
||||
"!**/build",
|
||||
"fluxer_app/scripts/build",
|
||||
"!**/dist",
|
||||
@@ -179,11 +183,8 @@
|
||||
"!packages/fonts",
|
||||
"!fluxer_admin/static/htmx.min.js",
|
||||
"!fluxer_api/src/api/openapi/openapi.json",
|
||||
"!fluxer_api/pkgs/email/src/email_i18n/locales",
|
||||
"!fluxer_api/pkgs/email/src/email_i18n/weblate",
|
||||
"!fluxer_api/src/api/content_i18n/locales",
|
||||
"!fluxer_api/src/api/content_i18n/weblate",
|
||||
"!packages/errors/src/i18n/locales",
|
||||
"!packages/errors/src/i18n/weblate",
|
||||
"!**/auto-i18n-reviewed-unchanged.json"
|
||||
],
|
||||
|
||||
Vendored
+3
-23
@@ -34,7 +34,6 @@ FLUXER_KV_URL=redis://valkey:6379/0
|
||||
FLUXER_NATS_URL=nats://nats:4222
|
||||
FLUXER_NATS_JETSTREAM_URL=nats://nats:4222
|
||||
FLUXER_INTERNAL_API_ENDPOINT=http://127.0.0.1:8080
|
||||
FLUXER_INTERNAL_GATEWAY_ENDPOINT=http://127.0.0.1:8771
|
||||
FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT=http://127.0.0.1:8082
|
||||
FLUXER_MEDIA_PROXY_ENDPOINT=http://127.0.0.1:8082
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT=http://localhost:8088/media
|
||||
@@ -42,7 +41,6 @@ FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT=http://localhost:8088/media
|
||||
FLUXER_SVC_NATS_URL=nats://nats:4222
|
||||
FLUXER_SVC_SHARD_COUNT=1
|
||||
FLUXER_SVC_CACHE_TTL_MS=30000
|
||||
FLUXER_SVC_CACHE_HARD_TTL_MS=600000
|
||||
|
||||
FLUXER_S3_ENDPOINT=http://127.0.0.1:8333
|
||||
FLUXER_S3_PUBLIC_ENDPOINT=http://localhost:8088
|
||||
@@ -52,7 +50,6 @@ FLUXER_S3_SECRET_ACCESS_KEY=fluxer-secret
|
||||
FLUXER_S3_FORCE_PATH_STYLE=true
|
||||
FLUXER_S3_BUCKET_CDN=fluxer
|
||||
FLUXER_S3_BUCKET_UPLOADS=fluxer-uploads
|
||||
FLUXER_S3_BUCKET_DOWNLOADS=fluxer-downloads
|
||||
FLUXER_S3_BUCKET_REPORTS=fluxer-reports
|
||||
FLUXER_S3_BUCKET_HARVESTS=fluxer-harvests
|
||||
FLUXER_S3_BUCKET_STATIC=fluxer-static
|
||||
@@ -62,14 +59,8 @@ FLUXER_LIVEKIT_URL=ws://localhost:8088/livekit
|
||||
FLUXER_LIVEKIT_INTERNAL_URL=http://localhost:7880
|
||||
FLUXER_LIVEKIT_API_KEY=devkey
|
||||
FLUXER_LIVEKIT_API_SECRET=fluxer-livekit-development-secret
|
||||
FLUXER_LIVEKIT_WEBHOOK_URL=http://localhost:8088/api/webhooks/livekit
|
||||
FLUXER_LIVEKIT_DEFAULT_REGION={"id":"local","name":"Local","emoji":"LC","latitude":59.3293,"longitude":18.0686}
|
||||
|
||||
FLUXER_RECON_MODE=observing
|
||||
FLUXER_RECON_EXPECTED_ROOMS=64
|
||||
FLUXER_RECON_WARMUP_SECONDS=15
|
||||
FLUXER_RECON_MAX_HOT_ROOMS=8
|
||||
|
||||
FLUXER_API_PORT=8080
|
||||
FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED=true
|
||||
FLUXER_API_WORKER_MODE=all_lanes
|
||||
@@ -95,13 +86,10 @@ FLUXER_ADMIN_BASE_PATH=/admin
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE=dev-admin-secret-key-base
|
||||
FLUXER_ADMIN_OAUTH_CLIENT_SECRET=dev-admin-oauth-secret
|
||||
FLUXER_ADMIN_OAUTH_REDIRECT_URI=http://localhost:8088/admin/oauth2_callback
|
||||
FLUXER_MARKETING_PORT=3010
|
||||
FLUXER_MARKETING_HOST=0.0.0.0
|
||||
FLUXER_MARKETING_BASE_PATH=/marketing
|
||||
FLUXER_MARKETING_SECRET_KEY_BASE=dev-marketing-secret-key-base
|
||||
|
||||
FLUXER_SUDO_MODE_SECRET=dev-sudo-secret
|
||||
FLUXER_CONNECTION_INITIATION_SECRET=dev-connection-initiation-secret
|
||||
FLUXER_PROFILE_PSEUDONYM_SECRET=fluxer-dev-profile-pseudonym-secret
|
||||
FLUXER_VAPID_PUBLIC_KEY=BHIbdKs24FdPkOQS7hbeg3adceLS0IqlKsn71ywEe6kbeopeFFiG3lkvJac7BVqkuk7mxwEa555O2FXV3HLt56w
|
||||
FLUXER_VAPID_PRIVATE_KEY=cs24JvXSxHiqJQgkJNocJFAdzJpPmpfU9xD-fDpn3tw
|
||||
FLUXER_VAPID_EMAIL=dev@localhost
|
||||
@@ -116,10 +104,7 @@ FLUXER_EMAIL_SMTP_HOST=mailpit
|
||||
FLUXER_EMAIL_SMTP_PORT=1025
|
||||
FLUXER_EMAIL_SMTP_USERNAME=dev
|
||||
FLUXER_EMAIL_SMTP_PASSWORD=dev
|
||||
FLUXER_EMAIL_SMTP_SECURE=false
|
||||
FLUXER_SMS_ENABLED=false
|
||||
FLUXER_CAPTCHA_ENABLED=false
|
||||
FLUXER_CAPTCHA_PROVIDER=none
|
||||
FLUXER_EMAIL_SMTP_TLS_MODE=none
|
||||
FLUXER_SEARCH_ENGINE=meilisearch
|
||||
FLUXER_SEARCH_URL=http://meilisearch:7700
|
||||
FLUXER_SEARCH_API_KEY=fluxer-dev-meilisearch
|
||||
@@ -127,7 +112,6 @@ FLUXER_SEARCH_USERNAME=
|
||||
FLUXER_SEARCH_PASSWORD=
|
||||
FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED=false
|
||||
FLUXER_STRIPE_ENABLED=false
|
||||
FLUXER_NCMEC_ENABLED=false
|
||||
FLUXER_CLAMAV_ENABLED=false
|
||||
FLUXER_DISCOVERY_ENABLED=true
|
||||
FLUXER_SELF_HOSTED=true
|
||||
@@ -136,10 +120,6 @@ FLUXER_TEST_MODE_ENABLED=false
|
||||
|
||||
PUBLIC_BUILD_VERSION=dev
|
||||
PUBLIC_RELEASE_CHANNEL=canary
|
||||
PUBLIC_BOOTSTRAP_API_ENDPOINT=/api
|
||||
PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT=http://localhost:8088/api
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=Zmx1eGVyLWRldi11cGxvYWQtcmVsYXktc2VjcmV0LTAwMDA=
|
||||
FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64=Zmx1eGVyLWRldi1hdHRhY2htZW50LXVybC1zZWNyZXQ=
|
||||
AWS_EC2_METADATA_DISABLED=true
|
||||
AWS_ACCESS_KEY_ID=fluxer
|
||||
AWS_SECRET_ACCESS_KEY=fluxer-secret
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
@@ -79,34 +79,41 @@ deny = [
|
||||
{ crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" },
|
||||
]
|
||||
skip = [
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older crypto API" },
|
||||
{ crate = "[email protected].7", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected].6", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older hashbrown API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the prior hashbrown API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older HTTP API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older HTTP body API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older WASI API" },
|
||||
{ crate = "[email protected].6", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "[email protected].4", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "[email protected].8", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "[email protected].5", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older randomness API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the prior randomness API" },
|
||||
{ crate = "[email protected].6", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected].7", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older digest API" },
|
||||
{ crate = "s[email protected]0", reason = "transitive dependency requires the older socket API" },
|
||||
{ crate = "s[email protected].0", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]+wasi-snapshot-preview1", reason = "transitive dependency requires the legacy WASI API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older release line" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older Windows API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the prior Windows API" },
|
||||
{ crate = "[email protected]", reason = "transitive dependency requires the older WASI binding API" },
|
||||
]
|
||||
skip-tree = []
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
.git/
|
||||
.gitignore
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
ci/
|
||||
@@ -0,0 +1,13 @@
|
||||
apiVersion: v2
|
||||
name: fluxer
|
||||
description: A complete Fluxer instance on Kubernetes, at parity with the self-hosted Docker Compose stack.
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "v1"
|
||||
kubeVersion: ">=1.29.0-0"
|
||||
home: https://docs.fluxer.app/operator/kubernetes/
|
||||
sources:
|
||||
- https://github.com/fluxerapp/fluxer/tree/main/deploy/helm/fluxer
|
||||
keywords:
|
||||
- fluxer
|
||||
- chat
|
||||
@@ -0,0 +1 @@
|
||||
domain: chat.example.test
|
||||
@@ -0,0 +1,3 @@
|
||||
domain: chat.example.test
|
||||
secrets:
|
||||
existingSecret: fluxer-ci-secret
|
||||
@@ -0,0 +1,38 @@
|
||||
domain: chat.example.test
|
||||
postgres:
|
||||
enabled: false
|
||||
external:
|
||||
host: db.example.test
|
||||
ssl: "true"
|
||||
valkey:
|
||||
enabled: false
|
||||
nats:
|
||||
enabled: false
|
||||
external:
|
||||
url: nats://mq.example.test:4222
|
||||
meilisearch:
|
||||
enabled: false
|
||||
external:
|
||||
url: https://search.example.test
|
||||
seaweedfs:
|
||||
enabled: false
|
||||
s3:
|
||||
endpoint: https://s3.example.test
|
||||
publicEndpoint: https://cdn.example.test
|
||||
region: eu-central-1
|
||||
forcePathStyle: false
|
||||
livekit:
|
||||
enabled: false
|
||||
external:
|
||||
url: http://livekit.voice.svc:7880
|
||||
publicUrl: wss://livekit.example.test
|
||||
secrets:
|
||||
values:
|
||||
FLUXER_POSTGRES_PASSWORD: ci-postgres-password
|
||||
FLUXER_SEARCH_API_KEY: ci-search-key
|
||||
FLUXER_S3_SECRET_ACCESS_KEY: ci-s3-secret
|
||||
FLUXER_NATS_AUTH_TOKEN: ci-nats-token
|
||||
FLUXER_LIVEKIT_API_KEY: ci-livekit-key
|
||||
FLUXER_LIVEKIT_API_SECRET: ci-livekit-secret-of-at-least-32-characters
|
||||
extra:
|
||||
FLUXER_KV_URL: redis://:[email protected]:6379/0
|
||||
@@ -0,0 +1,11 @@
|
||||
domain: chat.example.test
|
||||
edge:
|
||||
expose: gateway
|
||||
gateway:
|
||||
parentRefs:
|
||||
- name: shared
|
||||
namespace: gateway-system
|
||||
timeouts:
|
||||
request: 0s
|
||||
livekit:
|
||||
expose: nodePort
|
||||
@@ -0,0 +1,10 @@
|
||||
domain: chat.example.test
|
||||
email: [email protected]
|
||||
edge:
|
||||
expose: loadBalancer
|
||||
loadBalancer:
|
||||
http3: true
|
||||
livekit:
|
||||
expose: loadBalancer
|
||||
nodeIP: 203.0.113.10
|
||||
loadBalancerIP: 203.0.113.10
|
||||
@@ -0,0 +1,15 @@
|
||||
domain: chat.example.test
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
livekit:
|
||||
enabled: false
|
||||
nats:
|
||||
replicas: 3
|
||||
initImage:
|
||||
repository: registry.example.test/busybox
|
||||
digest: sha256:0000000000000000000000000000000000000000000000000000000000000000
|
||||
postgres:
|
||||
image:
|
||||
repository: registry.example.test/postgres
|
||||
tag: "16"
|
||||
runAsUser: 999
|
||||
@@ -0,0 +1,13 @@
|
||||
domain: chat.example.test
|
||||
networkPolicy:
|
||||
ingressNamespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: ingress-nginx
|
||||
edge:
|
||||
ingress:
|
||||
className: nginx
|
||||
livekit:
|
||||
nodeSelector:
|
||||
kubernetes.io/hostname: voice-1
|
||||
nodeCIDRs:
|
||||
- 10.0.0.12/32
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
servers {
|
||||
trusted_proxies static {$FLUXER_EDGE_TRUSTED_PROXIES:private_ranges}
|
||||
trusted_proxies_strict
|
||||
}
|
||||
{$FLUXER_EDGE_GLOBAL_OPTIONS}
|
||||
}
|
||||
|
||||
{$FLUXER_EDGE_SITE_ADDRESS} {
|
||||
encode {$FLUXER_EDGE_ENCODE:zstd gzip}
|
||||
|
||||
handle /_health {
|
||||
respond "OK" 200
|
||||
}
|
||||
|
||||
handle_path /api/* {
|
||||
reverse_proxy api:8080
|
||||
}
|
||||
|
||||
handle /gateway {
|
||||
rewrite * /
|
||||
reverse_proxy gateway:8080
|
||||
}
|
||||
|
||||
handle_path /gateway/* {
|
||||
reverse_proxy gateway:8080
|
||||
}
|
||||
|
||||
handle_path /media/* {
|
||||
reverse_proxy media-proxy:8080
|
||||
}
|
||||
|
||||
handle_path /livekit/* {
|
||||
reverse_proxy livekit:7880
|
||||
}
|
||||
|
||||
handle {$FLUXER_ADMIN_BASE_PATH:/admin} {
|
||||
rewrite * /
|
||||
reverse_proxy admin:8080
|
||||
}
|
||||
|
||||
handle_path {$FLUXER_ADMIN_BASE_PATH:/admin}/* {
|
||||
reverse_proxy admin:8080
|
||||
}
|
||||
|
||||
@staticAssets path /web/* /emoji/* /libs/* /avatars/* /badges/* /desktop/*
|
||||
handle @staticAssets {
|
||||
reverse_proxy static-proxy:8080
|
||||
}
|
||||
|
||||
handle /.well-known/fluxer {
|
||||
reverse_proxy api:8080
|
||||
}
|
||||
|
||||
handle {
|
||||
reverse_proxy app-proxy:8080
|
||||
}
|
||||
}
|
||||
|
||||
:8088 {
|
||||
handle /.well-known/fluxer {
|
||||
reverse_proxy api:8080
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
trap 'exit 0' TERM INT
|
||||
buckets="$FLUXER_S3_BUCKET_CDN $FLUXER_S3_BUCKET_UPLOADS $FLUXER_S3_BUCKET_REPORTS $FLUXER_S3_BUCKET_HARVESTS"
|
||||
missing="$buckets"
|
||||
for attempt in $(seq 1 "$FLUXER_SEAWEEDFS_INIT_ATTEMPTS"); do
|
||||
if ! nc -z 127.0.0.1 9333 2>/dev/null; then
|
||||
sleep 2
|
||||
continue
|
||||
fi
|
||||
listed=$(echo "s3.bucket.list" | timeout 10 weed shell -master=127.0.0.1:9333 2>&1)
|
||||
missing=""
|
||||
for b in $buckets; do
|
||||
echo "$listed" | grep -q "^[[:space:]]*$b[[:space:]]" || missing="${missing:+$missing }$b"
|
||||
done
|
||||
if [ -z "$missing" ]; then
|
||||
if ! echo "s3.configure -user=fluxer -access_key=$FLUXER_S3_ACCESS_KEY -secret_key=$FLUXER_S3_SECRET_KEY -actions=Admin,Read,Write,List,Tagging -apply" | timeout 10 weed shell -master=127.0.0.1:9333 >/dev/null 2>&1; then
|
||||
echo "seaweedfs-init could not configure the S3 identity" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "buckets ready"
|
||||
touch /tmp/fluxer-buckets-ready
|
||||
while :; do
|
||||
sleep 86400 &
|
||||
wait $!
|
||||
done
|
||||
fi
|
||||
for b in $missing; do
|
||||
echo "s3.bucket.create -name $b" | timeout 10 weed shell -master=127.0.0.1:9333 >/dev/null 2>&1
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
echo "seaweedfs-init could not verify buckets: $missing" >&2
|
||||
exit 1
|
||||
@@ -0,0 +1,72 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- $v := .Values }}
|
||||
{{- $o := include "fluxer.origin" . }}
|
||||
Fluxer is installing in namespace {{ .Release.Namespace }}. Run one release per namespace.
|
||||
|
||||
Open {{ $o }} once every pod is ready:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get pods
|
||||
|
||||
The admin panel is at {{ $o }}{{ $v.adminBasePath }}.
|
||||
|
||||
The setup wizard asks for the setup token before it creates the admin account:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get secret {{ include "fluxer.secretName" . }} -o jsonpath='{.data.FLUXER_SETUP_TOKEN}' | base64 -d
|
||||
|
||||
A Secret with no FLUXER_SETUP_TOKEN has a token the API wrote to its log once:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} logs deployment/api | grep -i 'setup token'
|
||||
{{- if eq $v.edge.expose "loadBalancer" }}
|
||||
|
||||
Point DNS for {{ $v.domain }} at the external address of the edge-public Service:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get service edge-public
|
||||
{{- else if eq $v.edge.expose "none" }}
|
||||
|
||||
Nothing exposes the instance yet. Send your proxy or tunnel to the Service edge
|
||||
on port 8080, with the Host header {{ $v.domain }}.
|
||||
{{- else }}
|
||||
|
||||
Point DNS for {{ $v.domain }} at your {{ ternary "ingress controller" "gateway" (eq $v.edge.expose "ingress") }}. Its Service needs
|
||||
externalTrafficPolicy Local or the PROXY protocol, or every client shares one IP
|
||||
and registration hits its per-IP limit.
|
||||
{{- end }}
|
||||
{{- if $v.livekit.external.url }}
|
||||
|
||||
Voice uses your LiveKit server at {{ $v.livekit.external.publicUrl }}. Point its webhook at
|
||||
{{ $o }}/api/webhooks/livekit, signed with the key in FLUXER_LIVEKIT_API_KEY.
|
||||
{{- end }}
|
||||
{{- if $v.livekit.enabled }}
|
||||
|
||||
Voice media does not pass through the edge. Open these ports to the internet:
|
||||
{{- if eq $v.livekit.expose "hostNetwork" }}
|
||||
{{ $v.livekit.tcpPort }}/tcp and {{ $v.livekit.udpPort }}/udp on the node that runs the livekit pod.
|
||||
hostNetwork needs a namespace that allows privileged pods. Keep 7880 closed.
|
||||
{{- else if eq $v.livekit.expose "nodePort" }}
|
||||
{{ $v.livekit.nodePorts.tcp }}/tcp and {{ $v.livekit.nodePorts.udp }}/udp on the node that runs the livekit pod.
|
||||
{{- else }}
|
||||
{{ $v.livekit.tcpPort }}/tcp and {{ $v.livekit.udpPort }}/udp on the load balancer of the Service livekit-rtc,
|
||||
whose public IP must be {{ $v.livekit.nodeIP }}.
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
Back up the Secret {{ include "fluxer.secretName" . }}{{ if $v.postgres.enabled }}, the database in postgres-0{{ end }}{{ if $v.seaweedfs.enabled }} and the uploads on the volume data-seaweedfs-0{{ end }}.
|
||||
{{- if $v.secrets.existingSecret }}
|
||||
After editing the Secret {{ $v.secrets.existingSecret }}, restart the release:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} rollout restart deployment,statefulset -l app.kubernetes.io/instance={{ .Release.Name }}
|
||||
{{- end }}
|
||||
{{- if and $v.secrets.generate (not $v.secrets.existingSecret) }}
|
||||
|
||||
Argo CD and helm template cannot read the existing Secret and would generate
|
||||
new secrets on every render. Under them, set secrets.generate=false and use
|
||||
secrets.existingSecret or give every key in secrets.values.
|
||||
{{- end }}
|
||||
|
||||
To move to the latest v1 images, pull the repository and upgrade with your values file:
|
||||
|
||||
helm upgrade {{ .Release.Name }} <chart> -n {{ .Release.Namespace }} -f <your values file> --set-string image.revision=$(date +%s)
|
||||
|
||||
Leave out --reuse-values, which keeps the defaults of the old chart version.
|
||||
|
||||
Documentation: https://docs.fluxer.app/operator/kubernetes/
|
||||
@@ -0,0 +1,109 @@
|
||||
{{- define "fluxer.s3Endpoint" -}}
|
||||
{{- .Values.s3.endpoint | default (ternary "http://seaweedfs:8333" "" .Values.seaweedfs.enabled) }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.configData" -}}
|
||||
{{- $v := .Values }}
|
||||
{{- $o := include "fluxer.origin" . }}
|
||||
{{- $hostOrigin := include "fluxer.hostOrigin" . }}
|
||||
{{- $media := printf "%s/media" $o }}
|
||||
{{- $s3 := include "fluxer.s3Endpoint" . }}
|
||||
{{- $c := dict }}
|
||||
{{- $_ := set $c "FLUXER_ENV" "production" }}
|
||||
{{- $_ := set $c "FLUXER_SELF_HOSTED" "true" }}
|
||||
{{- $_ := set $c "FLUXER_DATABASE_BACKEND" "postgres" }}
|
||||
{{- $_ := set $c "FLUXER_TRUST_CLIENT_IP_HEADER" "true" }}
|
||||
{{- $_ := set $c "FLUXER_BASE_DOMAIN" $v.domain }}
|
||||
{{- $_ := set $c "FLUXER_PUBLIC_SCHEME" $v.scheme }}
|
||||
{{- $_ := set $c "FLUXER_PUBLIC_PORT" (include "fluxer.string" $v.port) }}
|
||||
{{- $_ := set $c "FLUXER_PUBLIC_ORIGIN" (trimSuffix "/" ($v.publicOrigin | default "")) }}
|
||||
{{- $_ := set $c "FLUXER_APP_ENDPOINT" $o }}
|
||||
{{- $_ := set $c "FLUXER_MARKETING_ENDPOINT" $o }}
|
||||
{{- $_ := set $c "FLUXER_MEDIA_ENDPOINT" $media }}
|
||||
{{- $_ := set $c "FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT" $media }}
|
||||
{{- $_ := set $c "FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT" $media }}
|
||||
{{- $_ := set $c "FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT" $media }}
|
||||
{{- $_ := set $c "FLUXER_ADMIN_ENDPOINT" (printf "%s%s" $o $v.adminBasePath) }}
|
||||
{{- $_ := set $c "FLUXER_GATEWAY_STATIC_CDN_ENDPOINT" $o }}
|
||||
{{- $_ := set $c "FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS" $hostOrigin }}
|
||||
{{- $_ := set $c "FLUXER_PASSKEY_RP_ID" $v.domain }}
|
||||
{{- $_ := set $c "FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS" $o }}
|
||||
{{- if $v.postgres.enabled }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_HOST" "postgres" }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_PORT" "5432" }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_DATABASE" $v.postgres.database }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_USERNAME" $v.postgres.username }}
|
||||
{{- else }}
|
||||
{{- with $v.postgres.external }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_HOST" (.host | default "") }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_PORT" (include "fluxer.string" (.port | default "")) }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_DATABASE" (.database | default "") }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_USERNAME" (.username | default "") }}
|
||||
{{- $_ := set $c "FLUXER_POSTGRES_SSL" (include "fluxer.string" (.ssl | default "")) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not $v.valkey.enabled }}
|
||||
{{- $_ := set $c "FLUXER_KV_URL" ($v.valkey.external.url | default "") }}
|
||||
{{- end }}
|
||||
{{- $nats := ternary "nats://nats:4222" ($v.nats.external.url | default "") $v.nats.enabled }}
|
||||
{{- $_ := set $c "FLUXER_NATS_URL" $nats }}
|
||||
{{- $_ := set $c "FLUXER_NATS_JETSTREAM_URL" $nats }}
|
||||
{{- $_ := set $c "FLUXER_SVC_NATS_URL" $nats }}
|
||||
{{- $_ := set $c "FLUXER_SEARCH_ENGINE" "meilisearch" }}
|
||||
{{- $_ := set $c "FLUXER_SEARCH_URL" (ternary "http://meilisearch:7700" ($v.meilisearch.external.url | default "") $v.meilisearch.enabled) }}
|
||||
{{- $_ := set $c "FLUXER_S3_ENDPOINT" $s3 }}
|
||||
{{- $_ := set $c "FLUXER_S3_PUBLIC_ENDPOINT" ($v.s3.publicEndpoint | default $s3) }}
|
||||
{{- $_ := set $c "FLUXER_S3_REGION" $v.s3.region }}
|
||||
{{- $_ := set $c "FLUXER_S3_FORCE_PATH_STYLE" (toString $v.s3.forcePathStyle) }}
|
||||
{{- $_ := set $c "FLUXER_S3_BUCKET_CDN" $v.s3.buckets.cdn }}
|
||||
{{- $_ := set $c "FLUXER_S3_BUCKET_UPLOADS" $v.s3.buckets.uploads }}
|
||||
{{- $_ := set $c "FLUXER_S3_BUCKET_REPORTS" $v.s3.buckets.reports }}
|
||||
{{- $_ := set $c "FLUXER_S3_BUCKET_HARVESTS" $v.s3.buckets.harvests }}
|
||||
{{- $livekit := include "fluxer.livekitMode" . }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_ENABLED" (toString (ne $livekit "off")) }}
|
||||
{{- if eq $livekit "bundled" }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_INTERNAL_URL" "http://livekit:7880" }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_DEFAULT_REGION" $v.livekit.defaultRegion }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_URL" (printf "%s/livekit" $hostOrigin) }}
|
||||
{{- else if eq $livekit "external" }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_INTERNAL_URL" $v.livekit.external.url }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_DEFAULT_REGION" $v.livekit.defaultRegion }}
|
||||
{{- $_ := set $c "FLUXER_LIVEKIT_URL" $v.livekit.external.publicUrl }}
|
||||
{{- end }}
|
||||
{{- $_ := set $c "FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED" "false" }}
|
||||
{{- $_ := set $c "FLUXER_EMAIL_FROM_EMAIL" "noreply@localhost" }}
|
||||
{{- $_ := set $c "FLUXER_VAPID_EMAIL" ($v.email | default (printf "admin@%s" $v.domain)) }}
|
||||
{{- $_ := set $c "FLUXER_INTERNAL_API_ENDPOINT" "http://api:8080" }}
|
||||
{{- $_ := set $c "FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT" "http://media-proxy:8080" }}
|
||||
{{- range $k, $val := $v.env | default dict }}
|
||||
{{- $_ := set $c $k (include "fluxer.string" ($val | default "")) }}
|
||||
{{- end }}
|
||||
{{- $out := dict }}
|
||||
{{- range $k, $val := $c }}
|
||||
{{- if $val }}
|
||||
{{- $_ := set $out $k $val }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- toYaml $out }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.appProxyConfigData" -}}
|
||||
{{- $v := .Values }}
|
||||
{{- $s3 := include "fluxer.s3Endpoint" . }}
|
||||
{{- $c := dict }}
|
||||
{{- $_ := set $c "FLUXER_APP_PROXY_PORT" "8080" }}
|
||||
{{- $_ := set $c "DISCOVERY_UPSTREAM_URL" "http://api:8080/.well-known/fluxer" }}
|
||||
{{- $_ := set $c "FLUXER_S3_PUBLIC_ENDPOINT" ($v.s3.publicEndpoint | default $s3) }}
|
||||
{{- $_ := set $c "FLUXER_S3_BUCKET_UPLOADS" $v.s3.buckets.uploads }}
|
||||
{{- $_ := set $c "FLUXER_MEDIA_ENDPOINT" (printf "%s/media" (include "fluxer.origin" .)) }}
|
||||
{{- range $k, $val := $v.appProxy.config | default dict }}
|
||||
{{- $_ := set $c $k (include "fluxer.string" ($val | default "")) }}
|
||||
{{- end }}
|
||||
{{- $out := dict }}
|
||||
{{- range $k, $val := $c }}
|
||||
{{- if $val }}
|
||||
{{- $_ := set $out $k $val }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- toYaml $out }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,375 @@
|
||||
{{- define "fluxer.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ .name }}
|
||||
app.kubernetes.io/instance: {{ .root.Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.podLabels" -}}
|
||||
{{ include "fluxer.selectorLabels" . }}
|
||||
{{- with .component }}
|
||||
app.kubernetes.io/component: {{ . }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/part-of: fluxer
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.labels" -}}
|
||||
{{ include "fluxer.podLabels" . }}
|
||||
app.kubernetes.io/managed-by: {{ .root.Release.Service }}
|
||||
app.kubernetes.io/version: {{ .root.Chart.AppVersion | quote }}
|
||||
helm.sh/chart: {{ include "fluxer.chart" .root }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.string" -}}
|
||||
{{- if and (kindIs "float64" .) (eq . (float64 (int64 .))) }}
|
||||
{{- int64 . | toString }}
|
||||
{{- else }}
|
||||
{{- toString . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.origin" -}}
|
||||
{{- $v := .Values }}
|
||||
{{- if $v.publicOrigin }}
|
||||
{{- trimSuffix "/" $v.publicOrigin }}
|
||||
{{- else }}
|
||||
{{- $default := ternary 443 80 (eq $v.scheme "https") }}
|
||||
{{- if eq (int $v.port) $default }}
|
||||
{{- printf "%s://%s" $v.scheme $v.domain }}
|
||||
{{- else }}
|
||||
{{- printf "%s://%s:%d" $v.scheme $v.domain (int $v.port) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.hostOrigin" -}}
|
||||
{{- $v := .Values }}
|
||||
{{- if $v.publicOrigin }}
|
||||
{{- trimSuffix "/" $v.publicOrigin }}
|
||||
{{- else }}
|
||||
{{- printf "%s://%s:%d" $v.scheme $v.domain (int $v.port) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.secretName" -}}
|
||||
{{- .Values.secrets.existingSecret | default "fluxer-env" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.thirdPartyImage" -}}
|
||||
{{- $ref := printf "%s:%s" .repository (include "fluxer.string" .tag) }}
|
||||
{{- with .digest }}
|
||||
{{- $ref = printf "%s@%s" $ref . }}
|
||||
{{- end }}
|
||||
{{- $ref | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.image" -}}
|
||||
{{- $v := .root.Values.image }}
|
||||
{{- $img := (.w | default dict).image | default dict }}
|
||||
{{- $registry := $img.registry | default $v.registry }}
|
||||
{{- $tag := $img.tag | default (get ($v.tags | default dict) .image) | default $v.tag }}
|
||||
{{- $ref := printf "%s/%s:%s" $registry .image (include "fluxer.string" $tag) }}
|
||||
{{- with $img.digest | default (get ($v.digests | default dict) .image) }}
|
||||
{{- $ref = printf "%s@%s" $ref . }}
|
||||
{{- end }}
|
||||
{{- $ref | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.pullPolicy" -}}
|
||||
{{- (((.w | default dict).image | default dict).pullPolicy) | default .root.Values.image.pullPolicy }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.pullSecrets" -}}
|
||||
{{- with .Values.image.pullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.podSecurityContext" -}}
|
||||
runAsNonRoot: true
|
||||
{{- with .uid }}
|
||||
runAsUser: {{ . }}
|
||||
runAsGroup: {{ $.gid | default . }}
|
||||
fsGroup: {{ $.gid | default . }}
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
{{- end }}
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.containerSecurityContext" -}}
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.scheduling" -}}
|
||||
{{- $w := .w | default dict }}
|
||||
{{- with $w.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- with $w.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- with $w.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- $spread := list }}
|
||||
{{- range $w.topologySpreadConstraints }}
|
||||
{{- $c := deepCopy . }}
|
||||
{{- if not (hasKey $c "labelSelector") }}
|
||||
{{- $_ := set $c "labelSelector" (dict "matchLabels" (include "fluxer.selectorLabels" $ | fromYaml)) }}
|
||||
{{- end }}
|
||||
{{- $spread = append $spread $c }}
|
||||
{{- end }}
|
||||
{{- with $spread }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.pvc" -}}
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
{{- with .storageClassName }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .size }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.vapidPair" -}}
|
||||
{{- $der := genPrivateKey "ecdsa" | replace "-----BEGIN EC PRIVATE KEY-----" "" | replace "-----END EC PRIVATE KEY-----" "" | nospace | b64dec -}}
|
||||
private: {{ substr 7 39 $der | b64enc | replace "+" "-" | replace "/" "_" | trimAll "=" }}
|
||||
public: {{ substr 56 121 $der | b64enc | replace "+" "-" | replace "/" "_" | trimAll "=" }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.livekitMode" -}}
|
||||
{{- if .Values.livekit.enabled }}bundled{{ else if .Values.livekit.external.url }}external{{ else }}off{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.noGenerateKeys" -}}
|
||||
{{- $keys := list }}
|
||||
{{- $extra := .Values.secrets.extra | default dict }}
|
||||
{{- if and (not .Values.postgres.enabled) (not (get $extra "FLUXER_POSTGRES_URL")) }}
|
||||
{{- $keys = append $keys "FLUXER_POSTGRES_PASSWORD" }}
|
||||
{{- end }}
|
||||
{{- if not .Values.meilisearch.enabled }}
|
||||
{{- $keys = append $keys "FLUXER_SEARCH_API_KEY" }}
|
||||
{{- end }}
|
||||
{{- if not .Values.seaweedfs.enabled }}
|
||||
{{- $keys = append $keys "FLUXER_S3_SECRET_ACCESS_KEY" }}
|
||||
{{- end }}
|
||||
{{- if eq (include "fluxer.livekitMode" .) "external" }}
|
||||
{{- $keys = append $keys "FLUXER_LIVEKIT_API_SECRET" }}
|
||||
{{- end }}
|
||||
{{- toJson $keys }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.requiredSecretKeys" -}}
|
||||
{{- $keys := list }}
|
||||
{{- $extra := .Values.secrets.extra | default dict }}
|
||||
{{- range $k, $_ := .Values.secrets.values }}
|
||||
{{- if and (eq $k "FLUXER_POSTGRES_PASSWORD") (get $extra "FLUXER_POSTGRES_URL") }}
|
||||
{{- else if and (hasPrefix "FLUXER_LIVEKIT_" $k) (eq (include "fluxer.livekitMode" $) "off") }}
|
||||
{{- else if and (eq $k "FLUXER_VALKEY_PASSWORD") (not $.Values.valkey.enabled) }}
|
||||
{{- else if and (eq $k "FLUXER_NATS_AUTH_TOKEN") (not $.Values.nats.enabled) }}
|
||||
{{- else if and (eq $k "FLUXER_SETUP_TOKEN") (or $.Values.secrets.existingSecret (and (not $.Values.secrets.generate) (not (include "fluxer.string" (get $.Values.secrets.values $k | default ""))))) }}
|
||||
{{- else }}
|
||||
{{- $keys = append $keys $k }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- toJson $keys }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.resolveSecrets" -}}
|
||||
{{- $v := .Values }}
|
||||
{{- $existing := dict }}
|
||||
{{- $found := lookup "v1" "Secret" .Release.Namespace (include "fluxer.secretName" .) }}
|
||||
{{- range $k, $val := ($found.data | default dict) }}
|
||||
{{- $_ := set $existing $k (b64dec $val) }}
|
||||
{{- end }}
|
||||
{{- $out := dict }}
|
||||
{{- if $v.secrets.existingSecret }}
|
||||
{{- $out = $existing }}
|
||||
{{- else }}
|
||||
{{- $noGenerate := include "fluxer.noGenerateKeys" . | fromJsonArray }}
|
||||
{{- $explicit := $v.secrets.values | default dict }}
|
||||
{{- $vapidPublic := include "fluxer.string" (get $explicit "FLUXER_VAPID_PUBLIC_KEY" | default "") }}
|
||||
{{- $vapidPrivate := include "fluxer.string" (get $explicit "FLUXER_VAPID_PRIVATE_KEY" | default "") }}
|
||||
{{- if and $vapidPublic $vapidPrivate }}
|
||||
{{- else if or $vapidPublic $vapidPrivate }}
|
||||
{{- fail "secrets.values.FLUXER_VAPID_PUBLIC_KEY and secrets.values.FLUXER_VAPID_PRIVATE_KEY must be set together, or both left empty to generate a pair" }}
|
||||
{{- else if and (get $existing "FLUXER_VAPID_PUBLIC_KEY") (get $existing "FLUXER_VAPID_PRIVATE_KEY") }}
|
||||
{{- $vapidPublic = get $existing "FLUXER_VAPID_PUBLIC_KEY" }}
|
||||
{{- $vapidPrivate = get $existing "FLUXER_VAPID_PRIVATE_KEY" }}
|
||||
{{- else if $v.secrets.generate }}
|
||||
{{- $pair := include "fluxer.vapidPair" . | fromYaml }}
|
||||
{{- $vapidPublic = $pair.public }}
|
||||
{{- $vapidPrivate = $pair.private }}
|
||||
{{- else }}
|
||||
{{- fail "secrets.values.FLUXER_VAPID_PUBLIC_KEY and secrets.values.FLUXER_VAPID_PRIVATE_KEY are empty and secrets.generate is false" }}
|
||||
{{- end }}
|
||||
{{- $_ := set $out "FLUXER_VAPID_PUBLIC_KEY" $vapidPublic }}
|
||||
{{- $_ := set $out "FLUXER_VAPID_PRIVATE_KEY" $vapidPrivate }}
|
||||
{{- $required := include "fluxer.requiredSecretKeys" . | fromJsonArray }}
|
||||
{{- $outsideToken := include "fluxer.string" (get $explicit "FLUXER_NATS_AUTH_TOKEN" | default "") }}
|
||||
{{- if and $outsideToken (not $v.nats.enabled) }}
|
||||
{{- $_ := set $out "FLUXER_NATS_AUTH_TOKEN" $outsideToken }}
|
||||
{{- end }}
|
||||
{{- range $k := $required }}
|
||||
{{- if not (hasPrefix "FLUXER_VAPID_" $k) }}
|
||||
{{- $given := include "fluxer.string" (get $explicit $k | default "") }}
|
||||
{{- if $given }}
|
||||
{{- $_ := set $out $k $given }}
|
||||
{{- else if has $k $noGenerate }}
|
||||
{{- fail (printf "secrets.values.%s is required when the matching service is not bundled" $k) }}
|
||||
{{- else if get $existing $k }}
|
||||
{{- $_ := set $out $k (get $existing $k) }}
|
||||
{{- else if $v.secrets.generate }}
|
||||
{{- if eq $k "FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64" }}
|
||||
{{- $_ := set $out $k (randBytes 32) }}
|
||||
{{- else }}
|
||||
{{- $_ := set $out $k (randAlphaNum 64) }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- fail (printf "secrets.values.%s is empty and secrets.generate is false" $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $k, $val := $v.secrets.extra | default dict }}
|
||||
{{- $s := include "fluxer.string" ($val | default "") }}
|
||||
{{- if $s }}
|
||||
{{- $_ := set $out $k $s }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- toJson $out }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.secretData" -}}
|
||||
{{- if not (hasKey .Values "fluxerResolvedSecrets") }}
|
||||
{{- $_ := set .Values "fluxerResolvedSecrets" (include "fluxer.resolveSecrets" . | fromJson) }}
|
||||
{{- end }}
|
||||
{{- toJson (get .Values "fluxerResolvedSecrets") }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.secretEnv" -}}
|
||||
- name: {{ .name }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "fluxer.secretName" .root }}
|
||||
key: {{ .key }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.instanceChecksum" -}}
|
||||
{{- $parts := list (include "fluxer.configData" .) (include "fluxer.appProxyConfigData" .) (toJson .Values.secrets.values) (toJson (.Values.secrets.extra | default dict)) (include "fluxer.string" .Values.image.revision) (include "fluxer.secretData" .) }}
|
||||
{{- join "\n" $parts | sha256sum }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.envList" -}}
|
||||
{{- $w := .w | default dict }}
|
||||
{{- $own := $w.env | default dict }}
|
||||
{{- $base := .env | default list }}
|
||||
{{- $names := list }}
|
||||
{{- range $base }}
|
||||
{{- $names = append $names .name }}
|
||||
{{- end }}
|
||||
{{- $out := list }}
|
||||
{{- range $base }}
|
||||
{{- if hasKey $own .name }}
|
||||
{{- $val := get $own .name }}
|
||||
{{- if not (kindIs "invalid" $val) }}
|
||||
{{- $out = append $out (dict "name" .name "value" (include "fluxer.string" $val)) }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- $out = append $out . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $k := keys $own | sortAlpha }}
|
||||
{{- $val := get $own $k }}
|
||||
{{- if and (not (has $k $names)) (not (kindIs "invalid" $val)) }}
|
||||
{{- $out = append $out (dict "name" $k "value" (include "fluxer.string" $val)) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $out = concat $out ($w.extraEnv | default list) }}
|
||||
{{- with $out }}
|
||||
{{- toYaml . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.probes" -}}
|
||||
{{- $own := (.w | default dict).probes | default dict }}
|
||||
{{- range $probe := list "startup" "liveness" "readiness" }}
|
||||
{{- $p := get ($.probes | default dict) $probe }}
|
||||
{{- if hasKey $own $probe }}
|
||||
{{- $p = get $own $probe }}
|
||||
{{- end }}
|
||||
{{- with $p }}
|
||||
{{ $probe }}Probe:
|
||||
{{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.waitFor" -}}
|
||||
{{- with .waitFor }}
|
||||
initContainers:
|
||||
- name: wait-for-dependencies
|
||||
image: {{ include "fluxer.thirdPartyImage" $.root.Values.initImage }}
|
||||
imagePullPolicy: {{ $.root.Values.initImage.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- {{ printf "for t in %s; do until nc -z -w 2 ${t%%:*} ${t##*:}; do echo waiting for $t; sleep 2; done; done" (join " " .) | quote }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 32Mi
|
||||
securityContext:
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.appWaitFor" -}}
|
||||
{{- $out := list }}
|
||||
{{- if and .usesNats .root.Values.nats.enabled }}
|
||||
{{- $out = append $out "nats:4222" }}
|
||||
{{- end }}
|
||||
{{- if and .usesPostgres .root.Values.postgres.enabled }}
|
||||
{{- $out = append $out "postgres:5432" }}
|
||||
{{- end }}
|
||||
{{- if and .usesS3 .root.Values.seaweedfs.enabled }}
|
||||
{{- $out = append $out "seaweedfs:8333" }}
|
||||
{{- end }}
|
||||
{{- if and .usesKv .root.Values.valkey.enabled }}
|
||||
{{- $out = append $out "valkey:6379" }}
|
||||
{{- end }}
|
||||
{{- if .usesSvc }}
|
||||
{{- range $name := list "gifs" "messages" "snowflakes" "unfurl" "users" }}
|
||||
{{- $svc := get $.root.Values.svc $name | default dict }}
|
||||
{{- if gt (int (include "fluxer.replicas" (dict "w" ($svc.router | default dict)))) 0 }}
|
||||
{{- $out = append $out (printf "%s:8090" $name) }}
|
||||
{{- end }}
|
||||
{{- if gt (int (include "fluxer.replicas" (dict "w" ($svc.shard | default dict)))) 0 }}
|
||||
{{- $out = append $out (printf "%s-shard:8090" $name) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- toJson $out }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,245 @@
|
||||
{{- define "fluxer.validate" -}}
|
||||
{{- if not (hasKey .Values "fluxerValidated") }}
|
||||
{{- $_ := set .Values "fluxerValidated" true }}
|
||||
{{- $v := .Values }}
|
||||
{{- if not (regexMatch "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$" ($v.domain | default "")) }}
|
||||
{{- fail "domain must be set to the lowercase public hostname of the instance, for example --set domain=chat.example.com" }}
|
||||
{{- end }}
|
||||
{{- if not (has $v.scheme (list "http" "https")) }}
|
||||
{{- fail "scheme must be http or https" }}
|
||||
{{- end }}
|
||||
{{- if and $v.publicOrigin (not (regexMatch "^https?://[^/]+$" (trimSuffix "/" $v.publicOrigin))) }}
|
||||
{{- fail "publicOrigin must be a scheme, host and optional port, for example https://chat.example.com" }}
|
||||
{{- end }}
|
||||
{{- if not (regexMatch "^/[^/].*[^/]$|^/[^/]$" $v.adminBasePath) }}
|
||||
{{- fail "adminBasePath must start with a slash and must not end with one, for example /admin" }}
|
||||
{{- end }}
|
||||
{{- $extra := $v.secrets.extra | default dict }}
|
||||
{{- if and (not $v.postgres.enabled) (not $v.postgres.external.host) (not (get $extra "FLUXER_POSTGRES_URL")) (not $v.secrets.existingSecret) }}
|
||||
{{- fail "postgres.enabled is false, so set postgres.external.host or secrets.extra.FLUXER_POSTGRES_URL" }}
|
||||
{{- end }}
|
||||
{{- if and (not $v.valkey.enabled) (not $v.valkey.external.url) (not (get $extra "FLUXER_KV_URL")) (not $v.secrets.existingSecret) }}
|
||||
{{- fail "valkey.enabled is false, so set valkey.external.url or secrets.extra.FLUXER_KV_URL" }}
|
||||
{{- end }}
|
||||
{{- if and (not $v.nats.enabled) (not $v.nats.external.url) }}
|
||||
{{- fail "nats.enabled is false, so set nats.external.url" }}
|
||||
{{- end }}
|
||||
{{- if and (not $v.meilisearch.enabled) (not $v.meilisearch.external.url) }}
|
||||
{{- fail "meilisearch.enabled is false, so set meilisearch.external.url" }}
|
||||
{{- end }}
|
||||
{{- if and (not $v.seaweedfs.enabled) (not $v.s3.endpoint) }}
|
||||
{{- fail "seaweedfs.enabled is false, so set s3.endpoint to your S3 store" }}
|
||||
{{- end }}
|
||||
{{- if gt (int $v.nats.replicas) 1 }}
|
||||
{{- if lt (int $v.nats.replicas) 3 }}
|
||||
{{- fail "nats.replicas must be 1, or 3 or more for a JetStream cluster" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if gt (int (include "fluxer.replicas" (dict "w" $v.worker))) 1 }}
|
||||
{{- fail "worker.replicas must be 1, because the worker runs the cron scheduler" }}
|
||||
{{- end }}
|
||||
{{- if not (has $v.livekit.expose (list "hostNetwork" "loadBalancer" "nodePort")) }}
|
||||
{{- fail "livekit.expose must be hostNetwork, loadBalancer or nodePort" }}
|
||||
{{- end }}
|
||||
{{- if and $v.livekit.enabled $v.livekit.external.url }}
|
||||
{{- fail "livekit.external.url names a LiveKit server of your own, so set livekit.enabled to false" }}
|
||||
{{- end }}
|
||||
{{- if and $v.livekit.external.url (not (regexMatch "^(wss?|https?)://[^/]+" ($v.livekit.external.publicUrl | default ""))) }}
|
||||
{{- fail "livekit.external.url is set, so set livekit.external.publicUrl to the address browsers use to reach that LiveKit server, for example wss://livekit.example.com" }}
|
||||
{{- end }}
|
||||
{{- if and $v.livekit.enabled (eq $v.livekit.expose "loadBalancer") (not $v.livekit.nodeIP) }}
|
||||
{{- fail "livekit.expose is loadBalancer, so reserve a public address before installing, set livekit.nodeIP to it, and pin the Service livekit-rtc to it with livekit.loadBalancerIP or livekit.serviceAnnotations. Use hostNetwork or nodePort where a load balancer cannot serve TCP and UDP on one address" }}
|
||||
{{- end }}
|
||||
{{- if not (has $v.edge.expose (list "ingress" "gateway" "loadBalancer" "none")) }}
|
||||
{{- fail "edge.expose must be ingress, gateway, loadBalancer or none" }}
|
||||
{{- end }}
|
||||
{{- if not (has $v.edge.ingress.tls.mode (list "certManager" "existingSecret" "none")) }}
|
||||
{{- fail "edge.ingress.tls.mode must be certManager, existingSecret or none" }}
|
||||
{{- end }}
|
||||
{{- if and (eq $v.edge.expose "gateway") (not $v.edge.gateway.parentRefs) }}
|
||||
{{- fail "edge.expose is gateway, so set edge.gateway.parentRefs to the Gateway the route attaches to" }}
|
||||
{{- end }}
|
||||
{{- if and (eq $v.edge.expose "loadBalancer") (ne $v.scheme "https") }}
|
||||
{{- fail "edge.expose loadBalancer serves HTTPS itself, so scheme must be https" }}
|
||||
{{- end }}
|
||||
{{- if and $v.networkPolicy.enabled $v.networkPolicy.ingressNamespaceSelector $v.livekit.enabled (eq $v.livekit.expose "hostNetwork") (not $v.livekit.nodeCIDRs) }}
|
||||
{{- fail "networkPolicy.ingressNamespaceSelector limits who reaches the edge, and LiveKit on the host network sends its webhook to the edge from a node address. Set livekit.nodeCIDRs to the address of the LiveKit node, for example 10.0.0.12/32" }}
|
||||
{{- end }}
|
||||
|
||||
{{- $placeholder := "(?i)change[_-]?me" }}
|
||||
{{- $secretName := "_(PASSWORD|SECRET|TOKEN|COOKIE|KEY|KEYS|KEY_BASE|SECRETS?_BASE64)$" }}
|
||||
{{- $credentialUrl := "://[^/@ ]*:[^/@ ]*@" }}
|
||||
{{- $plain := dict "env" ($v.env | default dict) "appProxy.config" ($v.appProxy.config | default dict) "edge.env" ($v.edge.env | default dict) }}
|
||||
{{- range $name := list "api" "worker" "gateway" "mediaProxy" "push" "admin" "appProxy" "staticProxy" }}
|
||||
{{- $_ := set $plain (printf "%s.env" $name) ((get $v $name).env | default dict) }}
|
||||
{{- end }}
|
||||
{{- range $name, $svc := $v.svc }}
|
||||
{{- range $part := list "router" "shard" }}
|
||||
{{- $_ := set $plain (printf "svc.%s.%s.env" $name $part) ((get ($svc | default dict) $part | default dict).env | default dict) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $where, $map := $plain }}
|
||||
{{- range $k, $val := $map }}
|
||||
{{- $s := include "fluxer.string" ($val | default "") }}
|
||||
{{- if regexMatch $placeholder $s }}
|
||||
{{- fail (printf "%s.%s is a placeholder. Remove it or set a real value" $where $k) }}
|
||||
{{- end }}
|
||||
{{- if or (hasKey $v.secrets.values $k) (regexMatch $secretName $k) }}
|
||||
{{- fail (printf "%s.%s is a secret and would be stored in plain text. Set it in secrets.extra instead, or reference a Secret of your own from extraEnv" $where $k) }}
|
||||
{{- end }}
|
||||
{{- if regexMatch $credentialUrl $s }}
|
||||
{{- fail (printf "%s.%s is a URL that holds a password and would be stored in plain text. Set it in secrets.extra instead" $where $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $k, $val := $v.secrets.values }}
|
||||
{{- if regexMatch $placeholder (include "fluxer.string" ($val | default "")) }}
|
||||
{{- fail (printf "secrets.values.%s is a placeholder. Leave it empty to generate a value, or set a real one" $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range $k, $val := $extra }}
|
||||
{{- if regexMatch $placeholder (include "fluxer.string" ($val | default "")) }}
|
||||
{{- fail (printf "secrets.extra.%s is a placeholder. Remove it or set a real value" $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- $connected := false }}
|
||||
{{- if $v.clusterChecks }}
|
||||
{{- $connected = or (lookup "v1" "Namespace" "" .Release.Namespace) (lookup "v1" "Namespace" "" "kube-system") | not | not }}
|
||||
{{- end }}
|
||||
{{- $data := include "fluxer.secretData" . | fromJson }}
|
||||
{{- $secret := include "fluxer.secretName" . }}
|
||||
{{- if $v.secrets.existingSecret }}
|
||||
{{- if and $connected (not $data) }}
|
||||
{{- fail (printf "secrets.existingSecret names the Secret %s, which does not exist in namespace %s. Create it first with every key listed under secrets.values" $secret .Release.Namespace) }}
|
||||
{{- end }}
|
||||
{{- if $data }}
|
||||
{{- $missing := list }}
|
||||
{{- range $k := include "fluxer.requiredSecretKeys" . | fromJsonArray }}
|
||||
{{- if not (get $data $k) }}
|
||||
{{- $missing = append $missing $k }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $missing }}
|
||||
{{- fail (printf "Secret %s is missing these keys: %s" $secret (join ", " .)) }}
|
||||
{{- end }}
|
||||
{{- $config := include "fluxer.configData" . | fromYaml }}
|
||||
{{- if $v.valkey.enabled }}
|
||||
{{- $_ := set $config "FLUXER_KV_URL" "the bundled Valkey" }}
|
||||
{{- end }}
|
||||
{{- $shadowed := list }}
|
||||
{{- range $k := keys $config | sortAlpha }}
|
||||
{{- if and (hasKey $data $k) (ne (get $data $k) (get $config $k)) }}
|
||||
{{- $shadowed = append $shadowed $k }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $shadowed }}
|
||||
{{- fail (printf "Secret %s holds settings the chart sets itself, and the two disagree: %s. Remove them from the Secret. To keep an outside service, turn the bundled one off, as the External services section of the Kubernetes page describes" $secret (join ", " .)) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $data }}
|
||||
{{- range $k, $val := $data }}
|
||||
{{- if regexMatch $placeholder $val }}
|
||||
{{- fail (printf "%s in the Secret %s is a placeholder. Set a real value" $k $secret) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with get $data "FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64" }}
|
||||
{{- if or (not (regexMatch "^[A-Za-z0-9+/]+={0,2}$" .)) (lt (len (b64dec .)) 32) }}
|
||||
{{- fail "FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64 must be standard base64 of at least 32 bytes, for example the output of openssl rand -base64 32" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $long := list "FLUXER_SUDO_MODE_SECRET" "FLUXER_CONNECTION_INITIATION_SECRET" "FLUXER_PROFILE_PSEUDONYM_SECRET" "FLUXER_GATEWAY_RPC_AUTH_TOKEN" "FLUXER_ERLANG_COOKIE" "FLUXER_MEDIA_PROXY_SECRET_KEY" "FLUXER_ADMIN_SECRET_KEY_BASE" "FLUXER_ADMIN_OAUTH_CLIENT_SECRET" "FLUXER_SETUP_TOKEN" }}
|
||||
{{- if ne (include "fluxer.livekitMode" .) "off" }}
|
||||
{{- $long = append $long "FLUXER_LIVEKIT_API_SECRET" }}
|
||||
{{- end }}
|
||||
{{- if $v.valkey.enabled }}
|
||||
{{- $long = append $long "FLUXER_VALKEY_PASSWORD" }}
|
||||
{{- end }}
|
||||
{{- if $v.nats.enabled }}
|
||||
{{- $long = append $long "FLUXER_NATS_AUTH_TOKEN" }}
|
||||
{{- end }}
|
||||
{{- range $k := $long }}
|
||||
{{- if and (hasKey $data $k) (lt (len (get $data $k)) 32) }}
|
||||
{{- fail (printf "%s must be at least 32 characters, for example the output of openssl rand -hex 32" $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and $v.meilisearch.enabled (hasKey $data "FLUXER_SEARCH_API_KEY") (lt (len (get $data "FLUXER_SEARCH_API_KEY")) 16) }}
|
||||
{{- fail "FLUXER_SEARCH_API_KEY must be at least 16 characters, or the bundled Meilisearch refuses to start" }}
|
||||
{{- end }}
|
||||
{{- if eq (get $data "FLUXER_POSTGRES_PASSWORD" | default "") "fluxer" }}
|
||||
{{- fail "FLUXER_POSTGRES_PASSWORD must not be fluxer, which the services refuse in production" }}
|
||||
{{- end }}
|
||||
{{- $plainOnly := list }}
|
||||
{{- if $v.seaweedfs.enabled }}
|
||||
{{- $plainOnly = concat $plainOnly (list "FLUXER_S3_ACCESS_KEY_ID" "FLUXER_S3_SECRET_ACCESS_KEY") }}
|
||||
{{- end }}
|
||||
{{- if $v.valkey.enabled }}
|
||||
{{- $plainOnly = append $plainOnly "FLUXER_VALKEY_PASSWORD" }}
|
||||
{{- end }}
|
||||
{{- if $v.nats.enabled }}
|
||||
{{- $plainOnly = append $plainOnly "FLUXER_NATS_AUTH_TOKEN" }}
|
||||
{{- end }}
|
||||
{{- range $k := $plainOnly }}
|
||||
{{- if and (hasKey $data $k) (not (regexMatch "^[A-Za-z0-9]+$" (get $data $k))) }}
|
||||
{{- fail (printf "%s must contain letters and digits only, because the bundled service takes it on a command line or in a URL" $k) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $pub := get $data "FLUXER_VAPID_PUBLIC_KEY" | default "" }}
|
||||
{{- $priv := get $data "FLUXER_VAPID_PRIVATE_KEY" | default "" }}
|
||||
{{- if or $pub $priv }}
|
||||
{{- if or (not (regexMatch "^[A-Za-z0-9_-]{87}$" $pub)) (not (regexMatch "^[A-Za-z0-9_-]{43}$" $priv)) }}
|
||||
{{- fail "FLUXER_VAPID_PUBLIC_KEY and FLUXER_VAPID_PRIVATE_KEY must be an unpadded base64url P-256 pair of 87 and 43 characters" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- if $connected }}
|
||||
{{- if and (eq $v.edge.expose "ingress") (eq $v.edge.ingress.tls.mode "certManager") (not (.Capabilities.APIVersions.Has "cert-manager.io/v1")) }}
|
||||
{{- fail "edge.ingress.tls.mode is certManager, but cert-manager is not installed. Install cert-manager, or set edge.ingress.tls.mode to existingSecret or none, or set edge.expose to loadBalancer so the edge obtains its own certificate" }}
|
||||
{{- end }}
|
||||
{{- if and (eq $v.edge.expose "gateway") (not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1")) }}
|
||||
{{- fail "edge.expose is gateway, but the Gateway API is not installed in the cluster" }}
|
||||
{{- end }}
|
||||
{{- $emptyClass := and (eq $v.edge.expose "loadBalancer") (not $v.edge.loadBalancer.persistence.storageClassName) }}
|
||||
{{- range $c := list $v.postgres $v.valkey $v.nats $v.meilisearch $v.seaweedfs }}
|
||||
{{- if and $c.enabled (not $c.persistence.storageClassName) }}
|
||||
{{- $emptyClass = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if $emptyClass }}
|
||||
{{- $default := false }}
|
||||
{{- range (lookup "storage.k8s.io/v1" "StorageClass" "" "").items | default list }}
|
||||
{{- if eq (get (.metadata.annotations | default dict) "storageclass.kubernetes.io/is-default-class") "true" }}
|
||||
{{- $default = true }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not $default }}
|
||||
{{- fail "The cluster has no default StorageClass, so the volumes would never bind. Install a storage provisioner and mark its class as default, or set persistence.storageClassName on postgres, valkey, nats, meilisearch and seaweedfs" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if and (eq $v.edge.expose "ingress") (not $v.edge.ingress.className) }}
|
||||
{{- $classes := (lookup "networking.k8s.io/v1" "IngressClass" "" "").items | default list }}
|
||||
{{- if not $classes }}
|
||||
{{- fail "The cluster has no IngressClass, so nothing would serve the Ingress. Install an ingress controller, or set edge.expose to loadBalancer, gateway or none. Set clusterChecks to false if your controller serves an Ingress without a class" }}
|
||||
{{- end }}
|
||||
{{- $defaults := list }}
|
||||
{{- range $classes }}
|
||||
{{- if eq (get (.metadata.annotations | default dict) "ingressclass.kubernetes.io/is-default-class") "true" }}
|
||||
{{- $defaults = append $defaults .metadata.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if not $defaults }}
|
||||
{{- $names := list }}
|
||||
{{- range $classes }}
|
||||
{{- $names = append $names .metadata.name }}
|
||||
{{- end }}
|
||||
{{- fail (printf "The cluster has no default IngressClass. Set edge.ingress.className to one of: %s" (join ", " $names)) }}
|
||||
{{- end }}
|
||||
{{- if gt (len $defaults) 1 }}
|
||||
{{- fail (printf "The cluster marks more than one IngressClass as default (%s), so the Ingress may land on the wrong controller. Set edge.ingress.className" (join ", " $defaults)) }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,188 @@
|
||||
{{- define "fluxer.replicas" -}}
|
||||
{{- $w := .w | default dict }}
|
||||
{{- if kindIs "invalid" $w.replicas }}1{{ else }}{{ $w.replicas | int }}{{ end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.appPod" -}}
|
||||
{{- $root := .root }}
|
||||
{{- $w := .w | default dict }}
|
||||
{{- $readsSecret := and (not .noEnvFrom) (not .configMapOnly) }}
|
||||
{{- $env := .env | default list }}
|
||||
{{- if and $readsSecret $root.Values.valkey.enabled }}
|
||||
{{- $env = prepend $env (dict "name" "FLUXER_KV_URL" "value" "redis://:$(FLUXER_VALKEY_PASSWORD)@valkey:6379/0") }}
|
||||
{{- end }}
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" . | nindent 4 }}
|
||||
annotations:
|
||||
checksum/instance: {{ include "fluxer.instanceChecksum" $root }}
|
||||
{{- with $w.podAnnotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" $root | nindent 2 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict) | nindent 4 }}
|
||||
{{- include "fluxer.scheduling" . | nindent 2 }}
|
||||
{{- with $w.terminationGracePeriodSeconds }}
|
||||
terminationGracePeriodSeconds: {{ . }}
|
||||
{{- end }}
|
||||
{{- include "fluxer.waitFor" . | nindent 2 }}
|
||||
containers:
|
||||
- name: {{ .containerName | default .name }}
|
||||
image: {{ include "fluxer.image" . }}
|
||||
imagePullPolicy: {{ include "fluxer.pullPolicy" . }}
|
||||
{{- with .command }}
|
||||
command:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with include "fluxer.envList" (dict "w" $w "env" $env) }}
|
||||
env:
|
||||
{{- . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if not .noEnvFrom }}
|
||||
envFrom:
|
||||
{{- if .configMapOnly }}
|
||||
- configMapRef:
|
||||
name: {{ .configMapOnly }}
|
||||
{{- else }}
|
||||
- configMapRef:
|
||||
name: fluxer-config
|
||||
- secretRef:
|
||||
name: {{ include "fluxer.secretName" $root }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
{{- range .extraPorts }}
|
||||
- name: {{ .name }}
|
||||
containerPort: {{ .port }}
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- with include "fluxer.probes" . | trim }}
|
||||
{{- . | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- with .lifecycle }}
|
||||
lifecycle:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with $w.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 8 }}
|
||||
{{- with concat (.volumeMounts | default list) ($w.extraVolumeMounts | default list) }}
|
||||
volumeMounts:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with concat (.volumes | default list) ($w.extraVolumes | default list) }}
|
||||
volumes:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.app" -}}
|
||||
{{- $root := .root }}
|
||||
{{- $w := .w | default dict }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: {{ .kind | default "Deployment" }}
|
||||
metadata:
|
||||
name: {{ .name }}
|
||||
namespace: {{ $root.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- if not (and $w.hpa $w.hpa.maxReplicas) }}
|
||||
replicas: {{ include "fluxer.replicas" . }}
|
||||
{{- end }}
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" . | nindent 6 }}
|
||||
{{- if eq (.kind | default "Deployment") "StatefulSet" }}
|
||||
serviceName: {{ .serviceName }}
|
||||
podManagementPolicy: Parallel
|
||||
{{- else if .strategy }}
|
||||
strategy:
|
||||
{{- toYaml .strategy | nindent 4 }}
|
||||
{{- end }}
|
||||
template:
|
||||
{{- include "fluxer.appPod" . | nindent 4 }}
|
||||
{{- if not .noService }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ .name }}
|
||||
namespace: {{ $root.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
{{- if .headless }}
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" . | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .servicePort | default .port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- with $w.pdb }}
|
||||
{{- if or (not (kindIs "invalid" .minAvailable)) (not (kindIs "invalid" .maxUnavailable)) }}
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ $.name }}
|
||||
namespace: {{ $root.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ | nindent 4 }}
|
||||
spec:
|
||||
{{- if not (kindIs "invalid" .minAvailable) }}
|
||||
minAvailable: {{ .minAvailable }}
|
||||
{{- end }}
|
||||
{{- if not (kindIs "invalid" .maxUnavailable) }}
|
||||
maxUnavailable: {{ .maxUnavailable }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ | nindent 6 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- with $w.hpa }}
|
||||
{{- if .maxReplicas }}
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ $.name }}
|
||||
namespace: {{ $root.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ | nindent 4 }}
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: {{ $.kind | default "Deployment" }}
|
||||
name: {{ $.name }}
|
||||
minReplicas: {{ .minReplicas | default 1 }}
|
||||
maxReplicas: {{ .maxReplicas }}
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .targetCPUUtilizationPercentage | default 80 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,90 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- $v := .Values }}
|
||||
{{- $o := include "fluxer.origin" . }}
|
||||
{{- $http := dict "port" "http" }}
|
||||
{{- $start := dict "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 30 }}
|
||||
{{- $check := dict "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 3 }}
|
||||
{{- $health := merge (dict "path" "/_health") $http }}
|
||||
{{- $healthProbes := dict "startup" (merge (dict "httpGet" $health) $start) "liveness" (merge (dict "httpGet" $health) $check) "readiness" (merge (dict "httpGet" $health) $check) }}
|
||||
{{- $nodeEnv := list (dict "name" "NODE_ENV" "value" "production") (dict "name" "NODE_OPTIONS" "value" "--enable-source-maps") (dict "name" "NODE_EXTRA_CA_CERTS" "value" "/etc/ssl/certs/ca-certificates.crt") (dict "name" "FLUXER_POSTGRES_MAX_CONNECTIONS" "value" "25") }}
|
||||
|
||||
{{- include "fluxer.app" (dict "root" . "name" "api" "component" "api" "w" $v.api "image" "fluxer-api" "port" 8080
|
||||
"env" (concat (list (dict "name" "FLUXER_API_PORT" "value" "8080") (dict "name" "FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED" "value" "true")) $nodeEnv)
|
||||
"probes" $healthProbes
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" . "usesNats" true "usesPostgres" true "usesS3" true "usesKv" true "usesSvc" true) | fromJsonArray)) }}
|
||||
|
||||
{{- $heartbeat := dict "exec" (dict "command" (list "node" "-e" "const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}")) "initialDelaySeconds" 90 "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 3 }}
|
||||
{{- include "fluxer.app" (dict "root" . "name" "worker" "component" "worker" "w" $v.worker "image" "fluxer-api" "port" 8080 "noService" true
|
||||
"command" (list "node" "dist/WorkerEntrypoint.js")
|
||||
"env" (concat (list (dict "name" "FLUXER_API_WORKER_MODE" "value" "all_lanes") (dict "name" "FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER" "value" "true")) $nodeEnv)
|
||||
"probes" (dict "liveness" $heartbeat)
|
||||
"strategy" (dict "type" "Recreate")
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" . "usesNats" true "usesPostgres" true "usesS3" true "usesKv" true "usesSvc" true) | fromJsonArray)) }}
|
||||
|
||||
{{- $gatewayEnv := list
|
||||
(dict "name" "FLUXER_GATEWAY_PORT" "value" "8080")
|
||||
(dict "name" "FLUXER_GATEWAY_ROLE" "value" "all")
|
||||
(dict "name" "POD_IP" "valueFrom" (dict "fieldRef" (dict "apiVersion" "v1" "fieldPath" "status.podIP")))
|
||||
(dict "name" "FLUXER_ERLANG_NODE_NAME" "value" "fluxer_gateway@$(POD_IP)")
|
||||
(dict "name" "FLUXER_ERLANG_DIST_PORT" "value" "8081")
|
||||
(dict "name" "FLUXER_GATEWAY_CLUSTER_ENABLED" "value" "true")
|
||||
(dict "name" "FLUXER_GATEWAY_CLUSTER_DISCOVERY_DNS_NAME" "value" (printf "gateway-headless.%s.svc.%s" .Release.Namespace $v.clusterDomain))
|
||||
(dict "name" "FLUXER_GATEWAY_CLUSTER_DISCOVERY_NODE_BASENAME" "value" "fluxer_gateway") }}
|
||||
{{- $gatewayReady := dict "exec" (dict "command" (list "curl" "-fsS" "-o" "/dev/null" "--max-time" "4" "http://127.0.0.1:8080/_health/ready")) "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 3 }}
|
||||
{{- $gatewayCtx := dict "root" . "name" "gateway" "component" "gateway" "w" $v.gateway "image" "fluxer-gateway" "port" 8080
|
||||
"extraPorts" (list (dict "name" "epmd" "port" 4369) (dict "name" "erl-dist" "port" 8081))
|
||||
"env" $gatewayEnv
|
||||
"probes" (dict "startup" (merge (dict "httpGet" $health) $start) "liveness" (merge (dict "httpGet" $health) $check) "readiness" $gatewayReady)
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" . "usesNats" true) | fromJsonArray)
|
||||
"lifecycle" (dict "preStop" (dict "exec" (dict "command" (list "/bin/sh" "-c" "curl -fsS -o /dev/null --max-time 2 http://127.0.0.1:8080/_health/drain; sleep 5")))) }}
|
||||
{{- include "fluxer.app" $gatewayCtx }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: gateway-headless
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $gatewayCtx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $gatewayCtx | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
- name: epmd
|
||||
port: 4369
|
||||
targetPort: epmd
|
||||
protocol: TCP
|
||||
- name: erl-dist
|
||||
port: 8081
|
||||
targetPort: erl-dist
|
||||
protocol: TCP
|
||||
|
||||
{{- include "fluxer.app" (dict "root" . "name" "media-proxy" "component" "media" "w" $v.mediaProxy "image" "fluxer-media-proxy" "port" 8080
|
||||
"env" (list (dict "name" "FLUXER_MEDIA_PROXY_PORT" "value" "8080") (dict "name" "FLUXER_MEDIA_PROXY_MODE" "value" "upload") (dict "name" "FLUXER_MEDIA_PROXY_STORAGE_BACKEND" "value" "s3") (dict "name" "FLUXER_S3_READ_SIGNED" "value" "true"))
|
||||
"probes" $healthProbes
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" . "usesNats" true "usesS3" true) | fromJsonArray)) }}
|
||||
|
||||
{{- $pushCheck := dict "exec" (dict "command" (list "/usr/local/bin/fluxer-push" "healthcheck")) }}
|
||||
{{- include "fluxer.app" (dict "root" . "name" "push" "component" "push" "w" $v.push "image" "fluxer-push" "port" 8126
|
||||
"env" (list (dict "name" "FLUXER_PUSH_SERVICE_PORT" "value" "8126"))
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" . "usesNats" true) | fromJsonArray)
|
||||
"probes" (dict "startup" (merge (deepCopy $pushCheck) (dict "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 12)) "liveness" (merge (deepCopy $pushCheck) $check) "readiness" (merge (deepCopy $pushCheck) $check))) }}
|
||||
|
||||
{{- include "fluxer.app" (dict "root" . "name" "admin" "component" "admin" "w" $v.admin "image" "fluxer-admin" "port" 8080
|
||||
"env" (list (dict "name" "FLUXER_ADMIN_PORT" "value" "8080") (dict "name" "FLUXER_ADMIN_BASE_PATH" "value" $v.adminBasePath) (dict "name" "FLUXER_API_ENDPOINT" "value" "http://api:8080") (dict "name" "FLUXER_STATIC_CDN_ENDPOINT" "value" $o))
|
||||
"probes" $healthProbes) }}
|
||||
|
||||
{{- $ready := merge (dict "path" "/_ready") $http }}
|
||||
{{- include "fluxer.app" (dict "root" . "name" "app-proxy" "component" "web" "w" $v.appProxy "image" "fluxer-app-proxy-self-hosted" "port" 8080 "configMapOnly" "fluxer-app-proxy"
|
||||
"probes" (dict "startup" (merge (dict "httpGet" $health) $start) "liveness" (merge (dict "httpGet" $health) $check) "readiness" (merge (dict "httpGet" $ready) $check))) }}
|
||||
|
||||
{{- $avatar := merge (dict "path" "/avatars/0.png") $http }}
|
||||
{{- include "fluxer.app" (dict "root" . "name" "static-proxy" "component" "web" "w" $v.staticProxy "image" "fluxer-static" "port" 8080 "noEnvFrom" true
|
||||
"probes" (dict "startup" (merge (dict "httpGet" $avatar) $start) "liveness" (merge (dict "httpGet" $avatar) $check) "readiness" (merge (dict "httpGet" $avatar) $check))) }}
|
||||
@@ -0,0 +1,20 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: fluxer-config
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-config" "component" "config") | nindent 4 }}
|
||||
data:
|
||||
{{- include "fluxer.configData" . | nindent 2 }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: fluxer-app-proxy
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-app-proxy" "component" "config") | nindent 4 }}
|
||||
data:
|
||||
{{- include "fluxer.appProxyConfigData" . | nindent 2 }}
|
||||
@@ -0,0 +1,296 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- $v := .Values }}
|
||||
{{- $e := $v.edge }}
|
||||
{{- $ctx := dict "root" . "name" "edge" "component" "edge" "w" $e }}
|
||||
{{- $lb := eq $e.expose "loadBalancer" }}
|
||||
{{- $caddyfile := .Files.Get "files/Caddyfile" }}
|
||||
{{- $globalOptions := ternary (printf "email %s" $v.email) "" (and $lb (ne $v.email "")) }}
|
||||
{{- $siteAddress := ternary (printf "https://%s, :8080" $v.domain) ":8080" $lb }}
|
||||
{{- $trusted := ternary "127.0.0.1/32" "private_ranges" $lb }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: edge-caddyfile
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
data:
|
||||
Caddyfile: {{ $caddyfile | quote }}
|
||||
{{- if $lb }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: edge-data
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
{{- with $e.loadBalancer.persistence.storageClassName }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ $e.loadBalancer.persistence.size }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: edge
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ ternary 1 ($e.replicas | int) $lb }}
|
||||
revisionHistoryLimit: 3
|
||||
{{- if $lb }}
|
||||
strategy:
|
||||
type: Recreate
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ printf "%s\n%s\n%s\n%s" $caddyfile $siteAddress $globalOptions (toJson (list $e.env $e.extraEnv)) | sha256sum }}
|
||||
{{- with $e.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" . | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 65532) | nindent 8 }}
|
||||
{{- if $lb }}
|
||||
sysctls:
|
||||
- name: net.ipv4.ip_unprivileged_port_start
|
||||
value: "0"
|
||||
{{- end }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: edge
|
||||
image: {{ include "fluxer.thirdPartyImage" $e.image }}
|
||||
imagePullPolicy: {{ $e.image.pullPolicy }}
|
||||
{{- $edgeEnv := list (dict "name" "FLUXER_EDGE_SITE_ADDRESS" "value" $siteAddress) (dict "name" "FLUXER_EDGE_TRUSTED_PROXIES" "value" $trusted) (dict "name" "FLUXER_EDGE_GLOBAL_OPTIONS" "value" $globalOptions) (dict "name" "FLUXER_ADMIN_BASE_PATH" "value" $v.adminBasePath) }}
|
||||
env:
|
||||
{{- include "fluxer.envList" (dict "w" $e "env" $edgeEnv) | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
{{- if $lb }}
|
||||
- name: public-http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
- name: public-https
|
||||
containerPort: 443
|
||||
protocol: TCP
|
||||
- name: public-quic
|
||||
containerPort: 443
|
||||
protocol: UDP
|
||||
{{- end }}
|
||||
startupProbe:
|
||||
exec:
|
||||
command: ["wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
|
||||
periodSeconds: 5
|
||||
failureThreshold: 24
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
{{- with $e.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- NET_BIND_SERVICE
|
||||
volumeMounts:
|
||||
- name: caddyfile
|
||||
mountPath: /etc/caddy
|
||||
readOnly: true
|
||||
- name: config
|
||||
mountPath: /config
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: caddyfile
|
||||
configMap:
|
||||
name: edge-caddyfile
|
||||
- name: config
|
||||
emptyDir: {}
|
||||
- name: data
|
||||
{{- if $lb }}
|
||||
persistentVolumeClaim:
|
||||
claimName: edge-data
|
||||
{{- else }}
|
||||
emptyDir: {}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: edge
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 8080
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
{{- if $lb }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: edge-public
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
{{- with $e.loadBalancer.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
externalTrafficPolicy: Local
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: public-http
|
||||
protocol: TCP
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: public-https
|
||||
protocol: TCP
|
||||
{{- if $e.loadBalancer.http3 }}
|
||||
- name: quic
|
||||
port: 443
|
||||
targetPort: public-quic
|
||||
protocol: UDP
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $e.expose "ingress" }}
|
||||
{{- $tls := $e.ingress.tls }}
|
||||
{{- $annotations := dict
|
||||
"nginx.ingress.kubernetes.io/proxy-body-size" "512m"
|
||||
"nginx.ingress.kubernetes.io/proxy-read-timeout" "3600"
|
||||
"nginx.ingress.kubernetes.io/proxy-send-timeout" "3600" }}
|
||||
{{- if eq $tls.mode "certManager" }}
|
||||
{{- $_ := set $annotations "cert-manager.io/issuer" "fluxer-acme" }}
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Issuer
|
||||
metadata:
|
||||
name: fluxer-acme
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
acme:
|
||||
server: {{ $tls.acmeServer }}
|
||||
{{- with $v.email }}
|
||||
email: {{ . }}
|
||||
{{- end }}
|
||||
privateKeySecretRef:
|
||||
name: fluxer-acme-account
|
||||
solvers:
|
||||
- http01:
|
||||
{{- with $e.ingress.className }}
|
||||
ingress:
|
||||
ingressClassName: {{ . }}
|
||||
{{- else }}
|
||||
ingress: {}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- $annotations = merge (deepCopy ($e.ingress.annotations | default dict)) $annotations }}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: fluxer
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
{{- with $annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with $e.ingress.className }}
|
||||
ingressClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- if ne $tls.mode "none" }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ $v.domain }}
|
||||
secretName: {{ $tls.secretName }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ $v.domain }}
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: edge
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
{{- if eq $e.expose "gateway" }}
|
||||
---
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: fluxer
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
parentRefs:
|
||||
{{- toYaml $e.gateway.parentRefs | nindent 4 }}
|
||||
hostnames:
|
||||
- {{ $v.domain }}
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: edge
|
||||
port: 8080
|
||||
{{- with $e.gateway.timeouts }}
|
||||
timeouts:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,156 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.livekit }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "livekit" "component" "voice" "w" . }}
|
||||
{{- $tcp := .tcpPort | int }}
|
||||
{{- $udp := .udpPort | int }}
|
||||
{{- if eq .expose "nodePort" }}
|
||||
{{- $tcp = .nodePorts.tcp | int }}
|
||||
{{- $udp = .nodePorts.udp | int }}
|
||||
{{- end }}
|
||||
{{- $useExternal := and .useExternalIP (ne .expose "loadBalancer") }}
|
||||
{{- $secrets := include "fluxer.secretData" $ | fromJson }}
|
||||
{{- $config := dict
|
||||
"port" 7880
|
||||
"log_level" .logLevel
|
||||
"rtc" (dict "tcp_port" $tcp "udp_port" $udp "use_external_ip" $useExternal "node_ip" (.nodeIP | default "") "stun_servers" .stunServers) }}
|
||||
{{- $config = mergeOverwrite $config (deepCopy (.extraConfig | default dict)) }}
|
||||
{{- $configYaml := toYaml $config }}
|
||||
{{- $root := dict "path" "/" "port" "http" }}
|
||||
{{- $webhook := ternary "http://edge:8080/api/webhooks/livekit" "http://api:8080/webhooks/livekit" (eq .expose "hostNetwork") }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: livekit
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 7880
|
||||
targetPort: 7880
|
||||
protocol: TCP
|
||||
{{- if ne .expose "hostNetwork" }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: livekit-rtc
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
{{- with .serviceAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ ternary "LoadBalancer" "NodePort" (eq .expose "loadBalancer") }}
|
||||
externalTrafficPolicy: Local
|
||||
{{- if and (eq .expose "loadBalancer") .loadBalancerIP }}
|
||||
loadBalancerIP: {{ .loadBalancerIP }}
|
||||
{{- end }}
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: rtc-tcp
|
||||
port: {{ $tcp }}
|
||||
targetPort: rtc-tcp
|
||||
protocol: TCP
|
||||
{{- if eq .expose "nodePort" }}
|
||||
nodePort: {{ $tcp }}
|
||||
{{- end }}
|
||||
- name: rtc-udp
|
||||
port: {{ $udp }}
|
||||
targetPort: rtc-udp
|
||||
protocol: UDP
|
||||
{{- if eq .expose "nodePort" }}
|
||||
nodePort: {{ $udp }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: livekit
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 3
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ list $configYaml $webhook (get $secrets "FLUXER_LIVEKIT_API_KEY" | default "") (get $secrets "FLUXER_LIVEKIT_API_SECRET" | default "") | toJson | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- if eq .expose "hostNetwork" }}
|
||||
hostNetwork: true
|
||||
dnsPolicy: ClusterFirstWithHostNet
|
||||
{{- end }}
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 65532) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: livekit
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
export LIVEKIT_KEYS="${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}"
|
||||
export LIVEKIT_CONFIG="$(printf '%s\nwebhook:\n api_key: %s\n urls:\n - %s\n' "$LIVEKIT_CONFIG" "$LIVEKIT_API_KEY" "$LIVEKIT_WEBHOOK_URL")"
|
||||
exec /livekit-server
|
||||
env:
|
||||
- name: LIVEKIT_CONFIG
|
||||
value: {{ $configYaml | quote }}
|
||||
- name: LIVEKIT_WEBHOOK_URL
|
||||
value: {{ $webhook }}
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "LIVEKIT_API_KEY" "key" "FLUXER_LIVEKIT_API_KEY") | nindent 12 }}
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "LIVEKIT_API_SECRET" "key" "FLUXER_LIVEKIT_API_SECRET") | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 7880
|
||||
protocol: TCP
|
||||
- name: rtc-tcp
|
||||
containerPort: {{ $tcp }}
|
||||
protocol: TCP
|
||||
- name: rtc-udp
|
||||
containerPort: {{ $udp }}
|
||||
protocol: UDP
|
||||
startupProbe:
|
||||
httpGet: {{ toJson $root }}
|
||||
periodSeconds: 5
|
||||
failureThreshold: 60
|
||||
livenessProbe:
|
||||
httpGet: {{ toJson $root }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet: {{ toJson $root }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,97 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.meilisearch }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "meilisearch" "component" "search" "w" . }}
|
||||
{{- $health := dict "path" "/health" "port" "http" }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: meilisearch
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: http
|
||||
port: 7700
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: meilisearch
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: meilisearch
|
||||
podManagementPolicy: Parallel
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 65532) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: meilisearch
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
env:
|
||||
- name: MEILI_ENV
|
||||
value: production
|
||||
- name: MEILI_NO_ANALYTICS
|
||||
value: "true"
|
||||
- name: MEILI_UPGRADE_DB
|
||||
value: "true"
|
||||
- name: MEILI_MAX_INDEXING_MEMORY
|
||||
value: {{ .maxIndexingMemory | quote }}
|
||||
- name: MEILI_MAX_INDEXING_THREADS
|
||||
value: {{ include "fluxer.string" .maxIndexingThreads | quote }}
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "MEILI_MASTER_KEY" "key" "FLUXER_SEARCH_API_KEY") | nindent 12 }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 7700
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet: {{ toJson $health }}
|
||||
periodSeconds: 5
|
||||
failureThreshold: 120
|
||||
livenessProbe:
|
||||
httpGet: {{ toJson $health }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet: {{ toJson $health }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /meili_data
|
||||
volumeClaimTemplates:
|
||||
{{- include "fluxer.pvc" .persistence | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,169 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.nats }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "nats" "component" "messaging" "w" . }}
|
||||
{{- $replicas := .replicas | int }}
|
||||
{{- $conf := include "fluxer.natsConf" (dict "root" $ "replicas" $replicas) }}
|
||||
{{- $secrets := include "fluxer.secretData" $ | fromJson }}
|
||||
{{- $healthz := dict "exec" (dict "command" (list "wget" "-q" "-O" "/dev/null" "http://127.0.0.1:8222/healthz")) }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: nats-config
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
data:
|
||||
nats.conf: {{ $conf | quote }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nats
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: client
|
||||
port: 4222
|
||||
targetPort: client
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nats-headless
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: client
|
||||
port: 4222
|
||||
targetPort: client
|
||||
protocol: TCP
|
||||
- name: cluster
|
||||
port: 6222
|
||||
targetPort: cluster
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: nats
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ $replicas }}
|
||||
serviceName: nats-headless
|
||||
podManagementPolicy: Parallel
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ printf "%s\n%s" $conf (get $secrets "FLUXER_NATS_AUTH_TOKEN" | default "") | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 65534) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: nats
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- sed "s/__FLUXER_NATS_AUTH_TOKEN__/$FLUXER_NATS_AUTH_TOKEN/g" /etc/nats/nats.conf > /run/nats/nats.conf && exec nats-server --config /run/nats/nats.conf
|
||||
env:
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
apiVersion: v1
|
||||
fieldPath: metadata.name
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "FLUXER_NATS_AUTH_TOKEN" "key" "FLUXER_NATS_AUTH_TOKEN") | nindent 12 }}
|
||||
ports:
|
||||
- name: client
|
||||
containerPort: 4222
|
||||
protocol: TCP
|
||||
- name: cluster
|
||||
containerPort: 6222
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
{{- toYaml (merge (dict "periodSeconds" 5 "timeoutSeconds" 5 "failureThreshold" 60) $healthz) | nindent 12 }}
|
||||
livenessProbe:
|
||||
{{- toYaml (merge (dict "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 6) $healthz) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml (merge (dict "periodSeconds" 10 "timeoutSeconds" 5) $healthz) | nindent 12 }}
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/nats
|
||||
readOnly: true
|
||||
- name: rendered
|
||||
mountPath: /run/nats
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: nats-config
|
||||
- name: rendered
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
volumeClaimTemplates:
|
||||
{{- include "fluxer.pvc" .persistence | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "fluxer.natsConf" -}}
|
||||
server_name: $POD_NAME
|
||||
listen: 0.0.0.0:4222
|
||||
http: 127.0.0.1:8222
|
||||
authorization {
|
||||
token: "__FLUXER_NATS_AUTH_TOKEN__"
|
||||
}
|
||||
jetstream {
|
||||
store_dir: /data
|
||||
}
|
||||
{{- if gt .replicas 1 }}
|
||||
cluster {
|
||||
name: fluxer
|
||||
listen: 0.0.0.0:6222
|
||||
authorization {
|
||||
user: fluxer
|
||||
password: "__FLUXER_NATS_AUTH_TOKEN__"
|
||||
}
|
||||
routes: [
|
||||
{{- range $i := until .replicas }}
|
||||
nats://fluxer:__FLUXER_NATS_AUTH_TOKEN__@nats-{{ $i }}.nats-headless.{{ $.root.Release.Namespace }}.svc.{{ $.root.Values.clusterDomain }}:6222
|
||||
{{- end }}
|
||||
]
|
||||
}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,83 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- if .Values.networkPolicy.enabled }}
|
||||
{{- $v := .Values }}
|
||||
{{- $release := dict "app.kubernetes.io/instance" .Release.Name }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: fluxer
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer" "component" "network") | nindent 4 }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- toYaml $release | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
{{- toYaml $release | nindent 14 }}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: fluxer-edge
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-edge" "component" "network") | nindent 4 }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" (dict "root" . "name" "edge") | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
{{- with $v.networkPolicy.ingressNamespaceSelector }}
|
||||
from:
|
||||
- namespaceSelector:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- if and $v.livekit.enabled (eq $v.livekit.expose "hostNetwork") }}
|
||||
{{- range $v.livekit.nodeCIDRs }}
|
||||
- ipBlock:
|
||||
cidr: {{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if eq $v.edge.expose "loadBalancer" }}
|
||||
- ports:
|
||||
- port: 80
|
||||
protocol: TCP
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
- port: 443
|
||||
protocol: UDP
|
||||
{{- end }}
|
||||
{{- if and $v.livekit.enabled (ne $v.livekit.expose "hostNetwork") }}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: fluxer-livekit
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-livekit" "component" "network") | nindent 4 }}
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" (dict "root" . "name" "livekit") | nindent 6 }}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- ports:
|
||||
- port: {{ ternary $v.livekit.nodePorts.tcp $v.livekit.tcpPort (eq $v.livekit.expose "nodePort") }}
|
||||
protocol: TCP
|
||||
- port: {{ ternary $v.livekit.nodePorts.udp $v.livekit.udpPort (eq $v.livekit.expose "nodePort") }}
|
||||
protocol: UDP
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,147 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.postgres }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "postgres" "component" "database" "w" . }}
|
||||
{{- $args := list }}
|
||||
{{- range $k := keys .parameters | sortAlpha }}
|
||||
{{- $args = concat $args (list "-c" (printf "%s=%s" $k (include "fluxer.string" (get $.Values.postgres.parameters $k)))) }}
|
||||
{{- end }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: postgres
|
||||
port: 5432
|
||||
targetPort: postgres
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: postgres
|
||||
podManagementPolicy: Parallel
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ toJson $args | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
terminationGracePeriodSeconds: 60
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" (.runAsUser | int)) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
initContainers:
|
||||
- name: version-guard
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
want=$(postgres --version | sed -E 's/[^0-9]*([0-9]+).*/\1/')
|
||||
if [ -s "$PGDATA/PG_VERSION" ]; then
|
||||
have=$(cat "$PGDATA/PG_VERSION")
|
||||
if [ "$have" != "$want" ]; then
|
||||
echo "The data directory was created by Postgres $have and this image runs Postgres $want. Set postgres.image.tag back to the $have image, or dump the database and restore it into a fresh volume. See the Kubernetes page of the operator docs." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
env:
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
containers:
|
||||
- name: postgres
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
args:
|
||||
{{- toYaml $args | nindent 12 }}
|
||||
env:
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
- name: POSTGRES_DB
|
||||
value: {{ .database | quote }}
|
||||
- name: POSTGRES_USER
|
||||
value: {{ .username | quote }}
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "POSTGRES_PASSWORD" "key" "FLUXER_POSTGRES_PASSWORD") | nindent 12 }}
|
||||
ports:
|
||||
- name: postgres
|
||||
containerPort: 5432
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", "pg_isready -U \"$POSTGRES_USER\" -d \"$POSTGRES_DB\""]
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 30
|
||||
livenessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", "pg_isready -U \"$POSTGRES_USER\" -d \"$POSTGRES_DB\""]
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["sh", "-c", "pg_isready -U \"$POSTGRES_USER\" -d \"$POSTGRES_DB\""]
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
- name: shm
|
||||
mountPath: /dev/shm
|
||||
- name: run
|
||||
mountPath: /var/run/postgresql
|
||||
volumes:
|
||||
- name: shm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: {{ .shmSize }}
|
||||
- name: run
|
||||
emptyDir: {}
|
||||
volumeClaimTemplates:
|
||||
{{- include "fluxer.pvc" .persistence | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,136 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.seaweedfs }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "seaweedfs" "component" "storage" "w" . }}
|
||||
{{- $healthz := dict "path" "/healthz" "port" "s3" }}
|
||||
{{- $secrets := include "fluxer.secretData" $ | fromJson }}
|
||||
{{- $init := $.Files.Get "files/seaweedfs-init.sh" }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: seaweedfs
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: s3
|
||||
port: 8333
|
||||
targetPort: s3
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: seaweedfs
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: seaweedfs
|
||||
podManagementPolicy: Parallel
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/init: {{ list $init (get $secrets "FLUXER_S3_ACCESS_KEY_ID" | default "") (get $secrets "FLUXER_S3_SECRET_ACCESS_KEY" | default "") $.Values.s3.buckets | toJson | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 1000) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: seaweedfs
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
args:
|
||||
- server
|
||||
- -s3
|
||||
- -dir=/data
|
||||
- -master.telemetry=false
|
||||
- -ip=127.0.0.1
|
||||
- -ip.bind=127.0.0.1
|
||||
- -s3.ip.bind=0.0.0.0
|
||||
env:
|
||||
- name: GOMEMLIMIT
|
||||
value: {{ .gomemlimit | quote }}
|
||||
- name: WEED_MASTER_VOLUME_GROWTH_COPY_1
|
||||
value: {{ include "fluxer.string" .volumeGrowth | quote }}
|
||||
ports:
|
||||
- name: s3
|
||||
containerPort: 8333
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet: {{ toJson $healthz }}
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 24
|
||||
livenessProbe:
|
||||
httpGet: {{ toJson $healthz }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readinessProbe:
|
||||
httpGet: {{ toJson $healthz }}
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: seaweedfs-init
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- {{ $init | quote }}
|
||||
env:
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "FLUXER_S3_ACCESS_KEY" "key" "FLUXER_S3_ACCESS_KEY_ID") | nindent 12 }}
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "FLUXER_S3_SECRET_KEY" "key" "FLUXER_S3_SECRET_ACCESS_KEY") | nindent 12 }}
|
||||
- name: FLUXER_S3_BUCKET_CDN
|
||||
value: {{ $.Values.s3.buckets.cdn | quote }}
|
||||
- name: FLUXER_S3_BUCKET_UPLOADS
|
||||
value: {{ $.Values.s3.buckets.uploads | quote }}
|
||||
- name: FLUXER_S3_BUCKET_REPORTS
|
||||
value: {{ $.Values.s3.buckets.reports | quote }}
|
||||
- name: FLUXER_S3_BUCKET_HARVESTS
|
||||
value: {{ $.Values.s3.buckets.harvests | quote }}
|
||||
- name: FLUXER_SEAWEEDFS_INIT_ATTEMPTS
|
||||
value: {{ include "fluxer.string" .initAttempts | quote }}
|
||||
readinessProbe:
|
||||
exec:
|
||||
command: ["test", "-f", "/tmp/fluxer-buckets-ready"]
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 128Mi
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeClaimTemplates:
|
||||
{{- include "fluxer.pvc" .persistence | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- if not .Values.secrets.existingSecret }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: fluxer-env
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-env" "component" "config") | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/resource-policy: keep
|
||||
type: Opaque
|
||||
data:
|
||||
{{- range $k, $val := include "fluxer.secretData" . | fromJson }}
|
||||
{{ $k }}: {{ $val | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- $healthz := dict "path" "/_healthz" "port" "http" }}
|
||||
{{- $health := dict "path" "/_health" "port" "http" }}
|
||||
{{- $probes := dict
|
||||
"startup" (dict "httpGet" $healthz "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 12)
|
||||
"liveness" (dict "httpGet" $healthz "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 3)
|
||||
"readiness" (dict "httpGet" $health "periodSeconds" 10 "timeoutSeconds" 5 "failureThreshold" 3) }}
|
||||
{{- range $name := list "gifs" "messages" "snowflakes" "unfurl" "users" }}
|
||||
{{- $svc := get $.Values.svc $name | default dict }}
|
||||
{{- $router := $svc.router | default dict }}
|
||||
{{- $shard := $svc.shard | default dict }}
|
||||
{{- $count := include "fluxer.replicas" (dict "w" $shard) }}
|
||||
{{- $common := list (dict "name" "FLUXER_SVC_NAME" "value" $name) (dict "name" "FLUXER_SVC_PORT" "value" "8090") (dict "name" "FLUXER_SVC_SHARD_COUNT" "value" $count) }}
|
||||
{{- $shardEnv := concat $common (list (dict "name" "FLUXER_SVC_MODE" "value" "shard") (dict "name" "FLUXER_SVC_SHARD_ID" "valueFrom" (dict "fieldRef" (dict "apiVersion" "v1" "fieldPath" "metadata.labels['apps.kubernetes.io/pod-index']"))) (dict "name" "POD_NAME" "valueFrom" (dict "fieldRef" (dict "apiVersion" "v1" "fieldPath" "metadata.name")))) }}
|
||||
{{- $usesPostgres := has $name (list "users" "messages") }}
|
||||
{{- if $usesPostgres }}
|
||||
{{- $shardEnv = append $shardEnv (dict "name" "FLUXER_POSTGRES_MAX_CONNECTIONS" "value" "20") }}
|
||||
{{- end }}
|
||||
{{- if eq $name "unfurl" }}
|
||||
{{- $shardEnv = append $shardEnv (dict "name" "FLUXER_MEDIA_PROXY_ENDPOINT" "value" "http://media-proxy:8080") }}
|
||||
{{- end }}
|
||||
{{- include "fluxer.app" (dict "root" $ "name" $name "component" "svc" "w" $router "image" (printf "fluxer-%s" $name) "port" 8090 "containerName" "router"
|
||||
"env" (append $common (dict "name" "FLUXER_SVC_MODE" "value" "router"))
|
||||
"probes" $probes
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" $ "usesNats" true) | fromJsonArray)) }}
|
||||
{{- include "fluxer.app" (dict "root" $ "name" (printf "%s-shard" $name) "component" "svc" "w" $shard "image" (printf "fluxer-%s" $name) "port" 8090 "containerName" "shard"
|
||||
"kind" "StatefulSet" "serviceName" (printf "%s-shard" $name) "headless" true
|
||||
"env" $shardEnv
|
||||
"probes" $probes
|
||||
"waitFor" (include "fluxer.appWaitFor" (dict "root" $ "usesNats" true "usesPostgres" $usesPostgres) | fromJsonArray)) }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,46 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: fluxer-test-edge
|
||||
namespace: {{ .Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" (dict "root" . "name" "fluxer-test" "component" "test") | nindent 4 }}
|
||||
annotations:
|
||||
helm.sh/hook: test
|
||||
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" . | nindent 2 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 65532) | nindent 4 }}
|
||||
containers:
|
||||
- name: check
|
||||
image: {{ include "fluxer.thirdPartyImage" .Values.edge.image }}
|
||||
imagePullPolicy: {{ .Values.edge.image.pullPolicy }}
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
for i in $(seq 1 30); do
|
||||
if wget -q -O /dev/null http://edge:8080/_health && wget -q -O - --header "Host: $DOMAIN" http://edge:8080/.well-known/fluxer > /tmp/discovery && grep -q "$DOMAIN" /tmp/discovery; then
|
||||
echo "edge and discovery answer for $DOMAIN"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
echo "edge or discovery did not answer for $DOMAIN within 60 seconds" >&2
|
||||
exit 1
|
||||
env:
|
||||
- name: DOMAIN
|
||||
value: {{ .Values.domain | quote }}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 16Mi
|
||||
limits:
|
||||
memory: 64Mi
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 8 }}
|
||||
@@ -0,0 +1,84 @@
|
||||
{{- include "fluxer.validate" . }}
|
||||
{{- with .Values.valkey }}
|
||||
{{- if .enabled }}
|
||||
{{- $ctx := dict "root" $ "name" "valkey" "component" "cache" "w" . }}
|
||||
{{- $args := list "valkey-server" "--appendonly" "yes" "--appendfsync" .appendfsync "--dir" "/data" "--maxmemory" .maxmemory "--maxmemory-policy" .maxmemoryPolicy "--requirepass" "$(REDISCLI_AUTH)" }}
|
||||
{{- $ping := dict "exec" (dict "command" (list "sh" "-c" "valkey-cli ping | grep -q PONG")) "periodSeconds" 10 "timeoutSeconds" 5 }}
|
||||
{{- $secrets := include "fluxer.secretData" $ | fromJson }}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: valkey
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 4 }}
|
||||
ports:
|
||||
- name: redis
|
||||
port: 6379
|
||||
targetPort: redis
|
||||
protocol: TCP
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: valkey
|
||||
namespace: {{ $.Release.Namespace }}
|
||||
labels:
|
||||
{{- include "fluxer.labels" $ctx | nindent 4 }}
|
||||
spec:
|
||||
replicas: 1
|
||||
serviceName: valkey
|
||||
podManagementPolicy: Parallel
|
||||
persistentVolumeClaimRetentionPolicy:
|
||||
whenDeleted: Retain
|
||||
whenScaled: Retain
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "fluxer.selectorLabels" $ctx | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "fluxer.podLabels" $ctx | nindent 8 }}
|
||||
annotations:
|
||||
checksum/config: {{ printf "%s\n%s" (toJson $args) (get $secrets "FLUXER_VALKEY_PASSWORD" | default "") | sha256sum }}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
enableServiceLinks: false
|
||||
{{- include "fluxer.pullSecrets" $ | nindent 6 }}
|
||||
securityContext:
|
||||
{{- include "fluxer.podSecurityContext" (dict "uid" 999 "gid" 1000) | nindent 8 }}
|
||||
{{- include "fluxer.scheduling" $ctx | nindent 6 }}
|
||||
containers:
|
||||
- name: valkey
|
||||
image: {{ include "fluxer.thirdPartyImage" .image }}
|
||||
imagePullPolicy: {{ .image.pullPolicy }}
|
||||
command:
|
||||
{{- toYaml $args | nindent 12 }}
|
||||
env:
|
||||
{{- include "fluxer.secretEnv" (dict "root" $ "name" "REDISCLI_AUTH" "key" "FLUXER_VALKEY_PASSWORD") | nindent 12 }}
|
||||
ports:
|
||||
- name: redis
|
||||
containerPort: 6379
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
{{- toYaml (merge (dict "failureThreshold" 6 "initialDelaySeconds" 10) $ping) | nindent 12 }}
|
||||
readinessProbe:
|
||||
{{- toYaml (merge (dict "failureThreshold" 3) $ping) | nindent 12 }}
|
||||
{{- with .resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- include "fluxer.containerSecurityContext" . | nindent 12 }}
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumeClaimTemplates:
|
||||
{{- include "fluxer.pvc" .persistence | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,975 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Fluxer chart values",
|
||||
"definitions": {
|
||||
"workload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"registry": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"extraEnv": {
|
||||
"type": "array"
|
||||
},
|
||||
"probes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startup": {
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"liveness": {
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"readiness": {
|
||||
"type": ["object", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"podAnnotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"topologySpreadConstraints": {
|
||||
"type": "array"
|
||||
},
|
||||
"pdb": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minAvailable": {
|
||||
"type": ["string", "integer"]
|
||||
},
|
||||
"maxUnavailable": {
|
||||
"type": ["string", "integer"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"hpa": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minReplicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"maxReplicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"targetCPUUtilizationPercentage": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"extraVolumes": {
|
||||
"type": "array"
|
||||
},
|
||||
"extraVolumeMounts": {
|
||||
"type": "array"
|
||||
},
|
||||
"terminationGracePeriodSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"thirdPartyImage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"repository": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"tag": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"persistence": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"size": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"storageClassName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"svc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"router": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"shard": {
|
||||
"$ref": "#/definitions/workload"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["domain"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"global": {
|
||||
"type": "object"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string",
|
||||
"description": "Public hostname of the instance, for example chat.example.com"
|
||||
},
|
||||
"scheme": {
|
||||
"type": "string",
|
||||
"enum": ["http", "https"]
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"publicOrigin": {
|
||||
"type": "string"
|
||||
},
|
||||
"adminBasePath": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"clusterDomain": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"clusterChecks": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"registry": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"tag": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"]
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"digests": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"pullSecrets": {
|
||||
"type": "array"
|
||||
},
|
||||
"revision": {
|
||||
"type": ["string", "number"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"initImage": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"secrets": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"existingSecret": {
|
||||
"type": "string"
|
||||
},
|
||||
"generate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"values": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"FLUXER_POSTGRES_PASSWORD": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_SEARCH_API_KEY": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_S3_ACCESS_KEY_ID": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_S3_SECRET_ACCESS_KEY": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_SUDO_MODE_SECRET": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_CONNECTION_INITIATION_SECRET": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_PROFILE_PSEUDONYM_SECRET": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_GATEWAY_RPC_AUTH_TOKEN": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_VALKEY_PASSWORD": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_NATS_AUTH_TOKEN": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_ERLANG_COOKIE": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_MEDIA_PROXY_SECRET_KEY": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_ADMIN_SECRET_KEY_BASE": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_ADMIN_OAUTH_CLIENT_SECRET": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_SETUP_TOKEN": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_LIVEKIT_API_KEY": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_LIVEKIT_API_SECRET": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_VAPID_PUBLIC_KEY": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"FLUXER_VAPID_PRIVATE_KEY": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"extra": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"external": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"database": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"ssl": {
|
||||
"type": ["string", "boolean"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"database": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"username": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"runAsUser": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"shmSize": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"valkey": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"external": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"maxmemory": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxmemoryPolicy": {
|
||||
"type": "string"
|
||||
},
|
||||
"appendfsync": {
|
||||
"type": "string",
|
||||
"enum": ["always", "everysec", "no"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"nats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"external": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"meilisearch": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"external": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"maxIndexingMemory": {
|
||||
"type": "string"
|
||||
},
|
||||
"maxIndexingThreads": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"seaweedfs": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"volumeGrowth": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"gomemlimit": {
|
||||
"type": "string"
|
||||
},
|
||||
"initAttempts": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"s3": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"endpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicEndpoint": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"forcePathStyle": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"buckets": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cdn": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"uploads": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"reports": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"harvests": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"livekit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"external": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"publicUrl": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"expose": {
|
||||
"type": "string",
|
||||
"enum": ["hostNetwork", "loadBalancer", "nodePort"]
|
||||
},
|
||||
"nodeIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"loadBalancerIP": {
|
||||
"type": "string"
|
||||
},
|
||||
"useExternalIP": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tcpPort": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"udpPort": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"nodePorts": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tcp": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"udp": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 65535
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"stunServers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"logLevel": {
|
||||
"type": "string"
|
||||
},
|
||||
"extraConfig": {
|
||||
"type": "object"
|
||||
},
|
||||
"defaultRegion": {
|
||||
"type": "string"
|
||||
},
|
||||
"nodeCIDRs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"serviceAnnotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"api": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"worker": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"gateway": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"mediaProxy": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"push": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"admin": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"appProxy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"registry": {
|
||||
"type": "string"
|
||||
},
|
||||
"tag": {
|
||||
"type": ["string", "number"]
|
||||
},
|
||||
"digest": {
|
||||
"type": "string"
|
||||
},
|
||||
"pullPolicy": {
|
||||
"type": "string",
|
||||
"enum": ["Always", "IfNotPresent", "Never"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"extraEnv": {
|
||||
"type": "array"
|
||||
},
|
||||
"probes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startup": {
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"liveness": {
|
||||
"type": ["object", "null"]
|
||||
},
|
||||
"readiness": {
|
||||
"type": ["object", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"podAnnotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
},
|
||||
"topologySpreadConstraints": {
|
||||
"type": "array"
|
||||
},
|
||||
"pdb": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minAvailable": {
|
||||
"type": ["string", "integer"]
|
||||
},
|
||||
"maxUnavailable": {
|
||||
"type": ["string", "integer"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"hpa": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minReplicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"maxReplicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"targetCPUUtilizationPercentage": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"extraVolumes": {
|
||||
"type": "array"
|
||||
},
|
||||
"extraVolumeMounts": {
|
||||
"type": "array"
|
||||
},
|
||||
"terminationGracePeriodSeconds": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"staticProxy": {
|
||||
"$ref": "#/definitions/workload"
|
||||
},
|
||||
"svc": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"gifs": {
|
||||
"$ref": "#/definitions/svc"
|
||||
},
|
||||
"messages": {
|
||||
"$ref": "#/definitions/svc"
|
||||
},
|
||||
"snowflakes": {
|
||||
"$ref": "#/definitions/svc"
|
||||
},
|
||||
"unfurl": {
|
||||
"$ref": "#/definitions/svc"
|
||||
},
|
||||
"users": {
|
||||
"$ref": "#/definitions/svc"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"edge": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"image": {
|
||||
"$ref": "#/definitions/thirdPartyImage"
|
||||
},
|
||||
"expose": {
|
||||
"type": "string",
|
||||
"enum": ["ingress", "gateway", "loadBalancer", "none"]
|
||||
},
|
||||
"ingress": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"className": {
|
||||
"type": "string"
|
||||
},
|
||||
"annotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"tls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["certManager", "existingSecret", "none"]
|
||||
},
|
||||
"secretName": {
|
||||
"type": "string"
|
||||
},
|
||||
"acmeServer": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"gateway": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"parentRefs": {
|
||||
"type": "array"
|
||||
},
|
||||
"timeouts": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"loadBalancer": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"http3": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"annotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"persistence": {
|
||||
"$ref": "#/definitions/persistence"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"replicas": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"env": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": ["string", "number", "boolean", "null"]
|
||||
}
|
||||
},
|
||||
"extraEnv": {
|
||||
"type": "array"
|
||||
},
|
||||
"resources": {
|
||||
"type": "object"
|
||||
},
|
||||
"podAnnotations": {
|
||||
"type": "object"
|
||||
},
|
||||
"nodeSelector": {
|
||||
"type": "object"
|
||||
},
|
||||
"tolerations": {
|
||||
"type": "array"
|
||||
},
|
||||
"affinity": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"networkPolicy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"ingressNamespaceSelector": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,710 @@
|
||||
# The public hostname of the instance, lowercase, for example chat.example.com.
|
||||
# Required. Point DNS for it at your ingress controller or at the edge load balancer.
|
||||
domain: ""
|
||||
|
||||
# The scheme browsers use to reach the instance.
|
||||
scheme: https
|
||||
|
||||
# The port browsers use to reach the instance.
|
||||
port: 443
|
||||
|
||||
# The origin browsers see, with no trailing slash. Set it only when browsers
|
||||
# reach the instance on an origin that domain, scheme and port do not describe.
|
||||
# It wins over those three in every derived URL.
|
||||
publicOrigin: ""
|
||||
|
||||
# The path the admin panel is served under, with a leading slash and no
|
||||
# trailing slash.
|
||||
adminBasePath: /admin
|
||||
|
||||
# A contact address. It becomes FLUXER_VAPID_EMAIL for web push and the ACME
|
||||
# account email. Empty uses admin@<domain> for web push and no ACME email.
|
||||
email: ""
|
||||
|
||||
# The cluster DNS suffix, used in the gateway discovery name.
|
||||
clusterDomain: cluster.local
|
||||
|
||||
# Look up namespaces, StorageClasses, IngressClasses and installed APIs before
|
||||
# installing, to fail early on a cluster that cannot run the release. Set false
|
||||
# when your account cannot read those cluster-scoped objects.
|
||||
clusterChecks: true
|
||||
|
||||
image:
|
||||
# The registry and owner the Fluxer images come from.
|
||||
registry: ghcr.io/fluxerapp
|
||||
# The tag for every Fluxer image. v1 moves with each release.
|
||||
tag: v1
|
||||
# Always pulls the moving tag whenever a pod starts, including a pod that
|
||||
# restarts by itself. Pin with digests for rollouts you control.
|
||||
pullPolicy: Always
|
||||
# Per-image tag overrides, keyed by image name, for example fluxer-api: 2026.1012.120000.
|
||||
tags: {}
|
||||
# Per-image digests, keyed by image name, for example fluxer-api: sha256:0123.
|
||||
# A digest pins the image whatever the tag.
|
||||
digests: {}
|
||||
# Image pull secrets for every pod, as a list of {name: <secret>}.
|
||||
pullSecrets: []
|
||||
# Any new value restarts every Fluxer pod, which pulls the moving tag again.
|
||||
# Add --set-string image.revision=$(date +%s) to a helm upgrade that passes
|
||||
# your values file to move to the latest v1.
|
||||
revision: ""
|
||||
|
||||
# The image of the init container that waits for the backing services.
|
||||
initImage:
|
||||
repository: busybox
|
||||
tag: "1.37"
|
||||
# A digest pins the image whatever the tag.
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
secrets:
|
||||
# The name of a Secret you manage yourself. The chart then renders no Secret
|
||||
# and never changes this one. It needs every key listed under values below.
|
||||
# After editing it, run kubectl rollout restart for the deployments and
|
||||
# statefulsets of the release.
|
||||
existingSecret: ""
|
||||
# Generate every empty key below on first install and keep it on upgrade.
|
||||
# Set false under Argo CD or helm template, which cannot read the existing
|
||||
# Secret, and give every key here or use existingSecret.
|
||||
generate: true
|
||||
# Keys of the fluxer-env Secret. Empty means generate once, then keep.
|
||||
# Generated values are never rotated by an upgrade. Back up the Secret.
|
||||
values:
|
||||
# Password of the bundled Postgres role. Changing it after the first install
|
||||
# does not change the stored role password.
|
||||
FLUXER_POSTGRES_PASSWORD: ""
|
||||
# Meilisearch master key.
|
||||
FLUXER_SEARCH_API_KEY: ""
|
||||
# S3 access key id. The bundled store installs it on every upgrade.
|
||||
FLUXER_S3_ACCESS_KEY_ID: fluxer
|
||||
# S3 secret key. Letters and digits only for the bundled store.
|
||||
FLUXER_S3_SECRET_ACCESS_KEY: ""
|
||||
# Signs sudo mode tokens.
|
||||
FLUXER_SUDO_MODE_SECRET: ""
|
||||
# Signs connection initiation tokens.
|
||||
FLUXER_CONNECTION_INITIATION_SECRET: ""
|
||||
# Derives hidden profile names. Changing it renames every hidden profile.
|
||||
FLUXER_PROFILE_PSEUDONYM_SECRET: ""
|
||||
# Authenticates RPC between the gateway, push and the api.
|
||||
FLUXER_GATEWAY_RPC_AUTH_TOKEN: ""
|
||||
# Password of the bundled Valkey. Letters and digits only.
|
||||
FLUXER_VALKEY_PASSWORD: ""
|
||||
# Token of the bundled NATS, letters and digits only. With an outside NATS,
|
||||
# its token if it has one.
|
||||
FLUXER_NATS_AUTH_TOKEN: ""
|
||||
# Erlang distribution cookie of the gateway.
|
||||
FLUXER_ERLANG_COOKIE: ""
|
||||
# Signs media proxy URLs.
|
||||
FLUXER_MEDIA_PROXY_SECRET_KEY: ""
|
||||
# Standard base64 of at least 32 random bytes. Signs upload relay tokens.
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64: ""
|
||||
# Signs admin panel sessions.
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE: ""
|
||||
# Client secret of the built-in admin OAuth application.
|
||||
FLUXER_ADMIN_OAUTH_CLIENT_SECRET: ""
|
||||
# Setup token the wizard asks for before the first admin account. With
|
||||
# existingSecret, or with generate false and no value here, the API makes
|
||||
# one on its first start and writes it to its log once.
|
||||
FLUXER_SETUP_TOKEN: ""
|
||||
# LiveKit API key.
|
||||
FLUXER_LIVEKIT_API_KEY: fluxer
|
||||
# LiveKit API secret, at least 32 characters.
|
||||
FLUXER_LIVEKIT_API_SECRET: ""
|
||||
# Web push key pair, base64url. Set both or neither.
|
||||
FLUXER_VAPID_PUBLIC_KEY: ""
|
||||
FLUXER_VAPID_PRIVATE_KEY: ""
|
||||
# Optional secret-bearing settings, added to the Secret as given. The keys are
|
||||
# the variable names on the configuration page of the operator docs.
|
||||
extra: {}
|
||||
# A full Postgres URL. Wins over the host, port, database and password.
|
||||
# FLUXER_POSTGRES_URL: postgres://fluxer:[email protected]:5432/fluxer
|
||||
# PEM text of the CA that signed the Postgres server certificate.
|
||||
# FLUXER_POSTGRES_SSL_CA: ""
|
||||
# The URL of an outside key-value store that holds a password.
|
||||
# FLUXER_KV_URL: redis://:[email protected]:6379/0
|
||||
# The password of an outside search server.
|
||||
# FLUXER_SEARCH_PASSWORD: ""
|
||||
# A temporary S3 session token.
|
||||
# FLUXER_S3_SESSION_TOKEN: ""
|
||||
# Comma-separated base64 secrets that sign attachment URLs.
|
||||
# FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64: ""
|
||||
# SMTP password for outgoing email.
|
||||
# FLUXER_EMAIL_SMTP_PASSWORD: ""
|
||||
# Secret that authenticates email provider webhooks.
|
||||
# FLUXER_EMAIL_WEBHOOK_SECRET: ""
|
||||
# Stripe secret key.
|
||||
# FLUXER_STRIPE_SECRET_KEY: ""
|
||||
# Stripe webhook signing secret.
|
||||
# FLUXER_STRIPE_WEBHOOK_SECRET: ""
|
||||
# Key of the donation proxy.
|
||||
# FLUXER_API_DONATION_PROXY_KEY: ""
|
||||
# Bearer token of the HTTP cache purge endpoint.
|
||||
# FLUXER_CACHE_PURGE_HTTP_TOKEN: ""
|
||||
# Klipy GIF API key.
|
||||
# FLUXER_KLIPY_API_KEY: ""
|
||||
# YouTube Data API key.
|
||||
# FLUXER_YOUTUBE_API_KEY: ""
|
||||
# Bluesky OAuth signing keys.
|
||||
# FLUXER_AUTH_BLUESKY_KEYS: ""
|
||||
# APNs private key for iOS push.
|
||||
# FLUXER_PUSH_APNS_PRIVATE_KEY: ""
|
||||
# FCM private key for Android push.
|
||||
# FLUXER_PUSH_FCM_PRIVATE_KEY: ""
|
||||
|
||||
# Extra non-secret settings for every Fluxer service, as NAME: value. The names
|
||||
# and meanings are on the configuration page of the operator docs. An empty
|
||||
# value is left out. The chart refuses a secret here. Secrets belong in
|
||||
# secrets.extra.
|
||||
env: {}
|
||||
|
||||
postgres:
|
||||
# Run Postgres in the release. Set false to use your own server.
|
||||
enabled: true
|
||||
image:
|
||||
repository: postgres
|
||||
# A new major needs a dump and restore. The pod refuses a data directory
|
||||
# from another major.
|
||||
tag: 16-alpine
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
# The user and group id of the postgres user in the image. 70 in the Alpine
|
||||
# images, 999 in the Debian ones.
|
||||
runAsUser: 70
|
||||
# Your own server, used when enabled is false. Its password goes in
|
||||
# secrets.values.FLUXER_POSTGRES_PASSWORD, or a URL in secrets.extra.FLUXER_POSTGRES_URL.
|
||||
# The database and the role have to exist already.
|
||||
external:
|
||||
host: ""
|
||||
port: 5432
|
||||
database: fluxer
|
||||
username: fluxer
|
||||
# true or false. Empty keeps the service default.
|
||||
ssl: ""
|
||||
# The database and role the bundled server creates.
|
||||
database: fluxer
|
||||
username: fluxer
|
||||
# Server settings, passed as -c name=value.
|
||||
parameters:
|
||||
max_connections: "150"
|
||||
shared_buffers: 512MB
|
||||
effective_cache_size: 2GB
|
||||
work_mem: 8MB
|
||||
maintenance_work_mem: 256MB
|
||||
autovacuum_work_mem: 128MB
|
||||
random_page_cost: "1.1"
|
||||
effective_io_concurrency: "200"
|
||||
default_statistics_target: "200"
|
||||
jit: "off"
|
||||
min_wal_size: 512MB
|
||||
max_wal_size: 2GB
|
||||
checkpoint_completion_target: "0.9"
|
||||
wal_buffers: 16MB
|
||||
wal_compression: zstd
|
||||
bgwriter_delay: 50ms
|
||||
bgwriter_lru_maxpages: "1000"
|
||||
autovacuum_vacuum_scale_factor: "0.05"
|
||||
autovacuum_analyze_scale_factor: "0.02"
|
||||
autovacuum_vacuum_cost_limit: "2000"
|
||||
track_io_timing: "on"
|
||||
shared_preload_libraries: pg_stat_statements
|
||||
# Size of the in-memory /dev/shm. Parallel vacuum needs it.
|
||||
shmSize: 1Gi
|
||||
# The size and the class are fixed once the volume exists. To grow a volume
|
||||
# later, edit the PersistentVolumeClaim, not this value.
|
||||
persistence:
|
||||
size: 20Gi
|
||||
# Empty uses the cluster default StorageClass.
|
||||
storageClassName: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
memory: 5Gi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
valkey:
|
||||
# Run Valkey in the release. Set false and give external.url to use your own.
|
||||
enabled: true
|
||||
image:
|
||||
repository: valkey/valkey
|
||||
tag: 9.1-alpine
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
external:
|
||||
# A URL without a password. One with a password goes in secrets.extra.FLUXER_KV_URL.
|
||||
url: ""
|
||||
maxmemory: 192mb
|
||||
maxmemoryPolicy: noeviction
|
||||
appendfsync: everysec
|
||||
persistence:
|
||||
size: 1Gi
|
||||
storageClassName: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
nats:
|
||||
# Run NATS with JetStream in the release. Set false and give external.url to use your own.
|
||||
enabled: true
|
||||
image:
|
||||
repository: nats
|
||||
tag: 2.14-alpine
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
external:
|
||||
# Its token, if any, goes in secrets.values.FLUXER_NATS_AUTH_TOKEN.
|
||||
url: ""
|
||||
# 1, or 3 or more for a JetStream cluster.
|
||||
replicas: 1
|
||||
persistence:
|
||||
size: 10Gi
|
||||
storageClassName: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
meilisearch:
|
||||
# Run Meilisearch in the release. Set false and give external.url to use your own.
|
||||
enabled: true
|
||||
image:
|
||||
repository: getmeili/meilisearch
|
||||
tag: v1.53
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
external:
|
||||
# Its key goes in secrets.values.FLUXER_SEARCH_API_KEY.
|
||||
url: ""
|
||||
maxIndexingMemory: 256mb
|
||||
maxIndexingThreads: 2
|
||||
persistence:
|
||||
size: 10Gi
|
||||
storageClassName: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 1536Mi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
seaweedfs:
|
||||
# Run SeaweedFS as the S3 store and create the buckets. Only its S3 port
|
||||
# listens outside the pod. Set false and give s3.endpoint to use your own
|
||||
# store, with the buckets already created.
|
||||
enabled: true
|
||||
image:
|
||||
repository: chrislusf/seaweedfs
|
||||
tag: "4.47"
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
# Volumes grown per bucket. 1 keeps the store usable on small disks.
|
||||
volumeGrowth: 1
|
||||
gomemlimit: 1536MiB
|
||||
# Tries, 2 seconds apart, the bucket setup makes before it starts over.
|
||||
initAttempts: 60
|
||||
persistence:
|
||||
size: 20Gi
|
||||
storageClassName: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
s3:
|
||||
# The S3 endpoint the services use. Empty uses the bundled SeaweedFS.
|
||||
endpoint: ""
|
||||
# The S3 endpoint browsers use for direct uploads. Empty uses endpoint.
|
||||
publicEndpoint: ""
|
||||
region: us-east-1
|
||||
forcePathStyle: true
|
||||
buckets:
|
||||
cdn: fluxer
|
||||
uploads: fluxer-uploads
|
||||
reports: fluxer-reports
|
||||
harvests: fluxer-harvests
|
||||
|
||||
livekit:
|
||||
# Run LiveKit for voice and video. false with no external.url turns voice off.
|
||||
enabled: true
|
||||
# A LiveKit server of your own, used when enabled is false. Its key and
|
||||
# secret go in secrets.values.FLUXER_LIVEKIT_API_KEY and FLUXER_LIVEKIT_API_SECRET.
|
||||
# Point its webhook at <origin>/api/webhooks/livekit, signed with the same key.
|
||||
external:
|
||||
# The address the api uses to reach it, for example http://livekit.voice.svc:7880.
|
||||
url: ""
|
||||
# The address browsers use to reach it, for example wss://livekit.example.com.
|
||||
publicUrl: ""
|
||||
image:
|
||||
repository: livekit/livekit-server
|
||||
tag: v1.12.0
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
# How clients reach the media ports. hostNetwork binds them on the node and
|
||||
# needs a privileged namespace. loadBalancer and nodePort use the Service
|
||||
# livekit-rtc, which serves TCP and UDP on one address.
|
||||
expose: hostNetwork
|
||||
# The public IP LiveKit advertises. Required for loadBalancer, where it is the
|
||||
# address of the load balancer. Empty finds it over STUN.
|
||||
nodeIP: ""
|
||||
# The address the loadBalancer Service asks for. Reserve it with your
|
||||
# provider first. Some providers take it from an annotation instead.
|
||||
loadBalancerIP: ""
|
||||
# Find the public IP over STUN.
|
||||
useExternalIP: true
|
||||
tcpPort: 7881
|
||||
udpPort: 7882
|
||||
# The node ports in nodePort mode. LiveKit advertises these too.
|
||||
nodePorts:
|
||||
tcp: 30881
|
||||
udp: 30882
|
||||
stunServers:
|
||||
- stun.l.google.com:19302
|
||||
- stun1.l.google.com:19302
|
||||
logLevel: info
|
||||
# More LiveKit configuration, merged over the chart's, for example
|
||||
# {rtc: {ips: {includes: [10.0.0.0/8]}}} to advertise only the node network.
|
||||
extraConfig: {}
|
||||
# The voice region seeded on every api start, as JSON.
|
||||
defaultRegion: '{"id":"default","name":"Default","emoji":"🌍","latitude":0,"longitude":0}'
|
||||
# Node addresses allowed to reach the edge in hostNetwork mode, for the
|
||||
# LiveKit webhook. Needed only with networkPolicy.ingressNamespaceSelector.
|
||||
# Keep it as narrow as the LiveKit node, for example 10.0.0.12/32 together
|
||||
# with nodeSelector.
|
||||
nodeCIDRs: []
|
||||
# Annotations for the Service livekit-rtc.
|
||||
serviceAnnotations: {}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
# Every Fluxer workload below takes the same keys:
|
||||
# replicas, image (registry, tag, digest, pullPolicy), resources, env (NAME: value),
|
||||
# extraEnv (a list of Kubernetes env entries), probes (startup, liveness or
|
||||
# readiness, each replacing the default probe whole), podAnnotations,
|
||||
# nodeSelector, tolerations, affinity, topologySpreadConstraints, pdb
|
||||
# (minAvailable or maxUnavailable), hpa (minReplicas, maxReplicas,
|
||||
# targetCPUUtilizationPercentage), extraVolumes and extraVolumeMounts.
|
||||
|
||||
api:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 768Mi
|
||||
limits:
|
||||
memory: 2560Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
worker:
|
||||
# Must stay 1. The worker runs the cron scheduler.
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 768Mi
|
||||
limits:
|
||||
memory: 2560Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
gateway:
|
||||
replicas: 1
|
||||
terminationGracePeriodSeconds: 30
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
mediaProxy:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
push:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
admin:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
appProxy:
|
||||
replicas: 1
|
||||
# Settings for app-proxy only: branding, CSP extras, the index upstream,
|
||||
# same-origin hosts and manifest scope, as NAME: value. For example
|
||||
# FLUXER_APP_PRODUCT_NAME, FLUXER_CSP_EXTRA_CONNECT_SRC or RUST_LOG.
|
||||
config: {}
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
staticProxy:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
env: {}
|
||||
extraEnv: []
|
||||
probes: {}
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
topologySpreadConstraints: []
|
||||
pdb: {}
|
||||
hpa: {}
|
||||
extraVolumes: []
|
||||
extraVolumeMounts: []
|
||||
|
||||
# The internal services. Each runs a router Deployment and a shard StatefulSet.
|
||||
# router and shard take the workload keys above. shard.replicas sets the shard count.
|
||||
svc:
|
||||
gifs:
|
||||
router:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {memory: 128Mi}
|
||||
shard:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 64Mi}
|
||||
limits: {memory: 256Mi}
|
||||
messages:
|
||||
router:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {memory: 128Mi}
|
||||
shard:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 64Mi}
|
||||
limits: {memory: 256Mi}
|
||||
snowflakes:
|
||||
router:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {memory: 128Mi}
|
||||
shard:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 64Mi}
|
||||
limits: {memory: 256Mi}
|
||||
unfurl:
|
||||
router:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {memory: 128Mi}
|
||||
shard:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 64Mi}
|
||||
limits: {memory: 256Mi}
|
||||
users:
|
||||
router:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 10m, memory: 32Mi}
|
||||
limits: {memory: 128Mi}
|
||||
shard:
|
||||
replicas: 1
|
||||
resources:
|
||||
requests: {cpu: 20m, memory: 64Mi}
|
||||
limits: {memory: 256Mi}
|
||||
|
||||
edge:
|
||||
image:
|
||||
repository: caddy
|
||||
tag: 2.11-alpine
|
||||
digest: ""
|
||||
pullPolicy: IfNotPresent
|
||||
# How traffic reaches the edge. ingress puts an Ingress in front of it,
|
||||
# gateway an HTTPRoute, loadBalancer a LoadBalancer Service with its own TLS,
|
||||
# and none leaves only the ClusterIP Service edge on port 8080.
|
||||
expose: ingress
|
||||
ingress:
|
||||
# The IngressClass. Empty uses the cluster default.
|
||||
className: ""
|
||||
# Extra annotations. The chart always adds the ingress-nginx body size and
|
||||
# timeout annotations, which other controllers ignore.
|
||||
annotations: {}
|
||||
tls:
|
||||
# certManager issues a certificate through a namespaced ACME Issuer.
|
||||
# existingSecret uses secretName. none serves plain HTTP behind the controller.
|
||||
mode: certManager
|
||||
secretName: fluxer-tls
|
||||
acmeServer: https://acme-v02.api.letsencrypt.org/directory
|
||||
gateway:
|
||||
# The Gateway the HTTPRoute attaches to, as Gateway API parentRefs.
|
||||
parentRefs: []
|
||||
# HTTPRoute timeouts, for example {request: 0s}. Envoy-based gateways cut
|
||||
# requests at 15 seconds without it.
|
||||
timeouts: {}
|
||||
loadBalancer:
|
||||
# Also publish UDP 443 for HTTP/3. Some clouds cannot mix TCP and UDP on one Service.
|
||||
http3: false
|
||||
annotations: {}
|
||||
# Size of the volume that keeps the certificates.
|
||||
persistence:
|
||||
size: 1Gi
|
||||
storageClassName: ""
|
||||
replicas: 1
|
||||
# Edge settings as NAME: value, overriding the chart's, for example
|
||||
# FLUXER_EDGE_GLOBAL_OPTIONS or FLUXER_EDGE_ENCODE.
|
||||
env: {}
|
||||
extraEnv: []
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
memory: 256Mi
|
||||
podAnnotations: {}
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
networkPolicy:
|
||||
# Admit traffic to the release pods only from the release itself, to the edge
|
||||
# from anywhere, and to the LiveKit media ports. Network plugins without
|
||||
# NetworkPolicy support ignore it. Add a NetworkPolicy of your own to let
|
||||
# another namespace in, for example a metrics scraper.
|
||||
enabled: true
|
||||
# Namespaces allowed to reach the edge on port 8080, as a namespaceSelector,
|
||||
# for example {matchLabels: {kubernetes.io/metadata.name: ingress-nginx}}.
|
||||
# Empty allows any source.
|
||||
ingressNamespaceSelector: {}
|
||||
+468
-203
@@ -1,108 +1,66 @@
|
||||
# Every variable docker-compose.yml reads is named here, uncommented when it has
|
||||
# no default and commented with its default when it has one. A name absent from
|
||||
# this file reaches a service only through a Compose override. Compose expands
|
||||
# top to bottom, so a line using ${...} must sit below every name it reads.
|
||||
# Every variable docker-compose.yml reads. A value an install must set is
|
||||
# uncommented. A commented line shows the default, or an example where its comment
|
||||
# says so, and nothing after = means the service decides. An empty value keeps the
|
||||
# default too. Compose expands top to bottom, so a line using ${...} must sit below
|
||||
# every name it reads.
|
||||
|
||||
FLUXER_DOMAIN=chat.example.com
|
||||
FLUXER_PUBLIC_SCHEME=https
|
||||
FLUXER_PUBLIC_PORT=443
|
||||
|
||||
# The lines above are the address browsers use, and every advertised endpoint
|
||||
# carries FLUXER_PUBLIC_PORT. They do not move what the host publishes.
|
||||
# FLUXER_HTTP_PORT and FLUXER_HTTPS_PORT below do that, and a non-default port
|
||||
# needs the matching one set as well. Complete recipes sit beside them.
|
||||
# The address browsers use. FLUXER_HTTP_PORT and FLUXER_HTTPS_PORT below decide
|
||||
# which host ports Fluxer binds.
|
||||
|
||||
# How browsers reach this instance.
|
||||
#
|
||||
# Default: Fluxer binds 80 and 443 and gets its own Let's Encrypt certificate.
|
||||
# Point DNS at this host.
|
||||
#
|
||||
# Behind your own reverse proxy (nginx, Traefik, HAProxy, Cloudflare Tunnel,
|
||||
# another Caddy): uncomment COMPOSE_FILE below. Fluxer then serves plain HTTP on
|
||||
# 127.0.0.1:8080 instead, and your proxy forwards everything to it. Keep
|
||||
# FLUXER_PUBLIC_SCHEME and FLUXER_PUBLIC_PORT describing the PUBLIC address your
|
||||
# proxy serves, not this local port.
|
||||
# By default Fluxer binds 80 and 443 and gets its own certificate. Point DNS here.
|
||||
# Behind your own reverse proxy, uncomment this instead: Fluxer then serves plain
|
||||
# HTTP on 127.0.0.1:8080. Keep the scheme and port above describing the public
|
||||
# address, not this one.
|
||||
#COMPOSE_FILE=docker-compose.yml:docker-compose.proxy.yml
|
||||
|
||||
# Where the plain-HTTP port binds when the proxy overlay is in use. Leave it on
|
||||
# loopback when the proxy runs on this host. Use 0.0.0.0:8080 only when the proxy
|
||||
# is on another machine, and firewall the port to that machine.
|
||||
# Where that plain-HTTP port binds. Use 0.0.0.0:8080 only when the proxy is on
|
||||
# another machine, and firewall it to that machine.
|
||||
#FLUXER_EDGE_BIND=127.0.0.1:8080
|
||||
|
||||
# Which upstream hops may set X-Forwarded-For. Fluxer rewrites the header from
|
||||
# this to the real client address, so IP bans, rate limits and abuse detection
|
||||
# see the caller rather than the proxy. The default covers proxies on private or
|
||||
# loopback addresses, which is every same-host setup. Set it to your proxy's
|
||||
# address if it reaches Fluxer from a public IP.
|
||||
# Which hops may set X-Forwarded-For. The default covers private and loopback
|
||||
# addresses. Set your proxy's address if it reaches Fluxer from a public IP.
|
||||
#FLUXER_EDGE_TRUSTED_PROXIES=private_ranges
|
||||
|
||||
# The origin browsers see, without a trailing slash. Leave it unset and each
|
||||
# service builds one from the three values at the top of this file. Set it and it
|
||||
# wins: every service reads the host, the scheme and the port out of it and
|
||||
# ignores those three names. Use it when browsers reach the instance on a host
|
||||
# FLUXER_DOMAIN does not name. It has to be a bare origin, a scheme and a host
|
||||
# and an optional port and nothing after them, or the services refuse to start.
|
||||
# It does not move the edge listener or the published ports either, so set the
|
||||
# publish below to the port written here.
|
||||
# The origin browsers see, no trailing slash. Set it when browsers reach the
|
||||
# instance on a host FLUXER_DOMAIN does not name, and it wins over the three
|
||||
# values above. Scheme, host and optional port only. It does not move the
|
||||
# published ports.
|
||||
#FLUXER_PUBLIC_ORIGIN=https://chat.example.com
|
||||
|
||||
# Overrides the address the edge listens on inside its container. Compose builds
|
||||
# it from FLUXER_PUBLIC_SCHEME and FLUXER_DOMAIN with no port, and the edge keeps
|
||||
# its container ports at 80 and 443 whatever the public port is. Caddy matches a
|
||||
# site by host and ignores the port in the Host header, so a request arriving on
|
||||
# a non-default published port still lands on this site. Put a port in this value
|
||||
# only if you also publish that same container port below, or nothing will be
|
||||
# listening where the publish points. Honoured in the default mode only:
|
||||
# docker-compose.proxy.yml sets the literal :8080 and tunnel.compose.yml the
|
||||
# literal :80, and Compose lets the last file win, so a value here is discarded
|
||||
# under either overlay with no warning. Set it for an unusual default-mode
|
||||
# layout, such as serving several hostnames. Write the scheme into it: a bare
|
||||
# hostname means automatic HTTPS on 443 whatever FLUXER_PUBLIC_SCHEME says.
|
||||
# The address the edge listens on inside its container. Both proxy overlays set
|
||||
# this themselves, so a value here is ignored under either. Include the scheme.
|
||||
#FLUXER_EDGE_SITE_ADDRESS=https://chat.example.com
|
||||
|
||||
# The old name for the value above, read only when FLUXER_EDGE_SITE_ADDRESS is
|
||||
# unset, so an existing .env keeps the listener it already had.
|
||||
# The old name for the line above, read only when it is unset.
|
||||
#FLUXER_CADDY_SITE_ADDRESS=
|
||||
|
||||
# Host side of the edge's publishes, and the only names that decide which host
|
||||
# ports Fluxer binds. The container side is fixed. Container 80 carries the
|
||||
# HTTP to HTTPS redirect and the Let's Encrypt HTTP challenge under an https
|
||||
# scheme, and the site itself under an http one. Container 443 carries the TLS
|
||||
# site. FLUXER_HTTPS_PORT moves the TCP and the UDP publish together, because
|
||||
# HTTP/3 needs both on the same port. Both take an optional bind address in front
|
||||
# of the port, and 127.0.0.1 keeps the publish off every public interface. Give
|
||||
# them different host ports: the same host port on both is two publishes of one
|
||||
# port and the edge refuses to start.
|
||||
# Host ports. Container 80 handles the redirect and the certificate challenge,
|
||||
# container 443 the TLS site. FLUXER_HTTPS_PORT moves TCP and UDP together, since
|
||||
# HTTP/3 needs both. Both accept a bind address. Give them different host ports.
|
||||
#FLUXER_HTTP_PORT=80
|
||||
#FLUXER_HTTPS_PORT=443
|
||||
#FLUXER_HTTP_PORT=127.0.0.1:80
|
||||
#FLUXER_HTTPS_PORT=127.0.0.1:443
|
||||
|
||||
# HTTPS on 8443, complete. Host 80 stays published and still answers the ACME
|
||||
# challenge. Let's Encrypt only ever connects to the public 80 or 443, so the
|
||||
# certificate is issued if a router in front forwards public 80 to this host and
|
||||
# is not issued otherwise. Serve your own certificate from the Caddyfile when it
|
||||
# cannot.
|
||||
# HTTPS on 8443. Host 80 stays published for the certificate challenge, which
|
||||
# only ever arrives on public 80 or 443. Serve your own certificate if nothing
|
||||
# forwards those.
|
||||
#FLUXER_PUBLIC_PORT=8443
|
||||
#FLUXER_HTTPS_PORT=8443
|
||||
|
||||
# Plain HTTP on 19080, complete. The port 80 publish moves to 19080, so nothing
|
||||
# binds host 80. Under an http scheme nothing listens on container 443, so the
|
||||
# last line parks that publish on loopback for a host that wants 443 for
|
||||
# something else. Drop it and 443 is published and idle, which is what earlier
|
||||
# releases did.
|
||||
# Plain HTTP on 19080. Nothing binds host 80, and the last line parks the idle
|
||||
# 443 publish on loopback.
|
||||
#FLUXER_PUBLIC_SCHEME=http
|
||||
#FLUXER_PUBLIC_PORT=19080
|
||||
#FLUXER_HTTP_PORT=19080
|
||||
#FLUXER_HTTPS_PORT=127.0.0.1:443
|
||||
|
||||
# A tunnel or another proxy in front of the stack needs no HTTPS publish at all.
|
||||
# tunnel.compose.yml ships beside this file and replaces Caddy's published ports
|
||||
# with a single loopback HTTP publish, so nothing binds 443, and points the edge
|
||||
# at plain HTTP on that publish so it stops redirecting to https. FLUXER_HTTP_PORT
|
||||
# still moves that one publish. Set the line below and plain docker compose
|
||||
# commands pick the file up, or add it to your own -f flags if you pass any. The
|
||||
# file uses the !override tag, which needs Compose 2.24.4 or newer.
|
||||
# A tunnel needs no HTTPS publish. tunnel.compose.yml ships beside this file and
|
||||
# leaves one loopback HTTP publish. Needs Compose 2.24.4 or newer.
|
||||
#COMPOSE_FILE=docker-compose.yml:tunnel.compose.yml
|
||||
|
||||
FLUXER_REGISTRY_OWNER=fluxerapp
|
||||
@@ -111,11 +69,10 @@ FLUXER_IMAGE_TAG=v1
|
||||
|
||||
POSTGRES_PASSWORD=CHANGE_ME
|
||||
MEILI_MASTER_KEY=CHANGE_ME
|
||||
# The stack ships its own Postgres and its own object store, and points at both
|
||||
# by service name. Set these to run either one outside the stack. Leave them
|
||||
# unset and the bundled services are used. Taking a service out of the stack
|
||||
# means an upgrade skips the backup step that reaches into it, and backing that
|
||||
# store up belongs to whoever runs it.
|
||||
# Set these to run Postgres or the object store outside the stack. Backing up a
|
||||
# store you moved out is yours to arrange. An upgrade dumps the bundled postgres
|
||||
# service and skips the dump only when the stack defines none. The values below
|
||||
# are examples.
|
||||
#FLUXER_POSTGRES_HOST=db.example.com
|
||||
#FLUXER_POSTGRES_PORT=5432
|
||||
#FLUXER_POSTGRES_DATABASE=fluxer
|
||||
@@ -125,51 +82,101 @@ MEILI_MASTER_KEY=CHANGE_ME
|
||||
#FLUXER_S3_PUBLIC_ENDPOINT=https://cdn.example.com
|
||||
#FLUXER_S3_REGION=eu-central-1
|
||||
#FLUXER_S3_FORCE_PATH_STYLE=false
|
||||
# Bucket names. The bundled object store creates whichever names these hold, so
|
||||
# the two stay in step. An object store outside the stack needs the buckets to
|
||||
|
||||
# Bucket names. The bundled store creates these. An outside store needs them to
|
||||
# exist already.
|
||||
#FLUXER_S3_BUCKET_CDN=fluxer
|
||||
#FLUXER_S3_BUCKET_UPLOADS=fluxer-uploads
|
||||
#FLUXER_S3_BUCKET_DOWNLOADS=fluxer-downloads
|
||||
#FLUXER_S3_BUCKET_REPORTS=fluxer-reports
|
||||
#FLUXER_S3_BUCKET_HARVESTS=fluxer-harvests
|
||||
# With the object store outside the stack, add this overlay to COMPOSE_FILE and
|
||||
# the bundled seaweedfs no longer starts. Put it after any other overlay, such as
|
||||
# docker-compose.yml:docker-compose.proxy.yml:external-object-store.compose.yml.
|
||||
# Needs Compose 2.24.4 or newer.
|
||||
#COMPOSE_FILE=docker-compose.yml:external-object-store.compose.yml
|
||||
|
||||
# The rest of the bundled services, pointed somewhere else the same way. Leave a
|
||||
# line unset and the service in the stack is used. Taking a service out of the
|
||||
# stack goes in an override file listed in COMPOSE_FILE, because an upgrade
|
||||
# replaces docker-compose.yml.
|
||||
# A full connection URL wins over the host, port and database above. The URL is an
|
||||
# example. The CA is the PEM text of the certificate, with \n for line breaks.
|
||||
#FLUXER_POSTGRES_URL=postgres://fluxer:[email protected]:5432/fluxer
|
||||
#FLUXER_POSTGRES_SSL_CA=
|
||||
# The Postgres table that holds the key-value store.
|
||||
#FLUXER_POSTGRES_KV_TABLE=fluxer_kv
|
||||
# media-proxy reads through these when the store serves reads from another
|
||||
# address or bucket.
|
||||
#FLUXER_S3_READ_ENDPOINT=
|
||||
#FLUXER_S3_READ_BUCKET=
|
||||
#FLUXER_S3_READ_BUCKET_STYLE=
|
||||
# A temporary S3 session token, read by media-proxy only.
|
||||
#FLUXER_S3_SESSION_TOKEN=
|
||||
# The bundled store refuses unsigned reads. Set false only for a public-read bucket.
|
||||
#FLUXER_S3_READ_SIGNED=true
|
||||
|
||||
# The other bundled services, pointed elsewhere. Removing a service from the
|
||||
# stack belongs in an override file, since an upgrade replaces docker-compose.yml.
|
||||
# The URLs below are examples.
|
||||
#FLUXER_KV_URL=redis://cache.example.com:6379/0
|
||||
#FLUXER_NATS_URL=nats://mq.example.com:4222
|
||||
#FLUXER_NATS_JETSTREAM_URL=nats://mq.example.com:4222
|
||||
#FLUXER_SVC_NATS_URL=nats://mq.example.com:4222
|
||||
#FLUXER_SEARCH_URL=https://search.example.com
|
||||
#FLUXER_LIVEKIT_INTERNAL_URL=http://livekit.example.com:7880
|
||||
# How the stack talks to those services.
|
||||
#FLUXER_KV_MODE=standalone
|
||||
#FLUXER_SEARCH_ENGINE=meilisearch
|
||||
#FLUXER_SEARCH_USERNAME=
|
||||
#FLUXER_SEARCH_PASSWORD=
|
||||
#FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED=true
|
||||
|
||||
# Voice off. The livekit service still runs until an override file takes it out.
|
||||
# Voice off. The livekit service still runs until an override removes it.
|
||||
#FLUXER_LIVEKIT_ENABLED=false
|
||||
|
||||
# Optional systems, each off unless the instance is configured for it.
|
||||
#FLUXER_SMS_ENABLED=false
|
||||
# Optional systems, each off unless configured.
|
||||
#FLUXER_STRIPE_ENABLED=false
|
||||
#FLUXER_NCMEC_ENABLED=false
|
||||
#FLUXER_CLAMAV_ENABLED=false
|
||||
#FLUXER_STRIPE_SECRET_KEY=
|
||||
#FLUXER_STRIPE_WEBHOOK_SECRET=
|
||||
# Stripe prices as one JSON object. The admin dashboard can set them instead.
|
||||
#FLUXER_STRIPE_PRICES={}
|
||||
#FLUXER_STRIPE_LEGACY_PRICES={}
|
||||
#FLUXER_API_DONATION_PROXY_KEY=
|
||||
#FLUXER_API_TRUSTED_CALLERS=[]
|
||||
#FLUXER_VISIONARIES_GUILD_ID=
|
||||
#FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID=
|
||||
|
||||
# The client address. Set the header name a proxy in front actually writes, and
|
||||
# turn the trust off when nothing sits in front, because a trusted header an
|
||||
# attacker can set is a spoofed client address.
|
||||
#FLUXER_CLIENT_IP_HEADER_NAME=cf-connecting-ip
|
||||
# Upload virus scanning, off by default. No ClamAV container ships, so point
|
||||
# this at your own. The values below are examples.
|
||||
#FLUXER_CLAMAV_ENABLED=true
|
||||
#FLUXER_CLAMAV_HOST=clamav
|
||||
#FLUXER_CLAMAV_PORT=3310
|
||||
#FLUXER_CLAMAV_FAIL_OPEN=false
|
||||
|
||||
# Outside lookups, off unless turned on. The breached password check asks
|
||||
# api.pwnedpasswords.com.
|
||||
#FLUXER_BREACHED_PASSWORD_CHECK_ENABLED=false
|
||||
#FLUXER_BLOCKLIST_FEEDS_ENABLED=false
|
||||
# A local path, or an s3:// URL read with the S3 credentials of this file.
|
||||
#FLUXER_GEOIP_DB_PATH=
|
||||
|
||||
# The client address. The edge sets X-Forwarded-For on every hop, so keep the
|
||||
# trust on and the default header. Turning the trust off makes the api ignore
|
||||
# the header and use the connection's peer address, which here is the edge.
|
||||
#FLUXER_CLIENT_IP_HEADER_NAME=x-forwarded-for
|
||||
#FLUXER_TRUST_CLIENT_IP_HEADER=true
|
||||
|
||||
# How much the services write. trace, debug, info, warn, error or fatal. Every
|
||||
# service names the object storage endpoint and its addressing at info on start,
|
||||
# so a bucket that answers 404 is visible without raising this.
|
||||
#LOG_LEVEL=debug
|
||||
# How much the services write. LOG_LEVEL covers the api and worker and takes trace,
|
||||
# debug, info, warn, error or fatal. RUST_LOG covers the Rust services and takes
|
||||
# an EnvFilter such as debug. The gateway takes an Erlang level such as notice,
|
||||
# and LOGGER_LEVEL beats FLUXER_GATEWAY_LOGGER_LEVEL.
|
||||
#LOG_LEVEL=info
|
||||
#RUST_LOG=info
|
||||
#FLUXER_GATEWAY_LOGGER_LEVEL=info
|
||||
#LOGGER_LEVEL=
|
||||
|
||||
FLUXER_S3_ACCESS_KEY=fluxer
|
||||
FLUXER_S3_SECRET_KEY=CHANGE_ME
|
||||
|
||||
FLUXER_SUDO_MODE_SECRET=CHANGE_ME
|
||||
FLUXER_CONNECTION_INITIATION_SECRET=CHANGE_ME
|
||||
FLUXER_PROFILE_PSEUDONYM_SECRET=CHANGE_ME
|
||||
FLUXER_GATEWAY_RPC_AUTH_TOKEN=CHANGE_ME
|
||||
FLUXER_ERLANG_COOKIE=CHANGE_ME
|
||||
FLUXER_MEDIA_PROXY_SECRET_KEY=CHANGE_ME
|
||||
@@ -177,34 +184,78 @@ FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=CHANGE_ME
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE=CHANGE_ME
|
||||
FLUXER_ADMIN_OAUTH_CLIENT_SECRET=CHANGE_ME
|
||||
|
||||
# The token every service sends to NATS. The bundled NATS runs without
|
||||
# authentication, so this stays empty unless a Compose override points the stack
|
||||
# at an external NATS that requires a token. Compose forwards the name to every
|
||||
# container that connects.
|
||||
# The token every service sends to NATS. The bundled NATS needs none, so this
|
||||
# stays empty unless an override points at an external one.
|
||||
#FLUXER_NATS_AUTH_TOKEN=
|
||||
|
||||
FLUXER_VAPID_PUBLIC_KEY=CHANGE_ME
|
||||
FLUXER_VAPID_PRIVATE_KEY=CHANGE_ME
|
||||
|
||||
# The VAPID contact address defaults to admin@ followed by FLUXER_DOMAIN. Set it
|
||||
# only if that mailbox does not exist.
|
||||
# Defaults to admin@ followed by FLUXER_DOMAIN. Set it if that mailbox does not
|
||||
# exist.
|
||||
#[email protected]
|
||||
|
||||
# Passkeys follow FLUXER_DOMAIN by default. Set these only if browsers reach the
|
||||
# instance on a different host, and note that changing FLUXER_PASSKEY_RP_ID
|
||||
# invalidates every passkey already registered against the old value.
|
||||
# The passkey RP ID defaults to FLUXER_DOMAIN, whatever FLUXER_PUBLIC_ORIGIN says.
|
||||
# Changing the RP ID invalidates every passkey registered against the old value.
|
||||
#FLUXER_PASSKEY_RP_ID=chat.example.com
|
||||
#FLUXER_PASSKEY_RP_NAME=Fluxer
|
||||
#FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS=https://chat.example.com
|
||||
#FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS=http://chat.example.com:19080
|
||||
|
||||
# Extra Content-Security-Policy sources, appended to the built-in ones. Set these
|
||||
# only when a browser must reach an origin the defaults do not cover, such as a
|
||||
# voice server hosted on a domain other than FLUXER_DOMAIN. Separate several
|
||||
# sources with spaces or commas. Every one of them is empty by default, and the
|
||||
# three carrying a value below are illustrations, not defaults.
|
||||
# Notification jobs the push container holds at once, 1 to 1000000.
|
||||
#FLUXER_PUSH_SERVICE_QUEUE_CAPACITY=10000
|
||||
# Provider requests the push container sends at once, 1 to 65536.
|
||||
#FLUXER_PUSH_SERVICE_SEND_CONCURRENCY=256
|
||||
# The push container's provider addresses and relay hosts.
|
||||
#FLUXER_PUSH_SERVICE_APNS_BASE_URL=
|
||||
#FLUXER_PUSH_SERVICE_FCM_BASE_URL=https://fcm.googleapis.com
|
||||
#FLUXER_PUSH_SERVICE_MANAGED_RELAY_HOSTS=push.fluxer.com
|
||||
#FLUXER_PUSH_SERVICE_OWN_RELAY_HOSTS=
|
||||
# Push hosts on your own network, such as a ntfy server, that may resolve to
|
||||
# private addresses. Comma separated.
|
||||
#FLUXER_PUSH_SERVICE_PRIVATE_HOSTS=ntfy.example.com
|
||||
#FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED=false
|
||||
|
||||
# Direct mobile push through your own APNs and FCM credentials, off by default.
|
||||
#FLUXER_PUSH_APNS_ENABLED=false
|
||||
#FLUXER_PUSH_APNS_TEAM_ID=
|
||||
#FLUXER_PUSH_APNS_KEY_ID=
|
||||
#FLUXER_PUSH_APNS_PRIVATE_KEY=
|
||||
#FLUXER_PUSH_APNS_PRIVATE_KEY_PATH=
|
||||
#FLUXER_PUSH_APNS_APPS=
|
||||
#FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT=production
|
||||
#FLUXER_PUSH_FCM_ENABLED=false
|
||||
#FLUXER_PUSH_FCM_PROJECT_ID=
|
||||
#FLUXER_PUSH_FCM_CLIENT_EMAIL=
|
||||
#FLUXER_PUSH_FCM_PRIVATE_KEY=
|
||||
#FLUXER_PUSH_FCM_PRIVATE_KEY_PATH=
|
||||
#FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH=
|
||||
#FLUXER_PUSH_FCM_TOKEN_URI=https://oauth2.googleapis.com/token
|
||||
#FLUXER_PUSH_FCM_APPS=
|
||||
|
||||
|
||||
# Optional media policies, both off by default. See the operator docs.
|
||||
#
|
||||
# CORS limits which web origins may read media. A request with no Origin, or
|
||||
# from the desktop app, is always served.
|
||||
#
|
||||
# Signatures make an attachment read need a signed URL, so a copied link stops
|
||||
# working. Needs a secret from openssl rand -base64 32, first entry signs and
|
||||
# every entry verifies.
|
||||
#
|
||||
# Each mode is off, report or enforce, and off is the default. Start at report.
|
||||
# media-proxy reads these at start, so apply with docker compose up -d
|
||||
# media-proxy. The values below are examples.
|
||||
#FLUXER_MEDIA_PROXY_CORS_MODE=enforce
|
||||
#FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS=https://chat.example.com
|
||||
#FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64=
|
||||
#FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE=enforce
|
||||
|
||||
# Extra Content-Security-Policy sources, appended to the built-in ones. Set one
|
||||
# only when a browser must reach an origin the defaults do not cover. Separate
|
||||
# several with spaces or commas. The three values below are illustrations.
|
||||
#FLUXER_CSP_EXTRA_DEFAULT_SRC=
|
||||
#FLUXER_CSP_EXTRA_CONNECT_SRC=wss://livekit.example.com:7881
|
||||
#FLUXER_CSP_EXTRA_CONNECT_SRC=wss://livekit.example.com
|
||||
#FLUXER_CSP_EXTRA_IMG_SRC=https://cdn.example.com
|
||||
#FLUXER_CSP_EXTRA_MEDIA_SRC=
|
||||
#FLUXER_CSP_EXTRA_FONT_SRC=
|
||||
@@ -214,76 +265,185 @@ FLUXER_VAPID_PRIVATE_KEY=CHANGE_ME
|
||||
#FLUXER_CSP_EXTRA_WORKER_SRC=
|
||||
#FLUXER_CSP_EXTRA_MANIFEST_SRC=
|
||||
|
||||
# One report-uri for Content-Security-Policy violation reports. Empty leaves the
|
||||
# directive off the header.
|
||||
# One report-uri for CSP violation reports. Empty leaves the directive off.
|
||||
#FLUXER_CSP_REPORT_URI=
|
||||
|
||||
# Allow the SSO identity provider to resolve to a private or internal address.
|
||||
# Off by default: the API refuses to call non-public addresses so a misconfigured
|
||||
# provider URL cannot be used to reach internal services. Turn it on only when the
|
||||
# provider genuinely lives on your own network, such as split-horizon DNS or a LAN
|
||||
# identity provider, and only when you trust everyone who can configure SSO.
|
||||
# Let the SSO provider resolve to a private address. Off by default, so a
|
||||
# misconfigured provider URL cannot reach internal services. Turn it on only for
|
||||
# a provider on your own network. The value below is an example.
|
||||
#FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES=true
|
||||
|
||||
# Both reach LiveKit as LIVEKIT_KEYS and the webhook signing key, and the API as
|
||||
# FLUXER_LIVEKIT_API_KEY and FLUXER_LIVEKIT_API_SECRET. Change them together.
|
||||
# These reach both LiveKit and the api. Change them together.
|
||||
LIVEKIT_API_KEY=fluxer
|
||||
LIVEKIT_API_SECRET=CHANGE_ME
|
||||
|
||||
# The URL browsers use for voice signalling. Compose builds it from
|
||||
# FLUXER_PUBLIC_ORIGIN, or from FLUXER_PUBLIC_SCHEME, FLUXER_DOMAIN and
|
||||
# FLUXER_PUBLIC_PORT, as that origin followed by /livekit. The client rewrites a
|
||||
# leading http to ws itself. Set it only when LiveKit is served from another
|
||||
# host.
|
||||
# The URL browsers use for voice signalling. Built from the public origin plus
|
||||
# /livekit. Set it only when LiveKit is served from another host.
|
||||
#FLUXER_LIVEKIT_URL=
|
||||
|
||||
# Media ports. LiveKit advertises these in ICE candidates, so the host must
|
||||
# forward the same numbers.
|
||||
# Media ports. LiveKit advertises these, so forward the same numbers.
|
||||
#FLUXER_LIVEKIT_TCP_PORT=7881
|
||||
#FLUXER_LIVEKIT_UDP_PORT=7882
|
||||
|
||||
# LiveKit finds the address browsers dial by asking a STUN server. A host that
|
||||
# cannot reach one over UDP stops with "could not resolve external IP", and the
|
||||
# address is then set by hand: put it in FLUXER_LIVEKIT_NODE_IP and set
|
||||
# FLUXER_LIVEKIT_USE_EXTERNAL_IP to false. Point the STUN entries at another
|
||||
# server to keep the lookup and leave Google out of it.
|
||||
# LiveKit finds its public address over STUN. A host that cannot reach one stops
|
||||
# with "could not resolve external IP", so set the address by hand instead, or
|
||||
# point STUN elsewhere. The values below are examples.
|
||||
#FLUXER_LIVEKIT_USE_EXTERNAL_IP=false
|
||||
#FLUXER_LIVEKIT_NODE_IP=203.0.113.10
|
||||
#FLUXER_LIVEKIT_STUN_PRIMARY=stun.l.google.com:19302
|
||||
#FLUXER_LIVEKIT_STUN_SECONDARY=stun1.l.google.com:19302
|
||||
|
||||
FLUXER_KLIPY_API_KEY=
|
||||
# The voice region users see, and how much LiveKit logs.
|
||||
#FLUXER_LIVEKIT_DEFAULT_REGION={"id":"default","name":"Default","emoji":"🌍","latitude":0,"longitude":0}
|
||||
#FLUXER_LIVEKIT_LOG_LEVEL=info
|
||||
|
||||
# STUN servers for peer-to-peer calls, which need the voice_p2p experiment turned
|
||||
# on in the admin panel. STUN finds each peer's public address. The value below
|
||||
# is an example.
|
||||
#FLUXER_VOICE_P2P_STUN_URLS=stun:stun.example.com:3478
|
||||
|
||||
FLUXER_KLIPY_API_KEY=
|
||||
#FLUXER_YOUTUBE_API_KEY=
|
||||
# Hosts the api never unfurls, comma separated.
|
||||
#FLUXER_API_UNFURL_IGNORED_HOSTS=
|
||||
|
||||
# Email delivery. Only an instance where members sign in with email needs it.
|
||||
FLUXER_EMAIL_ENABLED=false
|
||||
FLUXER_EMAIL_PROVIDER=none
|
||||
FLUXER_EMAIL_FROM_EMAIL=[email protected]
|
||||
FLUXER_EMAIL_FROM_NAME=Fluxer
|
||||
FLUXER_EMAIL_FROM_NAME=
|
||||
#[email protected]
|
||||
FLUXER_EMAIL_APP_BASE_URL=
|
||||
FLUXER_EMAIL_SMTP_HOST=
|
||||
FLUXER_EMAIL_SMTP_PORT=587
|
||||
FLUXER_EMAIL_SMTP_USERNAME=
|
||||
FLUXER_EMAIL_SMTP_PASSWORD=
|
||||
FLUXER_EMAIL_SMTP_SECURE=true
|
||||
# How the SMTP connection is encrypted: implicit, starttls, opportunistic or
|
||||
# none. Leave it empty for implicit TLS on port 465 and required STARTTLS on any
|
||||
# other port.
|
||||
FLUXER_EMAIL_SMTP_TLS_MODE=
|
||||
# The older switch, read only while the mode above is empty. true is implicit
|
||||
# and false is opportunistic.
|
||||
#FLUXER_EMAIL_SMTP_SECURE=false
|
||||
#FLUXER_EMAIL_WEBHOOK_SECRET=
|
||||
|
||||
FLUXER_CAPTCHA_ENABLED=false
|
||||
FLUXER_CAPTCHA_PROVIDER=none
|
||||
FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY=
|
||||
FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY=
|
||||
FLUXER_CAPTCHA_TURNSTILE_SITE_KEY=
|
||||
FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY=
|
||||
FLUXER_DISCOVERY_ENABLED=true
|
||||
#FLUXER_DISCOVERY_MIN_MEMBER_COUNT=1
|
||||
|
||||
# Container memory. The limits sum to 18.25 GiB, which is a sum of ceilings and
|
||||
# not an allocation, so the defaults fit a host with 8 GB and are sized for 16 GB.
|
||||
# The reservations are cgroup memory.low, which biases the kernel away from
|
||||
# reclaiming from services whose death takes the instance down. They reserve
|
||||
# nothing. Lower the limits on a smaller host.
|
||||
# Instance identity and account policy. The terms, privacy and community
|
||||
# guidelines links have no variable here. Set them in the admin panel under
|
||||
# Instance Config. Until a guidelines link is set, the clients, report forms and
|
||||
# enforcement emails show none.
|
||||
#FLUXER_APP_PRODUCT_NAME=Fluxer
|
||||
#FLUXER_APP_ICON_URL=
|
||||
#FLUXER_APP_SYMBOL_URL=
|
||||
#FLUXER_APP_LOGO_URL=
|
||||
#FLUXER_APP_WORDMARK_URL=
|
||||
#FLUXER_APP_FAVICON_URL=
|
||||
#FLUXER_APP_THEME_COLOR=
|
||||
#FLUXER_APP_STATUS_PAGE_URL=
|
||||
#FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL=
|
||||
#FLUXER_INSTANCE_SETUP_CONFIGURED=false
|
||||
# Setup token the wizard asks for before the first admin account. 32 to 256 characters. Unset means the API generates one on the first start and writes it to its log once.
|
||||
#FLUXER_SETUP_TOKEN=
|
||||
# How members sign in on a new instance, username or email. Unset means username. Read only on the first start.
|
||||
#FLUXER_ACCOUNT_IDENTITY=
|
||||
# Username tags on a new email instance. none gives unique names with no tag, random gives name#4821. Unset means none. A username instance always uses none. Read only on the first start.
|
||||
#FLUXER_TAG_STYLE=
|
||||
#FLUXER_AUTO_JOIN_INVITE_CODE=
|
||||
#FLUXER_DELETION_GRACE_PERIOD_HOURS=336
|
||||
|
||||
# Report retention. A daily job deletes each report this many days after it was
|
||||
# filed, with its evidence copies and search entry, unless a legal hold is set.
|
||||
#FLUXER_REPORT_RETENTION_DAYS=365
|
||||
# Delete resolved reports this many days after they were resolved, when that
|
||||
# comes first. Unset leaves them to FLUXER_REPORT_RETENTION_DAYS.
|
||||
#FLUXER_RESOLVED_REPORT_RETENTION_DAYS=
|
||||
# true only logs what the job would delete. Deleted evidence cannot be
|
||||
# restored, so check the "Processed report retention" log line of the worker
|
||||
# in a dry run first if you are unsure.
|
||||
#FLUXER_REPORT_RETENTION_DRY_RUN=false
|
||||
|
||||
# Sign in with Bluesky, off unless turned on.
|
||||
#FLUXER_AUTH_BLUESKY_ENABLED=false
|
||||
#FLUXER_AUTH_BLUESKY_CLIENT_NAME=Fluxer
|
||||
#FLUXER_AUTH_BLUESKY_CLIENT_URI=
|
||||
#FLUXER_AUTH_BLUESKY_LOGO_URI=
|
||||
#FLUXER_AUTH_BLUESKY_TOS_URI=
|
||||
#FLUXER_AUTH_BLUESKY_POLICY_URI=
|
||||
#FLUXER_AUTH_BLUESKY_KEYS=
|
||||
|
||||
# Public addresses. Each follows the public origin unless set here.
|
||||
#FLUXER_API_ENDPOINT=
|
||||
#FLUXER_API_CLIENT_ENDPOINT=
|
||||
#FLUXER_APP_ENDPOINT=
|
||||
#FLUXER_GATEWAY_ENDPOINT=
|
||||
#FLUXER_MEDIA_ENDPOINT=
|
||||
#FLUXER_STATIC_CDN_ENDPOINT=
|
||||
#FLUXER_ADMIN_ENDPOINT=
|
||||
#FLUXER_MARKETING_ENDPOINT=
|
||||
#FLUXER_INVITE_ENDPOINT=
|
||||
#FLUXER_GIFT_ENDPOINT=
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT=
|
||||
# This follows FLUXER_STATIC_CDN_ENDPOINT first, then the public origin.
|
||||
#FLUXER_GATEWAY_STATIC_CDN_ENDPOINT=
|
||||
# These follow FLUXER_MEDIA_ENDPOINT first, then the public origin.
|
||||
#FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT=
|
||||
#FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT=
|
||||
|
||||
# Extra hosts for static assets, invites, gifts and the web app. Empty by default.
|
||||
#FLUXER_STATIC_CDN_DOMAIN=
|
||||
#FLUXER_INVITE_DOMAIN=
|
||||
#FLUXER_GIFT_DOMAIN=
|
||||
#FLUXER_APP_ORIGIN_ALIASES=
|
||||
|
||||
# The path the admin panel is served under. The edge and the admin service read
|
||||
# it. The api follows it through the default FLUXER_ADMIN_ENDPOINT, and not when
|
||||
# FLUXER_ADMIN_ENDPOINT is set. Write it with a leading slash and no trailing
|
||||
# slash.
|
||||
#FLUXER_ADMIN_BASE_PATH=/admin
|
||||
|
||||
# The compression the edge offers, as Caddy encode arguments.
|
||||
#FLUXER_EDGE_ENCODE=zstd gzip
|
||||
|
||||
# Extra Caddy global options, one line, added to the global block of the edge
|
||||
# Caddyfile. Empty adds nothing. Example: email [email protected]
|
||||
#FLUXER_EDGE_GLOBAL_OPTIONS=
|
||||
|
||||
# Images of the bundled services, for a mirror or another tag. A new Postgres
|
||||
# major needs a dump and restore, as the upgrade guide describes.
|
||||
#FLUXER_CADDY_IMAGE=caddy:2.11-alpine
|
||||
#FLUXER_POSTGRES_IMAGE=postgres:16-alpine
|
||||
#FLUXER_VALKEY_IMAGE=valkey/valkey:9.1-alpine
|
||||
#FLUXER_NATS_IMAGE=nats:2.14-alpine
|
||||
#FLUXER_MEILISEARCH_IMAGE=getmeili/meilisearch:v1.53
|
||||
#FLUXER_SEAWEEDFS_IMAGE=chrislusf/seaweedfs:4.47
|
||||
#FLUXER_LIVEKIT_IMAGE=livekit/livekit-server:v1.12.0
|
||||
|
||||
# Restart policy for every long-running service.
|
||||
#FLUXER_RESTART_POLICY=unless-stopped
|
||||
|
||||
# Health checks. Raise the retries or start periods on a slow host.
|
||||
#FLUXER_HEALTHCHECK_INTERVAL=10s
|
||||
#FLUXER_HEALTHCHECK_TIMEOUT=5s
|
||||
#FLUXER_HEALTHCHECK_RETRIES=10
|
||||
#FLUXER_APP_HEALTHCHECK_RETRIES=30
|
||||
#FLUXER_APP_HEALTHCHECK_START_PERIOD=90s
|
||||
#FLUXER_SVC_HEALTHCHECK_START_PERIOD=60s
|
||||
#FLUXER_WORKER_HEALTHCHECK_RETRIES=3
|
||||
#FLUXER_SEAWEEDFS_HEALTHCHECK_RETRIES=20
|
||||
#FLUXER_SEAWEEDFS_HEALTHCHECK_START_PERIOD=60s
|
||||
#FLUXER_SEAWEEDFS_INIT_ATTEMPTS=60
|
||||
|
||||
# Container memory. These are ceilings, not allocations, and the defaults suit a
|
||||
# 16 GB host. The reservations bias the kernel away from reclaiming from services
|
||||
# whose death takes the instance down. Lower the limits on a smaller host.
|
||||
#FLUXER_CADDY_MEMORY_LIMIT=256mb
|
||||
#FLUXER_POSTGRES_MEMORY_LIMIT=5gb
|
||||
#FLUXER_POSTGRES_MEMORY_RESERVATION=3gb
|
||||
#FLUXER_VALKEY_MEMORY_LIMIT=256mb
|
||||
#FLUXER_NATS_MEMORY_LIMIT=256mb
|
||||
#FLUXER_MEILISEARCH_MEMORY_LIMIT=768mb
|
||||
#FLUXER_MEILISEARCH_MEMORY_LIMIT=1536mb
|
||||
#FLUXER_SEAWEEDFS_MEMORY_LIMIT=2gb
|
||||
#FLUXER_SEAWEEDFS_INIT_MEMORY_LIMIT=128mb
|
||||
#FLUXER_LIVEKIT_MEMORY_LIMIT=512mb
|
||||
@@ -294,6 +454,7 @@ FLUXER_DISCOVERY_ENABLED=true
|
||||
#FLUXER_GATEWAY_MEMORY_LIMIT=1gb
|
||||
#FLUXER_GATEWAY_MEMORY_RESERVATION=384mb
|
||||
#FLUXER_MEDIA_PROXY_MEMORY_LIMIT=512mb
|
||||
#FLUXER_PUSH_MEMORY_LIMIT=256mb
|
||||
#FLUXER_STATIC_PROXY_MEMORY_LIMIT=256mb
|
||||
#FLUXER_APP_PROXY_MEMORY_LIMIT=256mb
|
||||
#FLUXER_SNOWFLAKES_MEMORY_LIMIT=128mb
|
||||
@@ -308,80 +469,184 @@ FLUXER_DISCOVERY_ENABLED=true
|
||||
#FLUXER_UNFURL_SHARD_MEMORY_LIMIT=256mb
|
||||
#FLUXER_ADMIN_MEMORY_LIMIT=256mb
|
||||
|
||||
# Meilisearch indexing memory. Keep it well under FLUXER_MEILISEARCH_MEMORY_LIMIT,
|
||||
# which is the container ceiling the indexer shares with the search process.
|
||||
#FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY=384mb
|
||||
# Meilisearch indexing memory and threads. Each indexing thread needs its own
|
||||
# buffers on top of the indexing memory, so raise the threads only together with
|
||||
# the container limit above.
|
||||
#FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY=256mb
|
||||
#FLUXER_MEILISEARCH_MAX_INDEXING_THREADS=2
|
||||
#FLUXER_MEILISEARCH_ENV=production
|
||||
#FLUXER_MEILISEARCH_NO_ANALYTICS=true
|
||||
|
||||
# SeaweedFS heap ceiling. Go collects against this value instead of against the
|
||||
# container limit, which it cannot see, so without it an upload burst grows the
|
||||
# heap past FLUXER_SEAWEEDFS_MEMORY_LIMIT and the kernel OOM-kills the container
|
||||
# mid-upload (exit 137). Keep it near three quarters of that limit, and raise both
|
||||
# together: the peak is the parts of one upload in flight at once, which is 25 MB
|
||||
# times 20 for a 500 MB attachment.
|
||||
# SeaweedFS heap ceiling. Go cannot see the container limit, so without this an
|
||||
# upload burst gets the container OOM-killed. Keep it near three quarters of
|
||||
# FLUXER_SEAWEEDFS_MEMORY_LIMIT and raise both together.
|
||||
#FLUXER_SEAWEEDFS_GOMEMLIMIT=1536MiB
|
||||
#FLUXER_SEAWEEDFS_TELEMETRY=false
|
||||
|
||||
# Node sizes its own heap from the container memory limit by default, at roughly
|
||||
# 55 percent of it, which always leaves room for the buffers and stacks that live
|
||||
# outside the heap. Leave these unset unless you have a reason to pin the value.
|
||||
# Any value set here must stay well below the container limit above: a heap ceiling
|
||||
# above the container limit makes the kernel OOM-kill the container (exit 137, no
|
||||
# diagnostics) instead of Node reporting a JavaScript heap out of memory error.
|
||||
# Volumes SeaweedFS creates at once when a bucket needs space. Each reserves 1 GB
|
||||
# of free disk from the start, and SeaweedFS's own default of 7 fills a small
|
||||
# disk before every bucket has one, so uploads fail with no free volumes left.
|
||||
#FLUXER_SEAWEEDFS_VOLUME_GROWTH=1
|
||||
|
||||
# Node sizes its heap from the container limit by default. Leave these unset
|
||||
# unless you need to pin it. A heap ceiling above the container limit gets the
|
||||
# container OOM-killed instead of reporting a heap error. The values below are
|
||||
# examples.
|
||||
#FLUXER_API_NODE_HEAP_MB=1792
|
||||
#FLUXER_WORKER_NODE_HEAP_MB=1792
|
||||
|
||||
# Bundled Postgres tuning. Keep these consistent with FLUXER_POSTGRES_MEMORY_LIMIT:
|
||||
# budget roughly shared_buffers + (server max_connections x 12 MB) +
|
||||
# (3 x autovacuum_work_mem) + 300 MB for page cache and WAL. Note this is the
|
||||
# server setting, distinct from the per-service FLUXER_POSTGRES_MAX_CONNECTIONS
|
||||
# pool sizes used by the api, worker and shards.
|
||||
# Extra Node flags for api and worker, appended to NODE_OPTIONS. Empty by
|
||||
# default. The value below is an example.
|
||||
#FLUXER_API_NODE_OPTIONS=--heapsnapshot-near-heap-limit=1
|
||||
#FLUXER_WORKER_NODE_OPTIONS=--heapsnapshot-near-heap-limit=1
|
||||
|
||||
# Extra CA certificates api and worker trust, as a PEM bundle path inside the
|
||||
# container. The default is the image's system bundle.
|
||||
#FLUXER_NODE_EXTRA_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
# Bundled Postgres tuning. Keep it consistent with the memory limit above. This
|
||||
# is the server setting, not the per-service pool sizes.
|
||||
#FLUXER_POSTGRES_SERVER_MAX_CONNECTIONS=150
|
||||
#FLUXER_POSTGRES_SHARED_BUFFERS=512MB
|
||||
#FLUXER_POSTGRES_EFFECTIVE_CACHE_SIZE=2GB
|
||||
#FLUXER_POSTGRES_WORK_MEM=8MB
|
||||
#FLUXER_POSTGRES_MAINTENANCE_WORK_MEM=256MB
|
||||
#FLUXER_POSTGRES_AUTOVACUUM_WORK_MEM=128MB
|
||||
#FLUXER_POSTGRES_SHM_SIZE=1gb
|
||||
#FLUXER_POSTGRES_RANDOM_PAGE_COST=1.1
|
||||
#FLUXER_POSTGRES_EFFECTIVE_IO_CONCURRENCY=200
|
||||
#FLUXER_POSTGRES_DEFAULT_STATISTICS_TARGET=200
|
||||
#FLUXER_POSTGRES_JIT=off
|
||||
#FLUXER_POSTGRES_MIN_WAL_SIZE=512MB
|
||||
#FLUXER_POSTGRES_MAX_WAL_SIZE=2GB
|
||||
#FLUXER_POSTGRES_CHECKPOINT_COMPLETION_TARGET=0.9
|
||||
#FLUXER_POSTGRES_WAL_BUFFERS=16MB
|
||||
#FLUXER_POSTGRES_WAL_COMPRESSION=zstd
|
||||
#FLUXER_POSTGRES_BGWRITER_DELAY=50ms
|
||||
#FLUXER_POSTGRES_BGWRITER_LRU_MAXPAGES=1000
|
||||
#FLUXER_POSTGRES_AUTOVACUUM_VACUUM_SCALE_FACTOR=0.05
|
||||
#FLUXER_POSTGRES_AUTOVACUUM_ANALYZE_SCALE_FACTOR=0.02
|
||||
#FLUXER_POSTGRES_AUTOVACUUM_VACUUM_COST_LIMIT=2000
|
||||
#FLUXER_POSTGRES_TRACK_IO_TIMING=on
|
||||
#FLUXER_POSTGRES_SHARED_PRELOAD_LIBRARIES=pg_stat_statements
|
||||
|
||||
# The bundled Valkey holds durable state as well as cache. The bulk message
|
||||
# deletion queue and the account deletion queue are sorted sets with no expiry,
|
||||
# and nothing else stores the first of the two. It therefore runs with an
|
||||
# append-only file on a named volume and with noeviction, so an over-limit write
|
||||
# fails loudly instead of silently deleting queued work. Distributed locks all
|
||||
# carry a TTL and are not what the durability is for. Only change the policy if
|
||||
# you have moved that durable state elsewhere.
|
||||
# Postgres pool size of each service that opens a pool.
|
||||
#FLUXER_API_POSTGRES_MAX_CONNECTIONS=25
|
||||
#FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS=25
|
||||
#FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS=20
|
||||
#FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS=20
|
||||
|
||||
# The bundled Valkey holds durable state as well as cache, so it runs with an
|
||||
# append-only file and with noeviction, which fails an over-limit write instead
|
||||
# of dropping queued work. Change the policy only if that state is kept elsewhere.
|
||||
#FLUXER_VALKEY_MAXMEMORY=192mb
|
||||
#FLUXER_VALKEY_MAXMEMORY_POLICY=noeviction
|
||||
#FLUXER_VALKEY_APPENDFSYNC=everysec
|
||||
|
||||
# The gateway derives its BEAM scheduler count from the container CPU quota,
|
||||
# clamped to this range. The floor matters: a single scheduler lets one blocking
|
||||
# operation stall every websocket on the node. The ceiling stops a large host
|
||||
# from starting far more schedulers than the container can actually use.
|
||||
# The gateway derives its scheduler count from the CPU quota, clamped here. One
|
||||
# scheduler lets a single blocking operation stall every websocket on the node.
|
||||
#FLUXER_ERLANG_SCHEDULERS_MIN=2
|
||||
#FLUXER_ERLANG_SCHEDULERS_MAX=16
|
||||
# A fixed scheduler count skips the clamp. Dirty CPU schedulers default to two
|
||||
# thirds of it.
|
||||
#FLUXER_ERLANG_SCHEDULERS=
|
||||
#FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS=
|
||||
|
||||
# In-flight request ceiling for the services Compose forwards it to: the users
|
||||
# and messages routers and their shards. Leave it unset and each service uses its
|
||||
# built-in default. Set it and the one value replaces that default on all of
|
||||
# them, so size it for the busiest. The built-in defaults are 192 for
|
||||
# messages, 320 for snowflakes and 64 elsewhere, and they govern every service
|
||||
# Compose does not forward this to. A router holds a slot for the whole round
|
||||
# trip to its shard, so this is a ceiling on requests in flight at once and not a
|
||||
# rate: too low a value does not slow requests down, it rejects them, and the api
|
||||
# turns that rejection into a 503.
|
||||
#FLUXER_SVC_MAX_CONCURRENT_REQUESTS=192
|
||||
# Gateway push and RPC tuning.
|
||||
#FLUXER_GATEWAY_PUSH_ENABLED=true
|
||||
#FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED=true
|
||||
#FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS=100000
|
||||
#FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES=128
|
||||
#FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES=1048576
|
||||
#FLUXER_GATEWAY_HTTP_RPC_MAX_CONCURRENCY=512
|
||||
#FLUXER_GATEWAY_NATS_RPC_MAX_HANDLERS=512
|
||||
#FLUXER_GATEWAY_HTTP_FAILURE_THRESHOLD=6
|
||||
#FLUXER_GATEWAY_HTTP_RECOVERY_TIMEOUT_MS=15000
|
||||
|
||||
# The api and the Rust services name their fixed Postgres statement shapes so the
|
||||
# server can reuse their plans. Named prepared statements require a session that
|
||||
# outlives the transaction, so set this to false if you put a transaction-pooling
|
||||
# connection pooler such as PgBouncer in front of Postgres. One setting governs
|
||||
# every service. The bundled compose talks to Postgres directly, where naming is
|
||||
# a win and the default is correct.
|
||||
# In-flight request ceiling for every svc router and shard. Unset, each keeps its
|
||||
# own default: 192 for messages, 320 for snowflakes and 64 for the rest. One value
|
||||
# replaces all of them, so size it for the busiest. Too low a value rejects
|
||||
# requests rather than slowing them, and the api turns that into a 503. The value
|
||||
# below is an example.
|
||||
#FLUXER_SVC_MAX_CONCURRENT_REQUESTS=320
|
||||
|
||||
# svc caches, and how the api calls the svc services over NATS.
|
||||
#FLUXER_SVC_CACHE_MAX_ENTRIES=100000
|
||||
#FLUXER_SVC_CACHE_TTL_MS=30000
|
||||
#FLUXER_GIFS_SHARD_CACHE_MAX_BYTES=536870912
|
||||
#FLUXER_GIF_SERVICE_NATS_CLIENT_NAME=fluxer-api-gifs
|
||||
#FLUXER_GIF_SERVICE_TIMEOUT_MS=12000
|
||||
#FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS=3000
|
||||
#FLUXER_USERS_SERVICE_NATS_CLIENT_NAME=fluxer-api-users
|
||||
#FLUXER_USERS_SERVICE_TIMEOUT_MS=6000
|
||||
#FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES=10000
|
||||
#FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME=fluxer-api-snowflakes
|
||||
#FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE=128
|
||||
#FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK=
|
||||
#FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS=5000
|
||||
#FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS=6000
|
||||
|
||||
# Worker concurrency per lane, as a JSON object keyed by lane.
|
||||
#FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES=
|
||||
|
||||
# Named prepared statements need a session that outlives the transaction, so set
|
||||
# this to false behind a transaction-pooling connection pooler. The bundled
|
||||
# compose talks to Postgres directly, where the default is correct.
|
||||
#FLUXER_POSTGRES_PREPARED_STATEMENTS=true
|
||||
|
||||
# The api bounds how long a client may take to send a request. The header timeout
|
||||
# covers the request line and headers only, while the request timeout covers the
|
||||
# whole exchange, so a slow uploader is bounded by the second value and not by
|
||||
# the first. Raise both if you front large uploads or serve clients on high
|
||||
# latency links. The header timeout is clamped down to the request timeout, so
|
||||
# raising it alone does nothing. Both are milliseconds, between 1000 and 3600000.
|
||||
# How long a client may take to send a request. The header timeout covers the
|
||||
# request line and headers, the request timeout the whole exchange, and the first
|
||||
# is clamped down to the second. Milliseconds, 1000 to 3600000.
|
||||
#FLUXER_API_HEADERS_TIMEOUT_MS=30000
|
||||
#FLUXER_API_REQUEST_TIMEOUT_MS=120000
|
||||
|
||||
# api request limits and IP bans. A refresh interval of 0 stops the periodic
|
||||
# ban reload.
|
||||
#FLUXER_API_MAX_INFLIGHT_REQUESTS=512
|
||||
#FLUXER_API_IP_BAN_EXEMPT_IPS=
|
||||
#FLUXER_IP_BAN_REFRESH_INTERVAL_MS=300000
|
||||
|
||||
# Uploads and data exports. Presigned exports link to FLUXER_S3_PUBLIC_ENDPOINT,
|
||||
# so turn them on only once browsers can reach it.
|
||||
#FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED=true
|
||||
#FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED=false
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES=524288000
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS=900
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES=
|
||||
#FLUXER_API_STORAGE_CHANGE_FEED_ENABLED=false
|
||||
#FLUXER_API_STORAGE_CHANGE_FEED_STREAM=STORAGE_CHANGES
|
||||
#FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS=
|
||||
#FLUXER_CACHE_PURGE_ADAPTER=none
|
||||
#FLUXER_CACHE_PURGE_HTTP_ENDPOINT=
|
||||
#FLUXER_CACHE_PURGE_HTTP_TOKEN=
|
||||
#FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS=10000
|
||||
#FLUXER_CACHE_PURGE_CONTROLLER_ENDPOINT=
|
||||
#FLUXER_CACHE_PURGE_CONTROLLER_TOKEN=
|
||||
#FLUXER_CACHE_PURGE_CONTROLLER_TIMEOUT_MS=10000
|
||||
|
||||
# media-proxy limits and timeouts.
|
||||
#FLUXER_MEDIA_PROXY_READ_ONLY=false
|
||||
#FLUXER_MEDIA_PROXY_NSFW_THRESHOLD=0.85
|
||||
#FLUXER_NSFW_SERVICE_ENDPOINT=
|
||||
#FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS=
|
||||
#FLUXER_MEDIA_PROXY_WORKER_QUEUE_CAPACITY=
|
||||
#FLUXER_MEDIA_PROXY_MAX_ENCODE_DURATION_MS=30000
|
||||
#FLUXER_MEDIA_PROXY_MAX_ENCODE_FRAMES=20000
|
||||
#FLUXER_MEDIA_PROXY_TRANSFORM_TIMEOUT_MS=15000
|
||||
#FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_BYTES=268435456
|
||||
#FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES=67108864
|
||||
#FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_TTL_MS=120000
|
||||
#FLUXER_MEDIA_PROXY_SOCKET_IO_TIMEOUT_MS=30000
|
||||
#FLUXER_MEDIA_PROXY_SHUTDOWN_GRACE_MS=30000
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS=900000
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES=33554432
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES=536870912
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_DIR=
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES=1048576
|
||||
#FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES=8589934592
|
||||
|
||||
# app-proxy discovery refresh, index upstream and manifest scope.
|
||||
#DISCOVERY_REFRESH_INTERVAL_MS=60000
|
||||
#FLUXER_APP_PROXY_INDEX_UPSTREAM_URL=
|
||||
#FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS=
|
||||
#FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS=
|
||||
@@ -3,10 +3,11 @@
|
||||
trusted_proxies static {$FLUXER_EDGE_TRUSTED_PROXIES:private_ranges}
|
||||
trusted_proxies_strict
|
||||
}
|
||||
{$FLUXER_EDGE_GLOBAL_OPTIONS}
|
||||
}
|
||||
|
||||
{$FLUXER_EDGE_SITE_ADDRESS} {
|
||||
encode zstd gzip
|
||||
encode {$FLUXER_EDGE_ENCODE:zstd gzip}
|
||||
|
||||
handle /_health {
|
||||
respond "OK" 200
|
||||
@@ -33,16 +34,16 @@
|
||||
reverse_proxy livekit:7880
|
||||
}
|
||||
|
||||
handle /admin {
|
||||
handle {$FLUXER_ADMIN_BASE_PATH:/admin} {
|
||||
rewrite * /
|
||||
reverse_proxy admin:8080
|
||||
}
|
||||
|
||||
handle_path /admin/* {
|
||||
handle_path {$FLUXER_ADMIN_BASE_PATH:/admin}/* {
|
||||
reverse_proxy admin:8080
|
||||
}
|
||||
|
||||
@staticAssets path /web/* /emoji/* /libs/* /avatars/* /badges/* /desktop/* /embeds/*
|
||||
@staticAssets path /web/* /emoji/* /libs/* /avatars/* /badges/* /desktop/*
|
||||
handle @staticAssets {
|
||||
reverse_proxy static-proxy:8080
|
||||
}
|
||||
|
||||
@@ -3,38 +3,81 @@ name: fluxer
|
||||
x-fluxer-postgres-env: &fluxer-postgres-env
|
||||
FLUXER_DATABASE_BACKEND: postgres
|
||||
FLUXER_POSTGRES_HOST: ${FLUXER_POSTGRES_HOST:-postgres}
|
||||
FLUXER_POSTGRES_PORT: "${FLUXER_POSTGRES_PORT:-5432}"
|
||||
FLUXER_POSTGRES_DATABASE: ${FLUXER_POSTGRES_DATABASE:-fluxer}
|
||||
FLUXER_POSTGRES_USERNAME: ${FLUXER_POSTGRES_USERNAME:-fluxer}
|
||||
FLUXER_POSTGRES_PORT: ${FLUXER_POSTGRES_PORT:-}
|
||||
FLUXER_POSTGRES_DATABASE: ${FLUXER_POSTGRES_DATABASE:-}
|
||||
FLUXER_POSTGRES_USERNAME: ${FLUXER_POSTGRES_USERNAME:-}
|
||||
FLUXER_POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
||||
FLUXER_POSTGRES_SSL: "${FLUXER_POSTGRES_SSL:-false}"
|
||||
FLUXER_POSTGRES_PREPARED_STATEMENTS: ${FLUXER_POSTGRES_PREPARED_STATEMENTS:-true}
|
||||
FLUXER_POSTGRES_URL: ${FLUXER_POSTGRES_URL:-}
|
||||
FLUXER_POSTGRES_SSL: ${FLUXER_POSTGRES_SSL:-}
|
||||
FLUXER_POSTGRES_SSL_CA: ${FLUXER_POSTGRES_SSL_CA:-}
|
||||
FLUXER_POSTGRES_PREPARED_STATEMENTS: ${FLUXER_POSTGRES_PREPARED_STATEMENTS:-}
|
||||
FLUXER_POSTGRES_KV_TABLE: ${FLUXER_POSTGRES_KV_TABLE:-}
|
||||
|
||||
x-fluxer-env: &fluxer-env
|
||||
<<: *fluxer-postgres-env
|
||||
FLUXER_ENV: production
|
||||
NODE_ENV: production
|
||||
LOG_LEVEL: ${LOG_LEVEL:-info}
|
||||
LOG_LEVEL: ${LOG_LEVEL:-}
|
||||
RUST_LOG: ${RUST_LOG:-}
|
||||
FLUXER_SELF_HOSTED: "true"
|
||||
FLUXER_BASE_DOMAIN: ${FLUXER_DOMAIN:?set FLUXER_DOMAIN in .env}
|
||||
FLUXER_PUBLIC_SCHEME: ${FLUXER_PUBLIC_SCHEME:-https}
|
||||
FLUXER_PUBLIC_PORT: ${FLUXER_PUBLIC_PORT:-443}
|
||||
FLUXER_PUBLIC_ORIGIN: ${FLUXER_PUBLIC_ORIGIN:-}
|
||||
FLUXER_TRUST_CLIENT_IP_HEADER: "${FLUXER_TRUST_CLIENT_IP_HEADER:-true}"
|
||||
FLUXER_CLIENT_IP_HEADER_NAME: ${FLUXER_CLIENT_IP_HEADER_NAME:-x-forwarded-for}
|
||||
FLUXER_API_HEADERS_TIMEOUT_MS: ${FLUXER_API_HEADERS_TIMEOUT_MS:-30000}
|
||||
FLUXER_API_REQUEST_TIMEOUT_MS: ${FLUXER_API_REQUEST_TIMEOUT_MS:-120000}
|
||||
FLUXER_CLIENT_IP_HEADER_NAME: ${FLUXER_CLIENT_IP_HEADER_NAME:-}
|
||||
FLUXER_API_HEADERS_TIMEOUT_MS: ${FLUXER_API_HEADERS_TIMEOUT_MS:-}
|
||||
FLUXER_API_REQUEST_TIMEOUT_MS: ${FLUXER_API_REQUEST_TIMEOUT_MS:-}
|
||||
FLUXER_API_MAX_INFLIGHT_REQUESTS: ${FLUXER_API_MAX_INFLIGHT_REQUESTS:-}
|
||||
FLUXER_API_IP_BAN_EXEMPT_IPS: ${FLUXER_API_IP_BAN_EXEMPT_IPS:-}
|
||||
FLUXER_IP_BAN_REFRESH_INTERVAL_MS: ${FLUXER_IP_BAN_REFRESH_INTERVAL_MS:-}
|
||||
FLUXER_APP_ORIGIN_ALIASES: ${FLUXER_APP_ORIGIN_ALIASES:-}
|
||||
FLUXER_BREACHED_PASSWORD_CHECK_ENABLED: ${FLUXER_BREACHED_PASSWORD_CHECK_ENABLED:-}
|
||||
FLUXER_BLOCKLIST_FEEDS_ENABLED: ${FLUXER_BLOCKLIST_FEEDS_ENABLED:-}
|
||||
FLUXER_GEOIP_DB_PATH: ${FLUXER_GEOIP_DB_PATH:-}
|
||||
|
||||
FLUXER_API_ENDPOINT: ${FLUXER_API_ENDPOINT:-}
|
||||
FLUXER_API_CLIENT_ENDPOINT: ${FLUXER_API_CLIENT_ENDPOINT:-}
|
||||
FLUXER_APP_ENDPOINT: ${FLUXER_APP_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}
|
||||
FLUXER_GATEWAY_ENDPOINT: ${FLUXER_GATEWAY_ENDPOINT:-}
|
||||
FLUXER_MEDIA_ENDPOINT: ${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media}
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT:-${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media}}
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-}
|
||||
FLUXER_ADMIN_ENDPOINT: ${FLUXER_ADMIN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}${FLUXER_ADMIN_BASE_PATH:-/admin}}
|
||||
FLUXER_MARKETING_ENDPOINT: ${FLUXER_MARKETING_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}
|
||||
FLUXER_INVITE_ENDPOINT: ${FLUXER_INVITE_ENDPOINT:-}
|
||||
FLUXER_GIFT_ENDPOINT: ${FLUXER_GIFT_ENDPOINT:-}
|
||||
FLUXER_STATIC_CDN_DOMAIN: ${FLUXER_STATIC_CDN_DOMAIN:-}
|
||||
FLUXER_INVITE_DOMAIN: ${FLUXER_INVITE_DOMAIN:-}
|
||||
FLUXER_GIFT_DOMAIN: ${FLUXER_GIFT_DOMAIN:-}
|
||||
|
||||
FLUXER_KV_URL: ${FLUXER_KV_URL:-redis://valkey:6379/0}
|
||||
FLUXER_KV_MODE: ${FLUXER_KV_MODE:-}
|
||||
FLUXER_NATS_URL: ${FLUXER_NATS_URL:-nats://nats:4222}
|
||||
FLUXER_NATS_JETSTREAM_URL: ${FLUXER_NATS_JETSTREAM_URL:-${FLUXER_NATS_URL:-nats://nats:4222}}
|
||||
FLUXER_NATS_AUTH_TOKEN: ${FLUXER_NATS_AUTH_TOKEN:-}
|
||||
FLUXER_SVC_NATS_URL: ${FLUXER_SVC_NATS_URL:-${FLUXER_NATS_URL:-nats://nats:4222}}
|
||||
FLUXER_SVC_SHARD_COUNT: "1"
|
||||
FLUXER_SVC_MAX_CONCURRENT_REQUESTS: ${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}
|
||||
FLUXER_SVC_CACHE_MAX_ENTRIES: ${FLUXER_SVC_CACHE_MAX_ENTRIES:-}
|
||||
FLUXER_SVC_CACHE_TTL_MS: ${FLUXER_SVC_CACHE_TTL_MS:-}
|
||||
FLUXER_GIF_SERVICE_NATS_CLIENT_NAME: ${FLUXER_GIF_SERVICE_NATS_CLIENT_NAME:-}
|
||||
FLUXER_GIF_SERVICE_TIMEOUT_MS: ${FLUXER_GIF_SERVICE_TIMEOUT_MS:-}
|
||||
FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS: ${FLUXER_GIF_SERVICE_REGISTER_SHARE_TIMEOUT_MS:-}
|
||||
FLUXER_USERS_SERVICE_NATS_CLIENT_NAME: ${FLUXER_USERS_SERVICE_NATS_CLIENT_NAME:-}
|
||||
FLUXER_USERS_SERVICE_TIMEOUT_MS: ${FLUXER_USERS_SERVICE_TIMEOUT_MS:-}
|
||||
FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES: ${FLUXER_USERS_SERVICE_INFLIGHT_MAX_ENTRIES:-}
|
||||
FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME: ${FLUXER_SNOWFLAKE_SERVICE_NATS_CLIENT_NAME:-}
|
||||
FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE: ${FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE:-}
|
||||
FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK: ${FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK:-}
|
||||
FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS: ${FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS:-}
|
||||
FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS: ${FLUXER_SNOWFLAKE_SERVICE_REQUEST_TIMEOUT_MS:-}
|
||||
|
||||
FLUXER_SEARCH_ENGINE: meilisearch
|
||||
FLUXER_SEARCH_ENGINE: ${FLUXER_SEARCH_ENGINE:-meilisearch}
|
||||
FLUXER_SEARCH_URL: ${FLUXER_SEARCH_URL:-http://meilisearch:7700}
|
||||
FLUXER_SEARCH_API_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY in .env}
|
||||
FLUXER_SEARCH_USERNAME: ${FLUXER_SEARCH_USERNAME:-}
|
||||
FLUXER_SEARCH_PASSWORD: ${FLUXER_SEARCH_PASSWORD:-}
|
||||
FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED: ${FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED:-}
|
||||
|
||||
FLUXER_S3_ENDPOINT: ${FLUXER_S3_ENDPOINT:-http://seaweedfs:8333}
|
||||
FLUXER_S3_PUBLIC_ENDPOINT: ${FLUXER_S3_PUBLIC_ENDPOINT:-${FLUXER_S3_ENDPOINT:-http://seaweedfs:8333}}
|
||||
@@ -44,91 +87,145 @@ x-fluxer-env: &fluxer-env
|
||||
FLUXER_S3_FORCE_PATH_STYLE: "${FLUXER_S3_FORCE_PATH_STYLE:-true}"
|
||||
FLUXER_S3_BUCKET_CDN: ${FLUXER_S3_BUCKET_CDN:-fluxer}
|
||||
FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-fluxer-uploads}
|
||||
FLUXER_S3_BUCKET_DOWNLOADS: ${FLUXER_S3_BUCKET_DOWNLOADS:-fluxer-downloads}
|
||||
FLUXER_S3_BUCKET_REPORTS: ${FLUXER_S3_BUCKET_REPORTS:-fluxer-reports}
|
||||
FLUXER_S3_BUCKET_HARVESTS: ${FLUXER_S3_BUCKET_HARVESTS:-fluxer-harvests}
|
||||
AWS_ACCESS_KEY_ID: ${FLUXER_S3_ACCESS_KEY:?set FLUXER_S3_ACCESS_KEY in .env}
|
||||
AWS_SECRET_ACCESS_KEY: ${FLUXER_S3_SECRET_KEY:?set FLUXER_S3_SECRET_KEY in .env}
|
||||
AWS_DEFAULT_REGION: ${FLUXER_S3_REGION:-us-east-1}
|
||||
AWS_EC2_METADATA_DISABLED: "true"
|
||||
FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED: "${FLUXER_API_PRESIGNED_HARVEST_DOWNLOADS_ENABLED:-false}"
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_ENABLED: ${FLUXER_API_STORAGE_CHANGE_FEED_ENABLED:-}
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_STREAM: ${FLUXER_API_STORAGE_CHANGE_FEED_STREAM:-}
|
||||
FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS: ${FLUXER_API_STORAGE_CHANGE_FEED_SKIP_BUCKETS:-}
|
||||
FLUXER_CACHE_PURGE_ADAPTER: ${FLUXER_CACHE_PURGE_ADAPTER:-}
|
||||
FLUXER_CACHE_PURGE_HTTP_ENDPOINT: ${FLUXER_CACHE_PURGE_HTTP_ENDPOINT:-}
|
||||
FLUXER_CACHE_PURGE_HTTP_TOKEN: ${FLUXER_CACHE_PURGE_HTTP_TOKEN:-}
|
||||
FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS: ${FLUXER_CACHE_PURGE_HTTP_TIMEOUT_MS:-}
|
||||
FLUXER_CACHE_PURGE_CONTROLLER_ENDPOINT: ${FLUXER_CACHE_PURGE_CONTROLLER_ENDPOINT:-}
|
||||
FLUXER_CACHE_PURGE_CONTROLLER_TOKEN: ${FLUXER_CACHE_PURGE_CONTROLLER_TOKEN:-}
|
||||
FLUXER_CACHE_PURGE_CONTROLLER_TIMEOUT_MS: ${FLUXER_CACHE_PURGE_CONTROLLER_TIMEOUT_MS:-}
|
||||
|
||||
FLUXER_LIVEKIT_ENABLED: "${FLUXER_LIVEKIT_ENABLED:-true}"
|
||||
FLUXER_LIVEKIT_API_KEY: ${LIVEKIT_API_KEY:?set LIVEKIT_API_KEY in .env}
|
||||
FLUXER_LIVEKIT_API_SECRET: ${LIVEKIT_API_SECRET:?set LIVEKIT_API_SECRET in .env}
|
||||
FLUXER_LIVEKIT_INTERNAL_URL: ${FLUXER_LIVEKIT_INTERNAL_URL:-http://livekit:7880}
|
||||
FLUXER_LIVEKIT_WEBHOOK_URL: http://api:8080/webhooks/livekit
|
||||
FLUXER_LIVEKIT_DEFAULT_REGION: '{"id":"default","name":"Default","emoji":"🌍","latitude":0,"longitude":0}'
|
||||
FLUXER_LIVEKIT_DEFAULT_REGION: '${FLUXER_LIVEKIT_DEFAULT_REGION:-{"id":"default","name":"Default","emoji":"🌍","latitude":0,"longitude":0}}'
|
||||
FLUXER_LIVEKIT_URL: ${FLUXER_LIVEKIT_URL:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}:${FLUXER_PUBLIC_PORT:-443}}/livekit}
|
||||
FLUXER_VOICE_P2P_STUN_URLS: ${FLUXER_VOICE_P2P_STUN_URLS:-}
|
||||
|
||||
FLUXER_KLIPY_API_KEY: ${FLUXER_KLIPY_API_KEY:-}
|
||||
FLUXER_YOUTUBE_API_KEY: ${FLUXER_YOUTUBE_API_KEY:-}
|
||||
FLUXER_API_UNFURL_IGNORED_HOSTS: ${FLUXER_API_UNFURL_IGNORED_HOSTS:-}
|
||||
|
||||
FLUXER_EMAIL_ENABLED: ${FLUXER_EMAIL_ENABLED:-false}
|
||||
FLUXER_EMAIL_PROVIDER: ${FLUXER_EMAIL_PROVIDER:-none}
|
||||
FLUXER_EMAIL_ENABLED: ${FLUXER_EMAIL_ENABLED:-}
|
||||
FLUXER_EMAIL_PROVIDER: ${FLUXER_EMAIL_PROVIDER:-}
|
||||
FLUXER_EMAIL_FROM_EMAIL: ${FLUXER_EMAIL_FROM_EMAIL:-noreply@localhost}
|
||||
FLUXER_EMAIL_FROM_NAME: ${FLUXER_EMAIL_FROM_NAME:-Fluxer}
|
||||
FLUXER_EMAIL_FROM_NAME: ${FLUXER_EMAIL_FROM_NAME:-}
|
||||
FLUXER_EMAIL_REPLY_TO_EMAIL: ${FLUXER_EMAIL_REPLY_TO_EMAIL:-}
|
||||
FLUXER_EMAIL_APP_BASE_URL: ${FLUXER_EMAIL_APP_BASE_URL:-}
|
||||
FLUXER_EMAIL_WEBHOOK_SECRET: ${FLUXER_EMAIL_WEBHOOK_SECRET:-}
|
||||
FLUXER_EMAIL_SMTP_HOST: ${FLUXER_EMAIL_SMTP_HOST:-}
|
||||
FLUXER_EMAIL_SMTP_PORT: ${FLUXER_EMAIL_SMTP_PORT:-587}
|
||||
FLUXER_EMAIL_SMTP_PORT: ${FLUXER_EMAIL_SMTP_PORT:-}
|
||||
FLUXER_EMAIL_SMTP_USERNAME: ${FLUXER_EMAIL_SMTP_USERNAME:-}
|
||||
FLUXER_EMAIL_SMTP_PASSWORD: ${FLUXER_EMAIL_SMTP_PASSWORD:-}
|
||||
FLUXER_EMAIL_SMTP_SECURE: ${FLUXER_EMAIL_SMTP_SECURE:-true}
|
||||
FLUXER_EMAIL_SMTP_TLS_MODE: ${FLUXER_EMAIL_SMTP_TLS_MODE:-}
|
||||
FLUXER_EMAIL_SMTP_SECURE: ${FLUXER_EMAIL_SMTP_SECURE:-}
|
||||
|
||||
FLUXER_SMS_ENABLED: "${FLUXER_SMS_ENABLED:-false}"
|
||||
FLUXER_CAPTCHA_ENABLED: ${FLUXER_CAPTCHA_ENABLED:-false}
|
||||
FLUXER_CAPTCHA_PROVIDER: ${FLUXER_CAPTCHA_PROVIDER:-none}
|
||||
FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY: ${FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY:-}
|
||||
FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY: ${FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY:-}
|
||||
FLUXER_CAPTCHA_TURNSTILE_SITE_KEY: ${FLUXER_CAPTCHA_TURNSTILE_SITE_KEY:-}
|
||||
FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY: ${FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY:-}
|
||||
FLUXER_STRIPE_ENABLED: "${FLUXER_STRIPE_ENABLED:-false}"
|
||||
FLUXER_NCMEC_ENABLED: "${FLUXER_NCMEC_ENABLED:-false}"
|
||||
FLUXER_CLAMAV_ENABLED: "${FLUXER_CLAMAV_ENABLED:-false}"
|
||||
FLUXER_DISCOVERY_ENABLED: ${FLUXER_DISCOVERY_ENABLED:-true}
|
||||
FLUXER_STRIPE_ENABLED: ${FLUXER_STRIPE_ENABLED:-}
|
||||
FLUXER_STRIPE_SECRET_KEY: ${FLUXER_STRIPE_SECRET_KEY:-}
|
||||
FLUXER_STRIPE_WEBHOOK_SECRET: ${FLUXER_STRIPE_WEBHOOK_SECRET:-}
|
||||
FLUXER_STRIPE_PRICES: ${FLUXER_STRIPE_PRICES:-}
|
||||
FLUXER_STRIPE_LEGACY_PRICES: ${FLUXER_STRIPE_LEGACY_PRICES:-}
|
||||
FLUXER_API_DONATION_PROXY_KEY: ${FLUXER_API_DONATION_PROXY_KEY:-}
|
||||
FLUXER_API_TRUSTED_CALLERS: ${FLUXER_API_TRUSTED_CALLERS:-}
|
||||
FLUXER_VISIONARIES_GUILD_ID: ${FLUXER_VISIONARIES_GUILD_ID:-}
|
||||
FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID: ${FLUXER_VISIONARIES_GUILD_VISIONARY_ROLE_ID:-}
|
||||
|
||||
FLUXER_CLAMAV_ENABLED: ${FLUXER_CLAMAV_ENABLED:-}
|
||||
FLUXER_CLAMAV_HOST: ${FLUXER_CLAMAV_HOST:-}
|
||||
FLUXER_CLAMAV_PORT: ${FLUXER_CLAMAV_PORT:-}
|
||||
FLUXER_CLAMAV_FAIL_OPEN: ${FLUXER_CLAMAV_FAIL_OPEN:-}
|
||||
|
||||
FLUXER_APP_PRODUCT_NAME: ${FLUXER_APP_PRODUCT_NAME:-}
|
||||
FLUXER_APP_ICON_URL: ${FLUXER_APP_ICON_URL:-}
|
||||
FLUXER_APP_SYMBOL_URL: ${FLUXER_APP_SYMBOL_URL:-}
|
||||
FLUXER_APP_LOGO_URL: ${FLUXER_APP_LOGO_URL:-}
|
||||
FLUXER_APP_WORDMARK_URL: ${FLUXER_APP_WORDMARK_URL:-}
|
||||
FLUXER_APP_FAVICON_URL: ${FLUXER_APP_FAVICON_URL:-}
|
||||
FLUXER_APP_THEME_COLOR: ${FLUXER_APP_THEME_COLOR:-}
|
||||
FLUXER_APP_STATUS_PAGE_URL: ${FLUXER_APP_STATUS_PAGE_URL:-}
|
||||
FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL: ${FLUXER_APP_STATUS_PAGE_INCIDENT_HISTORY_URL:-}
|
||||
FLUXER_INSTANCE_SETUP_CONFIGURED: ${FLUXER_INSTANCE_SETUP_CONFIGURED:-}
|
||||
FLUXER_ACCOUNT_IDENTITY: ${FLUXER_ACCOUNT_IDENTITY:-}
|
||||
FLUXER_TAG_STYLE: ${FLUXER_TAG_STYLE:-}
|
||||
FLUXER_AUTO_JOIN_INVITE_CODE: ${FLUXER_AUTO_JOIN_INVITE_CODE:-}
|
||||
FLUXER_DISCOVERY_ENABLED: ${FLUXER_DISCOVERY_ENABLED:-}
|
||||
FLUXER_DISCOVERY_MIN_MEMBER_COUNT: ${FLUXER_DISCOVERY_MIN_MEMBER_COUNT:-}
|
||||
FLUXER_DELETION_GRACE_PERIOD_HOURS: ${FLUXER_DELETION_GRACE_PERIOD_HOURS:-}
|
||||
FLUXER_REPORT_RETENTION_DAYS: ${FLUXER_REPORT_RETENTION_DAYS:-}
|
||||
FLUXER_RESOLVED_REPORT_RETENTION_DAYS: ${FLUXER_RESOLVED_REPORT_RETENTION_DAYS:-}
|
||||
FLUXER_REPORT_RETENTION_DRY_RUN: ${FLUXER_REPORT_RETENTION_DRY_RUN:-}
|
||||
FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES: ${FLUXER_API_WORKER_LANE_CONCURRENCY_OVERRIDES:-}
|
||||
|
||||
FLUXER_AUTH_BLUESKY_ENABLED: ${FLUXER_AUTH_BLUESKY_ENABLED:-}
|
||||
FLUXER_AUTH_BLUESKY_CLIENT_NAME: ${FLUXER_AUTH_BLUESKY_CLIENT_NAME:-}
|
||||
FLUXER_AUTH_BLUESKY_CLIENT_URI: ${FLUXER_AUTH_BLUESKY_CLIENT_URI:-}
|
||||
FLUXER_AUTH_BLUESKY_LOGO_URI: ${FLUXER_AUTH_BLUESKY_LOGO_URI:-}
|
||||
FLUXER_AUTH_BLUESKY_TOS_URI: ${FLUXER_AUTH_BLUESKY_TOS_URI:-}
|
||||
FLUXER_AUTH_BLUESKY_POLICY_URI: ${FLUXER_AUTH_BLUESKY_POLICY_URI:-}
|
||||
FLUXER_AUTH_BLUESKY_KEYS: ${FLUXER_AUTH_BLUESKY_KEYS:-}
|
||||
|
||||
FLUXER_PUSH_APNS_ENABLED: ${FLUXER_PUSH_APNS_ENABLED:-}
|
||||
FLUXER_PUSH_APNS_TEAM_ID: ${FLUXER_PUSH_APNS_TEAM_ID:-}
|
||||
FLUXER_PUSH_APNS_KEY_ID: ${FLUXER_PUSH_APNS_KEY_ID:-}
|
||||
FLUXER_PUSH_APNS_PRIVATE_KEY: ${FLUXER_PUSH_APNS_PRIVATE_KEY:-}
|
||||
FLUXER_PUSH_APNS_PRIVATE_KEY_PATH: ${FLUXER_PUSH_APNS_PRIVATE_KEY_PATH:-}
|
||||
FLUXER_PUSH_APNS_APPS: ${FLUXER_PUSH_APNS_APPS:-}
|
||||
|
||||
FLUXER_SUDO_MODE_SECRET: ${FLUXER_SUDO_MODE_SECRET:?set FLUXER_SUDO_MODE_SECRET in .env}
|
||||
FLUXER_CONNECTION_INITIATION_SECRET: ${FLUXER_CONNECTION_INITIATION_SECRET:?set FLUXER_CONNECTION_INITIATION_SECRET in .env}
|
||||
FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES: ${FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES:-false}
|
||||
FLUXER_PROFILE_PSEUDONYM_SECRET: ${FLUXER_PROFILE_PSEUDONYM_SECRET:?set FLUXER_PROFILE_PSEUDONYM_SECRET in .env}
|
||||
FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES: ${FLUXER_SSO_ALLOW_PRIVATE_ADDRESSES:-}
|
||||
FLUXER_VAPID_PUBLIC_KEY: ${FLUXER_VAPID_PUBLIC_KEY:?set FLUXER_VAPID_PUBLIC_KEY in .env}
|
||||
FLUXER_VAPID_PRIVATE_KEY: ${FLUXER_VAPID_PRIVATE_KEY:?set FLUXER_VAPID_PRIVATE_KEY in .env}
|
||||
FLUXER_VAPID_EMAIL: ${FLUXER_VAPID_EMAIL:-admin@${FLUXER_DOMAIN}}
|
||||
FLUXER_PASSKEY_RP_ID: ${FLUXER_PASSKEY_RP_ID:-${FLUXER_DOMAIN}}
|
||||
FLUXER_PASSKEY_RP_NAME: ${FLUXER_PASSKEY_RP_NAME:-Fluxer}
|
||||
FLUXER_PASSKEY_RP_NAME: ${FLUXER_PASSKEY_RP_NAME:-}
|
||||
FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS: ${FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}
|
||||
FLUXER_GATEWAY_RPC_AUTH_TOKEN: ${FLUXER_GATEWAY_RPC_AUTH_TOKEN:?set FLUXER_GATEWAY_RPC_AUTH_TOKEN in .env}
|
||||
FLUXER_MEDIA_PROXY_SECRET_KEY: ${FLUXER_MEDIA_PROXY_SECRET_KEY:?set FLUXER_MEDIA_PROXY_SECRET_KEY in .env}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64:?set FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64 in .env}
|
||||
FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64: ${FLUXER_MEDIA_PROXY_ATTACHMENT_URL_SECRETS_BASE64:-}
|
||||
FLUXER_ADMIN_SECRET_KEY_BASE: ${FLUXER_ADMIN_SECRET_KEY_BASE:?set FLUXER_ADMIN_SECRET_KEY_BASE in .env}
|
||||
FLUXER_ADMIN_OAUTH_CLIENT_SECRET: ${FLUXER_ADMIN_OAUTH_CLIENT_SECRET:?set FLUXER_ADMIN_OAUTH_CLIENT_SECRET in .env}
|
||||
|
||||
FLUXER_INTERNAL_API_ENDPOINT: http://api:8080
|
||||
FLUXER_INTERNAL_GATEWAY_ENDPOINT: http://gateway:8080
|
||||
FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT: http://media-proxy:8080
|
||||
FLUXER_MARKETING_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
FLUXER_MEDIA_PROXY_ENDPOINT: http://media-proxy:8080
|
||||
FLUXER_MEDIA_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_MAX_BODY_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_TOKEN_TTL_SECS:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES:-}
|
||||
|
||||
x-fluxer-service: &fluxer-service
|
||||
restart: unless-stopped
|
||||
restart: ${FLUXER_RESTART_POLICY:-unless-stopped}
|
||||
networks: [fluxer]
|
||||
|
||||
x-fluxer-svc-healthcheck: &fluxer-svc-healthcheck
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8090 && printf 'GET /_health HTTP/1.0\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 60s
|
||||
x-fluxer-app-healthcheck: &fluxer-app-healthcheck
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_APP_HEALTHCHECK_RETRIES:-30}
|
||||
start_period: ${FLUXER_APP_HEALTHCHECK_START_PERIOD:-90s}
|
||||
start_interval: 1s
|
||||
|
||||
x-fluxer-svc-healthcheck: &fluxer-svc-healthcheck
|
||||
<<: *fluxer-app-healthcheck
|
||||
start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8090 && printf 'GET /_health HTTP/1.0\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '"]
|
||||
|
||||
services:
|
||||
edge:
|
||||
image: caddy:2.10-alpine
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_CADDY_IMAGE:-caddy:2.11-alpine}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_CADDY_MEMORY_LIMIT:-256mb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
ports:
|
||||
- "${FLUXER_HTTP_PORT:-80}:80"
|
||||
- "${FLUXER_HTTPS_PORT:-443}:443"
|
||||
@@ -136,15 +233,18 @@ services:
|
||||
environment:
|
||||
FLUXER_EDGE_SITE_ADDRESS: ${FLUXER_EDGE_SITE_ADDRESS:-${FLUXER_CADDY_SITE_ADDRESS:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN:?set FLUXER_DOMAIN in .env}}}
|
||||
FLUXER_EDGE_TRUSTED_PROXIES: ${FLUXER_EDGE_TRUSTED_PROXIES:-private_ranges}
|
||||
FLUXER_EDGE_ENCODE: ${FLUXER_EDGE_ENCODE:-zstd gzip}
|
||||
FLUXER_EDGE_GLOBAL_OPTIONS: ${FLUXER_EDGE_GLOBAL_OPTIONS:-}
|
||||
FLUXER_ADMIN_BASE_PATH: ${FLUXER_ADMIN_BASE_PATH:-/admin}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- edge-data:/data
|
||||
- edge-config:/config
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:2019/config/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
depends_on:
|
||||
api: {condition: service_started}
|
||||
gateway: {condition: service_healthy}
|
||||
@@ -153,15 +253,14 @@ services:
|
||||
admin: {condition: service_started}
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_POSTGRES_IMAGE:-postgres:16-alpine}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_POSTGRES_MEMORY_LIMIT:-5gb}
|
||||
reservations:
|
||||
memory: ${FLUXER_POSTGRES_MEMORY_RESERVATION:-3gb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
command: >
|
||||
postgres
|
||||
-c max_connections=${FLUXER_POSTGRES_SERVER_MAX_CONNECTIONS:-150}
|
||||
@@ -170,114 +269,113 @@ services:
|
||||
-c work_mem=${FLUXER_POSTGRES_WORK_MEM:-8MB}
|
||||
-c maintenance_work_mem=${FLUXER_POSTGRES_MAINTENANCE_WORK_MEM:-256MB}
|
||||
-c autovacuum_work_mem=${FLUXER_POSTGRES_AUTOVACUUM_WORK_MEM:-128MB}
|
||||
-c random_page_cost=1.1
|
||||
-c effective_io_concurrency=200
|
||||
-c default_statistics_target=200
|
||||
-c jit=off
|
||||
-c min_wal_size=512MB
|
||||
-c max_wal_size=2GB
|
||||
-c checkpoint_completion_target=0.9
|
||||
-c wal_buffers=16MB
|
||||
-c wal_compression=zstd
|
||||
-c bgwriter_delay=50ms
|
||||
-c bgwriter_lru_maxpages=1000
|
||||
-c autovacuum_vacuum_scale_factor=0.05
|
||||
-c autovacuum_analyze_scale_factor=0.02
|
||||
-c autovacuum_vacuum_cost_limit=2000
|
||||
-c track_io_timing=on
|
||||
-c shared_preload_libraries=pg_stat_statements
|
||||
shm_size: 256mb
|
||||
-c random_page_cost=${FLUXER_POSTGRES_RANDOM_PAGE_COST:-1.1}
|
||||
-c effective_io_concurrency=${FLUXER_POSTGRES_EFFECTIVE_IO_CONCURRENCY:-200}
|
||||
-c default_statistics_target=${FLUXER_POSTGRES_DEFAULT_STATISTICS_TARGET:-200}
|
||||
-c jit=${FLUXER_POSTGRES_JIT:-off}
|
||||
-c min_wal_size=${FLUXER_POSTGRES_MIN_WAL_SIZE:-512MB}
|
||||
-c max_wal_size=${FLUXER_POSTGRES_MAX_WAL_SIZE:-2GB}
|
||||
-c checkpoint_completion_target=${FLUXER_POSTGRES_CHECKPOINT_COMPLETION_TARGET:-0.9}
|
||||
-c wal_buffers=${FLUXER_POSTGRES_WAL_BUFFERS:-16MB}
|
||||
-c wal_compression=${FLUXER_POSTGRES_WAL_COMPRESSION:-zstd}
|
||||
-c bgwriter_delay=${FLUXER_POSTGRES_BGWRITER_DELAY:-50ms}
|
||||
-c bgwriter_lru_maxpages=${FLUXER_POSTGRES_BGWRITER_LRU_MAXPAGES:-1000}
|
||||
-c autovacuum_vacuum_scale_factor=${FLUXER_POSTGRES_AUTOVACUUM_VACUUM_SCALE_FACTOR:-0.05}
|
||||
-c autovacuum_analyze_scale_factor=${FLUXER_POSTGRES_AUTOVACUUM_ANALYZE_SCALE_FACTOR:-0.02}
|
||||
-c autovacuum_vacuum_cost_limit=${FLUXER_POSTGRES_AUTOVACUUM_VACUUM_COST_LIMIT:-2000}
|
||||
-c track_io_timing=${FLUXER_POSTGRES_TRACK_IO_TIMING:-on}
|
||||
-c shared_preload_libraries=${FLUXER_POSTGRES_SHARED_PRELOAD_LIBRARIES:-pg_stat_statements}
|
||||
shm_size: ${FLUXER_POSTGRES_SHM_SIZE:-1gb}
|
||||
environment:
|
||||
POSTGRES_DB: fluxer
|
||||
POSTGRES_USER: fluxer
|
||||
POSTGRES_DB: ${FLUXER_POSTGRES_DATABASE:-fluxer}
|
||||
POSTGRES_USER: ${FLUXER_POSTGRES_USERNAME:-fluxer}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U fluxer -d fluxer"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
valkey:
|
||||
image: valkey/valkey:8.1-alpine
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_VALKEY_IMAGE:-valkey/valkey:9.1-alpine}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_VALKEY_MEMORY_LIMIT:-256mb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
command: ["valkey-server", "--appendonly", "yes", "--appendfsync", "everysec", "--dir", "/data",
|
||||
command: ["valkey-server", "--appendonly", "yes", "--appendfsync", "${FLUXER_VALKEY_APPENDFSYNC:-everysec}", "--dir", "/data",
|
||||
"--maxmemory", "${FLUXER_VALKEY_MAXMEMORY:-192mb}",
|
||||
"--maxmemory-policy", "${FLUXER_VALKEY_MAXMEMORY_POLICY:-noeviction}"]
|
||||
volumes:
|
||||
- valkey-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "valkey-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
nats:
|
||||
image: nats:2.14-alpine
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_NATS_IMAGE:-nats:2.14-alpine}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_NATS_MEMORY_LIMIT:-256mb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
command: ["-js", "-sd", "/data", "-m", "8222"]
|
||||
volumes:
|
||||
- nats-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8222/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:v1.12
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_MEILISEARCH_IMAGE:-getmeili/meilisearch:v1.53}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_MEILISEARCH_MEMORY_LIMIT:-768mb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
memory: ${FLUXER_MEILISEARCH_MEMORY_LIMIT:-1536mb}
|
||||
environment:
|
||||
MEILI_ENV: production
|
||||
MEILI_NO_ANALYTICS: "true"
|
||||
MEILI_MAX_INDEXING_MEMORY: ${FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY:-384mb}
|
||||
MEILI_ENV: ${FLUXER_MEILISEARCH_ENV:-production}
|
||||
MEILI_NO_ANALYTICS: "${FLUXER_MEILISEARCH_NO_ANALYTICS:-true}"
|
||||
MEILI_UPGRADE_DB: "true"
|
||||
MEILI_MAX_INDEXING_MEMORY: ${FLUXER_MEILISEARCH_MAX_INDEXING_MEMORY:-256mb}
|
||||
MEILI_MAX_INDEXING_THREADS: ${FLUXER_MEILISEARCH_MAX_INDEXING_THREADS:-2}
|
||||
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?set MEILI_MASTER_KEY in .env}
|
||||
volumes:
|
||||
- meilisearch-data:/meili_data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:7700/health"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
seaweedfs:
|
||||
image: chrislusf/seaweedfs:4.34
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_SEAWEEDFS_IMAGE:-chrislusf/seaweedfs:4.47}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_SEAWEEDFS_MEMORY_LIMIT:-2gb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
environment:
|
||||
GOMEMLIMIT: ${FLUXER_SEAWEEDFS_GOMEMLIMIT:-1536MiB}
|
||||
command: ["server", "-s3", "-dir=/data"]
|
||||
WEED_MASTER_VOLUME_GROWTH_COPY_1: ${FLUXER_SEAWEEDFS_VOLUME_GROWTH:-1}
|
||||
command: ["server", "-s3", "-dir=/data", "-master.telemetry=${FLUXER_SEAWEEDFS_TELEMETRY:-false}"]
|
||||
volumes:
|
||||
- seaweedfs-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8333/healthz"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 20
|
||||
start_period: 60s
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_SEAWEEDFS_HEALTHCHECK_RETRIES:-20}
|
||||
start_period: ${FLUXER_SEAWEEDFS_HEALTHCHECK_START_PERIOD:-60s}
|
||||
|
||||
seaweedfs-init:
|
||||
image: chrislusf/seaweedfs:4.34
|
||||
image: ${FLUXER_SEAWEEDFS_IMAGE:-chrislusf/seaweedfs:4.47}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
@@ -291,16 +389,16 @@ services:
|
||||
FLUXER_S3_SECRET_KEY: ${FLUXER_S3_SECRET_KEY:?set FLUXER_S3_SECRET_KEY in .env}
|
||||
FLUXER_S3_BUCKET_CDN: ${FLUXER_S3_BUCKET_CDN:-fluxer}
|
||||
FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-fluxer-uploads}
|
||||
FLUXER_S3_BUCKET_DOWNLOADS: ${FLUXER_S3_BUCKET_DOWNLOADS:-fluxer-downloads}
|
||||
FLUXER_S3_BUCKET_REPORTS: ${FLUXER_S3_BUCKET_REPORTS:-fluxer-reports}
|
||||
FLUXER_S3_BUCKET_HARVESTS: ${FLUXER_S3_BUCKET_HARVESTS:-fluxer-harvests}
|
||||
FLUXER_SEAWEEDFS_INIT_ATTEMPTS: ${FLUXER_SEAWEEDFS_INIT_ATTEMPTS:-60}
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- >
|
||||
buckets="$$FLUXER_S3_BUCKET_CDN $$FLUXER_S3_BUCKET_UPLOADS $$FLUXER_S3_BUCKET_DOWNLOADS $$FLUXER_S3_BUCKET_REPORTS $$FLUXER_S3_BUCKET_HARVESTS";
|
||||
buckets="$$FLUXER_S3_BUCKET_CDN $$FLUXER_S3_BUCKET_UPLOADS $$FLUXER_S3_BUCKET_REPORTS $$FLUXER_S3_BUCKET_HARVESTS";
|
||||
missing="$$buckets";
|
||||
for attempt in $$(seq 1 60); do
|
||||
for attempt in $$(seq 1 $$FLUXER_SEAWEEDFS_INIT_ATTEMPTS); do
|
||||
if ! nc -z seaweedfs 9333 2>/dev/null; then
|
||||
sleep 2;
|
||||
continue;
|
||||
@@ -327,18 +425,17 @@ services:
|
||||
exit 1;
|
||||
|
||||
livekit:
|
||||
image: livekit/livekit-server:v1.12.0
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_LIVEKIT_IMAGE:-livekit/livekit-server:v1.12.0}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_LIVEKIT_MEMORY_LIMIT:-512mb}
|
||||
restart: unless-stopped
|
||||
networks: [fluxer]
|
||||
environment:
|
||||
LIVEKIT_KEYS: "${LIVEKIT_API_KEY:?set LIVEKIT_API_KEY in .env}: ${LIVEKIT_API_SECRET:?set LIVEKIT_API_SECRET in .env}"
|
||||
LIVEKIT_CONFIG: |
|
||||
port: 7880
|
||||
log_level: info
|
||||
log_level: ${FLUXER_LIVEKIT_LOG_LEVEL:-info}
|
||||
rtc:
|
||||
tcp_port: ${FLUXER_LIVEKIT_TCP_PORT:-7881}
|
||||
udp_port: ${FLUXER_LIVEKIT_UDP_PORT:-7882}
|
||||
@@ -356,9 +453,9 @@ services:
|
||||
- "${FLUXER_LIVEKIT_UDP_PORT:-7882}:${FLUXER_LIVEKIT_UDP_PORT:-7882}/udp"
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:7880/"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
api:
|
||||
<<: *fluxer-service
|
||||
@@ -372,16 +469,14 @@ services:
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_API_PORT: "8080"
|
||||
NODE_OPTIONS: --enable-source-maps${FLUXER_API_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_API_NODE_HEAP_MB}
|
||||
FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED: "true"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "25"
|
||||
FLUXER_SETUP_TOKEN: ${FLUXER_SETUP_TOKEN:-}
|
||||
NODE_OPTIONS: --enable-source-maps${FLUXER_API_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_API_NODE_HEAP_MB}${FLUXER_API_NODE_OPTIONS:+ $FLUXER_API_NODE_OPTIONS}
|
||||
NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_API_POSTGRES_MAX_CONNECTIONS:-25}"
|
||||
FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED: "${FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED:-true}"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:8080/_health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 90s
|
||||
start_interval: 1s
|
||||
<<: *fluxer-app-healthcheck
|
||||
depends_on:
|
||||
postgres: {condition: service_healthy}
|
||||
valkey: {condition: service_healthy}
|
||||
@@ -406,22 +501,18 @@ services:
|
||||
memory: ${FLUXER_WORKER_MEMORY_LIMIT:-2560mb}
|
||||
reservations:
|
||||
memory: ${FLUXER_WORKER_MEMORY_RESERVATION:-1gb}
|
||||
working_dir: /usr/src/app/fluxer_api
|
||||
command: ["sh", "-c", "if [ -f dist/WorkerEntrypoint.js ]; then exec node dist/WorkerEntrypoint.js; else exec ./node_modules/.bin/tsx src/WorkerEntrypoint.ts; fi"]
|
||||
command: ["node", "dist/WorkerEntrypoint.js"]
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_WORKER_NODE_HEAP_MB}
|
||||
NODE_OPTIONS: --enable-source-maps${FLUXER_WORKER_NODE_HEAP_MB:+ --max-old-space-size=$FLUXER_WORKER_NODE_HEAP_MB}${FLUXER_WORKER_NODE_OPTIONS:+ $FLUXER_WORKER_NODE_OPTIONS}
|
||||
NODE_EXTRA_CA_CERTS: ${FLUXER_NODE_EXTRA_CA_CERTS:-/etc/ssl/certs/ca-certificates.crt}
|
||||
FLUXER_API_WORKER_MODE: all_lanes
|
||||
FLUXER_API_WORKER_ENABLE_CRON_SCHEDULER: "true"
|
||||
FLUXER_API_WORKER_ENABLE_VOICE_RECONCILIATION: "true"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "25"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_WORKER_POSTGRES_MAX_CONNECTIONS:-25}"
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "const age=Date.now()-require('node:fs').statSync('/tmp/fluxer-worker-heartbeat').mtimeMs;if(age>30000){console.error('worker heartbeat is '+Math.round(age)+'ms old');process.exit(1)}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 90s
|
||||
start_interval: 1s
|
||||
<<: *fluxer-app-healthcheck
|
||||
retries: ${FLUXER_WORKER_HEALTHCHECK_RETRIES:-3}
|
||||
depends_on:
|
||||
postgres: {condition: service_healthy}
|
||||
valkey: {condition: service_healthy}
|
||||
@@ -443,18 +534,30 @@ services:
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_GATEWAY_PORT: "8080"
|
||||
FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_GATEWAY_STATIC_CDN_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
FLUXER_GATEWAY_LOGGER_LEVEL: info
|
||||
FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT: ${FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT:-${FLUXER_MEDIA_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media}}
|
||||
FLUXER_GATEWAY_STATIC_CDN_ENDPOINT: ${FLUXER_GATEWAY_STATIC_CDN_ENDPOINT:-${FLUXER_STATIC_CDN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}}
|
||||
FLUXER_GATEWAY_LOGGER_LEVEL: ${FLUXER_GATEWAY_LOGGER_LEVEL:-}
|
||||
LOGGER_LEVEL: ${LOGGER_LEVEL:-}
|
||||
FLUXER_GATEWAY_PUSH_ENABLED: ${FLUXER_GATEWAY_PUSH_ENABLED:-}
|
||||
FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED: ${FLUXER_GATEWAY_PUSH_ENROLLED_CLEAR_NOTIFICATIONS_ENABLED:-}
|
||||
FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS: ${FLUXER_GATEWAY_PUSH_OUTBOX_REQUEST_TIMEOUT_MS:-}
|
||||
FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES: ${FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES:-}
|
||||
FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES: ${FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES:-}
|
||||
FLUXER_GATEWAY_HTTP_RPC_MAX_CONCURRENCY: ${FLUXER_GATEWAY_HTTP_RPC_MAX_CONCURRENCY:-}
|
||||
FLUXER_GATEWAY_NATS_RPC_MAX_HANDLERS: ${FLUXER_GATEWAY_NATS_RPC_MAX_HANDLERS:-}
|
||||
FLUXER_GATEWAY_HTTP_FAILURE_THRESHOLD: ${FLUXER_GATEWAY_HTTP_FAILURE_THRESHOLD:-}
|
||||
FLUXER_GATEWAY_HTTP_RECOVERY_TIMEOUT_MS: ${FLUXER_GATEWAY_HTTP_RECOVERY_TIMEOUT_MS:-}
|
||||
FLUXER_ERLANG_COOKIE: ${FLUXER_ERLANG_COOKIE:?set FLUXER_ERLANG_COOKIE in .env}
|
||||
FLUXER_ERLANG_SCHEDULERS_MIN: "${FLUXER_ERLANG_SCHEDULERS_MIN:-2}"
|
||||
FLUXER_ERLANG_SCHEDULERS_MAX: "${FLUXER_ERLANG_SCHEDULERS_MAX:-16}"
|
||||
FLUXER_ERLANG_SCHEDULERS: ${FLUXER_ERLANG_SCHEDULERS:-}
|
||||
FLUXER_ERLANG_SCHEDULERS_MIN: ${FLUXER_ERLANG_SCHEDULERS_MIN:-}
|
||||
FLUXER_ERLANG_SCHEDULERS_MAX: ${FLUXER_ERLANG_SCHEDULERS_MAX:-}
|
||||
FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS: ${FLUXER_ERLANG_DIRTY_CPU_SCHEDULERS:-}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1:8080/_health/ready"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 90s
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_APP_HEALTHCHECK_RETRIES:-30}
|
||||
start_period: ${FLUXER_APP_HEALTHCHECK_START_PERIOD:-90s}
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
valkey: {condition: service_healthy}
|
||||
@@ -468,16 +571,74 @@ services:
|
||||
memory: ${FLUXER_MEDIA_PROXY_MEMORY_LIMIT:-512mb}
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_MEDIA_PROXY_HOST: 0.0.0.0
|
||||
FLUXER_MEDIA_PROXY_PORT: "8080"
|
||||
FLUXER_MEDIA_PROXY_MODE: upload
|
||||
FLUXER_MEDIA_PROXY_STORAGE_BACKEND: s3
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_S3_READ_SIGNED: "true"
|
||||
FLUXER_MEDIA_PROXY_CORS_MODE: ${FLUXER_MEDIA_PROXY_CORS_MODE:-}
|
||||
FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS: ${FLUXER_MEDIA_PROXY_CORS_ALLOWED_ORIGINS:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}:${FLUXER_PUBLIC_PORT:-443}}}
|
||||
FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE: ${FLUXER_MEDIA_PROXY_ATTACHMENT_SIGNATURE_MODE:-}
|
||||
FLUXER_MEDIA_PROXY_READ_ONLY: ${FLUXER_MEDIA_PROXY_READ_ONLY:-}
|
||||
FLUXER_MEDIA_PROXY_NSFW_THRESHOLD: ${FLUXER_MEDIA_PROXY_NSFW_THRESHOLD:-}
|
||||
FLUXER_NSFW_SERVICE_ENDPOINT: ${FLUXER_NSFW_SERVICE_ENDPOINT:-}
|
||||
FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS: ${FLUXER_MEDIA_PROXY_MAX_NATIVE_TRANSFORMS:-}
|
||||
FLUXER_MEDIA_PROXY_WORKER_QUEUE_CAPACITY: ${FLUXER_MEDIA_PROXY_WORKER_QUEUE_CAPACITY:-}
|
||||
FLUXER_MEDIA_PROXY_MAX_ENCODE_DURATION_MS: ${FLUXER_MEDIA_PROXY_MAX_ENCODE_DURATION_MS:-}
|
||||
FLUXER_MEDIA_PROXY_MAX_ENCODE_FRAMES: ${FLUXER_MEDIA_PROXY_MAX_ENCODE_FRAMES:-}
|
||||
FLUXER_MEDIA_PROXY_TRANSFORM_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_TRANSFORM_TIMEOUT_MS:-}
|
||||
FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_BYTES: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_MAX_ENTRY_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_TTL_MS: ${FLUXER_MEDIA_PROXY_TRANSFORM_CACHE_TTL_MS:-}
|
||||
FLUXER_MEDIA_PROXY_SOCKET_IO_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_SOCKET_IO_TIMEOUT_MS:-}
|
||||
FLUXER_MEDIA_PROXY_SHUTDOWN_GRACE_MS: ${FLUXER_MEDIA_PROXY_SHUTDOWN_GRACE_MS:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_S3_TIMEOUT_MS:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_BUFFERED_RETRY_TOTAL_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_DIR: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_DIR:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_CHUNK_BYTES:-}
|
||||
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES: ${FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SPOOL_MAX_TOTAL_BYTES:-}
|
||||
FLUXER_S3_SESSION_TOKEN: ${FLUXER_S3_SESSION_TOKEN:-}
|
||||
FLUXER_S3_READ_ENDPOINT: ${FLUXER_S3_READ_ENDPOINT:-}
|
||||
FLUXER_S3_READ_BUCKET: ${FLUXER_S3_READ_BUCKET:-}
|
||||
FLUXER_S3_READ_BUCKET_STYLE: ${FLUXER_S3_READ_BUCKET_STYLE:-}
|
||||
FLUXER_S3_READ_SIGNED: "${FLUXER_S3_READ_SIGNED:-true}"
|
||||
depends_on:
|
||||
seaweedfs-init: {condition: service_completed_successfully}
|
||||
nats: {condition: service_healthy}
|
||||
|
||||
push:
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-push:${FLUXER_IMAGE_TAG:-v1}
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: ${FLUXER_PUSH_MEMORY_LIMIT:-256mb}
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_PUSH_SERVICE_QUEUE_CAPACITY: ${FLUXER_PUSH_SERVICE_QUEUE_CAPACITY:-}
|
||||
FLUXER_PUSH_SERVICE_SEND_CONCURRENCY: ${FLUXER_PUSH_SERVICE_SEND_CONCURRENCY:-}
|
||||
FLUXER_PUSH_SERVICE_APNS_BASE_URL: ${FLUXER_PUSH_SERVICE_APNS_BASE_URL:-}
|
||||
FLUXER_PUSH_SERVICE_FCM_BASE_URL: ${FLUXER_PUSH_SERVICE_FCM_BASE_URL:-}
|
||||
FLUXER_PUSH_SERVICE_MANAGED_RELAY_HOSTS: ${FLUXER_PUSH_SERVICE_MANAGED_RELAY_HOSTS:-}
|
||||
FLUXER_PUSH_SERVICE_OWN_RELAY_HOSTS: ${FLUXER_PUSH_SERVICE_OWN_RELAY_HOSTS:-}
|
||||
FLUXER_PUSH_SERVICE_PRIVATE_HOSTS: ${FLUXER_PUSH_SERVICE_PRIVATE_HOSTS:-}
|
||||
FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED: ${FLUXER_PUSH_SERVICE_RELAY_CONSENT_ACCEPTED:-}
|
||||
FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT: ${FLUXER_PUSH_APNS_DEFAULT_ENVIRONMENT:-}
|
||||
FLUXER_PUSH_FCM_ENABLED: ${FLUXER_PUSH_FCM_ENABLED:-}
|
||||
FLUXER_PUSH_FCM_PROJECT_ID: ${FLUXER_PUSH_FCM_PROJECT_ID:-}
|
||||
FLUXER_PUSH_FCM_CLIENT_EMAIL: ${FLUXER_PUSH_FCM_CLIENT_EMAIL:-}
|
||||
FLUXER_PUSH_FCM_PRIVATE_KEY: ${FLUXER_PUSH_FCM_PRIVATE_KEY:-}
|
||||
FLUXER_PUSH_FCM_PRIVATE_KEY_PATH: ${FLUXER_PUSH_FCM_PRIVATE_KEY_PATH:-}
|
||||
FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH: ${FLUXER_PUSH_FCM_SERVICE_ACCOUNT_JSON_PATH:-}
|
||||
FLUXER_PUSH_FCM_TOKEN_URI: ${FLUXER_PUSH_FCM_TOKEN_URI:-}
|
||||
FLUXER_PUSH_FCM_APPS: ${FLUXER_PUSH_FCM_APPS:-}
|
||||
healthcheck:
|
||||
test: ["CMD", "/usr/local/bin/fluxer-push", "healthcheck"]
|
||||
<<: *fluxer-app-healthcheck
|
||||
start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
api: {condition: service_healthy}
|
||||
|
||||
static-proxy:
|
||||
<<: *fluxer-service
|
||||
image: ${FLUXER_REGISTRY:-ghcr.io/${FLUXER_REGISTRY_OWNER:-fluxerapp}}/fluxer-static:${FLUXER_IMAGE_TAG:-v1}
|
||||
@@ -487,9 +648,9 @@ services:
|
||||
memory: ${FLUXER_STATIC_PROXY_MEMORY_LIMIT:-256mb}
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/avatars/0.png"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
interval: ${FLUXER_HEALTHCHECK_INTERVAL:-10s}
|
||||
timeout: ${FLUXER_HEALTHCHECK_TIMEOUT:-5s}
|
||||
retries: ${FLUXER_HEALTHCHECK_RETRIES:-10}
|
||||
|
||||
app-proxy:
|
||||
<<: *fluxer-service
|
||||
@@ -499,15 +660,24 @@ services:
|
||||
limits:
|
||||
memory: ${FLUXER_APP_PROXY_MEMORY_LIMIT:-256mb}
|
||||
environment:
|
||||
FLUXER_APP_PROXY_HOST: 0.0.0.0
|
||||
RUST_LOG: ${RUST_LOG:-}
|
||||
FLUXER_APP_PROXY_PORT: "8080"
|
||||
FLUXER_BASE_DOMAIN: ${FLUXER_DOMAIN:?set FLUXER_DOMAIN in .env}
|
||||
FLUXER_PUBLIC_SCHEME: ${FLUXER_PUBLIC_SCHEME:-https}
|
||||
FLUXER_PUBLIC_PORT: ${FLUXER_PUBLIC_PORT:-443}
|
||||
FLUXER_PUBLIC_ORIGIN: ${FLUXER_PUBLIC_ORIGIN:-}
|
||||
FLUXER_S3_PUBLIC_ENDPOINT: ${FLUXER_S3_PUBLIC_ENDPOINT:-}
|
||||
FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-}
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-}
|
||||
FLUXER_MEDIA_ENDPOINT: ${FLUXER_MEDIA_ENDPOINT:-}
|
||||
FLUXER_APP_PRODUCT_NAME: ${FLUXER_APP_PRODUCT_NAME:-}
|
||||
FLUXER_APP_ICON_URL: ${FLUXER_APP_ICON_URL:-}
|
||||
FLUXER_APP_SYMBOL_URL: ${FLUXER_APP_SYMBOL_URL:-}
|
||||
FLUXER_APP_LOGO_URL: ${FLUXER_APP_LOGO_URL:-}
|
||||
FLUXER_APP_WORDMARK_URL: ${FLUXER_APP_WORDMARK_URL:-}
|
||||
FLUXER_APP_FAVICON_URL: ${FLUXER_APP_FAVICON_URL:-}
|
||||
FLUXER_APP_THEME_COLOR: ${FLUXER_APP_THEME_COLOR:-}
|
||||
DISCOVERY_UPSTREAM_URL: http://edge:8088/.well-known/fluxer
|
||||
PUBLIC_BOOTSTRAP_API_ENDPOINT: /api
|
||||
PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/api
|
||||
DISCOVERY_REFRESH_INTERVAL_MS: ${DISCOVERY_REFRESH_INTERVAL_MS:-}
|
||||
FLUXER_APP_PROXY_INDEX_UPSTREAM_URL: ${FLUXER_APP_PROXY_INDEX_UPSTREAM_URL:-}
|
||||
FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS: ${FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS:-}
|
||||
FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS: ${FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS:-}
|
||||
FLUXER_CSP_EXTRA_DEFAULT_SRC: ${FLUXER_CSP_EXTRA_DEFAULT_SRC:-}
|
||||
FLUXER_CSP_EXTRA_CONNECT_SRC: ${FLUXER_CSP_EXTRA_CONNECT_SRC:-}
|
||||
FLUXER_CSP_EXTRA_IMG_SRC: ${FLUXER_CSP_EXTRA_IMG_SRC:-}
|
||||
@@ -565,7 +735,6 @@ services:
|
||||
<<: *fluxer-env
|
||||
FLUXER_SVC_NAME: users
|
||||
FLUXER_SVC_MODE: router
|
||||
FLUXER_SVC_MAX_CONCURRENT_REQUESTS: "${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}"
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -582,8 +751,7 @@ services:
|
||||
FLUXER_SVC_NAME: users
|
||||
FLUXER_SVC_MODE: shard
|
||||
FLUXER_SVC_SHARD_ID: "0"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "20"
|
||||
FLUXER_SVC_MAX_CONCURRENT_REQUESTS: "${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_USERS_SHARD_POSTGRES_MAX_CONNECTIONS:-20}"
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -600,7 +768,6 @@ services:
|
||||
<<: *fluxer-env
|
||||
FLUXER_SVC_NAME: gifs
|
||||
FLUXER_SVC_MODE: router
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -617,7 +784,7 @@ services:
|
||||
FLUXER_SVC_NAME: gifs
|
||||
FLUXER_SVC_MODE: shard
|
||||
FLUXER_SVC_SHARD_ID: "0"
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_GIFS_SHARD_CACHE_MAX_BYTES: ${FLUXER_GIFS_SHARD_CACHE_MAX_BYTES:-}
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -633,7 +800,6 @@ services:
|
||||
<<: *fluxer-env
|
||||
FLUXER_SVC_NAME: messages
|
||||
FLUXER_SVC_MODE: router
|
||||
FLUXER_SVC_MAX_CONCURRENT_REQUESTS: "${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}"
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -650,8 +816,7 @@ services:
|
||||
FLUXER_SVC_NAME: messages
|
||||
FLUXER_SVC_MODE: shard
|
||||
FLUXER_SVC_SHARD_ID: "0"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "20"
|
||||
FLUXER_SVC_MAX_CONCURRENT_REQUESTS: "${FLUXER_SVC_MAX_CONCURRENT_REQUESTS:-}"
|
||||
FLUXER_POSTGRES_MAX_CONNECTIONS: "${FLUXER_MESSAGES_SHARD_POSTGRES_MAX_CONNECTIONS:-20}"
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -668,8 +833,6 @@ services:
|
||||
<<: *fluxer-env
|
||||
FLUXER_SVC_NAME: unfurl
|
||||
FLUXER_SVC_MODE: router
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -686,8 +849,7 @@ services:
|
||||
FLUXER_SVC_NAME: unfurl
|
||||
FLUXER_SVC_MODE: shard
|
||||
FLUXER_SVC_SHARD_ID: "0"
|
||||
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
FLUXER_MEDIA_PROXY_ENDPOINT: http://media-proxy:8080
|
||||
healthcheck: *fluxer-svc-healthcheck
|
||||
depends_on:
|
||||
nats: {condition: service_healthy}
|
||||
@@ -701,22 +863,14 @@ services:
|
||||
memory: ${FLUXER_ADMIN_MEMORY_LIMIT:-256mb}
|
||||
environment:
|
||||
<<: *fluxer-env
|
||||
FLUXER_ADMIN_HOST: 0.0.0.0
|
||||
FLUXER_ADMIN_PORT: "8080"
|
||||
FLUXER_ADMIN_BASE_PATH: /admin
|
||||
FLUXER_ADMIN_BASE_PATH: ${FLUXER_ADMIN_BASE_PATH:-/admin}
|
||||
FLUXER_API_ENDPOINT: http://api:8080
|
||||
FLUXER_ADMIN_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/admin
|
||||
FLUXER_APP_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
FLUXER_MEDIA_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/media
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}
|
||||
FLUXER_ADMIN_OAUTH_REDIRECT_URI: ${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}/admin/oauth2_callback
|
||||
FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-${FLUXER_PUBLIC_ORIGIN:-${FLUXER_PUBLIC_SCHEME:-https}://${FLUXER_DOMAIN}}}
|
||||
healthcheck:
|
||||
test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /_health HTTP/1.0\\r\\n\\r\\n' >&3 && head -n 1 <&3 | grep -q ' 200 '"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
start_period: 60s
|
||||
start_interval: 1s
|
||||
<<: *fluxer-app-healthcheck
|
||||
start_period: ${FLUXER_SVC_HEALTHCHECK_START_PERIOD:-60s}
|
||||
depends_on:
|
||||
api: {condition: service_healthy}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
seaweedfs:
|
||||
profiles: [bundled-object-store]
|
||||
seaweedfs-init:
|
||||
profiles: [bundled-object-store]
|
||||
api:
|
||||
depends_on:
|
||||
seaweedfs-init: !reset null
|
||||
worker:
|
||||
depends_on:
|
||||
seaweedfs-init: !reset null
|
||||
media-proxy:
|
||||
depends_on:
|
||||
seaweedfs-init: !reset null
|
||||
+13
-14
@@ -9,32 +9,31 @@ build = "build.rs"
|
||||
[dependencies]
|
||||
anyhow = "1.0.104"
|
||||
axum = { version = "0.8.9", features = ["macros"] }
|
||||
base64 = "0.22.1"
|
||||
base64 = "0.23.1"
|
||||
chrono = { version = "0.4", default-features = false, features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
fluxer_common = { path = "../fluxer_common" }
|
||||
hmac = "0.13.0"
|
||||
maud = { version = "0.27.0", features = ["axum"] }
|
||||
rand = "0.10"
|
||||
regress = "0.11"
|
||||
reqwest = { version = "0.13.4", default-features = false, features = ["json", "rustls"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.150"
|
||||
regress = "0.12"
|
||||
reqwest = { version = "0.13.5", default-features = false, features = ["json", "rustls"] }
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
sha2 = "0.11.0"
|
||||
time = { version = "0.3.47", features = ["formatting", "parsing"] }
|
||||
tokio = { version = "1.52.3", features = ["macros", "net", "rt-multi-thread", "signal"] }
|
||||
time = { version = "0.3.55", features = ["formatting", "parsing"] }
|
||||
tokio = { version = "1.53.1", features = ["macros", "net", "rt-multi-thread", "signal"] }
|
||||
tower = { version = "0.5.3", features = ["util"] }
|
||||
tower-http = { version = "0.6.11", features = ["compression-gzip", "trace"] }
|
||||
tower-http = { version = "0.7.1", features = ["compression-gzip", "trace"] }
|
||||
tracing = "0.1.44"
|
||||
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
|
||||
tracing-subscriber = "0.3.23"
|
||||
url = "2.5"
|
||||
urlencoding = "2.1.3"
|
||||
progenitor-client = { version = "0.14.0", default-features = false }
|
||||
progenitor-client = { version = "0.15.0", default-features = false }
|
||||
|
||||
[build-dependencies]
|
||||
openapiv3 = "2.2.0"
|
||||
prettyplease = "0.2"
|
||||
progenitor = { version = "0.14.0", default-features = false }
|
||||
prettyplease = "0.3"
|
||||
progenitor = { version = "0.15.0", default-features = false }
|
||||
serde_json = "1"
|
||||
sha2 = "0.11.0"
|
||||
syn = "2"
|
||||
syn = "3"
|
||||
@@ -1,15 +1,14 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
FROM rust:1-bookworm AS builder
|
||||
FROM rust:1-trixie AS builder
|
||||
|
||||
ARG BUILD_VERSION=""
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates nodejs npm pkg-config \
|
||||
&& npm install -g pnpm@10.29.3 \
|
||||
&& npm install -g pnpm@11.27.0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN npm install --no-audit --no-fund @tailwindcss/[email protected] [email protected]
|
||||
@@ -45,8 +44,6 @@ RUN printf '%s\n' \
|
||||
'strip = "symbols"' \
|
||||
> Cargo.toml
|
||||
|
||||
ENV FLUXER_BUILD_VERSION="${BUILD_VERSION}"
|
||||
|
||||
RUN cargo build --release -p fluxer_admin \
|
||||
&& cp target/release/fluxer_admin /usr/local/bin/fluxer-admin
|
||||
|
||||
@@ -57,7 +54,7 @@ RUN test "$(ls target/release/build/fluxer_admin-*/out/static/fonts/*.woff2 | wc
|
||||
&& ls target/release/build/fluxer_admin-*/out/static/fonts/fonts.*.css \
|
||||
&& echo "Latin-core fonts bundled successfully"
|
||||
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
FROM debian:trixie-slim AS runtime
|
||||
|
||||
ARG BUILD_VERSION=""
|
||||
ARG SOURCE_SHA=""
|
||||
|
||||
+343
-3
@@ -38,18 +38,26 @@ fn generate_admin_api(manifest_dir: &Path, out_dir: &Path) {
|
||||
let mut spec: openapiv3::OpenAPI =
|
||||
serde_json::from_str(&json_str).expect("failed to parse openapi-admin.json");
|
||||
adapt_progenitor_throttled_errors(&mut spec);
|
||||
relax_guild_audit_log_schemas(&mut spec);
|
||||
relax_progenitor_schema_strictness(&mut spec);
|
||||
relax_integer_enums(&mut spec);
|
||||
|
||||
let mut settings = progenitor::GenerationSettings::new();
|
||||
settings.with_interface(progenitor::InterfaceStyle::Positional);
|
||||
settings.with_inner_type(
|
||||
"reqwest::header::HeaderMap"
|
||||
.parse()
|
||||
.expect("valid generated client header type"),
|
||||
);
|
||||
|
||||
let mut generator = progenitor::Generator::new(&settings);
|
||||
let tokens = generator
|
||||
.generate_tokens(&spec)
|
||||
.expect("failed to generate admin API client");
|
||||
|
||||
let content = prettyplease::unparse(
|
||||
let content = relax_required_nullable_fields(&prettyplease::unparse(
|
||||
&syn::parse2::<syn::File>(tokens).expect("failed to parse generated tokens"),
|
||||
);
|
||||
));
|
||||
|
||||
let output_path = out_dir.join("admin_api_generated.rs");
|
||||
fs::write(&output_path, content).expect("failed to write generated API code");
|
||||
@@ -136,6 +144,338 @@ fn adapt_progenitor_throttled_errors(spec: &mut openapiv3::OpenAPI) {
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_guild_audit_log_schemas(spec: &mut openapiv3::OpenAPI) {
|
||||
let components = spec.components.as_mut().expect("missing API components");
|
||||
|
||||
let entry = object_schema_mut(components, "GuildAuditLogEntryResponse");
|
||||
entry.additional_properties = None;
|
||||
let openapiv3::ReferenceOr::Item(options) = entry
|
||||
.properties
|
||||
.get_mut("options")
|
||||
.expect("GuildAuditLogEntryResponse has no options property")
|
||||
else {
|
||||
panic!("GuildAuditLogEntryResponse options must be an inline schema");
|
||||
};
|
||||
let openapiv3::SchemaKind::Type(openapiv3::Type::Object(options)) = &mut options.schema_kind
|
||||
else {
|
||||
panic!("GuildAuditLogEntryResponse options must be an object schema");
|
||||
};
|
||||
options.additional_properties = None;
|
||||
|
||||
let change = object_schema_mut(components, "AuditLogChangeSchema");
|
||||
change.additional_properties = None;
|
||||
for property in ["old_value", "new_value"] {
|
||||
change.properties.insert(
|
||||
property.to_string(),
|
||||
openapiv3::ReferenceOr::Item(Box::new(openapiv3::Schema {
|
||||
schema_data: openapiv3::SchemaData::default(),
|
||||
schema_kind: openapiv3::SchemaKind::Any(openapiv3::AnySchema::default()),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const OPEN_INTEGER_ENUMS: &[&str] = &[
|
||||
"ChannelType",
|
||||
"MessageType",
|
||||
"ReportStatus",
|
||||
"ReportType",
|
||||
"WebhookType",
|
||||
];
|
||||
|
||||
fn relax_integer_enums(spec: &mut openapiv3::OpenAPI) {
|
||||
let components = spec.components.as_mut().expect("missing API components");
|
||||
for name in OPEN_INTEGER_ENUMS {
|
||||
let Some(openapiv3::ReferenceOr::Item(schema)) = components.schemas.get_mut(*name) else {
|
||||
panic!("missing inline {name} schema");
|
||||
};
|
||||
let openapiv3::SchemaKind::Type(openapiv3::Type::Integer(integer)) =
|
||||
&mut schema.schema_kind
|
||||
else {
|
||||
panic!("{name} must be an integer schema");
|
||||
};
|
||||
integer.enumeration.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn object_schema_mut<'a>(
|
||||
components: &'a mut openapiv3::Components,
|
||||
name: &str,
|
||||
) -> &'a mut openapiv3::ObjectType {
|
||||
let Some(openapiv3::ReferenceOr::Item(schema)) = components.schemas.get_mut(name) else {
|
||||
panic!("missing inline {name} schema");
|
||||
};
|
||||
let openapiv3::SchemaKind::Type(openapiv3::Type::Object(object)) = &mut schema.schema_kind
|
||||
else {
|
||||
panic!("{name} must be an object schema");
|
||||
};
|
||||
object
|
||||
}
|
||||
|
||||
const MAX_SCHEMA_REFERENCE_DEPTH: usize = 32;
|
||||
|
||||
fn relax_required_nullable_fields(generated: &str) -> String {
|
||||
const PRESENCE_CHECK: &str =
|
||||
"#[serde(deserialize_with = \"::std::option::Option::deserialize\")]";
|
||||
generated
|
||||
.lines()
|
||||
.filter(|line| line.trim() != PRESENCE_CHECK)
|
||||
.flat_map(|line| [line, "\n"])
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn relax_progenitor_schema_strictness(spec: &mut openapiv3::OpenAPI) {
|
||||
let registry = spec.components.clone().unwrap_or_default();
|
||||
|
||||
if let Some(components) = spec.components.as_mut() {
|
||||
for schema in components.schemas.values_mut() {
|
||||
relax_schema_reference(schema, ®istry);
|
||||
}
|
||||
for response in components.responses.values_mut() {
|
||||
if let openapiv3::ReferenceOr::Item(response) = response {
|
||||
relax_response(response, ®istry);
|
||||
}
|
||||
}
|
||||
for parameter in components.parameters.values_mut() {
|
||||
if let openapiv3::ReferenceOr::Item(parameter) = parameter {
|
||||
relax_parameter(parameter, ®istry);
|
||||
}
|
||||
}
|
||||
for request_body in components.request_bodies.values_mut() {
|
||||
if let openapiv3::ReferenceOr::Item(request_body) = request_body {
|
||||
relax_content(&mut request_body.content, ®istry);
|
||||
}
|
||||
}
|
||||
for header in components.headers.values_mut() {
|
||||
if let openapiv3::ReferenceOr::Item(header) = header {
|
||||
relax_parameter_format(&mut header.format, ®istry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for path in spec.paths.paths.values_mut() {
|
||||
let openapiv3::ReferenceOr::Item(path) = path else {
|
||||
continue;
|
||||
};
|
||||
for parameter in &mut path.parameters {
|
||||
if let openapiv3::ReferenceOr::Item(parameter) = parameter {
|
||||
relax_parameter(parameter, ®istry);
|
||||
}
|
||||
}
|
||||
for operation in [
|
||||
&mut path.get,
|
||||
&mut path.put,
|
||||
&mut path.post,
|
||||
&mut path.delete,
|
||||
&mut path.options,
|
||||
&mut path.head,
|
||||
&mut path.patch,
|
||||
&mut path.trace,
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
{
|
||||
for parameter in &mut operation.parameters {
|
||||
if let openapiv3::ReferenceOr::Item(parameter) = parameter {
|
||||
relax_parameter(parameter, ®istry);
|
||||
}
|
||||
}
|
||||
if let Some(openapiv3::ReferenceOr::Item(request_body)) =
|
||||
operation.request_body.as_mut()
|
||||
{
|
||||
relax_content(&mut request_body.content, ®istry);
|
||||
}
|
||||
for response in operation
|
||||
.responses
|
||||
.responses
|
||||
.values_mut()
|
||||
.chain(operation.responses.default.iter_mut())
|
||||
{
|
||||
if let openapiv3::ReferenceOr::Item(response) = response {
|
||||
relax_response(response, ®istry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_response(response: &mut openapiv3::Response, registry: &openapiv3::Components) {
|
||||
relax_content(&mut response.content, registry);
|
||||
for header in response.headers.values_mut() {
|
||||
if let openapiv3::ReferenceOr::Item(header) = header {
|
||||
relax_parameter_format(&mut header.format, registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_content(content: &mut openapiv3::Content, registry: &openapiv3::Components) {
|
||||
for media_type in content.values_mut() {
|
||||
if let Some(schema) = media_type.schema.as_mut() {
|
||||
relax_schema_reference(schema, registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_parameter(parameter: &mut openapiv3::Parameter, registry: &openapiv3::Components) {
|
||||
let format = match parameter {
|
||||
openapiv3::Parameter::Query { parameter_data, .. }
|
||||
| openapiv3::Parameter::Header { parameter_data, .. }
|
||||
| openapiv3::Parameter::Path { parameter_data, .. }
|
||||
| openapiv3::Parameter::Cookie { parameter_data, .. } => &mut parameter_data.format,
|
||||
};
|
||||
relax_parameter_format(format, registry);
|
||||
}
|
||||
|
||||
fn relax_parameter_format(
|
||||
format: &mut openapiv3::ParameterSchemaOrContent,
|
||||
registry: &openapiv3::Components,
|
||||
) {
|
||||
match format {
|
||||
openapiv3::ParameterSchemaOrContent::Schema(schema) => {
|
||||
relax_schema_reference(schema, registry)
|
||||
}
|
||||
openapiv3::ParameterSchemaOrContent::Content(content) => relax_content(content, registry),
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_schema_reference(
|
||||
schema: &mut openapiv3::ReferenceOr<openapiv3::Schema>,
|
||||
registry: &openapiv3::Components,
|
||||
) {
|
||||
if let openapiv3::ReferenceOr::Item(schema) = schema {
|
||||
relax_schema(schema, registry);
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_boxed_schema_reference(
|
||||
schema: &mut openapiv3::ReferenceOr<Box<openapiv3::Schema>>,
|
||||
registry: &openapiv3::Components,
|
||||
) {
|
||||
if let openapiv3::ReferenceOr::Item(schema) = schema {
|
||||
relax_schema(schema, registry);
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_schema(schema: &mut openapiv3::Schema, registry: &openapiv3::Components) {
|
||||
if flattens_objects_beside_scalars(&schema.schema_kind, registry) {
|
||||
schema.schema_kind = openapiv3::SchemaKind::Any(openapiv3::AnySchema::default());
|
||||
return;
|
||||
}
|
||||
match &mut schema.schema_kind {
|
||||
openapiv3::SchemaKind::Type(openapiv3::Type::Object(object)) => {
|
||||
relax_additional_properties(&mut object.additional_properties, registry);
|
||||
for property in object.properties.values_mut() {
|
||||
relax_boxed_schema_reference(property, registry);
|
||||
}
|
||||
}
|
||||
openapiv3::SchemaKind::Type(openapiv3::Type::Array(array)) => {
|
||||
if let Some(items) = array.items.as_mut() {
|
||||
relax_boxed_schema_reference(items, registry);
|
||||
}
|
||||
}
|
||||
openapiv3::SchemaKind::Type(_) => {}
|
||||
openapiv3::SchemaKind::OneOf { one_of: subschemas }
|
||||
| openapiv3::SchemaKind::AllOf { all_of: subschemas }
|
||||
| openapiv3::SchemaKind::AnyOf { any_of: subschemas } => {
|
||||
for subschema in subschemas {
|
||||
relax_schema_reference(subschema, registry);
|
||||
}
|
||||
}
|
||||
openapiv3::SchemaKind::Not { not } => relax_schema_reference(not, registry),
|
||||
openapiv3::SchemaKind::Any(any) => {
|
||||
relax_additional_properties(&mut any.additional_properties, registry);
|
||||
for property in any.properties.values_mut() {
|
||||
relax_boxed_schema_reference(property, registry);
|
||||
}
|
||||
if let Some(items) = any.items.as_mut() {
|
||||
relax_boxed_schema_reference(items, registry);
|
||||
}
|
||||
for subschema in any
|
||||
.one_of
|
||||
.iter_mut()
|
||||
.chain(any.all_of.iter_mut())
|
||||
.chain(any.any_of.iter_mut())
|
||||
{
|
||||
relax_schema_reference(subschema, registry);
|
||||
}
|
||||
if let Some(not) = any.not.as_mut() {
|
||||
relax_schema_reference(not, registry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relax_additional_properties(
|
||||
additional_properties: &mut Option<openapiv3::AdditionalProperties>,
|
||||
registry: &openapiv3::Components,
|
||||
) {
|
||||
match additional_properties {
|
||||
Some(openapiv3::AdditionalProperties::Any(false)) => *additional_properties = None,
|
||||
Some(openapiv3::AdditionalProperties::Schema(schema)) => {
|
||||
relax_schema_reference(schema, registry)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn flattens_objects_beside_scalars(
|
||||
schema_kind: &openapiv3::SchemaKind,
|
||||
registry: &openapiv3::Components,
|
||||
) -> bool {
|
||||
let subschemas = match schema_kind {
|
||||
openapiv3::SchemaKind::OneOf { one_of } => one_of,
|
||||
openapiv3::SchemaKind::AnyOf { any_of } => any_of,
|
||||
_ => return false,
|
||||
};
|
||||
let mut objects = false;
|
||||
let mut scalars = false;
|
||||
for subschema in subschemas {
|
||||
if resolves_to_object(subschema, registry, MAX_SCHEMA_REFERENCE_DEPTH) {
|
||||
objects = true;
|
||||
} else {
|
||||
scalars = true;
|
||||
}
|
||||
}
|
||||
objects && scalars
|
||||
}
|
||||
|
||||
fn resolves_to_object(
|
||||
schema: &openapiv3::ReferenceOr<openapiv3::Schema>,
|
||||
registry: &openapiv3::Components,
|
||||
depth: usize,
|
||||
) -> bool {
|
||||
let Some(depth) = depth.checked_sub(1) else {
|
||||
return false;
|
||||
};
|
||||
let schema = match schema {
|
||||
openapiv3::ReferenceOr::Reference { reference } => {
|
||||
let Some(target) = reference
|
||||
.strip_prefix("#/components/schemas/")
|
||||
.and_then(|name| registry.schemas.get(name))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
return resolves_to_object(target, registry, depth);
|
||||
}
|
||||
openapiv3::ReferenceOr::Item(schema) => schema,
|
||||
};
|
||||
match &schema.schema_kind {
|
||||
openapiv3::SchemaKind::Type(openapiv3::Type::Object(_)) => true,
|
||||
openapiv3::SchemaKind::Type(_) => false,
|
||||
openapiv3::SchemaKind::OneOf { one_of: subschemas }
|
||||
| openapiv3::SchemaKind::AllOf { all_of: subschemas }
|
||||
| openapiv3::SchemaKind::AnyOf { any_of: subschemas } => subschemas
|
||||
.iter()
|
||||
.any(|subschema| resolves_to_object(subschema, registry, depth)),
|
||||
openapiv3::SchemaKind::Not { .. } => false,
|
||||
openapiv3::SchemaKind::Any(any) => {
|
||||
any.typ.as_deref() == Some("object")
|
||||
|| !any.properties.is_empty()
|
||||
|| any.additional_properties.is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Face {
|
||||
css_family: String,
|
||||
weight: u64,
|
||||
@@ -266,7 +606,7 @@ fn select_faces(package_dir: &Path) -> Vec<Face> {
|
||||
}
|
||||
assert!(
|
||||
face["unicodeRange"].is_null(),
|
||||
"{wanted} face {} carries a unicode-range; Latin-core faces must not",
|
||||
"{wanted} face {} has a unicode-range; Latin-core faces must not",
|
||||
face["file"]
|
||||
);
|
||||
faces.push(Face {
|
||||
|
||||
Generated
+3076
-834
File diff suppressed because it is too large.
Load diff
+8
-18
@@ -17,9 +17,6 @@ pub const JOBS_CANCEL: &str = "jobs:cancel";
|
||||
pub const BAN_EMAIL_ADD: &str = "ban:email:add";
|
||||
pub const BAN_EMAIL_CHECK: &str = "ban:email:check";
|
||||
pub const BAN_EMAIL_REMOVE: &str = "ban:email:remove";
|
||||
pub const SUSPICIOUS_EMAIL_DOMAIN_ADD: &str = "suspicious_email_domain:add";
|
||||
pub const SUSPICIOUS_EMAIL_DOMAIN_CHECK: &str = "suspicious_email_domain:check";
|
||||
pub const SUSPICIOUS_EMAIL_DOMAIN_REMOVE: &str = "suspicious_email_domain:remove";
|
||||
pub const BAN_PHRASE_ADD: &str = "ban:phrase:add";
|
||||
pub const BAN_PHRASE_CHECK: &str = "ban:phrase:check";
|
||||
pub const BAN_PHRASE_REMOVE: &str = "ban:phrase:remove";
|
||||
@@ -45,9 +42,7 @@ pub const BULK_ADD_GUILD_MEMBERS: &str = "bulk:add:guild_members";
|
||||
pub const BULK_DELETE_USERS: &str = "bulk:delete:users";
|
||||
pub const BULK_DELETE_USER_MESSAGES: &str = "bulk:delete:user_messages";
|
||||
pub const BULK_UPDATE_GUILD_FEATURES: &str = "bulk:update:guild_features";
|
||||
pub const BULK_UPDATE_SUSPICIOUS_ACTIVITY: &str = "bulk:update:suspicious_activity";
|
||||
pub const BULK_UPDATE_USER_FLAGS: &str = "bulk:update:user_flags";
|
||||
pub const CSAM_SUBMIT_NCMEC: &str = "csam:submit_ncmec";
|
||||
pub const DISCOVERY_REMOVE: &str = "discovery:remove";
|
||||
pub const DISCOVERY_REVIEW: &str = "discovery:review";
|
||||
pub const GATEWAY_MEMORY_STATS: &str = "gateway:memory_stats";
|
||||
@@ -75,14 +70,16 @@ pub const MESSAGE_DELETE_ALL: &str = "message:delete_all";
|
||||
pub const MESSAGE_DELETE: &str = "message:delete";
|
||||
pub const MESSAGE_LOOKUP: &str = "message:lookup";
|
||||
pub const MESSAGE_SHRED: &str = "message:shred";
|
||||
pub const REGISTRATION_REVIEW: &str = "registration:review";
|
||||
pub const REPORT_DELETE: &str = "report:delete";
|
||||
pub const REPORT_RESOLVE: &str = "report:resolve";
|
||||
pub const REPORT_VIEW: &str = "report:view";
|
||||
pub const REPORT_VIEW_REPORTER_PII: &str = "report:view:reporter_pii";
|
||||
pub const SYSTEM_DM_SEND: &str = "system_dm:send";
|
||||
pub const SYSTEM_HEAP_SNAPSHOT: &str = "system:heap_snapshot";
|
||||
pub const USER_CANCEL_BULK_MESSAGE_DELETION: &str = "user:cancel:bulk_message_deletion";
|
||||
pub const USER_CREATE_PASSWORD_RESET_LINK: &str = "user:create:password_reset_link";
|
||||
pub const USER_DELETE_RECOVERY_KIT: &str = "user:delete:recovery_kit";
|
||||
pub const USER_DELETE: &str = "user:delete";
|
||||
pub const USER_DISABLE_SUSPICIOUS: &str = "user:disable:suspicious";
|
||||
pub const USER_LIST_DM_CHANNELS: &str = "user:list:dm_channels";
|
||||
pub const USER_LIST_GUILDS: &str = "user:list:guilds";
|
||||
pub const USER_LIST_RELATIONSHIPS: &str = "user:list:relationships";
|
||||
@@ -99,9 +96,7 @@ pub const USER_UPDATE_DOB: &str = "user:update:dob";
|
||||
pub const USER_UPDATE_EMAIL: &str = "user:update:email";
|
||||
pub const USER_UPDATE_FLAGS: &str = "user:update:flags";
|
||||
pub const USER_UPDATE_MFA: &str = "user:update:mfa";
|
||||
pub const USER_UPDATE_PHONE: &str = "user:update:phone";
|
||||
pub const USER_UPDATE_PROFILE: &str = "user:update:profile";
|
||||
pub const USER_UPDATE_SUSPICIOUS_ACTIVITY: &str = "user:update:suspicious_activity";
|
||||
pub const USER_UPDATE_TRAITS: &str = "user:update:traits";
|
||||
pub const USER_UPDATE_USERNAME: &str = "user:update:username";
|
||||
pub const VOICE_REGION_CREATE: &str = "voice:region:create";
|
||||
@@ -130,9 +125,6 @@ pub const ALL_ACLS: &[&str] = &[
|
||||
BAN_EMAIL_ADD,
|
||||
BAN_EMAIL_CHECK,
|
||||
BAN_EMAIL_REMOVE,
|
||||
SUSPICIOUS_EMAIL_DOMAIN_ADD,
|
||||
SUSPICIOUS_EMAIL_DOMAIN_CHECK,
|
||||
SUSPICIOUS_EMAIL_DOMAIN_REMOVE,
|
||||
BAN_PHRASE_ADD,
|
||||
BAN_PHRASE_CHECK,
|
||||
BAN_PHRASE_REMOVE,
|
||||
@@ -158,9 +150,7 @@ pub const ALL_ACLS: &[&str] = &[
|
||||
BULK_DELETE_USERS,
|
||||
BULK_DELETE_USER_MESSAGES,
|
||||
BULK_UPDATE_GUILD_FEATURES,
|
||||
BULK_UPDATE_SUSPICIOUS_ACTIVITY,
|
||||
BULK_UPDATE_USER_FLAGS,
|
||||
CSAM_SUBMIT_NCMEC,
|
||||
DISCOVERY_REMOVE,
|
||||
DISCOVERY_REVIEW,
|
||||
GATEWAY_MEMORY_STATS,
|
||||
@@ -188,14 +178,16 @@ pub const ALL_ACLS: &[&str] = &[
|
||||
MESSAGE_DELETE,
|
||||
MESSAGE_LOOKUP,
|
||||
MESSAGE_SHRED,
|
||||
REGISTRATION_REVIEW,
|
||||
REPORT_DELETE,
|
||||
REPORT_RESOLVE,
|
||||
REPORT_VIEW,
|
||||
REPORT_VIEW_REPORTER_PII,
|
||||
SYSTEM_DM_SEND,
|
||||
SYSTEM_HEAP_SNAPSHOT,
|
||||
USER_CANCEL_BULK_MESSAGE_DELETION,
|
||||
USER_CREATE_PASSWORD_RESET_LINK,
|
||||
USER_DELETE_RECOVERY_KIT,
|
||||
USER_DELETE,
|
||||
USER_DISABLE_SUSPICIOUS,
|
||||
USER_LIST_DM_CHANNELS,
|
||||
USER_LIST_GUILDS,
|
||||
USER_LIST_RELATIONSHIPS,
|
||||
@@ -212,9 +204,7 @@ pub const ALL_ACLS: &[&str] = &[
|
||||
USER_UPDATE_EMAIL,
|
||||
USER_UPDATE_FLAGS,
|
||||
USER_UPDATE_MFA,
|
||||
USER_UPDATE_PHONE,
|
||||
USER_UPDATE_PROFILE,
|
||||
USER_UPDATE_SUSPICIOUS_ACTIVITY,
|
||||
USER_UPDATE_TRAITS,
|
||||
USER_UPDATE_USERNAME,
|
||||
VOICE_REGION_CREATE,
|
||||
|
||||
+104
-50
@@ -17,21 +17,21 @@ pub mod user_flag_bits {
|
||||
pub const FRIENDLY_BOT: u64 = 1 << 4;
|
||||
pub const FRIENDLY_BOT_MANUAL_APPROVAL: u64 = 1 << 5;
|
||||
pub const SPAMMER: u64 = 1 << 6;
|
||||
pub const PROFILE_HIDDEN: u64 = 1 << 7;
|
||||
pub const HIGH_GLOBAL_RATE_LIMIT: u64 = 1 << 33;
|
||||
pub const DELETED: u64 = 1 << 34;
|
||||
pub const DISABLED_SUSPICIOUS_ACTIVITY: u64 = 1 << 35;
|
||||
pub const SELF_DELETED: u64 = 1 << 36;
|
||||
pub const DISABLED: u64 = 1 << 38;
|
||||
pub const HAS_SESSION_STARTED: u64 = 1 << 39;
|
||||
pub const RATE_LIMIT_BYPASS: u64 = 1 << 47;
|
||||
pub const REPORT_BANNED: u64 = 1 << 48;
|
||||
pub const VERIFIED_NOT_UNDERAGE: u64 = 1 << 49;
|
||||
pub const ACCOUNT_LIMITED: u64 = 1 << 50;
|
||||
pub const HAS_DISMISSED_PREMIUM_ONBOARDING: u64 = 1 << 51;
|
||||
pub const APP_STORE_REVIEWER: u64 = 1 << 53;
|
||||
pub const STAFF_HIDDEN: u64 = 1 << 57;
|
||||
pub const AGE_VERIFIED_ADULT: u64 = 1 << 60;
|
||||
pub const FORCE_INBOUND_PHONE_VERIFICATION: u64 = 1 << 61;
|
||||
pub const NOT_SUSPICIOUS: u64 = 1 << 62;
|
||||
pub const LIMIT_EXEMPT: u64 = 1 << 62;
|
||||
}
|
||||
|
||||
pub const USER_FLAGS: &[U64Flag] = &[
|
||||
@@ -59,6 +59,10 @@ pub const USER_FLAGS: &[U64Flag] = &[
|
||||
name: "SPAMMER",
|
||||
value: user_flag_bits::SPAMMER,
|
||||
},
|
||||
U64Flag {
|
||||
name: "PROFILE_HIDDEN",
|
||||
value: user_flag_bits::PROFILE_HIDDEN,
|
||||
},
|
||||
U64Flag {
|
||||
name: "HIGH_GLOBAL_RATE_LIMIT",
|
||||
value: user_flag_bits::HIGH_GLOBAL_RATE_LIMIT,
|
||||
@@ -67,10 +71,6 @@ pub const USER_FLAGS: &[U64Flag] = &[
|
||||
name: "DELETED",
|
||||
value: user_flag_bits::DELETED,
|
||||
},
|
||||
U64Flag {
|
||||
name: "DISABLED_SUSPICIOUS_ACTIVITY",
|
||||
value: user_flag_bits::DISABLED_SUSPICIOUS_ACTIVITY,
|
||||
},
|
||||
U64Flag {
|
||||
name: "SELF_DELETED",
|
||||
value: user_flag_bits::SELF_DELETED,
|
||||
@@ -95,6 +95,10 @@ pub const USER_FLAGS: &[U64Flag] = &[
|
||||
name: "VERIFIED_NOT_UNDERAGE",
|
||||
value: user_flag_bits::VERIFIED_NOT_UNDERAGE,
|
||||
},
|
||||
U64Flag {
|
||||
name: "ACCOUNT_LIMITED",
|
||||
value: user_flag_bits::ACCOUNT_LIMITED,
|
||||
},
|
||||
U64Flag {
|
||||
name: "HAS_DISMISSED_PREMIUM_ONBOARDING",
|
||||
value: user_flag_bits::HAS_DISMISSED_PREMIUM_ONBOARDING,
|
||||
@@ -112,12 +116,8 @@ pub const USER_FLAGS: &[U64Flag] = &[
|
||||
value: user_flag_bits::AGE_VERIFIED_ADULT,
|
||||
},
|
||||
U64Flag {
|
||||
name: "FORCE_INBOUND_PHONE_VERIFICATION",
|
||||
value: user_flag_bits::FORCE_INBOUND_PHONE_VERIFICATION,
|
||||
},
|
||||
U64Flag {
|
||||
name: "NOT_SUSPICIOUS",
|
||||
value: user_flag_bits::NOT_SUSPICIOUS,
|
||||
name: "LIMIT_EXEMPT",
|
||||
value: user_flag_bits::LIMIT_EXEMPT,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -160,41 +160,95 @@ pub const PREMIUM_FLAGS: &[I32Flag] = &[
|
||||
},
|
||||
];
|
||||
|
||||
pub const SUSPICIOUS_ACTIVITY_FLAGS: &[I32Flag] = &[
|
||||
I32Flag {
|
||||
name: "REQUIRE_VERIFIED_EMAIL",
|
||||
value: 1 << 0,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_REVERIFIED_EMAIL",
|
||||
value: 1 << 1,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_VERIFIED_PHONE",
|
||||
value: 1 << 2,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_REVERIFIED_PHONE",
|
||||
value: 1 << 3,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_VERIFIED_EMAIL_OR_VERIFIED_PHONE",
|
||||
value: 1 << 4,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_REVERIFIED_EMAIL_OR_VERIFIED_PHONE",
|
||||
value: 1 << 5,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_VERIFIED_EMAIL_OR_REVERIFIED_PHONE",
|
||||
value: 1 << 6,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_REVERIFIED_EMAIL_OR_REVERIFIED_PHONE",
|
||||
value: 1 << 7,
|
||||
},
|
||||
I32Flag {
|
||||
name: "REQUIRE_INBOUND_PHONE_VERIFICATION",
|
||||
value: 1 << 8,
|
||||
},
|
||||
pub const DELETION_REASONS: &[(&str, &str)] = &[
|
||||
("1", "User requested"),
|
||||
("2", "Other"),
|
||||
("3", "Spam"),
|
||||
("4", "Cheating or exploitation"),
|
||||
("5", "Coordinated raiding or manipulation"),
|
||||
("6", "Automation or self-bot usage"),
|
||||
("7", "Nonconsensual sexual content"),
|
||||
("8", "Scam or social engineering"),
|
||||
("9", "Child sexual content"),
|
||||
("10", "Privacy violation or doxxing"),
|
||||
("11", "Harassment or bullying"),
|
||||
("12", "Payment fraud"),
|
||||
("13", "Child safety violation"),
|
||||
("14", "Billing dispute or abuse"),
|
||||
("15", "Unsolicited explicit content"),
|
||||
("16", "Graphic violence"),
|
||||
("17", "Ban evasion"),
|
||||
("18", "Token or credential scam"),
|
||||
("19", "Inactivity"),
|
||||
("20", "Hate speech or extremist content"),
|
||||
("21", "Malicious links or malware distribution"),
|
||||
("22", "Impersonation or fake identity"),
|
||||
];
|
||||
|
||||
pub const GUILD_FEATURES: &[&str] = &[
|
||||
"ANIMATED_ICON",
|
||||
"ANIMATED_BANNER",
|
||||
"AUDIO_BITRATE_128_KBPS",
|
||||
"AUDIO_BITRATE_256_KBPS",
|
||||
"AUDIO_BITRATE_384_KBPS",
|
||||
"BANNER",
|
||||
"CLONE_EMOJI_DISABLED",
|
||||
"CLONE_EMOJI_ENABLED",
|
||||
"CLONE_STICKER_DISABLED",
|
||||
"CLONE_STICKER_ENABLED",
|
||||
"DETACHED_BANNER",
|
||||
"INVITE_SPLASH",
|
||||
"INVITES_DISABLED",
|
||||
"RAID_DETECTED",
|
||||
"TEXT_CHANNEL_FLEXIBLE_NAMES",
|
||||
"HIDE_OWNER_CROWN",
|
||||
"MORE_EMOJI",
|
||||
"MORE_STICKERS",
|
||||
"UNLIMITED_EMOJI",
|
||||
"UNLIMITED_STICKERS",
|
||||
"EXPRESSION_PURGE_ALLOWED",
|
||||
"VANITY_URL",
|
||||
"DISCOVERABLE",
|
||||
"PARTNERED",
|
||||
"VERIFIED",
|
||||
"VIP_VOICE",
|
||||
"VOICE_E2EE",
|
||||
"UNAVAILABLE_FOR_EVERYONE",
|
||||
"UNAVAILABLE_FOR_EVERYONE_BUT_STAFF",
|
||||
"UNAVAILABLE_HIDDEN",
|
||||
"VISIONARY",
|
||||
"LARGE_GUILD_OVERRIDE",
|
||||
"VERY_LARGE_GUILD",
|
||||
"ANNOUNCEMENT_CHANNELS_DISABLED",
|
||||
];
|
||||
|
||||
pub const DEPRECATED_GUILD_FEATURES: &[&str] = &["CLONE_EMOJI_DISABLED", "CLONE_STICKER_DISABLED"];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::USER_FLAGS;
|
||||
|
||||
#[test]
|
||||
fn user_flags_cover_every_flag_in_the_admin_spec() {
|
||||
let spec: serde_json::Value =
|
||||
serde_json::from_str(include_str!("../openapi-admin.json")).expect("admin spec");
|
||||
let values = spec["components"]["schemas"]["UserFlags"]["x-bitflagValues"]
|
||||
.as_array()
|
||||
.expect("UserFlags bitflag values");
|
||||
assert!(!values.is_empty());
|
||||
for entry in values {
|
||||
let name = entry["name"].as_str().expect("flag name");
|
||||
let value: u64 = entry["value"]
|
||||
.as_str()
|
||||
.expect("flag value")
|
||||
.parse()
|
||||
.expect("numeric flag value");
|
||||
assert!(
|
||||
USER_FLAGS
|
||||
.iter()
|
||||
.any(|flag| flag.name == name && flag.value == value),
|
||||
"{name} ({value}) is missing from USER_FLAGS"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::templates::components::tooltip::{Hint, HintLink};
|
||||
|
||||
pub fn limit_key_hint(key: &str) -> Option<Hint<'static>> {
|
||||
match key {
|
||||
"feature_guild_create" => Some(Hint {
|
||||
name: Some("Community Creation Access"),
|
||||
body: "Admins with the wildcard ACL can always create communities.",
|
||||
link: Some(HintLink::new(
|
||||
"/instance-config#community-creation",
|
||||
"Community creation policy",
|
||||
)),
|
||||
}),
|
||||
"max_screen_share_bitrate" => Some(Hint {
|
||||
name: Some("Max Screen Share Bitrate"),
|
||||
body: "In bits per second, so 9000000 is 9 Mbps. Raising it also raises the automatic bitrate of every screen share quality. Accounts with Higher Video Quality can pick their own bitrate up to this value.",
|
||||
link: None,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ impl AdminApiClient {
|
||||
acls: &[String],
|
||||
) -> ApiResult<CreateAdminApiKeyResponse> {
|
||||
let body = generated_types::CreateAdminApiKeyRequest {
|
||||
acls: parse_acls(acls)?,
|
||||
acls: parse_acls(acls),
|
||||
expires_in_days: None,
|
||||
name: generated_types::CreateAdminApiKeyRequestName::try_from(name)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
@@ -44,11 +44,8 @@ impl AdminApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_acls(acls: &[String]) -> ApiResult<Vec<generated_types::AdminAclType>> {
|
||||
pub(super) fn parse_acls(acls: &[String]) -> Vec<generated_types::AdminAclType> {
|
||||
acls.iter()
|
||||
.map(|acl| {
|
||||
generated_types::AdminAclType::try_from(acl.as_str())
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))
|
||||
})
|
||||
.filter_map(|acl| generated_types::AdminAclType::try_from(acl.as_str()).ok())
|
||||
.collect()
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use super::client::{AdminApiClient, ApiResult};
|
||||
use super::types::{Application, ApplicationUpdateResponse, LookupApplicationResponse};
|
||||
use serde::Serialize;
|
||||
@@ -43,12 +45,13 @@ impl AdminApiClient {
|
||||
new_owner_id: &str,
|
||||
) -> ApiResult<ApplicationUpdateResponse> {
|
||||
let body = TransferApplicationOwnershipRequest { new_owner_id };
|
||||
self.patch_typed_with_reason(
|
||||
self.send(
|
||||
Method::PATCH,
|
||||
&format!(
|
||||
"/admin/applications/{}",
|
||||
urlencoding::encode(application_id)
|
||||
),
|
||||
&body,
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -10,10 +10,11 @@ pub struct SearchAuditLogsParams {
|
||||
pub admin_user_id: Option<String>,
|
||||
pub target_id: Option<String>,
|
||||
pub target_type: Option<String>,
|
||||
pub access: Option<String>,
|
||||
pub sort_by: Option<String>,
|
||||
pub sort_order: Option<String>,
|
||||
pub limit: u32,
|
||||
pub offset: u32,
|
||||
pub offset: u64,
|
||||
}
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -21,6 +22,12 @@ impl AdminApiClient {
|
||||
&self,
|
||||
params: &SearchAuditLogsParams,
|
||||
) -> ApiResult<AuditLogsListResponse> {
|
||||
let access = params
|
||||
.access
|
||||
.as_deref()
|
||||
.map(audit_access)
|
||||
.transpose()?
|
||||
.map(|value| value.to_string());
|
||||
let sort_by = params
|
||||
.sort_by
|
||||
.as_deref()
|
||||
@@ -46,6 +53,7 @@ impl AdminApiClient {
|
||||
params.target_type.as_deref().unwrap_or_default(),
|
||||
),
|
||||
("target_id", params.target_id.as_deref().unwrap_or_default()),
|
||||
("access", access.as_deref().unwrap_or_default()),
|
||||
("sort_by", sort_by.as_deref().unwrap_or_default()),
|
||||
("sort_order", sort_order.as_deref().unwrap_or_default()),
|
||||
("limit", limit.as_str()),
|
||||
@@ -55,6 +63,11 @@ impl AdminApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn audit_access(value: &str) -> ApiResult<generated_types::ListAdminAuditLogsAccess> {
|
||||
generated_types::ListAdminAuditLogsAccess::try_from(value)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))
|
||||
}
|
||||
|
||||
fn audit_sort_by(value: &str) -> ApiResult<generated_types::ListAdminAuditLogsSortBy> {
|
||||
let value = match value {
|
||||
"created_at" => "createdAt",
|
||||
@@ -83,6 +96,13 @@ mod tests {
|
||||
assert_eq!(audit_sort_order("desc").unwrap().to_string(), "desc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_only_known_access_filters() {
|
||||
assert_eq!(audit_access("read").unwrap().to_string(), "read");
|
||||
assert_eq!(audit_access("write").unwrap().to_string(), "write");
|
||||
assert!(audit_access("all").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_lossy_audit_totals() {
|
||||
for total in [serde_json::json!(1.5), serde_json::json!(-1)] {
|
||||
@@ -99,6 +119,7 @@ mod tests {
|
||||
"admin_user_id": "234567890123456789",
|
||||
"admin_user": null,
|
||||
"action": "USER_UPDATE",
|
||||
"access": "write",
|
||||
"target_id": "345678901234567890",
|
||||
"target_type": "user",
|
||||
"target_user": null,
|
||||
@@ -118,6 +139,26 @@ mod tests {
|
||||
assert_eq!(generated.logs[0].action.to_string(), "USER_UPDATE");
|
||||
|
||||
let response: AuditLogsListResponse = serde_json::from_value(json.clone()).unwrap();
|
||||
assert_eq!(response.logs[0].access.as_deref(), Some("write"));
|
||||
assert_eq!(serde_json::to_value(response).unwrap(), json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserializes_audit_entries_from_an_api_without_access() {
|
||||
let json = serde_json::json!({
|
||||
"logs": [{
|
||||
"log_id": "123456789012345678",
|
||||
"admin_user_id": "234567890123456789",
|
||||
"action": "USER_UPDATE",
|
||||
"target_id": "345678901234567890",
|
||||
"target_type": "user",
|
||||
"audit_log_reason": null,
|
||||
"metadata": {},
|
||||
"created_at": "2026-09-11T12:00:00.000Z"
|
||||
}],
|
||||
"total": 1
|
||||
});
|
||||
let response: AuditLogsListResponse = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(response.logs[0].access, None);
|
||||
}
|
||||
}
|
||||
+152
-136
@@ -1,138 +1,148 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::types as generated_types;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{BanAvatarResult, BanCheckResult, BulkBanResult};
|
||||
use super::types::{BanCheckResult, BlocklistEntryPage, BulkBanResult};
|
||||
|
||||
impl AdminApiClient {
|
||||
pub async fn ban_email(&self, email: &str) -> ApiResult<()> {
|
||||
pub async fn ban_email(&self, email: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"email",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_1: Some(generated_types::BanEmailRequest {
|
||||
email: generated_types::EmailType::from(email.to_owned()),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanEmailRequest {
|
||||
email: generated_types::EmailBlocklistEntryType::from(email.to_owned()),
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_email(&self, email: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("email", email, None).await
|
||||
pub async fn unban_email(&self, email: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("email", email, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_email_ban(&self, email: &str) -> ApiResult<BanCheckResult> {
|
||||
self.check_blocklist_entry("email", email, None).await
|
||||
}
|
||||
|
||||
pub async fn ban_ip(&self, ip: &str) -> ApiResult<()> {
|
||||
pub async fn ban_ip(
|
||||
&self,
|
||||
ip: &str,
|
||||
duration_hours: u32,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"ip",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_0: Some(generated_types::BanIpRequest { ip: ip.to_owned() }),
|
||||
..Default::default()
|
||||
},
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanIpRequest {
|
||||
duration_hours: Some(
|
||||
i32::try_from(duration_hours)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?
|
||||
.into(),
|
||||
),
|
||||
ip: ip.to_owned(),
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_ip(&self, ip: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("ip", ip, None).await
|
||||
pub async fn unban_ip(&self, ip: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("ip", ip, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_ip_ban(&self, ip: &str) -> ApiResult<BanCheckResult> {
|
||||
self.check_blocklist_entry("ip", ip, None).await
|
||||
}
|
||||
|
||||
pub async fn add_suspicious_email_domain(&self, domain: &str) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
SUSPICIOUS_EMAIL_DOMAIN_LIST,
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_2: Some(suspicious_email_domain_request(domain)?),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn remove_suspicious_email_domain(&self, domain: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry(SUSPICIOUS_EMAIL_DOMAIN_LIST, domain, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_suspicious_email_domain(&self, domain: &str) -> ApiResult<BanCheckResult> {
|
||||
self.check_blocklist_entry(SUSPICIOUS_EMAIL_DOMAIN_LIST, domain, None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ban_phrase(&self, phrase: &str) -> ApiResult<()> {
|
||||
pub async fn ban_phrase(&self, phrase: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"phrase",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_3: Some(generated_types::BanPhraseRequest {
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanPhraseRequest {
|
||||
phrase: phrase.to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_phrase(&self, phrase: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("phrase", phrase, None).await
|
||||
pub async fn unban_phrase(
|
||||
&self,
|
||||
phrase: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("phrase", phrase, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_phrase_ban(&self, phrase: &str) -> ApiResult<BanCheckResult> {
|
||||
self.check_blocklist_entry("phrase", phrase, None).await
|
||||
}
|
||||
|
||||
pub async fn ban_url(&self, url: &str) -> ApiResult<()> {
|
||||
pub async fn ban_url(&self, url: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"url",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_4: Some(generated_types::BanUrlRequest {
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanUrlRequest {
|
||||
category: None,
|
||||
notes: None,
|
||||
severity: None,
|
||||
source_url: None,
|
||||
url: url.to_owned(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_url(&self, url: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("url", url, None).await
|
||||
pub async fn unban_url(&self, url: &str, audit_log_reason: Option<&str>) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("url", url, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_url_ban(&self, url: &str) -> ApiResult<BanCheckResult> {
|
||||
self.check_blocklist_entry("url", url, None).await
|
||||
}
|
||||
|
||||
pub async fn ban_url_domain(&self, domain: &str, match_subdomains: bool) -> ApiResult<()> {
|
||||
pub async fn ban_url_domain(
|
||||
&self,
|
||||
domain: &str,
|
||||
match_subdomains: bool,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"url-domain",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_5: Some(generated_types::BanUrlDomainRequest {
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanUrlDomainRequest {
|
||||
category: None,
|
||||
domain: domain.to_owned(),
|
||||
match_subdomains,
|
||||
notes: None,
|
||||
severity: None,
|
||||
source_url: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_url_domain(&self, domain: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("url-domain", domain, None)
|
||||
pub async fn unban_url_domain(
|
||||
&self,
|
||||
domain: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("url-domain", domain, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -140,25 +150,36 @@ impl AdminApiClient {
|
||||
self.check_blocklist_entry("url-domain", domain, None).await
|
||||
}
|
||||
|
||||
pub async fn list_url_domain_entries(
|
||||
&self,
|
||||
after: Option<&str>,
|
||||
) -> ApiResult<BlocklistEntryPage> {
|
||||
let list_type = blocklist_list_type("url-domain")?;
|
||||
let response = self
|
||||
.generated()
|
||||
.list_admin_blocklist_entries(list_type, after, Some(BLOCKLIST_PAGE_SIZE), None)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn ban_file_sha(
|
||||
&self,
|
||||
sha256_hex: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let body = generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_6: Some(generated_types::BanFileShaRequest {
|
||||
category: None,
|
||||
content_type: None,
|
||||
notes: None,
|
||||
severity: None,
|
||||
sha256_hex: sha256_hex.to_owned(),
|
||||
source_url: None,
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
self.post_void_with_reason(
|
||||
"/admin/blocklists/file-sha/entries",
|
||||
Some(&serde_json::to_value(&body).map_err(|e| ApiError::Parse(e.to_string()))?),
|
||||
self.create_blocklist_entry(
|
||||
"file-sha",
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanFileShaRequest {
|
||||
category: None,
|
||||
content_type: None,
|
||||
notes: None,
|
||||
severity: None,
|
||||
sha256_hex: sha256_hex.to_owned(),
|
||||
source_url: None,
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
@@ -169,12 +190,8 @@ impl AdminApiClient {
|
||||
sha256_hex: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.delete_void_with_reason(
|
||||
&blocklist_entry_path("file-sha", sha256_hex),
|
||||
None,
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
self.delete_blocklist_entry("file-sha", sha256_hex, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_file_sha_ban(&self, sha256_hex: &str) -> ApiResult<BanCheckResult> {
|
||||
@@ -190,34 +207,43 @@ impl AdminApiClient {
|
||||
let body = generated_types::BulkBanFileShasRequest {
|
||||
sha256_list: sha256_list.to_vec(),
|
||||
};
|
||||
self.put_typed_with_reason(
|
||||
self.send(
|
||||
Method::PUT,
|
||||
"/admin/blocklists/file-sha/entries",
|
||||
&body,
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ban_avatar_hash(&self, hash_short: &str) -> ApiResult<()> {
|
||||
pub async fn ban_avatar_hash(
|
||||
&self,
|
||||
hash_short: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
"avatar-hash",
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_7: Some(generated_types::BanAvatarHashRequest {
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(
|
||||
generated_types::BanAvatarHashRequest {
|
||||
category: None,
|
||||
hashes: vec![hash_short.to_owned()],
|
||||
notes: None,
|
||||
reason: None,
|
||||
severity: None,
|
||||
source_url: None,
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_avatar_hash(&self, hash_short: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("avatar-hash", hash_short, None)
|
||||
pub async fn unban_avatar_hash(
|
||||
&self,
|
||||
hash_short: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry("avatar-hash", hash_short, None, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -226,30 +252,35 @@ impl AdminApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn ban_user_avatar(&self, user_id: &str) -> ApiResult<BanAvatarResult> {
|
||||
let body = generated_types::BanUserAvatarRequest::default();
|
||||
let response = self
|
||||
.generated()
|
||||
.ban_admin_user_avatar(&snowflake(user_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn ban_profile_substring(&self, scope: &str, substring: &str) -> ApiResult<()> {
|
||||
pub async fn ban_profile_substring(
|
||||
&self,
|
||||
scope: &str,
|
||||
substring: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.create_blocklist_entry(
|
||||
PROFILE_SUBSTRING_LIST,
|
||||
generated_types::AdminBlocklistEntryCreateRequest {
|
||||
subtype_8: Some(profile_substring_request(scope, substring)?),
|
||||
..Default::default()
|
||||
},
|
||||
generated_types::AdminBlocklistEntryCreateRequest::from(profile_substring_request(
|
||||
scope, substring,
|
||||
)?),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn unban_profile_substring(&self, scope: &str, substring: &str) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry(PROFILE_SUBSTRING_LIST, substring, Some(scope))
|
||||
.await
|
||||
pub async fn unban_profile_substring(
|
||||
&self,
|
||||
scope: &str,
|
||||
substring: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
self.delete_blocklist_entry(
|
||||
PROFILE_SUBSTRING_LIST,
|
||||
substring,
|
||||
Some(scope),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn check_profile_substring_ban(
|
||||
@@ -265,13 +296,14 @@ impl AdminApiClient {
|
||||
&self,
|
||||
list_type: &str,
|
||||
body: generated_types::AdminBlocklistEntryCreateRequest,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let list_type = blocklist_list_type(list_type)?;
|
||||
self.generated()
|
||||
self.generated_with_reason(audit_log_reason)?
|
||||
.create_admin_blocklist_entry(list_type, &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
Ok(())
|
||||
.map(drop)
|
||||
.map_err(|error| self.generated_error(error))
|
||||
}
|
||||
|
||||
async fn delete_blocklist_entry(
|
||||
@@ -279,14 +311,15 @@ impl AdminApiClient {
|
||||
list_type: &str,
|
||||
entry_value: &str,
|
||||
scope: Option<&str>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let list_type = blocklist_list_type(list_type)?;
|
||||
let scope = scope.map(blocklist_delete_scope).transpose()?;
|
||||
self.generated()
|
||||
self.generated_with_reason(audit_log_reason)?
|
||||
.delete_admin_blocklist_entry(list_type, entry_value, scope)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
Ok(())
|
||||
.map(drop)
|
||||
.map_err(|error| self.generated_error(error))
|
||||
}
|
||||
|
||||
async fn check_blocklist_entry(
|
||||
@@ -306,23 +339,15 @@ impl AdminApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
const SUSPICIOUS_EMAIL_DOMAIN_LIST: &str = "email-domain-suspicious";
|
||||
|
||||
const PROFILE_SUBSTRING_LIST: &str = "profile-substring";
|
||||
|
||||
const BLOCKLIST_PAGE_SIZE: &str = "200";
|
||||
|
||||
fn blocklist_list_type(list_type: &str) -> ApiResult<generated_types::AdminBlocklistListType> {
|
||||
generated_types::AdminBlocklistListType::try_from(list_type)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))
|
||||
}
|
||||
|
||||
fn blocklist_entry_path(list_type: &str, entry_value: &str) -> String {
|
||||
format!(
|
||||
"/admin/blocklists/{}/entries/{}",
|
||||
urlencoding::encode(list_type),
|
||||
urlencoding::encode(entry_value)
|
||||
)
|
||||
}
|
||||
|
||||
fn blocklist_get_scope(scope: &str) -> ApiResult<generated_types::GetAdminBlocklistEntryScope> {
|
||||
generated_types::GetAdminBlocklistEntryScope::try_from(scope)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))
|
||||
@@ -335,15 +360,6 @@ fn blocklist_delete_scope(
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))
|
||||
}
|
||||
|
||||
fn suspicious_email_domain_request(
|
||||
domain: &str,
|
||||
) -> ApiResult<generated_types::SuspiciousEmailDomainRequest> {
|
||||
Ok(generated_types::SuspiciousEmailDomainRequest {
|
||||
domain: generated_types::SuspiciousEmailDomainRequestDomain::try_from(domain)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
})
|
||||
}
|
||||
|
||||
fn profile_substring_request(
|
||||
scope: &str,
|
||||
substring: &str,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::{guild_features, user_flags};
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
@@ -14,28 +18,17 @@ impl AdminApiClient {
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<BulkJobResponse> {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::UpdateUserFlags {
|
||||
add_flags: user_flags(add_flags),
|
||||
remove_flags: user_flags(remove_flags),
|
||||
add_flags: user_flags(add_flags)?,
|
||||
remove_flags: user_flags(remove_flags)?,
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn bulk_update_suspicious_activity_flags(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
add_flags: &[String],
|
||||
remove_flags: &[String],
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<BulkJobResponse> {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::UpdateSuspiciousActivityFlags {
|
||||
add_flags: add_flags.to_vec(),
|
||||
remove_flags: remove_flags.to_vec(),
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/bulk-jobs",
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn bulk_update_guild_features(
|
||||
@@ -50,8 +43,13 @@ impl AdminApiClient {
|
||||
guild_ids: snowflakes(guild_ids),
|
||||
remove_features: guild_features(remove_features),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/bulk-jobs",
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn bulk_add_guild_members(
|
||||
@@ -64,8 +62,13 @@ impl AdminApiClient {
|
||||
guild_id: snowflake(guild_id),
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/bulk-jobs",
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn bulk_delete_user_messages(
|
||||
@@ -76,8 +79,13 @@ impl AdminApiClient {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::DeleteUserMessages {
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/bulk-jobs",
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn bulk_schedule_user_deletion(
|
||||
@@ -86,6 +94,7 @@ impl AdminApiClient {
|
||||
reason_code: u32,
|
||||
days_until_deletion: u32,
|
||||
public_reason: Option<&str>,
|
||||
notify_user: bool,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<BulkJobResponse> {
|
||||
let body = generated_types::AdminBulkJobCreateRequest::ScheduleUserDeletion {
|
||||
@@ -95,6 +104,7 @@ impl AdminApiClient {
|
||||
)
|
||||
.map_err(ApiError::Parse)?
|
||||
.into(),
|
||||
notify_user,
|
||||
public_reason: public_reason.map(std::borrow::ToOwned::to_owned),
|
||||
reason_code: crate::api::generated::deletion_reason_code(
|
||||
i32::try_from(reason_code).map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
@@ -103,27 +113,16 @@ impl AdminApiClient {
|
||||
.map_err(ApiError::Parse)?,
|
||||
user_ids: snowflakes(user_ids),
|
||||
};
|
||||
self.post_typed_with_reason("/admin/bulk-jobs", &body, audit_log_reason)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/bulk-jobs",
|
||||
Some(&body),
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
fn snowflakes(values: &[String]) -> Vec<generated_types::SnowflakeType> {
|
||||
values.iter().map(|value| snowflake(value)).collect()
|
||||
}
|
||||
|
||||
fn user_flags(values: &[String]) -> Vec<generated_types::UserFlags> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(generated_types::UserFlags::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn guild_features(values: &[String]) -> Vec<generated_types::GuildFeatureSchema> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(generated_types::GuildFeatureSchema::from)
|
||||
.collect()
|
||||
}
|
||||
+104
-205
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::GeneratedClient;
|
||||
use crate::{config::AdminConfig, session::Session};
|
||||
use progenitor_client::ClientInfo;
|
||||
use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderName, HeaderValue};
|
||||
use reqwest::{Method, RequestBuilder};
|
||||
use serde::Serialize;
|
||||
@@ -34,46 +36,48 @@ impl<T> ApiResultExt<T> for ApiResult<T> {
|
||||
}
|
||||
|
||||
pub struct AdminApiClient {
|
||||
http_client: reqwest::Client,
|
||||
generated: crate::api::generated::GeneratedClient,
|
||||
base_url: String,
|
||||
access_token: String,
|
||||
proxy_client_ip_headers: HeaderMap,
|
||||
generated: GeneratedClient,
|
||||
}
|
||||
|
||||
impl AdminApiClient {
|
||||
pub fn new(http_client: &reqwest::Client, config: &AdminConfig, session: &Session) -> Self {
|
||||
let generated_http_client = build_generated_http_client(config, session);
|
||||
let generated = crate::api::generated::GeneratedClient::new_with_client(
|
||||
let generated = GeneratedClient::new_with_client(
|
||||
&config.api_endpoint,
|
||||
generated_http_client,
|
||||
http_client.clone(),
|
||||
build_session_headers(config, session),
|
||||
);
|
||||
Self {
|
||||
http_client: http_client.clone(),
|
||||
generated,
|
||||
base_url: config.api_endpoint.clone(),
|
||||
access_token: session.access_token.clone(),
|
||||
proxy_client_ip_headers: build_proxy_client_ip_headers(config),
|
||||
}
|
||||
Self { generated }
|
||||
}
|
||||
|
||||
pub fn anonymous(http_client: &reqwest::Client, config: &AdminConfig) -> Self {
|
||||
let generated = GeneratedClient::new_with_client(
|
||||
&config.api_endpoint,
|
||||
http_client.clone(),
|
||||
build_proxy_client_ip_headers(config),
|
||||
);
|
||||
Self { generated }
|
||||
}
|
||||
|
||||
fn build_url(&self, path: &str, query_params: Option<&[(&str, &str)]>) -> String {
|
||||
let base = format!("{}{}", self.base_url, path);
|
||||
match query_params {
|
||||
None => base,
|
||||
Some(params) => {
|
||||
let filtered: Vec<_> = params.iter().filter(|(_, v)| !v.is_empty()).collect();
|
||||
if filtered.is_empty() {
|
||||
return base;
|
||||
}
|
||||
let query = filtered
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
|
||||
.collect::<Vec<_>>()
|
||||
.join("&");
|
||||
format!("{base}?{query}")
|
||||
}
|
||||
let mut url = format!("{}{}", self.generated.baseurl(), path);
|
||||
let query = query_params
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter(|(_, value)| !value.is_empty())
|
||||
.map(|(key, value)| {
|
||||
format!(
|
||||
"{}={}",
|
||||
urlencoding::encode(key),
|
||||
urlencoding::encode(value)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("&");
|
||||
if !query.is_empty() {
|
||||
url.push('?');
|
||||
url.push_str(&query);
|
||||
}
|
||||
url
|
||||
}
|
||||
|
||||
fn request(
|
||||
@@ -81,30 +85,23 @@ impl AdminApiClient {
|
||||
method: Method,
|
||||
path: &str,
|
||||
query_params: Option<&[(&str, &str)]>,
|
||||
) -> RequestBuilder {
|
||||
let url = self.build_url(path, query_params);
|
||||
self.http_client
|
||||
.request(method, &url)
|
||||
.header("Authorization", format!("Bearer {}", self.access_token))
|
||||
.header("Content-Type", "application/json")
|
||||
.headers(self.proxy_client_ip_headers.clone())
|
||||
}
|
||||
|
||||
fn with_audit_log_reason(
|
||||
builder: RequestBuilder,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> RequestBuilder {
|
||||
match audit_log_reason {
|
||||
Some(reason) => builder.header("X-Audit-Log-Reason", reason),
|
||||
None => builder,
|
||||
}
|
||||
) -> ApiResult<RequestBuilder> {
|
||||
let url = self.build_url(path, query_params);
|
||||
Ok(self
|
||||
.generated
|
||||
.client()
|
||||
.request(method, &url)
|
||||
.header("Content-Type", "application/json")
|
||||
.headers(self.headers_with_reason(audit_log_reason)?))
|
||||
}
|
||||
|
||||
fn with_json_body(builder: RequestBuilder, body: Option<&serde_json::Value>) -> RequestBuilder {
|
||||
match body {
|
||||
Some(body) => builder.json(body),
|
||||
None => builder,
|
||||
fn headers_with_reason(&self, audit_log_reason: Option<&str>) -> ApiResult<HeaderMap> {
|
||||
let mut headers = self.generated.inner().clone();
|
||||
if let Some(reason) = audit_log_reason {
|
||||
headers.insert("x-audit-log-reason", audit_log_reason_header(reason)?);
|
||||
}
|
||||
Ok(headers)
|
||||
}
|
||||
|
||||
async fn send_request(builder: RequestBuilder) -> ApiResult<reqwest::Response> {
|
||||
@@ -114,179 +111,57 @@ impl AdminApiClient {
|
||||
.map_err(|e| ApiError::Network(e.to_string()))
|
||||
}
|
||||
|
||||
async fn send_json<B: Serialize + ?Sized>(
|
||||
&self,
|
||||
method: Method,
|
||||
path: &str,
|
||||
body: Option<&B>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<reqwest::Response> {
|
||||
let builder = self.request(method, path, None, audit_log_reason)?;
|
||||
let builder = match body {
|
||||
Some(body) => builder.json(body),
|
||||
None => builder,
|
||||
};
|
||||
Self::send_request(builder).await
|
||||
}
|
||||
|
||||
pub async fn get<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
query_params: Option<&[(&str, &str)]>,
|
||||
) -> ApiResult<T> {
|
||||
let response = Self::send_request(self.request(Method::GET, path, query_params)).await?;
|
||||
let response =
|
||||
Self::send_request(self.request(Method::GET, path, query_params, None)?).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn post<T: DeserializeOwned>(
|
||||
pub async fn send<T, B>(
|
||||
&self,
|
||||
method: Method,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
) -> ApiResult<T> {
|
||||
self.post_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn post_typed<T, B>(&self, path: &str, body: &B) -> ApiResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Serialize + ?Sized,
|
||||
{
|
||||
self.post_typed_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn post_typed_with_reason<T, B>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
body: Option<&B>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Serialize + ?Sized,
|
||||
{
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::POST, path, None), audit_log_reason);
|
||||
let response = Self::send_request(builder.json(body)).await?;
|
||||
let response = self.send_json(method, path, body, audit_log_reason).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn post_with_reason<T: DeserializeOwned>(
|
||||
pub async fn send_void<B: Serialize + ?Sized>(
|
||||
&self,
|
||||
method: Method,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::POST, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn post_void(&self, path: &str, body: Option<&serde_json::Value>) -> ApiResult<()> {
|
||||
self.post_void_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn post_void_with_reason(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
body: Option<&B>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::POST, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
let response = self.send_json(method, path, body, audit_log_reason).await?;
|
||||
Self::parse_void_response(response).await
|
||||
}
|
||||
|
||||
pub async fn patch<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
) -> ApiResult<T> {
|
||||
self.patch_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn patch_with_reason<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::PATCH, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn patch_typed_with_reason<T, B>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Serialize + ?Sized,
|
||||
{
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::PATCH, path, None), audit_log_reason);
|
||||
let response = Self::send_request(builder.json(body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn put_with_reason<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::PUT, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn put_typed_with_reason<T, B>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: &B,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
B: Serialize + ?Sized,
|
||||
{
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::PUT, path, None), audit_log_reason);
|
||||
let response = Self::send_request(builder.json(body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
pub async fn put_void_with_reason(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::PUT, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_void_response(response).await
|
||||
}
|
||||
|
||||
pub async fn delete_void(&self, path: &str, body: Option<&serde_json::Value>) -> ApiResult<()> {
|
||||
self.delete_void_with_reason(path, body, None).await
|
||||
}
|
||||
|
||||
pub async fn delete_void_with_reason(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::DELETE, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_void_response(response).await
|
||||
}
|
||||
|
||||
pub async fn delete_with_reason<T: DeserializeOwned>(
|
||||
&self,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<T> {
|
||||
let builder =
|
||||
Self::with_audit_log_reason(self.request(Method::DELETE, path, None), audit_log_reason);
|
||||
let response = Self::send_request(Self::with_json_body(builder, body)).await?;
|
||||
Self::parse_response(response).await
|
||||
}
|
||||
|
||||
async fn parse_void_response(response: reqwest::Response) -> ApiResult<()> {
|
||||
Self::check_response_status(response).await.map(drop)
|
||||
}
|
||||
@@ -302,10 +177,21 @@ impl AdminApiClient {
|
||||
Err(ApiError::Http { status, message })
|
||||
}
|
||||
|
||||
pub(crate) fn generated(&self) -> &crate::api::generated::GeneratedClient {
|
||||
pub(crate) fn generated(&self) -> &GeneratedClient {
|
||||
&self.generated
|
||||
}
|
||||
|
||||
pub(crate) fn generated_with_reason(
|
||||
&self,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<GeneratedClient> {
|
||||
Ok(GeneratedClient::new_with_client(
|
||||
self.generated.baseurl(),
|
||||
self.generated.client().clone(),
|
||||
self.headers_with_reason(audit_log_reason)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn generated_value<T, U>(&self, value: U) -> ApiResult<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
@@ -346,17 +232,14 @@ impl AdminApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_generated_http_client(config: &AdminConfig, session: &Session) -> reqwest::Client {
|
||||
fn build_session_headers(config: &AdminConfig, session: &Session) -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
let auth_value = HeaderValue::from_str(&format!("Bearer {}", session.access_token))
|
||||
let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", session.access_token))
|
||||
.expect("failed to build generated API Authorization header");
|
||||
auth_value.set_sensitive(true);
|
||||
headers.insert(AUTHORIZATION, auth_value);
|
||||
headers.extend(build_proxy_client_ip_headers(config));
|
||||
reqwest::Client::builder()
|
||||
.user_agent(format!("FluxerAdmin/{} (Rust)", config.build_version))
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.expect("failed to create generated API HTTP client")
|
||||
headers
|
||||
}
|
||||
|
||||
pub(crate) fn with_proxy_client_ip_header(
|
||||
@@ -407,11 +290,27 @@ impl std::fmt::Display for ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
fn audit_log_reason_header(reason: &str) -> ApiResult<HeaderValue> {
|
||||
let mut value = HeaderValue::from_bytes(reason.as_bytes())
|
||||
.map_err(|_| ApiError::Parse("invalid audit log reason header".to_owned()))?;
|
||||
value.set_sensitive(true);
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
#[test]
|
||||
fn audit_log_reason_header_keeps_utf8_bytes() {
|
||||
let reason = "§ 3 Regel – wiederholt 日本";
|
||||
let value = audit_log_reason_header(reason).expect("valid reason header");
|
||||
assert_eq!(value.as_bytes(), reason.as_bytes());
|
||||
assert!(value.is_sensitive());
|
||||
assert!(audit_log_reason_header("line one\nline two").is_err());
|
||||
}
|
||||
|
||||
fn response(status: u16, body: &'static str) -> reqwest::Response {
|
||||
axum::http::Response::builder()
|
||||
.status(status)
|
||||
|
||||
@@ -9,7 +9,7 @@ impl AdminApiClient {
|
||||
pub async fn generate_gift_codes(
|
||||
&self,
|
||||
count: u32,
|
||||
duration_type: &str,
|
||||
duration_type: generated_types::GiftCodeDurationTypeSchema,
|
||||
duration_quantity: u32,
|
||||
) -> ApiResult<CodesResponse> {
|
||||
let body = generated_types::GenerateGiftCodesRequest {
|
||||
@@ -22,8 +22,7 @@ impl AdminApiClient {
|
||||
)
|
||||
.map_err(ApiError::Parse)?
|
||||
.into(),
|
||||
duration_type: generated_types::GiftCodeDurationTypeSchema::try_from(duration_type)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
duration_type,
|
||||
};
|
||||
let response = self
|
||||
.generated()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::float_to_u64;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
@@ -144,10 +146,6 @@ fn listed_guild(
|
||||
})
|
||||
}
|
||||
|
||||
fn float_to_u64(value: f64, field: &str) -> ApiResult<u64> {
|
||||
crate::api::generated::number_to_u64(value, field).map_err(ApiError::Parse)
|
||||
}
|
||||
|
||||
fn float_to_i32(value: f64, field: &str) -> ApiResult<i32> {
|
||||
let parsed = crate::api::generated::number_to_u64(value, field).map_err(ApiError::Parse)?;
|
||||
i32::try_from(parsed).map_err(|_| ApiError::Parse(format!("{field} is out of range: {value}")))
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use self::types as generated_types;
|
||||
use crate::api::client::{ApiError, ApiResult};
|
||||
|
||||
use progenitor_client::{ClientHooks, ClientInfo, Error, OperationInfo};
|
||||
use reqwest::header::HeaderMap;
|
||||
|
||||
#[allow(
|
||||
clippy::all,
|
||||
unused_imports,
|
||||
@@ -16,6 +22,20 @@ pub use inner::types;
|
||||
|
||||
pub use inner::Client as GeneratedClient;
|
||||
|
||||
impl ClientHooks<HeaderMap> for GeneratedClient {
|
||||
async fn pre<E>(
|
||||
&self,
|
||||
request: &mut reqwest::Request,
|
||||
_info: &OperationInfo,
|
||||
) -> Result<(), Error<E>> {
|
||||
let headers = request.headers_mut();
|
||||
for (name, value) in self.inner() {
|
||||
headers.entry(name.clone()).or_insert_with(|| value.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn snowflake(value: &str) -> types::SnowflakeType {
|
||||
types::SnowflakeType::Variant0(value.to_owned())
|
||||
}
|
||||
@@ -44,97 +64,35 @@ pub(crate) fn deletion_reason_code(
|
||||
.map_err(|_| format!("{field} is not a deletion reason code: {value}"))
|
||||
}
|
||||
|
||||
pub(crate) fn user_flags(values: &[String]) -> ApiResult<Vec<generated_types::UserFlags>> {
|
||||
values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
generated_types::UserFlags::try_from(value.as_str())
|
||||
.map_err(|error| ApiError::Parse(error.to_string()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn guild_features(values: &[String]) -> Vec<generated_types::GuildFeatureSchema> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(generated_types::GuildFeatureSchema::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn bool_param(value: bool) -> &'static str {
|
||||
if value { "true" } else { "false" }
|
||||
}
|
||||
|
||||
pub(crate) fn float_to_u64(value: f64, field: &str) -> ApiResult<u64> {
|
||||
crate::api::generated::number_to_u64(value, field).map_err(ApiError::Parse)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{number_to_u64, types::*};
|
||||
|
||||
#[test]
|
||||
fn deserialize_admin_users_me() {
|
||||
let json = serde_json::json!({
|
||||
"user": {
|
||||
"id": "123456789012345678",
|
||||
"username": "testadmin",
|
||||
"discriminator": 1,
|
||||
"global_name": "Test Admin",
|
||||
"bot": false,
|
||||
"system": false,
|
||||
"flags": "131141",
|
||||
"premium_flags": 0,
|
||||
"avatar": "abc123def456",
|
||||
"banner": null,
|
||||
"bio": "Hello world",
|
||||
"pronouns": null,
|
||||
"accent_color": null,
|
||||
"email": "[email protected]",
|
||||
"email_verified": true,
|
||||
"email_bounced": false,
|
||||
"has_verified_phone": false,
|
||||
"date_of_birth": "2000-01-15",
|
||||
"locale": "en-US",
|
||||
"premium_type": 2,
|
||||
"premium_since": "2024-01-01T00:00:00.000Z",
|
||||
"premium_until": null,
|
||||
"premium_grace_ends_at": null,
|
||||
"premium_lifetime_sequence": null,
|
||||
"suspicious_activity_flags": 0,
|
||||
"phone_verification_deferred": false,
|
||||
"temp_banned_until": null,
|
||||
"pending_deletion_at": null,
|
||||
"pending_bulk_message_deletion_at": null,
|
||||
"deletion_reason_code": null,
|
||||
"deletion_public_reason": null,
|
||||
"acls": ["MANAGE_USERS", "VIEW_AUDIT_LOG"],
|
||||
"traits": ["STAFF"],
|
||||
"has_totp": true,
|
||||
"authenticator_types": [0],
|
||||
"last_active_at": "2026-05-26T12:00:00.000Z",
|
||||
"last_active_ip": "203.0.113.1",
|
||||
"last_active_ip_reverse": "1.113.0.203.in-addr.arpa",
|
||||
"last_active_location": "New York, US"
|
||||
}
|
||||
});
|
||||
|
||||
let response: AdminUsersMeResponse =
|
||||
serde_json::from_value(json).expect("failed to deserialize AdminUsersMeResponse");
|
||||
|
||||
assert_eq!(response.user.id.to_string(), "123456789012345678");
|
||||
assert_eq!(response.user.username, "testadmin");
|
||||
assert!(!response.user.bot);
|
||||
assert!(response.user.email_verified);
|
||||
assert_eq!(response.user.acls.len(), 2);
|
||||
assert_eq!(response.user.global_name, Some("Test Admin".to_string()));
|
||||
assert!(response.user.banner.is_none());
|
||||
assert!(response.user.has_totp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_search_guilds_response() {
|
||||
let json = serde_json::json!({
|
||||
"guilds": [
|
||||
{
|
||||
"id": "987654321098765432",
|
||||
"name": "Test Guild",
|
||||
"icon": "def456abc123",
|
||||
"banner": null,
|
||||
"owner_id": "123456789012345678",
|
||||
"owner_username": "guildowner",
|
||||
"owner_global_name": "Guild Owner",
|
||||
"owner_discriminator": "0001",
|
||||
"member_count": 42,
|
||||
"features": ["COMMUNITY", "NEWS"]
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
});
|
||||
|
||||
let response: SearchGuildsResponse =
|
||||
serde_json::from_value(json).expect("failed to deserialize SearchGuildsResponse");
|
||||
|
||||
assert_eq!(response.total, 1.0);
|
||||
assert_eq!(response.guilds.len(), 1);
|
||||
assert_eq!(response.guilds[0].name, "Test Guild");
|
||||
assert_eq!(*response.guilds[0].member_count, 42);
|
||||
}
|
||||
use super::number_to_u64;
|
||||
|
||||
#[test]
|
||||
fn reject_lossy_generated_number_count() {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::snowflake;
|
||||
|
||||
use super::client::{AdminApiClient, ApiResult};
|
||||
use super::types::ListGuildThreadsResponse;
|
||||
|
||||
impl AdminApiClient {
|
||||
pub async fn list_guild_threads(&self, guild_id: &str) -> ApiResult<ListGuildThreadsResponse> {
|
||||
let response = self
|
||||
.generated()
|
||||
.list_admin_guild_threads(&snowflake(guild_id))
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn delete_thread_channel(&self, channel_id: &str) -> ApiResult<()> {
|
||||
self.generated()
|
||||
.delete_admin_thread_channel(&snowflake(channel_id))
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::api::generated::guild_features;
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::reports::SearchReportsParams;
|
||||
use super::types::{
|
||||
GuildAuditLogResponse, GuildDetailInfo, GuildInfo, GuildUpdateResponse,
|
||||
ListGuildMembersResponse, LookupGuildResponse, SearchGuildsResponse, SearchReportsResponse,
|
||||
SuccessResponse,
|
||||
GuildAuditLogResponse, GuildDetailInfo, GuildInfo, ListGuildMembersResponse,
|
||||
LookupGuildResponse, SearchGuildsResponse, SearchReportsResponse, SuccessResponse,
|
||||
};
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -16,7 +19,7 @@ impl AdminApiClient {
|
||||
&self,
|
||||
query: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
) -> ApiResult<SearchGuildsResponse> {
|
||||
let limit = limit.to_string();
|
||||
let offset = offset.to_string();
|
||||
@@ -53,18 +56,17 @@ impl AdminApiClient {
|
||||
guild_id: &str,
|
||||
add_features: &[String],
|
||||
remove_features: &[String],
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
) -> ApiResult<()> {
|
||||
let body = generated_types::UpdateGuildRequest {
|
||||
add_features: guild_features(add_features),
|
||||
remove_features: guild_features(remove_features),
|
||||
..Default::default()
|
||||
};
|
||||
let response = self
|
||||
.generated()
|
||||
self.generated()
|
||||
.update_admin_guild(&snowflake(guild_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_guild(&self, guild_id: &str) -> ApiResult<SuccessResponse> {
|
||||
@@ -80,24 +82,23 @@ impl AdminApiClient {
|
||||
&self,
|
||||
guild_id: &str,
|
||||
new_owner_id: &str,
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
) -> ApiResult<()> {
|
||||
let body = generated_types::UpdateGuildRequest {
|
||||
new_owner_id: Some(snowflake(new_owner_id)),
|
||||
..Default::default()
|
||||
};
|
||||
let response = self
|
||||
.generated()
|
||||
self.generated()
|
||||
.update_admin_guild(&snowflake(guild_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_guild_members(
|
||||
&self,
|
||||
guild_id: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
) -> ApiResult<ListGuildMembersResponse> {
|
||||
let limit = limit.to_string();
|
||||
let offset = offset.to_string();
|
||||
@@ -177,44 +178,37 @@ impl AdminApiClient {
|
||||
&self,
|
||||
guild_id: &str,
|
||||
settings: &serde_json::Value,
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
) -> ApiResult<()> {
|
||||
let body = guild_settings_request(settings)?;
|
||||
let response = self
|
||||
.generated()
|
||||
self.generated()
|
||||
.update_admin_guild(&snowflake(guild_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
guild_update_response(response.into_inner())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_guild_name(
|
||||
&self,
|
||||
guild_id: &str,
|
||||
name: &str,
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
pub async fn update_guild_name(&self, guild_id: &str, name: &str) -> ApiResult<()> {
|
||||
let body = generated_types::UpdateGuildRequest {
|
||||
name: Some(name.to_owned()),
|
||||
..Default::default()
|
||||
};
|
||||
let response = self
|
||||
.generated()
|
||||
self.generated()
|
||||
.update_admin_guild(&snowflake(guild_id), &body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_guild_vanity(
|
||||
&self,
|
||||
guild_id: &str,
|
||||
vanity: Option<&str>,
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
pub async fn update_guild_vanity(&self, guild_id: &str, vanity: Option<&str>) -> ApiResult<()> {
|
||||
let body = serde_json::json!({"vanity_url_code": vanity});
|
||||
self.patch(
|
||||
self.send::<serde::de::IgnoredAny, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/guilds/{}", urlencoding::encode(guild_id)),
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub async fn reload_guild(&self, guild_id: &str) -> ApiResult<SuccessResponse> {
|
||||
@@ -252,7 +246,7 @@ impl AdminApiClient {
|
||||
&self,
|
||||
guild_id: &str,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
offset: u64,
|
||||
) -> ApiResult<SearchReportsResponse> {
|
||||
self.search_reports(&SearchReportsParams {
|
||||
reported_guild_id: Some(guild_id),
|
||||
@@ -316,36 +310,6 @@ fn guild_admin_response(response: generated_types::GuildAdminResponse) -> ApiRes
|
||||
})
|
||||
}
|
||||
|
||||
fn guild_update_response(
|
||||
response: generated_types::GuildUpdateResponse,
|
||||
) -> ApiResult<GuildUpdateResponse> {
|
||||
let guild = response.guild;
|
||||
Ok(GuildUpdateResponse {
|
||||
guild: GuildInfo {
|
||||
id: String::from(guild.id),
|
||||
name: String::from(guild.name),
|
||||
icon: guild.icon.map(String::from),
|
||||
banner: guild.banner.map(String::from),
|
||||
owner_id: String::from(guild.owner_id),
|
||||
owner_username: None,
|
||||
owner_global_name: None,
|
||||
owner_discriminator: None,
|
||||
member_count: crate::api::generated::i64_to_u64(
|
||||
i64::from(i32::from(guild.member_count)),
|
||||
"member_count",
|
||||
)
|
||||
.map_err(ApiError::Parse)?,
|
||||
features: guild.features.into_iter().map(String::from).collect(),
|
||||
nsfw_level: guild.nsfw_level.map(i32::from),
|
||||
nsfw: guild.nsfw,
|
||||
content_warning_level: guild.content_warning_level.map(i32::from),
|
||||
content_warning_text: guild.content_warning_text.map(String::from),
|
||||
description: None,
|
||||
vanity_url_code: None,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn guild_settings_request(
|
||||
settings: &serde_json::Value,
|
||||
) -> ApiResult<generated_types::UpdateGuildRequest> {
|
||||
@@ -365,14 +329,6 @@ fn guild_settings_request(
|
||||
})
|
||||
}
|
||||
|
||||
fn guild_features(values: &[String]) -> Vec<generated_types::GuildFeatureSchema> {
|
||||
values
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(generated_types::GuildFeatureSchema::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use super::client::{AdminApiClient, ApiResult};
|
||||
use super::types::{
|
||||
CreateRegistrationUrlRequest, CreateRegistrationUrlResponse, InstanceConfigResponse,
|
||||
InstanceConfigUpdateRequest, InstanceEmailSmtpTestRequest, InstanceEmailSmtpTestResponse,
|
||||
AccountIdentitySettings, CreateRegistrationUrlRequest, CreateRegistrationUrlResponse,
|
||||
InstanceAccountIdentityDiscovery, InstanceConfigResponse, InstanceConfigUpdateRequest,
|
||||
InstanceEmailSmtpTestResponse, InstancePremiumDiscovery, PendingRegistrationListResponse,
|
||||
PendingRegistrationResponse, PendingRegistrationStatus,
|
||||
};
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -11,70 +15,90 @@ impl AdminApiClient {
|
||||
self.get("/admin/instance/config", None).await
|
||||
}
|
||||
|
||||
pub async fn get_instance_premium_discovery(&self) -> ApiResult<InstancePremiumDiscovery> {
|
||||
self.get("/.well-known/fluxer", None).await
|
||||
}
|
||||
|
||||
pub async fn get_instance_account_identity(&self) -> ApiResult<AccountIdentitySettings> {
|
||||
let discovery: InstanceAccountIdentityDiscovery =
|
||||
self.get("/.well-known/fluxer", None).await?;
|
||||
let mode = discovery.features.account_identity;
|
||||
Ok(AccountIdentitySettings {
|
||||
mode,
|
||||
tag_style: discovery.features.tag_style,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_instance_config(
|
||||
&self,
|
||||
update: &InstanceConfigUpdateRequest,
|
||||
) -> ApiResult<InstanceConfigResponse> {
|
||||
self.patch_typed_with_reason("/admin/instance/config", update, None)
|
||||
self.send(Method::PATCH, "/admin/instance/config", Some(update), None)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn test_instance_smtp_config(
|
||||
pub async fn test_saved_instance_smtp_config(
|
||||
&self,
|
||||
request: &InstanceEmailSmtpTestRequest,
|
||||
) -> ApiResult<InstanceEmailSmtpTestResponse> {
|
||||
self.post_typed("/admin/instance/config/smtp-tests", request)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/instance/config/smtp-tests",
|
||||
Some(&serde_json::json!({})),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_registration_url(
|
||||
&self,
|
||||
request: &CreateRegistrationUrlRequest,
|
||||
) -> ApiResult<CreateRegistrationUrlResponse> {
|
||||
self.post_typed("/admin/instance/registration-urls", request)
|
||||
.await
|
||||
self.send(
|
||||
Method::POST,
|
||||
"/admin/instance/registration-urls",
|
||||
Some(request),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn revoke_registration_url(&self, id: &str) -> ApiResult<InstanceConfigResponse> {
|
||||
self.delete_with_reason(
|
||||
self.send(
|
||||
Method::DELETE,
|
||||
&format!(
|
||||
"/admin/instance/registration-urls/{}",
|
||||
urlencoding::encode(id)
|
||||
),
|
||||
None,
|
||||
None::<&serde_json::Value>,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn approve_pending_registration(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> ApiResult<InstanceConfigResponse> {
|
||||
self.decide_pending_registration(user_id, "approved").await
|
||||
pub async fn list_pending_registrations(&self) -> ApiResult<Vec<PendingRegistrationResponse>> {
|
||||
let response: PendingRegistrationListResponse = self
|
||||
.get("/admin/instance/pending-registrations", None)
|
||||
.await?;
|
||||
Ok(response.pending_registrations)
|
||||
}
|
||||
|
||||
pub async fn reject_pending_registration(
|
||||
pub async fn decide_pending_registration(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> ApiResult<InstanceConfigResponse> {
|
||||
self.decide_pending_registration(user_id, "rejected").await
|
||||
}
|
||||
|
||||
async fn decide_pending_registration(
|
||||
&self,
|
||||
user_id: &str,
|
||||
status: &str,
|
||||
) -> ApiResult<InstanceConfigResponse> {
|
||||
let body = serde_json::json!({"status": status});
|
||||
self.patch_with_reason(
|
||||
&format!(
|
||||
"/admin/instance/pending-registrations/{}",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
status: PendingRegistrationStatus,
|
||||
) -> ApiResult<Vec<PendingRegistrationResponse>> {
|
||||
let body = serde_json::json!({"status": status.as_str()});
|
||||
let response: PendingRegistrationListResponse = self
|
||||
.send(
|
||||
Method::PATCH,
|
||||
&format!(
|
||||
"/admin/instance/pending-registrations/{}",
|
||||
urlencoding::encode(user_id)
|
||||
),
|
||||
Some(&body),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
Ok(response.pending_registrations)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::snowflake;
|
||||
|
||||
use super::client::{AdminApiClient, ApiResult};
|
||||
use super::types::{ActiveJobsResponse, CancelJobResponse, GetJobResponse, ListJobsResponse};
|
||||
use super::types::{CancelJobResponse, GetJobResponse, ListJobsResponse};
|
||||
|
||||
pub struct ListJobsParams {
|
||||
pub limit: u32,
|
||||
@@ -24,9 +26,9 @@ impl AdminApiClient {
|
||||
let max_lookback_days = params.max_lookback_days.to_string();
|
||||
let query_params = [
|
||||
("limit", limit.as_str()),
|
||||
("cursor_bucket_day", cursor_bucket_day.as_str()),
|
||||
("cursor_created_at", cursor_created_at.as_str()),
|
||||
("cursor_job_id", cursor_job_id.as_str()),
|
||||
("cursor_bucket_day", cursor_bucket_day),
|
||||
("cursor_created_at", cursor_created_at),
|
||||
("cursor_job_id", cursor_job_id),
|
||||
("max_lookback_days", max_lookback_days.as_str()),
|
||||
("status", params.status.as_deref().unwrap_or_default()),
|
||||
("task_type", params.task_type.as_deref().unwrap_or_default()),
|
||||
@@ -52,28 +54,19 @@ impl AdminApiClient {
|
||||
job_id: &str,
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<CancelJobResponse> {
|
||||
self.put_with_reason(
|
||||
self.send(
|
||||
Method::PUT,
|
||||
&format!("/admin/jobs/{}/cancellation", urlencoding::encode(job_id)),
|
||||
None,
|
||||
None::<&serde_json::Value>,
|
||||
audit_log_reason,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_active_jobs(&self) -> ApiResult<ActiveJobsResponse> {
|
||||
let response = self
|
||||
.generated()
|
||||
.list_admin_active_jobs()
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_field(cursor: Option<&serde_json::Value>, field: &str) -> String {
|
||||
fn cursor_field<'a>(cursor: Option<&'a serde_json::Value>, field: &str) -> &'a str {
|
||||
cursor
|
||||
.and_then(|cursor| cursor.get(field))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_owned()
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use super::client::{AdminApiClient, ApiResult};
|
||||
use super::types::{LimitConfigResponse, LimitConfigUpdateRequest};
|
||||
|
||||
@@ -12,7 +14,7 @@ impl AdminApiClient {
|
||||
&self,
|
||||
request: &LimitConfigUpdateRequest,
|
||||
) -> ApiResult<LimitConfigResponse> {
|
||||
self.put_typed_with_reason("/admin/limit-config", request, None)
|
||||
self.send(Method::PUT, "/admin/limit-config", Some(request), None)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::generated::{snowflake, types as generated_types};
|
||||
|
||||
use super::client::{AdminApiClient, ApiError, ApiResult};
|
||||
use super::types::{
|
||||
BrowseChannelResponse, DeleteAllUserMessagesResponse, LookupMessageResponse,
|
||||
MessageShredResponse, MessageShredStatusResponse, NcmecAttachmentSubmitResult,
|
||||
SearchChannelMessagesResponse,
|
||||
MessageShredResponse, MessageShredStatusResponse, SearchChannelMessagesResponse,
|
||||
};
|
||||
|
||||
impl AdminApiClient {
|
||||
@@ -17,50 +18,20 @@ impl AdminApiClient {
|
||||
audit_log_reason: Option<&str>,
|
||||
) -> ApiResult<()> {
|
||||
let _: serde_json::Value = self
|
||||
.delete_with_reason(
|
||||
.send(
|
||||
Method::DELETE,
|
||||
&format!(
|
||||
"/admin/channels/{}/messages/{}",
|
||||
urlencoding::encode(channel_id),
|
||||
urlencoding::encode(message_id)
|
||||
),
|
||||
None,
|
||||
None::<&serde_json::Value>,
|
||||
audit_log_reason,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn report_attachment_to_ncmec(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
message_id: &str,
|
||||
attachment_id: &str,
|
||||
filename: &str,
|
||||
reporter_full_name: &str,
|
||||
source_report_id: Option<&str>,
|
||||
) -> ApiResult<NcmecAttachmentSubmitResult> {
|
||||
let body = generated_types::ReportAttachmentToNcmecRequest {
|
||||
attachment_id: snowflake(attachment_id),
|
||||
channel_id: snowflake(channel_id),
|
||||
confirmed_viewed: true,
|
||||
filename: generated_types::ReportAttachmentToNcmecRequestFilename::try_from(filename)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
message_id: snowflake(message_id),
|
||||
reporter_full_name:
|
||||
generated_types::ReportAttachmentToNcmecRequestReporterFullName::try_from(
|
||||
reporter_full_name,
|
||||
)
|
||||
.map_err(|e| ApiError::Parse(e.to_string()))?,
|
||||
source_report_id: source_report_id.map(snowflake),
|
||||
};
|
||||
let response = self
|
||||
.generated()
|
||||
.create_admin_ncmec_report(&body)
|
||||
.await
|
||||
.map_err(|e| self.generated_error(e))?;
|
||||
self.generated_value(response.into_inner())
|
||||
}
|
||||
|
||||
pub async fn lookup_message(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod client;
|
||||
pub mod codes;
|
||||
pub mod discovery;
|
||||
pub mod guild_assets;
|
||||
pub mod guild_threads;
|
||||
pub mod guilds;
|
||||
pub mod instance_config;
|
||||
pub mod jobs;
|
||||
|
||||
Loaded 100 of 8894 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user