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]
|
[*.vault]
|
||||||
end_of_line = unset
|
end_of_line = unset
|
||||||
insert_final_newline = 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') {
|
stage('config and metadata determinism') {
|
||||||
steps {
|
steps {
|
||||||
sh """
|
sh """
|
||||||
|
|
|
@ -30,13 +30,13 @@ Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
||||||
| 20010 | mautrix-telegram | Bridge |
|
| 20010 | mautrix-telegram | Bridge |
|
||||||
| 20020 | mautrix-whatsapp | Bridge |
|
| 20020 | mautrix-whatsapp | Bridge |
|
||||||
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
||||||
|
| 20070 | matrix-synapse | sliding-sync |
|
||||||
| 20080 | matrix-synapse | client, federation |
|
| 20080 | matrix-synapse | client, federation |
|
||||||
| 20081 | matrix-synapse | prometheus metrics |
|
| 20081 | matrix-synapse | prometheus metrics |
|
||||||
| 20090 | matrix-media-repo | media_repo |
|
| 20090 | matrix-media-repo | media_repo |
|
||||||
| 20090 | matrix-media-repo | prometheus metrics |
|
| 20090 | matrix-media-repo | prometheus metrics |
|
||||||
| 21000 | pleroma | pleroma |
|
|
||||||
| 21010 | grafana | grafana |
|
| 21010 | grafana | grafana |
|
||||||
| 22000 | gitea | forgejo |
|
| 22000 | forgejo | forgejo |
|
||||||
| 22010 | jenkins-ci | Jenkins CI |
|
| 22010 | jenkins-ci | Jenkins CI |
|
||||||
| 22020 | travelynx | Travelynx Web |
|
| 22020 | travelynx | Travelynx Web |
|
||||||
| 22030 | octoprint | OctoPrint Web Interface |
|
| 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 |
|
| 22060 | pretalx | gunicorn |
|
||||||
| 22070 | paperless-ng | gunicorn |
|
| 22070 | paperless-ng | gunicorn |
|
||||||
| 22080 | netbox | gunicorn |
|
| 22080 | netbox | gunicorn |
|
||||||
| 22100 | woodpecker-server | http |
|
| 22090 | jugendhackt_tools | gunicorn |
|
||||||
| 22101 | woodpecker-server | gRPC |
|
| 22100 | powerdnsadmin | gunicorn |
|
||||||
|
| 22110 | icinga2-statuspage | gunicorn |
|
||||||
| 22999 | nginx | stub_status |
|
| 22999 | nginx | stub_status |
|
||||||
| 22100 | ntfy | http |
|
| 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.
|
`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/)
|
[![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 -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 autoremove
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -y -q clean
|
||||||
|
|
||||||
% if clean_old_kernels:
|
% if clean_old_kernels:
|
||||||
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
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
|
# Workaround, because /var/tmp is usually 1777
|
||||||
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
[[ "$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" ]]
|
if [[ -n "$logins" ]]
|
||||||
then
|
then
|
||||||
echo "Will abort now, there are active SSH logins: $logins"
|
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" ]]
|
if [[ -f /var/run/reboot-required ]] && [[ "$auto_reboot_enabled" == "True" ]]
|
||||||
then
|
then
|
||||||
if [[ -n "$reboot_mail_to" ]]
|
|
||||||
then
|
|
||||||
date | mail -s "SYSREBOOTNOW $nodename" "$reboot_mail_to"
|
|
||||||
fi
|
|
||||||
systemctl reboot
|
systemctl reboot
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
nodename="${node.name}"
|
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)}"
|
auto_reboot_enabled="${node.metadata.get('apt/unattended-upgrades/reboot_enabled', True)}"
|
||||||
|
|
|
@ -4,11 +4,9 @@ supported_os = {
|
||||||
'debian': {
|
'debian': {
|
||||||
10: 'buster',
|
10: 'buster',
|
||||||
11: 'bullseye',
|
11: 'bullseye',
|
||||||
|
12: 'bookworm',
|
||||||
99: 'unstable',
|
99: 'unstable',
|
||||||
},
|
},
|
||||||
'raspbian': {
|
|
||||||
10: 'buster',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -26,6 +24,10 @@ actions = {
|
||||||
'triggered': True,
|
'triggered': True,
|
||||||
'cascade_skip': False,
|
'cascade_skip': False,
|
||||||
},
|
},
|
||||||
|
'apt_execute_update_commands': {
|
||||||
|
'command': ' && '.join(sorted(node.metadata.get('apt/additional_update_commands', {'true'}))),
|
||||||
|
'triggered': True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
|
@ -113,7 +115,7 @@ pkg_apt = {
|
||||||
'mtr': {},
|
'mtr': {},
|
||||||
'ncdu': {},
|
'ncdu': {},
|
||||||
'ncurses-term': {},
|
'ncurses-term': {},
|
||||||
'netcat': {},
|
'netcat-openbsd': {},
|
||||||
'nmap': {},
|
'nmap': {},
|
||||||
'python3': {},
|
'python3': {},
|
||||||
'python3-dev': {},
|
'python3-dev': {},
|
||||||
|
@ -152,6 +154,9 @@ pkg_apt = {
|
||||||
'popularity-contest': {
|
'popularity-contest': {
|
||||||
'installed': False,
|
'installed': False,
|
||||||
},
|
},
|
||||||
|
'python3-packaging': {
|
||||||
|
'installed': False,
|
||||||
|
},
|
||||||
'unattended-upgrades': {
|
'unattended-upgrades': {
|
||||||
'installed': False,
|
'installed': False,
|
||||||
},
|
},
|
||||||
|
@ -168,6 +173,7 @@ if node.os_version[0] >= 11:
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, data in node.metadata.get('apt/repos', {}).items():
|
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||||
|
if 'items' in data:
|
||||||
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'content': ("\n".join(sorted(data['items']))).format(
|
'content': ("\n".join(sorted(data['items']))).format(
|
||||||
|
@ -178,8 +184,30 @@ for name, data in node.metadata.get('apt/repos', {}).items():
|
||||||
'action:apt_update',
|
'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 data.get('install_gpg_key', True):
|
||||||
|
if 'items' in data:
|
||||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,24 @@ defaults = {
|
||||||
'cron/jobs/upgrade-and-reboot'
|
'cron/jobs/upgrade-and-reboot'
|
||||||
)
|
)
|
||||||
def patchday(metadata):
|
def patchday(metadata):
|
||||||
|
if not node.metadata.get('apt/unattended-upgrades/enabled', True):
|
||||||
|
return {}
|
||||||
|
|
||||||
day = metadata.get('apt/unattended-upgrades/day')
|
day = metadata.get('apt/unattended-upgrades/day')
|
||||||
hour = metadata.get('apt/unattended-upgrades/hour')
|
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 {
|
return {
|
||||||
'cron': {
|
'cron': {
|
||||||
'jobs': {
|
'jobs': {
|
||||||
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||||
minute=node.magic_number % 30,
|
minute=node.magic_number % 30,
|
||||||
hour=hour,
|
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 = {
|
svc_systemd = {
|
||||||
|
'avahi-daemon': {
|
||||||
|
'needs': {
|
||||||
|
'pkg_pacman:avahi',
|
||||||
|
},
|
||||||
|
},
|
||||||
'sddm': {
|
'sddm': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'pkg_pacman:sddm',
|
'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')):
|
for filename in listdir(join(repo.path, 'data', 'arch-with-gui', 'files', 'fonts')):
|
||||||
if filename.startswith('.'):
|
if filename.startswith('.'):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -9,6 +9,14 @@ defaults = {
|
||||||
'icinga_options': {
|
'icinga_options': {
|
||||||
'exclude_from_monitoring': True,
|
'exclude_from_monitoring': True,
|
||||||
},
|
},
|
||||||
|
'nftables': {
|
||||||
|
'input': {
|
||||||
|
'50-avahi': {
|
||||||
|
'udp dport 5353 accept',
|
||||||
|
'udp sport 5353 accept',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
'pacman': {
|
'pacman': {
|
||||||
'packages': {
|
'packages': {
|
||||||
# fonts
|
# fonts
|
||||||
|
@ -23,8 +31,9 @@ defaults = {
|
||||||
'sddm': {},
|
'sddm': {},
|
||||||
|
|
||||||
# networking
|
# networking
|
||||||
|
'avahi': {},
|
||||||
'netctl': {},
|
'netctl': {},
|
||||||
'rfkill': {},
|
'util-linux': {}, # provides rfkill
|
||||||
'wpa_supplicant': {},
|
'wpa_supplicant': {},
|
||||||
'wpa_actiond': {},
|
'wpa_actiond': {},
|
||||||
|
|
||||||
|
@ -45,6 +54,7 @@ defaults = {
|
||||||
'pipewire': {},
|
'pipewire': {},
|
||||||
'pipewire-jack': {},
|
'pipewire-jack': {},
|
||||||
'pipewire-pulse': {},
|
'pipewire-pulse': {},
|
||||||
|
'pipewire-zeroconf': {},
|
||||||
'qpwgraph': {},
|
'qpwgraph': {},
|
||||||
|
|
||||||
# window management
|
# window management
|
||||||
|
|
|
@ -62,10 +62,13 @@ trap "on_exit" EXIT
|
||||||
|
|
||||||
# redirect stdout and stderr to logfile
|
# redirect stdout and stderr to logfile
|
||||||
prepare_and_cleanup_logdir
|
prepare_and_cleanup_logdir
|
||||||
|
if [[ -z "$DEBUG" ]]
|
||||||
|
then
|
||||||
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
||||||
echo "All log output will go to $logfile" | logger -it backup-client
|
echo "All log output will go to $logfile" | logger -it backup-client
|
||||||
exec > >(gzip >"$logfile")
|
exec > >(gzip >"$logfile")
|
||||||
exec 2>&1
|
exec 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
# this is where the real work starts
|
# this is where the real work starts
|
||||||
ts_begin=$(date +%s)
|
ts_begin=$(date +%s)
|
||||||
|
|
|
@ -19,12 +19,12 @@ else:
|
||||||
|
|
||||||
if node.metadata.get('backups/exclude_from_backups', False):
|
if node.metadata.get('backups/exclude_from_backups', False):
|
||||||
# make sure nobody tries to do something funny
|
# make sure nobody tries to do something funny
|
||||||
for file in [
|
for file in {
|
||||||
'/etc/backup.priv',
|
'/etc/backup.priv',
|
||||||
'/usr/local/bin/generate-backup',
|
'/usr/local/bin/generate-backup',
|
||||||
'/usr/local/bin/generate-backup-with-retries',
|
'/usr/local/bin/generate-backup-with-retries',
|
||||||
'/var/tmp/backup.monitoring', # status file
|
'/var/tmp/backup.monitoring', # status file
|
||||||
]:
|
}:
|
||||||
files[file] = {
|
files[file] = {
|
||||||
'delete': True,
|
'delete': True,
|
||||||
}
|
}
|
||||||
|
@ -33,14 +33,17 @@ else:
|
||||||
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
||||||
|
|
||||||
files['/etc/backup.priv'] = {
|
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',
|
'mode': '0400',
|
||||||
}
|
}
|
||||||
|
|
||||||
files['/usr/local/bin/generate-backup'] = {
|
files['/usr/local/bin/generate-backup'] = {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'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'),
|
'server': backup_target.metadata.get('backup-server/my_hostname'),
|
||||||
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
||||||
'paths': backup_paths,
|
'paths': backup_paths,
|
||||||
|
|
|
@ -27,9 +27,6 @@ directories['/etc/backup-server/clients'] = {
|
||||||
sudoers = {}
|
sudoers = {}
|
||||||
|
|
||||||
for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
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
|
sudoers[config['user']] = nodename
|
||||||
|
|
||||||
users[config['user']] = {
|
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'] = {
|
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
|
||||||
'content': pubkey,
|
'content': repo.libs.ssh.generate_ed25519_public_key(
|
||||||
|
config['user'],
|
||||||
|
node,
|
||||||
|
),
|
||||||
'owner': config['user'],
|
'owner': config['user'],
|
||||||
'mode': '0400',
|
'mode': '0400',
|
||||||
'needs': {
|
'needs': {
|
||||||
|
|
|
@ -35,8 +35,15 @@ def get_my_clients(metadata):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
my_clients[rnode.name] = {
|
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),
|
'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24),
|
||||||
|
'user': rnode.metadata.get('backup-client/user-name'),
|
||||||
'retain': {
|
'retain': {
|
||||||
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
||||||
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
||||||
|
@ -153,7 +160,7 @@ def monitoring(metadata):
|
||||||
client,
|
client,
|
||||||
config['one_backup_every_hours'],
|
config['one_backup_every_hours'],
|
||||||
),
|
),
|
||||||
'vars.sshmon_timeout': 20,
|
'vars.sshmon_timeout': 40,
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -32,8 +32,8 @@ account_guest_in_cpu_meter=0
|
||||||
color_scheme=0
|
color_scheme=0
|
||||||
enable_mouse=0
|
enable_mouse=0
|
||||||
delay=10
|
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
|
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
|
right_meter_modes=2 3 1
|
||||||
hide_function_bar=0
|
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()
|
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'] = {
|
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
||||||
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
||||||
'unless': f"grep -e '^{locale}' /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'}
|
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
||||||
|
|
||||||
actions = {
|
actions['locale-gen'] = {
|
||||||
'locale-gen': {
|
|
||||||
'triggered': True,
|
'triggered': True,
|
||||||
'command': 'locale-gen',
|
'command': 'locale-gen',
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
description = []
|
description = []
|
||||||
|
|
||||||
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
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'):
|
if node.has_bundle('telegraf'):
|
||||||
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
||||||
|
|
|
@ -19,7 +19,9 @@ protocol static {
|
||||||
ipv4;
|
ipv4;
|
||||||
|
|
||||||
% for route in sorted(node.metadata.get('bird/static_routes', set())):
|
% 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
|
% endfor
|
||||||
}
|
}
|
||||||
% endif
|
% endif
|
||||||
|
|
|
@ -24,7 +24,7 @@ defaults = {
|
||||||
},
|
},
|
||||||
'sysctl': {
|
'sysctl': {
|
||||||
'options': {
|
'options': {
|
||||||
'net.ipv4.ip_forward': '1',
|
'net.ipv4.conf.all.forwarding': '1',
|
||||||
'net.ipv6.conf.all.forwarding': '1',
|
'net.ipv6.conf.all.forwarding': '1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -43,6 +43,9 @@ def neighbor_info_from_wireguard(metadata):
|
||||||
except NoSuchNode:
|
except NoSuchNode:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not rnode.has_bundle('bird'):
|
||||||
|
continue
|
||||||
|
|
||||||
neighbors[name] = {
|
neighbors[name] = {
|
||||||
'local_ip': config['my_ip'],
|
'local_ip': config['my_ip'],
|
||||||
'local_as': my_as,
|
'local_as': my_as,
|
||||||
|
@ -62,7 +65,10 @@ def neighbor_info_from_wireguard(metadata):
|
||||||
)
|
)
|
||||||
def my_ip(metadata):
|
def my_ip(metadata):
|
||||||
if node.has_bundle('wireguard'):
|
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:
|
else:
|
||||||
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
||||||
|
|
||||||
|
@ -84,7 +90,7 @@ def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
'179': atomic(sources),
|
'179/tcp': atomic(sources),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,19 @@
|
||||||
from bundlewrap.exceptions import BundleError
|
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 = {
|
CONFLICTING_BUNDLES = {
|
||||||
'apt',
|
'apt',
|
||||||
'nginx',
|
'nginx',
|
||||||
|
@ -57,6 +71,18 @@ actions = {
|
||||||
'svc_systemd:',
|
'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 = {
|
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():
|
for crontab, content in node.metadata.get('cron/jobs', {}).items():
|
||||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||||
'source': 'cron_template',
|
'source': 'cron_template',
|
||||||
|
|
|
@ -17,7 +17,7 @@ files = {
|
||||||
directories = {
|
directories = {
|
||||||
'/etc/cron.d': {
|
'/etc/cron.d': {
|
||||||
'purge': True,
|
'purge': True,
|
||||||
'needs': {
|
'after': {
|
||||||
'pkg_apt:',
|
'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
|
default_pass_scheme = MD5-CRYPT
|
||||||
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = true
|
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
|
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
|
mail_location = maildir:/var/mail/vmail/%d/%n
|
||||||
protocols = imap lmtp sieve
|
protocols = imap lmtp sieve
|
||||||
|
|
||||||
ssl = yes
|
ssl = required
|
||||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/fullchain.pem
|
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/fullchain.pem
|
||||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/privkey.pem
|
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname')}/privkey.pem
|
||||||
ssl_dh = </etc/dovecot/ssl/dhparam.pem
|
ssl_dh = </etc/ssl/certs/dhparam.pem
|
||||||
ssl_min_protocol = TLSv1.2
|
ssl_min_protocol = TLSv1.2
|
||||||
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
|
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 = yes
|
ssl_prefer_server_ciphers = no
|
||||||
|
|
||||||
login_greeting = IMAPd ready
|
login_greeting = IMAPd ready
|
||||||
auth_mechanisms = plain login
|
auth_mechanisms = plain login
|
||||||
first_valid_uid = 65534
|
first_valid_uid = 65534
|
||||||
disable_plaintext_auth = yes
|
disable_plaintext_auth = yes
|
||||||
mail_plugins = $mail_plugins zlib old_stats
|
mail_plugins = $mail_plugins zlib old_stats fts fts_xapian
|
||||||
|
|
||||||
plugin {
|
plugin {
|
||||||
zlib_save_level = 6
|
zlib_save_level = 6
|
||||||
|
@ -56,6 +56,15 @@ plugin {
|
||||||
old_stats_refresh = 30 secs
|
old_stats_refresh = 30 secs
|
||||||
old_stats_track_cmds = yes
|
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'):
|
% if node.has_bundle('rspamd'):
|
||||||
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
||||||
|
|
||||||
|
@ -86,14 +95,19 @@ service auth {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
service lmtp {
|
service decode2text {
|
||||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
executable = script /usr/lib/dovecot/decode2text.sh
|
||||||
group = postfix
|
user = dovecot
|
||||||
mode = 0600
|
unix_listener decode2text {
|
||||||
user = postfix
|
mode = 0666
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service indexer-worker {
|
||||||
|
vsz_limit = 0
|
||||||
|
process_limit = 0
|
||||||
|
}
|
||||||
|
|
||||||
service imap {
|
service imap {
|
||||||
executable = imap
|
executable = imap
|
||||||
}
|
}
|
||||||
|
@ -104,6 +118,14 @@ service imap-login {
|
||||||
vsz_limit = 64M
|
vsz_limit = 64M
|
||||||
}
|
}
|
||||||
|
|
||||||
|
service lmtp {
|
||||||
|
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||||
|
group = postfix
|
||||||
|
mode = 0600
|
||||||
|
user = postfix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
service managesieve-login {
|
service managesieve-login {
|
||||||
inet_listener sieve {
|
inet_listener sieve {
|
||||||
port = 4190
|
port = 4190
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
# by this bundle
|
# by this bundle
|
||||||
repo.libs.tools.require_bundle(node, 'postfix')
|
repo.libs.tools.require_bundle(node, 'postfix')
|
||||||
|
|
||||||
directories = {
|
|
||||||
'/etc/dovecot/ssl': {},
|
|
||||||
}
|
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/etc/dovecot/dovecot.conf': {
|
'/etc/dovecot/dovecot.conf': {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
|
@ -49,25 +45,17 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actions = {
|
symlinks['/usr/lib/dovecot/decode2text.sh'] = {
|
||||||
'dovecot_generate_dhparam': {
|
'target': '/usr/share/doc/dovecot-core/examples/decode2text.sh',
|
||||||
'command': 'openssl dhparam -out /etc/dovecot/ssl/dhparam.pem 2048',
|
'before': {
|
||||||
'unless': 'test -f /etc/dovecot/ssl/dhparam.pem',
|
'svc_systemd:dovecot',
|
||||||
'cascade_skip': False,
|
|
||||||
'needs': {
|
|
||||||
'directory:/etc/dovecot/ssl',
|
|
||||||
'pkg_apt:'
|
|
||||||
},
|
|
||||||
'triggers': {
|
|
||||||
'svc_systemd:dovecot:restart',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'dovecot': {
|
'dovecot': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:dovecot_generate_dhparam',
|
'action:generate-dhparam',
|
||||||
'file:/etc/dovecot/dovecot.conf',
|
'file:/etc/dovecot/dovecot.conf',
|
||||||
'file:/etc/dovecot/dovecot-sql.conf',
|
'file:/etc/dovecot/dovecot-sql.conf',
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@ from bundlewrap.metadata import atomic
|
||||||
defaults = {
|
defaults = {
|
||||||
'apt': {
|
'apt': {
|
||||||
'packages': {
|
'packages': {
|
||||||
|
'dovecot-fts-xapian': {},
|
||||||
'dovecot-imapd': {},
|
'dovecot-imapd': {},
|
||||||
'dovecot-lmtpd': {},
|
'dovecot-lmtpd': {},
|
||||||
'dovecot-managesieved': {},
|
'dovecot-managesieved': {},
|
||||||
|
@ -35,6 +36,16 @@ defaults = {
|
||||||
'dovecot',
|
'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'):
|
if node.has_bundle('postfixadmin'):
|
||||||
|
@ -76,19 +87,19 @@ def import_database_settings_from_postfixadmin(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'firewall/port_rules/143',
|
'firewall/port_rules',
|
||||||
'firewall/port_rules/993',
|
'firewall/port_rules',
|
||||||
'firewall/port_rules/4190',
|
'firewall/port_rules',
|
||||||
)
|
)
|
||||||
def firewall(metadata):
|
def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
# imap(s)
|
# imap(s)
|
||||||
'143': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'143/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
'993': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'993/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
# managesieve
|
# managesieve
|
||||||
'4190': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
'4190/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ directories = {
|
||||||
|
|
||||||
git_deploy = {
|
git_deploy = {
|
||||||
'/opt/element-web': {
|
'/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',
|
'repo': 'https://github.com/vector-im/element-web.git',
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'action:element-web_yarn',
|
'action:element-web_yarn',
|
||||||
|
@ -18,28 +18,22 @@ git_deploy = {
|
||||||
|
|
||||||
files = {
|
files = {
|
||||||
'/opt/element-web/webapp/config.json': {
|
'/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': {
|
'needs': {
|
||||||
'action:element-web_yarn',
|
'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 = {
|
actions = {
|
||||||
'element-web_yarn': {
|
'element-web_yarn': {
|
||||||
'command': ' && '.join([
|
'command': ' && '.join([
|
||||||
*extra_install_cmds,
|
|
||||||
'cd /opt/element-web',
|
'cd /opt/element-web',
|
||||||
'yarn install --pure-lockfile --ignore-scripts',
|
'yarn install --pure-lockfile --ignore-scripts',
|
||||||
'yarn build',
|
'yarn build',
|
||||||
]),
|
]),
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:nodejs_install_yarn',
|
'action:apt_execute_update_commands',
|
||||||
'pkg_apt:nodejs',
|
'pkg_apt:nodejs',
|
||||||
},
|
},
|
||||||
'triggered': True,
|
'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(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/element-web',
|
'nginx/vhosts/element-web',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
APP_NAME = ${app_name}
|
APP_NAME = ${app_name}
|
||||||
RUN_USER = git
|
RUN_USER = git
|
||||||
RUN_MODE = prod
|
RUN_MODE = prod
|
||||||
|
WORK_PATH = /var/lib/forgejo
|
||||||
|
|
||||||
[repository]
|
[repository]
|
||||||
ROOT = /home/git/gitea-repositories
|
ROOT = /var/lib/forgejo/repositories
|
||||||
MAX_CREATION_LIMIT = 0
|
MAX_CREATION_LIMIT = 0
|
||||||
DEFAULT_BRANCH = main
|
DEFAULT_BRANCH = main
|
||||||
|
|
|
@ -5,14 +5,13 @@ After=network.target
|
||||||
Requires=postgresql.service
|
Requires=postgresql.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
RestartSec=2s
|
RestartSec=10
|
||||||
Type=simple
|
Type=simple
|
||||||
User=git
|
User=git
|
||||||
Group=git
|
Group=git
|
||||||
WorkingDirectory=/var/lib/gitea/
|
WorkingDirectory=/var/lib/forgejo
|
||||||
ExecStart=/usr/local/bin/gitea web -c /etc/gitea/app.ini
|
ExecStart=/usr/local/bin/forgejo web -c /etc/forgejo/app.ini
|
||||||
Restart=always
|
Restart=always
|
||||||
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
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 = {
|
defaults = {
|
||||||
'backups': {
|
'backups': {
|
||||||
'paths': {
|
'paths': {
|
||||||
'/home/git',
|
'/var/lib/forgejo',
|
||||||
'/var/lib/gitea',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'gitea': {
|
'forgejo': {
|
||||||
'app_name': 'Forgejo',
|
'app_name': 'Forgejo',
|
||||||
'database': {
|
'database': {
|
||||||
'username': 'gitea',
|
'username': 'forgejo',
|
||||||
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||||
'database': 'gitea',
|
'database': 'forgejo',
|
||||||
},
|
},
|
||||||
'disable_registration': True,
|
'disable_registration': True,
|
||||||
'email_domain_blocklist': set(),
|
'email_domain_blocklist': set(),
|
||||||
'enable_git_hooks': False,
|
'enable_git_hooks': False,
|
||||||
'internal_token': repo.vault.password_for('{} gitea internal_token'.format(node.name)),
|
'internal_token': repo.vault.password_for('{} forgejo internal_token'.format(node.name)),
|
||||||
'lfs_secret_key': repo.vault.password_for('{} gitea lfs_secret_key'.format(node.name)),
|
'lfs_secret_key': repo.vault.password_for('{} forgejo lfs_secret_key'.format(node.name)),
|
||||||
'oauth_secret_key': repo.vault.password_for('{} gitea oauth_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('{} gitea security_secret_key'.format(node.name)),
|
'security_secret_key': repo.vault.password_for('{} forgejo security_secret_key'.format(node.name)),
|
||||||
},
|
},
|
||||||
'icinga2_api': {
|
'icinga2_api': {
|
||||||
'gitea': {
|
'forgejo': {
|
||||||
'services': {
|
'services': {
|
||||||
'FORGEJO PROCESS': {
|
'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': {
|
'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,
|
'vars.notification.mail': True,
|
||||||
'check_interval': '60m',
|
'check_interval': '60m',
|
||||||
},
|
},
|
||||||
|
@ -41,29 +39,39 @@ defaults = {
|
||||||
},
|
},
|
||||||
'postgresql': {
|
'postgresql': {
|
||||||
'roles': {
|
'roles': {
|
||||||
'gitea': {
|
'forgejo': {
|
||||||
'password': repo.vault.password_for('{} postgresql gitea'.format(node.name)),
|
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'databases': {
|
'databases': {
|
||||||
'gitea': {
|
'forgejo': {
|
||||||
'owner': 'gitea',
|
'owner': 'forgejo',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'zfs': {
|
'zfs': {
|
||||||
'datasets': {
|
'datasets': {
|
||||||
'tank/gitea': {},
|
'tank/forgejo': {
|
||||||
'tank/gitea/home': {
|
'mountpoint': '/var/lib/forgejo',
|
||||||
'mountpoint': '/home/git',
|
|
||||||
'needed_by': {
|
'needed_by': {
|
||||||
'directory:/home/git',
|
'directory:/var/lib/forgejo',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'tank/gitea/var': {
|
},
|
||||||
'mountpoint': '/var/lib/gitea',
|
},
|
||||||
'needed_by': {
|
}
|
||||||
'directory:/var/lib/gitea',
|
|
||||||
|
|
||||||
|
@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')),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -82,7 +90,7 @@ def nginx(metadata):
|
||||||
'nginx': {
|
'nginx': {
|
||||||
'vhosts': {
|
'vhosts': {
|
||||||
'forgejo': {
|
'forgejo': {
|
||||||
'domain': metadata.get('gitea/domain'),
|
'domain': metadata.get('forgejo/domain'),
|
||||||
'locations': {
|
'locations': {
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:22000',
|
'target': 'http://127.0.0.1:22000',
|
||||||
|
@ -92,16 +100,8 @@ def nginx(metadata):
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'website_check_path': '/user/login',
|
'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',
|
'renderer': 'flot',
|
||||||
'seriesOverrides': [],
|
'seriesOverrides': [],
|
||||||
'spaceLength': 10,
|
'spaceLength': 10,
|
||||||
'span': 8,
|
'span': 12,
|
||||||
'stack': False,
|
'stack': False,
|
||||||
'steppedLine': False,
|
'steppedLine': False,
|
||||||
'targets': [
|
'targets': [
|
||||||
|
@ -114,115 +114,5 @@ def dashboard_row_smartd(panel_id, node):
|
||||||
'alignLevel': None
|
'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': {
|
'locations': {
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:21010',
|
'target': 'http://127.0.0.1:21010',
|
||||||
|
'websockets': True,
|
||||||
},
|
},
|
||||||
'/api/ds/query': {
|
'/api/ds/query': {
|
||||||
'target': 'http://127.0.0.1:21010',
|
'target': 'http://127.0.0.1:21010',
|
||||||
|
|
|
@ -33,7 +33,11 @@ ProtectSystem=strict
|
||||||
ProtectHome=true
|
ProtectHome=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
SystemCallArchitectures=native
|
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
|
# You may have to adjust these settings
|
||||||
User=hedgedoc
|
User=hedgedoc
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from semver import compare
|
||||||
|
|
||||||
repo.libs.tools.require_bundle(node, 'nodejs')
|
repo.libs.tools.require_bundle(node, 'nodejs')
|
||||||
|
|
||||||
git_deploy = {
|
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 = {
|
actions = {
|
||||||
'hedgedoc_yarn': {
|
'hedgedoc_yarn': {
|
||||||
'command': ' && '.join([
|
'command': ' && '.join([
|
||||||
'cd /opt/hedgedoc',
|
'cd /opt/hedgedoc',
|
||||||
'yarn install --production=true --pure-lockfile --ignore-scripts',
|
'yarn install --immutable',
|
||||||
'yarn install --ignore-scripts',
|
|
||||||
'yarn build',
|
'yarn build',
|
||||||
]),
|
]),
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:nodejs_install_yarn',
|
|
||||||
'file:/opt/hedgedoc/config.json',
|
'file:/opt/hedgedoc/config.json',
|
||||||
'git_deploy:/opt/hedgedoc',
|
'git_deploy:/opt/hedgedoc',
|
||||||
'pkg_apt:nodejs',
|
'pkg_apt:nodejs',
|
||||||
|
|
|
@ -2,48 +2,42 @@
|
||||||
|
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
import requests
|
from packaging.version import parse
|
||||||
from packaging import version
|
from requests import get
|
||||||
|
|
||||||
bearer = "${bearer}"
|
API_TOKEN = "${token}"
|
||||||
domain = "${domain}"
|
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"})
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stable_version = version.parse(
|
r = get("https://version.home-assistant.io/stable.json")
|
||||||
s.get("https://version.home-assistant.io/stable.json").json()["homeassistant"][
|
r.raise_for_status()
|
||||||
"generic-x86-64"
|
stable_version = parse(r.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}"
|
|
||||||
except Exception as e:
|
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)
|
try:
|
||||||
exit(status)
|
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]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
User=homeassistant
|
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
|
WorkingDirectory=/var/opt/homeassistant
|
||||||
ExecStart=/opt/homeassistant/venv/bin/hass -c "/var/opt/homeassistant"
|
ExecStart=/opt/homeassistant/venv/bin/hass -c "/var/opt/homeassistant"
|
||||||
RestartForceExitStatus=100
|
RestartForceExitStatus=100
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
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 = {
|
users = {
|
||||||
'homeassistant': {
|
'homeassistant': {
|
||||||
'home': '/var/opt/homeassistant',
|
'home': '/var/opt/homeassistant',
|
||||||
|
"groups": ["dialout"],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +30,7 @@ files = {
|
||||||
'/usr/local/share/icinga/plugins/check_homeassistant_update': {
|
'/usr/local/share/icinga/plugins/check_homeassistant_update': {
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'context': {
|
||||||
'bearer': repo.vault.decrypt(node.metadata.get('homeassistant/api_secret')),
|
'token': node.metadata.get('homeassistant/api_secret'),
|
||||||
'domain': node.metadata.get('homeassistant/domain'),
|
'domain': node.metadata.get('homeassistant/domain'),
|
||||||
},
|
},
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
@ -32,11 +39,18 @@ files = {
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
'homeassistant_create_virtualenv': {
|
'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/',
|
'unless': 'test -d /opt/homeassistant/venv/',
|
||||||
'needs': {
|
'needs': {
|
||||||
'directory:/opt/homeassistant',
|
'directory:/opt/homeassistant',
|
||||||
'user: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:bluez',
|
||||||
'pkg_apt:libffi-dev',
|
'pkg_apt:libffi-dev',
|
||||||
'pkg_apt:libssl-dev',
|
'pkg_apt:libssl-dev',
|
||||||
|
@ -45,17 +59,10 @@ actions = {
|
||||||
'pkg_apt:autoconf',
|
'pkg_apt:autoconf',
|
||||||
'pkg_apt:build-essential',
|
'pkg_apt:build-essential',
|
||||||
'pkg_apt:libopenjp2-7',
|
'pkg_apt:libopenjp2-7',
|
||||||
'pkg_apt:libtiff5',
|
'pkg_apt:libtiff6',
|
||||||
'pkg_apt:libturbojpeg0-dev',
|
'pkg_apt:libturbojpeg0-dev',
|
||||||
'pkg_apt:tzdata',
|
'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': {
|
'triggers': {
|
||||||
'svc_systemd:homeassistant:restart',
|
'svc_systemd:homeassistant:restart',
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,11 +4,12 @@ defaults = {
|
||||||
'autoconf': {},
|
'autoconf': {},
|
||||||
'bluez': {},
|
'bluez': {},
|
||||||
'build-essential': {},
|
'build-essential': {},
|
||||||
|
'ffmpeg': {},
|
||||||
'libffi-dev': {},
|
'libffi-dev': {},
|
||||||
'libjpeg-dev': {},
|
'libjpeg-dev': {},
|
||||||
'libopenjp2-7': {},
|
'libopenjp2-7': {},
|
||||||
'libssl-dev': {},
|
'libssl-dev': {},
|
||||||
'libtiff5': {},
|
'libtiff6': {},
|
||||||
'libturbojpeg0-dev': {},
|
'libturbojpeg0-dev': {},
|
||||||
'python3-packaging': {},
|
'python3-packaging': {},
|
||||||
'tzdata': {},
|
'tzdata': {},
|
||||||
|
@ -22,6 +23,8 @@ defaults = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'icinga2_api/homeassistant/services',
|
'icinga2_api/homeassistant/services',
|
||||||
)
|
)
|
||||||
|
@ -31,15 +34,17 @@ def icinga_check_for_new_release(metadata):
|
||||||
'homeassistant': {
|
'homeassistant': {
|
||||||
'services': {
|
'services': {
|
||||||
'HOMEASSISTANT UPDATE': {
|
'HOMEASSISTANT UPDATE': {
|
||||||
|
'check_interval': '60m',
|
||||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_homeassistant_update',
|
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_homeassistant_update',
|
||||||
'vars.notification.mail': True,
|
'vars.notification.mail': True,
|
||||||
'check_interval': '60m',
|
'vars.sshmon_timeout': 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
'nginx/vhosts/homeassistant',
|
'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
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from json import load
|
||||||
from sys import exit
|
from sys import exit
|
||||||
|
|
||||||
from requests import get
|
from requests import get
|
||||||
|
|
||||||
SIPGATE_USER = '${node.metadata['icinga2']['sipgate_user']}'
|
with open('/etc/icinga2/notification_config.json') as f:
|
||||||
SIPGATE_PASS = '${node.metadata['icinga2']['sipgate_pass']}'
|
CONFIG = load(f)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = get(
|
r = get(
|
||||||
'https://api.sipgate.com/v2/balance',
|
'https://api.sipgate.com/v2/balance',
|
||||||
auth=(SIPGATE_USER, SIPGATE_PASS),
|
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
||||||
headers={'Accept': 'application/json'},
|
headers={'Accept': 'application/json'},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -5,30 +5,33 @@ from ipaddress import IPv6Address, ip_address
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from sys import argv, exit
|
from sys import argv, exit
|
||||||
|
|
||||||
BLOCKLISTS = [
|
BLOCKLISTS = {
|
||||||
'0spam.fusionzero.com',
|
'0spam.fusionzero.com': set(),
|
||||||
'bl.mailspike.org',
|
'bl.mailspike.org': set(),
|
||||||
'bl.spamcop.net',
|
'bl.spamcop.net': set(),
|
||||||
'blackholes.brainerd.net',
|
'blackholes.brainerd.net': set(),
|
||||||
'dnsbl-1.uceprotect.net',
|
'dnsbl-1.uceprotect.net': set(),
|
||||||
'dnsbl-2.uceprotect.net',
|
'l2.spews.dnsbl.sorbs.net': set(),
|
||||||
'l2.spews.dnsbl.sorbs.net',
|
'list.dsbl.org': set(),
|
||||||
'list.dsbl.org',
|
'multihop.dsbl.org': set(),
|
||||||
'map.spam-rbl.com',
|
'ns1.unsubscore.com': set(),
|
||||||
'multihop.dsbl.org',
|
'opm.blitzed.org': set(),
|
||||||
'ns1.unsubscore.com',
|
'psbl.surriel.com': set(),
|
||||||
'opm.blitzed.org',
|
'rbl.efnet.org': set(),
|
||||||
'psbl.surriel.com',
|
'rbl.schulte.org': set(),
|
||||||
'rbl.efnet.org',
|
'spamguard.leadmon.net': set(),
|
||||||
'rbl.schulte.org',
|
'ubl.unsubscore.com': set(),
|
||||||
'spamguard.leadmon.net',
|
'unconfirmed.dsbl.org': set(),
|
||||||
'ubl.unsubscore.com',
|
'virbl.dnsbl.bit.nl': set(),
|
||||||
'unconfirmed.dsbl.org',
|
'zen.spamhaus.org': {
|
||||||
'virbl.dnsbl.bit.nl',
|
# https://www.spamhaus.org/news/article/807/using-our-public-mirrors-check-your-return-codes-now.
|
||||||
'zen.spamhaus.org',
|
'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(
|
dns_name = '{}.{}'.format(
|
||||||
'.'.join(ip_list),
|
'.'.join(ip_list),
|
||||||
blocklist,
|
blocklist,
|
||||||
|
@ -41,16 +44,21 @@ def check_list(ip_list, blocklist):
|
||||||
result = check_output([
|
result = check_output([
|
||||||
'dig',
|
'dig',
|
||||||
'+tries=2',
|
'+tries=2',
|
||||||
'+time=5',
|
'+time=10',
|
||||||
'+short',
|
'+short',
|
||||||
dns_name
|
dns_name
|
||||||
]).decode().splitlines()
|
]).decode().splitlines()
|
||||||
for item in result:
|
for item in result:
|
||||||
|
if item.startswith(';;'):
|
||||||
|
continue
|
||||||
msgs.append('{} listed in {} as {}'.format(
|
msgs.append('{} listed in {} as {}'.format(
|
||||||
ip,
|
ip,
|
||||||
blocklist,
|
blocklist,
|
||||||
item,
|
item,
|
||||||
))
|
))
|
||||||
|
if item in warn_ips and returncode < 2:
|
||||||
|
returncode = 1
|
||||||
|
else:
|
||||||
returncode = 2
|
returncode = 2
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if e.returncode == 9:
|
if e.returncode == 9:
|
||||||
|
@ -78,8 +86,8 @@ exitcode = 0
|
||||||
with ThreadPoolExecutor(max_workers=len(BLOCKLISTS)) as executor:
|
with ThreadPoolExecutor(max_workers=len(BLOCKLISTS)) as executor:
|
||||||
futures = set()
|
futures = set()
|
||||||
|
|
||||||
for blocklist in BLOCKLISTS:
|
for blocklist, warn_ips in BLOCKLISTS.items():
|
||||||
futures.add(executor.submit(check_list, ip_list, blocklist))
|
futures.add(executor.submit(check_list, ip_list, blocklist, warn_ips))
|
||||||
|
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
msgs, this_exitcode = future.result()
|
msgs, this_exitcode = future.result()
|
||||||
|
|
|
@ -1,31 +1,18 @@
|
||||||
% for monitored_node in sorted(repo.nodes):
|
% for dt in downtimes:
|
||||||
<%
|
object ScheduledDowntime "${dt['name']}" {
|
||||||
auto_updates_enabled = (
|
host_name = "${dt['host']}"
|
||||||
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}"
|
|
||||||
|
|
||||||
author = "unattended-upgrades"
|
author = "${dt['name']}"
|
||||||
comment = "Downtime for upgrade-and-reboot of node ${monitored_node.name}"
|
comment = "${dt['comment']}"
|
||||||
|
|
||||||
fixed = true
|
fixed = true
|
||||||
|
|
||||||
ranges = {
|
ranges = {
|
||||||
% if monitored_node.has_bundle('pacman'):
|
% for d,t in dt['times'].items():
|
||||||
"${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}"
|
"${d}" = "${t}"
|
||||||
% else:
|
% endfor
|
||||||
"${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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
child_options = "DowntimeTriggeredChildren"
|
child_options = "DowntimeTriggeredChildren"
|
||||||
}
|
}
|
||||||
% endif
|
|
||||||
% endfor
|
% endfor
|
||||||
|
|
|
@ -33,3 +33,11 @@ object ServiceGroup "checks_with_sms" {
|
||||||
assign where service.vars.notification.sms == true
|
assign where service.vars.notification.sms == true
|
||||||
ignore where host.vars.notification.sms == false
|
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}"
|
vars.os = "${rnode.os}"
|
||||||
|
|
||||||
# used for status page
|
# 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')}"
|
vars.period = "${rnode.metadata.get('icinga_options/period', '24x7')}"
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,11 @@ app = Flask(__name__)
|
||||||
@app.route('/status')
|
@app.route('/status')
|
||||||
def statuspage():
|
def statuspage():
|
||||||
everything_fine = True
|
everything_fine = True
|
||||||
|
try:
|
||||||
|
check_output(['/usr/local/share/icinga/plugins/check_mounts'])
|
||||||
|
except:
|
||||||
|
everything_fine = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
check_output(['/usr/lib/nagios/plugins/check_procs', '-C', 'icinga2', '-c', '1:'])
|
check_output(['/usr/lib/nagios/plugins/check_procs', '-C', 'icinga2', '-c', '1:'])
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -3,8 +3,6 @@ Description=Icinga2 Statusmonitor
|
||||||
After=network.target
|
After=network.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
User=nagios
|
|
||||||
Group=nagios
|
|
||||||
Environment="FLASK_APP=/etc/icinga2/icinga_statusmonitor.py"
|
Environment="FLASK_APP=/etc/icinga2/icinga_statusmonitor.py"
|
||||||
ExecStart=/usr/bin/python3 -m flask run
|
ExecStart=/usr/bin/python3 -m flask run
|
||||||
WorkingDirectory=/tmp
|
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 email.mime.text
|
||||||
import smtplib
|
import smtplib
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from json import dumps
|
from json import dumps, load
|
||||||
from subprocess import run
|
from subprocess import run
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
|
||||||
from requests import post
|
from requests import post
|
||||||
|
|
||||||
SIPGATE_USER='${node.metadata['icinga2']['sipgate_user']}'
|
with open('/etc/icinga2/notification_config.json') as f:
|
||||||
SIPGATE_PASS='${node.metadata['icinga2']['sipgate_pass']}'
|
CONFIG = load(f)
|
||||||
|
|
||||||
STATUS_TO_EMOJI = {
|
|
||||||
'critical': '🔥',
|
|
||||||
'down': '🚨🚨🚨',
|
|
||||||
'ok': '🆗',
|
|
||||||
'up': '👌',
|
|
||||||
'warning': '⚡',
|
|
||||||
}
|
|
||||||
|
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog='icinga_notification_wrapper',
|
prog='icinga_notification_wrapper',
|
||||||
|
@ -73,36 +65,31 @@ def notify_per_sms():
|
||||||
output_text = ''
|
output_text = ''
|
||||||
else:
|
else:
|
||||||
output_text = '\n\n{}'.format(args.output)
|
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(
|
message_text = 'ICINGA: {host}{service} is {state}{output}'.format(
|
||||||
host=args.host_name,
|
host=args.host_name,
|
||||||
service=('/'+args.service_name if args.service_name else ''),
|
service=('/'+args.service_name if args.service_name else ''),
|
||||||
state=args.state.upper(),
|
state=args.state.upper(),
|
||||||
output=output_text,
|
output=output_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
message = {
|
message = {
|
||||||
'message': message_text,
|
'message': message_text,
|
||||||
'smsId': 's0', # XXX what does this mean? Documentation is unclear
|
'smsId': 's0', # XXX what does this mean? Documentation is unclear
|
||||||
'recipient': args.sms
|
'recipient': args.sms
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Content-type': 'application/json',
|
'Content-type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = post(
|
r = post(
|
||||||
'https://api.sipgate.com/v2/sessions/sms',
|
'https://api.sipgate.com/v2/sessions/sms',
|
||||||
json=message,
|
json=message,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=(SIPGATE_USER, SIPGATE_PASS),
|
auth=(CONFIG['sipgate']['user'], CONFIG['sipgate']['password']),
|
||||||
)
|
)
|
||||||
|
|
||||||
if r.status_code == 204:
|
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)))
|
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():
|
def notify_per_mail():
|
||||||
if args.notification_type.lower() == 'recovery':
|
if args.notification_type.lower() == 'recovery':
|
||||||
# Do not send recovery emails.
|
# Do not send recovery emails.
|
||||||
|
@ -176,4 +199,7 @@ if __name__ == '__main__':
|
||||||
notify_per_mail()
|
notify_per_mail()
|
||||||
|
|
||||||
if args.sms:
|
if args.sms:
|
||||||
|
if not args.service_name:
|
||||||
notify_per_sms()
|
notify_per_sms()
|
||||||
|
if CONFIG['ntfy']['user']:
|
||||||
|
notify_per_ntfy()
|
||||||
|
|
|
@ -76,8 +76,6 @@ files = {
|
||||||
},
|
},
|
||||||
'/usr/local/share/icinga/plugins/check_sipgate_account_balance': {
|
'/usr/local/share/icinga/plugins/check_sipgate_account_balance': {
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
'content_type': 'mako',
|
|
||||||
'cascade_skip': False, # contains faults
|
|
||||||
},
|
},
|
||||||
'/usr/local/share/icinga/plugins/check_freifunk_node': {
|
'/usr/local/share/icinga/plugins/check_freifunk_node': {
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
|
@ -114,11 +112,22 @@ files = {
|
||||||
'svc_systemd:icinga2:restart',
|
'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': {
|
'/etc/icinga2/scripts/icinga_notification_wrapper': {
|
||||||
'source': 'scripts/icinga_notification_wrapper',
|
'source': 'scripts/icinga_notification_wrapper',
|
||||||
'content_type': 'mako',
|
|
||||||
'mode': '0755',
|
'mode': '0755',
|
||||||
'cascade_skip': False, # contains faults
|
|
||||||
},
|
},
|
||||||
'/etc/icinga2/features-available/ido-pgsql.conf': {
|
'/etc/icinga2/features-available/ido-pgsql.conf': {
|
||||||
'source': 'icinga2/ido-pgsql.conf',
|
'source': 'icinga2/ido-pgsql.conf',
|
||||||
|
@ -245,6 +254,11 @@ files = {
|
||||||
'mode': '0660',
|
'mode': '0660',
|
||||||
'group': 'icingaweb2',
|
'group': 'icingaweb2',
|
||||||
},
|
},
|
||||||
|
'/etc/icingaweb2/modules/monitoring/config.ini': {
|
||||||
|
'source': 'icingaweb2/monitoring_config.ini',
|
||||||
|
'mode': '0660',
|
||||||
|
'group': 'icingaweb2',
|
||||||
|
},
|
||||||
'/etc/icingaweb2/groups.ini': {
|
'/etc/icingaweb2/groups.ini': {
|
||||||
'source': 'icingaweb2/groups.ini',
|
'source': 'icingaweb2/groups.ini',
|
||||||
'mode': '0660',
|
'mode': '0660',
|
||||||
|
@ -262,13 +276,13 @@ files = {
|
||||||
'group': 'icingaweb2',
|
'group': 'icingaweb2',
|
||||||
},
|
},
|
||||||
|
|
||||||
# Statusmonitor
|
# monitoring
|
||||||
'/etc/icinga2/icinga_statusmonitor.py': {
|
'/etc/icinga2/icinga_statusmonitor.py': {
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:icinga_statusmonitor:restart',
|
'svc_systemd:icinga_statusmonitor:restart',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'/etc/systemd/system/icinga_statusmonitor.service': {
|
'/usr/local/lib/systemd/system/icinga_statusmonitor.service': {
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'action:systemd-reload',
|
'action:systemd-reload',
|
||||||
'svc_systemd:icinga_statusmonitor:restart',
|
'svc_systemd:icinga_statusmonitor:restart',
|
||||||
|
@ -276,8 +290,12 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg_pip = {
|
svc_systemd['icinga_statusmonitor'] = {
|
||||||
'easysnmp': {}, # for check_usv_snmp
|
'needs': {
|
||||||
|
'file:/etc/icinga2/icinga_statusmonitor.py',
|
||||||
|
'file:/usr/local/lib/systemd/system/icinga_statusmonitor.service',
|
||||||
|
'pkg_apt:python3-flask',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actions = {
|
actions = {
|
||||||
|
@ -319,36 +337,22 @@ for name in files:
|
||||||
for name in symlinks:
|
for name in symlinks:
|
||||||
icinga_run_deps.add(f'symlink:{name}')
|
icinga_run_deps.add(f'symlink:{name}')
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd['icinga2'] = {
|
||||||
'icinga2': {
|
|
||||||
'needs': icinga_run_deps,
|
'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
|
# The actual hosts and services management starts here
|
||||||
bundles = set()
|
bundles = set()
|
||||||
for rnode in repo.nodes:
|
downtimes = []
|
||||||
|
for rnode in sorted(repo.nodes):
|
||||||
if rnode.metadata.get('icinga_options/exclude_from_monitoring', False):
|
if rnode.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||||
continue
|
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 = {}
|
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_type in ('ipv4', 'ipv6'):
|
||||||
for ip in sorted(host_ips[ip_type]):
|
for ip in sorted(host_ips[ip_type]):
|
||||||
if ip.is_private and not ip.is_link_local:
|
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())
|
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'] = {
|
files['/etc/icinga2/conf.d/groups.conf'] = {
|
||||||
'source': 'icinga2/groups.conf',
|
'source': 'icinga2/groups.conf',
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
|
@ -399,7 +438,7 @@ files['/etc/icinga2/conf.d/downtimes.conf'] = {
|
||||||
'source': 'icinga2/downtimes.conf',
|
'source': 'icinga2/downtimes.conf',
|
||||||
'content_type': 'mako',
|
'content_type': 'mako',
|
||||||
'context': {
|
'context': {
|
||||||
'days': DAYS_TO_STRING,
|
'downtimes': downtimes,
|
||||||
},
|
},
|
||||||
'owner': 'nagios',
|
'owner': 'nagios',
|
||||||
'group': 'nagios',
|
'group': 'nagios',
|
||||||
|
|
|
@ -17,12 +17,9 @@ defaults = {
|
||||||
'icinga2': {},
|
'icinga2': {},
|
||||||
'icinga2-ido-pgsql': {},
|
'icinga2-ido-pgsql': {},
|
||||||
'icingaweb2': {},
|
'icingaweb2': {},
|
||||||
|
'python3-easysnmp': {},
|
||||||
# apparently no longer needed
|
|
||||||
#'icingaweb2-module-monitoring': {},
|
|
||||||
|
|
||||||
# neeeded for statusmonitor
|
|
||||||
'python3-flask': {},
|
'python3-flask': {},
|
||||||
|
'snmp': {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'icinga2': {
|
'icinga2': {
|
||||||
|
@ -43,9 +40,6 @@ defaults = {
|
||||||
'check_interval': '30m',
|
'check_interval': '30m',
|
||||||
'vars.notification.mail': True,
|
'vars.notification.mail': True,
|
||||||
},
|
},
|
||||||
'ICINGA STATUSMONITOR': {
|
|
||||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit icinga_statusmonitor',
|
|
||||||
},
|
|
||||||
'IDO-PGSQL': {
|
'IDO-PGSQL': {
|
||||||
'check_command': 'ido',
|
'check_command': 'ido',
|
||||||
'vars.ido_type': 'IdoPgsqlConnection',
|
'vars.ido_type': 'IdoPgsqlConnection',
|
||||||
|
@ -59,6 +53,21 @@ defaults = {
|
||||||
'icingaweb2': {
|
'icingaweb2': {
|
||||||
'setup-token': repo.vault.password_for(f'{node.name} icingaweb2 setup-token'),
|
'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': {
|
'postgresql': {
|
||||||
'roles': {
|
'roles': {
|
||||||
'icinga2': {
|
'icinga2': {
|
||||||
|
@ -105,13 +114,29 @@ def add_users_from_json(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@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 {
|
return {
|
||||||
'firewall': {
|
'nginx': {
|
||||||
'port_rules': {
|
'vhosts': {
|
||||||
'5665': atomic(metadata.get('icinga2/restrict-to', set())),
|
'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': {
|
'repos': {
|
||||||
'influxdb': {
|
'influxdb': {
|
||||||
'items': {
|
'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
|
Requires=infobeamer-cms.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
Environment=SETTINGS=/opt/infobeamer-cms/settings.toml
|
||||||
|
WorkingDirectory=/opt/infobeamer-cms/src
|
||||||
User=infobeamer-cms
|
User=infobeamer-cms
|
||||||
Group=infobeamer-cms
|
Group=infobeamer-cms
|
||||||
WorkingDirectory=/opt/infobeamer-cms
|
ExecStart=/opt/infobeamer-cms/venv/bin/python syncer.py
|
||||||
ExecStart=curl -s -H "Host: ${domain}" http://127.0.0.1:8000/sync
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Description=Run infobeamer-cms sync
|
Description=Run infobeamer-cms sync
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnCalendar=*:0/5
|
OnCalendar=minutely
|
||||||
Persistent=true
|
Persistent=true
|
||||||
|
|
||||||
[Install]
|
[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 = {
|
actions = {
|
||||||
'infobeamer-cms_set_directory_permissions': {
|
|
||||||
'triggered': True,
|
|
||||||
'command': 'chown -R infobeamer-cms:infobeamer-cms /opt/infobeamer-cms/src/static/'
|
|
||||||
},
|
|
||||||
'infobeamer-cms_create_virtualenv': {
|
'infobeamer-cms_create_virtualenv': {
|
||||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/infobeamer-cms/venv/',
|
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/infobeamer-cms/venv/',
|
||||||
'unless': 'test -d /opt/infobeamer-cms/venv/',
|
'unless': 'test -d /opt/infobeamer-cms/venv/',
|
||||||
|
@ -12,7 +8,11 @@ actions = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'infobeamer-cms_install_requirements': {
|
'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': {
|
'needs': {
|
||||||
'action:infobeamer-cms_create_virtualenv',
|
'action:infobeamer-cms_create_virtualenv',
|
||||||
},
|
},
|
||||||
|
@ -23,13 +23,12 @@ actions = {
|
||||||
git_deploy = {
|
git_deploy = {
|
||||||
'/opt/infobeamer-cms/src': {
|
'/opt/infobeamer-cms/src': {
|
||||||
'rev': 'master',
|
'rev': 'master',
|
||||||
'repo': 'https://github.com/sophieschi/36c3-cms.git',
|
'repo': 'https://github.com/voc/infobeamer-cms.git',
|
||||||
'needs': {
|
'needs': {
|
||||||
'directory:/opt/infobeamer-cms/src',
|
'directory:/opt/infobeamer-cms/src',
|
||||||
},
|
},
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:infobeamer-cms:restart',
|
'svc_systemd:infobeamer-cms:restart',
|
||||||
'action:infobeamer-cms_set_directory_permissions',
|
|
||||||
'action:infobeamer-cms_install_requirements',
|
'action:infobeamer-cms_install_requirements',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -37,6 +36,9 @@ git_deploy = {
|
||||||
|
|
||||||
directories = {
|
directories = {
|
||||||
'/opt/infobeamer-cms/src': {},
|
'/opt/infobeamer-cms/src': {},
|
||||||
|
'/opt/infobeamer-cms/static': {
|
||||||
|
'owner': 'infobeamer-cms',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
config = node.metadata.get('infobeamer-cms/config', {})
|
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 = {
|
files = {
|
||||||
'/opt/infobeamer-cms/settings.toml': {
|
'/opt/infobeamer-cms/settings.toml': {
|
||||||
'content_type': 'mako',
|
'content': repo.libs.faults.dict_as_toml(config),
|
||||||
'context': {
|
|
||||||
'config': config,
|
|
||||||
},
|
|
||||||
'triggers': {
|
'triggers': {
|
||||||
'svc_systemd:infobeamer-cms:restart',
|
'svc_systemd:infobeamer-cms:restart',
|
||||||
},
|
},
|
||||||
|
@ -97,19 +96,11 @@ files = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg_pip = {
|
|
||||||
'github-flask': {
|
|
||||||
'needed_by': {
|
|
||||||
'svc_systemd:infobeamer-cms',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
svc_systemd = {
|
svc_systemd = {
|
||||||
'infobeamer-cms': {
|
'infobeamer-cms': {
|
||||||
'needs': {
|
'needs': {
|
||||||
'action:infobeamer-cms_install_requirements',
|
'action:infobeamer-cms_install_requirements',
|
||||||
'action:infobeamer-cms_set_directory_permissions',
|
'directory:/opt/infobeamer-cms/static',
|
||||||
'file:/etc/systemd/system/infobeamer-cms.service',
|
'file:/etc/systemd/system/infobeamer-cms.service',
|
||||||
'file:/opt/infobeamer-cms/settings.toml',
|
'file:/opt/infobeamer-cms/settings.toml',
|
||||||
'git_deploy:/opt/infobeamer-cms/src',
|
'git_deploy:/opt/infobeamer-cms/src',
|
||||||
|
@ -117,8 +108,12 @@ svc_systemd = {
|
||||||
},
|
},
|
||||||
'infobeamer-cms-runperiodic.timer': {
|
'infobeamer-cms-runperiodic.timer': {
|
||||||
'needs': {
|
'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.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,
|
'MAX_UPLOADS': 5,
|
||||||
'PREFERRED_URL_SCHEME': 'https',
|
'PREFERRED_URL_SCHEME': 'https',
|
||||||
'SESSION_COOKIE_NAME': '__Host-sess',
|
'SESSION_COOKIE_NAME': '__Host-sess',
|
||||||
|
'STATIC_PATH': '/opt/infobeamer-cms/static',
|
||||||
'URL_KEY': repo.vault.password_for(f'{node.name} infobeamer-cms url key'),
|
'URL_KEY': repo.vault.password_for(f'{node.name} infobeamer-cms url key'),
|
||||||
'VERSION': 1,
|
'VERSION': 1,
|
||||||
},
|
},
|
||||||
|
@ -29,15 +30,13 @@ def nginx(metadata):
|
||||||
'/': {
|
'/': {
|
||||||
'target': 'http://127.0.0.1:8000',
|
'target': 'http://127.0.0.1:8000',
|
||||||
},
|
},
|
||||||
'/sync': {
|
|
||||||
'return': 403,
|
|
||||||
},
|
|
||||||
'/static': {
|
'/static': {
|
||||||
'alias': '/opt/infobeamer-cms/src/static',
|
'alias': '/opt/infobeamer-cms/static',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'website_check_path': '/',
|
'website_check_path': '/',
|
||||||
'website_check_string': 'Share your projects',
|
'website_check_string': 'Share your projects',
|
||||||
|
'do_not_set_content_security_headers': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -45,6 +44,7 @@ def nginx(metadata):
|
||||||
|
|
||||||
|
|
||||||
@metadata_reactor.provides(
|
@metadata_reactor.provides(
|
||||||
|
'infobeamer-cms/config/DOMAIN',
|
||||||
'infobeamer-cms/config/TIME_MAX',
|
'infobeamer-cms/config/TIME_MAX',
|
||||||
'infobeamer-cms/config/TIME_MIN',
|
'infobeamer-cms/config/TIME_MIN',
|
||||||
)
|
)
|
||||||
|
@ -57,6 +57,7 @@ def event_times(metadata):
|
||||||
return {
|
return {
|
||||||
'infobeamer-cms': {
|
'infobeamer-cms': {
|
||||||
'config': {
|
'config': {
|
||||||
|
'DOMAIN': metadata.get('infobeamer-cms/domain'),
|
||||||
'TIME_MAX': int(event_end.timestamp()),
|
'TIME_MAX': int(event_end.timestamp()),
|
||||||
'TIME_MIN': int(event_start.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(
|
@metadata_reactor.provides(
|
||||||
'firewall/port_rules/8080',
|
'firewall/port_rules',
|
||||||
'firewall/port_rules/9090',
|
'firewall/port_rules',
|
||||||
)
|
)
|
||||||
def firewall(metadata):
|
def firewall(metadata):
|
||||||
return {
|
return {
|
||||||
'firewall': {
|
'firewall': {
|
||||||
'port_rules': {
|
'port_rules': {
|
||||||
'8080': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
'8080/tcp': atomic(metadata.get('kodi/restrict-to', {'*'})),
|
||||||
'9090': 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',
|
'/usr/bin/dehydrated --cleanup',
|
||||||
],
|
],
|
||||||
'when': '04:{}:00'.format(node.magic_number % 60),
|
'when': '04:{}:00'.format(node.magic_number % 60),
|
||||||
|
'exclude_from_monitoring': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
if node.os != 'routeros':
|
||||||
directories = {
|
directories = {
|
||||||
'/etc/lldpd.d': {
|
'/etc/lldpd.d': {
|
||||||
'purge': True,
|
'purge': True,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue