Compare commits
1491 Commits
wip-swaync
...
wip-nix-fa
Author | SHA1 | Date | |
---|---|---|---|
084541da4c | |||
f7a82a845c | |||
2bdef04552 | |||
2822a6f0dd | |||
ab6e362f0c | |||
3816393e06 | |||
4c6c470c86 | |||
409a4db232 | |||
c73684557e | |||
44067f6570 | |||
466e7a9ecd | |||
6b2d189771 | |||
6ef729bbaf | |||
8f424dcd5a | |||
7fb7f72bc0 | |||
67536e3c1f | |||
715de37954 | |||
c8035abddf | |||
ef1cdac6b4 | |||
e37a7d85b3 | |||
36f6c72183 | |||
20a1aeb5b3 | |||
9ba0833d5f | |||
15f353f883 | |||
27af0002c8 | |||
9265252e04 | |||
11a53c402d | |||
464f439f4a | |||
a4dbf18d7a | |||
1579e089e9 | |||
4ce0c23c19 | |||
9710d55c6a | |||
4b014af4b1 | |||
4379addf9e | |||
5c7eceeb55 | |||
50aa16df81 | |||
b1e943c9bb | |||
be5fae369f | |||
40e22533fb | |||
03c5f82dbf | |||
e90bbfe551 | |||
92033c8414 | |||
16f0424631 | |||
6fd1ce1f61 | |||
a7c325c8e1 | |||
fc7814e6cd | |||
245e6c93cd | |||
ec073592ed | |||
617525a317 | |||
1098d121b4 | |||
821c631b1d | |||
96347ad7ac | |||
4f933cc0fa | |||
03615ce244 | |||
7d613d90d8 | |||
afd52014d1 | |||
dd6e1c5e38 | |||
d0d7994c2f | |||
b5da7a86fa | |||
f2e1bb6b86 | |||
fe0f6988bd | |||
c402a265cd | |||
d5643a6a5d | |||
e757e35065 | |||
953dd98b0f | |||
c9c1181242 | |||
f9888fe8d6 | |||
036145e6ba | |||
5b647a1a90 | |||
7c486492c8 | |||
890b41f563 | |||
ca36fe1b96 | |||
d2df668c9e | |||
b7921ac41b | |||
c304367e21 | |||
2ad33a49df | |||
0b4efd2ab2 | |||
0745e9fc06 | |||
e0267b5669 | |||
b3c7aac8c5 | |||
c788596c45 | |||
f807d7c0a2 | |||
6ab5dd8a8f | |||
52b8cd0209 | |||
6865331b48 | |||
dd00a2fe6e | |||
4ee02151f4 | |||
00bf2f79cc | |||
04a6055d06 | |||
15a7793f0d | |||
f714bd8281 | |||
73b2594d9b | |||
a55dc5332d | |||
86108518da | |||
0f1ad0f3c9 | |||
bcd7a6f646 | |||
92c2eb8383 | |||
879d01ac2e | |||
0448df51e3 | |||
8e3eed7d51 | |||
88a70b41f1 | |||
6f59254a22 | |||
4023960dc0 | |||
fff9f9d49a | |||
eecb98e2ee | |||
5838603953 | |||
c6ebcfe66e | |||
d7402ae170 | |||
bd7ca20361 | |||
f5ef1e96ca | |||
6267e7f966 | |||
120a41b169 | |||
aa0991bd6c | |||
af2f97d61e | |||
5b8f13d9cc | |||
62b39bf01e | |||
0d8307e877 | |||
9b1a2ae9bb | |||
b8b805765b | |||
84eae20765 | |||
4a10c5f729 | |||
c2696c1cd9 | |||
c23e4dc9c7 | |||
ea6f45555c | |||
687db545b4 | |||
24d1d13d0a | |||
2ada436634 | |||
e5ad0862fb | |||
057b9e3fed | |||
1bcfccf7e3 | |||
170eeeacc4 | |||
a402822084 | |||
80ecdcc4f9 | |||
0864790bb7 | |||
478747a96e | |||
771dc2e1ce | |||
4a316d4b91 | |||
0ff8154e96 | |||
af03b3f6e8 | |||
5819f07181 | |||
122f3fa5cc | |||
ece612ea70 | |||
f27f994090 | |||
473999c001 | |||
d1de9efde1 | |||
50c3f04714 | |||
49bad8f186 | |||
fd9f500e97 | |||
386651044e | |||
55a6c828f2 | |||
7ecebd7521 | |||
7b299176e3 | |||
4da9cb5ac8 | |||
f068da709f | |||
5b21257e4f | |||
d77a12ce7b | |||
153d2a1047 | |||
2a528a5d8e | |||
b8f090be93 | |||
b16902bec1 | |||
c919372324 | |||
60371585e4 | |||
20cb850fb5 | |||
c6470918de | |||
c0f374bd80 | |||
5a0760a571 | |||
757ab79724 | |||
81148b7b42 | |||
429d0c53e7 | |||
6cf1bc5a28 | |||
768b340c93 | |||
d9901aa161 | |||
be2098c18a | |||
ee7d99289a | |||
bb569b1668 | |||
34524ea3e4 | |||
71025329e7 | |||
ca4d1e3b9d | |||
284b698015 | |||
bc50daf685 | |||
47dcfb9cba | |||
2bd99f6e51 | |||
8beac8df2f | |||
58db553c84 | |||
2ea3776d84 | |||
d596d005ca | |||
e92db138ef | |||
5fed127c23 | |||
db49f0461c | |||
73bb7827c0 | |||
a624571b22 | |||
53cbe5c8da | |||
46de7b7e0d | |||
d7be5da483 | |||
902e351085 | |||
9e8e1d82a6 | |||
a05184f956 | |||
36ad2d5421 | |||
b0f62830a5 | |||
f970679266 | |||
c7f4661c1c | |||
e8306831c5 | |||
41b1a013d7 | |||
f785ccd351 | |||
48744dcaaa | |||
9373864b60 | |||
c16c9dfe0b | |||
292a411fb3 | |||
2d17826731 | |||
34dedcff57 | |||
de297f22be | |||
4b47b76461 | |||
3effd59c9b | |||
a3d0691d99 | |||
44647e0d36 | |||
da1053d635 | |||
273b1b84e3 | |||
0b6b98bba6 | |||
8886177c23 | |||
7e343bfc05 | |||
f72bdb6f3a | |||
5666a05ef0 | |||
05daf738fc | |||
35b4cc779f | |||
c7d111a318 | |||
7e5eb6324d | |||
95cb5624ca | |||
55c305812d | |||
600f6eb56c | |||
fd6f8493a7 | |||
f10f1ee7b1 | |||
67395bdcd3 | |||
90ceeede74 | |||
32a704b1b8 | |||
a591be98d4 | |||
82e028e37d | |||
a531676d0d | |||
7f7543ee78 | |||
8d0e3e0db3 | |||
bf352d184c | |||
81a6600f54 | |||
9fde167e71 | |||
4e180e11df | |||
902166e45a | |||
797bc4e188 | |||
536f0aedc3 | |||
b855df902f | |||
80ce49c579 | |||
408059420d | |||
a3102c9395 | |||
6760fcf1f4 | |||
a90898491e | |||
059940d8e7 | |||
98aafead94 | |||
cef2591425 | |||
f8663cd827 | |||
af1ee1734d | |||
5375cab716 | |||
162b3f5674 | |||
a729f91d21 | |||
a273b559e2 | |||
785b375671 | |||
24cba0c856 | |||
df1db5d01c | |||
6749b64bca | |||
d3e4bdfcd5 | |||
799cd4373f | |||
2efa6d1e27 | |||
a1470956a5 | |||
556c20bc04 | |||
cf5f58dda6 | |||
fd30f7abbc | |||
6f8c299c69 | |||
bbf7aac062 | |||
7d1fd2f30a | |||
472987f164 | |||
784c2145f3 | |||
4ced02b0b2 | |||
0000afb315 | |||
31fa21bd20 | |||
9510817604 | |||
4a84de3ee4 | |||
ab42a4cc5a | |||
f6537b083a | |||
5ff1d014b8 | |||
fa41e6c402 | |||
1b4306e649 | |||
af8a8358bd | |||
464c6c56c5 | |||
8e314e8b73 | |||
198029f95f | |||
1d646459ab | |||
8f3bab3636 | |||
a909a93c29 | |||
6aaa724abf | |||
a1c721d5b4 | |||
4002a57e03 | |||
74a0b0d125 | |||
cd3b4dde7b | |||
a9d384688a | |||
fffd6f4204 | |||
324485d105 | |||
7cb8b144b2 | |||
c2bb97e7e6 | |||
3cbdc03369 | |||
5c7fa591a0 | |||
18c54e8b04 | |||
1416856fb6 | |||
2a5bc6f612 | |||
c56a6a8c24 | |||
f5a4bdedaf | |||
114a45f347 | |||
d53344d527 | |||
561447de70 | |||
b6f918c32f | |||
9cc12fab5d | |||
5cda3b2805 | |||
4afd56ff4c | |||
029ba43bd6 | |||
00e4078300 | |||
94b4f78e39 | |||
3fd89ec91b | |||
4085828575 | |||
1a972927b6 | |||
5f3ec42f57 | |||
28aaeb051f | |||
9d252d095e | |||
4e5e4219ec | |||
824dd7c1f5 | |||
b840a0d61c | |||
36bcecfd68 | |||
c3a5fb9394 | |||
30507c3564 | |||
2b66ffc58a | |||
48d96c1f36 | |||
cdf61755a3 | |||
dd1dc69530 | |||
481f54ea2f | |||
511752fab5 | |||
40ed7cff1b | |||
5e7f914354 | |||
8c9c6ec979 | |||
0dec8b6d5b | |||
7eaffc9fa0 | |||
b7c1a6331d | |||
d6868d58e6 | |||
1edb1fc8b6 | |||
52d768a162 | |||
7a685d8de9 | |||
838c6d7dc8 | |||
8d20dcadd1 | |||
9d706df5b5 | |||
06f1f1e9ea | |||
2fbbe7fd78 | |||
24d23f7903 | |||
0394aa65e9 | |||
5090c4e88c | |||
081114da65 | |||
c943442c94 | |||
02b7586ffa | |||
02dd629616 | |||
25dcb7f89a | |||
88f1d63b6e | |||
d36e269edd | |||
40af8b95fd | |||
582a003739 | |||
df60be8c61 | |||
e8b4c36442 | |||
2f699737f5 | |||
4a3d24be3f | |||
10feb319fe | |||
fde1e5d872 | |||
b2fcf6fdfd | |||
dcc2eb265d | |||
5f1036118f | |||
8ac4869f10 | |||
226425bbef | |||
518c3afd07 | |||
90dee85664 | |||
26fc283fd9 | |||
d0430ce1e9 | |||
368a52b91e | |||
d90dacee1f | |||
a6e2b3bc5c | |||
8863a3c674 | |||
fa8d6dbb9f | |||
e5e79a6b60 | |||
95f7eeeb5c | |||
29d638c68b | |||
7d22a5466f | |||
b747742e23 | |||
5907d9fa42 | |||
67fe8d4666 | |||
22ca253ae0 | |||
c9e02bfd8a | |||
03b58b3cab | |||
ae01c17c05 | |||
677e6e679b | |||
3eb47a9a8d | |||
f11e443678 | |||
9faf1bb52c | |||
e599724811 | |||
c0b03950dc | |||
8f8ec090c4 | |||
e174eaeff0 | |||
8b32f2f231 | |||
f12b7afa1e | |||
080bd856ec | |||
548a95a7e1 | |||
2d7c5b9fa5 | |||
e696cb96b6 | |||
83cb29aeeb | |||
34b148f6cc | |||
44c2f8bcc0 | |||
9c18aa2765 | |||
4458a74e4c | |||
1a18ed533b | |||
18eec98cae | |||
82c386a6a4 | |||
634dc318cd | |||
6eaaeeb91a | |||
94be4a7551 | |||
b4a20da78a | |||
bb68506839 | |||
77e2af0ed9 | |||
126f3e4922 | |||
73afceb8c6 | |||
371af5939e | |||
27fd81ad80 | |||
d82b4b0f62 | |||
7b28023e08 | |||
2b9db897a1 | |||
6124cb9b36 | |||
b0394d877d | |||
14d8230821 | |||
e94e338040 | |||
354ce378f6 | |||
a90b5b53db | |||
eee3e138ff | |||
f61cd17e99 | |||
3e0b0a0f02 | |||
2ee34e9af3 | |||
f9a998eb92 | |||
7c05d221d6 | |||
93012664e5 | |||
c424f7ac3b | |||
088b6f1b9a | |||
96575acf3a | |||
1e05119adc | |||
e81df0ac86 | |||
b19492ba23 | |||
8b26fa1303 | |||
c0883dc777 | |||
6b3a71aadf | |||
8d0d20757e | |||
66ca822ac1 | |||
db7a414030 | |||
87050a0500 | |||
bf53e3628a | |||
d35f938806 | |||
d719eb0f11 | |||
0861edd7f9 | |||
b6bf8720c9 | |||
0fbc10fce3 | |||
772f1070e7 | |||
50c6e406bc | |||
41020b2c0d | |||
590a239f7d | |||
bcbc57f5ef | |||
0d3adcdc5c | |||
d19907a38d | |||
9ac0e0e4fc | |||
c9af5bf9b4 | |||
bc85169e3d | |||
7b9b3344a0 | |||
f6ca6210f9 | |||
19cfc86d1a | |||
227d159c66 | |||
a6becb8c42 | |||
2a5398beb3 | |||
0f12ed68f7 | |||
0c050d1953 | |||
2fc1fe7510 | |||
8d705af7a0 | |||
e91ec2c35e | |||
5fbf66fb15 | |||
97d50629e9 | |||
5f8699fcef | |||
7ce957c3af | |||
d7612d5034 | |||
5ff7bf0c69 | |||
2495200b67 | |||
4c499629f5 | |||
7b9f54dd54 | |||
bda932c3df | |||
3f96f4af82 | |||
1c4e2f97fe | |||
594a729968 | |||
5c8bb55cec | |||
6eb2a3d67f | |||
ddc41bc9d8 | |||
7d833ebf76 | |||
bfc0eadfaa | |||
ff1cbcc16b | |||
fd81e35c31 | |||
9a8d8a20bd | |||
cd1d22e7b9 | |||
2c0e93826d | |||
cab346f3ad | |||
568a72f6a4 | |||
a2decaff9c | |||
23411ed973 | |||
8ef9f7a485 | |||
12846732b9 | |||
e84079e84c | |||
45ffd9246d | |||
ed3935318d | |||
8052f62796 | |||
413903d03c | |||
6d1eae2200 | |||
4d51c34ad2 | |||
bc50a8c489 | |||
ee8e33b795 | |||
8afb6406a1 | |||
7ac1ee66ad | |||
8a47eb92ed | |||
b87934d5f8 | |||
293eab8225 | |||
abdbb83e10 | |||
4a96fa233a | |||
4bd73ddca3 | |||
dc74bca06a | |||
42523b75a8 | |||
79736a4a0a | |||
111946eb1d | |||
09f3bfc944 | |||
b8fc75ebd6 | |||
8de015f098 | |||
6da85f6d8f | |||
2dc6da476b | |||
453f40d0a8 | |||
14b20fd9c2 | |||
2df1b20f02 | |||
56e7e9a7cc | |||
2f9fad503c | |||
3439ca34b8 | |||
24e6e6cacc | |||
0ee9f2026c | |||
5e3c2636db | |||
cd0a046776 | |||
27edee0bbf | |||
56734fe5da | |||
832a572d56 | |||
3c96f6d418 | |||
86b23e8183 | |||
2bb9115f35 | |||
065d045640 | |||
d3eaa69261 | |||
6151eee8d5 | |||
483a1d1780 | |||
567c7993b6 | |||
f6eeab5650 | |||
2824671bde | |||
efcaef2c35 | |||
25707eb79e | |||
18679cd8c3 | |||
09923b60ea | |||
3100189172 | |||
715ac42f13 | |||
a9810e7343 | |||
4f352c5725 | |||
17f35a3619 | |||
89d4f3eec3 | |||
44419d71a5 | |||
02e597a862 | |||
00f995aec9 | |||
368eb2c29b | |||
5f793523d1 | |||
33bee7ac2e | |||
84af8aca3c | |||
a0f00313a7 | |||
6603115192 | |||
ac968e1589 | |||
2d4fc4f274 | |||
1d72e13a98 | |||
d9667653e7 | |||
8c6bf07102 | |||
634520a1e9 | |||
13be5a1731 | |||
30288cd67f | |||
87e2509af4 | |||
8736ca478b | |||
cb3960fb21 | |||
6e24a1ff28 | |||
91eae95b32 | |||
f5c88853ee | |||
0009e5ca4c | |||
0403d5c03e | |||
db6ba61429 | |||
881d2f79ed | |||
47abdfb831 | |||
3831c6f087 | |||
d3f7a036ce | |||
0454abacd9 | |||
4f8d476ebf | |||
1cb2c5225f | |||
7af970f38c | |||
6f86e61a00 | |||
3ea3776281 | |||
a7eb8dd6fa | |||
c1a1f51ca2 | |||
32824cfade | |||
51fc61b211 | |||
7b9795ea3d | |||
5f3e481fe4 | |||
86219d7006 | |||
381da74e6c | |||
24c70c3683 | |||
bfec531fa2 | |||
de11edffa5 | |||
294f167df0 | |||
e536e3c718 | |||
17d14dbac2 | |||
94981ef335 | |||
3cd244be76 | |||
f100595257 | |||
e84da827c2 | |||
42f9fa029d | |||
40fee97b06 | |||
3cc8292d8b | |||
9261d30a34 | |||
3eb3a8db5a | |||
97129268f0 | |||
fa39a965ca | |||
7da979503b | |||
3b32c26026 | |||
cad25306e7 | |||
4d7414c941 | |||
b29b8bdec7 | |||
a7d081bfcb | |||
5ca208d07f | |||
6c605944c5 | |||
02b6e17449 | |||
770db96ec6 | |||
ff356fdd49 | |||
eec89e2cc1 | |||
d69d8f64f3 | |||
4ee2562202 | |||
08b1ece56e | |||
26b978dcf2 | |||
b22c2e094c | |||
b40775f97c | |||
a27a72646c | |||
100ddad40e | |||
d8b6d419b6 | |||
1bde38bf72 | |||
a06c81643c | |||
15fd7bf4a5 | |||
0a25ef544f | |||
a6b824d3c4 | |||
79ee47bada | |||
be06e61bfb | |||
3b4884fcf1 | |||
4319dc58eb | |||
3122434908 | |||
dae7785ee2 | |||
d54f8b1e93 | |||
27f3b2bd76 | |||
b417f60769 | |||
df2d5b6d01 | |||
3e6278fa21 | |||
a66b257644 | |||
ef66d2ec72 | |||
e21dbd507d | |||
64878bee67 | |||
557a080ffc | |||
8ecb17ed3e | |||
c4874c85b1 | |||
563a75e9b2 | |||
7f002b8718 | |||
79e2bd2913 | |||
95161b55cd | |||
d91759068c | |||
c23c496066 | |||
824630f7d1 | |||
f8e8d23857 | |||
8484bb7978 | |||
57105c6861 | |||
3758044e7b | |||
bfaf098c31 | |||
0e99b296bc | |||
089f86d5e4 | |||
d0e1241bd1 | |||
c1a0a08b76 | |||
e8748ce0a0 | |||
7cf9b342cc | |||
8739851f48 | |||
d945b43f6b | |||
fcc3ea1e39 | |||
7722acecee | |||
bdd70f8fa2 | |||
571a0a9d06 | |||
ccf4f66dd9 | |||
b38e5403a5 | |||
09af041745 | |||
cb5131746f | |||
2fbd0f8ee1 | |||
bfd5630e21 | |||
026f5dee4d | |||
b59be8338a | |||
ab4bbc2224 | |||
156fcd1bf2 | |||
576d2c32f0 | |||
bb63a594ab | |||
25739ec2ba | |||
f148334b58 | |||
da537ea8ea | |||
18d224dc34 | |||
3a6ee8708e | |||
983bf93d8f | |||
40cc8f5d1c | |||
cce03a5dc8 | |||
38fd171713 | |||
84c78d9256 | |||
973203d85e | |||
f9174dd2aa | |||
98dfc3aa5a | |||
27b56b1a12 | |||
6e9220d2bb | |||
0ddcfcaa23 | |||
a4cb6645b4 | |||
2492ed2ca7 | |||
f49d2a1e0e | |||
0dc3f4f7f2 | |||
0bed4d0ada | |||
f3e8af3fdb | |||
af542ec05f | |||
399a1d2052 | |||
bb6e5611d4 | |||
d5901afb8e | |||
c11f5a1401 | |||
5b220f3fec | |||
8bf41ea858 | |||
df861a3ef0 | |||
d6754b6cac | |||
b03d7f7fb0 | |||
008b186479 | |||
914f9b3703 | |||
ed7ec4a371 | |||
2d338201a5 | |||
a8aad1f98f | |||
2d06b93118 | |||
60547204a8 | |||
3d763a0021 | |||
ad474873e2 | |||
dd35136ac0 | |||
cfe6e9c20a | |||
0f3f0933b1 | |||
f8440e3811 | |||
829460a076 | |||
9ecd0adcbe | |||
ad92a2e158 | |||
5f5891d241 | |||
cf475c4696 | |||
992194a1f0 | |||
bad6a7bfee | |||
66d5e204be | |||
ce35330923 | |||
bdab1aa7e3 | |||
080c8dbe3d | |||
a31fe44624 | |||
59187a0ec0 | |||
03fbf42680 | |||
f3b2a98874 | |||
2e9084c9ef | |||
0907240fda | |||
7d670facd4 | |||
61e5704fd6 | |||
fd0723169f | |||
a725d42bf5 | |||
c03cea2d4e | |||
f43d6bff92 | |||
43a8ca90a7 | |||
dac6046828 | |||
e2a6ae22dc | |||
f2ee43d1ef | |||
3d80b46570 | |||
e7d383604a | |||
7d504892be | |||
d7a2bf9d26 | |||
d6184a7b6d | |||
851c15aa6d | |||
c45898f903 | |||
0efec20904 | |||
2f04b563d1 | |||
5b9c58dbc6 | |||
a7964c4f0c | |||
006a7e9f72 | |||
3856710faf | |||
6cbc0bedf3 | |||
fbc0c7615a | |||
34bcdb5128 | |||
a5c6e41622 | |||
02e03227d8 | |||
faa0a7c9ea | |||
812a02bc6b | |||
27898ecdc8 | |||
1c2324cca4 | |||
70f059eaac | |||
bac72be730 | |||
99858c1384 | |||
103a300e77 | |||
6b5cdd7508 | |||
2f1e354400 | |||
585a87130c | |||
0e68533776 | |||
882cc5bfd0 | |||
91847a9a8e | |||
5c649ff216 | |||
abdd224211 | |||
0c72c59190 | |||
432170a69e | |||
805b37a9a5 | |||
87a0bda011 | |||
5d2c6e1978 | |||
abafbd811b | |||
aca50d9946 | |||
bd4f4dab81 | |||
aebd11ea82 | |||
fa6906fdf9 | |||
cec21375a5 | |||
0428f64afa | |||
e0864edefc | |||
7460fd283c | |||
7a7dee1630 | |||
913403aac6 | |||
432a66bf5f | |||
e2a43ddfa0 | |||
b2ba204ca1 | |||
892b045342 | |||
8644e6705a | |||
3f60206eef | |||
568ead4bd7 | |||
14241d54c9 | |||
e4d75c5f38 | |||
c42dda1bab | |||
3295ae3b74 | |||
e63438bedf | |||
25422da9ba | |||
37583d8c9c | |||
62b3863722 | |||
b11f03bd18 | |||
63620fa058 | |||
cecb114810 | |||
4ce93f74c6 | |||
09b806d7a7 | |||
2f31100c3f | |||
ca3f97ec51 | |||
7378d6c5b2 | |||
276de5d662 | |||
6f449cf35f | |||
daf046861c | |||
43498c62f9 | |||
22f5853741 | |||
fe217f6667 | |||
41ae86f40f | |||
6d52c8ecf8 | |||
75b649543a | |||
1261a6f452 | |||
041855dbc7 | |||
3e52956a3a | |||
d8f4158bc6 | |||
36638e80a3 | |||
28d0a72c62 | |||
6471524f4a | |||
61b2b8f2cd | |||
02aae4bb8b | |||
3efecb9560 | |||
8d0707699c | |||
318774a2a0 | |||
b14e997a43 | |||
b949438be5 | |||
6ee9e8e405 | |||
09ee8e6efc | |||
49527edaa9 | |||
92d193ffe3 | |||
4805510073 | |||
6fe195e2dd | |||
c54df8d9c4 | |||
6d8b6c61a2 | |||
822653ec10 | |||
68502ca944 | |||
103d11a87c | |||
0028c41bdc | |||
a4fe002607 | |||
b54ab9391b | |||
0c7612c83f | |||
f9361af41c | |||
3cd3ebed51 | |||
4ad209020a | |||
556327740b | |||
b0ddb1b31c | |||
70ee98736a | |||
5de06cef35 | |||
4f3706622c | |||
104e76de47 | |||
1df99978bb | |||
3846322f12 | |||
623b2c6611 | |||
cb4d73f959 | |||
58febf51bd | |||
b254379fb1 | |||
835d933719 | |||
31130d90bc | |||
237c493252 | |||
18e7acd9e7 | |||
906026e333 | |||
9e24fba5ee | |||
12edd60969 | |||
0f429caaca | |||
940f1140a3 | |||
dbb6773634 | |||
245a0544bc | |||
cbd65f0816 | |||
f8ea711f6a | |||
ace94cf4d6 | |||
829fde4336 | |||
ba8774d6e5 | |||
7597853cda | |||
21077c0e34 | |||
b6a45656af | |||
33d2f0895f | |||
5cd92279b7 | |||
4085f60018 | |||
3faee78717 | |||
e96e07ac21 | |||
0c34aec8ec | |||
9d04037bec | |||
6af44bfd86 | |||
5ceefa4d6d | |||
f618925190 | |||
68ae723543 | |||
e4123759f5 | |||
5e727a83b3 | |||
dc288d9aa7 | |||
8d49c423ca | |||
c056564c9c | |||
efb2815fa5 | |||
577d149728 | |||
45c2bfaaeb | |||
16d4c9cdf2 | |||
1063a89541 | |||
fd0f709d50 | |||
5edd10c332 | |||
5c36ee79be | |||
b2bf9d63a3 | |||
e297df011d | |||
bcac00d766 | |||
c256d7ded5 | |||
7ba39ea831 | |||
28f90e4421 | |||
5d66a1e6a5 | |||
1522eccfb3 | |||
728604e036 | |||
58d4f0d512 | |||
1f7fc8700e | |||
a933f8b512 | |||
83b83841d6 | |||
ef8a8bc246 | |||
136ddda055 | |||
5fbf2166f1 | |||
ba7bc3bd03 | |||
311412c5ee | |||
d18e94ea87 | |||
6a548366cd | |||
54d2e875f6 | |||
c5cc0e90a3 | |||
50ce8da68c | |||
3449bfc2a9 | |||
18d301d9dd | |||
357bf7f4ca | |||
f763448d6f | |||
deb828e98a | |||
cbca41accf | |||
ac22e07388 | |||
cb0d9e077b | |||
58105e9b62 | |||
32fb79d43d | |||
f129afdae8 | |||
29cde5e724 | |||
3467a5df48 | |||
694dd59e27 | |||
540b3e4af2 | |||
e0211646b2 | |||
94dcb0f08a | |||
0b38ed2f2a | |||
15622251ef | |||
4eb79a4a5c | |||
9f54413d46 | |||
f467898a04 | |||
413c8a4fef | |||
d4440736dd | |||
bb1ceaed12 | |||
51a90136ea | |||
e7cfa19897 | |||
41411e005f | |||
c22119f69b | |||
354a4e523b | |||
b34b8a249c | |||
508257da87 | |||
fadcf7d7c1 | |||
7f43360120 | |||
f9a8389f58 | |||
f77a18a655 | |||
7e4d6853f5 | |||
5615c7cf6e | |||
54c51a5636 | |||
1119726c64 | |||
101a2bc3af | |||
f4bfaf3581 | |||
e8dfc1dc71 | |||
ef26b9085c | |||
85d9c11733 | |||
6d41f1f1db | |||
f9434215db | |||
83d402eb77 | |||
cec48e0270 | |||
322038ca21 | |||
6395e60f17 | |||
7969eb12d6 | |||
f942e2c5a9 | |||
089f676c4a | |||
d2012b4e40 | |||
a319017567 | |||
a669c9c88b | |||
8391e500c9 | |||
5f27c8fddf | |||
a4ae41e627 | |||
a5126ae8fb | |||
f33776e0ed | |||
189eccb01e | |||
4336d68e6f | |||
4f45adb063 | |||
e6b16624c3 | |||
e87d2f545c | |||
69bc219efa | |||
e4f1cfb53f | |||
f1e59061d7 | |||
cd312e41d4 | |||
1bd2d0dfc1 | |||
49235a4d83 | |||
e7826e0648 | |||
e7edb4739f | |||
4a622c558e | |||
bfe69a4708 | |||
688b4edf13 | |||
7ca2e5f539 | |||
4c5fb74c7d | |||
ad82bb2630 | |||
008a6192d4 | |||
f4d4c7a92a | |||
0a41192eb1 | |||
f044fcb584 | |||
9e2c0a7112 | |||
d2e1441d1f | |||
abbd28a634 | |||
b309402784 | |||
a7d3ac95aa | |||
255da2b976 | |||
8cdb4aa53d | |||
4d5b462b2c | |||
f7a318c937 | |||
eb5b9b083c | |||
e0d9a59d10 | |||
119ac4cf95 | |||
f53d0e16ff | |||
5321ccc980 | |||
e8a6fa3506 | |||
26e1cc2a7a | |||
cec4b4b78e | |||
7ce3cb79c9 | |||
4c553b1525 | |||
84ec809fb5 | |||
f49e466ce8 | |||
402baa1011 | |||
01de6f84cf | |||
e1e9047664 | |||
0be9831b0c | |||
1db9d4d10b | |||
ccef9d1414 | |||
8b09599c5e | |||
368099e95a | |||
34342b7f48 | |||
fcc7ebf5c1 | |||
114bdb30e8 | |||
4caf61387e | |||
ab020327f4 | |||
bacad0f111 | |||
9619c6d2e1 | |||
07c7050335 | |||
24a6fba008 | |||
51c53b2103 | |||
4ae01aa353 | |||
0db1e3728a | |||
83c7657951 | |||
e20386299f | |||
d6e43effde | |||
bbe8f4a852 | |||
8c98e38053 | |||
96a36d4d6b | |||
366a9cea0d | |||
e810774202 | |||
f2de781cbc | |||
7f08ad01db | |||
2c66d8cad0 | |||
fc4803f3fd | |||
5a6d1dd3c2 | |||
ba42ff7469 | |||
a6cc698c69 | |||
19b0a62fee | |||
1a6ce11b07 | |||
49d8578b83 | |||
53c0cd570a | |||
4d84bdafed | |||
059cd38e7b | |||
8f89d11435 | |||
243f78ff0e | |||
21be1b392e | |||
8b71e6ac5a | |||
f5f6298284 | |||
c8370bc290 | |||
abc0ac88d3 | |||
9da604c0af | |||
801da9d321 | |||
ac3b0b873b | |||
9beee146f2 | |||
2d06401f3c | |||
2db56f2499 | |||
63ea6d7002 | |||
3e2523cc2c | |||
ad3f5e305e | |||
aa5b9e3db3 | |||
46123719e9 | |||
16bce990c6 | |||
d55e387187 | |||
e75c3375dc | |||
b1c7cb367a | |||
d63d660ec2 | |||
f24a0a84b5 | |||
9704dcc997 | |||
80875d6312 | |||
79f4c9f98c | |||
e2735e151e | |||
afb4a88830 | |||
84dc8cfd23 | |||
6ef52677ee | |||
73c0e9a742 | |||
7ff259073e | |||
2bf10c60ee | |||
72f4b43b54 | |||
e1ced7a7fe | |||
f41b1cf3b5 | |||
70693c2052 | |||
f61d7d0f7d | |||
3d7ea75bfc | |||
f350d7949c | |||
10c21714ef | |||
2dbae69d50 | |||
4cc5eed884 | |||
9967868e80 | |||
bb79752101 | |||
ebd24e5999 | |||
7a3fa88559 | |||
cff4fdc5f5 | |||
e7fc52ff20 | |||
b061aff76e | |||
b14214761b | |||
9bd684a971 | |||
89286be9e1 | |||
991a6a7552 | |||
04af5558b5 | |||
3ee487ca94 | |||
20352ff170 | |||
819894ccbf | |||
aa46c4cb8f | |||
1c75977da7 | |||
c099483305 | |||
959e200837 | |||
d9f0bdb089 | |||
b50d723158 | |||
14739af1b9 | |||
747511c6a8 | |||
c96f9cd4de | |||
31da2f10c9 | |||
9e51d7f150 | |||
b1b1f8d659 | |||
0c0e7881b1 | |||
6c2f07aab1 | |||
84d2b31c51 | |||
2f23d916f5 | |||
d413f4a782 | |||
c2080cfe1e | |||
c687d059c5 | |||
a131358c36 | |||
0ba012fd7c | |||
b43a693a1e | |||
6f4072efdd | |||
908984c285 | |||
8772aaec65 | |||
f3d605bb63 | |||
6741e0b9e1 | |||
a9f932408c | |||
a00e6984d9 | |||
b4738438b1 | |||
416c2f2f39 | |||
589f86010f | |||
76a7c19996 | |||
3fa676e169 | |||
3193028c48 | |||
ba823e8283 | |||
55f4ef9a4f | |||
200b0dcf7c | |||
181f9597c2 | |||
e55c264c29 | |||
dfbae7e7b5 | |||
98fa50d0eb | |||
9503658dec | |||
3c9bf681b2 | |||
c1b20675c1 | |||
5703caac19 | |||
7abf7459f9 | |||
b14c6ecd5b | |||
455127219d | |||
e235014bde | |||
ed1bf899b6 | |||
36c7f77a98 | |||
996b4f8366 | |||
39d94b34d7 | |||
6edc6841bf | |||
b2806bd649 | |||
936118b8cb | |||
120f251590 | |||
d9962e1b03 | |||
1396eb2c58 | |||
12daa9830e | |||
3e5e1477b9 | |||
c100f55f1c | |||
5a0c0dff41 | |||
8fc5e3611e | |||
3c3fe16569 | |||
8eb83bb283 | |||
e559f1b960 | |||
24a485c213 | |||
413669d118 | |||
1729f29374 | |||
e58833da3b | |||
e5d4b57d9e | |||
1d61834a95 | |||
b74f55cf54 | |||
eb07a416b4 | |||
ca277567f4 | |||
68c2f8f333 | |||
ae5dee394c | |||
a94c460a95 | |||
78bf5caf00 | |||
c5dbda67ad | |||
2260fbaec5 | |||
4d2fecec13 | |||
101677688e | |||
ca8fefe0c6 | |||
3e8d7ef8e3 | |||
71aed74e20 | |||
712e2c2d12 | |||
892ba7d63e | |||
96ca2a6585 | |||
3ebf6470c1 | |||
f8db994129 | |||
dcb74234a6 | |||
ac7c0709e8 | |||
7d8595233c | |||
5452286493 | |||
5528b6d87d | |||
6ae3e61d1d | |||
a9093a6a69 | |||
3dcf7a1204 | |||
c2c63d400f | |||
8f9c9efca1 | |||
1cb83032a1 | |||
eba9253efe | |||
9bd0537854 | |||
9491190ce4 | |||
9b70d8884d | |||
9824094fdc | |||
361be1e5d1 | |||
1d38aa62de | |||
d8a4702f1e | |||
75124f18c0 | |||
f54df71d2a | |||
b40b29350a | |||
6a9b8b558a | |||
58f17eac2d | |||
41709b6eac | |||
f9f247df39 | |||
4c4a8a0897 | |||
10aea555dd | |||
43f7f07d0e | |||
3bde4a70ca | |||
b9fefdab80 | |||
2ac2aa4e6c | |||
8f526cd2b5 | |||
6382ac22cb | |||
e1845d37da | |||
9ccbfd8bf0 | |||
37a95b97f6 | |||
4e0845eb9c | |||
dc8b79b721 | |||
dd0ab41396 | |||
c3c3cff6ca | |||
1f26b36fb8 | |||
e990d5a645 | |||
121e86013e | |||
e0a1dcd51f | |||
758281f772 | |||
fe19065a6a | |||
a9ba9b77ad | |||
23f4b2e2e4 | |||
2d65282643 | |||
0bd9125484 | |||
175144663d | |||
77a0a36bb8 | |||
f26b64c660 | |||
3ff9c0ad0c | |||
3eb6ce6ff6 | |||
845b4b219d | |||
ffe53086fb | |||
5c34c807c5 | |||
de2a33580a | |||
08a875d862 | |||
7eeebd632d | |||
a72e9b1a3e | |||
56808821da | |||
b53eca6323 | |||
5a1edb51ef | |||
b03328b54f | |||
4e2615f321 | |||
1e14654d95 | |||
0519db4d2c | |||
5b9e4df03b | |||
2dbde57f46 | |||
d51b7eb124 | |||
bfcc071d94 | |||
72e1ab6ad6 | |||
d54efbaacf | |||
7d2f166d67 | |||
aff3e1aee8 | |||
9343447c03 | |||
beb13b8f84 | |||
70b273a0d2 | |||
fc2bf35588 | |||
05893ad661 | |||
fdc9df6b91 | |||
c6d68e1450 | |||
d294be9f35 | |||
98ea4d2dfe | |||
6a950b4e97 | |||
70292e4f8e | |||
67f8b82740 | |||
e9eb139b80 | |||
61d5b9f048 | |||
c5c86c3964 | |||
0f233f3a22 | |||
166bd70a1f | |||
776b4a6c02 | |||
75dcc60be5 | |||
53034a6ff4 | |||
1ea6df9e6c | |||
a98a14da3d | |||
629cb8776e | |||
96a63d0e89 | |||
c7b065eed9 | |||
89b0b8884b | |||
644983d27a | |||
04d3ea97f3 | |||
11baf471a4 | |||
505c2d83f2 | |||
f84ab9a4d1 | |||
0127b61901 | |||
b7247f6082 | |||
9cc72c09dc | |||
d763f3b912 | |||
f8899aada0 | |||
2e983267d4 | |||
df0c63b300 | |||
1db2031b76 | |||
2720ccc1fc | |||
f2aea2c201 | |||
6b9c5f518e | |||
6d6d2320bd | |||
a1298d6cda | |||
52b59bcde8 | |||
256c85ba5c | |||
5e484719c2 | |||
6b88379b01 | |||
7b29624776 | |||
18f8825cd5 | |||
3d94d02960 | |||
1f8886684f | |||
29f1da873b | |||
97ec517a1e | |||
2fccaf684c | |||
008063e645 | |||
867c949604 | |||
7a1af6ee5c | |||
0893c90c51 | |||
3c7ebb5385 | |||
91c2f6fc95 | |||
ead08fbb5d | |||
3ad6a15f56 | |||
12adb9f10a | |||
7b2932b02b | |||
57a47da12c | |||
84a51faa70 | |||
ad495301c0 | |||
43bd745228 | |||
fea056d9be | |||
2f320db5e2 | |||
130268491c | |||
caf95675d6 | |||
b23281e9dc | |||
850354b7d7 | |||
5c7851e4d9 | |||
d85dbf1d33 | |||
93ea668db3 | |||
5f426b3efd | |||
4b6a18e4e7 | |||
35629a2a07 | |||
50651d1c03 | |||
412667dd0e | |||
c46a5089a6 | |||
1b3f902dc2 | |||
bfcb4f92e8 | |||
13dda2e533 | |||
29c5811b68 | |||
8111757357 | |||
93ff8f25a1 | |||
bb810ac75a | |||
87b78d1c89 | |||
bc56f78fd2 | |||
41ac63f445 | |||
b538044d9a | |||
02882dd781 | |||
a24d5581f1 | |||
3125acc95c | |||
d4c7cfcdf8 | |||
6ff01649d6 | |||
dfe724ff52 | |||
6c759c226a | |||
d22c2ea56a | |||
319bfe205d | |||
c4367644dd | |||
69464c2405 | |||
1da78d093f | |||
70ccbb3f59 | |||
214f24805e | |||
37f6c9c3bf | |||
c0ba6dc9f5 | |||
92159f2a3d | |||
3855fb5eb6 | |||
5b3a716819 | |||
48b6045ba3 | |||
fd965177ff | |||
b34d332a32 | |||
23db2bf1bf | |||
5996e1f301 | |||
70a61386b8 | |||
53df000ba6 | |||
802294ec9c | |||
ed4e289209 | |||
796977713d | |||
1f0f84f2f0 | |||
4e328ae0a3 | |||
b572d6d27b | |||
cd79be5414 | |||
28dbf10a30 | |||
96cabc30bc | |||
f5376f2dbb | |||
8b25bc96a4 | |||
6acd363f55 | |||
539ee010ab | |||
5202c572fb | |||
5630b6d8d7 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
/keep
|
||||
.working
|
||||
result
|
||||
result-*
|
||||
/secrets/local.nix
|
||||
/working
|
||||
|
119
README.md
119
README.md
@@ -1,3 +1,7 @@
|
||||

|
||||
|
||||
# .❄️≡We|_c0m3 7o m`/ f14k≡❄️.
|
||||
|
||||
## What's Here
|
||||
|
||||
this is the top-level repo from which i configure/deploy all my NixOS machines:
|
||||
@@ -6,18 +10,18 @@ this is the top-level repo from which i configure/deploy all my NixOS machines:
|
||||
- server
|
||||
- mobile phone (Pinephone)
|
||||
|
||||
everything outside of <./hosts/> and <./secrets/> is intended for export, to be importable for use by 3rd parties.
|
||||
everything outside of [hosts/](./hosts/) and [secrets/](./secrets/) is intended for export, to be importable for use by 3rd parties.
|
||||
the only hard dependency for my exported pkgs/modules should be [nixpkgs][nixpkgs].
|
||||
building <./hosts/> will require [sops][sops].
|
||||
building [hosts/](./hosts/) will require [sops][sops].
|
||||
|
||||
you might specifically be interested in these files (elaborated further in #key-points-of-interest):
|
||||
- [`sxmo-utils-latest`](./pkgs/additional/sxmo-utils/default.nix)
|
||||
- [`sxmo-utils`](./pkgs/additional/sxmo-utils/default.nix)
|
||||
- [example SXMO deployment](./hosts/modules/gui/sxmo/default.nix)
|
||||
- [my implementation of impermanence](./modules/persist/default.nix)
|
||||
- my way of deploying dotfiles/configuring programs per-user:
|
||||
- <./modules/fs/default.nix>
|
||||
- <./modules/programs.nix>
|
||||
- <./modules/users.nix>
|
||||
- [modules/fs/](./modules/fs/default.nix)
|
||||
- [modules/programs/](./modules/programs/default.nix)
|
||||
- [modules/users.nix](./modules/users.nix)
|
||||
|
||||
[nixpkgs]: https://github.com/NixOS/nixpkgs
|
||||
[sops]: https://github.com/Mic92/sops-nix
|
||||
@@ -35,37 +39,37 @@ or follow the instructions [here][NUR] to use it via the Nix User Repositories.
|
||||
|
||||
## Layout
|
||||
- `doc/`
|
||||
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
||||
- instructions for tasks i find myself doing semi-occasionally in this repo.
|
||||
- `hosts/`
|
||||
- the bulk of config which isn't factored with external use in mind.
|
||||
- that is, if you were to add this repo to a flake.nix for your own use,
|
||||
you won't likely be depending on anything in this directory.
|
||||
- the bulk of config which isn't factored with external use in mind.
|
||||
- that is, if you were to add this repo to a flake.nix for your own use,
|
||||
you won't likely be depending on anything in this directory.
|
||||
- `integrations/`
|
||||
- code intended for consumption by external tools (e.g. the Nix User Repos)
|
||||
- code intended for consumption by external tools (e.g. the Nix User Repos)
|
||||
- `modules/`
|
||||
- config which is gated behind `enable` flags, in similar style to nixpkgs'
|
||||
`nixos/` directory.
|
||||
- if you depend on this repo, it's most likely for something in this directory.
|
||||
- config which is gated behind `enable` flags, in similar style to nixpkgs'
|
||||
`nixos/` directory.
|
||||
- if you depend on this repo, it's most likely for something in this directory.
|
||||
- `nixpatches/`
|
||||
- literally, diffs i apply atop upstream nixpkgs before performing further eval.
|
||||
- literally, diffs i apply atop upstream nixpkgs before performing further eval.
|
||||
- `overlays/`
|
||||
- exposed via the `overlays` output in `flake.nix`.
|
||||
- predominantly a list of `callPackage` directives.
|
||||
- exposed via the `overlays` output in `flake.nix`.
|
||||
- predominantly a list of `callPackage` directives.
|
||||
- `pkgs/`
|
||||
- derivations for things not yet packaged in nixpkgs.
|
||||
- derivations for things from nixpkgs which i need to `override` for some reason.
|
||||
- inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools
|
||||
that are highly specific to my setup).
|
||||
- derivations for things not yet packaged in nixpkgs.
|
||||
- derivations for things from nixpkgs which i need to `override` for some reason.
|
||||
- inline code for wholly custom packages (e.g. `pkgs/additional/sane-scripts/` for CLI tools
|
||||
that are highly specific to my setup).
|
||||
- `scripts/`
|
||||
- scripts which aren't reachable on a deployed system, but may aid manual deployments
|
||||
- scripts which aren't reachable on a deployed system, but may aid manual deployments
|
||||
- `secrets/`
|
||||
- encrypted keys, API tokens, anything which one or more of my machines needs
|
||||
read access to but shouldn't be world-readable.
|
||||
- not much to see here
|
||||
- encrypted keys, API tokens, anything which one or more of my machines needs
|
||||
read access to but shouldn't be world-readable.
|
||||
- not much to see here
|
||||
- `templates/`
|
||||
- exposed via the `templates` output in `flake.nix`.
|
||||
- used to instantiate short-lived environments.
|
||||
- used to auto-fill the boiler-plate portions of new packages.
|
||||
- exposed via the `templates` output in `flake.nix`.
|
||||
- used to instantiate short-lived environments.
|
||||
- used to auto-fill the boiler-plate portions of new packages.
|
||||
|
||||
|
||||
## Key Points of Interest
|
||||
@@ -73,35 +77,40 @@ or follow the instructions [here][NUR] to use it via the Nix User Repositories.
|
||||
i.e. you might find value in using these in your own config:
|
||||
|
||||
- `modules/fs/`
|
||||
- use this to statically define leafs and nodes anywhere in the filesystem,
|
||||
not just inside `/nix/store`.
|
||||
- e.g. specify that `/var/www` should be:
|
||||
- owned by a specific user/group
|
||||
- set to a specific mode
|
||||
- symlinked to some other path
|
||||
- populated with some statically-defined data
|
||||
- populated according to some script
|
||||
- created as a dependency of some service (e.g. `nginx`)
|
||||
- values defined here are applied neither at evaluation time _nor_ at activation time.
|
||||
- rather, they become systemd services.
|
||||
- systemd manages dependencies
|
||||
- e.g. link `/var/www -> /mnt/my-drive/www` only _after_ `/mnt/my-drive/www` appears)
|
||||
- this is akin to using [Home Manager's][home-manager] file API -- the part which lets you
|
||||
statically define `~/.config` files -- just with a different philosophy.
|
||||
- use this to statically define leafs and nodes anywhere in the filesystem,
|
||||
not just inside `/nix/store`.
|
||||
- e.g. specify that `/var/www` should be:
|
||||
- owned by a specific user/group
|
||||
- set to a specific mode
|
||||
- symlinked to some other path
|
||||
- populated with some statically-defined data
|
||||
- populated according to some script
|
||||
- created as a dependency of some service (e.g. `nginx`)
|
||||
- values defined here are applied neither at evaluation time _nor_ at activation time.
|
||||
- rather, they become systemd services.
|
||||
- systemd manages dependencies
|
||||
- e.g. link `/var/www -> /mnt/my-drive/www` only _after_ `/mnt/my-drive/www` appears)
|
||||
- this is akin to using [Home Manager's][home-manager] file API -- the part which lets you
|
||||
statically define `~/.config` files -- just with a different philosophy.
|
||||
- `modules/persist/`
|
||||
- my alternative to the Impermanence module.
|
||||
- this builds atop `modules/fs/` to achieve things stock impermanence can't:
|
||||
- persist things to encrypted storage which is unlocked at login time (pam_mount).
|
||||
- "persist" cache directories -- to free up RAM -- but auto-wipe them on mount
|
||||
and encrypt them to ephemeral keys so they're unreadable post shutdown/unmount.
|
||||
- `modules/programs.nix`
|
||||
- like nixpkgs' `programs` options, but allows both system-wide or per-user deployment.
|
||||
- allows `fs` and `persist` config values to be gated behind program deployment:
|
||||
- e.g. `/home/<user>/.mozilla/firefox` is persisted only for users who
|
||||
`sane.programs.firefox.enableFor.user."<user>" = true;`
|
||||
- my alternative to the Impermanence module.
|
||||
- this builds atop `modules/fs/` to achieve things stock impermanence can't:
|
||||
- persist things to encrypted storage which is unlocked at login time (pam_mount).
|
||||
- "persist" cache directories -- to free up RAM -- but auto-wipe them on mount
|
||||
and encrypt them to ephemeral keys so they're unreadable post shutdown/unmount.
|
||||
- `modules/programs/`
|
||||
- like nixpkgs' `programs` options, but allows both system-wide or per-user deployment.
|
||||
- allows `fs` and `persist` config values to be gated behind program deployment:
|
||||
- e.g. `/home/<user>/.mozilla/firefox` is persisted only for users who
|
||||
`sane.programs.firefox.enableFor.user."<user>" = true;`
|
||||
- allows aggressive sandboxing any program:
|
||||
- `sane.programs.firefox.sandbox.method = "bwrap"; # sandbox with bubblewrap`
|
||||
- `sane.programs.firefox.sandbox.whitelistWayland = true; # allow it to render a wayland window`
|
||||
- `sane.programs.firefox.sandbox.extraHomePaths = [ "Downloads" ]; # allow it read/write access to ~/Downloads`
|
||||
- integrated with `fs` and `persist` modules so that programs' config files and persisted data stores are linked into the sandbox w/o any extra involvement.
|
||||
- `modules/users.nix`
|
||||
- convenience layer atop the above modules so that you can just write
|
||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||
- convenience layer atop the above modules so that you can just write
|
||||
`fs.".config/git"` instead of `fs."/home/colin/.config/git"`
|
||||
|
||||
some things in here could easily find broader use. if you would find benefit in
|
||||
them being factored out of my config, message me and we could work to make that happen.
|
||||
|
80
TODO.md
80
TODO.md
@@ -1,12 +1,14 @@
|
||||
## BUGS
|
||||
- why i need to manually restart `wireguard-wg-ovpns` on servo periodically
|
||||
- else DNS fails
|
||||
- ringer (i.e. dino incoming call) doesn't prevent moby from sleeping
|
||||
- `nix` operations from lappy hang when `desko` is unreachable
|
||||
- could at least direct the cache to `http://desko-hn:5001`
|
||||
|
||||
## REFACTORING:
|
||||
- consolidate ~/dev and ~/ref
|
||||
- ~/dev becomes a link to ~/ref/cat/mine
|
||||
- fold hosts/common/home/ssh.nix -> hosts/common/users/colin.nix
|
||||
|
||||
### sops/secrets
|
||||
- attach secrets to the thing they're used by (sane.programs)
|
||||
- rework secrets to leverage `sane.fs`
|
||||
- remove sops activation script as it's covered by my systemd sane.fs impl
|
||||
|
||||
@@ -19,49 +21,66 @@
|
||||
- bump nodejs version in lemmy-ui
|
||||
- add updateScripts to all my packages in nixpkgs
|
||||
- fix lightdm-mobile-greeter for newer libhandy
|
||||
- port zecwallet-lite to a from-source build
|
||||
- REVIEW/integrate jellyfin dataDir config: <https://github.com/NixOS/nixpkgs/pull/233617>
|
||||
- remove `libsForQt5.callPackage` broadly: <https://github.com/NixOS/nixpkgs/issues/180841>
|
||||
|
||||
#### upstreaming to non-nixpkgs repos
|
||||
- gtk: build schemas even on cross compilation: <https://github.com/NixOS/nixpkgs/pull/247844>
|
||||
- sxmo: add new app entries
|
||||
|
||||
|
||||
## IMPROVEMENTS:
|
||||
### security/resilience
|
||||
- matrix/ntfy: automatically add the ntfy.uninsane.org push URL as part of synapse launch
|
||||
- ntfy: use a more secure topic
|
||||
- validate duplicity backups!
|
||||
- encrypt more ~ dirs (~/archives, ~/records, ..?)
|
||||
- best to do this after i know for sure i have good backups
|
||||
- have `sane.programs` be wrapped such that they run in a cgroup?
|
||||
- at least, only give them access to the portion of the fs they *need*.
|
||||
- Android takes approach of giving each app its own user: could hack that in here.
|
||||
- **systemd-run** takes a command and runs it in a temporary scope (cgroup)
|
||||
- presumably uses the same options as systemd services
|
||||
- see e.g. <https://github.com/NixOS/nixpkgs/issues/113903#issuecomment-857296349>
|
||||
- flatpak does this, somehow
|
||||
- apparmor? SElinux? (desktop) "portals"?
|
||||
- see Spectrum OS; Alyssa Ross; etc
|
||||
- bubblewrap-based sandboxing: <https://github.com/nixpak/nixpak>
|
||||
- /mnt/desko/home, etc, shouldn't include secrets (~/private)
|
||||
- 95% of its use is for remote media access and stuff which isn't in VCS (~/records)
|
||||
- port all sane.programs to be sandboxed
|
||||
- enforce that all `environment.packages` has a sandbox profile (or explicitly opts out)
|
||||
- revisit "non-sandboxable" apps and check that i'm not actually just missing mountpoints
|
||||
- LL_FS_RW=/ isn't enough -- need all mount points like `=/:/proc:/sys:...`.
|
||||
- ensure non-bin package outputs are linked for sandboxed apps
|
||||
- i.e. `outputs.man`, `outputs.debug`, `outputs.doc`, ...
|
||||
- lock down dbus calls within the sandbox
|
||||
- otherwise anyone can `systemd-run --user ...` to potentially escape a sandbox
|
||||
- <https://github.com/flatpak/xdg-dbus-proxy>
|
||||
- remove `.ssh` access from Firefox!
|
||||
- limit access to `~/knowledge/secrets` through an agent that requires GUI approval, so a firefox exploit can't steal all my logins
|
||||
- port sane-sandboxed to a compiled language (hare?)
|
||||
- it adds like 50-70ms launch time _on my laptop_. i'd hate to know how much that is on the pinephone.
|
||||
- make dconf stuff less monolithic
|
||||
- i.e. per-app dconf profiles for those which need it. possible static config.
|
||||
- canaries for important services
|
||||
- e.g. daily email checks; daily backup checks
|
||||
- integrate `nix check` into Gitea actions?
|
||||
|
||||
### user experience
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
- install apps:
|
||||
- display QR codes for WiFi endpoints: <https://linuxphoneapps.org/apps/noappid.wisperwind.wifi2qr/>
|
||||
- shopping list: <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
||||
- offline Wikipedia
|
||||
- shopping list (not in nixpkgs): <https://linuxphoneapps.org/apps/ro.hume.cosmin.shoppinglist/>
|
||||
- offline Wikipedia (or, add to `wike`)
|
||||
- offline docs viewer (gtk): <https://github.com/workbenchdev/Biblioteca>
|
||||
- some type of games manager/launcher
|
||||
- Gnome Highscore (retro games)?: <https://gitlab.gnome.org/World/highscore>
|
||||
- better maps for mobile (Osmin (QtQuick)? Pure Maps (Qt/Kirigami)? Gnome Maps is improved in 45)
|
||||
- note-taking app: <https://linuxphoneapps.org/categories/note-taking/>
|
||||
- OSK overlay specifically for mobile gaming
|
||||
- i.e. mock joysticks, for use with SuperTux and SuperTuxKart
|
||||
- install mobile-friendly games:
|
||||
- Shattered Pixel Dungeon (nixpkgs `shattered-pixel-dungeon`; doesn't cross-compile b/c openjdk/libIDL) <https://github.com/ebolalex/shattered-pixel-dungeon>
|
||||
- UnCiv (Civ V clone; nixpkgs `unciv`; doesn't cross-compile): <https://github.com/yairm210/UnCiv>
|
||||
- Simon Tatham's Puzzle Collection (not in nixpkgs) <https://git.tartarus.org/?p=simon/puzzles.git>
|
||||
- Shootin Stars (Godot; not in nixpkgs) <https://gitlab.com/greenbeast/shootin-stars>
|
||||
- numberlink (generic name for Flow Free). not packaged in Nix
|
||||
- Neverball (https://neverball.org/screenshots.php). nix: as `neverball`
|
||||
- blurble (https://linuxphoneapps.org/games/app.drey.blurble/). nix: not as of 2024-02-05
|
||||
|
||||
#### moby
|
||||
- fix cpuidle (gets better power consumption): <https://xnux.eu/log/077.html>
|
||||
- SwayNC:
|
||||
- don't show MPRIS if no players detected
|
||||
- this is a problem of playerctld, i guess
|
||||
- add option to change audio output
|
||||
- fix colors (red alert) to match overall theme
|
||||
- extend width to 100% of portrait mode
|
||||
- moby: tune GPS
|
||||
- run only geoclue, and not gpsd, to save power?
|
||||
- tune QGPS setting in eg25-control, for less jitter?
|
||||
@@ -70,17 +89,13 @@
|
||||
- manually do smoothing, as some layer between mepo and geoclue/gpsd?
|
||||
- moby: show battery state on ssh login
|
||||
- moby: improve gPodder launch time
|
||||
- sxmo: port to swaybar like i use on desktop
|
||||
- users in #sxmo claim it's way better perf
|
||||
- sxmo: fix youtube scripts (package youtube-cli)
|
||||
- moby: theme GTK apps (i.e. non-adwaita styles)
|
||||
- combine multiple icon themes to get one which has the full icon set?
|
||||
- get adwaita-icon-theme to ship everything even when cross-compiled?
|
||||
- especially, make the menubar collapsible
|
||||
- try Gradience tool specifically for theming adwaita? <https://linuxphoneapps.org/apps/com.github.gradienceteam.gradience/>
|
||||
- phog: remove the gnome-shell runtime dependency to save hella closure size
|
||||
|
||||
#### non-moby
|
||||
- RSS: integrate a paywall bypass
|
||||
- e.g. self-hosted [ladder](https://github.com/everywall/ladder) (like 12ft.io)
|
||||
- neovim: set up language server (lsp; rnix-lsp; nvim-lspconfig)
|
||||
- Helix: make copy-to-system clipboard be the default
|
||||
- firefox/librewolf: persist history
|
||||
@@ -98,16 +113,13 @@
|
||||
- could change junk filter from "no DKIM success" to explicit "DKIM failed"
|
||||
|
||||
### perf
|
||||
- debug nixos-rebuild times
|
||||
- i bet sane.programs adds a LOT of time, with how it automatically creates an attrs for EVERY package in nixpkgs.
|
||||
- add `pkgs.impure-cached.<foo>` package set to build things with ccache enabled
|
||||
- every package here can be auto-generated, and marked with some env var so that it doesn't pollute the pure package set
|
||||
- would be super handy for package prototyping!
|
||||
- get moby to build without binfmt emulation (i.e. make all emulation explicit)
|
||||
- then i can distribute builds across servo + desko, and also allow servo to pull packages from desko w/o worrying about purity
|
||||
|
||||
|
||||
## NEW FEATURES:
|
||||
- migrate MAME cabinet to nix
|
||||
- boot it from PXE from servo?
|
||||
- deploy to new server, and use it as a remote builder
|
||||
- enable IPv6
|
||||
- package lemonade lemmy app: <https://linuxphoneapps.org/apps/ml.mdwalters.lemonade/>
|
||||
|
BIN
doc/hello.gif
Normal file
BIN
doc/hello.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 127 KiB |
145
flake.lock
generated
145
flake.lock
generated
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1687709756,
|
||||
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||
"lastModified": 1698882062,
|
||||
"narHash": "sha256-HkhafUayIqxXyHH1X8d9RDl1M2CkFgZLjKD3MzabiEo=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "8c9fa2545007b49a5db5f650ae91f227672c3877",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -35,34 +35,104 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"nix-fast-build": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698544399,
|
||||
"narHash": "sha256-vhRmPyEyoPkrXF2iykBsWHA05MIaOSmMRLMF7Hul6+s=",
|
||||
"lastModified": 1703607026,
|
||||
"narHash": "sha256-Emh0BPoqlS4ntp2UJrwydXfIP4qIMF0VBB2FUE3/M/E=",
|
||||
"owner": "Mic92",
|
||||
"repo": "nix-fast-build",
|
||||
"rev": "4376b8a33b217ee2f78ba3dcff01a3e464d13a46",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"repo": "nix-fast-build",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1698890957,
|
||||
"narHash": "sha256-DJ+SppjpPBoJr0Aro9TAcP3sxApCSieY6BYBCoWGUX8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d87c5d8c41c9b3b39592563242f3a448b5cc4bc9",
|
||||
"rev": "c082856b850ec60cda9f0a0db2bc7bd8900d708c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.05",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1698611440,
|
||||
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-next-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1708992120,
|
||||
"narHash": "sha256-t/8QV+lEroW5fK44w5oEUalIM0eYYVGs833AHDCIl4s=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6daf4de0662e1d895d220a4a4ddb356eb000abe9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "staging-next",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1708819810,
|
||||
"narHash": "sha256-1KosU+ZFXf31GPeCBNxobZWMgHsSOJcrSFA6F2jhzdE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "89a2a12e6c8c6a56c72eb3589982c8e2f89c70ea",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "release-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-unpatched": {
|
||||
"locked": {
|
||||
"lastModified": 1698611440,
|
||||
"narHash": "sha256-jPjHjrerhYDy3q9+s5EAsuhyhuknNfowY6yt6pjn9pc=",
|
||||
"lastModified": 1708995544,
|
||||
"narHash": "sha256-YJgLopKOKVTggnKzjX4OiAS22hx/vNv397DcsAyTZgY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0cbe9f69c234a7700596e943bfae7ef27a31b735",
|
||||
"rev": "5bd8df40204f47a12263f3614c72cd5b6832a9a0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"ref": "master",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -70,6 +140,8 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"mobile-nixos": "mobile-nixos",
|
||||
"nix-fast-build": "nix-fast-build",
|
||||
"nixpkgs-next-unpatched": "nixpkgs-next-unpatched",
|
||||
"nixpkgs-unpatched": "nixpkgs-unpatched",
|
||||
"sops-nix": "sops-nix",
|
||||
"uninsane-dot-org": "uninsane-dot-org"
|
||||
@@ -83,11 +155,11 @@
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698548647,
|
||||
"narHash": "sha256-7c03OjBGqnwDW0FBaBc+NjfEBxMkza+dxZGJPyIzfFE=",
|
||||
"lastModified": 1708987867,
|
||||
"narHash": "sha256-k2lDaDWNTU5sBVHanYzjDKVDmk29RHIgdbbXu5sdzBA=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "632c3161a6cc24142c8e3f5529f5d81042571165",
|
||||
"rev": "a1c8de14f60924fafe13aea66b46157f0150f4cf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -96,34 +168,39 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nix-fast-build",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"lastModified": 1698438538,
|
||||
"narHash": "sha256-AWxaKTDL3MtxaVTVU5lYBvSnlspOS0Fjt8GxBgnU0Do=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "5deb8dc125a9f83b65ca86cf0c8167c46593e0b1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"uninsane-dot-org": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-unpatched"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1698634059,
|
||||
"narHash": "sha256-+Oyv6vDyCtBzab/5cTG0nUrHD9gj7KgGfD4D1Rn4fCk=",
|
||||
"lastModified": 1707981105,
|
||||
"narHash": "sha256-YCU1eNslBHabjP+OCY+BxPycEFO9SRUts10MrN9QORE=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "2419750ca98fc04af42c91e50c49a29c68d465d2",
|
||||
"revCount": 210,
|
||||
"rev": "bb10cd8853d05191e4d62947d93687c462e92c30",
|
||||
"revCount": 235,
|
||||
"type": "git",
|
||||
"url": "https://git.uninsane.org/colin/uninsane"
|
||||
},
|
||||
|
379
flake.nix
379
flake.nix
@@ -29,22 +29,24 @@
|
||||
# - daily:
|
||||
# - nixos-unstable cut from master after enough packages have been built in caches.
|
||||
# - every 6 hours:
|
||||
# - master auto-merged into staging.
|
||||
# - master auto-merged into staging and staging-next
|
||||
# - staging-next auto-merged into staging.
|
||||
# - manually, approximately once per month:
|
||||
# - staging-next is cut from staging.
|
||||
# - staging-next merged into master.
|
||||
#
|
||||
# which branch to source from?
|
||||
# - for everyday development, prefer `nixos-unstable` branch, as it provides good caching.
|
||||
# - if need to test bleeding updates (e.g. if submitting code into staging):
|
||||
# - use `staging-next` if it's been cut (i.e. if there's an active staging-next -> master PR)
|
||||
# - use `staging` if no staging-next branch has been cut.
|
||||
# - nixos-unstable: for everyday development; it provides good caching
|
||||
# - master: temporarily if i'm otherwise cherry-picking lots of already-applied patches
|
||||
# - staging-next: if testing stuff that's been PR'd into staging, i.e. base library updates.
|
||||
# - staging: maybe if no staging-next -> master PR has been cut yet?
|
||||
#
|
||||
# <https://github.com/nixos/nixpkgs/tree/nixos-unstable>
|
||||
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=staging";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=master";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging";
|
||||
# nixpkgs-unpatched.url = "github:nixos/nixpkgs?ref=nixos-staging-next";
|
||||
nixpkgs-next-unpatched.url = "github:nixos/nixpkgs?ref=staging-next";
|
||||
|
||||
mobile-nixos = {
|
||||
# <https://github.com/nixos/mobile-nixos>
|
||||
@@ -55,6 +57,10 @@
|
||||
url = "github:nixos/mobile-nixos?ref=d25d3b87e7f300d8066e31d792337d9cd7ecd23b";
|
||||
flake = false;
|
||||
};
|
||||
nix-fast-build = {
|
||||
# https://github.com/Mic92/nix-fast-build
|
||||
url = "github:Mic92/nix-fast-build";
|
||||
};
|
||||
sops-nix = {
|
||||
# <https://github.com/Mic92/sops-nix>
|
||||
# used to distribute secrets to my hosts
|
||||
@@ -73,7 +79,9 @@
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs-unpatched,
|
||||
nixpkgs-next-unpatched ? nixpkgs-unpatched,
|
||||
mobile-nixos,
|
||||
nix-fast-build,
|
||||
sops-nix,
|
||||
uninsane-dot-org,
|
||||
...
|
||||
@@ -91,39 +99,52 @@
|
||||
# rather than apply our nixpkgs patches as a flake input, do that here instead.
|
||||
# this (temporarily?) resolves the bad UX wherein a subflake residing in the same git
|
||||
# repo as the main flake causes the main flake to have an unstable hash.
|
||||
nixpkgs = (import ./nixpatches/flake.nix).outputs {
|
||||
self = nixpkgs;
|
||||
nixpkgs = nixpkgs-unpatched;
|
||||
patchNixpkgs = variant: nixpkgs: (import ./nixpatches/flake.nix).outputs {
|
||||
inherit variant nixpkgs;
|
||||
self = patchNixpkgs variant nixpkgs;
|
||||
} // {
|
||||
# provide values that nixpkgs ordinarily sources from the flake.lock file,
|
||||
# inaccessible to it here because of the import-from-derivation.
|
||||
# rev and shortRev seem to not always exist (e.g. if the working tree is dirty),
|
||||
# so those are made conditional.
|
||||
# sourceInfo includes fields (square brackets for the ones which are not always present):
|
||||
# - [dirtyRev]
|
||||
# - [dirtyShortRev]
|
||||
# - lastModified
|
||||
# - lastModifiedDate
|
||||
# - narHash
|
||||
# - outPath
|
||||
# - [rev]
|
||||
# - [revCount]
|
||||
# - [shortRev]
|
||||
# - submodules
|
||||
#
|
||||
# these values impact the name of a produced nixos system. having date/rev in the
|
||||
# `readlink /run/current-system` store path helps debuggability.
|
||||
inherit (self) lastModifiedDate lastModified;
|
||||
} // optionalAttrs (self ? rev) {
|
||||
inherit (self) rev;
|
||||
} // optionalAttrs (self ? shortRev) {
|
||||
inherit (self) shortRev;
|
||||
};
|
||||
# these values are used within nixpkgs:
|
||||
# - to give a friendly name to the nixos system (`readlink /run/current-system` -> `...nixos-system-desko-24.05.20240227.dirty`)
|
||||
# - to alias `import <nixpkgs>` so that nix uses the system's nixpkgs when called externally (supposedly).
|
||||
#
|
||||
# these values seem to exist both within the `sourceInfo` attrset and at the top-level.
|
||||
# for a list of all implicit flake outputs (which is what these seem to be):
|
||||
# $ nix-repl
|
||||
# > lf .
|
||||
# > <tab>
|
||||
inherit (self) sourceInfo;
|
||||
} // self.sourceInfo;
|
||||
|
||||
nixpkgsCompiledBy = system: nixpkgs.legacyPackages."${system}";
|
||||
nixpkgs' = patchNixpkgs "master" nixpkgs-unpatched;
|
||||
nixpkgsCompiledBy = system: nixpkgs'.legacyPackages."${system}";
|
||||
|
||||
evalHost = { name, local, target }: nixpkgs.lib.nixosSystem {
|
||||
evalHost = { name, local, target, light ? false, nixpkgs ? nixpkgs' }: nixpkgs.lib.nixosSystem {
|
||||
system = target;
|
||||
modules = [
|
||||
{
|
||||
nixpkgs = (if (local != null) then {
|
||||
buildPlatform = local;
|
||||
} else {}) // {
|
||||
# TODO: does the earlier `system` arg to nixosSystem make its way here?
|
||||
hostPlatform.system = target;
|
||||
};
|
||||
# nixpkgs.buildPlatform = local; # set by instantiate.nix instead
|
||||
nixpkgs.buildPlatform.system = local;
|
||||
# nixpkgs.config.replaceStdenv = { pkgs }: pkgs.ccacheStdenv;
|
||||
}
|
||||
(optionalAttrs (local != target) {
|
||||
# XXX(2023/12/11): cache.nixos.org uses `system = ...` instead of `hostPlatform.system`, and that choice impacts the closure of every package.
|
||||
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
|
||||
nixpkgs.hostPlatform.system = target;
|
||||
})
|
||||
(optionalAttrs light {
|
||||
sane.enableSlowPrograms = false;
|
||||
})
|
||||
(import ./hosts/instantiate.nix { hostName = name; })
|
||||
self.nixosModules.default
|
||||
self.nixosModules.passthru
|
||||
@@ -136,39 +157,24 @@
|
||||
];
|
||||
};
|
||||
in {
|
||||
nixosConfigurations =
|
||||
let
|
||||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
# cross-compiled builds: instead of emulating the host, build using a cross-compiler.
|
||||
# - these are faster to *build* than the emulated variants (useful when tweaking packages),
|
||||
# - but fewer of their packages can be found in upstream caches.
|
||||
cross = mapAttrValues evalHost hosts;
|
||||
emulated = mapAttrValues
|
||||
({name, local, target}: evalHost {
|
||||
inherit name target;
|
||||
local = null;
|
||||
})
|
||||
hosts;
|
||||
prefixAttrs = prefix: attrs: mapAttrs'
|
||||
(name: value: {
|
||||
name = prefix + name;
|
||||
inherit value;
|
||||
})
|
||||
attrs;
|
||||
in
|
||||
(prefixAttrs "cross-" cross) //
|
||||
(prefixAttrs "emulated-" emulated) // {
|
||||
# prefer native builds for these machines:
|
||||
inherit (emulated) servo desko lappy rescue;
|
||||
# prefer cross-compiled builds for these machines:
|
||||
inherit (cross) moby;
|
||||
};
|
||||
nixosConfigurations = let
|
||||
hosts = {
|
||||
servo = { name = "servo"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
desko-light = { name = "desko"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
lappy = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
lappy-light = { name = "lappy"; local = "x86_64-linux"; target = "x86_64-linux"; light = true; };
|
||||
moby = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; };
|
||||
moby-light = { name = "moby"; local = "x86_64-linux"; target = "aarch64-linux"; light = true; };
|
||||
rescue = { name = "rescue"; local = "x86_64-linux"; target = "x86_64-linux"; };
|
||||
};
|
||||
hostsNext = mapAttrs' (h: v: {
|
||||
name = "${h}-next";
|
||||
value = v // { nixpkgs = patchNixpkgs "staging-next" nixpkgs-next-unpatched; };
|
||||
}) hosts;
|
||||
in mapAttrValues evalHost (
|
||||
hosts // hostsNext
|
||||
);
|
||||
|
||||
# unofficial output
|
||||
# this produces a EFI-bootable .img file (GPT with a /boot partition and a system (/ or /nix) partition).
|
||||
@@ -187,25 +193,30 @@
|
||||
imgs = mapAttrValues (host: host.config.system.build.img) self.nixosConfigurations;
|
||||
|
||||
# unofficial output
|
||||
hostConfigs = mapAttrValues (host: host.config) self.nixosConfigurations;
|
||||
hostSystems = mapAttrValues (host: host.config.system.build.toplevel) self.nixosConfigurations;
|
||||
hostPkgs = mapAttrValues (host: host.config.system.build.pkgs) self.nixosConfigurations;
|
||||
hostPrograms = mapAttrValues (host: mapAttrValues (p: p.package) host.config.sane.programs) self.nixosConfigurations;
|
||||
|
||||
patched.nixpkgs = nixpkgs';
|
||||
|
||||
overlays = {
|
||||
# N.B.: `nix flake check` requires every overlay to take `final: prev:` at defn site,
|
||||
# hence the weird redundancy.
|
||||
default = final: prev: self.overlays.pkgs final prev;
|
||||
sane-all = final: prev: import ./overlays/all.nix final prev;
|
||||
disable-flakey-tests = final: prev: import ./overlays/disable-flakey-tests.nix final prev;
|
||||
pkgs = final: prev: import ./overlays/pkgs.nix final prev;
|
||||
pins = final: prev: import ./overlays/pins.nix final prev;
|
||||
preferences = final: prev: import ./overlays/preferences.nix final prev;
|
||||
optimizations = final: prev: import ./overlays/optimizations.nix final prev;
|
||||
passthru = final: prev:
|
||||
let
|
||||
mobile = (import "${mobile-nixos}/overlay/overlay.nix");
|
||||
uninsane = uninsane-dot-org.overlay;
|
||||
uninsane = uninsane-dot-org.overlays.default;
|
||||
# TODO: why do i have to use `self.inputs.nix-fast-build` instead of just `nix-fast-build` here?
|
||||
nix-fast-build = (_: prev: self.inputs.nix-fast-build.packages."${prev.stdenv.system}" or {});
|
||||
in
|
||||
(mobile final prev)
|
||||
// (nix-fast-build final prev)
|
||||
// (uninsane final prev)
|
||||
;
|
||||
};
|
||||
@@ -234,23 +245,27 @@
|
||||
# extract only our own packages from the full set.
|
||||
# because of `nix flake check`, we flatten the package set and only surface x86_64-linux packages.
|
||||
packages = mapAttrs
|
||||
(system: allPkgs:
|
||||
allPkgs.lib.filterAttrs (name: pkg:
|
||||
(system: passthruPkgs: passthruPkgs.lib.filterAttrs
|
||||
(name: pkg:
|
||||
# keep only packages which will pass `nix flake check`, i.e. keep only:
|
||||
# - derivations (not package sets)
|
||||
# - packages that build for the given platform
|
||||
(! elem name [ "feeds" "pythonPackagesExtensions" ])
|
||||
&& (allPkgs.lib.meta.availableOn allPkgs.stdenv.hostPlatform pkg)
|
||||
&& (passthruPkgs.lib.meta.availableOn passthruPkgs.stdenv.hostPlatform pkg)
|
||||
)
|
||||
(
|
||||
# expose sane packages and chosen inputs (uninsane.org)
|
||||
(import ./pkgs { pkgs = allPkgs; }) // {
|
||||
inherit (allPkgs) uninsane-dot-org;
|
||||
(import ./pkgs { pkgs = passthruPkgs; }) // {
|
||||
inherit (passthruPkgs) uninsane-dot-org;
|
||||
}
|
||||
)
|
||||
)
|
||||
# self.legacyPackages;
|
||||
{ inherit (self.legacyPackages) x86_64-linux; }
|
||||
{
|
||||
x86_64-linux = (nixpkgsCompiledBy "x86_64-linux").appendOverlays [
|
||||
self.overlays.passthru
|
||||
];
|
||||
}
|
||||
;
|
||||
|
||||
apps."x86_64-linux" =
|
||||
@@ -258,17 +273,52 @@
|
||||
pkgs = self.legacyPackages."x86_64-linux";
|
||||
sanePkgs = import ./pkgs { inherit pkgs; };
|
||||
deployScript = host: addr: action: pkgs.writeShellScript "deploy-${host}" ''
|
||||
nix build '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} $@
|
||||
sudo nix sign-paths -r -k /run/secrets/nix_serve_privkey $(readlink ./result-${host})
|
||||
host="${host}"
|
||||
addr="${addr}"
|
||||
action="${if action != null then action else ""}"
|
||||
runOnTarget() {
|
||||
# run the command ($@) on the machine we're deploying to.
|
||||
# if that's a remote machine, then do it via ssh, else local shell.
|
||||
if [ -n "$addr" ]; then
|
||||
ssh "$addr" "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# XXX: this triggers another config eval & (potentially) build.
|
||||
# if the config changed between these invocations, the above signatures might not apply to the deployed config.
|
||||
# let the user handle that edge case by re-running this whole command
|
||||
nixos-rebuild --flake '.#${host}' ${action} --target-host colin@${addr} --use-remote-sudo $@
|
||||
nix build ".#nixosConfigurations.$host.config.system.build.toplevel" --out-link "./result-$host" "$@"
|
||||
storePath="$(readlink ./result-$host)"
|
||||
|
||||
# mimic `nixos-rebuild --target-host`, in effect:
|
||||
# - nix-copy-closure ...
|
||||
# - nix-env --set ...
|
||||
# - switch-to-configuration <boot|dry-activate|switch|test|>
|
||||
# avoid the actual `nixos-rebuild` for a few reasons:
|
||||
# - fewer nix evals
|
||||
# - more introspectability and debuggability
|
||||
# - sandbox friendliness (especially: `git` doesn't have to be run as root)
|
||||
|
||||
if [ -n "$addr" ]; then
|
||||
sudo nix store sign -r -k /run/secrets/nix_serve_privkey "$storePath"
|
||||
# add more `-v` for more verbosity (up to 5).
|
||||
# builders-use-substitutes false: optimizes so that the remote machine doesn't try to get paths from its substituters.
|
||||
# we already have all paths here, and the remote substitution is slow to check and SERIOUSLY flaky on moby in particular.
|
||||
nix copy -vv --option builders-use-substitutes false --to "ssh-ng://$addr" "$storePath"
|
||||
fi
|
||||
|
||||
if [ -n "$action" ]; then
|
||||
runOnTarget sudo nix-env -p /nix/var/nix/profiles/system --set "$storePath"
|
||||
runOnTarget sudo "$storePath/bin/switch-to-configuration" "$action"
|
||||
fi
|
||||
'';
|
||||
deployApp = host: addr: action: {
|
||||
type = "app";
|
||||
program = ''${deployScript host addr action}'';
|
||||
};
|
||||
|
||||
# pkg updating.
|
||||
# a cleaner alternative lives here: <https://discourse.nixos.org/t/how-can-i-run-the-updatescript-of-personal-packages/25274/2>
|
||||
# mkUpdater :: [ String ] -> { type = "app"; program = path; }
|
||||
mkUpdater = attrPath: {
|
||||
type = "app";
|
||||
program = let
|
||||
@@ -293,7 +343,7 @@
|
||||
} else {}
|
||||
)
|
||||
(pkgs.lib.getAttrFromPath basePath sanePkgs);
|
||||
mkUpdaters = { ignore ? [] }@opts: basePath:
|
||||
mkUpdaters = { ignore ? [], flakePrefix ? [] }@opts: basePath:
|
||||
let
|
||||
updaters = mkUpdatersNoAliases opts basePath;
|
||||
invokeUpdater = name: pkg:
|
||||
@@ -303,7 +353,7 @@
|
||||
|
||||
# in case `name` has a `.` in it, we have to quote it
|
||||
escapedPath = builtins.map (p: ''"${p}"'') fullPath;
|
||||
updatePath = builtins.concatStringsSep "." ([ "update" "pkgs" ] ++ escapedPath);
|
||||
updatePath = builtins.concatStringsSep "." (flakePrefix ++ escapedPath);
|
||||
in pkgs.lib.optionalString doUpdateByDefault (
|
||||
pkgs.lib.escapeShellArgs [
|
||||
"nix" "run" ".#${updatePath}"
|
||||
@@ -311,8 +361,9 @@
|
||||
);
|
||||
in {
|
||||
type = "app";
|
||||
# top-level app just invokes the updater of everything one layer below it
|
||||
program = builtins.toString (pkgs.writeShellScript
|
||||
(builtins.concatStringsSep "-" (["update"] ++ basePath))
|
||||
(builtins.concatStringsSep "-" (flakePrefix ++ basePath))
|
||||
(builtins.concatStringsSep
|
||||
"\n"
|
||||
(pkgs.lib.mapAttrsToList invokeUpdater updaters)
|
||||
@@ -332,7 +383,11 @@
|
||||
- `nix run '.#update.feeds'`
|
||||
- updates metadata for all feeds
|
||||
- `nix run '.#init-feed' <url>`
|
||||
- `nix run '.#deploy-{lappy,moby,moby-test,servo}' [nixos-rebuild args ...]`
|
||||
- `nix run '.#deploy.{desko,lappy,moby,servo}[-light|-test]' [nix args ...]`
|
||||
- build and deploy the host
|
||||
- `nix run '.#preDeploy.{desko,lappy,moby,servo}[-light]' [nix args ...]`
|
||||
- copy closures to a host, but don't activate it
|
||||
- or `nix run '.#preDeploy'` to target all hosts
|
||||
- `nix run '.#check'`
|
||||
- make sure all systems build; NUR evaluates
|
||||
|
||||
@@ -346,48 +401,107 @@
|
||||
nix flake show --option allow-import-from-derivation true
|
||||
'');
|
||||
};
|
||||
update.pkgs = mkUpdaters { ignore = [ ["feeds"] ]; } [];
|
||||
update.feeds = mkUpdaters {} [ "feeds" ];
|
||||
# wrangle some names to get package updaters which refer back into the flake, but also conditionally ignore certain paths (e.g. sane.feeds).
|
||||
# TODO: better design
|
||||
update = rec {
|
||||
_impl.pkgs.sane = mkUpdaters { flakePrefix = [ "update" "_impl" "pkgs" ]; ignore = [ [ "sane" "feeds" ] ]; } [ "sane" ];
|
||||
pkgs = _impl.pkgs.sane;
|
||||
_impl.feeds.sane.feeds = mkUpdaters { flakePrefix = [ "update" "_impl" "feeds" ]; } [ "sane" "feeds" ];
|
||||
feeds = _impl.feeds.sane.feeds;
|
||||
};
|
||||
|
||||
init-feed = {
|
||||
type = "app";
|
||||
program = "${pkgs.feeds.init-feed}";
|
||||
};
|
||||
|
||||
deploy-lappy = {
|
||||
type = "app";
|
||||
program = ''${deployScript "lappy" "lappy" "switch"}'';
|
||||
};
|
||||
deploy-moby-test = {
|
||||
type = "app";
|
||||
program = ''${deployScript "moby" "moby-hn" "test"}'';
|
||||
};
|
||||
deploy-moby = {
|
||||
type = "app";
|
||||
program = ''${deployScript "moby" "moby-hn" "switch"}'';
|
||||
};
|
||||
deploy-servo = {
|
||||
type = "app";
|
||||
program = ''${deployScript "servo" "servo" "switch"}'';
|
||||
};
|
||||
deploy = {
|
||||
desko = deployApp "desko" "desko" "switch";
|
||||
desko-light = deployApp "desko-light" "desko" "switch";
|
||||
lappy = deployApp "lappy" "lappy" "switch";
|
||||
lappy-light = deployApp "lappy-light" "lappy" "switch";
|
||||
moby = deployApp "moby" "moby" "switch";
|
||||
moby-light = deployApp "moby-light" "moby" "switch";
|
||||
moby-test = deployApp "moby" "moby" "test";
|
||||
servo = deployApp "servo" "servo" "switch";
|
||||
|
||||
# like `nixos-rebuild --flake . switch`
|
||||
self = deployApp "$(hostname)" "" "switch";
|
||||
self-light = deployApp "$(hostname)-light" "" "switch";
|
||||
|
||||
sync-moby = {
|
||||
# copy music from the current device to moby
|
||||
# TODO: should i actually sync from /mnt/servo-media/Music instead of the local drive?
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
||||
sudo mount /mnt/moby-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music ~/Music /mnt/moby-home/Music
|
||||
program = builtins.toString (pkgs.writeShellScript "deploy-all" ''
|
||||
nix run '.#deploy.lappy'
|
||||
nix run '.#deploy.moby'
|
||||
nix run '.#deploy.desko'
|
||||
nix run '.#deploy.servo'
|
||||
'');
|
||||
};
|
||||
preDeploy = {
|
||||
# build the host and copy the runtime closure to that host, but don't activate it.
|
||||
desko = deployApp "desko" "desko" null;
|
||||
desko-light = deployApp "desko-light" "desko" null;
|
||||
lappy = deployApp "lappy" "lappy" null;
|
||||
lappy-light = deployApp "lappy-light" "lappy" null;
|
||||
moby = deployApp "moby" "moby" null;
|
||||
moby-light = deployApp "moby-light" "moby" null;
|
||||
servo = deployApp "servo" "servo" null;
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "predeploy-all" ''
|
||||
# copy the -light variants first; this might be run while waiting on a full build. or the full build failed.
|
||||
nix run '.#preDeploy.moby-light' -- "$@"
|
||||
nix run '.#preDeploy.lappy-light' -- "$@"
|
||||
nix run '.#preDeploy.desko-light' -- "$@"
|
||||
nix run '.#preDeploy.lappy' -- "$@"
|
||||
nix run '.#preDeploy.servo' -- "$@"
|
||||
nix run '.#preDeploy.moby' -- "$@"
|
||||
nix run '.#preDeploy.desko' -- "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
sync-lappy = {
|
||||
sync = {
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-all" ''
|
||||
RC_lappy=$(nix run '.#sync.lappy' -- "$@")
|
||||
RC_moby=$(nix run '.#sync.moby' -- "$@")
|
||||
RC_desko=$(nix run '.#sync.desko' -- "$@")
|
||||
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "moby: $RC_moby"
|
||||
echo "desko: $RC_desko"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.desko = {
|
||||
# copy music from servo to desko
|
||||
# can run this from any device that has ssh access to desko and servo
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-desko" ''
|
||||
sudo mount /mnt/desko/home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compat /mnt/servo/media/Music /mnt/desko/home/Music "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.lappy = {
|
||||
# copy music from servo to lappy
|
||||
# can run this from any device that has ssh access to lappy
|
||||
# can run this from any device that has ssh access to lappy and servo
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-lappy" ''
|
||||
sudo mount /mnt/lappy-home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music /mnt/servo-media/Music /mnt/lappy-home/Music
|
||||
sudo mount /mnt/lappy/home
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat /mnt/servo/media/Music /mnt/lappy/home/Music "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
sync.moby = {
|
||||
# copy music from servo to moby
|
||||
# can run this from any device that has ssh access to moby and servo
|
||||
type = "app";
|
||||
program = builtins.toString (pkgs.writeShellScript "sync-to-moby" ''
|
||||
sudo mount /mnt/moby/home
|
||||
sudo mount /mnt/desko/home
|
||||
${pkgs.rsync}/bin/rsync -arv --exclude servo-macros /mnt/moby/home/Pictures/ /mnt/desko/home/Pictures/moby/
|
||||
# N.B.: limited by network/disk -> reduce job count to improve pause/resume behavior
|
||||
${pkgs.sane-scripts.sync-music}/bin/sane-sync-music --compress --compat --jobs 4 /mnt/servo/media/Music /mnt/moby/home/Music "$@"
|
||||
'');
|
||||
};
|
||||
|
||||
@@ -396,12 +510,12 @@
|
||||
program = builtins.toString (pkgs.writeShellScript "check-all" ''
|
||||
nix run '.#check.nur'
|
||||
RC0=$?
|
||||
nix run '.#check.host-configs'
|
||||
nix run '.#check.hostConfigs'
|
||||
RC1=$?
|
||||
nix run '.#check.rescue'
|
||||
RC2=$?
|
||||
echo "nur: $RC0"
|
||||
echo "host-configs: $RC1"
|
||||
echo "hostConfigs: $RC1"
|
||||
echo "rescue: $RC2"
|
||||
exit $(($RC0 | $RC1 | $RC2))
|
||||
'');
|
||||
@@ -418,32 +532,59 @@
|
||||
--option restrict-eval true \
|
||||
--option allow-import-from-derivation true \
|
||||
--drv-path --show-trace \
|
||||
-I nixpkgs=$(nix-instantiate --find-file nixpkgs) \
|
||||
-I nixpkgs=${nixpkgs-unpatched} \
|
||||
-I ../../ \
|
||||
| tee # tee to prevent interactive mode
|
||||
'');
|
||||
};
|
||||
|
||||
check.host-configs = {
|
||||
check.hostConfigs = {
|
||||
type = "app";
|
||||
program = let
|
||||
checkHost = host: ''
|
||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 $@
|
||||
RC_${host}=$?
|
||||
checkHost = host: let
|
||||
shellHost = pkgs.lib.replaceStrings [ "-" ] [ "_" ] host;
|
||||
in ''
|
||||
nix build -v '.#nixosConfigurations.${host}.config.system.build.toplevel' --out-link ./result-${host} -j2 "$@"
|
||||
RC_${shellHost}=$?
|
||||
'';
|
||||
in builtins.toString (pkgs.writeShellScript
|
||||
"check-host-configs"
|
||||
''
|
||||
# build minimally-usable hosts first, then their full image.
|
||||
# this gives me a minimal image i can deploy or copy over, early.
|
||||
${checkHost "desko-light"}
|
||||
${checkHost "moby-light"}
|
||||
${checkHost "lappy-light"}
|
||||
|
||||
${checkHost "desko"}
|
||||
${checkHost "lappy"}
|
||||
${checkHost "servo"}
|
||||
${checkHost "moby"}
|
||||
${checkHost "rescue"}
|
||||
|
||||
# still want to build the -light variants first so as to avoid multiple simultaneous webkitgtk builds
|
||||
${checkHost "desko-light-next"}
|
||||
${checkHost "moby-light-next"}
|
||||
|
||||
${checkHost "desko-next"}
|
||||
${checkHost "lappy-next"}
|
||||
${checkHost "servo-next"}
|
||||
${checkHost "moby-next"}
|
||||
${checkHost "rescue-next"}
|
||||
|
||||
echo "desko: $RC_desko"
|
||||
echo "lappy: $RC_lappy"
|
||||
echo "servo: $RC_servo"
|
||||
echo "moby: $RC_moby"
|
||||
echo "rescue: $RC_rescue"
|
||||
|
||||
echo "desko-next: $RC_desko_next"
|
||||
echo "lappy-next: $RC_lappy_next"
|
||||
echo "servo-next: $RC_servo_next"
|
||||
echo "moby-next: $RC_moby_next"
|
||||
echo "rescue-next: $RC_rescue_next"
|
||||
|
||||
# i don't really care if the -next hosts fail. i build them mostly to keep the cache fresh/ready
|
||||
exit $(($RC_desko | $RC_lappy | $RC_servo | $RC_moby | $RC_rescue))
|
||||
''
|
||||
);
|
||||
|
@@ -9,25 +9,34 @@
|
||||
# services.distccd.enable = true;
|
||||
# sane.programs.distcc.enableFor.user.guest = true;
|
||||
|
||||
# TODO: remove emulation, but need to fix nixos-rebuild to moby for that.
|
||||
# sane.roles.build-machine.emulation = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.ports.openFirewall = true; # for e.g. nix-serve
|
||||
|
||||
sane.roles.build-machine.enable = true;
|
||||
sane.roles.ac = true;
|
||||
sane.roles.client = true;
|
||||
sane.roles.dev-machine = true;
|
||||
sane.roles.pc = true;
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."desko".wg-home.ip;
|
||||
sane.services.duplicity.enable = true;
|
||||
sane.services.nixserve.secretKeyFile = config.sops.secrets.nix_serve_privkey.path;
|
||||
|
||||
sane.gui.sway.enable = true;
|
||||
sane.nixcache.substituters.desko = false;
|
||||
sane.nixcache.remote-builders.desko = false;
|
||||
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
sane.programs.iphoneUtils.enableFor.user.colin = true;
|
||||
sane.programs.steam.enableFor.user.colin = true;
|
||||
|
||||
sane.programs.guiApps.suggestedPrograms = [ "desktopGuiApps" ];
|
||||
sane.programs.consoleUtils.suggestedPrograms = [ "consoleMediaUtils" "desktopConsoleUtils" ];
|
||||
# sane.programs.devPkgs.enableFor.user.colin = true;
|
||||
|
||||
sane.programs."gnome.geary".config.autostart = true;
|
||||
sane.programs.signal-desktop.config.autostart = true;
|
||||
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
|
@@ -1,14 +1,12 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||
# a cross-compiled kernel, particularly, will easily use 30+GB of tmp
|
||||
fileSystems."/tmp".options = [ "size=64G" ];
|
||||
|
||||
fileSystems."/nix" = {
|
||||
# device = "/dev/disk/by-uuid/985a0a32-da52-4043-9df7-615adec2e4ff";
|
||||
device = "/dev/disk/by-uuid/0ab0770b-7734-4167-88d9-6e4e20bb2a56";
|
||||
device = "/dev/disk/by-uuid/845d85bf-761d-431b-a406-e6f20909154f";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
"compress=zstd"
|
||||
@@ -17,8 +15,7 @@
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
# device = "/dev/disk/by-uuid/CAA7-E7D2";
|
||||
device = "/dev/disk/by-uuid/41B6-BAEF";
|
||||
device = "/dev/disk/by-uuid/5049-9AFD";
|
||||
fsType = "vfat";
|
||||
};
|
||||
}
|
||||
|
@@ -7,19 +7,18 @@
|
||||
|
||||
sane.roles.client = true;
|
||||
sane.roles.dev-machine = true;
|
||||
sane.roles.pc = true;
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."lappy".wg-home.ip;
|
||||
|
||||
# sane.guest.enable = true;
|
||||
sane.gui.sway.enable = true;
|
||||
boot.loader.efi.canTouchEfiVariables = false;
|
||||
sane.image.extraBootFiles = [ pkgs.bootpart-uefi-x86_64 ];
|
||||
|
||||
sane.programs.guiApps.suggestedPrograms = [
|
||||
"desktopGuiApps"
|
||||
"stepmania"
|
||||
];
|
||||
sane.programs.consoleUtils.suggestedPrograms = [ "consoleMediaUtils" "desktopConsoleUtils" ];
|
||||
sane.programs.sway.enableFor.user.colin = true;
|
||||
sane.programs."gnome.geary".config.autostart = true;
|
||||
sane.programs.signal-desktop.config.autostart = true;
|
||||
sane.programs.stepmania.enableFor.user.colin = true;
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
|
@@ -1,8 +1,6 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/75230e56-2c69-4e41-b03e-68475f119980";
|
||||
fsType = "btrfs";
|
||||
@@ -16,24 +14,4 @@
|
||||
device = "/dev/disk/by-uuid/BD79-D6BB";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
# fileSystems."/nix" = {
|
||||
# device = "/dev/disk/by-uuid/5a7fa69c-9394-8144-a74c-6726048b129f";
|
||||
# fsType = "btrfs";
|
||||
# };
|
||||
|
||||
# fileSystems."/boot" = {
|
||||
# device = "/dev/disk/by-uuid/4302-1685";
|
||||
# fsType = "vfat";
|
||||
# };
|
||||
|
||||
# fileSystems."/" = {
|
||||
# device = "none";
|
||||
# fsType = "tmpfs";
|
||||
# options = [
|
||||
# "mode=755"
|
||||
# "size=1G"
|
||||
# "defaults"
|
||||
# ];
|
||||
# };
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.gui.sxmo = {
|
||||
greeter = "greetd-sway-gtkgreet";
|
||||
noidle = true; #< power button requires 1s hold, which makes it impractical to be dealing with.
|
||||
settings = {
|
||||
# XXX: make sure the user is part of the `input` group!
|
||||
@@ -22,7 +21,6 @@
|
||||
# the device type informs (at least):
|
||||
# - SXMO_WIFI_MODULE
|
||||
# - SXMO_RTW_SCAN_INTERVAL
|
||||
# - SXMO_SYS_FILES
|
||||
# - SXMO_TOUCHSCREEN_ID
|
||||
# - SXMO_MONITOR
|
||||
# - SXMO_ALSA_CONTROL_NAME
|
||||
@@ -33,7 +31,7 @@
|
||||
# and so it just wouldn't handle any button inputs (sxmo_hook_inputhandler.sh not on path)
|
||||
SXMO_DEVICE_NAME = "three_button_touchscreen";
|
||||
};
|
||||
package = (pkgs.sxmo-utils-latest.override { preferSystemd = true; }).overrideAttrs (base: {
|
||||
package = (pkgs.sxmo-utils.override { preferSystemd = true; }).overrideAttrs (base: {
|
||||
postPatch = (base.postPatch or "") + ''
|
||||
# after volume-button navigation mode, restore full keyboard functionality
|
||||
cp ${./xkb_mobile_normal_buttons} ./configs/xkb/xkb_mobile_normal_buttons
|
||||
|
@@ -20,27 +20,28 @@
|
||||
];
|
||||
|
||||
sane.roles.client = true;
|
||||
sane.roles.handheld = true;
|
||||
sane.zsh.showDeadlines = false; # unlikely to act on them when in shell
|
||||
sane.services.wg-home.enable = true;
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."moby".wg-home.ip;
|
||||
|
||||
# for some reason desko -> moby deploys are super flaky when desko is also a nixcache (not true of desko -> lappy deploys, though!)
|
||||
# > unable to download 'http://desko:5001/<hash>.narinfo': Server returned nothing (no headers, no data) (52)
|
||||
sane.nixcache.substituters.desko = false;
|
||||
|
||||
# XXX colin: phosh doesn't work well with passwordless login,
|
||||
# so set this more reliable default password should anything go wrong
|
||||
users.users.colin.initialPassword = "147147";
|
||||
services.getty.autologinUser = "root"; # allows for emergency maintenance?
|
||||
# services.getty.autologinUser = "root"; # allows for emergency maintenance?
|
||||
|
||||
sops.secrets.colin-passwd.neededForUsers = true;
|
||||
|
||||
sane.gui.sxmo.enable = true;
|
||||
sane.programs.guiApps.suggestedPrograms = [ "handheldGuiApps" ];
|
||||
# sane.programs.consoleUtils.enableFor.user.colin = false;
|
||||
# sane.programs.guiApps.enableFor.user.colin = false;
|
||||
sane.programs.blueberry.enableFor.user.colin = false; # bluetooth manager: doesn't cross compile!
|
||||
sane.programs.mercurial.enableFor.user.colin = false; # does not cross compile
|
||||
sane.programs.sequoia.enableFor.user.colin = false;
|
||||
sane.programs.tuiApps.enableFor.user.colin = false; # visidata, others, don't compile well
|
||||
# disabled for faster deploys
|
||||
sane.programs.soundconverter.enableFor.user.colin = false;
|
||||
sane.programs.nvme-cli.enableFor.system = false; # does not cross compile (libhugetlbfs)
|
||||
|
||||
# enabled for easier debugging
|
||||
sane.programs.eg25-control.enableFor.user.colin = true;
|
||||
@@ -48,6 +49,8 @@
|
||||
|
||||
# sane.programs.ntfy-sh.config.autostart = true;
|
||||
sane.programs.dino.config.autostart = true;
|
||||
# sane.programs.signal-desktop.config.autostart = true; # TODO: enable once electron stops derping.
|
||||
# sane.programs."gnome.geary".config.autostart = true;
|
||||
# sane.programs.calls.config.autostart = true;
|
||||
sane.programs.mpv.config.vo = "wlshm"; #< see hosts/common/programs/mpv.nix for details
|
||||
|
||||
@@ -55,7 +58,6 @@
|
||||
# HACK/TODO: make `programs.P.env.VAR` behave according to `mime.priority`
|
||||
sane.programs.firefox.env = lib.mkForce {};
|
||||
sane.programs.epiphany.env.BROWSER = "epiphany";
|
||||
sane.programs.firefox.enableFor.user.colin = false; # use epiphany instead
|
||||
|
||||
# note the .conf.d approach: using ~/.config/pipewire/pipewire.conf directly breaks all audio,
|
||||
# presumably because that deletes the defaults entirely whereas the .conf.d approach selectively overrides defaults
|
||||
|
@@ -1,7 +1,6 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/1f1271f8-53ce-4081-8a29-60a4a6b5d6f9";
|
||||
fsType = "btrfs";
|
||||
|
@@ -74,6 +74,7 @@ in
|
||||
# without this some GUI apps fail: `DRM_IOCTL_MODE_CREATE_DUMB failed: Cannot allocate memory`
|
||||
# this is because they can't allocate enough video ram.
|
||||
# see related nixpkgs issue: <https://github.com/NixOS/nixpkgs/issues/260222>
|
||||
# TODO(2023/12/03): remove once mesa 23.3.1 lands: <https://github.com/NixOS/nixpkgs/pull/265740>
|
||||
#
|
||||
# the default CMA seems to be 32M.
|
||||
# i was running fine with 256MB from 2022/07-ish through 2022/12-ish, but then the phone quit reliably coming back from sleep (phosh): maybe a memory leak?
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
fileSystems."/" = {
|
||||
fileSystems."/nix" = {
|
||||
device = "/dev/disk/by-uuid/44445555-6666-7777-8888-999900001111";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
@@ -14,12 +14,11 @@
|
||||
signaldctl.enableFor.user.colin = true;
|
||||
};
|
||||
|
||||
sane.roles.ac = true;
|
||||
sane.roles.build-machine.enable = true;
|
||||
sane.roles.build-machine.emulation = false;
|
||||
sane.zsh.showDeadlines = false; # ~/knowledge doesn't always exist
|
||||
sane.programs.consoleUtils.suggestedPrograms = [
|
||||
"desktopConsoleUtils"
|
||||
"consoleMediaUtils" # notably, for go2tv / casting
|
||||
"pcConsoleUtils"
|
||||
"sane-scripts.stop-all-servo"
|
||||
];
|
||||
sane.services.dyn-dns.enable = true;
|
||||
@@ -30,6 +29,8 @@
|
||||
sane.services.wg-home.ip = config.sane.hosts.by-name."servo".wg-home.ip;
|
||||
sane.nixcache.substituters.servo = false;
|
||||
sane.nixcache.substituters.desko = false;
|
||||
sane.nixcache.remote-builders.desko = false;
|
||||
sane.nixcache.remote-builders.servo = false;
|
||||
# sane.services.duplicity.enable = true; # TODO: re-enable after HW upgrade
|
||||
|
||||
# automatically log in at the virtual consoles.
|
||||
|
@@ -1,7 +1,58 @@
|
||||
# zfs docs:
|
||||
# - <https://nixos.wiki/wiki/ZFS>
|
||||
# - <repo:nixos/nixpkgs:nixos/modules/tasks/filesystems/zfs.nix>
|
||||
#
|
||||
# zfs check health: `zpool status`
|
||||
#
|
||||
# zfs pool creation (requires `boot.supportedFilesystems = [ "zfs" ];`
|
||||
# - 1. identify disk IDs: `ls -l /dev/disk/by-id`
|
||||
# - 2. pool these disks: `zpool create -f -m legacy pool raidz ata-ST4000VN008-2DR166_WDH0VB45 ata-ST4000VN008-2DR166_WDH17616 ata-ST4000VN008-2DR166_WDH0VC8Q ata-ST4000VN008-2DR166_WDH17680`
|
||||
# - legacy documented: <https://superuser.com/questions/790036/what-is-a-zfs-legacy-mount-point>
|
||||
#
|
||||
# import pools: `zpool import pool`
|
||||
# show zfs datasets: `zfs list` (will be empty if haven't imported)
|
||||
# show zfs properties (e.g. compression): `zfs get all pool`
|
||||
# set zfs properties: `zfs set compression=on pool`
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.root-on-tmpfs = true;
|
||||
# hostId: not used for anything except zfs guardrail?
|
||||
# [hex(ord(x)) for x in 'serv']
|
||||
networking.hostId = "73657276";
|
||||
boot.supportedFilesystems = [ "zfs" ];
|
||||
# boot.zfs.enabled = true;
|
||||
boot.zfs.forceImportRoot = false;
|
||||
# scrub all zfs pools weekly:
|
||||
services.zfs.autoScrub.enable = true;
|
||||
boot.extraModprobeConfig = ''
|
||||
# ZFS likes to use half the ram for its own cache and let the kernel push everything else to swap.
|
||||
# so, reduce its cache size
|
||||
# see: <https://askubuntu.com/a/1290387>
|
||||
# see: <https://serverfault.com/a/1119083>
|
||||
# see: <https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max>
|
||||
# for all tunables, see: `man 4 zfs`
|
||||
# to update these parameters without rebooting:
|
||||
# - `echo '4294967296' | sane-sudo-redirect /sys/module/zfs/parameters/zfs_arc_max`
|
||||
options zfs zfs_arc_max=4294967296
|
||||
'';
|
||||
# to be able to mount the pool like this, make sure to tell zfs to NOT manage it itself.
|
||||
# otherwise local-fs.target will FAIL and you will be dropped into a rescue shell.
|
||||
# - `zfs set mountpoint=legacy pool`
|
||||
# if done correctly, the pool can be mounted before this `fileSystems` entry is created:
|
||||
# - `sudo mount -t zfs pool /mnt/persist/pool`
|
||||
fileSystems."/mnt/pool" = {
|
||||
device = "pool";
|
||||
fsType = "zfs";
|
||||
};
|
||||
# services.zfs.zed = ... # TODO: zfs can send me emails when disks fail
|
||||
sane.programs.sysadminUtils.suggestedPrograms = [ "zfs" ];
|
||||
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/pool/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
defaultMethod = "bind"; #< TODO: change to "symlink"?
|
||||
};
|
||||
|
||||
# increase /tmp space (defaults to 50% of RAM) for building large nix things.
|
||||
# even the stock `nixpkgs.linux` consumes > 16 GB of tmp
|
||||
fileSystems."/tmp".options = [ "size=32G" ];
|
||||
@@ -21,7 +72,7 @@
|
||||
};
|
||||
|
||||
# slow, external storage (for archiving, etc)
|
||||
fileSystems."/mnt/persist/ext" = {
|
||||
fileSystems."/mnt/usb-hdd" = {
|
||||
device = "/dev/disk/by-uuid/aa272cff-0fcc-498e-a4cb-0d95fb60631b";
|
||||
fsType = "btrfs";
|
||||
options = [
|
||||
@@ -29,22 +80,18 @@
|
||||
"defaults"
|
||||
];
|
||||
};
|
||||
|
||||
sane.persist.stores."ext" = {
|
||||
origin = "/mnt/persist/ext/persist";
|
||||
storeDescription = "external HDD storage";
|
||||
};
|
||||
sane.fs."/mnt/persist/ext".mount = {};
|
||||
sane.fs."/mnt/usb-hdd".mount = {};
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: this is overly broad; only need media and share directories to be persisted
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; }
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/uninsane"; method = "bind"; }
|
||||
];
|
||||
# force some problematic directories to always get correct permissions:
|
||||
sane.fs."/var/lib/uninsane/media".dir.acl = {
|
||||
user = "colin"; group = "media"; mode = "0775";
|
||||
};
|
||||
sane.fs."/var/lib/uninsane/media/archive".dir = {};
|
||||
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
|
||||
sane.fs."/var/lib/uninsane/media/archive/README.md".file.text = ''
|
||||
this directory is for media i wish to remove from my library,
|
||||
but keep for a short time in case i reverse my decision.
|
||||
@@ -63,13 +110,14 @@
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Film".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Shows".dir = {};
|
||||
sane.fs."/var/lib/uninsane/media/Videos/Talks".dir = {};
|
||||
# this is file.text instead of symlink.text so that it may be read over a remote mount (where consumers might not have any /nix/store/.../README.md path)
|
||||
sane.fs."/var/lib/uninsane/datasets/README.md".file.text = ''
|
||||
this directory may seem redundant with ../media/datasets. it isn't.
|
||||
this directory exists on SSD, allowing for speedy access to specific datasets when necessary.
|
||||
the contents should be a subset of what's in ../media/datasets.
|
||||
'';
|
||||
# make sure large media is stored to the HDD
|
||||
sane.persist.sys.ext = [
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{
|
||||
user = "colin";
|
||||
group = "users";
|
||||
|
@@ -24,61 +24,12 @@ in
|
||||
sane.ports.openFirewall = true;
|
||||
sane.ports.openUpnp = true;
|
||||
|
||||
# view refused packets with: `sudo journalctl -k`
|
||||
# networking.firewall.logRefusedPackets = true;
|
||||
|
||||
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
|
||||
# Per-interface useDHCP will be mandatory in the future, so this generated config
|
||||
# replicates the default behaviour.
|
||||
networking.useDHCP = false;
|
||||
networking.interfaces.eth0.useDHCP = true;
|
||||
# XXX colin: probably don't need this. wlan0 won't be populated unless i touch a value in networking.interfaces.wlan0
|
||||
networking.wireless.enable = false;
|
||||
|
||||
# this is needed to forward packets from the VPN to the host
|
||||
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
|
||||
|
||||
# unless we add interface-specific settings for each VPN, we have to define nameservers globally.
|
||||
# networking.nameservers = [
|
||||
# "1.1.1.1"
|
||||
# "9.9.9.9"
|
||||
# ];
|
||||
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in the ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# there also seems to be some cache somewhere that's shared between the two namespaces.
|
||||
# i think this is a libc thing. might need to leverage proper cgroups to _really_ kill it.
|
||||
# - getent ahostsv4 www.google.com
|
||||
# - try fix: <https://serverfault.com/questions/765989/connect-to-3rd-party-vpn-server-but-dont-use-it-as-the-default-route/766290#766290>
|
||||
services.resolved.enable = true;
|
||||
# without DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => works
|
||||
# with default DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => fails
|
||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
services.resolved.dnssec = "false";
|
||||
networking.nameservers = [
|
||||
# use systemd-resolved resolver
|
||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||
# in the netns and we query upstream DNS more often than needed. hm.
|
||||
# TODO: run a separate recursive resolver in each namespace.
|
||||
services.nscd.enableNsncd = true;
|
||||
|
||||
# services.resolved.extraConfig = ''
|
||||
# # docs: `man resolved.conf`
|
||||
# # DNS servers to use via the `wg-ovpns` interface.
|
||||
|
@@ -13,7 +13,7 @@ in
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ inherit user group; mode = "0700"; path = svc-dir; }
|
||||
{ inherit user group; mode = "0700"; path = svc-dir; method = "bind"; }
|
||||
];
|
||||
|
||||
services.calibre-web.enable = true;
|
||||
|
83
hosts/by-name/servo/services/cryptocurrencies/bitcoin.nix
Normal file
83
hosts/by-name/servo/services/cryptocurrencies/bitcoin.nix
Normal file
@@ -0,0 +1,83 @@
|
||||
# as of 2023/12/02: complete blockchain is 530 GiB (on-disk size may be larger)
|
||||
#
|
||||
# ports:
|
||||
# - 8333: for node-to-node communications
|
||||
# - 8332: rpc (client-to-node)
|
||||
#
|
||||
# rpc setup:
|
||||
# - generate a password
|
||||
# - use: <https://github.com/bitcoin/bitcoin/blob/master/share/rpcauth/rpcauth.py>
|
||||
# (rpcauth.py is not included in the `'.#bitcoin'` package result)
|
||||
# - `wget https://raw.githubusercontent.com/bitcoin/bitcoin/master/share/rpcauth/rpcauth.py`
|
||||
# - `python ./rpcauth.py colin`
|
||||
# - copy the hash here. it's SHA-256, so safe to be public.
|
||||
# - add "rpcuser=colin" and "rpcpassword=<output>" to secrets/servo/bitcoin.conf (i.e. ~/.bitcoin/bitcoin.conf)
|
||||
# - bitcoin.conf docs: <https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md>
|
||||
# - validate with `bitcoin-cli -netinfo`
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
# wrapper to run bitcoind with the tor onion address as externalip (computed at runtime)
|
||||
_bitcoindWithExternalIp = with pkgs; writeShellScriptBin "bitcoind" ''
|
||||
externalip="$(cat /var/lib/tor/onion/bitcoind/hostname)"
|
||||
exec ${bitcoind}/bin/bitcoind "-externalip=$externalip" "$@"
|
||||
'';
|
||||
# the package i provide to services.bitcoind ends up on system PATH, and used by other tools like clightning.
|
||||
# therefore, even though services.bitcoind only needs `bitcoind` binary, provide all the other bitcoin-related binaries (notably `bitcoin-cli`) as well:
|
||||
bitcoindWithExternalIp = with pkgs; symlinkJoin {
|
||||
name = "bitcoind-with-external-ip";
|
||||
paths = [ _bitcoindWithExternalIp bitcoind ];
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "bitcoind-mainnet"; group = "bitcoind-mainnet"; path = "/var/lib/bitcoind-mainnet"; method = "bind"; }
|
||||
];
|
||||
|
||||
# sane.ports.ports."8333" = {
|
||||
# # this allows other nodes and clients to download blocks from me.
|
||||
# protocol = [ "tcp" ];
|
||||
# visibleTo.wan = true;
|
||||
# description = "colin-bitcoin";
|
||||
# };
|
||||
|
||||
services.tor.relay.onionServices.bitcoind = {
|
||||
version = 3;
|
||||
map = [{
|
||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
||||
port = 8333;
|
||||
# target.port; target.addr; #< set if tor port != clightning port
|
||||
}];
|
||||
# allow "tor" group (i.e. bitcoind-mainnet) to read /var/lib/tor/onion/bitcoind/hostname
|
||||
settings.HiddenServiceDirGroupReadable = true;
|
||||
};
|
||||
|
||||
services.bitcoind.mainnet = {
|
||||
enable = true;
|
||||
package = bitcoindWithExternalIp;
|
||||
rpc.users.colin = {
|
||||
# see docs at top of file for how to generate this
|
||||
passwordHMAC = "30002c05d82daa210550e17a182db3f3$6071444151281e1aa8a2729f75e3e2d224e9d7cac3974810dab60e7c28ffaae4";
|
||||
};
|
||||
extraConfig = ''
|
||||
# don't load the wallet, and disable wallet RPC calls
|
||||
disablewallet=1
|
||||
# proxy all outbound traffic through Tor
|
||||
proxy=127.0.0.1:9050
|
||||
'';
|
||||
};
|
||||
|
||||
users.users.bitcoind-mainnet.extraGroups = [ "tor" ];
|
||||
|
||||
systemd.services.bitcoind-mainnet.serviceConfig.RestartSec = "30s"; #< default is 0
|
||||
|
||||
sane.users.colin.fs.".bitcoin/bitcoin.conf" = sane-lib.fs.wantedSymlinkTo config.sops.secrets."bitcoin.conf".path;
|
||||
sops.secrets."bitcoin.conf" = {
|
||||
mode = "0600";
|
||||
owner = "colin";
|
||||
group = "users";
|
||||
};
|
||||
|
||||
sane.programs.bitcoind.enableFor.user.colin = true; # for debugging/administration: `bitcoin-cli`
|
||||
}
|
766
hosts/by-name/servo/services/cryptocurrencies/clightning-sane/clightning-sane
Executable file
766
hosts/by-name/servo/services/cryptocurrencies/clightning-sane/clightning-sane
Executable file
@@ -0,0 +1,766 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i python3 -p "python3.withPackages (ps: [ ps.pyln-client ])"
|
||||
|
||||
# pyln-client docs: <https://github.com/ElementsProject/lightning/tree/master/contrib/pyln-client>
|
||||
# terminology:
|
||||
# - "scid": "Short Channel ID", e.g. 123456x7890x0
|
||||
# from this id, we can locate the actual channel, its peers, and its parameters
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import math
|
||||
import sys
|
||||
import time
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
from pyln.client import LightningRpc, Millisatoshi, RpcError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RPC_FILE = "/var/lib/clightning/bitcoin/lightning-rpc"
|
||||
# CLTV (HLTC delta) of the final hop
|
||||
# set this too low and you might get inadvertent channel closures (?)
|
||||
CLTV = 18
|
||||
|
||||
# for every sequentally failed transaction, delay this much before trying again.
|
||||
# note that the initial route building process can involve 10-20 "transient" failures, as it discovers dead channels.
|
||||
TX_FAIL_BACKOFF = 0.8
|
||||
MAX_SEQUENTIAL_JOB_FAILURES = 200
|
||||
|
||||
class LoopError(Enum):
|
||||
""" error when trying to loop sats, or when unable to calculate a route for the loop """
|
||||
TRANSIENT = "TRANSIENT" # try again, we'll maybe find a different route
|
||||
NO_ROUTE = "NO_ROUTE"
|
||||
|
||||
class RouteError(Enum):
|
||||
""" error when calculated a route """
|
||||
HAS_BASE_FEE = "HAS_BASE_FEE"
|
||||
NO_ROUTE = "NO_ROUTE"
|
||||
|
||||
class Metrics:
|
||||
looped_msat: int = 0
|
||||
sendpay_fail: int = 0
|
||||
sendpay_succeed: int = 0
|
||||
own_bad_channel: int = 0
|
||||
no_route: int = 0
|
||||
in_ch_unsatisfiable: int = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"looped:{self.looped_msat}, tx:{self.sendpay_succeed}, tx_fail:{self.sendpay_fail}, own_bad_ch:{self.own_bad_channel}, no_route:{self.no_route}, in_ch_restricted:{self.in_ch_unsatisfiable}"
|
||||
|
||||
@dataclass
|
||||
class TxBounds:
|
||||
max_msat: int
|
||||
min_msat: int = 0
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"TxBounds({self.min_msat} <= msat <= {self.max_msat})"
|
||||
|
||||
def is_satisfiable(self) -> bool:
|
||||
return self.min_msat <= self.max_msat
|
||||
|
||||
def raise_max_to_be_satisfiable(self) -> "Self":
|
||||
if self.max_msat < self.min_msat:
|
||||
logger.debug(f"raising max_msat to be consistent: {self.max_msat} -> {self.min_msat}")
|
||||
return TxBounds(self.min_msat, self.min_msat)
|
||||
return TxBounds(min_msat=self.min_msat, max_msat=self.max_msat)
|
||||
|
||||
def intersect(self, other: "TxBounds") -> "Self":
|
||||
return TxBounds(
|
||||
min_msat=max(self.min_msat, other.min_msat),
|
||||
max_msat=min(self.max_msat, other.max_msat),
|
||||
)
|
||||
|
||||
def restrict_to_htlc(self, ch: "LocalChannel", why: str = "") -> "Self":
|
||||
"""
|
||||
apply min/max HTLC size restrictions of the given channel.
|
||||
"""
|
||||
if ch:
|
||||
why = why or ch.directed_scid_to_me
|
||||
if why: why = f"{why}: "
|
||||
|
||||
new_min, new_max = self.min_msat, self.max_msat
|
||||
if ch.htlc_minimum_to_me > self.min_msat:
|
||||
new_min = ch.htlc_minimum_to_me
|
||||
logger.debug(f"{why}raising min_msat due to HTLC requirements: {self.min_msat} -> {new_min}")
|
||||
if ch.htlc_maximum_to_me < self.max_msat:
|
||||
new_max = ch.htlc_maximum_to_me
|
||||
logger.debug(f"{why}lowering max_msat due to HTLC requirements: {self.max_msat} -> {new_max}")
|
||||
return TxBounds(min_msat=new_min, max_msat=new_max)
|
||||
|
||||
def restrict_to_zero_fees(self, ch: "LocalChannel"=None, base: int=0, ppm: int=0, why:str = "") -> "Self":
|
||||
"""
|
||||
restrict tx size such that PPM fees are zero.
|
||||
if the channel has a base fee, then `max_msat` is forced to 0.
|
||||
"""
|
||||
if ch:
|
||||
why = why or ch.directed_scid_to_me
|
||||
self = self.restrict_to_zero_fees(base=ch.to_me["base_fee_millisatoshi"], ppm=ch.to_me["fee_per_millionth"], why=why)
|
||||
|
||||
if why: why = f"{why}: "
|
||||
|
||||
new_max = self.max_msat
|
||||
ppm_max = math.ceil(1000000 / ppm) - 1 if ppm != 0 else new_max
|
||||
if ppm_max < new_max:
|
||||
logger.debug(f"{why}decreasing max_msat due to fee ppm: {new_max} -> {ppm_max}")
|
||||
new_max = ppm_max
|
||||
|
||||
if base != 0:
|
||||
logger.debug(f"{why}free route impossible: channel has base fees")
|
||||
new_max = 0
|
||||
|
||||
return TxBounds(min_msat=self.min_msat, max_msat=new_max)
|
||||
|
||||
|
||||
class LocalChannel:
|
||||
def __init__(self, channels: list, rpc: "RpcHelper"):
|
||||
assert 0 < len(channels) <= 2, f"unexpected: channel count: {channels}"
|
||||
out = None
|
||||
in_ = None
|
||||
for c in channels:
|
||||
if c["source"] == rpc.self_id:
|
||||
assert out is None, f"unexpected: multiple channels from self: {channels}"
|
||||
out = c
|
||||
if c["destination"] == rpc.self_id:
|
||||
assert in_ is None, f"unexpected: multiple channels to self: {channels}"
|
||||
in_ = c
|
||||
|
||||
# assert out is not None, f"no channel from self: {channels}"
|
||||
# assert in_ is not None, f"no channel to self: {channels}"
|
||||
|
||||
if out and in_:
|
||||
assert out["destination"] == in_["source"], f"channel peers are asymmetric?! {channels}"
|
||||
assert out["short_channel_id"] == in_["short_channel_id"], f"channel ids differ?! {channels}"
|
||||
|
||||
self.from_me = out
|
||||
self.to_me = in_
|
||||
self.remote_node = rpc.node(self.remote_peer)
|
||||
self.peer_ch = rpc.peerchannel(self.scid, self.remote_peer)
|
||||
self.forwards_from_me = rpc.rpc.listforwards(out_channel=self.scid, status="settled")["forwards"]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.to_str(with_scid=True, with_bal_ratio=True, with_cost=False, with_ppm_theirs=False)
|
||||
|
||||
def to_str(
|
||||
self,
|
||||
with_peer_id:bool = False,
|
||||
with_scid:bool = False,
|
||||
with_bal_msat:bool = False,
|
||||
with_bal_ratio:bool = False,
|
||||
with_cost:bool = False,
|
||||
with_ppm_theirs:bool = False,
|
||||
with_ppm_mine:bool = False,
|
||||
with_profits:bool = True,
|
||||
with_payments:bool = False,
|
||||
) -> str:
|
||||
base_flag = "*" if not self.online or self.base_fee_to_me != 0 else ""
|
||||
alias = f"({self.remote_alias}){base_flag}"
|
||||
peerid = f" {self.remote_peer}" if with_peer_id else ""
|
||||
scid = f" scid:{self.scid:>13}" if with_scid else ""
|
||||
bal = f" S:{int(self.sendable):11}/R:{int(self.receivable):11}" if with_bal_msat else ""
|
||||
ratio = f" MINE:{(100*self.send_ratio):>8.4f}%" if with_bal_ratio else ""
|
||||
payments = f" OUT:{int(self.out_fulfilled_msat):>11}/IN:{int(self.in_fulfilled_msat):>11}" if with_payments else ""
|
||||
profits = f" P$:{int(self.fees_lifetime_mine):>8}" if with_profits else ""
|
||||
cost = f" COST:{self.opportunity_cost_lent:>8}" if with_cost else ""
|
||||
ppm_theirs = self.ppm_to_me if self.to_me else "N/A"
|
||||
ppm_theirs = f" PPM_THEIRS:{ppm_theirs:>6}" if with_ppm_theirs else ""
|
||||
ppm_mine = self.ppm_from_me if self.from_me else "N/A"
|
||||
ppm_mine = f" PPM_MINE:{ppm_mine:>6}" if with_ppm_mine else ""
|
||||
return f"channel{alias:30}{peerid}{scid}{bal}{ratio}{payments}{profits}{cost}{ppm_theirs}{ppm_mine}"
|
||||
|
||||
|
||||
@property
|
||||
def online(self) -> bool:
|
||||
return self.from_me and self.to_me
|
||||
|
||||
@property
|
||||
def remote_peer(self) -> str:
|
||||
if self.from_me:
|
||||
return self.from_me["destination"]
|
||||
else:
|
||||
return self.to_me["source"]
|
||||
|
||||
@property
|
||||
def remote_alias(self) -> str:
|
||||
return self.remote_node["alias"]
|
||||
|
||||
@property
|
||||
def scid(self) -> str:
|
||||
if self.from_me:
|
||||
return self.from_me["short_channel_id"]
|
||||
else:
|
||||
return self.to_me["short_channel_id"]
|
||||
|
||||
@property
|
||||
def htlc_minimum_to_me(self) -> Millisatoshi:
|
||||
return self.to_me["htlc_minimum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_minimum_from_me(self) -> Millisatoshi:
|
||||
return self.from_me["htlc_minimum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_minimum(self) -> Millisatoshi:
|
||||
return max(self.htlc_minimum_to_me, self.htlc_minimum_from_me)
|
||||
|
||||
@property
|
||||
def htlc_maximum_to_me(self) -> Millisatoshi:
|
||||
return self.to_me["htlc_maximum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_maximum_from_me(self) -> Millisatoshi:
|
||||
return self.from_me["htlc_maximum_msat"]
|
||||
|
||||
@property
|
||||
def htlc_maximum(self) -> Millisatoshi:
|
||||
return min(self.htlc_maximum_to_me, self.htlc_maximum_from_me)
|
||||
|
||||
@property
|
||||
def direction_to_me(self) -> int:
|
||||
return self.to_me["direction"]
|
||||
|
||||
@property
|
||||
def direction_from_me(self) -> int:
|
||||
return self.from_me["direction"]
|
||||
|
||||
@property
|
||||
def directed_scid_to_me(self) -> str:
|
||||
return f"{self.scid}/{self.direction_to_me}"
|
||||
|
||||
@property
|
||||
def directed_scid_from_me(self) -> str:
|
||||
return f"{self.scid}/{self.direction_from_me}"
|
||||
|
||||
@property
|
||||
def delay_them(self) -> str:
|
||||
return self.to_me["delay"]
|
||||
|
||||
@property
|
||||
def delay_me(self) -> str:
|
||||
return self.from_me["delay"]
|
||||
|
||||
@property
|
||||
def ppm_to_me(self) -> int:
|
||||
return self.to_me["fee_per_millionth"]
|
||||
|
||||
@property
|
||||
def ppm_from_me(self) -> int:
|
||||
return self.from_me["fee_per_millionth"]
|
||||
# return self.peer_ch["fee_proportional_millionths"]
|
||||
|
||||
@property
|
||||
def base_fee_to_me(self) -> int:
|
||||
return self.to_me["base_fee_millisatoshi"]
|
||||
|
||||
@property
|
||||
def receivable(self) -> int:
|
||||
return self.peer_ch["receivable_msat"]
|
||||
|
||||
@property
|
||||
def sendable(self) -> int:
|
||||
return self.peer_ch["spendable_msat"]
|
||||
|
||||
@property
|
||||
def in_fulfilled_msat(self) -> Millisatoshi:
|
||||
return self.peer_ch["in_fulfilled_msat"]
|
||||
|
||||
@property
|
||||
def out_fulfilled_msat(self) -> Millisatoshi:
|
||||
return self.peer_ch["out_fulfilled_msat"]
|
||||
|
||||
@property
|
||||
def fees_lifetime_mine(self) -> Millisatoshi:
|
||||
return sum(fwd["fee_msat"] for fwd in self.forwards_from_me)
|
||||
|
||||
@property
|
||||
def send_ratio(self) -> float:
|
||||
cap = self.receivable + self.sendable
|
||||
return self.sendable / cap
|
||||
|
||||
@property
|
||||
def opportunity_cost_lent(self) -> int:
|
||||
""" how much msat did we gain by pushing their channel to its current balance? """
|
||||
return int(self.receivable * self.ppm_from_me / 1000000)
|
||||
|
||||
class RpcHelper:
|
||||
def __init__(self, rpc: LightningRpc):
|
||||
self.rpc = rpc
|
||||
self.self_id = rpc.getinfo()["id"]
|
||||
|
||||
def localchannel(self, scid: str) -> LocalChannel:
|
||||
listchan = self.rpc.listchannels(scid)
|
||||
# this assertion would probably indicate a typo in the scid
|
||||
assert listchan and listchan.get("channels", []) != [], f"bad listchannels for {scid}: {listchan}"
|
||||
return LocalChannel(listchan["channels"], self)
|
||||
|
||||
def node(self, id: str) -> dict:
|
||||
nodes = self.rpc.listnodes(id)["nodes"]
|
||||
assert len(nodes) == 1, f"unexpected: multiple nodes for {id}: {nodes}"
|
||||
return nodes[0]
|
||||
|
||||
def peerchannel(self, scid: str, peer_id: str) -> dict:
|
||||
peerchannels = self.rpc.listpeerchannels(peer_id)["channels"]
|
||||
channels = [c for c in peerchannels if c["short_channel_id"] == scid]
|
||||
assert len(channels) == 1, f"expected exactly 1 channel, got: {channels}"
|
||||
return channels[0]
|
||||
|
||||
def try_getroute(self, *args, **kwargs) -> dict | None:
|
||||
""" wrapper for getroute which returns None instead of error if no route exists """
|
||||
try:
|
||||
route = self.rpc.getroute(*args, **kwargs)
|
||||
except RpcError as e:
|
||||
logger.debug(f"rpc failed: {e}")
|
||||
return None
|
||||
else:
|
||||
route = route["route"]
|
||||
if route == []: return None
|
||||
return route
|
||||
|
||||
class LoopRouter:
|
||||
def __init__(self, rpc: RpcHelper, metrics: Metrics = None):
|
||||
self.rpc = rpc
|
||||
self.metrics = metrics or Metrics()
|
||||
self.bad_channels = [] # list of directed scid
|
||||
self.nonzero_base_channels = [] # list of directed scid
|
||||
|
||||
def drop_caches(self) -> None:
|
||||
logger.info("LoopRouter.drop_caches()")
|
||||
self.bad_channels = []
|
||||
|
||||
def _get_directed_scid(self, scid: str, direction: int) -> dict:
|
||||
channels = self.rpc.rpc.listchannels(scid)["channels"]
|
||||
channels = [c for c in channels if c["direction"] == direction]
|
||||
assert len(channels) == 1, f"expected exactly 1 channel: {channels}"
|
||||
return channels[0]
|
||||
|
||||
def loop_once(self, out_scid: str, in_scid: str, bounds: TxBounds) -> LoopError|int:
|
||||
out_ch = self.rpc.localchannel(out_scid)
|
||||
in_ch = self.rpc.localchannel(in_scid)
|
||||
|
||||
if out_ch.directed_scid_from_me in self.bad_channels or in_ch.directed_scid_to_me in self.bad_channels:
|
||||
logger.info(f"loop {out_scid} -> {in_scid} failed in our own channel")
|
||||
self.metrics.own_bad_channel += 1
|
||||
return LoopError.TRANSIENT
|
||||
|
||||
# bounds = bounds.restrict_to_htlc(out_ch) # htlc bounds seem to be enforced only in the outward direction
|
||||
bounds = bounds.restrict_to_htlc(in_ch)
|
||||
bounds = bounds.restrict_to_zero_fees(in_ch)
|
||||
if not bounds.is_satisfiable():
|
||||
self.metrics.in_ch_unsatisfiable += 1
|
||||
return LoopError.NO_ROUTE
|
||||
|
||||
logger.debug(f"route with bounds {bounds}")
|
||||
route = self.route(out_ch, in_ch, bounds)
|
||||
logger.debug(f"route: {route}")
|
||||
if route == RouteError.NO_ROUTE:
|
||||
self.metrics.no_route += 1
|
||||
return LoopError.NO_ROUTE
|
||||
elif route == RouteError.HAS_BASE_FEE:
|
||||
# try again with a different route
|
||||
return LoopError.TRANSIENT
|
||||
|
||||
amount_msat = route[0]["amount_msat"]
|
||||
invoice_id = f"loop-{time.time():.6f}".replace(".", "_")
|
||||
invoice_desc = f"bal {out_scid}:{in_scid}"
|
||||
invoice = self.rpc.rpc.invoice("any", invoice_id, invoice_desc)
|
||||
logger.debug(f"invoice: {invoice}")
|
||||
|
||||
payment = self.rpc.rpc.sendpay(route, invoice["payment_hash"], invoice_id, amount_msat, invoice["bolt11"], invoice["payment_secret"])
|
||||
logger.debug(f"sent: {payment}")
|
||||
|
||||
try:
|
||||
wait = self.rpc.rpc.waitsendpay(invoice["payment_hash"])
|
||||
logger.debug(f"result: {wait}")
|
||||
except RpcError as e:
|
||||
self.metrics.sendpay_fail += 1
|
||||
err_data = e.error["data"]
|
||||
err_scid, err_dir = err_data["erring_channel"], err_data["erring_direction"]
|
||||
err_directed_scid = f"{err_scid}/{err_dir}"
|
||||
logger.debug(f"ch failed, adding to excludes: {err_directed_scid}; {e.error}")
|
||||
self.bad_channels.append(err_directed_scid)
|
||||
return LoopError.TRANSIENT
|
||||
else:
|
||||
self.metrics.sendpay_succeed += 1
|
||||
self.metrics.looped_msat += int(amount_msat)
|
||||
return int(amount_msat)
|
||||
|
||||
def route(self, out_ch: LocalChannel, in_ch: LocalChannel, bounds: TxBounds) -> list[dict] | RouteError:
|
||||
exclude = [
|
||||
# ensure the payment doesn't cross either channel in reverse.
|
||||
# note that this doesn't preclude it from taking additional trips through self, with other peers.
|
||||
# out_ch.directed_scid_to_me,
|
||||
# in_ch.directed_scid_from_me,
|
||||
|
||||
# alternatively, never route through self. this avoids a class of logic error, like what to do with fees i charge "myself".
|
||||
self.rpc.self_id
|
||||
] + self.bad_channels + self.nonzero_base_channels
|
||||
|
||||
out_peer = out_ch.remote_peer
|
||||
in_peer = in_ch.remote_peer
|
||||
|
||||
route_or_bounds = bounds
|
||||
while isinstance(route_or_bounds, TxBounds):
|
||||
old_bounds = route_or_bounds
|
||||
route_or_bounds = self._find_partial_route(out_peer, in_peer, old_bounds, exclude=exclude)
|
||||
if route_or_bounds == old_bounds:
|
||||
return RouteError.NO_ROUTE
|
||||
|
||||
if isinstance(route_or_bounds, RouteError):
|
||||
return route_or_bounds
|
||||
|
||||
route = self._add_route_endpoints(route_or_bounds, out_ch, in_ch)
|
||||
return route
|
||||
|
||||
def _find_partial_route(self, out_peer: str, in_peer: str, bounds: TxBounds, exclude: list[str]=[]) -> list[dict] | RouteError | TxBounds:
|
||||
route = self.rpc.try_getroute(in_peer, amount_msat=bounds.max_msat, riskfactor=0, fromid=out_peer, exclude=exclude, cltv=CLTV)
|
||||
if route is None:
|
||||
logger.debug(f"no route for {bounds.max_msat}msat {out_peer} -> {in_peer}")
|
||||
return RouteError.NO_ROUTE
|
||||
|
||||
send_msat = route[0]["amount_msat"]
|
||||
if send_msat != Millisatoshi(bounds.max_msat):
|
||||
logger.debug(f"found route with non-zero fee: {send_msat} -> {bounds.max_msat}. {route}")
|
||||
|
||||
error = None
|
||||
for hop in route:
|
||||
hop_scid = hop["channel"]
|
||||
hop_dir = hop["direction"]
|
||||
directed_scid = f"{hop_scid}/{hop_dir}"
|
||||
ch = self._get_directed_scid(hop_scid, hop_dir)
|
||||
if ch["base_fee_millisatoshi"] != 0:
|
||||
self.nonzero_base_channels.append(directed_scid)
|
||||
error = RouteError.HAS_BASE_FEE
|
||||
bounds = bounds.restrict_to_zero_fees(ppm=ch["fee_per_millionth"], why=directed_scid)
|
||||
|
||||
return bounds.raise_max_to_be_satisfiable() if error is None else error
|
||||
|
||||
return route
|
||||
|
||||
def _add_route_endpoints(self, route, out_ch: LocalChannel, in_ch: LocalChannel):
|
||||
inbound_hop = dict(
|
||||
id=self.rpc.self_id,
|
||||
channel=in_ch.scid,
|
||||
direction=in_ch.direction_to_me,
|
||||
amount_msat=route[-1]["amount_msat"],
|
||||
delay=route[-1]["delay"],
|
||||
style="tlv",
|
||||
)
|
||||
route = self._add_route_delay(route, in_ch.delay_them) + [ inbound_hop ]
|
||||
|
||||
outbound_hop = dict(
|
||||
id=out_ch.remote_peer,
|
||||
channel=out_ch.scid,
|
||||
direction=out_ch.direction_from_me,
|
||||
amount_msat=route[0]["amount_msat"],
|
||||
delay=route[0]["delay"] + out_ch.delay_them,
|
||||
style="tlv",
|
||||
)
|
||||
route = [ outbound_hop ] + route
|
||||
return route
|
||||
|
||||
def _add_route_delay(self, route: list[dict], delay: int) -> list[dict]:
|
||||
return [ dict(hop, delay=hop["delay"] + delay) for hop in route ]
|
||||
|
||||
@dataclass
|
||||
class LoopJob:
|
||||
out: str # scid
|
||||
in_: str # scid
|
||||
amount: int
|
||||
|
||||
@dataclass
|
||||
class LoopJobIdle:
|
||||
sec: int = 10
|
||||
|
||||
class LoopJobDone(Enum):
|
||||
COMPLETED = "COMPLETED"
|
||||
ABORTED = "ABORTED"
|
||||
|
||||
class AbstractLoopRunner:
|
||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int):
|
||||
self.looper = looper
|
||||
self.bounds = bounds
|
||||
self.parallelism = parallelism
|
||||
self.bounds_map = {} # map (out:str, in_:str) -> TxBounds. it's a cache so we don't have to try 10 routes every time.
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
raise NotImplemented # abstract method
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int|LoopError) -> None:
|
||||
raise NotImplemented # abstract method
|
||||
|
||||
def run_to_completion(self, exit_on_any_completed:bool = False) -> None:
|
||||
self.exiting = False
|
||||
self.exit_on_any_completed = exit_on_any_completed
|
||||
if self.parallelism == 1:
|
||||
# run inline to aid debugging
|
||||
self._worker_thread()
|
||||
else:
|
||||
with ThreadPoolExecutor(max_workers=self.parallelism) as executor:
|
||||
_ = list(executor.map(lambda _i: self._try_invoke(self._worker_thread), range(self.parallelism)))
|
||||
|
||||
def drop_caches(self) -> None:
|
||||
logger.info("AbstractLoopRunner.drop_caches()")
|
||||
self.looper.drop_caches()
|
||||
self.bounds_map = {}
|
||||
|
||||
|
||||
def _try_invoke(self, f, *args) -> None:
|
||||
"""
|
||||
try to invoke `f` with the provided `args`, and log if it fails.
|
||||
this overcomes the issue that background tasks which fail via Exception otherwise do so silently.
|
||||
"""
|
||||
try:
|
||||
f(*args)
|
||||
except Exception as e:
|
||||
logger.error(f"task failed: {e}")
|
||||
|
||||
|
||||
def _worker_thread(self) -> None:
|
||||
while not self.exiting:
|
||||
job = self.pop_job()
|
||||
logger.debug(f"popped job: {job}")
|
||||
if isinstance(job, LoopJobDone):
|
||||
return self._worker_finished(job)
|
||||
|
||||
if isinstance(job, LoopJobIdle):
|
||||
logger.debug(f"idling for {job.sec}")
|
||||
time.sleep(job.sec)
|
||||
continue
|
||||
|
||||
result = self._execute_job(job)
|
||||
logger.debug(f"finishing job {job} with {result}")
|
||||
self.finished_job(job, result)
|
||||
|
||||
def _execute_job(self, job: LoopJob) -> LoopError|int:
|
||||
bounds = self.bounds_map.get((job.out, job.in_), self.bounds)
|
||||
bounds = bounds.intersect(TxBounds(max_msat=job.amount))
|
||||
if not bounds.is_satisfiable():
|
||||
logger.debug(f"TxBounds for job are unsatisfiable; skipping: {bounds} {job}")
|
||||
return LoopError.NO_ROUTE
|
||||
|
||||
amt_looped = self.looper.loop_once(job.out, job.in_, bounds)
|
||||
if amt_looped in (0, LoopError.NO_ROUTE, LoopError.TRANSIENT):
|
||||
return amt_looped
|
||||
|
||||
logger.info(f"looped {amt_looped} from {job.out} -> {job.in_}")
|
||||
bounds = bounds.intersect(TxBounds(max_msat=amt_looped))
|
||||
|
||||
self.bounds_map[(job.out, job.in_)] = bounds
|
||||
return amt_looped
|
||||
|
||||
def _worker_finished(self, job: LoopJobDone) -> None:
|
||||
if job == LoopJobDone.COMPLETED and self.exit_on_any_completed:
|
||||
logger.debug(f"worker completed -> exiting pool")
|
||||
self.exiting = True
|
||||
|
||||
class LoopPairState:
|
||||
# TODO: use this in MultiLoopBalancer, or stop shoving state in here and put it on LoopBalancer instead.
|
||||
def __init__(self, out: str, in_: str, amount: int):
|
||||
self.out = out
|
||||
self.in_ = in_
|
||||
self.amount_target = amount
|
||||
self.amount_looped = 0
|
||||
self.amount_outstanding = 0
|
||||
self.tx_fail_count = 0
|
||||
self.route_fail_count = 0
|
||||
self.last_job_start_time = None
|
||||
self.failed_tx_throttler = 0 # increase by one every time we fail, decreases more gradually, when we succeed
|
||||
|
||||
class LoopBalancer(AbstractLoopRunner):
|
||||
def __init__(self, out: str, in_: str, amount: int, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
||||
super().__init__(looper, bounds, parallelism)
|
||||
self.state = LoopPairState(out, in_, amount)
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
if self.state.tx_fail_count + 10*self.state.route_fail_count >= MAX_SEQUENTIAL_JOB_FAILURES:
|
||||
logger.info(f"giving up ({self.state.out} -> {self.state.in_}): {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
||||
return LoopJobDone.ABORTED
|
||||
|
||||
if self.state.tx_fail_count + self.state.route_fail_count > 0:
|
||||
# N.B.: last_job_start_time is guaranteed to have been set by now
|
||||
idle_until = self.state.last_job_start_time + TX_FAIL_BACKOFF*self.state.failed_tx_throttler
|
||||
idle_for = idle_until - time.time()
|
||||
if self.state.amount_outstanding != 0 or idle_for > 0:
|
||||
# when we hit transient failures, restrict to just one job in flight at a time.
|
||||
# this is aimed for the initial route building, where multiple jobs in flight is just useless,
|
||||
# but it's not a bad idea for network blips, etc, either.
|
||||
logger.info(f"throttling ({self.state.out} -> {self.state.in_}) for {idle_for:.0f}: {self.state.tx_fail_count} tx failures, {self.state.route_fail_count} route failures")
|
||||
return LoopJobIdle(idle_for) if idle_for > 0 else LoopJobIdle()
|
||||
|
||||
amount_avail = self.state.amount_target - self.state.amount_looped - self.state.amount_outstanding
|
||||
if amount_avail < self.bounds.min_msat:
|
||||
if self.state.amount_outstanding == 0: return LoopJobDone.COMPLETED
|
||||
return LoopJobIdle() # sending out another job would risk over-transferring
|
||||
amount_this_job = min(amount_avail, self.bounds.max_msat)
|
||||
|
||||
self.state.amount_outstanding += amount_this_job
|
||||
self.state.last_job_start_time = time.time()
|
||||
return LoopJob(out=self.state.out, in_=self.state.in_, amount=amount_this_job)
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
||||
self.state.amount_outstanding -= job.amount
|
||||
if progress == LoopError.NO_ROUTE:
|
||||
self.state.route_fail_count += 1
|
||||
self.state.failed_tx_throttler += 10
|
||||
elif progress == LoopError.TRANSIENT:
|
||||
self.state.tx_fail_count += 1
|
||||
self.state.failed_tx_throttler += 1
|
||||
else:
|
||||
self.state.amount_looped += progress
|
||||
self.state.tx_fail_count = 0
|
||||
self.state.route_fail_count = 0
|
||||
self.state.failed_tx_throttler = max(0, self.state.failed_tx_throttler - 0.2)
|
||||
logger.info(f"loop progressed ({job.out} -> {job.in_}) {progress}: {self.state.amount_looped} of {self.state.amount_target}")
|
||||
|
||||
class MultiLoopBalancer(AbstractLoopRunner):
|
||||
"""
|
||||
multiplexes jobs between multiple LoopBalancers.
|
||||
note that the child LoopBalancers don't actually execute the jobs -- just produce them.
|
||||
"""
|
||||
def __init__(self, looper: LoopRouter, bounds: TxBounds, parallelism: int=1):
|
||||
super().__init__(looper, bounds, parallelism)
|
||||
self.loops = []
|
||||
# job_index: increments on every job so we can grab jobs evenly from each LoopBalancer.
|
||||
# in that event that producers are idling, it can actually increment more than once,
|
||||
# so don't take this too literally
|
||||
self.job_index = 0
|
||||
|
||||
def add_loop(self, out: LocalChannel, in_: LocalChannel, amount: int) -> None:
|
||||
"""
|
||||
start looping sats from out -> in_
|
||||
"""
|
||||
assert not any(l.state.out == out.scid and l.state.in_ == in_.scid for l in self.loops), f"tried to add duplicate loops from {out} -> {in_}"
|
||||
logger.info(f"looping from ({out}) to ({in_})")
|
||||
self.loops.append(LoopBalancer(out.scid, in_.scid, amount, self.looper, self.bounds, self.parallelism))
|
||||
|
||||
def pop_job(self) -> LoopJob | LoopJobIdle | LoopJobDone:
|
||||
# N.B.: this can be called in parallel, so try to be consistent enough to not crash
|
||||
|
||||
idle_job = None
|
||||
abort_job = None
|
||||
for i, _ in enumerate(self.loops):
|
||||
loop = self.loops[(self.job_index + i) % len(self.loops)]
|
||||
self.job_index += 1
|
||||
job = loop.pop_job()
|
||||
if isinstance(job, LoopJob):
|
||||
return job
|
||||
if isinstance(job, LoopJobIdle):
|
||||
idle_job = LoopJobIdle(min(job.sec, idle_job.sec)) if idle_job is not None else job
|
||||
if job == LoopJobDone.ABORTED:
|
||||
abort_job = job
|
||||
|
||||
# either there's a task to idle, or we have to terminate.
|
||||
# if terminating, terminate ABORTED if any job aborted, else COMPLETED
|
||||
if idle_job is not None: return idle_job
|
||||
if abort_job is not None: return abort_job
|
||||
return LoopJobDone.COMPLETED
|
||||
|
||||
def finished_job(self, job: LoopJob, progress: int) -> None:
|
||||
# this assumes (enforced externally) that we have only one loop for a given out/in_ pair
|
||||
for l in self.loops:
|
||||
if l.state.out == job.out and l.state.in_ == job.in_:
|
||||
l.finished_job(job, progress)
|
||||
|
||||
logger.info(f"total: {self.looper.metrics}")
|
||||
|
||||
|
||||
def balance_loop(rpc: RpcHelper, out: str, in_: str, amount_msat: int, min_msat: int, max_msat: int, parallelism: int):
|
||||
looper = LoopRouter(rpc)
|
||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
||||
balancer = LoopBalancer(out, in_, amount_msat, looper, bounds, parallelism)
|
||||
|
||||
balancer.run_to_completion()
|
||||
|
||||
def autobalance_once(rpc: RpcHelper, metrics: Metrics, bounds: TxBounds, parallelism: int) -> bool:
|
||||
"""
|
||||
autobalances all channels.
|
||||
returns True if channels are balanced (or as balanced as can be); False if in need of further balancing
|
||||
"""
|
||||
looper = LoopRouter(rpc, metrics)
|
||||
balancer = MultiLoopBalancer(looper, bounds, parallelism)
|
||||
|
||||
channels = []
|
||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
||||
try:
|
||||
channels.append(rpc.localchannel(peerch["short_channel_id"]))
|
||||
except:
|
||||
logger.info(f"NO CHANNELS for {peerch['peer_id']}")
|
||||
|
||||
channels = [ch for ch in channels if ch.online and ch.base_fee_to_me == 0]
|
||||
give_to = [ ch for ch in channels if ch.send_ratio > 0.95 ]
|
||||
take_from = [ ch for ch in channels if ch.send_ratio < 0.20 ]
|
||||
|
||||
if give_to == [] and take_from == []:
|
||||
return True
|
||||
|
||||
for to in give_to:
|
||||
for from_ in take_from:
|
||||
balancer.add_loop(to, from_, 10000000)
|
||||
|
||||
balancer.run_to_completion(exit_on_any_completed=True)
|
||||
return False
|
||||
|
||||
|
||||
def autobalance(rpc: RpcHelper, min_msat: int, max_msat: int, parallelism: int):
|
||||
bounds = TxBounds(min_msat=min_msat, max_msat=max_msat)
|
||||
metrics = Metrics()
|
||||
while not autobalance_once(rpc, metrics, bounds, parallelism):
|
||||
pass
|
||||
|
||||
def show_status(rpc: RpcHelper, full: bool=False):
|
||||
"""
|
||||
show a table of channel balances between peers.
|
||||
"""
|
||||
for peerch in rpc.rpc.listpeerchannels()["channels"]:
|
||||
try:
|
||||
ch = rpc.localchannel(peerch["short_channel_id"])
|
||||
except:
|
||||
print(f"{peerch['peer_id']} scid:{peerch['short_channel_id']} state:{peerch['state']} NO CHANNELS")
|
||||
else:
|
||||
print(ch.to_str(with_scid=True, with_bal_ratio=True, with_payments=True, with_cost=full, with_ppm_theirs=True, with_ppm_mine=True, with_peer_id=full))
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
parser = argparse.ArgumentParser(description="rebalance lightning channel balances")
|
||||
parser.add_argument("--verbose", action="store_true", help="more logging")
|
||||
parser.add_argument("--min-msat", default="999", help="min transaction size")
|
||||
parser.add_argument("--max-msat", default="1000000", help="max transaction size")
|
||||
parser.add_argument("--jobs", default="1", help="how many HTLCs to keep in-flight at once")
|
||||
subparsers = parser.add_subparsers(help="action")
|
||||
|
||||
status_parser = subparsers.add_parser("status")
|
||||
status_parser.set_defaults(action="status")
|
||||
status_parser.add_argument("--full", action="store_true", help="more info per channel")
|
||||
|
||||
loop_parser = subparsers.add_parser("loop")
|
||||
loop_parser.set_defaults(action="loop")
|
||||
loop_parser.add_argument("out", help="peer id to send tx through")
|
||||
loop_parser.add_argument("in_", help="peer id to receive tx through")
|
||||
loop_parser.add_argument("amount", help="total amount of msat to loop")
|
||||
|
||||
autobal_parser = subparsers.add_parser("autobalance")
|
||||
autobal_parser.set_defaults(action="autobalance")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
rpc = RpcHelper(LightningRpc(RPC_FILE))
|
||||
|
||||
if args.action == "status":
|
||||
show_status(rpc, full=args.full)
|
||||
|
||||
if args.action == "loop":
|
||||
balance_loop(rpc, out=args.out, in_=args.in_, amount_msat=int(args.amount), min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
||||
|
||||
if args.action == "autobalance":
|
||||
autobalance(rpc, min_msat=int(args.min_msat), max_msat=int(args.max_msat), parallelism=int(args.jobs))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
135
hosts/by-name/servo/services/cryptocurrencies/clightning.nix
Normal file
135
hosts/by-name/servo/services/cryptocurrencies/clightning.nix
Normal file
@@ -0,0 +1,135 @@
|
||||
# clightning is an implementation of Bitcoin's Lightning Network.
|
||||
# as such, this assumes that `services.bitcoin` is enabled.
|
||||
# docs:
|
||||
# - tor clightning config: <https://docs.corelightning.org/docs/tor>
|
||||
# - `lightning-cli` and subcommands: <https://docs.corelightning.org/reference/lightning-cli>
|
||||
# - `man lightningd-config`
|
||||
#
|
||||
# management/setup/use:
|
||||
# - guide: <https://github.com/ElementsProject/lightning>
|
||||
#
|
||||
# debugging:
|
||||
# - `lightning-cli getlog debug`
|
||||
# - `lightning-cli listpays` -> show payments this node sent
|
||||
# - `lightning-cli listinvoices` -> show payments this node received
|
||||
#
|
||||
# first, acquire peers:
|
||||
# - `lightning-cli connect id@host`
|
||||
# where `id` is the node's pubkey, and `host` is perhaps an ip:port tuple, or a hash.onion:port tuple.
|
||||
# for testing, choose any node listed on <https://1ml.com>
|
||||
# - `lightning-cli listpeers`
|
||||
# should show the new peer, with `connected: true`
|
||||
#
|
||||
# then, fund the clightning wallet
|
||||
# - `lightning-cli newaddr`
|
||||
#
|
||||
# then, open channels
|
||||
# - `lightning-cli connect ...`
|
||||
# - `lightning-cli fundchannel <node_id> <amount_in_satoshis>`
|
||||
#
|
||||
# who to federate with?
|
||||
# - a lot of the larger nodes allow hands-free channel creation
|
||||
# - either inbound or outbound, sometimes paid
|
||||
# - find nodes on:
|
||||
# - <https://terminal.lightning.engineering/>
|
||||
# - <https://1ml.com>
|
||||
# - tor nodes: <https://1ml.com/node?order=capacity&iponionservice=true>
|
||||
# - <https://lightningnetwork.plus>
|
||||
# - <https://mempool.space/lightning>
|
||||
# - <https://amboss.space>
|
||||
# - a few tor-capable nodes which allow channel creation:
|
||||
# - <https://c-otto.de/>
|
||||
# - <https://cyberdyne.sh/>
|
||||
# - <https://yalls.org/about/>
|
||||
# - <https://coincept.com/>
|
||||
# - more resources: <https://www.lopp.net/lightning-information.html>
|
||||
# - node routability: https://hashxp.org/lightning/node/<id>
|
||||
# - especially, acquire inbound liquidity via lightningnetwork.plus's swap feature
|
||||
# - most of the opportunities are gated behind a minimum connection or capacity requirement
|
||||
#
|
||||
# tune payment parameters
|
||||
# - `lightning-cli setchannel <id> [feebase] [feeppm] [htlcmin] [htlcmax] [enforcedelay] [ignorefeelimits]`
|
||||
# - e.g. `lightning-cli setchannel all 0 10`
|
||||
# - it's suggested that feebase=0 simplifies routing.
|
||||
#
|
||||
# teardown:
|
||||
# - `lightning-cli withdraw <bc1... dest addr> <amount in satoshis> [feerate]`
|
||||
#
|
||||
# sanity:
|
||||
# - `lightning-cli listfunds`
|
||||
#
|
||||
# to receive a payment (do as `clightning` user):
|
||||
# - `lightning-cli invoice <amount in millisatoshi> <label> <description>`
|
||||
# - specify amount as `any` if undetermined
|
||||
# - then give the resulting bolt11 URI to the payer
|
||||
# to send a payment:
|
||||
# - `lightning-cli pay <bolt11 URI>`
|
||||
# - or `lightning-cli pay <bolt11 URI> [amount_msat] [label] [riskfactor] [maxfeepercent] ...`
|
||||
# - amount_msat must be "null" if the bolt11 URI specifies a value
|
||||
# - riskfactor defaults to 10
|
||||
# - maxfeepercent defaults to 0.5
|
||||
# - label is a human-friendly label for my records
|
||||
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "clightning"; group = "clightning"; mode = "0710"; path = "/var/lib/clightning"; method = "bind"; }
|
||||
];
|
||||
|
||||
# `lightning-cli` finds its RPC file via `~/.lightning/bitcoin/lightning-rpc`, to message the daemon
|
||||
sane.user.fs.".lightning".symlink.target = "/var/lib/clightning";
|
||||
|
||||
# see bitcoin.nix for how to generate this
|
||||
services.bitcoind.mainnet.rpc.users.clightning.passwordHMAC =
|
||||
"befcb82d9821049164db5217beb85439$2c31ac7db3124612e43893ae13b9527dbe464ab2d992e814602e7cb07dc28985";
|
||||
|
||||
sane.services.clightning.enable = true;
|
||||
sane.services.clightning.proxy = "127.0.0.1:9050"; # proxy outgoing traffic through tor
|
||||
# sane.services.clightning.publicAddress = "statictor:127.0.0.1:9051";
|
||||
sane.services.clightning.getPublicAddressCmd = "cat /var/lib/tor/onion/clightning/hostname";
|
||||
|
||||
services.tor.relay.onionServices.clightning = {
|
||||
version = 3;
|
||||
map = [{
|
||||
# by default tor will route public tor port P to 127.0.0.1:P.
|
||||
# so if this port is the same as clightning would natively use, then no further config is needed here.
|
||||
# see: <https://2019.www.torproject.org/docs/tor-manual.html.en#HiddenServicePort>
|
||||
port = 9735;
|
||||
# target.port; target.addr; #< set if tor port != clightning port
|
||||
}];
|
||||
# allow "tor" group (i.e. clightning) to read /var/lib/tor/onion/clightning/hostname
|
||||
settings.HiddenServiceDirGroupReadable = true;
|
||||
};
|
||||
|
||||
# must be in "tor" group to read /var/lib/tor/onion/*/hostname
|
||||
users.users.clightning.extraGroups = [ "tor" ];
|
||||
|
||||
systemd.services.clightning.after = [ "tor.service" ];
|
||||
|
||||
# lightning-config contains fields from here:
|
||||
# - <https://docs.corelightning.org/docs/configuration>
|
||||
# secret config includes:
|
||||
# - bitcoin-rpcpassword
|
||||
# - alias=nodename
|
||||
# - rgb=rrggbb
|
||||
# - fee-base=<millisatoshi>
|
||||
# - fee-per-satoshi=<ppm>
|
||||
# - feature configs (i.e. experimental-xyz options)
|
||||
sane.services.clightning.extraConfig = ''
|
||||
log-level=debug:lightningd
|
||||
# peerswap:
|
||||
# - config example: <https://github.com/fort-nix/nix-bitcoin/pull/462/files#diff-b357d832705b8ce8df1f41934d613f79adb77c4cd5cd9e9eb12a163fca3e16c6>
|
||||
# XXX: peerswap crashes clightning on launch. stacktrace is useless.
|
||||
# plugin=${pkgs.peerswap}/bin/peerswap
|
||||
# peerswap-db-path=/var/lib/clightning/peerswap/swaps
|
||||
# peerswap-policy-path=...
|
||||
'';
|
||||
sane.services.clightning.extraConfigFiles = [ config.sops.secrets."lightning-config".path ];
|
||||
sops.secrets."lightning-config" = {
|
||||
mode = "0640";
|
||||
owner = "clightning";
|
||||
group = "clightning";
|
||||
};
|
||||
|
||||
sane.programs.clightning.enableFor.user.colin = true; # for debugging/admin: `lightning-cli`
|
||||
}
|
10
hosts/by-name/servo/services/cryptocurrencies/default.nix
Normal file
10
hosts/by-name/servo/services/cryptocurrencies/default.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./bitcoin.nix
|
||||
./clightning.nix
|
||||
./i2p.nix
|
||||
./monero.nix
|
||||
./tor.nix
|
||||
];
|
||||
}
|
4
hosts/by-name/servo/services/cryptocurrencies/i2p.nix
Normal file
4
hosts/by-name/servo/services/cryptocurrencies/i2p.nix
Normal file
@@ -0,0 +1,4 @@
|
||||
{ ... }:
|
||||
{
|
||||
services.i2p.enable = true;
|
||||
}
|
31
hosts/by-name/servo/services/cryptocurrencies/monero.nix
Normal file
31
hosts/by-name/servo/services/cryptocurrencies/monero.nix
Normal file
@@ -0,0 +1,31 @@
|
||||
# as of 2023/11/26: complete downloaded blockchain should be 200GiB on disk, give or take.
|
||||
{ ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
# /var/lib/monero/lmdb is what consumes most of the space
|
||||
{ user = "monero"; group = "monero"; path = "/var/lib/monero"; method = "bind"; }
|
||||
];
|
||||
|
||||
services.monero.enable = true;
|
||||
services.monero.limits.upload = 5000; # in kB/s
|
||||
services.monero.extraConfig = ''
|
||||
# see: monero doc/ANONYMITY_NETWORKS.md
|
||||
#
|
||||
# "If any anonymity network is enabled, transactions being broadcast that lack a valid 'context'
|
||||
# (i.e. the transaction did not come from a P2P connection) will only be sent to peers on anonymity networks."
|
||||
#
|
||||
# i think this means that setting tx-proxy here ensures any transactions sent locally to my node (via RPC)
|
||||
# will be sent over an anonymity network.
|
||||
tx-proxy=i2p,127.0.0.1:9000
|
||||
tx-proxy=tor,127.0.0.1:9050
|
||||
'';
|
||||
|
||||
# monero ports: <https://monero.stackexchange.com/questions/604/what-ports-does-monero-use-rpc-p2p-etc>
|
||||
# - 18080 = "P2P" monero node <-> monero node connections
|
||||
# - 18081 = "RPC" monero client -> monero node connections
|
||||
sane.ports.ports."18080" = {
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.wan = true;
|
||||
description = "colin-monero-p2p";
|
||||
};
|
||||
}
|
25
hosts/by-name/servo/services/cryptocurrencies/tor.nix
Normal file
25
hosts/by-name/servo/services/cryptocurrencies/tor.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
# tor settings: <https://2019.www.torproject.org/docs/tor-manual.html.en>
|
||||
{ lib, ... }:
|
||||
{
|
||||
# tor hidden service hostnames aren't deterministic, so persist.
|
||||
# might be able to get away with just persisting /var/lib/tor/onion, not sure.
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "tor"; group = "tor"; mode = "0710"; path = "/var/lib/tor"; method = "bind"; }
|
||||
];
|
||||
|
||||
# tor: `tor.enable` doesn't start a relay, exit node, proxy, etc. it's minimal.
|
||||
# tor.client.enable configures a torsocks proxy, accessible *only* to localhost.
|
||||
# at 127.0.0.1:9050
|
||||
services.tor.enable = true;
|
||||
services.tor.client.enable = true;
|
||||
|
||||
# in order for services to read /var/lib/tor/onion/*/hostname, they must be able to traverse /var/lib/tor,
|
||||
# and /var/lib/tor must have g+x.
|
||||
# DataDirectoryGroupReadable causes tor to use g+rx, technically more than we need, but all the files are 600 so it's fine.
|
||||
services.tor.settings.DataDirectoryGroupReadable = true;
|
||||
# StateDirectoryMode defaults to 0700, and thereby prevents the onion hostnames from being group readable
|
||||
systemd.services.tor.serviceConfig.StateDirectoryMode = lib.mkForce "0710";
|
||||
users.users.tor.homeMode = "0710"; # home mode defaults to 0700, causing readability problems, enforced by nixos "users" activation script
|
||||
|
||||
services.tor.settings.SafeLogging = false; # show actual .onion names in the syslog, else debugging is impossible
|
||||
}
|
@@ -1,27 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# using manual ddns now
|
||||
lib.mkIf false
|
||||
{
|
||||
systemd.services.ddns-afraid = {
|
||||
description = "update dynamic DNS entries for freedns.afraid.org";
|
||||
serviceConfig = {
|
||||
EnvironmentFile = config.sops.secrets."ddns_afraid.env".path;
|
||||
# TODO: ProtectSystem = "strict";
|
||||
# TODO: ProtectHome = "full";
|
||||
# TODO: PrivateTmp = true;
|
||||
};
|
||||
script = let
|
||||
curl = "${pkgs.curl}/bin/curl -4";
|
||||
in ''
|
||||
${curl} "https://freedns.afraid.org/dynamic/update.php?$AFRAID_KEY"
|
||||
'';
|
||||
};
|
||||
systemd.timers.ddns-afraid = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig = {
|
||||
OnStartupSec = "2min";
|
||||
OnUnitActiveSec = "10min";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# we use manual DDNS now
|
||||
lib.mkIf false
|
||||
{
|
||||
systemd.services.ddns-he = {
|
||||
description = "update dynamic DNS entries for HurricaneElectric";
|
||||
serviceConfig = {
|
||||
EnvironmentFile = config.sops.secrets."ddns_he.env".path;
|
||||
# TODO: ProtectSystem = "strict";
|
||||
# TODO: ProtectHome = "full";
|
||||
# TODO: PrivateTmp = true;
|
||||
};
|
||||
# HE DDNS API is documented: https://dns.he.net/docs.html
|
||||
script = let
|
||||
crl = "${pkgs.curl}/bin/curl -4";
|
||||
in ''
|
||||
${crl} "https://he.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=he.uninsane.org"
|
||||
${crl} "https://native.uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=native.uninsane.org"
|
||||
${crl} "https://uninsane.org:$HE_PASSPHRASE@dyn.dns.he.net/nic/update?hostname=uninsane.org"
|
||||
'';
|
||||
};
|
||||
systemd.timers.ddns-he = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig = {
|
||||
OnStartupSec = "2min";
|
||||
OnUnitActiveSec = "10min";
|
||||
};
|
||||
};
|
||||
}
|
@@ -3,8 +3,7 @@
|
||||
imports = [
|
||||
./calibre.nix
|
||||
./coturn.nix
|
||||
./ddns-afraid.nix
|
||||
./ddns-he.nix
|
||||
./cryptocurrencies
|
||||
./email
|
||||
./ejabberd.nix
|
||||
./freshrss.nix
|
||||
@@ -20,12 +19,14 @@
|
||||
./matrix
|
||||
./navidrome.nix
|
||||
./nginx.nix
|
||||
./nixos-prebuild.nix
|
||||
./nixserve.nix
|
||||
./ntfy
|
||||
./pict-rs.nix
|
||||
./pleroma.nix
|
||||
./postgres.nix
|
||||
./prosody
|
||||
./slskd.nix
|
||||
./transmission.nix
|
||||
./trust-dns.nix
|
||||
./wikipedia.nix
|
||||
|
@@ -45,7 +45,7 @@ in
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "ejabberd"; group = "ejabberd"; path = "/var/lib/ejabberd"; }
|
||||
{ user = "ejabberd"; group = "ejabberd"; path = "/var/lib/ejabberd"; method = "bind"; }
|
||||
];
|
||||
sane.ports.ports = lib.mkMerge ([
|
||||
{
|
||||
|
@@ -127,10 +127,11 @@
|
||||
services.dovecot2.modules = [
|
||||
pkgs.dovecot_pigeonhole # enables sieve execution (?)
|
||||
];
|
||||
services.dovecot2.sieveScripts = {
|
||||
services.dovecot2.sieve = {
|
||||
extensions = [ "fileinto" ];
|
||||
# if any messages fail to pass (or lack) DKIM, move them to Junk
|
||||
# XXX the key name ("after") is only used to order sieve execution/ordering
|
||||
after = builtins.toFile "ensuredkim.sieve" ''
|
||||
scripts.after = builtins.toFile "ensuredkim.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if not header :contains "Authentication-Results" "dkim=pass" {
|
||||
@@ -139,4 +140,6 @@
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.dovecot2.serviceConfig.RestartSec = lib.mkForce "15s"; # nixos defaults this to 1s
|
||||
}
|
||||
|
@@ -20,9 +20,9 @@ in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; }
|
||||
{ user = "root"; group = "root"; path = "/var/lib/postfix"; }
|
||||
{ user = "root"; group = "root"; path = "/var/spool/mail"; }
|
||||
{ user = "opendkim"; group = "opendkim"; path = "/var/lib/opendkim"; method = "bind"; }
|
||||
{ user = "root"; group = "root"; path = "/var/lib/postfix"; method = "bind"; }
|
||||
{ user = "root"; group = "root"; path = "/var/spool/mail"; method = "bind"; }
|
||||
# *probably* don't need these dirs:
|
||||
# "/var/lib/dhparams" # https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/security/dhparams.nix
|
||||
# "/var/lib/dovecot"
|
||||
|
@@ -29,8 +29,8 @@
|
||||
# - `sudo btrfs qgroup limit 20G /mnt/persist/ext/persist/var/export/playground`
|
||||
# to query the quota/status:
|
||||
# - `sudo btrfs qgroup show -re /var/export/playground`
|
||||
sane.persist.sys.ext = [
|
||||
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; }
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "root"; group = "export"; mode = "0775"; path = "/var/export/playground"; method = "bind"; }
|
||||
];
|
||||
|
||||
sane.fs."/var/export/README.md" = {
|
||||
|
@@ -91,7 +91,7 @@ let
|
||||
authFailJson = pkgs.writeText "sftp-auth-fail.json" (builtins.toJSON authResponseFail);
|
||||
unwrappedAuthProgram = pkgs.static-nix-shell.mkBash {
|
||||
pname = "sftpgo_external_auth_hook";
|
||||
src = ./.;
|
||||
srcRoot = ./.;
|
||||
pkgs = [ "coreutils" ];
|
||||
};
|
||||
authProgram = pkgs.writeShellScript "sftpgo-auth-hook" ''
|
||||
@@ -172,13 +172,15 @@ in
|
||||
|
||||
users.users.sftpgo.extraGroups = [ "export" ];
|
||||
|
||||
systemd.services.sftpgo.serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
systemd.services.sftpgo = {
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
serviceConfig = {
|
||||
ReadOnlyPaths = [ "/var/export" ];
|
||||
ReadWritePaths = [ "/var/export/playground" ];
|
||||
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@
|
||||
mode = "0400";
|
||||
};
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "freshrss"; group = "freshrss"; path = "/var/lib/freshrss"; }
|
||||
{ user = "freshrss"; group = "freshrss"; path = "/var/lib/freshrss"; method = "bind"; }
|
||||
];
|
||||
|
||||
services.freshrss.enable = true;
|
||||
|
@@ -4,7 +4,7 @@
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "git"; group = "gitea"; path = "/var/lib/gitea"; }
|
||||
{ user = "git"; group = "gitea"; path = "/var/lib/gitea"; method = "bind"; }
|
||||
];
|
||||
services.gitea.enable = true;
|
||||
services.gitea.user = "git"; # default is 'gitea'
|
||||
@@ -13,6 +13,10 @@
|
||||
services.gitea.appName = "Perfectly Sane Git";
|
||||
# services.gitea.disableRegistration = true;
|
||||
|
||||
services.gitea.database.createDatabase = false; #< silence warning which wants db user and name to be equal
|
||||
# TODO: remove this after merge: <https://github.com/NixOS/nixpkgs/pull/268849>
|
||||
services.gitea.database.socket = "/run/postgresql"; #< would have been set if createDatabase = true
|
||||
|
||||
# gitea doesn't create the git user
|
||||
users.users.git = {
|
||||
description = "Gitea Service";
|
||||
@@ -96,6 +100,24 @@
|
||||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
};
|
||||
# gitea serves all `raw` files as content-type: plain, but i'd like to serve them as their actual content type.
|
||||
# or at least, enough to make specific pages viewable (serving unoriginal content as arbitrary content type is dangerous).
|
||||
locations."~ ^/colin/phone-case-cq/raw/.*.html" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
extraConfig = ''
|
||||
proxy_hide_header Content-Type;
|
||||
default_type text/html;
|
||||
add_header Content-Type text/html;
|
||||
'';
|
||||
};
|
||||
locations."~ ^/colin/phone-case-cq/raw/.*.js" = {
|
||||
proxyPass = "http://127.0.0.1:3000";
|
||||
extraConfig = ''
|
||||
proxy_hide_header Content-Type;
|
||||
default_type text/html;
|
||||
add_header Content-Type text/javascript;
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."git" = "native";
|
||||
|
@@ -12,7 +12,7 @@ lib.mkIf false # i don't actively use ipfs anymore
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode? could be more granular
|
||||
{ user = "261"; group = "261"; path = "/var/lib/ipfs"; }
|
||||
{ user = "261"; group = "261"; path = "/var/lib/ipfs"; method = "bind"; }
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 4001 ];
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode? we only need this to save Indexer creds ==> migrate to config?
|
||||
{ user = "root"; group = "root"; path = "/var/lib/jackett"; }
|
||||
{ user = "root"; group = "root"; path = "/var/lib/jackett"; method = "bind"; }
|
||||
];
|
||||
services.jackett.enable = true;
|
||||
|
||||
|
@@ -41,7 +41,7 @@
|
||||
};
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin"; }
|
||||
{ user = "jellyfin"; group = "jellyfin"; mode = "0700"; path = "/var/lib/jellyfin"; method = "bind"; }
|
||||
];
|
||||
sane.fs."/var/lib/jellyfin/config/logging.json" = {
|
||||
# "Emby.Dlna" logging: <https://jellyfin.org/docs/general/networking/dlna>
|
||||
|
@@ -1,9 +1,19 @@
|
||||
# how to update wikipedia snapshot:
|
||||
# - browse for later snapshots:
|
||||
# - <https://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia>
|
||||
# - DL directly, or via rsync (resumable):
|
||||
# - `rsync --progress --append-verify rsync://mirror.accum.se/mirror/wikimedia.org/other/kiwix/zim/wikipedia/wikipedia_en_all_maxi_2022-05.zim .`
|
||||
|
||||
{ ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.ext = [
|
||||
{ user = "colin"; group = "users"; path = "/var/lib/kiwix"; method = "bind"; }
|
||||
];
|
||||
|
||||
sane.services.kiwix-serve = {
|
||||
enable = true;
|
||||
port = 8013;
|
||||
zimPaths = [ "/var/lib/uninsane/www-archive/wikipedia_en_all_maxi_2022-05.zim" ];
|
||||
zimPaths = [ "/var/lib/kiwix/wikipedia_en_all_maxi_2023-11.zim" ];
|
||||
};
|
||||
|
||||
services.nginx.virtualHosts."w.uninsane.org" = {
|
||||
|
@@ -5,7 +5,7 @@ let
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ inherit user group; mode = "0700"; path = stateDir; }
|
||||
{ inherit user group; mode = "0700"; path = stateDir; method = "bind"; }
|
||||
];
|
||||
|
||||
services.komga.enable = true;
|
||||
|
@@ -78,8 +78,8 @@ in {
|
||||
# CLI args: <https://git.asonix.dog/asonix/pict-rs#user-content-running>
|
||||
systemd.services.pict-rs.serviceConfig.ExecStart = lib.mkForce (lib.concatStringsSep " " [
|
||||
"${lib.getBin pict-rs}/bin/pict-rs run"
|
||||
"--media-max-frame-count" (builtins.toString (30*60*60))
|
||||
"--media-video-max-frame-count" (builtins.toString (30*60*60))
|
||||
"--media-process-timeout 120"
|
||||
"--media-enable-full-video true" # allow audio
|
||||
"--media-video-allow-audio" # allow audio
|
||||
]);
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@
|
||||
];
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; }
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/matrix-synapse"; method = "bind"; }
|
||||
];
|
||||
services.matrix-synapse.enable = true;
|
||||
services.matrix-synapse.settings = {
|
||||
|
@@ -6,7 +6,7 @@
|
||||
lib.mkIf false
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/mx-puppet-discord"; }
|
||||
{ user = "matrix-synapse"; group = "matrix-synapse"; path = "/var/lib/mx-puppet-discord"; method = "bind"; }
|
||||
];
|
||||
|
||||
services.matrix-synapse.settings.app_service_config_files = [
|
||||
|
@@ -103,7 +103,7 @@ in
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "matrix-appservice-irc"; group = "matrix-appservice-irc"; path = "/var/lib/matrix-appservice-irc"; }
|
||||
{ user = "matrix-appservice-irc"; group = "matrix-appservice-irc"; path = "/var/lib/matrix-appservice-irc"; method = "bind"; }
|
||||
];
|
||||
|
||||
# XXX: matrix-appservice-irc PreStart tries to chgrp the registration.yml to matrix-synapse,
|
||||
|
@@ -1,10 +1,12 @@
|
||||
# config options:
|
||||
# - <https://github.com/mautrix/signal/blob/master/mautrix_signal/example-config.yaml>
|
||||
{ config, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
lib.mkIf false # disabled 2024/01/11: i don't use it, and pkgs.mautrix-signal had some API changes
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; }
|
||||
{ user = "signald"; group = "signald"; path = "/var/lib/signald"; }
|
||||
{ user = "mautrix-signal"; group = "mautrix-signal"; path = "/var/lib/mautrix-signal"; method = "bind"; }
|
||||
{ user = "signald"; group = "signald"; path = "/var/lib/signald"; method = "bind"; }
|
||||
];
|
||||
|
||||
# allow synapse to read the registration file
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "navidrome"; group = "navidrome"; path = "/var/lib/navidrome"; }
|
||||
{ user = "navidrome"; group = "navidrome"; path = "/var/lib/navidrome"; method = "bind"; }
|
||||
];
|
||||
services.navidrome.enable = true;
|
||||
services.navidrome.settings = {
|
||||
|
@@ -54,8 +54,10 @@ in
|
||||
services.nginx.recommendedOptimisation = true;
|
||||
|
||||
# web blog/personal site
|
||||
# alternative way to link stuff into the share:
|
||||
# sane.fs."/var/lib/uninsane/share/Ubunchu".mount.bind = "/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu";
|
||||
# sane.fs."/var/lib/uninsane/media/Books/Visual/HiroshiSeo/Ubunchu".dir = {};
|
||||
services.nginx.virtualHosts."uninsane.org" = publog {
|
||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||
# a lot of places hardcode https://uninsane.org,
|
||||
# and then when we mix http + non-https, we get CORS violations
|
||||
# and things don't look right. so force SSL.
|
||||
@@ -65,9 +67,28 @@ in
|
||||
# for OCSP stapling
|
||||
sslTrustedCertificate = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||
|
||||
# uninsane.org/share/foo => /var/lib/uninsane/root/share/foo.
|
||||
# yes, nginx does not strip the prefix when evaluating against the root.
|
||||
locations."/share".root = "/var/lib/uninsane/root";
|
||||
locations."/" = {
|
||||
root = "${pkgs.uninsane-dot-org}/share/uninsane-dot-org";
|
||||
tryFiles = "$uri $uri/ @fallback";
|
||||
};
|
||||
|
||||
# unversioned files
|
||||
locations."@fallback" = {
|
||||
root = "/var/www/sites/uninsane.org";
|
||||
};
|
||||
|
||||
# uninsane.org/share/foo => /var/www/sites/uninsane.org/share/foo.
|
||||
# special-cased to enable directory listings
|
||||
locations."/share" = {
|
||||
root = "/var/www/sites/uninsane.org";
|
||||
extraConfig = ''
|
||||
# autoindex => render directory listings
|
||||
autoindex on;
|
||||
# don't follow any symlinks when serving files
|
||||
# otherwise it allows a directory escape
|
||||
disable_symlinks on;
|
||||
'';
|
||||
};
|
||||
|
||||
# allow matrix users to discover that @user:uninsane.org is reachable via matrix.uninsane.org
|
||||
locations."= /.well-known/matrix/server".extraConfig =
|
||||
@@ -108,6 +129,19 @@ in
|
||||
# proxyPass = "http://127.0.0.1:4000";
|
||||
# extraConfig = pleromaExtraConfig;
|
||||
# };
|
||||
|
||||
# redirect common feed URIs to the canonical feed
|
||||
locations."= /atom".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /feed".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /rss".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/atom".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/atom.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/feed".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/feed.xml".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/rss".extraConfig = "return 301 /atom.xml;";
|
||||
locations."= /blog/rss.xml".extraConfig = "return 301 /atom.xml;";
|
||||
};
|
||||
|
||||
|
||||
@@ -135,9 +169,8 @@ in
|
||||
security.acme.defaults.email = "admin.acme@uninsane.org";
|
||||
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; }
|
||||
{ user = "colin"; group = "users"; path = "/var/www/sites"; }
|
||||
{ user = "acme"; group = "acme"; path = "/var/lib/acme"; method = "bind"; }
|
||||
{ user = "colin"; group = "users"; path = "/var/www/sites"; method = "bind"; }
|
||||
];
|
||||
|
||||
# let's encrypt default chain looks like:
|
||||
|
26
hosts/by-name/servo/services/nixos-prebuild.nix
Normal file
26
hosts/by-name/servo/services/nixos-prebuild.nix
Normal file
@@ -0,0 +1,26 @@
|
||||
{ lib, pkgs, ... }:
|
||||
|
||||
lib.optionalAttrs false # disabled until i can be sure it's not gonna OOM my server in the middle of the night
|
||||
{
|
||||
systemd.services.nixos-prebuild = {
|
||||
description = "build a nixos image with all updated deps";
|
||||
path = with pkgs; [ coreutils git nix ];
|
||||
script = ''
|
||||
working=$(mktemp -d /tmp/nixos-prebuild.XXXXXX)
|
||||
pushd "$working"
|
||||
git clone https://git.uninsane.org/colin/nix-files.git \
|
||||
&& cd nix-files \
|
||||
&& nix flake update \
|
||||
|| true
|
||||
RC=$(nix run "$working/nix-files#check" -- -j1 --cores 5 --builders "")
|
||||
popd
|
||||
rm -rf "$working"
|
||||
exit "$RC"
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.timers.nixos-prebuild = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
timerConfig.OnCalendar = "11,23:00:00";
|
||||
};
|
||||
}
|
@@ -34,7 +34,7 @@ in
|
||||
# not 100% necessary to persist this, but ntfy does keep a 12hr (by default) cache
|
||||
# for pushing notifications to users who become offline.
|
||||
# ACLs also live here.
|
||||
{ user = "ntfy-sh"; group ="ntfy-sh"; path = "/var/lib/ntfy-sh"; }
|
||||
{ user = "ntfy-sh"; group ="ntfy-sh"; path = "/var/lib/ntfy-sh"; method = "bind"; }
|
||||
];
|
||||
|
||||
services.ntfy-sh.enable = true;
|
||||
|
@@ -49,7 +49,7 @@ in
|
||||
type = types.package;
|
||||
default = pkgs.static-nix-shell.mkPython3Bin {
|
||||
pname = "ntfy-waiter";
|
||||
src = ./.;
|
||||
srcRoot = ./.;
|
||||
pkgs = [ "ntfy-sh" ];
|
||||
};
|
||||
description = ''
|
||||
@@ -64,7 +64,7 @@ in
|
||||
protocol = [ "tcp" ];
|
||||
visibleTo.lan = true;
|
||||
visibleTo.wan = true;
|
||||
description = "colin-notification-waiter-${builtins.toString (port+1)}-of-${builtins.toString numPorts}";
|
||||
description = "colin-notification-waiter-${builtins.toString (port - portLow + 1)}-of-${builtins.toString numPorts}";
|
||||
};
|
||||
}));
|
||||
systemd.services = lib.mkMerge (builtins.map mkService portRange);
|
||||
|
@@ -6,7 +6,7 @@ let
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = lib.mkIf cfg.enable [
|
||||
{ user = "pict-rs"; group = "pict-rs"; path = cfg.dataDir; }
|
||||
{ user = "pict-rs"; group = "pict-rs"; path = cfg.dataDir; method = "bind"; }
|
||||
];
|
||||
|
||||
systemd.services.pict-rs.serviceConfig = {
|
||||
|
@@ -15,7 +15,7 @@ let
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; }
|
||||
{ user = "pleroma"; group = "pleroma"; path = "/var/lib/pleroma"; method = "bind"; }
|
||||
];
|
||||
services.pleroma.enable = true;
|
||||
services.pleroma.secretConfigFile = config.sops.secrets.pleroma_secrets.path;
|
||||
|
@@ -8,7 +8,7 @@ in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode?
|
||||
{ user = "postgres"; group = "postgres"; path = "/var/lib/postgresql"; }
|
||||
{ user = "postgres"; group = "postgres"; path = "/var/lib/postgresql"; method = "bind"; }
|
||||
];
|
||||
services.postgresql.enable = true;
|
||||
|
||||
|
@@ -57,7 +57,7 @@ let
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; }
|
||||
{ user = "prosody"; group = "prosody"; path = "/var/lib/prosody"; method = "bind"; }
|
||||
];
|
||||
sane.ports.ports."5000" = {
|
||||
protocol = [ "tcp" ];
|
||||
|
74
hosts/by-name/servo/services/slskd.nix
Normal file
74
hosts/by-name/servo/services/slskd.nix
Normal file
@@ -0,0 +1,74 @@
|
||||
# Soulseek daemon (p2p file sharing with an emphasis on Music)
|
||||
# docs: <https://github.com/slskd/slskd/blob/master/docs/config.md>
|
||||
#
|
||||
# config precedence (higher precedence overrules lower precedence):
|
||||
# - Default Values < Environment Variables < YAML Configuraiton File < Command Line Arguments
|
||||
{ config, lib, ... }:
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
{ user = "slskd"; group = "slskd"; path = "/var/lib/slskd"; method = "bind"; }
|
||||
];
|
||||
sops.secrets."slskd_env" = {
|
||||
owner = config.users.users.slskd.name;
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
users.users.slskd.extraGroups = [ "media" ];
|
||||
|
||||
sane.ports.ports."50000" = {
|
||||
protocol = [ "tcp" ];
|
||||
# not visible to WAN: i run this in a separate netns
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-soulseek";
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."soulseek" = "native";
|
||||
|
||||
services.nginx.virtualHosts."soulseek.uninsane.org" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://10.0.1.6:5001";
|
||||
proxyWebsockets = true;
|
||||
};
|
||||
};
|
||||
|
||||
services.slskd.enable = true;
|
||||
# env file, for auth (SLSKD_SLSK_PASSWORD, SLSKD_SLSK_USERNAME)
|
||||
services.slskd.environmentFile = config.sops.secrets.slskd_env.path;
|
||||
services.slskd.settings = {
|
||||
soulseek.diagnostic_level = "Debug"; # one of "None"|"Warning"|"Info"|"Debug"
|
||||
shares.directories = [
|
||||
# folders to share
|
||||
# syntax: <https://github.com/slskd/slskd/blob/master/docs/config.md#directories>
|
||||
# [Alias]/path/on/disk
|
||||
# NOTE: Music library is quick to scan; videos take a solid 10min to scan.
|
||||
# TODO: re-enable the other libraries
|
||||
# "[Audioooks]/var/lib/uninsane/media/Books/Audiobooks"
|
||||
# "[Books]/var/lib/uninsane/media/Books/Books"
|
||||
# "[Manga]/var/lib/uninsane/media/Books/Visual"
|
||||
# "[games]/var/lib/uninsane/media/games"
|
||||
"[Music]/var/lib/uninsane/media/Music"
|
||||
# "[Film]/var/lib/uninsane/media/Videos/Film"
|
||||
# "[Shows]/var/lib/uninsane/media/Videos/Shows"
|
||||
];
|
||||
# directories.downloads = "..." # TODO
|
||||
# directories.incomplete = "..." # TODO
|
||||
# what unit is this? kbps??
|
||||
global.upload.speed_limit = 32000;
|
||||
web.logging = true;
|
||||
# debug = true;
|
||||
flags.no_logo = true; # don't show logo at start
|
||||
# flags.volatile = true; # store searches and active transfers in RAM (completed transfers still go to disk). rec for btrfs/zfs
|
||||
};
|
||||
|
||||
systemd.services.slskd = {
|
||||
serviceConfig = {
|
||||
# run this behind the OVPN static VPN
|
||||
NetworkNamespacePath = "/run/netns/ovpns";
|
||||
Restart = lib.mkForce "always"; # exits "success" when it fails to connect to soulseek server
|
||||
RestartSec = "60s";
|
||||
Group = "media";
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,14 +1,37 @@
|
||||
{ config, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
# 2023/09/06: nixpkgs `transmission` defaults to old 3.00
|
||||
# 2024/02/15: some torrent trackers whitelist clients; everyone is still on 3.00 for some reason :|
|
||||
# some do this via peer-id (e.g. baka); others via user-agent (e.g. MAM).
|
||||
# peer-id format is essentially the same between 3.00 and 4.x (just swap the MAJOR/MINOR/PATCH numbers).
|
||||
# user-agent format has changed. `Transmission/3.00` (old) v.s. `TRANSMISSION/MAJ.MIN.PATCH` (new).
|
||||
realTransmission = pkgs.transmission_4;
|
||||
realVersion = {
|
||||
major = lib.versions.major realTransmission.version;
|
||||
minor = lib.versions.minor realTransmission.version;
|
||||
patch = lib.versions.patch realTransmission.version;
|
||||
};
|
||||
package = realTransmission.overrideAttrs (upstream: {
|
||||
# `cmakeFlags = [ "-DTR_VERSION_MAJOR=3" ]`, etc, doesn't seem to take effect.
|
||||
postPatch = (upstream.postPatch or "") + ''
|
||||
substituteInPlace CMakeLists.txt \
|
||||
--replace-fail 'TR_VERSION_MAJOR "${realVersion.major}"' 'TR_VERSION_MAJOR "3"' \
|
||||
--replace-fail 'TR_VERSION_MINOR "${realVersion.minor}"' 'TR_VERSION_MINOR "0"' \
|
||||
--replace-fail 'TR_VERSION_PATCH "${realVersion.patch}"' 'TR_VERSION_PATCH "0"' \
|
||||
--replace-fail 'set(TR_USER_AGENT_PREFIX "''${TR_SEMVER}")' 'set(TR_USER_AGENT_PREFIX "3.00")'
|
||||
'';
|
||||
});
|
||||
in
|
||||
{
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: mode? we need this specifically for the stats tracking in .config/
|
||||
{ user = "transmission"; group = config.users.users.transmission.group; path = "/var/lib/transmission"; }
|
||||
{ user = "transmission"; group = config.users.users.transmission.group; path = "/var/lib/transmission"; method = "bind"; }
|
||||
];
|
||||
users.users.transmission.extraGroups = [ "media" ];
|
||||
|
||||
services.transmission.enable = true;
|
||||
services.transmission.package = pkgs.transmission_4; #< 2023/09/06: nixpkgs `transmission` defaults to old 3.00
|
||||
services.transmission.package = package;
|
||||
#v setting `group` this way doesn't tell transmission to `chown` the files it creates
|
||||
# it's a nixpkgs setting which just runs the transmission daemon as this group
|
||||
services.transmission.group = "media";
|
||||
@@ -16,10 +39,12 @@
|
||||
# transmission will by default not allow the world to read its files.
|
||||
services.transmission.downloadDirPermissions = "775";
|
||||
services.transmission.extraFlags = [
|
||||
"--log-level=debug"
|
||||
# "--log-level=debug"
|
||||
];
|
||||
|
||||
services.transmission.settings = {
|
||||
# DOCUMENTATION/options list: <https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#options>
|
||||
|
||||
# message-level = 3; #< enable for debug logging. 0-3, default is 2.
|
||||
# 0.0.0.0 => allow rpc from any host: we gate it via firewall and auth requirement
|
||||
rpc-bind-address = "0.0.0.0";
|
||||
@@ -39,9 +64,9 @@
|
||||
encryption = 2;
|
||||
|
||||
# units in kBps
|
||||
speed-limit-down = 3000;
|
||||
speed-limit-down = 12000;
|
||||
speed-limit-down-enabled = true;
|
||||
speed-limit-up = 600;
|
||||
speed-limit-up = 800;
|
||||
speed-limit-up-enabled = true;
|
||||
|
||||
# see: https://git.zknt.org/mirror/transmission/commit/cfce6e2e3a9b9d31a9dafedd0bdc8bf2cdb6e876?lang=bg-BG
|
||||
@@ -91,5 +116,10 @@
|
||||
};
|
||||
|
||||
sane.dns.zones."uninsane.org".inet.CNAME."bt" = "native";
|
||||
sane.ports.ports."51413" = {
|
||||
protocol = [ "tcp" "udp" ];
|
||||
visibleTo.ovpn = true;
|
||||
description = "colin-bittorrent";
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,8 @@ in lib.mkMerge [
|
||||
# don't bind to IPv6 until i explicitly test that stack
|
||||
services.trust-dns.settings.listen_addrs_ipv6 = [];
|
||||
services.trust-dns.quiet = true;
|
||||
# FIXME(2023/11/26): services.trust-dns.debug doesn't log requests: use RUST_LOG=debug env for that.
|
||||
# - see: <https://github.com/hickory-dns/hickory-dns/issues/2082>
|
||||
# services.trust-dns.debug = true;
|
||||
|
||||
sane.ports.ports."53" = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./feeds.nix
|
||||
@@ -8,93 +8,52 @@
|
||||
./hosts.nix
|
||||
./ids.nix
|
||||
./machine-id.nix
|
||||
./net.nix
|
||||
./nix-path
|
||||
./net
|
||||
./nix
|
||||
./persist.nix
|
||||
./polyunfill.nix
|
||||
./programs
|
||||
./secrets.nix
|
||||
./ssh.nix
|
||||
./systemd.nix
|
||||
./users
|
||||
./vpn.nix
|
||||
];
|
||||
|
||||
sane.nixcache.enable-trusted-keys = true;
|
||||
sane.nixcache.enable = lib.mkDefault true;
|
||||
sane.persist.enable = lib.mkDefault true;
|
||||
sane.root-on-tmpfs = lib.mkDefault true;
|
||||
sane.programs.sysadminUtils.enableFor.system = lib.mkDefault true;
|
||||
sane.programs.consoleUtils.enableFor.user.colin = lib.mkDefault true;
|
||||
|
||||
nixpkgs.config.allowUnfree = true;
|
||||
nixpkgs.config.allowBroken = true; # NIXPKGS_ALLOW_BROKEN
|
||||
nixpkgs.config.allowUnfree = true; # NIXPKGS_ALLOW_UNFREE=1
|
||||
nixpkgs.config.allowBroken = true; # NIXPKGS_ALLOW_BROKEN=1
|
||||
|
||||
# time.timeZone = "America/Los_Angeles";
|
||||
time.timeZone = "Etc/UTC"; # DST is too confusing for me => use a stable timezone
|
||||
|
||||
# allow `nix flake ...` command
|
||||
# TODO: is this still required?
|
||||
nix.extraOptions = ''
|
||||
experimental-features = nix-command flakes
|
||||
'';
|
||||
# hardlinks identical files in the nix store to save 25-35% disk space.
|
||||
# unclear _when_ this occurs. it's not a service.
|
||||
# does the daemon continually scan the nix store?
|
||||
# does the builder use some content-addressed db to efficiently dedupe?
|
||||
nix.settings.auto-optimise-store = true;
|
||||
|
||||
services.journald.extraConfig = ''
|
||||
# docs: `man journald.conf`
|
||||
# merged journald config is deployed to /etc/systemd/journald.conf
|
||||
[Journal]
|
||||
# disable journal compression because the underlying fs is compressed
|
||||
Compress=no
|
||||
'';
|
||||
|
||||
systemd.services.nix-daemon.serviceConfig = {
|
||||
# the nix-daemon manages nix builders
|
||||
# kill nix-daemon subprocesses when systemd-oomd detects an out-of-memory condition
|
||||
# see:
|
||||
# - nixos PR that enabled systemd-oomd: <https://github.com/NixOS/nixpkgs/pull/169613>
|
||||
# - systemd's docs on these properties: <https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill>
|
||||
#
|
||||
# systemd's docs warn that without swap, systemd-oomd might not be able to react quick enough to save the system.
|
||||
# see `man oomd.conf` for further tunables that may help.
|
||||
#
|
||||
# alternatively, apply this more broadly with `systemd.oomd.enableSystemSlice = true` or `enableRootSlice`
|
||||
# TODO: also apply this to the guest user's slice (user-1100.slice)
|
||||
# TODO: also apply this to distccd
|
||||
ManagedOOMMemoryPressure = "kill";
|
||||
ManagedOOMSwap = "kill";
|
||||
};
|
||||
|
||||
|
||||
system.activationScripts.nixClosureDiff = {
|
||||
supportsDryActivation = true;
|
||||
text = ''
|
||||
# show which packages changed versions or are new/removed in this upgrade
|
||||
# source: <https://github.com/luishfonseca/dotfiles/blob/32c10e775d9ec7cc55e44592a060c1c9aadf113e/modules/upgrade-diff.nix>
|
||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||
# modified to not error on boot (when /run/current-system doesn't exist)
|
||||
if [ -d /run/current-system ]; then
|
||||
${pkgs.nvd}/bin/nvd --nix-bin-dir=${pkgs.nix}/bin diff /run/current-system "$systemConfig"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
system.activationScripts.notifyActive = {
|
||||
text = ''
|
||||
# send a notification to any sway users logged in, that the system has been activated/upgraded.
|
||||
# this probably doesn't work if more than one sway session exists on the system.
|
||||
_notifyActiveSwaySock="$(echo /run/user/*/sway-ipc*.sock)"
|
||||
if [ -e "$_notifyActiveSwaySock" ]; then
|
||||
SWAYSOCK="$_notifyActiveSwaySock" ${config.sane.programs.sway.packageUnwrapped}/bin/swaymsg -- exec \
|
||||
"${pkgs.libnotify}/bin/notify-send 'nixos activated' 'version: $(cat $systemConfig/nixos-version)'"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
# disable non-required packages like nano, perl, rsync, strace
|
||||
environment.defaultPackages = [];
|
||||
|
||||
# dconf docs: <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/desktop_migration_and_administration_guide/profiles>
|
||||
# this lets programs temporarily write user-level dconf settings (aka gsettings).
|
||||
# they're written to ~/.config/dconf/user, unless `DCONF_PROFILE` is set to something other than the default of /etc/dconf/profile/user
|
||||
# find keys/values with `dconf dump /`
|
||||
programs.dconf.enable = true;
|
||||
programs.dconf.packages = [
|
||||
(pkgs.writeTextFile {
|
||||
name = "dconf-user-profile";
|
||||
destination = "/etc/dconf/profile/user";
|
||||
text = ''
|
||||
user-db:user
|
||||
system-db:site
|
||||
'';
|
||||
})
|
||||
];
|
||||
# sane.programs.glib.enableFor.user.colin = true; # for `gsettings`
|
||||
|
||||
# link debug symbols into /run/current-system/sw/lib/debug
|
||||
# hopefully picked up by gdb automatically?
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# where to find good stuff?
|
||||
# - universal search/directory: <https://podcastindex.org>
|
||||
# - podcasts w/ a community: <https://lemmyverse.net/communities?query=podcast>
|
||||
# - podcast rec thread: <https://lemmy.ml/post/1565858>
|
||||
#
|
||||
@@ -50,6 +51,8 @@ let
|
||||
else
|
||||
"infrequent"
|
||||
));
|
||||
} // lib.optionalAttrs (lib.hasPrefix "https://www.youtube.com/" raw.url) {
|
||||
format = "video";
|
||||
} // lib.optionalAttrs (raw.is_podcast or false) {
|
||||
format = "podcast";
|
||||
} // lib.optionalAttrs (raw.title or "" != "") {
|
||||
@@ -60,6 +63,7 @@ let
|
||||
(fromDb "acquiredlpbonussecretsecret.libsyn.com" // tech) # ACQ2 - more "Acquired" episodes
|
||||
(fromDb "allinchamathjason.libsyn.com" // pol)
|
||||
(fromDb "anchor.fm/s/34c7232c/podcast/rss" // tech) # Civboot -- https://anchor.fm/civboot
|
||||
(fromDb "anchor.fm/s/2da69154/podcast/rss" // tech) # POD OF JAKE -- https://podofjake.com/
|
||||
(fromDb "cast.postmarketos.org" // tech)
|
||||
(fromDb "congressionaldish.libsyn.com" // pol) # Jennifer Briney
|
||||
(fromDb "craphound.com" // pol) # Cory Doctorow -- both podcast & text entries
|
||||
@@ -69,21 +73,21 @@ let
|
||||
(fromDb "feeds.feedburner.com/80000HoursPodcast" // rat)
|
||||
(fromDb "feeds.feedburner.com/dancarlin/history" // rat)
|
||||
(fromDb "feeds.feedburner.com/radiolab" // pol) # Radiolab -- also available here, but ONLY OVER HTTP: <http://feeds.wnyc.org/radiolab>
|
||||
(fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||
(fromDb "feeds.megaphone.fm/behindthebastards" // pol) # also Maggie Killjoy
|
||||
(fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
(fromDb "feeds.megaphone.fm/recodedecode" // tech) # The Verge - Decoder
|
||||
(fromDb "feeds.simplecast.com/54nAGcIl" // pol) # The Daily
|
||||
(fromDb "feeds.simplecast.com/82FI35Px" // pol) # Ezra Klein Show
|
||||
(fromDb "feeds.simplecast.com/wgl4xEgL" // rat) # Econ Talk
|
||||
(fromDb "feeds.simplecast.com/xKJ93w_w" // uncat) # Atlas Obscura
|
||||
(fromDb "feeds.transistor.fm/acquired" // tech)
|
||||
(fromDb "fulltimenix.com" // tech)
|
||||
(fromDb "lexfridman.com/podcast" // rat)
|
||||
(fromDb "mapspodcast.libsyn.com" // uncat) # Multidisciplinary Association for Psychedelic Studies
|
||||
(fromDb "omegataupodcast.net" // tech) # 3/4 German; 1/4 eps are English
|
||||
(fromDb "omny.fm/shows/cool-people-who-did-cool-stuff" // pol) # Maggie Killjoy -- referenced by Cory Doctorow
|
||||
(fromDb "omny.fm/shows/the-dollop-with-dave-anthony-and-gareth-reynolds") # The Dollop history/comedy
|
||||
(fromDb "originstories.libsyn.com" // uncat)
|
||||
(fromDb "podcast.posttv.com/itunes/post-reports.xml" // pol)
|
||||
(fromDb "podcast.thelinuxexp.com" // tech)
|
||||
(fromDb "politicalorphanage.libsyn.com" // pol)
|
||||
(fromDb "reverseengineering.libsyn.com/rss" // tech) # UnNamed Reverse Engineering Podcast
|
||||
(fromDb "rss.acast.com/deconstructed") # The Intercept - Deconstructed
|
||||
@@ -92,17 +96,22 @@ let
|
||||
(fromDb "rss.art19.com/60-minutes" // pol)
|
||||
(fromDb "rss.art19.com/the-portal" // rat) # Eric Weinstein
|
||||
(fromDb "seattlenice.buzzsprout.com" // pol)
|
||||
(fromDb "srslywrong.com" // pol)
|
||||
(fromDb "sharkbytes.transistor.fm" // tech) # Wireshark Podcast o_0
|
||||
(fromDb "sscpodcast.libsyn.com" // rat) # Astral Codex Ten
|
||||
(fromDb "talesfromthebridge.buzzsprout.com" // tech) # Sci-Fi? has Peter Watts; author of No Moods, Ads or Cutesy Fucking Icons (rifters.com)
|
||||
(fromDb "techwontsave.us" // pol) # rec by Cory Doctorow
|
||||
# (fromDb "trashfuturepodcast.podbean.com" // pol) # rec by Cory Doctorow, but way rambly
|
||||
(fromDb "wakingup.libsyn.com" // pol) # Sam Harris
|
||||
(fromDb "werenotwrong.fireside.fm" // pol)
|
||||
|
||||
# (fromDb "feeds.libsyn.com/421877" // rat) # Less Wrong Curated
|
||||
# (fromDb "feeds.megaphone.fm/hubermanlab" // uncat) # Daniel Huberman on sleep
|
||||
# (fromDb "feeds.simplecast.com/l2i9YnTd" // tech // pol) # Hard Fork (NYtimes tech)
|
||||
# (fromDb "podcast.thelinuxexp.com" // tech) # low-brow linux/foss PR announcements
|
||||
# (fromDb "rss.art19.com/your-welcome" // pol) # Michael Malice - Your Welcome -- also available here: <https://origin.podcastone.com/podcast?categoryID2=2232>
|
||||
# (fromDb "rss.prod.firstlook.media/deconstructed/podcast.rss" // pol) #< possible URL rot
|
||||
# (fromDb "rss.prod.firstlook.media/intercepted/podcast.rss" // pol) #< possible URL rot
|
||||
# (fromDb "trashfuturepodcast.podbean.com" // pol) # rec by Cory Doctorow, but way rambly
|
||||
# (mkPod "https://anchor.fm/s/21bc734/podcast/rss" // pol // infrequent) # Emerge: making sense of what's next -- <https://www.whatisemerging.com/emergepodcast>
|
||||
# (mkPod "https://audioboom.com/channels/5097784.rss" // tech) # Lateral with Tom Scott
|
||||
# (mkPod "https://feeds.megaphone.fm/RUNMED9919162779" // pol // infrequent) # The Witch Trials of J.K. Rowling: <https://www.thefp.com/witchtrials>
|
||||
@@ -110,135 +119,120 @@ let
|
||||
];
|
||||
|
||||
texts = [
|
||||
# AGGREGATORS (> 1 post/day)
|
||||
(fromDb "lwn.net" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
(fromDb "acoup.blog/feed") # history, states. author: <https://historians.social/@bretdevereaux/following>
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
# (fromDb "balajis.com" // pol) # Balaji
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "bitsaboutmoney.com" // uncat)
|
||||
(fromDb "blog.danieljanus.pl" // tech)
|
||||
(fromDb "blog.dshr.org" // pol) # David Rosenthal
|
||||
(fromDb "blog.jmp.chat" // tech)
|
||||
(fromDb "blog.rust-lang.org" // tech)
|
||||
(fromDb "blog.thalheim.io" // tech) # Mic92
|
||||
(fromDb "bunniestudios.com" // tech) # Bunnie Juang
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
# (fromDb "econlib.org" // pol)
|
||||
|
||||
# AGGREGATORS (< 1 post/day)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "gwern.net" // rat)
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(fromDb "interconnected.org/home/feed" // rat) # Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "jeffgeerling.com" // tech)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "kill-the-newsletter.com/feeds/joh91bv7am2pnznv.xml" // pol) # Matt Levine - Money Stuff
|
||||
(fromDb "kosmosghost.github.io/index.xml" // tech)
|
||||
# (fromDb "lesswrong.com" // rat)
|
||||
(fromDb "linmob.net" // tech)
|
||||
(fromDb "lwn.net" // tech)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "mg.lol" // tech)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
(fromDb "overcomingbias.com" // rat) # Robin Hanson
|
||||
(fromDb "palladiummag.com" // uncat)
|
||||
(fromDb "philosopher.coach" // rat) # Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "preposterousuniverse.com" // rat) # Sean Carroll
|
||||
(fromDb "profectusmag.com" // uncat)
|
||||
(fromDb "project-insanity.org" // tech) # shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "putanumonit.com" // rat) # mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "rifters.com/crawl" // uncat) # No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "righto.com" // tech) # Ken Shirriff
|
||||
(fromDb "rootsofprogress.org" // rat) # Jason Crawford
|
||||
(fromDb "sagacioussuricata.com" // tech) # ian (Sanctuary)
|
||||
(fromDb "semiaccurate.com" // tech)
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "sideways-view.com" // rat) # Paul Christiano
|
||||
(fromDb "slimemoldtimemold.com" // rat)
|
||||
(fromDb "spectrum.ieee.org" // tech)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
# (fromDb "theregister.com" // tech)
|
||||
(fromDb "thisweek.gnome.org" // tech)
|
||||
# more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
## n.b.: quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent)
|
||||
|
||||
## No Moods, Ads or Cutesy Fucking Icons
|
||||
(fromDb "rifters.com/crawl" // uncat)
|
||||
|
||||
# DEVELOPERS
|
||||
(fromDb "blog.jmp.chat" // tech)
|
||||
(fromDb "tuxphones.com" // tech)
|
||||
(fromDb "uninsane.org" // tech)
|
||||
(fromDb "ascii.textfiles.com" // tech) # Jason Scott
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
# (fromDb "vitalik.ca" // tech) # moved to vitalik.eth.limo
|
||||
(fromDb "vitalik.eth.limo" // tech) # Vitalik Buterin
|
||||
(fromDb "webcurious.co.uk" // uncat)
|
||||
(fromDb "xn--gckvb8fzb.com" // tech)
|
||||
(fromDb "amosbbatto.wordpress.com" // tech)
|
||||
(fromDb "fasterthanli.me" // tech)
|
||||
(fromDb "mg.lol" // tech)
|
||||
# (fromDb "drewdevault.com" // tech)
|
||||
## Ken Shirriff
|
||||
(fromDb "righto.com" // tech)
|
||||
## shared blog by a few NixOS devs, notably onny
|
||||
(fromDb "project-insanity.org" // tech)
|
||||
## Vitalik Buterin
|
||||
(fromDb "vitalik.ca" // tech)
|
||||
## ian (Sanctuary)
|
||||
(fromDb "sagacioussuricata.com" // tech)
|
||||
(fromDb "artemis.sh" // tech)
|
||||
## Bunnie Juang
|
||||
(fromDb "bunniestudios.com" // tech)
|
||||
(fromDb "blog.danieljanus.pl" // tech)
|
||||
(fromDb "ianthehenry.com" // tech)
|
||||
(fromDb "bitbashing.io" // tech)
|
||||
(fromDb "idiomdrottning.org" // uncat)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||
(fromDb "jefftk.com" // tech)
|
||||
(fromDb "pomeroyb.com" // tech)
|
||||
(fromDb "harihareswara.net" // tech // pol) # rec by Cory Doctorow
|
||||
(fromDb "mako.cc/copyrighteous" // tech // pol) # rec by Cory Doctorow
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
|
||||
# TECH PROJECTS
|
||||
(fromDb "blog.rust-lang.org" // tech)
|
||||
|
||||
# (TECH; POL) COMMENTATORS
|
||||
## Matt Webb -- engineering-ish, but dreamy
|
||||
(fromDb "interconnected.org/home/feed" // rat)
|
||||
(fromDb "edwardsnowden.substack.com" // pol // text)
|
||||
## Julia Evans
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
## Ben Thompson
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly)
|
||||
## Balaji
|
||||
(fromDb "balajis.com" // pol)
|
||||
(fromDb "ben-evans.com/benedictevans" // pol)
|
||||
(fromDb "lynalden.com" // pol)
|
||||
(fromDb "austinvernon.site" // tech)
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "astralcodexten" // rat // daily) # Scott Alexander
|
||||
(mkSubstack "byrnehobart" // pol // infrequent)
|
||||
# (mkSubstack "doomberg" // tech // weekly) # articles are all pay-walled
|
||||
## David Rosenthal
|
||||
(fromDb "blog.dshr.org" // pol)
|
||||
## Matt Levine
|
||||
(mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly)
|
||||
(fromDb "stpeter.im/atom.xml" // pol)
|
||||
## Peter Saint-Andre -- side project of stpeter.im
|
||||
(fromDb "philosopher.coach" // rat)
|
||||
(fromDb "morningbrew.com/feed" // pol)
|
||||
|
||||
# RATIONALITY/PHILOSOPHY/ETC
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(fromDb "unintendedconsequenc.es" // rat)
|
||||
(fromDb "applieddivinitystudies.com" // rat)
|
||||
(fromDb "slimemoldtimemold.com" // rat)
|
||||
(fromDb "richardcarrier.info" // rat)
|
||||
(fromDb "gwern.net" // rat)
|
||||
## Jason Crawford
|
||||
(fromDb "rootsofprogress.org" // rat)
|
||||
## Robin Hanson
|
||||
(fromDb "overcomingbias.com" // rat)
|
||||
## Scott Alexander
|
||||
(mkSubstack "astralcodexten" // rat // daily)
|
||||
## Paul Christiano
|
||||
(fromDb "sideways-view.com" // rat)
|
||||
## Sean Carroll
|
||||
(fromDb "preposterousuniverse.com" // rat)
|
||||
(mkSubstack "eliqian" // rat // weekly)
|
||||
(mkText "https://acoup.blog/feed" // rat // weekly)
|
||||
(fromDb "mindingourway.com" // rat)
|
||||
|
||||
## mostly dating topics. not advice, or humor, but looking through a social lens
|
||||
(fromDb "putanumonit.com" // rat)
|
||||
|
||||
# LOCAL
|
||||
(fromDb "capitolhillseattle.com" // pol)
|
||||
|
||||
# CODE
|
||||
(mkSubstack "oversharing" // pol // daily)
|
||||
(mkSubstack "samkriss" // humor // infrequent)
|
||||
(mkText "http://benjaminrosshoffman.com/feed" // pol // weekly)
|
||||
(mkText "http://boginjr.com/feed" // tech // infrequent)
|
||||
(mkText "https://anish.lakhwara.com/home.html" // tech // weekly)
|
||||
(mkText "https://forum.merveilles.town/rss.xml" // pol // infrequent) #quality RSS list here: <https://forum.merveilles.town/thread/57/share-your-rss-feeds%21-6/>
|
||||
# (mkText "https://github.com/Kaiteki-Fedi/Kaiteki/commits/master.atom" // tech // infrequent)
|
||||
(mkText "https://jvns.ca/atom.xml" // tech // weekly) # Julia Evans
|
||||
(mkText "https://linuxphoneapps.org/blog/atom.xml" // tech // infrequent)
|
||||
(mkText "https://nixos.org/blog/announcements-rss.xml" // tech // infrequent) # more nixos stuff here, but unclear how to subscribe: <https://nixos.org/blog/categories.html>
|
||||
(mkText "https://nixos.org/blog/stories-rss.xml" // tech // weekly)
|
||||
# (mkText "https://til.simonwillison.net/tils/feed.atom" // tech // weekly)
|
||||
# (mkText "https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine.rss" // pol // weekly) # Matt Levine (preview/paywalled)
|
||||
(mkText "https://www.stratechery.com/rss" // pol // weekly) # Ben Thompson
|
||||
];
|
||||
|
||||
videos = [
|
||||
(fromDb "youtube.com/@Channel5YouTube" // pol)
|
||||
(fromDb "youtube.com/@ColdFusion")
|
||||
(fromDb "youtube.com/@ContraPoints" // pol)
|
||||
(fromDb "youtube.com/@Exurb1a")
|
||||
(fromDb "youtube.com/@hbomberguy")
|
||||
(fromDb "youtube.com/@JackStauber")
|
||||
(fromDb "youtube.com/@PolyMatter")
|
||||
# (fromDb "youtube.com/@rossmanngroup" // pol // tech) # Louis Rossmann
|
||||
(fromDb "youtube.com/@TechnologyConnections" // tech)
|
||||
(fromDb "youtube.com/@TheB1M")
|
||||
(fromDb "youtube.com/@TomScottGo")
|
||||
(fromDb "youtube.com/@Vihart")
|
||||
(fromDb "youtube.com/@Vox")
|
||||
(fromDb "youtube.com/@Vsauce")
|
||||
];
|
||||
|
||||
images = [
|
||||
(fromDb "smbc-comics.com" // img // humor)
|
||||
(fromDb "xkcd.com" // img // humor)
|
||||
(fromDb "turnoff.us" // img // humor)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
# (mkImg "http://dilbert.com/feed" // humor // daily)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
|
||||
# ART
|
||||
(fromDb "catandgirl.com" // img // humor)
|
||||
(fromDb "miniature-calendar.com" // img // art // daily)
|
||||
(fromDb "pbfcomics.com" // img // humor)
|
||||
(fromDb "poorlydrawnlines.com/feed" // img // humor)
|
||||
(fromDb "smbc-comics.com" // img // humor)
|
||||
(fromDb "turnoff.us" // img // humor)
|
||||
(fromDb "xkcd.com" // img // humor)
|
||||
];
|
||||
in
|
||||
{
|
||||
sane.feeds = texts ++ images ++ podcasts;
|
||||
sane.feeds = texts ++ images ++ podcasts ++ videos;
|
||||
|
||||
assertions = builtins.map
|
||||
(p: {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# docs
|
||||
# - x-systemd options: <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
|
||||
# - fuse options: `man mount.fuse`
|
||||
|
||||
{ lib, pkgs, sane-lib, ... }:
|
||||
|
||||
@@ -8,13 +9,22 @@ let
|
||||
common = [
|
||||
"_netdev"
|
||||
"noatime"
|
||||
"user" # allow any user with access to the device to mount the fs
|
||||
# user: allow any user with access to the device to mount the fs.
|
||||
# note that this requires a suid `mount` binary; see: <https://zameermanji.com/blog/2022/8/5/using-fuse-without-root-on-linux/>
|
||||
"user"
|
||||
"x-systemd.requires=network-online.target"
|
||||
"x-systemd.after=network-online.target"
|
||||
"x-systemd.mount-timeout=10s" # how long to wait for mount **and** how long to wait for unmount
|
||||
];
|
||||
auto = [ "x-systemd.automount" ];
|
||||
noauto = [ "noauto" ]; # don't mount as part of remote-fs.target
|
||||
# x-systemd.automount: mount the fs automatically *on first access*.
|
||||
# creates a `path-to-mount.automount` systemd unit.
|
||||
automount = [ "x-systemd.automount" ];
|
||||
# noauto: don't mount as part of remote-fs.target.
|
||||
# N.B.: `remote-fs.target` is a dependency of multi-user.target, itself of graphical.target.
|
||||
# hence, omitting `noauto` can slow down boots.
|
||||
noauto = [ "noauto" ];
|
||||
# lazyMount: defer mounting until first access from userspace
|
||||
lazyMount = noauto ++ automount;
|
||||
wg = [
|
||||
"x-systemd.requires=wireguard-wg-home.service"
|
||||
"x-systemd.after=wireguard-wg-home.service"
|
||||
@@ -22,19 +32,34 @@ let
|
||||
|
||||
ssh = common ++ [
|
||||
"identityfile=/home/colin/.ssh/id_ed25519"
|
||||
"allow_other"
|
||||
"allow_other" # allow users other than the one who mounts it to access it. needed, if systemd is the one mounting this fs (as root)
|
||||
# allow_root: allow root to access files on this fs (if mounted by non-root, else it can always access them).
|
||||
# N.B.: if both allow_root and allow_other are specified, then only allow_root takes effect.
|
||||
# "allow_root"
|
||||
# default_permissions: enforce local permissions check. CRUCIAL if using `allow_other`.
|
||||
# w/o this, permissions mode of sshfs is like:
|
||||
# - sshfs runs all remote commands as the remote user.
|
||||
# - if a local user has local permissions to the sshfs mount, then their file ops are sent blindly across the tunnel.
|
||||
# - `allow_other` allows *any* local user to access the mount, and hence any local user can now freely become the remote mapped user.
|
||||
# with default_permissions, sshfs doesn't tunnel file ops from users until checking that said user could perform said op on an equivalent local fs.
|
||||
"default_permissions"
|
||||
];
|
||||
sshColin = ssh ++ [
|
||||
# follow_symlinks: remote files which are symlinks are presented to the local system as ordinary files (as the target of the symlink).
|
||||
# if the symlink target does not exist, the presentation is unspecified.
|
||||
# symlinks which point outside the mount ARE followed. so this is more capable than `transform_symlinks`
|
||||
"follow_symlinks"
|
||||
# symlinks on the remote fs which are absolute paths are presented to the local system as relative symlinks pointing to the expected data on the remote fs.
|
||||
# only symlinks which would point inside the mountpoint are translated.
|
||||
"transform_symlinks"
|
||||
"idmap=user"
|
||||
"uid=1000"
|
||||
"gid=100"
|
||||
];
|
||||
sshRoot = ssh ++ [
|
||||
# we don't transform_symlinks because that breaks the validity of remote /nix stores
|
||||
"sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
|
||||
];
|
||||
# sshRoot = ssh ++ [
|
||||
# # we don't transform_symlinks because that breaks the validity of remote /nix stores
|
||||
# "sftp_server=/run/wrappers/bin/sudo\\040/run/current-system/sw/libexec/sftp-server"
|
||||
# ];
|
||||
# in the event of hunt NFS mounts, consider:
|
||||
# - <https://unix.stackexchange.com/questions/31979/stop-broken-nfs-mounts-from-locking-a-directory>
|
||||
|
||||
@@ -57,13 +82,17 @@ let
|
||||
];
|
||||
};
|
||||
remoteHome = host: {
|
||||
fileSystems."/mnt/${host}-home" = {
|
||||
fileSystems."/mnt/${host}/home" = {
|
||||
device = "colin@${host}:/home/colin";
|
||||
fsType = "fuse.sshfs";
|
||||
options = fsOpts.sshColin ++ fsOpts.noauto;
|
||||
options = fsOpts.sshColin ++ fsOpts.lazyMount;
|
||||
noCheck = true;
|
||||
};
|
||||
sane.fs."/mnt/${host}-home" = sane-lib.fs.wantedDir;
|
||||
sane.fs."/mnt/${host}/home" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0700";
|
||||
};
|
||||
};
|
||||
in
|
||||
lib.mkMerge [
|
||||
@@ -103,34 +132,38 @@ lib.mkMerge [
|
||||
# device = "servo-hn:/";
|
||||
# noCheck = true;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
# options = fsOpts.nfs ++ fsOpts.automount ++ fsOpts.wg;
|
||||
# };
|
||||
fileSystems."/mnt/servo-nfs/media" = {
|
||||
fileSystems."/mnt/servo/media" = {
|
||||
device = "servo-hn:/media";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
|
||||
};
|
||||
fileSystems."/mnt/servo-nfs/playground" = {
|
||||
sane.fs."/mnt/servo/media" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0750";
|
||||
};
|
||||
fileSystems."/mnt/servo/playground" = {
|
||||
device = "servo-hn:/playground";
|
||||
noCheck = true;
|
||||
fsType = "nfs";
|
||||
options = fsOpts.nfs ++ fsOpts.auto ++ fsOpts.wg;
|
||||
options = fsOpts.nfs ++ fsOpts.lazyMount ++ fsOpts.wg;
|
||||
};
|
||||
sane.fs."/mnt/servo/playground" = sane-lib.fs.wanted {
|
||||
dir.acl.user = "colin";
|
||||
dir.acl.group = "users";
|
||||
dir.acl.mode = "0750";
|
||||
};
|
||||
# fileSystems."/mnt/servo-media-nfs" = {
|
||||
# device = "servo-hn:/media";
|
||||
# noCheck = true;
|
||||
# fsType = "nfs";
|
||||
# options = fsOpts.common ++ fsOpts.auto;
|
||||
# };
|
||||
sane.fs."/mnt/servo-media" = sane-lib.fs.wantedSymlinkTo "/mnt/servo-nfs/media";
|
||||
|
||||
environment.pathsToLink = [
|
||||
# needed to achieve superuser access for user-mounted filesystems (see optionsRoot above)
|
||||
# we can only link whole directories here, even though we're only interested in pkgs.openssh
|
||||
"/libexec"
|
||||
];
|
||||
# environment.pathsToLink = [
|
||||
# # needed to achieve superuser access for user-mounted filesystems (see sshRoot above)
|
||||
# # we can only link whole directories here, even though we're only interested in pkgs.openssh
|
||||
# "/libexec"
|
||||
# ];
|
||||
|
||||
programs.fuse.userAllowOther = true; #< necessary for `allow_other` or `allow_root` options.
|
||||
environment.systemPackages = [
|
||||
pkgs.sshfs-fuse
|
||||
];
|
||||
|
@@ -1,4 +1,4 @@
|
||||
{ lib, pkgs, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
@@ -12,8 +12,9 @@
|
||||
copy_bin_and_libs ${pkgs.util-linux}/bin/{cfdisk,lsblk,lscpu}
|
||||
copy_bin_and_libs ${pkgs.gptfdisk}/bin/{cgdisk,gdisk}
|
||||
copy_bin_and_libs ${pkgs.smartmontools}/bin/smartctl
|
||||
copy_bin_and_libs ${pkgs.nvme-cli}/bin/nvme
|
||||
copy_bin_and_libs ${pkgs.e2fsprogs}/bin/resize2fs
|
||||
'' + lib.optionalString pkgs.stdenv.hostPlatform.isx86_64 ''
|
||||
copy_bin_and_libs ${pkgs.nvme-cli}/bin/nvme # doesn't cross compile
|
||||
'';
|
||||
boot.kernelParams = [
|
||||
"boot.shell_on_fail"
|
||||
@@ -27,6 +28,13 @@
|
||||
# "systemd.log_level=debug"
|
||||
# "systemd.log_target=console"
|
||||
|
||||
# moby has to run recent kernels (defined elsewhere).
|
||||
# meanwhile, kernel variation plays some minor role in things like sandboxing (landlock) and capabilities.
|
||||
# simpler to keep near the latest kernel on all devices,
|
||||
# and also makes certain that any weird system-level bugs i see aren't likely to be stale kernel bugs.
|
||||
# servo needs zfs though, which doesn't support every kernel.
|
||||
boot.kernelPackages = lib.mkDefault pkgs.zfs.latestCompatibleLinuxPackages;
|
||||
|
||||
# hack in the `boot.shell_on_fail` arg since that doesn't always seem to work.
|
||||
boot.initrd.preFailCommands = "allowShell=1";
|
||||
|
||||
@@ -39,6 +47,12 @@
|
||||
# non-free firmware
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# default is 252274, which is too low particularly for servo.
|
||||
# manifests as spurious "No space left on device" when trying to install watches,
|
||||
# e.g. in dyn-dns by `systemctl start dyn-dns-watcher.path`.
|
||||
# see: <https://askubuntu.com/questions/828779/failed-to-add-run-systemd-ask-password-to-directory-watch-no-space-left-on-dev>
|
||||
boot.kernel.sysctl."fs.inotify.max_user_watches" = 1048576;
|
||||
|
||||
# powertop will default to putting USB devices -- including HID -- to sleep after TWO SECONDS
|
||||
powerManagement.powertop.enable = false;
|
||||
# linux CPU governor: <https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt>
|
||||
@@ -57,10 +71,19 @@
|
||||
powerManagement.cpuFreqGovernor = "ondemand";
|
||||
|
||||
services.logind.extraConfig = ''
|
||||
# don’t shutdown when power button is short-pressed
|
||||
HandlePowerKey=ignore
|
||||
# see: `man logind.conf`
|
||||
# don’t shutdown when power button is short-pressed (commonly done an accident, or by cats).
|
||||
# but do on long-press: useful to gracefully power-off server.
|
||||
HandlePowerKey=lock
|
||||
HandlePowerKeyLongPress=poweroff
|
||||
HandleLidSwitch=lock
|
||||
'';
|
||||
|
||||
# some packages build only if binfmt *isn't* present
|
||||
nix.settings.system-features = lib.mkIf (config.boot.binfmt.emulatedSystems == []) [
|
||||
"no-binfmt"
|
||||
];
|
||||
|
||||
# services.snapper.configs = {
|
||||
# root = {
|
||||
# subvolume = "/";
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./keyring
|
||||
./fs.nix
|
||||
./mime.nix
|
||||
./ssh.nix
|
||||
./xdg-dirs.nix
|
||||
|
42
hosts/common/home/fs.nix
Normal file
42
hosts/common/home/fs.nix
Normal file
@@ -0,0 +1,42 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
sane.user.persist.byStore.plaintext = [
|
||||
"archive"
|
||||
"dev"
|
||||
# TODO: records should be private
|
||||
"records"
|
||||
"ref"
|
||||
"tmp"
|
||||
"use"
|
||||
"Books/local"
|
||||
"Music"
|
||||
"Pictures/albums"
|
||||
"Pictures/cat"
|
||||
"Pictures/from"
|
||||
"Pictures/Screenshots" #< XXX: something is case-sensitive about this?
|
||||
"Pictures/Photos"
|
||||
"Videos/local"
|
||||
|
||||
# these are persisted simply to save on RAM.
|
||||
# ~/.cache/nix can become several GB.
|
||||
# mesa_shader_cache is < 10 MB.
|
||||
# TODO: integrate with sane.programs.sandbox?
|
||||
".cache/mesa_shader_cache"
|
||||
".cache/nix"
|
||||
];
|
||||
sane.user.persist.byStore.private = [
|
||||
"knowledge"
|
||||
];
|
||||
|
||||
# convenience
|
||||
sane.user.fs.".persist/private".symlink.target = config.sane.persist.stores.private.origin;
|
||||
sane.user.fs.".persist/plaintext".symlink.target = config.sane.persist.stores.plaintext.origin;
|
||||
sane.user.fs.".persist/ephemeral".symlink.target = config.sane.persist.stores.cryptClearOnBoot.origin;
|
||||
|
||||
sane.user.fs."nixos".symlink.target = "dev/nixos";
|
||||
|
||||
sane.user.fs."Books/servo".symlink.target = "/mnt/servo/media/Books";
|
||||
sane.user.fs."Videos/servo".symlink.target = "/mnt/servo/media/Videos";
|
||||
# sane.user.fs."Music/servo".symlink.target = "/mnt/servo/media/Music";
|
||||
sane.user.fs."Pictures/servo-macros".symlink.target = "/mnt/servo/media/Pictures/macros";
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
{ config, pkgs, sane-lib, ... }:
|
||||
|
||||
let
|
||||
init-keyring = pkgs.static-nix-shell.mkBash {
|
||||
pname = "init-keyring";
|
||||
src = ./.;
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.user.persist.byStore.private = [ ".local/share/keyrings" ];
|
||||
|
||||
sane.user.fs."private/.local/share/keyrings/default" = {
|
||||
generated.command = [ "${init-keyring}/bin/init-keyring" ];
|
||||
wantedBy = [ config.sane.fs."/home/colin/private".unit ];
|
||||
wantedBeforeBy = [ ]; # don't created this as part of `multi-user.target`
|
||||
};
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
# initializes the default libsecret keyring (used by gnome-keyring) if not already initialized.
|
||||
# this initializes it to be plaintext/unencrypted.
|
||||
|
||||
ringdir=/home/colin/private/.local/share/keyrings
|
||||
if test -f "$ringdir/default"
|
||||
then
|
||||
echo 'keyring already initialized: not doing anything'
|
||||
else
|
||||
keyring="$ringdir/Default_keyring.keyring"
|
||||
|
||||
echo 'initializing default user keyring:' "$keyring.new"
|
||||
echo '[keyring]' > "$keyring.new"
|
||||
echo 'display-name=Default keyring' >> "$keyring.new"
|
||||
echo 'lock-on-idle=false' >> "$keyring.new"
|
||||
echo 'lock-after=false' >> "$keyring.new"
|
||||
chown colin:users "$keyring.new"
|
||||
# closest to an atomic update we can achieve
|
||||
mv "$keyring.new" "$keyring" && echo -n "Default_keyring" > "$ringdir/default"
|
||||
fi
|
@@ -1,28 +1,94 @@
|
||||
{ config, lib, ...}:
|
||||
# TODO: move into modules/users.nix
|
||||
{ config, lib, pkgs, ...}:
|
||||
|
||||
let
|
||||
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
||||
weightedMimes = prog: builtins.mapAttrs (_key: desktop: { priority = prog.mime.priority; desktop = desktop; }) prog.mime.associations;
|
||||
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
||||
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
||||
# [ { priority, desktop } ... ] -> Self
|
||||
sortOneMimeType = associations: builtins.sort (l: r: assert l.priority != r.priority; l.priority < r.priority) associations;
|
||||
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
||||
removePriorities = mimes: builtins.mapAttrs (_k: associations: builtins.map (a: a.desktop) associations) mimes;
|
||||
# [ ProgramConfig ]
|
||||
enabledPrograms = builtins.filter
|
||||
(p: p.enabled)
|
||||
(builtins.attrValues config.sane.programs);
|
||||
|
||||
# [ ProgramConfig ]
|
||||
enabledPrograms = builtins.filter (p: p.enabled) (builtins.attrValues config.sane.programs);
|
||||
enabledProgramsWithPackage = builtins.filter (p: p.package != null) enabledPrograms;
|
||||
|
||||
# [ { "<mime-type>" = { prority, desktop } ]
|
||||
enabledWeightedMimes = builtins.map weightedMimes enabledPrograms;
|
||||
|
||||
# ProgramConfig -> { "<mime-type>" = { priority, desktop }; }
|
||||
weightedMimes = prog: builtins.mapAttrs
|
||||
(_key: desktop: {
|
||||
priority = prog.mime.priority; desktop = desktop;
|
||||
})
|
||||
prog.mime.associations;
|
||||
|
||||
# [ { "<mime-type>" = { priority, desktop } ]; } ] -> { "<mime-type>" = [ { priority, desktop } ... ]; }
|
||||
mergeMimes = mimes: lib.foldAttrs (item: acc: [item] ++ acc) [] mimes;
|
||||
|
||||
# [ { priority, desktop } ... ] -> Self
|
||||
sortOneMimeType = associations: builtins.sort
|
||||
(l: r: lib.throwIf
|
||||
(l.priority == r.priority)
|
||||
"${l.desktop} and ${r.desktop} share a preferred mime type with identical priority ${builtins.toString l.priority} (and so the desired association is ambiguous)"
|
||||
(l.priority < r.priority)
|
||||
)
|
||||
associations;
|
||||
sortMimes = mimes: builtins.mapAttrs (_k: sortOneMimeType) mimes;
|
||||
# { "<mime-type>"} = [ { priority, desktop } ... ]; } -> { "<mime-type>" = [ "<desktop>" ... ]; }
|
||||
removePriorities = mimes: builtins.mapAttrs
|
||||
(_k: associations: builtins.map (a: a.desktop) associations)
|
||||
mimes;
|
||||
# { "<mime-type>" = [ "<desktop>" ... ]; } -> { "<mime-type>" = "<desktop1>;<desktop2>;..."; }
|
||||
formatDesktopLists = mimes: builtins.mapAttrs
|
||||
(_k: desktops: lib.concatStringsSep ";" desktops)
|
||||
mimes;
|
||||
|
||||
mimeappsListPkg = pkgs.writeTextDir "share/applications/mimeapps.list" (
|
||||
lib.generators.toINI { } {
|
||||
"Default Applications" = formatDesktopLists (removePriorities (sortMimes (mergeMimes enabledWeightedMimes)));
|
||||
}
|
||||
);
|
||||
|
||||
localShareApplicationsPkg = (pkgs.symlinkJoin {
|
||||
name = "user-local-share-applications";
|
||||
paths = builtins.map
|
||||
(p: "${p.package}")
|
||||
(enabledProgramsWithPackage ++ [ { package=mimeappsListPkg; } ]);
|
||||
}).overrideAttrs (orig: {
|
||||
# like normal symlinkJoin, but don't error if the path doesn't exist
|
||||
buildCommand = ''
|
||||
mkdir -p $out/share/applications
|
||||
for i in $(cat $pathsPath); do
|
||||
if [ -e "$i/share/applications" ]; then
|
||||
${pkgs.buildPackages.xorg.lndir}/bin/lndir -silent $i/share/applications $out/share/applications
|
||||
fi
|
||||
done
|
||||
runHook postBuild
|
||||
'';
|
||||
postBuild = ''
|
||||
# rebuild `mimeinfo.cache`, used by file openers to show the list of *all* apps, not just the user's defaults.
|
||||
${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database $out/share/applications
|
||||
'';
|
||||
});
|
||||
|
||||
in
|
||||
{
|
||||
# the xdg mime type for a file can be found with:
|
||||
# - `xdg-mime query filetype path/to/thing.ext`
|
||||
# the default handler for a mime type can be found with:
|
||||
# - `xdg-mime query default <mimetype>` (e.g. x-scheme-handler/http)
|
||||
# the nix-configured handler can be found `nix-repl > :lf . > hostConfigs.desko.xdg.mime.defaultApplications`
|
||||
#
|
||||
# glib/gio is queried via glib.bin output:
|
||||
# - `gio mime x-scheme-handler/https`
|
||||
# - `gio open <path_or_url>`
|
||||
# - `gio launch </path/to/app.desktop>`
|
||||
#
|
||||
# we can have single associations or a list of associations.
|
||||
# there's also options to *remove* [non-default] associations from specific apps
|
||||
xdg.mime.enable = true;
|
||||
xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
||||
# N.B.: don't use nixos' `xdg.mime` option becaue that caues `/share/applications` to be linked into the whole system,
|
||||
# which limits what i can do around sandboxing. getting the default associations to live in ~/ makes it easier to expose
|
||||
# the associations to apps selectively.
|
||||
# xdg.mime.enable = true;
|
||||
# xdg.mime.defaultApplications = removePriorities (sortMimes (mergeMimes enabledWeightedMimes));
|
||||
|
||||
sane.user.fs.".local/share/applications".symlink.target = "${localShareApplicationsPkg}/share/applications";
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
};
|
||||
|
||||
sane.hosts.by-name."moby" = {
|
||||
ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
# ssh.authorized = lib.mkDefault false; # moby's too easy to hijack: don't let it ssh places
|
||||
ssh.user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrR+gePnl0nV/vy7I5BzrGeyVL+9eOuXHU1yNE3uCwU";
|
||||
ssh.host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO1N/IT3nQYUD+dBlU1sTEEVMxfOyMkrrDeyHcYgnJvw";
|
||||
wg-home.pubkey = "I7XIR1hm8bIzAtcAvbhWOwIAabGkuEvbWH/3kyIB1yA=";
|
||||
|
@@ -49,6 +49,16 @@
|
||||
sane.ids.media.gid = 2414;
|
||||
sane.ids.ntfy-sh.uid = 2415;
|
||||
sane.ids.ntfy-sh.gid = 2415;
|
||||
sane.ids.monero.uid = 2416;
|
||||
sane.ids.monero.gid = 2416;
|
||||
sane.ids.slskd.uid = 2417;
|
||||
sane.ids.slskd.gid = 2417;
|
||||
sane.ids.bitcoind-mainnet.uid = 2418;
|
||||
sane.ids.bitcoind-mainnet.gid = 2418;
|
||||
sane.ids.clightning.uid = 2419;
|
||||
sane.ids.clightning.gid = 2419;
|
||||
sane.ids.nix-serve.uid = 2420;
|
||||
sane.ids.nix-serve.gid = 2420;
|
||||
|
||||
sane.ids.colin.uid = 1000;
|
||||
sane.ids.guest.uid = 1100;
|
||||
@@ -63,6 +73,8 @@
|
||||
sane.ids.systemd-oom.uid = 2005;
|
||||
sane.ids.systemd-oom.gid = 2005;
|
||||
sane.ids.wireshark.gid = 2006;
|
||||
sane.ids.nixremote.uid = 2007;
|
||||
sane.ids.nixremote.gid = 2007;
|
||||
|
||||
# found on graphical hosts
|
||||
sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy
|
||||
|
@@ -1,6 +1,30 @@
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./dns.nix
|
||||
./hostnames.nix
|
||||
./upnp.nix
|
||||
./vpn.nix
|
||||
];
|
||||
|
||||
systemd.network.enable = true;
|
||||
networking.useNetworkd = true;
|
||||
|
||||
# view refused/dropped packets with: `sudo journalctl -k`
|
||||
# networking.firewall.logRefusedPackets = true;
|
||||
# networking.firewall.logRefusedUnicastsOnly = false;
|
||||
networking.firewall.logReversePathDrops = true;
|
||||
# linux will drop inbound packets if it thinks a reply to that packet wouldn't exit via the same interface (rpfilter).
|
||||
# that heuristic fails for complicated VPN-style routing, especially with SNAT.
|
||||
# networking.firewall.checkReversePath = false; # or "loose" to keep it partially.
|
||||
# networking.firewall.enable = false; #< set false to debug
|
||||
|
||||
# this is needed to forward packets from the VPN to the host.
|
||||
# this is required separately by servo and by any `sane-vpn` users,
|
||||
# however Nix requires this be set centrally, in only one location (i.e. here)
|
||||
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
|
||||
|
||||
# the default backend is "wpa_supplicant".
|
||||
# wpa_supplicant reliably picks weak APs to connect to.
|
||||
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>
|
||||
@@ -35,10 +59,6 @@
|
||||
# e.g. openconnect drags in webkitgtk (for SSO)!
|
||||
networking.networkmanager.plugins = lib.mkForce [];
|
||||
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
1900 # to received UPnP advertisements. required by sane-ip-check-upnp
|
||||
];
|
||||
|
||||
# keyfile.path = where networkmanager should look for connection credentials
|
||||
networking.networkmanager.extraConfig = ''
|
||||
[keyfile]
|
67
hosts/common/net/dns.nix
Normal file
67
hosts/common/net/dns.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
# things to consider when changing these parameters:
|
||||
# - temporary VPN access (`sane-vpn up ...`)
|
||||
# - servo `ovpns` namespace (it *relies* on /etc/resolv.conf mentioning 127.0.0.53)
|
||||
# - jails: `firejail --net=br-ovpnd-us --noprofile --dns=46.227.67.134 ping 1.1.1.1`
|
||||
#
|
||||
# components:
|
||||
# - /etc/nsswitch.conf:
|
||||
# - glibc uses this to provide `getaddrinfo`, i.e. host -> ip address lookup
|
||||
# call directly with `getent ahostsv4 www.google.com`
|
||||
# - `nss` (a component of glibc) is modular: names mentioned in that file are `dlopen`'d (i think that's the mechanism)
|
||||
# in NixOS, that means _they have to be on LDPATH_.
|
||||
# - `nscd` is used by NixOS simply to proxy nss requests.
|
||||
# here, /etc/nsswitch.conf consumers contact nscd via /var/run/nscd/socket.
|
||||
# in this way, only `nscd` needs to have the nss modules on LDPATH.
|
||||
# - /etc/resolv.conf
|
||||
# - contains the DNS servers for a system.
|
||||
# - historically, NetworkManager would update this file as you switch networks.
|
||||
# - modern implementations hardcodes `127.0.0.53` and then systemd-resolved proxies everything (and caches).
|
||||
#
|
||||
# namespacing:
|
||||
# - each namespace can use a different /etc/resolv.conf to specify different DNS servers (see `firejail --dns=...`)
|
||||
# - nscd breaks namespacing: the host nscd is unaware of the guest's /etc/resolv.conf, and so direct's the guest's DNS requests to the host's servers.
|
||||
# - this is fixed by either `firejail --blacklist=/var/run/nscd/socket`, or disabling nscd altogether.
|
||||
{ lib, ... }:
|
||||
{
|
||||
# use systemd's stub resolver.
|
||||
# /etc/resolv.conf isn't sophisticated enough to use different servers per net namespace (or link).
|
||||
# instead, running the stub resolver on a known address in the root ns lets us rewrite packets
|
||||
# in servo's ovnps namespace to use the provider's DNS resolvers.
|
||||
# a weakness is we can only query 1 NS at a time (unless we were to clone the packets?)
|
||||
# TODO: rework servo's netns to use `firejail`, which is capable of spoofing /etc/resolv.conf.
|
||||
services.resolved.enable = true; #< to disable, set ` = lib.mkForce false`, as other systemd features default to enabling `resolved`.
|
||||
# without DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => works
|
||||
# with default DNSSEC:
|
||||
# - dig matrix.org => works
|
||||
# - curl https://matrix.org => fails
|
||||
# i don't know why. this might somehow be interfering with the DNS run on this device (trust-dns)
|
||||
services.resolved.dnssec = "false";
|
||||
networking.nameservers = [
|
||||
# use systemd-resolved resolver
|
||||
# full resolver (which understands /etc/hosts) lives on 127.0.0.53
|
||||
# stub resolver (just forwards upstream) lives on 127.0.0.54
|
||||
"127.0.0.53"
|
||||
];
|
||||
|
||||
# nscd -- the Name Service Caching Daemon -- caches DNS query responses
|
||||
# in a way that's unaware of my VPN routing, so routes are frequently poor against
|
||||
# services which advertise different IPs based on geolocation.
|
||||
# nscd claims to be usable without a cache, but in practice i can't get it to not cache!
|
||||
# nsncd is the Name Service NON-Caching Daemon. it's a drop-in that doesn't cache;
|
||||
# this is OK on the host -- because systemd-resolved caches. it's probably sub-optimal
|
||||
# in the netns and we query upstream DNS more often than needed. hm.
|
||||
# services.nscd.enableNsncd = true;
|
||||
|
||||
# disabling nscd LOSES US SOME FUNCTIONALITY. in particular, only the glibc-builtin modules are accessible via /etc/resolv.conf.
|
||||
# - dns: glibc-bultin
|
||||
# - files: glibc-builtin
|
||||
# - myhostname: systemd
|
||||
# - mymachines: systemd
|
||||
# - resolve: systemd
|
||||
# in practice, i see no difference with nscd disabled.
|
||||
# disabling nscd VASTLY simplifies netns and process isolation. see explainer at top of file.
|
||||
services.nscd.enable = false;
|
||||
system.nssModules = lib.mkForce [];
|
||||
}
|
15
hosts/common/net/hostnames.nix
Normal file
15
hosts/common/net/hostnames.nix
Normal file
@@ -0,0 +1,15 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
{
|
||||
# give each host a shortname that all the other hosts know, to allow easy comms.
|
||||
networking.hosts = lib.mkMerge (builtins.map
|
||||
(host: let
|
||||
cfg = config.sane.hosts.by-name."${host}";
|
||||
in {
|
||||
"${cfg.lan-ip}" = [ host ];
|
||||
} // lib.optionalAttrs (cfg.wg-home.ip != null) {
|
||||
"${cfg.wg-home.ip}" = [ "${host}-hn" ];
|
||||
})
|
||||
(builtins.attrNames config.sane.hosts.by-name)
|
||||
);
|
||||
}
|
20
hosts/common/net/upnp.nix
Normal file
20
hosts/common/net/upnp.nix
Normal file
@@ -0,0 +1,20 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
# to receive UPnP advertisements. required by sane-ip-check.
|
||||
# N.B. sane-ip-check isn't query/response based. it needs to receive on port 1900 -- not receive responses FROM port 1900.
|
||||
1900
|
||||
];
|
||||
|
||||
networking.firewall.extraCommands = with pkgs; ''
|
||||
# after an outgoing SSDP query to the multicast address, open FW for incoming responses.
|
||||
# necessary for anything DLNA, especially go2tv
|
||||
# source: <https://serverfault.com/a/911286>
|
||||
# context: <https://github.com/alexballas/go2tv/issues/72>
|
||||
|
||||
# ipset -! means "don't fail if set already exists"
|
||||
${ipset}/bin/ipset create -! upnp hash:ip,port timeout 10
|
||||
${iptables}/bin/iptables -A OUTPUT -d 239.255.255.250/32 -p udp -m udp --dport 1900 -j SET --add-set upnp src,src --exist
|
||||
${iptables}/bin/iptables -A INPUT -p udp -m set --match-set upnp dst,dst -j ACCEPT
|
||||
'';
|
||||
}
|
56
hosts/common/net/vpn.nix
Normal file
56
hosts/common/net/vpn.nix
Normal file
@@ -0,0 +1,56 @@
|
||||
# to add a new OVPN VPN:
|
||||
# - generate a privkey `wg genkey`
|
||||
# - add this key to `sops secrets/universal.yaml`
|
||||
# - upload pubkey to OVPN.com (`cat wg.priv | wg pubkey`)
|
||||
# - generate config @ OVPN.com
|
||||
# - copy the Address, PublicKey, Endpoint from OVPN's config
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
def-ovpn = name: { endpoint, publicKey, addrV4, id }: {
|
||||
sane.vpn."ovpnd-${name}" = {
|
||||
inherit endpoint publicKey addrV4 id;
|
||||
privateKeyFile = config.sops.secrets."wg/ovpnd_${name}_privkey".path;
|
||||
dns = [
|
||||
"46.227.67.134"
|
||||
"192.165.9.158"
|
||||
];
|
||||
};
|
||||
|
||||
sops.secrets."wg/ovpnd_${name}_privkey" = {
|
||||
# needs to be readable by systemd-network or else it says "Ignoring network device" and doesn't expose it to networkctl.
|
||||
owner = "systemd-network";
|
||||
};
|
||||
};
|
||||
in lib.mkMerge [
|
||||
(def-ovpn "us" {
|
||||
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
|
||||
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";
|
||||
id = 1;
|
||||
addrV4 = "172.27.237.218";
|
||||
# addrV6 = "fd00:0000:1337:cafe:1111:1111:ab00:4c8f";
|
||||
})
|
||||
# TODO: us-atl disabled until i can give it a different link-local address and wireguard key than us-mi
|
||||
# (def-ovpn "us-atl" {
|
||||
# endpoint = "vpn18.prd.atlanta.ovpn.com:9929";
|
||||
# publicKey = "Dpg/4v5s9u0YbrXukfrMpkA+XQqKIFpf8ZFgyw0IkE0=";
|
||||
# address = [
|
||||
# "172.21.182.178/32"
|
||||
# "fd00:0000:1337:cafe:1111:1111:cfcb:27e3/128"
|
||||
# ];
|
||||
# })
|
||||
(def-ovpn "us-mi" {
|
||||
endpoint = "vpn34.prd.miami.ovpn.com:9929";
|
||||
publicKey = "VtJz2irbu8mdkIQvzlsYhU+k9d55or9mx4A2a14t0V0=";
|
||||
id = 2;
|
||||
addrV4 = "172.21.182.178";
|
||||
# addrV6 = "fd00:0000:1337:cafe:1111:1111:cfcb:27e3";
|
||||
})
|
||||
(def-ovpn "ukr" {
|
||||
endpoint = "vpn96.prd.kyiv.ovpn.com:9929";
|
||||
publicKey = "CjZcXDxaaKpW8b5As1EcNbI6+42A6BjWahwXDCwfVFg=";
|
||||
id = 3;
|
||||
addrV4 = "172.18.180.159";
|
||||
# addrV6 = "fd00:0000:1337:cafe:1111:1111:ec5c:add3";
|
||||
})
|
||||
]
|
@@ -1,13 +0,0 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages
|
||||
nix.nixPath = [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
||||
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
|
||||
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
|
||||
# to avoid switching so much during development
|
||||
"nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix-path/overlay"
|
||||
];
|
||||
}
|
84
hosts/common/nix/default.nix
Normal file
84
hosts/common/nix/default.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
nix.settings = {
|
||||
# see: `man nix.conf`
|
||||
|
||||
# useful when a remote builder has a faster internet connection than me.
|
||||
# note that this also applies to `nix copy --to`, though.
|
||||
# i think any time a remote machine wants a path, this means we ask them to try getting it themselves before we supply it.
|
||||
builders-use-substitutes = true; # default: false
|
||||
|
||||
# maximum seconds to wait when connecting to binary substituter
|
||||
connect-timeout = 3; # default: 0
|
||||
|
||||
# download-attempts = 5; # default: 5
|
||||
|
||||
# allow `nix flake ...` command
|
||||
experimental-features = [ "nix-command" "flakes "];
|
||||
|
||||
# whether to build from source when binary substitution fails
|
||||
fallback = true; # default: false
|
||||
|
||||
# whether to keep building dependencies if any other one fails
|
||||
keep-going = true; # default: false
|
||||
|
||||
# whether to keep build-only dependencies of GC roots (e.g. C compiler) when doing GC
|
||||
keep-outputs = true; # default: false
|
||||
|
||||
# how many lines to show from failed build
|
||||
log-lines = 30; # default: 10
|
||||
|
||||
# how many substitution downloads to perform in parallel.
|
||||
# i wonder if parallelism is causing moby's substitutions to fail?
|
||||
max-substitution-jobs = 6; # default: 16
|
||||
|
||||
# narinfo-cache-negative-ttl = 3600 # default: 3600
|
||||
# whether to use ~/.local/state/nix/profile instead of ~/.nix-profile, etc
|
||||
use-xdg-base-directories = true; # default: false
|
||||
|
||||
# whether to warn if repository has uncommited changes
|
||||
warn-dirty = false; # default: true
|
||||
|
||||
# hardlinks identical files in the nix store to save 25-35% disk space.
|
||||
# unclear _when_ this occurs. it's not a service.
|
||||
# does the daemon continually scan the nix store?
|
||||
# does the builder use some content-addressed db to efficiently dedupe?
|
||||
auto-optimise-store = true;
|
||||
|
||||
# allow #!nix-shell scripts to locate my patched nixpkgs & custom packages.
|
||||
# this line might become unnecessary: see <https://github.com/NixOS/nixpkgs/pull/273170>
|
||||
nix-path = config.nix.nixPath;
|
||||
};
|
||||
|
||||
# allow `nix-shell` (and probably nix-index?) to locate our patched and custom packages.
|
||||
# this is actually a no-op, and the real action happens in assigning `nix.settings.nix-path`.
|
||||
nix.nixPath = [
|
||||
"nixpkgs=${pkgs.path}"
|
||||
# note the import starts at repo root: this allows `./overlay/default.nix` to access the stuff at the root
|
||||
# "nixpkgs-overlays=${../../..}/hosts/common/nix-path/overlay"
|
||||
# as long as my system itself doesn't rely on NIXPKGS at runtime, we can point the overlays to git
|
||||
# to avoid switching so much during development
|
||||
"nixpkgs-overlays=/home/colin/dev/nixos/hosts/common/nix/overlay"
|
||||
];
|
||||
|
||||
# ensure new deployments have a source of this repo with which they can bootstrap.
|
||||
environment.etc."nixos".source = ../../..;
|
||||
|
||||
systemd.services.nix-daemon.serviceConfig = {
|
||||
# the nix-daemon manages nix builders
|
||||
# kill nix-daemon subprocesses when systemd-oomd detects an out-of-memory condition
|
||||
# see:
|
||||
# - nixos PR that enabled systemd-oomd: <https://github.com/NixOS/nixpkgs/pull/169613>
|
||||
# - systemd's docs on these properties: <https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill>
|
||||
#
|
||||
# systemd's docs warn that without swap, systemd-oomd might not be able to react quick enough to save the system.
|
||||
# see `man oomd.conf` for further tunables that may help.
|
||||
#
|
||||
# alternatively, apply this more broadly with `systemd.oomd.enableSystemSlice = true` or `enableRootSlice`
|
||||
# TODO: also apply this to the guest user's slice (user-1100.slice)
|
||||
# TODO: also apply this to distccd
|
||||
ManagedOOMMemoryPressure = "kill";
|
||||
ManagedOOMSwap = "kill";
|
||||
};
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
sane.persist.stores.private.origin = "/home/colin/private";
|
||||
# store /home/colin/a/b in /home/private/a/b instead of /home/private/home/colin/a/b
|
||||
# store /home/colin/a/b in /mnt/persist/private/a/b instead of /mnt/persist/private/home/colin/a/b
|
||||
sane.persist.stores.private.prefix = "/home/colin";
|
||||
|
||||
sane.persist.sys.byStore.initrd = [
|
||||
"/var/log"
|
||||
];
|
||||
sane.persist.sys.byStore.plaintext = [
|
||||
# TODO: these should be private.. somehow
|
||||
"/var/log"
|
||||
"/var/backup" # for e.g. postgres dumps
|
||||
];
|
||||
sane.persist.sys.byStore.cryptClearOnBoot = [
|
||||
|
45
hosts/common/polyunfill.nix
Normal file
45
hosts/common/polyunfill.nix
Normal file
@@ -0,0 +1,45 @@
|
||||
# strictly *decrease* the scope of the default nixos installation/config
|
||||
|
||||
{ lib, ... }:
|
||||
{
|
||||
# disable non-required packages like nano, perl, rsync, strace
|
||||
environment.defaultPackages = [];
|
||||
|
||||
# remove all the non-existent default directories from XDG_DATA_DIRS, XDG_CONFIG_DIRS to simplify debugging.
|
||||
# this is defaulted in <repo:nixos/nixpkgs:nixos/modules/programs/environment.nix>,
|
||||
# without being gated by any higher config.
|
||||
environment.profiles = lib.mkForce [
|
||||
"/etc/profiles/per-user/$USER"
|
||||
"/run/current-system/sw"
|
||||
];
|
||||
|
||||
# NIXPKGS_CONFIG defaults to "/etc/nix/nixpkgs-config.nix", for idfk why.
|
||||
# that's never existed on my system and everything does fine without it set empty (no nixpkgs API to forcibly *unset* it).
|
||||
environment.variables.NIXPKGS_CONFIG = lib.mkForce "";
|
||||
# XDG_CONFIG_DIRS defaults to "/etc/xdg", which doesn't exist.
|
||||
# in practice, pam appends the values i want to XDG_CONFIG_DIRS, though this approach causes an extra leading `:`
|
||||
environment.sessionVariables.XDG_CONFIG_DIRS = lib.mkForce [];
|
||||
# XCURSOR_PATH: defaults to `[ "$HOME/.icons" "$HOME/.local/share/icons" ]`, neither of which i use, just adding noise.
|
||||
# see: <repo:nixos/nixpkgs:nixos/modules/config/xdg/icons.nix>
|
||||
environment.sessionVariables.XCURSOR_PATH = lib.mkForce [];
|
||||
|
||||
# disable nixos' portal module, otherwise /share/applications gets linked into the system and complicates things (sandboxing).
|
||||
# instead, i manage portals myself via the sane.programs API (e.g. sane.programs.xdg-desktop-portal).
|
||||
xdg.portal.enable = false;
|
||||
xdg.menus.enable = false; #< links /share/applications, and a bunch of other empty (i.e. unused) dirs
|
||||
|
||||
# xdg.autostart.enable defaults to true, and links /etc/xdg/autostart into the environment, populated with .desktop files.
|
||||
# see: <repo:nixos/nixpkgs:nixos/modules/config/xdg/autostart.nix>
|
||||
# .desktop files are a questionable way to autostart things: i generally prefer a service manager for that.
|
||||
xdg.autostart.enable = false;
|
||||
|
||||
# nix.channel.enable: populates `/nix/var/nix/profiles/per-user/root/channels`, `/root/.nix-channels`, `$HOME/.nix-defexpr/channels`
|
||||
# <repo:nixos/nixpkgs:nixos/modules/config/nix-channel.nix>
|
||||
# TODO: may want to recreate NIX_PATH, nix.settings.nix-path
|
||||
nix.channel.enable = false;
|
||||
|
||||
# environment.stub-ld: populate /lib/ld-linux.so with an object that unconditionally errors on launch,
|
||||
# so as to inform when trying to run a non-nixos binary?
|
||||
# IMO that's confusing: i thought /lib/ld-linux.so was some file actually required by nix.
|
||||
environment.stub-ld.enable = false;
|
||||
}
|
99
hosts/common/programs/abaddon.nix
Normal file
99
hosts/common/programs/abaddon.nix
Normal file
@@ -0,0 +1,99 @@
|
||||
# discord gtk3 client
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.abaddon;
|
||||
in
|
||||
{
|
||||
sane.programs.abaddon = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
packageUnwrapped = pkgs.abaddon.overrideAttrs (upstream: {
|
||||
patches = (upstream.patches or []) ++ [
|
||||
(pkgs.fetchpatch {
|
||||
url = "https://git.uninsane.org/colin/abaddon/commit/eb551f188d34679f75adcbc83cb8d5beb4d19fd6.patch";
|
||||
name = ''"view members" default to false'';
|
||||
hash = "sha256-9BX8iO86CU1lNrKS1G2BjDR+3IlV9bmhRNTsLrxChwQ=";
|
||||
})
|
||||
];
|
||||
});
|
||||
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
|
||||
fs.".config/abaddon/abaddon.ini".symlink.text = ''
|
||||
# see abaddon README.md for options.
|
||||
# at time of writing:
|
||||
# | Setting | Type | Default | Description |
|
||||
# |[discord]------|---------|---------|--------------------------------------------------------------------------------------------------|
|
||||
# | `gateway` | string | | override url for Discord gateway. must be json format and use zlib stream compression |
|
||||
# | `api_base` | string | | override base url for Discord API |
|
||||
# | `memory_db` | boolean | false | if true, Discord data will be kept in memory as opposed to on disk |
|
||||
# | `token` | string | | Discord token used to login, this can be set from the menu |
|
||||
# | `prefetch` | boolean | false | if true, new messages will cause the avatar and image attachments to be automatically downloaded |
|
||||
# | `autoconnect` | boolean | false | autoconnect to discord |
|
||||
# |[http]--------|--------|---------|---------------------------------------------------------------------------------------------|
|
||||
# | `user_agent` | string | | sets the user-agent to use in HTTP requests to the Discord API (not including media/images) |
|
||||
# | `concurrent` | int | 20 | how many images can be concurrently retrieved |
|
||||
# |[gui}------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------|
|
||||
# | `member_list_discriminator` | boolean | true | show user discriminators in the member list |
|
||||
# | `stock_emojis` | boolean | true | allow abaddon to substitute unicode emojis with images from emojis.bin, must be false to allow GTK to render emojis itself |
|
||||
# | `custom_emojis` | boolean | true | download and use custom Discord emojis |
|
||||
# | `css` | string | | path to the main CSS file |
|
||||
# | `animations` | boolean | true | use animated images where available (e.g. server icons, emojis, avatars). false means static images will be used |
|
||||
# | `animated_guild_hover_only` | boolean | true | only animate guild icons when the guild is being hovered over |
|
||||
# | `owner_crown` | boolean | true | show a crown next to the owner |
|
||||
# | `unreads` | boolean | true | show unread indicators and mention badges |
|
||||
# | `save_state` | boolean | true | save the state of the gui (active channels, tabs, expanded channels) |
|
||||
# | `alt_menu` | boolean | false | keep the menu hidden unless revealed with alt key |
|
||||
# | `hide_to_tray` | boolean | false | hide abaddon to the system tray on window close |
|
||||
# | `show_deleted_indicator` | boolean | true | show \[deleted\] indicator next to deleted messages instead of actually deleting the message |
|
||||
# | `font_scale` | double | | scale font rendering. 1 is unchanged |
|
||||
# |[style]------------------|--------|-----------------------------------------------------|
|
||||
# | `linkcolor` | string | color to use for links in messages |
|
||||
# | `expandercolor` | string | color to use for the expander in the channel list |
|
||||
# | `nsfwchannelcolor` | string | color to use for NSFW channels in the channel list |
|
||||
# | `channelcolor` | string | color to use for SFW channels in the channel list |
|
||||
# | `mentionbadgecolor` | string | background color for mention badges |
|
||||
# | `mentionbadgetextcolor` | string | color to use for number displayed on mention badges |
|
||||
# | `unreadcolor` | string | color to use for the unread indicator |
|
||||
# |[notifications]|---------|--------------------------|-------------------------------------------------------------------------------|
|
||||
# | `enabled` | boolean | true (if not on Windows) | Enable desktop notifications |
|
||||
# | `playsound` | boolean | true | Enable notification sounds. Requires ENABLE_NOTIFICATION_SOUNDS=TRUE in CMake |
|
||||
# |[voice]--|--------|------------------------------------|------------------------------------------------------------|
|
||||
# | `vad` | string | rnnoise if enabled, gate otherwise | Method used for voice activity detection. Changeable in UI |
|
||||
# |[windows]|---------|---------|-------------------------|
|
||||
# | `hideconsole` | boolean | true | Hide console on startup |
|
||||
|
||||
# N.B.: abaddon writes this file itself (and even when i don't change anything internally).
|
||||
# it prefers no spaces around the equal sign.
|
||||
[discord]
|
||||
autoconnect=true
|
||||
|
||||
[notifications]
|
||||
# playsound: i manage sounds via swaync
|
||||
playsound=false
|
||||
'';
|
||||
|
||||
persist.byStore.private = [
|
||||
".cache/abaddon"
|
||||
];
|
||||
|
||||
services.abaddon = {
|
||||
description = "unofficial Discord chat client";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/abaddon";
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "20s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -3,6 +3,9 @@
|
||||
|
||||
{
|
||||
sane.programs.aerc = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace";
|
||||
sandbox.net = "clearnet";
|
||||
secrets.".config/aerc/accounts.conf" = ../../../secrets/common/aerc_accounts.conf.bin;
|
||||
mime.associations."x-scheme-handler/mailto" = "aerc.desktop";
|
||||
};
|
||||
|
@@ -6,19 +6,36 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
sane.programs.alacritty = {
|
||||
sandbox.enable = false;
|
||||
env.TERMINAL = lib.mkDefault "alacritty";
|
||||
# note: alacritty will switch to .toml config in 13.0 release
|
||||
# - run `alacritty migrate` to convert the yaml to toml
|
||||
fs.".config/alacritty/alacritty.yml".symlink.text = ''
|
||||
font:
|
||||
size: 14
|
||||
fs.".config/alacritty/alacritty.toml".symlink.text = ''
|
||||
[font]
|
||||
size = 14
|
||||
|
||||
key_bindings:
|
||||
- { key: N, mods: Control, action: CreateNewWindow }
|
||||
- { key: PageUp, mods: Control, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control, action: ScrollPageDown }
|
||||
- { key: PageUp, mods: Control|Shift, action: ScrollPageUp }
|
||||
- { key: PageDown, mods: Control|Shift, action: ScrollPageDown }
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "N"
|
||||
action = "CreateNewWindow"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "PageUp"
|
||||
action = "ScrollPageUp"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control"
|
||||
key = "PageDown"
|
||||
action = "ScrollPageDown"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control|Shift"
|
||||
key = "PageUp"
|
||||
action = "ScrollPageUp"
|
||||
|
||||
[[keyboard.bindings]]
|
||||
mods = "Control|Shift"
|
||||
key = "PageDown"
|
||||
action = "ScrollPageDown"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
42
hosts/common/programs/animatch.nix
Normal file
42
hosts/common/programs/animatch.nix
Normal file
@@ -0,0 +1,42 @@
|
||||
# debug with:
|
||||
# - `animatch --debug`
|
||||
# - `gdb animatch`
|
||||
# try:
|
||||
# - `animatch --fullscreen`
|
||||
# - `animatch --windowed`
|
||||
# the other config options (e.g. verbose logging -- which doesn't seem to do anything) have to be configured via .ini file
|
||||
# ```ini
|
||||
# # ~/.config/Holy Pangolin/Animatch/SuperDerpy.ini
|
||||
# [SuperDerpy]
|
||||
# debug=1
|
||||
# disableTouch=1
|
||||
# [game]
|
||||
# verbose=1
|
||||
# ```
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.animatch = {
|
||||
packageUnwrapped = with pkgs; animatch.override {
|
||||
# allegro has no native wayland support, and so by default crashes when run without Xwayland.
|
||||
# enable the allegro SDL backend, and achieve Wayland support via SDL's Wayland support.
|
||||
# TODO: see about upstreaming this to nixpkgs?
|
||||
allegro5 = allegro5.overrideAttrs (upstream: {
|
||||
buildInputs = upstream.buildInputs ++ [
|
||||
SDL2
|
||||
];
|
||||
cmakeFlags = upstream.cmakeFlags ++ [
|
||||
"-DALLEGRO_SDL=on"
|
||||
];
|
||||
});
|
||||
};
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
persist.byStore.plaintext = [
|
||||
# ".config/Holy Pangolin/Animatch" #< used for SuperDerpy config (e.g. debug, disableTouch, fullscreen, enable sound, etc). SuperDerpy.ini
|
||||
".local/share/Holy Pangolin/Animatch" #< used for game state (level clears). SuperDerpy.ini
|
||||
];
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
35
hosts/common/programs/audacity.nix
Normal file
35
hosts/common/programs/audacity.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.audacity = {
|
||||
packageUnwrapped = pkgs.audacity.override {
|
||||
# wxGTK32 uses webkitgtk-4.0.
|
||||
# audacity doesn't actually need webkit though, so diable to reduce closure
|
||||
wxGTK32 = pkgs.wxGTK32.override {
|
||||
withWebKit = false;
|
||||
};
|
||||
};
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.autodetectCliPaths = true;
|
||||
sandbox.extraHomePaths = [
|
||||
# support media imports via file->open dir to some common media directories
|
||||
"tmp"
|
||||
"Music"
|
||||
# audacity needs the entire config dir mounted if running in a sandbox
|
||||
".config/audacity"
|
||||
];
|
||||
|
||||
# disable first-run splash screen
|
||||
fs.".config/audacity/audacity.cfg".file.text = ''
|
||||
PrefsVersion=1.1.1r1
|
||||
[GUI]
|
||||
ShowSplashScreen=0
|
||||
[Version]
|
||||
Major=3
|
||||
Minor=4
|
||||
'';
|
||||
};
|
||||
}
|
@@ -87,7 +87,14 @@ let
|
||||
in
|
||||
{
|
||||
sane.programs.bemenu = {
|
||||
package = pkgs.bemenu.overrideAttrs (upstream: {
|
||||
sandbox.method = "bwrap"; # landlock works, but requires *all* of /run/user/$ID to be granted.
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
".cache/fontconfig" #< else it complains, and is *way* slower
|
||||
];
|
||||
|
||||
packageUnwrapped = pkgs.bemenu.overrideAttrs (upstream: {
|
||||
nativeBuildInputs = (upstream.nativeBuildInputs or []) ++ [
|
||||
pkgs.makeWrapper
|
||||
];
|
||||
|
130
hosts/common/programs/bonsai.nix
Normal file
130
hosts/common/programs/bonsai.nix
Normal file
@@ -0,0 +1,130 @@
|
||||
# bonsai docs: <https://sr.ht/~stacyharper/bonsai/>
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.bonsai;
|
||||
|
||||
delayType = with lib; types.submodule {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "delay" ];
|
||||
# default = "delay";
|
||||
};
|
||||
delay_duration = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
used for "delay" types only.
|
||||
nanoseconds until the event is finalized.
|
||||
'';
|
||||
};
|
||||
transitions = mkOption {
|
||||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
description = ''
|
||||
list of transitions out of this state (i.e. after completing the delay).
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
eventType = with lib; types.submodule {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "event" ];
|
||||
# default = "event";
|
||||
};
|
||||
event_name = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
name of event which this transition applies to.
|
||||
'';
|
||||
};
|
||||
transitions = mkOption {
|
||||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
description = ''
|
||||
list of transitions out of this state.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
execType = with lib; types.submodule {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = types.enum [ "exec" ];
|
||||
# default = "exec";
|
||||
};
|
||||
command = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
command to run when the event is triggered.
|
||||
'';
|
||||
};
|
||||
transitions = mkOption {
|
||||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
description = ''
|
||||
list of transitions out of this state (i.e. after successfully executing the command)
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
isDelay = x: delayType.check x && x.type == "delay";
|
||||
isEvent = x: eventType.check x && x.type == "event";
|
||||
isExec = x: execType.check x && x.type == "exec";
|
||||
# unfortunately, `types.oneOf` is naive about submodules, so we need our own type.
|
||||
# transitionType = lib.types.oneOf [ delayType eventType execType ];
|
||||
transitionType = with lib.types; mkOptionType {
|
||||
name = "transition";
|
||||
check = x: isDelay x || isEvent x || isExec x;
|
||||
merge = loc: defs: let
|
||||
defList = builtins.map (d: d.value) defs;
|
||||
in
|
||||
if builtins.all isDelay defList then
|
||||
delayType.merge loc defs
|
||||
else if builtins.all isEvent defList then
|
||||
eventType.merge loc defs
|
||||
else if builtins.all isExec defList then
|
||||
execType.merge loc defs
|
||||
else
|
||||
mergeOneOption loc defs
|
||||
;
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.bonsai = {
|
||||
configOption = with lib; mkOption {
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
options = {
|
||||
transitions = mkOption {
|
||||
type = types.listOf transitionType;
|
||||
default = [];
|
||||
};
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = pkgs.writeText "bonsai_tree.json" (builtins.toJSON cfg.config.transitions);
|
||||
description = ''
|
||||
configuration file to pass to bonsai.
|
||||
usually auto-generated from the sibling options; exposed mainly for debugging or convenience.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.bonsaid = {
|
||||
description = "bonsai: programmable input dispatcher";
|
||||
after = [ "graphical-session.target" ];
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
script = ''
|
||||
${pkgs.coreutils}/bin/rm -f $XDG_RUNTIME_DIR/bonsai
|
||||
exec ${cfg.package}/bin/bonsaid -t ${cfg.config.configFile}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,6 +1,17 @@
|
||||
{ ... }:
|
||||
{
|
||||
sane.programs.brave = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # /opt/share/brave.com vendor-style packaging
|
||||
sandbox.net = "all";
|
||||
sandbox.extraHomePaths = [
|
||||
"dev" # for developing anything web-related
|
||||
"tmp"
|
||||
];
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDri = true;
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
persist.byStore.cryptClearOnBoot = [
|
||||
".cache/BraveSoftware"
|
||||
".config/BraveSoftware"
|
||||
|
35
hosts/common/programs/bubblewrap.nix
Normal file
35
hosts/common/programs/bubblewrap.nix
Normal file
@@ -0,0 +1,35 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.bubblewrap = {
|
||||
sandbox.enable = false; # don't sandbox the sandboxer :)
|
||||
packageUnwrapped = pkgs.bubblewrap.overrideAttrs (base: {
|
||||
# patches = (base.patches or []) ++ [
|
||||
# (pkgs.fetchpatch {
|
||||
# url = "https://git.uninsane.org/colin/bubblewrap/commit/9843f9b2b5f086563fd37250658d69350a2939be.patch";
|
||||
# name = "enable debug logging and add a bunch more tracing";
|
||||
# hash = "sha256-AlDsqddaBahhqGibZlCjgmChuK7mmxDt0aYHNgY05OI=";
|
||||
# })
|
||||
# ];
|
||||
postPatch = (base.postPatch or "") + ''
|
||||
# bwrap doesn't like to be invoked with any capabilities, which is troublesome if i
|
||||
# want to do things like ship CAP_NET_ADMIN,CAP_NET_RAW in the ambient set for tools like Wireshark.
|
||||
# but this limitation of bwrap is artificial and at first look is just a scenario the author probably
|
||||
# never expected: patch out the guard check.
|
||||
#
|
||||
# see: <https://github.com/containers/bubblewrap/issues/397>
|
||||
#
|
||||
# note that invoking bwrap with capabilities in the 'init' namespace does NOT grant the sandboxed process
|
||||
# capabilities in the 'init' namespace. it's a limitation of namespaces that namespaced processes can
|
||||
# never receive capabilities in their parent namespace.
|
||||
substituteInPlace bubblewrap.c --replace \
|
||||
'die ("Unexpected capabilities but not setuid, old file caps config?");' \
|
||||
'// die ("Unexpected capabilities but not setuid, old file caps config?");'
|
||||
|
||||
# enable debug printing
|
||||
# substituteInPlace utils.h --replace \
|
||||
# '#define __debug__(x)' \
|
||||
# '#define __debug__(x) printf x'
|
||||
'';
|
||||
});
|
||||
};
|
||||
}
|
@@ -44,7 +44,7 @@ in
|
||||
services.gnome-calls = {
|
||||
# TODO: prevent gnome-calls from daemonizing when started manually
|
||||
description = "gnome-calls daemon to monitor incoming SIP calls";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
# add --verbose for more debugging
|
||||
ExecStart = "${cfg.package}/bin/gnome-calls --daemon";
|
||||
|
29
hosts/common/programs/catt.nix
Normal file
29
hosts/common/programs/catt.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
# use like:
|
||||
# - catt -d lgtv_chrome cast ./path/to.mp4
|
||||
#
|
||||
# support matrix:
|
||||
# - webm: audio only
|
||||
# - mp4: audio + video
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.sane.programs.catt;
|
||||
in
|
||||
{
|
||||
sane.programs.catt = {
|
||||
fs.".config/catt/catt.cfg".symlink.text = ''
|
||||
[options]
|
||||
device = lgtv_chrome
|
||||
|
||||
[aliases]
|
||||
lgtv_chrome = 10.78.79.106
|
||||
'';
|
||||
};
|
||||
|
||||
# necessary to cast local files
|
||||
networking.firewall.allowedTCPPortRanges = lib.mkIf cfg.enabled [
|
||||
{
|
||||
from = 45000;
|
||||
to = 47000;
|
||||
}
|
||||
];
|
||||
}
|
@@ -1,40 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
chattyNoOauth = pkgs.chatty.override {
|
||||
# the OAuth feature (presumably used for web-based logins) pulls a full webkitgtk.
|
||||
# especially when using the gtk3 version of evolution-data-server, it's an ancient webkitgtk_4_1.
|
||||
# disable OAuth for a faster build & smaller closure
|
||||
evolution-data-server = pkgs.evolution-data-server.override {
|
||||
enableOAuth2 = false;
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
chatty-latest = pkgs.chatty-latest.override {
|
||||
evolution-data-server-gtk4 = pkgs.evolution-data-server-gtk4.override {
|
||||
gnome-online-accounts = pkgs.gnome-online-accounts.override {
|
||||
# disables the upstream "goabackend" feature -- presumably "Gnome Online Accounts Backend"
|
||||
# frees us from webkit_4_1, in turn.
|
||||
enableBackend = false;
|
||||
gvfs = pkgs.gvfs.override {
|
||||
# saves 20 minutes of build time and cross issues, for unused feature
|
||||
samba = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
sane.programs.chatty = {
|
||||
# package = chattyNoOauth;
|
||||
package = chatty-latest;
|
||||
# packageUnwrapped = chattyNoOauth;
|
||||
packageUnwrapped = pkgs.chatty-latest;
|
||||
suggestedPrograms = [ "gnome-keyring" ];
|
||||
persist.byStore.private = [
|
||||
".local/share/chatty" # matrix avatars and files
|
||||
|
@@ -1,32 +1,184 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env nix-shell
|
||||
#!nix-shell -i bash
|
||||
|
||||
full=$(cat /sys/class/power_supply/axp20x-battery/charge_full_design)
|
||||
rate=$(cat /sys/class/power_supply/axp20x-battery/current_now)
|
||||
perc=$(cat /sys/class/power_supply/axp20x-battery/capacity)
|
||||
perc_left=$((100 - $perc))
|
||||
# these icons come from sxmo; they only render in nerdfonts
|
||||
bat_dis=""
|
||||
bat_chg=""
|
||||
usage() {
|
||||
echo "usage: battery_estimate [options...]"
|
||||
echo
|
||||
echo "pretty-prints a battery estimate (icon to indicate state, and a duration estimate)"
|
||||
echo
|
||||
echo "options:"
|
||||
echo " --debug: output additional information, to stderr"
|
||||
echo " --minute-suffix <string>: use the provided string as a minutes suffix"
|
||||
echo " --hour-suffix <string>: use the provided string as an hours suffix"
|
||||
echo " --icon-suffix <string>: use the provided string as an icon suffix"
|
||||
echo " --percent-suffix <string>: use the provided string when displaying percents"
|
||||
}
|
||||
|
||||
fmt_minutes() {
|
||||
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
||||
if [[ $3 -gt 1440 ]]; then
|
||||
printf "%s %s" "$1" "$2" # more than 1d
|
||||
else
|
||||
hr=$(($3 / 60))
|
||||
hr_in_min=$(($hr * 60))
|
||||
min=$(($3 - $hr_in_min))
|
||||
printf "%s %dh%02dm" "$1" "$hr" "$min"
|
||||
# these icons come from sxmo; they only render in nerdfonts
|
||||
icon_bat_chg=("" "" "" "")
|
||||
icon_bat_dis=("" "" "" "")
|
||||
suffix_icon=" " # thin space
|
||||
suffix_percent="%"
|
||||
# suffix_icon=" "
|
||||
|
||||
# render time like: 2ʰ08ᵐ
|
||||
# unicode sub/super-scripts: <https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts>
|
||||
# symbol_hr="ʰ"
|
||||
# symbol_min="ᵐ"
|
||||
|
||||
# render time like: 2ₕ08ₘ
|
||||
# symbol_hr="ₕ"
|
||||
# symbol_min="ₘ"
|
||||
|
||||
# render time like: 2h08m
|
||||
# symbol_hr="h"
|
||||
# symbol_min="m"
|
||||
|
||||
# render time like: 2:08
|
||||
# symbol_hr=":"
|
||||
# symbol_min=
|
||||
|
||||
# render time like: 2꞉08⧗
|
||||
symbol_hr="꞉"
|
||||
symbol_min="⧗"
|
||||
# variants:
|
||||
# symbol_hr=":"
|
||||
# symbol_min="⧖"
|
||||
# symbol_min="⌛"
|
||||
|
||||
# render time like: 2'08"
|
||||
# symbol_hr="'"
|
||||
# symbol_min='"'
|
||||
|
||||
log() {
|
||||
if [ "$BATTERY_ESTIMATE_DEBUG" = "1" ]; then
|
||||
printf "$@" >&2
|
||||
echo >&2
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $rate -lt 0 ]]; then
|
||||
# discharging
|
||||
fmt_minutes "$bat_dis" '∞' "$(($full * 60 * $perc / (-100 * $rate)))"
|
||||
elif [[ $rate -gt 0 ]]; then
|
||||
# charging
|
||||
fmt_minutes "$bat_chg" '100%' "$(($full * 60 * $perc_left / (100 * $rate)))"
|
||||
else
|
||||
echo "$bat_dis $perc%"
|
||||
fi
|
||||
render_icon() {
|
||||
# args:
|
||||
# 1: "chg" or "dis"
|
||||
# 2: current battery percentage
|
||||
level=$(($2 / 25))
|
||||
level=$(($level > 3 ? 3 : $level))
|
||||
level=$(($level < 0 ? 0 : $level))
|
||||
log "icon: %s %d" "$1" "$level"
|
||||
if [ "$1" = "dis" ]; then
|
||||
printf "%s" "${icon_bat_dis[$level]}"
|
||||
elif [ "$1" = "chg" ]; then
|
||||
printf "%s" "${icon_bat_chg[$level]}"
|
||||
fi
|
||||
}
|
||||
|
||||
try_path() {
|
||||
# assigns output variables:
|
||||
# - perc, perc_from_full (0-100)
|
||||
# - full, rate (pos means charging)
|
||||
if [ -f "$1/capacity" ]; then
|
||||
log "perc, perc_from_full from %s" "$1/capacity"
|
||||
perc=$(cat "$1/capacity")
|
||||
perc_from_full=$((100 - $perc))
|
||||
fi
|
||||
|
||||
if [ -f "$1/charge_full_design" ] && [ -f "$1/current_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/charge_full_design" "$1/current_now"
|
||||
# current is positive when charging
|
||||
full=$(cat "$1/charge_full_design")
|
||||
rate=$(cat "$1/current_now")
|
||||
elif [ -f "$1/energy_full" ] && [ -f "$1/power_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/energy_full" "$1/power_now"
|
||||
# power_now is positive when discharging
|
||||
full=$(cat "$1/energy_full")
|
||||
rate=-$(cat "$1/power_now")
|
||||
elif [ -f "$1/energy_full" ] && [ -f "$1/energy_now" ]; then
|
||||
log "full, rate from %s and %s" "$1/energy_full" "$1/energy_now"
|
||||
log " this is a compatibility path for legacy Thinkpad batteries which do not populate the 'power_now' field, and incorrectly populate 'energy_now' with power info"
|
||||
# energy_now is positive when discharging
|
||||
full=$(cat "$1/energy_full")
|
||||
rate=-$(cat "$1/energy_now")
|
||||
fi
|
||||
}
|
||||
|
||||
try_all_paths() {
|
||||
try_path "/sys/class/power_supply/axp20x-battery" # Pinephone
|
||||
try_path "/sys/class/power_supply/BAT0" # Thinkpad
|
||||
log "perc: %d, perc_from_full: %d" "$perc" "$perc_from_full"
|
||||
log "full: %f, rate: %f" "$full" "$rate"
|
||||
log " rate > 0 means charging, else discharging"
|
||||
}
|
||||
|
||||
fmt_minutes() {
|
||||
# args:
|
||||
# 1: icon to render
|
||||
# 2: string to show if charge/discharge time is indefinite
|
||||
# 3: minutes to stable state (i.e. to full charge or full discharge)
|
||||
# - we work in minutes instead of hours for precision: bash math is integer-only
|
||||
log "charge/discharge time: %f min" "$3"
|
||||
# args: <battery symbol> <text if ludicrous estimate> <estimated minutes to full/empty>
|
||||
if [ -n "$3" ] && [ "$3" -lt 1440 ]; then
|
||||
hr=$(($3 / 60))
|
||||
hr_in_min=$(($hr * 60))
|
||||
min=$(($3 - $hr_in_min))
|
||||
printf "%s%s%d%s%02d%s" "$1" "$suffix_icon" "$hr" "$symbol_hr" "$min" "$symbol_min"
|
||||
else
|
||||
log "charge/discharge duration > 1d"
|
||||
printf "%s%s%s" "$1" "$suffix_icon" "$2" # more than 1d
|
||||
fi
|
||||
}
|
||||
|
||||
pretty_output() {
|
||||
if [ -n "$perc" ]; then
|
||||
duration=""
|
||||
if [ "$rate" -gt 0 ]; then
|
||||
log "charging"
|
||||
icon="$(render_icon chg $perc)"
|
||||
duration="$(($full * 60 * $perc_from_full / (100 * $rate)))"
|
||||
else
|
||||
log "discharging"
|
||||
icon="$(render_icon dis $perc)"
|
||||
if [ "$rate" -lt 0 ]; then
|
||||
duration="$(($full * 60 * $perc / (-100 * $rate)))"
|
||||
fi
|
||||
fi
|
||||
fmt_minutes "$icon" "$perc$suffix_percent" "$duration"
|
||||
fi
|
||||
}
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
"--debug")
|
||||
shift
|
||||
BATTERY_ESTIMATE_DEBUG=1
|
||||
;;
|
||||
"--icon-suffix")
|
||||
shift
|
||||
suffix_icon="$1"
|
||||
shift
|
||||
;;
|
||||
"--hour-suffix")
|
||||
shift
|
||||
symbol_hr="$1"
|
||||
shift
|
||||
;;
|
||||
"--minute-suffix")
|
||||
shift
|
||||
symbol_min="$1"
|
||||
shift
|
||||
;;
|
||||
"--percent-suffix")
|
||||
shift
|
||||
suffix_percent="$1"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
try_all_paths
|
||||
pretty_output
|
||||
|
@@ -3,48 +3,80 @@
|
||||
-- - can also use #rrggbb syntax
|
||||
-- example configs: <https://forum.manjaro.org/t/conky-showcase-2022/97123>
|
||||
-- example configs: <https://www.reddit.com/r/Conkyporn/>
|
||||
--
|
||||
-- exec options:
|
||||
-- `exec <cmd>` => executes the command, synchronously, renders its output as text
|
||||
-- `texeci <interval_sec> <cmd>` => executes the command periodically, async (to not block render), renders as text
|
||||
-- `pexec <cmd>` => executes the command, synchronously, parses its output
|
||||
|
||||
conky.config = {
|
||||
out_to_wayland = true,
|
||||
update_interval = 10,
|
||||
out_to_wayland = true,
|
||||
update_interval = 10,
|
||||
|
||||
alignment = 'middle_middle',
|
||||
own_window_type = 'desktop',
|
||||
-- own_window_argb_value: opacity of the background (0-255)
|
||||
own_window_argb_value = 0,
|
||||
-- own_window_argb_value = 92,
|
||||
-- own_window_colour = '#beebe5', -- beebe5 matches nixos flake bg color
|
||||
alignment = 'middle_middle',
|
||||
own_window_type = 'desktop',
|
||||
-- own_window_argb_value: opacity of the background (0-255)
|
||||
own_window_argb_value = 0,
|
||||
-- own_window_argb_value = 92,
|
||||
-- own_window_colour = '#beebe5', -- beebe5 matches nixos flake bg color
|
||||
|
||||
-- "border" pads the entire conky window
|
||||
-- this can be used to control the extent of the own_window background
|
||||
border_inner_margin = 8,
|
||||
-- optionally, actually draw borders
|
||||
-- draw_borders = true,
|
||||
-- "border" pads the entire conky window
|
||||
-- this can be used to control the extent of the own_window background
|
||||
border_inner_margin = 8,
|
||||
-- optionally, actually draw borders
|
||||
-- draw_borders = true,
|
||||
|
||||
-- shades are drop-shadows, outline is the centered version. both apply to text only
|
||||
draw_shades = true,
|
||||
draw_outline = false,
|
||||
default_shade_color = '#beebe5',
|
||||
default_outline_color = '#beebe5',
|
||||
-- shades are drop-shadows, outline is the centered version. both apply to text only
|
||||
draw_shades = true,
|
||||
draw_outline = false,
|
||||
default_shade_color = '#beebe5',
|
||||
default_outline_color = '#beebe5',
|
||||
|
||||
font = 'sans-serif:size=8',
|
||||
use_xft = true,
|
||||
font = 'sans-serif:size=8',
|
||||
use_xft = true,
|
||||
|
||||
default_color = '#ffffff',
|
||||
color1 = '000000',
|
||||
color2 = '404040',
|
||||
default_color = '#ffffff',
|
||||
color1 = '000000',
|
||||
color2 = '404040',
|
||||
}
|
||||
|
||||
-- texeci <interval_sec> <cmd>: run the command periodically, _in a separate thread_ so as not to block rendering
|
||||
vars = {
|
||||
-- kBps = 'K/s',
|
||||
kBps = 'ᴷᐟˢ',
|
||||
-- percent = '%',
|
||||
-- percent = '﹪',
|
||||
percent = '٪',
|
||||
-- percent = '⁒',
|
||||
-- percent = '%',
|
||||
icon_suffix = nil,
|
||||
hour_suffix = nil,
|
||||
minute_suffix = '${font sans-serif:size=14}${color2}⧗',
|
||||
}
|
||||
|
||||
bat_args = ""
|
||||
if vars.icon_suffix ~= nil then
|
||||
bat_args = bat_args .. " --icon-suffix '" .. vars.icon_suffix .. "'"
|
||||
end
|
||||
if vars.hour_suffix ~= nil then
|
||||
bat_args = bat_args .. " --hour-suffix '" .. vars.hour_suffix .. "'"
|
||||
end
|
||||
if vars.minute_suffix ~= nil then
|
||||
bat_args = bat_args .. " --minute-suffix '" .. vars.minute_suffix .. "'"
|
||||
end
|
||||
if vars.percent ~= nil then
|
||||
bat_args = bat_args .. " --percent-suffix '" .. vars.percent .. "'"
|
||||
end
|
||||
|
||||
-- N.B.: `[[ <text> ]]` is Lua's multiline string literal
|
||||
conky.text = [[
|
||||
${color1}${shadecolor 707070}${font sans-serif:size=50:style=Bold}${alignc}${exec date +"%H:%M"}${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=20}${alignc}${exec date +"%a %d %b"}${font}
|
||||
|
||||
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${exec @bat@ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=22:style=Bold}${alignc}${execp @bat@ ]] .. bat_args .. [[ }${font}
|
||||
${color1}${shadecolor}${font sans-serif:size=20:style=Bold}${alignc}${texeci 600 @weather@ }${font}
|
||||
|
||||
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}K/s${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc% $cpu%${font}
|
||||
${color2}${shadecolor a4d7d0}${font sans-serif:size=16}${alignc}⇅ ${downspeedf wlan0}]] .. vars.kBps .. [[${font}
|
||||
${font sans-serif:size=16}${alignc}☵ $memperc]] .. vars.percent .. [[ $cpu]] .. vars.percent .. [[${font}
|
||||
]]
|
||||
|
@@ -1,11 +1,22 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
sane.programs.conky = {
|
||||
# TODO: non-sandboxed `conky` still ships via `sxmo-utils`, but unused
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.net = "clearnet"; #< for the scripts it calls (weather)
|
||||
sandbox.extraPaths = [
|
||||
"/sys/class/power_supply"
|
||||
"/sys/devices" # needed by battery_estimate
|
||||
# "/sys/devices/cpu"
|
||||
# "/sys/devices/system"
|
||||
];
|
||||
sandbox.whitelistWayland = true;
|
||||
|
||||
fs.".config/conky/conky.conf".symlink.target =
|
||||
let
|
||||
battery_estimate = pkgs.static-nix-shell.mkBash {
|
||||
pname = "battery_estimate";
|
||||
src = ./.;
|
||||
srcRoot = ./.;
|
||||
};
|
||||
in pkgs.substituteAll {
|
||||
src = ./conky.conf;
|
||||
@@ -15,20 +26,14 @@
|
||||
|
||||
services.conky = {
|
||||
description = "conky dynamic desktop background";
|
||||
wantedBy = [ "default.target" ];
|
||||
# XXX: should be part of graphical-session.target, but whatever mix of greetd/sway
|
||||
# i'm using means that target's never reached...
|
||||
# wantedBy = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ]; # propagate stop/restart signal from graphical-session to this unit
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig.ExecStart = "${config.sane.programs.conky.package}/bin/conky";
|
||||
serviceConfig.Type = "simple";
|
||||
serviceConfig.Restart = "on-failure";
|
||||
serviceConfig.RestartSec = "10s";
|
||||
# serviceConfig.Slice = "session.slice";
|
||||
|
||||
# don't start conky until after sway
|
||||
preStart = ''test -n "$SWAYSOCK"'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@@ -2,6 +2,16 @@
|
||||
|
||||
{
|
||||
sane.programs.cozy = {
|
||||
sandbox.method = "bwrap"; # landlock gives: _multiprocessing.SemLock: Permission Denied
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # mpris
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
"Books/local"
|
||||
"Books/servo"
|
||||
];
|
||||
|
||||
# cozy uses a sqlite db for its config and exposes no CLI options other than --help and --debug
|
||||
persist.byStore.plaintext = [
|
||||
".local/share/cozy" # sqlite db (config & index?)
|
||||
|
33
hosts/common/programs/dconf.nix
Normal file
33
hosts/common/programs/dconf.nix
Normal file
@@ -0,0 +1,33 @@
|
||||
# dconf docs: <https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/desktop_migration_and_administration_guide/profiles>
|
||||
# this lets programs temporarily write user-level dconf settings (aka gsettings).
|
||||
# they're written to ~/.config/dconf/user, unless `DCONF_PROFILE` is set to something other than the default of /etc/dconf/profile/user
|
||||
# find keys/values with `dconf dump /`
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.sane.programs.dconf;
|
||||
in
|
||||
{
|
||||
sane.programs.dconf = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
persist.byStore.private = [
|
||||
".config/dconf"
|
||||
];
|
||||
};
|
||||
|
||||
programs.dconf = lib.mkIf cfg.enabled {
|
||||
# note that `programs.dconf` doesn't allow specifying the dconf package.
|
||||
enable = true;
|
||||
packages = [
|
||||
(pkgs.writeTextFile {
|
||||
name = "dconf-user-profile";
|
||||
destination = "/etc/dconf/profile/user";
|
||||
text = ''
|
||||
user-db:user
|
||||
system-db:site
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
@@ -2,72 +2,119 @@
|
||||
|
||||
{
|
||||
imports = [
|
||||
./abaddon.nix
|
||||
./aerc.nix
|
||||
./alacritty.nix
|
||||
./animatch.nix
|
||||
./assorted.nix
|
||||
./audacity.nix
|
||||
./bemenu.nix
|
||||
./bonsai.nix
|
||||
./brave.nix
|
||||
./bubblewrap.nix
|
||||
./calls.nix
|
||||
./cantata.nix
|
||||
./catt.nix
|
||||
./chatty.nix
|
||||
./conky
|
||||
./cozy.nix
|
||||
./dconf.nix
|
||||
./dialect.nix
|
||||
./dino.nix
|
||||
./element-desktop.nix
|
||||
./epiphany.nix
|
||||
./evince.nix
|
||||
./feedbackd.nix
|
||||
./firefox.nix
|
||||
./firejail.nix
|
||||
./flare-signal.nix
|
||||
./fontconfig.nix
|
||||
./fractal.nix
|
||||
./frozen-bubble.nix
|
||||
./fwupd.nix
|
||||
./g4music.nix
|
||||
./gajim.nix
|
||||
./gdbus.nix
|
||||
./geary.nix
|
||||
./git.nix
|
||||
./gnome-feeds.nix
|
||||
./gnome-keyring.nix
|
||||
./gnome-keyring
|
||||
./gnome-maps.nix
|
||||
./gnome-weather.nix
|
||||
./go2tv.nix
|
||||
./gpodder.nix
|
||||
./grimshot.nix
|
||||
./gthumb.nix
|
||||
./gtkcord4.nix
|
||||
./handbrake.nix
|
||||
./helix.nix
|
||||
./imagemagick.nix
|
||||
./jellyfin-media-player.nix
|
||||
./kdenlive.nix
|
||||
./komikku.nix
|
||||
./koreader
|
||||
./libreoffice.nix
|
||||
./lemoa.nix
|
||||
./loupe.nix
|
||||
./mako.nix
|
||||
./megapixels.nix
|
||||
./mepo.nix
|
||||
./mimeo
|
||||
./mopidy.nix
|
||||
./mpv.nix
|
||||
./msmtp.nix
|
||||
./nautilus.nix
|
||||
./neovim.nix
|
||||
./newsflash.nix
|
||||
./nheko.nix
|
||||
./nicotine-plus.nix
|
||||
./nix-index.nix
|
||||
./notejot.nix
|
||||
./ntfy-sh.nix
|
||||
./obsidian.nix
|
||||
./offlineimap.nix
|
||||
./open-in-mpv.nix
|
||||
./pipewire.nix
|
||||
./planify.nix
|
||||
./portfolio-filemanager.nix
|
||||
./playerctl.nix
|
||||
./rhythmbox.nix
|
||||
./ripgrep.nix
|
||||
./rofi
|
||||
./sane-scripts.nix
|
||||
./sfeed.nix
|
||||
./signal-desktop.nix
|
||||
./splatmoji.nix
|
||||
./spot.nix
|
||||
./spotify.nix
|
||||
./steam.nix
|
||||
./stepmania.nix
|
||||
./strings.nix
|
||||
./sublime-music.nix
|
||||
./supertuxkart.nix
|
||||
./sway
|
||||
./sway-autoscaler
|
||||
./swaylock.nix
|
||||
./swaynotificationcenter.nix
|
||||
./tangram.nix
|
||||
./tor-browser-bundle-bin.nix
|
||||
./tor-browser.nix
|
||||
./tuba.nix
|
||||
./unl0kr
|
||||
./vlc.nix
|
||||
./waybar
|
||||
./waylock.nix
|
||||
./wike.nix
|
||||
./wine.nix
|
||||
./wireplumber.nix
|
||||
./wireshark.nix
|
||||
./wob
|
||||
./xarchiver.nix
|
||||
./xdg-desktop-portal.nix
|
||||
./xdg-desktop-portal-gtk.nix
|
||||
./xdg-desktop-portal-wlr.nix
|
||||
./xdg-utils.nix
|
||||
./zeal.nix
|
||||
./zecwallet-lite.nix
|
||||
./zsh
|
||||
];
|
||||
|
||||
|
19
hosts/common/programs/dialect.nix
Normal file
19
hosts/common/programs/dialect.nix
Normal file
@@ -0,0 +1,19 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
sane.programs.dialect = {
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "inplace"; # share/search_providers/ calls back into the binary, weird wrap semantics
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.net = "clearnet";
|
||||
suggestedPrograms = [ "dconf" ]; #< to persist settings
|
||||
|
||||
packageUnwrapped = pkgs.dialect.overrideAttrs (upstream: {
|
||||
# TODO: send upstream
|
||||
# TODO: figure out how to get audio working
|
||||
# TODO: move to runtimeDependencies?
|
||||
buildInputs = upstream.buildInputs ++ [
|
||||
pkgs.glib-networking # for TLS
|
||||
];
|
||||
});
|
||||
};
|
||||
}
|
@@ -40,16 +40,39 @@ in
|
||||
type = types.submodule {
|
||||
options.autostart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
sandbox.method = "bwrap";
|
||||
sandbox.wrapperType = "wrappedDerivation";
|
||||
sandbox.net = "clearnet";
|
||||
sandbox.whitelistAudio = true;
|
||||
sandbox.whitelistDbus = [ "user" ]; # notifications
|
||||
sandbox.whitelistDri = true; #< not strictly necessary, but we need all the perf we can get on moby
|
||||
sandbox.whitelistWayland = true;
|
||||
sandbox.extraHomePaths = [
|
||||
"Music"
|
||||
"Pictures/albums"
|
||||
"Pictures/cat"
|
||||
"Pictures/from"
|
||||
"Pictures/Photos"
|
||||
"Pictures/Screenshots"
|
||||
"Pictures/servo-macros"
|
||||
"Videos/local"
|
||||
"Videos/servo"
|
||||
"tmp"
|
||||
];
|
||||
|
||||
persist.byStore.private = [ ".local/share/dino" ];
|
||||
|
||||
services.dino = {
|
||||
description = "auto-start and maintain dino XMPP connection";
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "default.target" ];
|
||||
description = "dino XMPP client";
|
||||
after = [ "graphical-session.target" ];
|
||||
# partOf = [ "graphical-session.target" ];
|
||||
wantedBy = lib.mkIf cfg.config.autostart [ "graphical-session.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/dino";
|
||||
Type = "simple";
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user