Compare commits
762 commits
kunsi-wood
...
main
Author | SHA1 | Date | |
---|---|---|---|
a9b16c18ad | |||
8f705fc8e3 | |||
b3070a8b8b | |||
6a203085b9 | |||
669b28f6ed | |||
9884b703cd | |||
fa63ad72d5 | |||
3a56995ab1 | |||
50b71bc8b8 | |||
fcd097599d | |||
563ba266ff | |||
209dedccf9 | |||
e51c24f837 | |||
72638e0856 | |||
2c83a5c4fc | |||
ec49c8d3ff | |||
46ec4cc2e7 | |||
e29a838fad | |||
f6cb540007 | |||
6eb2c6651b | |||
c006748165 | |||
1be5ab268b | |||
12c735f4aa | |||
9b0e627274 | |||
4f0ced4d9a | |||
af78e959ae | |||
58964cc10f | |||
ec8af84fb1 | |||
9bfb531214 | |||
84867ff1e6 | |||
6647e71484 | |||
2e8cbd6061 | |||
453d2a7889 | |||
4238eeb6d8 | |||
729b975b77 | |||
c4e3d0abc2 | |||
078d52c075 | |||
a83b380490 | |||
ed9607433d | |||
d1b369fb26 | |||
07a44598d2 | |||
e35fbdd183 | |||
c5fb1b8a28 | |||
814b67a9d0 | |||
3ff7db7d6d | |||
b57f205696 | |||
ef8d3368c1 | |||
a5ea87b4e9 | |||
df69b876a9 | |||
4fbbf83952 | |||
a1d1351411 | |||
e2b430fd0e | |||
663f7eec9f | |||
95860e978b | |||
52e891d3a7 | |||
8ba63e112c | |||
67f901c1c9 | |||
8c28d612cb | |||
54f669313a | |||
7b6d811128 | |||
2564f416c2 | |||
8a28886012 | |||
c699f0d510 | |||
4a28bc55c0 | |||
abdc7f751e | |||
423049667f | |||
c6421c7bd4 | |||
95c5b28469 | |||
7dc0afe299 | |||
|
0d28883da3 | ||
|
8980c05c74 | ||
9415b281ce | |||
64fb1906d1 | |||
ce76b03fe7 | |||
a712c098c6 | |||
ec834f2a92 | |||
aa30b78fcf | |||
be3a7a44d6 | |||
2e72f107e9 | |||
07f6fb99f2 | |||
3f9f84f230 | |||
40fcaf56ee | |||
06a94d7cba | |||
6483f863ff | |||
3a52cf55c4 | |||
455c5c5ce5 | |||
5e55dc6fb9 | |||
d5881da154 | |||
|
121a261ecd | ||
|
b9216f230b | ||
|
497d4fff30 | ||
9f1dc01d6b | |||
a7baf225ff | |||
331d363a45 | |||
3f02f7b8f5 | |||
b73ac2b7ce | |||
41b76aec9c | |||
2b5a76ffb0 | |||
319dc8ad21 | |||
bfbbffe22c | |||
23fb2aba1c | |||
3d86923e9e | |||
2fbf122660 | |||
4234070514 | |||
13bae5c993 | |||
aff1329122 | |||
82aeeb585d | |||
e8983829ed | |||
10b1fb8a5b | |||
c66bc8b5eb | |||
422303ee5b | |||
c48e11d787 | |||
a8678fc01b | |||
6fe0598032 | |||
6fb8d81159 | |||
e4eb00bdbe | |||
94e56fd92d | |||
01a8d7a6db | |||
f0ebed5dba | |||
30cf20c28d | |||
5af7b92663 | |||
5a1e37a41c | |||
a1eb9cb3fc | |||
6854bd55ff | |||
|
fb70a068d8 | ||
|
6fa3abc217 | ||
|
7fd248af8d | ||
98d2bb3942 | |||
|
89000c12e6 | ||
fa47322bb0 | |||
de6073bdcf | |||
7649396b8a | |||
b1790ece35 | |||
242279636f | |||
95bb7c52fe | |||
2a8c1ef84b | |||
c1fc942b1d | |||
c4bf96482f | |||
69691f75c5 | |||
263440296d | |||
55a3e6675f | |||
350c436e4d | |||
205fea377a | |||
fb46d81f97 | |||
466a620bca | |||
04094df418 | |||
c348953611 | |||
e4dfd17bb6 | |||
08f2c46c31 | |||
b2028855d1 | |||
a472ca4657 | |||
d08e9f12ab | |||
2fddd57ed8 | |||
5a86e657ff | |||
52b68d6e42 | |||
fbe2197055 | |||
ced6479b8e | |||
6e677a7a0b | |||
c0b3db55ec | |||
fc4aaf4abb | |||
ce44926920 | |||
4736e3b281 | |||
b3ab18a32c | |||
79bb4169a7 | |||
101928339f | |||
67198c5fd9 | |||
791eb8d1a9 | |||
0ce0e34382 | |||
668ae0432b | |||
b72d82b894 | |||
d1f182607d | |||
|
9be31b8850 | ||
|
182cdada22 | ||
|
2c51caa524 | ||
|
263301b265 | ||
|
2f4b90c147 | ||
|
e5c5672554 | ||
c47b412cf3 | |||
cda7e3b7fd | |||
e876d39002 | |||
b9583d9a64 | |||
60a0737187 | |||
52c093427f | |||
|
658acbd12b | ||
56df06e981 | |||
d1e28c3f0c | |||
|
1c2127437c | ||
|
768ae0a37a | ||
bebc603c43 | |||
43fe831395 | |||
5b8784e916 | |||
ea21e4b119 | |||
a6c1d67b55 | |||
a8ef19f4ff | |||
8c42c9411a | |||
1dce906b3d | |||
5c1ff593e1 | |||
fd1cbcfd50 | |||
799f275e4e | |||
cf82ed5dd3 | |||
88fce3405e | |||
a17833698d | |||
c806d7b890 | |||
a8da2aef44 | |||
cc9c127296 | |||
35331f5f4c | |||
dd32ed075b | |||
c9b393c6dc | |||
9e78b9e07b | |||
65af9ae0c5 | |||
516a543719 | |||
dbf17424d2 | |||
09e59af95f | |||
610c1d0978 | |||
0bfcd8df45 | |||
27cb0cb0df | |||
d02d26cb5e | |||
bbc69dfd25 | |||
e64ae3aef7 | |||
1ec545e080 | |||
7491ec840c | |||
a155fe22cb | |||
0f9222424e | |||
6be9fb3614 | |||
ab61444a1f | |||
f8b833720a | |||
33ae4796d4 | |||
8f09170b44 | |||
a6e7359ec0 | |||
128ac48fd6 | |||
4a44ae1048 | |||
ed05a74f56 | |||
896781e53d | |||
c0c83338ad | |||
b028c20758 | |||
efeee3fa62 | |||
139d5ff948 | |||
df8955fa35 | |||
713f7e02d8 | |||
272bccf42d | |||
a3d582c2c5 | |||
cad026c1ef | |||
a027faa8ca | |||
773e8d118f | |||
1d5bcf74c0 | |||
9b4a473236 | |||
aa0d4e5a76 | |||
e6f6229b87 | |||
104d1f11bf | |||
ae14265abc | |||
a4e51c5d54 | |||
6296ab583d | |||
f5b87d995b | |||
abb408c907 | |||
bd0cb5e1b4 | |||
4c5167fefa | |||
a344bde87d | |||
1573bdc384 | |||
4d92211862 | |||
ac10630fb9 | |||
6b387c9d11 | |||
0d362bdb22 | |||
e386b44442 | |||
dd80579fae | |||
faa30962aa | |||
232e087905 | |||
e3d7cae251 | |||
0fa9ef91ae | |||
f5a1a50472 | |||
8d8f457468 | |||
ffc9c1651c | |||
b34879d0ca | |||
32e67ff5ec | |||
409a1c900a | |||
3749be6144 | |||
c5550bf552 | |||
699c7acf93 | |||
79c4dcdf97 | |||
661d8895dc | |||
a045e701a6 | |||
575fe91685 | |||
12c6b5fc54 | |||
4514541e8f | |||
0d0548311c | |||
e73dcf16e3 | |||
decbcf9bfd | |||
304ce8aa54 | |||
b89ba32f4c | |||
7c9bb42c03 | |||
9e59bb044a | |||
9c4d1c94a5 | |||
577a175bd0 | |||
182be4e690 | |||
6bb72f4b27 | |||
7d4624ce62 | |||
02e25f89ff | |||
c6552e8dd2 | |||
781264432a | |||
20b1e5dccc | |||
281696d411 | |||
9df3e5539d | |||
b60fb4ff60 | |||
26ee966bd6 | |||
72f756a686 | |||
898ebe4d6b | |||
012726a2ce | |||
297726f297 | |||
ac7f73588d | |||
8c4611452e | |||
418015b484 | |||
698f203936 | |||
050931edf2 | |||
fa375d0d69 | |||
8f28781572 | |||
2ca460269e | |||
c934bc45aa | |||
e2ed513169 | |||
512454a949 | |||
80ca8b7e50 | |||
8df380357e | |||
dcb9db3639 | |||
c02a1f2a90 | |||
643151c052 | |||
a3cc5a9347 | |||
e3b63a99c2 | |||
980f4cb41a | |||
5ffbe50b1e | |||
bb56f0fb9a | |||
ee58509e93 | |||
57c76e5eba | |||
fa8d05fc74 | |||
8fa488e411 | |||
28d4839822 | |||
ec183da69b | |||
87e30e84fa | |||
44baf7cbf9 | |||
ccfe2ff0b0 | |||
70127f797b | |||
17334a8e3e | |||
edc95ac2ab | |||
58d978292a | |||
739ce09e60 | |||
f917f9a2b7 | |||
|
e9d4c85676 | ||
d5491648f2 | |||
bc63ef97ab | |||
fabe11d5b2 | |||
3bddab5f67 | |||
7c70c600f4 | |||
dfadffd921 | |||
fa107dcc3f | |||
a05a809131 | |||
adba83feea | |||
4889ea4d31 | |||
46e00d6fc8 | |||
a929f24977 | |||
ec1efaafcc | |||
8dde3dba0b | |||
e33cc65cb1 | |||
2e2e8cf7c0 | |||
c5ea690621 | |||
14c01e3bf0 | |||
9be370f8df | |||
b5475df467 | |||
2670d60906 | |||
3ddc75d846 | |||
|
66bb1a80c6 | ||
d9f9690518 | |||
2875bb7160 | |||
8331c04b51 | |||
e7e2fd184f | |||
3b7e14755c | |||
9cf5fa2e5f | |||
005804d839 | |||
41d909f34d | |||
3ea9da16e8 | |||
08628f4721 | |||
2fddfcd4ff | |||
8ca2cfeeb2 | |||
8435b2401f | |||
50bc26deaf | |||
b11fece803 | |||
24373d0ac9 | |||
5b19b2052d | |||
9a026b1fd9 | |||
|
b22ee8aa30 | ||
eb30240dc3 | |||
3cff203bec | |||
2fc8b125e3 | |||
86b8cd8edf | |||
f3269ce979 | |||
cd48cc5911 | |||
2497800f4a | |||
493dc91e0d | |||
63d42c6b42 | |||
ffb5125ddd | |||
0084257872 | |||
4e0f286381 | |||
c8bb51715e | |||
526a0ec64d | |||
9a3134cf46 | |||
4e50bfe1a2 | |||
81bb8653d8 | |||
a21102724a | |||
d364b3c152 | |||
7b646110f9 | |||
308b66c407 | |||
7199371065 | |||
22fb8fc162 | |||
935f68ee97 | |||
1bce530ba1 | |||
48b453ceed | |||
9bde59d7e3 | |||
400b10789a | |||
b454fe4745 | |||
75ef2e7bb9 | |||
d6db192f53 | |||
90ca65eb9f | |||
210f17da53 | |||
6f318f21ae | |||
1ae02ad4ec | |||
c473f730d2 | |||
807024eb98 | |||
529e999e69 | |||
9476771565 | |||
99ca3b6282 | |||
0b155a8a4d | |||
|
60fffd6714 | ||
f9ef74600f | |||
32afd183b1 | |||
74bcebfd05 | |||
01ffa3cc89 | |||
0e03038bdb | |||
ea42188904 | |||
08bf3b6565 | |||
588f1218c2 | |||
7a9401cd6c | |||
ebc59f2843 | |||
3ab970a04a | |||
fb55226ba0 | |||
b712142fd1 | |||
|
34428034dc | ||
3c77ff530d | |||
60a8c70cae | |||
3767825b84 | |||
7cfe098b20 | |||
497ecb5279 | |||
d88645c7bd | |||
ad9a920a48 | |||
cd48cf495d | |||
be62c1270f | |||
b9d4204060 | |||
a09b5b98ca | |||
458606649e | |||
0e40b03060 | |||
53ff288d89 | |||
e27e374983 | |||
d6eb0b4228 | |||
4084e764e4 | |||
361bb6a563 | |||
74baeb4bf4 | |||
787607b5a1 | |||
c2460e5291 | |||
77ed050ade | |||
2d3d0ca02a | |||
6f31d6c0e4 | |||
d999895450 | |||
951d254c7a | |||
07de570175 | |||
e9f3268e15 | |||
3a0ed4a7f5 | |||
d47f7db708 | |||
0d79216ae5 | |||
799cff884b | |||
667fd6a2f0 | |||
4a9596988d | |||
c444722291 | |||
c59a3038a1 | |||
a61a3816ed | |||
a926825b4b | |||
dda3c4162c | |||
c6b01aa219 | |||
8d2daeeb77 | |||
5d69595bbf | |||
b17d7bccf6 | |||
aab7a1abc4 | |||
3bf0e1124e | |||
32141b6e98 | |||
049cc899be | |||
d4f7f1b08d | |||
40a283d5c9 | |||
4f260932c3 | |||
|
15eaa94397 | ||
9bde0d9410 | |||
aaf67f1a3d | |||
234e81431d | |||
e70a86a6c1 | |||
5b1d814d40 | |||
563735d31a | |||
b38bc67a60 | |||
7845faeac3 | |||
5238937044 | |||
5fda0ab464 | |||
|
30604db869 | ||
e7a652503f | |||
54d55bbb8d | |||
40aeeab265 | |||
b38ba55ed3 | |||
1f2266302f | |||
cb6f12b218 | |||
d9cb324bb6 | |||
25a484f04e | |||
f061196f0d | |||
f2b538a168 | |||
711230a472 | |||
b3b305076f | |||
20ff2f40f4 | |||
fe4d4abc9c | |||
a2ceb8cc3a | |||
2b51812118 | |||
6539923644 | |||
4a0aa81e8d | |||
bf6ed289e1 | |||
e6e9e425fc | |||
99e261fe24 | |||
5db3856218 | |||
e029329a03 | |||
8f500b121c | |||
deb0c7b597 | |||
d1bb94fd74 | |||
7df6b1d13a | |||
7b8740601f | |||
7e335cc3ae | |||
9dacd4a14b | |||
e2e5eaa236 | |||
5863105d64 | |||
895f26d2f3 | |||
e087daae94 | |||
0964bd1695 | |||
94bee38ca7 | |||
a33076186b | |||
2d201ebf0e | |||
ad24c0ea5b | |||
0001b5639b | |||
ea77c68e16 | |||
72607adbfe | |||
8cfcefcfc4 | |||
b08c9fb5a4 | |||
fc75e92a78 | |||
194c60ddb2 | |||
59fd245a3f | |||
43d26650b0 | |||
e3784158de | |||
dd8fd452eb | |||
97afd6c522 | |||
a838f6c5bd | |||
b01dcb0ff9 | |||
553ed05ba2 | |||
01531c62de | |||
d450a43a96 | |||
39576fda38 | |||
36dac3be7c | |||
ab3f2df29f | |||
bb478430b9 | |||
ad2312b715 | |||
7dda27b69d | |||
50cba7cb49 | |||
0190555f16 | |||
757e9e6bb8 | |||
c6bb00c124 | |||
8cf2dde6e0 | |||
c6120accc1 | |||
d0302d826a | |||
|
0977dd5042 | ||
|
48d3f8eee6 | ||
bca4d152ea | |||
33d42e2472 | |||
e754b68f06 | |||
bf9b9b4189 | |||
10a9e61026 | |||
daae710624 | |||
8482f6a270 | |||
a8adde8c63 | |||
6aa0114db5 | |||
7a1dc40584 | |||
aecaebcefd | |||
20d1c0af05 | |||
4b6f680248 | |||
8ec785ffd8 | |||
1834bedf91 | |||
726023db17 | |||
5604763303 | |||
5f0ba20622 | |||
d3f55dc821 | |||
b692b09c00 | |||
f1045172fd | |||
88ccd3ca72 | |||
a16fcdd935 | |||
c121110f00 | |||
3826ccf4ec | |||
3a8e3ce01b | |||
92acae3cbe | |||
4b434e7946 | |||
00cbabea1b | |||
80e0a29a31 | |||
9d1fc65b82 | |||
21ec75a398 | |||
3ab8eb88bd | |||
272a11f7d3 | |||
9aacb8f506 | |||
76eef92ee2 | |||
54d0c42da6 | |||
|
f12d19fec6 | ||
e9ee2039d5 | |||
471e2ba6f6 | |||
838b61a2b9 | |||
8d5fe0d926 | |||
6b27128b6d | |||
3936e64227 | |||
bbfa985e1d | |||
d0825a51ee | |||
14ec3c0ee2 | |||
59c913b97c | |||
97307fc6f3 | |||
70bd7d295d | |||
40c90163ad | |||
cff3fe558e | |||
5fa8c72863 | |||
a5677e7d15 | |||
c6b20aea4e | |||
b8600255fc | |||
cba412ecc1 | |||
fa4ea575b4 | |||
|
e9ee11cd08 | ||
d5f5fd853b | |||
dff2bb0289 | |||
c3fe24c7b9 | |||
91b3d2f850 | |||
341a43baf3 | |||
3a2006739c | |||
8968252ba6 | |||
6fb982e94c | |||
2e6e80d1c5 | |||
42e20b122c | |||
|
85b95576c4 | ||
|
d17b146476 | ||
0ca35a2e7e | |||
d360dfb087 | |||
712454c1e3 | |||
5b9ce2faa1 | |||
55f80b468e | |||
c3701da258 | |||
e6111efe2d | |||
|
b8805c6f97 | ||
829ebccad6 | |||
926776fba2 | |||
9fc0004746 | |||
b35bfc85e9 | |||
2607049f8d | |||
6374f6b71e | |||
c44badb1e1 | |||
6a573b3231 | |||
1708f6ae17 | |||
3a5c944926 | |||
b1567443ca | |||
0db4c19457 | |||
b955633a23 | |||
2d433264e7 | |||
32e6e61a3b | |||
cff42ef0f7 | |||
c07b428cc9 | |||
3aedd7395b | |||
048fb83ee7 | |||
92cca7f396 | |||
604170f133 | |||
9b1cea1e1d | |||
a44a3b3024 | |||
768a445e84 | |||
a6f865104c | |||
1260410eae | |||
261c284f2f | |||
6d2cf0fa24 | |||
f8416215d5 | |||
a4bb7f89ec | |||
b68a80c8c3 | |||
a15cc2f121 | |||
1ed9a4ff15 | |||
034047dcd8 | |||
6449797b06 | |||
77930b9a2f | |||
b2ad9ce3d8 | |||
906994b50f | |||
714fa88d72 | |||
556e0d75c8 | |||
f12a176759 | |||
|
83930e12bc | ||
e59aa59124 | |||
87184bc07b | |||
5a594ad308 | |||
9a32534c49 | |||
7b8eb63672 | |||
acc3f3022a | |||
1c42226a42 | |||
ac8c1fd3f3 | |||
d78102adb8 | |||
f2e238d879 | |||
19feb78bf6 | |||
b3e490720e | |||
25aabad865 | |||
c6cf997102 | |||
f17117d640 | |||
5ff46edd8c | |||
4d46401629 | |||
3e497c3545 | |||
95d5c0cfc8 | |||
2297f1dacf | |||
d7d46c2681 | |||
|
e573f42730 | ||
68c4ee9482 | |||
a27ac38bec | |||
4bcf15a64c | |||
9a6be52b05 | |||
60fc0e64e7 | |||
28298d3ce6 | |||
8d3e913a8c | |||
bbbcfee042 | |||
5af85ad535 | |||
a9874ce8fb | |||
83f720d234 | |||
7ff8319f09 | |||
9b11e69a73 | |||
3c921e5d2e | |||
5116ba8a27 | |||
7eb2bf68d8 | |||
fe7d57aca0 | |||
f6da1f6d71 | |||
317a3df11d | |||
fe9716088a | |||
64716d12cf | |||
0522425218 | |||
ee68c9075b | |||
6835793d6a | |||
6c48c25a94 | |||
8ec7f9e992 | |||
f254b9bb12 | |||
ca614efec1 | |||
190833c54a | |||
66c6a92ec5 | |||
a738b49aa4 | |||
08aadcaf36 | |||
51cdcba9e9 | |||
8da5650134 | |||
445ec0ea15 | |||
e3b1d14fe7 | |||
cc49d34475 | |||
b1b8df7dd8 | |||
9c590635b6 | |||
de6579140d | |||
985bb3cdec | |||
5272a212a7 | |||
b1d032df90 | |||
d4e1da0689 |
460 changed files with 8602 additions and 6293 deletions
|
@ -22,3 +22,6 @@ indent_size = unset
|
|||
[*.vault]
|
||||
end_of_line = unset
|
||||
insert_final_newline = unset
|
||||
|
||||
[*.json]
|
||||
insert_final_newline = unset
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
pipeline:
|
||||
install-deps:
|
||||
image: python:3.10-slim
|
||||
commands:
|
||||
- pip install -r requirements.txt
|
||||
|
||||
test-dummymode:
|
||||
image: python:3.10-slim
|
||||
commands:
|
||||
- bw test
|
||||
environment:
|
||||
BW_VAULT_DUMMY_MODE: 1
|
||||
BW_PASS_DUMMY_MODE: 1
|
||||
|
||||
test-ignore-missing-faults:
|
||||
image: python:3.10-slim
|
||||
commands:
|
||||
- bw test --ignore-missing-faults
|
||||
|
||||
test-determinism:
|
||||
image: python:3.10-slim
|
||||
commands:
|
||||
- bw test --metadata-determinism 3 --config-determinism 3
|
||||
environment:
|
||||
BW_VAULT_DUMMY_MODE: 1
|
||||
BW_PASS_DUMMY_MODE: 1
|
|
@ -1,8 +0,0 @@
|
|||
pipeline:
|
||||
editorconfig:
|
||||
image: alpine:latest
|
||||
commands:
|
||||
- wget -O ec-linux-amd64.tar.gz https://github.com/editorconfig-checker/editorconfig-checker/releases/latest/download/ec-linux-amd64.tar.gz
|
||||
- tar -xzf ec-linux-amd64.tar.gz
|
||||
- rm ec-linux-amd64.tar.gz
|
||||
- bin/ec-linux-amd64 -no-color -exclude '^bin/'
|
9
Jenkinsfile
vendored
9
Jenkinsfile
vendored
|
@ -25,15 +25,6 @@ pipeline {
|
|||
"""
|
||||
}
|
||||
}
|
||||
stage('syntax checking using isort') {
|
||||
steps {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
|
||||
isort --check .
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('config and metadata determinism') {
|
||||
steps {
|
||||
sh """
|
||||
|
|
|
@ -30,13 +30,13 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
|||
| 20010 | mautrix-telegram | Bridge |
|
||||
| 20020 | mautrix-whatsapp | Bridge |
|
||||
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
||||
| 20070 | matrix-synapse | sliding-sync |
|
||||
| 20080 | matrix-synapse | client, federation |
|
||||
| 20081 | matrix-synapse | prometheus metrics |
|
||||
| 20090 | matrix-media-repo | media_repo |
|
||||
| 20090 | matrix-media-repo | prometheus metrics |
|
||||
| 21000 | pleroma | pleroma |
|
||||
| 21010 | grafana | grafana |
|
||||
| 22000 | gitea | forgejo |
|
||||
| 22000 | forgejo | forgejo |
|
||||
| 22010 | jenkins-ci | Jenkins CI |
|
||||
| 22020 | travelynx | Travelynx Web |
|
||||
| 22030 | octoprint | OctoPrint Web Interface |
|
||||
|
@ -45,8 +45,9 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
|||
| 22060 | pretalx | gunicorn |
|
||||
| 22070 | paperless-ng | gunicorn |
|
||||
| 22080 | netbox | gunicorn |
|
||||
| 22100 | woodpecker-server | http |
|
||||
| 22101 | woodpecker-server | gRPC |
|
||||
| 22090 | jugendhackt_tools | gunicorn |
|
||||
| 22100 | powerdnsadmin | gunicorn |
|
||||
| 22110 | icinga2-statuspage | gunicorn |
|
||||
| 22999 | nginx | stub_status |
|
||||
| 22100 | ntfy | http |
|
||||
|
||||
|
|
13
README.md
13
README.md
|
@ -7,3 +7,16 @@ onto shared webhosting.
|
|||
|
||||
`bw test` runs according to Jenkinsfile after every commit.
|
||||
[![Build Status](https://jenkins.franzi.business/buildStatus/icon?job=kunsi%2Fbundlewrap%2Fmain)](https://jenkins.franzi.business/job/kunsi/job/bundlewrap/job/main/)
|
||||
|
||||
## automatix
|
||||
|
||||
Ensure you set `bundlewrap: true` in your `~/.automatix.cfg.yaml`.
|
||||
|
||||
## system naming
|
||||
|
||||
All systems should be named after their location and use.
|
||||
|
||||
For example, influxdb hosted at hetzner cloud will be `htz-cloud.influxdb`.
|
||||
|
||||
The only exception to this are name servers, they are named after [demons
|
||||
in fiction](https://en.wikipedia.org/wiki/List_of_demons_in_fiction).
|
||||
|
|
45
automatix/upgrade_debian_bullseye.yaml
Normal file
45
automatix/upgrade_debian_bullseye.yaml
Normal file
|
@ -0,0 +1,45 @@
|
|||
name: Upgrade to debian bullseye
|
||||
systems:
|
||||
node: foonode
|
||||
|
||||
always:
|
||||
- has_zfs=python: NODES.node.has_bundle('zfs')
|
||||
|
||||
pipeline:
|
||||
- manual: "set icinga2 downtime: https://icinga.franzi.business/monitoring/host/schedule-downtime?host={SYSTEMS.node}"
|
||||
|
||||
# apply first so we only see the upgrade changes later
|
||||
- local: bw apply {SYSTEMS.node}
|
||||
- manual: update debian version in node groups
|
||||
- local: "bw apply -o bundle:apt -s symlink:/usr/bin/python pkg_apt: -- {SYSTEMS.node}"
|
||||
|
||||
# double time!
|
||||
- remote@node: DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||
- remote@node: DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||
|
||||
# reboot into bullseye
|
||||
- remote@node: systemctl reboot
|
||||
- local: |
|
||||
exit=1
|
||||
while [[ $exit -ne 0 ]];
|
||||
do
|
||||
sleep 1
|
||||
ssh {SYSTEMS.node} true
|
||||
exit=$?
|
||||
done
|
||||
|
||||
# fix zfs and reboot again
|
||||
- has_zfs?remote@node: zpool import tank -f
|
||||
- has_zfs?remote@node: zpool upgrade -a
|
||||
- has_zfs?remote@node: systemctl reboot
|
||||
- has_zfs?local: |
|
||||
exit=1
|
||||
while [[ $exit -ne 0 ]];
|
||||
do
|
||||
sleep 1
|
||||
ssh {SYSTEMS.node} true
|
||||
exit=$?
|
||||
done
|
||||
|
||||
# final apply
|
||||
- local: bw apply {SYSTEMS.node}
|
9
bundles/apt/files/deb822-sources
Normal file
9
bundles/apt/files/deb822-sources
Normal file
|
@ -0,0 +1,9 @@
|
|||
% for uri in sorted(uris):
|
||||
Types: ${' '.join(sorted(data.get('types', {'deb'})))}
|
||||
URIs: ${uri}
|
||||
Suites: ${os_release}
|
||||
Components: ${' '.join(sorted(data.get('components', {'main'})))}
|
||||
Architectures: ${' '.join(sorted(data.get('architectures', {'amd64'})))}
|
||||
Signed-By: /etc/apt/trusted.gpg.d/${name}.list.asc
|
||||
|
||||
% endfor
|
|
@ -6,10 +6,10 @@ apt-get update
|
|||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoclean
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoremove
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q clean
|
||||
|
||||
% if clean_old_kernels:
|
||||
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
||||
|
||||
|
|
3
bundles/apt/files/sources.list-debian-bookworm
Normal file
3
bundles/apt/files/sources.list-debian-bookworm
Normal file
|
@ -0,0 +1,3 @@
|
|||
deb http://deb.debian.org/debian/ bookworm main non-free contrib non-free-firmware
|
||||
deb http://security.debian.org/debian-security bookworm-security main contrib non-free
|
||||
deb http://deb.debian.org/debian/ bookworm-updates main contrib non-free
|
|
@ -1 +0,0 @@
|
|||
deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi
|
|
@ -19,7 +19,7 @@ statusfile="/var/tmp/unattended_upgrades.status"
|
|||
# Workaround, because /var/tmp is usually 1777
|
||||
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
||||
|
||||
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon"')
|
||||
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon" && $1 != "nobody"')
|
||||
if [[ -n "$logins" ]]
|
||||
then
|
||||
echo "Will abort now, there are active SSH logins: $logins"
|
||||
|
@ -46,10 +46,6 @@ fi
|
|||
|
||||
if [[ -f /var/run/reboot-required ]] && [[ "$auto_reboot_enabled" == "True" ]]
|
||||
then
|
||||
if [[ -n "$reboot_mail_to" ]]
|
||||
then
|
||||
date | mail -s "SYSREBOOTNOW $nodename" "$reboot_mail_to"
|
||||
fi
|
||||
systemctl reboot
|
||||
fi
|
||||
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
nodename="${node.name}"
|
||||
reboot_mail_to="${node.metadata.get('apt/unattended-upgrades/reboot_mail_to', '')}"
|
||||
auto_reboot_enabled="${node.metadata.get('apt/unattended-upgrades/reboot_enabled', True)}"
|
||||
|
|
|
@ -4,11 +4,9 @@ supported_os = {
|
|||
'debian': {
|
||||
10: 'buster',
|
||||
11: 'bullseye',
|
||||
12: 'bookworm',
|
||||
99: 'unstable',
|
||||
},
|
||||
'raspbian': {
|
||||
10: 'buster',
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
|
@ -26,6 +24,10 @@ actions = {
|
|||
'triggered': True,
|
||||
'cascade_skip': False,
|
||||
},
|
||||
'apt_execute_update_commands': {
|
||||
'command': ' && '.join(sorted(node.metadata.get('apt/additional_update_commands', {'true'}))),
|
||||
'triggered': True,
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
|
@ -113,7 +115,7 @@ pkg_apt = {
|
|||
'mtr': {},
|
||||
'ncdu': {},
|
||||
'ncurses-term': {},
|
||||
'netcat': {},
|
||||
'netcat-openbsd': {},
|
||||
'nmap': {},
|
||||
'python3': {},
|
||||
'python3-dev': {},
|
||||
|
@ -152,6 +154,9 @@ pkg_apt = {
|
|||
'popularity-contest': {
|
||||
'installed': False,
|
||||
},
|
||||
'python3-packaging': {
|
||||
'installed': False,
|
||||
},
|
||||
'unattended-upgrades': {
|
||||
'installed': False,
|
||||
},
|
||||
|
@ -168,6 +173,7 @@ if node.os_version[0] >= 11:
|
|||
}
|
||||
|
||||
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||
if 'items' in data:
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||
'content_type': 'mako',
|
||||
'content': ("\n".join(sorted(data['items']))).format(
|
||||
|
@ -178,8 +184,30 @@ for name, data in node.metadata.get('apt/repos', {}).items():
|
|||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
elif 'uris' in data:
|
||||
uris = {
|
||||
x.format(
|
||||
os=node.os,
|
||||
os_release=supported_os[node.os][node.os_version[0]],
|
||||
) for x in data['uris']
|
||||
}
|
||||
|
||||
files['/etc/apt/sources.list.d/{}.sources'.format(name)] = {
|
||||
'source': 'deb822-sources',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'data': data,
|
||||
'name': name,
|
||||
'os_release': supported_os[node.os][node.os_version[0]],
|
||||
'uris': uris,
|
||||
},
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
if data.get('install_gpg_key', True):
|
||||
if 'items' in data:
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||
}
|
||||
|
|
|
@ -21,16 +21,24 @@ defaults = {
|
|||
'cron/jobs/upgrade-and-reboot'
|
||||
)
|
||||
def patchday(metadata):
|
||||
if not node.metadata.get('apt/unattended-upgrades/enabled', True):
|
||||
return {}
|
||||
|
||||
day = metadata.get('apt/unattended-upgrades/day')
|
||||
hour = metadata.get('apt/unattended-upgrades/hour')
|
||||
|
||||
spread = metadata.get('apt/unattended-upgrades/spread_in_group', None)
|
||||
if spread is not None:
|
||||
spread_nodes = sorted(repo.nodes_in_group(spread))
|
||||
day += spread_nodes.index(node)
|
||||
|
||||
return {
|
||||
'cron': {
|
||||
'jobs': {
|
||||
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||
minute=node.magic_number % 30,
|
||||
hour=hour,
|
||||
day=day,
|
||||
day=day%7,
|
||||
),
|
||||
},
|
||||
},
|
||||
|
|
5
bundles/arch-with-gui/files/50-network.conf
Normal file
5
bundles/arch-with-gui/files/50-network.conf
Normal file
|
@ -0,0 +1,5 @@
|
|||
context.exec = [
|
||||
{ path = "pactl" args = "load-module module-native-protocol-tcp" }
|
||||
{ path = "pactl" args = "load-module module-zeroconf-discover" }
|
||||
{ path = "pactl" args = "load-module module-zeroconf-publish" }
|
||||
]
|
|
@ -44,6 +44,11 @@ directories = {
|
|||
}
|
||||
|
||||
svc_systemd = {
|
||||
'avahi-daemon': {
|
||||
'needs': {
|
||||
'pkg_pacman:avahi',
|
||||
},
|
||||
},
|
||||
'sddm': {
|
||||
'needs': {
|
||||
'pkg_pacman:sddm',
|
||||
|
@ -61,6 +66,8 @@ git_deploy = {
|
|||
},
|
||||
}
|
||||
|
||||
files['/etc/pipewire/pipewire-pulse.conf.d/50-network.conf'] = {}
|
||||
|
||||
for filename in listdir(join(repo.path, 'data', 'arch-with-gui', 'files', 'fonts')):
|
||||
if filename.startswith('.'):
|
||||
continue
|
||||
|
|
|
@ -9,6 +9,14 @@ defaults = {
|
|||
'icinga_options': {
|
||||
'exclude_from_monitoring': True,
|
||||
},
|
||||
'nftables': {
|
||||
'input': {
|
||||
'50-avahi': {
|
||||
'udp dport 5353 accept',
|
||||
'udp sport 5353 accept',
|
||||
},
|
||||
},
|
||||
},
|
||||
'pacman': {
|
||||
'packages': {
|
||||
# fonts
|
||||
|
@ -23,8 +31,9 @@ defaults = {
|
|||
'sddm': {},
|
||||
|
||||
# networking
|
||||
'avahi': {},
|
||||
'netctl': {},
|
||||
'rfkill': {},
|
||||
'util-linux': {}, # provides rfkill
|
||||
'wpa_supplicant': {},
|
||||
'wpa_actiond': {},
|
||||
|
||||
|
@ -45,6 +54,7 @@ defaults = {
|
|||
'pipewire': {},
|
||||
'pipewire-jack': {},
|
||||
'pipewire-pulse': {},
|
||||
'pipewire-zeroconf': {},
|
||||
'qpwgraph': {},
|
||||
|
||||
# window management
|
||||
|
|
|
@ -62,10 +62,13 @@ trap "on_exit" EXIT
|
|||
|
||||
# redirect stdout and stderr to logfile
|
||||
prepare_and_cleanup_logdir
|
||||
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
||||
echo "All log output will go to $logfile" | logger -it backup-client
|
||||
exec > >(gzip >"$logfile")
|
||||
exec 2>&1
|
||||
if [[ -z "$DEBUG" ]]
|
||||
then
|
||||
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
||||
echo "All log output will go to $logfile" | logger -it backup-client
|
||||
exec > >(gzip >"$logfile")
|
||||
exec 2>&1
|
||||
fi
|
||||
|
||||
# this is where the real work starts
|
||||
ts_begin=$(date +%s)
|
||||
|
|
|
@ -19,12 +19,12 @@ else:
|
|||
|
||||
if node.metadata.get('backups/exclude_from_backups', False):
|
||||
# make sure nobody tries to do something funny
|
||||
for file in [
|
||||
for file in {
|
||||
'/etc/backup.priv',
|
||||
'/usr/local/bin/generate-backup',
|
||||
'/usr/local/bin/generate-backup-with-retries',
|
||||
'/var/tmp/backup.monitoring', # status file
|
||||
]:
|
||||
}:
|
||||
files[file] = {
|
||||
'delete': True,
|
||||
}
|
||||
|
@ -33,14 +33,17 @@ else:
|
|||
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
||||
|
||||
files['/etc/backup.priv'] = {
|
||||
'content': repo.vault.decrypt_file(join('backup', 'keys', f'{node.name}.key.vault')),
|
||||
'content': repo.libs.ssh.generate_ed25519_private_key(
|
||||
node.metadata.get('backup-client/user-name'),
|
||||
backup_target,
|
||||
),
|
||||
'mode': '0400',
|
||||
}
|
||||
|
||||
files['/usr/local/bin/generate-backup'] = {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'username': node.metadata['backup-client']['user-name'],
|
||||
'username': node.metadata.get('backup-client/user-name'),
|
||||
'server': backup_target.metadata.get('backup-server/my_hostname'),
|
||||
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
||||
'paths': backup_paths,
|
||||
|
|
|
@ -27,9 +27,6 @@ directories['/etc/backup-server/clients'] = {
|
|||
sudoers = {}
|
||||
|
||||
for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
||||
with open(join(repo.path, 'data', 'backup', 'keys', f'{nodename}.pub'), 'r') as f:
|
||||
pubkey = f.read().strip()
|
||||
|
||||
sudoers[config['user']] = nodename
|
||||
|
||||
users[config['user']] = {
|
||||
|
@ -41,7 +38,10 @@ for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
|||
}
|
||||
|
||||
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
|
||||
'content': pubkey,
|
||||
'content': repo.libs.ssh.generate_ed25519_public_key(
|
||||
config['user'],
|
||||
node,
|
||||
),
|
||||
'owner': config['user'],
|
||||
'mode': '0400',
|
||||
'needs': {
|
||||
|
|
|
@ -35,8 +35,15 @@ def get_my_clients(metadata):
|
|||
continue
|
||||
|
||||
my_clients[rnode.name] = {
|
||||
'user': rnode.metadata.get('backup-client/user-name'),
|
||||
'exclude_from_monitoring': rnode.metadata.get(
|
||||
'backup-client/exclude_from_monitoring',
|
||||
rnode.metadata.get(
|
||||
'icinga_options/exclude_from_monitoring',
|
||||
False,
|
||||
),
|
||||
),
|
||||
'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24),
|
||||
'user': rnode.metadata.get('backup-client/user-name'),
|
||||
'retain': {
|
||||
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
||||
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
||||
|
@ -153,7 +160,7 @@ def monitoring(metadata):
|
|||
client,
|
||||
config['one_backup_every_hours'],
|
||||
),
|
||||
'vars.sshmon_timeout': 20,
|
||||
'vars.sshmon_timeout': 40,
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -32,8 +32,8 @@ account_guest_in_cpu_meter=0
|
|||
color_scheme=0
|
||||
enable_mouse=0
|
||||
delay=10
|
||||
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs CPU
|
||||
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs2 CPU
|
||||
left_meter_modes=2 2 2 1 1 1 2
|
||||
right_meters=Hostname CPU RightCPUs
|
||||
right_meters=Hostname CPU RightCPUs2
|
||||
right_meter_modes=2 3 1
|
||||
hide_function_bar=0
|
||||
|
|
|
@ -29,8 +29,19 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
if node.has_any_bundle([
|
||||
'dovecot',
|
||||
'nginx',
|
||||
'postfix',
|
||||
]):
|
||||
actions['generate-dhparam'] = {
|
||||
'command': 'openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048',
|
||||
'unless': 'test -f /etc/ssl/certs/dhparam.pem',
|
||||
}
|
||||
|
||||
|
||||
locale_needs = set()
|
||||
for locale in sorted(node.metadata['locale']['installed']):
|
||||
for locale in sorted(node.metadata.get('locale/installed')):
|
||||
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
||||
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
||||
'unless': f"grep -e '^{locale}' /etc/locale.gen",
|
||||
|
@ -41,17 +52,15 @@ for locale in sorted(node.metadata['locale']['installed']):
|
|||
}
|
||||
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
||||
|
||||
actions = {
|
||||
'locale-gen': {
|
||||
actions['locale-gen'] = {
|
||||
'triggered': True,
|
||||
'command': 'locale-gen',
|
||||
},
|
||||
}
|
||||
|
||||
description = []
|
||||
|
||||
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||
description.append('icingaweb2: https://icinga.kunsmann.eu/monitoring/host/show?host={}'.format(node.name))
|
||||
description.append('icingaweb2: https://icinga.franzi.business/monitoring/host/show?host={}'.format(node.name))
|
||||
|
||||
if node.has_bundle('telegraf'):
|
||||
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
||||
|
|
|
@ -19,7 +19,9 @@ protocol static {
|
|||
ipv4;
|
||||
|
||||
% for route in sorted(node.metadata.get('bird/static_routes', set())):
|
||||
route ${route} via ${node.metadata.get('bird/my_ip')};
|
||||
% for name, config in sorted(node.metadata.get('bird/bgp_neighbors', {}).items()):
|
||||
route ${route} via ${config['local_ip']};
|
||||
% endfor
|
||||
% endfor
|
||||
}
|
||||
% endif
|
||||
|
|
|
@ -24,7 +24,7 @@ defaults = {
|
|||
},
|
||||
'sysctl': {
|
||||
'options': {
|
||||
'net.ipv4.ip_forward': '1',
|
||||
'net.ipv4.conf.all.forwarding': '1',
|
||||
'net.ipv6.conf.all.forwarding': '1',
|
||||
},
|
||||
},
|
||||
|
@ -43,6 +43,9 @@ def neighbor_info_from_wireguard(metadata):
|
|||
except NoSuchNode:
|
||||
continue
|
||||
|
||||
if not rnode.has_bundle('bird'):
|
||||
continue
|
||||
|
||||
neighbors[name] = {
|
||||
'local_ip': config['my_ip'],
|
||||
'local_as': my_as,
|
||||
|
@ -62,7 +65,10 @@ def neighbor_info_from_wireguard(metadata):
|
|||
)
|
||||
def my_ip(metadata):
|
||||
if node.has_bundle('wireguard'):
|
||||
my_ip = sorted(metadata.get('interfaces/wg0/ips'))[0].split('/')[0]
|
||||
wg_ifaces = sorted({iface for iface in metadata.get('interfaces').keys() if iface.startswith('wg_')})
|
||||
if not wg_ifaces:
|
||||
return {}
|
||||
my_ip = sorted(metadata.get(f'interfaces/{wg_ifaces[0]}/ips'))[0].split('/')[0]
|
||||
else:
|
||||
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
||||
|
||||
|
@ -84,7 +90,7 @@ def firewall(metadata):
|
|||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'179': atomic(sources),
|
||||
'179/tcp': atomic(sources),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,19 @@
|
|||
from bundlewrap.exceptions import BundleError
|
||||
|
||||
supported_os = {
|
||||
'debian': {
|
||||
10: 'buster',
|
||||
11: 'bullseye',
|
||||
12: 'bookworm',
|
||||
99: 'unstable',
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
supported_os[node.os][node.os_version[0]]
|
||||
except (KeyError, IndexError):
|
||||
raise BundleError(f'{node.name}: OS {node.os} {node.os_version} is not supported by bundle:apt')
|
||||
|
||||
CONFLICTING_BUNDLES = {
|
||||
'apt',
|
||||
'nginx',
|
||||
|
@ -57,6 +71,18 @@ actions = {
|
|||
'svc_systemd:',
|
||||
},
|
||||
},
|
||||
'apt_update': {
|
||||
'command': 'apt-get update',
|
||||
'needed_by': {
|
||||
'pkg_apt:',
|
||||
},
|
||||
'triggered': True,
|
||||
'cascade_skip': False,
|
||||
},
|
||||
'apt_execute_update_commands': {
|
||||
'command': ' && '.join(sorted(node.metadata.get('apt/additional_update_commands', {'true'}))),
|
||||
'triggered': True,
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
|
@ -92,6 +118,30 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||
'content_type': 'mako',
|
||||
'content': ("\n".join(sorted(data['items']))).format(
|
||||
os=node.os,
|
||||
os_release=supported_os[node.os][node.os_version[0]],
|
||||
),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
if data.get('install_gpg_key', True):
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||
}
|
||||
|
||||
files['/etc/apt/trusted.gpg.d/{}.list.asc'.format(name)] = {
|
||||
'source': 'gpg-keys/{}.asc'.format(name),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
for crontab, content in node.metadata.get('cron/jobs', {}).items():
|
||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||
'source': 'cron_template',
|
||||
|
|
|
@ -17,7 +17,7 @@ files = {
|
|||
directories = {
|
||||
'/etc/cron.d': {
|
||||
'purge': True,
|
||||
'needs': {
|
||||
'after': {
|
||||
'pkg_apt:',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
<%
|
||||
import re
|
||||
from ipaddress import ip_network
|
||||
%>
|
||||
ddns-update-style none;
|
||||
|
||||
authoritative;
|
||||
|
||||
% for interface, subnet in sorted(dhcp_config.get('subnets', {}).items()):
|
||||
<%
|
||||
network = ip_network(subnet['subnet'])
|
||||
%>
|
||||
# interface ${interface} provides ${subnet['subnet']}
|
||||
subnet ${network.network_address} netmask ${network.netmask} {
|
||||
% if subnet.get('range_lower', None) and subnet.get('range_higher', None):
|
||||
range ${subnet['range_lower']} ${subnet['range_higher']};
|
||||
% endif
|
||||
interface "${interface}";
|
||||
default-lease-time ${subnet.get('default-lease-time', 600)};
|
||||
max-lease-time ${subnet.get('max-lease-time', 3600)};
|
||||
% for option, value in sorted(subnet.get('options', {}).items()):
|
||||
% if re.match('([^0-9\.,\ ])', value):
|
||||
option ${option} "${value}";
|
||||
% else:
|
||||
option ${option} ${value};
|
||||
% endif
|
||||
% endfor
|
||||
}
|
||||
% endfor
|
||||
|
||||
% for identifier, allocation in dhcp_config.get('fixed_allocations', {}).items():
|
||||
host ${identifier} {
|
||||
hardware ethernet ${allocation['mac']};
|
||||
fixed-address ${allocation['ipv4']};
|
||||
}
|
||||
% endfor
|
|
@ -1,18 +0,0 @@
|
|||
# Defaults for isc-dhcp-server (sourced by /etc/init.d/isc-dhcp-server)
|
||||
|
||||
# Path to dhcpd's config file (default: /etc/dhcp/dhcpd.conf).
|
||||
#DHCPDv4_CONF=/etc/dhcp/dhcpd.conf
|
||||
#DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf
|
||||
|
||||
# Path to dhcpd's PID file (default: /var/run/dhcpd.pid).
|
||||
#DHCPDv4_PID=/var/run/dhcpd.pid
|
||||
#DHCPDv6_PID=/var/run/dhcpd6.pid
|
||||
|
||||
# Additional options to start dhcpd with.
|
||||
# Don't use options -cf or -pf here; use DHCPD_CONF/ DHCPD_PID instead
|
||||
#OPTIONS=""
|
||||
|
||||
# On what interfaces should the DHCP server (dhcpd) serve DHCP requests?
|
||||
# Separate multiple interfaces with spaces, e.g. "eth0 eth1".
|
||||
INTERFACESv4="${' '.join(sorted(node.metadata.get('dhcpd/subnets', {})))}"
|
||||
INTERFACESv6=""
|
|
@ -1,41 +0,0 @@
|
|||
files = {
|
||||
'/etc/dhcp/dhcpd.conf': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'dhcp_config': node.metadata['dhcpd'],
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:isc-dhcp-server'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:isc-dhcp-server:restart',
|
||||
},
|
||||
},
|
||||
'/etc/default/isc-dhcp-server': {
|
||||
'content_type': 'mako',
|
||||
'needs': {
|
||||
'pkg_apt:isc-dhcp-server'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:isc-dhcp-server:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
# needed for dhcp-lease-list
|
||||
'dhcpd_download_oui.txt': {
|
||||
'command': 'wget http://standards-oui.ieee.org/oui.txt -O /usr/local/etc/oui.txt',
|
||||
'unless': 'test -f /usr/local/etc/oui.txt',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'isc-dhcp-server': {
|
||||
'needs': {
|
||||
'pkg_apt:isc-dhcp-server',
|
||||
'file:/etc/dhcp/dhcpd.conf',
|
||||
'file:/etc/default/isc-dhcp-server',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'isc-dhcp-server': {},
|
||||
},
|
||||
},
|
||||
'bash_aliases': {
|
||||
'leases': 'sudo dhcp-lease-list | tail -n +4 | sort -k 2,2',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'dhcpd/fixed_allocations',
|
||||
)
|
||||
def get_static_allocations(metadata):
|
||||
allocations = {}
|
||||
for rnode in repo.nodes:
|
||||
if rnode.metadata.get('location', '') != metadata.get('location', ''):
|
||||
continue
|
||||
|
||||
for iface_name, iface_config in rnode.metadata.get('interfaces', {}).items():
|
||||
if iface_config.get('dhcp', False):
|
||||
try:
|
||||
allocations[f'{rnode.name}_{iface_name}'] = {
|
||||
'ipv4': sorted(iface_config['ips'])[0],
|
||||
'mac': iface_config['mac'],
|
||||
}
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return {
|
||||
'dhcpd': {
|
||||
'fixed_allocations': allocations,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nftables/rules/10-dhcpd',
|
||||
)
|
||||
def nftables(metadata):
|
||||
rules = set()
|
||||
for iface in node.metadata.get('dhcpd/subnets', {}):
|
||||
rules.add(f'inet filter input udp dport {{ 67, 68 }} iif {iface} accept')
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'rules': {
|
||||
# can't use port_rules here, because we're generating interface based rules.
|
||||
'10-dhcpd': sorted(rules),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
from bundlewrap.metadata import metadata_to_json
|
||||
|
||||
files['/etc/docker/daemon.json'] = {
|
||||
'content': metadata_to_json({
|
||||
'iptables': False,
|
||||
}),
|
||||
'before': {
|
||||
'pkg_apt:docker-ce',
|
||||
'pkg_apt:docker-ce-cli',
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'repos': {
|
||||
'docker': {
|
||||
'items': {
|
||||
'deb https://download.docker.com/linux/debian {os_release} stable',
|
||||
},
|
||||
},
|
||||
},
|
||||
'packages': {
|
||||
'docker-ce': {},
|
||||
'docker-ce-cli': {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nftables/rules/00-docker-ce',
|
||||
)
|
||||
def nftables_nat(metadata):
|
||||
rules = {
|
||||
'inet filter forward ct state { related, established } accept',
|
||||
'inet filter forward iifname docker0 accept',
|
||||
}
|
||||
|
||||
for iface in metadata.get('interfaces'):
|
||||
rules.add(f'nat postrouting oifname {iface} masquerade')
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'rules': {
|
||||
'00-docker-ce': sorted(rules),
|
||||
},
|
||||
},
|
||||
}
|
39
bundles/docker-engine/files/check_docker_container
Normal file
39
bundles/docker-engine/files/check_docker_container
Normal file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from json import loads
|
||||
from subprocess import check_output
|
||||
from sys import argv
|
||||
|
||||
try:
|
||||
container_name = argv[1]
|
||||
|
||||
docker_ps = check_output([
|
||||
'docker',
|
||||
'container',
|
||||
'ls',
|
||||
'--all',
|
||||
'--format',
|
||||
'json',
|
||||
'--filter',
|
||||
f'name={container_name}'
|
||||
])
|
||||
|
||||
containers = loads(f"[{','.join([l for l in docker_ps.decode().splitlines() if l])}]")
|
||||
|
||||
if not containers:
|
||||
print(f'CRITICAL: container {container_name} not found!')
|
||||
exit(2)
|
||||
|
||||
if len(containers) > 1:
|
||||
print(f'Found more than one container matching {container_name}!')
|
||||
print(docker_ps)
|
||||
exit(3)
|
||||
|
||||
if containers[0]['State'] != 'running':
|
||||
print(f'WARNING: container {container_name} not "running"')
|
||||
exit(2)
|
||||
|
||||
print(f"OK: {containers[0]['Status']}")
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
exit(2)
|
50
bundles/docker-engine/files/docker-wrapper
Normal file
50
bundles/docker-engine/files/docker-wrapper
Normal file
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash
|
||||
|
||||
[[ -n "$DEBUG" ]] && set -x
|
||||
|
||||
ACTION="$1"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "$ACTION" ]]
|
||||
then
|
||||
echo "Usage: $0 start|stop"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PUID="$(id -u "docker-${name}")"
|
||||
PGID="$(id -g "docker-${name}")"
|
||||
|
||||
if [ "$ACTION" == "start" ]
|
||||
then
|
||||
docker run -d \
|
||||
--name "${name}" \
|
||||
--env "PUID=$PUID" \
|
||||
--env "PGID=$PGID" \
|
||||
--env "TZ=${timezone}" \
|
||||
% for k, v in sorted(environment.items()):
|
||||
--env "${k}=${v}" \
|
||||
% endfor
|
||||
--network host \
|
||||
% for host_port, container_port in sorted(ports.items()):
|
||||
--expose "127.0.0.1:${host_port}:${container_port}" \
|
||||
% endfor
|
||||
% for host_path, container_path in sorted(volumes.items()):
|
||||
--volume "/var/opt/docker-engine/${name}/${host_path}:${container_path}" \
|
||||
% endfor
|
||||
--restart unless-stopped \
|
||||
"${image}"
|
||||
|
||||
elif [ "$ACTION" == "stop" ]
|
||||
then
|
||||
docker stop "${name}"
|
||||
docker rm "${name}"
|
||||
|
||||
else
|
||||
echo "Unknown action $ACTION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
% if node.has_bundle('nftables'):
|
||||
systemctl reload nftables
|
||||
% endif
|
14
bundles/docker-engine/files/docker-wrapper.service
Normal file
14
bundles/docker-engine/files/docker-wrapper.service
Normal file
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=docker-engine app ${name}
|
||||
After=network.target
|
||||
Requires=${' '.join(sorted(requires))}
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/opt/docker-engine/${name}/
|
||||
ExecStart=/opt/docker-engine/${name} start
|
||||
ExecStop=/opt/docker-engine/${name} stop
|
||||
Type=simple
|
||||
RemainAfterExit=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
99
bundles/docker-engine/items.py
Normal file
99
bundles/docker-engine/items.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
from bundlewrap.metadata import metadata_to_json
|
||||
|
||||
deps = {
|
||||
'pkg_apt:docker-ce',
|
||||
'pkg_apt:docker-ce-cli',
|
||||
}
|
||||
|
||||
directories['/opt/docker-engine'] = {
|
||||
'purge': True,
|
||||
}
|
||||
directories['/var/opt/docker-engine'] = {}
|
||||
|
||||
files['/etc/docker/daemon.json'] = {
|
||||
'content': metadata_to_json(node.metadata.get('docker-engine/config')),
|
||||
'triggers': {
|
||||
'svc_systemd:docker:restart',
|
||||
},
|
||||
# install config before installing packages to ensure the config is
|
||||
# applied to the first start as well
|
||||
'before': deps,
|
||||
}
|
||||
|
||||
svc_systemd['docker'] = {
|
||||
'needs': deps,
|
||||
}
|
||||
|
||||
files['/usr/local/share/icinga/plugins/check_docker_container'] = {
|
||||
'mode': '0755',
|
||||
}
|
||||
|
||||
for app, config in node.metadata.get('docker-engine/containers', {}).items():
|
||||
volumes = config.get('volumes', {})
|
||||
|
||||
files[f'/opt/docker-engine/{app}'] = {
|
||||
'source': 'docker-wrapper',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'environment': config.get('environment', {}),
|
||||
'image': config['image'],
|
||||
'name': app,
|
||||
'ports': config.get('ports', {}),
|
||||
'timezone': node.metadata.get('timezone'),
|
||||
'volumes': volumes,
|
||||
},
|
||||
'mode': '0755',
|
||||
'triggers': {
|
||||
f'svc_systemd:docker-{app}:restart',
|
||||
},
|
||||
}
|
||||
|
||||
users[f'docker-{app}'] = {
|
||||
'home': f'/var/opt/docker-engine/{app}',
|
||||
'groups': {
|
||||
'docker',
|
||||
},
|
||||
'after': {
|
||||
# provides docker group
|
||||
'pkg_apt:docker-ce',
|
||||
},
|
||||
}
|
||||
|
||||
files[f'/usr/local/lib/systemd/system/docker-{app}.service'] = {
|
||||
'source': 'docker-wrapper.service',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'name': app,
|
||||
'requires': {
|
||||
*set(config.get('requires', set())),
|
||||
'docker.service',
|
||||
}
|
||||
},
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
f'svc_systemd:docker-{app}:restart',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd[f'docker-{app}'] = {
|
||||
'needs': {
|
||||
*deps,
|
||||
f'file:/opt/docker-engine/{app}',
|
||||
f'file:/usr/local/lib/systemd/system/docker-{app}.service',
|
||||
f'user:docker-{app}',
|
||||
'svc_systemd:docker',
|
||||
*set(config.get('needs', set())),
|
||||
},
|
||||
}
|
||||
|
||||
for volume in volumes:
|
||||
directories[f'/var/opt/docker-engine/{app}/{volume}'] = {
|
||||
'owner': f'docker-{app}',
|
||||
'group': f'docker-{app}',
|
||||
'needed_by': {
|
||||
f'svc_systemd:docker-{app}',
|
||||
},
|
||||
# don't do anything if the directory exists, docker images
|
||||
# mangle owners
|
||||
'unless': f'test -d /var/opt/docker-engine/{app}/{volume}',
|
||||
}
|
83
bundles/docker-engine/metadata.py
Normal file
83
bundles/docker-engine/metadata.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'docker-ce': {},
|
||||
'docker-ce-cli': {},
|
||||
'docker-compose-plugin': {},
|
||||
},
|
||||
'repos': {
|
||||
'docker': {
|
||||
'items': {
|
||||
'deb https://download.docker.com/linux/debian {os_release} stable',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'backups': {
|
||||
'paths': {
|
||||
'/var/opt/docker-engine',
|
||||
},
|
||||
},
|
||||
'hosts': {
|
||||
'entries': {
|
||||
'172.17.0.1': {
|
||||
'host.docker.internal',
|
||||
},
|
||||
},
|
||||
},
|
||||
'docker-engine': {
|
||||
'config': {
|
||||
'iptables': False,
|
||||
'no-new-privileges': True,
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/docker-data': {
|
||||
'mountpoint': '/var/opt/docker-engine',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/docker-engine/services',
|
||||
)
|
||||
def monitoring(metadata):
|
||||
services = {
|
||||
'DOCKER PROCESS': {
|
||||
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C dockerd -c 1:',
|
||||
},
|
||||
}
|
||||
|
||||
for app in metadata.get('docker-engine/containers', {}):
|
||||
services[f'DOCKER CONTAINER {app}'] = {
|
||||
'command_on_monitored_host': f'sudo /usr/local/share/icinga/plugins/check_docker_container {app}'
|
||||
}
|
||||
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'docker-engine': {
|
||||
'services': services,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'zfs/datasets',
|
||||
)
|
||||
def zfs(metadata):
|
||||
datasets = {}
|
||||
|
||||
for app in metadata.get('docker-engine/containers', {}):
|
||||
datasets[f'tank/docker-data/{app}'] = {
|
||||
'mountpoint': f'/var/opt/docker-engine/{app}'
|
||||
}
|
||||
|
||||
return {
|
||||
'zfs': {
|
||||
'datasets': datasets,
|
||||
},
|
||||
}
|
64
bundles/docker-immich/metadata.py
Normal file
64
bundles/docker-immich/metadata.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
assert node.has_bundle('docker-engine')
|
||||
assert node.has_bundle('redis')
|
||||
assert not node.has_bundle('postgresql') # docker container uses that port
|
||||
|
||||
defaults = {
|
||||
'docker-engine': {
|
||||
'containers': {
|
||||
'immich': {
|
||||
'image': 'ghcr.io/imagegenius/immich:latest',
|
||||
'environment': {
|
||||
'DB_DATABASE_NAME': 'immich',
|
||||
'DB_HOSTNAME': 'host.docker.internal',
|
||||
'DB_PASSWORD': repo.vault.password_for(f'{node.name} postgresql immich'),
|
||||
'DB_USERNAME': 'immich',
|
||||
'REDIS_HOSTNAME': 'host.docker.internal',
|
||||
},
|
||||
'volumes': {
|
||||
'config': '/config',
|
||||
'libraries': '/libraries',
|
||||
'photos': '/photos',
|
||||
},
|
||||
'needs': {
|
||||
'svc_systemd:docker-postgresql14',
|
||||
},
|
||||
'requires': {
|
||||
'docker-postgresql14.service',
|
||||
},
|
||||
},
|
||||
'postgresql14': {
|
||||
'image': 'tensorchord/pgvecto-rs:pg14-v0.2.0',
|
||||
'environment': {
|
||||
'POSTGRES_PASSWORD': repo.vault.password_for(f'{node.name} postgresql immich'),
|
||||
'POSTGRES_USER': 'immich',
|
||||
'POSTGRES_DB': 'immich',
|
||||
},
|
||||
'volumes': {
|
||||
'database': '/var/lib/postgresql/data',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'immich': {
|
||||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:8080/',
|
||||
'websockets': True,
|
||||
'max_body_size': '500m',
|
||||
},
|
||||
#'/api/socket.io/': {
|
||||
# 'target': 'http://127.0.0.1:8081/',
|
||||
# 'websockets': True,
|
||||
#},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'redis': {
|
||||
'bind': '0.0.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -3,3 +3,4 @@ driver = pgsql
|
|||
default_pass_scheme = MD5-CRYPT
|
||||
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = true
|
||||
user_query = SELECT '/var/mail/vmail/' || maildir as home, 65534 as uid, 65534 as gid FROM mailbox WHERE username = '%u' AND active = true
|
||||
iterate_query = SELECT username as user FROM mailbox WHERE active = true
|
||||
|
|
|
@ -28,19 +28,19 @@ namespace inbox {
|
|||
mail_location = maildir:/var/mail/vmail/%d/%n
|
||||
protocols = imap lmtp sieve
|
||||
|
||||
ssl = yes
|
||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/fullchain.pem
|
||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/privkey.pem
|
||||
ssl_dh = </etc/dovecot/ssl/dhparam.pem
|
||||
ssl = required
|
||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/fullchain.pem
|
||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/privkey.pem
|
||||
ssl_dh = </etc/ssl/certs/dhparam.pem
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
|
||||
ssl_prefer_server_ciphers = yes
|
||||
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305
|
||||
ssl_prefer_server_ciphers = no
|
||||
|
||||
login_greeting = IMAPd ready
|
||||
auth_mechanisms = plain login
|
||||
first_valid_uid = 65534
|
||||
disable_plaintext_auth = yes
|
||||
mail_plugins = $mail_plugins zlib old_stats
|
||||
mail_plugins = $mail_plugins zlib old_stats fts fts_xapian
|
||||
|
||||
plugin {
|
||||
zlib_save_level = 6
|
||||
|
@ -56,6 +56,15 @@ plugin {
|
|||
old_stats_refresh = 30 secs
|
||||
old_stats_track_cmds = yes
|
||||
|
||||
fts = xapian
|
||||
fts_xapian = partial=3 full=20
|
||||
|
||||
fts_autoindex = yes
|
||||
fts_enforced = yes
|
||||
|
||||
# Index attachements
|
||||
fts_decoder = decode2text
|
||||
|
||||
% if node.has_bundle('rspamd'):
|
||||
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
||||
|
||||
|
@ -86,14 +95,19 @@ service auth {
|
|||
}
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
service decode2text {
|
||||
executable = script /usr/lib/dovecot/decode2text.sh
|
||||
user = dovecot
|
||||
unix_listener decode2text {
|
||||
mode = 0666
|
||||
}
|
||||
}
|
||||
|
||||
service indexer-worker {
|
||||
vsz_limit = 0
|
||||
process_limit = 0
|
||||
}
|
||||
|
||||
service imap {
|
||||
executable = imap
|
||||
}
|
||||
|
@ -104,6 +118,14 @@ service imap-login {
|
|||
vsz_limit = 64M
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
# by this bundle
|
||||
repo.libs.tools.require_bundle(node, 'postfix')
|
||||
|
||||
directories = {
|
||||
'/etc/dovecot/ssl': {},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/dovecot/dovecot.conf': {
|
||||
'content_type': 'mako',
|
||||
|
@ -49,25 +45,17 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
'dovecot_generate_dhparam': {
|
||||
'command': 'openssl dhparam -out /etc/dovecot/ssl/dhparam.pem 2048',
|
||||
'unless': 'test -f /etc/dovecot/ssl/dhparam.pem',
|
||||
'cascade_skip': False,
|
||||
'needs': {
|
||||
'directory:/etc/dovecot/ssl',
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
symlinks['/usr/lib/dovecot/decode2text.sh'] = {
|
||||
'target': '/usr/share/doc/dovecot-core/examples/decode2text.sh',
|
||||
'before': {
|
||||
'svc_systemd:dovecot',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'dovecot': {
|
||||
'needs': {
|
||||
'action:dovecot_generate_dhparam',
|
||||
'action:generate-dhparam',
|
||||
'file:/etc/dovecot/dovecot.conf',
|
||||
'file:/etc/dovecot/dovecot-sql.conf',
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ from bundlewrap.metadata import atomic
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'dovecot-fts-xapian': {},
|
||||
'dovecot-imapd': {},
|
||||
'dovecot-lmtpd': {},
|
||||
'dovecot-managesieved': {},
|
||||
|
@ -35,6 +36,16 @@ defaults = {
|
|||
'dovecot',
|
||||
},
|
||||
},
|
||||
'systemd-timers': {
|
||||
'timers': {
|
||||
'dovecot_fts_optimize': {
|
||||
'command': [
|
||||
'/usr/bin/doveadm fts optimize -A',
|
||||
],
|
||||
'when': '02:{}:00'.format(node.magic_number % 60),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.has_bundle('postfixadmin'):
|
||||
|
@ -76,19 +87,19 @@ def import_database_settings_from_postfixadmin(metadata):
|
|||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules/143',
|
||||
'firewall/port_rules/993',
|
||||
'firewall/port_rules/4190',
|
||||
'firewall/port_rules',
|
||||
'firewall/port_rules',
|
||||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
# imap(s)
|
||||
'143': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
'993': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
'143/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
'993/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
# managesieve
|
||||
'4190': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
'4190/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ directories = {
|
|||
|
||||
git_deploy = {
|
||||
'/opt/element-web': {
|
||||
'rev': node.metadata['element-web']['version'],
|
||||
'rev': node.metadata.get('element-web/version'),
|
||||
'repo': 'https://github.com/vector-im/element-web.git',
|
||||
'triggers': {
|
||||
'action:element-web_yarn',
|
||||
|
@ -18,28 +18,22 @@ git_deploy = {
|
|||
|
||||
files = {
|
||||
'/opt/element-web/webapp/config.json': {
|
||||
'content': metadata_to_json(node.metadata['element-web']['config']),
|
||||
'content': metadata_to_json(node.metadata.get('element-web/config')),
|
||||
'needs': {
|
||||
'action:element-web_yarn',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
extra_install_cmds = []
|
||||
if node.metadata.get('nodejs/version') >= 17:
|
||||
# TODO verify this is still needed when upgrading to 1.12
|
||||
extra_install_cmds.append('export NODE_OPTIONS=--openssl-legacy-provider')
|
||||
|
||||
actions = {
|
||||
'element-web_yarn': {
|
||||
'command': ' && '.join([
|
||||
*extra_install_cmds,
|
||||
'cd /opt/element-web',
|
||||
'yarn install --pure-lockfile --ignore-scripts',
|
||||
'yarn build',
|
||||
]),
|
||||
'needs': {
|
||||
'action:nodejs_install_yarn',
|
||||
'action:apt_execute_update_commands',
|
||||
'pkg_apt:nodejs',
|
||||
},
|
||||
'triggered': True,
|
||||
|
|
|
@ -11,6 +11,26 @@ defaults = {
|
|||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nodejs/version',
|
||||
)
|
||||
def nodejs(metadata):
|
||||
version = tuple([int(i) for i in metadata.get('element-web/version')[1:].split('.')])
|
||||
|
||||
if version >= (1, 11, 71):
|
||||
return {
|
||||
'nodejs': {
|
||||
'version': 20,
|
||||
},
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'nodejs': {
|
||||
'version': 18,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/element-web',
|
||||
)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
APP_NAME = ${app_name}
|
||||
RUN_USER = git
|
||||
RUN_MODE = prod
|
||||
WORK_PATH = /var/lib/forgejo
|
||||
|
||||
[repository]
|
||||
ROOT = /home/git/gitea-repositories
|
||||
ROOT = /var/lib/forgejo/repositories
|
||||
MAX_CREATION_LIMIT = 0
|
||||
DEFAULT_BRANCH = main
|
||||
|
|
@ -5,14 +5,13 @@ After=network.target
|
|||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
RestartSec=2s
|
||||
RestartSec=10
|
||||
Type=simple
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/gitea/
|
||||
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini
|
||||
WorkingDirectory=/var/lib/forgejo
|
||||
ExecStart=/usr/local/bin/forgejo web -c /etc/forgejo/app.ini
|
||||
Restart=always
|
||||
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
65
bundles/forgejo/items.py
Normal file
65
bundles/forgejo/items.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
users = {
|
||||
'git': {
|
||||
'home': '/var/lib/forgejo',
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/var/lib/forgejo/.ssh': {
|
||||
'mode': '0700',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
},
|
||||
'/var/lib/forgejo': {
|
||||
'owner': 'git',
|
||||
'mode': '0700',
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/usr/local/lib/systemd/system/forgejo.service': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('forgejo'),
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
'/etc/forgejo/app.ini': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('forgejo'),
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
'/usr/local/bin/forgejo': {
|
||||
'content_type': 'download',
|
||||
'source': 'https://codeberg.org/forgejo/forgejo/releases/download/v{0}/forgejo-{0}-linux-amd64'.format(node.metadata.get('forgejo/version')),
|
||||
'content_hash': node.metadata.get('forgejo/sha1', None),
|
||||
'mode': '0755',
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.metadata.get('forgejo/install_ssh_key', False):
|
||||
files['/var/lib/forgejo/.ssh/id_ed25519'] = {
|
||||
'content': repo.vault.decrypt_file(f'forgejo/files/ssh-keys/{node.name}.key.vault'),
|
||||
'mode': '0600',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'forgejo': {
|
||||
'needs': {
|
||||
'file:/etc/forgejo/app.ini',
|
||||
'file:/usr/local/bin/forgejo',
|
||||
'file:/usr/local/lib/systemd/system/forgejo.service',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,33 +1,31 @@
|
|||
defaults = {
|
||||
'backups': {
|
||||
'paths': {
|
||||
'/home/git',
|
||||
'/var/lib/gitea',
|
||||
'/var/lib/forgejo',
|
||||
},
|
||||
},
|
||||
'gitea': {
|
||||
'forgejo': {
|
||||
'app_name': 'Forgejo',
|
||||
'database': {
|
||||
'username': 'gitea',
|
||||
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
||||
'database': 'gitea',
|
||||
'username': 'forgejo',
|
||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||
'database': 'forgejo',
|
||||
},
|
||||
'disable_registration': True,
|
||||
'email_domain_blocklist': set(),
|
||||
'enable_git_hooks': False,
|
||||
'internal_token': repo.vault.password_for('{} gitea internal_token'.format(node.name)),
|
||||
'lfs_secret_key': repo.vault.password_for('{} gitea lfs_secret_key'.format(node.name)),
|
||||
'oauth_secret_key': repo.vault.password_for('{} gitea oauth_secret_key'.format(node.name)),
|
||||
'security_secret_key': repo.vault.password_for('{} gitea security_secret_key'.format(node.name)),
|
||||
'internal_token': repo.vault.password_for('{} forgejo internal_token'.format(node.name)),
|
||||
'lfs_secret_key': repo.vault.password_for('{} forgejo lfs_secret_key'.format(node.name)),
|
||||
'oauth_secret_key': repo.vault.password_for('{} forgejo oauth_secret_key'.format(node.name)),
|
||||
'security_secret_key': repo.vault.password_for('{} forgejo security_secret_key'.format(node.name)),
|
||||
},
|
||||
'icinga2_api': {
|
||||
'gitea': {
|
||||
'forgejo': {
|
||||
'services': {
|
||||
'FORGEJO PROCESS': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit gitea',
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit forgejo',
|
||||
},
|
||||
'FORGEJO UPDATE': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_forgejo_for_new_release codeberg.org forgejo/forgejo v$(gitea --version | cut -d" " -f3)',
|
||||
'vars.notification.mail': True,
|
||||
'check_interval': '60m',
|
||||
},
|
||||
|
@ -41,29 +39,22 @@ defaults = {
|
|||
},
|
||||
'postgresql': {
|
||||
'roles': {
|
||||
'gitea': {
|
||||
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
||||
'forgejo': {
|
||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||
},
|
||||
},
|
||||
'databases': {
|
||||
'gitea': {
|
||||
'owner': 'gitea',
|
||||
'forgejo': {
|
||||
'owner': 'forgejo',
|
||||
},
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/gitea': {},
|
||||
'tank/gitea/home': {
|
||||
'mountpoint': '/home/git',
|
||||
'tank/forgejo': {
|
||||
'mountpoint': '/var/lib/forgejo',
|
||||
'needed_by': {
|
||||
'directory:/home/git',
|
||||
},
|
||||
},
|
||||
'tank/gitea/var': {
|
||||
'mountpoint': '/var/lib/gitea',
|
||||
'needed_by': {
|
||||
'directory:/var/lib/gitea',
|
||||
'directory:/var/lib/forgejo',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -71,6 +62,23 @@ defaults = {
|
|||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/forgejo',
|
||||
)
|
||||
def update_monitoring(metadata):
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'forgejo': {
|
||||
'services': {
|
||||
'FORGEJO UPDATE': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_forgejo_for_new_release codeberg.org forgejo/forgejo v{}'.format(metadata.get('forgejo/version')),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/forgejo',
|
||||
)
|
||||
|
@ -82,7 +90,7 @@ def nginx(metadata):
|
|||
'nginx': {
|
||||
'vhosts': {
|
||||
'forgejo': {
|
||||
'domain': metadata.get('gitea/domain'),
|
||||
'domain': metadata.get('forgejo/domain'),
|
||||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:22000',
|
||||
|
@ -92,16 +100,8 @@ def nginx(metadata):
|
|||
},
|
||||
},
|
||||
'website_check_path': '/user/login',
|
||||
'website_check_string': 'Sign In',
|
||||
'website_check_string': 'Sign in',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/gitea/services',
|
||||
)
|
||||
def icinga_check_for_new_release(metadata):
|
||||
return {
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
svc_systemd = {}
|
||||
pkg_apt = {}
|
||||
|
||||
for i in {
|
||||
'gce-disk-expand',
|
||||
'google-cloud-packages-archive-keyring',
|
||||
'google-cloud-sdk',
|
||||
'google-compute-engine',
|
||||
'google-compute-engine-oslogin',
|
||||
'google-guest-agent',
|
||||
'google-osconfig-agent',
|
||||
}:
|
||||
pkg_apt[i] = {
|
||||
'installed': False,
|
||||
}
|
||||
|
||||
for i in {
|
||||
'google-accounts-daemon.service',
|
||||
'google-accounts-manager.service',
|
||||
'google-clock-skew-daemon.service',
|
||||
'google-clock-sync-manager.service',
|
||||
'google-guest-agent.service',
|
||||
'google-osconfig-agent.service',
|
||||
'google-shutdown-scripts.service',
|
||||
'google-startup-scripts.service',
|
||||
'sshguard.service',
|
||||
|
||||
'google-oslogin-cache.timer',
|
||||
}:
|
||||
svc_systemd[i] = {
|
||||
'enabled': False,
|
||||
'running': False,
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
users = {
|
||||
'git': {},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/home/git': {
|
||||
'mode': '0755',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
},
|
||||
'/home/git/.ssh': {
|
||||
'mode': '0755',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
},
|
||||
'/var/lib/gitea': {
|
||||
'owner': 'git',
|
||||
'mode': '0700',
|
||||
'triggers': {
|
||||
'svc_systemd:gitea:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/systemd/system/gitea.service': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('gitea'),
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:gitea:restart',
|
||||
},
|
||||
},
|
||||
'/etc/gitea/app.ini': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('gitea'),
|
||||
'triggers': {
|
||||
'svc_systemd:gitea:restart',
|
||||
},
|
||||
},
|
||||
'/usr/local/bin/gitea': {
|
||||
'content_type': 'download',
|
||||
'source': node.metadata.get('gitea/url'),
|
||||
'content_hash': node.metadata.get('gitea/sha1', None),
|
||||
'mode': '0755',
|
||||
'triggers': {
|
||||
'svc_systemd:gitea:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.metadata['gitea'].get('install_ssh_key', False):
|
||||
files['/home/git/.ssh/id_ed25519'] = {
|
||||
'content': repo.vault.decrypt_file(f'gitea/files/ssh-keys/{node.name}.key.vault'),
|
||||
'mode': '0600',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'gitea': {
|
||||
'needs': {
|
||||
'file:/etc/gitea/app.ini',
|
||||
'file:/etc/systemd/system/gitea.service',
|
||||
'file:/usr/local/bin/gitea',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -47,7 +47,7 @@ def dashboard_row_smartd(panel_id, node):
|
|||
'renderer': 'flot',
|
||||
'seriesOverrides': [],
|
||||
'spaceLength': 10,
|
||||
'span': 8,
|
||||
'span': 12,
|
||||
'stack': False,
|
||||
'steppedLine': False,
|
||||
'targets': [
|
||||
|
@ -114,115 +114,5 @@ def dashboard_row_smartd(panel_id, node):
|
|||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.device}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 0,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'hideEmpty': True,
|
||||
'hideZero': True,
|
||||
'max': False,
|
||||
'min': False,
|
||||
'rightSide': False,
|
||||
'show': True,
|
||||
'total': False,
|
||||
'values': False
|
||||
},
|
||||
'lines': True,
|
||||
'linewidth': 1,
|
||||
'NonePointMode': 'None',
|
||||
'options': {
|
||||
'alertThreshold': True
|
||||
},
|
||||
'percentage': False,
|
||||
'pluginVersion': '7.5.5',
|
||||
'pointradius': 2,
|
||||
'points': False,
|
||||
'renderer': 'flot',
|
||||
'seriesOverrides': [],
|
||||
'spaceLength': 10,
|
||||
'span': 4,
|
||||
'stack': False,
|
||||
'steppedLine': False,
|
||||
'targets': [
|
||||
{
|
||||
'groupBy': [
|
||||
{'type': 'time', 'params': ['$__interval']},
|
||||
{'type': 'fill', 'params': ['linear']},
|
||||
],
|
||||
'orderByTime': "ASC",
|
||||
'policy': "default",
|
||||
'query': f"""from(bucket: "telegraf")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) =>
|
||||
r["_measurement"] == "smartd_stats" and
|
||||
r["_field"] == "power_on_hours" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "fan")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'fans',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'hours',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ def nginx(metadata):
|
|||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:21010',
|
||||
'websockets': True,
|
||||
},
|
||||
'/api/ds/query': {
|
||||
'target': 'http://127.0.0.1:21010',
|
||||
|
|
|
@ -33,7 +33,11 @@ ProtectSystem=strict
|
|||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
SystemCallArchitectures=native
|
||||
SystemCallFilter=@system-service
|
||||
# FIXME
|
||||
# causes problems on bookworm
|
||||
# see https://github.com/hedgedoc/hedgedoc/issues/4686
|
||||
# cmmented out for now ...
|
||||
#SystemCallFilter=@system-service
|
||||
|
||||
# You may have to adjust these settings
|
||||
User=hedgedoc
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from semver import compare
|
||||
|
||||
repo.libs.tools.require_bundle(node, 'nodejs')
|
||||
|
||||
git_deploy = {
|
||||
|
@ -47,16 +49,29 @@ directories = {
|
|||
},
|
||||
}
|
||||
|
||||
if compare(node.metadata.get('hedgedoc/version'), '1.9.7') <= 0:
|
||||
command = ' && '.join([
|
||||
'cd /opt/hedgedoc',
|
||||
'yarn workspaces focus --production',
|
||||
'yarn install --ignore-scripts',
|
||||
'yarn build',
|
||||
])
|
||||
elif compare(node.metadata.get('hedgedoc/version'), '1.9.9') >= 0:
|
||||
command = ' && '.join([
|
||||
'cd /opt/hedgedoc',
|
||||
'bin/setup',
|
||||
'yarn install --immutable',
|
||||
'yarn build',
|
||||
])
|
||||
|
||||
actions = {
|
||||
'hedgedoc_yarn': {
|
||||
'command': ' && '.join([
|
||||
'cd /opt/hedgedoc',
|
||||
'yarn install --production=true --pure-lockfile --ignore-scripts',
|
||||
'yarn install --ignore-scripts',
|
||||
'yarn install --immutable',
|
||||
'yarn build',
|
||||
]),
|
||||
'needs': {
|
||||
'action:nodejs_install_yarn',
|
||||
'file:/opt/hedgedoc/config.json',
|
||||
'git_deploy:/opt/hedgedoc',
|
||||
'pkg_apt:nodejs',
|
||||
|
|
|
@ -2,48 +2,42 @@
|
|||
|
||||
from sys import exit
|
||||
|
||||
import requests
|
||||
from packaging import version
|
||||
from packaging.version import parse
|
||||
from requests import get
|
||||
|
||||
bearer = "${bearer}"
|
||||
domain = "${domain}"
|
||||
OK = 0
|
||||
WARN = 1
|
||||
CRITICAL = 2
|
||||
UNKNOWN = 3
|
||||
|
||||
status = 3
|
||||
message = "Unknown Update Status"
|
||||
|
||||
|
||||
domain = "hass.home.kunbox.net"
|
||||
|
||||
s = requests.Session()
|
||||
s.headers.update({"Content-Type": "application/json"})
|
||||
API_TOKEN = "${token}"
|
||||
DOMAIN = "${domain}"
|
||||
|
||||
try:
|
||||
stable_version = version.parse(
|
||||
s.get("https://version.home-assistant.io/stable.json").json()["homeassistant"][
|
||||
"generic-x86-64"
|
||||
]
|
||||
)
|
||||
s.headers.update(
|
||||
{"Authorization": f"Bearer {bearer}", "Content-Type": "application/json"}
|
||||
)
|
||||
running_version = version.parse(
|
||||
s.get(f"https://{domain}/api/config").json()["version"]
|
||||
)
|
||||
if running_version == stable_version:
|
||||
status = 0
|
||||
message = f"OK - running version {running_version} equals stable version {stable_version}"
|
||||
elif running_version > stable_version:
|
||||
status = 1
|
||||
message = f"WARNING - stable version {stable_version} is lower than running version {running_version}, check if downgrade is necessary."
|
||||
else:
|
||||
status = 2
|
||||
message = f"CRITICAL - update necessary, running version {running_version} is lower than stable version {stable_version}"
|
||||
r = get("https://version.home-assistant.io/stable.json")
|
||||
r.raise_for_status()
|
||||
stable_version = parse(r.json()["homeassistant"]["generic-x86-64"])
|
||||
except Exception as e:
|
||||
message = f"{message}: {repr(e)}"
|
||||
print(f"Could not get stable version information from home-assistant.io: {e!r}")
|
||||
exit(3)
|
||||
|
||||
print(message)
|
||||
exit(status)
|
||||
try:
|
||||
r = get(
|
||||
f"https://{DOMAIN}/api/config",
|
||||
headers={"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"},
|
||||
)
|
||||
r.raise_for_status()
|
||||
running_version = parse(r.json()["version"])
|
||||
except Exception as e:
|
||||
print(f"Could not get running version information from homeassistant: {e!r}")
|
||||
exit(3)
|
||||
|
||||
try:
|
||||
if stable_version > running_version:
|
||||
print(
|
||||
f"There is a newer version available: {stable_version} (currently installed: {running_version})"
|
||||
)
|
||||
exit(2)
|
||||
else:
|
||||
print(
|
||||
f"Currently running version {running_version} matches newest release on home-assistant.io"
|
||||
)
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
exit(3)
|
||||
|
|
|
@ -5,9 +5,13 @@ After=network-online.target
|
|||
[Service]
|
||||
Type=simple
|
||||
User=homeassistant
|
||||
Environment="VIRTUAL_ENV=/opt/homeassistant/venv"
|
||||
Environment="PATH=/opt/homeassistant/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
WorkingDirectory=/var/opt/homeassistant
|
||||
ExecStart=/opt/homeassistant/venv/bin/hass -c "/var/opt/homeassistant"
|
||||
RestartForceExitStatus=100
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
if node.has_bundle('pyenv'):
|
||||
python_version = sorted(node.metadata.get('pyenv/python_versions'))[-1]
|
||||
python_path = f'/opt/pyenv/versions/{python_version}/bin/python'
|
||||
else:
|
||||
python_path = '/usr/bin/python3'
|
||||
|
||||
users = {
|
||||
'homeassistant': {
|
||||
'home': '/var/opt/homeassistant',
|
||||
"groups": ["dialout"],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -23,7 +30,7 @@ files = {
|
|||
'/usr/local/share/icinga/plugins/check_homeassistant_update': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'bearer': repo.vault.decrypt(node.metadata.get('homeassistant/api_secret')),
|
||||
'token': node.metadata.get('homeassistant/api_secret'),
|
||||
'domain': node.metadata.get('homeassistant/domain'),
|
||||
},
|
||||
'mode': '0755',
|
||||
|
@ -32,11 +39,18 @@ files = {
|
|||
|
||||
actions = {
|
||||
'homeassistant_create_virtualenv': {
|
||||
'command': 'sudo -u homeassistant /usr/bin/python3 -m virtualenv -p python3 /opt/homeassistant/venv/',
|
||||
'command': f'sudo -u homeassistant virtualenv -p {python_path} /opt/homeassistant/venv/',
|
||||
'unless': 'test -d /opt/homeassistant/venv/',
|
||||
'needs': {
|
||||
'directory:/opt/homeassistant',
|
||||
'user:homeassistant',
|
||||
},
|
||||
},
|
||||
'homeassistant_install': {
|
||||
'command': 'sudo -u homeassistant /opt/homeassistant/venv/bin/pip install homeassistant',
|
||||
'unless': 'test -f /opt/homeassistant/venv/bin/hass',
|
||||
'needs': {
|
||||
'action:homeassistant_create_virtualenv',
|
||||
'pkg_apt:bluez',
|
||||
'pkg_apt:libffi-dev',
|
||||
'pkg_apt:libssl-dev',
|
||||
|
@ -45,17 +59,10 @@ actions = {
|
|||
'pkg_apt:autoconf',
|
||||
'pkg_apt:build-essential',
|
||||
'pkg_apt:libopenjp2-7',
|
||||
'pkg_apt:libtiff5',
|
||||
'pkg_apt:libtiff6',
|
||||
'pkg_apt:libturbojpeg0-dev',
|
||||
'pkg_apt:tzdata',
|
||||
},
|
||||
},
|
||||
'homeassistant_install': {
|
||||
'command': 'sudo -u homeassistant /opt/homeassistant/venv/bin/pip install homeassistant',
|
||||
'unless': 'test -f /opt/homeassistant/venv/bin/hass',
|
||||
'needs': {
|
||||
'action:homeassistant_create_virtualenv',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:homeassistant:restart',
|
||||
},
|
||||
|
|
|
@ -4,11 +4,12 @@ defaults = {
|
|||
'autoconf': {},
|
||||
'bluez': {},
|
||||
'build-essential': {},
|
||||
'ffmpeg': {},
|
||||
'libffi-dev': {},
|
||||
'libjpeg-dev': {},
|
||||
'libopenjp2-7': {},
|
||||
'libssl-dev': {},
|
||||
'libtiff5': {},
|
||||
'libtiff6': {},
|
||||
'libturbojpeg0-dev': {},
|
||||
'python3-packaging': {},
|
||||
'tzdata': {},
|
||||
|
@ -22,6 +23,8 @@ defaults = {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/homeassistant/services',
|
||||
)
|
||||
|
@ -31,15 +34,17 @@ def icinga_check_for_new_release(metadata):
|
|||
'homeassistant': {
|
||||
'services': {
|
||||
'HOMEASSISTANT UPDATE': {
|
||||
'check_interval': '60m',
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_homeassistant_update',
|
||||
'vars.notification.mail': True,
|
||||
'check_interval': '60m',
|
||||
'vars.sshmon_timeout': 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/homeassistant',
|
||||
)
|
||||
|
|
16
bundles/icinga2-statuspage/files/icinga2-statuspage.service
Normal file
16
bundles/icinga2-statuspage/files/icinga2-statuspage.service
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=icinga2-statuspage
|
||||
After=network.target
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
User=www-data
|
||||
Group=www-data
|
||||
Environment=APP_CONFIG=/opt/icinga2-statuspage/config.json
|
||||
WorkingDirectory=/opt/icinga2-statuspage/src
|
||||
ExecStart=/usr/bin/gunicorn statuspage:app --workers 4 --max-requests 1200 --max-requests-jitter 50 --log-level=info --bind=127.0.0.1:22110
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
34
bundles/icinga2-statuspage/items.py
Normal file
34
bundles/icinga2-statuspage/items.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
directories['/opt/icinga2-statuspage/src'] = {}
|
||||
|
||||
git_deploy['/opt/icinga2-statuspage/src'] = {
|
||||
'repo': 'https://git.franzi.business/kunsi/icinga-dynamic-statuspage.git',
|
||||
'rev': 'main',
|
||||
'triggers': {
|
||||
'svc_systemd:icinga2-statuspage:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/opt/icinga2-statuspage/config.json'] = {
|
||||
'content': repo.libs.faults.dict_as_json(node.metadata.get('icinga2-statuspage')),
|
||||
'triggers': {
|
||||
'svc_systemd:icinga2-statuspage:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/usr/local/lib/systemd/system/icinga2-statuspage.service'] = {
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:icinga2-statuspage:restart',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
svc_systemd['icinga2-statuspage'] = {
|
||||
'needs': {
|
||||
'file:/opt/icinga2-statuspage/config.json',
|
||||
'git_deploy:/opt/icinga2-statuspage/src',
|
||||
'pkg_apt:gunicorn',
|
||||
'pkg_apt:python3-flask',
|
||||
'pkg_apt:python3-psycopg2',
|
||||
},
|
||||
}
|
47
bundles/icinga2-statuspage/metadata.py
Normal file
47
bundles/icinga2-statuspage/metadata.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'gunicorn': {},
|
||||
'python3-flask': {},
|
||||
'python3-psycopg2': {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2-statuspage',
|
||||
)
|
||||
def import_db_settings_from_icinga(metadata):
|
||||
return {
|
||||
'icinga2-statuspage': {
|
||||
'DB_USER': 'icinga2',
|
||||
'DB_PASS': metadata.get('postgresql/roles/icinga2/password'),
|
||||
'DB_NAME': 'icinga2',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/icinga2-statuspage',
|
||||
)
|
||||
def nginx(metadata):
|
||||
if not node.has_bundle('nginx'):
|
||||
raise DoNotRunAgain
|
||||
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'icinga2-statuspage': {
|
||||
'domain': metadata.get('icinga2-statuspage/DOMAIN'),
|
||||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:22110',
|
||||
},
|
||||
},
|
||||
'website_check_path': '/',
|
||||
'website_check_string': 'status page',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
132
bundles/icinga2/files/check_omm.py
Normal file
132
bundles/icinga2/files/check_omm.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import re
|
||||
from hashlib import md5
|
||||
from sys import argv, exit
|
||||
|
||||
# Supress SSL certificate warnings for ssl_verify=False
|
||||
import urllib3
|
||||
from lxml import html
|
||||
from requests import Session
|
||||
|
||||
USERNAME_FIELD = "g2"
|
||||
PASSWORD_FIELD = "g3"
|
||||
CRSF_FIELD = "password"
|
||||
|
||||
STATUS_OK = 0
|
||||
STATUS_WARNING = 1
|
||||
STATUS_CRITICAL = 2
|
||||
STATUS_UNKNOWN = 3
|
||||
|
||||
|
||||
class OMMCrawler:
|
||||
def __init__(self, hostname, username, password):
|
||||
self.session = Session()
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
self.session.verify = False
|
||||
|
||||
self.url = f"https://{hostname}"
|
||||
self.login_data = {
|
||||
USERNAME_FIELD: username,
|
||||
PASSWORD_FIELD: password,
|
||||
CRSF_FIELD: md5(password.encode()).hexdigest(),
|
||||
}
|
||||
self.logged_in = False
|
||||
|
||||
def login(self):
|
||||
# if we have multiple dect masters, find out which one is the current master
|
||||
current_master_url = self.session.get(self.url, verify=False).url
|
||||
self.hostname = re.search(r"^(.*[\\\/])", current_master_url).group(0)[:-1]
|
||||
|
||||
response = self.session.post(f"{self.url}/login_set.html", data=self.login_data)
|
||||
response.raise_for_status()
|
||||
|
||||
# set cookie
|
||||
pass_value = re.search(r"(?<=pass=)\d+(?=;)", response.text).group(0)
|
||||
self.session.cookies.set("pass", pass_value)
|
||||
self.logged_in = True
|
||||
|
||||
def get_station_status(self):
|
||||
if not self.logged_in:
|
||||
self.login()
|
||||
|
||||
data = {}
|
||||
response = self.session.get(f"{self.url}/fp_pnp_status.html")
|
||||
response.raise_for_status()
|
||||
tree = html.fromstring(response.text)
|
||||
xpath_results = tree.xpath('//tr[@class="l0" or @class="l1"]')
|
||||
|
||||
for result in xpath_results:
|
||||
bubble_is_in_inactive_cluster = False
|
||||
bubble_is_connected = False
|
||||
bubble_is_active = False
|
||||
|
||||
bubble_name = result.xpath("td[4]/text()")[0]
|
||||
try:
|
||||
bubble_is_connected = result.xpath("td[11]/img/@alt")[0] == "yes"
|
||||
|
||||
if bubble_is_connected:
|
||||
try:
|
||||
bubble_is_active = result.xpath("td[12]/img/@alt")[0] == "yes"
|
||||
except IndexError:
|
||||
# If an IndexError occurs, there is no image in the
|
||||
# 12th td. This means this bubble is in the not inside
|
||||
# an active DECT cluster, but is a backup bubble.
|
||||
# This is probably fine.
|
||||
bubble_is_active = False
|
||||
bubble_is_in_inactive_cluster = True
|
||||
else:
|
||||
bubble_is_active = False
|
||||
except:
|
||||
# There is no Image in the 11th td. This usually means there
|
||||
# is a warning message in the 10th td. We do not care about
|
||||
# that, currently.
|
||||
pass
|
||||
|
||||
data[bubble_name] = {
|
||||
"is_connected": bubble_is_connected,
|
||||
"is_active": bubble_is_active,
|
||||
"is_in_inactive_cluster": bubble_is_in_inactive_cluster,
|
||||
}
|
||||
return data
|
||||
|
||||
def handle_station_data(self):
|
||||
try:
|
||||
data = self.get_station_status()
|
||||
except Exception as e:
|
||||
print(f"Something went wrong. You should take a look at {self.url}")
|
||||
print(repr(e))
|
||||
exit(STATUS_UNKNOWN)
|
||||
|
||||
critical = False
|
||||
for name, status in data.items():
|
||||
if not status["is_active"] and not status["is_connected"]:
|
||||
print(
|
||||
f"Base station {name} is not active or connected! Check manually!"
|
||||
)
|
||||
critical = True
|
||||
elif not status["is_active"] and not status["is_in_inactive_cluster"]:
|
||||
# Bubble is part of an active DECT cluster, but not active.
|
||||
# This shouldn't happen.
|
||||
print(
|
||||
f"Base station {name} is not active but connected! Check manually!"
|
||||
)
|
||||
critical = True
|
||||
elif not status["is_connected"]:
|
||||
# This should never happen. Seeing this state means OMM
|
||||
# itself is broken.
|
||||
print(
|
||||
f"Base station {name} is not connected but active! Check manually!"
|
||||
)
|
||||
critical = True
|
||||
|
||||
if critical:
|
||||
exit(STATUS_CRITICAL)
|
||||
else:
|
||||
print(f"OK - {len(data)} base stations connected")
|
||||
exit(STATUS_OK)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
omm = OMMCrawler(argv[1], argv[2], argv[3])
|
||||
omm.handle_station_data()
|
|
@ -1,16 +1,17 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from json import load
|
||||
from sys import exit
|
||||
|
||||
from requests import get
|
||||
|
||||
SIPGATE_USER = '${node.metadata['icinga2']['sipgate_user']}'
|
||||
SIPGATE_PASS = '${node.metadata['icinga2']['sipgate_pass']}'
|
||||
with open('/etc/icinga2/notification_config.json') as f:
|
||||
CONFIG = load(f)
|
||||
|
||||
try:
|
||||
r = get(
|
||||
'https://api.sipgate.com/v2/balance',
|
||||
auth=(SIPGATE_USER, SIPGATE_PASS),
|
||||
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
|
||||
|
|
|
@ -5,30 +5,33 @@ from ipaddress import IPv6Address, ip_address
|
|||
from subprocess import check_output
|
||||
from sys import argv, exit
|
||||
|
||||
BLOCKLISTS = [
|
||||
'0spam.fusionzero.com',
|
||||
'bl.mailspike.org',
|
||||
'bl.spamcop.net',
|
||||
'blackholes.brainerd.net',
|
||||
'dnsbl-1.uceprotect.net',
|
||||
'dnsbl-2.uceprotect.net',
|
||||
'l2.spews.dnsbl.sorbs.net',
|
||||
'list.dsbl.org',
|
||||
'map.spam-rbl.com',
|
||||
'multihop.dsbl.org',
|
||||
'ns1.unsubscore.com',
|
||||
'opm.blitzed.org',
|
||||
'psbl.surriel.com',
|
||||
'rbl.efnet.org',
|
||||
'rbl.schulte.org',
|
||||
'spamguard.leadmon.net',
|
||||
'ubl.unsubscore.com',
|
||||
'unconfirmed.dsbl.org',
|
||||
'virbl.dnsbl.bit.nl',
|
||||
'zen.spamhaus.org',
|
||||
]
|
||||
BLOCKLISTS = {
|
||||
'0spam.fusionzero.com': set(),
|
||||
'bl.mailspike.org': set(),
|
||||
'bl.spamcop.net': set(),
|
||||
'blackholes.brainerd.net': set(),
|
||||
'dnsbl-1.uceprotect.net': set(),
|
||||
'l2.spews.dnsbl.sorbs.net': set(),
|
||||
'list.dsbl.org': set(),
|
||||
'multihop.dsbl.org': set(),
|
||||
'ns1.unsubscore.com': set(),
|
||||
'opm.blitzed.org': set(),
|
||||
'psbl.surriel.com': set(),
|
||||
'rbl.efnet.org': set(),
|
||||
'rbl.schulte.org': set(),
|
||||
'spamguard.leadmon.net': set(),
|
||||
'ubl.unsubscore.com': set(),
|
||||
'unconfirmed.dsbl.org': set(),
|
||||
'virbl.dnsbl.bit.nl': set(),
|
||||
'zen.spamhaus.org': {
|
||||
# https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now.
|
||||
'127.255.255.252', # Typing Error
|
||||
'127.255.255.254', # public resolver / generic rdns
|
||||
'127.255.255.255', # rate limited
|
||||
},
|
||||
}
|
||||
|
||||
def check_list(ip_list, blocklist):
|
||||
def check_list(ip_list, blocklist, warn_ips):
|
||||
dns_name = '{}.{}'.format(
|
||||
'.'.join(ip_list),
|
||||
blocklist,
|
||||
|
@ -41,16 +44,21 @@ def check_list(ip_list, blocklist):
|
|||
result = check_output([
|
||||
'dig',
|
||||
'+tries=2',
|
||||
'+time=5',
|
||||
'+time=10',
|
||||
'+short',
|
||||
dns_name
|
||||
]).decode().splitlines()
|
||||
for item in result:
|
||||
if item.startswith(';;'):
|
||||
continue
|
||||
msgs.append('{} listed in {} as {}'.format(
|
||||
ip,
|
||||
blocklist,
|
||||
item,
|
||||
))
|
||||
if item in warn_ips and returncode < 2:
|
||||
returncode = 1
|
||||
else:
|
||||
returncode = 2
|
||||
except Exception as e:
|
||||
if e.returncode == 9:
|
||||
|
@ -78,8 +86,8 @@ exitcode = 0
|
|||
with ThreadPoolExecutor(max_workers=len(BLOCKLISTS)) as executor:
|
||||
futures = set()
|
||||
|
||||
for blocklist in BLOCKLISTS:
|
||||
futures.add(executor.submit(check_list, ip_list, blocklist))
|
||||
for blocklist, warn_ips in BLOCKLISTS.items():
|
||||
futures.add(executor.submit(check_list, ip_list, blocklist, warn_ips))
|
||||
|
||||
for future in as_completed(futures):
|
||||
msgs, this_exitcode = future.result()
|
||||
|
|
|
@ -1,31 +1,18 @@
|
|||
% for monitored_node in sorted(repo.nodes):
|
||||
<%
|
||||
auto_updates_enabled = (
|
||||
monitored_node.has_any_bundle(['apt', 'c3voc-addons'])
|
||||
or (
|
||||
monitored_node.has_bundle('pacman')
|
||||
and monitored_node.metadata.get('pacman/unattended-upgrades/is_enabled', False)
|
||||
)
|
||||
) and not monitored_node.metadata.get('icinga_options/exclude_from_monitoring', False)
|
||||
%>\
|
||||
% if auto_updates_enabled:
|
||||
object ScheduledDowntime "unattended_upgrades" {
|
||||
host_name = "${monitored_node.name}"
|
||||
% for dt in downtimes:
|
||||
object ScheduledDowntime "${dt['name']}" {
|
||||
host_name = "${dt['host']}"
|
||||
|
||||
author = "unattended-upgrades"
|
||||
comment = "Downtime for upgrade-and-reboot of node ${monitored_node.name}"
|
||||
author = "${dt['name']}"
|
||||
comment = "${dt['comment']}"
|
||||
|
||||
fixed = true
|
||||
|
||||
ranges = {
|
||||
% if monitored_node.has_bundle('pacman'):
|
||||
"${days[monitored_node.metadata.get('pacman/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('pacman/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}"
|
||||
% else:
|
||||
"${days[monitored_node.metadata.get('apt/unattended-upgrades/day')]}" = "${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${monitored_node.magic_number%30}-${monitored_node.metadata.get('apt/unattended-upgrades/hour')}:${(monitored_node.magic_number%30)+30}"
|
||||
% endif
|
||||
% for d,t in dt['times'].items():
|
||||
"${d}" = "${t}"
|
||||
% endfor
|
||||
}
|
||||
|
||||
child_options = "DowntimeTriggeredChildren"
|
||||
}
|
||||
% endif
|
||||
% endfor
|
||||
|
|
|
@ -33,3 +33,11 @@ object ServiceGroup "checks_with_sms" {
|
|||
assign where service.vars.notification.sms == true
|
||||
ignore where host.vars.notification.sms == false
|
||||
}
|
||||
|
||||
object ServiceGroup "statuspage" {
|
||||
display_name = "Checks which are show on the public status page"
|
||||
|
||||
assign where service.vars.notification.sms == true
|
||||
ignore where host.vars.notification.sms == false
|
||||
ignore where host.vars.show_on_statuspage == false
|
||||
}
|
||||
|
|
|
@ -14,7 +14,8 @@ object Host "${rnode.name}" {
|
|||
vars.os = "${rnode.os}"
|
||||
|
||||
# used for status page
|
||||
vars.pretty_name = "${rnode.metadata.get('icinga_options/pretty_name', rnode.name)}"
|
||||
vars.pretty_name = "${rnode.metadata.get('icinga_options/pretty_name', rnode.metadata.get('hostname'))}"
|
||||
vars.show_on_statuspage = ${str(rnode.metadata.get('icinga_options/show_on_statuspage', True)).lower()}
|
||||
|
||||
vars.period = "${rnode.metadata.get('icinga_options/period', '24x7')}"
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ app = Flask(__name__)
|
|||
@app.route('/status')
|
||||
def statuspage():
|
||||
everything_fine = True
|
||||
try:
|
||||
check_output(['/usr/local/share/icinga/plugins/check_mounts'])
|
||||
except:
|
||||
everything_fine = False
|
||||
|
||||
try:
|
||||
check_output(['/usr/lib/nagios/plugins/check_procs', '-C', 'icinga2', '-c', '1:'])
|
||||
except:
|
||||
|
|
|
@ -3,8 +3,6 @@ Description=Icinga2 Statusmonitor
|
|||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=nagios
|
||||
Group=nagios
|
||||
Environment="FLASK_APP=/etc/icinga2/icinga_statusmonitor.py"
|
||||
ExecStart=/usr/bin/python3 -m flask run
|
||||
WorkingDirectory=/tmp
|
||||
|
|
5
bundles/icinga2/files/icingaweb2/monitoring_config.ini
Normal file
5
bundles/icinga2/files/icingaweb2/monitoring_config.ini
Normal file
|
@ -0,0 +1,5 @@
|
|||
[settings]
|
||||
acknowledge_sticky = 1
|
||||
hostdowntime_all_services = 1
|
||||
hostdowntime_end_fixed = P1W
|
||||
servicedowntime_end_fixed = P2D
|
|
@ -3,22 +3,14 @@
|
|||
import email.mime.text
|
||||
import smtplib
|
||||
from argparse import ArgumentParser
|
||||
from json import dumps
|
||||
from json import dumps, load
|
||||
from subprocess import run
|
||||
from sys import argv
|
||||
|
||||
from requests import post
|
||||
|
||||
SIPGATE_USER='${node.metadata['icinga2']['sipgate_user']}'
|
||||
SIPGATE_PASS='${node.metadata['icinga2']['sipgate_pass']}'
|
||||
|
||||
STATUS_TO_EMOJI = {
|
||||
'critical': '🔥',
|
||||
'down': '🚨🚨🚨',
|
||||
'ok': '🆗',
|
||||
'up': '👌',
|
||||
'warning': '⚡',
|
||||
}
|
||||
with open('/etc/icinga2/notification_config.json') as f:
|
||||
CONFIG = load(f)
|
||||
|
||||
parser = ArgumentParser(
|
||||
prog='icinga_notification_wrapper',
|
||||
|
@ -73,36 +65,31 @@ def notify_per_sms():
|
|||
output_text = ''
|
||||
else:
|
||||
output_text = '\n\n{}'.format(args.output)
|
||||
if args.state.lower() in STATUS_TO_EMOJI:
|
||||
message_text = '{emoji} {host}{service} {emoji}{output}'.format(
|
||||
emoji=STATUS_TO_EMOJI[args.state.lower()],
|
||||
host=args.host_name,
|
||||
service=('/'+args.service_name if args.service_name else ''),
|
||||
state=args.state.upper(),
|
||||
output=output_text,
|
||||
)
|
||||
else:
|
||||
|
||||
message_text = 'ICINGA: {host}{service} is {state}{output}'.format(
|
||||
host=args.host_name,
|
||||
service=('/'+args.service_name if args.service_name else ''),
|
||||
state=args.state.upper(),
|
||||
output=output_text,
|
||||
)
|
||||
|
||||
message = {
|
||||
'message': message_text,
|
||||
'smsId': 's0', # XXX what does this mean? Documentation is unclear
|
||||
'recipient': args.sms
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
|
||||
try:
|
||||
r = post(
|
||||
'https://api.sipgate.com/v2/sessions/sms',
|
||||
json=message,
|
||||
headers=headers,
|
||||
auth=(SIPGATE_USER, SIPGATE_PASS),
|
||||
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
||||
)
|
||||
|
||||
if r.status_code == 204:
|
||||
|
@ -113,6 +100,42 @@ def notify_per_sms():
|
|||
log_to_syslog('Sending a SMS to "{}" failed: {}'.format(args.sms, repr(e)))
|
||||
|
||||
|
||||
def notify_per_ntfy():
|
||||
message_text = 'ICINGA: {host}{service} is {state}\n\n{output}'.format(
|
||||
host=args.host_name,
|
||||
service=('/'+args.service_name if args.service_name else ''),
|
||||
state=args.state.upper(),
|
||||
output=args.output,
|
||||
)
|
||||
|
||||
if args.service_name:
|
||||
subject = '[ICINGA] {}/{}'.format(args.host_name, args.service_name)
|
||||
else:
|
||||
subject = '[ICINGA] {}'.format(args.host_name)
|
||||
|
||||
if args.notification_type.lower() == 'recovery':
|
||||
priority = 'default'
|
||||
else:
|
||||
priority = 'urgent'
|
||||
|
||||
headers = {
|
||||
'Title': subject,
|
||||
'Priority': priority,
|
||||
}
|
||||
|
||||
try:
|
||||
r = post(
|
||||
CONFIG['ntfy']['url'],
|
||||
data=message_text,
|
||||
headers=headers,
|
||||
auth=(CONFIG['ntfy']['user'], CONFIG['ntfy']['password']),
|
||||
)
|
||||
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
log_to_syslog('Sending a Notification failed: {}'.format(repr(e)))
|
||||
|
||||
|
||||
def notify_per_mail():
|
||||
if args.notification_type.lower() == 'recovery':
|
||||
# Do not send recovery emails.
|
||||
|
@ -176,4 +199,7 @@ if __name__ == '__main__':
|
|||
notify_per_mail()
|
||||
|
||||
if args.sms:
|
||||
if not args.service_name:
|
||||
notify_per_sms()
|
||||
if CONFIG['ntfy']['user']:
|
||||
notify_per_ntfy()
|
||||
|
|
|
@ -76,8 +76,6 @@ files = {
|
|||
},
|
||||
'/usr/local/share/icinga/plugins/check_sipgate_account_balance': {
|
||||
'mode': '0755',
|
||||
'content_type': 'mako',
|
||||
'cascade_skip': False, # contains faults
|
||||
},
|
||||
'/usr/local/share/icinga/plugins/check_freifunk_node': {
|
||||
'mode': '0755',
|
||||
|
@ -114,11 +112,22 @@ files = {
|
|||
'svc_systemd:icinga2:restart',
|
||||
},
|
||||
},
|
||||
'/etc/icinga2/notification_config.json': {
|
||||
'content': repo.libs.faults.dict_as_json({
|
||||
'sipgate': {
|
||||
'user': node.metadata.get('icinga2/sipgate/user'),
|
||||
'password': node.metadata.get('icinga2/sipgate/pass'),
|
||||
},
|
||||
'ntfy': {
|
||||
'url': node.metadata.get('icinga2/ntfy/url'),
|
||||
'user': node.metadata.get('icinga2/ntfy/user'),
|
||||
'password': node.metadata.get('icinga2/ntfy/pass'),
|
||||
},
|
||||
}),
|
||||
},
|
||||
'/etc/icinga2/scripts/icinga_notification_wrapper': {
|
||||
'source': 'scripts/icinga_notification_wrapper',
|
||||
'content_type': 'mako',
|
||||
'mode': '0755',
|
||||
'cascade_skip': False, # contains faults
|
||||
},
|
||||
'/etc/icinga2/features-available/ido-pgsql.conf': {
|
||||
'source': 'icinga2/ido-pgsql.conf',
|
||||
|
@ -245,6 +254,11 @@ files = {
|
|||
'mode': '0660',
|
||||
'group': 'icingaweb2',
|
||||
},
|
||||
'/etc/icingaweb2/modules/monitoring/config.ini': {
|
||||
'source': 'icingaweb2/monitoring_config.ini',
|
||||
'mode': '0660',
|
||||
'group': 'icingaweb2',
|
||||
},
|
||||
'/etc/icingaweb2/groups.ini': {
|
||||
'source': 'icingaweb2/groups.ini',
|
||||
'mode': '0660',
|
||||
|
@ -262,13 +276,13 @@ files = {
|
|||
'group': 'icingaweb2',
|
||||
},
|
||||
|
||||
# Statusmonitor
|
||||
# monitoring
|
||||
'/etc/icinga2/icinga_statusmonitor.py': {
|
||||
'triggers': {
|
||||
'svc_systemd:icinga_statusmonitor:restart',
|
||||
},
|
||||
},
|
||||
'/etc/systemd/system/icinga_statusmonitor.service': {
|
||||
'/usr/local/lib/systemd/system/icinga_statusmonitor.service': {
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:icinga_statusmonitor:restart',
|
||||
|
@ -276,8 +290,12 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
pkg_pip = {
|
||||
'easysnmp': {}, # for check_usv_snmp
|
||||
svc_systemd['icinga_statusmonitor'] = {
|
||||
'needs': {
|
||||
'file:/etc/icinga2/icinga_statusmonitor.py',
|
||||
'file:/usr/local/lib/systemd/system/icinga_statusmonitor.service',
|
||||
'pkg_apt:python3-flask',
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
|
@ -319,36 +337,22 @@ for name in files:
|
|||
for name in symlinks:
|
||||
icinga_run_deps.add(f'symlink:{name}')
|
||||
|
||||
svc_systemd = {
|
||||
'icinga2': {
|
||||
svc_systemd['icinga2'] = {
|
||||
'needs': icinga_run_deps,
|
||||
},
|
||||
'icinga_statusmonitor': {
|
||||
'needs': {
|
||||
'file:/etc/icinga2/icinga_statusmonitor.py',
|
||||
'file:/etc/systemd/system/icinga_statusmonitor.service',
|
||||
'pkg_apt:python3-flask',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# The actual hosts and services management starts here
|
||||
bundles = set()
|
||||
for rnode in repo.nodes:
|
||||
downtimes = []
|
||||
for rnode in sorted(repo.nodes):
|
||||
if rnode.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||
continue
|
||||
|
||||
host_ips = repo.libs.tools.resolve_identifier(repo, rnode.name)
|
||||
host_ips = repo.libs.tools.resolve_identifier(repo, rnode.name, only_physical=True)
|
||||
icinga_ips = {}
|
||||
|
||||
# XXX for the love of god, PLEASE remove this once DNS is no longer
|
||||
# hosted at GCE
|
||||
if rnode.in_group('gce'):
|
||||
icinga_ips['ipv4'] = rnode.metadata.get('external_ipv4')
|
||||
else:
|
||||
for ip_type in ('ipv4', 'ipv6'):
|
||||
for ip in sorted(host_ips[ip_type]):
|
||||
if ip.is_private and not ip.is_link_local:
|
||||
|
@ -379,6 +383,41 @@ for rnode in repo.nodes:
|
|||
|
||||
bundles |= set(rnode.metadata.get('icinga2_api', {}).keys())
|
||||
|
||||
if rnode.has_any_bundle(['apt', 'c3voc-addons']):
|
||||
day = rnode.metadata.get('apt/unattended-upgrades/day')
|
||||
hour = rnode.metadata.get('apt/unattended-upgrades/hour')
|
||||
minute = rnode.magic_number%30
|
||||
|
||||
spread = rnode.metadata.get('apt/unattended-upgrades/spread_in_group', None)
|
||||
if spread is not None:
|
||||
spread_nodes = sorted(repo.nodes_in_group(spread))
|
||||
day += spread_nodes.index(rnode)
|
||||
|
||||
downtimes.append({
|
||||
'name': 'unattended-upgrades',
|
||||
'host': rnode.name,
|
||||
'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}',
|
||||
'times': {
|
||||
DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+15}',
|
||||
},
|
||||
})
|
||||
elif (
|
||||
rnode.has_bundle('pacman')
|
||||
and rnode.metadata.get('pacman/unattended-upgrades/is_enabled', False)
|
||||
):
|
||||
day = rnode.metadata.get('pacman/unattended-upgrades/day')
|
||||
hour = rnode.metadata.get('pacman/unattended-upgrades/hour')
|
||||
minute = rnode.magic_number%30
|
||||
|
||||
downtimes.append({
|
||||
'name': 'unattended-upgrades',
|
||||
'host': rnode.name,
|
||||
'comment': f'Downtime for upgrade-and-reboot of node {rnode.name}',
|
||||
'times': {
|
||||
DAYS_TO_STRING[day%7]: f'{hour}:{minute}-{hour}:{minute+15}',
|
||||
},
|
||||
})
|
||||
|
||||
files['/etc/icinga2/conf.d/groups.conf'] = {
|
||||
'source': 'icinga2/groups.conf',
|
||||
'content_type': 'mako',
|
||||
|
@ -399,7 +438,7 @@ files['/etc/icinga2/conf.d/downtimes.conf'] = {
|
|||
'source': 'icinga2/downtimes.conf',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'days': DAYS_TO_STRING,
|
||||
'downtimes': downtimes,
|
||||
},
|
||||
'owner': 'nagios',
|
||||
'group': 'nagios',
|
||||
|
|
|
@ -17,12 +17,9 @@ defaults = {
|
|||
'icinga2': {},
|
||||
'icinga2-ido-pgsql': {},
|
||||
'icingaweb2': {},
|
||||
|
||||
# apparently no longer needed
|
||||
#'icingaweb2-module-monitoring': {},
|
||||
|
||||
# neeeded for statusmonitor
|
||||
'python3-easysnmp': {},
|
||||
'python3-flask': {},
|
||||
'snmp': {},
|
||||
}
|
||||
},
|
||||
'icinga2': {
|
||||
|
@ -43,9 +40,6 @@ defaults = {
|
|||
'check_interval': '30m',
|
||||
'vars.notification.mail': True,
|
||||
},
|
||||
'ICINGA STATUSMONITOR': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit icinga_statusmonitor',
|
||||
},
|
||||
'IDO-PGSQL': {
|
||||
'check_command': 'ido',
|
||||
'vars.ido_type': 'IdoPgsqlConnection',
|
||||
|
@ -59,6 +53,21 @@ defaults = {
|
|||
'icingaweb2': {
|
||||
'setup-token': repo.vault.password_for(f'{node.name} icingaweb2 setup-token'),
|
||||
},
|
||||
'php': {
|
||||
'version': '8.2',
|
||||
'packages': {
|
||||
'curl',
|
||||
'gd',
|
||||
'intl',
|
||||
'imagick',
|
||||
'ldap',
|
||||
'mysql',
|
||||
'opcache',
|
||||
'pgsql',
|
||||
'readline',
|
||||
'xml',
|
||||
},
|
||||
},
|
||||
'postgresql': {
|
||||
'roles': {
|
||||
'icinga2': {
|
||||
|
@ -105,13 +114,29 @@ def add_users_from_json(metadata):
|
|||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules/5665',
|
||||
'nginx/vhosts/icingaweb2',
|
||||
'nginx/vhosts/icinga_statusmonitor',
|
||||
)
|
||||
def firewall(metadata):
|
||||
def nginx(metadata):
|
||||
if not node.has_bundle('nginx'):
|
||||
raise DoNotRunAgain
|
||||
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'5665': atomic(metadata.get('icinga2/restrict-to', set())),
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'icingaweb2': {
|
||||
'domain': metadata.get('icinga2/web_domain'),
|
||||
'webroot': '/usr/share/icingaweb2/public',
|
||||
'locations': {
|
||||
'/api/': {
|
||||
'target': 'https://127.0.0.1:5665/',
|
||||
},
|
||||
'/statusmonitor/': {
|
||||
'target': 'http://127.0.0.1:5000/',
|
||||
},
|
||||
},
|
||||
'extras': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ defaults = {
|
|||
'repos': {
|
||||
'influxdb': {
|
||||
'items': {
|
||||
'deb https://repos.influxdata.com/{os} {os_release} stable',
|
||||
'deb https://repos.influxdata.com/{os} stable main',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -4,7 +4,8 @@ After=network.target
|
|||
Requires=infobeamer-cms.service
|
||||
|
||||
[Service]
|
||||
Environment=SETTINGS=/opt/infobeamer-cms/settings.toml
|
||||
WorkingDirectory=/opt/infobeamer-cms/src
|
||||
User=infobeamer-cms
|
||||
Group=infobeamer-cms
|
||||
WorkingDirectory=/opt/infobeamer-cms
|
||||
ExecStart=curl -s -H "Host: ${domain}" http://127.0.0.1:8000/sync
|
||||
ExecStart=/opt/infobeamer-cms/venv/bin/python syncer.py
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Description=Run infobeamer-cms sync
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*:0/5
|
||||
OnCalendar=minutely
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
<%
|
||||
from tomlkit import dumps as toml_dumps
|
||||
from bundlewrap.utils.text import toml_clean
|
||||
%>${toml_clean(toml_dumps(repo.libs.faults.resolve_faults(config), sort_keys=True))}
|
|
@ -1,8 +1,4 @@
|
|||
actions = {
|
||||
'infobeamer-cms_set_directory_permissions': {
|
||||
'triggered': True,
|
||||
'command': 'chown -R infobeamer-cms:infobeamer-cms /opt/infobeamer-cms/src/static/'
|
||||
},
|
||||
'infobeamer-cms_create_virtualenv': {
|
||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/infobeamer-cms/venv/',
|
||||
'unless': 'test -d /opt/infobeamer-cms/venv/',
|
||||
|
@ -12,7 +8,11 @@ actions = {
|
|||
},
|
||||
},
|
||||
'infobeamer-cms_install_requirements': {
|
||||
'command': 'cd /opt/infobeamer-cms/src && /opt/infobeamer-cms/venv/bin/pip install --upgrade pip gunicorn -r requirements.txt',
|
||||
'command': ' && '.join([
|
||||
'cd /opt/infobeamer-cms/src',
|
||||
'/opt/infobeamer-cms/venv/bin/pip install --upgrade pip gunicorn -r requirements.txt',
|
||||
'rsync /opt/infobeamer-cms/src/static/* /opt/infobeamer-cms/static/',
|
||||
]),
|
||||
'needs': {
|
||||
'action:infobeamer-cms_create_virtualenv',
|
||||
},
|
||||
|
@ -23,13 +23,12 @@ actions = {
|
|||
git_deploy = {
|
||||
'/opt/infobeamer-cms/src': {
|
||||
'rev': 'master',
|
||||
'repo': 'https://github.com/sophieschi/36c3-cms.git',
|
||||
'repo': 'https://github.com/voc/infobeamer-cms.git',
|
||||
'needs': {
|
||||
'directory:/opt/infobeamer-cms/src',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:infobeamer-cms:restart',
|
||||
'action:infobeamer-cms_set_directory_permissions',
|
||||
'action:infobeamer-cms_install_requirements',
|
||||
},
|
||||
},
|
||||
|
@ -37,6 +36,9 @@ git_deploy = {
|
|||
|
||||
directories = {
|
||||
'/opt/infobeamer-cms/src': {},
|
||||
'/opt/infobeamer-cms/static': {
|
||||
'owner': 'infobeamer-cms',
|
||||
},
|
||||
}
|
||||
|
||||
config = node.metadata.get('infobeamer-cms/config', {})
|
||||
|
@ -66,10 +68,7 @@ for room, device_id in sorted(node.metadata.get('infobeamer-cms/rooms', {}).item
|
|||
|
||||
files = {
|
||||
'/opt/infobeamer-cms/settings.toml': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'config': config,
|
||||
},
|
||||
'content': repo.libs.faults.dict_as_toml(config),
|
||||
'triggers': {
|
||||
'svc_systemd:infobeamer-cms:restart',
|
||||
},
|
||||
|
@ -97,19 +96,11 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
pkg_pip = {
|
||||
'github-flask': {
|
||||
'needed_by': {
|
||||
'svc_systemd:infobeamer-cms',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'infobeamer-cms': {
|
||||
'needs': {
|
||||
'action:infobeamer-cms_install_requirements',
|
||||
'action:infobeamer-cms_set_directory_permissions',
|
||||
'directory:/opt/infobeamer-cms/static',
|
||||
'file:/etc/systemd/system/infobeamer-cms.service',
|
||||
'file:/opt/infobeamer-cms/settings.toml',
|
||||
'git_deploy:/opt/infobeamer-cms/src',
|
||||
|
@ -117,8 +108,12 @@ svc_systemd = {
|
|||
},
|
||||
'infobeamer-cms-runperiodic.timer': {
|
||||
'needs': {
|
||||
'file:/etc/systemd/system/infobeamer-cms-runperiodic.timer',
|
||||
'action:infobeamer-cms_install_requirements',
|
||||
'directory:/opt/infobeamer-cms/static',
|
||||
'file:/etc/systemd/system/infobeamer-cms-runperiodic.service',
|
||||
'file:/etc/systemd/system/infobeamer-cms-runperiodic.timer',
|
||||
'file:/opt/infobeamer-cms/settings.toml',
|
||||
'git_deploy:/opt/infobeamer-cms/src',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ defaults = {
|
|||
'MAX_UPLOADS': 5,
|
||||
'PREFERRED_URL_SCHEME': 'https',
|
||||
'SESSION_COOKIE_NAME': '__Host-sess',
|
||||
'STATIC_PATH': '/opt/infobeamer-cms/static',
|
||||
'URL_KEY': repo.vault.password_for(f'{node.name} infobeamer-cms url key'),
|
||||
'VERSION': 1,
|
||||
},
|
||||
|
@ -29,15 +30,13 @@ def nginx(metadata):
|
|||
'/': {
|
||||
'target': 'http://127.0.0.1:8000',
|
||||
},
|
||||
'/sync': {
|
||||
'return': 403,
|
||||
},
|
||||
'/static': {
|
||||
'alias': '/opt/infobeamer-cms/src/static',
|
||||
'alias': '/opt/infobeamer-cms/static',
|
||||
},
|
||||
},
|
||||
'website_check_path': '/',
|
||||
'website_check_string': 'Share your projects',
|
||||
'do_not_set_content_security_headers': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -45,6 +44,7 @@ def nginx(metadata):
|
|||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'infobeamer-cms/config/DOMAIN',
|
||||
'infobeamer-cms/config/TIME_MAX',
|
||||
'infobeamer-cms/config/TIME_MIN',
|
||||
)
|
||||
|
@ -57,6 +57,7 @@ def event_times(metadata):
|
|||
return {
|
||||
'infobeamer-cms': {
|
||||
'config': {
|
||||
'DOMAIN': metadata.get('infobeamer-cms/domain'),
|
||||
'TIME_MAX': int(event_end.timestamp()),
|
||||
'TIME_MIN': int(event_start.timestamp()),
|
||||
},
|
||||
|
|
15
bundles/infobeamer-monitor/files/infobeamer-monitor.service
Normal file
15
bundles/infobeamer-monitor/files/infobeamer-monitor.service
Normal file
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=infobeamer-monitor
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=exec
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStart=/opt/infobeamer-cms/venv/bin/python monitor.py
|
||||
User=infobeamer-cms
|
||||
Group=infobeamer-cms
|
||||
WorkingDirectory=/opt/infobeamer-monitor/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
227
bundles/infobeamer-monitor/files/monitor.py
Normal file
227
bundles/infobeamer-monitor/files/monitor.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from json import dumps
|
||||
from time import sleep
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
from requests import RequestException, get
|
||||
|
||||
try:
|
||||
# python 3.11
|
||||
from tomllib import loads as toml_load
|
||||
except ImportError:
|
||||
from rtoml import load as toml_load
|
||||
|
||||
with open("config.toml") as f:
|
||||
CONFIG = toml_load(f.read())
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
format="[%(levelname)s %(name)s] %(message)s",
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
LOG = logging.getLogger("main")
|
||||
MLOG = logging.getLogger("mqtt")
|
||||
|
||||
state = None
|
||||
|
||||
client = mqtt.Client()
|
||||
client.username_pw_set(CONFIG["mqtt"]["user"], CONFIG["mqtt"]["password"])
|
||||
client.connect(CONFIG["mqtt"]["host"], 1883, 60)
|
||||
client.loop_start()
|
||||
|
||||
|
||||
def mqtt_out(message, level="INFO", device=None):
|
||||
key = "infobeamer"
|
||||
if device:
|
||||
key += f"/{device['id']}"
|
||||
message = f"[{device['description']}] {message}"
|
||||
|
||||
client.publish(
|
||||
CONFIG["mqtt"]["topic"],
|
||||
dumps(
|
||||
{
|
||||
"level": level,
|
||||
"component": key,
|
||||
"msg": message,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def mqtt_dump_state(device):
|
||||
if not device["is_online"]:
|
||||
return
|
||||
|
||||
out = []
|
||||
if device["location"]:
|
||||
out.append("Location: {}".format(device["location"]))
|
||||
out.append("Setup: {} ({})".format(device["setup"]["name"], device["setup"]["id"]))
|
||||
out.append("Resolution: {}".format(device["run"].get("resolution", "unknown")))
|
||||
|
||||
mqtt_out(
|
||||
" - ".join(out),
|
||||
device=device,
|
||||
)
|
||||
|
||||
|
||||
mqtt_out("Monitor starting up")
|
||||
while True:
|
||||
try:
|
||||
online_devices = set()
|
||||
available_credits = None
|
||||
|
||||
try:
|
||||
r = get(
|
||||
"https://info-beamer.com/api/v1/device/list",
|
||||
auth=("", CONFIG["api_key"]),
|
||||
)
|
||||
r.raise_for_status()
|
||||
ib_state = r.json()["devices"]
|
||||
except RequestException as e:
|
||||
LOG.exception("Could not get data from info-beamer")
|
||||
mqtt_out(
|
||||
f"Could not get data from info-beamer: {e!r}",
|
||||
level="WARN",
|
||||
)
|
||||
else:
|
||||
new_state = {}
|
||||
for device in ib_state:
|
||||
did = str(device["id"])
|
||||
|
||||
if did in new_state:
|
||||
mqtt_out("DUPLICATE DETECTED!", level="ERROR", device=device)
|
||||
continue
|
||||
|
||||
new_state[did] = device
|
||||
must_dump_state = False
|
||||
|
||||
if state is not None:
|
||||
if did not in state:
|
||||
LOG.info(
|
||||
"new device found: {} [{}]".format(
|
||||
did,
|
||||
device["description"],
|
||||
)
|
||||
)
|
||||
mqtt_out(
|
||||
"new device found!",
|
||||
device=device,
|
||||
)
|
||||
must_dump_state = True
|
||||
|
||||
else:
|
||||
if device["is_online"] != state[did]["is_online"]:
|
||||
online_status = (
|
||||
"online from {}".format(device["run"]["public_addr"])
|
||||
if device["is_online"]
|
||||
else "offline"
|
||||
)
|
||||
|
||||
LOG.info("device {} is now {}".format(did, online_status))
|
||||
mqtt_out(
|
||||
f"status changed to {online_status}",
|
||||
level="INFO" if device["is_online"] else "WARN",
|
||||
device=device,
|
||||
)
|
||||
must_dump_state = True
|
||||
|
||||
if device["description"] != state[did]["description"]:
|
||||
LOG.info(
|
||||
"device {} changed name to {}".format(
|
||||
did, device["description"]
|
||||
)
|
||||
)
|
||||
must_dump_state = True
|
||||
|
||||
if device["is_online"]:
|
||||
if device["maintenance"]:
|
||||
mqtt_out(
|
||||
"maintenance required: {}".format(
|
||||
" ".join(sorted(device["maintenance"]))
|
||||
),
|
||||
level="WARN",
|
||||
device=device,
|
||||
)
|
||||
|
||||
if (
|
||||
device["location"] != state[did]["location"]
|
||||
or device["setup"]["id"] != state[did]["setup"]["id"]
|
||||
or device["run"].get("resolution")
|
||||
!= state[did]["run"].get("resolution")
|
||||
):
|
||||
must_dump_state = True
|
||||
|
||||
if must_dump_state:
|
||||
mqtt_dump_state(device)
|
||||
else:
|
||||
LOG.info("adding device {} to empty state".format(device["id"]))
|
||||
|
||||
if device["is_online"]:
|
||||
online_devices.add(
|
||||
"{} ({})".format(
|
||||
device["id"],
|
||||
device["description"],
|
||||
)
|
||||
)
|
||||
|
||||
state = new_state
|
||||
|
||||
try:
|
||||
r = get(
|
||||
"https://info-beamer.com/api/v1/account",
|
||||
auth=("", CONFIG["api_key"]),
|
||||
)
|
||||
r.raise_for_status()
|
||||
ib_account = r.json()
|
||||
except RequestException as e:
|
||||
LOG.exception("Could not get data from info-beamer")
|
||||
mqtt_out(
|
||||
f"Could not get data from info-beamer: {e!r}",
|
||||
level="WARN",
|
||||
)
|
||||
else:
|
||||
available_credits = ib_account["balance"]
|
||||
if available_credits < 50:
|
||||
mqtt_out(
|
||||
f"balance has dropped below 50 credits! (available: {available_credits})",
|
||||
level="ERROR",
|
||||
)
|
||||
elif available_credits < 100:
|
||||
mqtt_out(
|
||||
f"balance has dropped below 100 credits! (available: {available_credits})",
|
||||
level="WARN",
|
||||
)
|
||||
|
||||
for quota_name, quota_config in sorted(ib_account["quotas"].items()):
|
||||
value = quota_config["count"]["value"]
|
||||
limit = quota_config["count"]["limit"]
|
||||
if value > limit * 0.9:
|
||||
mqtt_out(
|
||||
f"quota {quota_name} is over 90% (limit {limit}, value {value})",
|
||||
level="ERROR",
|
||||
)
|
||||
elif value > limit * 0.8:
|
||||
mqtt_out(
|
||||
f"quota {quota_name} is over 80% (limit {limit}, value {value})",
|
||||
level="WARN",
|
||||
)
|
||||
|
||||
if datetime.now(ZoneInfo("Europe/Berlin")).strftime("%H%M") == "0900":
|
||||
if available_credits is not None:
|
||||
mqtt_out(f"Available Credits: {available_credits}")
|
||||
|
||||
if online_devices:
|
||||
mqtt_out(
|
||||
"Online Devices: {}".format(", ".join(sorted(online_devices)))
|
||||
)
|
||||
|
||||
sleep(60)
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
|
||||
mqtt_out("Monitor exiting")
|
30
bundles/infobeamer-monitor/items.py
Normal file
30
bundles/infobeamer-monitor/items.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
assert node.has_bundle('infobeamer-cms') # uses same venv
|
||||
|
||||
files['/opt/infobeamer-monitor/config.toml'] = {
|
||||
'content': repo.libs.faults.dict_as_toml(node.metadata.get('infobeamer-monitor')),
|
||||
'triggers': {
|
||||
'svc_systemd:infobeamer-monitor:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/opt/infobeamer-monitor/monitor.py'] = {
|
||||
'mode': '0755',
|
||||
'triggers': {
|
||||
'svc_systemd:infobeamer-monitor:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/usr/local/lib/systemd/system/infobeamer-monitor.service'] = {
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:infobeamer-monitor:restart',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd['infobeamer-monitor'] = {
|
||||
'needs': {
|
||||
'file:/opt/infobeamer-monitor/config.toml',
|
||||
'file:/opt/infobeamer-monitor/monitor.py',
|
||||
'file:/usr/local/lib/systemd/system/infobeamer-monitor.service',
|
||||
},
|
||||
}
|
7
bundles/jellyfin/files/jellyfin-sudoers
Normal file
7
bundles/jellyfin/files/jellyfin-sudoers
Normal file
|
@ -0,0 +1,7 @@
|
|||
Cmnd_Alias RESTARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl restart jellyfin
|
||||
Cmnd_Alias STARTSERVER_SYSTEMD = /usr/bin/systemd-run systemctl start jellyfin
|
||||
Cmnd_Alias STOPSERVER_SYSTEMD = /usr/bin/systemd-run systemctl stop jellyfin
|
||||
|
||||
jellyfin ALL=(ALL) NOPASSWD: RESTARTSERVER_SYSTEMD
|
||||
jellyfin ALL=(ALL) NOPASSWD: STARTSERVER_SYSTEMD
|
||||
jellyfin ALL=(ALL) NOPASSWD: STOPSERVER_SYSTEMD
|
5
bundles/jellyfin/items.py
Normal file
5
bundles/jellyfin/items.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
files['/etc/sudoers.d/jellyfin-sudoers'] = {
|
||||
'after': {
|
||||
'pkg_apt:jellyfin',
|
||||
},
|
||||
}
|
69
bundles/jellyfin/metadata.py
Normal file
69
bundles/jellyfin/metadata.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from bundlewrap.metadata import atomic
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'jellyfin': {},
|
||||
},
|
||||
'repos': {
|
||||
'jellyfin': {
|
||||
'uris': {
|
||||
'https://repo.jellyfin.org/{os}'
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'backups': {
|
||||
'paths': {
|
||||
f'/var/lib/jellyfin/{x}' for x in ('data', 'metadata', 'plugins', 'root')
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'transmission': {
|
||||
'services': {
|
||||
'JELLYFIN PROCESS': {
|
||||
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C jellyfin -c 1:',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/jellyfin',
|
||||
)
|
||||
def nginx(metadata):
|
||||
if not node.has_bundle('nginx'):
|
||||
raise DoNotRunAgain
|
||||
|
||||
if 'jellyfin' not in metadata.get('nginx/vhosts', {}):
|
||||
return {}
|
||||
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'jellyfin': {
|
||||
'do_not_add_content_security_headers': True,
|
||||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:8096',
|
||||
'websockets': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'8096/tcp': atomic(metadata.get('jellyfin/restrict-to', set())),
|
||||
},
|
||||
},
|
||||
}
|
15
bundles/jool/items.py
Normal file
15
bundles/jool/items.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
actions['modprobe_jool'] = {
|
||||
'command': 'modprobe jool',
|
||||
'unless': 'lsmod | grep -F jool',
|
||||
}
|
||||
|
||||
actions['jool_add_nat64_instance'] = {
|
||||
'command': 'jool instance add "nat64" --netfilter --pool6 64:ff9b::/96',
|
||||
'unless': 'jool instance display --no-headers --csv | grep -E ",nat64,netfilter$"',
|
||||
'needs': {
|
||||
'action:modprobe_jool',
|
||||
'pkg_apt:jool-dkms',
|
||||
'pkg_apt:jool-tools',
|
||||
'pkg_apt:linux-headers-amd64',
|
||||
},
|
||||
}
|
14
bundles/jool/metadata.py
Normal file
14
bundles/jool/metadata.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'jool-dkms': {},
|
||||
'jool-tools': {},
|
||||
'linux-headers-amd64': {},
|
||||
},
|
||||
},
|
||||
'modules': {
|
||||
'jool': [
|
||||
'jool',
|
||||
],
|
||||
},
|
||||
}
|
16
bundles/jugendhackt_tools/files/jugendhackt_tools.service
Normal file
16
bundles/jugendhackt_tools/files/jugendhackt_tools.service
Normal file
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=jugendhackt_tools web service
|
||||
After=network.target
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
User=jugendhackt_tools
|
||||
Group=jugendhackt_tools
|
||||
Environment=CONFIG_PATH=/opt/jugendhackt_tools/config.toml
|
||||
WorkingDirectory=/opt/jugendhackt_tools/src
|
||||
ExecStart=/opt/jugendhackt_tools/venv/bin/gunicorn jugendhackt_tools.wsgi --name jugendhackt_tools --workers 4 --max-requests 1200 --max-requests-jitter 50 --log-level=info --bind=127.0.0.1:22090
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
75
bundles/jugendhackt_tools/items.py
Normal file
75
bundles/jugendhackt_tools/items.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
directories['/opt/jugendhackt_tools/src'] = {}
|
||||
|
||||
git_deploy['/opt/jugendhackt_tools/src'] = {
|
||||
'repo': 'https://github.com/kunsi/jugendhackt_schedule.git',
|
||||
'rev': 'main',
|
||||
'triggers': {
|
||||
'action:jugendhackt_tools_install',
|
||||
'action:jugendhackt_tools_migrate',
|
||||
'svc_systemd:jugendhackt_tools:restart',
|
||||
},
|
||||
}
|
||||
|
||||
actions['jugendhackt_tools_create_virtualenv'] = {
|
||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/jugendhackt_tools/venv/',
|
||||
'unless': 'test -d /opt/jugendhackt_tools/venv/',
|
||||
'needs': {
|
||||
# actually /opt/jugendhackt_tools, but we don't create that
|
||||
'directory:/opt/jugendhackt_tools/src',
|
||||
},
|
||||
}
|
||||
|
||||
actions['jugendhackt_tools_install'] = {
|
||||
'command': ' && '.join([
|
||||
'cd /opt/jugendhackt_tools/src',
|
||||
'/opt/jugendhackt_tools/venv/bin/pip install --upgrade pip wheel gunicorn psycopg2-binary',
|
||||
'/opt/jugendhackt_tools/venv/bin/pip install --upgrade -r requirements.txt',
|
||||
]),
|
||||
'needs': {
|
||||
'action:jugendhackt_tools_create_virtualenv',
|
||||
},
|
||||
'triggered': True,
|
||||
}
|
||||
|
||||
actions['jugendhackt_tools_migrate'] = {
|
||||
'command': ' && '.join([
|
||||
'cd /opt/jugendhackt_tools/src',
|
||||
'CONFIG_PATH=/opt/jugendhackt_tools/config.toml /opt/jugendhackt_tools/venv/bin/python manage.py migrate',
|
||||
'CONFIG_PATH=/opt/jugendhackt_tools/config.toml /opt/jugendhackt_tools/venv/bin/python manage.py collectstatic --noinput',
|
||||
]),
|
||||
'needs': {
|
||||
'action:jugendhackt_tools_install',
|
||||
'file:/opt/jugendhackt_tools/config.toml',
|
||||
'postgres_db:jugendhackt_tools',
|
||||
'postgres_role:jugendhackt_tools',
|
||||
},
|
||||
'triggered': True,
|
||||
}
|
||||
|
||||
files['/opt/jugendhackt_tools/config.toml'] = {
|
||||
'content': repo.libs.faults.dict_as_toml(node.metadata.get('jugendhackt_tools')),
|
||||
'triggers': {
|
||||
'svc_systemd:jugendhackt_tools:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/usr/local/lib/systemd/system/jugendhackt_tools.service'] = {
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:jugendhackt_tools:restart',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd['jugendhackt_tools'] = {
|
||||
'needs': {
|
||||
'action:jugendhackt_tools_migrate',
|
||||
'file:/opt/jugendhackt_tools/config.toml',
|
||||
'file:/usr/local/lib/systemd/system/jugendhackt_tools.service',
|
||||
'git_deploy:/opt/jugendhackt_tools/src',
|
||||
'user:jugendhackt_tools',
|
||||
},
|
||||
}
|
||||
|
||||
users['jugendhackt_tools'] = {
|
||||
'home': '/opt/jugendhackt_tools/src',
|
||||
}
|
28
bundles/jugendhackt_tools/metadata.py
Normal file
28
bundles/jugendhackt_tools/metadata.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
defaults = {
|
||||
'jugendhackt_tools': {
|
||||
'django_secret': repo.vault.random_bytes_as_base64_for(f'{node.name} jugendhackt_tools django_secret'),
|
||||
'django_debug': False,
|
||||
'static_root': '/opt/jugendhackt_tools/src/static/',
|
||||
'database': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'jugendhackt_tools',
|
||||
'USER': 'jugendhackt_tools',
|
||||
'PASSWORD': repo.vault.password_for(f'{node.name} postgresql jugendhackt_tools'),
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432'
|
||||
},
|
||||
},
|
||||
'postgresql': {
|
||||
'roles': {
|
||||
'jugendhackt_tools': {
|
||||
'password': repo.vault.password_for(f'{node.name} postgresql jugendhackt_tools'),
|
||||
},
|
||||
},
|
||||
'databases': {
|
||||
'jugendhackt_tools': {
|
||||
'owner': 'jugendhackt_tools',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
37
bundles/kea-dhcp-server/files/kea-lease-list
Normal file
37
bundles/kea-dhcp-server/files/kea-lease-list
Normal file
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from csv import DictReader
|
||||
from datetime import datetime, timezone
|
||||
from os import scandir
|
||||
from os.path import join
|
||||
|
||||
|
||||
def parse():
|
||||
NOW = datetime.now()
|
||||
active_leases = {}
|
||||
for file in scandir("/var/lib/kea/"):
|
||||
with open(file.path) as f:
|
||||
for row in DictReader(f):
|
||||
expires = datetime.fromtimestamp(int(row["expire"]))
|
||||
|
||||
if expires >= NOW:
|
||||
if (
|
||||
row["address"] not in active_leases
|
||||
or active_leases[row["address"]]["expires_dt"] < expires
|
||||
):
|
||||
row["expires_dt"] = expires
|
||||
active_leases[row["address"]] = row
|
||||
return active_leases.values()
|
||||
|
||||
|
||||
def print_table(leases):
|
||||
print(""" address | MAC | expires | hostname
|
||||
-----------------+-------------------+---------+----------""")
|
||||
for lease in sorted(leases, key=lambda r: r["address"]):
|
||||
print(
|
||||
f' {lease["address"]:<15} | {lease["hwaddr"].lower()} | {lease["expires_dt"]:%H:%M} | {lease["hostname"]}'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print_table(parse())
|
56
bundles/kea-dhcp-server/items.py
Normal file
56
bundles/kea-dhcp-server/items.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
kea_config = {
|
||||
'Dhcp4': {
|
||||
**node.metadata.get('kea-dhcp-server/config'),
|
||||
'interfaces-config': {
|
||||
'interfaces': sorted(node.metadata.get('kea-dhcp-server/subnets', {}).keys()),
|
||||
},
|
||||
'subnet4': [],
|
||||
'loggers': [{
|
||||
'name': 'kea-dhcp4',
|
||||
'output_options': [{
|
||||
# -> journal
|
||||
'output': 'stdout',
|
||||
}],
|
||||
'severity': 'WARN',
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
||||
for iface, config in sorted(node.metadata.get('kea-dhcp-server/subnets', {}).items()):
|
||||
kea_config['Dhcp4']['subnet4'].append({
|
||||
'subnet': config['subnet'],
|
||||
'pools': [{
|
||||
'pool': f'{config["lower"]} - {config["higher"]}',
|
||||
}],
|
||||
'option-data': [
|
||||
{
|
||||
'name': k,
|
||||
'data': v,
|
||||
} for k, v in sorted(config.get('options', {}).items())
|
||||
],
|
||||
'reservations': [
|
||||
{
|
||||
'ip-address': v['ip'],
|
||||
'hw-address': v['mac'],
|
||||
'hostname': k,
|
||||
} for k, v in sorted(node.metadata.get(f'kea-dhcp-server/fixed_allocations/{iface}', {}).items())
|
||||
]
|
||||
})
|
||||
|
||||
files['/etc/kea/kea-dhcp4.conf'] = {
|
||||
'content': repo.libs.faults.dict_as_json(kea_config),
|
||||
'triggers': {
|
||||
'svc_systemd:kea-dhcp4-server:restart',
|
||||
},
|
||||
}
|
||||
|
||||
files['/usr/local/bin/kea-lease-list'] = {
|
||||
'mode': '0500',
|
||||
}
|
||||
|
||||
svc_systemd['kea-dhcp4-server'] = {
|
||||
'needs': {
|
||||
'file:/etc/kea/kea-dhcp4.conf',
|
||||
'pkg_apt:kea-dhcp4-server',
|
||||
},
|
||||
}
|
83
bundles/kea-dhcp-server/metadata.py
Normal file
83
bundles/kea-dhcp-server/metadata.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
from ipaddress import ip_address, ip_network
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'kea-dhcp4-server': {},
|
||||
},
|
||||
},
|
||||
'kea-dhcp-server': {
|
||||
'config': {
|
||||
'authoritative': True,
|
||||
'rebind-timer': 450,
|
||||
'renew-timer': 300,
|
||||
'valid-lifetime': 600,
|
||||
'expired-leases-processing': {
|
||||
'max-reclaim-leases': 0,
|
||||
'max-reclaim-time': 0,
|
||||
},
|
||||
'lease-database': {
|
||||
'lfc-interval': 3600,
|
||||
'name': '/var/lib/kea/kea-leases4.csv',
|
||||
'persist': True,
|
||||
'type': 'memfile',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'kea-dhcp-server/fixed_allocations',
|
||||
)
|
||||
def get_static_allocations(metadata):
|
||||
result = {}
|
||||
mapping = {}
|
||||
|
||||
for iface, config in metadata.get('kea-dhcp-server/subnets', {}).items():
|
||||
result[iface] = {}
|
||||
mapping[iface] = ip_network(config['subnet'])
|
||||
|
||||
for rnode in repo.nodes:
|
||||
if (
|
||||
rnode.metadata.get('location', '') != metadata.get('location', '')
|
||||
or rnode == node
|
||||
):
|
||||
continue
|
||||
|
||||
for iface_name, iface_config in rnode.metadata.get('interfaces', {}).items():
|
||||
if iface_config.get('dhcp', False) and iface_config.get('mac'):
|
||||
for ip in iface_config.get('ips', set()):
|
||||
ipaddr = ip_address(ip)
|
||||
|
||||
for kea_iface, kea_subnet in mapping.items():
|
||||
if ipaddr in kea_subnet:
|
||||
result[kea_iface][f'{rnode.name}_{iface_name}'] = {
|
||||
'ip': ip,
|
||||
'mac': iface_config['mac'],
|
||||
}
|
||||
break
|
||||
|
||||
return {
|
||||
'kea-dhcp-server': {
|
||||
'fixed_allocations': result,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nftables/input/10-kea-dhcp-server',
|
||||
)
|
||||
def nftables(metadata):
|
||||
rules = set()
|
||||
for iface in node.metadata.get('kea-dhcp-server/subnets', {}):
|
||||
rules.add(f'udp dport {{ 67, 68 }} iifname {iface} accept')
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'input': {
|
||||
# can't use port_rules here, because we're generating interface based rules.
|
||||
'10-kea-dhcp-server': sorted(rules),
|
||||
},
|
||||
}
|
||||
}
|
8
bundles/kernel-modules/files/modules
Normal file
8
bundles/kernel-modules/files/modules
Normal file
|
@ -0,0 +1,8 @@
|
|||
# This file is managed using bundlewrap
|
||||
% for identifier, modules in sorted(node.metadata.get('modules', {}).items()):
|
||||
|
||||
# ${identifier}
|
||||
% for module in modules:
|
||||
${module}
|
||||
% endfor
|
||||
% endfor
|
3
bundles/kernel-modules/items.py
Normal file
3
bundles/kernel-modules/items.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
files['/etc/modules'] = {
|
||||
'content_type': 'mako',
|
||||
}
|
|
@ -43,15 +43,15 @@ defaults = {
|
|||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules/8080',
|
||||
'firewall/port_rules/9090',
|
||||
'firewall/port_rules',
|
||||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'8080': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||
'9090': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||
'8080/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||
'9090/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ def cron(metadata):
|
|||
'/usr/bin/dehydrated --cleanup',
|
||||
],
|
||||
'when': '04:{}:00'.format(node.magic_number % 60),
|
||||
'exclude_from_monitoring': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
directories = {
|
||||
if node.os != 'routeros':
|
||||
directories = {
|
||||
'/etc/lldpd.d': {
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'svc_systemd:lldpd:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
files = {
|
||||
files = {
|
||||
'/etc/lldpd.conf': {
|
||||
'delete': True,
|
||||
},
|
||||
|
@ -17,12 +18,12 @@ files = {
|
|||
'svc_systemd:lldpd:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
svc_systemd = {
|
||||
'lldpd': {
|
||||
'needs': {
|
||||
'file:/etc/lldpd.d/bundlewrap.conf',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue