Compare commits
770 Commits
vm-mamange
...
yggdrasil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f197ebd861 | ||
|
|
b0feef1a40 | ||
|
|
d4c26087df | ||
|
|
1a9bbab667 | ||
|
|
b23171f291 | ||
|
|
087423597b | ||
|
|
602dc192f3 | ||
|
|
dba166cc8a | ||
|
|
21b872a1c9 | ||
|
|
be48ffe724 | ||
|
|
7673b72991 | ||
|
|
823114435a | ||
|
|
e7efbb701b | ||
|
|
30d9c86015 | ||
|
|
313b77be79 | ||
|
|
6229e62281 | ||
|
|
49ff4da6be | ||
|
|
6d6521803d | ||
|
|
afd7bfc8c0 | ||
|
|
88fa3dff83 | ||
|
|
629ef65ce5 | ||
|
|
92151331f3 | ||
|
|
67dcd45dd5 | ||
|
|
95a4a69ffb | ||
|
|
88343ce523 | ||
|
|
fd9dd6f872 | ||
|
|
aaaa310c7f | ||
|
|
ffbf22eb60 | ||
|
|
8d3e0d2209 | ||
|
|
c05a890d50 | ||
|
|
03458ffbd8 | ||
|
|
ea098048c8 | ||
|
|
838ed6ead7 | ||
|
|
7e7278b99b | ||
|
|
f4d7728f3f | ||
|
|
c9b71496eb | ||
|
|
cd1f9c5a8b | ||
|
|
56379510d0 | ||
|
|
389299ac7d | ||
|
|
9cf04bcb5f | ||
|
|
c370598564 | ||
|
|
04001ff178 | ||
|
|
194c3080ea | ||
|
|
60d1e524ac | ||
|
|
672af1c63d | ||
|
|
6cb728a4ca | ||
|
|
a074650947 | ||
|
|
f169a40c69 | ||
|
|
480d5ee18c | ||
|
|
ba47d797e4 | ||
|
|
3e5f84dcb4 | ||
|
|
e398d98b42 | ||
|
|
09e5f78aae | ||
|
|
ae1680a720 | ||
|
|
9abf557353 | ||
|
|
dc0ec3443e | ||
|
|
d6c6918f85 | ||
|
|
24756442c8 | ||
|
|
c61a0f0712 | ||
|
|
f05bfcb13d | ||
|
|
6d8ea1f2c5 | ||
|
|
f1de0e28ff | ||
|
|
53ce3cf53d | ||
|
|
0ac6d7be87 | ||
|
|
e55401ecd9 | ||
|
|
37a49a14f4 | ||
|
|
7f68b10611 | ||
|
|
a2867ba29d | ||
|
|
0817cf868b | ||
|
|
018ffdaeeb | ||
|
|
eebb9b6a12 | ||
|
|
36f73d40b3 | ||
|
|
db84369000 | ||
|
|
359b2d4e7a | ||
|
|
2af9bd5003 | ||
|
|
a8cbfcbd18 | ||
|
|
dc17d62131 | ||
|
|
f97e22e125 | ||
|
|
1d9ad2ae54 | ||
|
|
c266261d3b | ||
|
|
93c31d4c26 | ||
|
|
c9275db377 | ||
|
|
cf83833d8b | ||
|
|
494f79edb4 | ||
|
|
de3102614a | ||
|
|
a6f0924c05 | ||
|
|
99dc4f6787 | ||
|
|
5f2ad6432e | ||
|
|
f8c34caaab | ||
|
|
8c2399446b | ||
|
|
95c781bf4d | ||
|
|
fe58de0997 | ||
|
|
7582458bae | ||
|
|
3a7d7afaab | ||
|
|
321eeacff0 | ||
|
|
8ae43ff9a0 | ||
|
|
e6efd5e731 | ||
|
|
7c1c8a5486 | ||
|
|
7932562fa6 | ||
|
|
ac22843abc | ||
|
|
eb83386098 | ||
|
|
7877075847 | ||
|
|
7206dd8219 | ||
|
|
f21e1e7641 | ||
|
|
c2a3f5e498 | ||
|
|
63c0db482f | ||
|
|
d2456be3dd | ||
|
|
c3c08482ac | ||
|
|
62126f0c32 | ||
|
|
28139560c2 | ||
|
|
45c916fb6d | ||
|
|
727d4e70ae | ||
|
|
261c5d2be8 | ||
|
|
87ea942399 | ||
|
|
39a032a285 | ||
|
|
a06940e981 | ||
|
|
4aebfadc8a | ||
|
|
f45f26994e | ||
|
|
c777a1a2b9 | ||
|
|
36fe7822f7 | ||
|
|
0ccf3310f9 | ||
|
|
a8d6552caa | ||
|
|
a131448dcf | ||
|
|
14a52dbc2e | ||
|
|
565391bd8c | ||
|
|
9bffa2a774 | ||
|
|
e42a07423e | ||
|
|
c5178ac16a | ||
|
|
33791e06cd | ||
|
|
c7e3bf624e | ||
|
|
ba027c2239 | ||
|
|
25fdabee29 | ||
|
|
de69c63ee3 | ||
|
|
b9573636d8 | ||
|
|
3862ad2a06 | ||
|
|
c447aec9d3 | ||
|
|
5137d19b0f | ||
|
|
453f2649d3 | ||
|
|
58cfcf3d25 | ||
|
|
c260a97cc1 | ||
|
|
3eb64870b0 | ||
|
|
7412b958c6 | ||
|
|
a0c27194a6 | ||
|
|
3437af29cb | ||
|
|
0b1c12d2e5 | ||
|
|
8620761bbd | ||
|
|
d793b6ca07 | ||
|
|
17e9231657 | ||
|
|
acc2674d79 | ||
|
|
c34a21a3bb | ||
|
|
275bff23da | ||
|
|
1a766a3447 | ||
|
|
c22844c83b | ||
|
|
5472ca0e21 | ||
|
|
ad890b0b6b | ||
|
|
a364b5ebf3 | ||
|
|
d0134d131e | ||
|
|
ccf0dace11 | ||
|
|
9977a903ce | ||
|
|
dc9bf5068e | ||
|
|
6b4f79c9fa | ||
|
|
b2985b59e9 | ||
|
|
d4ac3b83ee | ||
|
|
00bf55be5a | ||
|
|
851d6aaa89 | ||
|
|
f007279bee | ||
|
|
5a3381d9ff | ||
|
|
83e51db2e7 | ||
|
|
4e4af8a52f | ||
|
|
54a8ec717e | ||
|
|
d3e5e6edf1 | ||
|
|
a4277ad312 | ||
|
|
8877f2d451 | ||
|
|
9275b66bd9 | ||
|
|
6a964f37d5 | ||
|
|
73f2a4f56f | ||
|
|
85fb0187ee | ||
|
|
db9812a08b | ||
|
|
ca69530591 | ||
|
|
fc5b0e4113 | ||
|
|
278af5f0f4 | ||
|
|
e7baf25ff7 | ||
|
|
fada75144c | ||
|
|
803ef5476f | ||
|
|
016bd263d0 | ||
|
|
f9143f8a5d | ||
|
|
92eb27fcb1 | ||
|
|
0cc9b91ae8 | ||
|
|
2ed3608e34 | ||
|
|
a92a1a7dd1 | ||
|
|
9a903be6d4 | ||
|
|
adea270b27 | ||
|
|
765eb142a5 | ||
|
|
faa1405d6b | ||
|
|
0c93aab818 | ||
|
|
56923ae2c3 | ||
|
|
e2f64e1d40 | ||
|
|
c574b84278 | ||
|
|
640f15d55e | ||
|
|
789d326273 | ||
|
|
1763d85d91 | ||
|
|
082fa05083 | ||
|
|
9ed7190606 | ||
|
|
6c22539dd4 | ||
|
|
e6819ede61 | ||
|
|
186a760529 | ||
|
|
a84aee7b0c | ||
|
|
cab2fa44ba | ||
|
|
5962149e55 | ||
|
|
00f9d08a4b | ||
|
|
3d0c843308 | ||
|
|
847138472b | ||
|
|
c7786a59fd | ||
|
|
3b2d357f10 | ||
|
|
a83dbf604c | ||
|
|
f77456a123 | ||
|
|
6e4c3a638d | ||
|
|
3d2127ce1e | ||
|
|
a4a5916fa2 | ||
|
|
f6727055cd | ||
|
|
0517d87caa | ||
|
|
89e587592c | ||
|
|
439495d738 | ||
|
|
0b2fd681be | ||
|
|
41de615331 | ||
|
|
b7639b1d81 | ||
|
|
602879c9e4 | ||
|
|
53e16242b9 | ||
|
|
24c5146763 | ||
|
|
dca7aa0487 | ||
|
|
647bc4e4df | ||
|
|
1c80223fe3 | ||
|
|
7ac9b00398 | ||
|
|
d37c9e3b04 | ||
|
|
0fe9d0e157 | ||
|
|
5479c767c1 | ||
|
|
edc389ba4b | ||
|
|
4cb17d42e1 | ||
|
|
f26499edb8 | ||
|
|
2857cb7ed8 | ||
|
|
3168fecd52 | ||
|
|
24c20ff243 | ||
|
|
8ba8fda54b | ||
|
|
0992a47b00 | ||
|
|
d5b09f18ed | ||
|
|
fb2fe36c87 | ||
|
|
3db51887b1 | ||
|
|
24f3bcca57 | ||
|
|
85006c8103 | ||
|
|
db5571d623 | ||
|
|
d4bdaec586 | ||
|
|
cb9c8e5b5a | ||
|
|
0a1802c341 | ||
|
|
dfae1a4429 | ||
|
|
c1dc73a21b | ||
|
|
8145740cc1 | ||
|
|
b2a54f5b0d | ||
|
|
9c9adc6e16 | ||
|
|
f7cde8eb0f | ||
|
|
501d020562 | ||
|
|
a9bafd71e1 | ||
|
|
166e4b8081 | ||
|
|
c3eb40f17a | ||
|
|
7330285150 | ||
|
|
8cf8573c61 | ||
|
|
5bfa0d7a9d | ||
|
|
8ea2dd9b72 | ||
|
|
6efcade56a | ||
|
|
6d2372be56 | ||
|
|
626af4691b | ||
|
|
63697ac4b1 | ||
|
|
0ebb1f0c66 | ||
|
|
1dda60847e | ||
|
|
a7bce4cb19 | ||
|
|
a5474bc25f | ||
|
|
f634b8f1fb | ||
|
|
0ad40a0233 | ||
|
|
78abc36cd3 | ||
|
|
f5158b068f | ||
|
|
e6066a6cb1 | ||
|
|
fc8b66effa | ||
|
|
16b92963fd | ||
|
|
2ff3d871ac | ||
|
|
108936ef07 | ||
|
|
c45d4cfec9 | ||
|
|
64217e1281 | ||
|
|
d1421bb534 | ||
|
|
ac20514a8e | ||
|
|
79c4e73a15 | ||
|
|
61a647b436 | ||
|
|
c9a709783a | ||
|
|
c55b369899 | ||
|
|
084b8bacd3 | ||
|
|
47ad7d8a95 | ||
|
|
3798808013 | ||
|
|
43a39267f3 | ||
|
|
db94ea2d2e | ||
|
|
f0533f9bba | ||
|
|
360048fd04 | ||
|
|
8f8426de52 | ||
|
|
4bce390e64 | ||
|
|
2b7837e2b6 | ||
|
|
cbf9678534 | ||
|
|
b38b10c9a6 | ||
|
|
31cbb7dc00 | ||
|
|
0fa4377793 | ||
|
|
7b0d10e8c2 | ||
|
|
bb41adab4b | ||
|
|
648aa7dc59 | ||
|
|
3073969c92 | ||
|
|
2f1dc3a33d | ||
|
|
b707dcea2d | ||
|
|
4f0c8025b2 | ||
|
|
b91bee537a | ||
|
|
7207a3e8cd | ||
|
|
ac675a5af0 | ||
|
|
64caebde62 | ||
|
|
4934884e0c | ||
|
|
22cd9baee2 | ||
|
|
84232b5355 | ||
|
|
5bc7c255c1 | ||
|
|
d11d83f699 | ||
|
|
2ef1b2a8fa | ||
|
|
f7414d7e6e | ||
|
|
ab384150b2 | ||
|
|
0b6939ffee | ||
|
|
bc6a1a9d17 | ||
|
|
7055461cf0 | ||
|
|
a9564df6a9 | ||
|
|
e2dfc74d02 | ||
|
|
326cb60aea | ||
|
|
68b264970a | ||
|
|
1fa4ef82e9 | ||
|
|
bd93651f12 | ||
|
|
85ad51ce4c | ||
|
|
59e50c6150 | ||
|
|
f347568de3 | ||
|
|
bdad7d81b2 | ||
|
|
b8203cdf73 | ||
|
|
431e45cc3a | ||
|
|
f185d28f68 | ||
|
|
d8e6fcf773 | ||
|
|
23b7d24399 | ||
|
|
a1ed512da4 | ||
|
|
40ac96cd10 | ||
|
|
c4da43da0f | ||
|
|
8822f6dadc | ||
|
|
b5a7a91612 | ||
|
|
453b1a91a8 | ||
|
|
70274d69e9 | ||
|
|
c57d8b30d3 | ||
|
|
7407fef21b | ||
|
|
23c152541a | ||
|
|
6765e27031 | ||
|
|
cbb789bc69 | ||
|
|
7f68a21257 | ||
|
|
fc66dc78c3 | ||
|
|
1d0e0f243e | ||
|
|
8134ffd787 | ||
|
|
7f1590c729 | ||
|
|
c65bb0b1ce | ||
|
|
d8bc5269ee | ||
|
|
917407c475 | ||
|
|
d9e6e0c540 | ||
|
|
ef5ab0c2f4 | ||
|
|
34816013ad | ||
|
|
05665b1c7e | ||
|
|
2bebcab736 | ||
|
|
306f83e357 | ||
|
|
04457b1272 | ||
|
|
4986fe30c3 | ||
|
|
de33a07875 | ||
|
|
5233eb7fdb | ||
|
|
94a158b77a | ||
|
|
98af47d0b5 | ||
|
|
4470bb886e | ||
|
|
f4feac0d6b | ||
|
|
7547761812 | ||
|
|
23d11651fc | ||
|
|
03a4ac5bde | ||
|
|
ab50b433ee | ||
|
|
123e8398d8 | ||
|
|
6a2dfb8176 | ||
|
|
332d10e306 | ||
|
|
f3f6692e4d | ||
|
|
954301465f | ||
|
|
2199f4efd5 | ||
|
|
e208c02be7 | ||
|
|
7747e3cc0d | ||
|
|
1c24b4c6cb | ||
|
|
4b1ab4cdde | ||
|
|
4852e79c3c | ||
|
|
0a70ed6268 | ||
|
|
136acc7901 | ||
|
|
70d1dd0deb | ||
|
|
df32da304f | ||
|
|
76eb3c13e9 | ||
|
|
6e88046fd4 | ||
|
|
b3cafa4a8c | ||
|
|
d1cf87d2ce | ||
|
|
dc5485d9f1 | ||
|
|
1b12882e29 | ||
|
|
5be9b8383b | ||
|
|
c308fd63a7 | ||
|
|
fcdfd80b34 | ||
|
|
c5d975542d | ||
|
|
526eccdf16 | ||
|
|
f7dd34be21 | ||
|
|
289732ad20 | ||
|
|
a50b6f7bc7 | ||
|
|
51c679d3a9 | ||
|
|
470c3d330f | ||
|
|
df596ed59f | ||
|
|
f2c1202b03 | ||
|
|
cdd241d8ff | ||
|
|
0803d9c864 | ||
|
|
7171864a5e | ||
|
|
7aa9a34168 | ||
|
|
0ec2c32ff8 | ||
|
|
ea2d6aab65 | ||
|
|
4101ebc45b | ||
|
|
4414403dec | ||
|
|
2d78730037 | ||
|
|
45c7c42634 | ||
|
|
8baf4fcedd | ||
|
|
a41e0ba80f | ||
|
|
798d445f3e | ||
|
|
00bd003be4 | ||
|
|
5841432b6f | ||
|
|
1fb91ec161 | ||
|
|
fc16879336 | ||
|
|
290510ae74 | ||
|
|
7b926d43dc | ||
|
|
d91a44c7c5 | ||
|
|
a47ed71bb7 | ||
|
|
18f9df29da | ||
|
|
2438dc09a2 | ||
|
|
420412e60c | ||
|
|
aee6bc335b | ||
|
|
6ae679fb3d | ||
|
|
b40a13b4c5 | ||
|
|
dd2aa70efd | ||
|
|
2a9c9f7f2c | ||
|
|
82001544fd | ||
|
|
9f352aa362 | ||
|
|
35177ead40 | ||
|
|
1931c17513 | ||
|
|
b12debf373 | ||
|
|
0b3d362357 | ||
|
|
d8119f2308 | ||
|
|
ce36894ab1 | ||
|
|
c5f4f2e1d6 | ||
|
|
c861ffe07b | ||
|
|
6df980bc57 | ||
|
|
9d1d07b0ca | ||
|
|
24a774b5d6 | ||
|
|
442f673128 | ||
|
|
8905b5c5f1 | ||
|
|
3eff656dfa | ||
|
|
79e6f34c9e | ||
|
|
9c6e8f7735 | ||
|
|
cc4fd1369e | ||
|
|
7f32d6f81a | ||
|
|
a450ca10b8 | ||
|
|
06fbf32691 | ||
|
|
d4bd297439 | ||
|
|
acc8043f26 | ||
|
|
35e5d0daab | ||
|
|
e51c9ef1ad | ||
|
|
cdcbe3359a | ||
|
|
e5b51e6a2b | ||
|
|
694ebc5b30 | ||
|
|
ff2555cc4a | ||
|
|
016255459c | ||
|
|
14f03bcab0 | ||
|
|
4dc90b3d39 | ||
|
|
8cdce6c0c8 | ||
|
|
8904cf27a4 | ||
|
|
493194c124 | ||
|
|
5d1600a077 | ||
|
|
7daaacbddf | ||
|
|
30e18bbc66 | ||
|
|
16dffa99c0 | ||
|
|
58ad50b749 | ||
|
|
bc25074f5b | ||
|
|
c79916d06c | ||
|
|
4d53542f79 | ||
|
|
d3ef03aeb3 | ||
|
|
9949fac5ea | ||
|
|
6d236a6282 | ||
|
|
6e6a920796 | ||
|
|
99092a6ef2 | ||
|
|
1897b7bb06 | ||
|
|
878789cf38 | ||
|
|
8a59cf7ea3 | ||
|
|
7ade9cd222 | ||
|
|
447f619ecc | ||
|
|
657a55517b | ||
|
|
16a5b34ddf | ||
|
|
23f303b6ba | ||
|
|
84bf9f3bc5 | ||
|
|
48736011de | ||
|
|
cf5675b7f3 | ||
|
|
f0bbdad9ef | ||
|
|
5f83fe02a1 | ||
|
|
8cb92e143d | ||
|
|
73f5f887f3 | ||
|
|
db4e6c0be5 | ||
|
|
c24892f865 | ||
|
|
6badc14936 | ||
|
|
3d1fb401fd | ||
|
|
f2cdac75e2 | ||
|
|
5d6e35832c | ||
|
|
9aa9ba500e | ||
|
|
2934269279 | ||
|
|
1c7323c90a | ||
|
|
e667e03832 | ||
|
|
7f227b232c | ||
|
|
9d887805a8 | ||
|
|
244e1c7447 | ||
|
|
78911063a6 | ||
|
|
d86509e97b | ||
|
|
6de431df2c | ||
|
|
cda49b5b20 | ||
|
|
678841e64c | ||
|
|
74549164e4 | ||
|
|
6afe8695de | ||
|
|
460800b6fb | ||
|
|
5558bf3b9a | ||
|
|
62701f7730 | ||
|
|
a2f3e2e513 | ||
|
|
4867d467de | ||
|
|
d9685acc37 | ||
|
|
1aaa157f20 | ||
|
|
9a0ad4182f | ||
|
|
65d194af58 | ||
|
|
1f2f71ab03 | ||
|
|
f985187999 | ||
|
|
396a8d1e5e | ||
|
|
651f630080 | ||
|
|
21de41f1c0 | ||
|
|
98e5987e22 | ||
|
|
a77af2d379 | ||
|
|
ccde9e0ba6 | ||
|
|
6f6f582fe3 | ||
|
|
ec70de406b | ||
|
|
29a3140702 | ||
|
|
465eda24bc | ||
|
|
2888907109 | ||
|
|
f770f600c6 | ||
|
|
729f1673b3 | ||
|
|
7c95cb0177 | ||
|
|
b7f159aea3 | ||
|
|
06a0062311 | ||
|
|
aa840d9758 | ||
|
|
d1e6da0779 | ||
|
|
e6981ddd72 | ||
|
|
101c52f7c2 | ||
|
|
a83f301e59 | ||
|
|
5120d90b85 | ||
|
|
ea1e470502 | ||
|
|
f4d6edc501 | ||
|
|
cbbc235570 | ||
|
|
56d9256c02 | ||
|
|
e131d3d036 | ||
|
|
7f5b7b5057 | ||
|
|
c27fa9f56e | ||
|
|
1a1addb19d | ||
|
|
349da24b29 | ||
|
|
717f66b613 | ||
|
|
dcbc8c9a50 | ||
|
|
9834f413cc | ||
|
|
fb5645ae33 | ||
|
|
dc311d78e2 | ||
|
|
f0b1d8b2af | ||
|
|
7f0d55ef74 | ||
|
|
6e8860b3a0 | ||
|
|
5a5ec468c7 | ||
|
|
fbc2b889b5 | ||
|
|
fb094e8f3b | ||
|
|
e2eb26345f | ||
|
|
6f1a94e825 | ||
|
|
05951ffdb9 | ||
|
|
69de5f10c0 | ||
|
|
c01a191f3a | ||
|
|
dfe1a3e67f | ||
|
|
e975b67fad | ||
|
|
5c08893db0 | ||
|
|
cb679dbee2 | ||
|
|
f339ca0d85 | ||
|
|
547ba4276e | ||
|
|
cae63cc45d | ||
|
|
527b4b2e40 | ||
|
|
de0b1b2d70 | ||
|
|
6996a6340a | ||
|
|
3c433da8f5 | ||
|
|
ef2a2bdb67 | ||
|
|
7b61a668e9 | ||
|
|
bdab3e23af | ||
|
|
2b068928a2 | ||
|
|
ec798f89fd | ||
|
|
9efee40477 | ||
|
|
448c22c280 | ||
|
|
6c6e30ae60 | ||
|
|
b27ff67a14 | ||
|
|
c0ffb17e00 | ||
|
|
e9ccf157b6 | ||
|
|
451f2427fe | ||
|
|
1676cdd9a4 | ||
|
|
109e6473ab | ||
|
|
55acff50d0 | ||
|
|
eee1bd1ae0 | ||
|
|
e46d5870ff | ||
|
|
f6ec32a5d1 | ||
|
|
e336d1b19c | ||
|
|
7399f59652 | ||
|
|
088abe396e | ||
|
|
26b31e24a3 | ||
|
|
099f4c2b8b | ||
|
|
b43605c168 | ||
|
|
899dba5a08 | ||
|
|
d2b94ced5a | ||
|
|
cdf9fa1753 | ||
|
|
d1e7e2993d | ||
|
|
e05d85c759 | ||
|
|
53873411a6 | ||
|
|
39e0ab21bd | ||
|
|
8269d869c3 | ||
|
|
e19d1c8122 | ||
|
|
0cd4ff1b12 | ||
|
|
9aebf02f05 | ||
|
|
ffb7b91da7 | ||
|
|
2d264a8e5e | ||
|
|
abf6893714 | ||
|
|
699c56c721 | ||
|
|
2ce5388a75 | ||
|
|
3e664255d6 | ||
|
|
5b1a9d6848 | ||
|
|
1850abdd0d | ||
|
|
ed503f64da | ||
|
|
4074a184b2 | ||
|
|
6fe2b06f09 | ||
|
|
8fe7cb1b3d | ||
|
|
815c6c9438 | ||
|
|
9ce563aa08 | ||
|
|
c25844dd07 | ||
|
|
a167e70e63 | ||
|
|
dd96fe6b73 | ||
|
|
40d35d37e2 | ||
|
|
071f0f8034 | ||
|
|
81d88fe253 | ||
|
|
ab274ce932 | ||
|
|
ba1e598a76 | ||
|
|
b5d29bd301 | ||
|
|
e174e8e029 | ||
|
|
453d2b4a0a | ||
|
|
aadc8a1d63 | ||
|
|
aaca8f4763 | ||
|
|
0a1a63dfdd | ||
|
|
ee87f20471 | ||
|
|
43febe5f33 | ||
|
|
c63bbabceb | ||
|
|
8f1b270b59 | ||
|
|
da0af8bd53 | ||
|
|
f82d18d649 | ||
|
|
287a303484 | ||
|
|
1213608f30 | ||
|
|
fa1693e8c0 | ||
|
|
ed3ed7cb2a | ||
|
|
b2e88fb3fa | ||
|
|
d6ca50218a | ||
|
|
7d1f0956d6 | ||
|
|
d150c80854 | ||
|
|
2d1828d088 | ||
|
|
f7f897a311 | ||
|
|
683ffbdc76 | ||
|
|
480ad3a5f1 | ||
|
|
16361f03e9 | ||
|
|
3fb8b6587d | ||
|
|
6aee353b43 | ||
|
|
e109361e81 | ||
|
|
3c34f81a44 | ||
|
|
72e7c2e9b9 | ||
|
|
03968d8fbc | ||
|
|
2f27b3941e | ||
|
|
e9dc5b9ba6 | ||
|
|
e4ef885cd5 | ||
|
|
9fe457ebd5 | ||
|
|
4a51aa9316 | ||
|
|
308a10d6e6 | ||
|
|
90f513a08f | ||
|
|
4ddc61d132 | ||
|
|
fc0088e9ea | ||
|
|
71094f7fa1 | ||
|
|
a8516cf9c6 | ||
|
|
a89e2f877a | ||
|
|
ed78e49c47 | ||
|
|
3ef0a7919d | ||
|
|
36812d5f95 | ||
|
|
f5bcdb4ba0 | ||
|
|
b69ad0eca5 | ||
|
|
b221c29694 | ||
|
|
7dc7f09173 | ||
|
|
ec3d224e1d | ||
|
|
00c5312080 | ||
|
|
7811a56d2b | ||
|
|
e9401177b7 | ||
|
|
ef56258e8b | ||
|
|
c4d9b39a17 | ||
|
|
1f59b75c20 | ||
|
|
6b6da7b897 | ||
|
|
4391c19ee9 | ||
|
|
eb993b7060 | ||
|
|
08cb6993a8 | ||
|
|
872f640211 | ||
|
|
c58f7c573d | ||
|
|
7b807a0745 | ||
|
|
62805c66ff | ||
|
|
30b737ae1f | ||
|
|
cc41185f98 | ||
|
|
606aae7212 | ||
|
|
c31d884dc7 | ||
|
|
f546ce82f6 | ||
|
|
b173bc37f5 | ||
|
|
0c20cfb34a | ||
|
|
6c096a276d | ||
|
|
b7436b5b7f | ||
|
|
a84ab5d4bf | ||
|
|
a82ecbcbff | ||
|
|
4ae3abe8c2 | ||
|
|
90c7951704 | ||
|
|
116ff37156 | ||
|
|
f11df276a9 | ||
|
|
d44b43a937 | ||
|
|
716b74bc02 | ||
|
|
c85969c2b4 | ||
|
|
edb7dcc154 | ||
|
|
3586b4f48c | ||
|
|
9cdc6a27b6 | ||
|
|
ceecdc0eef | ||
|
|
96014c02c5 | ||
|
|
810a2c67f9 | ||
|
|
fbb28afb2f | ||
|
|
a6ef38dadd | ||
|
|
328e0b20ac | ||
|
|
7e77505316 | ||
|
|
245453b461 | ||
|
|
21e6a01cf3 | ||
|
|
302adf6f41 | ||
|
|
f754b88ae4 | ||
|
|
34d27e6bab | ||
|
|
5817713e39 | ||
|
|
cc283e88c9 | ||
|
|
1bb9f4741d | ||
|
|
0d26e991e6 | ||
|
|
961beda3e5 | ||
|
|
0a8a1d4354 | ||
|
|
daf8d8e80d | ||
|
|
011b2a5872 | ||
|
|
da06babcc2 | ||
|
|
c43eeb68a5 | ||
|
|
5e485a37f5 | ||
|
|
ce902bed0a | ||
|
|
a5d401b715 | ||
|
|
2637496059 | ||
|
|
87c8a4549b | ||
|
|
35e5f4a42a | ||
|
|
e4949755d7 | ||
|
|
b239c5bd88 | ||
|
|
4312e3fc2f |
@@ -1,9 +0,0 @@
|
|||||||
name: checks
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
jobs:
|
|
||||||
checks-impure:
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: nix run .#impure-checks
|
|
||||||
@@ -21,5 +21,9 @@ jobs:
|
|||||||
# Exclude private flakes and update-clan-core checks flake
|
# Exclude private flakes and update-clan-core checks flake
|
||||||
exclude-patterns: "checks/impure/flake.nix"
|
exclude-patterns: "checks/impure/flake.nix"
|
||||||
auto-merge: true
|
auto-merge: true
|
||||||
|
git-author-name: "clan-bot"
|
||||||
|
git-committer-name: "clan-bot"
|
||||||
|
git-author-email: "clan-bot@clan.lol"
|
||||||
|
git-committer-email: "clan-bot@clan.lol"
|
||||||
gitea-token: ${{ secrets.CI_BOT_TOKEN }}
|
gitea-token: ${{ secrets.CI_BOT_TOKEN }}
|
||||||
github-token: ${{ secrets.CI_BOT_GITHUB_TOKEN }}
|
github-token: ${{ secrets.CI_BOT_GITHUB_TOKEN }}
|
||||||
|
|||||||
2
.github/workflows/repo-sync.yml
vendored
2
.github/workflows/repo-sync.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
|||||||
if: github.repository_owner == 'clan-lol'
|
if: github.repository_owner == 'clan-lol'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: actions/create-github-app-token@v2
|
- uses: actions/create-github-app-token@v2
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -39,7 +39,6 @@ select
|
|||||||
# Generated files
|
# Generated files
|
||||||
pkgs/clan-app/ui/api/API.json
|
pkgs/clan-app/ui/api/API.json
|
||||||
pkgs/clan-app/ui/api/API.ts
|
pkgs/clan-app/ui/api/API.ts
|
||||||
pkgs/clan-app/ui/api/Inventory.ts
|
|
||||||
pkgs/clan-app/ui/api/modules_schemas.json
|
pkgs/clan-app/ui/api/modules_schemas.json
|
||||||
pkgs/clan-app/ui/api/schema.json
|
pkgs/clan-app/ui/api/schema.json
|
||||||
pkgs/clan-app/ui/.fonts
|
pkgs/clan-app/ui/.fonts
|
||||||
|
|||||||
20
CODEOWNERS
20
CODEOWNERS
@@ -0,0 +1,20 @@
|
|||||||
|
clanServices/.* @pinpox @kenji
|
||||||
|
|
||||||
|
lib/test/container-test-driver/.* @DavHau @mic92
|
||||||
|
lib/modules/inventory/.* @hsjobeki
|
||||||
|
lib/modules/inventoryClass/.* @hsjobeki
|
||||||
|
|
||||||
|
pkgs/clan-app/ui/.* @hsjobeki @brianmcgee
|
||||||
|
pkgs/clan-app/clan_app/.* @qubasa @hsjobeki
|
||||||
|
|
||||||
|
pkgs/clan-cli/clan_cli/.* @lassulus @mic92 @kenji
|
||||||
|
pkgs/clan-cli/clan_cli/(secrets|vars)/.* @DavHau @lassulus
|
||||||
|
|
||||||
|
pkgs/clan-cli/clan_lib/log_machines/.* @Qubasa
|
||||||
|
pkgs/clan-cli/clan_lib/ssh/.* @Qubasa @Mic92 @lassulus
|
||||||
|
pkgs/clan-cli/clan_lib/tags/.* @hsjobeki
|
||||||
|
pkgs/clan-cli/clan_lib/persist/.* @hsjobeki
|
||||||
|
pkgs/clan-cli/clan_lib/flake/.* @lassulus
|
||||||
|
|
||||||
|
pkgs/clan-cli/api.py @hsjobeki
|
||||||
|
pkgs/clan-cli/openapi.py @hsjobeki
|
||||||
@@ -8,7 +8,7 @@ Our mission is simple: to democratize computing by providing tools that empower
|
|||||||
|
|
||||||
## Features of Clan
|
## Features of Clan
|
||||||
|
|
||||||
- **Full-Stack System Deployment:** Utilize Clan’s toolkit alongside Nix's reliability to build and manage systems effortlessly.
|
- **Full-Stack System Deployment:** Utilize Clan's toolkit alongside Nix's reliability to build and manage systems effortlessly.
|
||||||
- **Overlay Networks:** Secure, private communication channels between devices.
|
- **Overlay Networks:** Secure, private communication channels between devices.
|
||||||
- **Virtual Machine Integration:** Seamless operation of VM applications within the main operating system.
|
- **Virtual Machine Integration:** Seamless operation of VM applications within the main operating system.
|
||||||
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
- **Robust Backup Management:** Long-term, self-hosted data preservation.
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
(
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
name = "borgbackup";
|
|
||||||
|
|
||||||
nodes.machine =
|
|
||||||
{ self, pkgs, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.clanModules.borgbackup
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
{
|
|
||||||
services.openssh.enable = true;
|
|
||||||
services.borgbackup.repos.testrepo = {
|
|
||||||
authorizedKeys = [ (builtins.readFile ../assets/ssh/pubkey) ];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
{
|
|
||||||
clan.core.settings.directory = ./.;
|
|
||||||
clan.core.state.testState.folders = [ "/etc/state" ];
|
|
||||||
environment.etc.state.text = "hello world";
|
|
||||||
systemd.tmpfiles.settings."vmsecrets" = {
|
|
||||||
"/etc/secrets/borgbackup/borgbackup.ssh" = {
|
|
||||||
C.argument = "${../assets/ssh/privkey}";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/borgbackup/borgbackup.repokey" = {
|
|
||||||
C.argument = builtins.toString (pkgs.writeText "repokey" "repokey12345");
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# clan.core.facts.secretStore = "vm";
|
|
||||||
clan.core.vars.settings.secretStore = "vm";
|
|
||||||
|
|
||||||
clan.borgbackup.destinations.test.repo = "borg@localhost:.";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
machine.systemctl("start --wait borgbackup-job-test.service")
|
|
||||||
assert "machine-test" in machine.succeed("BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes /run/current-system/sw/bin/borg-job-test list")
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{ fetchgit }:
|
|
||||||
fetchgit {
|
|
||||||
url = "https://git.clan.lol/clan/clan-core.git";
|
|
||||||
rev = "5d884cecc2585a29b6a3596681839d081b4de192";
|
|
||||||
sha256 = "09is1afmncamavb2q88qac37vmsijxzsy1iz1vr6gsyjq2rixaxc";
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,6 @@ in
|
|||||||
++ filter pathExists [
|
++ filter pathExists [
|
||||||
./devshell/flake-module.nix
|
./devshell/flake-module.nix
|
||||||
./flash/flake-module.nix
|
./flash/flake-module.nix
|
||||||
./impure/flake-module.nix
|
|
||||||
./installation/flake-module.nix
|
./installation/flake-module.nix
|
||||||
./update/flake-module.nix
|
./update/flake-module.nix
|
||||||
./morph/flake-module.nix
|
./morph/flake-module.nix
|
||||||
@@ -93,13 +92,10 @@ in
|
|||||||
|
|
||||||
# Base Tests
|
# Base Tests
|
||||||
nixos-test-secrets = self.clanLib.test.baseTest ./secrets nixosTestArgs;
|
nixos-test-secrets = self.clanLib.test.baseTest ./secrets nixosTestArgs;
|
||||||
nixos-test-borgbackup-legacy = self.clanLib.test.baseTest ./borgbackup-legacy nixosTestArgs;
|
|
||||||
nixos-test-wayland-proxy-virtwl = self.clanLib.test.baseTest ./wayland-proxy-virtwl nixosTestArgs;
|
nixos-test-wayland-proxy-virtwl = self.clanLib.test.baseTest ./wayland-proxy-virtwl nixosTestArgs;
|
||||||
|
|
||||||
# Container Tests
|
# Container Tests
|
||||||
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
nixos-test-container = self.clanLib.test.containerTest ./container nixosTestArgs;
|
||||||
nixos-test-zt-tcp-relay = self.clanLib.test.containerTest ./zt-tcp-relay nixosTestArgs;
|
|
||||||
nixos-test-matrix-synapse = self.clanLib.test.containerTest ./matrix-synapse nixosTestArgs;
|
|
||||||
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
|
nixos-test-user-firewall-iptables = self.clanLib.test.containerTest ./user-firewall/iptables.nix nixosTestArgs;
|
||||||
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
|
nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs;
|
||||||
|
|
||||||
@@ -114,6 +110,8 @@ in
|
|||||||
"dont-depend-on-repo-root"
|
"dont-depend-on-repo-root"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Temporary workaround: Filter out docs package and devshell for aarch64-darwin due to CI builder hangs
|
||||||
|
# TODO: Remove this filter once macOS CI builder is updated
|
||||||
flakeOutputs =
|
flakeOutputs =
|
||||||
lib.mapAttrs' (
|
lib.mapAttrs' (
|
||||||
name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel
|
name: config: lib.nameValuePair "nixos-${name}" config.config.system.build.toplevel
|
||||||
@@ -121,8 +119,18 @@ in
|
|||||||
// lib.mapAttrs' (
|
// lib.mapAttrs' (
|
||||||
name: config: lib.nameValuePair "darwin-${name}" config.config.system.build.toplevel
|
name: config: lib.nameValuePair "darwin-${name}" config.config.system.build.toplevel
|
||||||
) (self.darwinConfigurations or { })
|
) (self.darwinConfigurations or { })
|
||||||
// lib.mapAttrs' (n: lib.nameValuePair "package-${n}") packagesToBuild
|
// lib.mapAttrs' (n: lib.nameValuePair "package-${n}") (
|
||||||
// lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") self'.devShells
|
if system == "aarch64-darwin" then
|
||||||
|
lib.filterAttrs (n: _: n != "docs" && n != "deploy-docs" && n != "docs-options") packagesToBuild
|
||||||
|
else
|
||||||
|
packagesToBuild
|
||||||
|
)
|
||||||
|
// lib.mapAttrs' (n: lib.nameValuePair "devShell-${n}") (
|
||||||
|
if system == "aarch64-darwin" then
|
||||||
|
lib.filterAttrs (n: _: n != "docs") self'.devShells
|
||||||
|
else
|
||||||
|
self'.devShells
|
||||||
|
)
|
||||||
// lib.mapAttrs' (name: config: lib.nameValuePair "home-manager-${name}" config.activation-script) (
|
// lib.mapAttrs' (name: config: lib.nameValuePair "home-manager-${name}" config.activation-script) (
|
||||||
self'.legacyPackages.homeConfigurations or { }
|
self'.legacyPackages.homeConfigurations or { }
|
||||||
);
|
);
|
||||||
@@ -130,33 +138,6 @@ in
|
|||||||
nixosTests
|
nixosTests
|
||||||
// flakeOutputs
|
// flakeOutputs
|
||||||
// {
|
// {
|
||||||
# TODO: Automatically provide this check to downstream users to check their modules
|
|
||||||
clan-modules-json-compatible =
|
|
||||||
let
|
|
||||||
allSchemas = lib.mapAttrs (
|
|
||||||
_n: m:
|
|
||||||
let
|
|
||||||
schema =
|
|
||||||
(self.clanLib.evalService {
|
|
||||||
modules = [ m ];
|
|
||||||
prefix = [
|
|
||||||
"checks"
|
|
||||||
system
|
|
||||||
];
|
|
||||||
}).config.result.api.schema;
|
|
||||||
in
|
|
||||||
schema
|
|
||||||
) self.clan.modules;
|
|
||||||
in
|
|
||||||
pkgs.runCommand "combined-result"
|
|
||||||
{
|
|
||||||
schemaFile = builtins.toFile "schemas.json" (builtins.toJSON allSchemas);
|
|
||||||
}
|
|
||||||
''
|
|
||||||
mkdir -p $out
|
|
||||||
cat $schemaFile > $out/allSchemas.json
|
|
||||||
'';
|
|
||||||
|
|
||||||
clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } ''
|
clan-core-for-checks = pkgs.runCommand "clan-core-for-checks" { } ''
|
||||||
cp -r ${privateInputs.clan-core-for-checks} $out
|
cp -r ${privateInputs.clan-core-for-checks} $out
|
||||||
chmod -R +w $out
|
chmod -R +w $out
|
||||||
|
|||||||
@@ -50,12 +50,14 @@
|
|||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.toplevel
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript
|
||||||
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
|
self.nixosConfigurations."test-flash-machine-${pkgs.hostPlatform.system}".config.system.build.diskoScript.drvPath
|
||||||
|
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
checks = pkgs.lib.mkIf pkgs.stdenv.isLinux {
|
# Skip flash test on aarch64-linux for now as it's too slow
|
||||||
|
checks = lib.optionalAttrs (pkgs.stdenv.isLinux && pkgs.hostPlatform.system != "aarch64-linux") {
|
||||||
nixos-test-flash = self.clanLib.test.baseTest {
|
nixos-test-flash = self.clanLib.test.baseTest {
|
||||||
name = "flash";
|
name = "flash";
|
||||||
nodes.target = {
|
nodes.target = {
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
perSystem =
|
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
self',
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# a script that executes all other checks
|
|
||||||
packages.impure-checks = pkgs.writeShellScriptBin "impure-checks" ''
|
|
||||||
#!${pkgs.bash}/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
unset CLAN_DIR
|
|
||||||
|
|
||||||
export PATH="${
|
|
||||||
lib.makeBinPath (
|
|
||||||
[
|
|
||||||
pkgs.gitMinimal
|
|
||||||
pkgs.nix
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.rsync # needed to have rsync installed on the dummy ssh server
|
|
||||||
]
|
|
||||||
++ self'.packages.clan-cli-full.runtimeDependencies
|
|
||||||
)
|
|
||||||
}"
|
|
||||||
ROOT=$(git rev-parse --show-toplevel)
|
|
||||||
cd "$ROOT/pkgs/clan-cli"
|
|
||||||
|
|
||||||
# Set up custom git configuration for tests
|
|
||||||
export GIT_CONFIG_GLOBAL=$(mktemp)
|
|
||||||
git config --file "$GIT_CONFIG_GLOBAL" user.name "Test User"
|
|
||||||
git config --file "$GIT_CONFIG_GLOBAL" user.email "test@example.com"
|
|
||||||
export GIT_CONFIG_SYSTEM=/dev/null
|
|
||||||
|
|
||||||
# this disables dynamic dependency loading in clan-cli
|
|
||||||
export CLAN_NO_DYNAMIC_DEPS=1
|
|
||||||
|
|
||||||
jobs=$(nproc)
|
|
||||||
# Spawning worker in pytest is relatively slow, so we limit the number of jobs to 13
|
|
||||||
# (current number of impure tests)
|
|
||||||
jobs="$((jobs > 13 ? 13 : jobs))"
|
|
||||||
|
|
||||||
nix develop "$ROOT#clan-cli" -c bash -c "TMPDIR=/tmp python -m pytest -n $jobs -m impure ./clan_cli $@"
|
|
||||||
|
|
||||||
# Clean up temporary git config
|
|
||||||
rm -f "$GIT_CONFIG_GLOBAL"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
10
checks/installation/facter-report.nix
Normal file
10
checks/installation/facter-report.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
system:
|
||||||
|
builtins.fetchurl {
|
||||||
|
url = "https://git.clan.lol/clan/test-fixtures/raw/commit/4a2bc56d886578124b05060d3fb7eddc38c019f8/nixos-vm-facter-json/${system}.json";
|
||||||
|
sha256 =
|
||||||
|
{
|
||||||
|
aarch64-linux = "sha256:1rlfymk03rmfkm2qgrc8l5kj5i20srx79n1y1h4nzlpwaz0j7hh2";
|
||||||
|
x86_64-linux = "sha256:16myh0ll2gdwsiwkjw5ba4dl23ppwbsanxx214863j7nvzx42pws";
|
||||||
|
}
|
||||||
|
.${system};
|
||||||
|
}
|
||||||
@@ -18,27 +18,23 @@
|
|||||||
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||||
|
|
||||||
imports = [ self.nixosModules.test-install-machine-without-system ];
|
imports = [
|
||||||
|
self.nixosModules.test-install-machine-without-system
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
clan.machines.test-install-machine-with-system =
|
clan.machines.test-install-machine-with-system =
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
# https://git.clan.lol/clan/test-fixtures
|
# https://git.clan.lol/clan/test-fixtures
|
||||||
facter.reportPath = builtins.fetchurl {
|
facter.reportPath = import ./facter-report.nix pkgs.hostPlatform.system;
|
||||||
url = "https://git.clan.lol/clan/test-fixtures/raw/commit/4a2bc56d886578124b05060d3fb7eddc38c019f8/nixos-vm-facter-json/${pkgs.hostPlatform.system}.json";
|
|
||||||
sha256 =
|
|
||||||
{
|
|
||||||
aarch64-linux = "sha256:1rlfymk03rmfkm2qgrc8l5kj5i20srx79n1y1h4nzlpwaz0j7hh2";
|
|
||||||
x86_64-linux = "sha256:16myh0ll2gdwsiwkjw5ba4dl23ppwbsanxx214863j7nvzx42pws";
|
|
||||||
}
|
|
||||||
.${pkgs.hostPlatform.system};
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
fileSystems."/".device = lib.mkDefault "/dev/vda";
|
||||||
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
boot.loader.grub.device = lib.mkDefault "/dev/vda";
|
||||||
|
|
||||||
imports = [ self.nixosModules.test-install-machine-without-system ];
|
imports = [ self.nixosModules.test-install-machine-without-system ];
|
||||||
};
|
};
|
||||||
|
|
||||||
flake.nixosModules = {
|
flake.nixosModules = {
|
||||||
test-install-machine-without-system =
|
test-install-machine-without-system =
|
||||||
{ lib, modulesPath, ... }:
|
{ lib, modulesPath, ... }:
|
||||||
@@ -159,6 +155,7 @@
|
|||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
|
(import ./facter-report.nix pkgs.hostPlatform.system)
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
};
|
};
|
||||||
@@ -232,6 +229,7 @@
|
|||||||
"-i", ssh_conn.ssh_key,
|
"-i", ssh_conn.ssh_key,
|
||||||
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
||||||
"--update-hardware-config", "nixos-facter",
|
"--update-hardware-config", "nixos-facter",
|
||||||
|
"--no-persist-state",
|
||||||
]
|
]
|
||||||
|
|
||||||
subprocess.run(clan_cmd, check=True)
|
subprocess.run(clan_cmd, check=True)
|
||||||
@@ -241,7 +239,7 @@
|
|||||||
target.shutdown()
|
target.shutdown()
|
||||||
except BrokenPipeError:
|
except BrokenPipeError:
|
||||||
# qemu has already exited
|
# qemu has already exited
|
||||||
pass
|
target.connected = False
|
||||||
|
|
||||||
# Create a new machine instance that boots from the installed system
|
# Create a new machine instance that boots from the installed system
|
||||||
installed_machine = create_test_machine(target, "${pkgs.qemu_test}", name="after_install")
|
installed_machine = create_test_machine(target, "${pkgs.qemu_test}", name="after_install")
|
||||||
@@ -301,7 +299,8 @@
|
|||||||
"test-install-machine-without-system",
|
"test-install-machine-without-system",
|
||||||
"-i", ssh_conn.ssh_key,
|
"-i", ssh_conn.ssh_key,
|
||||||
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
||||||
f"nonrootuser@localhost:{ssh_conn.host_port}"
|
"--target-host", f"nonrootuser@localhost:{ssh_conn.host_port}",
|
||||||
|
"--yes"
|
||||||
]
|
]
|
||||||
|
|
||||||
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
|
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
|
||||||
@@ -325,7 +324,9 @@
|
|||||||
"test-install-machine-without-system",
|
"test-install-machine-without-system",
|
||||||
"-i", ssh_conn.ssh_key,
|
"-i", ssh_conn.ssh_key,
|
||||||
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
"--option", "store", os.environ['CLAN_TEST_STORE'],
|
||||||
f"nonrootuser@localhost:{ssh_conn.host_port}"
|
"--target-host",
|
||||||
|
f"nonrootuser@localhost:{ssh_conn.host_port}",
|
||||||
|
"--yes"
|
||||||
]
|
]
|
||||||
|
|
||||||
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
|
result = subprocess.run(clan_cmd, capture_output=True, cwd=flake_dir)
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
(
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
name = "matrix-synapse";
|
|
||||||
|
|
||||||
nodes.machine =
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
self,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.clanModules.matrix-synapse
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
{
|
|
||||||
clan.core.settings.directory = ./.;
|
|
||||||
|
|
||||||
services.nginx.virtualHosts."matrix.clan.test" = {
|
|
||||||
enableACME = lib.mkForce false;
|
|
||||||
forceSSL = lib.mkForce false;
|
|
||||||
};
|
|
||||||
clan.nginx.acme.email = "admins@clan.lol";
|
|
||||||
clan.matrix-synapse = {
|
|
||||||
server_tld = "clan.test";
|
|
||||||
app_domain = "matrix.clan.test";
|
|
||||||
};
|
|
||||||
clan.matrix-synapse.users.admin.admin = true;
|
|
||||||
clan.matrix-synapse.users.someuser = { };
|
|
||||||
|
|
||||||
clan.core.facts.secretStore = "vm";
|
|
||||||
clan.core.vars.settings.secretStore = "vm";
|
|
||||||
clan.core.vars.settings.publicStore = "in_repo";
|
|
||||||
|
|
||||||
# because we use systemd-tmpfiles to copy the secrets, we need to a separate systemd-tmpfiles call to provision 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/matrix-synapse/synapse-registration_shared_secret" = {
|
|
||||||
f.argument = "supersecret";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/matrix-password-admin/matrix-password-admin" = {
|
|
||||||
f.argument = "matrix-password1";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/etc/secrets/matrix-password-someuser/matrix-password-someuser" = {
|
|
||||||
f.argument = "matrix-password2";
|
|
||||||
z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "root";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("matrix-synapse")
|
|
||||||
machine.succeed("${pkgs.netcat}/bin/nc -z -v ::1 8008")
|
|
||||||
machine.wait_until_succeeds("${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'")
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
registration_shared_secret: supersecret
|
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.stdenvNoCC
|
pkgs.stdenvNoCC
|
||||||
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
|
self.nixosConfigurations.test-morph-machine.config.system.build.toplevel
|
||||||
|
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ nixosLib.runTest (
|
|||||||
|
|
||||||
# This tests the compatibility of the inventory
|
# This tests the compatibility of the inventory
|
||||||
# With the test framework
|
# With the test framework
|
||||||
# - legacy-modules
|
|
||||||
# - clan.service modules
|
# - clan.service modules
|
||||||
name = "service-dummy-test-from-flake";
|
name = "service-dummy-test-from-flake";
|
||||||
|
|
||||||
@@ -37,9 +36,6 @@ nixosLib.runTest (
|
|||||||
start_all()
|
start_all()
|
||||||
admin1.wait_for_unit("multi-user.target")
|
admin1.wait_for_unit("multi-user.target")
|
||||||
peer1.wait_for_unit("multi-user.target")
|
peer1.wait_for_unit("multi-user.target")
|
||||||
# Provided by the legacy module
|
|
||||||
print(admin1.succeed("systemctl status dummy-service"))
|
|
||||||
print(peer1.succeed("systemctl status dummy-service"))
|
|
||||||
|
|
||||||
# peer1 should have the 'hello' file
|
# peer1 should have the 'hello' file
|
||||||
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
||||||
|
|||||||
@@ -15,12 +15,6 @@
|
|||||||
meta.name = "foo";
|
meta.name = "foo";
|
||||||
machines.peer1 = { };
|
machines.peer1 = { };
|
||||||
machines.admin1 = { };
|
machines.admin1 = { };
|
||||||
services = {
|
|
||||||
legacy-module.default = {
|
|
||||||
roles.peer.machines = [ "peer1" ];
|
|
||||||
roles.admin.machines = [ "admin1" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
instances."test" = {
|
instances."test" = {
|
||||||
module.name = "new-service";
|
module.name = "new-service";
|
||||||
@@ -28,9 +22,6 @@
|
|||||||
roles.peer.machines.peer1 = { };
|
roles.peer.machines.peer1 = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
modules = {
|
|
||||||
legacy-module = ./legacy-module;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
modules.new-service = {
|
modules.new-service = {
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Set up dummy-module"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory" ]
|
|
||||||
|
|
||||||
[constraints]
|
|
||||||
roles.admin.min = 1
|
|
||||||
roles.admin.max = 1
|
|
||||||
---
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
{ config, ... }:
|
|
||||||
{
|
|
||||||
systemd.services.dummy-service = {
|
|
||||||
enable = true;
|
|
||||||
description = "Dummy service";
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
};
|
|
||||||
script = ''
|
|
||||||
generated_password_path="${config.clan.core.vars.generators.dummy-generator.files.generated-password.path}"
|
|
||||||
if [ ! -f "$generated_password_path" ]; then
|
|
||||||
echo "Generated password file not found: $generated_password_path"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
host_id_path="${config.clan.core.vars.generators.dummy-generator.files.host-id.path}"
|
|
||||||
if [ ! -e "$host_id_path" ]; then
|
|
||||||
echo "Host ID file not found: $host_id_path"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
# TODO: add and prompt and make it work in the test framework
|
|
||||||
clan.core.vars.generators.dummy-generator = {
|
|
||||||
files.host-id.secret = false;
|
|
||||||
files.generated-password.secret = true;
|
|
||||||
script = ''
|
|
||||||
echo $RANDOM > "$out"/host-id
|
|
||||||
echo $RANDOM > "$out"/generated-password
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ nixosLib.runTest (
|
|||||||
|
|
||||||
# This tests the compatibility of the inventory
|
# This tests the compatibility of the inventory
|
||||||
# With the test framework
|
# With the test framework
|
||||||
# - legacy-modules
|
|
||||||
# - clan.service modules
|
# - clan.service modules
|
||||||
name = "service-dummy-test";
|
name = "service-dummy-test";
|
||||||
|
|
||||||
@@ -24,12 +23,6 @@ nixosLib.runTest (
|
|||||||
inventory = {
|
inventory = {
|
||||||
machines.peer1 = { };
|
machines.peer1 = { };
|
||||||
machines.admin1 = { };
|
machines.admin1 = { };
|
||||||
services = {
|
|
||||||
legacy-module.default = {
|
|
||||||
roles.peer.machines = [ "peer1" ];
|
|
||||||
roles.admin.machines = [ "admin1" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
instances."test" = {
|
instances."test" = {
|
||||||
module.name = "new-service";
|
module.name = "new-service";
|
||||||
@@ -37,9 +30,6 @@ nixosLib.runTest (
|
|||||||
roles.peer.machines.peer1 = { };
|
roles.peer.machines.peer1 = { };
|
||||||
};
|
};
|
||||||
|
|
||||||
modules = {
|
|
||||||
legacy-module = ./legacy-module;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
modules.new-service = {
|
modules.new-service = {
|
||||||
_class = "clan.service";
|
_class = "clan.service";
|
||||||
@@ -78,9 +68,6 @@ nixosLib.runTest (
|
|||||||
start_all()
|
start_all()
|
||||||
admin1.wait_for_unit("multi-user.target")
|
admin1.wait_for_unit("multi-user.target")
|
||||||
peer1.wait_for_unit("multi-user.target")
|
peer1.wait_for_unit("multi-user.target")
|
||||||
# Provided by the legacy module
|
|
||||||
print(admin1.succeed("systemctl status dummy-service"))
|
|
||||||
print(peer1.succeed("systemctl status dummy-service"))
|
|
||||||
|
|
||||||
# peer1 should have the 'hello' file
|
# peer1 should have the 'hello' file
|
||||||
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
peer1.succeed("cat ${nodes.peer1.clan.core.vars.generators.new-service.files.not-a-secret.path}")
|
||||||
|
|||||||
@@ -112,6 +112,7 @@
|
|||||||
pkgs.stdenv.drvPath
|
pkgs.stdenv.drvPath
|
||||||
pkgs.bash.drvPath
|
pkgs.bash.drvPath
|
||||||
pkgs.buildPackages.xorg.lndir
|
pkgs.buildPackages.xorg.lndir
|
||||||
|
(import ../installation/facter-report.nix pkgs.hostPlatform.system)
|
||||||
]
|
]
|
||||||
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
(
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
name = "zt-tcp-relay";
|
|
||||||
|
|
||||||
nodes.machine =
|
|
||||||
{ self, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
self.nixosModules.clanCore
|
|
||||||
self.clanModules.zt-tcp-relay
|
|
||||||
{
|
|
||||||
clan.core.settings.directory = ./.;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
testScript = ''
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("zt-tcp-relay.service")
|
|
||||||
out = machine.succeed("${pkgs.netcat}/bin/nc -z -v localhost 4443")
|
|
||||||
print(out)
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Convenient Administration for the Clan App"
|
|
||||||
categories = ["Utility"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
options.clan.admin = {
|
|
||||||
allowedKeys = lib.mkOption {
|
|
||||||
default = { };
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
description = "The allowed public keys for ssh access to the admin user";
|
|
||||||
example = {
|
|
||||||
"key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD...";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
# Bad practice.
|
|
||||||
# Should we add 'clanModules' to specialArgs?
|
|
||||||
imports = [
|
|
||||||
../../sshd
|
|
||||||
../../root-password
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.admin module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
users.users.root.openssh.authorizedKeys.keys = builtins.attrValues config.clan.admin.allowedKeys;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Set up automatic upgrades"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
|
|
||||||
Whether to periodically upgrade NixOS to the latest version. If enabled, a
|
|
||||||
systemd timer will run `nixos-rebuild switch --upgrade` once a day.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.clan.auto-upgrade;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.auto-upgrade = {
|
|
||||||
flake = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Flake reference";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.auto-upgrade module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
system.autoUpgrade = {
|
|
||||||
inherit (cfg) flake;
|
|
||||||
enable = true;
|
|
||||||
dates = "02:00";
|
|
||||||
randomizedDelaySec = "45min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Statically configure borgbackup with sane defaults."
|
|
||||||
---
|
|
||||||
!!! Danger "Deprecated"
|
|
||||||
Use [borgbackup](borgbackup.md) instead.
|
|
||||||
|
|
||||||
Don't use borgbackup-static through [inventory](../../concepts/inventory.md).
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
let
|
|
||||||
dir = config.clan.core.settings.directory;
|
|
||||||
machineDir = dir + "/machines/";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ../borgbackup ];
|
|
||||||
|
|
||||||
options.clan.borgbackup-static = {
|
|
||||||
excludeMachines = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
example = lib.literalExpression "[ config.clan.core.settings.machine.name ]";
|
|
||||||
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 = lib.literalExpression "[ config.clan.core.settings.machine.name ]";
|
|
||||||
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.settings.machine.name
|
|
||||||
) 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.settings.machine.name}";
|
|
||||||
};
|
|
||||||
}) config.clan.borgbackup-static.targets;
|
|
||||||
in
|
|
||||||
lib.mkIf (builtins.any (
|
|
||||||
target: target == config.clan.core.settings.machine.name
|
|
||||||
) 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.
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
];
|
|
||||||
config.warnings = lib.optional (
|
|
||||||
builtins.length config.clan.borgbackup-static.targets > 0
|
|
||||||
) "The borgbackup-static module is deprecated use the service via the inventory interface instead.";
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Efficient, deduplicating backup program with optional compression and secure encryption."
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/client.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
# Instances might be empty, if the module is not used via the inventory
|
|
||||||
instances = config.clan.inventory.services.borgbackup or { };
|
|
||||||
# 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;
|
|
||||||
|
|
||||||
machineName = config.clan.core.settings.machine.name;
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
options.clan.borgbackup.destinations = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
|
|
||||||
default = name;
|
|
||||||
description = "the name of the backup job";
|
|
||||||
};
|
|
||||||
repo = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "the borgbackup repository to backup to";
|
|
||||||
};
|
|
||||||
rsh = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "ssh -i ${
|
|
||||||
config.clan.core.vars.generators.borgbackup.files."borgbackup.ssh".path
|
|
||||||
} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=Yes";
|
|
||||||
defaultText = "ssh -i \${config.clan.core.vars.generators.borgbackup.files.\"borgbackup.ssh\".path} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null";
|
|
||||||
description = "the rsh to use for the backup";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
destinations where the machine should be backuped to
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
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.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.borgbackup module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
# Destinations
|
|
||||||
clan.borgbackup.destinations =
|
|
||||||
let
|
|
||||||
destinations = builtins.map (serverName: {
|
|
||||||
name = serverName;
|
|
||||||
value = {
|
|
||||||
repo = "borg@${serverName}:/var/lib/borgbackup/${machineName}";
|
|
||||||
};
|
|
||||||
}) allServers;
|
|
||||||
in
|
|
||||||
(builtins.listToAttrs destinations);
|
|
||||||
|
|
||||||
# Derived from the 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: {
|
|
||||||
paths = lib.unique (
|
|
||||||
lib.flatten (map (state: state.folders) (lib.attrValues config.clan.core.state))
|
|
||||||
);
|
|
||||||
exclude = cfg.exclude;
|
|
||||||
repo = dest.repo;
|
|
||||||
environment.BORG_RSH = dest.rsh;
|
|
||||||
compression = "auto,zstd";
|
|
||||||
startAt = "*-*-* 01:00:00";
|
|
||||||
persistentTimer = true;
|
|
||||||
|
|
||||||
encryption = {
|
|
||||||
mode = "repokey";
|
|
||||||
passCommand = "cat ${config.clan.core.vars.generators.borgbackup.files."borgbackup.repokey".path}";
|
|
||||||
};
|
|
||||||
|
|
||||||
prune.keep = {
|
|
||||||
within = "1d"; # Keep all archives from the last day
|
|
||||||
daily = 7;
|
|
||||||
weekly = 4;
|
|
||||||
monthly = 0;
|
|
||||||
};
|
|
||||||
}) cfg.destinations;
|
|
||||||
|
|
||||||
environment.systemPackages = [
|
|
||||||
(pkgs.writeShellApplication {
|
|
||||||
name = "borgbackup-create";
|
|
||||||
runtimeInputs = [ config.systemd.package ];
|
|
||||||
text = ''
|
|
||||||
${lib.concatMapStringsSep "\n" (dest: ''
|
|
||||||
systemctl start borgbackup-job-${dest.name}
|
|
||||||
'') (lib.attrValues cfg.destinations)}
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
(pkgs.writeShellApplication {
|
|
||||||
name = "borgbackup-list";
|
|
||||||
runtimeInputs = [ pkgs.jq ];
|
|
||||||
text = ''
|
|
||||||
(${
|
|
||||||
lib.concatMapStringsSep "\n" (
|
|
||||||
dest:
|
|
||||||
# we need yes here to skip the changed url verification
|
|
||||||
''echo y | /run/current-system/sw/bin/borg-job-${dest.name} list --json | jq '[.archives[] | {"name": ("${dest.name}::${dest.repo}::" + .name)}]' ''
|
|
||||||
) (lib.attrValues cfg.destinations)
|
|
||||||
}) | jq -s 'add // []'
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
(pkgs.writeShellApplication {
|
|
||||||
name = "borgbackup-restore";
|
|
||||||
runtimeInputs = [ pkgs.gawk ];
|
|
||||||
text = ''
|
|
||||||
cd /
|
|
||||||
IFS=':' read -ra FOLDER <<< "''${FOLDERS-}"
|
|
||||||
job_name=$(echo "$NAME" | awk -F'::' '{print $1}')
|
|
||||||
backup_name=''${NAME#"$job_name"::}
|
|
||||||
if [[ ! -x /run/current-system/sw/bin/borg-job-"$job_name" ]]; then
|
|
||||||
echo "borg-job-$job_name not found: Backup name is invalid" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo y | /run/current-system/sw/bin/borg-job-"$job_name" extract "$backup_name" "''${FOLDER[@]}"
|
|
||||||
'';
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
clan.core.vars.generators.borgbackup = {
|
|
||||||
files."borgbackup.ssh.pub".secret = false;
|
|
||||||
files."borgbackup.ssh" = { };
|
|
||||||
files."borgbackup.repokey" = { };
|
|
||||||
|
|
||||||
migrateFact = "borgbackup";
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssh
|
|
||||||
pkgs.xkcdpass
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/borgbackup.ssh
|
|
||||||
xkcdpass -n 4 -d - > "$out"/borgbackup.repokey
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.backups.providers.borgbackup = {
|
|
||||||
list = "borgbackup-list";
|
|
||||||
create = "borgbackup-create";
|
|
||||||
restore = "borgbackup-restore";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
let
|
|
||||||
dir = config.clan.core.settings.directory;
|
|
||||||
machineDir = dir + "/vars/per-machine/";
|
|
||||||
machineName = config.clan.core.settings.machine.name;
|
|
||||||
|
|
||||||
# Instances might be empty, if the module is not used via the inventory
|
|
||||||
#
|
|
||||||
# Type: { ${instanceName} :: { roles :: Roles } }
|
|
||||||
# Roles :: { ${role_name} :: { machines :: [string] } }
|
|
||||||
instances = config.clan.inventory.services.borgbackup or { };
|
|
||||||
|
|
||||||
allClients = lib.foldlAttrs (
|
|
||||||
acc: _instanceName: instanceConfig:
|
|
||||||
acc
|
|
||||||
++ (
|
|
||||||
if (builtins.elem machineName instanceConfig.roles.server.machines) then
|
|
||||||
instanceConfig.roles.client.machines
|
|
||||||
else
|
|
||||||
[ ]
|
|
||||||
)
|
|
||||||
) [ ] instances;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
clan.borgbackup.directory = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/borgbackup";
|
|
||||||
description = ''
|
|
||||||
The directory where the borgbackup repositories are stored.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config.services.borgbackup.repos =
|
|
||||||
let
|
|
||||||
borgbackupIpMachinePath = machine: machineDir + machine + "/borgbackup/borgbackup.ssh.pub/value";
|
|
||||||
|
|
||||||
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 vars generate ${machine}` to generate it.
|
|
||||||
'' null
|
|
||||||
) allClients;
|
|
||||||
|
|
||||||
machinesWithKey = lib.filter (x: x != null) machinesMaybeKey;
|
|
||||||
|
|
||||||
hosts = builtins.map (machine: {
|
|
||||||
name = machine;
|
|
||||||
value = {
|
|
||||||
path = "${config.clan.borgbackup.directory}/${machine}";
|
|
||||||
authorizedKeys = [ (builtins.readFile (borgbackupIpMachinePath machine)) ];
|
|
||||||
};
|
|
||||||
}) machinesWithKey;
|
|
||||||
in
|
|
||||||
if (builtins.listToAttrs hosts) != [ ] then builtins.listToAttrs hosts else { };
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Set up data-mesher"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory" ]
|
|
||||||
|
|
||||||
[constraints]
|
|
||||||
roles.admin.min = 1
|
|
||||||
roles.admin.max = 1
|
|
||||||
---
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
lib: {
|
|
||||||
|
|
||||||
machines =
|
|
||||||
config:
|
|
||||||
let
|
|
||||||
instanceNames = builtins.attrNames config.clan.inventory.services.data-mesher;
|
|
||||||
instanceName = builtins.head instanceNames;
|
|
||||||
dataMesherInstances = config.clan.inventory.services.data-mesher.${instanceName};
|
|
||||||
|
|
||||||
uniqueStrings = list: builtins.attrNames (builtins.groupBy lib.id list);
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
admins = dataMesherInstances.roles.admin.machines or [ ];
|
|
||||||
signers = dataMesherInstances.roles.signer.machines or [ ];
|
|
||||||
peers = dataMesherInstances.roles.peer.machines or [ ];
|
|
||||||
bootstrap = uniqueStrings (admins ++ signers);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{ lib, config, ... }:
|
|
||||||
let
|
|
||||||
cfg = config.clan.data-mesher;
|
|
||||||
|
|
||||||
dmLib = import ../lib.nix lib;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
options.clan.data-mesher = {
|
|
||||||
network = {
|
|
||||||
tld = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = (config.networking.domain or "clan");
|
|
||||||
description = "Top level domain to use for the network";
|
|
||||||
};
|
|
||||||
|
|
||||||
hostTTL = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "672h"; # 28 days
|
|
||||||
example = "24h";
|
|
||||||
description = "The TTL for hosts in the network, in the form of a Go time.Duration";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.admin module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
services.data-mesher.initNetwork =
|
|
||||||
let
|
|
||||||
# for a given machine, read it's public key and remove any new lines
|
|
||||||
readHostKey =
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
path = "${config.clan.core.settings.directory}/vars/per-machine/${machine}/data-mesher-host-key/public_key/value";
|
|
||||||
in
|
|
||||||
builtins.elemAt (lib.splitString "\n" (builtins.readFile path)) 1;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
enable = true;
|
|
||||||
keyPath = config.clan.core.vars.generators.data-mesher-network-key.files.private_key.path;
|
|
||||||
|
|
||||||
tld = cfg.network.tld;
|
|
||||||
hostTTL = cfg.network.hostTTL;
|
|
||||||
|
|
||||||
# admin and signer host public keys
|
|
||||||
signingKeys = builtins.map readHostKey (dmLib.machines config).bootstrap;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.clan.data-mesher;
|
|
||||||
dmLib = import ./lib.nix lib;
|
|
||||||
|
|
||||||
# the default bootstrap nodes are any machines with the admin or signers role
|
|
||||||
# we iterate through those machines, determining an IP address for them based on their VPN
|
|
||||||
# currently only supports zerotier
|
|
||||||
defaultBootstrapNodes = builtins.foldl' (
|
|
||||||
urls: name:
|
|
||||||
let
|
|
||||||
|
|
||||||
ipPath = "${config.clan.core.settings.directory}/vars/per-machine/${name}/zerotier/zerotier-ip/value";
|
|
||||||
|
|
||||||
in
|
|
||||||
if builtins.pathExists ipPath then
|
|
||||||
let
|
|
||||||
ip = builtins.readFile ipPath;
|
|
||||||
in
|
|
||||||
urls ++ [ "[${ip}]:${builtins.toString cfg.network.port}" ]
|
|
||||||
else
|
|
||||||
urls
|
|
||||||
) [ ] (dmLib.machines config).bootstrap;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.data-mesher = {
|
|
||||||
|
|
||||||
bootstrapNodes = lib.mkOption {
|
|
||||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
A list of bootstrap nodes that act as an initial gateway when joining
|
|
||||||
the cluster.
|
|
||||||
'';
|
|
||||||
example = [
|
|
||||||
"192.168.1.1:7946"
|
|
||||||
"192.168.1.2:7946"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
network = {
|
|
||||||
|
|
||||||
interface = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = ''
|
|
||||||
The interface over which cluster communication should be performed.
|
|
||||||
All the ip addresses associate with this interface will be part of
|
|
||||||
our host claim, including both ipv4 and ipv6.
|
|
||||||
|
|
||||||
This should be set to an internal/VPN interface.
|
|
||||||
'';
|
|
||||||
example = "tailscale0";
|
|
||||||
};
|
|
||||||
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.port;
|
|
||||||
default = 7946;
|
|
||||||
description = ''
|
|
||||||
Port to listen on for cluster communication.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
services.data-mesher = {
|
|
||||||
enable = true;
|
|
||||||
openFirewall = true;
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
log_level = "warn";
|
|
||||||
state_dir = "/var/lib/data-mesher";
|
|
||||||
|
|
||||||
# read network id from vars
|
|
||||||
network.id = config.clan.core.vars.generators.data-mesher-network-key.files.public_key.value;
|
|
||||||
|
|
||||||
host = {
|
|
||||||
names = [ config.networking.hostName ];
|
|
||||||
key_path = config.clan.core.vars.generators.data-mesher-host-key.files.private_key.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
cluster = {
|
|
||||||
port = cfg.network.port;
|
|
||||||
join_interval = "30s";
|
|
||||||
push_pull_interval = "30s";
|
|
||||||
|
|
||||||
interface = cfg.network.interface;
|
|
||||||
|
|
||||||
bootstrap_nodes = if cfg.bootstrapNodes == null then defaultBootstrapNodes else cfg.bootstrapNodes;
|
|
||||||
};
|
|
||||||
|
|
||||||
http.port = 7331;
|
|
||||||
http.interface = "lo";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Generate host key.
|
|
||||||
clan.core.vars.generators.data-mesher-host-key = {
|
|
||||||
files =
|
|
||||||
let
|
|
||||||
owner = config.users.users.data-mesher.name;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
private_key = {
|
|
||||||
inherit owner;
|
|
||||||
};
|
|
||||||
public_key.secret = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
config.services.data-mesher.package
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
data-mesher generate keypair \
|
|
||||||
--public-key-path "$out"/public_key \
|
|
||||||
--private-key-path "$out"/private_key
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.data-mesher-network-key = {
|
|
||||||
# generated once per clan
|
|
||||||
share = true;
|
|
||||||
|
|
||||||
files =
|
|
||||||
let
|
|
||||||
owner = config.users.users.data-mesher.name;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
private_key = {
|
|
||||||
inherit owner;
|
|
||||||
};
|
|
||||||
public_key.secret = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
config.services.data-mesher.package
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
data-mesher generate keypair \
|
|
||||||
--public-key-path "$out"/public_key \
|
|
||||||
--private-key-path "$out"/private_key
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Email-based instant messaging for Desktop."
|
|
||||||
categories = ["Social"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! info
|
|
||||||
This module will automatically configure an email server on the machine for handling the e-mail messaging seamlessly.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- [x] **Email-based**: Uses any email account as its backend.
|
|
||||||
- [x] **End-to-End Encryption**: Supports Autocrypt to automatically encrypt messages.
|
|
||||||
- [x] **No Phone Number Required**: Uses your email address instead of a phone number.
|
|
||||||
- [x] **Cross-Platform**: Available on desktop and mobile platforms.
|
|
||||||
- [x] **Automatic Server Setup**: Includes your own DeltaChat server for enhanced control and privacy.
|
|
||||||
- [ ] **Bake a cake**: This module cannot cake a bake.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
warnings = [
|
|
||||||
"The clan.deltachat module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 25 ]; # smtp with other hosts
|
|
||||||
environment.systemPackages = [ pkgs.deltachat-desktop ];
|
|
||||||
|
|
||||||
services.maddy =
|
|
||||||
let
|
|
||||||
domain = "${config.clan.core.settings.machine.name}.local";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
enable = true;
|
|
||||||
primaryDomain = domain;
|
|
||||||
config = ''
|
|
||||||
# Minimal configuration with TLS disabled, adapted from upstream example
|
|
||||||
# configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
|
|
||||||
# Do not use this in unencrypted networks!
|
|
||||||
|
|
||||||
auth.pass_table local_authdb {
|
|
||||||
table sql_table {
|
|
||||||
driver sqlite3
|
|
||||||
dsn credentials.db
|
|
||||||
table_name passwords
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.imapsql local_mailboxes {
|
|
||||||
driver sqlite3
|
|
||||||
dsn imapsql.db
|
|
||||||
}
|
|
||||||
|
|
||||||
table.chain local_rewrites {
|
|
||||||
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
|
|
||||||
optional_step static {
|
|
||||||
entry postmaster postmaster@$(primary_domain)
|
|
||||||
}
|
|
||||||
optional_step file /etc/maddy/aliases
|
|
||||||
}
|
|
||||||
|
|
||||||
msgpipeline local_routing {
|
|
||||||
destination postmaster $(local_domains) {
|
|
||||||
modify {
|
|
||||||
replace_rcpt &local_rewrites
|
|
||||||
}
|
|
||||||
deliver_to &local_mailboxes
|
|
||||||
}
|
|
||||||
default_destination {
|
|
||||||
reject 550 5.1.1 "User doesn't exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
smtp tcp://[::]:25 {
|
|
||||||
limits {
|
|
||||||
all rate 20 1s
|
|
||||||
all concurrency 10
|
|
||||||
}
|
|
||||||
dmarc yes
|
|
||||||
check {
|
|
||||||
require_mx_record
|
|
||||||
dkim
|
|
||||||
spf
|
|
||||||
}
|
|
||||||
source $(local_domains) {
|
|
||||||
reject 501 5.1.8 "Use Submission for outgoing SMTP"
|
|
||||||
}
|
|
||||||
default_source {
|
|
||||||
destination postmaster $(local_domains) {
|
|
||||||
deliver_to &local_routing
|
|
||||||
}
|
|
||||||
default_destination {
|
|
||||||
reject 550 5.1.1 "User doesn't exist"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submission tcp://[::1]:587 {
|
|
||||||
limits {
|
|
||||||
all rate 50 1s
|
|
||||||
}
|
|
||||||
auth &local_authdb
|
|
||||||
source $(local_domains) {
|
|
||||||
check {
|
|
||||||
authorize_sender {
|
|
||||||
prepare_email &local_rewrites
|
|
||||||
user_to_email identity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destination postmaster $(local_domains) {
|
|
||||||
deliver_to &local_routing
|
|
||||||
}
|
|
||||||
default_destination {
|
|
||||||
modify {
|
|
||||||
dkim $(primary_domain) $(local_domains) default
|
|
||||||
}
|
|
||||||
deliver_to &remote_queue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default_source {
|
|
||||||
reject 501 5.1.8 "Non-local sender domain"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target.remote outbound_delivery {
|
|
||||||
limits {
|
|
||||||
destination rate 20 1s
|
|
||||||
destination concurrency 10
|
|
||||||
}
|
|
||||||
mx_auth {
|
|
||||||
dane
|
|
||||||
mtasts {
|
|
||||||
cache fs
|
|
||||||
fs_dir mtasts_cache/
|
|
||||||
}
|
|
||||||
local_policy {
|
|
||||||
min_tls_level encrypted
|
|
||||||
min_mx_level none
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
target.queue remote_queue {
|
|
||||||
target &outbound_delivery
|
|
||||||
autogenerated_msg_domain $(primary_domain)
|
|
||||||
bounce {
|
|
||||||
destination postmaster $(local_domains) {
|
|
||||||
deliver_to &local_routing
|
|
||||||
}
|
|
||||||
default_destination {
|
|
||||||
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imap tcp://[::1]:143 {
|
|
||||||
auth &local_authdb
|
|
||||||
storage &local_mailboxes
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
ensureAccounts = [ "user@${domain}" ];
|
|
||||||
ensureCredentials = {
|
|
||||||
"user@${domain}".passwordFile = pkgs.writeText "dummy" "foobar";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Generates a uuid for use in disk device naming"
|
|
||||||
features = [ "inventory" ]
|
|
||||||
categories = [ "System" ]
|
|
||||||
---
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
''
|
|
||||||
The clan.disk-id module is deprecated and will be removed on 2025-07-15.
|
|
||||||
For migration see: https://docs.clan.lol/guides/migrations/disk-id/
|
|
||||||
|
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
!!! Please migrate. Otherwise you may not be able to boot your system after that date. !!!
|
|
||||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
||||||
''
|
|
||||||
];
|
|
||||||
clan.core.vars.generators.disk-id = {
|
|
||||||
files.diskId.secret = false;
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.bash
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
uuid=$(bash ${./uuid4.sh})
|
|
||||||
|
|
||||||
# Remove the hyphens from the UUID
|
|
||||||
uuid_no_hyphens=$(echo -n "$uuid" | tr -d '-')
|
|
||||||
|
|
||||||
echo -n "$uuid_no_hyphens" > "$out/diskId"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Read 16 bytes from /dev/urandom
|
|
||||||
uuid=$(dd if=/dev/urandom bs=1 count=16 2>/dev/null | od -An -tx1 | tr -d ' \n')
|
|
||||||
|
|
||||||
# Break the UUID into pieces and apply the required modifications
|
|
||||||
byte6=${uuid:12:2}
|
|
||||||
byte8=${uuid:16:2}
|
|
||||||
|
|
||||||
# Construct the correct version and variant
|
|
||||||
hex_byte6=$(printf "%x" $((0x$byte6 & 0x0F | 0x40)))
|
|
||||||
hex_byte8=$(printf "%x" $((0x$byte8 & 0x3F | 0x80)))
|
|
||||||
|
|
||||||
# Rebuild the UUID with the correct fields
|
|
||||||
uuid_v4="${uuid:0:12}${hex_byte6}${uuid:14:2}${hex_byte8}${uuid:18:14}"
|
|
||||||
|
|
||||||
# Format the UUID correctly 8-4-4-4-12
|
|
||||||
uuid_formatted="${uuid_v4:0:8}-${uuid_v4:8:4}-${uuid_v4:12:4}-${uuid_v4:16:4}-${uuid_v4:20:12}"
|
|
||||||
|
|
||||||
echo -n "$uuid_formatted"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A dynamic DNS service to update domain IPs"
|
|
||||||
---
|
|
||||||
|
|
||||||
To understand the possible options that can be set visit the documentation of [ddns-updater](https://github.com/qdm12/ddns-updater?tab=readme-ov-file#versioned-documentation)
|
|
||||||
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
name = "dyndns";
|
|
||||||
cfg = config.clan.${name};
|
|
||||||
|
|
||||||
# We dedup secrets if they have the same provider + base domain
|
|
||||||
secret_id = opt: "${name}-${opt.provider}-${opt.domain}";
|
|
||||||
secret_path =
|
|
||||||
opt: config.clan.core.vars.generators."${secret_id opt}".files."${secret_id opt}".path;
|
|
||||||
|
|
||||||
# We check that a secret has not been set in extraSettings.
|
|
||||||
extraSettingsSafe =
|
|
||||||
opt:
|
|
||||||
if (builtins.hasAttr opt.secret_field_name opt.extraSettings) then
|
|
||||||
throw "Please do not set ${opt.secret_field_name} in extraSettings, it is automatically set by the dyndns module."
|
|
||||||
else
|
|
||||||
opt.extraSettings;
|
|
||||||
/*
|
|
||||||
We go from:
|
|
||||||
{home.example.com:{value:{domain:example.com,host:home, provider:namecheap}}}
|
|
||||||
To:
|
|
||||||
{settings: [{domain: example.com, host: home, provider: namecheap, password: dyndns-namecheap-example.com}]}
|
|
||||||
*/
|
|
||||||
service_config = {
|
|
||||||
settings = builtins.catAttrs "value" (
|
|
||||||
builtins.attrValues (
|
|
||||||
lib.mapAttrs (_: opt: {
|
|
||||||
value =
|
|
||||||
(extraSettingsSafe opt)
|
|
||||||
// {
|
|
||||||
domain = opt.domain;
|
|
||||||
provider = opt.provider;
|
|
||||||
}
|
|
||||||
// {
|
|
||||||
"${opt.secret_field_name}" = secret_id opt;
|
|
||||||
};
|
|
||||||
}) cfg.settings
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
secret_generator = _: opt: {
|
|
||||||
name = secret_id opt;
|
|
||||||
value = {
|
|
||||||
share = true;
|
|
||||||
migrateFact = "${secret_id opt}";
|
|
||||||
prompts.${secret_id opt} = {
|
|
||||||
type = "hidden";
|
|
||||||
persist = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.${name} = {
|
|
||||||
server = {
|
|
||||||
enable = lib.mkEnableOption "dyndns webserver";
|
|
||||||
domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "Domain to serve the webservice on";
|
|
||||||
};
|
|
||||||
port = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 54805;
|
|
||||||
description = "Port to listen on";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
period = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 5;
|
|
||||||
description = "Domain update period in minutes";
|
|
||||||
};
|
|
||||||
|
|
||||||
settings = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
provider = lib.mkOption {
|
|
||||||
example = "namecheap";
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The dyndns provider to use";
|
|
||||||
};
|
|
||||||
domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
example = "example.com";
|
|
||||||
description = "The top level domain to update.";
|
|
||||||
};
|
|
||||||
secret_field_name = lib.mkOption {
|
|
||||||
example = [
|
|
||||||
"password"
|
|
||||||
"api_key"
|
|
||||||
];
|
|
||||||
type = lib.types.enum [
|
|
||||||
"password"
|
|
||||||
"token"
|
|
||||||
"api_key"
|
|
||||||
"secret_api_key"
|
|
||||||
];
|
|
||||||
default = "password";
|
|
||||||
description = "The field name for the secret";
|
|
||||||
};
|
|
||||||
# TODO: Ideally we would create a gigantic list of all possible settings / types
|
|
||||||
# optimally we would have a way to generate the options from the source code
|
|
||||||
extraSettings = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf lib.types.str;
|
|
||||||
default = { };
|
|
||||||
description = ''
|
|
||||||
Extra settings for the provider.
|
|
||||||
Provider specific settings: https://github.com/qdm12/ddns-updater#configuration
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = [ ];
|
|
||||||
description = "Configuration for which domains to update";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
../nginx
|
|
||||||
];
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
(lib.mkIf (cfg.settings != { }) {
|
|
||||||
clan.core.vars.generators = lib.mapAttrs' secret_generator cfg.settings;
|
|
||||||
|
|
||||||
users.groups.${name} = { };
|
|
||||||
users.users.${name} = {
|
|
||||||
group = name;
|
|
||||||
isSystemUser = true;
|
|
||||||
description = "User for ${name} service";
|
|
||||||
home = "/var/lib/${name}";
|
|
||||||
createHome = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx = lib.mkIf cfg.server.enable {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts = {
|
|
||||||
"${cfg.server.domain}" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/" = {
|
|
||||||
proxyPass = "http://localhost:${toString cfg.server.port}";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.${name} = {
|
|
||||||
path = [ ];
|
|
||||||
description = "Dynamic DNS updater";
|
|
||||||
after = [ "network.target" ];
|
|
||||||
wantedBy = [ "multi-user.target" ];
|
|
||||||
environment = {
|
|
||||||
MYCONFIG = "${builtins.toJSON service_config}";
|
|
||||||
SERVER_ENABLED = if cfg.server.enable then "yes" else "no";
|
|
||||||
PERIOD = "${toString cfg.period}m";
|
|
||||||
LISTENING_ADDRESS = ":${toString cfg.server.port}";
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceConfig =
|
|
||||||
let
|
|
||||||
pyscript =
|
|
||||||
pkgs.writers.writePython3Bin "generate_secret_config.py"
|
|
||||||
{
|
|
||||||
libraries = [ ];
|
|
||||||
doCheck = false;
|
|
||||||
}
|
|
||||||
''
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
import os
|
|
||||||
|
|
||||||
cred_dir = Path(os.getenv("CREDENTIALS_DIRECTORY"))
|
|
||||||
config_str = os.getenv("MYCONFIG")
|
|
||||||
|
|
||||||
|
|
||||||
def get_credential(name):
|
|
||||||
secret_p = cred_dir / name
|
|
||||||
with open(secret_p, 'r') as f:
|
|
||||||
return f.read().strip()
|
|
||||||
|
|
||||||
|
|
||||||
config = json.loads(config_str)
|
|
||||||
print(f"Config: {config}")
|
|
||||||
for attrset in config["settings"]:
|
|
||||||
if "password" in attrset:
|
|
||||||
attrset['password'] = get_credential(attrset['password'])
|
|
||||||
elif "token" in attrset:
|
|
||||||
attrset['token'] = get_credential(attrset['token'])
|
|
||||||
elif "secret_api_key" in attrset:
|
|
||||||
attrset['secret_api_key'] = get_credential(attrset['secret_api_key'])
|
|
||||||
elif "api_key" in attrset:
|
|
||||||
attrset['api_key'] = get_credential(attrset['api_key'])
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Missing secret field in {attrset}")
|
|
||||||
|
|
||||||
# create directory data if it does not exist
|
|
||||||
data_dir = Path('data')
|
|
||||||
data_dir.mkdir(mode=0o770, exist_ok=True)
|
|
||||||
|
|
||||||
# Create a temporary config file
|
|
||||||
# with appropriate permissions
|
|
||||||
tmp_config_path = data_dir / '.config.json'
|
|
||||||
tmp_config_path.touch(mode=0o660, exist_ok=False)
|
|
||||||
|
|
||||||
# Write the config with secrets back
|
|
||||||
with open(tmp_config_path, 'w') as f:
|
|
||||||
f.write(json.dumps(config, indent=4))
|
|
||||||
|
|
||||||
# Move config into place
|
|
||||||
config_path = data_dir / 'config.json'
|
|
||||||
tmp_config_path.rename(config_path)
|
|
||||||
|
|
||||||
# Set file permissions to read
|
|
||||||
# and write only by the user and group
|
|
||||||
for file in data_dir.iterdir():
|
|
||||||
file.chmod(0o660)
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
ExecStartPre = lib.getExe pyscript;
|
|
||||||
ExecStart = lib.getExe pkgs.ddns-updater;
|
|
||||||
LoadCredential = lib.mapAttrsToList (_: opt: "${secret_id opt}:${secret_path opt}") cfg.settings;
|
|
||||||
User = name;
|
|
||||||
Group = name;
|
|
||||||
NoNewPrivileges = true;
|
|
||||||
PrivateTmp = true;
|
|
||||||
ProtectSystem = "strict";
|
|
||||||
ReadOnlyPaths = "/";
|
|
||||||
PrivateDevices = "yes";
|
|
||||||
ProtectKernelModules = "yes";
|
|
||||||
ProtectKernelTunables = "yes";
|
|
||||||
WorkingDirectory = "/var/lib/${name}";
|
|
||||||
ReadWritePaths = [
|
|
||||||
"/proc/self"
|
|
||||||
"/var/lib/${name}"
|
|
||||||
];
|
|
||||||
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = 60;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A modern IRC server"
|
|
||||||
categories = ["Social"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
_: {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.ergochat module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
services.ergochat = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
datastore = {
|
|
||||||
autoupgrade = true;
|
|
||||||
path = "/var/lib/ergo/ircd.db";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.state.ergochat.folders = [ "/var/lib/ergo" ];
|
|
||||||
}
|
|
||||||
@@ -1,51 +1,62 @@
|
|||||||
{ lib, ... }:
|
{ ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
inherit (lib)
|
error = builtins.throw ''
|
||||||
filterAttrs
|
|
||||||
pathExists
|
###############################################################################
|
||||||
;
|
# #
|
||||||
|
# Clan modules (clanModules) have been deprecated and removed in favor of #
|
||||||
|
# Clan services! #
|
||||||
|
# #
|
||||||
|
# Refer to https://docs.clan.lol/guides/migrations/migrate-inventory-services #
|
||||||
|
# for migration instructions. #
|
||||||
|
# #
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
'';
|
||||||
|
|
||||||
|
modnames = [
|
||||||
|
"admin"
|
||||||
|
"borgbackup"
|
||||||
|
"borgbackup-static"
|
||||||
|
"deltachat"
|
||||||
|
"disk-id"
|
||||||
|
"dyndns"
|
||||||
|
"ergochat"
|
||||||
|
"garage"
|
||||||
|
"heisenbridge"
|
||||||
|
"iwd"
|
||||||
|
"localbackup"
|
||||||
|
"localsend"
|
||||||
|
"matrix-synapse"
|
||||||
|
"moonlight"
|
||||||
|
"mumble"
|
||||||
|
"nginx"
|
||||||
|
"packages"
|
||||||
|
"postgresql"
|
||||||
|
"root-password"
|
||||||
|
"single-disk"
|
||||||
|
"sshd"
|
||||||
|
"state-version"
|
||||||
|
"static-hosts"
|
||||||
|
"sunshine"
|
||||||
|
"syncthing"
|
||||||
|
"syncthing-static-peers"
|
||||||
|
"thelounge"
|
||||||
|
"trusted-nix-caches"
|
||||||
|
"user-password"
|
||||||
|
"vaultwarden"
|
||||||
|
"xfce"
|
||||||
|
"zerotier-static-peers"
|
||||||
|
"zt-tcp-relay"
|
||||||
|
];
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
# only import available files, as this allows to filter the files for tests.
|
flake.clanModules = builtins.listToAttrs (
|
||||||
flake.clanModules = filterAttrs (_name: pathExists) {
|
map (name: {
|
||||||
auto-upgrade = ./auto-upgrade;
|
inherit name;
|
||||||
admin = ./admin;
|
value = error;
|
||||||
borgbackup = ./borgbackup;
|
}) modnames
|
||||||
borgbackup-static = ./borgbackup-static;
|
);
|
||||||
deltachat = ./deltachat;
|
|
||||||
data-mesher = ./data-mesher;
|
|
||||||
disk-id = ./disk-id;
|
|
||||||
dyndns = ./dyndns;
|
|
||||||
ergochat = ./ergochat;
|
|
||||||
garage = ./garage;
|
|
||||||
heisenbridge = ./heisenbridge;
|
|
||||||
importer = ./importer;
|
|
||||||
iwd = ./iwd;
|
|
||||||
localbackup = ./localbackup;
|
|
||||||
localsend = ./localsend;
|
|
||||||
matrix-synapse = ./matrix-synapse;
|
|
||||||
moonlight = ./moonlight;
|
|
||||||
mumble = ./mumble;
|
|
||||||
mycelium = ./mycelium;
|
|
||||||
nginx = ./nginx;
|
|
||||||
packages = ./packages;
|
|
||||||
postgresql = ./postgresql;
|
|
||||||
root-password = ./root-password;
|
|
||||||
single-disk = ./single-disk;
|
|
||||||
sshd = ./sshd;
|
|
||||||
state-version = ./state-version;
|
|
||||||
static-hosts = ./static-hosts;
|
|
||||||
sunshine = ./sunshine;
|
|
||||||
syncthing = ./syncthing;
|
|
||||||
syncthing-static-peers = ./syncthing-static-peers;
|
|
||||||
thelounge = ./thelounge;
|
|
||||||
trusted-nix-caches = ./trusted-nix-caches;
|
|
||||||
user-password = ./user-password;
|
|
||||||
vaultwarden = ./vaultwarden;
|
|
||||||
wifi = ./wifi;
|
|
||||||
xfce = ./xfce;
|
|
||||||
zerotier = ./zerotier;
|
|
||||||
zerotier-static-peers = ./zerotier-static-peers;
|
|
||||||
zt-tcp-relay = ./zt-tcp-relay;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
description = "S3-compatible object store for small self-hosted geo-distributed deployments"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
|
|
||||||
This module generates garage specific keys automatically.
|
|
||||||
Also shares the `rpc_secret` between instances.
|
|
||||||
|
|
||||||
Options: [NixosModuleOptions](https://search.nixos.org/options?channel=unstable&size=50&sort=relevance&type=packages&query=garage)
|
|
||||||
Documentation: https://garagehq.deuxfleurs.fr/
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
{
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.ergochat module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.services.garage.serviceConfig = {
|
|
||||||
LoadCredential = [
|
|
||||||
"rpc_secret_path:${config.clan.core.vars.generators.garage-shared.files.rpc_secret.path}"
|
|
||||||
"admin_token_path:${config.clan.core.vars.generators.garage.files.admin_token.path}"
|
|
||||||
"metrics_token_path:${config.clan.core.vars.generators.garage.files.metrics_token.path}"
|
|
||||||
];
|
|
||||||
Environment = [
|
|
||||||
"GARAGE_ALLOW_WORLD_READABLE_SECRETS=true"
|
|
||||||
"GARAGE_RPC_SECRET_FILE=%d/rpc_secret_path"
|
|
||||||
"GARAGE_ADMIN_TOKEN_FILE=%d/admin_token_path"
|
|
||||||
"GARAGE_METRICS_TOKEN_FILE=%d/metrics_token_path"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.garage = {
|
|
||||||
files.admin_token = { };
|
|
||||||
files.metrics_token = { };
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
openssl rand -base64 -out "$out"/admin_token 32
|
|
||||||
openssl rand -base64 -out "$out"/metrics_token 32
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.garage-shared = {
|
|
||||||
share = true;
|
|
||||||
files.rpc_secret = { };
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
openssl rand -hex -out "$out"/rpc_secret 32
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.state.garage.folders = [ config.services.garage.settings.metadata_dir ];
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A matrix bridge to communicate with IRC"
|
|
||||||
categories = ["Social"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"heisenbridge"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
warnings = [
|
|
||||||
"The clan.heisenbridge module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
services.heisenbridge = {
|
|
||||||
enable = true;
|
|
||||||
homeserver = "http://localhost:8008"; # TODO: Sync with matrix-synapse
|
|
||||||
};
|
|
||||||
services.matrix-synapse.settings.app_service_config_files = [
|
|
||||||
"/var/lib/heisenbridge/registration.yml"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Convenient, structured module imports for hosts."
|
|
||||||
categories = ["Utility"]
|
|
||||||
features = [ "inventory" ]
|
|
||||||
---
|
|
||||||
The importer module allows users to configure importing modules in a flexible and structured way.
|
|
||||||
|
|
||||||
It exposes the `extraModules` functionality of the inventory, without any added configuration.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```nix
|
|
||||||
inventory.services = {
|
|
||||||
importer.base = {
|
|
||||||
roles.default.tags = [ "all" ];
|
|
||||||
roles.default.extraModules = [ "modules/base.nix" ];
|
|
||||||
};
|
|
||||||
importer.zone1 = {
|
|
||||||
roles.default.tags = [ "zone1" ];
|
|
||||||
roles.default.extraModules = [ "modules/zone1.nix" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This will import the module `modules/base.nix` to all machines that have the `all` tag,
|
|
||||||
which by default is every machine managed by the clan.
|
|
||||||
And also import for all machines tagged with `zone1` the module at `modules/zone1.nix`.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{ }
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Automatically provisions wifi credentials"
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
categories = [ "Network" ]
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! Warning
|
|
||||||
If you've been using network manager + wpa_supplicant and now are switching to IWD read this migration guide:
|
|
||||||
https://archive.kernel.org/oldwiki/iwd.wiki.kernel.org/networkmanager.html#converting_network_profiles
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.clan.iwd;
|
|
||||||
secret_path = ssid: config.clan.core.vars.generators."iwd.${ssid}".files."iwd.${ssid}".path;
|
|
||||||
secret_generator = name: value: {
|
|
||||||
name = "iwd.${value.ssid}";
|
|
||||||
value =
|
|
||||||
let
|
|
||||||
secret_name = "iwd.${value.ssid}";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
prompts.${secret_name} = {
|
|
||||||
description = "Wifi password for '${value.ssid}'";
|
|
||||||
persist = true;
|
|
||||||
};
|
|
||||||
migrateFact = secret_name;
|
|
||||||
# ref. man iwd.network
|
|
||||||
script = ''
|
|
||||||
config="
|
|
||||||
[Settings]
|
|
||||||
AutoConnect=${if value.AutoConnect then "true" else "false"}
|
|
||||||
[Security]
|
|
||||||
Passphrase=$(echo -e "$prompt_value/${secret_name}" | ${lib.getExe pkgs.gnused} "s=\\\=\\\\\\\=g;s=\t=\\\t=g;s=\r=\\\r=g;s=^ =\\\s=")
|
|
||||||
"
|
|
||||||
echo "$config" > "$out/${secret_name}"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.iwd = {
|
|
||||||
networks = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
ssid = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = name;
|
|
||||||
description = "The name of the wifi network";
|
|
||||||
};
|
|
||||||
AutoConnect = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Automatically try to join this wifi network";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = { };
|
|
||||||
description = "Wifi networks to predefine";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"iwd"
|
|
||||||
"enable"
|
|
||||||
] "Just define clan.iwd.networks to enable it")
|
|
||||||
];
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
(lib.mkIf (cfg.networks != { }) {
|
|
||||||
# Systemd tmpfiles rule to create /var/lib/iwd/example.psk file
|
|
||||||
systemd.tmpfiles.rules = lib.mapAttrsToList (
|
|
||||||
_: value: "C /var/lib/iwd/${value.ssid}.psk 0600 root root - ${secret_path value.ssid}"
|
|
||||||
) cfg.networks;
|
|
||||||
|
|
||||||
clan.core.vars.generators = lib.mapAttrs' secret_generator cfg.networks;
|
|
||||||
|
|
||||||
# TODO: restart the iwd.service if something changes
|
|
||||||
})
|
|
||||||
{
|
|
||||||
warnings = [
|
|
||||||
"The clan.iwd module is deprecated and will be removed on 2025-07-15. Please migrate to a user-maintained configuration or use the wifi service."
|
|
||||||
];
|
|
||||||
|
|
||||||
# disable wpa supplicant
|
|
||||||
networking.wireless.enable = false;
|
|
||||||
|
|
||||||
# Set the network manager backend to iwd
|
|
||||||
networking.networkmanager.wifi.backend = "iwd";
|
|
||||||
|
|
||||||
# Use iwd instead of wpa_supplicant. It has a user friendly CLI
|
|
||||||
networking.wireless.iwd = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
Network = {
|
|
||||||
EnableIPv6 = true;
|
|
||||||
RoutePriorityOffset = 300;
|
|
||||||
};
|
|
||||||
Settings.AutoConnect = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Automatically backups current machine to local directory."
|
|
||||||
---
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.clan.localbackup;
|
|
||||||
uniqueFolders = lib.unique (
|
|
||||||
lib.flatten (lib.mapAttrsToList (_name: state: state.folders) config.clan.core.state)
|
|
||||||
);
|
|
||||||
rsnapshotConfig = target: ''
|
|
||||||
config_version 1.2
|
|
||||||
snapshot_root ${target.directory}
|
|
||||||
sync_first 1
|
|
||||||
cmd_cp ${pkgs.coreutils}/bin/cp
|
|
||||||
cmd_rm ${pkgs.coreutils}/bin/rm
|
|
||||||
cmd_rsync ${pkgs.rsync}/bin/rsync
|
|
||||||
cmd_ssh ${pkgs.openssh}/bin/ssh
|
|
||||||
cmd_logger ${pkgs.inetutils}/bin/logger
|
|
||||||
cmd_du ${pkgs.coreutils}/bin/du
|
|
||||||
cmd_rsnapshot_diff ${pkgs.rsnapshot}/bin/rsnapshot-diff
|
|
||||||
|
|
||||||
${lib.optionalString (target.postBackupHook != null) ''
|
|
||||||
cmd_postexec ${pkgs.writeShellScript "postexec.sh" ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
${target.postBackupHook}
|
|
||||||
''}
|
|
||||||
''}
|
|
||||||
retain snapshot ${builtins.toString config.clan.localbackup.snapshots}
|
|
||||||
${lib.concatMapStringsSep "\n" (folder: ''
|
|
||||||
backup ${folder} ${config.networking.hostName}/
|
|
||||||
'') uniqueFolders}
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.localbackup = {
|
|
||||||
targets = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf (
|
|
||||||
lib.types.submodule (
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
name = lib.mkOption {
|
|
||||||
type = lib.types.strMatching "^[a-zA-Z0-9._-]+$";
|
|
||||||
default = name;
|
|
||||||
description = "the name of the backup job";
|
|
||||||
};
|
|
||||||
directory = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "the directory to backup";
|
|
||||||
};
|
|
||||||
mountpoint = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "mountpoint of the directory to backup. If set, the directory will be mounted before the backup and unmounted afterwards";
|
|
||||||
};
|
|
||||||
preMountHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run before the directory is mounted";
|
|
||||||
};
|
|
||||||
postMountHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run after the directory is mounted";
|
|
||||||
};
|
|
||||||
preUnmountHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run before the directory is unmounted";
|
|
||||||
};
|
|
||||||
postUnmountHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run after the directory is unmounted";
|
|
||||||
};
|
|
||||||
preBackupHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run before the backup";
|
|
||||||
};
|
|
||||||
postBackupHook = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.lines;
|
|
||||||
default = null;
|
|
||||||
description = "Shell commands to run after the backup";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default = { };
|
|
||||||
description = "List of directories where backups are stored";
|
|
||||||
};
|
|
||||||
|
|
||||||
snapshots = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 20;
|
|
||||||
description = "Number of snapshots to keep";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config =
|
|
||||||
let
|
|
||||||
mountHook = target: ''
|
|
||||||
if [[ -x /run/current-system/sw/bin/localbackup-mount-${target.name} ]]; then
|
|
||||||
/run/current-system/sw/bin/localbackup-mount-${target.name}
|
|
||||||
fi
|
|
||||||
if [[ -x /run/current-system/sw/bin/localbackup-unmount-${target.name} ]]; then
|
|
||||||
trap "/run/current-system/sw/bin/localbackup-unmount-${target.name}" EXIT
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
lib.mkIf (cfg.targets != { }) {
|
|
||||||
environment.systemPackages = [
|
|
||||||
(pkgs.writeShellScriptBin "localbackup-create" ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
pkgs.rsnapshot
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
]
|
|
||||||
}
|
|
||||||
${lib.concatMapStringsSep "\n" (target: ''
|
|
||||||
${mountHook target}
|
|
||||||
echo "Creating backup '${target.name}'"
|
|
||||||
|
|
||||||
${lib.optionalString (target.preBackupHook != null) ''
|
|
||||||
(
|
|
||||||
${target.preBackupHook}
|
|
||||||
)
|
|
||||||
''}
|
|
||||||
|
|
||||||
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" ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
pkgs.jq
|
|
||||||
pkgs.findutils
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
]
|
|
||||||
}
|
|
||||||
(${
|
|
||||||
lib.concatMapStringsSep "\n" (target: ''
|
|
||||||
(
|
|
||||||
${mountHook target}
|
|
||||||
find ${lib.escapeShellArg target.directory} -mindepth 1 -maxdepth 1 -name "snapshot.*" -print0 -type d \
|
|
||||||
| jq -Rs 'split("\u0000") | .[] | select(. != "") | { "name": ("${target.name}::" + .)}'
|
|
||||||
)
|
|
||||||
'') (builtins.attrValues cfg.targets)
|
|
||||||
}) | jq -s .
|
|
||||||
'')
|
|
||||||
(pkgs.writeShellScriptBin "localbackup-restore" ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
export PATH=${
|
|
||||||
lib.makeBinPath [
|
|
||||||
pkgs.rsync
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.util-linux
|
|
||||||
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)
|
|
||||||
backupname=''${NAME#$name::}
|
|
||||||
|
|
||||||
if command -v localbackup-mount-$name; then
|
|
||||||
localbackup-mount-$name
|
|
||||||
fi
|
|
||||||
if command -v localbackup-unmount-$name; then
|
|
||||||
trap "localbackup-unmount-$name" EXIT
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -d $backupname ]]; then
|
|
||||||
echo "No backup found $backupname"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IFS=':' read -ra FOLDER <<< "''$FOLDERS"
|
|
||||||
for folder in "''${FOLDER[@]}"; do
|
|
||||||
mkdir -p "$folder"
|
|
||||||
rsync -a "$backupname/${config.networking.hostName}$folder/" "$folder"
|
|
||||||
done
|
|
||||||
'')
|
|
||||||
]
|
|
||||||
++ (lib.mapAttrsToList (
|
|
||||||
name: target:
|
|
||||||
pkgs.writeShellScriptBin ("localbackup-mount-" + name) ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
${lib.optionalString (target.preMountHook != null) target.preMountHook}
|
|
||||||
${lib.optionalString (target.mountpoint != null) ''
|
|
||||||
if ! ${pkgs.util-linux}/bin/mountpoint -q ${lib.escapeShellArg target.mountpoint}; then
|
|
||||||
${pkgs.util-linux}/bin/mount -o X-mount.mkdir ${lib.escapeShellArg target.mountpoint}
|
|
||||||
fi
|
|
||||||
''}
|
|
||||||
${lib.optionalString (target.postMountHook != null) target.postMountHook}
|
|
||||||
''
|
|
||||||
) cfg.targets)
|
|
||||||
++ lib.mapAttrsToList (
|
|
||||||
name: target:
|
|
||||||
pkgs.writeShellScriptBin ("localbackup-unmount-" + name) ''
|
|
||||||
set -efu -o pipefail
|
|
||||||
${lib.optionalString (target.preUnmountHook != null) target.preUnmountHook}
|
|
||||||
${lib.optionalString (
|
|
||||||
target.mountpoint != null
|
|
||||||
) "${pkgs.util-linux}/bin/umount ${lib.escapeShellArg target.mountpoint}"}
|
|
||||||
${lib.optionalString (target.postUnmountHook != null) target.postUnmountHook}
|
|
||||||
''
|
|
||||||
) cfg.targets;
|
|
||||||
|
|
||||||
clan.core.backups.providers.localbackup = {
|
|
||||||
# TODO list needs to run locally or on the remote machine
|
|
||||||
list = "localbackup-list";
|
|
||||||
create = "localbackup-create";
|
|
||||||
restore = "localbackup-restore";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Securely sharing files and messages over a local network without internet connectivity."
|
|
||||||
categories = ["Utility"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
writers,
|
|
||||||
writeShellScriptBin,
|
|
||||||
localsend,
|
|
||||||
alias ? null,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
localsend-ensure-config = writers.writePython3 "localsend-ensure-config" {
|
|
||||||
flakeIgnore = [
|
|
||||||
# We don't live in the dark ages anymore.
|
|
||||||
# Languages like Python that are whitespace heavy will overrun
|
|
||||||
# 79 characters..
|
|
||||||
"E501"
|
|
||||||
];
|
|
||||||
} (builtins.readFile ./localsend-ensure-config.py);
|
|
||||||
in
|
|
||||||
writeShellScriptBin "localsend" ''
|
|
||||||
set -xeu
|
|
||||||
${localsend-ensure-config} ${lib.optionalString (alias != null) alias}
|
|
||||||
${lib.getExe localsend}
|
|
||||||
''
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import json
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def load_json(file_path: Path) -> dict[str, any]:
|
|
||||||
try:
|
|
||||||
with file_path.open("r") as file:
|
|
||||||
return json.load(file)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def save_json(file_path: Path, data: dict[str, any]) -> None:
|
|
||||||
with file_path.open("w") as file:
|
|
||||||
json.dump(data, file, indent=4)
|
|
||||||
|
|
||||||
|
|
||||||
def update_json(file_path: Path, updates: dict[str, any]) -> None:
|
|
||||||
data = load_json(file_path)
|
|
||||||
data.update(updates)
|
|
||||||
save_json(file_path, data)
|
|
||||||
|
|
||||||
|
|
||||||
def config_location() -> str:
|
|
||||||
config_file = "shared_preferences.json"
|
|
||||||
config_directory = ".local/share/org.localsend.localsend_app"
|
|
||||||
config_path = Path.home() / Path(config_directory) / Path(config_file)
|
|
||||||
return config_path
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config_directory() -> None:
|
|
||||||
config_directory = Path(config_location()).parent
|
|
||||||
config_directory.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
|
|
||||||
def load_config() -> dict[str, any]:
|
|
||||||
return load_json(config_location())
|
|
||||||
|
|
||||||
|
|
||||||
def save_config(data: dict[str, any]) -> None:
|
|
||||||
save_json(config_location(), data)
|
|
||||||
|
|
||||||
|
|
||||||
def update_username(username: str, data: dict[str, any]) -> dict[str, any]:
|
|
||||||
data["flutter.ls_alias"] = username
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str]) -> None:
|
|
||||||
try:
|
|
||||||
display_name = argv[1]
|
|
||||||
except IndexError:
|
|
||||||
# This is not an error, just don't update the name
|
|
||||||
print("No display name provided.")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
ensure_config_directory()
|
|
||||||
updated_data = update_username(display_name, load_config())
|
|
||||||
save_config(updated_data)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main(sys.argv[:2])
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
cfg = config.clan.localsend;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
# Integration can be improved, if the following issues get implemented:
|
|
||||||
# - cli frontend: https://github.com/localsend/localsend/issues/11
|
|
||||||
# - ipv6 support: https://github.com/localsend/localsend/issues/549
|
|
||||||
options.clan.localsend = {
|
|
||||||
|
|
||||||
displayName = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = "The name that localsend will use to display your instance.";
|
|
||||||
};
|
|
||||||
|
|
||||||
package = lib.mkPackageOption pkgs "localsend" { };
|
|
||||||
|
|
||||||
ipv4Addr = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "192.168.56.2/24";
|
|
||||||
description = "Optional IPv4 address for ZeroTier network.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"localsend"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
warnings = [
|
|
||||||
"The clan.localsend module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
clan.core.state.localsend.folders = [
|
|
||||||
"/var/localsend"
|
|
||||||
];
|
|
||||||
environment.systemPackages = [
|
|
||||||
(pkgs.callPackage ./localsend-ensure-config {
|
|
||||||
localsend = config.clan.localsend.package;
|
|
||||||
alias = config.clan.localsend.displayName;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.firewall.interfaces."zt+".allowedTCPPorts = [ 53317 ];
|
|
||||||
networking.firewall.interfaces."zt+".allowedUDPPorts = [ 53317 ];
|
|
||||||
|
|
||||||
#TODO: This is currently needed because there is no ipv6 multicasting support yet
|
|
||||||
systemd.network.networks = lib.mkIf (cfg.ipv4Addr != null) {
|
|
||||||
"09-zerotier" = {
|
|
||||||
networkConfig = {
|
|
||||||
Address = cfg.ipv4Addr;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A federated messaging server with end-to-end encryption."
|
|
||||||
---
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.clan.matrix-synapse;
|
|
||||||
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://${cfg.app_domain}:443", "server_name": "${cfg.server_tld}" }' \
|
|
||||||
> $out/config.json < ${pkgs.element-web}/config.json
|
|
||||||
ln -s $out/config.json $out/config.${cfg.app_domain}.json
|
|
||||||
'';
|
|
||||||
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 = {
|
|
||||||
server_tld = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The address that is suffixed after your username i.e @alice:example.com";
|
|
||||||
example = "example.com";
|
|
||||||
};
|
|
||||||
|
|
||||||
app_domain = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The matrix server hostname also serves the element client";
|
|
||||||
example = "matrix.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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"matrix-synapse"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
../nginx
|
|
||||||
];
|
|
||||||
config = {
|
|
||||||
services.matrix-synapse = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
server_name = cfg.server_tld;
|
|
||||||
database = {
|
|
||||||
args.user = "matrix-synapse";
|
|
||||||
args.database = "matrix-synapse";
|
|
||||||
name = "psycopg2";
|
|
||||||
};
|
|
||||||
turn_uris = [
|
|
||||||
"turn:turn.matrix.org?transport=udp"
|
|
||||||
"turn:turn.matrix.org?transport=tcp"
|
|
||||||
];
|
|
||||||
registration_shared_secret_path = "/run/synapse-registration-shared-secret";
|
|
||||||
listeners = [
|
|
||||||
{
|
|
||||||
port = 8008;
|
|
||||||
bind_addresses = [ "::1" ];
|
|
||||||
type = "http";
|
|
||||||
tls = false;
|
|
||||||
x_forwarded = true;
|
|
||||||
resources = [
|
|
||||||
{
|
|
||||||
names = [ "client" ];
|
|
||||||
compress = true;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
names = [ "federation" ];
|
|
||||||
compress = false;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.postgresql.enable = true;
|
|
||||||
clan.core.postgresql.users.matrix-synapse = { };
|
|
||||||
clan.core.postgresql.databases.matrix-synapse.create.options = {
|
|
||||||
TEMPLATE = "template0";
|
|
||||||
LC_COLLATE = "C";
|
|
||||||
LC_CTYPE = "C";
|
|
||||||
ENCODING = "UTF8";
|
|
||||||
OWNER = "matrix-synapse";
|
|
||||||
};
|
|
||||||
clan.core.postgresql.databases.matrix-synapse.restore.stopOnRestore = [ "matrix-synapse" ];
|
|
||||||
|
|
||||||
clan.core.vars.generators = {
|
|
||||||
"matrix-synapse" = {
|
|
||||||
files."synapse-registration_shared_secret" = { };
|
|
||||||
runtimeInputs = with pkgs; [
|
|
||||||
coreutils
|
|
||||||
pwgen
|
|
||||||
];
|
|
||||||
migrateFact = "matrix-synapse";
|
|
||||||
script = ''
|
|
||||||
echo -n "$(pwgen -s 32 1)" > "$out"/synapse-registration_shared_secret
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// lib.mapAttrs' (
|
|
||||||
name: user:
|
|
||||||
lib.nameValuePair "matrix-password-${user.name}" {
|
|
||||||
files."matrix-password-${user.name}" = { };
|
|
||||||
migrateFact = "matrix-password-${user.name}";
|
|
||||||
runtimeInputs = with pkgs; [ xkcdpass ];
|
|
||||||
script = ''
|
|
||||||
xkcdpass -n 4 -d - > "$out"/${lib.escapeShellArg "matrix-password-${user.name}"}
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
) cfg.users;
|
|
||||||
|
|
||||||
systemd.services.matrix-synapse =
|
|
||||||
let
|
|
||||||
usersScript = ''
|
|
||||||
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.vars.generators."matrix-password-${user.name}".files."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.ExecStartPre = lib.mkBefore [
|
|
||||||
"+${pkgs.coreutils}/bin/install -o matrix-synapse -g matrix-synapse ${
|
|
||||||
lib.escapeShellArg
|
|
||||||
config.clan.core.vars.generators.matrix-synapse.files."synapse-registration_shared_secret".path
|
|
||||||
} /run/synapse-registration-shared-secret"
|
|
||||||
];
|
|
||||||
serviceConfig.ExecStartPost = [
|
|
||||||
''+${pkgs.writeShellScript "matrix-synapse-create-users" usersScript}''
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
virtualHosts = {
|
|
||||||
"${cfg.server_tld}" = {
|
|
||||||
locations."= /.well-known/matrix/server".extraConfig = ''
|
|
||||||
add_header Content-Type application/json;
|
|
||||||
return 200 '${builtins.toJSON { "m.server" = "${cfg.app_domain}:443"; }}';
|
|
||||||
'';
|
|
||||||
locations."= /.well-known/matrix/client".extraConfig = ''
|
|
||||||
add_header Content-Type application/json;
|
|
||||||
add_header Access-Control-Allow-Origin *;
|
|
||||||
return 200 '${
|
|
||||||
builtins.toJSON {
|
|
||||||
"m.homeserver" = {
|
|
||||||
"base_url" = "https://${cfg.app_domain}";
|
|
||||||
};
|
|
||||||
"m.identity_server" = {
|
|
||||||
"base_url" = "https://vector.im";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}';
|
|
||||||
'';
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
};
|
|
||||||
"${cfg.app_domain}" = {
|
|
||||||
forceSSL = true;
|
|
||||||
enableACME = true;
|
|
||||||
locations."/".root = element-web;
|
|
||||||
locations."/_matrix".proxyPass = "http://localhost:8008"; # TODO: We should make the port configurable
|
|
||||||
locations."/_synapse".proxyPass = "http://localhost:8008";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A desktop streaming client optimized for remote gaming and synchronized movie viewing."
|
|
||||||
---
|
|
||||||
|
|
||||||
**Warning**: This module was written with our VM integration in mind likely won't work outside of this context. They will be generalized in future.
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
{ pkgs, config, ... }:
|
|
||||||
let
|
|
||||||
ms-accept = pkgs.callPackage ../../pkgs/moonlight-sunshine-accept { };
|
|
||||||
defaultPort = 48011;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
warnings = [
|
|
||||||
"The clan.moonlight module is deprecated and will be removed on 2025-07-15. Please migrate to user-maintained configuration."
|
|
||||||
];
|
|
||||||
|
|
||||||
hardware.opengl.enable = true;
|
|
||||||
environment.systemPackages = [
|
|
||||||
pkgs.moonlight-qt
|
|
||||||
ms-accept
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d '/var/lib/moonlight' 0770 'user' 'users' - -"
|
|
||||||
"C '/var/lib/moonlight/moonlight.cert' 0644 'user' 'users' - ${
|
|
||||||
config.clan.core.vars.generators.moonlight.files."moonlight.cert".path or ""
|
|
||||||
}"
|
|
||||||
"C '/var/lib/moonlight/moonlight.key' 0644 'user' 'users' - ${
|
|
||||||
config.clan.core.vars.generators.moonlight.files."moonlight.key".path or ""
|
|
||||||
}"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.user.services.init-moonlight = {
|
|
||||||
enable = false;
|
|
||||||
description = "Initializes moonlight";
|
|
||||||
wantedBy = [ "graphical-session.target" ];
|
|
||||||
script = ''
|
|
||||||
${ms-accept}/bin/moonlight-sunshine-accept moonlight init-config --key /var/lib/moonlight/moonlight.key --cert /var/lib/moonlight/moonlight.cert
|
|
||||||
'';
|
|
||||||
serviceConfig = {
|
|
||||||
user = "user";
|
|
||||||
Type = "oneshot";
|
|
||||||
WorkingDirectory = "/home/user/";
|
|
||||||
RunTimeDirectory = "moonlight";
|
|
||||||
TimeoutSec = "infinity";
|
|
||||||
Restart = "on-failure";
|
|
||||||
RemainAfterExit = true;
|
|
||||||
ReadOnlyPaths = [
|
|
||||||
"/var/lib/moonlight/moonlight.key"
|
|
||||||
"/var/lib/moonlight/moonlight.cert"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.user.services.moonlight-join = {
|
|
||||||
description = "Join sunshine hosts";
|
|
||||||
script = ''${ms-accept}/bin/moonlight-sunshine-accept moonlight join --port ${builtins.toString defaultPort} --cert '${
|
|
||||||
config.clan.core.vars.generators.moonlight.files."moonlight.cert".value or ""
|
|
||||||
}' --host fd2e:25da:6035:c98f:cd99:93e0:b9b8:9ca1'';
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
TimeoutSec = "infinity";
|
|
||||||
Restart = "on-failure";
|
|
||||||
ReadOnlyPaths = [
|
|
||||||
"/var/lib/moonlight/moonlight.key"
|
|
||||||
"/var/lib/moonlight/moonlight.cert"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
systemd.user.timers.moonlight-join = {
|
|
||||||
description = "Join sunshine hosts";
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnUnitActiveSec = "5min";
|
|
||||||
OnBootSec = "0min";
|
|
||||||
Persistent = true;
|
|
||||||
Unit = "moonlight-join.service";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.moonlight = {
|
|
||||||
migrateFact = "moonlight";
|
|
||||||
files."moonlight.key" = { };
|
|
||||||
files."moonlight.cert" = { };
|
|
||||||
files."moonlight.cert".secret = false;
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
ms-accept
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
moonlight-sunshine-accept moonlight init
|
|
||||||
mv credentials/cakey.pem "$out"/moonlight.key
|
|
||||||
cp credentials/cacert.pem "$out"/moonlight.cert
|
|
||||||
mv credentials/cacert.pem "$out"/moonlight.cert
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Open Source, Low Latency, High Quality Voice Chat."
|
|
||||||
categories = ["Audio", "Social"]
|
|
||||||
features = [ "inventory" ]
|
|
||||||
|
|
||||||
[constraints]
|
|
||||||
roles.server.min = 1
|
|
||||||
---
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/server.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import sqlite3
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_config(path: Path, db_path: Path) -> None:
|
|
||||||
# Default JSON structure if the file doesn't exist
|
|
||||||
default_json = {
|
|
||||||
"misc": {
|
|
||||||
"audio_wizard_has_been_shown": True,
|
|
||||||
"database_location": str(db_path),
|
|
||||||
"viewed_server_ping_consent_message": True,
|
|
||||||
},
|
|
||||||
"settings_version": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the file exists
|
|
||||||
if path.exists():
|
|
||||||
data = json.loads(path.read_text())
|
|
||||||
else:
|
|
||||||
data = default_json
|
|
||||||
# Create the file with default JSON structure
|
|
||||||
with path.open("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 path.open("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()
|
|
||||||
|
|
||||||
|
|
||||||
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", type=Path)
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
dir = config.clan.core.settings.directory;
|
|
||||||
# TODO: this should actually use the inventory to figure out which machines to use.
|
|
||||||
machineDir = dir + "/vars/per-machine";
|
|
||||||
machinesFileSet = builtins.readDir machineDir;
|
|
||||||
machines = lib.mapAttrsToList (name: _: name) machinesFileSet;
|
|
||||||
machineJson = builtins.toJSON machines;
|
|
||||||
certificateMachinePath = machines: machineDir + "/${machines}" + "/mumble/mumble-cert/value";
|
|
||||||
certificatesUnchecked = builtins.map (
|
|
||||||
machine:
|
|
||||||
let
|
|
||||||
fullPath = certificateMachinePath machine;
|
|
||||||
in
|
|
||||||
if builtins.pathExists fullPath then machine else null
|
|
||||||
) machines;
|
|
||||||
certificate = lib.filter (machine: machine != null) certificatesUnchecked;
|
|
||||||
machineCert = builtins.map (
|
|
||||||
machine: (lib.nameValuePair machine (builtins.readFile (certificateMachinePath machine)))
|
|
||||||
) certificate;
|
|
||||||
machineCertJson = builtins.toJSON machineCert;
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.clan.services.mumble = {
|
|
||||||
user = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
example = "alice";
|
|
||||||
description = "The user mumble should be set up for.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.mumble module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
services.murmur = {
|
|
||||||
enable = true;
|
|
||||||
logDays = -1;
|
|
||||||
registerName = config.clan.core.settings.machine.name;
|
|
||||||
openFirewall = true;
|
|
||||||
bonjour = true;
|
|
||||||
sslKey = "/var/lib/murmur/sslKey";
|
|
||||||
sslCert = "/var/lib/murmur/sslCert";
|
|
||||||
};
|
|
||||||
|
|
||||||
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' - -"
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd.tmpfiles.settings."murmur" = {
|
|
||||||
"/var/lib/murmur/sslKey" = {
|
|
||||||
C.argument = config.clan.core.vars.generators.mumble.files.mumble-key.path;
|
|
||||||
Z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
"/var/lib/murmur/sslCert" = {
|
|
||||||
C.argument = config.clan.core.vars.generators.mumble.files.mumble-cert.path;
|
|
||||||
Z = {
|
|
||||||
mode = "0400";
|
|
||||||
user = "murmur";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
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}
|
|
||||||
${populate-channels} --machines '${machineJson}' --username ${config.clan.core.settings.machine.name} --db-location ${mumbleDatabasePath}
|
|
||||||
${populate-channels} --servers '${machineCertJson}' --username ${config.clan.core.settings.machine.name} --db-location ${mumbleDatabasePath} --cert True
|
|
||||||
${pkgs.mumble}/bin/mumble --config ${mumbleCfgPath} "$@"
|
|
||||||
popd
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
[ mumble ];
|
|
||||||
|
|
||||||
clan.core.vars.generators.mumble = {
|
|
||||||
migrateFact = "mumble";
|
|
||||||
files.mumble-key = { };
|
|
||||||
files.mumble-cert.secret = false;
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssl
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
openssl genrsa -out "$out/mumble-key" 2048
|
|
||||||
|
|
||||||
cat > mumble-cert.conf <<EOF
|
|
||||||
[ req ]
|
|
||||||
default_bits = 2048
|
|
||||||
distinguished_name = req_distinguished_name
|
|
||||||
req_extensions = req_ext
|
|
||||||
prompt = no
|
|
||||||
[ req_distinguished_name ]
|
|
||||||
C = "US"
|
|
||||||
ST = "California"
|
|
||||||
L = "San Francisco"
|
|
||||||
O = "Clan"
|
|
||||||
OU = "Clan"
|
|
||||||
CN = "${config.clan.core.settings.machine.name}"
|
|
||||||
[ req_ext ]
|
|
||||||
subjectAltName = @alt_names
|
|
||||||
[ alt_names ]
|
|
||||||
DNS.1 = "${config.clan.core.settings.machine.name}"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
openssl req -new -x509 -config mumble-cert.conf -key "$out/mumble-key" -out "$out/mumble-cert" < /dev/null
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
description = "End-2-end encrypted IPv6 overlay network"
|
|
||||||
categories = ["System", "Network"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
Mycelium is an IPv6 overlay network written in Rust. Each node that joins the overlay network will receive an overlay network IP in the 400::/7 range.
|
|
||||||
|
|
||||||
Features:
|
|
||||||
- Mycelium, is locality aware, it will look for the shortest path between nodes
|
|
||||||
- All traffic between the nodes is end-2-end encrypted
|
|
||||||
- Traffic can be routed over nodes of friends, location aware
|
|
||||||
- If a physical link goes down Mycelium will automatically reroute your traffic
|
|
||||||
- The IP address is IPV6 and linked to private key
|
|
||||||
- A simple reliable messagebus is implemented on top of Mycelium
|
|
||||||
- Mycelium has multiple ways how to communicate quic, tcp, ... and we are working on holepunching for Quick which means P2P traffic without middlemen for NATted networks e.g. most homes
|
|
||||||
- Scalability is very important for us, we tried many overlay networks before and got stuck on all of them, we are trying to design a network which scales to a planetary level
|
|
||||||
- You can run mycelium without TUN and only use it as reliable message bus.
|
|
||||||
|
|
||||||
|
|
||||||
An example configuration might look like this in the inventory:
|
|
||||||
```nix
|
|
||||||
mycelium.default = {
|
|
||||||
roles.peer.machines = [
|
|
||||||
"berlin"
|
|
||||||
"munich"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This will add the machines named `berlin` and `munich` to the `mycelium` vpn.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
options = {
|
|
||||||
clan.mycelium.openFirewall = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Open the firewall for mycelium";
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.mycelium.addHostedPublicNodes = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Add hosted Public nodes";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config.warnings = [
|
|
||||||
"The clan.mycelium module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
config.services.mycelium = {
|
|
||||||
enable = true;
|
|
||||||
addHostedPublicNodes = lib.mkDefault config.clan.mycelium.addHostedPublicNodes;
|
|
||||||
openFirewall = lib.mkDefault config.clan.mycelium.openFirewall;
|
|
||||||
keyFile = config.clan.core.vars.generators.mycelium.files.key.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
config.clan.core.vars.generators.mycelium = {
|
|
||||||
files."key" = { };
|
|
||||||
files."ip".secret = false;
|
|
||||||
files."pubkey".secret = false;
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.mycelium
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.jq
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
timeout 5 mycelium --key-file "$out"/key || :
|
|
||||||
mycelium inspect --key-file "$out"/key --json | jq -r .publicKey > "$out"/pubkey
|
|
||||||
mycelium inspect --key-file "$out"/key --json | jq -r .address > "$out"/ip
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Good defaults for the nginx webserver"
|
|
||||||
---
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"nginx"
|
|
||||||
"enable"
|
|
||||||
] "Importing the module will already enable the service.")
|
|
||||||
|
|
||||||
];
|
|
||||||
options = {
|
|
||||||
clan.nginx.acme.email = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = ''
|
|
||||||
Email address for account creation and correspondence from the CA.
|
|
||||||
It is recommended to use the same email for all certs to avoid account
|
|
||||||
creation limits.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
security.acme.acceptTerms = true;
|
|
||||||
security.acme.defaults.email = config.clan.nginx.acme.email;
|
|
||||||
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
443
|
|
||||||
80
|
|
||||||
];
|
|
||||||
|
|
||||||
services.nginx = {
|
|
||||||
enable = true;
|
|
||||||
|
|
||||||
statusPage = lib.mkDefault true;
|
|
||||||
recommendedBrotliSettings = lib.mkDefault true;
|
|
||||||
recommendedGzipSettings = lib.mkDefault true;
|
|
||||||
recommendedOptimisation = lib.mkDefault true;
|
|
||||||
recommendedProxySettings = lib.mkDefault true;
|
|
||||||
recommendedTlsSettings = lib.mkDefault true;
|
|
||||||
|
|
||||||
# Nginx sends all the access logs to /var/log/nginx/access.log by default.
|
|
||||||
# instead of going to the journal!
|
|
||||||
commonHttpConfig = "access_log syslog:server=unix:/dev/log;";
|
|
||||||
|
|
||||||
resolver.addresses =
|
|
||||||
let
|
|
||||||
isIPv6 = addr: builtins.match ".*:.*:.*" addr != null;
|
|
||||||
escapeIPv6 = addr: if isIPv6 addr then "[${addr}]" else addr;
|
|
||||||
cloudflare = [
|
|
||||||
"1.1.1.1"
|
|
||||||
"2606:4700:4700::1111"
|
|
||||||
];
|
|
||||||
resolvers =
|
|
||||||
if config.networking.nameservers == [ ] then cloudflare else config.networking.nameservers;
|
|
||||||
in
|
|
||||||
map escapeIPv6 resolvers;
|
|
||||||
|
|
||||||
sslDhparam = config.security.dhparams.params.nginx.path;
|
|
||||||
};
|
|
||||||
|
|
||||||
security.dhparams = {
|
|
||||||
enable = true;
|
|
||||||
params.nginx = { };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Define package sets from nixpkgs and install them on one or more machines"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
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 = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.packages module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
environment.systemPackages = map (
|
|
||||||
pName: lib.getAttrFromPath (lib.splitString "." pName) pkgs
|
|
||||||
) config.clan.packages.packages;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
description = "A free and open-source relational database management system (RDBMS) emphasizing extensibility and SQL compliance."
|
|
||||||
---
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(lib.mkRemovedOptionModule [
|
|
||||||
"clan"
|
|
||||||
"postgresql"
|
|
||||||
] "The postgresql module has been migrated to a clan core option. Use clan.core.postgresql instead")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Automatically generates and configures a password for the root user."
|
|
||||||
categories = ["System"]
|
|
||||||
features = ["inventory", "deprecated"]
|
|
||||||
---
|
|
||||||
|
|
||||||
This module is deprecated and will be removed in a future release. It's functionality has been replaced by the user-password service.
|
|
||||||
|
|
||||||
After the system was installed/deployed the following command can be used to display the root-password:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
clan vars get [machine_name] root-password/root-password
|
|
||||||
```
|
|
||||||
|
|
||||||
See also: [Vars](../../concepts/generators.md)
|
|
||||||
|
|
||||||
To regenerate the password run:
|
|
||||||
```
|
|
||||||
clan vars generate --regenerate [machine_name] --generator root-password
|
|
||||||
```
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
{
|
|
||||||
_class,
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.root-password module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
users.mutableUsers = false;
|
|
||||||
users.users.root.hashedPasswordFile =
|
|
||||||
config.clan.core.vars.generators.root-password.files.password-hash.path;
|
|
||||||
|
|
||||||
clan.core.vars.generators.root-password = {
|
|
||||||
files.password-hash = {
|
|
||||||
neededFor = "users";
|
|
||||||
}
|
|
||||||
// (lib.optionalAttrs (_class == "nixos") {
|
|
||||||
restartUnits = lib.optional (config.services.userborn.enable) "userborn.service";
|
|
||||||
});
|
|
||||||
files.password = {
|
|
||||||
deploy = false;
|
|
||||||
};
|
|
||||||
migrateFact = "root-password";
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.mkpasswd
|
|
||||||
pkgs.xkcdpass
|
|
||||||
];
|
|
||||||
prompts.password.type = "hidden";
|
|
||||||
prompts.password.persist = true;
|
|
||||||
prompts.password.description = "You can autogenerate a password, if you leave this prompt blank.";
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
prompt_value="$(cat "$prompts"/password)"
|
|
||||||
if [[ -n "''${prompt_value-}" ]]; then
|
|
||||||
echo "$prompt_value" | tr -d "\n" > "$out"/password
|
|
||||||
else
|
|
||||||
xkcdpass --numwords 4 --delimiter - --count 1 | tr -d "\n" > "$out"/password
|
|
||||||
fi
|
|
||||||
mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Configures partitioning of the main disk"
|
|
||||||
categories = ["System"]
|
|
||||||
features = [ "inventory" ]
|
|
||||||
---
|
|
||||||
# Primary Disk Layout
|
|
||||||
|
|
||||||
A module for the "disk-layout" category MUST be chosen.
|
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
imports = [ ./roles/default.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
{ 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";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
warnings = [
|
|
||||||
"clanModules.single-disk is deprecated. Please copy the disko config from the module into your machine 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";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
root = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
description = "Enables secure remote access to the machine over ssh."
|
|
||||||
categories = ["System", "Network"]
|
|
||||||
features = [ "inventory", "deprecated" ]
|
|
||||||
---
|
|
||||||
|
|
||||||
This module will setup the opensshd service.
|
|
||||||
It will generate a host key for each machine
|
|
||||||
|
|
||||||
|
|
||||||
## Roles
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Dont import this file
|
|
||||||
# It is only here for backwards compatibility.
|
|
||||||
# Dont author new modules with this file.
|
|
||||||
{
|
|
||||||
imports = [ ./roles/server.nix ];
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
../shared.nix
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
pkgs,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
stringSet = list: builtins.attrNames (builtins.groupBy lib.id list);
|
|
||||||
|
|
||||||
domains = stringSet config.clan.sshd.certificate.searchDomains;
|
|
||||||
|
|
||||||
cfg = config.clan.sshd;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ../shared.nix ];
|
|
||||||
options = {
|
|
||||||
clan.sshd.hostKeys.rsa.enable = lib.mkEnableOption "Generate RSA host key";
|
|
||||||
};
|
|
||||||
config = {
|
|
||||||
|
|
||||||
warnings = [
|
|
||||||
"The clan.sshd module is deprecated and will be removed on 2025-07-15.
|
|
||||||
Please migrate to user-maintained configuration or the new equivalent clan services
|
|
||||||
(https://docs.clan.lol/reference/clanServices)."
|
|
||||||
];
|
|
||||||
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
settings.PasswordAuthentication = false;
|
|
||||||
|
|
||||||
settings.HostCertificate = lib.mkIf (
|
|
||||||
cfg.certificate.searchDomains != [ ]
|
|
||||||
) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path;
|
|
||||||
|
|
||||||
hostKeys = [
|
|
||||||
{
|
|
||||||
path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path;
|
|
||||||
type = "ed25519";
|
|
||||||
}
|
|
||||||
]
|
|
||||||
++ lib.optional cfg.hostKeys.rsa.enable {
|
|
||||||
path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path;
|
|
||||||
type = "rsa";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.openssh = {
|
|
||||||
files."ssh.id_ed25519" = { };
|
|
||||||
files."ssh.id_ed25519.pub".secret = false;
|
|
||||||
migrateFact = "openssh";
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssh
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
programs.ssh.knownHosts.clan-sshd-self-ed25519 = {
|
|
||||||
hostNames = [
|
|
||||||
"localhost"
|
|
||||||
config.networking.hostName
|
|
||||||
]
|
|
||||||
++ (lib.optional (config.networking.domain != null) config.networking.fqdn);
|
|
||||||
publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value;
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.openssh-rsa = lib.mkIf config.clan.sshd.hostKeys.rsa.enable {
|
|
||||||
files."ssh.id_rsa" = { };
|
|
||||||
files."ssh.id_rsa.pub".secret = false;
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.coreutils
|
|
||||||
pkgs.openssh
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
clan.core.vars.generators.openssh-cert = lib.mkIf (cfg.certificate.searchDomains != [ ]) {
|
|
||||||
files."ssh.id_ed25519-cert.pub".secret = false;
|
|
||||||
dependencies = [
|
|
||||||
"openssh"
|
|
||||||
"openssh-ca"
|
|
||||||
];
|
|
||||||
validation = {
|
|
||||||
name = config.clan.core.settings.machine.name;
|
|
||||||
domains = lib.genAttrs config.clan.sshd.certificate.searchDomains lib.id;
|
|
||||||
};
|
|
||||||
runtimeInputs = [
|
|
||||||
pkgs.openssh
|
|
||||||
pkgs.jq
|
|
||||||
];
|
|
||||||
script = ''
|
|
||||||
ssh-keygen \
|
|
||||||
-s $in/openssh-ca/id_ed25519 \
|
|
||||||
-I ${config.clan.core.settings.machine.name} \
|
|
||||||
-h \
|
|
||||||
-n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \
|
|
||||||
$in/openssh/ssh.id_ed25519.pub
|
|
||||||
mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user