Compare commits
2495 commits
loudness-m
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
6cb56ab2ec | ||
|
5c4fc37a37 | ||
|
68d51450fd | ||
|
d57844928d | ||
|
4975562fbc | ||
|
25e03582b0 | ||
|
b49dc56c33 | ||
|
4122a7ccf8 | ||
|
429bc2a7c6 | ||
|
6f9fb78d4e | ||
|
bb1b430d16 | ||
|
1906e7c256 | ||
|
7dcad0d584 | ||
|
077b25f67e | ||
|
527181bba8 | ||
|
53e189c644 | ||
|
eeceebfd23 | ||
|
7bd8237876 | ||
|
55bebda4d4 | ||
|
ef16a2d081 | ||
|
264ea3e8a7 | ||
|
109914c039 | ||
|
8df4441028 | ||
|
733e4bf0e5 | ||
|
6cec7e2c9c | ||
|
f6b0c587d0 | ||
|
a8e2e6b5ad | ||
|
17aee0f6bb | ||
|
a3218ac41f | ||
|
932fd9e994 | ||
|
2e6e6b663e | ||
|
74d44535a8 | ||
|
cb2b01a2b4 | ||
|
9684e94e4d | ||
|
c93a4d0a99 | ||
|
31e614ab3b | ||
|
60585a3716 | ||
|
c717e86f70 | ||
|
ff8928dd0b | ||
|
ba97cd432f | ||
|
f45a759a43 | ||
|
b4b3fec8a7 | ||
|
1899dfc278 | ||
|
d8aa1e80d0 | ||
|
e634c184c0 | ||
|
07dce73bca | ||
|
c5ccc31ad9 | ||
|
ab76721ddb | ||
|
b460085bb0 | ||
|
ba3bf20db7 | ||
|
5ed4c1e9bd | ||
|
446e0d057e | ||
![]() |
e393f3cc3c | ||
![]() |
7ee2d08007 | ||
|
c94aef55a5 | ||
|
970d97b0a2 | ||
|
c04ce63c35 | ||
|
070b466abe | ||
|
82143e34ad | ||
|
9dae384cd1 | ||
![]() |
3019ee4355 | ||
![]() |
52983a51a9 | ||
|
638363e927 | ||
|
9a45e3c30e | ||
|
648a80362e | ||
|
e28494e9a0 | ||
|
931f3cd583 | ||
|
face47b9fe | ||
|
df303b3487 | ||
![]() |
edeffee5c2 | ||
![]() |
107fd6872b | ||
![]() |
c407a4520a | ||
|
fcb546baf6 | ||
|
f2e4d9e731 | ||
|
0c402791a9 | ||
|
e67033db8c | ||
|
c5e45cbafc | ||
|
120dfea24f | ||
|
3db7168589 | ||
|
90823b7984 | ||
|
caa54051ff | ||
|
8621c726bb | ||
|
a008a47559 | ||
|
97486a6e68 | ||
|
ee761507a2 | ||
|
6d49889f2f | ||
|
8a0ae4fa10 | ||
|
eaab905735 | ||
|
e12b9e6c12 | ||
|
191b74a6d3 | ||
|
195b87a457 | ||
|
bd64d52edb | ||
|
d6da47fc1e | ||
|
5dc60eb24e | ||
|
f790e2cee0 | ||
|
9c8523ab49 | ||
|
1189b58cd2 | ||
|
afa1e4abb7 | ||
|
c8dd809057 | ||
|
e8d24bc363 | ||
|
86990b92ea | ||
|
bed37a8392 | ||
|
1b3c789b7c | ||
|
d86dc32f51 | ||
|
15ea875742 | ||
|
615f6107bc | ||
|
dd420c4574 | ||
|
5d239177bc | ||
|
48514f0e61 | ||
|
ca18a8d231 | ||
|
6b2a6e64b3 | ||
|
98d6c117df | ||
|
d64657feef | ||
|
b7de8b3a4d | ||
|
bc589011d2 | ||
|
f3d741de41 | ||
![]() |
0033d6309f | ||
![]() |
24bfc8c7bd | ||
|
72cbe56b5f | ||
|
29dd1ee561 | ||
|
84f5dc65c0 | ||
![]() |
cad4037a38 | ||
|
2ea914dc0b | ||
|
7a14084417 | ||
|
019d5ce2b6 | ||
|
cb1222600d | ||
|
7f9d7ccfc3 | ||
|
52e36f7ae1 | ||
|
e6e563fb33 | ||
|
3dce0fb6ac | ||
|
fba5d6a782 | ||
|
a1b7006cdc | ||
|
b0a43fb128 | ||
|
545d107baf | ||
|
cea68687d3 | ||
|
73e6ba0872 | ||
|
ee9f7b8875 | ||
|
aa01329dbf | ||
|
e1fe424df9 | ||
|
8b1ccb760a | ||
|
fdfd12683f | ||
|
c7b27bd724 | ||
|
40cec956a9 | ||
|
3db6078d9b | ||
|
1bed137116 | ||
![]() |
16fb0fd1fa | ||
![]() |
bd3c92aac9 | ||
![]() |
014c7e5be8 | ||
![]() |
6e23f84a39 | ||
![]() |
a8cf858d44 | ||
|
deba777a80 | ||
|
2cecb14112 | ||
|
7fdaa3c26e | ||
|
6ac1583a23 | ||
|
2095696131 | ||
|
f42597260d | ||
|
8c77e5824a | ||
|
82ed8e1d3a | ||
|
98864c2d55 | ||
|
c907f6d1e7 | ||
|
06906f715b | ||
|
afd306a23a | ||
|
988d7e08a0 | ||
|
b642153e58 | ||
|
86742758c7 | ||
|
79bfef9c35 | ||
|
edaa22dab6 | ||
|
038f19ea5e | ||
|
d9be69d3a9 | ||
|
f004591e98 | ||
|
944c66354b | ||
|
0f6b757a34 | ||
|
95ebed94dc | ||
![]() |
70c7ab7b3a | ||
|
2b29a9727c | ||
|
00dddba445 | ||
|
a1bea394f2 | ||
|
2fd94c2a4b | ||
|
b84cd780b3 | ||
|
aa5c7ff8b4 | ||
|
0113b9a565 | ||
|
24ce6487bd | ||
|
89eddad1a3 | ||
|
65d65187f1 | ||
|
2a15887fb6 | ||
|
84917649dd | ||
|
97126f443e | ||
|
08bc40827d | ||
|
6b641890c3 | ||
|
f6d6ef7aa7 | ||
|
3714880406 | ||
|
2a041629af | ||
|
416599abab | ||
|
a1ce8bf91a | ||
|
5539957eb6 | ||
|
9036458aa8 | ||
|
86cc900d74 | ||
|
1f32a92296 | ||
|
0d13e60a21 | ||
|
feeb785425 | ||
|
3e65a185f6 | ||
|
2a938d19f1 | ||
|
696377cfbc | ||
|
9736576d0d | ||
|
dd5a97eced | ||
|
ac0f849871 | ||
|
5111e0c897 | ||
|
4f89345c8c | ||
|
53cce20d68 | ||
|
7b922841b7 | ||
|
ce670e4d1e | ||
![]() |
076b19f8fb | ||
|
4b7f6faac2 | ||
|
8312910588 | ||
|
1d58955ced | ||
|
8446e8eda2 | ||
|
c34f3ebbc3 | ||
|
313f43f66b | ||
|
29f64946b1 | ||
|
064b4c5f36 | ||
![]() |
aeb8467e5e | ||
|
d6bb5e973e | ||
|
51c9506a19 | ||
|
9730a2be13 | ||
|
bd5f65d7a4 | ||
|
303ca11c2e | ||
|
1c247bc4bd | ||
|
3614c23ef8 | ||
|
b86251f79c | ||
|
a98190139d | ||
|
15b97d0edd | ||
|
8cc116c0c4 | ||
|
12e21d893f | ||
|
3d2a97851b | ||
|
b9da0221bc | ||
|
3cdd6ce6ba | ||
|
8414e3580b | ||
|
7fffdbab32 | ||
|
a38b66a7c5 | ||
|
74b3a513f6 | ||
|
8145fb22e8 | ||
|
7126831a93 | ||
|
fc2a69fc2c | ||
|
553dc3cb9f | ||
![]() |
5979d8a1f9 | ||
![]() |
e76e48604e | ||
|
ca69969dad | ||
|
4390256abc | ||
|
a2d8979b91 | ||
|
1e9a4417be | ||
![]() |
9c74aed36e | ||
|
3b78343219 | ||
|
5640556ad9 | ||
|
4823653214 | ||
|
891823376f | ||
|
a340071ad8 | ||
|
d99a784e3a | ||
|
75fc89d160 | ||
|
ddd29bef3b | ||
|
193c038bab | ||
|
4ecf7ccb46 | ||
![]() |
a5f76e98ec | ||
|
d8f8f1377f | ||
|
ac48462043 | ||
|
7b9686977d | ||
|
6d9d0ee738 | ||
|
d336fc8506 | ||
|
dbf5cf88a8 | ||
|
7c49ac59f5 | ||
|
2f8306a14a | ||
|
26a26db886 | ||
|
eb958aed1f | ||
|
950c88aab2 | ||
|
2189cd1ef9 | ||
|
489c2386de | ||
|
916403f8d3 | ||
![]() |
1677670441 | ||
|
3873d683ee | ||
|
26b3e4101f | ||
|
ec45f6da4c | ||
|
3e7e355dd6 | ||
|
c983c50d21 | ||
![]() |
b24af786fc | ||
![]() |
11cd77436e | ||
|
206cdfe128 | ||
|
7604fef734 | ||
|
5179edb458 | ||
|
c0ebd25ffc | ||
|
212ba72b30 | ||
|
aa3ce32a7c | ||
|
eab9d6f97c | ||
|
c71d827691 | ||
|
690c0b7050 | ||
|
a9d4cc73c1 | ||
|
28b235514a | ||
|
8397739634 | ||
|
008940d75f | ||
|
cd1a33ccbb | ||
|
dab6065b89 | ||
|
65efdc2e2c | ||
|
a4fb9a15b5 | ||
![]() |
d989abcf68 | ||
|
f56703df2e | ||
|
759a711dc5 | ||
|
7a257279cf | ||
|
6a9da7efa5 | ||
|
72cf616114 | ||
|
42a66751e1 | ||
|
4a03a9f89c | ||
|
d7b47d2560 | ||
|
403b67ee48 | ||
![]() |
59cd441292 | ||
|
757f1cb3cd | ||
|
29f7aef27a | ||
|
93351340d0 | ||
|
15ae3b7a0b | ||
|
e181be3fc6 | ||
|
7a8d7b630e | ||
![]() |
8a2ee1bd00 | ||
![]() |
6c6066d93a | ||
|
64448af027 | ||
|
0082d3e014 | ||
|
c7e5002f17 | ||
|
05a2e501ce | ||
|
c023c144c3 | ||
|
f969b05468 | ||
![]() |
4458afe654 | ||
|
a280998eb4 | ||
|
b351703953 | ||
|
a693e90aa3 | ||
|
50ea6a92a5 | ||
![]() |
a0e943a243 | ||
|
e3c2650a89 | ||
|
56bafd73be | ||
|
70be652309 | ||
|
ca861a78fb | ||
|
8ed4aa3751 | ||
|
14e7fff081 | ||
|
371652fbe8 | ||
|
65ba43525f | ||
|
2a57eec1e3 | ||
|
1019341e5d | ||
|
32c47d3d2f | ||
|
98cd2df8ff | ||
|
be3dd6662e | ||
|
3b482f42ae | ||
|
b94de31f34 | ||
|
64371a5926 | ||
|
24fb5321b9 | ||
|
6684aaf380 | ||
|
696727d0d1 | ||
|
0ba3df7385 | ||
|
931d566736 | ||
|
e909144544 | ||
![]() |
58a093aa69 | ||
|
e69b8e5a66 | ||
|
b62e9066bd | ||
|
a1336c282b | ||
|
e1f7c691c3 | ||
|
83fb1a5e11 | ||
|
dcb563b31e | ||
|
88891b44be | ||
|
6267b4c33d | ||
|
0a4da160fd | ||
|
18e154b772 | ||
|
945e349d61 | ||
|
bd45def053 | ||
|
5de7e0245a | ||
|
18674a1a4a | ||
|
07d5a8cdae | ||
|
7e58e9c667 | ||
|
75e1ab0db4 | ||
|
8434eacd94 | ||
![]() |
a01c28da21 | ||
![]() |
3455e6daa2 | ||
![]() |
266fd5aaa8 | ||
|
14fed8bc6e | ||
|
40825cec87 | ||
|
19dee89039 | ||
|
b4dca888c8 | ||
|
94eff087a0 | ||
|
468a0b0023 | ||
|
d35770c122 | ||
|
5aa9f50c79 | ||
|
4883b4ebd0 | ||
|
a1026692da | ||
|
1f6520ac02 | ||
|
19207649dd | ||
|
22759ca52a | ||
|
83d58791bb | ||
|
31e698b8a5 | ||
|
be6e780217 | ||
|
4c034fb8aa | ||
|
e51ad5993a | ||
|
6944da6769 | ||
|
33d2d5beff | ||
|
1ee0b38133 | ||
|
26c7900e32 | ||
|
5d7f9d4dd1 | ||
|
8d72ca805d | ||
|
24951f3070 | ||
|
e45237d70e | ||
|
3c4700eb6d | ||
|
60c31d2d11 | ||
|
14b402cdf3 | ||
|
e4c317f677 | ||
|
998a529d4c | ||
|
3c763820ed | ||
|
8c442ede2c | ||
|
77e152f8ce | ||
|
5d7872042b | ||
|
9a8e7abef4 | ||
|
5be2610a86 | ||
|
262e935510 | ||
|
03d1ada220 | ||
|
32b732e509 | ||
|
b426ce811c | ||
|
c073599f6f | ||
|
87bf6fac68 | ||
|
c1bb43286d | ||
|
961a2891a0 | ||
|
556c2b6efe | ||
|
8e9097a8c1 | ||
|
40485ced8a | ||
|
39e9ececa2 | ||
|
bc4f6e507a | ||
|
7ee80bd2bd | ||
|
6d1a24b034 | ||
![]() |
198b2c31fb | ||
|
a627437fce | ||
|
1b3cd8dd10 | ||
|
46e6b0f704 | ||
|
0674b3f8db | ||
|
0599c4dae0 | ||
|
2a3a26c333 | ||
|
d51d7316d0 | ||
|
c43129104d | ||
|
e83f684c0f | ||
|
12af28cb13 | ||
|
0d865c93d4 | ||
|
533f8075ca | ||
|
f7943761e2 | ||
|
bc2fd6d3dd | ||
|
4cfbdb32d6 | ||
|
11969b6064 | ||
|
557c9f51a4 | ||
|
c6078e566c | ||
|
8a24af27ee | ||
|
b14f5aea58 | ||
|
031d647864 | ||
![]() |
248133a632 | ||
|
0ccb983b28 | ||
|
07157b6335 | ||
|
a6b7e5aabb | ||
|
2d8ac1d561 | ||
![]() |
7161cc345b | ||
|
22cc208e54 | ||
|
69c36394d1 | ||
|
1b5c53f8f9 | ||
|
13853a7407 | ||
|
993fa8fa60 | ||
|
7bdc32f03b | ||
|
b21c8f6dbb | ||
|
a02a2363e5 | ||
|
05f87bad77 | ||
|
b94c5fa020 | ||
|
0dd9b061b9 | ||
|
5b2e5fc838 | ||
|
5df546754f | ||
|
40a9ac4523 | ||
|
14e4415e5f | ||
|
4e5cb69d1c | ||
|
583a5ca594 | ||
|
b20e729298 | ||
|
c535ce24a4 | ||
![]() |
5acfd4e657 | ||
|
35104cb8ce | ||
|
fa2f12375c | ||
|
b5f93ceb48 | ||
|
c9054a243a | ||
|
b6eb12ed90 | ||
|
b40a8235d2 | ||
|
a3300cde98 | ||
|
a34c6539fd | ||
|
fd1e2690f8 | ||
![]() |
8684c2cf82 | ||
|
3e0269ba99 | ||
|
05aa78c254 | ||
|
3d90f544bf | ||
|
5a34d9d58c | ||
|
460a04278f | ||
|
245b2219ee | ||
|
95c04354ba | ||
|
2c4eb03214 | ||
|
67b8697808 | ||
|
7c9be01a4f | ||
|
fb931df4f0 | ||
|
4c59479d5c | ||
|
d0b8ccef64 | ||
|
906b63b123 | ||
|
02f4547652 | ||
|
36a4ebcdd6 | ||
|
226fac0d78 | ||
|
3a3ce5c5fd | ||
![]() |
e654a0b46e | ||
![]() |
ab8c11c0bd | ||
|
29000d3b8e | ||
|
d288222e8e | ||
|
dc2b2ae86b | ||
|
8be40c4adc | ||
|
f99697ddc1 | ||
|
59fd71ac6f | ||
|
390f18a3a4 | ||
|
0b4f0e142f | ||
|
803e1dc411 | ||
|
e30f7f44ef | ||
|
5cf32673fe | ||
|
509f8cfd49 | ||
|
956185fde1 | ||
|
cab71d60ba | ||
|
7cfe080e6f | ||
|
0ac0fe072d | ||
|
352b028e58 | ||
|
44fcdc7d11 | ||
![]() |
d6ec8de7c6 | ||
|
6292dd4c71 | ||
![]() |
b39d87f33f | ||
|
cda1dc2095 | ||
|
7dc584d8cb | ||
|
ba294f6a6c | ||
|
caf2ff6a30 | ||
|
230dd5e3ab | ||
|
0383fa0a67 | ||
![]() |
678f558f4a | ||
![]() |
18e30178a4 | ||
![]() |
2e4cccea00 | ||
![]() |
bac2a369c4 | ||
![]() |
bf125a73b1 | ||
|
d288923969 | ||
|
b6b5beaa27 | ||
|
b8a109efb0 | ||
|
58192620b9 | ||
|
7a596298e2 | ||
|
1057f45eb9 | ||
|
38b449af35 | ||
|
c5b7b0f97e | ||
|
de3580a7d3 | ||
|
04450d4b4c | ||
|
5261375574 | ||
|
573287e6f1 | ||
|
376dba347f | ||
|
2b06c77439 | ||
|
20bbb3eb8e | ||
|
0101e0c92d | ||
|
1742f51778 | ||
|
969b45d9f7 | ||
|
c6c5d40084 | ||
|
d2f1bb406d | ||
|
3fb125f043 | ||
|
3399b30efc | ||
|
5018826a6a | ||
|
e2efe87308 | ||
|
0e54a4e298 | ||
|
999bdf3336 | ||
![]() |
6638f8ce9f | ||
![]() |
d52ce706b6 | ||
|
7eec5c42e3 | ||
|
0531ecd217 | ||
|
14eb529fb7 | ||
|
d44c87e8a7 | ||
|
70cb460934 | ||
|
bcf664ed66 | ||
|
74f5b25f0d | ||
|
a76c60f881 | ||
|
df849873a9 | ||
|
6cf6307d97 | ||
|
02a2338250 | ||
|
632a1f6e75 | ||
|
998b776b9f | ||
|
e4e340e431 | ||
|
52fbcfb43e | ||
|
7969ae9ebe | ||
|
e17d1ab02f | ||
|
330ce6d3a2 | ||
|
c1c717e055 | ||
|
f97f5ea518 | ||
|
e10ac0e57f | ||
|
e58b447eb7 | ||
|
7f82b8f754 | ||
|
8f6b8632f5 | ||
|
e6428ce029 | ||
|
33b6700848 | ||
|
4a4f45bc9d | ||
|
0d62b4fa55 | ||
![]() |
eef463afbd | ||
![]() |
55798ac704 | ||
|
8e1f827f44 | ||
|
c0b5dbe89a | ||
|
f7845e89de | ||
|
3f4de13f8a | ||
|
0398df8f3a | ||
|
0412c9042a | ||
|
f482874310 | ||
|
aa8f19d948 | ||
|
2f885e7bfb | ||
|
ee86b5a121 | ||
|
c04ad04c75 | ||
|
de839880ff | ||
|
f2c8d93f57 | ||
![]() |
96757aa1bb | ||
![]() |
411819c872 | ||
![]() |
e4149575ed | ||
|
84f6cca167 | ||
|
25f7da7f98 | ||
|
980bea3351 | ||
|
68506ae41b | ||
|
0a131b4248 | ||
|
8300f48541 | ||
|
92b2e19e44 | ||
|
3775bcb9db | ||
|
5885e4b043 | ||
|
29eef9ec59 | ||
|
94cba4283b | ||
|
86953e60bd | ||
|
f02088d9fe | ||
|
095d425de1 | ||
|
9188b28b7a | ||
|
f79365304c | ||
|
8b7844bd2f | ||
|
6b8cef164f | ||
|
871b26c920 | ||
|
61e5591628 | ||
|
2662fa5976 | ||
|
505af6b503 | ||
|
c907b9b39d | ||
![]() |
134342d3e5 | ||
|
4380e80192 | ||
|
2d1f638894 | ||
|
b7482008df | ||
|
8656f99f8e | ||
|
13057d6f76 | ||
|
074184bb96 | ||
|
89f91f3857 | ||
|
30d4d989fc | ||
|
fe44417b14 | ||
|
996ef6e115 | ||
|
5f1f4fd654 | ||
|
902840ee7f | ||
|
8110ec508e | ||
|
8688726d94 | ||
|
c9717043bb | ||
|
2b0e3a4bf0 | ||
|
b81153e957 | ||
|
05d69c1f12 | ||
|
0edb7137f4 | ||
|
dc193ad4e8 | ||
|
bb8910d15f | ||
|
c2e503d2d5 | ||
|
b21f7c856a | ||
|
fbb13e4c2f | ||
|
51960556de | ||
|
ed2bb66e3e | ||
|
5a7d0b0afd | ||
|
74ba98624b | ||
![]() |
c9f9a27096 | ||
![]() |
738dbd94a0 | ||
![]() |
624b7526a1 | ||
![]() |
481c1c85e5 | ||
|
4214b0a4ee | ||
![]() |
0664bc36b2 | ||
![]() |
77a14a0017 | ||
|
1f6da4f15e | ||
![]() |
09da9a8d92 | ||
![]() |
3d3994789e | ||
![]() |
fcb39260c3 | ||
![]() |
478a8da9f4 | ||
|
25d7657e7d | ||
|
b159ecd744 | ||
|
f77d6df1e5 | ||
|
05f9712b24 | ||
|
3561a829d2 | ||
|
de6249c65d | ||
|
38ba67a335 | ||
|
cd31340c34 | ||
|
78fda4f9e9 | ||
|
e743de5404 | ||
|
3039a0900e | ||
|
e78c4ed056 | ||
|
97ae55427d | ||
|
4ca0926546 | ||
![]() |
530462e9d2 | ||
|
43bcd75e87 | ||
|
ab139513e4 | ||
|
a174b133ed | ||
|
45c2556a80 | ||
|
be2580b7f6 | ||
|
c7fd6ee041 | ||
|
2940bfd3d8 | ||
|
ad159cc29a | ||
|
3f38497585 | ||
|
65462ca536 | ||
|
f6231f2d66 | ||
|
ece7b343f9 | ||
|
08485aa827 | ||
|
48d48d2f5c | ||
|
d94df613c5 | ||
|
5a499b9321 | ||
|
00272f51e0 | ||
|
c6fe9d5eb2 | ||
|
b0d2503f08 | ||
|
f077346930 | ||
|
4cb89d699c | ||
|
0bc84e596e | ||
|
45dcd890b2 | ||
|
12ca6f4ff7 | ||
|
a39a72b27e | ||
|
dd05b62442 | ||
|
f98a769aa1 | ||
|
ee75e9d4ce | ||
|
ae5e4b9ee6 | ||
|
a2083b9ca0 | ||
|
3da3c00310 | ||
|
c3960b0dfd | ||
|
ea9abc5f73 | ||
|
2a7ab3a183 | ||
|
96f1604879 | ||
|
8301664d9a | ||
|
72d91767ec | ||
|
949e139978 | ||
|
e800ccfe10 | ||
|
dd30590420 | ||
|
f0987ccb44 | ||
|
35938ead04 | ||
|
1a9c74d3d8 | ||
|
4167d3cb22 | ||
|
649473e4b5 | ||
|
5b3ec48687 | ||
|
5bbd188b0b | ||
|
d507f6794f | ||
|
25ea5af2db | ||
|
bd10dc578f | ||
|
1d8733d0c2 | ||
|
bb519adb2c | ||
|
c14bb43993 | ||
|
36de4f0c58 | ||
![]() |
e8d1582ed4 | ||
![]() |
6e3603553f | ||
![]() |
ab21983a4f | ||
![]() |
856c9be73f | ||
![]() |
a09953a849 | ||
![]() |
940416c684 | ||
|
d9d98116e6 | ||
|
ea7a8236ce | ||
|
0a77b72cd3 | ||
|
05de574471 | ||
|
04cee2b4c3 | ||
|
b677eaf187 | ||
|
da49a907f7 | ||
|
8eb7cbf6fd | ||
|
8fd73e09de | ||
|
3c105d3295 | ||
|
ffefb56f83 | ||
|
0f425036b2 | ||
|
55d481b86c | ||
|
91a527c35a | ||
|
b94ab9de9c | ||
|
58d2576ec9 | ||
|
e596b45344 | ||
|
1ae328d8f3 | ||
|
9f8878ae8f | ||
|
9a3d78c38b | ||
|
407d84a8c6 | ||
|
2f79f55da5 | ||
|
40922473b7 | ||
|
e71fbdd235 | ||
|
df137f73a6 | ||
|
e51e1113bb | ||
|
6b231dff49 | ||
|
4082540b7d | ||
|
e77c31fb07 | ||
|
0d9e9ffa4b | ||
|
4666f1c923 | ||
|
36219dbb95 | ||
|
183dfbe00b | ||
|
9aa389ea28 | ||
|
804516c2f4 | ||
|
e7023d087b | ||
|
79e6c841a7 | ||
|
2b8ede117a | ||
|
d4d353f0b6 | ||
|
ff90f8cbca | ||
|
ca9e661b97 | ||
|
b5530b15ee | ||
|
c0af623782 | ||
|
b565d5c882 | ||
|
905a7917f8 | ||
|
46fcd1670f | ||
|
dcba3a446f | ||
|
58f33b86c4 | ||
|
66fce8e076 | ||
|
c9a1de5a8e | ||
|
6cadf33ae3 | ||
|
84d55c246a | ||
|
cee2a41771 | ||
|
9111d49bf4 | ||
|
fc0495f13a | ||
|
61062c8312 | ||
|
32c0ad3bd6 | ||
|
574b3a833a | ||
|
71a6f08ebf | ||
|
ff526a7f7f | ||
|
6a144cf991 | ||
|
9fc86a4d9f | ||
|
eb566fa5c5 | ||
|
2959d19391 | ||
|
ce9f6dd7ba | ||
|
337b660f5a | ||
|
77f785135d | ||
|
cbeb42c8ae | ||
|
1086ed28c3 | ||
|
b3e322ae08 | ||
![]() |
07dbcb51f4 | ||
![]() |
58691904fc | ||
![]() |
d076384ead | ||
![]() |
6ceb47a92e | ||
![]() |
2b1c53e47a | ||
![]() |
aa639dc913 | ||
![]() |
71a1a4d59b | ||
![]() |
f40036422f | ||
![]() |
568a73efaf | ||
![]() |
9fb5293c80 | ||
![]() |
2a78fa95a1 | ||
![]() |
ebee3b3de5 | ||
![]() |
8ee59cd036 | ||
![]() |
fd6f42cef7 | ||
![]() |
a5b6250c86 | ||
![]() |
c52482e98b | ||
|
6022bac0ef | ||
|
2ca14d0f62 | ||
|
999f413dfc | ||
|
9e305fc854 | ||
|
8aac46206a | ||
|
7d73c29ccd | ||
|
f7098b0d35 | ||
|
afdfc0f8f2 | ||
|
1d2ff1744f | ||
|
133627ace2 | ||
|
3c2f245b71 | ||
|
a210ada211 | ||
|
72f148425a | ||
|
0a14d46cf2 | ||
|
3871893c9b | ||
|
8da40eab67 | ||
|
a66d9c5765 | ||
|
7fef4d7a00 | ||
|
b193971625 | ||
|
8f4db6f2ba | ||
|
fe5b7b7335 | ||
|
3351767d56 | ||
|
76b859c629 | ||
|
5781869f03 | ||
|
6c9809b165 | ||
|
b17ce5f905 | ||
|
1e026c1769 | ||
|
6a4b24c0f2 | ||
|
1f25d0052e | ||
|
d0bdcacc94 | ||
|
1927837205 | ||
|
9bf0b8a0b0 | ||
|
82dd354f92 | ||
|
bd77f8da93 | ||
|
c2cb5a2546 | ||
|
95c5e0b6ea | ||
|
4a30aeaab6 | ||
|
3c23de4dfa | ||
|
9021c6f853 | ||
|
cbc0a1a927 | ||
|
b5ab21549d | ||
![]() |
a3a228fc2d | ||
|
e2c901dbe4 | ||
|
af53a610bb | ||
|
c87a8e2f15 | ||
|
e091adaa64 | ||
|
9c1b4f5dbe | ||
|
b7935e59e7 | ||
|
8ef6522ead | ||
|
f9e2715b30 | ||
|
7ac7c1a2b7 | ||
|
a2501ed2cb | ||
|
683ad81015 | ||
|
9816da4f85 | ||
|
210ae8dd2e | ||
|
6d5085c1e7 | ||
|
5ebed4a0fb | ||
|
686aa533e8 | ||
|
ba30015f7f | ||
|
0f383cfb2c | ||
|
d32f675de7 | ||
|
68e79b4883 | ||
|
20f4c182ad | ||
|
d4dabc90c5 | ||
|
7f27762054 | ||
|
73ebf746f9 | ||
|
da6a607033 | ||
|
f01e24f995 | ||
|
faae70bbb1 | ||
|
7b00e7484e | ||
|
dfbdbe73bd | ||
|
95ed5c6932 | ||
|
a594337965 | ||
|
450fb3f87a | ||
|
cd6e4b9767 | ||
|
bb15d1217f | ||
|
2193e0ca53 | ||
|
7d4a99344b | ||
|
1c10be5cdc | ||
|
9cc324f84c | ||
|
a793bb5fc1 | ||
|
b36018f36b | ||
![]() |
b949ba3e72 | ||
|
7f290dfce6 | ||
|
68865895e5 | ||
|
8d331b0086 | ||
|
d4659fd189 | ||
|
8c47b74ec3 | ||
|
5a8f7f4075 | ||
|
fc5e163fb9 | ||
|
e3578e669c | ||
|
05670ac2bb | ||
|
1e13e632c9 | ||
|
611954e199 | ||
|
1b70a4247c | ||
|
455d4d7551 | ||
|
94dba9139b | ||
|
fa4fe51155 | ||
|
3fc51d9eba | ||
|
82d2e5b416 | ||
|
840d8228ed | ||
|
db83b1614b | ||
|
72d4826dbb | ||
|
91cf6cd1e5 | ||
|
5dde5d1642 | ||
|
6138db1089 | ||
|
ff85191bbe | ||
|
95856a2c2d | ||
|
8d21e15106 | ||
|
1fbc08f74b | ||
|
0a7e5bcdcd | ||
|
5369490b79 | ||
![]() |
949690ad59 | ||
|
5c83287057 | ||
|
d569b00960 | ||
|
ecb67d012b | ||
|
faf27a3940 | ||
|
f84e9c533f | ||
|
a6434bd8d0 | ||
|
09397e2597 | ||
|
47c18913ca | ||
|
fba83415c7 | ||
|
cf3c45fdd5 | ||
|
6b90d568cf | ||
|
d756e3daf8 | ||
|
79facf2409 | ||
|
3468b719ed | ||
![]() |
c395438a41 | ||
![]() |
1461cf2827 | ||
|
6bfcd87976 | ||
![]() |
9a60d36a03 | ||
|
5d4ca086e6 | ||
|
d193ec8ef3 | ||
|
df3e18b476 | ||
|
fa59c547a9 | ||
|
22c98a4206 | ||
|
aaf937a89f | ||
|
3a44a9fbfb | ||
|
46f7aa93ef | ||
|
8dc66421c0 | ||
|
56a36f8cae | ||
|
24066d89a2 | ||
|
17f3537d74 | ||
|
5a182fadef | ||
![]() |
c136c33a58 | ||
![]() |
c200e4002e | ||
|
27f8cfb187 | ||
|
0fda52337c | ||
|
e7dce1c36b | ||
|
522cecb9fb | ||
|
78498b5a46 | ||
|
97cbef06a2 | ||
|
8568298a4e | ||
|
e54135b014 | ||
|
df192e543b | ||
|
3b3bdeecab | ||
|
c74a249a00 | ||
|
9e07af289e | ||
![]() |
4d33539a63 | ||
![]() |
d307bc0556 | ||
|
3e9c28b8ae | ||
|
976aa251d1 | ||
|
479b730be4 | ||
|
93e36156a0 | ||
|
25b305bddf | ||
|
32826ed131 | ||
|
eb6ae208cb | ||
|
d9595ad8e6 | ||
|
d51cded72d | ||
|
07c3d93e7d | ||
|
8e45b93b78 | ||
![]() |
8bde57be4b | ||
![]() |
d3c9550463 | ||
![]() |
a656eb7eb3 | ||
![]() |
88cfbc0699 | ||
![]() |
bd19749971 | ||
![]() |
3c5e778016 | ||
![]() |
ea73de81e5 | ||
|
f92eb37bfc | ||
|
459c7731cb | ||
|
1fce93ad30 | ||
|
dccdd4d97f | ||
|
7775f33679 | ||
|
835726f3a7 | ||
|
762a4d34c1 | ||
|
fab9a41024 | ||
|
c41c43cd5c | ||
|
276bd6ae8d | ||
|
f33f638d88 | ||
|
2bcc0bd22a | ||
|
a971bf5d3b | ||
|
c976be62d7 | ||
|
a2fa68f9e4 | ||
|
e6345bce30 | ||
|
974a5039f5 | ||
|
c1b1dc1a29 | ||
|
fe668fd5d4 | ||
|
0f387102b3 | ||
|
ada87897a4 | ||
|
40160fb25a | ||
|
3b1d1f7e94 | ||
|
1bc47fa231 | ||
|
ae0bb8ed58 | ||
|
9b9465502a | ||
|
0b972bc464 | ||
|
b299edbef4 | ||
|
7d9ce7aed9 | ||
|
68fed2439d | ||
|
c548a88ee7 | ||
|
1a1ea721d9 | ||
|
8c276b53a6 | ||
|
75fea7aa34 | ||
|
8f4023c1c5 | ||
|
8a95dfa90a | ||
|
3de85e6717 | ||
|
30efde6eb3 | ||
|
b235519ecf | ||
|
8fd83241ca | ||
|
bac0ac0b00 | ||
|
c2c59892fe | ||
|
671b460855 | ||
|
44d42de81c | ||
|
690e56f558 | ||
|
a8e07c62c3 | ||
|
d98a1adfd9 | ||
|
019d658442 | ||
|
1360a36a95 | ||
|
ffd899534a | ||
|
e5c49ab172 | ||
|
5b32ae836d | ||
|
e9ce0ce869 | ||
|
966ee7dae9 | ||
|
a9692317d2 | ||
|
6772b3b5d0 | ||
|
a980e22ecb | ||
|
f78c024edc | ||
|
2f222371c3 | ||
|
1a0e68e2e7 | ||
|
dc0695e38f | ||
|
c97d9ab948 | ||
|
76f46ca7d5 | ||
|
2432075f9a | ||
|
f58e66f701 | ||
|
2667a2c00d | ||
|
4e175e998e | ||
|
a37d31973a | ||
|
5e0541aef8 | ||
|
433754590d | ||
|
90a84fc9da | ||
|
0a3210c703 | ||
![]() |
883caac939 | ||
|
ebb6d287b2 | ||
|
e05b306702 | ||
|
8b14575657 | ||
|
616feb54b2 | ||
|
8687cd6bfa | ||
|
c0ff320281 | ||
|
c79b3f77c2 | ||
|
0c0a8e6263 | ||
|
b5fb5dd6c2 | ||
|
5e49e3204b | ||
|
24362768fb | ||
|
3bd851aae5 | ||
|
0028e0fcd0 | ||
|
12c04cf3be | ||
|
7738736120 | ||
|
8536e87475 | ||
|
51ee9be424 | ||
|
0c59af2fdc | ||
|
4973c63e62 | ||
|
4d5e75df68 | ||
|
6a88040826 | ||
|
ad5e628957 | ||
|
c3e3fc75bf | ||
|
bc8050cd3c | ||
|
ab13ed1ef5 | ||
|
b04a207262 | ||
|
9e1e3acfea | ||
|
f0eb6f0d1b | ||
|
b68726c413 | ||
|
e809ed4859 | ||
|
dca56140aa | ||
|
26c2be07cf | ||
|
69279ba34f | ||
|
018bdb2f83 | ||
|
9618e388c3 | ||
|
36bd6f5755 | ||
|
b33ddaadb5 | ||
|
9587bae4fe | ||
|
fc7655469f | ||
|
efd2875b17 | ||
|
8c292ff8e0 | ||
|
8b2771cd63 | ||
|
ef84b3f889 | ||
|
0d1a220b7b | ||
|
ac58f2a10c | ||
|
197ebe2e38 | ||
|
00d46cb1b1 | ||
|
af6b16cc35 | ||
|
3a7d612c7a | ||
|
02146a81d6 | ||
![]() |
913f2cde8f | ||
![]() |
995ae2f55f | ||
|
103c213583 | ||
|
28eb3f023c | ||
|
7bb5179b4f | ||
|
fd63d3d857 | ||
|
24f04e59aa | ||
|
aad27851bb | ||
|
68b8cf28d3 | ||
|
e36a352a42 | ||
|
c418102000 | ||
|
513eb4bed6 | ||
|
2027308249 | ||
|
9cbf866de7 | ||
|
f8bbe00d47 | ||
|
5d5930265a | ||
|
61cf881a03 | ||
|
4a3be10add | ||
|
6712d98040 | ||
|
a24fb12c21 | ||
|
7ca24d27d3 | ||
|
8a0c8f32ae | ||
|
5b276368b8 | ||
|
17f9aa9c3e | ||
|
c5eb2f4f70 | ||
|
763a071acc | ||
|
957cac5ebc | ||
|
3481d4e13c | ||
|
61c6188454 | ||
|
b7222e2cd1 | ||
|
6e423c24fb | ||
|
b679f568eb | ||
|
d787f8b0a3 | ||
|
b52a196c73 | ||
|
b7583bc8cc | ||
|
da9fe36646 | ||
|
8503a4a946 | ||
|
e924cc1322 | ||
|
72831ee386 | ||
|
7345543fa2 | ||
|
c388d5ea1e | ||
|
35e4bbf04b | ||
|
ce39850bda | ||
|
9fe4e2933d | ||
|
a4b2dc29a9 | ||
|
85d9b9fdac | ||
|
0ca57e8e24 | ||
|
39c1d34bbb | ||
|
8f0f635484 | ||
|
568a31586f | ||
|
f514e200f0 | ||
|
83db4ba886 | ||
|
f98720b57b | ||
|
8d7e5d3f66 | ||
|
65490b1d20 | ||
|
4bd61fedde | ||
|
27753d50c4 | ||
|
fdcec012f3 | ||
|
b99176be49 | ||
|
75f5c58764 | ||
|
50438d940e | ||
|
28dd9694af | ||
|
6a6198c9b9 | ||
|
559f743ce2 | ||
|
691c3e7bc2 | ||
|
087bc4c669 | ||
|
3bc5e55400 | ||
|
4b00c8b55a | ||
|
5a0aa82ec9 | ||
|
62f7080db9 | ||
|
626075ee94 | ||
|
850d860d59 | ||
|
31ddea7649 | ||
|
5775001301 | ||
|
c9f008ad82 | ||
|
b943d2d465 | ||
|
d3ea06c3e8 | ||
|
41dac92e1a | ||
|
841a86aa61 | ||
|
9e1685531c | ||
|
3fcd81960e | ||
|
52cab71fec | ||
|
adb808a683 | ||
|
70665abb0b | ||
|
d596d46783 | ||
|
6f80303782 | ||
![]() |
6ae0d31840 | ||
![]() |
800a4fc956 | ||
![]() |
c062c38971 | ||
![]() |
e8e513e6d4 | ||
![]() |
a8fd397a3d | ||
![]() |
718b7a9ce8 | ||
![]() |
36b6e801e5 | ||
![]() |
f8bea96752 | ||
![]() |
33be3a90d2 | ||
|
f6ecf2a465 | ||
![]() |
c87611c2e2 | ||
|
bac372ae67 | ||
|
f57681b098 | ||
|
8b07fce738 | ||
|
f214f70cd4 | ||
|
7e57c0f03e | ||
|
08d34b0e09 | ||
|
629922626b | ||
|
ebcf8e4445 | ||
|
84ece2731c | ||
|
2adf3c6a72 | ||
|
5f17afcbac | ||
|
e435ae582a | ||
|
3adfb9779a | ||
|
51ca74549e | ||
|
807b296078 | ||
|
836f065382 | ||
![]() |
452b7564c4 | ||
|
b3ad49ac8d | ||
|
fec26ab38f | ||
|
b470fddc12 | ||
|
8cb172a1c1 | ||
|
9aeb690589 | ||
|
ca857091e4 | ||
|
017c2c3421 | ||
|
f8c157ce50 | ||
|
33fb9fb3f5 | ||
|
1f3e7afb2c | ||
|
9b7454b57c | ||
|
5433859a86 | ||
|
e2d7d05783 | ||
|
ad5c8cc0ab | ||
|
97a1b3ae85 | ||
|
1c0a3ee8e7 | ||
|
194de9ef2d | ||
|
3fa81ddc85 | ||
|
74d81eb7ba | ||
|
228786f6aa | ||
|
014b6029c5 | ||
|
1ac6559b9f | ||
|
56ff2a794f | ||
|
c0b8d35a47 | ||
|
2bccbf9ded | ||
![]() |
bb661b391b | ||
|
debabe85b0 | ||
|
8ac9b2f204 | ||
|
b06532241b | ||
|
4be912ac31 | ||
|
6e9fb7044a | ||
|
fbf0371371 | ||
|
dba7beae1c | ||
|
1abc0153f5 | ||
|
f036336f30 | ||
|
75224f0d5c | ||
|
e54fd79bcd | ||
|
f4a644795e | ||
|
18572d56e6 | ||
|
32d129015e | ||
|
9bf7f856af | ||
|
8a2bef9b77 | ||
|
03840fd152 | ||
|
e0ffb4fd2f | ||
|
b1e665db7c | ||
|
4bd3945b6b | ||
|
6118e6a530 | ||
|
3517facfc0 | ||
|
8cef49cfce | ||
|
fbb8840dff | ||
|
b42e39ed0a | ||
|
9d5d80457f | ||
|
d1b1ca7729 | ||
|
abb99ed58a | ||
|
d2260b4699 | ||
|
5c1eba0d58 | ||
|
9a2f9038c4 | ||
|
6527c985a7 | ||
|
9d0a602b49 | ||
|
56fce7d460 | ||
|
69605acfc9 | ||
|
2b7704630c | ||
|
12d47ea0bc | ||
|
358a1869f4 | ||
|
cfd0863671 | ||
|
65e6b8d053 | ||
|
adeb8eff88 | ||
|
724537558e | ||
|
3d1468b214 | ||
|
7aeb46382d | ||
|
2fbbaa1586 | ||
|
b20f369ea8 | ||
|
077eaa265c | ||
|
978285bf32 | ||
|
f52df58517 | ||
|
2af911c29f | ||
|
c0353d2911 | ||
|
17dbba2c94 | ||
|
62e1d29617 | ||
|
767db8efdd | ||
|
d4b110087f | ||
|
638e37c05f | ||
|
493d7957fd | ||
|
9f8cbde7d7 | ||
|
a86e04683a | ||
|
c25233b991 | ||
|
a09f565c78 | ||
|
0d1e987a6f | ||
|
a8690b13b8 | ||
|
ed04aebc4b | ||
|
a3d52d026e | ||
|
988cf905d4 | ||
|
eb431d8da8 | ||
|
db317ec355 | ||
|
457052d42b | ||
|
d7398135d1 | ||
|
b6d23aaed4 | ||
|
c185a5bacd | ||
|
7e15f8adc3 | ||
|
d0c0425b65 | ||
|
8523754935 | ||
|
bdc5b4de33 | ||
|
71f033b7c2 | ||
|
569275329c | ||
|
161aec9314 | ||
|
f56852c27d | ||
|
fa462fbd0f | ||
|
b3e6063596 | ||
|
c31066fea8 | ||
|
fd421bf6f8 | ||
|
ce76430b4d | ||
|
4efcc73f55 | ||
|
f3d8a1412c | ||
|
2aaf7cf8f8 | ||
|
614bdf9dec | ||
|
d344664fa1 | ||
|
6b720c6c75 | ||
|
4a9463db5f | ||
|
a160e7cf46 | ||
|
7f36516faa | ||
|
75d0de8a99 | ||
|
c41ee0f806 | ||
|
51101fc615 | ||
|
c5109fbfe3 | ||
|
717159b61f | ||
|
ad51400cae | ||
|
52eebfeb16 | ||
|
63cdd470cf | ||
|
0893156723 | ||
|
0f0ee046b1 | ||
|
47012f9bff | ||
|
1041e092b1 | ||
|
4f62e25d5e | ||
|
3b90426b4d | ||
|
2b0678063c | ||
|
b5cc8c2c57 | ||
|
5d8cd80b38 | ||
|
3b4ba137e7 | ||
|
35abb92daf | ||
|
173746fe9c | ||
|
39aabd0546 | ||
|
ad84f62c0d | ||
|
988c71a6fb | ||
|
ec8802dd4a | ||
|
9f0fc90679 | ||
|
e4c3f5f2f2 | ||
|
70944d7065 | ||
|
0b9056bd2b | ||
|
bdac36bea8 | ||
|
5d1fc22813 | ||
|
8fc0017378 | ||
|
9854fc9dbc | ||
|
db3a15310c | ||
|
af86236f42 | ||
|
dd35f5dcd5 | ||
|
4e6be9b51e | ||
|
659e35686e | ||
|
4f6b57676a | ||
|
00fd1df67a | ||
|
8e54d6eb23 | ||
|
19dd29e847 | ||
|
33b85ff0de | ||
|
dca13263e2 | ||
|
17510b783c | ||
|
c49d9ffc56 | ||
|
fb42f9e667 | ||
|
2d42e5f7dd | ||
|
7f0fb7a6e2 | ||
![]() |
2ba4946975 | ||
![]() |
f6eeda0235 | ||
|
ec13a1edaa | ||
|
af0fe1cfea | ||
|
03d3ab6e9d | ||
|
e8d131b041 | ||
|
2ebf7ec32b | ||
|
b8bcc6c499 | ||
|
8752299e61 | ||
|
4f57a6c0e3 | ||
|
6e4c1ca502 | ||
|
fc94e63467 | ||
|
10fd67a0fd | ||
|
f329373a4a | ||
|
379dc9e1fe | ||
|
16ea6ce0d5 | ||
|
48fc341137 | ||
|
ed325848ab | ||
|
1a9fadce70 | ||
|
e9f225890a | ||
|
3e1d3b483e | ||
|
fede30c2cc | ||
|
4a3cee1623 | ||
|
914889da6c | ||
|
ee0b8a569e | ||
|
62d7baa3ec | ||
|
f5dcb808c4 | ||
|
31fdcae9ee | ||
|
55058bdfd9 | ||
|
d72c43083d | ||
|
ca7f3ed4a6 | ||
|
e40f88aa69 | ||
|
275249481f | ||
|
4d853c974c | ||
|
d2be654206 | ||
|
5e45efb7ae | ||
|
5935aed0db | ||
|
9d3d0bcc69 | ||
|
0b52f8e7e6 | ||
|
374ba3c16a | ||
|
7bf77f9a49 | ||
|
da4b139095 | ||
|
fb7b11fdb6 | ||
|
6045debe9e | ||
|
ec62dfdb9a | ||
|
5f5c3d5207 | ||
|
958f5893e6 | ||
|
487e4d0df6 | ||
|
e81fcafe7a | ||
|
63d455d242 | ||
|
65db8b1625 | ||
|
ecb7a93073 | ||
|
e33af1c845 | ||
|
9c6fe48859 | ||
|
3eeb253e55 | ||
|
c5e43188ca | ||
|
057d4f0c4c | ||
|
18c56cce9a | ||
|
77b19762d4 | ||
|
58d99eb402 | ||
|
2c062761e3 | ||
|
41ff060e99 | ||
|
cf4d0c1ca6 | ||
|
8be6f9b78d | ||
|
be15458e1e | ||
|
1d06d86205 | ||
|
bd217f0666 | ||
|
b80c0b12fe | ||
|
8a1a1bd8fd | ||
|
1a56b7d328 | ||
|
9398649db0 | ||
|
2384b69d0e | ||
|
febcacdfe3 | ||
|
9850a27ee2 | ||
|
67d8293201 | ||
|
3ab39f9ede | ||
|
295ff72b4b | ||
|
8456ac43c6 | ||
|
1bfeead5e8 | ||
|
a549936e09 | ||
|
d19f0dd5bd | ||
|
014b37082c | ||
|
c1885e20b6 | ||
|
dc9e378908 | ||
|
12ce8d8f6e | ||
|
3a56b0425c | ||
|
9651d740ae | ||
|
9cace7dace | ||
|
54219928e4 | ||
|
8c6c691e5e | ||
|
22d5ba12ee | ||
|
15826c73b0 | ||
|
d26b8ade45 | ||
|
4a57926577 | ||
|
b114ba3ff8 | ||
|
d428572461 | ||
|
0bc4b5439c | ||
|
12cf03e03f | ||
|
c77856f97a | ||
|
5639da4954 | ||
|
325f483a26 | ||
|
4f5e462c94 | ||
|
91f2fd839b | ||
|
a4ca98e79e | ||
|
e6acda1f52 | ||
|
2fdeeca9c3 | ||
|
2f7f35c85a | ||
|
1d450b9829 | ||
|
7631ff9a5e | ||
|
35025b40f6 | ||
|
5aee050c5d | ||
|
58ca3fa9ae | ||
|
e2490df48e | ||
|
4557ce2538 | ||
|
70513c47fa | ||
|
6cc0498e10 | ||
|
63fd31c226 | ||
|
daa90a4668 | ||
|
5419bf31fb | ||
|
11071914e0 | ||
|
d3de7a27be | ||
|
7fba53245a | ||
|
beb55a7974 | ||
|
cb93e6c160 | ||
|
6ad28bb375 | ||
|
fd99334a66 | ||
|
6b11a7b2a8 | ||
|
7d78ac9db8 | ||
|
2d856a1e9a | ||
|
88dd587fb4 | ||
|
9fa3757a96 | ||
|
d0b2e2fb61 | ||
|
8343838dc6 | ||
![]() |
75e199ae0d | ||
|
c9e5ae87a1 | ||
|
121dfb692c | ||
|
9df5cb1f16 | ||
|
c597244a9d | ||
|
d5bca495e0 | ||
|
ad569f073e | ||
|
cdef8cdb13 | ||
|
f6dafbc78e | ||
|
4f9281a4a4 | ||
|
2457d5b435 | ||
|
84d1984cc1 | ||
|
9e2cb92766 | ||
|
da39da4a5f | ||
|
ccfa56ad5e | ||
|
0aafeb96cd | ||
|
7e4c69c3f1 | ||
|
d83dbbdd75 | ||
|
5319cd1e8b | ||
|
f9c255cd1b | ||
|
6d58f2387f | ||
|
e56e875433 | ||
|
3213e462c5 | ||
|
1339564dc4 | ||
|
ff607777ce | ||
|
f405dca771 | ||
|
10732efa36 | ||
|
c8272b933a | ||
|
0ca8edf656 | ||
|
32b7dda61f | ||
|
877744b0ba | ||
|
a14580944b | ||
|
644eb37b82 | ||
|
fa224a9939 | ||
|
6b4eae842b | ||
|
00ba7e49d6 | ||
![]() |
06f6e454e3 | ||
![]() |
cbfc8c75ad | ||
![]() |
ef211f7356 | ||
![]() |
dcf944b198 | ||
|
6ba0f6df1d | ||
|
0025482240 | ||
|
870a5252e5 | ||
|
f65e216828 | ||
|
f355d1ec7b | ||
|
df9863ef31 | ||
|
e515378497 | ||
|
42133b92de | ||
|
297c8c84fb | ||
|
3b829caaf6 | ||
|
b1860b7e01 | ||
|
be2cdc39e8 | ||
|
99071bd8ba | ||
|
f7c85ddd8a | ||
|
223e799a87 | ||
|
2631531fd2 | ||
|
75d86f3339 | ||
|
4213b60052 | ||
|
b9b0a9c5ca | ||
|
8246a8199c | ||
|
df63f8c732 | ||
|
e91ac7e457 | ||
|
215c824893 | ||
|
d31910368c | ||
![]() |
277808a9c5 | ||
|
6bac83def2 | ||
|
6a2ecbdbf5 | ||
|
cff0870e63 | ||
|
56f1b1a6c6 | ||
|
52c36ae3fa | ||
|
f04dac11e5 | ||
|
cec8a14dfd | ||
|
45723a4c8a | ||
|
741f3b0032 | ||
|
0749fc75c5 | ||
|
f71653e3ce | ||
|
59c1cb8551 | ||
|
05e8d83ebf | ||
|
30ee0c8bdf | ||
|
56fd0049f7 | ||
|
e5c12f0628 | ||
|
2274e7aa37 | ||
|
5b1b7241b7 | ||
|
f2a0f0b46a | ||
|
633ccb97fd | ||
|
d01d89d432 | ||
|
e395a42160 | ||
|
e82b4ba78b | ||
|
504c4f2314 | ||
|
087f4bb74d | ||
|
11701a67c8 | ||
|
e0f02ef0f7 | ||
|
d47c39185b | ||
|
652fb72ccc | ||
|
ea55a02215 | ||
|
9dd7589f12 | ||
|
8180769120 | ||
|
f9bd2d695d | ||
|
aad1a742b7 | ||
|
f30aa48eca | ||
|
a176a1aa65 | ||
|
a9c00409b4 | ||
|
6cd02dc563 | ||
|
d0647b2e3c | ||
|
cca4fec761 | ||
|
c090a9c2c2 | ||
|
16e8c4fd00 | ||
|
3fbe32518a | ||
|
d087cea869 | ||
|
985f47ca99 | ||
|
a185e027f8 | ||
|
8cb997133a | ||
|
65a8efc97f | ||
|
f80896fa3b | ||
|
420cea15d2 | ||
|
3088ae0ba0 | ||
|
ca922ef5f7 | ||
|
2e2c504111 | ||
|
6ca0d863b1 | ||
|
c7362df6c4 | ||
|
eaf268aea9 | ||
|
67386d9efa | ||
|
1ffe8bd23e | ||
|
a58c5877bf | ||
|
4921e0b74f | ||
|
2e56feb27d | ||
|
bd89cd4cb5 | ||
|
6f87a1d240 | ||
|
394424951f | ||
|
78047da04a | ||
|
5bd642236c | ||
|
607da9d39b | ||
![]() |
5ffaa9b1c8 | ||
|
44414f2375 | ||
|
d90c9edc22 | ||
|
91fd33cfa0 | ||
|
5e7c7671e0 | ||
|
fbb4e2f7a5 | ||
|
aa477322ac | ||
|
c3c510c609 | ||
|
f96c53ee8a | ||
|
835da4db4d | ||
|
ea10ed96e5 | ||
|
af97226512 | ||
|
06e30cf23c | ||
|
90e3bb7fb2 | ||
|
c0986eb956 | ||
|
614b920890 | ||
|
3ff5d8a7dd | ||
|
d39cb5bd82 | ||
|
6a6a9748b4 | ||
|
64cffad6a7 | ||
|
0eca42d188 | ||
|
b4b988e5f2 | ||
|
eddabb0043 | ||
|
f85349f0c5 | ||
|
0647a8203d | ||
|
4ef9b119ef | ||
|
15428b03be | ||
|
a236444fe5 | ||
|
e36dbf0222 | ||
|
b00b2aa245 | ||
|
1b5ac55033 | ||
|
7080b0d89e | ||
|
18b573a9c6 | ||
|
f42dda5961 | ||
|
5550d2cc56 | ||
|
88afba9ce9 | ||
|
7ab121e7e5 | ||
|
d7f578742b | ||
|
c481fc1327 | ||
|
3dc5962627 | ||
|
f8bc4b2ad9 | ||
|
e2d8923dee | ||
|
3ee570a47a | ||
|
f1f7086aab | ||
|
67c5c23194 | ||
|
4873ec053e | ||
|
113221a9b2 | ||
|
9cc5c07466 | ||
|
76fe68a3d1 | ||
|
8eeaabf615 | ||
|
3c1361a2eb | ||
|
1fd452b006 | ||
|
20abb53260 | ||
|
6c937e547d | ||
|
554e7947ea | ||
|
50372572f4 | ||
|
1c5e97a10f | ||
|
3077d74318 | ||
|
351d779f20 | ||
|
2a992773f3 | ||
|
831545d8b1 | ||
|
de73552700 | ||
|
b70f9c9c9d | ||
|
a33d8e4201 | ||
|
29f488c082 | ||
|
152bb56fdd | ||
|
7a3e3ad68f | ||
|
43bd6b4774 | ||
|
78c553faf9 | ||
|
7f7e53cecf | ||
|
21dd1b615d | ||
|
a0702416a0 | ||
|
d76f69ab49 | ||
|
2bc659af5f | ||
|
246c408d83 | ||
|
6398e668e1 | ||
|
07dcb813f3 | ||
|
f36e36d74a | ||
|
48cc865892 | ||
|
fb27b4b00d | ||
|
5c31cdccdd | ||
|
4271c92d70 | ||
|
e32ad2eb19 | ||
|
ececab44b4 | ||
|
366374b4e5 | ||
|
b1992447f0 | ||
|
4b51c1f82a | ||
|
a5088a5358 | ||
|
55f304f1e1 | ||
|
c7ecf22c68 | ||
![]() |
5635571f97 | ||
|
ab4e1ac752 | ||
|
ae7c3220e0 | ||
|
4d6b867bb3 | ||
|
793220c0ec | ||
|
51e6d0534e | ||
|
ca1646d394 | ||
|
f2073e72ed | ||
|
31cc74951b | ||
|
c5fc67660a | ||
|
7c1c0f5e80 | ||
|
0a115d2372 | ||
|
e5a7aad0e8 | ||
|
155c93f739 | ||
|
9bba18d13e | ||
|
0533e4087a | ||
|
d442ab399e | ||
|
2c1a825b7d | ||
|
8acc0a7bb1 | ||
|
a553e736d1 | ||
|
72abcae348 | ||
|
df852e8ef9 | ||
|
fa67bd13f4 | ||
|
d029af554c | ||
|
b30e7a0ed8 | ||
|
c5c0c923d1 | ||
|
a08f483ac5 | ||
|
591d3fb947 | ||
|
697881ee23 | ||
|
6a8768fb2f | ||
|
21da0a7b80 | ||
|
0ef0a5ff26 | ||
|
fdf7e31492 | ||
![]() |
81a1af4c74 | ||
![]() |
3170e75bbe | ||
|
c478fb357d | ||
|
a955a7b84d | ||
|
c953d138ee | ||
|
36e70a656b | ||
|
5e8489a74c | ||
|
c2bcd1f0d9 | ||
|
2387b196b2 | ||
|
672660d131 | ||
|
f312f590c1 | ||
|
018bdd1d00 | ||
|
9b8c5450ec | ||
|
d394842929 | ||
|
60cfe90a1d | ||
|
23ff7a145b | ||
|
32fd2f7a7b | ||
|
fc44a9cd69 | ||
|
92020f1c6e | ||
|
675f8beea9 | ||
|
81db0e6e44 | ||
|
c2c9913514 | ||
|
5d1e71e83e | ||
|
01364c2c50 | ||
|
0784b85fbb | ||
|
bf7e982fdb | ||
|
4b41a00ac4 | ||
|
a67353e24f | ||
|
750f1ef20c | ||
|
c8adfcabf1 | ||
|
91c4846129 | ||
|
176a41e2f3 | ||
|
d02538368a | ||
|
7ea85247e0 | ||
|
d1672a9a45 | ||
|
eee9b00f10 | ||
|
44a6e8292a | ||
|
6602e684c0 | ||
|
151d8e92c3 | ||
|
6a67b1a4ae | ||
|
84a78f5fec | ||
|
577c3c7c49 | ||
|
04c632953d | ||
|
d6799088c4 | ||
|
2ac2982463 | ||
|
bb0b6f2079 | ||
|
3242d97cfa | ||
|
eede422e9a | ||
|
dc968b75e5 | ||
|
d1bbb5a855 | ||
|
7f153cf174 | ||
|
dd082d45e9 | ||
|
6adc4a675f | ||
|
2361466c84 | ||
|
a69f049ce2 | ||
|
f6ecaf3c29 | ||
|
cf2920f284 | ||
|
180802a669 | ||
|
1744596296 | ||
|
6a12b564ac | ||
|
a443272e02 | ||
|
e35f41c6dc | ||
|
27d6c1880a | ||
|
ccd4a09183 | ||
|
77051c0a0e | ||
|
f334b93756 | ||
|
626355178a | ||
|
f22b30e4fa | ||
|
c8ae22da8a | ||
|
4708ae720b | ||
|
664832313d | ||
|
b24253a64b | ||
|
768bad7b77 | ||
|
2e951c5814 | ||
|
049b77e702 | ||
|
68a4359a8c | ||
|
4934eb46fb | ||
|
b690ae25b0 | ||
|
880184807b | ||
|
1cd5476398 | ||
|
007a617785 | ||
|
568c6b55c9 | ||
|
0de62a0afa | ||
|
b4122bf3f9 | ||
|
4a81c05f6d | ||
|
0d8332ec7e | ||
|
1fb5787807 | ||
|
55d6257243 | ||
|
7432c52a36 |
27
.editorconfig
Normal file
27
.editorconfig
Normal file
|
@ -0,0 +1,27 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yaml]
|
||||
indent_size = 2
|
||||
|
||||
[*.exs]
|
||||
indent_size = 2
|
||||
|
||||
# possibly sql dumps
|
||||
[*.sql]
|
||||
indent_size = unset
|
||||
|
||||
# bundlewrap encrypted files
|
||||
[*.vault]
|
||||
end_of_line = unset
|
||||
insert_final_newline = unset
|
||||
|
||||
[*.json]
|
||||
insert_final_newline = unset
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1 +1,3 @@
|
|||
.secrets.cfg
|
||||
.secrets.cfg*
|
||||
__pycache__
|
||||
*.swp
|
||||
|
|
47
Jenkinsfile
vendored
47
Jenkinsfile
vendored
|
@ -5,50 +5,59 @@ pipeline {
|
|||
steps {
|
||||
sh """
|
||||
[ -d venv ] && rm -rf venv
|
||||
|
||||
virtualenv -p python3 venv
|
||||
. venv/bin/activate
|
||||
pip install --upgrade pip
|
||||
|
||||
pip install --upgrade pip isort
|
||||
pip install -r requirements.txt
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('bw test') {
|
||||
stage('tests') {
|
||||
parallel {
|
||||
stage('ignore missing vaults') {
|
||||
stage('syntax checking using editorconfig-checker') {
|
||||
steps {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
bw test --ignore-missing-faults
|
||||
wget -Oec-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/'
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('dummy mode') {
|
||||
when {
|
||||
branch 'master'
|
||||
}
|
||||
steps {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
export BW_VAULT_DUMMY_MODE=1
|
||||
bw test
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('determinism') {
|
||||
stage('config and metadata determinism') {
|
||||
steps {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
|
||||
export BW_VAULT_DUMMY_MODE=1
|
||||
export BW_PASS_DUMMY_MODE=1
|
||||
bw test --metadata-determinism 3 --config-determinism 3
|
||||
"""
|
||||
}
|
||||
}
|
||||
stage('bw test -i') {
|
||||
steps {
|
||||
sh """
|
||||
. venv/bin/activate
|
||||
|
||||
bw test --ignore-missing-faults
|
||||
|
||||
export BW_VAULT_DUMMY_MODE=1
|
||||
export BW_PASS_DUMMY_MODE=1
|
||||
bw test
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
sh 'rm -rf venv'
|
||||
sh """
|
||||
rm -rf venv
|
||||
rm -rf bin
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
58
PORT_MAP.md
Normal file
58
PORT_MAP.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Port Mapping Table
|
||||
|
||||
All the ports which are used by bundles. Collected here to be able to
|
||||
easily find available ports for other bundles.
|
||||
|
||||
## TCP
|
||||
Rule of thumb: keep ports below 10000 free for stuff that reserves ports.
|
||||
|
||||
| Port | bundle | usage |
|
||||
| ----------- | -------------------- | ----- |
|
||||
| 22 | openssh | sshd |
|
||||
| 25 | postfix | postfix postscreen |
|
||||
| 53 | powerdns | dns server |
|
||||
| 80 | nginx | http |
|
||||
| 113 | oidentd | oidentd |
|
||||
| 143 | dovecot | dovecot imap |
|
||||
| 443 | nginx | https |
|
||||
| 587 | postfix | postfix submission |
|
||||
| 993 | dovecot | dovecot imap |
|
||||
| 2525 | postfix | postfix postscreen |
|
||||
| 4190 | dovecot | dovecot managesieve |
|
||||
| 5232 | radicale | radicale |
|
||||
| 5432 | postgresql | postgres |
|
||||
| 5900 | vmhost | qemu-system-x86 |
|
||||
| 6379 | redis | redis |
|
||||
| 6667 | | bitlbee |
|
||||
| 8086 | influxdb2 | influx |
|
||||
| 11332-11334 | rspamd | rspamd |
|
||||
| 20000 | mx-puppet-discord | Bridge |
|
||||
| 20010 | mautrix-telegram | Bridge |
|
||||
| 20020 | mautrix-whatsapp | Bridge |
|
||||
| 20030 | matrix-dimension | Matrix Integrations Manager|
|
||||
| 20070 | matrix-synapse | sliding-sync |
|
||||
| 20080 | matrix-synapse | client, federation |
|
||||
| 20081 | matrix-synapse | prometheus metrics |
|
||||
| 20090 | matrix-media-repo | media_repo |
|
||||
| 20090 | matrix-media-repo | prometheus metrics |
|
||||
| 21010 | grafana | grafana |
|
||||
| 22000 | forgejo | forgejo |
|
||||
| 22010 | jenkins-ci | Jenkins CI |
|
||||
| 22020 | travelynx | Travelynx Web |
|
||||
| 22030 | octoprint | OctoPrint Web Interface |
|
||||
| 22040 | miniflux | Miniflux Web Interface |
|
||||
| 22050 | radicale | radicale carddav and caldav server |
|
||||
| 22060 | pretalx | gunicorn |
|
||||
| 22070 | paperless-ng | gunicorn |
|
||||
| 22080 | netbox | gunicorn |
|
||||
| 22090 | jugendhackt_tools | gunicorn |
|
||||
| 22100 | powerdnsadmin | gunicorn |
|
||||
| 22110 | icinga2-statuspage | gunicorn |
|
||||
| 22999 | nginx | stub_status |
|
||||
| 22100 | ntfy | http |
|
||||
|
||||
## UDP
|
||||
| Port | bundle | usage |
|
||||
| ----------- | -------------------- | ----- |
|
||||
| 53 | powerdns | dns server |
|
||||
| 15000-15100 | voc-loudness-monitor | ffmpeg processes outputting rtp streams |
|
15
README.md
15
README.md
|
@ -6,4 +6,17 @@ May also include some dummy nodes, for example for deploying websites
|
|||
onto shared webhosting.
|
||||
|
||||
`bw test` runs according to Jenkinsfile after every commit.
|
||||
[![Build Status](https://jenkins.kunsmann.eu/buildStatus/icon?job=bundlewrap%2Fmaster)](https://jenkins.kunsmann.eu/job/bundlewrap/job/master/)
|
||||
[![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}
|
|
@ -1,3 +0,0 @@
|
|||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
APT::Periodic::AutocleanInterval "7";
|
|
@ -1,27 +0,0 @@
|
|||
Unattended-Upgrade::Origins-Pattern {
|
||||
"origin=Debian,codename=${node.metadata['os_release']},label=Debian";
|
||||
"origin=Debian,codename=${node.metadata['os_release']},label=Debian-Security";
|
||||
|
||||
// External packages
|
||||
% for item in sorted(data.get('origins', set())):
|
||||
"${item}";
|
||||
% endfor
|
||||
};
|
||||
|
||||
Unattended-Upgrade::AutoFixInterruptedDpkg "true";
|
||||
Unattended-Upgrade::MinimalSteps "false";
|
||||
|
||||
% if data.get('mail', None):
|
||||
Unattended-Upgrade::Mail "${data['mail']}";
|
||||
Unattended-Upgrade::MailOnlyOnError "false";
|
||||
% endif
|
||||
|
||||
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
|
||||
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
|
||||
Unattended-Upgrade::Remove-Unused-Dependencies "true";
|
||||
|
||||
% if data.get('reboot', True):
|
||||
Unattended-Upgrade::Automatic-Reboot "true";
|
||||
% else:
|
||||
Unattended-Upgrade::Automatic-Reboot "false";
|
||||
% endif
|
38
bundles/apt/files/check_unattended_upgrades
Normal file
38
bundles/apt/files/check_unattended_upgrades
Normal file
|
@ -0,0 +1,38 @@
|
|||
#!/bin/bash
|
||||
|
||||
statusfile="/var/tmp/unattended_upgrades.status"
|
||||
if ! [[ -f "$statusfile" ]]
|
||||
then
|
||||
echo "Status file not found"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
mtime=$(stat -c %Y $statusfile)
|
||||
now=$(date +%s)
|
||||
if (( $now - $mtime > 60*60*24*8 ))
|
||||
then
|
||||
echo "Status file is older than 8 days!"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
exitcode=$(cat $statusfile)
|
||||
case "$exitcode" in
|
||||
abort_ssh)
|
||||
echo "Upgrades skipped due to active SSH login"
|
||||
exit 1
|
||||
;;
|
||||
0)
|
||||
if [[ -f /var/run/reboot-required ]]
|
||||
then
|
||||
echo "OK, but updates require a reboot"
|
||||
exit 1
|
||||
else
|
||||
echo "OK"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Last exitcode was $exitcode"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
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
|
47
bundles/apt/files/do-unattended-upgrades
Normal file
47
bundles/apt/files/do-unattended-upgrades
Normal file
|
@ -0,0 +1,47 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
apt-get update
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q -o Dpkg::Options::=--force-confold dist-upgrade
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q autoremove
|
||||
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -y -q clean
|
||||
|
||||
% if clean_old_kernels:
|
||||
existing=$(dpkg --get-selections | grep -E '^linux-(image|headers)-[0-9]' || true)
|
||||
|
||||
if [[ -z "$existing" ]]
|
||||
then
|
||||
echo "ERROR: No installed kernels found! Aborting!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
current=$(uname -r | sed -r 's/-[a-zA-Z]+$//')
|
||||
latest=$(echo "$existing" | sort --version-sort -t- -k 3,4 | tail -n 1 | sed -r 's/[^0-9]+([0-9]\.[^-]+-[0-9]+).*/\1/')
|
||||
todelete=$(echo "$existing" | grep -v -E "($current|$latest)" | awk '{ print $1 }' || true)
|
||||
|
||||
if [[ -n "$todelete" ]]
|
||||
then
|
||||
DEBIAN_FRONTEND=noninteractive apt-get -qy purge $todelete
|
||||
fi
|
||||
% endif
|
||||
|
||||
% for command in sorted(additional_update_commands):
|
||||
${command}
|
||||
% endfor
|
||||
|
||||
% for affected, restarts in sorted(restart_triggers.items()):
|
||||
up_since=$(systemctl show "${affected}" | sed -n 's/^ActiveEnterTimestamp=//p' || echo 0)
|
||||
up_since_ts=$(date -d "$up_since" +%s || echo 0)
|
||||
now=$(date +%s)
|
||||
|
||||
if [ $((now - up_since_ts)) -lt 3600 ]
|
||||
then
|
||||
% for restart in sorted(restarts):
|
||||
systemctl restart "${restart}" || true
|
||||
% endfor
|
||||
fi
|
||||
% endfor
|
15
bundles/apt/files/kernel-postinst.d
Normal file
15
bundles/apt/files/kernel-postinst.d
Normal file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/sh
|
||||
|
||||
# /etc/kernel/postinst.d/unattended-upgrades
|
||||
|
||||
case "$DPKG_MAINTSCRIPT_PACKAGE::$DPKG_MAINTSCRIPT_NAME" in
|
||||
linux-image-extra*::postrm)
|
||||
exit 0;;
|
||||
esac
|
||||
|
||||
if [ -d /var/run ]; then
|
||||
touch /var/run/reboot-required
|
||||
if ! grep -q "^$DPKG_MAINTSCRIPT_PACKAGE$" /var/run/reboot-required.pkgs 2> /dev/null ; then
|
||||
echo "$DPKG_MAINTSCRIPT_PACKAGE" >> /var/run/reboot-required.pkgs
|
||||
fi
|
||||
fi
|
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
|
3
bundles/apt/files/sources.list-debian-bullseye
Normal file
3
bundles/apt/files/sources.list-debian-bullseye
Normal file
|
@ -0,0 +1,3 @@
|
|||
deb http://deb.debian.org/debian/ bullseye main non-free contrib
|
||||
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
|
||||
deb http://deb.debian.org/debian/ bullseye-updates main contrib non-free
|
3
bundles/apt/files/sources.list-debian-buster
Normal file
3
bundles/apt/files/sources.list-debian-buster
Normal file
|
@ -0,0 +1,3 @@
|
|||
deb http://deb.debian.org/debian/ buster main non-free contrib
|
||||
deb http://security.debian.org/debian-security buster/updates main contrib non-free
|
||||
deb http://deb.debian.org/debian/ buster-updates main contrib non-free
|
1
bundles/apt/files/sources.list-debian-unstable
Normal file
1
bundles/apt/files/sources.list-debian-unstable
Normal file
|
@ -0,0 +1 @@
|
|||
deb http://deb.debian.org/debian/ unstable main non-free contrib
|
1
bundles/apt/files/sources.list-raspbian-buster
Normal file
1
bundles/apt/files/sources.list-raspbian-buster
Normal file
|
@ -0,0 +1 @@
|
|||
deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi
|
52
bundles/apt/files/upgrade-and-reboot
Normal file
52
bundles/apt/files/upgrade-and-reboot
Normal file
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
# With systemd, we can force logging to the journal. This is better than
|
||||
# spamming the world with cron mails. You can then view these logs using
|
||||
# "journalctl -rat upgrade-and-reboot".
|
||||
if which logger >/dev/null 2>&1
|
||||
then
|
||||
# Dump stdout and stderr to logger, which will then put everything
|
||||
# into the journal.
|
||||
exec 1> >(logger -t upgrade-and-reboot -p user.info)
|
||||
exec 2> >(logger -t upgrade-and-reboot -p user.error)
|
||||
fi
|
||||
|
||||
. /etc/upgrade-and-reboot.conf
|
||||
|
||||
echo "Starting upgrade-and-reboot for node $nodename ..."
|
||||
|
||||
statusfile="/var/tmp/unattended_upgrades.status"
|
||||
# Workaround, because /var/tmp is usually 1777
|
||||
[[ "$UID" == 0 ]] && chown root:root "$statusfile"
|
||||
|
||||
logins=$(ps h -C sshd -o euser | awk '$1 != "root" && $1 != "sshd" && $1 != "sshmon" && $1 != "nobody"')
|
||||
if [[ -n "$logins" ]]
|
||||
then
|
||||
echo "Will abort now, there are active SSH logins: $logins"
|
||||
echo "abort_ssh" > "$statusfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
softlockdir=/var/lib/bundlewrap/soft-$nodename
|
||||
mkdir -p "$softlockdir"
|
||||
printf '{"comment": "UPDATE", "date": %s, "expiry": %s, "id": "UNATTENDED", "items": ["*"], "user": "root@localhost"}\n' \
|
||||
$(date +%s) \
|
||||
$(date -d 'now + 30 mins' +%s) \
|
||||
>"$softlockdir"/UNATTENDED
|
||||
trap 'rm -f "$softlockdir"/UNATTENDED' EXIT
|
||||
|
||||
do-unattended-upgrades
|
||||
ret=$?
|
||||
|
||||
echo "$ret" > "$statusfile"
|
||||
if (( $ret != 0 ))
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f /var/run/reboot-required ]] && [[ "$auto_reboot_enabled" == "True" ]]
|
||||
then
|
||||
systemctl reboot
|
||||
fi
|
||||
|
||||
echo "upgrade-and-reboot for node $nodename is DONE"
|
2
bundles/apt/files/upgrade-and-reboot.conf
Normal file
2
bundles/apt/files/upgrade-and-reboot.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
nodename="${node.name}"
|
||||
auto_reboot_enabled="${node.metadata.get('apt/unattended-upgrades/reboot_enabled', True)}"
|
|
@ -1,3 +1,23 @@
|
|||
from bundlewrap.exceptions import BundleError
|
||||
|
||||
supported_os = {
|
||||
'debian': {
|
||||
10: 'buster',
|
||||
11: 'bullseye',
|
||||
12: 'bookworm',
|
||||
99: 'unstable',
|
||||
},
|
||||
'raspbian': {
|
||||
10: 'buster',
|
||||
},
|
||||
}
|
||||
|
||||
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')
|
||||
|
||||
|
||||
actions = {
|
||||
'apt_update': {
|
||||
'command': 'apt-get update',
|
||||
|
@ -10,39 +30,76 @@ actions = {
|
|||
}
|
||||
|
||||
files = {
|
||||
'/etc/apt/apt.conf.d/50unattended-upgrades': {
|
||||
'content_type': 'mako',
|
||||
'source': 'apt.conf-unattended-upgrades',
|
||||
'context': {'data': node.metadata.get('apt', {}).get('unattended-upgrades', {})}
|
||||
'/etc/apt/sources.list': {
|
||||
'source': 'sources.list-{}-{}'.format(node.os, supported_os[node.os][node.os_version[0]]),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
'/etc/apt/apt.conf.d/20auto-upgrades': {
|
||||
'source': 'apt.conf-auto-upgrades',
|
||||
},
|
||||
'/etc/cloud': {
|
||||
'delete': True,
|
||||
},
|
||||
'/etc/kernel/postinst.d/unattended-upgrades': {
|
||||
'source': 'kernel-postinst.d',
|
||||
'mode': '0755',
|
||||
},
|
||||
'/etc/netplan': {
|
||||
'delete': True,
|
||||
},
|
||||
'/etc/upgrade-and-reboot.conf': {
|
||||
'content_type': 'mako',
|
||||
},
|
||||
'/usr/local/sbin/upgrade-and-reboot': {
|
||||
'mode': '0700',
|
||||
},
|
||||
'/usr/local/sbin/do-unattended-upgrades': {
|
||||
'content_type': 'mako',
|
||||
'mode': '0700',
|
||||
'context': {
|
||||
'additional_update_commands': node.metadata.get('apt/additional_update_commands', set()),
|
||||
'clean_old_kernels': node.metadata.get('apt/clean_old_kernels', True),
|
||||
'restart_triggers': node.metadata.get('apt/restart_triggers', {}),
|
||||
}
|
||||
},
|
||||
'/usr/local/share/icinga/plugins/check_unattended_upgrades': {
|
||||
'mode': '0755',
|
||||
},
|
||||
'/var/lib/cloud': {
|
||||
'delete': True,
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/etc/apt/sources.list.d': {},
|
||||
'/etc/apt/sources.list.d': {
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'apt-daily.timer': {
|
||||
'running': False,
|
||||
'enabled': False,
|
||||
},
|
||||
'apt-daily-upgrade.timer': {
|
||||
'running': False,
|
||||
'enabled': False,
|
||||
},
|
||||
}
|
||||
|
||||
pkg_apt = {
|
||||
'apt-transport-https': {},
|
||||
'unattended-upgrades': {},
|
||||
|
||||
'arping': {},
|
||||
'at': {},
|
||||
'build-essential': {},
|
||||
'bzip2': {},
|
||||
'curl': {},
|
||||
'diffutils': {},
|
||||
'dnsutils': {},
|
||||
'git': {},
|
||||
'grep': {},
|
||||
'gzip': {},
|
||||
'htop': {},
|
||||
|
@ -52,20 +109,34 @@ pkg_apt = {
|
|||
'lsof': {},
|
||||
'mailutils': {},
|
||||
'manpages': {},
|
||||
'molly-guard': {},
|
||||
'moreutils': {},
|
||||
'mount': {},
|
||||
'mtr': {},
|
||||
'ncdu': {},
|
||||
'netcat': {},
|
||||
'ncurses-term': {},
|
||||
'netcat-openbsd': {},
|
||||
'nmap': {},
|
||||
'python3': {},
|
||||
'python3-dev': {},
|
||||
'python3-setuptools': {
|
||||
'needed_by': {
|
||||
'pkg_pip:',
|
||||
},
|
||||
},
|
||||
'python3-pip': {
|
||||
'needed_by': {
|
||||
'pkg_pip:',
|
||||
},
|
||||
},
|
||||
'python3-virtualenv': {},
|
||||
'rsync': {},
|
||||
'tar': {},
|
||||
'tcpdump': {},
|
||||
'telnet': {},
|
||||
'tmux': {},
|
||||
'tree': {},
|
||||
'unzip': {},
|
||||
'vim': {},
|
||||
'wget': {},
|
||||
'whois': {},
|
||||
'zip': {},
|
||||
|
@ -73,22 +144,69 @@ pkg_apt = {
|
|||
'cloud-init': {
|
||||
'installed': False,
|
||||
},
|
||||
'molly-guard': {
|
||||
'installed': False,
|
||||
},
|
||||
'netplan.io': {
|
||||
'installed': False,
|
||||
},
|
||||
'popularity-contest': {
|
||||
'installed': False,
|
||||
},
|
||||
'python3-packaging': {
|
||||
'installed': False,
|
||||
},
|
||||
'unattended-upgrades': {
|
||||
'installed': False,
|
||||
},
|
||||
}
|
||||
|
||||
if node.os_version[0] >= 11:
|
||||
symlinks = {
|
||||
'/usr/bin/python': {
|
||||
'target': '/usr/bin/python3',
|
||||
'needs': {
|
||||
'pkg_apt:python3',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, data in node.metadata.get('apt', {}).get('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)] = {
|
||||
'content_type': 'mako',
|
||||
'content': "\n".join(data['items']),
|
||||
'content': ("\n".join(sorted(data['items']))).format(
|
||||
os=node.os,
|
||||
os_release=supported_os[node.os][node.os_version[0]],
|
||||
),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
elif 'uris' in data:
|
||||
uris = {
|
||||
x.format(
|
||||
os=node.os,
|
||||
os_release=supported_os[node.os][node.os_version[0]],
|
||||
) for x in data['uris']
|
||||
}
|
||||
|
||||
files['/etc/apt/sources.list.d/{}.sources'.format(name)] = {
|
||||
'source': 'deb822-sources',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'data': data,
|
||||
'name': name,
|
||||
'os_release': supported_os[node.os][node.os_version[0]],
|
||||
'uris': uris,
|
||||
},
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
if data.get('install_gpg_key', True):
|
||||
if 'items' in data:
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||
}
|
||||
|
@ -100,6 +218,5 @@ for name, data in node.metadata.get('apt', {}).get('repos', {}).items():
|
|||
},
|
||||
}
|
||||
|
||||
if node.metadata.get('apt', {}).get('packages', {}):
|
||||
for package, options in node.metadata['apt']['packages'].items():
|
||||
for package, options in node.metadata.get('apt/packages', {}).items():
|
||||
pkg_apt[package] = options
|
||||
|
|
42
bundles/apt/metadata.py
Normal file
42
bundles/apt/metadata.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'unattended-upgrades': {
|
||||
'day': 5,
|
||||
'hour': 21,
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'apt': {
|
||||
'services': {
|
||||
'UNATTENDED UPGRADES': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_unattended_upgrades',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'cron/jobs/upgrade-and-reboot'
|
||||
)
|
||||
def patchday(metadata):
|
||||
day = metadata.get('apt/unattended-upgrades/day')
|
||||
hour = metadata.get('apt/unattended-upgrades/hour')
|
||||
|
||||
spread = metadata.get('apt/unattended-upgrades/spread_in_group', None)
|
||||
if spread is not None:
|
||||
spread_nodes = sorted(repo.nodes_in_group(spread))
|
||||
day += spread_nodes.index(node)
|
||||
|
||||
return {
|
||||
'cron': {
|
||||
'jobs': {
|
||||
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||
minute=node.magic_number % 30,
|
||||
hour=hour,
|
||||
day=day%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" }
|
||||
]
|
3
bundles/arch-with-gui/files/autologin.conf
Normal file
3
bundles/arch-with-gui/files/autologin.conf
Normal file
|
@ -0,0 +1,3 @@
|
|||
[Autologin]
|
||||
User=${user}
|
||||
Session=i3.desktop
|
110
bundles/arch-with-gui/items.py
Normal file
110
bundles/arch-with-gui/items.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
from os import listdir
|
||||
from os.path import join
|
||||
|
||||
actions = {
|
||||
'fc-cache_flush': {
|
||||
'command': 'fc-cache -f',
|
||||
'triggered': True,
|
||||
'needs': {
|
||||
'pkg_pacman:fontconfig',
|
||||
},
|
||||
},
|
||||
'i3pystatus_create_virtualenv': {
|
||||
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/i3pystatus/venv/',
|
||||
'unless': 'test -d /opt/i3pystatus/venv/',
|
||||
'needs': {
|
||||
'directory:/opt/i3pystatus/src',
|
||||
'pkg_pacman:python-virtualenv',
|
||||
},
|
||||
},
|
||||
'i3pystatus_install': {
|
||||
'command': ' && '.join([
|
||||
'cd /opt/i3pystatus/src',
|
||||
'/opt/i3pystatus/venv/bin/pip install --upgrade pip colour netifaces basiciw pytz',
|
||||
'/opt/i3pystatus/venv/bin/pip install --upgrade -e .',
|
||||
]),
|
||||
'needs': {
|
||||
'action:i3pystatus_create_virtualenv',
|
||||
},
|
||||
'triggered': True,
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/etc/sddm.conf.d': {
|
||||
'purge': True,
|
||||
},
|
||||
'/opt/i3pystatus/src': {},
|
||||
'/usr/share/fonts/bundlewrap': {
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'action:fc-cache_flush',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'avahi-daemon': {
|
||||
'needs': {
|
||||
'pkg_pacman:avahi',
|
||||
},
|
||||
},
|
||||
'sddm': {
|
||||
'needs': {
|
||||
'pkg_pacman:sddm',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
git_deploy = {
|
||||
'/opt/i3pystatus/src': {
|
||||
'repo': 'https://github.com/enkore/i3pystatus.git',
|
||||
'rev': 'current',
|
||||
'triggers': {
|
||||
'action:i3pystatus_install',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files['/etc/pipewire/pipewire-pulse.conf.d/50-network.conf'] = {}
|
||||
|
||||
for filename in listdir(join(repo.path, 'data', 'arch-with-gui', 'files', 'fonts')):
|
||||
if filename.startswith('.'):
|
||||
continue
|
||||
|
||||
if filename.endswith('.vault'):
|
||||
# XXX remove this once we have a new bundlewrap release
|
||||
# https://github.com/bundlewrap/bundlewrap/commit/2429b153dd1ca6781cf3812e2dec9c2b646a546b
|
||||
from os import environ
|
||||
if environ.get('BW_VAULT_DUMMY_MODE', '0') == '1':
|
||||
continue
|
||||
|
||||
font_name = filename[:-6]
|
||||
attrs = {
|
||||
'content': repo.vault.decrypt_file_as_base64(join('arch-with-gui', 'files', 'fonts', filename)),
|
||||
'content_type': 'base64',
|
||||
}
|
||||
else:
|
||||
font_name = filename
|
||||
attrs = {
|
||||
'source': join('fonts', filename),
|
||||
'content_type': 'binary',
|
||||
}
|
||||
|
||||
files[f'/usr/share/fonts/bundlewrap/{font_name}'] = {
|
||||
'triggers': {
|
||||
'action:fc-cache_flush',
|
||||
},
|
||||
**attrs,
|
||||
}
|
||||
|
||||
if node.metadata.get('arch-with-gui/autologin_as', None):
|
||||
files['/etc/sddm.conf.d/autologin.conf'] = {
|
||||
'context': {
|
||||
'user': node.metadata.get('arch-with-gui/autologin_as'),
|
||||
},
|
||||
'content_type': 'mako',
|
||||
'before': {
|
||||
'svc_systemd:sddm',
|
||||
},
|
||||
}
|
124
bundles/arch-with-gui/metadata.py
Normal file
124
bundles/arch-with-gui/metadata.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
assert node.os == 'arch'
|
||||
|
||||
defaults = {
|
||||
'backups': {
|
||||
'paths': {
|
||||
'/etc/netctl',
|
||||
},
|
||||
},
|
||||
'icinga_options': {
|
||||
'exclude_from_monitoring': True,
|
||||
},
|
||||
'nftables': {
|
||||
'input': {
|
||||
'50-avahi': {
|
||||
'udp dport 5353 accept',
|
||||
'udp sport 5353 accept',
|
||||
},
|
||||
},
|
||||
},
|
||||
'pacman': {
|
||||
'packages': {
|
||||
# fonts
|
||||
'fontconfig': {},
|
||||
'ttf-dejavu': {
|
||||
'needed_by': {
|
||||
'pkg_pacman:sddm',
|
||||
},
|
||||
},
|
||||
|
||||
# login management
|
||||
'sddm': {},
|
||||
|
||||
# networking
|
||||
'avahi': {},
|
||||
'netctl': {},
|
||||
'rfkill': {},
|
||||
'wpa_supplicant': {},
|
||||
'wpa_actiond': {},
|
||||
|
||||
# shell and other gui stuff
|
||||
'dunst': {},
|
||||
'fish': {},
|
||||
'kitty': {},
|
||||
'libnotify': {}, # provides notify-send
|
||||
'light': {},
|
||||
'redshift': {},
|
||||
'rofi': {},
|
||||
|
||||
# sound
|
||||
'calf': {},
|
||||
'easyeffects': {},
|
||||
'lsp-plugins': {},
|
||||
'pavucontrol': {},
|
||||
'pipewire': {},
|
||||
'pipewire-jack': {},
|
||||
'pipewire-pulse': {},
|
||||
'pipewire-zeroconf': {},
|
||||
'qpwgraph': {},
|
||||
|
||||
# window management
|
||||
'i3-wm': {},
|
||||
'i3lock': {},
|
||||
'xss-lock': {},
|
||||
|
||||
# i3pystatus dependencies
|
||||
'iw': {},
|
||||
'wireless_tools': {},
|
||||
|
||||
# Xorg
|
||||
'xf86-input-libinput': {},
|
||||
'xf86-input-wacom': {},
|
||||
'xorg-server': {},
|
||||
'xorg-setxkbmap': {},
|
||||
'xorg-xev': {},
|
||||
'xorg-xinput': {},
|
||||
'xorg-xset': {},
|
||||
|
||||
# all them apps
|
||||
'browserpass': {},
|
||||
'browserpass-firefox': {},
|
||||
'ffmpeg': {},
|
||||
'firefox': {},
|
||||
'gimp': {},
|
||||
'imagemagick': {},
|
||||
'inkscape': {},
|
||||
'kdenlive': {},
|
||||
'maim': {},
|
||||
'mosh': {},
|
||||
'mosquitto': {},
|
||||
'mpv': {},
|
||||
'pass': {},
|
||||
'pass-otp': {},
|
||||
'pdftk': {},
|
||||
'pwgen': {},
|
||||
'qpdfview': {},
|
||||
'samba': {},
|
||||
'shotcut': {},
|
||||
'sipcalc': {},
|
||||
'the_silver_searcher': {},
|
||||
'tlp': {},
|
||||
'virt-manager': {},
|
||||
'xclip': {},
|
||||
'xdotool': {}, # needed for maim window selection
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'backups/paths',
|
||||
)
|
||||
def backup_every_user_home(metadata):
|
||||
paths = set()
|
||||
|
||||
for user, config in metadata.get('users', {}).items():
|
||||
if config.get('delete', False):
|
||||
continue
|
||||
|
||||
paths.add(config.get('home', f'/home/{user}'))
|
||||
|
||||
return {
|
||||
'backups': {
|
||||
'paths': paths,
|
||||
},
|
||||
}
|
28
bundles/backup-client/files/check_backup_last_run
Normal file
28
bundles/backup-client/files/check_backup_last_run
Normal file
|
@ -0,0 +1,28 @@
|
|||
#!/bin/bash
|
||||
|
||||
statusfile="/var/tmp/backup.monitoring"
|
||||
|
||||
if [[ ! -r "$statusfile" ]]
|
||||
then
|
||||
echo "cannot read $statusfile"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
. "$statusfile"
|
||||
|
||||
if [[ -z "$msg" ]] || [[ -z "$status" ]] || [[ -z "$timestamp" ]]
|
||||
then
|
||||
echo "status file is corrupt, cannot read status"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
two_days_ago=$(($(date +%s) - 86400*2))
|
||||
|
||||
if [[ $timestamp -lt $two_days_ago ]]
|
||||
then
|
||||
echo "last saved status is older than two days"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "$msg"
|
||||
exit "$status"
|
121
bundles/backup-client/files/generate-backup
Normal file
121
bundles/backup-client/files/generate-backup
Normal file
|
@ -0,0 +1,121 @@
|
|||
#!/bin/bash
|
||||
|
||||
statusfile="/var/tmp/backup.monitoring"
|
||||
logdir="/var/log/backup-client"
|
||||
lock="/tmp/backup-client-is-running"
|
||||
ssh_login="${username}@${server}"
|
||||
ssh_opts="-o IdentityFile=/etc/backup.priv -o StrictHostKeyChecking=accept-new -p ${port}"
|
||||
nodename="${node.name}"
|
||||
|
||||
<%text>
|
||||
try="${1:-<unknown>}"
|
||||
[[ -n "$DEBUG" ]] && set -x
|
||||
|
||||
do_backup() {
|
||||
echo "==> starting backup for '$1'"
|
||||
|
||||
# Compress level 1 is a good compromise between speed and cpu usage.
|
||||
rsync --compress-level=1 -aAP --numeric-ids --delete --relative \
|
||||
--rsync-path="/usr/bin/rsync --fake-super" \
|
||||
-e "ssh $ssh_opts" \
|
||||
"$1" "$ssh_login":backups/
|
||||
|
||||
# Exit code 24 means some files have vanished during rsync.
|
||||
# I don't know why, but this is very common, apparently?
|
||||
exitcode=$?
|
||||
echo "==> backup for '$1' exited $exitcode"
|
||||
if [[ $exitcode != 0 ]] && [[ $exitcode != 24 ]]
|
||||
then
|
||||
rsync_errors+=" $1 ($exitcode)"
|
||||
fi
|
||||
}
|
||||
|
||||
on_exit() {
|
||||
rmdir "$lock"
|
||||
echo "*** END BACKUP RUN $(date '+%F %T %z') ***"
|
||||
}
|
||||
|
||||
prepare_and_cleanup_logdir() {
|
||||
# rsync logs tend to get very large. That's why we pipe them through
|
||||
# gzip when writing. Because we're running multiple tries, we cannot
|
||||
# rely on logrotate to rotate the logs, we have to do it ourselves.
|
||||
# Of course that means we have to clean up after ourselves, too.
|
||||
mkdir -p "$logdir"
|
||||
find "$logdir" -type f -mtime +14 -name "*.log" -delete
|
||||
find "$logdir" -type f -mtime +14 -name "*.gz" -delete
|
||||
}
|
||||
|
||||
save_result_for_monitoring() {
|
||||
code=$1
|
||||
msg=$2
|
||||
printf "status=%q\n" "$code" > "$statusfile"
|
||||
printf "msg=%q\n" "$msg" >> "$statusfile"
|
||||
printf "timestamp=%q\n" "$(date +%s)" >> "$statusfile"
|
||||
}
|
||||
|
||||
if ! mkdir "$lock" >/dev/null 2>&1
|
||||
then
|
||||
save_result_for_monitoring 2 "could not get lock"
|
||||
exit 1
|
||||
fi
|
||||
trap "on_exit" EXIT
|
||||
|
||||
# redirect stdout and stderr to logfile
|
||||
prepare_and_cleanup_logdir
|
||||
logfile="$logdir/backup--$(date '+%F--%H-%M-%S')--$$.log.gz"
|
||||
echo "All log output will go to $logfile" | logger -it backup-client
|
||||
exec > >(gzip >"$logfile")
|
||||
exec 2>&1
|
||||
|
||||
# this is where the real work starts
|
||||
ts_begin=$(date +%s)
|
||||
|
||||
echo "*** BEGIN BACKUP RUN $(date '+%F %T %z') ***"
|
||||
echo "This is attempt $try"
|
||||
echo "using ssh options [$ssh_opts]"
|
||||
echo "using ssh login [$ssh_login]"
|
||||
|
||||
if ! [[ -f /etc/backup.priv ]]
|
||||
then
|
||||
save_result_for_monitoring 2 "/etc/backup.priv does not exist"
|
||||
exit 100
|
||||
fi
|
||||
|
||||
for i in /etc/backup-pre-hooks.d/*
|
||||
do
|
||||
[[ -x "$i" ]] || continue
|
||||
|
||||
echo "Running pre-hook '$i'"
|
||||
if ! $i
|
||||
then
|
||||
save_result_for_monitoring 2 "pre-hook '$i' failed to run"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
rsync_errors=""
|
||||
</%text>
|
||||
|
||||
% for path in sorted(paths):
|
||||
do_backup "${path}"
|
||||
% endfor
|
||||
|
||||
<%text>
|
||||
if [[ -n "$rsync_errors" ]]
|
||||
then
|
||||
save_result_for_monitoring 2 "rsync failed:$rsync_errors"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ssh $ssh_opts $ssh_login "sudo /usr/local/bin/rotate-single-backup-client $nodename" </dev/null
|
||||
ssh_error=$?
|
||||
if [[ $ssh_error -ne 0 ]]
|
||||
then
|
||||
save_result_for_monitoring 2 "rotating backups failed with status code $ssh_error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ts_end=$(date +%s)
|
||||
echo "Success"
|
||||
save_result_for_monitoring 0 "Backup finished at $(date '+%F %T %z') (took $((ts_end - ts_begin)) seconds)"
|
||||
</%text>
|
22
bundles/backup-client/files/generate-backup-with-retries
Normal file
22
bundles/backup-client/files/generate-backup-with-retries
Normal file
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Try generating a backup multiple times. If one attempt succeeds, we're
|
||||
# done. If not, there will be logs for every attempt, plus monitoring
|
||||
# will read the result of the last backup.
|
||||
for try in {1..3}
|
||||
do
|
||||
generate-backup "$try"
|
||||
exitcode=$?
|
||||
|
||||
if [[ $exitcode -eq 100 ]]
|
||||
then
|
||||
# fatal error, cannot recover
|
||||
exit 1
|
||||
elif [[ $exitcode -eq 0 ]]
|
||||
then
|
||||
# successful backup
|
||||
exit 0
|
||||
else
|
||||
sleep 60
|
||||
fi
|
||||
done
|
74
bundles/backup-client/items.py
Normal file
74
bundles/backup-client/items.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from os.path import join
|
||||
|
||||
if node.has_bundle('zfs'):
|
||||
wanted_paths = node.metadata.get('backups/paths', set())
|
||||
snapshot_paths = node.metadata.get('zfs/filesystems_with_backup_snapshots', {})
|
||||
backup_paths = set()
|
||||
|
||||
for path in wanted_paths:
|
||||
path_found = False
|
||||
for zfs_paths in snapshot_paths.values():
|
||||
if path in zfs_paths:
|
||||
backup_paths.add(f'/mnt/backup-snapshot{path}')
|
||||
path_found = True
|
||||
|
||||
if not path_found:
|
||||
backup_paths.add(path)
|
||||
else:
|
||||
backup_paths = node.metadata.get('backups/paths', set())
|
||||
|
||||
if node.metadata.get('backups/exclude_from_backups', False):
|
||||
# make sure nobody tries to do something funny
|
||||
for file in {
|
||||
'/etc/backup.priv',
|
||||
'/usr/local/bin/generate-backup',
|
||||
'/usr/local/bin/generate-backup-with-retries',
|
||||
'/var/tmp/backup.monitoring', # status file
|
||||
}:
|
||||
files[file] = {
|
||||
'delete': True,
|
||||
}
|
||||
|
||||
else:
|
||||
backup_target = repo.get_node(node.metadata.get('backup-client/target'))
|
||||
|
||||
files['/etc/backup.priv'] = {
|
||||
'content': repo.libs.ssh.generate_ed25519_private_key(
|
||||
node.metadata.get('backup-client/user-name'),
|
||||
backup_target,
|
||||
),
|
||||
'mode': '0400',
|
||||
}
|
||||
|
||||
files['/usr/local/bin/generate-backup'] = {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'username': node.metadata.get('backup-client/user-name'),
|
||||
'server': backup_target.metadata.get('backup-server/my_hostname'),
|
||||
'port': backup_target.metadata.get('backup-server/my_ssh_port'),
|
||||
'paths': backup_paths,
|
||||
},
|
||||
'mode': '0700',
|
||||
}
|
||||
|
||||
files['/usr/local/bin/generate-backup-with-retries'] = {
|
||||
'mode': '0700',
|
||||
}
|
||||
|
||||
files['/usr/local/share/icinga/plugins/check_backup_last_run'] = {
|
||||
'mode': '0755',
|
||||
}
|
||||
|
||||
files['/etc/logrotate.d/backup-client'] = {
|
||||
'delete': True,
|
||||
}
|
||||
|
||||
directories['/etc/backup-pre-hooks.d'] = {
|
||||
'purge': True,
|
||||
}
|
||||
|
||||
for hname, hcontent in node.metadata.get('backup-client/pre-hooks', {}).items():
|
||||
files[f'/etc/backup-pre-hooks.d/50-{hname}'] = {
|
||||
'content': '#!/bin/sh\n\n' + hcontent,
|
||||
'mode': '0700',
|
||||
}
|
39
bundles/backup-client/metadata.py
Normal file
39
bundles/backup-client/metadata.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from hashlib import md5
|
||||
|
||||
defaults = {
|
||||
'backup-client': {
|
||||
# unix user names cannot be longer than 32 characters.
|
||||
# bundlewrap raises an error if the name is longer than 30 chars.
|
||||
'user-name': 'c-' + md5(node.name.encode('UTF-8')).hexdigest()[:28],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'cron/jobs/backup',
|
||||
'icinga2_api/backup-client/services',
|
||||
)
|
||||
def cron(metadata):
|
||||
if metadata.get('backups/exclude_from_backups', False):
|
||||
return {}
|
||||
|
||||
return {
|
||||
'cron': {
|
||||
'jobs': {
|
||||
# spread backups between 00:00 and 04:59 UTC
|
||||
'backup': '{} {} * * * root /usr/local/bin/generate-backup-with-retries'.format(
|
||||
(node.magic_number % 60),
|
||||
(node.magic_number % 2),
|
||||
),
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'backup-client': {
|
||||
'services': {
|
||||
'BACKUP LAST RUN': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_backup_last_run',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
57
bundles/backup-server/files/check_backup_for_node
Normal file
57
bundles/backup-server/files/check_backup_for_node
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from datetime import datetime
|
||||
from json import load
|
||||
from subprocess import check_output
|
||||
from sys import argv, exit
|
||||
from time import time
|
||||
|
||||
NODE = argv[1]
|
||||
ONE_BACKUP_EVERY_HOURS = int(argv[2])
|
||||
|
||||
NOW = int(time())
|
||||
HOUR_SECONDS = 60 * 60
|
||||
|
||||
snaps = set()
|
||||
|
||||
try:
|
||||
with open(f'/etc/backup-server/config.json', 'r') as f:
|
||||
server_settings = load(f)
|
||||
|
||||
# get all existing snapshots for NODE
|
||||
for line in check_output('LC_ALL=C zfs list -H -t snapshot -o name', shell=True).splitlines():
|
||||
line = line.decode('UTF-8')
|
||||
|
||||
if line.startswith('{}/{}@'.format(server_settings['zfs-base'], NODE)):
|
||||
_, snapname = line.split('@', 1)
|
||||
|
||||
if 'zfs-auto-snap' in snapname:
|
||||
# migration from auto-snapshots, ignore
|
||||
continue
|
||||
|
||||
ts, bucket = snapname.split('-', 1)
|
||||
snaps.add(int(ts))
|
||||
|
||||
if not snaps:
|
||||
print('No backups found!')
|
||||
exit(2)
|
||||
|
||||
last_snap = sorted(snaps)[-1]
|
||||
delta = NOW - last_snap
|
||||
|
||||
print('Last backup was on {} UTC'.format(
|
||||
datetime.fromtimestamp(last_snap).strftime('%Y-%m-%d %H:%M:%S'),
|
||||
))
|
||||
|
||||
# One day without backups is still okay. There may be fluctuations
|
||||
# because of transfer speed, amount of data, changes in backup
|
||||
# schedule etc.
|
||||
if delta > ((HOUR_SECONDS * (ONE_BACKUP_EVERY_HOURS + 1)) + (HOUR_SECONDS*24)):
|
||||
exit(2)
|
||||
elif delta > (HOUR_SECONDS * (ONE_BACKUP_EVERY_HOURS + 1)):
|
||||
exit(1)
|
||||
else:
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
exit(3)
|
112
bundles/backup-server/files/rotate-single-backup-client
Normal file
112
bundles/backup-server/files/rotate-single-backup-client
Normal file
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from json import load
|
||||
from subprocess import check_call, check_output
|
||||
from sys import argv
|
||||
from time import time
|
||||
|
||||
NODE = argv[1]
|
||||
|
||||
NOW = int(time())
|
||||
DAY_SECONDS = 60 * 60 * 24
|
||||
INTERVALS = {
|
||||
'daily': DAY_SECONDS,
|
||||
'weekly': 7 * DAY_SECONDS,
|
||||
'monthly': 30 * DAY_SECONDS,
|
||||
}
|
||||
|
||||
buckets = {}
|
||||
|
||||
def syslog(msg):
|
||||
check_output(['logger', '-t', f'backup-{NODE}', msg])
|
||||
|
||||
|
||||
with open(f'/etc/backup-server/config.json', 'r') as f:
|
||||
server_settings = load(f)
|
||||
|
||||
with open(f'/etc/backup-server/clients/{NODE}', 'r') as f:
|
||||
client_settings = load(f)
|
||||
|
||||
# get all existing snapshots for NODE
|
||||
for line in check_output('LC_ALL=C zfs list -H -t snapshot -o name', shell=True).splitlines():
|
||||
line = line.decode('UTF-8')
|
||||
|
||||
if line.startswith('{}/{}@'.format(server_settings['zfs-base'], NODE)):
|
||||
_, snapname = line.split('@', 1)
|
||||
|
||||
if 'zfs-auto-snap' in snapname:
|
||||
# migration from auto-snapshots, ignore
|
||||
continue
|
||||
|
||||
ts, bucket = snapname.split('-', 1)
|
||||
buckets.setdefault(bucket, set()).add(int(ts))
|
||||
syslog(f'classified {line} as {bucket} from {ts}')
|
||||
|
||||
# determine if we need to create a new snapshot
|
||||
for bucket in INTERVALS.keys():
|
||||
snapshots = sorted(buckets.get(bucket, set()))
|
||||
|
||||
if snapshots:
|
||||
last_snap = snapshots[-1]
|
||||
delta = NOW - last_snap
|
||||
fresh_age = INTERVALS[bucket] - DAY_SECONDS
|
||||
|
||||
if delta > fresh_age:
|
||||
# last snapshot is older than what we want. create a new one.
|
||||
check_call(
|
||||
'zfs snapshot {}/{}@{}-{}'.format(
|
||||
server_settings['zfs-base'],
|
||||
NODE,
|
||||
NOW,
|
||||
bucket,
|
||||
),
|
||||
shell=True,
|
||||
)
|
||||
buckets.setdefault(bucket, set()).add(NOW)
|
||||
syslog(f'created new snapshot {NOW}-{bucket}')
|
||||
else:
|
||||
syslog(f'existing snapshot {last_snap}-{bucket} is fresh enough')
|
||||
else:
|
||||
check_call(
|
||||
'zfs snapshot {}/{}@{}-{}'.format(
|
||||
server_settings['zfs-base'],
|
||||
NODE,
|
||||
NOW,
|
||||
bucket,
|
||||
),
|
||||
shell=True,
|
||||
)
|
||||
buckets.setdefault(bucket, set()).add(NOW)
|
||||
syslog(f'created initial snapshot {NOW}-{bucket}')
|
||||
|
||||
# finally, see if we can delete any snapshots, because they are old enough
|
||||
for bucket in INTERVALS.keys():
|
||||
snapshots = sorted(buckets.get(bucket, set()))
|
||||
|
||||
if not snapshots:
|
||||
syslog(f'something is wrong, there are no snapshots for {bucket}')
|
||||
continue
|
||||
|
||||
# see comment in zfs-auto-snapshot about doing +1 here
|
||||
keep_age = INTERVALS[bucket] * (client_settings[bucket]+1)
|
||||
|
||||
# oldest snapshots come first
|
||||
for ts in snapshots[:-int(client_settings[bucket])]:
|
||||
delta = NOW - ts
|
||||
|
||||
if delta >= keep_age:
|
||||
check_call(
|
||||
'zfs destroy {}/{}@{}-{}'.format(
|
||||
server_settings['zfs-base'],
|
||||
NODE,
|
||||
ts,
|
||||
bucket,
|
||||
),
|
||||
shell=True,
|
||||
)
|
||||
syslog(f'removing snapshot {ts}-{bucket}, age {delta}, keep_age {keep_age}')
|
||||
else:
|
||||
syslog(f'keeping snapshot {ts}-{bucket}, age not reached')
|
||||
|
||||
for ts in snapshots[int(client_settings[bucket]):]:
|
||||
syslog(f'keeping snapshot {ts}-{bucket}, count')
|
3
bundles/backup-server/files/sudoers
Normal file
3
bundles/backup-server/files/sudoers
Normal file
|
@ -0,0 +1,3 @@
|
|||
% for username, nodename in sorted(clients.items()):
|
||||
${username} ALL=NOPASSWD:/usr/local/bin/rotate-single-backup-client ${nodename}
|
||||
% endfor
|
66
bundles/backup-server/items.py
Normal file
66
bundles/backup-server/items.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
repo.libs.tools.require_bundle(node, 'zfs')
|
||||
|
||||
from os.path import join
|
||||
|
||||
from bundlewrap.metadata import metadata_to_json
|
||||
|
||||
dataset = node.metadata.get('backup-server/zfs-base')
|
||||
|
||||
files = {
|
||||
'/etc/backup-server/config.json': {
|
||||
'content': metadata_to_json({
|
||||
'zfs-base': dataset,
|
||||
}),
|
||||
},
|
||||
'/usr/local/bin/rotate-single-backup-client': {
|
||||
'mode': '0755',
|
||||
},
|
||||
'/usr/local/share/icinga/plugins/check_backup_for_node': {
|
||||
'mode': '0755',
|
||||
},
|
||||
}
|
||||
|
||||
directories['/etc/backup-server/clients'] = {
|
||||
'purge': True,
|
||||
}
|
||||
|
||||
sudoers = {}
|
||||
|
||||
for nodename, config in node.metadata.get('backup-server/clients', {}).items():
|
||||
sudoers[config['user']] = nodename
|
||||
|
||||
users[config['user']] = {
|
||||
'home': f'/srv/backups/{nodename}',
|
||||
}
|
||||
|
||||
files[f'/etc/backup-server/clients/{nodename}'] = {
|
||||
'content': metadata_to_json(config['retain']),
|
||||
}
|
||||
|
||||
files[f'/srv/backups/{nodename}/.ssh/authorized_keys'] = {
|
||||
'content': repo.libs.ssh.generate_ed25519_public_key(
|
||||
config['user'],
|
||||
node,
|
||||
),
|
||||
'owner': config['user'],
|
||||
'mode': '0400',
|
||||
'needs': {
|
||||
f'zfs_dataset:{dataset}/{nodename}',
|
||||
},
|
||||
}
|
||||
|
||||
directories[f'/srv/backups/{nodename}/backups'] = {
|
||||
'owner': config['user'],
|
||||
'mode': '0700',
|
||||
'needs': {
|
||||
f'zfs_dataset:{dataset}/{nodename}',
|
||||
},
|
||||
}
|
||||
|
||||
files['/etc/sudoers.d/backup-server'] = {
|
||||
'source': 'sudoers',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'clients': sudoers,
|
||||
},
|
||||
}
|
172
bundles/backup-server/metadata.py
Normal file
172
bundles/backup-server/metadata.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
defaults = {
|
||||
'backup-server': {
|
||||
'my_ssh_port': 22,
|
||||
},
|
||||
'openssh': {
|
||||
'allowed_users': {
|
||||
# Usernames for backup clients always start with 'c-'
|
||||
'c-*',
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
# The whole point of doing backups is to keep them for a long
|
||||
# time, which eliminates the need for this check.
|
||||
'enable_old_snapshots_check': False,
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'backup-server/clients',
|
||||
'backup-server/my_hostname',
|
||||
)
|
||||
def get_my_clients(metadata):
|
||||
my_clients = {}
|
||||
retain_defaults = {
|
||||
'daily': 14,
|
||||
'weekly': 4,
|
||||
'monthly': 6,
|
||||
}
|
||||
|
||||
for rnode in repo.nodes:
|
||||
if not rnode.has_bundle('backup-client') or rnode.metadata.get('backups/exclude_from_backups', False):
|
||||
continue
|
||||
|
||||
if node.name != rnode.metadata.get('backup-client/target'):
|
||||
continue
|
||||
|
||||
my_clients[rnode.name] = {
|
||||
'exclude_from_monitoring': rnode.metadata.get(
|
||||
'backup-client/exclude_from_monitoring',
|
||||
rnode.metadata.get(
|
||||
'icinga_options/exclude_from_monitoring',
|
||||
False,
|
||||
),
|
||||
),
|
||||
'one_backup_every_hours': rnode.metadata.get('backup-client/one_backup_every_hours', 24),
|
||||
'user': rnode.metadata.get('backup-client/user-name'),
|
||||
'retain': {
|
||||
'daily': rnode.metadata.get('backups/retain/daily', retain_defaults['daily']),
|
||||
'weekly': rnode.metadata.get('backups/retain/weekly', retain_defaults['weekly']),
|
||||
'monthly': rnode.metadata.get('backups/retain/monthly', retain_defaults['monthly']),
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
'backup-server': {
|
||||
'clients': my_clients,
|
||||
'my_hostname': metadata.get('hostname'),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'backup-server/zfs-base',
|
||||
'dm-crypt/encrypted-devices',
|
||||
'zfs/pools',
|
||||
)
|
||||
def zfs_pool(metadata):
|
||||
if not metadata.get('backup-server/encrypted-devices', {}):
|
||||
return {}
|
||||
|
||||
crypt_devices = {}
|
||||
pool_devices = set()
|
||||
unlock_actions = set()
|
||||
|
||||
for number, (device, passphrase) in enumerate(sorted(metadata.get('backup-server/encrypted-devices', {}).items())):
|
||||
crypt_devices[device] = {
|
||||
'dm-name': f'backup{number}',
|
||||
'passphrase': passphrase,
|
||||
}
|
||||
pool_devices.add(f'/dev/mapper/backup{number}')
|
||||
unlock_actions.add(f'action:dm-crypt_open_backup{number}')
|
||||
|
||||
pool_opts = {
|
||||
'devices': pool_devices,
|
||||
}
|
||||
|
||||
if len(pool_devices) > 2:
|
||||
pool_opts['type'] = 'raidz'
|
||||
elif len(pool_devices) > 1:
|
||||
pool_opts['type'] = 'mirror'
|
||||
|
||||
return {
|
||||
'backup-server': {
|
||||
'zfs-base': 'backups',
|
||||
},
|
||||
'dm-crypt': {
|
||||
'encrypted-devices': crypt_devices,
|
||||
},
|
||||
'zfs': {
|
||||
'pools': {
|
||||
'backups': {
|
||||
'when_creating': {
|
||||
'config': [
|
||||
pool_opts,
|
||||
],
|
||||
},
|
||||
'needs': unlock_actions,
|
||||
# That's a bit hacky. We do it this way to auto-import
|
||||
# the pool after decrypting the devices. Otherwise
|
||||
# the pool wouldn't exist, which leads to bundlewrap
|
||||
# trying to re-create the pool.
|
||||
# Also, -N to not auto-mount anything.
|
||||
'unless': 'zpool import -N backups',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'zfs/datasets',
|
||||
'zfs/snapshots/snapshot_never',
|
||||
)
|
||||
def zfs_datasets_and_snapshots(metadata):
|
||||
zfs_datasets = {}
|
||||
|
||||
for client in metadata.get('backup-server/clients', {}).keys():
|
||||
dataset = '{}/{}'.format(metadata.get('backup-server/zfs-base'), client)
|
||||
|
||||
zfs_datasets[dataset] = {
|
||||
'mountpoint': '/srv/backups/{}'.format(client),
|
||||
'compression': 'on',
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
'zfs': {
|
||||
'datasets': zfs_datasets,
|
||||
'snapshots': {
|
||||
'snapshot_never': {
|
||||
metadata.get('backup-server/zfs-base'),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/backup-server/services',
|
||||
)
|
||||
def monitoring(metadata):
|
||||
services = {}
|
||||
|
||||
for client, config in metadata.get('backup-server/clients', {}).items():
|
||||
if config.get('exclude_from_monitoring', False):
|
||||
continue
|
||||
|
||||
services[f'BACKUPS FOR NODE {client}'] = {
|
||||
'command_on_monitored_host': 'sudo /usr/local/share/icinga/plugins/check_backup_for_node {} {}'.format(
|
||||
client,
|
||||
config['one_backup_every_hours'],
|
||||
),
|
||||
'vars.sshmon_timeout': 20,
|
||||
}
|
||||
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'backup-server': {
|
||||
'services': services,
|
||||
},
|
||||
},
|
||||
}
|
3
bundles/basic/files/environment
Normal file
3
bundles/basic/files/environment
Normal file
|
@ -0,0 +1,3 @@
|
|||
% for k, v in sorted(node.metadata.get('environment', {}).items()):
|
||||
${k}=${v}
|
||||
% endfor
|
|
@ -1,4 +1,4 @@
|
|||
127.0.0.1 localhost ${node.name} ${node.hostname}
|
||||
127.0.0.1 localhost ${node.name} ${node.metadata['hostname']}
|
||||
|
||||
::1 ip6-localhost
|
||||
fe00::0 ip6-localnet
|
||||
|
@ -7,6 +7,6 @@ ff02::1 ip6-allnodes
|
|||
ff02::2 ip6-allrouters
|
||||
ff02::3 ip6-allhosts
|
||||
|
||||
% for ip, entries in sorted(node.metadata.get('hosts', {}).get('entries', {}).items()):
|
||||
% for ip, entries in sorted(node.metadata.get('hosts/entries', {}).items()):
|
||||
${ip} ${' '.join(sorted(entries))}
|
||||
% endfor
|
39
bundles/basic/files/htoprc
Normal file
39
bundles/basic/files/htoprc
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Beware! This file is rewritten by htop when settings are changed in the interface.
|
||||
# The parser is also very primitive, and not human-friendly.
|
||||
fields=0 48 17 18 38 39 40 2 46 47 49 1
|
||||
sort_key=46
|
||||
sort_direction=-1
|
||||
tree_sort_key=0
|
||||
tree_sort_direction=1
|
||||
hide_kernel_threads=1
|
||||
hide_userland_threads=0
|
||||
shadow_other_users=0
|
||||
show_thread_names=0
|
||||
show_program_path=1
|
||||
highlight_base_name=1
|
||||
highlight_megabytes=0
|
||||
highlight_threads=1
|
||||
highlight_changes=0
|
||||
highlight_changes_delay_secs=5
|
||||
find_comm_in_cmdline=1
|
||||
strip_exe_from_cmdline=1
|
||||
show_merged_command=0
|
||||
tree_view=0
|
||||
tree_view_always_by_pid=0
|
||||
header_margin=1
|
||||
detailed_cpu_time=1
|
||||
cpu_count_from_one=1
|
||||
show_cpu_usage=1
|
||||
show_cpu_frequency=0
|
||||
show_cpu_temperature=0
|
||||
degree_fahrenheit=0
|
||||
update_process_names=0
|
||||
account_guest_in_cpu_meter=0
|
||||
color_scheme=0
|
||||
enable_mouse=0
|
||||
delay=10
|
||||
left_meters=Tasks LoadAverage Uptime Memory CPU LeftCPUs2 CPU
|
||||
left_meter_modes=2 2 2 1 1 1 2
|
||||
right_meters=Hostname CPU RightCPUs2
|
||||
right_meter_modes=2 3 1
|
||||
hide_function_bar=0
|
1
bundles/basic/files/locale
Normal file
1
bundles/basic/files/locale
Normal file
|
@ -0,0 +1 @@
|
|||
LANG=${node.metadata['locale']['default']}
|
3
bundles/basic/files/locale.gen
Normal file
3
bundles/basic/files/locale.gen
Normal file
|
@ -0,0 +1,3 @@
|
|||
% for locale in sorted(node.metadata['locale']['installed']):
|
||||
${locale} ${locale.split('.')[-1]}
|
||||
% endfor
|
105
bundles/basic/items.py
Normal file
105
bundles/basic/items.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
from inspect import cleandoc
|
||||
from uuid import UUID
|
||||
|
||||
from bundlewrap.utils.text import italic
|
||||
|
||||
files = {
|
||||
'/etc/default/locale': {
|
||||
'content_type': 'mako',
|
||||
'needs': {
|
||||
'action:locale-gen',
|
||||
},
|
||||
},
|
||||
'/etc/hosts': {
|
||||
'content_type': 'mako',
|
||||
},
|
||||
'/etc/htoprc.global': {
|
||||
'source': 'htoprc',
|
||||
},
|
||||
'/etc/motd': {
|
||||
'content': '',
|
||||
},
|
||||
'/etc/environment': {
|
||||
'content_type': 'mako',
|
||||
'before': {
|
||||
'action:',
|
||||
'pkg_apt:',
|
||||
'pkg_pacman:',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.has_any_bundle([
|
||||
'dovecot',
|
||||
'nginx',
|
||||
'postfix',
|
||||
]):
|
||||
actions['generate-dhparam'] = {
|
||||
'command': 'openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048',
|
||||
'unless': 'test -f /etc/ssl/certs/dhparam.pem',
|
||||
}
|
||||
|
||||
|
||||
locale_needs = set()
|
||||
for locale in sorted(node.metadata.get('locale/installed')):
|
||||
actions[f'ensure_locale_{locale}_is_enabled'] = {
|
||||
'command': f"sed -i '/{locale}/s/^# *//g' /etc/locale.gen",
|
||||
'unless': f"grep -e '^{locale}' /etc/locale.gen",
|
||||
'triggers': {
|
||||
'action:locale-gen',
|
||||
},
|
||||
'needs': locale_needs,
|
||||
}
|
||||
locale_needs = {f'action:ensure_locale_{locale}_is_enabled'}
|
||||
|
||||
actions['locale-gen'] = {
|
||||
'triggered': True,
|
||||
'command': 'locale-gen',
|
||||
}
|
||||
|
||||
description = []
|
||||
|
||||
if not node.metadata.get('icinga_options/exclude_from_monitoring', False):
|
||||
description.append('icingaweb2: https://icinga.franzi.business/monitoring/host/show?host={}'.format(node.name))
|
||||
|
||||
if node.has_bundle('telegraf'):
|
||||
description.append('Grafana: https://grafana.kunsmann.eu/d/{}'.format(UUID(int=node.magic_number).hex[:10]))
|
||||
|
||||
if (
|
||||
not node.metadata.get('icinga_options/exclude_from_monitoring', False) or
|
||||
node.has_bundle('telegraf')
|
||||
):
|
||||
description.append('') # divider line
|
||||
|
||||
if node.metadata.get('nginx/vhosts', {}):
|
||||
description.append('nginx vhosts:')
|
||||
|
||||
for vname, vconfig in sorted(node.metadata.get('nginx/vhosts', {}).items()):
|
||||
if vconfig.get('ssl', 'letsencrypt') is not None:
|
||||
proto = 'https'
|
||||
else:
|
||||
proto = 'http'
|
||||
|
||||
domain = vconfig.get('domain', vname)
|
||||
|
||||
description.append(' {}: {}://{}{}'.format(
|
||||
vname,
|
||||
proto,
|
||||
domain,
|
||||
vconfig.get('website_check_path', '/'),
|
||||
))
|
||||
|
||||
if node.metadata.get('description', []):
|
||||
description.append('') # divider line
|
||||
|
||||
for line in node.metadata.get('description', []):
|
||||
description.append('# {}'.format(italic(line)))
|
||||
|
||||
if description:
|
||||
files['/etc/node.description'] = {
|
||||
'content': '\n'.join(description) + '\n',
|
||||
}
|
||||
else:
|
||||
files['/etc/node.description'] = {
|
||||
'delete': True,
|
||||
}
|
25
bundles/basic/metadata.py
Normal file
25
bundles/basic/metadata.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
defaults = {
|
||||
'bash_functions': {
|
||||
'h': 'cp /etc/htoprc.global ~/.htoprc; mkdir -p ~/.config/htop; cp /etc/htoprc.global ~/.config/htop/htoprc; htop',
|
||||
},
|
||||
'locale': {
|
||||
'default': 'en_US.UTF-8',
|
||||
'installed': {
|
||||
'de_DE.UTF-8',
|
||||
'en_US.UTF-8',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'locale/installed',
|
||||
)
|
||||
def ensure_default_is_installed(metadata):
|
||||
return {
|
||||
'locale': {
|
||||
'installed': {
|
||||
metadata.get('locale/default'),
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
% for key in keys:
|
||||
key ${key['name']} {
|
||||
algorithm ${key['algorithm']};
|
||||
secret "${key['secret']}";
|
||||
};
|
||||
% endfor
|
|
@ -1,30 +0,0 @@
|
|||
include "/etc/bind/keys.conf";
|
||||
|
||||
% for zone in sorted(primary_zones):
|
||||
zone "${zone}" IN {
|
||||
type master;
|
||||
file "/var/lib/bind/primary/${zone}";
|
||||
};
|
||||
% endfor
|
||||
|
||||
|
||||
zone "10.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
|
||||
zone "16.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "17.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "18.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "19.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "20.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "21.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "22.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "23.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "24.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "25.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "26.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "27.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "28.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "29.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "30.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "31.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
|
||||
zone "168.192.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
|
@ -1,3 +0,0 @@
|
|||
% for o in node.metadata.get('bind', {}).get('options', []):
|
||||
<%include file="options/${o}"/>
|
||||
% endfor
|
|
@ -1,144 +0,0 @@
|
|||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
from datetime import datetime
|
||||
from subprocess import check_output
|
||||
|
||||
ZONE_HEADER = """
|
||||
; _ ____ _ _ _____ _ _ _ _ ____
|
||||
; / \\ / ___| | | |_ _| | | | \\ | |/ ___|
|
||||
; / _ \\| | | |_| | | | | | | | \\| | | _
|
||||
; / ___ \\ |___| _ | | | | |_| | |\\ | |_| |
|
||||
; /_/ \\_\\____|_| |_| |_| \\___/|_| \\_|\\____|
|
||||
;
|
||||
; --> Diese Datei wird von BundleWrap verwaltet! <--
|
||||
|
||||
$TTL 60
|
||||
@ IN SOA ns-1.kunbox.net. hostmaster.kunbox.net. (
|
||||
{serial}
|
||||
3600
|
||||
3600
|
||||
86400
|
||||
300
|
||||
)
|
||||
@ IN NS ns-1.kunbox.net.
|
||||
IN NS ns-2.kunbox.net.
|
||||
"""
|
||||
|
||||
svc_systemd = {
|
||||
'bind9': {
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pkg_apt = {
|
||||
'bind9': {},
|
||||
}
|
||||
|
||||
directories = {
|
||||
"/var/lib/bind/primary": {
|
||||
'group': 'bind',
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
'owner': 'bind',
|
||||
'purge': True,
|
||||
},
|
||||
"/var/log/named": {
|
||||
'group': 'bind',
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
'owner': 'bind',
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
"/etc/bind/keys.conf": {
|
||||
'content_type': 'mako',
|
||||
'group': 'bind',
|
||||
'mode': '0440',
|
||||
'context': {
|
||||
'keys': node.metadata.get('bind', {}).get('keys', []),
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:bind9:reload',
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
},
|
||||
"/etc/bind/named.conf.options": {
|
||||
'content_type': 'mako',
|
||||
'group': 'bind',
|
||||
'mode': '0440',
|
||||
'triggers': {
|
||||
'svc_systemd:bind9:reload',
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.metadata.get('bind', {}).get('rndc', ''):
|
||||
files['/etc/bind/rndc.conf'] = {
|
||||
'mode': '0440',
|
||||
'source': 'rndc/{}'.format(node.metadata['bind']['rndc']),
|
||||
'content_type': 'mako',
|
||||
'triggers': {
|
||||
'svc_systemd:bind9:reload',
|
||||
},
|
||||
}
|
||||
|
||||
# this looks for zones either directly at data/bind/zones/ or in a subdirectory if so configured
|
||||
zone_path = join(
|
||||
repo.path,
|
||||
'data', 'bind', 'files', 'zones',
|
||||
node.metadata.get('bind', {}).get('zone_path', ""),
|
||||
)
|
||||
|
||||
primary_zones = set()
|
||||
|
||||
for zone in listdir(zone_path):
|
||||
if not isfile(join(zone_path, zone)) or zone.startswith(".") or zone.startswith("_"):
|
||||
continue
|
||||
|
||||
output = check_output(['git', 'log', '-1', '--pretty=%ci', join(zone_path, zone)]).decode('utf-8').strip()
|
||||
serial = datetime.strptime(output, '%Y-%m-%d %H:%M:%S %z').strftime('%y%m%d%H%M')
|
||||
|
||||
primary_zones.add(zone)
|
||||
|
||||
files["/var/lib/bind/primary/{}".format(zone)] = {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'header': ZONE_HEADER.format(serial=serial),
|
||||
'metadata_records': node.metadata.get('bind', {}).get('zones_primary', {}).get(zone, {}).get('records', []),
|
||||
},
|
||||
'mode': '0444',
|
||||
'owner': 'bind',
|
||||
'source': 'zones/{}'.format(join(node.metadata.get('bind', {}).get('zone_path', ""), zone)),
|
||||
'triggers': {
|
||||
'svc_systemd:bind9:reload',
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:bind9'
|
||||
},
|
||||
}
|
||||
|
||||
primary_zones.union(set(node.metadata.get('bind', {}).get('zones_primary', {}).keys()))
|
||||
|
||||
files['/etc/bind/named.conf.local'] = {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'primary_zones': list(primary_zones),
|
||||
},
|
||||
'group': 'bind',
|
||||
'triggers': {
|
||||
'svc_systemd:bind9:reload',
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:bind9',
|
||||
},
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
from bundlewrap.metadata import atomic
|
||||
|
||||
|
||||
defaults = {
|
||||
'icinga2_api': {
|
||||
'bind': {
|
||||
'services': {
|
||||
'BIND PROCESS': {
|
||||
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C named -c 1:1',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor
|
||||
def port_checks(metadata):
|
||||
services = {}
|
||||
|
||||
for interface in metadata.get('bind/listen', set()):
|
||||
services[f'BIND PORT {interface}'] = {
|
||||
'check_command': 'tcp',
|
||||
'vars.tcp_address': metadata.get(f'interfaces/{interface}/ip_addresses')[0],
|
||||
'vars.tcp_port': 53,
|
||||
}
|
||||
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'bind': {
|
||||
'services': services,
|
||||
},
|
||||
},
|
||||
}
|
43
bundles/bird/files/bird.conf
Normal file
43
bundles/bird/files/bird.conf
Normal file
|
@ -0,0 +1,43 @@
|
|||
log syslog all;
|
||||
router id ${node.metadata.get('bird/my_ip')};
|
||||
debug protocols all;
|
||||
|
||||
ipv4 table master4;
|
||||
|
||||
protocol device {
|
||||
}
|
||||
|
||||
protocol kernel {
|
||||
scan time 30;
|
||||
ipv4 {
|
||||
export where source != RTS_STATIC;
|
||||
};
|
||||
}
|
||||
% if node.metadata.get('bird/static_routes', set()):
|
||||
|
||||
protocol static {
|
||||
ipv4;
|
||||
|
||||
% for route in sorted(node.metadata.get('bird/static_routes', set())):
|
||||
% for name, config in sorted(node.metadata.get('bird/bgp_neighbors', {}).items()):
|
||||
route ${route} via ${config['local_ip']};
|
||||
% endfor
|
||||
% endfor
|
||||
}
|
||||
% endif
|
||||
% for name, config in sorted(node.metadata.get('bird/bgp_neighbors', {}).items()):
|
||||
|
||||
protocol bgp '${name}' {
|
||||
local ${config['local_ip']} as ${config['local_as']};
|
||||
neighbor ${config['neighbor_ip']} as ${config['neighbor_as']};
|
||||
hold time ${config.get('hold_time', 15)};
|
||||
error wait time 5, 10;
|
||||
direct;
|
||||
|
||||
ipv4 {
|
||||
next hop self;
|
||||
import all;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
% endfor
|
21
bundles/bird/items.py
Normal file
21
bundles/bird/items.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
if node.os == 'arch':
|
||||
filename = '/etc/bird.conf'
|
||||
else:
|
||||
filename = '/etc/bird/bird.conf'
|
||||
|
||||
files = {
|
||||
filename: {
|
||||
'content_type': 'mako',
|
||||
'triggers': {
|
||||
'svc_systemd:bird:reload',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'bird': {
|
||||
'needs': {
|
||||
f'file:{filename}',
|
||||
},
|
||||
},
|
||||
}
|
96
bundles/bird/metadata.py
Normal file
96
bundles/bird/metadata.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
from ipaddress import ip_network
|
||||
|
||||
from bundlewrap.exceptions import NoSuchNode
|
||||
from bundlewrap.metadata import atomic
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'bird2': {
|
||||
'needed_by': {
|
||||
'svc_systemd:bird',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'pacman': {
|
||||
'packages': {
|
||||
'bird': {
|
||||
'needed_by': {
|
||||
'svc_systemd:bird',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'sysctl': {
|
||||
'options': {
|
||||
'net.ipv4.conf.all.forwarding': '1',
|
||||
'net.ipv6.conf.all.forwarding': '1',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'bird/bgp_neighbors',
|
||||
)
|
||||
def neighbor_info_from_wireguard(metadata):
|
||||
neighbors = {}
|
||||
my_as = repo.libs.s2s.AS_NUMBERS[metadata.get('location')]
|
||||
|
||||
for name, config in metadata.get('wireguard/peers', {}).items():
|
||||
try:
|
||||
rnode = repo.get_node(name)
|
||||
except NoSuchNode:
|
||||
continue
|
||||
|
||||
if not rnode.has_bundle('bird'):
|
||||
continue
|
||||
|
||||
neighbors[name] = {
|
||||
'local_ip': config['my_ip'],
|
||||
'local_as': my_as,
|
||||
'neighbor_ip': config['their_ip'],
|
||||
'neighbor_as': repo.libs.s2s.AS_NUMBERS[rnode.metadata.get('location')],
|
||||
}
|
||||
|
||||
return {
|
||||
'bird': {
|
||||
'bgp_neighbors': neighbors,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'bird/my_ip',
|
||||
)
|
||||
def my_ip(metadata):
|
||||
if node.has_bundle('wireguard'):
|
||||
wg_ifaces = sorted({iface for iface in metadata.get('interfaces').keys() if iface.startswith('wg_')})
|
||||
if not wg_ifaces:
|
||||
return {}
|
||||
my_ip = sorted(metadata.get(f'interfaces/{wg_ifaces[0]}/ips'))[0].split('/')[0]
|
||||
else:
|
||||
my_ip = str(sorted(repo.libs.tools.resolve_identifier(repo, node.name))[0])
|
||||
|
||||
return {
|
||||
'bird': {
|
||||
'my_ip': my_ip,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
sources = set()
|
||||
for config in metadata.get('bird/bgp_neighbors', {}).values():
|
||||
sources.add(config['neighbor_ip'])
|
||||
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
'179/tcp': atomic(sources),
|
||||
},
|
||||
},
|
||||
}
|
1
bundles/c3voc-addons/files/check_unattended_upgrades
Symbolic link
1
bundles/c3voc-addons/files/check_unattended_upgrades
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../apt/files/check_unattended_upgrades
|
1
bundles/c3voc-addons/files/cron_template
Symbolic link
1
bundles/c3voc-addons/files/cron_template
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../cron/files/cron_template
|
1
bundles/c3voc-addons/files/do-unattended-upgrades
Symbolic link
1
bundles/c3voc-addons/files/do-unattended-upgrades
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../apt/files/do-unattended-upgrades
|
1
bundles/c3voc-addons/files/kernel-postinst.d
Symbolic link
1
bundles/c3voc-addons/files/kernel-postinst.d
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../apt/files/kernel-postinst.d
|
62
bundles/c3voc-addons/files/site_template
Normal file
62
bundles/c3voc-addons/files/site_template
Normal file
|
@ -0,0 +1,62 @@
|
|||
server {
|
||||
server_name ${domain};
|
||||
root ${webroot if webroot else '/var/www/{}/'.format(vhost)};
|
||||
index index.html index.htm;
|
||||
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/${domain}/chain.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
|
||||
ssl_dhparam /etc/ssl/dhparam4096.pem;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers 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;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
|
||||
|
||||
resolver 8.8.8.8 8.8.4.4 valid=300s;
|
||||
resolver_timeout 5s;
|
||||
|
||||
% if max_body_size:
|
||||
client_max_body_size ${max_body_size};
|
||||
% elif proxy:
|
||||
client_max_body_size 5M;
|
||||
% endif
|
||||
|
||||
add_header Permissions-Policy interest-cohort=();
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
alias /var/www/dehydrated;
|
||||
}
|
||||
|
||||
% if locations:
|
||||
% for location, options in locations.items():
|
||||
location ${location} {
|
||||
proxy_pass ${options['target']};
|
||||
proxy_http_version ${options.get('http_version', '1.1')};
|
||||
proxy_set_header Host ${domain};
|
||||
% if options.get('websockets', False):
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
% endif
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto HTTPS;
|
||||
proxy_set_header X-Forwarded-Host ${domain};
|
||||
% for option, value in options.get('proxy_set_header', {}).items():
|
||||
proxy_set_header ${option} ${value};
|
||||
% endfor
|
||||
% if location != '/':
|
||||
proxy_set_header X-Script-Name ${location};
|
||||
% endif
|
||||
proxy_buffering off;
|
||||
}
|
||||
% endfor
|
||||
% endif
|
||||
|
||||
% if extras:
|
||||
<%include file="extras/${node.name}/${vhost}" />
|
||||
% endif
|
||||
}
|
1
bundles/c3voc-addons/files/upgrade-and-reboot
Symbolic link
1
bundles/c3voc-addons/files/upgrade-and-reboot
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../apt/files/upgrade-and-reboot
|
1
bundles/c3voc-addons/files/upgrade-and-reboot.conf
Symbolic link
1
bundles/c3voc-addons/files/upgrade-and-reboot.conf
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../apt/files/upgrade-and-reboot.conf
|
188
bundles/c3voc-addons/items.py
Normal file
188
bundles/c3voc-addons/items.py
Normal file
|
@ -0,0 +1,188 @@
|
|||
from bundlewrap.exceptions import BundleError
|
||||
|
||||
supported_os = {
|
||||
'debian': {
|
||||
10: 'buster',
|
||||
11: 'bullseye',
|
||||
12: 'bookworm',
|
||||
99: 'unstable',
|
||||
},
|
||||
'raspbian': {
|
||||
10: 'buster',
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
supported_os[node.os][node.os_version[0]]
|
||||
except (KeyError, IndexError):
|
||||
raise BundleError(f'{node.name}: OS {node.os} {node.os_version} is not supported by bundle:apt')
|
||||
|
||||
CONFLICTING_BUNDLES = {
|
||||
'apt',
|
||||
'nginx',
|
||||
'telegraf',
|
||||
'users',
|
||||
}
|
||||
|
||||
if any(node.has_bundle(i) for i in CONFLICTING_BUNDLES):
|
||||
raise BundleError(f'{node.name}: bundle:c3voc-addons conflicts with bundles: {", ".join(sorted(CONFLICTING_BUNDLES))}')
|
||||
|
||||
pkg_apt = {
|
||||
'apt-transport-https': {},
|
||||
|
||||
'build-essential': {},
|
||||
'curl': {},
|
||||
'git': {},
|
||||
'grep': {},
|
||||
'gzip': {},
|
||||
'htop': {},
|
||||
'jq': {},
|
||||
'less': {},
|
||||
'mtr': {},
|
||||
'ncdu': {},
|
||||
'netcat': {},
|
||||
'python3': {},
|
||||
'python3-dev': {},
|
||||
'python3-setuptools': {
|
||||
'needed_by': {
|
||||
'pkg_pip:',
|
||||
},
|
||||
},
|
||||
'python3-pip': {
|
||||
'needed_by': {
|
||||
'pkg_pip:',
|
||||
},
|
||||
},
|
||||
'python3-virtualenv': {},
|
||||
'rsync': {},
|
||||
'tar': {},
|
||||
'tmux': {},
|
||||
'tree': {},
|
||||
'wget': {},
|
||||
}
|
||||
|
||||
if node.metadata.get('apt/packages', {}):
|
||||
for package, options in node.metadata['apt']['packages'].items():
|
||||
pkg_apt[package] = options
|
||||
|
||||
actions = {
|
||||
'systemd-reload': {
|
||||
'command': 'systemctl daemon-reload',
|
||||
'cascade_skip': False,
|
||||
'triggered': True,
|
||||
'needed_by': {
|
||||
'svc_systemd:',
|
||||
},
|
||||
},
|
||||
'apt_update': {
|
||||
'command': 'apt-get update',
|
||||
'needed_by': {
|
||||
'pkg_apt:',
|
||||
},
|
||||
'triggered': True,
|
||||
'cascade_skip': False,
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/etc/nginx/sites-enabled': {
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'svc_systemd:nginx:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/kernel/postinst.d/unattended-upgrades': {
|
||||
'source': 'kernel-postinst.d',
|
||||
},
|
||||
'/etc/upgrade-and-reboot.conf': {
|
||||
'content_type': 'mako',
|
||||
},
|
||||
'/usr/local/share/icinga/plugins/check_unattended_upgrades': {
|
||||
'mode': '0755',
|
||||
},
|
||||
'/usr/local/sbin/upgrade-and-reboot': {
|
||||
'mode': '0700',
|
||||
},
|
||||
'/usr/local/sbin/do-unattended-upgrades': {
|
||||
'content_type': 'mako',
|
||||
'mode': '0700',
|
||||
'context': {
|
||||
'additional_update_commands': node.metadata.get('apt/additional_update_commands', set()),
|
||||
'clean_old_kernels': node.metadata.get('apt/clean_old_kernels', True),
|
||||
'restart_triggers': node.metadata.get('apt/restart_triggers', {}),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
for name, data in node.metadata.get('apt/repos', {}).items():
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)] = {
|
||||
'content_type': 'mako',
|
||||
'content': ("\n".join(sorted(data['items']))).format(
|
||||
os=node.os,
|
||||
os_release=supported_os[node.os][node.os_version[0]],
|
||||
),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
if data.get('install_gpg_key', True):
|
||||
files['/etc/apt/sources.list.d/{}.list'.format(name)]['needs'] = {
|
||||
'file:/etc/apt/trusted.gpg.d/{}.list.asc'.format(name),
|
||||
}
|
||||
|
||||
files['/etc/apt/trusted.gpg.d/{}.list.asc'.format(name)] = {
|
||||
'source': 'gpg-keys/{}.asc'.format(name),
|
||||
'triggers': {
|
||||
'action:apt_update',
|
||||
},
|
||||
}
|
||||
|
||||
for crontab, content in node.metadata.get('cron/jobs', {}).items():
|
||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||
'source': 'cron_template',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'cron': content,
|
||||
}
|
||||
}
|
||||
|
||||
for vhost, config in node.metadata.get('nginx/vhosts', {}).items():
|
||||
if not 'domain' in config:
|
||||
config['domain'] = vhost
|
||||
|
||||
files['/etc/nginx/sites-available/{}'.format(vhost)] = {
|
||||
'source': 'site_template',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'vhost': vhost,
|
||||
**config,
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:nginx:restart',
|
||||
},
|
||||
}
|
||||
symlinks['/etc/nginx/sites-enabled/{}'.format(vhost)] = {
|
||||
'target': '/etc/nginx/sites-available/{}'.format(vhost),
|
||||
'triggers': {
|
||||
'svc_systemd:nginx:restart',
|
||||
},
|
||||
}
|
||||
|
||||
if not 'webroot' in config:
|
||||
directories['/var/www/{}'.format(vhost)] = config.get('webroot_config', {})
|
||||
|
||||
svc_systemd = {
|
||||
'nginx': {},
|
||||
'apt-daily.timer': {
|
||||
'running': False,
|
||||
'enabled': False,
|
||||
},
|
||||
'apt-daily-upgrade.timer': {
|
||||
'running': False,
|
||||
'enabled': False,
|
||||
},
|
||||
}
|
77
bundles/c3voc-addons/metadata.py
Normal file
77
bundles/c3voc-addons/metadata.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'unattended-upgrades': {
|
||||
'day': 5,
|
||||
'hour': 21,
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'apt': {
|
||||
'services': {
|
||||
'UNATTENDED UPGRADES': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_unattended_upgrades',
|
||||
},
|
||||
},
|
||||
},
|
||||
'nginx': {
|
||||
'services': {
|
||||
'NGINX PROCESS': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit nginx',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'cron/jobs/upgrade-and-reboot'
|
||||
)
|
||||
def patchday(metadata):
|
||||
day = metadata.get('apt/unattended-upgrades/day')
|
||||
hour = metadata.get('apt/unattended-upgrades/hour')
|
||||
|
||||
return {
|
||||
'cron': {
|
||||
'jobs': {
|
||||
'upgrade-and-reboot': '{minute} {hour} * * {day} root /usr/local/sbin/upgrade-and-reboot'.format(
|
||||
minute=node.magic_number % 30,
|
||||
hour=hour,
|
||||
day=day,
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/nginx/services',
|
||||
)
|
||||
def monitoring(metadata):
|
||||
services = {}
|
||||
|
||||
for vname, vconfig in metadata.get('nginx/vhosts', {}).items():
|
||||
domain = vconfig.get('domain', vname)
|
||||
|
||||
if 'website_check_path' in vconfig and 'website_check_string' in vconfig:
|
||||
services['NGINX VHOST {} CONTENT'.format(vname)] = {
|
||||
'check_command': 'check_http_wget',
|
||||
'vars.http_wget_contains': vconfig['website_check_string'],
|
||||
'vars.http_wget_url': 'https://{}{}'.format(domain, vconfig['website_check_path']),
|
||||
'vars.notification.sms': True,
|
||||
}
|
||||
|
||||
if vconfig.get('check_ssl', True):
|
||||
services['NGINX VHOST {} CERTIFICATE'.format(vname)] = {
|
||||
'check_command': 'check_https_cert_at_url',
|
||||
'vars.domain': domain,
|
||||
'vars.notification.mail': True,
|
||||
}
|
||||
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'nginx': {
|
||||
'services': services,
|
||||
},
|
||||
},
|
||||
}
|
42
bundles/check-mail-received/metadata.py
Normal file
42
bundles/check-mail-received/metadata.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
@metadata_reactor.provides(
|
||||
'cron/jobs/check-mail-received',
|
||||
'icinga2_api/check-mail-received/services',
|
||||
)
|
||||
def process_metadata(metadata):
|
||||
cron = set()
|
||||
services = {}
|
||||
|
||||
my_mail_address = 'root@{}'.format(metadata.get('hostname'))
|
||||
|
||||
for name, config in metadata.get('check-mail-received', {}).items():
|
||||
cron.add('{minute} {hour} * * * root date | mail -s "daily test mail from {node}" -r {source} {target}'.format(
|
||||
minute=node.magic_number%60,
|
||||
hour=node.magic_number%24,
|
||||
node=node.name,
|
||||
source=my_mail_address,
|
||||
target=config['email'],
|
||||
))
|
||||
|
||||
services[f'MAIL RECEIVED ON {name}'] = {
|
||||
'check_command': 'check_imap_for_mail_from',
|
||||
'check_interval': '15m',
|
||||
'retry_interval': '5m',
|
||||
'vars.sshmon_timeout': 30,
|
||||
'vars.imap_host': config['imap_host'],
|
||||
'vars.imap_user': config.get('imap_user', config['email']),
|
||||
'vars.imap_pass': config['imap_pass'],
|
||||
'vars.imap_from': my_mail_address,
|
||||
}
|
||||
|
||||
return {
|
||||
'cron': {
|
||||
'jobs': {
|
||||
'check-mail-received': '\n'.join(sorted(cron)),
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'check-mail-received': {
|
||||
'services': services,
|
||||
},
|
||||
},
|
||||
}
|
8
bundles/cron/files/cron_template
Normal file
8
bundles/cron/files/cron_template
Normal file
|
@ -0,0 +1,8 @@
|
|||
# CAUTION! This file is managed with bundlewrap.
|
||||
# Any manual edits will be lost!
|
||||
|
||||
SHELL=/bin/sh
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
MAILTO=${node.metadata.get('cron/mail_to', repo.libs.defaults.hostmaster_email)}
|
||||
|
||||
${cron}
|
11
bundles/cron/files/crontab
Normal file
11
bundles/cron/files/crontab
Normal file
|
@ -0,0 +1,11 @@
|
|||
# CAUTION! This file is managed with bundlewrap.
|
||||
# Any manual edits will be lost!
|
||||
|
||||
SHELL=/bin/sh
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
MAILTO=${node.metadata.get('cron/mail_to', repo.libs.defaults.hostmaster_email)}
|
||||
|
||||
${min} * * * * root cd / && run-parts --report /etc/cron.hourly
|
||||
${min} 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
|
||||
${min} 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
|
||||
${min} 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
|
|
@ -1,6 +1,41 @@
|
|||
files = {}
|
||||
if node.os == 'arch':
|
||||
service_name = 'cronie'
|
||||
package_name = 'pkg_pacman:cronie'
|
||||
else:
|
||||
service_name = 'cron'
|
||||
package_name = 'pkg_apt:cron'
|
||||
|
||||
for crontab, content in node.metadata.get('cron', {}).items():
|
||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||
'content': content + "\n",
|
||||
files = {
|
||||
'/etc/crontab': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'min': (node.magic_number%60),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/etc/cron.d': {
|
||||
'purge': True,
|
||||
'after': {
|
||||
'pkg_apt:',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
service_name: {
|
||||
'needs': {
|
||||
package_name,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for crontab, content in node.metadata.get('cron/jobs', {}).items():
|
||||
files['/etc/cron.d/{}'.format(crontab)] = {
|
||||
'source': 'cron_template',
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'cron': content,
|
||||
}
|
||||
}
|
||||
|
|
12
bundles/cron/metadata.py
Normal file
12
bundles/cron/metadata.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'cron': {},
|
||||
},
|
||||
},
|
||||
'pacman': {
|
||||
'packages': {
|
||||
'cronie': {},
|
||||
},
|
||||
},
|
||||
}
|
26
bundles/dm-crypt/items.py
Normal file
26
bundles/dm-crypt/items.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
for dev, attrs in node.metadata.get('dm-crypt/encrypted-devices', {}).items():
|
||||
actions['dm-crypt_format_' + dev] = {
|
||||
'cascade_skip': False,
|
||||
'command': f'cryptsetup --batch-mode luksFormat --cipher aes-xts-plain64 --key-size 512 {dev}',
|
||||
'comment': 'Careful: This destroys the current contents of that device. Afterwards, it will be encrypted using dm-crypt.',
|
||||
'data_stdin': attrs['passphrase'],
|
||||
'unless': f'blkid -t TYPE=crypto_LUKS {dev}',
|
||||
'needs': {
|
||||
'pkg_apt:cryptsetup',
|
||||
},
|
||||
}
|
||||
actions['dm-crypt_open_' + attrs['dm-name']] = {
|
||||
'cascade_skip': False,
|
||||
'command': 'cryptsetup --batch-mode luksOpen {dev} {dm_name}'.format(
|
||||
dev=dev,
|
||||
dm_name=attrs['dm-name'],
|
||||
),
|
||||
'comment': 'Unlocks the device and makes it available as /dev/mapper/{}'.format(attrs['dm-name']),
|
||||
'data_stdin': attrs['passphrase'],
|
||||
'needs': {
|
||||
f'action:dm-crypt_format_{dev}',
|
||||
'pkg_apt:cryptsetup',
|
||||
},
|
||||
'unless': 'test -e /dev/mapper/{}'.format(attrs['dm-name']),
|
||||
}
|
||||
|
7
bundles/dm-crypt/metadata.py
Normal file
7
bundles/dm-crypt/metadata.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'cryptsetup': {},
|
||||
},
|
||||
},
|
||||
}
|
6
bundles/dovecot/files/dovecot-sql.conf
Normal file
6
bundles/dovecot/files/dovecot-sql.conf
Normal file
|
@ -0,0 +1,6 @@
|
|||
connect = host=localhost dbname=${dbname} user=${dbuser} password=${dbpass}
|
||||
driver = pgsql
|
||||
default_pass_scheme = MD5-CRYPT
|
||||
password_query = SELECT username as user, password FROM mailbox WHERE username = '%u' AND active = true
|
||||
user_query = SELECT '/var/mail/vmail/' || maildir as home, 65534 as uid, 65534 as gid FROM mailbox WHERE username = '%u' AND active = true
|
||||
iterate_query = SELECT username as user FROM mailbox WHERE active = true
|
185
bundles/dovecot/files/dovecot.conf
Normal file
185
bundles/dovecot/files/dovecot.conf
Normal file
|
@ -0,0 +1,185 @@
|
|||
!include conf.d/*.conf
|
||||
|
||||
namespace inbox {
|
||||
type = private
|
||||
inbox = yes
|
||||
location =
|
||||
mailbox Drafts {
|
||||
auto = subscribe
|
||||
special_use = \Drafts
|
||||
}
|
||||
mailbox Junk {
|
||||
auto = create
|
||||
special_use = \Junk
|
||||
autoexpunge = 30d
|
||||
}
|
||||
mailbox Sent {
|
||||
auto = subscribe
|
||||
special_use = \Sent
|
||||
}
|
||||
mailbox Trash {
|
||||
auto = subscribe
|
||||
special_use = \Trash
|
||||
autoexpunge = 360d
|
||||
}
|
||||
prefix =
|
||||
}
|
||||
|
||||
mail_location = maildir:/var/mail/vmail/%d/%n
|
||||
protocols = imap lmtp sieve
|
||||
|
||||
ssl = required
|
||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/fullchain.pem
|
||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('postfix/myhostname', node.metadata['hostname'])}/privkey.pem
|
||||
ssl_dh = </etc/ssl/certs/dhparam.pem
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305
|
||||
ssl_prefer_server_ciphers = no
|
||||
|
||||
login_greeting = IMAPd ready
|
||||
auth_mechanisms = plain login
|
||||
first_valid_uid = 65534
|
||||
disable_plaintext_auth = yes
|
||||
mail_plugins = $mail_plugins zlib old_stats fts fts_xapian
|
||||
|
||||
plugin {
|
||||
zlib_save_level = 6
|
||||
zlib_save = gz
|
||||
|
||||
sieve = /var/mail/vmail/sieve/%d/%n.sieve
|
||||
sieve_dir = /var/mail/vmail/sieve/%d/%n/
|
||||
sieve_extensions = +vnd.dovecot.pipe
|
||||
sieve_pipe_bin_dir = /var/mail/vmail/sieve/bin
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve_user_log = /var/mail/vmail/sieve/%d/%n.log
|
||||
|
||||
old_stats_refresh = 30 secs
|
||||
old_stats_track_cmds = yes
|
||||
|
||||
fts = xapian
|
||||
fts_xapian = partial=3 full=20
|
||||
|
||||
fts_autoindex = yes
|
||||
fts_enforced = yes
|
||||
|
||||
# Index attachements
|
||||
fts_decoder = decode2text
|
||||
|
||||
% if node.has_bundle('rspamd'):
|
||||
sieve_before = /var/mail/vmail/sieve/global/spam-global.sieve
|
||||
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:/var/mail/vmail/sieve/global/learn-spam.sieve
|
||||
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:/var/mail/vmail/sieve/global/learn-ham.sieve
|
||||
% endif
|
||||
}
|
||||
|
||||
service auth {
|
||||
unix_listener /var/spool/postfix/private/auth {
|
||||
mode = 0660
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
|
||||
unix_listener auth-userdb {
|
||||
mode = 0660
|
||||
user = nobody
|
||||
group = nogroup
|
||||
}
|
||||
}
|
||||
|
||||
service decode2text {
|
||||
executable = script /usr/lib/dovecot/decode2text.sh
|
||||
user = dovecot
|
||||
unix_listener decode2text {
|
||||
mode = 0666
|
||||
}
|
||||
}
|
||||
|
||||
service indexer-worker {
|
||||
vsz_limit = 0
|
||||
process_limit = 0
|
||||
}
|
||||
|
||||
service imap {
|
||||
executable = imap
|
||||
}
|
||||
|
||||
service imap-login {
|
||||
service_count = 1
|
||||
process_min_avail = 8
|
||||
vsz_limit = 64M
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener /var/spool/postfix/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
}
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = sql
|
||||
args = /etc/dovecot/dovecot-sql.conf
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = sql
|
||||
args = /etc/dovecot/dovecot-sql.conf
|
||||
}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
postmaster_address = ${admin_email}
|
||||
}
|
||||
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_zlib imap_sieve imap_old_stats
|
||||
mail_max_userip_connections = 50
|
||||
imap_idle_notify_interval = 29 mins
|
||||
}
|
||||
|
||||
protocol sieve {
|
||||
plugin {
|
||||
sieve = /var/mail/vmail/sieve/%d/%n.sieve
|
||||
sieve_storage = /var/mail/vmail/sieve/%d/%n/
|
||||
}
|
||||
}
|
||||
|
||||
service old-stats {
|
||||
% if node.has_bundle('telegraf'):
|
||||
inet_listener {
|
||||
address = 127.0.0.1
|
||||
port = 24242
|
||||
}
|
||||
% endif
|
||||
unix_listener old-stats {
|
||||
mode = 0660
|
||||
user = nobody
|
||||
group = nogroup
|
||||
}
|
||||
fifo_listener old-stats-mail {
|
||||
mode = 0660
|
||||
user = nobody
|
||||
group = nogroup
|
||||
}
|
||||
fifo_listener old-stats-user {
|
||||
mode = 0660
|
||||
user = nobody
|
||||
group = nogroup
|
||||
}
|
||||
}
|
15
bundles/dovecot/files/learn-ham.sieve
Normal file
15
bundles/dovecot/files/learn-ham.sieve
Normal file
|
@ -0,0 +1,15 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "${1}";
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "sa-learn-ham.sh" [ "${username}" ];
|
7
bundles/dovecot/files/learn-spam.sieve
Normal file
7
bundles/dovecot/files/learn-spam.sieve
Normal file
|
@ -0,0 +1,7 @@
|
|||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "sa-learn-spam.sh" [ "${username}" ];
|
11
bundles/dovecot/files/spam-global.sieve
Normal file
11
bundles/dovecot/files/spam-global.sieve
Normal file
|
@ -0,0 +1,11 @@
|
|||
require ["fileinto", "imap4flags"];
|
||||
|
||||
if header :contains "X-Spam-Status" "Yes" {
|
||||
setflag "\\seen";
|
||||
fileinto "Junk";
|
||||
}
|
||||
|
||||
if header :contains "X-Spam" "Yes" {
|
||||
setflag "\\seen";
|
||||
fileinto "Junk";
|
||||
}
|
77
bundles/dovecot/items.py
Normal file
77
bundles/dovecot/items.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# Postfix bundle creates metadata and directories which are also used
|
||||
# by this bundle
|
||||
repo.libs.tools.require_bundle(node, 'postfix')
|
||||
|
||||
files = {
|
||||
'/etc/dovecot/dovecot.conf': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'admin_email': node.metadata['dovecot']['admin_email'],
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/etc/dovecot/dovecot-sql.conf': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata['dovecot']['database'],
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/etc/dovecot/conf.d/auth-system.conf.ext': {
|
||||
'delete': True,
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/etc/dovecot/conf.d/10-auth.conf': {
|
||||
'delete': True,
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
symlinks['/usr/lib/dovecot/decode2text.sh'] = {
|
||||
'target': '/usr/share/doc/dovecot-core/examples/decode2text.sh',
|
||||
'before': {
|
||||
'svc_systemd:dovecot',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'dovecot': {
|
||||
'needs': {
|
||||
'action:generate-dhparam',
|
||||
'file:/etc/dovecot/dovecot.conf',
|
||||
'file:/etc/dovecot/dovecot-sql.conf',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.has_bundle('rspamd'):
|
||||
files['/var/mail/vmail/sieve/global/learn-ham.sieve'] = {
|
||||
'owner': 'nobody',
|
||||
'group': 'nogroup',
|
||||
}
|
||||
files['/var/mail/vmail/sieve/global/learn-spam.sieve'] = {
|
||||
'owner': 'nobody',
|
||||
'group': 'nogroup',
|
||||
}
|
||||
files['/var/mail/vmail/sieve/global/spam-global.sieve'] = {
|
||||
'owner': 'nobody',
|
||||
'group': 'nogroup',
|
||||
}
|
105
bundles/dovecot/metadata.py
Normal file
105
bundles/dovecot/metadata.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
from bundlewrap.metadata import atomic
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'dovecot-fts-xapian': {},
|
||||
'dovecot-imapd': {},
|
||||
'dovecot-lmtpd': {},
|
||||
'dovecot-managesieved': {},
|
||||
'dovecot-pgsql': {},
|
||||
'dovecot-sieve': {},
|
||||
},
|
||||
},
|
||||
'icinga2_api': {
|
||||
'dovecot': {
|
||||
'services': {
|
||||
'DOVECOT PROCESS': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit dovecot',
|
||||
},
|
||||
'IMAP CONNECT': {
|
||||
'check_command': 'check_imap',
|
||||
'vars.imap_port': 143,
|
||||
'vars.notification.sms': True,
|
||||
},
|
||||
'IMAPS CONNECT': {
|
||||
'check_command': 'check_imap',
|
||||
'vars.imap_port': 993,
|
||||
'vars.imap_ssl': True,
|
||||
'vars.notification.sms': True,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'letsencrypt': {
|
||||
'reload_after': {
|
||||
'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'):
|
||||
defaults['dovecot'] = {
|
||||
'database': {
|
||||
'dbname': 'postfixadmin',
|
||||
'dbuser': 'postfixadmin',
|
||||
},
|
||||
}
|
||||
|
||||
if node.has_bundle('telegraf'):
|
||||
defaults['telegraf'] = {
|
||||
'input_plugins': {
|
||||
'builtin': {
|
||||
'dovecot': [{
|
||||
'type': 'global',
|
||||
}],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'dovecot/admin_email',
|
||||
'dovecot/database/dbpass',
|
||||
)
|
||||
def import_database_settings_from_postfixadmin(metadata):
|
||||
if not node.has_bundle('postfixadmin'):
|
||||
raise DoNotRunAgain
|
||||
|
||||
return {
|
||||
'dovecot': {
|
||||
'admin_email': metadata.get('postfixadmin/admin_email'),
|
||||
'database': {
|
||||
'dbpass': metadata.get('postgresql/roles/postfixadmin/password'),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'firewall/port_rules',
|
||||
'firewall/port_rules',
|
||||
'firewall/port_rules',
|
||||
)
|
||||
def firewall(metadata):
|
||||
return {
|
||||
'firewall': {
|
||||
'port_rules': {
|
||||
# imap(s)
|
||||
'143/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
'993/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
# managesieve
|
||||
'4190/tcp': atomic(metadata.get('dovecot/restrict-to', {'*'})),
|
||||
},
|
||||
},
|
||||
}
|
41
bundles/element-web/items.py
Normal file
41
bundles/element-web/items.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from bundlewrap.metadata import metadata_to_json
|
||||
|
||||
repo.libs.tools.require_bundle(node, 'nodejs')
|
||||
|
||||
directories = {
|
||||
'/opt/element-web': {}
|
||||
}
|
||||
|
||||
git_deploy = {
|
||||
'/opt/element-web': {
|
||||
'rev': node.metadata.get('element-web/version'),
|
||||
'repo': 'https://github.com/vector-im/element-web.git',
|
||||
'triggers': {
|
||||
'action:element-web_yarn',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/opt/element-web/webapp/config.json': {
|
||||
'content': metadata_to_json(node.metadata.get('element-web/config')),
|
||||
'needs': {
|
||||
'action:element-web_yarn',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
'element-web_yarn': {
|
||||
'command': ' && '.join([
|
||||
'cd /opt/element-web',
|
||||
'yarn install --pure-lockfile --ignore-scripts',
|
||||
'yarn build',
|
||||
]),
|
||||
'needs': {
|
||||
'action:nodejs_install_yarn',
|
||||
'pkg_apt:nodejs',
|
||||
},
|
||||
'triggered': True,
|
||||
},
|
||||
}
|
46
bundles/element-web/metadata.py
Normal file
46
bundles/element-web/metadata.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
defaults = {
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/element-web': {
|
||||
'mountpoint': '/opt/element-web',
|
||||
'needed_by': {
|
||||
'directory:/opt/element-web',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/element-web',
|
||||
)
|
||||
def nginx_config(metadata):
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'element-web': {
|
||||
'domain': metadata.get('element-web/url'),
|
||||
'webroot': '/opt/element-web/webapp/',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/element-web/services',
|
||||
)
|
||||
def icinga_check_for_new_release(metadata):
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'element-web': {
|
||||
'services': {
|
||||
'ELEMENT-WEB UPDATE': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_github_for_new_release vector-im/element-web {}'.format(metadata.get('element-web/version')),
|
||||
'vars.notification.mail': True,
|
||||
'check_interval': '60m',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
89
bundles/forgejo/files/app.ini
Normal file
89
bundles/forgejo/files/app.ini
Normal file
|
@ -0,0 +1,89 @@
|
|||
APP_NAME = ${app_name}
|
||||
RUN_USER = git
|
||||
RUN_MODE = prod
|
||||
WORK_PATH = /var/lib/forgejo
|
||||
|
||||
[repository]
|
||||
ROOT = /var/lib/forgejo/repositories
|
||||
MAX_CREATION_LIMIT = 0
|
||||
DEFAULT_BRANCH = main
|
||||
|
||||
[ui]
|
||||
ISSUE_PAGING_NUM = 50
|
||||
MEMBERS_PAGING_NUM = 100
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
SSH_DOMAIN = ${domain}
|
||||
DOMAIN = ${domain}
|
||||
HTTP_ADDR = 127.0.0.1
|
||||
HTTP_PORT = 22000
|
||||
ROOT_URL = https://${domain}/
|
||||
DISABLE_SSH = false
|
||||
SSH_PORT = 22
|
||||
LFS_START_SERVER = true
|
||||
LFS_JWT_SECRET = ${lfs_secret_key}
|
||||
OFFLINE_MODE = true
|
||||
START_SSH_SERVER = false
|
||||
DISABLE_ROUTER_LOG = true
|
||||
LANDING_PAGE = explore
|
||||
|
||||
[database]
|
||||
DB_TYPE = postgres
|
||||
HOST = ${database.get('host', 'localhost')}:5432
|
||||
NAME = ${database['database']}
|
||||
USER = ${database['username']}
|
||||
PASSWD = ${database['password']}
|
||||
SSL_MODE = disable
|
||||
LOG_SQL = false
|
||||
|
||||
[admin]
|
||||
DEFAULT_EMAIL_NOTIFICATIONS = onmention
|
||||
DISABLE_REGULAR_ORG_CREATION = true
|
||||
|
||||
[security]
|
||||
INTERNAL_TOKEN = ${internal_token}
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = ${security_secret_key}
|
||||
LOGIN_REMEMBER_DAYS = 30
|
||||
DISABLE_GIT_HOOKS = ${str(not enable_git_hooks).lower()}
|
||||
|
||||
[openid]
|
||||
ENABLE_OPENID_SIGNIN = false
|
||||
ENABLE_OPENID_SIGNUP = false
|
||||
|
||||
[service]
|
||||
REGISTER_EMAIL_CONFIRM = true
|
||||
ENABLE_NOTIFY_MAIL = true
|
||||
DISABLE_REGISTRATION = ${str(disable_registration).lower()}
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
||||
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
|
||||
DEFAULT_ENABLE_TIMETRACKING = true
|
||||
NO_REPLY_ADDRESS = noreply.${domain}
|
||||
EMAIL_DOMAIN_BLOCKLIST = ${','.join(sorted(email_domain_blocklist))}
|
||||
|
||||
[mailer]
|
||||
ENABLED = true
|
||||
PROTOCOL = sendmail
|
||||
FROM = "${app_name}" <noreply@${domain}>
|
||||
|
||||
[session]
|
||||
PROVIDER = file
|
||||
|
||||
[picture]
|
||||
DISABLE_GRAVATAR = true
|
||||
ENABLE_FEDERATED_AVATAR = false
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = warn
|
||||
|
||||
[oauth2]
|
||||
JWT_SECRET = ${oauth_secret_key}
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = true
|
||||
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
|
17
bundles/forgejo/files/forgejo.service
Normal file
17
bundles/forgejo/files/forgejo.service
Normal file
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=${app_name} at ${domain}
|
||||
After=syslog.target
|
||||
After=network.target
|
||||
Requires=postgresql.service
|
||||
|
||||
[Service]
|
||||
RestartSec=10
|
||||
Type=simple
|
||||
User=git
|
||||
Group=git
|
||||
WorkingDirectory=/var/lib/forgejo
|
||||
ExecStart=/usr/local/bin/forgejo web -c /etc/forgejo/app.ini
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
65
bundles/forgejo/items.py
Normal file
65
bundles/forgejo/items.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
users = {
|
||||
'git': {
|
||||
'home': '/var/lib/forgejo',
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/var/lib/forgejo/.ssh': {
|
||||
'mode': '0700',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
},
|
||||
'/var/lib/forgejo': {
|
||||
'owner': 'git',
|
||||
'mode': '0700',
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/usr/local/lib/systemd/system/forgejo.service': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('forgejo'),
|
||||
'triggers': {
|
||||
'action:systemd-reload',
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
'/etc/forgejo/app.ini': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('forgejo'),
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
'/usr/local/bin/forgejo': {
|
||||
'content_type': 'download',
|
||||
'source': 'https://codeberg.org/forgejo/forgejo/releases/download/v{0}/forgejo-{0}-linux-amd64'.format(node.metadata.get('forgejo/version')),
|
||||
'content_hash': node.metadata.get('forgejo/sha1', None),
|
||||
'mode': '0755',
|
||||
'triggers': {
|
||||
'svc_systemd:forgejo:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if node.metadata.get('forgejo/install_ssh_key', False):
|
||||
files['/var/lib/forgejo/.ssh/id_ed25519'] = {
|
||||
'content': repo.vault.decrypt_file(f'forgejo/files/ssh-keys/{node.name}.key.vault'),
|
||||
'mode': '0600',
|
||||
'owner': 'git',
|
||||
'group': 'git',
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'forgejo': {
|
||||
'needs': {
|
||||
'file:/etc/forgejo/app.ini',
|
||||
'file:/usr/local/bin/forgejo',
|
||||
'file:/usr/local/lib/systemd/system/forgejo.service',
|
||||
},
|
||||
},
|
||||
}
|
107
bundles/forgejo/metadata.py
Normal file
107
bundles/forgejo/metadata.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
defaults = {
|
||||
'backups': {
|
||||
'paths': {
|
||||
'/var/lib/forgejo',
|
||||
},
|
||||
},
|
||||
'forgejo': {
|
||||
'app_name': 'Forgejo',
|
||||
'database': {
|
||||
'username': 'forgejo',
|
||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||
'database': 'forgejo',
|
||||
},
|
||||
'disable_registration': True,
|
||||
'email_domain_blocklist': set(),
|
||||
'enable_git_hooks': False,
|
||||
'internal_token': repo.vault.password_for('{} forgejo internal_token'.format(node.name)),
|
||||
'lfs_secret_key': repo.vault.password_for('{} forgejo lfs_secret_key'.format(node.name)),
|
||||
'oauth_secret_key': repo.vault.password_for('{} forgejo oauth_secret_key'.format(node.name)),
|
||||
'security_secret_key': repo.vault.password_for('{} forgejo security_secret_key'.format(node.name)),
|
||||
},
|
||||
'icinga2_api': {
|
||||
'forgejo': {
|
||||
'services': {
|
||||
'FORGEJO PROCESS': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_systemd_unit forgejo',
|
||||
},
|
||||
'FORGEJO UPDATE': {
|
||||
'vars.notification.mail': True,
|
||||
'check_interval': '60m',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'openssh': {
|
||||
'allowed_users': {
|
||||
'git',
|
||||
},
|
||||
},
|
||||
'postgresql': {
|
||||
'roles': {
|
||||
'forgejo': {
|
||||
'password': repo.vault.password_for('{} postgresql forgejo'.format(node.name)),
|
||||
},
|
||||
},
|
||||
'databases': {
|
||||
'forgejo': {
|
||||
'owner': 'forgejo',
|
||||
},
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/forgejo': {
|
||||
'mountpoint': '/var/lib/forgejo',
|
||||
'needed_by': {
|
||||
'directory:/var/lib/forgejo',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'icinga2_api/forgejo',
|
||||
)
|
||||
def update_monitoring(metadata):
|
||||
return {
|
||||
'icinga2_api': {
|
||||
'forgejo': {
|
||||
'services': {
|
||||
'FORGEJO UPDATE': {
|
||||
'command_on_monitored_host': '/usr/local/share/icinga/plugins/check_forgejo_for_new_release codeberg.org forgejo/forgejo v{}'.format(metadata.get('forgejo/version')),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts/forgejo',
|
||||
)
|
||||
def nginx(metadata):
|
||||
if not node.has_bundle('nginx'):
|
||||
raise DoNotRunAgain
|
||||
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
'forgejo': {
|
||||
'domain': metadata.get('forgejo/domain'),
|
||||
'locations': {
|
||||
'/': {
|
||||
'target': 'http://127.0.0.1:22000',
|
||||
},
|
||||
'/debug': {
|
||||
'return': 403,
|
||||
},
|
||||
},
|
||||
'website_check_path': '/user/login',
|
||||
'website_check_string': 'Sign In',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
svc_systemd = {}
|
||||
|
||||
for i in (
|
||||
'google-accounts-daemon.service',
|
||||
'google-accounts-manager.service',
|
||||
'google-clock-skew-daemon.service',
|
||||
'google-clock-sync-manager.service',
|
||||
'sshguard.service'
|
||||
):
|
||||
svc_systemd[i] = {
|
||||
'enabled': False,
|
||||
'running': False,
|
||||
}
|
242
bundles/grafana/dashboard-rows/battery.py
Normal file
242
bundles/grafana/dashboard-rows/battery.py
Normal file
|
@ -0,0 +1,242 @@
|
|||
def dashboard_row_battery(panel_id, node):
|
||||
return {
|
||||
'title': 'battery',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'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': 6,
|
||||
'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"] == "battery" and
|
||||
r["host"] == "{node.name}" and
|
||||
(
|
||||
r["_field"] == "energy_full" or
|
||||
r["_field"] == "energy_now"
|
||||
)
|
||||
)
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: float(v: r.energy_now) / float(v: r.energy_full) * 100.0
|
||||
}})
|
||||
)
|
||||
|> drop(columns: ["energy_now", "energy_full"])""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'battery charge',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'percent',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 2,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'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': 6,
|
||||
'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"] == "battery" and
|
||||
r["_field"] == "power_now" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: float(v: r._value) / 1000000.0
|
||||
}})
|
||||
)
|
||||
|> 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': 'power draw from battery',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'watts',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 1,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
247
bundles/grafana/dashboard-rows/cpu.py
Normal file
247
bundles/grafana/dashboard-rows/cpu.py
Normal file
|
@ -0,0 +1,247 @@
|
|||
def dashboard_row_cpu(panel_id, node):
|
||||
queries_cpu = []
|
||||
queries_load = []
|
||||
|
||||
for measurement in [
|
||||
'user',
|
||||
'system',
|
||||
'steal',
|
||||
'iowait',
|
||||
'nice',
|
||||
'softirq',
|
||||
'guest',
|
||||
'guest_nice',
|
||||
]:
|
||||
queries_cpu.append({
|
||||
'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"] == "cpu" and
|
||||
r["_field"] == "usage_{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{measurement}"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'load1',
|
||||
'load5',
|
||||
'load15',
|
||||
]:
|
||||
queries_load.append({
|
||||
'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"] == "system" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'cpu/load',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 10,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'max': False,
|
||||
'min': False,
|
||||
'rightSide': False,
|
||||
'show': True,
|
||||
'total': False,
|
||||
'values': False
|
||||
},
|
||||
'lines': True,
|
||||
'linewidth': 0,
|
||||
'NonePointMode': 'None',
|
||||
'options': {
|
||||
'alertThreshold': True
|
||||
},
|
||||
'percentage': False,
|
||||
'pluginVersion': '7.5.5',
|
||||
'pointradius': 2,
|
||||
'points': False,
|
||||
'renderer': 'flot',
|
||||
'seriesOverrides': [],
|
||||
'spaceLength': 10,
|
||||
'span': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_cpu,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'cpu',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'percent',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': False,
|
||||
'steppedLine': False,
|
||||
'targets': queries_load,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'load',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'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
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
441
bundles/grafana/dashboard-rows/disk_iops.py
Normal file
441
bundles/grafana/dashboard-rows/disk_iops.py
Normal file
|
@ -0,0 +1,441 @@
|
|||
def dashboard_row_disk_iops(panel_id, node):
|
||||
return {
|
||||
'title': 'disk iops',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'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"] == "diskio" and
|
||||
r["_field"] == "reads" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "read")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'read IOPS',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'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"] == "diskio" and
|
||||
r["_field"] == "writes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "write")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'write IOPS',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'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"] == "diskio" and
|
||||
r["_field"] == "read_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "read")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'read bytes',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'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"] == "diskio" and
|
||||
r["_field"] == "write_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "write")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'write bytes',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
236
bundles/grafana/dashboard-rows/disk_space.py
Normal file
236
bundles/grafana/dashboard-rows/disk_space.py
Normal file
|
@ -0,0 +1,236 @@
|
|||
def dashboard_row_disk_space(panel_id, node):
|
||||
queries_bytes = []
|
||||
queries_inodes = []
|
||||
|
||||
for measurement in [
|
||||
'used',
|
||||
'free',
|
||||
]:
|
||||
queries_bytes.append({
|
||||
'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"] == "disk" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["fstype"] == "ext4" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'inodes_used',
|
||||
'inodes_free',
|
||||
]:
|
||||
queries_inodes.append({
|
||||
'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"] == "disk" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["fstype"] == "ext4" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'disk space',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.path}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_bytes,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'disk space',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.path}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_inodes,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'disk inodes',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
252
bundles/grafana/dashboard-rows/dovecot.py
Normal file
252
bundles/grafana/dashboard-rows/dovecot.py
Normal file
|
@ -0,0 +1,252 @@
|
|||
def dashboard_row_dovecot(panel_id, node):
|
||||
return {
|
||||
'title': 'dovecot',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "dovecot" and
|
||||
r["_field"] == "num_connected_sessions" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "num_connected_sessions")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
}],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'dovecot connected sessions',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "dovecot" and
|
||||
r["_field"] == "read_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "read")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "dovecot" and
|
||||
r["_field"] == "write_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "write")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'dovecot traffic',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'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
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
583
bundles/grafana/dashboard-rows/ip_traffic.py
Normal file
583
bundles/grafana/dashboard-rows/ip_traffic.py
Normal file
|
@ -0,0 +1,583 @@
|
|||
def dashboard_row_ip_traffic(panel_id, node):
|
||||
return {
|
||||
'title': 'ip traffic',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.interface}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "net" and
|
||||
r["_field"] == "bytes_recv" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "in"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "in")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "net" and
|
||||
r["_field"] == "bytes_sent" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1,
|
||||
_field: "out"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "out")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'bytes per interface',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.interface}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "net" and
|
||||
r["_field"] == "packets_recv" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "in"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "in")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "net" and
|
||||
r["_field"] == "packets_sent" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1,
|
||||
_field: "out"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "out")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'packets per interface',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'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
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "nstat" and
|
||||
r["_field"] == "IpExtInOctets" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "in"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "in")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "nstat" and
|
||||
r["_field"] == "IpExtOutOctets" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1,
|
||||
_field: "out"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "out")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'IPv4',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "nstat" and
|
||||
r["_field"] == "Ip6InOctets" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "in"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "in")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "nstat" and
|
||||
r["_field"] == "Ip6OutOctets" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1,
|
||||
_field: "out"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s)
|
||||
|> yield(name: "out")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'IPv6',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'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
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
447
bundles/grafana/dashboard-rows/ipmitool.py
Normal file
447
bundles/grafana/dashboard-rows/ipmitool.py
Normal file
|
@ -0,0 +1,447 @@
|
|||
def dashboard_row_ipmitool(panel_id, node):
|
||||
return {
|
||||
'title': 'ipmitool',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'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': 8,
|
||||
'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"] == "ipmi_sensor" and
|
||||
r["unit"] == "degrees_c" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "cpu")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'temperatures',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'celsius',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'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"] == "ipmi_sensor" and
|
||||
r["unit"] == "rpm" 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': 'rotrpm',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'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': 5,
|
||||
'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"] == "ipmi_sensor" and
|
||||
r["unit"] == "volts" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "cpu")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'voltages',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'volts',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.name}'
|
||||
},
|
||||
'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': 7,
|
||||
'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"] == "ipmi_sensor" and
|
||||
r["unit"] == "watts" 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': 'power',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'watts',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
126
bundles/grafana/dashboard-rows/memory.py
Normal file
126
bundles/grafana/dashboard-rows/memory.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
def dashboard_row_memory(panel_id, node):
|
||||
queries_mem = []
|
||||
|
||||
for measurement in [
|
||||
'used',
|
||||
'buffered',
|
||||
'cached',
|
||||
'sreclaimable',
|
||||
'sunreclaim',
|
||||
'free',
|
||||
]:
|
||||
queries_mem.append({
|
||||
'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"] == "mem" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'memory',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 12,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_mem,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'memory usage',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
376
bundles/grafana/dashboard-rows/nginx.py
Normal file
376
bundles/grafana/dashboard-rows/nginx.py
Normal file
|
@ -0,0 +1,376 @@
|
|||
def dashboard_row_nginx(panel_id, node):
|
||||
queries_through = []
|
||||
queries_conn = []
|
||||
queries_timing = []
|
||||
|
||||
for measurement in [
|
||||
'accepted',
|
||||
'handled',
|
||||
'requests',
|
||||
]:
|
||||
queries_through.append({
|
||||
'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"] == "nginx" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'active',
|
||||
'reading',
|
||||
'writing',
|
||||
'waiting',
|
||||
]:
|
||||
queries_conn.append({
|
||||
'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"] == "nginx" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'request_time',
|
||||
'upstream_response_time',
|
||||
]:
|
||||
queries_timing.append({
|
||||
'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"] == "nginx_timing" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{measurement}"
|
||||
}})
|
||||
)
|
||||
|> group(columns: ["path", "_field"])
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'nginx',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_conn,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'nginx connections',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_through,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'nginx throughput',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.path}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 0,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': True,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'max': False,
|
||||
'min': False,
|
||||
'rightSide': True,
|
||||
'show': True,
|
||||
'total': False,
|
||||
'values': False
|
||||
},
|
||||
'lines': False,
|
||||
'linewidth': 1,
|
||||
'NonePointMode': 'None',
|
||||
'options': {
|
||||
'alertThreshold': True
|
||||
},
|
||||
'percentage': False,
|
||||
'pluginVersion': '7.5.5',
|
||||
'pointradius': 2,
|
||||
'points': True,
|
||||
'renderer': 'flot',
|
||||
'seriesOverrides': [],
|
||||
'spaceLength': 10,
|
||||
'span': 12,
|
||||
'stack': False,
|
||||
'steppedLine': False,
|
||||
'targets': queries_timing,
|
||||
'thresholds': [
|
||||
{
|
||||
'colorMode': 'warning',
|
||||
'fill': False,
|
||||
'line': True,
|
||||
'op': 'gt',
|
||||
'value': 5,
|
||||
'yaxis': 'left'
|
||||
},
|
||||
{
|
||||
'colorMode': 'critical',
|
||||
'fill': False,
|
||||
'line': True,
|
||||
'op': 'gt',
|
||||
'value': 15,
|
||||
'yaxis': 'left'
|
||||
}
|
||||
],
|
||||
'timeRegions': [],
|
||||
'title': 'nginx timing',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 's',
|
||||
'label': 'request time',
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
126
bundles/grafana/dashboard-rows/postfix.py
Normal file
126
bundles/grafana/dashboard-rows/postfix.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
def dashboard_row_postfix(panel_id, node):
|
||||
queries = []
|
||||
|
||||
for measurement in [
|
||||
'active',
|
||||
'corrupt',
|
||||
'deferred',
|
||||
'hold',
|
||||
'incoming',
|
||||
]:
|
||||
queries.append({
|
||||
'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"] == "postfix_queue" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'postfix',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 12,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postfix queue',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
606
bundles/grafana/dashboard-rows/postgresql.py
Normal file
606
bundles/grafana/dashboard-rows/postgresql.py
Normal file
|
@ -0,0 +1,606 @@
|
|||
def dashboard_row_postgresql(panel_id, node):
|
||||
queries_transactions = []
|
||||
queries_rows = []
|
||||
queries_conflicts = []
|
||||
queries_blocks = []
|
||||
queries_buffers = []
|
||||
|
||||
for measurement in [
|
||||
'commit',
|
||||
'rollback',
|
||||
]:
|
||||
queries_transactions.append({
|
||||
'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"] == "postgresql" and
|
||||
r["_field"] == "xact_{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{measurement}"
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'deleted',
|
||||
'fetched',
|
||||
'inserted',
|
||||
'returned',
|
||||
'updated',
|
||||
]:
|
||||
queries_rows.append({
|
||||
'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"] == "postgresql" and
|
||||
r["_field"] == "tup_{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{measurement}"
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'conflicts',
|
||||
'deadlocks',
|
||||
]:
|
||||
queries_conflicts.append({
|
||||
'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"] == "postgresql" and
|
||||
r["_field"] == "{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement in [
|
||||
'read',
|
||||
'hit',
|
||||
]:
|
||||
queries_blocks.append({
|
||||
'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"] == "postgresql" and
|
||||
r["_field"] == "blks_{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{measurement}"
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
for measurement, alias in {
|
||||
'alloc': 'allocated',
|
||||
'backend': 'written by backend',
|
||||
'backend_fsync': 'fsync by backend',
|
||||
'checkpoint': 'written during checkpoints',
|
||||
'clean': 'written by background writer',
|
||||
}.items():
|
||||
queries_buffers.append({
|
||||
'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"] == "postgresql" and
|
||||
r["_field"] == "buffers_{measurement}" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "{alias}"
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "{measurement}")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
})
|
||||
|
||||
return {
|
||||
'title': 'postgresql',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '200px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.db}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_transactions,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postgresql transactions per second',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.db}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_rows,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postgresql rows per second',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.db}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_conflicts,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postgresql conflicts/deadlocks',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.db}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_blocks,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postgresql blocks read per second',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': True,
|
||||
'steppedLine': False,
|
||||
'targets': queries_buffers,
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'postgresql buffers',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
248
bundles/grafana/dashboard-rows/rspamd.py
Normal file
248
bundles/grafana/dashboard-rows/rspamd.py
Normal file
|
@ -0,0 +1,248 @@
|
|||
def dashboard_row_rspamd(panel_id, node):
|
||||
return {
|
||||
'title': 'rspamd',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': True,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'max': False,
|
||||
'min': False,
|
||||
'rightSide': False,
|
||||
'show': True,
|
||||
'total': False,
|
||||
'values': False
|
||||
},
|
||||
'lines': False,
|
||||
'linewidth': 1,
|
||||
'NonePointMode': 'None',
|
||||
'options': {
|
||||
'alertThreshold': True
|
||||
},
|
||||
'percentage': False,
|
||||
'pluginVersion': '7.5.5',
|
||||
'pointradius': 2,
|
||||
'points': False,
|
||||
'renderer': 'flot',
|
||||
'seriesOverrides': [],
|
||||
'spaceLength': 10,
|
||||
'span': 6,
|
||||
'stack': True,
|
||||
'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"] == "rspamd_actions" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1m, nonNegative: true)
|
||||
|> yield(name: "value")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
}],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'rspamd actions',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "rspamd_stats" and
|
||||
r["_field"] == "scanned" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1m, nonNegative: true)
|
||||
|> yield(name: "avg")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "rspamd_stats" and
|
||||
r["_field"] == "learned" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1m, nonNegative: true)
|
||||
|> yield(name: "mean")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'rspamd scanned/learned',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
228
bundles/grafana/dashboard-rows/sensors.py
Normal file
228
bundles/grafana/dashboard-rows/sensors.py
Normal file
|
@ -0,0 +1,228 @@
|
|||
def dashboard_row_sensors(panel_id, node):
|
||||
return {
|
||||
'title': 'sensors',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.chip} ${__field.labels.feature}'
|
||||
},
|
||||
'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': 8,
|
||||
'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"] == "sensors" and
|
||||
r["_field"] == "temp_input" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "cpu")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'temperatures',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'celsius',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.chip} ${__field.labels.feature}'
|
||||
},
|
||||
'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"] == "sensors" and
|
||||
r["_field"] == "fan_input" 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': 'rotrpm',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
118
bundles/grafana/dashboard-rows/smartd.py
Normal file
118
bundles/grafana/dashboard-rows/smartd.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
def dashboard_row_smartd(panel_id, node):
|
||||
return {
|
||||
'title': 'smartd',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'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': 12,
|
||||
'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"] == "temperature" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "cpu")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'temperatures',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'celsius',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
263
bundles/grafana/dashboard-rows/unbound.py
Normal file
263
bundles/grafana/dashboard-rows/unbound.py
Normal file
|
@ -0,0 +1,263 @@
|
|||
def dashboard_row_unbound(panel_id, node):
|
||||
return {
|
||||
'title': 'unbound',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "unbound" and
|
||||
r["_field"] == "total_num_queries" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "total_num_queries")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
}],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'unbound queries per second',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "unbound" and
|
||||
r["_field"] == "total_recursion_time_avg" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "avg")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "unbound" and
|
||||
r["_field"] == "total_recursion_time_mean" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [
|
||||
{
|
||||
'colorMode': 'warning',
|
||||
'fill': True,
|
||||
'line': True,
|
||||
'op': 'gt',
|
||||
'value': 1,
|
||||
'yaxis': 'left'
|
||||
},
|
||||
{
|
||||
'colorMode': 'critical',
|
||||
'fill': True,
|
||||
'line': True,
|
||||
'op': 'gt',
|
||||
'value': 5,
|
||||
'yaxis': 'left'
|
||||
}
|
||||
],
|
||||
'timeRegions': [],
|
||||
'title': 'unbound recursion time',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 's',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
258
bundles/grafana/dashboard-rows/wireguard.py
Normal file
258
bundles/grafana/dashboard-rows/wireguard.py
Normal file
|
@ -0,0 +1,258 @@
|
|||
def dashboard_row_wireguard(panel_id, node):
|
||||
return {
|
||||
'title': 'wireguard',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.public_key}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "wireguard_peer" and
|
||||
r["_field"] == "last_handshake_time_ns" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value / 1000000000
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s, nonNegative: true)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "last_handshake_time_ns")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
}],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'wireguard last handshake time',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 's',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name} ${__field.labels.public_key}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "wireguard_peer" and
|
||||
r["_field"] == "rx_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "in")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "wireguard_peer" and
|
||||
r["_field"] == "tx_bytes" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_value: r._value * -1
|
||||
}})
|
||||
)
|
||||
|> derivative(unit: 1s)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "out")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'wireguard traffic',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'binBps',
|
||||
'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
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
724
bundles/grafana/dashboard-rows/zfs.py
Normal file
724
bundles/grafana/dashboard-rows/zfs.py
Normal file
|
@ -0,0 +1,724 @@
|
|||
def dashboard_row_zfs(panel_id, node):
|
||||
return {
|
||||
'title': 'zfs',
|
||||
'collapse': False,
|
||||
'editable': False,
|
||||
'height': '250px',
|
||||
'panels': [
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_c" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "target"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "target")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_size" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "used"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "used")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'zfs arc usage',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_l2_size" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "used"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "used")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'zfs l2arc usage',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_hits" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "hits"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s, nonNegative: true)
|
||||
|> yield(name: "misses")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_misses" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "misses"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s, nonNegative: true)
|
||||
|> yield(name: "misses")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_l2_hits" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "l2hits"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s, nonNegative: true)
|
||||
|> yield(name: "misses")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs" and
|
||||
r["_field"] == "arcstats_l2_misses" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> map(fn: (r) => ({{
|
||||
r with
|
||||
_field: "l2misses"
|
||||
}})
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> derivative(unit: 1s, nonNegative: true)
|
||||
|> yield(name: "misses")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'zfs arc hits/misses',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.dataset} ${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'stack': True,
|
||||
'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"] == "zfs_dataset" and
|
||||
r["_field"] == "used" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "used")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs_dataset" and
|
||||
r["_field"] == "usedsnap" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "usedsnap")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'zfs usage per dataset',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
{
|
||||
'aliasColors': {},
|
||||
'bars': False,
|
||||
'dashLength': 10,
|
||||
'dashes': False,
|
||||
'datasource': None,
|
||||
'fieldConfig': {
|
||||
'defaults': {
|
||||
'displayName': '${__field.labels.pool} ${__field.name}'
|
||||
},
|
||||
'overrides': []
|
||||
},
|
||||
'fill': 1,
|
||||
'fillGradient': 0,
|
||||
'hiddenSeries': False,
|
||||
'id': next(panel_id),
|
||||
'legend': {
|
||||
'alignAsTable': False,
|
||||
'avg': False,
|
||||
'current': False,
|
||||
'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': 6,
|
||||
'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"] == "zfs_pool" and
|
||||
r["_field"] == "used" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "used")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
{
|
||||
'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"] == "zfs_pool" and
|
||||
r["_field"] == "size" and
|
||||
r["host"] == "{node.name}"
|
||||
)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "size")""",
|
||||
'resultFormat': 'time_series',
|
||||
'select': [[
|
||||
{'type': 'field', 'params': ['value']},
|
||||
{'type': 'mean', 'params': []},
|
||||
]],
|
||||
"tags": []
|
||||
},
|
||||
],
|
||||
'thresholds': [],
|
||||
'timeRegions': [],
|
||||
'title': 'zfs usage per pool',
|
||||
'tooltip': {
|
||||
'shared': True,
|
||||
'sort': 0,
|
||||
'value_type': 'individual'
|
||||
},
|
||||
'type': 'graph',
|
||||
'xaxis': {
|
||||
'buckets': None,
|
||||
'mode': 'time',
|
||||
'name': None,
|
||||
'show': True,
|
||||
'values': []
|
||||
},
|
||||
'yaxes': [
|
||||
{
|
||||
'format': 'bytes',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': 0,
|
||||
'show': True,
|
||||
'decimals': 0,
|
||||
},
|
||||
{
|
||||
'format': 'short',
|
||||
'label': None,
|
||||
'logBase': 1,
|
||||
'max': None,
|
||||
'min': None,
|
||||
'show': False,
|
||||
}
|
||||
],
|
||||
'yaxis': {
|
||||
'align': False,
|
||||
'alignLevel': None
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
15
bundles/grafana/files/dashboards.yaml
Normal file
15
bundles/grafana/files/dashboards.yaml
Normal file
|
@ -0,0 +1,15 @@
|
|||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'managed by bundlewrap'
|
||||
orgId: 1
|
||||
folder: 'Managed by BundleWrap'
|
||||
folderUid: '222af3a08b'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
updateIntervalSeconds: 10
|
||||
allowUiUpdates: false
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards
|
||||
foldersFromFilesStructure: false
|
||||
|
102
bundles/grafana/files/grafana.ini
Normal file
102
bundles/grafana/files/grafana.ini
Normal file
|
@ -0,0 +1,102 @@
|
|||
app_mode = production
|
||||
instance_name = ${node.name}
|
||||
|
||||
[paths]
|
||||
data = /var/lib/grafana
|
||||
;temp_data_lifetime = 24h
|
||||
logs = /var/log/grafana
|
||||
plugins = /var/lib/grafana/plugins
|
||||
provisioning = /etc/grafana/provisioning
|
||||
|
||||
[server]
|
||||
protocol = http
|
||||
http_port = 21010
|
||||
domain = ${domain}
|
||||
root_url = https://${domain}/
|
||||
|
||||
[database]
|
||||
type = sqlite3
|
||||
|
||||
# for postgres
|
||||
;host = 127.0.0.1:3306
|
||||
;name = grafana
|
||||
;user = root
|
||||
;password =
|
||||
;ssl_mode = disable
|
||||
|
||||
# for sqlite
|
||||
;path = grafana.db
|
||||
;cache_mode = private
|
||||
|
||||
[remote_cache]
|
||||
type = database
|
||||
|
||||
[analytics]
|
||||
reporting_enabled = false
|
||||
check_for_updates = false
|
||||
|
||||
[security]
|
||||
disable_initial_admin_creation = false
|
||||
secret_key = ${secret_key}
|
||||
disable_gravatar = true
|
||||
cookie_secure = true
|
||||
allow_embedding = ${str(allow_embedding).lower()}
|
||||
|
||||
[dashboards]
|
||||
min_refresh_interval = 10s
|
||||
|
||||
[users]
|
||||
allow_sign_up = ${str(allow_sign_up).lower()}
|
||||
allow_org_create = false
|
||||
auto_assign_org = false
|
||||
verify_email_enabled = true
|
||||
default_theme = dark
|
||||
viewers_can_edit = false
|
||||
editors_can_admin = false
|
||||
|
||||
[auth]
|
||||
login_maximum_inactive_lifetime_duration = ${login_max_duration}
|
||||
login_maximum_lifetime_duration = ${login_max_duration}
|
||||
|
||||
[auth.anonymous]
|
||||
enabled = ${str(allow_anonymous).lower()}
|
||||
org_name = ${anonymous_org}
|
||||
org_role = Viewer
|
||||
|
||||
[smtp]
|
||||
enabled = ${str(enable_smtp).lower()}
|
||||
host = localhost:25
|
||||
from_address = noreply@${domain}
|
||||
from_name = Grafana
|
||||
|
||||
[emails]
|
||||
welcome_email_on_sign_up = false
|
||||
templates_pattern = emails/*.html
|
||||
|
||||
[log]
|
||||
mode = console
|
||||
|
||||
[alerting]
|
||||
enabled = false
|
||||
|
||||
[explore]
|
||||
enabled = true
|
||||
|
||||
[plugins]
|
||||
enable_alpha = true
|
||||
|
||||
[date_formats]
|
||||
full_date = YYYY-MM-DD HH:mm:ss
|
||||
interval_second = HH:mm:ss
|
||||
interval_minute = HH:mm
|
||||
interval_hour = YYYY-MM-DD HH:mm
|
||||
interval_day = YYYY-MM-DD
|
||||
interval_month = YYYY-MM
|
||||
interval_year = YYYY
|
||||
default_timezone = browser
|
||||
|
||||
[expressions]
|
||||
enabled = true
|
||||
|
||||
[metrics]
|
||||
enabled = false
|
172
bundles/grafana/items.py
Normal file
172
bundles/grafana/items.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
from itertools import count
|
||||
from os import listdir
|
||||
from os.path import isdir, isfile, join
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
from bundlewrap.metadata import metadata_to_json
|
||||
|
||||
for row in Path(join(repo.path, 'bundles', 'grafana', 'dashboard-rows')).rglob("*.py"):
|
||||
with open(row, 'r') as f:
|
||||
exec(f.read())
|
||||
|
||||
directories = {
|
||||
# Don't ask me why these permissions are that weird. It's what the
|
||||
# debian package sets them to after upgrades.
|
||||
'/etc/grafana/provisioning/dashboards': {
|
||||
'group': 'grafana',
|
||||
'purge': True,
|
||||
},
|
||||
'/etc/grafana/provisioning/datasources': {
|
||||
'group': 'grafana',
|
||||
'purge': True,
|
||||
},
|
||||
'/etc/grafana/provisioning/notifiers': {
|
||||
'group': 'grafana',
|
||||
'purge': True,
|
||||
},
|
||||
'/etc/grafana/provisioning/plugins': {
|
||||
'group': 'grafana',
|
||||
'purge': True,
|
||||
},
|
||||
'/var/lib/grafana/dashboards': {
|
||||
'owner': 'grafana',
|
||||
'group': 'grafana',
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'svc_systemd:grafana-server:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/grafana/grafana.ini': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata['grafana'],
|
||||
'group': 'grafana',
|
||||
'mode': '0640',
|
||||
'triggers': {
|
||||
'svc_systemd:grafana-server:restart',
|
||||
},
|
||||
},
|
||||
'/etc/grafana/provisioning/dashboards/bundlewrap.yaml': {
|
||||
'source': 'dashboards.yaml',
|
||||
'group': 'grafana',
|
||||
'mode': '0640',
|
||||
'triggers': {
|
||||
'svc_systemd:grafana-server:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'grafana-server': {
|
||||
'needs': {
|
||||
'file:/etc/grafana/grafana.ini',
|
||||
'pkg_apt:grafana',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
### dashboard management starts here
|
||||
for rnode in repo.nodes:
|
||||
if not rnode.has_bundle('telegraf'):
|
||||
continue
|
||||
|
||||
panel_id = count(start=1)
|
||||
dashboard = {
|
||||
'title': rnode.name,
|
||||
'uid': UUID(int=rnode.magic_number).hex[:10],
|
||||
|
||||
'editable': False,
|
||||
'graphTooltip': 1,
|
||||
'schemaVersion': 12,
|
||||
'style': 'dark',
|
||||
'tags': {'bw'},
|
||||
'time': {
|
||||
'from': 'now-1d',
|
||||
'to': 'now'
|
||||
},
|
||||
'version': 1,
|
||||
|
||||
'rows': [
|
||||
dashboard_row_cpu(panel_id, rnode),
|
||||
dashboard_row_ip_traffic(panel_id, rnode),
|
||||
dashboard_row_memory(panel_id, rnode),
|
||||
],
|
||||
}
|
||||
|
||||
if rnode.has_bundle('ipmitool'):
|
||||
dashboard['rows'].append(dashboard_row_ipmitool(panel_id, rnode))
|
||||
dashboard['tags'].add('ipmitool')
|
||||
elif rnode.has_bundle('lm-sensors'):
|
||||
dashboard['rows'].append(dashboard_row_sensors(panel_id, rnode))
|
||||
dashboard['tags'].add('lm-sensors')
|
||||
|
||||
if rnode.has_bundle('smartd'):
|
||||
dashboard['rows'].append(dashboard_row_smartd(panel_id, rnode))
|
||||
dashboard['tags'].add('smartd')
|
||||
|
||||
if rnode.has_bundle('telegraf-battery-usage'):
|
||||
dashboard['rows'].append(dashboard_row_battery(panel_id, rnode))
|
||||
|
||||
dashboard['rows'].append(dashboard_row_disk_space(panel_id, rnode))
|
||||
dashboard['rows'].append(dashboard_row_disk_iops(panel_id, rnode))
|
||||
|
||||
if rnode.has_bundle('nginx'):
|
||||
dashboard['rows'].append(dashboard_row_nginx(panel_id, rnode))
|
||||
dashboard['tags'].add('nginx')
|
||||
|
||||
if rnode.has_bundle('postfix'):
|
||||
dashboard['rows'].append(dashboard_row_postfix(panel_id, rnode))
|
||||
dashboard['tags'].add('postfix')
|
||||
|
||||
if rnode.has_bundle('dovecot'):
|
||||
dashboard['rows'].append(dashboard_row_dovecot(panel_id, rnode))
|
||||
dashboard['tags'].add('dovecot')
|
||||
|
||||
if rnode.has_bundle('rspamd'):
|
||||
dashboard['rows'].append(dashboard_row_rspamd(panel_id, rnode))
|
||||
dashboard['tags'].add('rspamd')
|
||||
|
||||
if rnode.has_bundle('postgresql'):
|
||||
dashboard['rows'].append(dashboard_row_postgresql(panel_id, rnode))
|
||||
dashboard['tags'].add('postgresql')
|
||||
|
||||
if rnode.has_bundle('wireguard'):
|
||||
dashboard['rows'].append(dashboard_row_wireguard(panel_id, rnode))
|
||||
dashboard['tags'].add('wireguard')
|
||||
|
||||
if rnode.has_bundle('zfs'):
|
||||
dashboard['rows'].append(dashboard_row_zfs(panel_id, rnode))
|
||||
dashboard['tags'].add('zfs')
|
||||
|
||||
if rnode.has_bundle('unbound'):
|
||||
dashboard['rows'].append(dashboard_row_unbound(panel_id, rnode))
|
||||
dashboard['tags'].add('unbound')
|
||||
|
||||
files[f'/var/lib/grafana/dashboards/{rnode.name}.json'] = {
|
||||
'owner': 'grafana',
|
||||
'group': 'grafana',
|
||||
# use metadata_to_json, because this supports sets
|
||||
'content': metadata_to_json(dashboard),
|
||||
'triggers': {
|
||||
'svc_systemd:grafana-server:restart',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
additional_path = join(repo.path, 'data', 'grafana', 'files', node.name, 'dashboards')
|
||||
if isdir(additional_path):
|
||||
for file in listdir(additional_path):
|
||||
if not isfile(join(additional_path, file)) or file.startswith('.') or file.startswith('_'):
|
||||
continue
|
||||
|
||||
files[f'/var/lib/grafana/dashboards/{file}'] = {
|
||||
'owner': 'grafana',
|
||||
'group': 'grafana',
|
||||
'source': join(node.name, 'dashboards', file),
|
||||
'triggers': {
|
||||
'svc_systemd:grafana-server:restart',
|
||||
},
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue