Compare commits
768 Commits
clan/cli/f
...
vars-deplo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af692321f1 | ||
|
|
1397beb95e | ||
|
|
9381d78feb | ||
|
|
0fdfbf99e6 | ||
|
|
b2646aa0fe | ||
|
|
b93aa1896e | ||
|
|
d7475bef37 | ||
|
|
a9f0e90d12 | ||
|
|
c5b8948509 | ||
|
|
337a2ea883 | ||
|
|
15ba9b8bfb | ||
|
|
91397adbfc | ||
|
|
79e15bff24 | ||
|
|
5d12154f96 | ||
|
|
ac6b7f84e0 | ||
|
|
ef58bea020 | ||
|
|
d51508656f | ||
|
|
8fd09b8403 | ||
|
|
f6daaf79a7 | ||
|
|
d3542cf1c8 | ||
|
|
844c219348 | ||
|
|
8086f70b79 | ||
|
|
cc9c828598 | ||
|
|
a40ddd2b24 | ||
|
|
58a36a5c12 | ||
|
|
f1d124dabd | ||
|
|
e2bdf22f78 | ||
|
|
c7c6012bcc | ||
|
|
b3af929490 | ||
|
|
a465ad8638 | ||
|
|
26edcb8562 | ||
|
|
6c5f9ca6db | ||
|
|
0894ee95c8 | ||
|
|
350d565ddd | ||
|
|
4b2d1b7923 | ||
|
|
acb5560de8 | ||
|
|
2d4cd0c9be | ||
|
|
d360cf86cb | ||
|
|
f11ddf92e9 | ||
|
|
9f6f754606 | ||
|
|
03f87b24d3 | ||
|
|
ccdfd0c6fc | ||
|
|
e21bfbc257 | ||
|
|
f3c909b563 | ||
|
|
b357b545d3 | ||
|
|
915b2fbe73 | ||
|
|
13c70168a4 | ||
|
|
99a87a6120 | ||
|
|
c037ec2eeb | ||
|
|
65c03ecf96 | ||
|
|
d04d5b2c3b | ||
|
|
ded91c308a | ||
|
|
0dcc9f903b | ||
|
|
35e5ad513d | ||
|
|
6a13cb80cb | ||
|
|
d80488f786 | ||
|
|
2f07e47926 | ||
|
|
034593f742 | ||
|
|
2f9945bec5 | ||
|
|
94f65d3118 | ||
|
|
2155675fc6 | ||
|
|
3da42e698b | ||
|
|
21f7a3c269 | ||
|
|
aaa8411d89 | ||
|
|
a5065a1936 | ||
|
|
7b053abb13 | ||
|
|
d11e725a80 | ||
|
|
5223506e29 | ||
|
|
3e5ceb0eeb | ||
|
|
a958532766 | ||
|
|
b015f1f123 | ||
|
|
c648e647bf | ||
|
|
2b6a5f8385 | ||
|
|
53d7c2507e | ||
|
|
7783f17425 | ||
|
|
8dc02474fc | ||
|
|
16e87c52e9 | ||
|
|
5e16de17a3 | ||
|
|
eded0a62b0 | ||
|
|
777962003f | ||
|
|
3583c25c67 | ||
|
|
2532e93667 | ||
|
|
a6f4d6fbf0 | ||
|
|
6a8bb1be8d | ||
|
|
fe10c5fbd6 | ||
|
|
ef973ff475 | ||
|
|
eeb35a26db | ||
|
|
9996f5596c | ||
|
|
8020010fcf | ||
|
|
122ce665ed | ||
|
|
1d51ae5c85 | ||
|
|
ac5d421f84 | ||
|
|
63970defc0 | ||
|
|
0222ebf482 | ||
|
|
7a8d458581 | ||
|
|
14b88fe695 | ||
|
|
794285aa77 | ||
|
|
bf5cc8c215 | ||
|
|
4d25118853 | ||
|
|
c8407e2052 | ||
|
|
5b4105b4d8 | ||
|
|
d38983c784 | ||
|
|
f787acdaa4 | ||
|
|
580cc12671 | ||
|
|
679fd914e0 | ||
|
|
c6466f070e | ||
|
|
a294c6d454 | ||
|
|
4193d9e9a9 | ||
|
|
bb9da50d45 | ||
|
|
b1af97f2a5 | ||
|
|
c6a0cd9398 | ||
|
|
00f7a6300b | ||
|
|
aec1238f20 | ||
|
|
5bd20fcf2c | ||
|
|
9127dc3358 | ||
|
|
9ff076aec1 | ||
|
|
72019d6bcb | ||
|
|
249a18a734 | ||
|
|
00bad3d614 | ||
|
|
ed5dd02879 | ||
|
|
6c7e9bafea | ||
|
|
a1a36606e4 | ||
|
|
3d12aabf0c | ||
|
|
e79e199c9a | ||
|
|
1db0321163 | ||
|
|
d356a63d6c | ||
|
|
824c5d3f80 | ||
|
|
563ead4652 | ||
|
|
79a6ad2715 | ||
|
|
2516f38c37 | ||
|
|
f3c9c379e6 | ||
|
|
3546586dde | ||
|
|
aa792fedfd | ||
|
|
f1182af5a1 | ||
|
|
728f8f5758 | ||
|
|
1cb69cb5fc | ||
|
|
f66b809866 | ||
|
|
6d441a1494 | ||
|
|
5c18f67fed | ||
|
|
a7e3fd431d | ||
|
|
3435db68c8 | ||
|
|
f00ddcad10 | ||
|
|
988ed9dccd | ||
|
|
aab6a45cda | ||
|
|
afa0984b57 | ||
|
|
ee65d3918b | ||
|
|
67b76c8ced | ||
|
|
13b8b949f9 | ||
|
|
3a3f8e0756 | ||
|
|
6d49f5c926 | ||
|
|
c92ee71d42 | ||
|
|
07965598f5 | ||
|
|
7e84eaa4b3 | ||
|
|
fd0ebc7ec0 | ||
|
|
8ad7c2b89a | ||
|
|
75f34bdf50 | ||
|
|
2018a79fc2 | ||
|
|
76cad49446 | ||
|
|
d51bf05821 | ||
|
|
5814e9790f | ||
|
|
0d6e2539e3 | ||
|
|
55fc9dd00d | ||
|
|
47833067e5 | ||
|
|
8f74a2d1de | ||
|
|
3706b6b80c | ||
|
|
9c61182bc9 | ||
|
|
f317495e80 | ||
|
|
69874a2405 | ||
|
|
076a5cad89 | ||
|
|
af04e513a0 | ||
|
|
117843021e | ||
|
|
24b3674983 | ||
|
|
0bfba72739 | ||
|
|
ef18d60286 | ||
|
|
7e21428548 | ||
|
|
ac95878ead | ||
|
|
2947f64f3a | ||
|
|
dfb1f18e2d | ||
|
|
7e97141687 | ||
|
|
bfe952d910 | ||
|
|
fa4e083352 | ||
|
|
ccece77680 | ||
|
|
be4f90eca1 | ||
|
|
54dcbfae01 | ||
|
|
14bb11d702 | ||
|
|
d700f651b4 | ||
|
|
91c2ad8974 | ||
|
|
c115e9b0db | ||
|
|
8a092cfed4 | ||
|
|
cc238ecc60 | ||
|
|
08cd44ea03 | ||
|
|
a2c3c09564 | ||
|
|
645bd98c3a | ||
|
|
37e6ca7a30 | ||
|
|
c9de01b9d0 | ||
|
|
0e335f7ecc | ||
|
|
495a7a4289 | ||
|
|
5c0b482431 | ||
|
|
7c83c89643 | ||
|
|
2eeab7f556 | ||
|
|
f8a7f0ecb1 | ||
|
|
7b18caca9b | ||
|
|
9afed2295d | ||
|
|
ce387482bb | ||
|
|
e4b11a6dc1 | ||
|
|
c80e2538c4 | ||
|
|
4afad03fe9 | ||
|
|
cd48b8df0c | ||
|
|
729e893820 | ||
|
|
25fea331d0 | ||
|
|
bb9058f5ef | ||
|
|
aa286e4e63 | ||
|
|
914d50a1c5 | ||
|
|
a76221da33 | ||
|
|
fabea318d9 | ||
|
|
094631350d | ||
|
|
beded7c21f | ||
|
|
3e3b9fbbb8 | ||
|
|
a54dc76af1 | ||
|
|
ec38945c6d | ||
|
|
44330ddcff | ||
|
|
c0d0b01324 | ||
|
|
e8119880f3 | ||
|
|
8bafa7e43d | ||
|
|
5a4a7e6694 | ||
|
|
7e3881d618 | ||
|
|
0aac83b8c5 | ||
|
|
0bb5000f2a | ||
|
|
82ec1f8d10 | ||
|
|
d4b0f3bed4 | ||
|
|
f863be3412 | ||
|
|
11bb60fc80 | ||
|
|
a97960cb41 | ||
|
|
191f435539 | ||
|
|
898fa5579c | ||
|
|
f2320e907f | ||
|
|
a1c74c4a10 | ||
|
|
ad321976ff | ||
|
|
7bbe63a525 | ||
|
|
6d1af2f6b3 | ||
|
|
425590ae12 | ||
|
|
e54101165f | ||
|
|
3034b9ef92 | ||
|
|
b01d12dd26 | ||
|
|
bdbf8a8da0 | ||
|
|
2a4a3f42cd | ||
|
|
01343788d7 | ||
|
|
0bbab94841 | ||
|
|
ba8a9c7565 | ||
|
|
3736f492d3 | ||
|
|
b324e1a4f4 | ||
|
|
af4e843131 | ||
|
|
ac413a4d13 | ||
|
|
1e4a761c53 | ||
|
|
060f020d83 | ||
|
|
8077053100 | ||
|
|
d2e94b8188 | ||
|
|
dfec6afd6b | ||
|
|
1a125cc9e7 | ||
|
|
9988fb744c | ||
|
|
7055b352d4 | ||
|
|
75b969b1ad | ||
|
|
5909d546fb | ||
|
|
d21926db47 | ||
|
|
b066c3633a | ||
|
|
e7908c2af5 | ||
|
|
8498b71f0f | ||
|
|
4e9778f7d6 | ||
|
|
61765fb2d2 | ||
|
|
10bae7dcb0 | ||
|
|
0a98bd6cc7 | ||
|
|
d917144819 | ||
|
|
444be70796 | ||
|
|
668b4a0a9c | ||
|
|
6a2e24e7ff | ||
|
|
b76d5ce46b | ||
|
|
872a5fdc80 | ||
|
|
338ea1217c | ||
|
|
a24c183ec5 | ||
|
|
6c91cff672 | ||
|
|
bb78eff301 | ||
|
|
9c8d993342 | ||
|
|
455db8f37d | ||
|
|
59e56ac949 | ||
|
|
00dbdaf071 | ||
|
|
3e6c41593d | ||
|
|
9fe0c07eb4 | ||
|
|
3447a98bee | ||
|
|
26ff2beea9 | ||
|
|
9ff0045698 | ||
|
|
d62f221309 | ||
|
|
e1b7805aef | ||
|
|
83371fca47 | ||
|
|
4f2f663b3b | ||
|
|
f62c30f81d | ||
|
|
9e6d5124ad | ||
|
|
29616a8b5d | ||
|
|
b4a0f8484d | ||
|
|
d4917cd4ab | ||
|
|
bd8515fa59 | ||
|
|
50a2b90d66 | ||
|
|
487b7330ab | ||
|
|
a55bf3044d | ||
|
|
7ad5b63cb6 | ||
|
|
8e3a265ce9 | ||
|
|
770a2c3e1e | ||
|
|
04ef8d824e | ||
|
|
2ebc0902c1 | ||
|
|
a7b7cc888b | ||
|
|
cb13ddb464 | ||
|
|
d8ff8b042f | ||
|
|
9eb00df6b7 | ||
|
|
4cde2d96be | ||
|
|
fb38516a86 | ||
|
|
e5c692f0cf | ||
|
|
0367fc1e90 | ||
|
|
9778444706 | ||
|
|
4c6c5b86ad | ||
|
|
f37379908c | ||
|
|
8a4b96c9c9 | ||
|
|
802f047341 | ||
|
|
a89b301425 | ||
|
|
afbd4a984d | ||
|
|
737cf9412c | ||
|
|
32f3b3a309 | ||
|
|
d9c8079eae | ||
|
|
208021ee1c | ||
|
|
0a4abd0e46 | ||
|
|
85a9d82132 | ||
|
|
61adaf0bdd | ||
|
|
a31e70b2ec | ||
|
|
3eb445cd0e | ||
|
|
d9c5f16e05 | ||
|
|
c44a99e304 | ||
|
|
5b606c035f | ||
|
|
0c12157c61 | ||
|
|
0ee79a5fab | ||
|
|
093da22577 | ||
|
|
fa41f94ae7 | ||
|
|
c33b3b4807 | ||
|
|
044cf3923e | ||
|
|
7050dcc37f | ||
|
|
b4698528ef | ||
|
|
dc583ece4f | ||
|
|
1dd4db0034 | ||
|
|
fc31d1aae7 | ||
|
|
186784d0fd | ||
|
|
be4628d235 | ||
|
|
53c4195932 | ||
|
|
f6e77f3c1b | ||
|
|
7a888fbbae | ||
|
|
c26b7e0a0a | ||
|
|
ef9b733631 | ||
|
|
881196188c | ||
|
|
a666a6b126 | ||
|
|
d6493ed64d | ||
|
|
129db95426 | ||
|
|
1a969d884e | ||
|
|
f228239834 | ||
|
|
6e15140583 | ||
|
|
975d4487bf | ||
|
|
cc583dd79e | ||
|
|
6b70792cae | ||
|
|
5467f0256a | ||
|
|
88cd52fd0f | ||
|
|
e637394370 | ||
|
|
c4bbdb2212 | ||
|
|
d93deacb4b | ||
|
|
16c9aa99a9 | ||
|
|
1448e593e9 | ||
|
|
815bb336be | ||
|
|
4bdcc4dd5e | ||
|
|
5ab22d043d | ||
|
|
47010f458c | ||
|
|
58b9e5e66e | ||
|
|
a7d1ea455b | ||
|
|
f37d0c746d | ||
|
|
1b7369cf0d | ||
|
|
f7c80834cb | ||
|
|
9f484c1d39 | ||
|
|
b73b8fef77 | ||
|
|
d9ba61c30a | ||
|
|
33ea53ee8f | ||
|
|
7c3e7dab60 | ||
|
|
d27e474b66 | ||
|
|
d3f31acc5c | ||
|
|
1172acdc04 | ||
|
|
3a0f591c8c | ||
|
|
df934334a2 | ||
|
|
d8380ebb98 | ||
|
|
41f46848b9 | ||
|
|
c678608105 | ||
|
|
e7ba8dbe15 | ||
|
|
cfc09ca270 | ||
|
|
0f95bfd279 | ||
|
|
b5a04debf5 | ||
|
|
498f2c02be | ||
|
|
92669a0d59 | ||
|
|
0ead3b477f | ||
|
|
05380828c6 | ||
|
|
fca586ff21 | ||
|
|
d40563ea9f | ||
|
|
2e2358d850 | ||
|
|
bae0a888c9 | ||
|
|
8f0e537d34 | ||
|
|
5668bc561d | ||
|
|
d4f2f7944c | ||
|
|
60076ef492 | ||
|
|
bc0e727bd7 | ||
|
|
ea87166e44 | ||
|
|
27b0d18f0d | ||
|
|
1628fdeaee | ||
|
|
2535fdcb12 | ||
|
|
3777a4cf02 | ||
|
|
cecd6011d6 | ||
|
|
3e001a2809 | ||
|
|
1a8abaa2ac | ||
|
|
fa37d528b3 | ||
|
|
09f7cd7e12 | ||
|
|
66d67b18d7 | ||
|
|
fe21d2edb9 | ||
|
|
74dd48320e | ||
|
|
9b0e2a87e8 | ||
|
|
4022c13b31 | ||
|
|
25db02368a | ||
|
|
db951f1d9e | ||
|
|
d03422d004 | ||
|
|
4fb15d8733 | ||
|
|
c0293b889c | ||
|
|
26c655ff3c | ||
|
|
712ed3f738 | ||
|
|
e6c78054c4 | ||
|
|
7f674e6f63 | ||
|
|
3aa7a6ee69 | ||
|
|
6378a96b4d | ||
|
|
b74590f381 | ||
|
|
2f8b782a1f | ||
|
|
c89080deb4 | ||
|
|
e44b07df66 | ||
|
|
afca7ae0cc | ||
|
|
3a9c56deb2 | ||
|
|
5f72778ade | ||
|
|
d934b67c72 | ||
|
|
241cca5b70 | ||
|
|
39ec23bd31 | ||
|
|
62839b6fa0 | ||
|
|
5ae8ccbbdd | ||
|
|
af2ffb7e5e | ||
|
|
d1f2679c45 | ||
|
|
3bcaeda737 | ||
|
|
4983c6d302 | ||
|
|
63e6aaf1fe | ||
|
|
b2332e796e | ||
|
|
cd8ec83881 | ||
|
|
7ef86e99dc | ||
|
|
70ca824e88 | ||
|
|
690a1fe64c | ||
|
|
38c0233496 | ||
|
|
ff1863f37e | ||
|
|
eac869dde5 | ||
|
|
88f97bd2b6 | ||
|
|
fdd7ac7bbf | ||
|
|
8038a9b488 | ||
|
|
37311f8145 | ||
|
|
d7dc66da03 | ||
|
|
51154c1d54 | ||
|
|
13c3169b41 | ||
|
|
fd62efc745 | ||
|
|
5575c5d214 | ||
|
|
294c5548b9 | ||
|
|
fd9ad38900 | ||
|
|
21e9945c97 | ||
|
|
f4283982b3 | ||
|
|
6086f27263 | ||
|
|
0dfa1d969f | ||
|
|
1ff58adcef | ||
|
|
641ec7e097 | ||
|
|
8ee33950e6 | ||
|
|
b3123b150f | ||
|
|
20b952b4cd | ||
|
|
aa5ccfb8bd | ||
|
|
ef9ed1ebea | ||
|
|
117aed49e3 | ||
|
|
9bbf7f668a | ||
|
|
afdfa6181b | ||
|
|
6c11e0ced7 | ||
|
|
399ce2e35c | ||
|
|
e575c2e769 | ||
|
|
56b2347a30 | ||
|
|
70954acf3d | ||
|
|
13aa60529f | ||
|
|
7474f01193 | ||
|
|
bd9883baaf | ||
|
|
313db5643f | ||
|
|
93a6d7a476 | ||
|
|
d221d90972 | ||
|
|
30fd5dcfb8 | ||
|
|
c79680344d | ||
|
|
ad544a7d24 | ||
|
|
1cd606b879 | ||
|
|
39f74c0f52 | ||
|
|
8feea28a19 | ||
|
|
b73246bdfd | ||
|
|
36a418b6ac | ||
|
|
43e8804eb4 | ||
|
|
8790e5a0eb | ||
|
|
5e39514251 | ||
|
|
b28950f310 | ||
|
|
3ebee252aa | ||
|
|
720fb4af63 | ||
|
|
af19950dfa | ||
|
|
149be249fa | ||
|
|
0cf86806b2 | ||
|
|
cb847cab82 | ||
|
|
a89fd31844 | ||
|
|
870948306d | ||
|
|
ec49d1f844 | ||
|
|
e3d84a5daf | ||
|
|
79b5ad0754 | ||
|
|
24b0d72d96 | ||
|
|
084cd8751f | ||
|
|
3d77e0a3a9 | ||
|
|
06bbae6d14 | ||
|
|
5f22493361 | ||
|
|
56a4caf39b | ||
|
|
83056f743d | ||
|
|
6743ff96a9 | ||
|
|
1f3c4f4ac3 | ||
|
|
7766829fb1 | ||
|
|
175b219246 | ||
|
|
48aee84547 | ||
|
|
d587b326b5 | ||
|
|
ac099d9e6f | ||
|
|
913ab4627c | ||
|
|
be868ee107 | ||
|
|
36b1bb65af | ||
|
|
4a752bb951 | ||
|
|
3dabb4e89a | ||
|
|
e2474f4e66 | ||
|
|
f4ee0b0387 | ||
|
|
5df1f9f9d2 | ||
|
|
3368255473 | ||
|
|
1cbb2d6aa4 | ||
|
|
bc0e0088a0 | ||
|
|
a6a9f763db | ||
|
|
8dcb009e5b | ||
|
|
9f0f44b470 | ||
|
|
67aa84760d | ||
|
|
b05c937151 | ||
|
|
3322bbd681 | ||
|
|
a1acf0b05d | ||
|
|
66bdc61e3d | ||
|
|
dd2bd2f989 | ||
|
|
6f18a5de92 | ||
|
|
1d542d4396 | ||
|
|
07fb01d9db | ||
|
|
8a5d4a0f8f | ||
|
|
48069f99cd | ||
|
|
1eaf6cec39 | ||
|
|
f0c9de9e50 | ||
|
|
ef42bcc525 | ||
|
|
e7995ad344 | ||
|
|
6e3c2506c9 | ||
|
|
5473e2733c | ||
|
|
006a7044f1 | ||
|
|
c647197b8c | ||
|
|
62735ebfe2 | ||
|
|
8ff00fd8fe | ||
|
|
bd586575b3 | ||
|
|
f14f7368d7 | ||
|
|
6adcd1fdf2 | ||
|
|
6e99beb335 | ||
|
|
6689d45a4f | ||
|
|
6d82a5851b | ||
|
|
337ba1f8f6 | ||
|
|
bf7b148592 | ||
|
|
a7f724a804 | ||
|
|
7c06b65def | ||
|
|
7286c7250c | ||
|
|
4e841d3087 | ||
|
|
2ce704dd40 | ||
|
|
6279610691 | ||
|
|
297d53dac8 | ||
|
|
6f1300f819 | ||
|
|
02a015a1b6 | ||
|
|
5c11a30b46 | ||
|
|
0dc3b9f056 | ||
|
|
c0d8aaf73a | ||
|
|
2a0019457d | ||
|
|
6dec2a9222 | ||
|
|
f71295e640 | ||
|
|
c1aedc5bb8 | ||
|
|
d6a9f6d3f9 | ||
|
|
ba6840d978 | ||
|
|
86b08258dd | ||
|
|
9ccff4ab2e | ||
|
|
cf310be1c8 | ||
|
|
d8e80bb0c8 | ||
|
|
9206182e15 | ||
|
|
d25eaa48d0 | ||
|
|
5a2c91959a | ||
|
|
193d54153d | ||
|
|
510634bc04 | ||
|
|
954f1fe605 | ||
|
|
764b53275f | ||
|
|
44fc1be270 | ||
|
|
5ef170020d | ||
|
|
5f7099fc89 | ||
|
|
fe08fef015 | ||
|
|
edb744f654 | ||
|
|
5ff5b46896 | ||
|
|
49e67ac46c | ||
|
|
5024973896 | ||
|
|
7dce6ad6c4 | ||
|
|
779229a907 | ||
|
|
af23ed027a | ||
|
|
06412865bb | ||
|
|
fab311b53a | ||
|
|
bc602dbf3c | ||
|
|
0fb207bb59 | ||
|
|
c751bc78d8 | ||
|
|
c9038ad0b3 | ||
|
|
b4699cd8a3 | ||
|
|
d0a87d8e3c | ||
|
|
78dbabf901 | ||
|
|
ad771ae6a0 | ||
|
|
92bc2962b8 | ||
|
|
836754d7ad | ||
|
|
6576290160 | ||
|
|
db88e63148 | ||
|
|
f2d2102127 | ||
|
|
b9bf453731 | ||
|
|
fb98247a8d | ||
|
|
4bd927cbcf | ||
|
|
3725d5703e | ||
|
|
bf0cc19c8f | ||
|
|
8af137545f | ||
|
|
3d71ebcc5f | ||
|
|
c6fcb833b3 | ||
|
|
c926f23c09 | ||
|
|
21ac1f7204 | ||
|
|
05ff7bd261 | ||
|
|
b2109351ff | ||
|
|
0bd13727de | ||
|
|
e1d6d04b48 | ||
|
|
9dbbb6f2f6 | ||
|
|
836170e5b6 | ||
|
|
d4fabff7f4 | ||
|
|
b21bef0b98 | ||
|
|
533ed97fc1 | ||
|
|
e7e5a1ded8 | ||
|
|
4e95030e55 | ||
|
|
b331a8c730 | ||
|
|
2923051a12 | ||
|
|
fe96137c56 | ||
|
|
addc4de735 | ||
|
|
2460ba9b67 | ||
|
|
62be27ec62 | ||
|
|
8515d41fe3 | ||
|
|
d4d69d6990 | ||
|
|
0027c46313 | ||
|
|
ca2001040b | ||
|
|
d6725100ac | ||
|
|
503ce29c84 | ||
|
|
87444cd2b8 | ||
|
|
31eca9e8bc | ||
|
|
822afe08b5 | ||
|
|
cfb78b0edb | ||
|
|
65fd7d3efe | ||
|
|
e8241fb7c9 | ||
|
|
259d51bdc8 | ||
|
|
f6fb52afbf | ||
|
|
8089b87bbb | ||
|
|
578162425d | ||
|
|
dbad63f155 | ||
|
|
da8a733899 | ||
|
|
8f58f1998d | ||
|
|
c43fe5187f | ||
|
|
0993fe45f6 | ||
|
|
ba86b49952 | ||
|
|
0b34c340fc | ||
|
|
d513f66170 | ||
|
|
320fb776ea | ||
|
|
1a39957dbb | ||
|
|
b5abe4025a | ||
|
|
55f4dcc460 | ||
|
|
ef4a83f739 | ||
|
|
133f2b705f | ||
|
|
83fe58e003 | ||
|
|
481f926b17 | ||
|
|
788eae432a | ||
|
|
b7936c4ed2 | ||
|
|
750c8df003 | ||
|
|
276c39aba4 | ||
|
|
90e25eeb76 | ||
|
|
56676701ae | ||
|
|
bcccf301f0 | ||
|
|
e343ba5635 | ||
|
|
66fe5ec4fd | ||
|
|
f2a884ec30 | ||
|
|
d31aa7cf88 | ||
|
|
9f19a8e605 | ||
|
|
23ef39a2d9 | ||
|
|
dda82c49b0 | ||
|
|
c91c90a2a6 | ||
|
|
5794cdf8fa | ||
|
|
01a4748d6b | ||
|
|
a8762522c8 | ||
|
|
adef52a938 | ||
|
|
c8fbf87fc8 | ||
|
|
f63e3618c2 | ||
|
|
b18d7bfeac | ||
|
|
076b98ff00 | ||
|
|
6999685bba | ||
|
|
f1c02bbd46 | ||
|
|
2caa837537 | ||
|
|
e1ddbf226a | ||
|
|
7cb8c114c2 | ||
|
|
5945630870 | ||
|
|
ccadac4bb3 | ||
|
|
15b77f6b8a | ||
|
|
9bf76037aa | ||
|
|
d0d973b797 | ||
|
|
c1e2bc9ea9 | ||
|
|
0eef21e2ef | ||
|
|
461aa579c2 | ||
|
|
da442c47f6 | ||
|
|
491d37ea67 | ||
|
|
7e087d18ee | ||
|
|
5c75a6490b | ||
|
|
750b6aec59 | ||
|
|
d138e29a53 | ||
|
|
a7febba9c8 | ||
|
|
f0f97baa65 | ||
|
|
c2dc94507e | ||
|
|
7c0aaab463 | ||
|
|
5dcac604d1 | ||
|
|
96746b7c98 | ||
|
|
2ae50b7398 | ||
|
|
3c905c5072 | ||
|
|
5b926f57cc | ||
|
|
b9788a5dba | ||
|
|
7078f09872 | ||
|
|
1aa7808c02 | ||
|
|
ba8a51101d | ||
|
|
de69c970aa | ||
|
|
fe5fa6a85d | ||
|
|
de74febf64 | ||
|
|
3b6483e819 | ||
|
|
dcd6ad0983 | ||
|
|
567d979243 | ||
|
|
c81a8681b0 | ||
|
|
31cde90819 | ||
|
|
a77bf5bf21 | ||
|
|
4befa80eb8 | ||
|
|
52584662a8 | ||
|
|
de147f63e9 | ||
|
|
96c33dec7a | ||
|
|
3c0b5f0867 | ||
|
|
c252f11c1f | ||
|
|
f1f040397d | ||
|
|
418e9937cb | ||
|
|
c34664429c | ||
|
|
6fe5928297 | ||
|
|
eee99730d1 | ||
|
|
9394760e3b | ||
|
|
a0b0e1a0ac | ||
|
|
e2d7e6e86c |
4
.envrc
4
.envrc
@@ -1,11 +1,13 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then
|
||||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4="
|
||||||
fi
|
fi
|
||||||
|
|
||||||
watch_file .direnv/selected-shell
|
watch_file .direnv/selected-shell
|
||||||
|
watch_file formatter.nix
|
||||||
|
|
||||||
if [ -e .direnv/selected-shell ]; then
|
if [ -e .direnv/selected-shell ]; then
|
||||||
use flake .#$(cat .direnv/selected-shell)
|
use flake ".#$(cat .direnv/selected-shell)"
|
||||||
else
|
else
|
||||||
use flake
|
use flake
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
name: deploy
|
name: deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,8 +1,10 @@
|
|||||||
.direnv
|
.direnv
|
||||||
|
**/.nixos-test-history
|
||||||
***/.hypothesis
|
***/.hypothesis
|
||||||
out.log
|
out.log
|
||||||
.coverage.*
|
.coverage.*
|
||||||
**/qubeclan
|
**/qubeclan
|
||||||
|
pkgs/repro-hook
|
||||||
**/testdir
|
**/testdir
|
||||||
democlan
|
democlan
|
||||||
example_clan
|
example_clan
|
||||||
@@ -13,6 +15,7 @@ nixos.qcow2
|
|||||||
**/*.glade~
|
**/*.glade~
|
||||||
/docs/out
|
/docs/out
|
||||||
|
|
||||||
|
|
||||||
# dream2nix
|
# dream2nix
|
||||||
.dream2nix
|
.dream2nix
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,5 @@ Run a local server:
|
|||||||
```shell-session
|
```shell-session
|
||||||
mkdocs serve
|
mkdocs serve
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Open http://localhost:8000/ in your browser.
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Clan Core Repository
|
# Clan core repository
|
||||||
|
|
||||||
Welcome to the Clan Core Repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
|
Welcome to the Clan core repository, the heart of the [clan.lol](https://clan.lol/) project! This monorepo is the foundation of Clan, a revolutionary open-source project aimed at restoring fun, freedom, and functionality to computing. Here, you'll find all the essential packages, NixOS modules, CLI tools, and tests needed to contribute to and work with the Clan project. Clan leverages the Nix system to ensure reliability, security, and seamless management of digital environments, putting the power back into the hands of users.
|
||||||
|
|
||||||
## Why Clan?
|
## Why Clan?
|
||||||
|
|
||||||
@@ -14,13 +14,13 @@ Our mission is simple: to democratize computing by providing tools that empower
|
|||||||
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
||||||
- **Intuitive Secret Management:** Simplified encryption and password management processes.
|
- **Intuitive Secret Management:** Simplified encryption and password management processes.
|
||||||
|
|
||||||
## Getting Started with Clan
|
## Getting started with Clan
|
||||||
|
|
||||||
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
|
If you're new to Clan and eager to dive in, start with our quickstart guide and explore the core functionalities that Clan offers:
|
||||||
|
|
||||||
- **Quickstart Guide**: Check out [getting started](https://docs.clan.lol/#starting-with-a-new-clan-project)<!-- [docs/site/index.md](docs/site/index.md) --> to get up and running with Clan in no time.
|
- **Quickstart Guide**: Check out [getting started](https://docs.clan.lol/#starting-with-a-new-clan-project)<!-- [docs/site/index.md](docs/site/index.md) --> to get up and running with Clan in no time.
|
||||||
|
|
||||||
### Managing Secrets
|
### Managing secrets
|
||||||
|
|
||||||
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
|
In the Clan ecosystem, security is paramount. Learn how to handle secrets effectively:
|
||||||
|
|
||||||
@@ -32,14 +32,14 @@ The Clan project thrives on community contributions. We welcome everyone to cont
|
|||||||
|
|
||||||
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
- **Contribution Guidelines**: Make a meaningful impact by following the steps in [contributing](https://docs.clan.lol/contributing/contributing/)<!-- [contributing.md](docs/CONTRIBUTING.md) -->.
|
||||||
|
|
||||||
## Join the Revolution
|
## Join the revolution
|
||||||
|
|
||||||
Clan is more than a tool; it's a movement towards a better digital future. By contributing to the Clan project, you're part of changing technology for the better, together.
|
Clan is more than a tool; it's a movement towards a better digital future. By contributing to the Clan project, you're part of changing technology for the better, together.
|
||||||
|
|
||||||
### Community and Support
|
### Community and support
|
||||||
|
|
||||||
Connect with us and the Clan community for support and discussion:
|
Connect with us and the Clan community for support and discussion:
|
||||||
|
|
||||||
- [Matrix channel](https://matrix.to/#/#clan:lassul.us) for live discussions.
|
- [Matrix channel](https://matrix.to/#/#clan:clan.lol) for live discussions.
|
||||||
- IRC bridges (coming soon) for real-time chat support.
|
- IRC bridges (coming soon) for real-time chat support.
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
self.clanModules.localbackup
|
self.clanModules.localbackup
|
||||||
self.clanModules.sshd
|
self.clanModules.sshd
|
||||||
];
|
];
|
||||||
clan.networking.targetHost = "machine";
|
clan.core.networking.targetHost = "machine";
|
||||||
networking.hostName = "machine";
|
networking.hostName = "machine";
|
||||||
services.openssh.settings.UseDns = false;
|
services.openssh.settings.UseDns = false;
|
||||||
|
|
||||||
@@ -68,17 +68,9 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
clanCore.facts.secretStore = "vm";
|
clan.core.facts.secretStore = "vm";
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||||
self.packages.${pkgs.system}.clan-cli
|
|
||||||
(pkgs.writeShellScriptBin "pre-restore-command" ''
|
|
||||||
touch /var/test-service/pre-restore-command
|
|
||||||
'')
|
|
||||||
(pkgs.writeShellScriptBin "post-restore-command" ''
|
|
||||||
touch /var/test-service/post-restore-command
|
|
||||||
'')
|
|
||||||
];
|
|
||||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||||
nix.settings = {
|
nix.settings = {
|
||||||
substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
@@ -87,11 +79,18 @@
|
|||||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
};
|
};
|
||||||
system.extraDependencies = dependencies;
|
system.extraDependencies = dependencies;
|
||||||
clanCore.state.test-backups.folders = [ "/var/test-backups" ];
|
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
||||||
|
|
||||||
clanCore.state.test-service = {
|
clan.core.state.test-service = {
|
||||||
preRestoreCommand = "pre-restore-command";
|
preBackupScript = ''
|
||||||
postRestoreCommand = "post-restore-command";
|
touch /var/test-service/pre-backup-command
|
||||||
|
'';
|
||||||
|
preRestoreScript = ''
|
||||||
|
touch /var/test-service/pre-restore-command
|
||||||
|
'';
|
||||||
|
postRestoreScript = ''
|
||||||
|
touch /var/test-service/post-restore-command
|
||||||
|
'';
|
||||||
folders = [ "/var/test-service" ];
|
folders = [ "/var/test-service" ];
|
||||||
};
|
};
|
||||||
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
|
clan.borgbackup.destinations.test-backup.repo = "borg@machine:.";
|
||||||
@@ -145,14 +144,14 @@
|
|||||||
machine.succeed("echo testing > /var/test-backups/somefile")
|
machine.succeed("echo testing > /var/test-backups/somefile")
|
||||||
|
|
||||||
# create
|
# create
|
||||||
machine.succeed("clan --debug --flake ${self} backups create test-backup")
|
machine.succeed("clan backups create --debug --flake ${self} test-backup")
|
||||||
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
|
machine.wait_until_succeeds("! systemctl is-active borgbackup-job-test-backup >&2")
|
||||||
machine.succeed("test -f /run/mount-external-disk")
|
machine.succeed("test -f /run/mount-external-disk")
|
||||||
machine.succeed("test -f /run/unmount-external-disk")
|
machine.succeed("test -f /run/unmount-external-disk")
|
||||||
|
|
||||||
# list
|
# list
|
||||||
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
backup_id = json.loads(machine.succeed("borg-job-test-backup list --json"))["archives"][0]["archive"]
|
||||||
out = machine.succeed("clan --debug --flake ${self} backups list test-backup").strip()
|
out = machine.succeed("clan backups list --debug --flake ${self} test-backup").strip()
|
||||||
print(out)
|
print(out)
|
||||||
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
assert backup_id in out, f"backup {backup_id} not found in {out}"
|
||||||
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
localbackup_id = "hdd::/mnt/external-disk/snapshot.0"
|
||||||
@@ -160,17 +159,19 @@
|
|||||||
|
|
||||||
## borgbackup restore
|
## borgbackup restore
|
||||||
machine.succeed("rm -f /var/test-backups/somefile")
|
machine.succeed("rm -f /var/test-backups/somefile")
|
||||||
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup borgbackup 'test-backup::borg@machine:.::{backup_id}' >&2")
|
||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
|
machine.succeed("test -f /var/test-service/pre-backup-command")
|
||||||
|
|
||||||
## localbackup restore
|
## localbackup restore
|
||||||
machine.succeed("rm -f /var/test-backups/somefile /var/test-service/{pre,post}-restore-command")
|
machine.succeed("rm -rf /var/test-backups/somefile /var/test-service/ && mkdir -p /var/test-service")
|
||||||
machine.succeed(f"clan --debug --flake ${self} backups restore test-backup localbackup '{localbackup_id}' >&2")
|
machine.succeed(f"clan backups restore --debug --flake ${self} test-backup localbackup '{localbackup_id}' >&2")
|
||||||
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
assert machine.succeed("cat /var/test-backups/somefile").strip() == "testing", "restore failed"
|
||||||
machine.succeed("test -f /var/test-service/pre-restore-command")
|
machine.succeed("test -f /var/test-service/pre-restore-command")
|
||||||
machine.succeed("test -f /var/test-service/post-restore-command")
|
machine.succeed("test -f /var/test-service/post-restore-command")
|
||||||
|
machine.succeed("test -f /var/test-service/pre-backup-command")
|
||||||
'';
|
'';
|
||||||
} { inherit pkgs self; };
|
} { inherit pkgs self; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,9 +16,9 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
clanCore.state.testState.folders = [ "/etc/state" ];
|
clan.core.state.testState.folders = [ "/etc/state" ];
|
||||||
environment.etc.state.text = "hello world";
|
environment.etc.state.text = "hello world";
|
||||||
systemd.tmpfiles.settings."vmsecrets" = {
|
systemd.tmpfiles.settings."vmsecrets" = {
|
||||||
"/etc/secrets/borgbackup.ssh" = {
|
"/etc/secrets/borgbackup.ssh" = {
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
clanCore.facts.secretStore = "vm";
|
clan.core.facts.secretStore = "vm";
|
||||||
|
|
||||||
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
self.clanModules.deltachat
|
self.clanModules.deltachat
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
22
checks/devshell/flake-module.nix
Normal file
22
checks/devshell/flake-module.nix
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ self', pkgs, ... }:
|
||||||
|
{
|
||||||
|
checks.devshell =
|
||||||
|
pkgs.runCommand "check-devshell-not-depends-on-clan-cli"
|
||||||
|
{
|
||||||
|
exportReferencesGraph = [
|
||||||
|
"graph"
|
||||||
|
self'.devShells.default
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
if grep -q "${self'.packages.clan-cli}" ./graph; then
|
||||||
|
echo "devshell depends on clan-cli, which is not allowed";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
mkdir $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
{ self, ... }:
|
{ self, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./impure/flake-module.nix
|
|
||||||
./backups/flake-module.nix
|
./backups/flake-module.nix
|
||||||
./installation/flake-module.nix
|
./devshell/flake-module.nix
|
||||||
./flash/flake-module.nix
|
./flash/flake-module.nix
|
||||||
|
./impure/flake-module.nix
|
||||||
|
./installation/flake-module.nix
|
||||||
];
|
];
|
||||||
perSystem =
|
perSystem =
|
||||||
{
|
{
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
options =
|
options =
|
||||||
(pkgs.nixos {
|
(pkgs.nixos {
|
||||||
imports = [ self.nixosModules.clanCore ];
|
imports = [ self.nixosModules.clanCore ];
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
}).options;
|
}).options;
|
||||||
warningsAreErrors = false;
|
warningsAreErrors = false;
|
||||||
};
|
};
|
||||||
@@ -40,10 +41,12 @@
|
|||||||
secrets = import ./secrets nixosTestArgs;
|
secrets = import ./secrets nixosTestArgs;
|
||||||
container = import ./container nixosTestArgs;
|
container = import ./container nixosTestArgs;
|
||||||
deltachat = import ./deltachat nixosTestArgs;
|
deltachat = import ./deltachat nixosTestArgs;
|
||||||
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
|
||||||
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
|
||||||
borgbackup = import ./borgbackup nixosTestArgs;
|
borgbackup = import ./borgbackup nixosTestArgs;
|
||||||
|
matrix-synapse = import ./matrix-synapse nixosTestArgs;
|
||||||
|
mumble = import ./mumble nixosTestArgs;
|
||||||
syncthing = import ./syncthing nixosTestArgs;
|
syncthing = import ./syncthing nixosTestArgs;
|
||||||
|
zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs;
|
||||||
|
postgresql = import ./postgresql nixosTestArgs;
|
||||||
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,50 @@
|
|||||||
{ ... }:
|
{ self, ... }:
|
||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
{ ... }:
|
|
||||||
{
|
{
|
||||||
# checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
nodes,
|
||||||
# flash = (import ../lib/test-base.nix) {
|
pkgs,
|
||||||
# name = "flash";
|
lib,
|
||||||
# nodes.target = {
|
...
|
||||||
# virtualisation.emptyDiskImages = [ 4096 ];
|
}:
|
||||||
# virtualisation.memorySize = 3000;
|
let
|
||||||
# environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
dependencies = [
|
||||||
# environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
pkgs.disko
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.toplevel
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.build.diskoScript.drvPath
|
||||||
|
self.clanInternals.machines.${pkgs.hostPlatform.system}.test_install_machine.config.system.clan.deployment.file
|
||||||
|
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Currently disabled...
|
||||||
|
checks = pkgs.lib.mkIf (pkgs.stdenv.isLinux) {
|
||||||
|
flash = (import ../lib/test-base.nix) {
|
||||||
|
name = "flash";
|
||||||
|
nodes.target = {
|
||||||
|
virtualisation.emptyDiskImages = [ 4096 ];
|
||||||
|
virtualisation.memorySize = 3000;
|
||||||
|
environment.systemPackages = [ self.packages.${pkgs.system}.clan-cli ];
|
||||||
|
environment.etc."install-closure".source = "${closureInfo}/store-paths";
|
||||||
|
|
||||||
# nix.settings = {
|
nix.settings = {
|
||||||
# substituters = lib.mkForce [ ];
|
substituters = lib.mkForce [ ];
|
||||||
# hashed-mirrors = null;
|
hashed-mirrors = null;
|
||||||
# connect-timeout = lib.mkForce 3;
|
connect-timeout = lib.mkForce 3;
|
||||||
# flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||||
# experimental-features = [
|
experimental-features = [
|
||||||
# "nix-command"
|
"nix-command"
|
||||||
# "flakes"
|
"flakes"
|
||||||
# ];
|
];
|
||||||
# };
|
};
|
||||||
# };
|
};
|
||||||
# testScript = ''
|
testScript = ''
|
||||||
# start_all()
|
start_all()
|
||||||
# machine.succeed("clan --debug --flake ${../..} flash --yes --disk main /dev/vdb test_install_machine")
|
|
||||||
# '';
|
machine.succeed("clan flash --debug --flake ${../..} --yes --disk main /dev/vdb test_install_machine")
|
||||||
# } { inherit pkgs self; };
|
'';
|
||||||
# };
|
} { inherit pkgs self; };
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
{ pkgs, lib, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
self',
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
# a script that executes all other checks
|
# a script that executes all other checks
|
||||||
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
||||||
@@ -10,14 +15,21 @@
|
|||||||
unset CLAN_DIR
|
unset CLAN_DIR
|
||||||
|
|
||||||
export PATH="${
|
export PATH="${
|
||||||
lib.makeBinPath [
|
lib.makeBinPath (
|
||||||
pkgs.gitMinimal
|
[
|
||||||
pkgs.nix
|
pkgs.gitMinimal
|
||||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
pkgs.nix
|
||||||
]
|
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
||||||
|
]
|
||||||
|
++ self'.packages.clan-cli-full.runtimeDependencies
|
||||||
|
)
|
||||||
}"
|
}"
|
||||||
ROOT=$(git rev-parse --show-toplevel)
|
ROOT=$(git rev-parse --show-toplevel)
|
||||||
cd "$ROOT/pkgs/clan-cli"
|
cd "$ROOT/pkgs/clan-cli"
|
||||||
|
|
||||||
|
# this disables dynamic dependency loading in clan-cli
|
||||||
|
export CLAN_NO_DYNAMIC_DEPS=1
|
||||||
|
|
||||||
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -s -m impure ./tests $@"
|
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -s -m impure ./tests $@"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{ self, lib, ... }:
|
{ self, lib, ... }:
|
||||||
{
|
{
|
||||||
clan.machines.test_install_machine = {
|
clan.machines.test_install_machine = {
|
||||||
clan.networking.targetHost = "test_install_machine";
|
clan.core.networking.targetHost = "test_install_machine";
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/null";
|
fileSystems."/".device = lib.mkDefault "/dev/vdb";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/null";
|
boot.loader.grub.device = lib.mkDefault "/dev/vdb";
|
||||||
|
|
||||||
imports = [ self.nixosModules.test_install_machine ];
|
imports = [ self.nixosModules.test_install_machine ];
|
||||||
};
|
};
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
{ lib, modulesPath, ... }:
|
{ lib, modulesPath, ... }:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
self.clanModules.disk-layouts
|
"${self}/nixosModules/disk-layouts"
|
||||||
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
(modulesPath + "/testing/test-instrumentation.nix") # we need these 2 modules always to be able to run the tests
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
];
|
];
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
client.succeed("${pkgs.coreutils}/bin/install -Dm 600 ${../lib/ssh/privkey} /root/.ssh/id_ed25519")
|
||||||
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
client.wait_until_succeeds("ssh -o StrictHostKeyChecking=accept-new -v root@target hostname")
|
||||||
|
|
||||||
client.succeed("clan --debug --flake ${../..} machines install --yes test_install_machine root@target >&2")
|
client.succeed("clan machines install --debug --flake ${../..} --yes test_install_machine root@target >&2")
|
||||||
try:
|
try:
|
||||||
target.shutdown()
|
target.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class Machine:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Always run command with shell opts
|
# Always run command with shell opts
|
||||||
command = f"set -euo pipefail; {command}"
|
command = f"set -eo pipefail; source /etc/profile; set -u; {command}"
|
||||||
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ in
|
|||||||
hostPkgs = pkgs;
|
hostPkgs = pkgs;
|
||||||
# speed-up evaluation
|
# speed-up evaluation
|
||||||
defaults = {
|
defaults = {
|
||||||
|
nix.package = pkgs.nixVersions.latest;
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
boot.isContainer = true;
|
boot.isContainer = true;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ in
|
|||||||
defaults = {
|
defaults = {
|
||||||
documentation.enable = lib.mkDefault false;
|
documentation.enable = lib.mkDefault false;
|
||||||
nix.settings.min-free = 0;
|
nix.settings.min-free = 0;
|
||||||
|
nix.package = pkgs.nixVersions.latest;
|
||||||
};
|
};
|
||||||
|
|
||||||
# to accept external dependencies such as disko
|
# to accept external dependencies such as disko
|
||||||
|
|||||||
@@ -4,26 +4,61 @@
|
|||||||
name = "matrix-synapse";
|
name = "matrix-synapse";
|
||||||
|
|
||||||
nodes.machine =
|
nodes.machine =
|
||||||
{ self, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
self,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
self.clanModules.matrix-synapse
|
self.clanModules.matrix-synapse
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
clan.matrix-synapse = {
|
|
||||||
enable = true;
|
|
||||||
domain = "clan.test";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# secret override
|
|
||||||
clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path = "${./synapse-registration_shared_secret}";
|
|
||||||
services.nginx.virtualHosts."matrix.clan.test" = {
|
services.nginx.virtualHosts."matrix.clan.test" = {
|
||||||
enableACME = lib.mkForce false;
|
enableACME = lib.mkForce false;
|
||||||
forceSSL = lib.mkForce false;
|
forceSSL = lib.mkForce false;
|
||||||
};
|
};
|
||||||
|
clan.matrix-synapse.domain = "clan.test";
|
||||||
|
clan.matrix-synapse.users.admin.admin = true;
|
||||||
|
clan.matrix-synapse.users.someuser = { };
|
||||||
|
|
||||||
|
clan.core.facts.secretStore = "vm";
|
||||||
|
|
||||||
|
# because we use systemd-tmpfiles to copy the secrets, we need to a seperate systemd-tmpfiles call to provison them.
|
||||||
|
boot.postBootCommands = "${config.systemd.package}/bin/systemd-tmpfiles --create /etc/tmpfiles.d/00-vmsecrets.conf";
|
||||||
|
|
||||||
|
systemd.tmpfiles.settings."00-vmsecrets" = {
|
||||||
|
# run before 00-nixos.conf
|
||||||
|
"/etc/secrets" = {
|
||||||
|
d.mode = "0700";
|
||||||
|
z.mode = "0700";
|
||||||
|
};
|
||||||
|
"/etc/secrets/synapse-registration_shared_secret" = {
|
||||||
|
f.argument = "supersecret";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"/etc/secrets/matrix-password-admin" = {
|
||||||
|
f.argument = "matrix-password1";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"/etc/secrets/matrix-password-someuser" = {
|
||||||
|
f.argument = "matrix-password2";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "root";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -32,6 +67,12 @@
|
|||||||
machine.wait_for_unit("matrix-synapse")
|
machine.wait_for_unit("matrix-synapse")
|
||||||
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
||||||
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
||||||
|
|
||||||
|
machine.systemctl("restart matrix-synapse >&2") # check if user creation is idempotent
|
||||||
|
machine.execute("journalctl -u matrix-synapse --no-pager >&2")
|
||||||
|
machine.wait_for_unit("matrix-synapse")
|
||||||
|
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
||||||
|
machine.succeed("${pkgs.curl}/bin/curl -Ssf -L http://localhost/_matrix/static/ -H 'Host: matrix.clan.test'")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
146
checks/mumble/default.nix
Normal file
146
checks/mumble/default.nix
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
(import ../lib/test-base.nix) (
|
||||||
|
{ ... }:
|
||||||
|
let
|
||||||
|
common =
|
||||||
|
{ self, pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.clanModules.mumble
|
||||||
|
self.nixosModules.clanCore
|
||||||
|
(self.inputs.nixpkgs + "/nixos/tests/common/x11.nix")
|
||||||
|
{
|
||||||
|
clan.core.clanDir = ./.;
|
||||||
|
environment.systemPackages = [ pkgs.killall ];
|
||||||
|
services.murmur.sslKey = "/etc/mumble-key";
|
||||||
|
services.murmur.sslCert = "/etc/mumble-cert";
|
||||||
|
clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key";
|
||||||
|
clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "mumble";
|
||||||
|
|
||||||
|
enableOCR = true;
|
||||||
|
|
||||||
|
nodes.peer1 =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
common
|
||||||
|
{
|
||||||
|
clan.core.machineName = "peer1";
|
||||||
|
environment.etc = {
|
||||||
|
"mumble-key".source = ./peer_1/peer_1_test_key;
|
||||||
|
"mumble-cert".source = ./peer_1/peer_1_test_cert;
|
||||||
|
};
|
||||||
|
systemd.tmpfiles.settings."vmsecrets" = {
|
||||||
|
"/etc/secrets/mumble-key" = {
|
||||||
|
C.argument = "${./peer_1/peer_1_test_key}";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "murmur";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"/etc/secrets/mumble-cert" = {
|
||||||
|
C.argument = "${./peer_1/peer_1_test_cert}";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "murmur";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
services.murmur.sslKey = "/etc/mumble-key";
|
||||||
|
services.murmur.sslCert = "/etc/mumble-cert";
|
||||||
|
clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key";
|
||||||
|
clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nodes.peer2 =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
common
|
||||||
|
{
|
||||||
|
clan.core.machineName = "peer2";
|
||||||
|
environment.etc = {
|
||||||
|
"mumble-key".source = ./peer_2/peer_2_test_key;
|
||||||
|
"mumble-cert".source = ./peer_2/peer_2_test_cert;
|
||||||
|
};
|
||||||
|
systemd.tmpfiles.settings."vmsecrets" = {
|
||||||
|
"/etc/secrets/mumble-key" = {
|
||||||
|
C.argument = "${./peer_2/peer_2_test_key}";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "murmur";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"/etc/secrets/mumble-cert" = {
|
||||||
|
C.argument = "${./peer_2/peer_2_test_cert}";
|
||||||
|
z = {
|
||||||
|
mode = "0400";
|
||||||
|
user = "murmur";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
with subtest("Waiting for x"):
|
||||||
|
peer1.wait_for_x()
|
||||||
|
peer2.wait_for_x()
|
||||||
|
|
||||||
|
with subtest("Waiting for murmur"):
|
||||||
|
peer1.wait_for_unit("murmur.service")
|
||||||
|
peer2.wait_for_unit("murmur.service")
|
||||||
|
|
||||||
|
with subtest("Starting Mumble"):
|
||||||
|
# starting mumble is blocking
|
||||||
|
peer1.execute("mumble >&2 &")
|
||||||
|
peer2.execute("mumble >&2 &")
|
||||||
|
|
||||||
|
with subtest("Wait for Mumble"):
|
||||||
|
peer1.wait_for_window(r"^Mumble$")
|
||||||
|
peer2.wait_for_window(r"^Mumble$")
|
||||||
|
|
||||||
|
with subtest("Wait for certificate creation"):
|
||||||
|
peer1.wait_for_window(r"^Mumble$")
|
||||||
|
peer1.sleep(3) # mumble is slow to register handlers
|
||||||
|
peer1.send_chars("\n")
|
||||||
|
peer1.send_chars("\n")
|
||||||
|
peer2.wait_for_window(r"^Mumble$")
|
||||||
|
peer2.sleep(3) # mumble is slow to register handlers
|
||||||
|
peer2.send_chars("\n")
|
||||||
|
peer2.send_chars("\n")
|
||||||
|
|
||||||
|
with subtest("Wait for server connect"):
|
||||||
|
peer1.wait_for_window(r"^Mumble Server Connect$")
|
||||||
|
peer2.wait_for_window(r"^Mumble Server Connect$")
|
||||||
|
|
||||||
|
with subtest("Check validity of server certificates"):
|
||||||
|
peer1.execute("killall .mumble-wrapped")
|
||||||
|
peer1.sleep(1)
|
||||||
|
peer1.execute("mumble mumble://peer2 >&2 &")
|
||||||
|
peer1.wait_for_window(r"^Mumble$")
|
||||||
|
peer1.sleep(3) # mumble is slow to register handlers
|
||||||
|
peer1.send_chars("\n")
|
||||||
|
peer1.send_chars("\n")
|
||||||
|
peer1.wait_for_text("Connected.")
|
||||||
|
|
||||||
|
peer2.execute("killall .mumble-wrapped")
|
||||||
|
peer2.sleep(1)
|
||||||
|
peer2.execute("mumble mumble://peer1 >&2 &")
|
||||||
|
peer2.wait_for_window(r"^Mumble$")
|
||||||
|
peer2.sleep(3) # mumble is slow to register handlers
|
||||||
|
peer2.send_chars("\n")
|
||||||
|
peer2.send_chars("\n")
|
||||||
|
peer2.wait_for_text("Connected.")
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
)
|
||||||
22
checks/mumble/machines/peer1/facts/mumble-cert
Normal file
22
checks/mumble/machines/peer1/facts/mumble-cert
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
||||||
|
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||||
|
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
||||||
|
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
||||||
|
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
||||||
|
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
||||||
|
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
||||||
|
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
||||||
|
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
||||||
|
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
||||||
|
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
||||||
|
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
||||||
|
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
||||||
|
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
||||||
|
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
||||||
|
ZqMUUkA4DSucb6xxEcTK
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
1
checks/mumble/machines/peer1/key.age
Normal file
1
checks/mumble/machines/peer1/key.age
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
||||||
14
checks/mumble/machines/peer1/peer_1_test_cert
Normal file
14
checks/mumble/machines/peer1/peer_1_test_cert
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHTCCAaKgAwIBAgIIT2gZuvqVFP0wCgYIKoZIzj0EAwIwSjESMBAGA1UEChMJ
|
||||||
|
U3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdlbmVyYXRlZDESMBAG
|
||||||
|
A1UEAxMJc3luY3RoaW5nMB4XDTIzMTIwNjAwMDAwMFoXDTQzMTIwMTAwMDAwMFow
|
||||||
|
SjESMBAGA1UEChMJU3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdl
|
||||||
|
bmVyYXRlZDESMBAGA1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACID
|
||||||
|
YgAEBAr1CsciwCa0vi7eC6xxuSGijY3txbjtsyFanec/fge4oJBD3rVpaLKFETb3
|
||||||
|
TvHHsuvblzElcP483MEVq6FMUoxwuL9CzTtpJrRhtwSmAs8AHLFu8irVn8sZjgkL
|
||||||
|
sXMho1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
|
||||||
|
AQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJc3luY3RoaW5nMAoGCCqG
|
||||||
|
SM49BAMCA2kAMGYCMQDbrtLgfcyMMIkNQn+PJe9DHYAqj8C47LQcWuIY/nekhOu0
|
||||||
|
aUfKctEAwyBtI60Y5zcCMQCEdgD/6CNBh7Qqq3z3CKPhlrpxHtCO5tNw17k0jfdH
|
||||||
|
haCwJInHZvZgclHk4EtFpTw=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
6
checks/mumble/machines/peer1/peer_1_test_key
Normal file
6
checks/mumble/machines/peer1/peer_1_test_key
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIGkAgEBBDA14Nqo17Xs/xRLGH2KLuyzjKp4eW9iWFobVNM93RZZbECT++W3XcQc
|
||||||
|
cEc5WVtiPmWgBwYFK4EEACKhZANiAAQECvUKxyLAJrS+Lt4LrHG5IaKNje3FuO2z
|
||||||
|
IVqd5z9+B7igkEPetWlosoURNvdO8cey69uXMSVw/jzcwRWroUxSjHC4v0LNO2km
|
||||||
|
tGG3BKYCzwAcsW7yKtWfyxmOCQuxcyE=
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
22
checks/mumble/machines/peer2/facts/mumble-cert
Normal file
22
checks/mumble/machines/peer2/facts/mumble-cert
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
||||||
|
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||||
|
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
||||||
|
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
||||||
|
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
||||||
|
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
||||||
|
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
||||||
|
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
||||||
|
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
||||||
|
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
||||||
|
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
||||||
|
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
||||||
|
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
||||||
|
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
||||||
|
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
||||||
|
Ek4u3+HXZMl5a0LGJ76u
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
14
checks/mumble/machines/peer2/peer_2_test_cert
Normal file
14
checks/mumble/machines/peer2/peer_2_test_cert
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHjCCAaOgAwIBAgIJAKbMWefkf1rVMAoGCCqGSM49BAMCMEoxEjAQBgNVBAoT
|
||||||
|
CVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQ
|
||||||
|
BgNVBAMTCXN5bmN0aGluZzAeFw0yMzEyMDYwMDAwMDBaFw00MzEyMDEwMDAwMDBa
|
||||||
|
MEoxEjAQBgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBH
|
||||||
|
ZW5lcmF0ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzB2MBAGByqGSM49AgEGBSuBBAAi
|
||||||
|
A2IABFZTMt4RfsfBue0va7QuNdjfXMI4HfZzJCEcG+b9MtV7FlDmwMKX5fgGykD9
|
||||||
|
FBbC7yiza3+xCobdMb5bakz1qYJ7nUFCv1mwSDo2eNM+/XE+rJmlre8NwkwGmvzl
|
||||||
|
h1uhyqNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
|
||||||
|
BgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCXN5bmN0aGluZzAKBggq
|
||||||
|
hkjOPQQDAgNpADBmAjEAwzhsroN6R4/quWeXj6dO5gt5CfSTLkLee6vrcuIP5i1U
|
||||||
|
rZvJ3OKQVmmGG6IWYe7iAjEAyuq3X2wznaqiw2YK3IDI4qVeYWpCUap0fwRNq7/x
|
||||||
|
4dC4k+BOzHcuJOwNBIY/bEuK
|
||||||
|
-----END CERTIFICATE-----
|
||||||
6
checks/mumble/machines/peer2/peer_2_test_key
Normal file
6
checks/mumble/machines/peer2/peer_2_test_key
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIGkAgEBBDCXHGpvumKjjDRxB6SsjZOb7duw3w+rdlGQCJTIvRThLjD6zwjnyImi
|
||||||
|
7c3PD5nWtLqgBwYFK4EEACKhZANiAARWUzLeEX7HwbntL2u0LjXY31zCOB32cyQh
|
||||||
|
HBvm/TLVexZQ5sDCl+X4BspA/RQWwu8os2t/sQqG3TG+W2pM9amCe51BQr9ZsEg6
|
||||||
|
NnjTPv1xPqyZpa3vDcJMBpr85Ydboco=
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
1
checks/mumble/peer_1/key.age
Normal file
1
checks/mumble/peer_1/key.age
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX
|
||||||
22
checks/mumble/peer_1/peer_1_test_cert
Normal file
22
checks/mumble/peer_1/peer_1_test_cert
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3
|
||||||
|
MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||||
|
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc
|
||||||
|
R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+
|
||||||
|
7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF
|
||||||
|
vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY
|
||||||
|
aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym
|
||||||
|
gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud
|
||||||
|
DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi
|
||||||
|
oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg
|
||||||
|
F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH
|
||||||
|
5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg
|
||||||
|
TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz
|
||||||
|
DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj
|
||||||
|
i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB
|
||||||
|
ZqMUUkA4DSucb6xxEcTK
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
28
checks/mumble/peer_1/peer_1_test_key
Normal file
28
checks/mumble/peer_1/peer_1_test_key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCcdZEJvXJIeOK
|
||||||
|
O5pF5XUFvUeJtCCiwfWvWS662bxcR/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS
|
||||||
|
4zSGQoTEAVzqzVdi3a/gNvsdVLb+7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfd
|
||||||
|
WV1Y5T1tuwc3G8ATrguQ33Uo5vvFvcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIh
|
||||||
|
jMpRG/uZ3u7wtbyZ+WqjsjxZNfnYaMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2Zqh
|
||||||
|
VSYXyDfpAWQFznwKGzD5mjtcyKymgnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4Kn
|
||||||
|
ID30OQW7AgMBAAECggEAGVKn+/Iy+kG+l2cRvV6XseqnoWhjA69M5swviMgIfuAl
|
||||||
|
Xx/boeI4mwoS+dJQKi/0zEbB1MB+gwIDB/0s/vs0vS4MQswBQG/skr+2TmiU+Hgb
|
||||||
|
CF0dIYUZv5rAbScFTumx/mCCqxwc+1QIMzyLKqOYL203EFc92ZJGEVT4th321haZ
|
||||||
|
8Wd+dllcYAb7BbEeBhCrTqRe9T3zt5reZgtZTquTF5hGm8EAyBp6rLjZK7dyZ9dd
|
||||||
|
gyIsDbWgPC9vkRc6x/eANn70hgDbYOuoXwAP/qIFnWLL1Zzy8LKUyOsSgQ91S3S3
|
||||||
|
Il4Lt6lEyU3+61MsCYss7jDoP/7REEjz5h6gfxlFSQKBgQD9u8nhHuwte4/d9VNU
|
||||||
|
rhSBW9h8IJzwPif/eS8vh9VaS2SjR2dDCcHg6rGYKnexeEzUcx56aQMA+p3nRJwy
|
||||||
|
Uwnx5BfEWs9FO6yPR8VEI0a2sBp+hoWKJX/Lvat+QCs6IFuGmlQpczD7/RYAkhG4
|
||||||
|
mwyt/ymqzjukb9mFaeYIltOfPwKBgQDELnkH1ChTUH5u3HgDoelFbzR18okz6dxH
|
||||||
|
urMbfZMAl8W5h2zAvHsAX5qxyHHankOUsiH2y3BrAgqQtTuIA2a5W7j+yHBkYiEZ
|
||||||
|
EUNeI9YNA0KU+wwZpVVvRGUsRB5SUBo5LlcSYmX/V32f0oU5Np44i0vjl3Ju8esx
|
||||||
|
2MLfj1A2hQKBgQDCxtZZZ0h8Pb8Z7wpSFfQNvXi5CLwQvFYuClQLk6VXVErkAJsn
|
||||||
|
XiUjyGYeXnNVm/i2mcyKwXQZ20k90HBrPU2ED8mi5Ob5ya5Uqw6mmMHe2d7sw81d
|
||||||
|
WB37RBWSrCXC0DYSZQQ4cYHn3sd2Fqtd4EBijV7qDLjCKU582OdKLqYzNwKBgH31
|
||||||
|
UKQkJZgIkIThbPT4GewI0GgCRvFb76DmUGUQJTg2Oi86siq1WUwOFiabie5RuxZX
|
||||||
|
oNLyH8W008/BbO2RMX1FVOvRCciJ8LJFkTl6TM6iDzfUUBqPOuFryoG3Yrh60btw
|
||||||
|
81rMbqyZIgFhi0QGu2OWnC0Oadyt2tJwV/5t55R5AoGBAPspZttDmOzVkAJDSn9Z
|
||||||
|
iByYt1KmwBQ6l7LpFg33a7ds9zWqW4+i6r0PzXvSewf/z69L0cAywSk5CaJJjDso
|
||||||
|
dTlNMqwux01wd6V+nQGR871xnsOg+qzgJ565TJZelWgRmNRUooi4DMp5POJA33xp
|
||||||
|
rqAISUfW0w2S+q7/5Lm0QiJE
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
22
checks/mumble/peer_2/peer_2_test_cert
Normal file
22
checks/mumble/peer_2/peer_2_test_cert
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3
|
||||||
|
MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
|
||||||
|
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
|
||||||
|
AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24
|
||||||
|
AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz
|
||||||
|
j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun
|
||||||
|
ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE
|
||||||
|
pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc
|
||||||
|
gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud
|
||||||
|
DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ
|
||||||
|
NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO
|
||||||
|
7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm
|
||||||
|
uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga
|
||||||
|
TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv
|
||||||
|
pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r
|
||||||
|
RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A
|
||||||
|
Ek4u3+HXZMl5a0LGJ76u
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
28
checks/mumble/peer_2/peer_2_test_key
Normal file
28
checks/mumble/peer_2/peer_2_test_key
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfP6cZhCs9jOnW
|
||||||
|
qyQP12vrOOxlBrWofYZFf9amUA24AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEv
|
||||||
|
HujKyy8PgcEGP+pwmsfWNQMvU0Dzj3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU
|
||||||
|
1l7fO/OXUlq5kyvIjln7Za4sUHunixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjG
|
||||||
|
G+R6MccH8wwQwmLg5oVBkFEZrnREpnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f
|
||||||
|
8TYGHqbeMQFCKwusnlWPRtrNdaIcgaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO
|
||||||
|
0Bwx8fmRAgMBAAECggEACAkjOnNj5zA0IIP0RuRc6rqtmw9ynTTwUJN51lyVxKI8
|
||||||
|
dQDMEq/S2En+J2VyS7z92/XtbgkBIFx83u7VWl5UWpj2j4UsJFB7IwD7zyiJT4D+
|
||||||
|
+3cM/kX8Wx4XyQZbfbm47N0MXAgFCkn45hxHH0acLReXwmN9wxoDyl7AIjZRdwvG
|
||||||
|
Qq0rnOnIc8kkkew7L6AiFwQS8b77eyzua3d6moKXN9hU/kfiJ6YUFG/WLe0pmQA1
|
||||||
|
HbF27YghfeLnYUt50oDuX6jF6CzQhflchWVq/wn8/cxEpg/RMicWE8ulrTk7o27l
|
||||||
|
JwCrHrhYEBsPuZO4mxX/DHrAMmhTeFjLaV5bQlz0PQKBgQDgRPSOEixYnKz9iPs/
|
||||||
|
EDTlji5LA3Rm6TytRCNsjYY6Trw60KcvYqwyDUCiEjruvOQ9mqgBiQm1VHSalrG3
|
||||||
|
RcbVfpEMouyZbEwmTjS8KdOi5x4Z6AX+4yWDN31jX3b8sktgbxV/HRdg3sA3q7MJ
|
||||||
|
vExTUuoXg57W+FepIZ+XlhSoQwKBgQC1x6UMAlAeW45/yUUm/LFRcCgb/bdCQx+e
|
||||||
|
hSb8w3jdvVoNWgx1j7RsjjFKaZUnseK3qQvVfCm4Qjvlz6MpKDxslaUYuR162Ku0
|
||||||
|
e153z/xc7XRoXyPyPLdGZFlWii30jirB7ZqPdyz6mwlWwqdImNerbUqdFt9R8bId
|
||||||
|
pYsyHB5zmwKBgBjYCq9iW/9E+/TqI8sMpI95fK9app5v4AThs3rnAqOa7Ucmrh6V
|
||||||
|
s7Wnui06D8U6r54Tb+EbqTOpM3Gcl/tRg4FLEA5yTfuA/76Ok1D04Tj+mVsNVPyz
|
||||||
|
dQhgMUe835WGusroA12df2V/x5NjNeYyMdJZMQ2ByyrNQAjAbMmCGq+5AoGBAIj8
|
||||||
|
ERFysMOfxUvg9b7CkDFJrsAhOzew86P2vYGfIHchGTqUkG0LRTDFGrnzxNXsBGjY
|
||||||
|
+DUB40Kajx7IkTETxC0jvA1ceq23l/VjPrZVQt0YiC+a+rCyNn7SYkyHxsfTVr9b
|
||||||
|
ea0BZyDXMntyJrPbkjL6Ik8tDE9pLwuOU84ISJ5fAoGAZ2+Ams/VhdZj/wpRpMky
|
||||||
|
K4jtS4nzbCmJzzTa6vdVV7Kjer5kFxSFFqMrS/FtJ/RxHeHvxdze9dfGu9jIdTKK
|
||||||
|
vSzbyQdHFfZgRkmAKfcoN9u567z7Oc74AQ9UgFEGdEVFQUbfWOevmr8KIPt8nDQK
|
||||||
|
J9HuVfILi1kH0jzDd/64TvA=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
72
checks/postgresql/default.nix
Normal file
72
checks/postgresql/default.nix
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
(import ../lib/container-test.nix) ({
|
||||||
|
name = "postgresql";
|
||||||
|
|
||||||
|
nodes.machine =
|
||||||
|
{ self, config, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.clanCore
|
||||||
|
self.clanModules.postgresql
|
||||||
|
self.clanModules.localbackup
|
||||||
|
];
|
||||||
|
clan.postgresql.users.test = { };
|
||||||
|
clan.postgresql.databases.test.create.options.OWNER = "test";
|
||||||
|
clan.postgresql.databases.test.restore.stopOnRestore = [ "sample-service" ];
|
||||||
|
clan.localbackup.targets.hdd.directory = "/mnt/external-disk";
|
||||||
|
|
||||||
|
systemd.services.sample-service = {
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
script = ''
|
||||||
|
while true; do
|
||||||
|
echo "Hello, world!"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [ config.services.postgresql.package ];
|
||||||
|
};
|
||||||
|
testScript =
|
||||||
|
{ nodes, ... }:
|
||||||
|
''
|
||||||
|
start_all()
|
||||||
|
machine.wait_for_unit("postgresql")
|
||||||
|
machine.wait_for_unit("sample-service")
|
||||||
|
# Create a test table
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -c 'CREATE TABLE test (id serial PRIMARY KEY);' test")
|
||||||
|
|
||||||
|
machine.succeed("/run/current-system/sw/bin/localbackup-create >&2")
|
||||||
|
timestamp_before = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
|
||||||
|
|
||||||
|
machine.succeed("test -e /mnt/external-disk/snapshot.0/machine/var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'INSERT INTO test DEFAULT VALUES;'")
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'DROP TABLE test;'")
|
||||||
|
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
||||||
|
|
||||||
|
machine.succeed("rm -rf /var/backup/postgres")
|
||||||
|
|
||||||
|
machine.succeed("NAME=/mnt/external-disk/snapshot.0 FOLDERS=/var/backup/postgres/test /run/current-system/sw/bin/localbackup-restore >&2")
|
||||||
|
machine.succeed("test -e /var/backup/postgres/test/pg-dump || { echo 'pg-dump not found'; exit 1; }")
|
||||||
|
|
||||||
|
machine.succeed("""
|
||||||
|
set -x
|
||||||
|
${nodes.machine.clan.core.state.test.postRestoreCommand}
|
||||||
|
""")
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -l >&2")
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
|
||||||
|
|
||||||
|
timestamp_after = int(machine.succeed("systemctl show --property=ExecMainStartTimestampMonotonic sample-service | cut -d= -f2").strip())
|
||||||
|
assert timestamp_before < timestamp_after, f"{timestamp_before} >= {timestamp_after}: expected sample-service to be restarted after restore"
|
||||||
|
|
||||||
|
# Check that the table is still there
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c 'SELECT * FROM test;'")
|
||||||
|
output = machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql --csv -c \"SELECT datdba::regrole FROM pg_database WHERE datname = 'test'\"")
|
||||||
|
owner = output.split("\n")[1]
|
||||||
|
assert owner == "test", f"Expected database owner to be 'test', got '{owner}'"
|
||||||
|
|
||||||
|
# check if restore works if the database does not exist
|
||||||
|
machine.succeed("runuser -u postgres -- dropdb test")
|
||||||
|
machine.succeed("${nodes.machine.clan.core.state.test.postRestoreCommand}")
|
||||||
|
machine.succeed("runuser -u postgres -- /run/current-system/sw/bin/psql -d test -c '\dt' >&2")
|
||||||
|
'';
|
||||||
|
})
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
|
environment.etc."group-secret".source = config.sops.secrets.group-secret.path;
|
||||||
sops.age.keyFile = "/etc/privkey.age";
|
sops.age.keyFile = "/etc/privkey.age";
|
||||||
|
|
||||||
clanCore.clanDir = "${./.}";
|
clan.core.clanDir = "${./.}";
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
|
|
||||||
networking.hostName = "machine";
|
networking.hostName = "machine";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "introducer";
|
clan.core.machineName = "introducer";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
environment.etc = {
|
environment.etc = {
|
||||||
"syncthing.pam".source = ./introducer/introducer_test_cert;
|
"syncthing.pam".source = ./introducer/introducer_test_cert;
|
||||||
"syncthing.key".source = ./introducer/introducer_test_key;
|
"syncthing.key".source = ./introducer/introducer_test_key;
|
||||||
"syncthing.api".source = ./introducer/introducer_test_api;
|
"syncthing.api".source = ./introducer/introducer_test_api;
|
||||||
};
|
};
|
||||||
clanCore.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
|
clan.core.facts.services.syncthing.secret."syncthing.api".path = "/etc/syncthing.api";
|
||||||
services.syncthing.cert = "/etc/syncthing.pam";
|
services.syncthing.cert = "/etc/syncthing.pam";
|
||||||
services.syncthing.key = "/etc/syncthing.key";
|
services.syncthing.key = "/etc/syncthing.key";
|
||||||
# Doesn't test zerotier!
|
# Doesn't test zerotier!
|
||||||
@@ -53,8 +53,8 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "peer1";
|
clan.core.machineName = "peer1";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
||||||
builtins.readFile ./introducer/introducer_device_id
|
builtins.readFile ./introducer/introducer_device_id
|
||||||
);
|
);
|
||||||
@@ -75,8 +75,8 @@
|
|||||||
self.clanModules.syncthing
|
self.clanModules.syncthing
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "peer2";
|
clan.core.machineName = "peer2";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
clan.syncthing.introducer = lib.strings.removeSuffix "\n" (
|
||||||
builtins.readFile ./introducer/introducer_device_id
|
builtins.readFile ./introducer/introducer_device_id
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import ../lib/test-base.nix (
|
|||||||
imports = [
|
imports = [
|
||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
{
|
{
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
services.wayland-proxy-virtwl.enable = true;
|
services.wayland-proxy-virtwl.enable = true;
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
self.nixosModules.clanCore
|
self.nixosModules.clanCore
|
||||||
self.clanModules.zt-tcp-relay
|
self.clanModules.zt-tcp-relay
|
||||||
{
|
{
|
||||||
clanCore.machineName = "machine";
|
clan.core.machineName = "machine";
|
||||||
clanCore.clanDir = ./.;
|
clan.core.clanDir = ./.;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
11
clanModules/borgbackup-static/README.md
Normal file
11
clanModules/borgbackup-static/README.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
description = "Statically configure borgbackup with sane defaults."
|
||||||
|
---
|
||||||
|
This module implements the `borgbackup` backend and implements sane defaults
|
||||||
|
for backup management through `borgbackup` for members of the clan.
|
||||||
|
|
||||||
|
Configure target machines where the backups should be sent to through `targets`.
|
||||||
|
|
||||||
|
Configure machines that should be backuped either through `includeMachines`
|
||||||
|
which will exclusively add the included machines to be backuped, or through
|
||||||
|
`excludeMachines`, which will add every machine except the excluded machine to the backup.
|
||||||
101
clanModules/borgbackup-static/default.nix
Normal file
101
clanModules/borgbackup-static/default.nix
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
clanDir = config.clan.core.clanDir;
|
||||||
|
machineDir = clanDir + "/machines/";
|
||||||
|
in
|
||||||
|
lib.warn "This module is deprecated use the service via the inventory interface instead." {
|
||||||
|
imports = [ ../borgbackup ];
|
||||||
|
|
||||||
|
options.clan.borgbackup-static = {
|
||||||
|
excludeMachines = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
example = [ config.clan.core.machineName ];
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Machines that should not be backuped.
|
||||||
|
Mutually exclusive with includeMachines.
|
||||||
|
If this is not empty, every other machine except the targets in the clan will be backuped by this module.
|
||||||
|
If includeMachines is set, only the included machines will be backuped.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
includeMachines = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
example = [ config.clan.core.machineName ];
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Machines that should be backuped.
|
||||||
|
Mutually exclusive with excludeMachines.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
targets = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Machines that should act as target machines for backups.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.services.borgbackup.repos =
|
||||||
|
let
|
||||||
|
machines = builtins.readDir machineDir;
|
||||||
|
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
|
||||||
|
filteredMachines =
|
||||||
|
if ((builtins.length config.clan.borgbackup-static.includeMachines) != 0) then
|
||||||
|
lib.filterAttrs (name: _: (lib.elem name config.clan.borgbackup-static.includeMachines)) machines
|
||||||
|
else
|
||||||
|
lib.filterAttrs (name: _: !(lib.elem name config.clan.borgbackup-static.excludeMachines)) machines;
|
||||||
|
machinesMaybeKey = lib.mapAttrsToList (
|
||||||
|
machine: _:
|
||||||
|
let
|
||||||
|
fullPath = borgbackupIpMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then machine else null
|
||||||
|
) filteredMachines;
|
||||||
|
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
||||||
|
hosts = builtins.map (machine: {
|
||||||
|
name = machine;
|
||||||
|
value = {
|
||||||
|
path = "/var/lib/borgbackup/${machine}";
|
||||||
|
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
||||||
|
};
|
||||||
|
}) machinesWithKey;
|
||||||
|
in
|
||||||
|
lib.mkIf
|
||||||
|
(builtins.any (
|
||||||
|
target: target == config.clan.core.machineName
|
||||||
|
) config.clan.borgbackup-static.targets)
|
||||||
|
(if (builtins.listToAttrs hosts) != null then builtins.listToAttrs hosts else { });
|
||||||
|
|
||||||
|
config.clan.borgbackup.destinations =
|
||||||
|
let
|
||||||
|
destinations = builtins.map (d: {
|
||||||
|
name = d;
|
||||||
|
value = {
|
||||||
|
repo = "borg@${d}:/var/lib/borgbackup/${config.clan.core.machineName}";
|
||||||
|
};
|
||||||
|
}) config.clan.borgbackup-static.targets;
|
||||||
|
in
|
||||||
|
lib.mkIf (builtins.any (
|
||||||
|
target: target == config.clan.core.machineName
|
||||||
|
) config.clan.borgbackup-static.includeMachines) (builtins.listToAttrs destinations);
|
||||||
|
|
||||||
|
config.assertions = [
|
||||||
|
{
|
||||||
|
assertion =
|
||||||
|
!(
|
||||||
|
((builtins.length config.clan.borgbackup-static.excludeMachines) != 0)
|
||||||
|
&& ((builtins.length config.clan.borgbackup-static.includeMachines) != 0)
|
||||||
|
);
|
||||||
|
message = ''
|
||||||
|
The options:
|
||||||
|
config.clan.borgbackup-static.excludeMachines = [${builtins.toString config.clan.borgbackup-static.excludeMachines}]
|
||||||
|
and
|
||||||
|
config.clan.borgbackup-static.includeMachines = [${builtins.toString config.clan.borgbackup-static.includeMachines}]
|
||||||
|
are mutually exclusive.
|
||||||
|
Use excludeMachines to exclude certain machines and backup the other clan machines.
|
||||||
|
Use include machines to only backup certain machines.
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,2 +1,13 @@
|
|||||||
Efficient, deduplicating backup program with optional compression and secure encryption.
|
|
||||||
---
|
---
|
||||||
|
description = "Efficient, deduplicating backup program with optional compression and secure encryption."
|
||||||
|
categories = ["backup"]
|
||||||
|
---
|
||||||
|
BorgBackup (short: Borg) gives you:
|
||||||
|
|
||||||
|
- Space efficient storage of backups.
|
||||||
|
- Secure, authenticated encryption.
|
||||||
|
- Compression: lz4, zstd, zlib, lzma or none.
|
||||||
|
- Mountable backups with FUSE.
|
||||||
|
- Easy installation on multiple platforms: Linux, macOS, BSD, ...
|
||||||
|
- Free software (BSD license).
|
||||||
|
- Backed by a large and active open source community.
|
||||||
@@ -6,8 +6,73 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.borgbackup;
|
cfg = config.clan.borgbackup;
|
||||||
|
preBackupScript = ''
|
||||||
|
declare -A preCommandErrors
|
||||||
|
|
||||||
|
${lib.concatMapStringsSep "\n" (
|
||||||
|
state:
|
||||||
|
lib.optionalString (state.preBackupCommand != null) ''
|
||||||
|
echo "Running pre-backup command for ${state.name}"
|
||||||
|
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
||||||
|
preCommandErrors["${state.name}"]=1
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
) (lib.attrValues config.clan.core.state)}
|
||||||
|
|
||||||
|
if [[ ''${#preCommandErrors[@]} -gt 0 ]]; then
|
||||||
|
echo "pre-backup commands failed for the following services:"
|
||||||
|
for state in "''${!preCommandErrors[@]}"; do
|
||||||
|
echo " $state"
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
|
# Each .nix file in the roles directory is a role
|
||||||
|
# TODO: Helper function to set available roles within module meta.
|
||||||
|
# roles =
|
||||||
|
# if builtins.pathExists ./roles then
|
||||||
|
# lib.pipe ./roles [
|
||||||
|
# builtins.readDir
|
||||||
|
# (lib.filterAttrs (_n: v: v == "regular"))
|
||||||
|
# lib.attrNames
|
||||||
|
# (map (fileName: lib.removeSuffix ".nix" fileName))
|
||||||
|
# ]
|
||||||
|
# else
|
||||||
|
# null;
|
||||||
|
# TODO: make this an interface of every module
|
||||||
|
# Maybe load from readme.md
|
||||||
|
# metaInfoOption = lib.mkOption {
|
||||||
|
# readOnly = true;
|
||||||
|
# description = ''
|
||||||
|
# Meta is used to retrieve information about this module.
|
||||||
|
# - `availableRoles` is a list of roles that can be assigned via the inventory.
|
||||||
|
# - `category` is used to group services in the clan marketplace.
|
||||||
|
# - `description` is a short description of the service for the clan marketplace.
|
||||||
|
# '';
|
||||||
|
# default = {
|
||||||
|
# description = "Borgbackup is a backup program. Optionally, it supports compression and authenticated encryption.";
|
||||||
|
# availableRoles = roles;
|
||||||
|
# category = "backup";
|
||||||
|
# };
|
||||||
|
# type = lib.types.submodule {
|
||||||
|
# options = {
|
||||||
|
# description = lib.mkOption { type = lib.types.str; };
|
||||||
|
# availableRoles = lib.mkOption { type = lib.types.nullOr (lib.types.listOf lib.types.str); };
|
||||||
|
# category = lib.mkOption {
|
||||||
|
# description = "A category for the service. This is used to group services in the clan ui";
|
||||||
|
# type = lib.types.enum [
|
||||||
|
# "backup"
|
||||||
|
# "network"
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
{
|
{
|
||||||
|
|
||||||
|
# options.clan.borgbackup.meta = metaInfoOption;
|
||||||
|
|
||||||
options.clan.borgbackup.destinations = lib.mkOption {
|
options.clan.borgbackup.destinations = lib.mkOption {
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
lib.types.submodule (
|
lib.types.submodule (
|
||||||
@@ -26,9 +91,9 @@ in
|
|||||||
rsh = lib.mkOption {
|
rsh = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "ssh -i ${
|
default = "ssh -i ${
|
||||||
config.clanCore.facts.services.borgbackup.secret."borgbackup.ssh".path
|
config.clan.core.facts.services.borgbackup.secret."borgbackup.ssh".path
|
||||||
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
||||||
defaultText = "ssh -i \${config.clanCore.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
defaultText = "ssh -i \${config.clan.core.facts.services.borgbackup.secret.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
||||||
description = "the rsh to use for the backup";
|
description = "the rsh to use for the backup";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -41,6 +106,16 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.clan.borgbackup.exclude = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
example = [ "*.pyc" ];
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Directories/Files to exclude from the backup.
|
||||||
|
Use * as a wildcard.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
imports = [
|
imports = [
|
||||||
(lib.mkRemovedOptionModule [
|
(lib.mkRemovedOptionModule [
|
||||||
"clan"
|
"clan"
|
||||||
@@ -50,21 +125,30 @@ in
|
|||||||
];
|
];
|
||||||
|
|
||||||
config = lib.mkIf (cfg.destinations != { }) {
|
config = lib.mkIf (cfg.destinations != { }) {
|
||||||
|
systemd.services = lib.mapAttrs' (
|
||||||
|
_: dest:
|
||||||
|
lib.nameValuePair "borgbackup-job-${dest.name}" {
|
||||||
|
# since borgbackup mounts the system read-only, we need to run in a ExecStartPre script, so we can generate additional files.
|
||||||
|
serviceConfig.ExecStartPre = [
|
||||||
|
''+${pkgs.writeShellScript "borgbackup-job-${dest.name}-pre-backup-commands" preBackupScript}''
|
||||||
|
];
|
||||||
|
}
|
||||||
|
) cfg.destinations;
|
||||||
|
|
||||||
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
services.borgbackup.jobs = lib.mapAttrs (_: dest: {
|
||||||
paths = lib.flatten (map (state: state.folders) (lib.attrValues config.clanCore.state));
|
paths = lib.unique (
|
||||||
exclude = [ "*.pyc" ];
|
lib.flatten (map (state: state.folders) (lib.attrValues config.clan.core.state))
|
||||||
|
);
|
||||||
|
exclude = cfg.exclude;
|
||||||
repo = dest.repo;
|
repo = dest.repo;
|
||||||
environment.BORG_RSH = dest.rsh;
|
environment.BORG_RSH = dest.rsh;
|
||||||
compression = "auto,zstd";
|
compression = "auto,zstd";
|
||||||
startAt = "*-*-* 01:00:00";
|
startAt = "*-*-* 01:00:00";
|
||||||
persistentTimer = true;
|
persistentTimer = true;
|
||||||
preHook = ''
|
|
||||||
set -x
|
|
||||||
'';
|
|
||||||
|
|
||||||
encryption = {
|
encryption = {
|
||||||
mode = "repokey";
|
mode = "repokey";
|
||||||
passCommand = "cat ${config.clanCore.facts.services.borgbackup.secret."borgbackup.repokey".path}";
|
passCommand = "cat ${config.clan.core.facts.services.borgbackup.secret."borgbackup.repokey".path}";
|
||||||
};
|
};
|
||||||
|
|
||||||
prune.keep = {
|
prune.keep = {
|
||||||
@@ -75,7 +159,7 @@ in
|
|||||||
};
|
};
|
||||||
}) cfg.destinations;
|
}) cfg.destinations;
|
||||||
|
|
||||||
clanCore.facts.services.borgbackup = {
|
clan.core.facts.services.borgbackup = {
|
||||||
public."borgbackup.ssh.pub" = { };
|
public."borgbackup.ssh.pub" = { };
|
||||||
secret."borgbackup.ssh" = { };
|
secret."borgbackup.ssh" = { };
|
||||||
secret."borgbackup.repokey" = { };
|
secret."borgbackup.repokey" = { };
|
||||||
@@ -111,7 +195,7 @@ in
|
|||||||
(pkgs.writeShellScriptBin "borgbackup-restore" ''
|
(pkgs.writeShellScriptBin "borgbackup-restore" ''
|
||||||
set -efux
|
set -efux
|
||||||
cd /
|
cd /
|
||||||
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
IFS=':' read -ra FOLDER <<< "$FOLDERS"
|
||||||
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
|
job_name=$(echo "$NAME" | ${pkgs.gawk}/bin/awk -F'::' '{print $1}')
|
||||||
backup_name=''${NAME#"$job_name"::}
|
backup_name=''${NAME#"$job_name"::}
|
||||||
if ! command -v borg-job-"$job_name" &> /dev/null; then
|
if ! command -v borg-job-"$job_name" &> /dev/null; then
|
||||||
@@ -122,7 +206,7 @@ in
|
|||||||
'')
|
'')
|
||||||
];
|
];
|
||||||
|
|
||||||
clanCore.backups.providers.borgbackup = {
|
clan.core.backups.providers.borgbackup = {
|
||||||
list = "borgbackup-list";
|
list = "borgbackup-list";
|
||||||
create = "borgbackup-create";
|
create = "borgbackup-create";
|
||||||
restore = "borgbackup-restore";
|
restore = "borgbackup-restore";
|
||||||
|
|||||||
30
clanModules/borgbackup/roles/client.nix
Normal file
30
clanModules/borgbackup/roles/client.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
instances = config.clan.inventory.services.borgbackup;
|
||||||
|
# roles = { ${role_name} :: { machines :: [string] } }
|
||||||
|
allServers = lib.foldlAttrs (
|
||||||
|
acc: _instanceName: instanceConfig:
|
||||||
|
acc
|
||||||
|
++ (
|
||||||
|
if builtins.elem machineName instanceConfig.roles.client.machines then
|
||||||
|
instanceConfig.roles.server.machines
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
)
|
||||||
|
) [ ] instances;
|
||||||
|
|
||||||
|
inherit (config.clan.core) machineName;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config.clan.borgbackup.destinations =
|
||||||
|
let
|
||||||
|
|
||||||
|
destinations = builtins.map (serverName: {
|
||||||
|
name = serverName;
|
||||||
|
value = {
|
||||||
|
repo = "borg@${serverName}:/var/lib/borgbackup/${machineName}";
|
||||||
|
};
|
||||||
|
}) allServers;
|
||||||
|
in
|
||||||
|
(builtins.listToAttrs destinations);
|
||||||
|
}
|
||||||
51
clanModules/borgbackup/roles/server.nix
Normal file
51
clanModules/borgbackup/roles/server.nix
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
clanDir = config.clan.core.clanDir;
|
||||||
|
machineDir = clanDir + "/machines/";
|
||||||
|
inherit (config.clan.core) machineName;
|
||||||
|
|
||||||
|
instances = config.clan.inventory.services.borgbackup;
|
||||||
|
|
||||||
|
# roles = { ${role_name} :: { machines :: [string] } }
|
||||||
|
|
||||||
|
allClients = lib.foldlAttrs (
|
||||||
|
acc: _instanceName: instanceConfig:
|
||||||
|
acc
|
||||||
|
++ (
|
||||||
|
if (builtins.elem machineName instanceConfig.roles.server.machines) then
|
||||||
|
instanceConfig.roles.client.machines
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
)
|
||||||
|
) [ ] instances;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config.services.borgbackup.repos =
|
||||||
|
let
|
||||||
|
borgbackupIpMachinePath = machines: machineDir + machines + "/facts/borgbackup.ssh.pub";
|
||||||
|
machinesMaybeKey = builtins.map (
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = borgbackupIpMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then
|
||||||
|
machine
|
||||||
|
else
|
||||||
|
lib.warn ''
|
||||||
|
Machine ${machine} does not have a borgbackup key at ${fullPath},
|
||||||
|
run `clan facts generate ${machine}` to generate it.
|
||||||
|
'' null
|
||||||
|
) allClients;
|
||||||
|
|
||||||
|
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
||||||
|
|
||||||
|
hosts = builtins.map (machine: {
|
||||||
|
name = machine;
|
||||||
|
value = {
|
||||||
|
path = "/var/lib/borgbackup/${machine}";
|
||||||
|
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
||||||
|
};
|
||||||
|
}) machinesWithKey;
|
||||||
|
in
|
||||||
|
if (builtins.listToAttrs hosts) != [ ] then builtins.listToAttrs hosts else { };
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
Email-based instant messaging for Desktop.
|
---
|
||||||
|
description = "Email-based instant messaging for Desktop."
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! warning "Under construction"
|
!!! warning "Under construction"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
services.maddy =
|
services.maddy =
|
||||||
let
|
let
|
||||||
domain = "${config.clanCore.machineName}.local";
|
domain = "${config.clan.core.machineName}.local";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
Automatically format a disk drive on clan installation
|
|
||||||
---
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
A modern IRC server
|
---
|
||||||
|
description = "A modern IRC server"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ _: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clanCore.state.ergochat.folders = [ "/var/lib/ergo" ];
|
clan.core.state.ergochat.folders = [ "/var/lib/ergo" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
{ ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
flake.clanModules = {
|
flake.clanModules = {
|
||||||
disk-layouts = {
|
|
||||||
imports = [ ./disk-layouts ];
|
|
||||||
};
|
|
||||||
borgbackup = ./borgbackup;
|
borgbackup = ./borgbackup;
|
||||||
|
borgbackup-static = ./borgbackup-static;
|
||||||
deltachat = ./deltachat;
|
deltachat = ./deltachat;
|
||||||
ergochat = ./ergochat;
|
ergochat = ./ergochat;
|
||||||
localbackup = ./localbackup;
|
localbackup = ./localbackup;
|
||||||
localsend = ./localsend;
|
localsend = ./localsend;
|
||||||
|
single-disk = ./single-disk;
|
||||||
matrix-synapse = ./matrix-synapse;
|
matrix-synapse = ./matrix-synapse;
|
||||||
moonlight = ./moonlight;
|
moonlight = ./moonlight;
|
||||||
|
packages = ./packages;
|
||||||
|
mumble = ./mumble;
|
||||||
|
postgresql = ./postgresql;
|
||||||
root-password = ./root-password;
|
root-password = ./root-password;
|
||||||
sshd = ./sshd;
|
sshd = ./sshd;
|
||||||
sunshine = ./sunshine;
|
sunshine = ./sunshine;
|
||||||
static-hosts = ./static-hosts;
|
static-hosts = ./static-hosts;
|
||||||
syncthing = ./syncthing;
|
syncthing = ./syncthing;
|
||||||
|
syncthing-static-peers = ./syncthing-static-peers;
|
||||||
thelounge = ./thelounge;
|
thelounge = ./thelounge;
|
||||||
trusted-nix-caches = ./trusted-nix-caches;
|
trusted-nix-caches = ./trusted-nix-caches;
|
||||||
user-password = ./user-password;
|
user-password = ./user-password;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
Automatically backups current machine to local directory.
|
---
|
||||||
|
description = "Automatically backups current machine to local directory."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.localbackup;
|
cfg = config.clan.localbackup;
|
||||||
rsnapshotConfig = target: states: ''
|
uniqueFolders = lib.unique (
|
||||||
|
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
|
||||||
|
);
|
||||||
|
rsnapshotConfig = target: ''
|
||||||
config_version 1.2
|
config_version 1.2
|
||||||
snapshot_root ${target.directory}
|
snapshot_root ${target.directory}
|
||||||
sync_first 1
|
sync_first 1
|
||||||
@@ -17,12 +20,6 @@ let
|
|||||||
cmd_logger ${pkgs.inetutils}/bin/logger
|
cmd_logger ${pkgs.inetutils}/bin/logger
|
||||||
cmd_du ${pkgs.coreutils}/bin/du
|
cmd_du ${pkgs.coreutils}/bin/du
|
||||||
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
|
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
|
||||||
${lib.optionalString (target.preBackupHook != null) ''
|
|
||||||
cmd_preexec ${pkgs.writeShellScript "preexec.sh" ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
${target.preBackupHook}
|
|
||||||
''}
|
|
||||||
''}
|
|
||||||
|
|
||||||
${lib.optionalString (target.postBackupHook != null) ''
|
${lib.optionalString (target.postBackupHook != null) ''
|
||||||
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
||||||
@@ -31,11 +28,9 @@ let
|
|||||||
''}
|
''}
|
||||||
''}
|
''}
|
||||||
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
||||||
${lib.concatMapStringsSep "\n" (state: ''
|
${lib.concatMapStringsSep "\n" (folder: ''
|
||||||
${lib.concatMapStringsSep "\n" (folder: ''
|
backup ${folder} ${config.networking.hostName}/
|
||||||
backup ${folder} ${config.networking.hostName}/
|
'') uniqueFolders}
|
||||||
'') state.folders}
|
|
||||||
'') states}
|
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -129,14 +124,29 @@ in
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
${lib.concatMapStringsSep "\n" (target: ''
|
${lib.concatMapStringsSep "\n" (target: ''
|
||||||
(
|
${mountHook target}
|
||||||
${mountHook target}
|
echo "Creating backup '${target.name}'"
|
||||||
echo "Creating backup '${target.name}'"
|
|
||||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" sync
|
${lib.optionalString (target.preBackupHook != null) ''
|
||||||
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target (lib.attrValues config.clanCore.state))}" snapshot
|
(
|
||||||
)
|
${target.preBackupHook}
|
||||||
'') (builtins.attrValues cfg.targets)}
|
)
|
||||||
'')
|
''}
|
||||||
|
|
||||||
|
declare -A preCommandErrors
|
||||||
|
${lib.concatMapStringsSep "\n" (
|
||||||
|
state:
|
||||||
|
lib.optionalString (state.preBackupCommand != null) ''
|
||||||
|
echo "Running pre-backup command for ${state.name}"
|
||||||
|
if ! /run/current-system/sw/bin/${state.preBackupCommand}; then
|
||||||
|
preCommandErrors["${state.name}"]=1
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
) (builtins.attrValues config.clan.core.state)}
|
||||||
|
|
||||||
|
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" sync
|
||||||
|
rsnapshot -c "${pkgs.writeText "rsnapshot.conf" (rsnapshotConfig target)}" snapshot
|
||||||
|
'') (builtins.attrValues cfg.targets)}'')
|
||||||
(pkgs.writeShellScriptBin "localbackup-list" ''
|
(pkgs.writeShellScriptBin "localbackup-list" ''
|
||||||
set -efu -o pipefail
|
set -efu -o pipefail
|
||||||
export PATH=${
|
export PATH=${
|
||||||
@@ -167,6 +177,14 @@ in
|
|||||||
pkgs.gawk
|
pkgs.gawk
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
if [[ "''${NAME:-}" == "" ]]; then
|
||||||
|
echo "No backup name given via NAME environment variable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "''${FOLDERS:-}" == "" ]]; then
|
||||||
|
echo "No folders given via FOLDERS environment variable"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
name=$(awk -F'::' '{print $1}' <<< $NAME)
|
||||||
backupname=''${NAME#$name::}
|
backupname=''${NAME#$name::}
|
||||||
|
|
||||||
@@ -182,8 +200,9 @@ in
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IFS=';' read -ra FOLDER <<< "$FOLDERS"
|
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
|
||||||
for folder in "''${FOLDER[@]}"; do
|
for folder in "''${FOLDER[@]}"; do
|
||||||
|
mkdir -p "$folder"
|
||||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
||||||
done
|
done
|
||||||
'')
|
'')
|
||||||
@@ -213,7 +232,7 @@ in
|
|||||||
''
|
''
|
||||||
) cfg.targets;
|
) cfg.targets;
|
||||||
|
|
||||||
clanCore.backups.providers.localbackup = {
|
clan.core.backups.providers.localbackup = {
|
||||||
# TODO list needs to run locally or on the remote machine
|
# TODO list needs to run locally or on the remote machine
|
||||||
list = "localbackup-list";
|
list = "localbackup-list";
|
||||||
create = "localbackup-create";
|
create = "localbackup-create";
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
Securely sharing files and messages over a local network without internet connectivity.
|
---
|
||||||
|
description = "Securely sharing files and messages over a local network without internet connectivity."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
# - cli frontend: https://github.com/localsend/localsend/issues/11
|
# - cli frontend: https://github.com/localsend/localsend/issues/11
|
||||||
# - ipv6 support: https://github.com/localsend/localsend/issues/549
|
# - ipv6 support: https://github.com/localsend/localsend/issues/549
|
||||||
options.clan.localsend = {
|
options.clan.localsend = {
|
||||||
enable = lib.mkEnableOption (lib.mdDoc "enable the localsend module");
|
enable = lib.mkEnableOption "enable the localsend module";
|
||||||
defaultLocation = lib.mkOption {
|
defaultLocation = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The default download location";
|
description = "The default download location";
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.clan.localsend.enable {
|
config = lib.mkIf config.clan.localsend.enable {
|
||||||
clanCore.state.localsend.folders = [
|
clan.core.state.localsend.folders = [
|
||||||
"/var/localsend"
|
"/var/localsend"
|
||||||
config.clan.localsend.defaultLocation
|
config.clan.localsend.defaultLocation
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
A federated messaging server with end-to-end encryption.
|
---
|
||||||
|
description = "A federated messaging server with end-to-end encryption."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -6,16 +6,65 @@
|
|||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.clan.matrix-synapse;
|
cfg = config.clan.matrix-synapse;
|
||||||
|
nginx-vhost = "matrix.${config.clan.matrix-synapse.domain}";
|
||||||
|
element-web =
|
||||||
|
pkgs.runCommand "element-web-with-config" { nativeBuildInputs = [ pkgs.buildPackages.jq ]; }
|
||||||
|
''
|
||||||
|
cp -r ${pkgs.element-web} $out
|
||||||
|
chmod -R u+w $out
|
||||||
|
jq '."default_server_config"."m.homeserver" = { "base_url": "https://${nginx-vhost}:443", "server_name": "${config.clan.matrix-synapse.domain}" }' \
|
||||||
|
> $out/config.json < ${pkgs.element-web}/config.json
|
||||||
|
ln -s $out/config.json $out/config.${nginx-vhost}.json
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
|
# FIXME: This was taken from upstream. Drop this when our patch is upstream
|
||||||
{
|
{
|
||||||
|
options.services.matrix-synapse.package = lib.mkOption { readOnly = false; };
|
||||||
options.clan.matrix-synapse = {
|
options.clan.matrix-synapse = {
|
||||||
enable = lib.mkEnableOption "Enable matrix-synapse";
|
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The domain name of the matrix server";
|
description = "The domain name of the matrix server";
|
||||||
|
example = "example.com";
|
||||||
|
};
|
||||||
|
users = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "The name of the user";
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether the user should be an admin";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
description = "A list of users. Not that only new users will be created and existing ones are not modified.";
|
||||||
|
example.alice = {
|
||||||
|
admin = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = lib.mkIf cfg.enable {
|
imports = [
|
||||||
|
../postgresql
|
||||||
|
(lib.mkRemovedOptionModule [
|
||||||
|
"clan"
|
||||||
|
"matrix-synapse"
|
||||||
|
"enable"
|
||||||
|
] "Importing the module will already enable the service.")
|
||||||
|
|
||||||
|
../postgresql
|
||||||
|
];
|
||||||
|
config = {
|
||||||
services.matrix-synapse = {
|
services.matrix-synapse = {
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
@@ -29,6 +78,7 @@ in
|
|||||||
"turn:turn.matrix.org?transport=udp"
|
"turn:turn.matrix.org?transport=udp"
|
||||||
"turn:turn.matrix.org?transport=tcp"
|
"turn:turn.matrix.org?transport=tcp"
|
||||||
];
|
];
|
||||||
|
registration_shared_secret_path = "/run/synapse-registration-shared-secret";
|
||||||
listeners = [
|
listeners = [
|
||||||
{
|
{
|
||||||
port = 8008;
|
port = 8008;
|
||||||
@@ -49,45 +99,76 @@ in
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
extraConfigFiles = [ "/var/lib/matrix-synapse/registration_shared_secret.yaml" ];
|
|
||||||
};
|
|
||||||
systemd.services.matrix-synapse.serviceConfig.ExecStartPre = [
|
|
||||||
"+${pkgs.writeScript "copy_registration_shared_secret" ''
|
|
||||||
#!/bin/sh
|
|
||||||
cp ${config.clanCore.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path} /var/lib/matrix-synapse/registration_shared_secret.yaml
|
|
||||||
chown matrix-synapse:matrix-synapse /var/lib/matrix-synapse/registration_shared_secret.yaml
|
|
||||||
chmod 600 /var/lib/matrix-synapse/registration_shared_secret.yaml
|
|
||||||
''}"
|
|
||||||
];
|
|
||||||
|
|
||||||
clanCore.facts.services."matrix-synapse" = {
|
|
||||||
secret."synapse-registration_shared_secret" = { };
|
|
||||||
generator.path = with pkgs; [
|
|
||||||
coreutils
|
|
||||||
pwgen
|
|
||||||
];
|
|
||||||
generator.script = ''
|
|
||||||
echo "registration_shared_secret: $(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.postgresql.enable = true;
|
systemd.tmpfiles.settings."01-matrix" = {
|
||||||
# we need to use both ensusureDatabases and initialScript, because the former runs everytime but with the wrong collation
|
"/run/synapse-registration-shared-secret" = {
|
||||||
services.postgresql = {
|
C.argument =
|
||||||
ensureDatabases = [ "matrix-synapse" ];
|
config.clan.core.facts.services.matrix-synapse.secret.synapse-registration_shared_secret.path;
|
||||||
ensureUsers = [
|
z = {
|
||||||
{
|
mode = "0400";
|
||||||
name = "matrix-synapse";
|
user = "matrix-synapse";
|
||||||
ensureDBOwnership = true;
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.postgresql.users.matrix-synapse = { };
|
||||||
|
clan.postgresql.databases.matrix-synapse.create.options = {
|
||||||
|
TEMPLATE = "template0";
|
||||||
|
LC_COLLATE = "C";
|
||||||
|
LC_CTYPE = "C";
|
||||||
|
ENCODING = "UTF8";
|
||||||
|
OWNER = "matrix-synapse";
|
||||||
|
};
|
||||||
|
clan.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
|
||||||
|
|
||||||
|
clan.core.facts.services =
|
||||||
|
{
|
||||||
|
"matrix-synapse" = {
|
||||||
|
secret."synapse-registration_shared_secret" = { };
|
||||||
|
generator.path = with pkgs; [
|
||||||
|
coreutils
|
||||||
|
pwgen
|
||||||
|
];
|
||||||
|
generator.script = ''
|
||||||
|
echo -n "$(pwgen -s 32 1)" > "$secrets"/synapse-registration_shared_secret
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// lib.mapAttrs' (
|
||||||
|
name: user:
|
||||||
|
lib.nameValuePair "matrix-password-${user.name}" {
|
||||||
|
secret."matrix-password-${user.name}" = { };
|
||||||
|
generator.path = with pkgs; [ xkcdpass ];
|
||||||
|
generator.script = ''
|
||||||
|
xkcdpass -n 4 -d - > "$secrets"/${lib.escapeShellArg "matrix-password-${user.name}"}
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
];
|
) cfg.users;
|
||||||
initialScript = pkgs.writeText "synapse-init.sql" ''
|
|
||||||
CREATE DATABASE "matrix-synapse"
|
systemd.services.matrix-synapse =
|
||||||
TEMPLATE template0
|
let
|
||||||
LC_COLLATE = "C"
|
usersScript =
|
||||||
LC_CTYPE = "C";
|
''
|
||||||
'';
|
while ! ${pkgs.netcat}/bin/nc -z -v ::1 8008; do
|
||||||
};
|
if ! kill -0 "$MAINPID"; then exit 1; fi
|
||||||
|
sleep 1;
|
||||||
|
done
|
||||||
|
''
|
||||||
|
+ lib.concatMapStringsSep "\n" (user: ''
|
||||||
|
# only create user if it doesn't exist
|
||||||
|
/run/current-system/sw/bin/matrix-synapse-register_new_matrix_user --exists-ok --password-file ${
|
||||||
|
config.clan.core.facts.services."matrix-password-${user.name}".secret."matrix-password-${user.name}".path
|
||||||
|
} --user "${user.name}" ${if user.admin then "--admin" else "--no-admin"}
|
||||||
|
'') (lib.attrValues cfg.users);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
path = [ pkgs.curl ];
|
||||||
|
serviceConfig.ExecStartPost = [
|
||||||
|
(''+${pkgs.writeShellScript "matrix-synapse-create-users" usersScript}'')
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts = {
|
virtualHosts = {
|
||||||
@@ -102,7 +183,7 @@ in
|
|||||||
return 200 '${
|
return 200 '${
|
||||||
builtins.toJSON {
|
builtins.toJSON {
|
||||||
"m.homeserver" = {
|
"m.homeserver" = {
|
||||||
"base_url" = "https://matrix.${cfg.domain}";
|
"base_url" = "https://${nginx-vhost}";
|
||||||
};
|
};
|
||||||
"m.identity_server" = {
|
"m.identity_server" = {
|
||||||
"base_url" = "https://vector.im";
|
"base_url" = "https://vector.im";
|
||||||
@@ -111,15 +192,12 @@ in
|
|||||||
}';
|
}';
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"matrix.${cfg.domain}" = {
|
${nginx-vhost} = {
|
||||||
forceSSL = true;
|
forceSSL = true;
|
||||||
enableACME = true;
|
enableACME = true;
|
||||||
locations."/_matrix" = {
|
locations."/_matrix".proxyPass = "http://localhost:8008";
|
||||||
proxyPass = "http://localhost:8008";
|
locations."/_synapse".proxyPass = "http://localhost:8008";
|
||||||
};
|
locations."/".root = element-web;
|
||||||
locations."/test".extraConfig = ''
|
|
||||||
return 200 "Hello, world!";
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
A desktop streaming client optimized for remote gaming and synchronized movie viewing.
|
---
|
||||||
|
description = "A desktop streaming client optimized for remote gaming and synchronized movie viewing."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ in
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
||||||
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
||||||
config.clanCore.facts.services.moonlight.secret."moonlight.cert".path or ""
|
config.clan.core.facts.services.moonlight.secret."moonlight.cert".path or ""
|
||||||
}"
|
}"
|
||||||
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
||||||
config.clanCore.facts.services.moonlight.secret."moonlight.key".path or ""
|
config.clan.core.facts.services.moonlight.secret."moonlight.key".path or ""
|
||||||
}"
|
}"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ in
|
|||||||
systemd.user.services.moonlight-join = {
|
systemd.user.services.moonlight-join = {
|
||||||
description = "Join sunshine hosts";
|
description = "Join sunshine hosts";
|
||||||
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
||||||
config.clanCore.facts.services.moonlight.public."moonlight.cert".value or ""
|
config.clan.core.facts.services.moonlight.public."moonlight.cert".value or ""
|
||||||
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
@@ -68,7 +68,7 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clanCore.facts.services.moonlight = {
|
clan.core.facts.services.moonlight = {
|
||||||
secret."moonlight.key" = { };
|
secret."moonlight.key" = { };
|
||||||
secret."moonlight.cert" = { };
|
secret."moonlight.cert" = { };
|
||||||
public."moonlight.cert" = { };
|
public."moonlight.cert" = { };
|
||||||
|
|||||||
14
clanModules/mumble/README.md
Normal file
14
clanModules/mumble/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
description = "Open Source, Low Latency, High Quality Voice Chat."
|
||||||
|
categories = ["chat", "voice"]
|
||||||
|
---
|
||||||
|
The mumble clan module gives you:
|
||||||
|
|
||||||
|
- True low latency voice communication.
|
||||||
|
- Secure, authenticated encryption.
|
||||||
|
- Free software.
|
||||||
|
- Backed by a large and active open-source community.
|
||||||
|
|
||||||
|
This all set up in a way that allows peer-to-peer hosting.
|
||||||
|
Every machine inside the clan can be a host for mumble,
|
||||||
|
and thus it doesn't matter who in the network is online - as long as two people are online they are able to chat with each other.
|
||||||
105
clanModules/mumble/default.nix
Normal file
105
clanModules/mumble/default.nix
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
clanDir = lib.trace config.clan.core.clanDir config.clan.core.clanDir;
|
||||||
|
machineDir = clanDir + "/machines/";
|
||||||
|
machinesFileSet = builtins.readDir machineDir;
|
||||||
|
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
||||||
|
machineJson = builtins.toJSON (lib.trace machines machines);
|
||||||
|
certificateMachinePath = machines: machineDir + "/${machines}" + "/facts/mumble-cert";
|
||||||
|
certificatesUnchecked = builtins.map (
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = certificateMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists (lib.trace fullPath fullPath) then machine else null
|
||||||
|
) machines;
|
||||||
|
certificate = lib.filter (machine: machine != null) certificatesUnchecked;
|
||||||
|
machineCert = builtins.map (
|
||||||
|
machine:
|
||||||
|
lib.trace machine (lib.nameValuePair machine (builtins.readFile (certificateMachinePath machine)))
|
||||||
|
) certificate;
|
||||||
|
machineCertJson = builtins.toJSON (lib.trace machineCert machineCert);
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.clan.services.mumble = {
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.string;
|
||||||
|
default = "alice";
|
||||||
|
description = "The user mumble should be set up for.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
services.murmur = {
|
||||||
|
enable = true;
|
||||||
|
logDays = -1;
|
||||||
|
registerName = config.clan.core.machineName;
|
||||||
|
openFirewall = true;
|
||||||
|
bonjour = true;
|
||||||
|
sslKey = config.clan.core.facts.services.mumble.secret.mumble-key.path;
|
||||||
|
sslCert = config.clan.core.facts.services.mumble.public.mumble-cert.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.state.mumble.folders = [
|
||||||
|
"/var/lib/mumble"
|
||||||
|
"/var/lib/murmur"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d '/var/lib/mumble' 0770 '${config.clan.services.mumble.user}' 'users' - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages =
|
||||||
|
let
|
||||||
|
mumbleCfgDir = "/var/lib/mumble";
|
||||||
|
mumbleDatabasePath = "${mumbleCfgDir}/mumble.sqlite";
|
||||||
|
mumbleCfgPath = "/var/lib/mumble/mumble_settings.json";
|
||||||
|
populate-channels = pkgs.writers.writePython3 "mumble-populate-channels" {
|
||||||
|
libraries = [
|
||||||
|
pkgs.python3Packages.cryptography
|
||||||
|
pkgs.python3Packages.pyopenssl
|
||||||
|
];
|
||||||
|
flakeIgnore = [
|
||||||
|
# We don't live in the dark ages anymore.
|
||||||
|
# Languages like Python that are whitespace heavy will overrun
|
||||||
|
# 79 characters..
|
||||||
|
"E501"
|
||||||
|
];
|
||||||
|
} (builtins.readFile ./mumble-populate-channels.py);
|
||||||
|
mumble = pkgs.writeShellScriptBin "mumble" ''
|
||||||
|
set -xeu
|
||||||
|
mkdir -p ${mumbleCfgDir}
|
||||||
|
pushd "${mumbleCfgDir}"
|
||||||
|
XDG_DATA_HOME=${mumbleCfgDir}
|
||||||
|
XDG_DATA_DIR=${mumbleCfgDir}
|
||||||
|
${populate-channels} --ensure-config '${mumbleCfgPath}' --db-location ${mumbleDatabasePath}
|
||||||
|
echo ${machineCertJson}
|
||||||
|
${populate-channels} --machines '${machineJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath}
|
||||||
|
${populate-channels} --servers '${machineCertJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath} --cert True
|
||||||
|
${pkgs.mumble}/bin/mumble --config ${mumbleCfgPath} "$@"
|
||||||
|
popd
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
[ mumble ];
|
||||||
|
|
||||||
|
clan.core.facts.services.mumble = {
|
||||||
|
secret.mumble-key = { };
|
||||||
|
public.mumble-cert = { };
|
||||||
|
generator.path = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.openssl
|
||||||
|
];
|
||||||
|
generator.script = ''
|
||||||
|
openssl genrsa -out $secrets/mumble-key 2048
|
||||||
|
openssl req -new -x509 -key $secrets/mumble-key -out $facts/mumble-cert
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
249
clanModules/mumble/mumble-populate-channels.py
Normal file
249
clanModules/mumble/mumble-populate-channels.py
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_config(path: str, db_path: str) -> None:
|
||||||
|
# Default JSON structure if the file doesn't exist
|
||||||
|
default_json = {
|
||||||
|
"misc": {
|
||||||
|
"audio_wizard_has_been_shown": True,
|
||||||
|
"database_location": db_path,
|
||||||
|
"viewed_server_ping_consent_message": True,
|
||||||
|
},
|
||||||
|
"settings_version": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if the file exists
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path) as file:
|
||||||
|
data = json.load(file)
|
||||||
|
else:
|
||||||
|
data = default_json
|
||||||
|
# Create the file with default JSON structure
|
||||||
|
with open(path, "w") as file:
|
||||||
|
json.dump(data, file, indent=4)
|
||||||
|
|
||||||
|
# TODO: make sure to only update the diff
|
||||||
|
updated_data = {**default_json, **data}
|
||||||
|
|
||||||
|
# Write the modified JSON object back to the file
|
||||||
|
with open(path, "w") as file:
|
||||||
|
json.dump(updated_data, file, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_database(db_location: str) -> None:
|
||||||
|
"""
|
||||||
|
Initializes the database. If the database or the servers table does not exist, it creates them.
|
||||||
|
|
||||||
|
:param db_location: The path to the SQLite database
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(db_location)
|
||||||
|
try:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Create the servers table if it doesn't exist
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS servers (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
hostname TEXT NOT NULL,
|
||||||
|
port INTEGER NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
url TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print(f"An error occurred while initializing the database: {e}")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_certificates(
|
||||||
|
db_location: str, hostname: str, port: str, digest: str
|
||||||
|
) -> None:
|
||||||
|
# Connect to the SQLite database
|
||||||
|
conn = sqlite3.connect(db_location)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a cursor object
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# TODO: check if cert already there
|
||||||
|
# if server_check(cursor, name, hostname):
|
||||||
|
# print(
|
||||||
|
# f"Server with name '{name}' and hostname '{hostname}' already exists."
|
||||||
|
# )
|
||||||
|
# return
|
||||||
|
|
||||||
|
# SQL command to insert data into the servers table
|
||||||
|
insert_query = """
|
||||||
|
INSERT INTO cert (hostname, port, digest)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Data to be inserted
|
||||||
|
data = (hostname, port, digest)
|
||||||
|
|
||||||
|
# Execute the insert command with the provided data
|
||||||
|
cursor.execute(insert_query, data)
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print("Data has been successfully inserted.")
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
finally:
|
||||||
|
# Close the connection
|
||||||
|
conn.close()
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_digest(cert: str) -> str:
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
|
||||||
|
cert = cert.strip()
|
||||||
|
cert = cert.encode("utf-8")
|
||||||
|
cert = x509.load_pem_x509_certificate(cert, default_backend())
|
||||||
|
digest = cert.fingerprint(hashes.SHA1()).hex()
|
||||||
|
return digest
|
||||||
|
|
||||||
|
|
||||||
|
def server_check(cursor: str, name: str, hostname: str) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a server with the given name and hostname already exists.
|
||||||
|
|
||||||
|
:param cursor: The database cursor
|
||||||
|
:param name: The name of the server
|
||||||
|
:param hostname: The hostname of the server
|
||||||
|
:return: True if the server exists, False otherwise
|
||||||
|
"""
|
||||||
|
check_query = """
|
||||||
|
SELECT 1 FROM servers WHERE name = ? AND hostname = ?
|
||||||
|
"""
|
||||||
|
cursor.execute(check_query, (name, hostname))
|
||||||
|
return cursor.fetchone() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def insert_server(
|
||||||
|
name: str,
|
||||||
|
hostname: str,
|
||||||
|
port: str,
|
||||||
|
username: str,
|
||||||
|
password: str,
|
||||||
|
url: str,
|
||||||
|
db_location: str,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Inserts a new server record into the servers table.
|
||||||
|
|
||||||
|
:param name: The name of the server
|
||||||
|
:param hostname: The hostname of the server
|
||||||
|
:param port: The port number
|
||||||
|
:param username: The username
|
||||||
|
:param password: The password
|
||||||
|
:param url: The URL
|
||||||
|
"""
|
||||||
|
# Connect to the SQLite database
|
||||||
|
conn = sqlite3.connect(db_location)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a cursor object
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
if server_check(cursor, name, hostname):
|
||||||
|
print(
|
||||||
|
f"Server with name '{name}' and hostname '{hostname}' already exists."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# SQL command to insert data into the servers table
|
||||||
|
insert_query = """
|
||||||
|
INSERT INTO servers (name, hostname, port, username, password, url)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Data to be inserted
|
||||||
|
data = (name, hostname, port, username, password, url)
|
||||||
|
|
||||||
|
# Execute the insert command with the provided data
|
||||||
|
cursor.execute(insert_query, data)
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print("Data has been successfully inserted.")
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
finally:
|
||||||
|
# Close the connection
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
port = 64738
|
||||||
|
password = ""
|
||||||
|
url = None
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="initialize_mumble",
|
||||||
|
)
|
||||||
|
|
||||||
|
subparser = parser.add_subparsers(dest="certificates")
|
||||||
|
# cert_parser = subparser.add_parser("certificates")
|
||||||
|
|
||||||
|
parser.add_argument("--cert")
|
||||||
|
parser.add_argument("--digest")
|
||||||
|
parser.add_argument("--machines")
|
||||||
|
parser.add_argument("--servers")
|
||||||
|
parser.add_argument("--username")
|
||||||
|
parser.add_argument("--db-location")
|
||||||
|
parser.add_argument("--ensure-config")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
if args.ensure_config:
|
||||||
|
ensure_config(args.ensure_config, args.db_location)
|
||||||
|
print("Initialized config")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
if args.servers:
|
||||||
|
print(args.servers)
|
||||||
|
servers = json.loads(f"{args.servers}")
|
||||||
|
db_location = args.db_location
|
||||||
|
for server in servers:
|
||||||
|
digest = calculate_digest(server.get("value"))
|
||||||
|
name = server.get("name")
|
||||||
|
initialize_certificates(db_location, name, port, digest)
|
||||||
|
print("Initialized certificates")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
initialize_database(args.db_location)
|
||||||
|
|
||||||
|
# Insert the server into the database
|
||||||
|
print(args.machines)
|
||||||
|
machines = json.loads(f"{args.machines}")
|
||||||
|
print(machines)
|
||||||
|
print(list(machines))
|
||||||
|
|
||||||
|
for machine in list(machines):
|
||||||
|
print(f"Inserting {machine}.")
|
||||||
|
insert_server(
|
||||||
|
machine,
|
||||||
|
machine,
|
||||||
|
port,
|
||||||
|
args.username,
|
||||||
|
password,
|
||||||
|
url,
|
||||||
|
args.db_location,
|
||||||
|
)
|
||||||
42
clanModules/mumble/test.nix
Normal file
42
clanModules/mumble/test.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{ pkgs, self, ... }:
|
||||||
|
pkgs.nixosTest {
|
||||||
|
name = "mumble";
|
||||||
|
nodes.peer1 =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.mumble
|
||||||
|
self.inputs.clan-core.nixosModules.clanCore
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
clan.core.machineName = "peer1";
|
||||||
|
clan.core.clanDir = ./.;
|
||||||
|
|
||||||
|
documentation.enable = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
nodes.peer2 =
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.mumble
|
||||||
|
self.inputs.clan-core.nixosModules.clanCore
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
|
||||||
|
clan.core.machineName = "peer2";
|
||||||
|
clan.core.clanDir = ./.;
|
||||||
|
|
||||||
|
documentation.enable = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
start_all()
|
||||||
|
'';
|
||||||
|
|
||||||
|
}
|
||||||
4
clanModules/packages/README.md
Normal file
4
clanModules/packages/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
description = "Define package sets from nixpkgs and install them on one or more machines"
|
||||||
|
categories = ["packages"]
|
||||||
|
---
|
||||||
19
clanModules/packages/default.nix
Normal file
19
clanModules/packages/default.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options.clan.packages = {
|
||||||
|
packages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
description = "The packages to install on the machine";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
environment.systemPackages = map (
|
||||||
|
pName: lib.getAttrFromPath (lib.splitString "." pName) pkgs
|
||||||
|
) config.clan.packages.packages;
|
||||||
|
};
|
||||||
|
}
|
||||||
1
clanModules/packages/roles/default.nix
Normal file
1
clanModules/packages/roles/default.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ }
|
||||||
3
clanModules/postgresql/README.md
Normal file
3
clanModules/postgresql/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
description = "A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance."
|
||||||
|
---
|
||||||
226
clanModules/postgresql/default.nix
Normal file
226
clanModules/postgresql/default.nix
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
createDatatbaseState =
|
||||||
|
db:
|
||||||
|
let
|
||||||
|
folder = "/var/backup/postgres/${db.name}";
|
||||||
|
current = "${folder}/pg-dump";
|
||||||
|
compression = lib.optionalString (lib.versionAtLeast config.services.postgresql.package.version "16") "--compress=zstd";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
folders = [ folder ];
|
||||||
|
preBackupScript = ''
|
||||||
|
export PATH=${
|
||||||
|
lib.makeBinPath [
|
||||||
|
config.services.postgresql.package
|
||||||
|
config.systemd.package
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.util-linux
|
||||||
|
pkgs.zstd
|
||||||
|
]
|
||||||
|
}
|
||||||
|
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "${folder}"
|
||||||
|
runuser -u postgres -- pg_dump ${compression} --dbname=${db.name} -Fc -c > "${current}.tmp"
|
||||||
|
mv "${current}.tmp" ${current}
|
||||||
|
'';
|
||||||
|
postRestoreScript = ''
|
||||||
|
export PATH=${
|
||||||
|
lib.makeBinPath [
|
||||||
|
config.services.postgresql.package
|
||||||
|
config.systemd.package
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.util-linux
|
||||||
|
pkgs.zstd
|
||||||
|
pkgs.gnugrep
|
||||||
|
]
|
||||||
|
}
|
||||||
|
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "Waiting for postgres to be ready..."
|
||||||
|
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
|
||||||
|
if ! systemctl is-active postgresql; then exit 1; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -e "${current}" ]]; then
|
||||||
|
(
|
||||||
|
systemctl stop ${lib.concatStringsSep " " db.restore.stopOnRestore}
|
||||||
|
trap "systemctl start ${lib.concatStringsSep " " db.restore.stopOnRestore}" EXIT
|
||||||
|
|
||||||
|
mkdir -p "${folder}"
|
||||||
|
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
|
||||||
|
runuser -u postgres -- dropdb "${db.name}"
|
||||||
|
fi
|
||||||
|
runuser -u postgres -- pg_restore -C -d postgres "${current}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
echo No database backup found, skipping restore
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
createDatabase = db: ''
|
||||||
|
CREATE DATABASE "${db.name}" ${
|
||||||
|
lib.concatStringsSep " " (
|
||||||
|
lib.mapAttrsToList (name: value: "${name} = '${value}'") db.create.options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
cfg = config.clan.postgresql;
|
||||||
|
|
||||||
|
userClauses = lib.mapAttrsToList (
|
||||||
|
_: user:
|
||||||
|
''$PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"' ''
|
||||||
|
) cfg.users;
|
||||||
|
databaseClauses = lib.mapAttrsToList (
|
||||||
|
name: db:
|
||||||
|
lib.optionalString db.create.enable ''$PSQL -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${name}'" | grep -q 1 || $PSQL -d postgres -c ${lib.escapeShellArg (createDatabase db)} ''
|
||||||
|
) cfg.databases;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.clan.postgresql = {
|
||||||
|
# we are reimplemeting ensureDatabase and ensureUser options here to allow to create databases with options
|
||||||
|
databases = lib.mkOption {
|
||||||
|
description = "Databases to create";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "Database name.";
|
||||||
|
};
|
||||||
|
service = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
description = "Service name that we associate with the database.";
|
||||||
|
};
|
||||||
|
# set to false, in case the upstream module uses ensureDatabase option
|
||||||
|
create.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Create the database if it does not exist.";
|
||||||
|
};
|
||||||
|
create.options = lib.mkOption {
|
||||||
|
description = "Options to pass to the CREATE DATABASE command.";
|
||||||
|
type = lib.types.lazyAttrsOf lib.types.str;
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
TEMPLATE = "template0";
|
||||||
|
LC_COLLATE = "C";
|
||||||
|
LC_CTYPE = "C";
|
||||||
|
ENCODING = "UTF8";
|
||||||
|
OWNER = "foo";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
restore.stopOnRestore = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "List of systemd services to stop before restoring the database.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
users = lib.mkOption {
|
||||||
|
description = "Users to create";
|
||||||
|
default = { };
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options.name = lib.mkOption {
|
||||||
|
description = "User name";
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
services.postgresql.settings = {
|
||||||
|
wal_level = "replica";
|
||||||
|
max_wal_senders = 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgresql.enable = true;
|
||||||
|
# We are duplicating a bit the upstream module but allow to create databases with options
|
||||||
|
systemd.services.postgresql.postStart = ''
|
||||||
|
PSQL="psql --port=${builtins.toString config.services.postgresql.settings.port}"
|
||||||
|
|
||||||
|
while ! $PSQL -d postgres -c "" 2> /dev/null; do
|
||||||
|
if ! kill -0 "$MAINPID"; then exit 1; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
${lib.concatStringsSep "\n" userClauses}
|
||||||
|
${lib.concatStringsSep "\n" databaseClauses}
|
||||||
|
'';
|
||||||
|
|
||||||
|
clan.core.state = lib.mapAttrs' (
|
||||||
|
_: db: lib.nameValuePair db.service (createDatatbaseState db)
|
||||||
|
) config.clan.postgresql.databases;
|
||||||
|
|
||||||
|
environment.systemPackages = builtins.map (
|
||||||
|
db:
|
||||||
|
let
|
||||||
|
folder = "/var/backup/postgres/${db.name}";
|
||||||
|
current = "${folder}/pg-dump";
|
||||||
|
in
|
||||||
|
pkgs.writeShellScriptBin "postgres-db-restore-command-${db.name}" ''
|
||||||
|
export PATH=${
|
||||||
|
lib.makeBinPath [
|
||||||
|
config.services.postgresql.package
|
||||||
|
config.systemd.package
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.util-linux
|
||||||
|
pkgs.zstd
|
||||||
|
pkgs.gnugrep
|
||||||
|
]
|
||||||
|
}
|
||||||
|
while [[ "$(systemctl is-active postgresql)" == activating ]]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "Waiting for postgres to be ready..."
|
||||||
|
while ! runuser -u postgres -- psql --port=${builtins.toString config.services.postgresql.settings.port} -d postgres -c "" ; do
|
||||||
|
if ! systemctl is-active postgresql; then exit 1; fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -e "${current}" ]]; then
|
||||||
|
(
|
||||||
|
${
|
||||||
|
lib.optionalString (db.restore.stopOnRestore != [ ]) ''
|
||||||
|
systemctl stop ${builtins.toString db.restore.stopOnRestore}
|
||||||
|
trap "systemctl start ${builtins.toString db.restore.stopOnRestore}" EXIT
|
||||||
|
''
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir -p "${folder}"
|
||||||
|
if runuser -u postgres -- psql -d postgres -c "SELECT 1 FROM pg_database WHERE datname = '${db.name}'" | grep -q 1; then
|
||||||
|
runuser -u postgres -- dropdb "${db.name}"
|
||||||
|
fi
|
||||||
|
runuser -u postgres -- pg_restore -C -d postgres "${current}"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
echo No database backup found, skipping restore
|
||||||
|
fi
|
||||||
|
''
|
||||||
|
) (builtins.attrValues config.clan.postgresql.databases);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
Automatically generates and configures a password for the root user.
|
---
|
||||||
|
description = "Automatically generates and configures a password for the root user."
|
||||||
---
|
---
|
||||||
|
|
||||||
After the system was installed/deployed the following command can be used to display the root-password:
|
After the system was installed/deployed the following command can be used to display the root-password:
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
{ pkgs, config, ... }:
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
users.mutableUsers = false;
|
users.mutableUsers = false;
|
||||||
users.users.root.hashedPasswordFile =
|
users.users.root.hashedPasswordFile =
|
||||||
config.clanCore.facts.services.root-password.secret.password-hash.path;
|
config.clan.core.facts.services.root-password.secret.password-hash.path;
|
||||||
sops.secrets."${config.clanCore.machineName}-password-hash".neededForUsers = true;
|
|
||||||
clanCore.facts.services.root-password = {
|
sops.secrets = lib.mkIf (config.clan.core.facts.secretStore == "sops") {
|
||||||
|
"${config.clan.core.machineName}-password-hash".neededForUsers = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.facts.services.root-password = {
|
||||||
secret.password = { };
|
secret.password = { };
|
||||||
secret.password-hash = { };
|
secret.password-hash = { };
|
||||||
generator.path = with pkgs; [
|
generator.path = with pkgs; [
|
||||||
@@ -13,8 +22,8 @@
|
|||||||
mkpasswd
|
mkpasswd
|
||||||
];
|
];
|
||||||
generator.script = ''
|
generator.script = ''
|
||||||
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/password
|
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > $secrets/password
|
||||||
cat $secrets/password | mkpasswd -s -m sha-512 > $secrets/password-hash
|
cat $secrets/password | mkpasswd -s -m sha-512 | tr -d "\n" > $secrets/password-hash
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
42
clanModules/single-disk/README.md
Normal file
42
clanModules/single-disk/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
description = "Configures partitioning of the main disk"
|
||||||
|
categories = ["disk-layout"]
|
||||||
|
---
|
||||||
|
# Primary Disk Layout
|
||||||
|
|
||||||
|
A module for the "disk-layout" category MUST be choosen.
|
||||||
|
|
||||||
|
There is exactly one slot for this type of module in the UI, if you don't fill the slot, your machine cannot boot
|
||||||
|
|
||||||
|
This module is a good choice for most machines. In the future clan will offer a broader choice of disk-layouts
|
||||||
|
|
||||||
|
The UI will ask for the options of this module:
|
||||||
|
|
||||||
|
`device: "/dev/null"`
|
||||||
|
|
||||||
|
# Usage example
|
||||||
|
|
||||||
|
`inventory.json`
|
||||||
|
```json
|
||||||
|
"services": {
|
||||||
|
"single-disk": {
|
||||||
|
"default": {
|
||||||
|
"meta": {
|
||||||
|
"name": "single-disk"
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"default": {
|
||||||
|
"machines": ["jon"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"machines": {
|
||||||
|
"jon": {
|
||||||
|
"config": {
|
||||||
|
"device": "/dev/null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
53
clanModules/single-disk/default.nix
Normal file
53
clanModules/single-disk/default.nix
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
{
|
||||||
|
options.clan.single-disk = {
|
||||||
|
device = lib.mkOption {
|
||||||
|
default = null;
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
description = "The primary disk device to install the system on";
|
||||||
|
# Question: should we set a default here?
|
||||||
|
# default = "/dev/null";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
boot.loader.grub.efiSupport = lib.mkDefault true;
|
||||||
|
boot.loader.grub.efiInstallAsRemovable = lib.mkDefault true;
|
||||||
|
disko.devices = {
|
||||||
|
disk = {
|
||||||
|
main = {
|
||||||
|
type = "disk";
|
||||||
|
# This is set through the UI
|
||||||
|
device = config.clan.single-disk.device;
|
||||||
|
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02"; # for grub MBR
|
||||||
|
priority = 1;
|
||||||
|
};
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
1
clanModules/single-disk/roles/default.nix
Normal file
1
clanModules/single-disk/roles/default.nix
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{ }
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
Enables secure remote access to the machine over ssh
|
---
|
||||||
|
description = "Enables secure remote access to the machine over ssh"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
|
|
||||||
services.openssh.hostKeys = [
|
services.openssh.hostKeys = [
|
||||||
{
|
{
|
||||||
path = config.clanCore.facts.services.openssh.secret."ssh.id_ed25519".path;
|
path = config.clan.core.facts.services.openssh.secret."ssh.id_ed25519".path;
|
||||||
type = "ed25519";
|
type = "ed25519";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
clanCore.facts.services.openssh = {
|
clan.core.facts.services.openssh = {
|
||||||
secret."ssh.id_ed25519" = { };
|
secret."ssh.id_ed25519" = { };
|
||||||
public."ssh.id_ed25519.pub" = { };
|
public."ssh.id_ed25519.pub" = { };
|
||||||
generator.path = [
|
generator.path = [
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
Statically configure the host names of machines based on their respective zerotier-ip.
|
---
|
||||||
|
description = "Statically configure the host names of machines based on their respective zerotier-ip."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -3,20 +3,36 @@
|
|||||||
options.clan.static-hosts = {
|
options.clan.static-hosts = {
|
||||||
excludeHosts = lib.mkOption {
|
excludeHosts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ config.clanCore.machineName ];
|
default =
|
||||||
|
if config.clan.static-hosts.topLevelDomain != "" then [ ] else [ config.clan.core.machineName ];
|
||||||
description = "Hosts that should be excluded";
|
description = "Hosts that should be excluded";
|
||||||
};
|
};
|
||||||
|
topLevelDomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Top level domain to reach hosts";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config.networking.hosts =
|
config.networking.hosts =
|
||||||
let
|
let
|
||||||
clanDir = config.clanCore.clanDir;
|
clanDir = config.clan.core.clanDir;
|
||||||
machineDir = clanDir + "/machines/";
|
machineDir = clanDir + "/machines/";
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
machines = builtins.readDir machineDir;
|
machinesFileSet = builtins.readDir machineDir;
|
||||||
|
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
||||||
|
networkIpsUnchecked = builtins.map (
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = zerotierIpMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then machine else null
|
||||||
|
) machines;
|
||||||
|
networkIps = lib.filter (machine: machine != null) networkIpsUnchecked;
|
||||||
|
machinesWithIp = lib.filterAttrs (name: _: (lib.elem name networkIps)) machinesFileSet;
|
||||||
filteredMachines = lib.filterAttrs (
|
filteredMachines = lib.filterAttrs (
|
||||||
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
||||||
) machines;
|
) machinesWithIp;
|
||||||
in
|
in
|
||||||
lib.filterAttrs (_: value: value != null) (
|
lib.filterAttrs (_: value: value != null) (
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
@@ -24,7 +40,15 @@
|
|||||||
let
|
let
|
||||||
path = zerotierIpMachinePath machine;
|
path = zerotierIpMachinePath machine;
|
||||||
in
|
in
|
||||||
if builtins.pathExists path then lib.nameValuePair (builtins.readFile path) [ machine ] else null
|
if builtins.pathExists path then
|
||||||
|
lib.nameValuePair (builtins.readFile path) (
|
||||||
|
if (config.clan.static-hosts.topLevelDomain == "") then
|
||||||
|
[ machine ]
|
||||||
|
else
|
||||||
|
[ "${machine}.${config.clan.static-hosts.topLevelDomain}" ]
|
||||||
|
)
|
||||||
|
else
|
||||||
|
{ }
|
||||||
) filteredMachines
|
) filteredMachines
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
A desktop streaming server optimized for remote gaming and synchronized movie viewing.
|
---
|
||||||
|
description = "A desktop streaming server optimized for remote gaming and synchronized movie viewing."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ in
|
|||||||
systemd.tmpfiles.rules = [
|
systemd.tmpfiles.rules = [
|
||||||
"d '/var/lib/sunshine' 0770 'user' 'users' - -"
|
"d '/var/lib/sunshine' 0770 'user' 'users' - -"
|
||||||
"C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${
|
"C '/var/lib/sunshine/sunshine.cert' 0644 'user' 'users' - ${
|
||||||
config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or ""
|
config.clan.core.facts.services.sunshine.secret."sunshine.cert".path or ""
|
||||||
}"
|
}"
|
||||||
"C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${
|
"C '/var/lib/sunshine/sunshine.key' 0644 'user' 'users' - ${
|
||||||
config.clanCore.facts.services.sunshine.secret."sunshine.key".path or ""
|
config.clan.core.facts.services.sunshine.secret."sunshine.key".path or ""
|
||||||
}"
|
}"
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -117,8 +117,8 @@ in
|
|||||||
RestartSec = "5s";
|
RestartSec = "5s";
|
||||||
ReadWritePaths = [ "/var/lib/sunshine" ];
|
ReadWritePaths = [ "/var/lib/sunshine" ];
|
||||||
ReadOnlyPaths = [
|
ReadOnlyPaths = [
|
||||||
(config.clanCore.facts.services.sunshine.secret."sunshine.key".path or "")
|
(config.clan.core.facts.services.sunshine.secret."sunshine.key".path or "")
|
||||||
(config.clanCore.facts.services.sunshine.secret."sunshine.cert".path or "")
|
(config.clan.core.facts.services.sunshine.secret."sunshine.cert".path or "")
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
@@ -137,7 +137,7 @@ in
|
|||||||
startLimitIntervalSec = 500;
|
startLimitIntervalSec = 500;
|
||||||
script = ''
|
script = ''
|
||||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
|
${ms-accept}/bin/moonlight-sunshine-accept sunshine init-state --uuid ${
|
||||||
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
config.clan.core.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||||
} --state-file /var/lib/sunshine/state.json
|
} --state-file /var/lib/sunshine/state.json
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@@ -173,9 +173,9 @@ in
|
|||||||
startLimitIntervalSec = 500;
|
startLimitIntervalSec = 500;
|
||||||
script = ''
|
script = ''
|
||||||
${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${
|
${ms-accept}/bin/moonlight-sunshine-accept sunshine listen --port ${builtins.toString listenPort} --uuid ${
|
||||||
config.clanCore.facts.services.sunshine.public.sunshine-uuid.value or null
|
config.clan.core.facts.services.sunshine.public.sunshine-uuid.value or null
|
||||||
} --state /var/lib/sunshine/state.json --cert '${
|
} --state /var/lib/sunshine/state.json --cert '${
|
||||||
config.clanCore.facts.services.sunshine.public."sunshine.cert".value or null
|
config.clan.core.facts.services.sunshine.public."sunshine.cert".value or null
|
||||||
}'
|
}'
|
||||||
'';
|
'';
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
@@ -187,7 +187,7 @@ in
|
|||||||
wantedBy = [ "graphical-session.target" ];
|
wantedBy = [ "graphical-session.target" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
clanCore.facts.services.ergochat = {
|
clan.core.facts.services.ergochat = {
|
||||||
secret."sunshine.key" = { };
|
secret."sunshine.key" = { };
|
||||||
secret."sunshine.cert" = { };
|
secret."sunshine.cert" = { };
|
||||||
public."sunshine-uuid" = { };
|
public."sunshine-uuid" = { };
|
||||||
|
|||||||
3
clanModules/syncthing-static-peers/README.md
Normal file
3
clanModules/syncthing-static-peers/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
description = "Statically configure syncthing peers through clan"
|
||||||
|
---
|
||||||
108
clanModules/syncthing-static-peers/default.nix
Normal file
108
clanModules/syncthing-static-peers/default.nix
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
clanDir = config.clan.core.clanDir;
|
||||||
|
machineDir = clanDir + "/machines/";
|
||||||
|
syncthingPublicKeyPath = machines: machineDir + machines + "/facts/syncthing.pub";
|
||||||
|
machinesFileSet = builtins.readDir machineDir;
|
||||||
|
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
||||||
|
syncthingPublicKeysUnchecked = builtins.map (
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = syncthingPublicKeyPath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then machine else null
|
||||||
|
) machines;
|
||||||
|
syncthingPublicKeyMachines = lib.filter (machine: machine != null) syncthingPublicKeysUnchecked;
|
||||||
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
|
networkIpsUnchecked = builtins.map (
|
||||||
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = zerotierIpMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then machine else null
|
||||||
|
) machines;
|
||||||
|
networkIpMachines = lib.filter (machine: machine != null) networkIpsUnchecked;
|
||||||
|
devices = builtins.map (machine: {
|
||||||
|
name = machine;
|
||||||
|
value = {
|
||||||
|
name = machine;
|
||||||
|
id = (lib.removeSuffix "\n" (builtins.readFile (syncthingPublicKeyPath machine)));
|
||||||
|
addresses =
|
||||||
|
[ "dynamic" ]
|
||||||
|
++ (
|
||||||
|
if (lib.elem machine networkIpMachines) then
|
||||||
|
[ "tcp://[${(lib.removeSuffix "\n" (builtins.readFile (zerotierIpMachinePath machine)))}]:22000" ]
|
||||||
|
else
|
||||||
|
[ ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}) syncthingPublicKeyMachines;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.clan.syncthing-static-peers = {
|
||||||
|
excludeMachines = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
example = [ config.clan.core.machineName ];
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Machines that should not be added.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.services.syncthing.settings.devices = (builtins.listToAttrs devices);
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
{
|
||||||
|
# Syncthing ports: 8384 for remote access to GUI
|
||||||
|
# 22000 TCP and/or UDP for sync traffic
|
||||||
|
# 21027/UDP for discovery
|
||||||
|
# source: https://docs.syncthing.net/users/firewall.html
|
||||||
|
networking.firewall.interfaces."zt+".allowedTCPPorts = [
|
||||||
|
8384
|
||||||
|
22000
|
||||||
|
];
|
||||||
|
networking.firewall.allowedTCPPorts = [ 8384 ];
|
||||||
|
networking.firewall.interfaces."zt+".allowedUDPPorts = [
|
||||||
|
22000
|
||||||
|
21027
|
||||||
|
];
|
||||||
|
|
||||||
|
# Activates inotify compatibility on syncthing
|
||||||
|
# use mkOverride 900 here as it otherwise would collide with the default of the
|
||||||
|
# upstream nixos xserver.nix
|
||||||
|
boot.kernel.sysctl."fs.inotify.max_user_watches" = lib.mkOverride 900 524288;
|
||||||
|
|
||||||
|
services.syncthing = {
|
||||||
|
enable = true;
|
||||||
|
configDir = "/var/lib/syncthing";
|
||||||
|
group = "syncthing";
|
||||||
|
|
||||||
|
key = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.key".path or null;
|
||||||
|
cert = lib.mkDefault config.clan.core.facts.services.syncthing.secret."syncthing.cert".path or null;
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.facts.services.syncthing = {
|
||||||
|
secret."syncthing.key" = { };
|
||||||
|
secret."syncthing.cert" = { };
|
||||||
|
public."syncthing.pub" = { };
|
||||||
|
generator.path = [
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.gnugrep
|
||||||
|
pkgs.syncthing
|
||||||
|
];
|
||||||
|
generator.script = ''
|
||||||
|
syncthing generate --config "$secrets"
|
||||||
|
mv "$secrets"/key.pem "$secrets"/syncthing.key
|
||||||
|
mv "$secrets"/cert.pem "$secrets"/syncthing.cert
|
||||||
|
cat "$secrets"/config.xml | grep -oP '(?<=<device id=")[^"]+' | uniq > "$facts"/syncthing.pub
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
A secure, file synchronization app for devices over networks, offering a private alternative to cloud services.
|
---
|
||||||
|
description = "A secure, file synchronization app for devices over networks, offering a private alternative to cloud services."
|
||||||
---
|
---
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,14 @@
|
|||||||
{
|
{
|
||||||
options.clan.syncthing = {
|
options.clan.syncthing = {
|
||||||
id = lib.mkOption {
|
id = lib.mkOption {
|
||||||
|
description = ''
|
||||||
|
The ID of the machine.
|
||||||
|
It is generated automatically by default.
|
||||||
|
'';
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.nullOr lib.types.str;
|
||||||
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
|
example = "BABNJY4-G2ICDLF-QQEG7DD-N3OBNGF-BCCOFK6-MV3K7QJ-2WUZHXS-7DTW4AS";
|
||||||
default = config.clanCore.facts.services.syncthing.public."syncthing.pub".value or null;
|
default = config.clan.core.facts.services.syncthing.public."syncthing.pub".value or null;
|
||||||
defaultText = "config.clanCore.facts.services.syncthing.public.\"syncthing.pub\".value";
|
defaultText = "config.clan.core.facts.services.syncthing.public.\"syncthing.pub\".value";
|
||||||
};
|
};
|
||||||
introducer = lib.mkOption {
|
introducer = lib.mkOption {
|
||||||
description = ''
|
description = ''
|
||||||
@@ -94,7 +98,7 @@
|
|||||||
settings = {
|
settings = {
|
||||||
options = {
|
options = {
|
||||||
urAccepted = -1;
|
urAccepted = -1;
|
||||||
allowedNetworks = [ config.clan.networking.zerotier.subnet ];
|
allowedNetworks = [ config.clan.core.networking.zerotier.subnet ];
|
||||||
};
|
};
|
||||||
devices =
|
devices =
|
||||||
{ }
|
{ }
|
||||||
@@ -119,7 +123,7 @@
|
|||||||
getPendingDevices = "/rest/cluster/pending/devices";
|
getPendingDevices = "/rest/cluster/pending/devices";
|
||||||
postNewDevice = "/rest/config/devices";
|
postNewDevice = "/rest/config/devices";
|
||||||
SharedFolderById = "/rest/config/folders/";
|
SharedFolderById = "/rest/config/folders/";
|
||||||
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
apiKey = config.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||||
in
|
in
|
||||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||||
description = "Syncthing auto accept devices";
|
description = "Syncthing auto accept devices";
|
||||||
@@ -161,7 +165,7 @@
|
|||||||
|
|
||||||
systemd.services.syncthing-init-api-key =
|
systemd.services.syncthing-init-api-key =
|
||||||
let
|
let
|
||||||
apiKey = config.clanCore.facts.services.syncthing.secret."syncthing.api".path or null;
|
apiKey = config.clan.core.facts.services.syncthing.secret."syncthing.api".path or null;
|
||||||
in
|
in
|
||||||
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
lib.mkIf config.clan.syncthing.autoAcceptDevices {
|
||||||
description = "Set the api key";
|
description = "Set the api key";
|
||||||
@@ -183,7 +187,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clanCore.facts.services.syncthing = {
|
clan.core.facts.services.syncthing = {
|
||||||
secret."syncthing.key" = { };
|
secret."syncthing.key" = { };
|
||||||
secret."syncthing.cert" = { };
|
secret."syncthing.cert" = { };
|
||||||
secret."syncthing.api" = { };
|
secret."syncthing.api" = { };
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
Modern web IRC client
|
---
|
||||||
|
description = "Modern web IRC client"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ _: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
clanCore.state.thelounde.folders = [ "/var/lib/thelounge" ];
|
clan.core.state.thelounde.folders = [ "/var/lib/thelounge" ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
This module sets the `clan.lol` and `nix-community` cache up as a trusted cache.
|
---
|
||||||
|
description = "This module sets the `clan.lol` and `nix-community` cache up as a trusted cache."
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
Automatically generates and configures a password for the specified user account.
|
---
|
||||||
|
description = "Automatically generates and configures a password for the specified user account."
|
||||||
---
|
---
|
||||||
|
|
||||||
If setting the option prompt to true, the user will be prompted to type in their desired password.
|
If setting the option prompt to true, the user will be prompted to type in their desired password.
|
||||||
|
|||||||
@@ -22,9 +22,13 @@
|
|||||||
config = {
|
config = {
|
||||||
users.mutableUsers = false;
|
users.mutableUsers = false;
|
||||||
users.users.${config.clan.user-password.user}.hashedPasswordFile =
|
users.users.${config.clan.user-password.user}.hashedPasswordFile =
|
||||||
config.clanCore.facts.services.user-password.secret.user-password-hash.path;
|
config.clan.core.facts.services.user-password.secret.user-password-hash.path;
|
||||||
sops.secrets."${config.clanCore.machineName}-user-password-hash".neededForUsers = true;
|
|
||||||
clanCore.facts.services.user-password = {
|
sops.secrets = lib.mkIf (config.clan.core.facts.secretStore == "sops") {
|
||||||
|
"${config.clan.core.machineName}-user-password-hash".neededForUsers = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
clan.core.facts.services.user-password = {
|
||||||
secret.user-password = { };
|
secret.user-password = { };
|
||||||
secret.user-password-hash = { };
|
secret.user-password-hash = { };
|
||||||
generator.prompt = (
|
generator.prompt = (
|
||||||
@@ -37,12 +41,12 @@
|
|||||||
mkpasswd
|
mkpasswd
|
||||||
];
|
];
|
||||||
generator.script = ''
|
generator.script = ''
|
||||||
if [[ -n $prompt_value ]]; then
|
if [[ -n ''${prompt_value-} ]]; then
|
||||||
echo $prompt_value > $secrets/user-password
|
echo $prompt_value | tr -d "\n" > $secrets/user-password
|
||||||
else
|
else
|
||||||
xkcdpass --numwords 3 --delimiter - --count 1 > $secrets/user-password
|
xkcdpass --numwords 3 --delimiter - --count 1 | tr -d "\n" > $secrets/user-password
|
||||||
fi
|
fi
|
||||||
cat $secrets/user-password | mkpasswd -s -m sha-512 > $secrets/user-password-hash
|
cat $secrets/user-password | mkpasswd -s -m sha-512 | tr -d "\n" > $secrets/user-password-hash
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
A lightweight desktop manager
|
---
|
||||||
|
description = "A lightweight desktop manager"
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
Statically configure the `zerotier` peers of a clan network.
|
---
|
||||||
|
description = "Statically configure the `zerotier` peers of a clan network."
|
||||||
---
|
---
|
||||||
Statically configure the `zerotier` peers of a clan network.
|
Statically configure the `zerotier` peers of a clan network.
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
clanDir = config.clanCore.clanDir;
|
clanDir = config.clan.core.clanDir;
|
||||||
machineDir = clanDir + "/machines/";
|
machineDir = clanDir + "/machines/";
|
||||||
machinesFileSet = builtins.readDir machineDir;
|
machinesFileSet = builtins.readDir machineDir;
|
||||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
||||||
@@ -20,7 +19,7 @@ let
|
|||||||
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
if builtins.pathExists fullPath then builtins.readFile fullPath else null
|
||||||
) machines;
|
) machines;
|
||||||
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
networkIds = lib.filter (machine: machine != null) networkIdsUnchecked;
|
||||||
networkId = builtins.elemAt networkIds 0;
|
networkId = if builtins.length networkIds == 0 then null else builtins.elemAt networkIds 0;
|
||||||
in
|
in
|
||||||
#TODO:trace on multiple found network-ids
|
#TODO:trace on multiple found network-ids
|
||||||
#TODO:trace on no single found networkId
|
#TODO:trace on no single found networkId
|
||||||
@@ -28,44 +27,61 @@ in
|
|||||||
options.clan.zerotier-static-peers = {
|
options.clan.zerotier-static-peers = {
|
||||||
excludeHosts = lib.mkOption {
|
excludeHosts = lib.mkOption {
|
||||||
type = lib.types.listOf lib.types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ config.clanCore.machineName ];
|
default = [ config.clan.core.machineName ];
|
||||||
description = "Hosts that should be excluded";
|
description = "Hosts that should be excluded";
|
||||||
};
|
};
|
||||||
|
networkIps = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra zerotier network Ips that should be accepted";
|
||||||
|
};
|
||||||
|
networkIds = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Extra zerotier network Ids that should be accepted";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config.systemd.services.zerotier-static-peers-autoaccept =
|
config.systemd.services.zerotier-static-peers-autoaccept =
|
||||||
let
|
let
|
||||||
machines = builtins.readDir machineDir;
|
|
||||||
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
zerotierIpMachinePath = machines: machineDir + machines + "/facts/zerotier-ip";
|
||||||
filteredMachines = lib.filterAttrs (
|
networkIpsUnchecked = builtins.map (
|
||||||
name: _: !(lib.elem name config.clan.static-hosts.excludeHosts)
|
machine:
|
||||||
|
let
|
||||||
|
fullPath = zerotierIpMachinePath machine;
|
||||||
|
in
|
||||||
|
if builtins.pathExists fullPath then machine else null
|
||||||
) machines;
|
) machines;
|
||||||
|
networkIps = lib.filter (machine: machine != null) networkIpsUnchecked;
|
||||||
|
machinesWithIp = lib.filterAttrs (name: _: (lib.elem name networkIps)) machinesFileSet;
|
||||||
|
filteredMachines = lib.filterAttrs (
|
||||||
|
name: _: !(lib.elem name config.clan.zerotier-static-peers.excludeHosts)
|
||||||
|
) machinesWithIp;
|
||||||
hosts = lib.mapAttrsToList (host: _: host) (
|
hosts = lib.mapAttrsToList (host: _: host) (
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
machine: _:
|
machine: _:
|
||||||
let
|
let
|
||||||
fullPath = zerotierIpMachinePath machine;
|
fullPath = zerotierIpMachinePath machine;
|
||||||
in
|
in
|
||||||
if builtins.pathExists fullPath then
|
lib.nameValuePair (builtins.readFile fullPath) [ machine ]
|
||||||
lib.nameValuePair (builtins.readFile fullPath) [ machine ]
|
|
||||||
else
|
|
||||||
null
|
|
||||||
) filteredMachines
|
) filteredMachines
|
||||||
);
|
);
|
||||||
|
allHostIPs = config.clan.zerotier-static-peers.networkIps ++ hosts;
|
||||||
in
|
in
|
||||||
lib.mkIf (config.clan.networking.zerotier.controller.enable) {
|
lib.mkIf (config.clan.core.networking.zerotier.controller.enable) {
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
after = [ "zerotierone.service" ];
|
after = [ "zerotierone.service" ];
|
||||||
path = [ pkgs.zerotierone ];
|
path = [ config.clan.core.clanPkgs.zerotierone ];
|
||||||
serviceConfig.ExecStart = pkgs.writeScript "static-zerotier-peers-autoaccept" ''
|
serviceConfig.ExecStart = pkgs.writeScript "static-zerotier-peers-autoaccept" ''
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
${lib.concatMapStringsSep "\n" (host: ''
|
${lib.concatMapStringsSep "\n" (host: ''
|
||||||
${
|
${config.clan.core.clanPkgs.zerotier-members}/bin/zerotier-members allow --member-ip ${host}
|
||||||
inputs.clan-core.packages.${pkgs.system}.zerotier-members
|
'') allHostIPs}
|
||||||
}/bin/zerotier-members allow --member-ip ${host}
|
${lib.concatMapStringsSep "\n" (host: ''
|
||||||
'') hosts}
|
${config.clan.core.clanPkgs.zerotier-members}/bin/zerotier-members allow ${host}
|
||||||
|
'') config.clan.zerotier-static-peers.networkIds}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
config.clan.networking.zerotier.networkId = lib.mkDefault networkId;
|
config.clan.core.networking.zerotier.networkId = lib.mkDefault networkId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
Enable ZeroTier VPN over TCP for networks where UDP is blocked.
|
---
|
||||||
|
description = "Enable ZeroTier VPN over TCP for networks where UDP is blocked."
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -26,8 +26,10 @@
|
|||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
packages = [
|
packages = [
|
||||||
select-shell
|
select-shell
|
||||||
|
pkgs.nix-unit
|
||||||
pkgs.tea
|
pkgs.tea
|
||||||
pkgs.nix
|
# Better error messages than nix 2.18
|
||||||
|
pkgs.nixVersions.latest
|
||||||
self'.packages.tea-create-pr
|
self'.packages.tea-create-pr
|
||||||
self'.packages.merge-after-ci
|
self'.packages.merge-after-ci
|
||||||
self'.packages.pending-reviews
|
self'.packages.pending-reviews
|
||||||
@@ -36,6 +38,7 @@
|
|||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}"
|
echo -e "${ansiEscapes.green}switch to another dev-shell using: select-shell${ansiEscapes.reset}"
|
||||||
|
export PROJECT_ROOT=$(git rev-parse --show-toplevel)
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
source_up
|
source_up
|
||||||
|
|
||||||
watch_file $(find ./nix -name "*.nix" -printf '%p ')
|
mapfile -d '' -t nix_files < <(find ./nix -name "*.nix" -print0)
|
||||||
|
watch_file "${nix_files[@]}"
|
||||||
|
|
||||||
# Because we depend on nixpkgs sources, uploading to builders takes a long time
|
# Because we depend on nixpkgs sources, uploading to builders takes a long time
|
||||||
use flake .#docs --builders ''
|
use flake .#docs --builders ''
|
||||||
|
|||||||
7
docs/.gitignore
vendored
7
docs/.gitignore
vendored
@@ -1 +1,6 @@
|
|||||||
/site/reference
|
/site/reference/clan-core
|
||||||
|
/site/reference/clanModules
|
||||||
|
/site/reference/nix-api/inventory.md
|
||||||
|
/site/reference/cli
|
||||||
|
/site/static/Roboto-Regular.ttf
|
||||||
|
/site/static/FiraCode-VF.ttf
|
||||||
@@ -15,92 +15,131 @@ Let's get your development environment up and running:
|
|||||||
|
|
||||||
1. **Install Nix Package Manager**:
|
1. **Install Nix Package Manager**:
|
||||||
|
|
||||||
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
- You can install the Nix package manager by either [downloading the Nix installer](https://github.com/DeterminateSystems/nix-installer/releases) or running this command:
|
||||||
```bash
|
```bash
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Install direnv**:
|
2. **Install direnv**:
|
||||||
|
|
||||||
- Download the direnv package from [here](https://direnv.net/docs/installation.html) or run the following command:
|
- To automatically setup a devshell on entering the directory
|
||||||
```bash
|
```bash
|
||||||
curl -sfL https://direnv.net/install.sh | bash
|
nix profile install nixpkgs#nix-direnv-flakes
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Add direnv to your shell**:
|
3. **Add direnv to your shell**:
|
||||||
|
|
||||||
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
- Direnv needs to [hook into your shell](https://direnv.net/docs/hook.html) to work.
|
||||||
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
You can do this by executing following command. The example below will setup direnv for `zsh` and `bash`
|
||||||
|
|
||||||
```bash
|
|
||||||
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Clone the Repository and Navigate**:
|
|
||||||
|
|
||||||
- Clone this repository and navigate to it.
|
|
||||||
|
|
||||||
5. **Allow .envrc**:
|
|
||||||
|
|
||||||
- When you enter the directory, you'll receive an error message like this:
|
|
||||||
```bash
|
|
||||||
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
|
||||||
```
|
|
||||||
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
|
||||||
|
|
||||||
# Setting Up Your Git Workflow
|
|
||||||
|
|
||||||
Let's set up your Git workflow to collaborate effectively:
|
|
||||||
|
|
||||||
1. **Register Your Gitea Account Locally**:
|
|
||||||
|
|
||||||
- Execute the following command to add your Gitea account locally:
|
|
||||||
```bash
|
|
||||||
tea login add
|
|
||||||
```
|
|
||||||
- Fill out the prompt as follows:
|
|
||||||
- URL of Gitea instance: `https://git.clan.lol`
|
|
||||||
- Name of new Login [gitea.gchq.icu]: `gitea.gchq.icu:7171`
|
|
||||||
- Do you have an access token? No
|
|
||||||
- Username: YourUsername
|
|
||||||
- Password: YourPassword
|
|
||||||
- Set Optional settings: No
|
|
||||||
|
|
||||||
2. **Git Workflow**:
|
|
||||||
|
|
||||||
1. Add your changes to Git using `git add <file1> <file2>`.
|
|
||||||
2. Run `nix fmt` to lint your files.
|
|
||||||
3. Commit your changes with a descriptive message: `git commit -a -m "My descriptive commit message"`.
|
|
||||||
4. Make sure your branch has the latest changes from upstream by executing:
|
|
||||||
```bash
|
```bash
|
||||||
git fetch && git rebase origin/main --autostash
|
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && echo 'eval "$(direnv hook bash)"' >> ~/.bashrc && eval "$SHELL"
|
||||||
```
|
```
|
||||||
5. Use `git status` to check for merge conflicts.
|
|
||||||
6. If conflicts exist, resolve them. Here's a tutorial for resolving conflicts in [VSCode](https://code.visualstudio.com/docs/sourcecontrol/overview#_merge-conflicts).
|
|
||||||
7. After resolving conflicts, execute `git merge --continue` and repeat step 5 until there are no conflicts.
|
|
||||||
|
|
||||||
3. **Create a Pull Request**:
|
4. **Create a Gitea Account**:
|
||||||
|
- Register an account on https://git.clan.lol
|
||||||
|
- Fork the [clan-core](https://git.clan.lol/clan/clan-core) repository
|
||||||
|
- Clone the repository and navigate to it
|
||||||
|
- Add a new remote called upstream:
|
||||||
|
```bash
|
||||||
|
git remote add upstream gitea@git.clan.lol:clan/clan-core.git
|
||||||
|
```
|
||||||
|
5. **Create an access token**:
|
||||||
|
- Log in to Gitea.
|
||||||
|
- Go to your account settings.
|
||||||
|
- Navigate to the Applications section.
|
||||||
|
- Click Generate New Token.
|
||||||
|
- Name your token and select all available scopes.
|
||||||
|
- Generate the token and copy it for later use.
|
||||||
|
- Your access token is now ready to use with all permissions.
|
||||||
|
|
||||||
- To automatically open a pull request that gets merged if all tests pass, execute:
|
5. **Register Your Gitea Account Locally**:
|
||||||
```bash
|
|
||||||
merge-after-ci
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Review Your Pull Request**:
|
- Execute the following command to add your Gitea account locally:
|
||||||
|
```bash
|
||||||
|
tea login add
|
||||||
|
```
|
||||||
|
- Fill out the prompt as follows:
|
||||||
|
- URL of Gitea instance: `https://git.clan.lol`
|
||||||
|
- Name of new Login [git.clan.lol]:
|
||||||
|
- Do you have an access token? Yes
|
||||||
|
- Token: <yourtoken>
|
||||||
|
- Set Optional settings: No
|
||||||
|
|
||||||
- Visit https://git.clan.lol and go to the project page. Check under "Pull Requests" for any issues with your pull request.
|
|
||||||
|
|
||||||
5. **Push Your Changes**:
|
6. **Allow .envrc**:
|
||||||
- If there are issues, fix them and redo step 2. Afterward, execute:
|
|
||||||
```bash
|
- When you enter the directory, you'll receive an error message like this:
|
||||||
git push origin HEAD:YourUsername-main
|
```bash
|
||||||
```
|
direnv: error .envrc is blocked. Run `direnv allow` to approve its content
|
||||||
- This will directly push to your open pull request.
|
```
|
||||||
|
- Execute `direnv allow` to automatically execute the shell script `.envrc` when entering the directory.
|
||||||
|
|
||||||
|
7. **(Optional) Install Git Hooks**:
|
||||||
|
- To syntax check your code you can run:
|
||||||
|
```bash
|
||||||
|
nix fmt
|
||||||
|
```
|
||||||
|
- To make this automatic install the git hooks
|
||||||
|
```bash
|
||||||
|
./scripts/pre-commit
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Open a Pull Request**:
|
||||||
|
- To automatically open up a pull request you can use our tool called:
|
||||||
|
```
|
||||||
|
merge-after-ci --reviewers Mic92 Lassulus Qubasa
|
||||||
|
```
|
||||||
|
|
||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
Here are some methods for debugging and testing the clan-cli:
|
Here are some methods for debugging and testing the clan-cli:
|
||||||
|
|
||||||
|
## See all possible packages and tests
|
||||||
|
|
||||||
|
To quickly show all possible packages and tests execute:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake show --system no-eval
|
||||||
|
```
|
||||||
|
|
||||||
|
Under `checks` you will find all tests that are executed in our CI. Under `packages` you find all our projects.
|
||||||
|
|
||||||
|
```
|
||||||
|
git+file:///home/lhebendanz/Projects/clan-core
|
||||||
|
├───apps
|
||||||
|
│ └───x86_64-linux
|
||||||
|
│ ├───install-vm: app
|
||||||
|
│ └───install-vm-nogui: app
|
||||||
|
├───checks
|
||||||
|
│ └───x86_64-linux
|
||||||
|
│ ├───borgbackup omitted (use '--all-systems' to show)
|
||||||
|
│ ├───check-for-breakpoints omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-age omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-bash omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-e2fsprogs omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-fakeroot omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-git omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-nix omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-dep-openssh omitted (use '--all-systems' to show)
|
||||||
|
│ ├───"clan-dep-python3.11-mypy" omitted (use '--all-systems' to show)
|
||||||
|
├───packages
|
||||||
|
│ └───x86_64-linux
|
||||||
|
│ ├───clan-cli omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-cli-docs omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-ts-api omitted (use '--all-systems' to show)
|
||||||
|
│ ├───clan-app omitted (use '--all-systems' to show)
|
||||||
|
│ ├───default omitted (use '--all-systems' to show)
|
||||||
|
│ ├───deploy-docs omitted (use '--all-systems' to show)
|
||||||
|
│ ├───docs omitted (use '--all-systems' to show)
|
||||||
|
│ ├───editor omitted (use '--all-systems' to show)
|
||||||
|
└───templates
|
||||||
|
├───default: template: Initialize a new clan flake
|
||||||
|
└───new-clan: template: Initialize a new clan flake
|
||||||
|
```
|
||||||
|
|
||||||
|
You can execute every test separately by following the tree path `nix build .#checks.x86_64-linux.clan-pytest` for example.
|
||||||
|
|
||||||
## Test Locally in Devshell with Breakpoints
|
## Test Locally in Devshell with Breakpoints
|
||||||
|
|
||||||
To test the cli locally in a development environment and set breakpoints for debugging, follow these steps:
|
To test the cli locally in a development environment and set breakpoints for debugging, follow these steps:
|
||||||
@@ -150,12 +189,14 @@ If you need to inspect the Nix sandbox while running tests, follow these steps:
|
|||||||
2. Use `cntr` and `psgrep` to attach to the Nix sandbox. This allows you to interactively debug your code while it's paused. For example:
|
2. Use `cntr` and `psgrep` to attach to the Nix sandbox. This allows you to interactively debug your code while it's paused. For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cntr exec -w your_sandbox_name
|
|
||||||
psgrep -a -x your_python_process_name
|
psgrep -a -x your_python_process_name
|
||||||
|
cntr attach <container id, container name or process id>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or you can also use the [nix breakpoint hook](https://nixos.org/manual/nixpkgs/stable/#breakpointhook)
|
||||||
|
|
||||||
|
|
||||||
# Standards
|
# Standards
|
||||||
|
|
||||||
Every new module name should be in kebab-case.
|
- Every new module name should be in kebab-case.
|
||||||
Every fact definition, where possible should be in kebab-case.
|
- Every fact definition, where possible should be in kebab-case.
|
||||||
|
|||||||
29
docs/main.py
29
docs/main.py
@@ -16,15 +16,26 @@ def define_env(env: Any) -> None:
|
|||||||
@env.macro
|
@env.macro
|
||||||
def asciinema(name: str) -> str:
|
def asciinema(name: str) -> str:
|
||||||
return f"""<div id="{name}">
|
return f"""<div id="{name}">
|
||||||
<script src="{asciinema_dir}/asciinema-player.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
AsciinemaPlayer.create('{video_dir + name}',
|
// Function to load the script and then create the Asciinema player
|
||||||
document.getElementById("{name}"), {{
|
function loadAsciinemaPlayer() {{
|
||||||
loop: true,
|
var script = document.createElement('script');
|
||||||
autoPlay: true,
|
script.src = "{asciinema_dir}/asciinema-player.min.js";
|
||||||
controls: false,
|
script.onload = function() {{
|
||||||
speed: 1.5,
|
AsciinemaPlayer.create('{video_dir + name}', document.getElementById("{name}"), {{
|
||||||
theme: "solarized-light"
|
loop: true,
|
||||||
}});
|
autoPlay: true,
|
||||||
|
controls: false,
|
||||||
|
speed: 1.5,
|
||||||
|
theme: "solarized-light"
|
||||||
|
}});
|
||||||
|
}};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Load the Asciinema player script
|
||||||
|
loadAsciinemaPlayer();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{asciinema_dir}/asciinema-player.css" />
|
||||||
</div>"""
|
</div>"""
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
site_name: Clan Docs
|
site_name: Clan Documentation
|
||||||
site_url: https://docs.clan.lol
|
site_url: https://docs.clan.lol
|
||||||
repo_url: https://git.clan.lol/clan/clan-core/
|
repo_url: https://git.clan.lol/clan/clan-core/
|
||||||
repo_name: clan-core
|
repo_name: clan-core
|
||||||
@@ -14,6 +14,7 @@ markdown_extensions:
|
|||||||
- attr_list
|
- attr_list
|
||||||
- footnotes
|
- footnotes
|
||||||
- md_in_html
|
- md_in_html
|
||||||
|
- def_list
|
||||||
- meta
|
- meta
|
||||||
- plantuml_markdown
|
- plantuml_markdown
|
||||||
- pymdownx.emoji:
|
- pymdownx.emoji:
|
||||||
@@ -38,8 +39,6 @@ exclude_docs: |
|
|||||||
/drafts/
|
/drafts/
|
||||||
|
|
||||||
nav:
|
nav:
|
||||||
- Blog:
|
|
||||||
- blog/index.md
|
|
||||||
- Getting started:
|
- Getting started:
|
||||||
- index.md
|
- index.md
|
||||||
- Installer: getting-started/installer.md
|
- Installer: getting-started/installer.md
|
||||||
@@ -49,22 +48,32 @@ nav:
|
|||||||
- Mesh VPN: getting-started/mesh-vpn.md
|
- Mesh VPN: getting-started/mesh-vpn.md
|
||||||
- Backup & Restore: getting-started/backups.md
|
- Backup & Restore: getting-started/backups.md
|
||||||
- Flake-parts: getting-started/flake-parts.md
|
- Flake-parts: getting-started/flake-parts.md
|
||||||
- Modules:
|
- Guides:
|
||||||
|
- guides/index.md
|
||||||
|
- Inventory: guides/inventory.md
|
||||||
|
- Reference:
|
||||||
|
- reference/index.md
|
||||||
- Clan Modules:
|
- Clan Modules:
|
||||||
|
- reference/clanModules/index.md
|
||||||
|
- reference/clanModules/borgbackup-static.md
|
||||||
- reference/clanModules/borgbackup.md
|
- reference/clanModules/borgbackup.md
|
||||||
- reference/clanModules/deltachat.md
|
- reference/clanModules/deltachat.md
|
||||||
- reference/clanModules/disk-layouts.md
|
|
||||||
- reference/clanModules/ergochat.md
|
- reference/clanModules/ergochat.md
|
||||||
- reference/clanModules/localbackup.md
|
- reference/clanModules/localbackup.md
|
||||||
- reference/clanModules/localsend.md
|
- reference/clanModules/localsend.md
|
||||||
- reference/clanModules/matrix-synapse.md
|
- reference/clanModules/matrix-synapse.md
|
||||||
- reference/clanModules/moonlight.md
|
- reference/clanModules/moonlight.md
|
||||||
|
- reference/clanModules/packages.md
|
||||||
|
- reference/clanModules/postgresql.md
|
||||||
- reference/clanModules/root-password.md
|
- reference/clanModules/root-password.md
|
||||||
|
- reference/clanModules/single-disk.md
|
||||||
- reference/clanModules/sshd.md
|
- reference/clanModules/sshd.md
|
||||||
- reference/clanModules/sunshine.md
|
|
||||||
- reference/clanModules/syncthing.md
|
|
||||||
- reference/clanModules/static-hosts.md
|
- reference/clanModules/static-hosts.md
|
||||||
|
- reference/clanModules/sunshine.md
|
||||||
|
- reference/clanModules/syncthing-static-peers.md
|
||||||
|
- reference/clanModules/syncthing.md
|
||||||
- reference/clanModules/thelounge.md
|
- reference/clanModules/thelounge.md
|
||||||
|
- reference/clanModules/mumble.md
|
||||||
- reference/clanModules/trusted-nix-caches.md
|
- reference/clanModules/trusted-nix-caches.md
|
||||||
- reference/clanModules/user-password.md
|
- reference/clanModules/user-password.md
|
||||||
- reference/clanModules/xfce.md
|
- reference/clanModules/xfce.md
|
||||||
@@ -73,14 +82,15 @@ nav:
|
|||||||
- CLI:
|
- CLI:
|
||||||
- reference/cli/index.md
|
- reference/cli/index.md
|
||||||
- reference/cli/backups.md
|
- reference/cli/backups.md
|
||||||
- reference/cli/config.md
|
|
||||||
- reference/cli/facts.md
|
- reference/cli/facts.md
|
||||||
- reference/cli/flakes.md
|
- reference/cli/flakes.md
|
||||||
- reference/cli/flash.md
|
- reference/cli/flash.md
|
||||||
- reference/cli/history.md
|
- reference/cli/history.md
|
||||||
- reference/cli/machines.md
|
- reference/cli/machines.md
|
||||||
- reference/cli/secrets.md
|
- reference/cli/secrets.md
|
||||||
|
- reference/cli/show.md
|
||||||
- reference/cli/ssh.md
|
- reference/cli/ssh.md
|
||||||
|
- reference/cli/state.md
|
||||||
- reference/cli/vms.md
|
- reference/cli/vms.md
|
||||||
- Clan Core:
|
- Clan Core:
|
||||||
- reference/clan-core/index.md
|
- reference/clan-core/index.md
|
||||||
@@ -88,14 +98,23 @@ nav:
|
|||||||
- reference/clan-core/facts.md
|
- reference/clan-core/facts.md
|
||||||
- reference/clan-core/sops.md
|
- reference/clan-core/sops.md
|
||||||
- reference/clan-core/state.md
|
- reference/clan-core/state.md
|
||||||
|
- reference/clan-core/deployment.md
|
||||||
|
- reference/clan-core/networking.md
|
||||||
|
- Nix API:
|
||||||
|
- reference/nix-api/index.md
|
||||||
|
- buildClan: reference/nix-api/buildclan.md
|
||||||
|
- Inventory: reference/nix-api/inventory.md
|
||||||
- Contributing: contributing/contributing.md
|
- Contributing: contributing/contributing.md
|
||||||
|
- Blog:
|
||||||
|
- blog/index.md
|
||||||
|
|
||||||
docs_dir: site
|
docs_dir: site
|
||||||
site_dir: out
|
site_dir: out
|
||||||
|
|
||||||
theme:
|
theme:
|
||||||
|
font: false
|
||||||
logo: https://clan.lol/static/logo/clan-white.png
|
logo: https://clan.lol/static/logo/clan-white.png
|
||||||
favicon: https://clan.lol/static/logo/clan-dark.png
|
favicon: https://clan.lol/static/dark-favicon/128x128.png
|
||||||
name: material
|
name: material
|
||||||
features:
|
features:
|
||||||
- navigation.instant
|
- navigation.instant
|
||||||
@@ -104,9 +123,8 @@ theme:
|
|||||||
- content.code.copy
|
- content.code.copy
|
||||||
- content.tabs.link
|
- content.tabs.link
|
||||||
icon:
|
icon:
|
||||||
repo: fontawesome/brands/git
|
repo: fontawesome/brands/git-alt
|
||||||
font:
|
custom_dir: overrides
|
||||||
code: Roboto Mono
|
|
||||||
|
|
||||||
palette:
|
palette:
|
||||||
# Palette toggle for light mode
|
# Palette toggle for light mode
|
||||||
@@ -128,8 +146,7 @@ theme:
|
|||||||
name: Switch to light mode
|
name: Switch to light mode
|
||||||
|
|
||||||
extra_css:
|
extra_css:
|
||||||
- static/asciinema-player/custom-theme.css
|
- static/extra.css
|
||||||
- static/asciinema-player/asciinema-player.css
|
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
social:
|
social:
|
||||||
@@ -142,7 +159,6 @@ extra:
|
|||||||
- icon: fontawesome/solid/rss
|
- icon: fontawesome/solid/rss
|
||||||
link: /feed_rss_created.xml
|
link: /feed_rss_created.xml
|
||||||
|
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- search
|
- search
|
||||||
- blog
|
- blog
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
module-docs,
|
module-docs,
|
||||||
clan-cli-docs,
|
clan-cli-docs,
|
||||||
|
inventory-api-docs,
|
||||||
asciinema-player-js,
|
asciinema-player-js,
|
||||||
asciinema-player-css,
|
asciinema-player-css,
|
||||||
|
roboto,
|
||||||
|
fira-code,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -24,15 +27,21 @@ pkgs.stdenv.mkDerivation {
|
|||||||
mkdocs-material
|
mkdocs-material
|
||||||
mkdocs-rss-plugin
|
mkdocs-rss-plugin
|
||||||
mkdocs-macros
|
mkdocs-macros
|
||||||
|
filelock # FIXME: this should be already provided by mkdocs-rss-plugin
|
||||||
]);
|
]);
|
||||||
configurePhase = ''
|
configurePhase = ''
|
||||||
mkdir -p ./site/reference/cli
|
mkdir -p ./site/reference/cli
|
||||||
cp -af ${module-docs}/* ./site/reference/
|
cp -af ${module-docs}/* ./site/reference/
|
||||||
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
||||||
|
cp -af ${inventory-api-docs} ./site/reference/nix-api/inventory.md
|
||||||
|
|
||||||
mkdir -p ./site/static/asciinema-player
|
mkdir -p ./site/static/asciinema-player
|
||||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||||
|
|
||||||
|
# Link to fonts
|
||||||
|
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||||
|
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||||
'';
|
'';
|
||||||
|
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
|
|||||||
@@ -12,13 +12,14 @@
|
|||||||
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
# { clanCore = «derivation JSON»; clanModules = { ${name} = «derivation JSON» }; }
|
||||||
jsonDocs = import ./get-module-docs.nix {
|
jsonDocs = import ./get-module-docs.nix {
|
||||||
inherit (inputs) nixpkgs;
|
inherit (inputs) nixpkgs;
|
||||||
inherit pkgs self;
|
inherit pkgs;
|
||||||
inherit (self.nixosModules) clanCore;
|
inherit (self.nixosModules) clanCore;
|
||||||
inherit (self) clanModules;
|
inherit (self) clanModules;
|
||||||
};
|
};
|
||||||
|
|
||||||
clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules);
|
clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules);
|
||||||
clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes);
|
# clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes);
|
||||||
|
# clanModulesMeta = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesMeta);
|
||||||
|
|
||||||
# Simply evaluated options (JSON)
|
# Simply evaluated options (JSON)
|
||||||
renderOptions =
|
renderOptions =
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
pkgs.python3
|
pkgs.python3
|
||||||
pkgs.mypy
|
pkgs.mypy
|
||||||
|
self'.packages.clan-cli
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
patchShebangs --build $out
|
patchShebangs --build $out
|
||||||
|
|
||||||
ruff format --check --diff $out
|
ruff format --check --diff $out
|
||||||
ruff --line-length 88 $out
|
ruff check --line-length 88 $out
|
||||||
mypy --strict $out
|
mypy --strict $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
@@ -49,28 +51,41 @@
|
|||||||
sha256 = "sha256-GZMeZFFGvP5GMqqh516mjJKfQaiJ6bL38bSYOXkaohc=";
|
sha256 = "sha256-GZMeZFFGvP5GMqqh516mjJKfQaiJ6bL38bSYOXkaohc=";
|
||||||
};
|
};
|
||||||
|
|
||||||
module-docs = pkgs.runCommand "rendered" { nativeBuildInputs = [ pkgs.python3 ]; } ''
|
module-docs =
|
||||||
export CLAN_CORE=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
pkgs.runCommand "rendered"
|
||||||
# A file that contains the links to all clanModule docs
|
{
|
||||||
export CLAN_MODULES=${clanModulesFileInfo}
|
buildInputs = [
|
||||||
export CLAN_MODULES_READMES=${clanModulesReadmes}
|
pkgs.python3
|
||||||
|
self'.packages.clan-cli
|
||||||
|
# TODO: see postFixup clan-cli/default.nix:L188
|
||||||
|
self'.packages.clan-cli.propagatedBuildInputs
|
||||||
|
];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
export CLAN_CORE_PATH=${self}
|
||||||
|
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
||||||
|
# A file that contains the links to all clanModule docs
|
||||||
|
export CLAN_MODULES=${clanModulesFileInfo}
|
||||||
|
|
||||||
mkdir $out
|
mkdir $out
|
||||||
|
|
||||||
# The python script will place mkDocs files in the output directory
|
# The python script will place mkDocs files in the output directory
|
||||||
python3 ${renderOptions}
|
python3 ${renderOptions}
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.docs = pkgs.callPackage ./shell.nix {
|
devShells.docs = pkgs.callPackage ./shell.nix {
|
||||||
inherit (self'.packages) docs clan-cli-docs;
|
inherit (self'.packages) docs clan-cli-docs inventory-api-docs;
|
||||||
inherit module-docs;
|
inherit
|
||||||
inherit asciinema-player-js;
|
asciinema-player-js
|
||||||
inherit asciinema-player-css;
|
asciinema-player-css
|
||||||
|
module-docs
|
||||||
|
self'
|
||||||
|
;
|
||||||
};
|
};
|
||||||
packages = {
|
packages = {
|
||||||
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||||
inherit (self'.packages) clan-cli-docs;
|
inherit (self'.packages) clan-cli-docs inventory-api-docs;
|
||||||
inherit (inputs) nixpkgs;
|
inherit (inputs) nixpkgs;
|
||||||
inherit module-docs;
|
inherit module-docs;
|
||||||
inherit asciinema-player-js;
|
inherit asciinema-player-js;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
clanCore,
|
clanCore,
|
||||||
clanModules,
|
clanModules,
|
||||||
self,
|
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
allNixosModules = (import "${nixpkgs}/nixos/modules/module-list.nix") ++ [
|
allNixosModules = (import "${nixpkgs}/nixos/modules/module-list.nix") ++ [
|
||||||
@@ -13,7 +12,7 @@ let
|
|||||||
|
|
||||||
clanCoreNixosModules = [
|
clanCoreNixosModules = [
|
||||||
clanCore
|
clanCore
|
||||||
{ clanCore.clanDir = ./.; }
|
{ clan.core.clanDir = ./.; }
|
||||||
] ++ allNixosModules;
|
] ++ allNixosModules;
|
||||||
|
|
||||||
# TODO: optimally we would not have to evaluate all nixos modules for every page
|
# TODO: optimally we would not have to evaluate all nixos modules for every page
|
||||||
@@ -25,27 +24,24 @@ let
|
|||||||
# improves eval performance slightly (10%)
|
# improves eval performance slightly (10%)
|
||||||
getOptions = modules: (clanCoreNixos.extendModules { inherit modules; }).options;
|
getOptions = modules: (clanCoreNixos.extendModules { inherit modules; }).options;
|
||||||
|
|
||||||
|
getOptionsWithoutCore = modules: builtins.removeAttrs (getOptions modules) [ "core" ];
|
||||||
|
|
||||||
evalDocs =
|
evalDocs =
|
||||||
options:
|
options:
|
||||||
pkgs.nixosOptionsDoc {
|
pkgs.nixosOptionsDoc {
|
||||||
options = options;
|
options = options;
|
||||||
warningsAreErrors = false;
|
warningsAreErrors = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# clanModules docs
|
# clanModules docs
|
||||||
clanModulesDocs = builtins.mapAttrs (
|
clanModulesDocs = builtins.mapAttrs (
|
||||||
name: module: (evalDocs ((getOptions [ module ]).clan.${name} or { })).optionsJSON
|
name: module: (evalDocs ((getOptionsWithoutCore [ module ]).clan.${name} or { })).optionsJSON
|
||||||
) clanModules;
|
|
||||||
|
|
||||||
clanModulesReadmes = builtins.mapAttrs (
|
|
||||||
module_name: _module: self.lib.modules.getReadme module_name
|
|
||||||
) clanModules;
|
) clanModules;
|
||||||
|
|
||||||
# clanCore docs
|
# clanCore docs
|
||||||
clanCoreDocs = (evalDocs (getOptions [ ]).clanCore).optionsJSON;
|
clanCoreDocs = (evalDocs (getOptions [ ]).clan.core).optionsJSON;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit clanModulesReadmes;
|
|
||||||
clanCore = clanCoreDocs;
|
clanCore = clanCoreDocs;
|
||||||
clanModules = clanModulesDocs;
|
clanModules = clanModulesDocs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from clan_cli.api.modules import Frontmatter, extract_frontmatter, get_roles
|
||||||
|
|
||||||
# Get environment variables
|
# Get environment variables
|
||||||
CLAN_CORE = os.getenv("CLAN_CORE")
|
CLAN_CORE_PATH = os.getenv("CLAN_CORE_PATH")
|
||||||
|
CLAN_CORE_DOCS = os.getenv("CLAN_CORE_DOCS")
|
||||||
CLAN_MODULES = os.environ.get("CLAN_MODULES")
|
CLAN_MODULES = os.environ.get("CLAN_MODULES")
|
||||||
CLAN_MODULES_READMES = os.environ.get("CLAN_MODULES_READMES")
|
|
||||||
|
|
||||||
OUT = os.environ.get("out")
|
OUT = os.environ.get("out")
|
||||||
|
|
||||||
@@ -40,13 +42,14 @@ def sanitize(text: str) -> str:
|
|||||||
return text.replace(">", "\\>")
|
return text.replace(">", "\\>")
|
||||||
|
|
||||||
|
|
||||||
def replace_store_path(text: str) -> Path:
|
def replace_store_path(text: str) -> tuple[str, str]:
|
||||||
res = text
|
res = text
|
||||||
if text.startswith("/nix/store/"):
|
if text.startswith("/nix/store/"):
|
||||||
res = "https://git.clan.lol/clan/clan-core/src/branch/main/" + str(
|
res = "https://git.clan.lol/clan/clan-core/src/branch/main/" + str(
|
||||||
Path(*Path(text).parts[4:])
|
Path(*Path(text).parts[4:])
|
||||||
)
|
)
|
||||||
return Path(res)
|
name = Path(res).name
|
||||||
|
return (res, name)
|
||||||
|
|
||||||
|
|
||||||
def render_option_header(name: str) -> str:
|
def render_option_header(name: str) -> str:
|
||||||
@@ -75,7 +78,9 @@ def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
|
|||||||
|
|
||||||
res = f"""
|
res = f"""
|
||||||
{"#" * level} {sanitize(name)}
|
{"#" * level} {sanitize(name)}
|
||||||
{"Readonly" if read_only else ""}
|
|
||||||
|
{"**Readonly**" if read_only else ""}
|
||||||
|
|
||||||
{option.get("description", "No description available.")}
|
{option.get("description", "No description available.")}
|
||||||
|
|
||||||
**Type**: `{option["type"]}`
|
**Type**: `{option["type"]}`
|
||||||
@@ -108,17 +113,19 @@ def render_option(name: str, option: dict[str, Any], level: int = 3) -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
decls = option.get("declarations", [])
|
decls = option.get("declarations", [])
|
||||||
source_path = replace_store_path(decls[0])
|
if decls:
|
||||||
res += f"""
|
source_path, name = replace_store_path(decls[0])
|
||||||
:simple-git: [{source_path.name}]({source_path})
|
print(source_path, name)
|
||||||
|
res += f"""
|
||||||
|
:simple-git: [{name}]({source_path})
|
||||||
"""
|
"""
|
||||||
res += "\n"
|
res += "\n"
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def module_header(module_name: str) -> str:
|
def module_header(module_name: str) -> str:
|
||||||
return f"# {module_name}\n"
|
return f"# {module_name}\n\n"
|
||||||
|
|
||||||
|
|
||||||
def module_usage(module_name: str) -> str:
|
def module_usage(module_name: str) -> str:
|
||||||
@@ -144,9 +151,9 @@ options_head = "\n## Module Options\n"
|
|||||||
|
|
||||||
|
|
||||||
def produce_clan_core_docs() -> None:
|
def produce_clan_core_docs() -> None:
|
||||||
if not CLAN_CORE:
|
if not CLAN_CORE_DOCS:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Environment variables are not set correctly: $CLAN_CORE={CLAN_CORE}"
|
f"Environment variables are not set correctly: $CLAN_CORE_DOCS={CLAN_CORE_DOCS}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not OUT:
|
if not OUT:
|
||||||
@@ -154,14 +161,14 @@ def produce_clan_core_docs() -> None:
|
|||||||
|
|
||||||
# A mapping of output file to content
|
# A mapping of output file to content
|
||||||
core_outputs: dict[str, str] = {}
|
core_outputs: dict[str, str] = {}
|
||||||
with open(CLAN_CORE) as f:
|
with open(CLAN_CORE_DOCS) as f:
|
||||||
options: dict[str, dict[str, Any]] = json.load(f)
|
options: dict[str, dict[str, Any]] = json.load(f)
|
||||||
module_name = "clan-core"
|
module_name = "clan-core"
|
||||||
for option_name, info in options.items():
|
for option_name, info in options.items():
|
||||||
outfile = f"{module_name}/index.md"
|
outfile = f"{module_name}/index.md"
|
||||||
|
|
||||||
# Create seperate files for nested options
|
# Create separate files for nested options
|
||||||
if len(option_name.split(".")) <= 2:
|
if len(option_name.split(".")) <= 3:
|
||||||
# i.e. clan-core.clanDir
|
# i.e. clan-core.clanDir
|
||||||
output = core_outputs.get(
|
output = core_outputs.get(
|
||||||
outfile,
|
outfile,
|
||||||
@@ -172,7 +179,7 @@ def produce_clan_core_docs() -> None:
|
|||||||
core_outputs[outfile] = output
|
core_outputs[outfile] = output
|
||||||
else:
|
else:
|
||||||
# Clan sub-options
|
# Clan sub-options
|
||||||
[_, sub] = option_name.split(".")[0:2]
|
[_, sub] = option_name.split(".")[1:3]
|
||||||
outfile = f"{module_name}/{sub}.md"
|
outfile = f"{module_name}/{sub}.md"
|
||||||
# Get the content or write the header
|
# Get the content or write the header
|
||||||
output = core_outputs.get(outfile, render_option_header(sub))
|
output = core_outputs.get(outfile, render_option_header(sub))
|
||||||
@@ -186,14 +193,47 @@ def produce_clan_core_docs() -> None:
|
|||||||
of.write(output)
|
of.write(output)
|
||||||
|
|
||||||
|
|
||||||
|
def render_roles(roles: list[str] | None, module_name: str) -> str:
|
||||||
|
if roles:
|
||||||
|
roles_list = "\n".join([f" - `{r}`" for r in roles])
|
||||||
|
return f"""
|
||||||
|
???+ tip "Inventory usage"
|
||||||
|
|
||||||
|
Predefined roles:
|
||||||
|
|
||||||
|
{roles_list}
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```{{.nix hl_lines="4"}}
|
||||||
|
buildClan {{
|
||||||
|
inventory.services = {{
|
||||||
|
{module_name}.instance_1 = {{
|
||||||
|
roles.{roles[0]}.machines = [ "sara_machine" ];
|
||||||
|
# ...
|
||||||
|
}};
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
clan_modules_descr = """Clan modules are [NixOS modules](https://wiki.nixos.org/wiki/NixOS_modules) which have been enhanced with additional features provided by Clan, with certain option types restricted to enable configuration through a graphical interface.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def produce_clan_modules_docs() -> None:
|
def produce_clan_modules_docs() -> None:
|
||||||
if not CLAN_MODULES:
|
if not CLAN_MODULES:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Environment variables are not set correctly: $CLAN_MODULES={CLAN_MODULES}"
|
f"Environment variables are not set correctly: $CLAN_MODULES={CLAN_MODULES}"
|
||||||
)
|
)
|
||||||
if not CLAN_MODULES_READMES:
|
|
||||||
|
if not CLAN_CORE_PATH:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Environment variables are not set correctly: $CLAN_MODULES_READMES={CLAN_MODULES_READMES}"
|
f"Environment variables are not set correctly: $CLAN_CORE_PATH={CLAN_CORE_PATH}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not OUT:
|
if not OUT:
|
||||||
@@ -202,18 +242,44 @@ def produce_clan_modules_docs() -> None:
|
|||||||
with open(CLAN_MODULES) as f:
|
with open(CLAN_MODULES) as f:
|
||||||
links: dict[str, str] = json.load(f)
|
links: dict[str, str] = json.load(f)
|
||||||
|
|
||||||
with open(CLAN_MODULES_READMES) as readme:
|
# with open(CLAN_MODULES_READMES) as readme:
|
||||||
readme_map: dict[str, str] = json.load(readme)
|
# readme_map: dict[str, str] = json.load(readme)
|
||||||
|
|
||||||
|
# with open(CLAN_MODULES_META) as f:
|
||||||
|
# meta_map: dict[str, Any] = json.load(f)
|
||||||
|
# print(meta_map)
|
||||||
|
|
||||||
# {'borgbackup': '/nix/store/hi17dwgy7963ddd4ijh81fv0c9sbh8sw-options.json', ... }
|
# {'borgbackup': '/nix/store/hi17dwgy7963ddd4ijh81fv0c9sbh8sw-options.json', ... }
|
||||||
|
|
||||||
|
modules_index = "# Modules Overview\n\n"
|
||||||
|
modules_index += clan_modules_descr
|
||||||
|
modules_index += "## Overview\n\n"
|
||||||
|
modules_index += '<div class="grid cards" markdown>\n\n'
|
||||||
|
|
||||||
for module_name, options_file in links.items():
|
for module_name, options_file in links.items():
|
||||||
|
readme_file = Path(CLAN_CORE_PATH) / "clanModules" / module_name / "README.md"
|
||||||
|
print(module_name, readme_file)
|
||||||
|
with open(readme_file) as f:
|
||||||
|
readme = f.read()
|
||||||
|
frontmatter: Frontmatter
|
||||||
|
frontmatter, readme_content = extract_frontmatter(readme, str(readme_file))
|
||||||
|
print(frontmatter, readme_content)
|
||||||
|
|
||||||
|
modules_index += build_option_card(module_name, frontmatter)
|
||||||
|
|
||||||
with open(Path(options_file) / "share/doc/nixos/options.json") as f:
|
with open(Path(options_file) / "share/doc/nixos/options.json") as f:
|
||||||
options: dict[str, dict[str, Any]] = json.load(f)
|
options: dict[str, dict[str, Any]] = json.load(f)
|
||||||
print(f"Rendering options for {module_name}...")
|
print(f"Rendering options for {module_name}...")
|
||||||
output = module_header(module_name)
|
output = module_header(module_name)
|
||||||
|
|
||||||
if readme_map.get(module_name, None):
|
if frontmatter.description:
|
||||||
output += f"{readme_map[module_name]}\n"
|
output += f"**{frontmatter.description}**\n\n"
|
||||||
|
output += f"{readme_content}\n"
|
||||||
|
|
||||||
|
# get_roles(str) -> list[str] | None
|
||||||
|
roles = get_roles(str(Path(CLAN_CORE_PATH) / "clanModules" / module_name))
|
||||||
|
if roles:
|
||||||
|
output += render_roles(roles, module_name)
|
||||||
|
|
||||||
output += module_usage(module_name)
|
output += module_usage(module_name)
|
||||||
|
|
||||||
@@ -229,6 +295,39 @@ def produce_clan_modules_docs() -> None:
|
|||||||
with open(outfile, "w") as of:
|
with open(outfile, "w") as of:
|
||||||
of.write(output)
|
of.write(output)
|
||||||
|
|
||||||
|
modules_index += "</div>"
|
||||||
|
modules_index += "\n"
|
||||||
|
modules_outfile = Path(OUT) / "clanModules/index.md"
|
||||||
|
|
||||||
|
with open(modules_outfile, "w") as of:
|
||||||
|
of.write(modules_index)
|
||||||
|
|
||||||
|
|
||||||
|
def build_option_card(module_name: str, frontmatter: Frontmatter) -> str:
|
||||||
|
"""
|
||||||
|
Build the overview index card for each reference target option.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def indent_all(text: str, indent_size: int = 4) -> str:
|
||||||
|
"""
|
||||||
|
Indent all lines in a string.
|
||||||
|
"""
|
||||||
|
indent = " " * indent_size
|
||||||
|
lines = text.split("\n")
|
||||||
|
indented_text = indent + ("\n" + indent).join(lines)
|
||||||
|
return indented_text
|
||||||
|
|
||||||
|
def to_md_li(module_name: str, frontmatter: Frontmatter) -> str:
|
||||||
|
md_li = (
|
||||||
|
f"""- **[{module_name}](./{"-".join(module_name.split(" "))}.md)**\n\n"""
|
||||||
|
)
|
||||||
|
md_li += f"""{indent_all("---", 4)}\n\n"""
|
||||||
|
fmd = f"\n{frontmatter.description.strip()}" if frontmatter.description else ""
|
||||||
|
md_li += f"""{indent_all(fmd, 4)}"""
|
||||||
|
return md_li
|
||||||
|
|
||||||
|
return f"{to_md_li(module_name, frontmatter)}\n\n"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
produce_clan_core_docs()
|
produce_clan_core_docs()
|
||||||
|
|||||||
@@ -3,22 +3,36 @@
|
|||||||
pkgs,
|
pkgs,
|
||||||
module-docs,
|
module-docs,
|
||||||
clan-cli-docs,
|
clan-cli-docs,
|
||||||
|
inventory-api-docs,
|
||||||
asciinema-player-js,
|
asciinema-player-js,
|
||||||
asciinema-player-css,
|
asciinema-player-css,
|
||||||
|
roboto,
|
||||||
|
fira-code,
|
||||||
|
self',
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
inputsFrom = [ docs ];
|
inputsFrom = [
|
||||||
|
docs
|
||||||
|
self'.devShells.default
|
||||||
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
mkdir -p ./site/reference/cli
|
mkdir -p ./site/reference/cli
|
||||||
cp -af ${module-docs}/* ./site/reference/
|
cp -af ${module-docs}/* ./site/reference/
|
||||||
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
cp -af ${clan-cli-docs}/* ./site/reference/cli/
|
||||||
|
cp -af ${inventory-api-docs} ./site/reference/nix-api/inventory.md
|
||||||
|
|
||||||
chmod +w ./site/reference/*
|
chmod +w ./site/reference/*
|
||||||
|
|
||||||
echo "Generated API documentation in './site/reference/' "
|
echo "Generated API documentation in './site/reference/' "
|
||||||
|
|
||||||
mkdir -p ./site/static/asciinema-player
|
mkdir -p ./site/static/asciinema-player
|
||||||
|
|
||||||
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
ln -snf ${asciinema-player-js} ./site/static/asciinema-player/asciinema-player.min.js
|
||||||
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
ln -snf ${asciinema-player-css} ./site/static/asciinema-player/asciinema-player.css
|
||||||
|
|
||||||
|
# Link to fonts
|
||||||
|
ln -snf ${roboto}/share/fonts/truetype/Roboto-Regular.ttf ./site/static/
|
||||||
|
ln -snf ${fira-code}/share/fonts/truetype/FiraCode-VF.ttf ./site/static/
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
19
docs/overrides/main.html
Normal file
19
docs/overrides/main.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{% extends "base.html" %} {% block extrahead %}
|
||||||
|
<meta
|
||||||
|
property="og:title"
|
||||||
|
content="Clan - Documentation, Blog & Getting Started Guide"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Documentation for Clan. The peer-to-peer machine deployment framework."
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://clan.lol/static/dark-favicon/128x128.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:url" content="https://docs.clan.lol" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:site_name" content="Clan" />
|
||||||
|
<meta property="og:locale" content="en_US" />
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user