Compare commits
1206 Commits
cfe3cff764
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 10fb8e88ce | |||
| 5f300d9e7b | |||
| 99ac2ea3b0 | |||
| 2caa36c0ab | |||
| 55c15c790d | |||
| 450d9d6457 | |||
| e08bf42eaa | |||
| 8e0e5c0d16 | |||
| ada9db7942 | |||
| 5995612407 | |||
| 5762916970 | |||
| dd456eab69 | |||
| 18a8fde66e | |||
| f97c9185c1 | |||
| 8bf4b185a1 | |||
| 8424d771f6 | |||
| 840f99a7e9 | |||
| 1b27bafd41 | |||
| 4770d671c0 | |||
| 28a7bed3b9 | |||
| 170becceb0 | |||
| 6e8f530537 | |||
| 209bafd70f | |||
| 1d182437db | |||
| 6c046a549e | |||
| 0a30a2ac23 | |||
| 82c15e8d26 | |||
| f277d089bd | |||
| 7ed345b8e8 | |||
| bd6b15b617 | |||
| 3282b7d634 | |||
| 21ed381d18 | |||
| 4500f41983 | |||
| 1d30eeb939 | |||
| 537f144885 | |||
| dbada3c509 | |||
| c8be707420 | |||
| fdba2c75c7 | |||
| 55d600c0c0 | |||
| 71c5bd5e6c | |||
| 998f04713f | |||
| 6935fbea8b | |||
| 301e090251 | |||
| 58d8ef050c | |||
| 41cb2ec791 | |||
| 1faec5b2d1 | |||
| 111b8cec97 | |||
| 3aaebdb1c4 | |||
| 3e7b8c93e3 | |||
| 3e2f46377e | |||
| 38bead3dc8 | |||
| 351d36b217 | |||
| 59a37c9b46 | |||
| d7d3722ce7 | |||
| 6475524d23 | |||
| 1a70ca9564 | |||
| d6f206f0bb | |||
| b3c5366f31 | |||
| fab06ca4d5 | |||
| bd1d04943d | |||
| 8305d1b0c5 | |||
| 2d812c03eb | |||
| 156e63fd6c | |||
| a912c4dc55 | |||
| 8a2a68a91c | |||
| 01d3ab1357 | |||
| 20c5af7a69 | |||
| 865311bf49 | |||
| 9fab06795a | |||
| 038fb7ae76 | |||
| 3775e0dd7b | |||
| 66a5d69846 | |||
| 8747f887f8 | |||
| 39f4460e0a | |||
| 6f8626ca8a | |||
| 04c08bf419 | |||
| 709a24366a | |||
| 63dad8c626 | |||
| b57342f53e | |||
| 7cefa3a650 | |||
| 5d54ae898e | |||
| 794d5c2dad | |||
| 04cdf1bd2f | |||
| b4c4e31437 | |||
| 56bb321e4a | |||
| c0d868088e | |||
| df5c89f071 | |||
| b73bc3e80a | |||
| db25b2bfbb | |||
| 819bfc1531 | |||
| cfdb8d8474 | |||
| d50ed9858c | |||
| 7af4b6a5d1 | |||
| ca04f5d8c3 | |||
| a02cefc62a | |||
| 28974e9688 | |||
| aaf5f79895 | |||
| eccac4d4a2 | |||
| 399f67ba25 | |||
| 439a580dfe | |||
| bfae290927 | |||
| 1eeb0b7102 | |||
| f49ac19af1 | |||
| 27c85ff9d0 | |||
| b6d44b5a20 | |||
| b8b7574536 | |||
| 5ee2cb2b56 | |||
| 6c88bc1790 | |||
| 6be832b012 | |||
| fc9ef6b9ff | |||
| ec19103a81 | |||
| 7499a21cbd | |||
| 5758b3a320 | |||
| 7d2f818fca | |||
| 19d0946e06 | |||
| 7d5294e7b9 | |||
| 28ed3fcf74 | |||
| 5a35cd04a6 | |||
| 5648224062 | |||
| bbb9cacd71 | |||
| 40743442e9 | |||
| 7564c5d740 | |||
| 3a6d5bb8c4 | |||
| f256ca7fad | |||
| cb18e436ca | |||
| 019b1166ec | |||
| cc15f27205 | |||
| 356c049aaf | |||
| df6465fa8a | |||
| a05c33ad87 | |||
| 67906cbf16 | |||
| 09e381ecc4 | |||
| 7fd35b79c4 | |||
| c9900e4314 | |||
| 5ea3bac570 | |||
| eae7bb0e09 | |||
| 465daec0ab | |||
| f516f46b06 | |||
| 742d0172cf | |||
| e0568ddfdc | |||
| 9941dfa61f | |||
| aac9e9f38f | |||
| bdda87778c | |||
| fccec6d87c | |||
| 5e259e0b42 | |||
| 496c483050 | |||
| 1433f88d53 | |||
| 506c4f9357 | |||
| a4ed475237 | |||
| de43e917c5 | |||
| be515979cf | |||
| cc03069d57 | |||
| fe7aaadf64 | |||
| 0b6549a359 | |||
| af60555eea | |||
| 64334192de | |||
| 4751fb5582 | |||
| 34e56a13ea | |||
| c3f2603702 | |||
| 15f6b2edd0 | |||
| 6339b733c4 | |||
| 305ce21e41 | |||
| 8ab1c91b38 | |||
| bf5c7a74cb | |||
| b48ec98cb3 | |||
| 58089e558e | |||
| 97b6874258 | |||
| 8ad0c4d336 | |||
| 536fc2b463 | |||
| b7287b0d51 | |||
| a0ffb52f98 | |||
| eb40b7ff06 | |||
| b3a71cb9bc | |||
| 16594b3e7d | |||
| 7937e00018 | |||
| 0e91e1e7f5 | |||
| 99b387fe8b | |||
| fe53ea7551 | |||
| 541f9b3776 | |||
| 1c9302c773 | |||
| ba9ef3913d | |||
| 79b4a615f0 | |||
| 7225a5e787 | |||
| 467ade9340 | |||
| 619136674e | |||
| 3990566fe5 | |||
| 7f01dc4cac | |||
| da95b2fa71 | |||
| 9b628caaef | |||
| 4ec924b736 | |||
| 2712bd2197 | |||
| 03d3ff5712 | |||
| 6aeb0c9f89 | |||
| 91394ef68a | |||
| a7d304cc5b | |||
| 0b8619bf64 | |||
| 30e75d0ad5 | |||
| a18a0e913d | |||
| ecf3e03e81 | |||
| 934471bd88 | |||
| 0fff2f87a5 | |||
| e8bf13275e | |||
| 436903543b | |||
| c47f678220 | |||
| d4438c8585 | |||
| 0df4a4c1ec | |||
| 365d15767b | |||
| 4969520222 | |||
| 9cfd7f5052 | |||
| faad280aa0 | |||
| f1ea4b9b20 | |||
| b6b90bca7d | |||
| 39b9726be7 | |||
| 7fc3c3db63 | |||
| 89b2a1cf45 | |||
| 94ee6bc9a4 | |||
| 81f04c6c51 | |||
| d0c67baeb8 | |||
| 35fa61ef34 | |||
| 8b5fb0861d | |||
| 17a3602d3c | |||
| fa42667c2a | |||
| d161d5f421 | |||
| a36b1e8310 | |||
| 640ad93684 | |||
| 51a3a10701 | |||
| cf340ca277 | |||
| c0d51ee06d | |||
| 53d73142ae | |||
| 8e52274edd | |||
| cbde498ae8 | |||
| 9feace9558 | |||
| 2a5496118b | |||
| f362b2ab77 | |||
| 7a8cd490d5 | |||
| 5a20d97084 | |||
| 8def0af08f | |||
| 348d8e1d03 | |||
| a078503a89 | |||
| bc57914131 | |||
| 3b01625f7d | |||
| 8cf4762a65 | |||
| 6f9b384caa | |||
| 7ac54dd987 | |||
| 57684db260 | |||
| c20998d365 | |||
| 2f1d88b001 | |||
| b8453eaf43 | |||
| 87d22fba6d | |||
| e4eb5c80fc | |||
| c8e3542fe8 | |||
| 0ac30a5190 | |||
| c02651e65a | |||
| 9a5a28098c | |||
| 9cfc423a38 | |||
| 3b043eaf6d | |||
| 386b70314d | |||
| e2add63337 | |||
| 7de9b583d5 | |||
| 406c0f539e | |||
| 1651b8a550 | |||
| ff2fdd3c08 | |||
| 35ad68fbbe | |||
| bd4503c035 | |||
| c423af5498 | |||
| a2d482e16d | |||
| 12ef36af33 | |||
| 6ae6c5e0e5 | |||
| 44b47ce18c | |||
| c96c24f864 | |||
| df50e70f3e | |||
| da669efee2 | |||
| 776346ca19 | |||
| af8dd30afe | |||
| 5599bfad67 | |||
| 83d1e7677a | |||
| 16d91bac8f | |||
| 1c4cc1e393 | |||
| 03eaa38a08 | |||
| 640d2affa8 | |||
| bd9f1ce260 | |||
| 1746412a11 | |||
| 7ec31696fc | |||
| 96d26c5431 | |||
| 21d2e34025 | |||
| 5941526a5e | |||
| e15cb70bdc | |||
| 18312e12a9 | |||
| 033fe672df | |||
| b46fed0624 | |||
| c3d40022df | |||
| 0e81fa3bc9 | |||
| cdbe1f1083 | |||
| c0e2d93c49 | |||
| c9a024840b | |||
| d8db7df64e | |||
| ef8f774f4f | |||
| fa0a61f581 | |||
| addb5d7383 | |||
| 3aa2d3d3c8 | |||
| 7bbd895115 | |||
| 3eb9ce0e89 | |||
| c681eb3139 | |||
| b7bfb0f62a | |||
| 92099bd1e9 | |||
| fb32b88798 | |||
| f86996cd28 | |||
| d46990b7fb | |||
| a54a56ca6a | |||
| fdda6d3caa | |||
| b92b9b963a | |||
| 4a183af66c | |||
| 213101f8cd | |||
| ad8090f8f8 | |||
| 84cd8fa1cc | |||
| 66dcb2a9f2 | |||
| 9db05facea | |||
| b3f80e0818 | |||
| 53fc65d4fb | |||
| 5f11d8d5e5 | |||
| a20751002e | |||
| 99e6f5e476 | |||
| b0f3174ed0 | |||
| c7df114378 | |||
| 2e55d24791 | |||
| cc9c66ba1b | |||
| 2dcde1da86 | |||
| 65619f81b0 | |||
| c927ac6478 | |||
| 6b4dd0c291 | |||
| 51534cc80f | |||
| 157eaefa95 | |||
| b2fe9fed11 | |||
| 8facc35331 | |||
| 66288470f9 | |||
| 2b440d88f7 | |||
| a1907c8adb | |||
| 30f5ca374f | |||
| 51c2636c42 | |||
| 982a12ba08 | |||
| 972084cd65 | |||
| 3c7a9b3eae | |||
| 45f1713443 | |||
| 5d0827848e | |||
| c9e4ab6af0 | |||
| 8650c3f6ee | |||
| 802fcbd47f | |||
| 57a215fec4 | |||
| e6baa25011 | |||
| 5ebcd0818b | |||
| b4bf0ee486 | |||
| 270880c4f2 | |||
| b622a620be | |||
| b1ec7c6d73 | |||
| 46d1483880 | |||
| 8b20f2b13c | |||
| 3b207db752 | |||
| 82bda66d24 | |||
| b330d4610e | |||
| 027be96f9c | |||
| bdca649697 | |||
| 06c876eba5 | |||
| 9db6404ee7 | |||
| 7a5c217c9d | |||
| 8adb0328ab | |||
| 8db6e53f51 | |||
| 277b20f687 | |||
| b8bc7ed40f | |||
| ded214a0e6 | |||
| dd0c8a7b99 | |||
| 52b57a125b | |||
| df8c5a9dbe | |||
| b08bdf8499 | |||
| cd8b5b8d3e | |||
| 317a574583 | |||
| e8f16a7cce | |||
| de318cde63 | |||
| c512a56c11 | |||
| 6141537816 | |||
| 8613adba86 | |||
| 82a49c4728 | |||
| 59fd6b7d3f | |||
| 301b263993 | |||
| ca3dbf7bc4 | |||
| 7ee23f5571 | |||
| c7a2de295a | |||
| 35b1126a97 | |||
| 458f6ff0a4 | |||
| 3d57315867 | |||
| 9a1db71073 | |||
| 51eb78429c | |||
| a5322ef788 | |||
| 7e85907150 | |||
| 42f8c287ed | |||
| cf1bf90dc8 | |||
| 1260d88dc2 | |||
| b733a98a67 | |||
| 686e9f3ac3 | |||
| dc29de6da6 | |||
| 71191632d4 | |||
| 83a2a9cd46 | |||
| 40fb010825 | |||
| 3ea51364e2 | |||
| dffa1d96cf | |||
| ba0ec44434 | |||
| 934a4874a5 | |||
| 2924d613a2 | |||
| 50bd3d9e17 | |||
| 364268aa7f | |||
| 9fd574468a | |||
| 2201e054b9 | |||
| 81d90636f5 | |||
| 31f54d9de2 | |||
| c48e5aa581 | |||
| 6ef2b4ae41 | |||
| c8ebc7eff5 | |||
| 9ced007c27 | |||
| 47cdc1d854 | |||
| 783e0840c9 | |||
| fa82522895 | |||
| 405372e9d5 | |||
| cd4be108c4 | |||
| 552766ec99 | |||
| b9f1322690 | |||
| dabbf0e91a | |||
| c8a715bb67 | |||
| 02aa59a001 | |||
| 45d4a62af2 | |||
| 6956db456d | |||
| 2035e6be96 | |||
| 7767dabc47 | |||
| 96b52f7bd8 | |||
| a19a73d743 | |||
| 5785169d43 | |||
| 97198e7404 | |||
| 847ae4a2db | |||
| 6cfaa64fd2 | |||
| a45efc80dd | |||
| c658e237e2 | |||
| 5ec76883c7 | |||
| 140020c217 | |||
| 2375a00002 | |||
| 1a6a8aa584 | |||
| 3958b1891c | |||
| 8ba21ec498 | |||
| 5d69b79790 | |||
| 8ffefca044 | |||
| ff0dab4b03 | |||
| f3cea82f12 | |||
| 7c759b702f | |||
| 4d6118a021 | |||
| 84321167f8 | |||
| 9b797bfdc2 | |||
| 15bb5f5351 | |||
| 9eca272e46 | |||
| 772353bd71 | |||
| dc112c97c9 | |||
| 7bc17f648b | |||
| 6cc1f011f7 | |||
| 63baa26bce | |||
| c02140d91d | |||
| 62e9541d42 | |||
| ef176a800b | |||
| 38631545f1 | |||
| 528c030588 | |||
| fdd00af834 | |||
| bf03d3a3a8 | |||
| 5547534e6c | |||
| 31521248b8 | |||
| d8ebc6d1cf | |||
| ca6ab23403 | |||
| 0eead50afd | |||
| d4a5d45f68 | |||
| f3d64c7249 | |||
| fe7a61e848 | |||
| f721a2f969 | |||
| b61734c4c5 | |||
| 35fe864a8f | |||
| 008ca8b5b4 | |||
| 532c7d926f | |||
| fb75780712 | |||
| 3a04928079 | |||
| b4fe570ee4 | |||
| 679c86fc4f | |||
| 7d87c8ed75 | |||
| 3a8628cdd0 | |||
| d602ce42ee | |||
| e902b96e08 | |||
| 1fa79a5474 | |||
| bb5294e890 | |||
| 23b62a83c4 | |||
| 76e8ef8428 | |||
| 7bbca7d2ae | |||
| a14e17f92a | |||
| b43cdd98f3 | |||
| 3d5e94c2a4 | |||
| 2c8bd2e098 | |||
| 9e0624eaf1 | |||
| 85cdde777b | |||
| 51f934f57c | |||
| 699b5cc422 | |||
| e4a5915c5b | |||
| aede371ff1 | |||
| cb8e2526e3 | |||
| 855bd4d3ec | |||
| bc6e70971c | |||
| 280dea226c | |||
| b2d71564fe | |||
| 96138e0099 | |||
| 1d42a1c405 | |||
| 4832fcea2f | |||
| 1030766305 | |||
| 2be2aa013d | |||
| 560c3967aa | |||
| 28ff135610 | |||
| d29a421cc7 | |||
| 846119ebfe | |||
| 4509664a10 | |||
| 10460ca8b2 | |||
| 49d8907853 | |||
| 2748929e66 | |||
| 46278f9f72 | |||
| b45bf60e69 | |||
| 2ff59533a0 | |||
| 5d60630606 | |||
| 94e5d8ef86 | |||
| 83f2a219c4 | |||
| e039fa91da | |||
| ea8768931f | |||
| 4f3b06dcdf | |||
| 29952a822a | |||
| 6e0b29bbcd | |||
| a96451a797 | |||
| 7b50808621 | |||
| 2a5c40103a | |||
| d6d0e2d734 | |||
| b3c2b620b8 | |||
| 8b096a23cc | |||
| f8d7fc77a9 | |||
| a5d438d3c8 | |||
| 947d929d67 | |||
| e55094da3b | |||
| 0e4ba6ba5f | |||
| 7a5344a6b2 | |||
| 67490acd66 | |||
| 8f53cd9867 | |||
| 0eed1991d8 | |||
| 431e301673 | |||
| cb01ec7e4c | |||
| 1dbba8eac6 | |||
| 0353156163 | |||
| 807dd5124e | |||
| 35c8bbb1ac | |||
| 70e9e79717 | |||
| 52109abbc0 | |||
| 6ca4ad7ce4 | |||
| f619b5536b | |||
| c314da240e | |||
| f5a2793533 | |||
| 3befd4ebb8 | |||
| ffec38c03c | |||
| 5ba076ecdd | |||
| 886db8a070 | |||
| 56d613b570 | |||
| c68f86d904 | |||
| 906725fb9b | |||
| 5daf793143 | |||
| ed14fb9b0e | |||
| 8db9d3382e | |||
| 1f008042ac | |||
| 520979d85b | |||
| 56a35728c1 | |||
| add183ed8b | |||
| 518d043faf | |||
| 4436dad725 | |||
| 4c91007286 | |||
| 76cd48e6af | |||
| 1a4956af6e | |||
| 5d58ae7904 | |||
| 70fd15c5dd | |||
| 38d5a26e78 | |||
| 18747fe129 | |||
| e3e09db610 | |||
| d764cbde57 | |||
| 5da0671fc0 | |||
| 31e3b526f1 | |||
| 09379f901a | |||
| d8a94d3e5c | |||
| 75daf49c98 | |||
| bae3bb97fd | |||
| 22a59d1028 | |||
| 646c909612 | |||
| 7b6008a6c4 | |||
| 57543e5386 | |||
| 893e5bc1da | |||
| 59ee7435b2 | |||
| aac55293a4 | |||
| 7e41b61bac | |||
| d6028880e7 | |||
| 729e626326 | |||
| 8e373cec3f | |||
| f3263cda61 | |||
| cac9721b0c | |||
| 10bb263fb3 | |||
| fce723169d | |||
| 5ace56169a | |||
| 0bae524940 | |||
| 787903f299 | |||
| 62d6e85e5a | |||
| a1bbf635d3 | |||
| a4ce928926 | |||
| 9e291aea76 | |||
| 68d1456b45 | |||
| 52b6eddb40 | |||
| 7dba4938e6 | |||
| 100268f4a7 | |||
| b4e285c00c | |||
| 6390ace14c | |||
| 9891fb3f35 | |||
| b074b8dff7 | |||
| 44ce69444d | |||
| 601b031d44 | |||
| e72cf1585a | |||
| 6ebd768107 | |||
| efb6bb2c56 | |||
| 641b2b0edc | |||
| 02f73c8ee2 | |||
| 4b80ae9b9b | |||
| dc80bb1a46 | |||
| 6041684856 | |||
| 21733a4b12 | |||
| db516ef0b7 | |||
| e67fefa995 | |||
| 1d8d6b6de7 | |||
| c7260cce60 | |||
| 2ea53aa3ea | |||
| 7c5cf501d5 | |||
| 297e0441bb | |||
| 47b76ec980 | |||
| eb80b95d15 | |||
| 7f7c4ffa1b | |||
| 2f48c81e1a | |||
| d8dac4e2de | |||
| c00d3087a5 | |||
| 641ebc81e2 | |||
| bf6a9d31a9 | |||
| f9d4f9ad5f | |||
| 1718f4db11 | |||
| 69dacf6d30 | |||
| 81f6846cf9 | |||
| 13aa3b6dc1 | |||
| c8c72bcb0c | |||
| e8f76a82cc | |||
| 76d009d5df | |||
| 33c5971929 | |||
| 58befac8b9 | |||
| 9fe8c38772 | |||
| 6af562cc73 | |||
| 4158730c6a | |||
| d47ecc9ceb | |||
| 6ebf7e7f23 | |||
| 5ce972a635 | |||
| 26beae17a9 | |||
| d407f35d5d | |||
| 4c98785f39 | |||
| cfa0121853 | |||
| d0cb65965d | |||
| 7050b90fca | |||
| da33caa374 | |||
| baf796dcd7 | |||
| 00dec8a068 | |||
| 709c848813 | |||
| 93e768487a | |||
| e59d39a2ba | |||
| 23d4ce282f | |||
| f3fc94f0cb | |||
| b419bdb1fe | |||
| d6756bd94b | |||
| 160f16e35f | |||
| 5acea13591 | |||
| aed7c73032 | |||
| 579b768230 | |||
| db26c82ebd | |||
| b1a4dfc578 | |||
| 7368a44d0d | |||
| 7109987091 | |||
| 172f36c465 | |||
| 500b2c6982 | |||
| 4a5851457f | |||
| e750862680 | |||
| d52c70df51 | |||
| b72910e8b4 | |||
| f69bcce5ef | |||
| 5791df402d | |||
| 51f6f0e016 | |||
| 66a29ff5d3 | |||
| e0a558527d | |||
| aa75095654 | |||
| 750d8e1596 | |||
| 4b1ef58992 | |||
| 361e93b136 | |||
| b12eeef06d | |||
| f277fb8953 | |||
| 140b1d793a | |||
| b4ebe4d3f8 | |||
| e4559816d0 | |||
| 404845872b | |||
| 98facac514 | |||
| eb135c1031 | |||
| 9e5e253168 | |||
| c3f87ce990 | |||
| 3ebbe5d7d4 | |||
| 7580f897c2 | |||
| 66aac3ac19 | |||
| 6cab771a30 | |||
| 370d31dc5e | |||
| 6b06713a08 | |||
| 4bebf95f11 | |||
| 4f22eeeaa3 | |||
| addb063cee | |||
| 83ac3fea42 | |||
| fc0ea1d622 | |||
| 460a901633 | |||
| ced81c2e89 | |||
| 9810882b95 | |||
| 4d38581dad | |||
| 0ec9252419 | |||
| 55c2869bac | |||
| 1674dd0ec2 | |||
| 6235f34a3b | |||
| 67559f02ea | |||
| a4175263a4 | |||
| fea6d863d3 | |||
| 30fe42e857 | |||
| 4517d8f207 | |||
| 7e1640b3e8 | |||
| 3617bf22be | |||
| cc243d4c4d | |||
| 83a2c7848d | |||
| 6ad5b8c98a | |||
| 885660c4e4 | |||
| da256dadaa | |||
| 45598f92e5 | |||
| be99f1c89a | |||
| 87b3c81e2f | |||
| 08731a72a3 | |||
| 017be187eb | |||
| 4d95309ed6 | |||
| b0282c3013 | |||
| e0d314fe3a | |||
| fb41a82c31 | |||
| d07e0d8ec5 | |||
| 3da0233861 | |||
| a11e5b97f3 | |||
| bc7b3c059c | |||
| c45fb4d230 | |||
| d1437de4b1 | |||
| 41c343c336 | |||
| 0e57b505bc | |||
| cf5b1d32bc | |||
| 8be0cce54a | |||
| ef97530433 | |||
| 1c174baf5c | |||
| 9bc84699de | |||
| ba6ed4c5a8 | |||
| 2f3831683b | |||
| 64dab4fdf3 | |||
| 33aa510335 | |||
| 407dfd5d3a | |||
| 648a643252 | |||
| e02320fb07 | |||
| f0493aa05d | |||
| 12c05e146c | |||
| bfdff27021 | |||
| c5032b4e95 | |||
| e76c1eaf77 | |||
| 9d88cb16fc | |||
| 3f1ba57d29 | |||
| 3f66618dea | |||
| 92afa38c2e | |||
| 9850e02bda | |||
| 2591118a5d | |||
| dba986fadc | |||
| 6ec1378c97 | |||
| 42c009af19 | |||
| 0774fe0c5a | |||
| fb328941d8 | |||
| 84ca75ef59 | |||
| 9252bdda07 | |||
| 04d696be64 | |||
| a598d28959 | |||
| 8530ee662b | |||
| 38d4da83c8 | |||
| b81504eb62 | |||
| 7999721e1b | |||
| a63b679b66 | |||
| c24d7fb468 | |||
| f92a108579 | |||
| d54ced97ae | |||
| f82c463df5 | |||
| d0ca72f089 | |||
| b5487c1783 | |||
| 43ad01fb1f | |||
| 45678e4216 | |||
| 1342fe12c7 | |||
| bd50971827 | |||
| b1025e5dc0 | |||
| d53ed53527 | |||
| eddea2ad49 | |||
| a8e4d55e22 | |||
| 629c2abe16 | |||
| 3e6c4d98e6 | |||
| 795b3e2f99 | |||
| 7c7e3b67b0 | |||
| a6553a27e2 | |||
| f16086963b | |||
| d0ddc3f8be | |||
| 3d7c7e41b3 | |||
| ff6e8beab4 | |||
| 3260eaf3a3 | |||
| aaf92c8953 | |||
| 56282c8ba6 | |||
| f081efd17e | |||
| ed8b99d709 | |||
| 0b3c4dfc08 | |||
| d3178949f1 | |||
| 6b06f2740b | |||
| 0af4903072 | |||
| 3553f77bd1 | |||
| 339a9c20e8 | |||
| b10fff353c | |||
| 999010495e | |||
| 1b2c631684 | |||
| 3615be8dbb | |||
| 702ab238d7 | |||
| f92df32f6f | |||
| 161fac357d | |||
| b83b2149d1 | |||
| a62b258322 | |||
| 7f5d893898 | |||
| b33cbf963d | |||
| 131542bc1d | |||
| f3a37fcff8 | |||
| 104db7b032 | |||
| 7f8d2c8a3b | |||
| 986d6531a9 | |||
| c3362128bb | |||
| 0291b0be30 | |||
| 4c2cbc7355 | |||
| 283eca1c85 | |||
| 5d730c338e | |||
| 8c0c542dac | |||
| 6ea8ae80e7 | |||
| 9404b920af | |||
| 1896c629b3 | |||
| b2d7038a6c | |||
| 3cea85349f | |||
| c663c24d8e | |||
| 429709d559 | |||
| 5992595fb1 | |||
| d05f38e270 | |||
| 55ae74077e | |||
| 79f4e067f0 | |||
| 8a2ead3698 | |||
| cbbefd6923 | |||
| 64514421a3 | |||
| 3ec95fba7b | |||
| 1b1b6e11ac | |||
| 5a81312ef4 | |||
| bc321cedc1 | |||
| 834b103210 | |||
| 48a8a92232 | |||
| a6abeeb2c5 | |||
| 0169176ea7 | |||
| 35e371f2a6 | |||
| 7b72717d23 | |||
| 80d70d8aec | |||
| 278288a4a4 | |||
| 9ee5c1ebc4 | |||
| 37a657bc32 | |||
| 94c3b37096 | |||
| 4d44d7b960 | |||
| 4e3bf001b4 | |||
| b6d07d4142 | |||
| b22ebcd59e | |||
| a5774454b3 | |||
| 1f596f833f | |||
| a548bd08ac | |||
| 61d659989f | |||
| 413a7370db | |||
| 0d57967369 | |||
| 8d3e070007 | |||
| 261a5ded0b | |||
| 9f243b89a8 | |||
| 180215ef46 | |||
| 6ce1c882f6 | |||
| 450a4a3879 | |||
| 90a97abfdd | |||
| 8040c59a51 | |||
| b73763c6f8 | |||
| eb3e3972f3 | |||
| 4baf791e21 | |||
| 8934f20baf | |||
| b01e2661c7 | |||
| 0f0687cad7 | |||
| b7769e46a9 | |||
| c58fcd0af6 | |||
| 7807c9b817 | |||
| d665c931d3 | |||
| 4a1ccb1710 | |||
| 57050089d3 | |||
| d3c15a40dd | |||
| 3525235f35 | |||
| 426761d852 | |||
| 862b920e21 | |||
| ffd19d46d2 | |||
| 6604bdcb8e | |||
| 0bce2bf6fd | |||
| b00b09f139 | |||
| d8245f197c | |||
| c2832cf812 | |||
| 68edee4e90 | |||
| e6f44e9b21 | |||
| 52f2aba4d7 | |||
| ed88e082b9 | |||
| 3e35cde009 | |||
| 0ffbfb61fc | |||
| cee5ed5f88 | |||
| 6d47e49918 | |||
| a82b2fac68 | |||
| df57a56c16 | |||
| 3609174d67 | |||
| 0ca94f96d9 | |||
| 5ed197549d | |||
| 9e1fd9293c | |||
| d588727fbb | |||
| e1d3ab538a | |||
| 14efa66936 | |||
| 471204ad86 | |||
| 232621e155 | |||
| 5157ff97e2 | |||
| a4eb21471b | |||
| 41fa14358b | |||
| c305ef0e83 | |||
| 7f15a8f213 | |||
| 26ba2f4ddf | |||
| 0edfa742bd | |||
| cfb4f767df | |||
| 1722a479c3 | |||
| 4a3aac6419 | |||
| b5b2ce0236 | |||
| 8c37ee4eaf | |||
| 813be14942 | |||
| 2d5a1a68eb | |||
| de7dd1089b | |||
| 7d7c8a6a0b | |||
| 22e1efa377 | |||
| 45545dff05 | |||
| 76f56cc263 | |||
| ae9ec45bd4 | |||
| f0775355f4 | |||
| 93bd456d62 | |||
| 38fb866a4f | |||
| 8cc3783614 | |||
| 58c174a1fc | |||
| ab950494c9 | |||
| 5edd121651 | |||
| efa8fd7b9b | |||
| 2eab0bc0e6 | |||
| 4880d08c76 | |||
| a04658af9b | |||
| ac3fac8369 | |||
| b9a6a7df68 | |||
| e20599f71e | |||
| 50c0d396d6 | |||
| 33af4a02e4 | |||
| 29b33d73dd | |||
| 4aba370004 | |||
| c69e0e2c7c | |||
| 2ec5d2e743 | |||
| 20e95c50de | |||
| 69085b22d0 | |||
| d34ba2d59a | |||
| 05d9aa0be2 | |||
| 1a32dd6b4c | |||
| 36c6cdd901 | |||
| 90c09f99ff | |||
| bb0dfbd1f1 | |||
| 179f295245 | |||
| b21192977f | |||
| d7a21c5c72 | |||
| 9dcc69e510 | |||
| f8cc06cb6a | |||
| b4c9ff653a | |||
| a28935f261 | |||
| ea774e4463 | |||
| 030af881ff | |||
| 92c3981b4b | |||
| f96d446124 | |||
| 3566c4acb8 | |||
| bd0a2b3db2 | |||
| 8ed337d9a1 | |||
| aa38b2b1ee | |||
| 49e571c3d4 | |||
| 8bc67465f1 | |||
| 653657122e | |||
| f968ab5d1b | |||
| 67947bf60c | |||
| 056eeb6657 | |||
| 6e624127ea | |||
| 61c4f93bba | |||
| e071e24275 | |||
| a714c0b680 | |||
| c607abcbf5 | |||
| 3e9de29a97 | |||
| 817d965c54 | |||
| feb17342d8 | |||
| 016db1d7ed | |||
| fc58ce98cb | |||
| 8403c92792 | |||
| 653b503676 | |||
| f1d0ea2cda | |||
| 2673727654 | |||
| 1b0aab8d8f | |||
| 13fc6e9cfd | |||
| f3ec0914de | |||
| fe0aed764a | |||
| c9dee50372 | |||
| 9b9d1b18e5 | |||
| 0f6341949b | |||
| ff0960ef0a | |||
| 3faf185914 | |||
| a118bcbfe2 | |||
| a93726053b | |||
| 4572975219 | |||
| e637a44278 | |||
| 472e12e053 | |||
| ada3ae27d9 | |||
| 311a9144c6 | |||
| 2fc72c5dfc | |||
| d1ea9498c8 | |||
| 445ee05632 | |||
| 7a0d60c17c | |||
| e2353a4f97 | |||
| affc03de22 | |||
| e238280cfa | |||
| e1447458d7 | |||
| 2e4f4ad5d9 | |||
| 5ea365955d | |||
| 993aef1ece | |||
| edb55af964 | |||
| 1890173440 | |||
| 99a920306c | |||
| 93793616c0 | |||
| 22fef6fca8 | |||
| 784f450002 | |||
| 0212469963 | |||
| d79a6491c3 | |||
| 1d2da78329 | |||
| 1871feebdb | |||
| ec513e106e | |||
| c356c4dce9 | |||
| 89096b4d2c | |||
| be4fc2676e | |||
| 64455b070a | |||
| bd7f68b4a3 | |||
| 4921dfb6f7 | |||
| e413146d7c | |||
| e836852257 | |||
| 11cd9ce28a | |||
| e3dbd18235 | |||
| 931e5b4801 | |||
| 3a77a1bf54 | |||
| dd976ab3bc | |||
| 272ade9527 | |||
| 7d125d54b2 | |||
| 26f49c0ac1 | |||
| 8be5263ecb | |||
| 076a5c6602 | |||
| 42ece6a873 | |||
| 6d42a0ecac | |||
| 9f48ac2f3c | |||
| 9296314a1b | |||
| 490c681aed | |||
| 92c383debf | |||
| 228d0d334b | |||
| 6dc3798dc7 | |||
| 255896cb9f | |||
| 5651351d68 | |||
| 859eee40fa | |||
| 7d2819cafb | |||
| 58e7ad2080 | |||
| c6e471febf | |||
| 82021e93b4 | |||
| f3323924cb | |||
| 69d8cab82a | |||
| 7e11ad0029 | |||
| c05ba67bd7 | |||
| d9977599fc | |||
| 9474c4cf2a | |||
| 74dddaaa1d | |||
| bf19ab1c33 | |||
| b4dab8dc32 | |||
| e45c1b4ab1 | |||
| d63100d838 | |||
| dbed2df22b | |||
| 0a423be786 | |||
| 444e843ce1 | |||
| 5b51ca1591 | |||
| fca2eaf581 | |||
| 46d27d5199 | |||
| db48fbdd97 | |||
| 741a69ea3f | |||
| b672d5e47c | |||
| cffc6352e0 | |||
| bb719bc176 | |||
| 7a056a6c5a | |||
| 9dc702932b | |||
| 1ce0f54d5e | |||
| a96a27a89b | |||
| 2ceda342e7 | |||
| 5dddbbbb04 | |||
| f0945efff9 | |||
| 21c984d35c | |||
| 4a716a8fd2 | |||
| 12274240b6 | |||
| 4f14fb971b | |||
| 9e420cd183 | |||
| 38398a4cc9 | |||
| d2d3b29140 | |||
| a6bfdc9167 | |||
| fadf1a5a10 | |||
| 1da565f82f | |||
| 89aff6c7e4 | |||
| 6b1385f557 | |||
| 106c83e2b0 | |||
| 26e20340d3 | |||
| 232ce326a5 | |||
| 758b075645 | |||
| fb9ab56b9b | |||
| 652f31e2f1 | |||
| d14ee7fa5f | |||
| a833f47a84 | |||
| 1055714683 | |||
| 12f0c9bb1e | |||
| ed1c28c1fd | |||
| cbd5d2644d | |||
| e34b5f499b | |||
| 190c03ae2e | |||
| 0b2a92fcf1 | |||
| 9f88466440 | |||
| cb1baa15fc | |||
| 7f05bc88e7 | |||
| 819c34ee7d | |||
| 908a348803 | |||
| aff39fca6f | |||
| a7be64ec8e | |||
| c4d3c030ee | |||
| 8a295e54ea | |||
| 6685dd21ac | |||
| 9d0b5079b1 | |||
| 2c175a324b | |||
| 0e1cd46b45 | |||
| 47111140f4 | |||
| c1321f4564 | |||
| c92095f68f | |||
| e767299be4 | |||
| 4ad8f0a4ea | |||
| 1aa7dfc787 | |||
| 8ba9af9c7b | |||
| 7390d109b2 | |||
| 4f01bddd8b | |||
| a78e3b0813 | |||
| 371e4e131d | |||
| df965d90ac | |||
| f156120464 | |||
| 6ed4cd7415 | |||
| 099a00fd5b | |||
| 63da8aa6fb | |||
| 3f7c2cc163 | |||
| 39eff80ba9 | |||
| 518300e28b | |||
| dcf83b2e97 | |||
| 02c2873ba9 | |||
| 1ca10e4422 | |||
| 0244dcb281 | |||
| 7ddd0bae05 | |||
| 64f92d84ec | |||
| c06d6e3b11 | |||
| e5d05c1a2d | |||
| fad7a9223d | |||
| 96840bb17a | |||
| 6044d2da4c | |||
| eee540b359 | |||
| 1bf9eb4f4e | |||
| 975db4ae26 | |||
| 036f2fa76e | |||
| 33dd1fa571 | |||
| 8e428771c1 | |||
| 22620a747e | |||
| e797eb40e0 | |||
| 34289a34a4 | |||
| 1c0f69c213 | |||
| 2fed1e626e | |||
| 78c070d7a8 | |||
| b32c7d72b1 |
106
.chatgpt_config.yaml
Normal file
106
.chatgpt_config.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
project_name: "cloonar-nixos"
|
||||
default_prompt_blocks:
|
||||
- "basic-prompt"
|
||||
- "secure-coding"
|
||||
initial_prompt: |
|
||||
You are helping me build or refine a NixOS configuration (potentially with Nix Flakes). Please keep the following points in mind when generating or explaining code:
|
||||
|
||||
1. **Project & Directory Structure**
|
||||
- For single-host configurations, you may have a simple structure like:
|
||||
```
|
||||
/etc/nixos/
|
||||
├── configuration.nix
|
||||
├── hardware-configuration.nix
|
||||
└── other-module.nix
|
||||
```
|
||||
- For multi-host setups or more complex deployments, consider **modules** in a dedicated folder:
|
||||
```
|
||||
my-nix-config/
|
||||
├── flake.nix # (if using Flakes)
|
||||
├── hosts/
|
||||
│ ├── hostname1/
|
||||
│ │ └── configuration.nix
|
||||
│ └── hostname2/
|
||||
│ └── configuration.nix
|
||||
├── modules/
|
||||
│ ├── networking.nix
|
||||
│ ├── services.nix
|
||||
│ ├── users.nix
|
||||
│ └── ...
|
||||
└── hardware/
|
||||
└── hardware-configuration-<machine>.nix
|
||||
```
|
||||
- Split large configurations into multiple `.nix` files or modules for clarity. Import them in a top-level `configuration.nix` or `flake.nix`.
|
||||
|
||||
2. **Nix Flakes (Optional)**
|
||||
- If using Flakes, include a top-level `flake.nix` defining your outputs:
|
||||
- `outputs.nixosConfigurations.<hostname> = { ... }`
|
||||
- Reference your system with something like `nixos-rebuild switch --flake .#<hostname>`.
|
||||
- Keep pinned inputs (e.g., `nixpkgs` at a particular commit) in your `flake.lock` to ensure reproducibility.
|
||||
|
||||
3. **System Configuration & Modules**
|
||||
- Place typical NixOS settings (e.g., `networking.hostName`, `time.timeZone`, `environment.systemPackages`, etc.) in `configuration.nix` or a modular file structure.
|
||||
- Use [NixOS modules](https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules) to separate concerns. For example:
|
||||
- `networking.nix` for network settings,
|
||||
- `users.nix` for user/group management,
|
||||
- `services.nix` for enabling/configuring system services.
|
||||
- If you have custom logic or package overlays, keep them in separate files (e.g., `overlays.nix`).
|
||||
|
||||
4. **Home Manager Integration (Optional)**
|
||||
- For user-level configuration (e.g., dotfiles, user-specific packages), consider integrating [Home Manager](https://nix-community.github.io/home-manager/) either as a standalone or via Flakes.
|
||||
- Keep Home Manager configs in a separate `home.nix` file, referencing it in your main configuration or flake outputs.
|
||||
|
||||
5. **Security & Secrets Management**
|
||||
- Avoid committing plain-text secrets (passwords, tokens) to version control.
|
||||
- Consider using [sops-nix](https://github.com/Mic92/sops-nix) or other secret management solutions to encrypt sensitive files.
|
||||
- Enable recommended security settings, such as:
|
||||
- `security.sudo.wheelNeedsPassword = true`
|
||||
- `security.rtkit.enable = true`
|
||||
- `users.users.<name>.extraGroups` to limit privileges.
|
||||
- Regularly update your `nixpkgs` channel or flake inputs for the latest security patches.
|
||||
|
||||
6. **System Services & Daemons**
|
||||
- Use built-in NixOS modules for services (e.g., `services.nginx`, `services.postgresql`, etc.) instead of manual configuration whenever possible.
|
||||
- For each service, ensure you:
|
||||
- Set `enable = true;` if it’s needed,
|
||||
- Provide configuration in the same module file or a dedicated file if it’s complex.
|
||||
- Keep service-specific secrets (e.g., database passwords) out of the main config by referencing environment variables or a secret management solution.
|
||||
|
||||
7. **Package Management & Overlays**
|
||||
- Place packages you need system-wide into `environment.systemPackages`.
|
||||
- For overriding or extending packages from `nixpkgs`, use the [overlays](https://nixos.wiki/wiki/Overlays) mechanism:
|
||||
```nix
|
||||
self: super: {
|
||||
myPackage = super.callPackage ./pkgs/my-package { };
|
||||
}
|
||||
```
|
||||
- Maintain a dedicated `overlays/` folder if you have multiple custom overlays.
|
||||
|
||||
8. **Customization & Extensions**
|
||||
- Use `environment.etc` or NixOS options to create or manage custom config files in `/etc/`.
|
||||
- For advanced use cases, you can define your own modules to unify logic for related settings or services.
|
||||
- Document each module with comments about what it configures and why.
|
||||
|
||||
9. **Testing & Deployment**
|
||||
- Use the `nixos-rebuild test` command to evaluate changes without fully switching.
|
||||
- If using Flakes, run `nixos-rebuild test --flake .#<hostname>`.
|
||||
- Test critical services after switching (e.g., `systemctl status service-name`).
|
||||
- Consider building virtual machines via `nixos-rebuild build-vm` or [NixOS tests](https://nixos.org/manual/nixos/stable/index.html#sec-nixos-tests) to validate complex changes.
|
||||
|
||||
10. **Output Format**
|
||||
- Present any generated Nix configuration as well-structured `.nix` files, referencing them in a central place (`configuration.nix` or `flake.nix`).
|
||||
- When explaining your reasoning, describe which modules or options you chose and why (e.g., “I separated `networking.nix` to isolate network settings from system services.”).
|
||||
- If you modify existing files, specify precisely which lines or sections have changed, and why you made those changes.
|
||||
|
||||
Please follow these guidelines to ensure the generated or explained NixOS configuration adheres to best practices for maintainability, modularity, and security.
|
||||
|
||||
debug: false
|
||||
improved_debug: false
|
||||
|
||||
preview_changes: false
|
||||
interactive_file_selection: false
|
||||
partial_acceptance: false
|
||||
|
||||
enable_debug_commands: false
|
||||
prompt_char_limit: 300000
|
||||
enable_step_by_step: true
|
||||
1
.github/copilot-instructions.md
vendored
Symbolic link
1
.github/copilot-instructions.md
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../.roo/rules/rules.md
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1 +1,8 @@
|
||||
.null*.nix
|
||||
.commit
|
||||
|
||||
raspberry/.env
|
||||
raspberry/result
|
||||
|
||||
esphome/trash
|
||||
esphome/.esphome
|
||||
|
||||
8
.mcp.json
Normal file
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"nixos": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-nixos"]
|
||||
}
|
||||
}
|
||||
}
|
||||
84
.roo/rules/rules.md
Normal file
84
.roo/rules/rules.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# RULES.md
|
||||
|
||||
## Overview
|
||||
|
||||
This repository manages NixOS configurations for multiple systems, structured to promote modularity, security, and maintainability.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
Each host has its own directory under `hosts/`, containing:
|
||||
|
||||
```
|
||||
|
||||
hosts/
|
||||
└── hostname/
|
||||
├── configuration.nix
|
||||
├── modules/
|
||||
└── secrets.yaml
|
||||
```
|
||||
|
||||
|
||||
|
||||
* `configuration.nix`: Main configuration file for the host.
|
||||
* `modules/`: Custom NixOS modules specific to the host.
|
||||
* `secrets.yaml`: Encrypted secrets file (see [Secrets Management](#secrets-management)).
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Modularization
|
||||
|
||||
* Break down configurations into reusable modules placed in the `modules/` directory.
|
||||
* Use the `imports` directive in `configuration.nix` to include necessary modules.
|
||||
* Avoid monolithic configurations; modularity enhances clarity and reusability.
|
||||
|
||||
### Version Control
|
||||
|
||||
* Track all configuration files using Git.
|
||||
* Exclude sensitive files like `secrets.yaml` from version control.
|
||||
* Use descriptive commit messages to document changes.
|
||||
|
||||
## Deployment with Bento
|
||||
|
||||
Bento is utilized for deploying configurations across systems.
|
||||
|
||||
* Centralize configurations on a management server.
|
||||
* Ensure each host accesses only its specific configuration files.
|
||||
* Leverage Bento's features to manage deployments efficiently.([NixOS Discourse][1], [Reddit][2], [cbiit.github.io][3])
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Secrets Management
|
||||
|
||||
* Never store plain-text secrets in the Nix store or configuration files.
|
||||
* Use tools like [sops-nix](https://github.com/Mic92/sops-nix) to encrypt `secrets.yaml`.
|
||||
* Restrict access to decrypted secrets using appropriate file permissions.([Reddit][4], [dade][5])
|
||||
|
||||
### System Hardening
|
||||
|
||||
* Disable unnecessary services to minimize attack surfaces.
|
||||
* Configure firewalls to allow only essential traffic.
|
||||
* Regularly update systems to apply security patches.
|
||||
|
||||
### User Management
|
||||
|
||||
* Implement the principle of least privilege for user accounts.
|
||||
* Use SSH keys for authentication; disable password-based logins.
|
||||
* Monitor user activities and access logs for suspicious behavior.
|
||||
|
||||
## Maintenance Guidelines
|
||||
|
||||
* Regularly review and refactor modules for efficiency and clarity.
|
||||
* Document all modules and configurations for future reference.
|
||||
* Test configurations in a controlled environment before deploying to production systems.([NixOS & Flakes][6])
|
||||
* After developing a feature, delete the corresponding development plan.
|
||||
|
||||
---
|
||||
|
||||
Adhering to these guidelines will help maintain a secure, organized, and efficient NixOS configuration across multiple systems.
|
||||
|
||||
[1]: https://discourse.nixos.org/t/introducing-bento-a-nixos-deployment-framework/21446?utm_source=chatgpt.com "Introducing bento, a NixOS deployment framework"
|
||||
[2]: https://www.reddit.com/r/NixOS/comments/1e95b69/how_do_you_guys_organize_your_nix_config_files_i/?utm_source=chatgpt.com "How do you guys organize your .nix config files? I have a ... - Reddit"
|
||||
[3]: https://cbiit.github.io/bento-docs/master/installation/bento-quick-start.html?utm_source=chatgpt.com "1. Quick Start Tutorial — Bento release-4.1.0 documentation"
|
||||
[4]: https://www.reddit.com/r/NixOS/comments/1cnhx6z/best_security_practices_for_nixos_devices_exposed/?utm_source=chatgpt.com "Best Security practices for NixOS devices exposed to the Internet"
|
||||
[5]: https://0xda.de/blog/2024/07/framework-and-nixos-sops-nix-secrets-management/?utm_source=chatgpt.com "Framework and NixOS - Sops-nix Secrets Management - dade"
|
||||
[6]: https://nixos-and-flakes.thiscute.world/nixos-with-flakes/modularize-the-configuration?utm_source=chatgpt.com "Modularize Your NixOS Configuration | NixOS & Flakes Book"
|
||||
155
.sops.yaml
155
.sops.yaml
@@ -3,81 +3,154 @@
|
||||
# Also see https://github.com/Mic92/dotfiles/blob/master/nixos/.sops.yaml
|
||||
# for a more complex example.
|
||||
keys:
|
||||
- &dominik age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
- &tuxedo age17c4swm58zt07axl5u6kkxrwtr5haqkvu4ye4t98qdph98qdclgtq2cyzkq
|
||||
- &bitwarden age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7 # nixos age key
|
||||
- &dominik age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
- &dominik2 age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
- &git-server age106n5n3rrrss45eqqzz8pq90la3kqdtnw63uw0sfa2mahk5xpe30sxs5x58
|
||||
- &web-01-server age1y6lvl5jkwc47p5ae9yz9j9kuwhy7rtttua5xhygrgmr7ehd49svsszyt42
|
||||
- &home-assistant-server age1ezq2j34qngky22enhnslx6hzh4ekwk8dtmn6c9us0uqxqpn7hgpsspjz58
|
||||
- &ldap-server-test age1azmxsw5llmp2nnsv3yc2l8paelmq9rfepxd8jvmswgsmax0qyyxqdnsc7t
|
||||
- &testmodules age1zkzpnfeakyvg3fqtyay32sushjx2hqe28y6hs6ss7plemzqjqa5s6s5yu3
|
||||
- &web-02 age1gjm4c3swt8u88e36gf2qlg3syxfc0ly94u64c42f2tsf24npw4csa6e4fw
|
||||
- &web-arm age1ylrpaytkm0k5kcecsxvyv5xd9ts4md0uap48g6wsmj9pwm4lf5esffu0gw
|
||||
- &ldap-server-arm age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||
- &fw age1wq82xjyj80htz33x7agxddjfumr3wkwh3r24tasagepxw7ka893sau68df
|
||||
- &fw-new age12msc2c6drsaw0yk2hjlaw0q0lyq0emjx5e8rq7qc7ql689k593kqfmhss2
|
||||
- &netboot age14uarclad0ty5supc8ep09793xrnwkv8a4h9j0fq8d8lc92n2dadqkf64vw
|
||||
- &gpd-win4 age1ceg548u5ma6rgu3xgvd254y5xefqrdqfqhcjsjp3255q976fgd2qaua53d
|
||||
- &nb age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
- &amzebs-01 age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||
- &nas age1x3elhtccp4u8ha5ry32juj9fkpg0qg7qqx4gduuehgwwnnhcxp8s892hek
|
||||
|
||||
creation_rules:
|
||||
- path_regex: ^[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *tuxedo
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- path_regex: computers/git.cloonar.com/[^/]+\.yaml$
|
||||
- *dominik2
|
||||
- *nb
|
||||
- path_regex: hosts/nb/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *git-server
|
||||
- path_regex: computers/web-01.cloonar.com/[^/]+\.yaml$
|
||||
- *dominik2
|
||||
- *nb
|
||||
- path_regex: hosts/gpd-win4/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *web-01-server
|
||||
- path_regex: computers/home-assistant.cloonar.com/[^/]+\.yaml$
|
||||
- *dominik2
|
||||
- *gpd-win4
|
||||
- *nb
|
||||
- path_regex: hosts/fw/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *home-assistant-server
|
||||
- path_regex: computers/ldap.cloonar.com/[^/]+\.yaml$
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *fw
|
||||
- path_regex: hosts/fw-new/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *fw
|
||||
- *fw-new
|
||||
- path_regex: hosts/fw-new/modules/web/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *web-02
|
||||
- path_regex: hosts/web-arm/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *web-arm
|
||||
- path_regex: hosts/amzebs-01/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *amzebs-01
|
||||
- path_regex: hosts/nas/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *nas
|
||||
- path_regex: hosts/mail/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- path_regex: modules/lego/[^/]+\.yaml$
|
||||
- path_regex: hosts/fw/modules/web/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *web-02
|
||||
- path_regex: utils/modules/lego/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *git-server
|
||||
- *web-01-server
|
||||
- *home-assistant-server
|
||||
- *web-02
|
||||
- *web-arm
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- *testmodules
|
||||
- *netboot
|
||||
- path_regex: modules/bitwarden/[^/]+\.yaml$
|
||||
- *fw
|
||||
- *fw-new
|
||||
- path_regex: utils/modules/attic-cache/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *web-01-server
|
||||
- path_regex: modules/drone/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *dominik
|
||||
- *git-server
|
||||
- path_regex: modules/zammad/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *dominik
|
||||
- *web-01-server
|
||||
- path_regex: modules/plausible/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *dominik
|
||||
- *web-01-server
|
||||
- path_regex: modules/openldap/[^/]+\.yaml$
|
||||
- *dominik2
|
||||
- *nb
|
||||
- path_regex: utils/modules/promtail/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *web-arm
|
||||
- *ldap-server-arm
|
||||
- *ldap-server-test
|
||||
- path_regex: modules/home-assistant/[^/]+\.yaml$
|
||||
- *netboot
|
||||
- *fw
|
||||
- *fw-new
|
||||
- *nas
|
||||
- *amzebs-01
|
||||
- path_regex: utils/modules/victoriametrics/[^/]+\.yaml$
|
||||
key_groups:
|
||||
- age:
|
||||
- *bitwarden
|
||||
- *dominik
|
||||
- *home-assistant-server
|
||||
- *dominik2
|
||||
- *nb
|
||||
- *web-arm
|
||||
- *ldap-server-arm
|
||||
- *netboot
|
||||
- *fw
|
||||
- *fw-new
|
||||
- *nas
|
||||
- *amzebs-01
|
||||
|
||||
31
AGENTS.md
Normal file
31
AGENTS.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `hosts/<host>/configuration.nix` defines each machine; host modules, packages, and site configs live alongside for composability.
|
||||
- Shared building blocks sit in `utils/` (`modules/`, `overlays/`, `pkgs/`, `bento.nix`), while `fleet.nix` centralizes cross-host user provisioning.
|
||||
- Provisioning assets (ISO profiles, Raspberry Pi imaging, helper scripts) live under `iso/`, `raspberry*/`, and `scripts/`—refer to them before reinventing steps.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Enter the dev shell via `nix-shell` (uses `shell.nix`) to populate MCP helper configs and standard tooling.
|
||||
- Dry-run any change with `./scripts/test-configuration <host>`; append `-v` to mirror `nixos-rebuild --show-trace` for deeper diagnostics.
|
||||
- Deployment relies on the Git runner—once reviewed changes merge to main, the runner rebuilds and switches the relevant host automatically; treat a clean dry-run as the gate before pushing.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Format Nix files with two-space indentation; run `nixpkgs-fmt` (via `nix run nixpkgs#nixpkgs-fmt .`) before committing complex edits.
|
||||
- Keep module and derivation names in lower kebab-case (`web-arm`, `home-assistant.nix`) and align attribute names with actual host or service identifiers.
|
||||
- Use comments sparingly to justify non-obvious decisions (open ports, unusual service options) and prefer explicit imports over wildcard includes.
|
||||
|
||||
## Testing Guidelines
|
||||
- Always run `./scripts/test-configuration <host>` before raising a PR; it ensures evaluation succeeds and secrets are present.
|
||||
- For service changes, confirm activation with `nixos-rebuild test` (or `switch`) on a staging machine and capture any notable logs.
|
||||
- Document manual smoke checks (e.g., URLs defined in `hosts/web-arm/sites/`) in the PR so reviewers can repeat them quickly.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Follow the Conventional Commits pattern used in `git log` (`fix:`, `chore:`, `update:`) and scope by host when helpful (`fix(mail):`).
|
||||
- Split refactors, secrets rotations, and package bumps into distinct commits to simplify review and rollback.
|
||||
- PRs should call out affected hosts, link dry-build output (and confirm the runner result after merge), and tag the owners noted in `hosts/<host>/users/*.nix`; attach screenshots for UI-facing updates.
|
||||
|
||||
## Security & Configuration Tips
|
||||
- Configure `config.sh` before provisioning SFTP users so the values consumed by `fleet.nix` stay in sync with the chroot layout.
|
||||
- Store API keys referenced in `shell.nix` (such as the Brave Search token) under `~/.config/mcp-servers/` and keep real secrets out of version control.
|
||||
- Rotate and edit encrypted `hosts/<host>/secrets.yaml` via `nix-shell -p sops --run 'sops hosts/<host>/secrets.yaml'`; commit only the encrypted output.
|
||||
100
CLAUDE.md
Normal file
100
CLAUDE.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
This is a NixOS infrastructure repository managing multiple hosts (servers and personal machines) using a modular Nix configuration approach with SOPS for secrets management and Bento for deployment.
|
||||
|
||||
## Build and Test Commands
|
||||
|
||||
```bash
|
||||
# Enter development shell (sets up MCP configs)
|
||||
nix-shell
|
||||
|
||||
# Test configuration before deployment (required before PRs)
|
||||
./scripts/test-configuration <hostname>
|
||||
./scripts/test-configuration -v <hostname> # with --show-trace
|
||||
|
||||
# Edit encrypted secrets
|
||||
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||
|
||||
# Update secrets keys after adding new age keys
|
||||
./scripts/update-secrets-keys
|
||||
|
||||
# Format Nix files
|
||||
nix run nixpkgs#nixpkgs-fmt .
|
||||
|
||||
# Compute hash for new packages
|
||||
nix hash to-sri --type sha256 $(nix-prefetch-url https://example.com/file.tar.gz)
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Host Structure
|
||||
Each host in `hosts/<hostname>/` contains:
|
||||
- `configuration.nix` - Main entry point importing modules
|
||||
- `hardware-configuration.nix` - Machine-specific hardware config
|
||||
- `secrets.yaml` - SOPS-encrypted secrets
|
||||
- `modules/` - Host-specific service configurations
|
||||
- `fleet.nix` → symlink to root `fleet.nix` (SFTP user provisioning)
|
||||
- `utils/` → symlink to root `utils/` (shared modules)
|
||||
|
||||
Current hosts: `fw` (firewall/router), `nb` (notebook), `web-arm`, `mail`, `amzebs-01`, `nas`
|
||||
|
||||
### Shared Components (`utils/`)
|
||||
- `modules/` - Reusable NixOS modules (nginx, sops, borgbackup, lego, promtail, etc.)
|
||||
- `overlays/` - Nixpkgs overlays
|
||||
- `pkgs/` - Custom package derivations
|
||||
- `bento.nix` - Deployment helper module
|
||||
|
||||
### Secrets Management
|
||||
- SOPS with age encryption; keys defined in `.sops.yaml`
|
||||
- Each host has its own age key derived from SSH host key
|
||||
- Host secrets in `hosts/<hostname>/secrets.yaml`
|
||||
- Shared module secrets in `utils/modules/<module>/secrets.yaml`
|
||||
|
||||
**IMPORTANT: Never modify secrets files directly.** Instead, tell the user which secrets need to be added and where, so they can edit the encrypted files themselves using:
|
||||
```bash
|
||||
nix-shell -p sops --run 'sops hosts/<hostname>/secrets.yaml'
|
||||
```
|
||||
|
||||
### Deployment
|
||||
The Git runner handles deployment automatically when changes merge to main. A successful `./scripts/test-configuration <host>` dry-build is the gate before pushing.
|
||||
|
||||
## Custom Packages
|
||||
|
||||
When creating a new package in `utils/pkgs/`, always include an `update.sh` script to automate version updates. See `utils/pkgs/claude-code/update.sh` for the pattern:
|
||||
|
||||
1. Fetch latest version from upstream (npm, GitHub, etc.)
|
||||
2. Update version string in `default.nix`
|
||||
3. Update source hash using `nix-prefetch-url`
|
||||
4. Update dependency hashes (e.g., `npmDepsHash`) by triggering a build with a fake hash
|
||||
5. Verify the final build succeeds
|
||||
|
||||
Example structure:
|
||||
```
|
||||
utils/pkgs/<package-name>/
|
||||
├── default.nix
|
||||
├── update.sh # Always include this
|
||||
└── (other files like patches, lock files)
|
||||
```
|
||||
|
||||
**IMPORTANT: When modifying a custom package** (patches, version updates, etc.), always test by building the package directly, not just running `test-configuration`. The configuration test only checks that the Nix expression evaluates, but doesn't verify the package actually builds:
|
||||
|
||||
```bash
|
||||
# Build a custom package directly to verify it works
|
||||
nix-build -E 'with import <nixpkgs> { overlays = [ (import ./utils/overlays/packages.nix) ]; config.allowUnfree = true; }; <package-name>'
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
**IMPORTANT: Always run `./scripts/test-configuration <hostname>` after making any changes** to verify the NixOS configuration builds successfully. This is required before committing.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Nix files: two-space indentation, lower kebab-case naming
|
||||
- Commits: Conventional Commits format (`fix:`, `feat:`, `chore:`), scope by host when relevant (`fix(mail):`). Do not add "Generated with Claude Code" or "Co-Authored-By: Claude" footers.
|
||||
- Modules import via explicit paths, not wildcards
|
||||
- Comments explain non-obvious decisions (open ports, unusual service options)
|
||||
- **Never update `system.stateVersion`** - it should remain at the original installation version. To upgrade NixOS, update the `channel` file instead.
|
||||
101
README.md
101
README.md
@@ -2,22 +2,28 @@
|
||||
- install ubuntu 20.04
|
||||
- get age key from SSH
|
||||
```console
|
||||
$ nix-shell -p ssh-to-age --run 'ssh-keyscan example.com | ssh-to-age'
|
||||
curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | PROVIDER=hetznercloud NIX_CHANNEL=nixos-25.05 bash 2>&1 | tee /tmp/infect.log
|
||||
nix-shell -p ssh-to-age --run 'ssh-keyscan install.cloonar.com | ssh-to-age'
|
||||
```
|
||||
- fix secrets files
|
||||
```console
|
||||
$ sops': nix-shell -p sops --run "sops updatekeys -y secrets.yaml"
|
||||
nix-shell -p sops --run "sops updatekeys -y secrets.yaml"
|
||||
```
|
||||
- run install command
|
||||
```console
|
||||
$ ./install.sh example.com
|
||||
./install.sh example.com
|
||||
```
|
||||
|
||||
# 2. Sops command
|
||||
```console
|
||||
nix-shell -p sops --run 'sops hosts/cloonar.com/secrets.yaml'
|
||||
```
|
||||
|
||||
# 2. Web Server specific
|
||||
- change the permissions for /var/www
|
||||
```console
|
||||
$ chown nginx:nginx /var/www
|
||||
$ chmod 755 /var/www
|
||||
chown nginx:nginx /var/www
|
||||
chmod 755 /var/www
|
||||
```
|
||||
|
||||
# 3. Net data
|
||||
@@ -26,3 +32,88 @@ $ chmod 755 /var/www
|
||||
- create /var/lib/netdata/cloud.d/token and write the token in it
|
||||
- run nix-shell -p netdata --run "netdata-claim.sh -id=$(uuidgen)" as root
|
||||
- your node should be registered in Netdata cloud
|
||||
|
||||
# Borg Backup
|
||||
add ssh key to hetzner
|
||||
cat ~/.ssh/id_rsa.pub | ssh -p23 u149513-subx@u149513-subx.your-backup.de install-ssh-key
|
||||
|
||||
# 4. Add new Host
|
||||
```console
|
||||
sftp host@git.cloonar.com:/config/bootstrap.sh ./
|
||||
```
|
||||
|
||||
# 5. Yubikey
|
||||
```console
|
||||
ykman fido access change-pin --new-pin 654321
|
||||
systemd-cryptenroll --fido2-device=auto --fido2-with-client-pin=yes /dev/nvme0n1p2
|
||||
```
|
||||
|
||||
# 6. Wireguard
|
||||
```console
|
||||
wg genkey | (umask 077 && tee privatekey) | wg pubkey > publickey
|
||||
umask 0077; wg genpsk > psk
|
||||
```
|
||||
|
||||
# 7. Hash for new packages
|
||||
```console
|
||||
nix hash to-sri --type sha256 $(nix-prefetch-url https://tar.gz)
|
||||
```
|
||||
|
||||
# 8. Fingerprint Reader Setup (e.g., on Framework Laptop with Goodix reader)
|
||||
|
||||
This section assumes you have configured fingerprint support in your NixOS configuration, for example, by creating and importing a module like `hosts/nb/modules/fingerprint.nix` with the following content:
|
||||
|
||||
```nix
|
||||
# hosts/nb/modules/fingerprint.nix
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
services.fprintd.enable = true;
|
||||
|
||||
security.pam.services.login.fprintAuth = true;
|
||||
security.pam.services.sudo.fprintAuth = true;
|
||||
# Add other services like swaylock if needed
|
||||
# security.pam.services.swaylock.fprintAuth = true;
|
||||
}
|
||||
```
|
||||
|
||||
After rebuilding your NixOS configuration (`sudo nixos-rebuild switch`), you can enroll fingerprints for a user.
|
||||
|
||||
## Enrolling Fingerprints
|
||||
|
||||
To enroll a fingerprint for the current user:
|
||||
```console
|
||||
fprintd-enroll
|
||||
```
|
||||
Or for a specific user (e.g., `dominik`):
|
||||
```console
|
||||
fprintd-enroll dominik
|
||||
```
|
||||
Follow the on-screen prompts to scan your fingerprint multiple times.
|
||||
|
||||
## Verifying Enrollment
|
||||
You can verify enrolled fingerprints:
|
||||
```console
|
||||
fprintd-verify
|
||||
```
|
||||
|
||||
## Listing Enrolled Fingerprints
|
||||
To see which fingers are enrolled for the current user:
|
||||
```console
|
||||
fprintd-list $(whoami)
|
||||
```
|
||||
Or for a specific user:
|
||||
```console
|
||||
fprintd-list dominik
|
||||
```
|
||||
|
||||
## Deleting Fingerprints
|
||||
To delete all fingerprints for the current user:
|
||||
```console
|
||||
fprintd-delete $(whoami)
|
||||
```
|
||||
Or for a specific user:
|
||||
```console
|
||||
fprintd-delete dominik
|
||||
```
|
||||
You can also delete specific fingerprints by their ID if you know it.
|
||||
|
||||
2
buchhaltung.md
Normal file
2
buchhaltung.md
Normal file
@@ -0,0 +1,2 @@
|
||||
Bei EU Rechnungen das Hakerl machen bei "Nicht im Inland steuerbare Leistung (außerhalb EU, z.B. Schweiz)"
|
||||
VXEhGveIHdSj7JKq6zof48vLhKaCo0RJea6DhVqopA8=
|
||||
5
esphome/.gitignore
vendored
Normal file
5
esphome/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Gitignore settings for ESPHome
|
||||
# This is an example and may include too much for your use-case.
|
||||
# You can modify this file to suit your needs.
|
||||
/.esphome/
|
||||
/secrets.yaml
|
||||
9
esphome/README.md
Normal file
9
esphome/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Installation
|
||||
OTA Update Shelly Device to tasmota:
|
||||
https://github.com/arendst/mgos-to-tasmota
|
||||
On gen 3 devices just download zip file from below and upload it over the web interface to shelly:
|
||||
https://github.com/tasmota/mgos32-to-tasmota32/releases
|
||||
In Tasmota make OTA Update to minimal:
|
||||
http://ota.tasmota.com/tasmota/release/tasmota-minimal.bin.gz
|
||||
Make ESPHome Configuration in Dashboard:
|
||||
docker run --rm --network host -e ESPHOME_DASHBOARD_USE_PING=true -v "${PWD}":/config -it ghcr.io/esphome/esphome:latest
|
||||
19
esphome/archive/install.yaml
Normal file
19
esphome/archive/install.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
substitutions:
|
||||
device_name: "install"
|
||||
friendly_name: "Esphome Install"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: Cloonar-Smart
|
||||
password: 0m6sY7Ue3G31
|
||||
80
esphome/bathroom-bulb-1.yaml
Normal file
80
esphome/bathroom-bulb-1.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bathroom-bulb-1"
|
||||
friendly_name: "Bathroom Bulb 1"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 30%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/bathroom-bulb-2.yaml
Normal file
80
esphome/bathroom-bulb-2.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bathroom-bulb-2"
|
||||
friendly_name: "Bathroom Bulb 2"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 30%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
143
esphome/bathroom-switch-1.yaml
Normal file
143
esphome/bathroom-switch-1.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
substitutions:
|
||||
devicename: bathroom-switch-1
|
||||
# Name for the relays
|
||||
channel_1: Light
|
||||
channel_2: Air
|
||||
max_power: "2000.0" # watt
|
||||
max_temp: "80.0" # °C
|
||||
|
||||
esphome:
|
||||
name: ${devicename}
|
||||
platform: ESP8266
|
||||
board: esp01_1m
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
i2c:
|
||||
sda: GPIO12
|
||||
scl: GPIO14
|
||||
|
||||
sensor:
|
||||
- platform: ade7953_i2c
|
||||
voltage:
|
||||
name: ${devicename} voltage
|
||||
current_a:
|
||||
name: ${channel_2} current
|
||||
internal: true
|
||||
current_b:
|
||||
name: ${channel_1} current
|
||||
internal: true
|
||||
active_power_a:
|
||||
name: ${channel_2} power
|
||||
id: power_channel_2
|
||||
filters:
|
||||
- multiply: 1
|
||||
on_value_range:
|
||||
- above: ${max_power}
|
||||
then:
|
||||
- output.turn_off: shelly_25_relay_2
|
||||
- homeassistant.service:
|
||||
service: persistent_notification.create
|
||||
data:
|
||||
title: Message from ${devicename}
|
||||
data_template:
|
||||
message: Switch turned off because power exceeded ${max_power}W
|
||||
active_power_b:
|
||||
name: ${channel_1} power
|
||||
id: power_channel_1
|
||||
filters:
|
||||
- multiply: -1
|
||||
on_value_range:
|
||||
- above: ${max_power}
|
||||
then:
|
||||
- output.turn_off: shelly_25_relay_1
|
||||
- homeassistant.service:
|
||||
service: persistent_notification.create
|
||||
data:
|
||||
title: Message from ${devicename}
|
||||
data_template:
|
||||
message: Switch turned off because power exceeded ${max_power}W
|
||||
update_interval: 30s
|
||||
|
||||
# NTC Temperature
|
||||
- platform: ntc
|
||||
sensor: temp_resistance_reading
|
||||
name: ${devicename} temperature
|
||||
unit_of_measurement: "°C"
|
||||
accuracy_decimals: 1
|
||||
icon: "mdi:thermometer"
|
||||
calibration:
|
||||
b_constant: 3350
|
||||
reference_resistance: 10kOhm
|
||||
reference_temperature: 298.15K
|
||||
on_value_range:
|
||||
- above: ${max_temp}
|
||||
then:
|
||||
- output.turn_off: shelly_25_relay_1
|
||||
- output.turn_off: shelly_25_relay_2
|
||||
- homeassistant.service:
|
||||
service: persistent_notification.create
|
||||
data:
|
||||
title: Message from ${devicename}
|
||||
data_template:
|
||||
message: Switch turned off because temperature exceeded ${max_temp}°C
|
||||
- platform: resistance
|
||||
id: temp_resistance_reading
|
||||
sensor: temp_analog_reading
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 32kOhm
|
||||
- platform: adc
|
||||
id: temp_analog_reading
|
||||
pin: A0
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
number: GPIO0
|
||||
inverted: yes
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
pin: GPIO4
|
||||
id: shelly_25_relay_1
|
||||
- platform: gpio
|
||||
pin: GPIO15
|
||||
id: shelly_25_relay_2
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: "${channel_1}"
|
||||
output: shelly_25_relay_1
|
||||
id: lightid
|
||||
- platform: binary
|
||||
name: "${channel_2}"
|
||||
output: shelly_25_relay_2
|
||||
id: airid
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO13
|
||||
name: "${channel_1} input"
|
||||
internal: true
|
||||
on_state:
|
||||
then:
|
||||
- light.toggle: lightid
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO5
|
||||
name: "${channel_2} input"
|
||||
internal: true
|
||||
on_state:
|
||||
then:
|
||||
- light.toggle: airid
|
||||
|
||||
# Prevent short circuit with "floating" pin!
|
||||
- platform: gpio
|
||||
pin: GPIO16
|
||||
name: "ade7953 IRQ pin"
|
||||
internal: true
|
||||
80
esphome/bedroom-bulb-1.yaml
Normal file
80
esphome/bedroom-bulb-1.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bedroom-bulb-1"
|
||||
friendly_name: "Bedroom Bulb 1"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: true
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/bedroom-bulb-2.yaml
Normal file
80
esphome/bedroom-bulb-2.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bedroom-bulb-2"
|
||||
friendly_name: "Bedroom Bulb 2"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/bedroom-bulb-3.yaml
Normal file
80
esphome/bedroom-bulb-3.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bedroom-bulb-3"
|
||||
friendly_name: "Bedroom Bulb 3"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/bedroom-bulb-4.yaml
Normal file
80
esphome/bedroom-bulb-4.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "bedroom-bulb-4"
|
||||
friendly_name: "Bedroom Bulb 4"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/hallway-bulb-1.yaml
Normal file
80
esphome/hallway-bulb-1.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "hallway-bulb-1"
|
||||
friendly_name: "Hallway Bulb 1"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 30%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
80
esphome/hallway-bulb-2.yaml
Normal file
80
esphome/hallway-bulb-2.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
substitutions:
|
||||
device_name: "hallway-bulb-2"
|
||||
friendly_name: "Hallway Bulb 2"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
priority: 300
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 30%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
light:
|
||||
- platform: rgbw
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
red: pwm_r
|
||||
green: pwm_g
|
||||
blue: pwm_b
|
||||
white: pwm_w
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO13
|
||||
frequency: 1000 Hz
|
||||
id: pwm_r
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO12
|
||||
frequency: 1000 Hz
|
||||
id: pwm_g
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO14
|
||||
frequency: 1000 Hz
|
||||
id: pwm_b
|
||||
|
||||
- platform: esp8266_pwm
|
||||
pin: GPIO5
|
||||
frequency: 1000 Hz
|
||||
id: pwm_w
|
||||
340
esphome/hallway-light-switch.yaml
Normal file
340
esphome/hallway-light-switch.yaml
Normal file
@@ -0,0 +1,340 @@
|
||||
substitutions:
|
||||
# Default name
|
||||
name: "hallway-light-switch"
|
||||
# Default friendly name
|
||||
friendly_name: "Hallway Light Switch"
|
||||
# Allows ESP device to be automatically linked to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc
|
||||
room: "Hallway"
|
||||
# Description as appears in ESPHome & top of webserver page
|
||||
device_description: "Hallway Light Switch"
|
||||
# Project Name
|
||||
project_name: "Athom Technology.Mini Relay V2"
|
||||
# Projection version denotes the release version of the yaml file, allowing checking of deployed vs latest version
|
||||
project_version: "v2.0.4"
|
||||
# Restore the relay (GPO switch) upon reboot to state:
|
||||
light_restore_mode: RESTORE_DEFAULT_OFF
|
||||
# Set the update interval for sensors
|
||||
sensor_update_interval: 10s
|
||||
# Current Limit in Amps.
|
||||
current_limit : "10"
|
||||
# Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs)
|
||||
dns_domain: ".cloonar.smart"
|
||||
# Set timezone of the smart plug. Useful if the plug is in a location different to the HA server. Can be entered in unix Country/Area format (i.e. "Australia/Sydney")
|
||||
timezone: ""
|
||||
# Set the duration between the sntp service polling ntp.org servers for an update
|
||||
sntp_update_interval: 6h
|
||||
# Network time servers for your region, enter from lowest to highest priority. To use local servers update as per zones or countries at: https://www.ntppool.org/zone/@
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
# Enables faster network connections, with last connected SSID being connected to and no full scan for SSID being undertaken
|
||||
wifi_fast_connect: "false"
|
||||
# Define logging level: NONE, ERROR, WARN, INFO, DEBUG (Default), VERBOSE, VERY_VERBOSE
|
||||
log_level: "WARN"
|
||||
# Hide the ENERGY sensor that shows kWh consumed, but with no time period associated with it. Resets when device restarted and reflashed.
|
||||
hide_energy_sensor: "true"
|
||||
# Enable or disable the use of IPv6 networking on the device
|
||||
ipv6_enable: "false"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.5.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
platformio_options:
|
||||
board_build.mcu: esp32c3
|
||||
board_build.variant: esp32c3
|
||||
board_build.flash_mode: dio
|
||||
|
||||
esp32:
|
||||
board: esp32-c3-devkitm-1
|
||||
flash_size: 4MB
|
||||
variant: ESP32C3
|
||||
framework:
|
||||
type: arduino
|
||||
version: recommended
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 5min
|
||||
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
mdns:
|
||||
disabled: false
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
network:
|
||||
enable_ipv6: ${ipv6_enable}
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: ${dns_domain}
|
||||
|
||||
esp32_improv:
|
||||
authorizer: none
|
||||
|
||||
uart:
|
||||
rx_pin: GPIO20
|
||||
baud_rate: 4800
|
||||
data_bits: 8
|
||||
stop_bits: 1
|
||||
parity: EVEN
|
||||
|
||||
globals:
|
||||
- id: total_energy
|
||||
type: float
|
||||
restore_value: yes
|
||||
initial_value: '0.0'
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO3
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Power Button"
|
||||
disabled_by_default: true
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 1s
|
||||
- OFF for at least 0.2s
|
||||
then:
|
||||
- light.toggle: mini_relay
|
||||
- timing:
|
||||
- ON for at least 4s
|
||||
then:
|
||||
- button.press: Reset
|
||||
|
||||
- platform: gpio
|
||||
id: the_switch
|
||||
name: "Power Switch"
|
||||
pin:
|
||||
number: GPIO4
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
on_multi_click:
|
||||
- timing:
|
||||
- ON for at most 1s
|
||||
then:
|
||||
- light.toggle: mini_relay
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: "diagnostic"
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: "diagnostic"
|
||||
device_class: ""
|
||||
|
||||
- platform: cse7766
|
||||
id: athom_cse7766
|
||||
current:
|
||||
name: "Current"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
- lambda: if (x < 0.060) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected
|
||||
on_value_range:
|
||||
- above: ${current_limit}
|
||||
then:
|
||||
- light.turn_off: mini_relay
|
||||
|
||||
voltage:
|
||||
name: "Voltage"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
|
||||
power:
|
||||
name: "Power"
|
||||
id: power_sensor
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
- lambda: if (x < 3.0) return 0.0; else return x; #For the chip will report less than 3w power when no load is connected
|
||||
|
||||
energy:
|
||||
name: "Energy"
|
||||
id: energy
|
||||
unit_of_measurement: kWh
|
||||
filters:
|
||||
- throttle: ${sensor_update_interval}
|
||||
# Multiplication factor from W to kW is 0.001
|
||||
- multiply: 0.001
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
static float previous_energy_value = 0.0;
|
||||
float current_energy_value = id(energy).state;
|
||||
id(total_energy) += current_energy_value - previous_energy_value;
|
||||
previous_energy_value = current_energy_value;
|
||||
id(total_energy_sensor).update();
|
||||
|
||||
apparent_power:
|
||||
name: "Apparent Power"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
reactive_power:
|
||||
name: "Reactive Power"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
power_factor:
|
||||
name: "Power Factor"
|
||||
filters:
|
||||
- throttle_average: ${sensor_update_interval}
|
||||
|
||||
- platform: template
|
||||
name: "Total Energy"
|
||||
id: total_energy_sensor
|
||||
unit_of_measurement: kWh
|
||||
device_class: "energy"
|
||||
state_class: "total_increasing"
|
||||
icon: "mdi:lightning-bolt"
|
||||
accuracy_decimals: 3
|
||||
lambda: |-
|
||||
return id(total_energy);
|
||||
update_interval: ${sensor_update_interval}
|
||||
|
||||
- platform: total_daily_energy
|
||||
name: "Total Energy Today"
|
||||
restore: true
|
||||
power_id: power_sensor
|
||||
unit_of_measurement: kWh
|
||||
accuracy_decimals: 3
|
||||
filters:
|
||||
- multiply: 0.001
|
||||
|
||||
button:
|
||||
- platform: restart
|
||||
name: "Restart"
|
||||
entity_category: config
|
||||
|
||||
- platform: factory_reset
|
||||
name: "Factory Reset"
|
||||
id: Reset
|
||||
entity_category: config
|
||||
|
||||
- platform: safe_mode
|
||||
name: "Safe Mode"
|
||||
internal: false
|
||||
entity_category: config
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: relay_output
|
||||
pin: GPIO6
|
||||
|
||||
light:
|
||||
- platform: status_led
|
||||
id: led
|
||||
name: "Blue LED"
|
||||
disabled_by_default: true
|
||||
pin:
|
||||
number: GPIO7
|
||||
inverted: true
|
||||
|
||||
- platform: binary
|
||||
id: mini_relay
|
||||
output: relay_output
|
||||
name: "Mini Switch"
|
||||
restore_mode: ${light_restore_mode}
|
||||
on_turn_on:
|
||||
- light.turn_on: led
|
||||
on_turn_off:
|
||||
- light.turn_off: led
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
# Creates a sensor showing when the device was last restarted
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
# device_class: timestamp
|
||||
|
||||
# Creates a sensor of the uptime of the device, in formatted days, hours, minutes and seconds
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds = seconds % (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds = seconds % 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds = seconds % 60;
|
||||
if ( days > 3650 ) {
|
||||
return { "Starting up" };
|
||||
} else if ( days ) {
|
||||
return { (String(days) +"d " + String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else if ( hours ) {
|
||||
return { (String(hours) +"h " + String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else if ( minutes ) {
|
||||
return { (String(minutes) +"m "+ String(seconds) +"s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) +"s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
# Define the timezone of the device
|
||||
timezone: "${timezone}"
|
||||
# Change sync interval from default 5min to 6 hours (or as set in substitutions)
|
||||
update_interval: ${sntp_update_interval}
|
||||
# Set specific sntp servers to use
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
# Publish the time the device was last restarted
|
||||
on_time_sync:
|
||||
then:
|
||||
# Update last restart time, but only once.
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
100
esphome/hallway-switch.yaml
Normal file
100
esphome/hallway-switch.yaml
Normal file
@@ -0,0 +1,100 @@
|
||||
substitutions:
|
||||
devicename: hallway-switch
|
||||
max_power: "2000.0" # watt
|
||||
max_temp: "80.0" # °C
|
||||
|
||||
esphome:
|
||||
name: ${devicename}
|
||||
|
||||
#esp32:
|
||||
# board: esp32-c3-devkitm-1
|
||||
# flash_size: 8MB
|
||||
# framework:
|
||||
# type: esp-idf
|
||||
# variant: esp32c3
|
||||
|
||||
esp32:
|
||||
board: esp32-c3-devkitm-1
|
||||
flash_size: 8MB
|
||||
framework:
|
||||
type: esp-idf
|
||||
version: recommended
|
||||
sdkconfig_options:
|
||||
COMPILER_OPTIMIZATION_SIZE: y
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
sensor:
|
||||
- platform: ntc
|
||||
sensor: temp_resistance_reading
|
||||
name: "Temperature"
|
||||
unit_of_measurement: "°C"
|
||||
accuracy_decimals: 1
|
||||
icon: "mdi:thermometer"
|
||||
calibration:
|
||||
b_constant: 3350
|
||||
reference_resistance: 10kOhm
|
||||
reference_temperature: 298.15K
|
||||
on_value_range:
|
||||
- above: ${max_temp}
|
||||
then:
|
||||
- output.turn_off: relay_output
|
||||
- homeassistant.service:
|
||||
service: persistent_notification.create
|
||||
data:
|
||||
title: Message from ${devicename}
|
||||
data_template:
|
||||
message: Switch turned off because temperature exceeded ${max_temp}°C
|
||||
- platform: resistance
|
||||
id: temp_resistance_reading
|
||||
sensor: temp_analog_reading
|
||||
configuration: DOWNSTREAM
|
||||
resistor: 10kOhm
|
||||
- platform: adc
|
||||
id: temp_analog_reading
|
||||
pin: GPIO3
|
||||
attenuation: 12db
|
||||
|
||||
output:
|
||||
- platform: gpio
|
||||
id: "relay_output"
|
||||
pin: 7
|
||||
|
||||
switch:
|
||||
- platform: output
|
||||
id: "relay"
|
||||
name: "Relay"
|
||||
output: "relay_output"
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
name: "Switch"
|
||||
pin: 10
|
||||
on_press:
|
||||
then:
|
||||
- switch.toggle: "relay"
|
||||
filters:
|
||||
- delayed_on_off: 50ms
|
||||
|
||||
- platform: gpio
|
||||
name: "Button"
|
||||
pin:
|
||||
number: 1
|
||||
inverted: yes
|
||||
mode:
|
||||
input: true
|
||||
pullup: true
|
||||
|
||||
status_led:
|
||||
pin:
|
||||
number: 0
|
||||
inverted: true
|
||||
214
esphome/livingroom-bulb-1.yaml
Normal file
214
esphome/livingroom-bulb-1.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-1"
|
||||
friendly_name: "Living Room Bulb 1"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.11
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
214
esphome/livingroom-bulb-2.yaml
Normal file
214
esphome/livingroom-bulb-2.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-2"
|
||||
friendly_name: "Living Room Bulb 2"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.12
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
214
esphome/livingroom-bulb-3.yaml
Normal file
214
esphome/livingroom-bulb-3.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-3"
|
||||
friendly_name: "Living Room Bulb 3"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.13
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
214
esphome/livingroom-bulb-4.yaml
Normal file
214
esphome/livingroom-bulb-4.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-4"
|
||||
friendly_name: "Living Room Bulb 4"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.14
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
214
esphome/livingroom-bulb-5.yaml
Normal file
214
esphome/livingroom-bulb-5.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-5"
|
||||
friendly_name: "Living Room Bulb 5"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.15
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
214
esphome/livingroom-bulb-6.yaml
Normal file
214
esphome/livingroom-bulb-6.yaml
Normal file
@@ -0,0 +1,214 @@
|
||||
substitutions:
|
||||
name: "livingroom-bulb-6"
|
||||
friendly_name: "Living Room Bulb 6"
|
||||
room: "Living Room"
|
||||
device_description: "athom 7w rgbcw light bulb"
|
||||
project_name: "Athom Technology.Athom RGBCW Bulb"
|
||||
dns_domain: ".cloonar.smart"
|
||||
timezone: ""
|
||||
sntp_update_interval: 6h
|
||||
sntp_server_1: "0.pool.ntp.org"
|
||||
sntp_server_2: "1.pool.ntp.org"
|
||||
sntp_server_3: "2.pool.ntp.org"
|
||||
|
||||
esphome:
|
||||
name: "${name}"
|
||||
friendly_name: "${friendly_name}"
|
||||
comment: "${device_description}"
|
||||
area: "${room}"
|
||||
name_add_mac_suffix: false
|
||||
min_version: 2024.6.0
|
||||
project:
|
||||
name: "${project_name}"
|
||||
version: "${project_version}"
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
red: 100%
|
||||
green: 50%
|
||||
blue: 0%
|
||||
white: 100%
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected:
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: rgbww_light
|
||||
brightness: 100%
|
||||
|
||||
esp8266:
|
||||
board: esp8285
|
||||
restore_from_flash: true
|
||||
|
||||
preferences:
|
||||
flash_write_interval: 1min
|
||||
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
# Disable fast_connect so we do a full scan (required for hidden SSIDs)
|
||||
fast_connect: True
|
||||
domain: "${dns_domain}"
|
||||
|
||||
# Your hidden network
|
||||
networks:
|
||||
- ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
channel: 1
|
||||
hidden: true
|
||||
|
||||
manual_ip:
|
||||
static_ip: 10.42.100.16
|
||||
gateway: 10.42.100.1
|
||||
subnet: 255.255.255.0
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
|
||||
# Fallback access point if Wi-Fi fails
|
||||
ap:
|
||||
ssid: "${name}_AP"
|
||||
password: "bulb_fallback_pw"
|
||||
ap_timeout: 2min # after 2 min of failed join, enable AP
|
||||
|
||||
binary_sensor:
|
||||
- platform: status
|
||||
name: "Status"
|
||||
entity_category: diagnostic
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: "Uptime Sensor"
|
||||
id: uptime_sensor
|
||||
entity_category: diagnostic
|
||||
internal: true
|
||||
|
||||
- platform: wifi_signal
|
||||
name: "WiFi Signal dB"
|
||||
id: wifi_signal_db
|
||||
update_interval: 60s
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: copy
|
||||
source_id: wifi_signal_db
|
||||
name: "WiFi Signal Percent"
|
||||
filters:
|
||||
- lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
|
||||
unit_of_measurement: "Signal %"
|
||||
entity_category: diagnostic
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: red_output
|
||||
pin: GPIO4
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: green_output
|
||||
pin: GPIO12
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: blue_output
|
||||
pin: GPIO14
|
||||
min_power: 0.000499
|
||||
max_power: 1
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white_output
|
||||
pin: GPIO13
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
- platform: esp8266_pwm
|
||||
id: white_output
|
||||
pin: GPIO5
|
||||
min_power: 0.000499
|
||||
max_power: 0.9
|
||||
|
||||
light:
|
||||
- platform: rgbww
|
||||
id: rgbww_light
|
||||
name: "RGBCW_Bulb"
|
||||
red: red_output
|
||||
green: green_output
|
||||
blue: blue_output
|
||||
warm_white: warm_white_output
|
||||
cold_white: white_output
|
||||
cold_white_color_temperature: 6000 K
|
||||
warm_white_color_temperature: 3000 K
|
||||
color_interlock: true
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "IP Address"
|
||||
entity_category: diagnostic
|
||||
ssid:
|
||||
name: "Connected SSID"
|
||||
entity_category: diagnostic
|
||||
mac_address:
|
||||
name: "Mac Address"
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: 'Last Restart'
|
||||
id: device_last_restart
|
||||
icon: mdi:clock
|
||||
entity_category: diagnostic
|
||||
|
||||
- platform: template
|
||||
name: "Uptime"
|
||||
entity_category: diagnostic
|
||||
lambda: |-
|
||||
int seconds = (id(uptime_sensor).state);
|
||||
int days = seconds / (24 * 3600);
|
||||
seconds %= (24 * 3600);
|
||||
int hours = seconds / 3600;
|
||||
seconds %= 3600;
|
||||
int minutes = seconds / 60;
|
||||
seconds %= 60;
|
||||
if (days > 3650) {
|
||||
return { "Starting up" };
|
||||
} else if (days) {
|
||||
return { (String(days) + "d " + String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (hours) {
|
||||
return { (String(hours) + "h " + String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else if (minutes) {
|
||||
return { (String(minutes) + "m " + String(seconds) + "s").c_str() };
|
||||
} else {
|
||||
return { (String(seconds) + "s").c_str() };
|
||||
}
|
||||
icon: mdi:clock-start
|
||||
|
||||
time:
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
timezone: "${timezone}"
|
||||
update_interval: ${sntp_update_interval}
|
||||
servers:
|
||||
- "${sntp_server_1}"
|
||||
- "${sntp_server_2}"
|
||||
- "${sntp_server_3}"
|
||||
on_time_sync:
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return id(device_last_restart).state == "";'
|
||||
then:
|
||||
- text_sensor.template.publish:
|
||||
id: device_last_restart
|
||||
state: !lambda 'return id(sntp_time).now().strftime("%a %d %b %Y - %I:%M:%S %p");'
|
||||
33
esphome/presense-bedroom.yaml
Normal file
33
esphome/presense-bedroom.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
esphome:
|
||||
name: presense-bedroom
|
||||
friendly_name: presense-bedroom
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Presense-Bedroom Fallback"
|
||||
password: "jMTo5YkCC01q"
|
||||
|
||||
captive_portal:
|
||||
|
||||
bluetooth_proxy:
|
||||
|
||||
33
esphome/presense-hallway.yaml
Normal file
33
esphome/presense-hallway.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
esphome:
|
||||
name: presense-hallway
|
||||
friendly_name: presense-hallway
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Presense-Hallway Fallback"
|
||||
password: "jMTo5YkCC01q"
|
||||
|
||||
captive_portal:
|
||||
|
||||
bluetooth_proxy:
|
||||
|
||||
33
esphome/presense-kitchen.yaml
Normal file
33
esphome/presense-kitchen.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
esphome:
|
||||
name: presense-kitchen
|
||||
friendly_name: presense-kitchen
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: arduino
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Presense-Kitchen Fallback"
|
||||
password: "jMTo5YkCC01q"
|
||||
|
||||
captive_portal:
|
||||
|
||||
bluetooth_proxy:
|
||||
|
||||
55
esphome/presense-office.yaml
Normal file
55
esphome/presense-office.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
esphome:
|
||||
name: presense-office
|
||||
friendly_name: presense-office
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Presense-Office Fallback Hotspot"
|
||||
password: "jMTo5YkCC01q"
|
||||
|
||||
captive_portal:
|
||||
|
||||
bluetooth_proxy:
|
||||
|
||||
external_components:
|
||||
- source: github://koying/esphome-ble-remote@master
|
||||
components: [ ble_client_hid ]
|
||||
|
||||
ble_client:
|
||||
- id: ble_client_1
|
||||
mac_address: "90:f8:2e:f9:d7:32" # Replace with your remote's MAC address
|
||||
|
||||
ble_client_hid:
|
||||
- id: ble_client_hid_1
|
||||
ble_client_id: ble_client_1
|
||||
|
||||
sensor:
|
||||
- platform: ble_client_hid
|
||||
type: last_event_value
|
||||
name: "Last Event Value"
|
||||
ble_client_hid_id: ble_client_hid_1
|
||||
|
||||
text_sensor:
|
||||
- platform: ble_client_hid
|
||||
name: "Last Event Usage"
|
||||
ble_client_hid_id: ble_client_hid_1
|
||||
68
esphome/toilet-bulb.yaml
Normal file
68
esphome/toilet-bulb.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
substitutions:
|
||||
device_name: "toilet-bulb"
|
||||
friendly_name: "Toilet Bulb"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
comment: ${friendly_name}
|
||||
on_boot:
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 20%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
color_temperature: 2700 K
|
||||
|
||||
esp8266:
|
||||
board: esp01_1m
|
||||
|
||||
interval:
|
||||
- interval: 15s
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
api.connected: # check if api connected
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
brightness: 100%
|
||||
- delay: 100ms
|
||||
- light.turn_on:
|
||||
id: my_light
|
||||
color_temperature: 6500 K
|
||||
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
|
||||
ota:
|
||||
platform: esphome
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_password
|
||||
fast_connect: True
|
||||
domain: .cloonar.smart
|
||||
|
||||
output:
|
||||
- platform: esp8266_pwm
|
||||
id: warm_white
|
||||
pin: GPIO4
|
||||
- platform: esp8266_pwm
|
||||
id: brightness
|
||||
pin: GPIO5
|
||||
|
||||
light:
|
||||
- platform: cwww
|
||||
id: my_light
|
||||
name: ${friendly_name}
|
||||
warm_white: warm_white
|
||||
cold_white: brightness
|
||||
cold_white_color_temperature: 6500 K
|
||||
warm_white_color_temperature: 2700 K
|
||||
42
fleet.nix
42
fleet.nix
@@ -10,15 +10,51 @@
|
||||
isNormalUser = false;
|
||||
isSystemUser = true;
|
||||
group = "sftp_users";
|
||||
openssh.authorizedKeys.keys = [host.key];
|
||||
openssh.authorizedKeys.keys = [
|
||||
host.key
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
];
|
||||
shell = null;
|
||||
};
|
||||
};
|
||||
|
||||
users = [
|
||||
{
|
||||
username = "nb-epicenter";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7";
|
||||
username = "web-arm";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGzJRWe8hsqAVnGSjPrcheloteWMzORoQ5Gj4IfhCROF";
|
||||
}
|
||||
{
|
||||
username = "mail";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCfEuRazRv8zKWJSq+T3SssgOrkBFu6y/t6uoMNrD3P9WHowRDejo2rBsWFgPszhfgxLpWHiuSZFMG8z+07k5fVTdmbUwx0vXI1lmQ7AxB/CPwBef2Vpb7b8Rq6geejvP8X6UjQWP0rsCMtoX2SeBDTG8bDlyq1U3vYxVY4hery6a9Wu57OI5VbSIHhqQvExo7euz8V7ORsLyT8gi9x3r8gNaKJmvssB6QXXZ7U2sJaAUjhV/BmrZJD5qR9EwqwiMPJ2+SkZ0Vz6CFG6GLyB/ngXPEfclLKK7AzookJy7WepqojjFTzmOBMH903oR+MIpjDECKxgaFtW4xY0A/tj8ZDCBPtP8AKjediOASkAi7eUMPseQKDE0BNLSidC0hlQUe0aPaMeA8b1U86PblzpgF8ntkUPbxhO0AgHKq9fPN+f58f75fryNbhgPRRkeLet1q3hxguEMg2MIg/EqIw862YPWPtGRk0wJHwQU7jx+9BbjdptAVTJo/Cj9vM7mpZphE= root@mail";
|
||||
}
|
||||
{
|
||||
username = "nb";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ6g/lXONzSW1JbyXnj+/0QPWtaiNxu9A0GOCbi96603";
|
||||
}
|
||||
{
|
||||
username = "nb-new";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC1dDoAJUY58I+4SSfDAkO5kInsMcJT/r/mW+MYXLQVR";
|
||||
}
|
||||
{
|
||||
username = "fw";
|
||||
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDtxpJAFohRtBaET9e7EE4I6UmeUT/h1ZTD1zeOHFiWB/AT71ooDT4/QukJOA3LqklDjtDQHH+qjGY50Wa8/oGTA/X3aBDPg5GAHN+U+kYO2UTC69VVjh4TTS35ijg+AdgegtMI4c0VIUMZB24tthV9KEbD20w6XnTzy2Q6PjbBrwsOeHYr9pkygJZDU65ZeKmLyR6yLaadHzXX1I7V2SwiakPEebhQaGipm540d+tAbirKCHcmiORkpd++e3dfwi25hC9bCQ7b3bdaFPAmuhhFEid4jpCt79X+l0qqpClgRLziBjYykNJDFKAljFBJA11/3ofPCuaBCDUuJVhAH044gtT3sbvJq1prd8ElZy6L1yc5YbfFgDMwi71Y2hef780NmDs5Opk9xUCKqdl1YfLyUDgdiiaZ8uhUMd2Ai9BAxJAXtcz/V41ngt3YkUVyGTZdTAODIKk44blGIkgs7JO4yam4UB1curbD0faIZnWLyS5pdFQ+FI05YVjoHXJdme8=";
|
||||
}
|
||||
{
|
||||
username = "fw-new";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILnb9todh2b+c3iCmEz72smRwL37aZf3Xs3voT7+PLTP";
|
||||
}
|
||||
{
|
||||
username = "gpd-win4";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILjfS2DtS8PQgkf86dU+EVu5t+r/QlCWmY7+RPYprQrO";
|
||||
}
|
||||
{
|
||||
username = "nas";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICS6b97LPUpr7/kWvOcI40s5e+gfbfz0I2/hAPL6zTmU";
|
||||
}
|
||||
|
||||
{
|
||||
username = "amzebs-01";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINMkFZ60SPl8pzEtGrFq1+n6ZkDuNe3xJaccJMjr3y/q";
|
||||
}
|
||||
];
|
||||
in {
|
||||
|
||||
1
fömi-tool.md
Normal file
1
fömi-tool.md
Normal file
@@ -0,0 +1 @@
|
||||
dialogmail löscht personen die in keiner gruppe sind nach 2 wochen automatisch
|
||||
57
gpd-win-4.md
Normal file
57
gpd-win-4.md
Normal file
@@ -0,0 +1,57 @@
|
||||
I want a wall-mounted docking solution for my GPD Win 4, designed in OpenSCAD 2021.1. Here are the requirements and clarifications:
|
||||
|
||||
Orientation & Fit
|
||||
|
||||
The GPD Win 4 should be inserted upside down (top facing down), with the screen facing the wall.
|
||||
It slides in from the top and is guided by side rails.
|
||||
There should be a small clearance so the GPD Win 4 can be easily inserted/removed without excessive friction.
|
||||
Front Rail (Lip)
|
||||
|
||||
Side Rails:
|
||||
The dock should have two side rails that run from top to bottom, guiding the GPD Win 4.
|
||||
|
||||
The front is open for airflow.
|
||||
However, there should be a small lip (front rail) on each side, running from top to bottom and connected to the side rails. This lip prevents the GPD Win 4 from falling out forward.
|
||||
Back Plate / Wall Mount
|
||||
|
||||
The dock has a solid back plate that mounts to the wall with two countersunk screws.
|
||||
The default spacing and size of these screws can be parameterized (e.g., an M4 or M3 countersunk hole).
|
||||
The back plate thickness should be sufficient for strength (e.g., 3–4 mm).
|
||||
No special side or back vents are needed.
|
||||
Cable Brackets
|
||||
|
||||
At the bottom, back, inside the dock, there are two brackets, one for a 90° USB-C cable (standard USB-C power) and one for a 90° Oculink flat cable.
|
||||
The back plate should be open where these two brackets are, so the cables can exit the dock.
|
||||
Each bracket should have:
|
||||
An opening on the side facing the wall, to allow the cable to pass behind (i.e., into or through the wall).
|
||||
A hole for an M3 screw that presses against the cable from the side to lock it in place.
|
||||
Enough space to seat a 90° connector so it points upwards to plug into the GPD Win 4.
|
||||
Parametric Design
|
||||
|
||||
The design should be fully parameterized in OpenSCAD, including (but not limited to) the following parameters:
|
||||
device_width, device_thickness, device_length (for the GPD Win 4)
|
||||
clearance_x, clearance_y, clearance_z (how much extra space around the device)
|
||||
wall_plate_thickness
|
||||
rail_thickness
|
||||
front_rail_lip_width or front_rail_lip_thickness
|
||||
wall_mount_screw_hole_diameter, wall_mount_screw_spacing (for countersunk screws)
|
||||
bracket_inner_width_usbC, bracket_inner_height_usbC (for the USB-C connector dimensions)
|
||||
bracket_inner_width_oculink, bracket_inner_height_oculink (for the Oculink connector dimensions)
|
||||
m3_side_screw_hole_diameter (the hole that lets an M3 screw clamp the cable from the side)
|
||||
Any other geometry parameters (openings for cables, bracket thickness, etc.)
|
||||
Defaults
|
||||
|
||||
Please choose default dimensions that accurately reflect:
|
||||
Approximate GPD Win 4 size (if not exact, then close estimates).
|
||||
Standard 90° USB-C and 90° Oculink connector sizes.
|
||||
Typical M3 screws for cable clamps.
|
||||
Countersunk holes for M3 or M4 wall screws (whichever you prefer).
|
||||
Version
|
||||
|
||||
This must render successfully in OpenSCAD 2021.1.
|
||||
Summary
|
||||
|
||||
The final output should be an OpenSCAD file that, when the parameters are set to their defaults, produces the described wall-mounted docking station for the GPD Win 4 with side rails, minimal front lip, bracket cutouts for cables, and properly sized holes for screws.
|
||||
|
||||
If any additional measurements or details are needed, please ask.
|
||||
|
||||
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
471
hosts/amzebs-01/EMAIL_SETUP.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# Email Setup for amzebs-01 (amz.at)
|
||||
|
||||
This host is configured to send emails via Laravel with DKIM signing.
|
||||
|
||||
## Configuration Overview
|
||||
|
||||
- **Postfix**: Localhost-only SMTP server (no external access)
|
||||
- **Rspamd**: DKIM signing with host-specific key
|
||||
- **Domain**: amz.at
|
||||
- **DKIM Selector**: amzebs-01
|
||||
- **Secret Management**: DKIM private key stored in sops
|
||||
|
||||
## Initial Setup (Before First Deployment)
|
||||
|
||||
### 1. Generate DKIM Key Pair
|
||||
|
||||
You need to generate a DKIM key pair locally first. You'll need `rspamd` package installed.
|
||||
|
||||
#### Option A: Using rspamd (if installed locally)
|
||||
|
||||
```bash
|
||||
# Create a temporary directory
|
||||
mkdir -p /tmp/dkim-gen
|
||||
|
||||
# Generate the key pair
|
||||
rspamadm dkim_keygen -s amzebs-01 -d amz.at -k /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
This will output:
|
||||
- **Private key** saved to `/tmp/dkim-gen/amz.at.amzebs-01.key`
|
||||
- **Public key** printed to stdout (starts with `v=DKIM1; k=rsa; p=...`)
|
||||
|
||||
#### Option B: Using OpenSSL (alternative)
|
||||
|
||||
```bash
|
||||
# Create temporary directory
|
||||
mkdir -p /tmp/dkim-gen
|
||||
|
||||
# Generate private key (2048-bit RSA)
|
||||
openssl genrsa -out /tmp/dkim-gen/amz.at.amzebs-01.key 2048
|
||||
|
||||
# Extract public key in the correct format for DNS
|
||||
openssl rsa -in /tmp/dkim-gen/amz.at.amzebs-01.key -pubout -outform PEM | \
|
||||
grep -v '^-----' | tr -d '\n' > /tmp/dkim-gen/public.txt
|
||||
|
||||
# Display the DNS record value
|
||||
echo "v=DKIM1; k=rsa; p=$(cat /tmp/dkim-gen/public.txt)"
|
||||
```
|
||||
|
||||
**Save the public key output!** You'll need it for DNS configuration later.
|
||||
|
||||
### 2. Add DKIM Private Key to Sops Secrets
|
||||
|
||||
Now you need to encrypt and add the private key to your secrets file.
|
||||
|
||||
#### Step 1: View the private key
|
||||
|
||||
```bash
|
||||
cat /tmp/dkim-gen/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
#### Step 2: Edit the secrets file
|
||||
|
||||
```bash
|
||||
cd /home/dominik/projects/cloonar/cloonar-nixos/hosts/amzebs-01
|
||||
sops secrets.yaml
|
||||
```
|
||||
|
||||
#### Step 3: Add the key to secrets.yaml
|
||||
|
||||
In the sops editor, add a new key called `rspamd-dkim-key` with the **entire private key content** including the BEGIN/END markers:
|
||||
|
||||
```yaml
|
||||
rspamd-dkim-key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
|
||||
(paste the entire key content here)
|
||||
...
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Make sure to use the pipe `|` character for multiline content
|
||||
- Keep the proper indentation (2 spaces before each line of the key)
|
||||
- Include the full BEGIN/END markers
|
||||
|
||||
#### Step 4: Save and exit
|
||||
|
||||
Save the file in sops (it will be encrypted automatically).
|
||||
|
||||
#### Step 5: Clean up temporary files
|
||||
|
||||
```bash
|
||||
rm -rf /tmp/dkim-gen
|
||||
```
|
||||
|
||||
### 3. Verify Secret is Encrypted
|
||||
|
||||
Check that the secret is properly encrypted:
|
||||
|
||||
```bash
|
||||
cat hosts/amzebs-01/secrets.yaml
|
||||
```
|
||||
|
||||
You should see encrypted content, not the plain private key.
|
||||
|
||||
### 4. Extract Public Key for DNS (if needed later)
|
||||
|
||||
If you didn't save the public key earlier, you can extract it after deployment:
|
||||
|
||||
```bash
|
||||
# On the server after deployment
|
||||
sudo cat /var/lib/rspamd/dkim/amz.at.amzebs-01.key | \
|
||||
openssl rsa -pubout -outform PEM 2>/dev/null | \
|
||||
grep -v '^-----' | tr -d '\n'
|
||||
```
|
||||
|
||||
Then format it as:
|
||||
```
|
||||
v=DKIM1; k=rsa; p=<output_from_above>
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
### 1. Deploy Configuration
|
||||
|
||||
After adding the DKIM private key to sops, deploy the configuration:
|
||||
|
||||
```bash
|
||||
# Build and switch on the remote host
|
||||
nixos-rebuild switch --flake .#amzebs-01 --target-host amzebs-01 --use-remote-sudo
|
||||
```
|
||||
|
||||
Or if deploying locally on the server:
|
||||
|
||||
```bash
|
||||
sudo nixos-rebuild switch
|
||||
```
|
||||
|
||||
### 2. Verify Deployment
|
||||
|
||||
Check that the services are running:
|
||||
|
||||
```bash
|
||||
# Check rspamd-dkim-setup service
|
||||
systemctl status rspamd-dkim-setup
|
||||
|
||||
# Check that rspamd is running
|
||||
systemctl status rspamd
|
||||
|
||||
# Check that postfix is running
|
||||
systemctl status postfix
|
||||
|
||||
# Verify DKIM key was deployed
|
||||
ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key
|
||||
```
|
||||
|
||||
## DNS Configuration
|
||||
|
||||
Add the following DNS records to ensure proper email delivery and avoid spam classification.
|
||||
|
||||
### Critical: PTR Record (Reverse DNS)
|
||||
|
||||
**This is CRITICAL for email deliverability!** Without a proper PTR record, most mail servers will reject or spam your emails.
|
||||
|
||||
#### What is a PTR Record?
|
||||
A PTR (pointer) record is a reverse DNS entry that maps your IP address back to your hostname. Mail servers use this to verify you're a legitimate mail server.
|
||||
|
||||
#### Required PTR Record
|
||||
```
|
||||
IP Address: 23.88.38.1
|
||||
Points to: amzebs-01.amz.at
|
||||
```
|
||||
|
||||
#### How to Configure PTR Record
|
||||
|
||||
**Step 1: Contact Your Hosting Provider**
|
||||
|
||||
PTR records MUST be configured through your hosting provider (e.g., Hetzner, OVH, AWS, etc.). You cannot set PTR records through your domain registrar.
|
||||
|
||||
1. Log into your hosting provider's control panel
|
||||
2. Find the "Reverse DNS" or "PTR Record" section
|
||||
3. Set the PTR record for IP `23.88.38.1` to point to `amzebs-01.amz.at`
|
||||
|
||||
**Common Provider Links:**
|
||||
- **Hetzner**: Robot panel → IPs → Edit reverse DNS
|
||||
- **OVH**: Network → IP → ... → Modify reverse
|
||||
- **AWS EC2**: Select instance → Networking → Request reverse DNS
|
||||
|
||||
**Step 2: Verify Forward DNS First**
|
||||
|
||||
Before setting the PTR record, ensure your forward DNS is correct:
|
||||
|
||||
```bash
|
||||
# This should return 23.88.38.1
|
||||
dig +short amzebs-01.amz.at A
|
||||
host amzebs-01.amz.at
|
||||
```
|
||||
|
||||
**Step 3: Verify PTR Record**
|
||||
|
||||
After configuring, verify the PTR record is working:
|
||||
|
||||
```bash
|
||||
# Method 1: Using dig
|
||||
dig +short -x 23.88.38.1
|
||||
|
||||
# Method 2: Using host
|
||||
host 23.88.38.1
|
||||
|
||||
# Method 3: Using nslookup
|
||||
nslookup 23.88.38.1
|
||||
```
|
||||
|
||||
All commands should return: `amzebs-01.amz.at`
|
||||
|
||||
**Step 4: Verify FCrDNS (Forward-Confirmed Reverse DNS)**
|
||||
|
||||
This ensures forward and reverse DNS match properly:
|
||||
|
||||
```bash
|
||||
# Forward lookup
|
||||
dig +short amzebs-01.amz.at
|
||||
# Should output: 23.88.38.1
|
||||
|
||||
# Reverse lookup
|
||||
dig +short -x 23.88.38.1
|
||||
# Should output: amzebs-01.amz.at.
|
||||
```
|
||||
|
||||
If both work correctly, FCrDNS passes! ✓
|
||||
|
||||
**Why PTR Records Matter:**
|
||||
- Gmail, Microsoft, Yahoo require valid PTR records
|
||||
- Missing PTR = automatic spam classification or rejection
|
||||
- Can add 5-10 points to spam score alone
|
||||
- Required for professional email delivery
|
||||
|
||||
### Domain DNS Records (amz.at)
|
||||
|
||||
Add these records through your domain registrar's DNS management:
|
||||
|
||||
#### SPF Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: @
|
||||
Value: v=spf1 mx a:amzebs-01.amz.at ~all
|
||||
```
|
||||
|
||||
#### DKIM Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: amzebs-01._domainkey
|
||||
Value: [Your public key from step 1 above]
|
||||
```
|
||||
|
||||
The DKIM record will look something like:
|
||||
```
|
||||
v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
|
||||
```
|
||||
|
||||
#### DMARC Record
|
||||
```
|
||||
Type: TXT
|
||||
Name: _dmarc
|
||||
Value: v=DMARC1; p=quarantine; rua=mailto:postmaster@amz.at; ruf=mailto:postmaster@amz.at; fo=1
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `p=quarantine`: Failed messages should be quarantined (you can change to `p=reject` after testing)
|
||||
- `rua=mailto:...`: Aggregate reports sent to this address
|
||||
- `ruf=mailto:...`: Forensic reports sent to this address
|
||||
- `fo=1`: Generate forensic reports for any failure
|
||||
|
||||
## Laravel Configuration
|
||||
|
||||
Update your Laravel application's `.env` file:
|
||||
|
||||
#### Option A: Using sendmail (Recommended)
|
||||
```env
|
||||
MAIL_MAILER=sendmail
|
||||
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
#### Option B: Using SMTP
|
||||
```env
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=25
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS=noreply@amz.at
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
```
|
||||
|
||||
**Note**: Laravel can use ANY email address with @amz.at domain. All will be DKIM signed automatically.
|
||||
|
||||
## Testing Email
|
||||
|
||||
### Test from Command Line
|
||||
|
||||
```bash
|
||||
# Send a test email
|
||||
echo "Test email body" | mail -s "Test Subject" test@example.com -aFrom:test@amz.at
|
||||
```
|
||||
|
||||
### Check Postfix Queue
|
||||
|
||||
```bash
|
||||
# View mail queue
|
||||
mailq
|
||||
|
||||
# View logs
|
||||
journalctl -u postfix -f
|
||||
```
|
||||
|
||||
### Check Rspamd Logs
|
||||
|
||||
```bash
|
||||
# View rspamd logs
|
||||
journalctl -u rspamd -f
|
||||
```
|
||||
|
||||
### Test DKIM Signature and Deliverability
|
||||
|
||||
Send an email to test your complete email configuration:
|
||||
|
||||
#### Email Testing Services
|
||||
1. **Mail Tester** (https://www.mail-tester.com/)
|
||||
- Provides a temporary email address
|
||||
- Shows comprehensive spam score (0-10, higher is better)
|
||||
- Checks DKIM, SPF, DMARC, PTR, blacklists, content
|
||||
- **Target: 9/10 or higher**
|
||||
|
||||
2. **MXToolbox Email Health** (https://mxtoolbox.com/emailhealth/)
|
||||
- Comprehensive deliverability check
|
||||
- Checks DNS records, blacklists, configuration
|
||||
|
||||
3. **Google Admin Toolbox** (https://toolbox.googleapps.com/apps/messageheader/)
|
||||
- Paste email headers to see how Gmail scored your email
|
||||
- Shows SPF, DKIM, DMARC results
|
||||
|
||||
#### What to Check
|
||||
- ✓ DKIM signature is valid
|
||||
- ✓ SPF passes
|
||||
- ✓ DMARC passes
|
||||
- ✓ PTR record (reverse DNS) matches
|
||||
- ✓ Not on any blacklists
|
||||
- ✓ Spam score < 2.0 (lower is better)
|
||||
|
||||
#### Common Issues & Fixes
|
||||
|
||||
**High Spam Score (> 5.0)**
|
||||
- Check: PTR record configured correctly? (Critical!)
|
||||
- Check: HELO name matches hostname?
|
||||
- Check: All headers present (To:, From:, Subject:)?
|
||||
- Check: IP not blacklisted?
|
||||
|
||||
**Missing "To:" Header**
|
||||
Your Laravel app must set a recipient. In your code:
|
||||
```php
|
||||
Mail::to('recipient@example.com')
|
||||
->send(new YourMailable());
|
||||
```
|
||||
|
||||
**HELO/EHLO Mismatch**
|
||||
After applying this configuration, HELO should be `amzebs-01.amz.at`, not `localhost`
|
||||
|
||||
**Check Current HELO Name**
|
||||
```bash
|
||||
# On the server
|
||||
echo "HELO test" | nc localhost 25
|
||||
# Should see: 250 amzebs-01.amz.at
|
||||
```
|
||||
|
||||
## Verification Commands
|
||||
|
||||
```bash
|
||||
# Check if Postfix is running
|
||||
systemctl status postfix
|
||||
|
||||
# Check if Rspamd is running
|
||||
systemctl status rspamd
|
||||
|
||||
# Check if Postfix is listening on localhost only
|
||||
ss -tlnp | grep master
|
||||
|
||||
# View DKIM public key again
|
||||
systemctl start rspamd-show-dkim
|
||||
journalctl -u rspamd-show-dkim
|
||||
|
||||
# Check if DKIM key exists
|
||||
ls -la /var/lib/rspamd/dkim/
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Localhost-only**: Postfix is configured to listen ONLY on 127.0.0.1
|
||||
2. **No authentication**: Not needed since only local processes can connect
|
||||
3. **No firewall changes**: No external ports opened for email
|
||||
4. **DKIM signing**: All outgoing emails are automatically signed with DKIM
|
||||
5. **Host-specific key**: Using selector "amzebs-01" allows multiple hosts to send for amz.at
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Email not being sent
|
||||
|
||||
1. Check Postfix status: `systemctl status postfix`
|
||||
2. Check queue: `mailq`
|
||||
3. Check logs: `journalctl -u postfix -n 100`
|
||||
|
||||
### DKIM not signing
|
||||
|
||||
1. Check Rspamd status: `systemctl status rspamd`
|
||||
2. Check if key exists: `ls -la /var/lib/rspamd/dkim/amz.at.amzebs-01.key`
|
||||
3. Check Rspamd logs: `journalctl -u rspamd -n 100`
|
||||
|
||||
### Permission errors
|
||||
|
||||
```bash
|
||||
# Ensure proper ownership
|
||||
chown -R rspamd:rspamd /var/lib/rspamd/dkim/
|
||||
chmod 600 /var/lib/rspamd/dkim/*.key
|
||||
```
|
||||
|
||||
### Rotate DKIM key
|
||||
|
||||
```bash
|
||||
# 1. Generate new key pair locally (follow "Initial Setup" steps)
|
||||
# 2. Update the rspamd-dkim-key in secrets.yaml with new key
|
||||
# 3. Deploy the configuration
|
||||
nixos-rebuild switch
|
||||
|
||||
# 4. Restart the setup service to copy new key
|
||||
systemctl restart rspamd-dkim-setup
|
||||
|
||||
# 5. Restart rspamd to use new key
|
||||
systemctl restart rspamd
|
||||
|
||||
# 6. Update DNS with new public key
|
||||
# 7. Wait for DNS propagation before removing old DNS record
|
||||
```
|
||||
|
||||
## Related Files
|
||||
|
||||
- Postfix config: `hosts/amzebs-01/modules/postfix.nix`
|
||||
- Rspamd config: `hosts/amzebs-01/modules/rspamd.nix`
|
||||
- Main config: `hosts/amzebs-01/configuration.nix`
|
||||
- Secrets file: `hosts/amzebs-01/secrets.yaml` (encrypted)
|
||||
|
||||
## Sops Secret Configuration
|
||||
|
||||
The DKIM private key is stored as a sops secret with the following configuration:
|
||||
|
||||
```nix
|
||||
sops.secrets.rspamd-dkim-key = {
|
||||
owner = "rspamd";
|
||||
group = "rspamd";
|
||||
mode = "0400";
|
||||
};
|
||||
```
|
||||
|
||||
This ensures:
|
||||
- Only the rspamd user can read the key
|
||||
- The key is decrypted at boot time by sops-nix
|
||||
- The key is encrypted in version control
|
||||
- The key persists across rebuilds
|
||||
|
||||
The key is automatically copied from the sops secret path to `/var/lib/rspamd/dkim/amz.at.amzebs-01.key` by the `rspamd-dkim-setup.service` on every boot.
|
||||
1
hosts/amzebs-01/channel
Normal file
1
hosts/amzebs-01/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-25.11
|
||||
81
hosts/amzebs-01/configuration.nix
Normal file
81
hosts/amzebs-01/configuration.nix
Normal file
@@ -0,0 +1,81 @@
|
||||
{ config, lib, pkgs, ... }: {
|
||||
imports = [
|
||||
./utils/bento.nix
|
||||
./utils/modules/sops.nix
|
||||
./utils/modules/nginx.nix
|
||||
./utils/modules/set-nix-channel.nix
|
||||
|
||||
./modules/mysql.nix
|
||||
./modules/web/stack.nix
|
||||
./modules/laravel-storage.nix
|
||||
./modules/laravel-scheduler.nix
|
||||
./modules/blackbox-exporter.nix
|
||||
./modules/postfix.nix
|
||||
./modules/rspamd.nix
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/promtail
|
||||
./utils/modules/victoriametrics
|
||||
./utils/modules/borgbackup.nix
|
||||
|
||||
./hardware-configuration.nix
|
||||
|
||||
./sites
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
vim
|
||||
screen
|
||||
php82
|
||||
];
|
||||
|
||||
time.timeZone = "Europe/Vienna";
|
||||
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
options = "--delete-older-than 60d";
|
||||
};
|
||||
|
||||
boot.tmp.cleanOnBoot = true;
|
||||
zramSwap.enable = true;
|
||||
|
||||
networking.hostName = "amzebs-01";
|
||||
networking.domain = "cloonar.com";
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFshMhXwS0FQFPlITipshvNKrV8sA52ZFlnaoHd1thKg"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
programs.ssh = {
|
||||
knownHosts = {
|
||||
"git.cloonar.com" = {
|
||||
publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDlUj7eEfS/4+z/3IhFhOTXAfpGEpNv6UWuYSL5OAhus";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# backups - adjust repo for this host
|
||||
borgbackup.repo = "u149513-sub10@u149513-sub10.your-backup.de:borg";
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt (not DNS)
|
||||
security.acme.acceptTerms = true;
|
||||
security.acme.defaults.email = "admin+acme@cloonar.com";
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 22 80 443 3306 ];
|
||||
|
||||
# Allow MariaDB access only from specific IP
|
||||
extraCommands = ''
|
||||
iptables -A nixos-fw -p tcp --dport 3306 -s 77.119.230.30 -j nixos-fw-accept
|
||||
'';
|
||||
};
|
||||
|
||||
system.stateVersion = "25.11";
|
||||
}
|
||||
27
hosts/amzebs-01/hardware-configuration.nix
Normal file
27
hosts/amzebs-01/hardware-configuration.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
# Hardware configuration for amzebs-01
|
||||
# This is a template - update with actual hardware configuration after installation
|
||||
{ modulesPath, ... }:
|
||||
{
|
||||
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
|
||||
|
||||
boot.loader.grub = {
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
device = "nodev";
|
||||
configurationLimit = 2;
|
||||
};
|
||||
|
||||
# Update these with actual device UUIDs and paths after installation
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/sda15";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/sda1";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ];
|
||||
boot.initrd.kernelModules = [ "nvme" ];
|
||||
}
|
||||
83
hosts/amzebs-01/modules/blackbox-exporter.nix
Normal file
83
hosts/amzebs-01/modules/blackbox-exporter.nix
Normal file
@@ -0,0 +1,83 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
hostname = config.networking.hostName;
|
||||
|
||||
cfg = config.services.blackbox-exporter;
|
||||
nginxVHosts = config.services.nginx.virtualHosts or {};
|
||||
allDomains = lib.attrNames nginxVHosts;
|
||||
filteredDomains = builtins.filter (d: !builtins.elem d cfg.blacklistDomains) allDomains;
|
||||
httpsDomains = lib.map (d: "https://${d}") filteredDomains;
|
||||
domainsString = builtins.concatStringsSep "\n "
|
||||
(map (d: "\"${d}\",") httpsDomains);
|
||||
in {
|
||||
options.services.blackbox-exporter.blacklistDomains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List of domains to exclude from Blackbox Exporter monitoring";
|
||||
};
|
||||
|
||||
config = {
|
||||
services.blackbox-exporter = {
|
||||
blacklistDomains = [
|
||||
# Currently no domains blacklisted - monitoring all nginx virtualHosts
|
||||
];
|
||||
};
|
||||
|
||||
# Systemd service for Blackbox Exporter
|
||||
systemd.services.blackbox-exporter = {
|
||||
description = "Blackbox Exporter";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = ''
|
||||
${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
|
||||
--config.file=/etc/blackbox_exporter/blackbox.yml
|
||||
'';
|
||||
};
|
||||
|
||||
# Configuration file for Blackbox Exporter
|
||||
environment.etc."blackbox_exporter/blackbox.yml".text = ''
|
||||
modules:
|
||||
http_200_final:
|
||||
prober: http
|
||||
http:
|
||||
method: GET
|
||||
follow_redirects: true
|
||||
preferred_ip_protocol: "ip4" # avoid blanket IPv6 failures
|
||||
valid_http_versions: ["HTTP/1.1", "HTTP/2.0"]
|
||||
valid_status_codes: [200]
|
||||
'';
|
||||
|
||||
# Add scrape config for VictoriaMetrics agent
|
||||
services.victoriametrics.extraScrapeConfigs = [
|
||||
''
|
||||
- job_name: "blackbox_http_all_domains"
|
||||
metrics_path: "/probe"
|
||||
params:
|
||||
module: ["http_200_final"]
|
||||
|
||||
static_configs:
|
||||
- targets:
|
||||
[
|
||||
${domainsString}
|
||||
]
|
||||
|
||||
relabel_configs:
|
||||
- source_labels: ["__address__"]
|
||||
target_label: "__param_target"
|
||||
regex: '(.*)'
|
||||
replacement: "$1"
|
||||
- source_labels: ["__param_target"]
|
||||
target_label: "instance"
|
||||
- target_label: "__address__"
|
||||
replacement: "127.0.0.1:9115"
|
||||
- source_labels: ["__address__"]
|
||||
regex: "127\\.0\\.0\\.1:9115"
|
||||
target_label: "__scheme__"
|
||||
replacement: "http"
|
||||
''
|
||||
];
|
||||
};
|
||||
}
|
||||
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
51
hosts/amzebs-01/modules/laravel-scheduler.nix
Normal file
@@ -0,0 +1,51 @@
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
# Daily scheduled Laravel artisan jobs
|
||||
# Runs artisan finish:reports at 01:00 for production and staging APIs
|
||||
|
||||
let
|
||||
php = pkgs.php82;
|
||||
|
||||
sites = [
|
||||
{
|
||||
domain = "api.ebs.amz.at";
|
||||
user = "api_ebs_amz_at";
|
||||
}
|
||||
{
|
||||
domain = "api.stage.ebs.amz.at";
|
||||
user = "api_stage_ebs_amz_at";
|
||||
}
|
||||
];
|
||||
|
||||
mkArtisanService = site: {
|
||||
name = "artisan-finish-reports-${site.domain}";
|
||||
value = {
|
||||
description = "Laravel artisan finish:reports for ${site.domain}";
|
||||
after = [ "network.target" "mysql.service" "phpfpm-${site.domain}.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = site.user;
|
||||
Group = "nginx";
|
||||
WorkingDirectory = "/var/www/${site.domain}";
|
||||
ExecStart = "${php}/bin/php artisan finish:reports";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mkArtisanTimer = site: {
|
||||
name = "artisan-finish-reports-${site.domain}";
|
||||
value = {
|
||||
description = "Daily timer for artisan finish:reports on ${site.domain}";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 01:00:00";
|
||||
Persistent = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
systemd.services = builtins.listToAttrs (map mkArtisanService sites);
|
||||
systemd.timers = builtins.listToAttrs (map mkArtisanTimer sites);
|
||||
}
|
||||
30
hosts/amzebs-01/modules/laravel-storage.nix
Normal file
30
hosts/amzebs-01/modules/laravel-storage.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Create Laravel storage directories for all API instances
|
||||
# These directories are required for Laravel to function properly
|
||||
systemd.tmpfiles.rules = [
|
||||
# api.ebs.cloonar.dev
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/cache 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/testing 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/sessions 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/framework/views 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/storage/logs 0775 api_ebs_cloonar_dev nginx -"
|
||||
"d /var/www/api.ebs.cloonar.dev/bootstrap/cache 0775 api_ebs_cloonar_dev nginx -"
|
||||
|
||||
# api.ebs.amz.at
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/cache 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/testing 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/sessions 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/framework/views 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/storage/logs 0775 api_ebs_amz_at nginx -"
|
||||
"d /var/www/api.ebs.amz.at/bootstrap/cache 0775 api_ebs_amz_at nginx -"
|
||||
|
||||
# api.stage.ebs.amz.at
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/cache 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/testing 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/sessions 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/framework/views 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/storage/logs 0775 api_stage_ebs_amz_at nginx -"
|
||||
"d /var/www/api.stage.ebs.amz.at/bootstrap/cache 0775 api_stage_ebs_amz_at nginx -"
|
||||
];
|
||||
}
|
||||
43
hosts/amzebs-01/modules/mysql.nix
Normal file
43
hosts/amzebs-01/modules/mysql.nix
Normal file
@@ -0,0 +1,43 @@
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
settings = {
|
||||
mysqld = {
|
||||
max_allowed_packet = "64M";
|
||||
transaction_isolation = "READ-COMMITTED";
|
||||
binlog_format = "ROW";
|
||||
# Allow remote connections
|
||||
bind-address = "0.0.0.0";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# Create read-only user for remote access after MySQL starts
|
||||
systemd.services.mysql-setup-readonly-user = {
|
||||
description = "Setup MySQL read-only user";
|
||||
after = [ "mysql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = "root";
|
||||
};
|
||||
script = ''
|
||||
PASSWORD=$(cat ${config.sops.secrets.mysql-readonly-password.path})
|
||||
${pkgs.mariadb}/bin/mysql -u root <<EOF
|
||||
CREATE USER IF NOT EXISTS 'api_ebs_amz_at_ro'@'%' IDENTIFIED BY '$PASSWORD';
|
||||
GRANT SELECT ON api_ebs_amz_at.* TO 'api_ebs_amz_at_ro'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
EOF
|
||||
'';
|
||||
};
|
||||
|
||||
services.mysqlBackup.enable = true;
|
||||
|
||||
sops.secrets.mysql-readonly-password = {
|
||||
owner = "mysql";
|
||||
};
|
||||
}
|
||||
56
hosts/amzebs-01/modules/postfix.nix
Normal file
56
hosts/amzebs-01/modules/postfix.nix
Normal file
@@ -0,0 +1,56 @@
|
||||
{ pkgs
|
||||
, lib
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
headerChecksFile = pkgs.writeText "header_checks" ''
|
||||
# Warn about missing critical headers (but don't reject from localhost)
|
||||
# These help identify misconfigured applications
|
||||
/^$/ WARN Missing headers detected
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.postfix = {
|
||||
mapFiles."header_checks" = headerChecksFile;
|
||||
enable = true;
|
||||
hostname = "amzebs-01.amz.at";
|
||||
domain = "amz.at";
|
||||
|
||||
config = {
|
||||
# Explicitly set hostname to prevent "localhost" HELO issues
|
||||
myhostname = "amzebs-01.amz.at";
|
||||
|
||||
# Set proper HELO name for outgoing SMTP connections
|
||||
smtp_helo_name = "amzebs-01.amz.at";
|
||||
|
||||
# Professional SMTP banner (prevents appearing as default/misconfigured)
|
||||
smtpd_banner = "$myhostname ESMTP";
|
||||
|
||||
# Listen only on localhost for security
|
||||
# Laravel will send via localhost, no external access needed
|
||||
inet_interfaces = "loopback-only";
|
||||
|
||||
# Compatibility
|
||||
compatibility_level = "2";
|
||||
|
||||
# Only accept mail from localhost
|
||||
mynetworks = [ "127.0.0.0/8" "[::1]/128" ];
|
||||
|
||||
# Larger message size limits for attachments
|
||||
mailbox_size_limit = 202400000; # ~200MB
|
||||
message_size_limit = 51200000; # ~50MB
|
||||
|
||||
# Ensure proper header handling
|
||||
# Reject mail that's missing critical headers
|
||||
header_checks = "regexp:/var/lib/postfix/conf/header_checks";
|
||||
|
||||
# Rate limiting to prevent spam-like behavior
|
||||
# Allow reasonable sending rates for applications
|
||||
smtpd_client_message_rate_limit = 100;
|
||||
smtpd_client_recipient_rate_limit = 200;
|
||||
|
||||
# Milter configuration is handled automatically by rspamd.postfix.enable
|
||||
};
|
||||
};
|
||||
}
|
||||
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
84
hosts/amzebs-01/modules/rspamd.nix
Normal file
@@ -0,0 +1,84 @@
|
||||
{ pkgs
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
domain = "amz.at";
|
||||
selector = "amzebs-01";
|
||||
|
||||
localConfig = pkgs.writeText "local.conf" ''
|
||||
logging {
|
||||
level = "notice";
|
||||
}
|
||||
|
||||
# DKIM signing configuration with host-specific selector
|
||||
dkim_signing {
|
||||
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||
selector = "${selector}";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
|
||||
# ARC signing (Authenticated Received Chain)
|
||||
arc {
|
||||
path = "/var/lib/rspamd/dkim/${domain}.${selector}.key";
|
||||
selector = "${selector}";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
|
||||
# Add authentication results to headers
|
||||
milter_headers {
|
||||
use = ["authentication-results"];
|
||||
authenticated_headers = ["authentication-results"];
|
||||
}
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
.include(priority=1,duplicate=merge) "${localConfig}"
|
||||
'';
|
||||
|
||||
# Enable Postfix milter integration
|
||||
postfix.enable = true;
|
||||
};
|
||||
|
||||
# Copy DKIM key from sops secret to rspamd directory
|
||||
systemd.services.rspamd-dkim-setup = {
|
||||
description = "Setup DKIM key from sops secret for ${domain}";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "rspamd.service" ];
|
||||
after = [ "sops-nix.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
DKIM_DIR="/var/lib/rspamd/dkim"
|
||||
DKIM_KEY="$DKIM_DIR/${domain}.${selector}.key"
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
mkdir -p "$DKIM_DIR"
|
||||
|
||||
# Copy key from sops secret
|
||||
if [ -f "${config.sops.secrets.rspamd-dkim-key.path}" ]; then
|
||||
cp "${config.sops.secrets.rspamd-dkim-key.path}" "$DKIM_KEY"
|
||||
chown rspamd:rspamd "$DKIM_KEY"
|
||||
chmod 600 "$DKIM_KEY"
|
||||
echo "DKIM key deployed successfully from sops secret"
|
||||
else
|
||||
echo "ERROR: DKIM key not found in sops secrets!"
|
||||
echo "Please ensure rspamd-dkim-key is defined in secrets.yaml"
|
||||
exit 1
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
sops.secrets.rspamd-dkim-key = {
|
||||
owner = "rspamd";
|
||||
group = "rspamd";
|
||||
mode = "0400";
|
||||
};
|
||||
}
|
||||
@@ -43,8 +43,17 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
phpOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
"Options appended to the PHP configuration file {file}`php.ini` used for this PHP-FPM pool."
|
||||
'';
|
||||
};
|
||||
|
||||
enableMysql = mkEnableOption (lib.mdDoc "MySQL Database");
|
||||
enableDefaultLocations = mkEnableOption (lib.mdDoc "Create default nginx location directives") // { default = true; };
|
||||
enablePhp = mkEnableOption (lib.mdDoc "PHP-FPM support") // { default = true; };
|
||||
|
||||
authorizedKeys = mkOption {
|
||||
type = types.listOf types.str;
|
||||
@@ -130,12 +139,12 @@ in
|
||||
BindPaths = "BindPaths=/var/www/${domain}:/var/www/${domain}";
|
||||
};
|
||||
}
|
||||
) cfg.instances;
|
||||
) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances);
|
||||
|
||||
services.phpfpm.pools = mapAttrs' (instance: instanceOpts:
|
||||
let
|
||||
domain = if instanceOpts.domain != null then instanceOpts.domain else instance;
|
||||
user = if instanceOpts.user != null
|
||||
user = if instanceOpts.user != null
|
||||
then instanceOps.user
|
||||
else builtins.replaceStrings ["." "-"] ["_" "_"] domain;
|
||||
in
|
||||
@@ -154,10 +163,11 @@ in
|
||||
"php_admin_value[max_input_vars]" = 1500;
|
||||
"access.log" = "/var/log/$pool.access.log";
|
||||
};
|
||||
phpOptions = instanceOpts.phpOptions;
|
||||
phpPackage = instanceOpts.phpPackage;
|
||||
phpEnv."PATH" = pkgs.lib.makeBinPath [ instanceOpts.phpPackage ];
|
||||
}
|
||||
) cfg.instances;
|
||||
) (lib.filterAttrs (name: opts: opts.enablePhp) cfg.instances);
|
||||
|
||||
};
|
||||
|
||||
@@ -207,7 +217,7 @@ in
|
||||
'';
|
||||
|
||||
# Cache Media: images, icons, video, audio, HTC
|
||||
"~* \\.(?:jpg|jpeg|gif|png|webp|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
"~* \\.(?:css|js|jpg|jpeg|gif|png|webp|avif|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
@@ -219,19 +229,12 @@ in
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Cache CSS, Javascript, Images, Icons, Video, Audio, HTC, Fonts
|
||||
"~* \\.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff2)$".extraConfig = ''
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
"/".extraConfig = ''
|
||||
index index.php index.html;
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
})
|
||||
{
|
||||
(mkIf instanceOpts.enablePhp {
|
||||
"~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||
if (!-f $document_root$fastcgi_script_name) {
|
||||
@@ -247,7 +250,7 @@ in
|
||||
fastcgi_pass unix:${config.services.phpfpm.pools."${domain}".socket};
|
||||
fastcgi_index index.php;
|
||||
'';
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
extraConfig = instanceOpts.extraConfig;
|
||||
@@ -316,4 +319,3 @@ config.users.groups = mapAttrs' (instance: instanceOpts:
|
||||
mkIf instanceOpts.enableMysql user
|
||||
) cfg.instances;
|
||||
}
|
||||
|
||||
46
hosts/amzebs-01/secrets.yaml
Normal file
46
hosts/amzebs-01/secrets.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
borg-passphrase: ENC[AES256_GCM,data:Q2GvEat5EHmshFiya3yNqFTVS+oJv0al+bYMRwysb0yu7F2gCJd000Y3ibA+tUPSL9iSlMSy0cTkesGVEGBt9w==,iv:/kUJXgibF1cyaCPB55/0nKYq9sSva6psxu2P/l7iRN4=,tag:velr9LTfoj7gEWhUmvPtQg==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data:0YEvv7QDmGsur0PFMmz5HqDgDCEk0kRaOu1n7GGWAwmmr0K0bVbpKzHw5wMiMnXEBrvj0izo4P4LYGAlAAYn22Bhgi2eN/vdvSO5V5uDV1ep2dV/TN2m3oYgTIgot47YgwBhcNunIUtEsbZuhAsTGL6LFBPJ3OCLKvhXTNTDaajgH/e4CvyxHHl63MBzr0i1ajigl1IKCk2hhZF4Kd1YGBCVZRoNyyNXywihlcFeskNfldW/sd5Qn2nowVf1MEV9n6Il6Zc1FX69WUVy1k+kOT7HJZGq3uDmgwXQgQhqKm1wh5uOlLkGUX6fz/nz+YFzLFMuUVvs34CzbbEFuWmGU+aNQrfCfI1hqwB5s6wVNdpUmigX9AQMQklu85tHFJg1AaRvhA24Cp/GrptggrTThcjwVFoe9NSQouNYn+ImTvlsE4HuDRRFE6YUounGd2lpRd40LsEjwKiLtwBwqG94u4ZOI91+LG6ZqHftRehE9r/CtedLyqtluNyyQyUNKPraUOm9Rrapewsj0ZCZgGQU,iv:xdRUBQlZlwVIog5KgZRmGNxdmhFE9HgnK3Ahfo+zT9k=,tag:McsJKUEGnKXxiv8Tg5zA4A==,type:str]
|
||||
mysql-readonly-password: ENC[AES256_GCM,data:k2RplkUZPGZlh29KXXdtwe+MCqKzTI/bLdyuEeicdkbGlBk1SGyLF8vW4t8=,iv:a14IrXYVCDqPKGfJSEPP8g19sPvRTx5NT8IVJJeL48s=,tag:GWgU3oa/+u21/L2y3+vOsw==,type:str]
|
||||
rspamd-dkim-key: ENC[AES256_GCM,data:maOnsx8AQUIjXqHEzHLxtSvAkr9+YCZid9xWaflkffS0gHd/hoHrozHy+rHSjU7Mz7QHYhjUjFY7Hp7wdKQnHpQLJRV96iNPXTXXYtBr7oDL51cq8ozd094FuMeLNSPitV89OHDcM+9h1F4dsdDWPUiw7eoijQeZ8vx1/VCVAp4FVxTFX3qhoMhXlFabiyM85eKMwJG4BdSwqS624f2Z4tvECRp0pBGtd/3r4/EVRDV1qNsiFvH8mi8eyg9xiWDLDrePq4TuWSu1Xc7z0qpDy0o8iAwGhPu9egIyzHEPk07j9U7PpK56C2UCSY0JBm0hkBGbqLXyRklSMytxoKgw4GJykMwNPNXmA9yuLPanxagJB/z8b7X4HTuYhExzQcC6ke/y8xKcxU4qGt8Ayy5v+QoNpdqXIPsZkIuw9uWm6RIgDt2dCaOdI06lesZKjqU/T6EhDfGoGZX7DwQ7uV9xNDM4NW2jsKpUdFKnzPCCe7/jO/ck4P4i8V+6NWDjj4+/BXDNnKJbMcHIHoSvckCGZiginJsbGvSWd0HfbpR7GQAnL3uKB5/HuFAaUkx+dPHmmP2tOBv6vNt+tq+V9i4kQmwAdl8a9KI456tw9vLwXcBDZOO7n4X5H0jc4afoYCnvLxahvbIXm2QNcBYVKxkqBCvoYEMrBjrnujwbQdEfDKQf3g5p8LQwAfCQ3ng+XH/BDF3qMBdsN1u5Di0FQpCDaGKX8pJ1gg+il76fJgSU8ftoaT32hJnLAjal4cgNIxbta2UYQLixUqaWZ8xqvxrSopkWYrlBBUyQh9jMEoTzpxwCsEPQ72qgVcQfJYlMl4WUBwcasfJnySR+qZ22g3fhStpAQ2HuTGhLjTG1QOewYdwDXDhNmcbqZ478Sp1t7qbBx0R7vWFSyYCMlbmLmvzPm6Z3ET7lkfCrMjMNXaQ8cWSF19QaHAfqRwQooLL93yx7U0KHCilEg4bUjsw8MLQNa4A0ohpq6CG4s5O1+di7W4/h71/moggIebFb2eGLJ8BvbkwiVozXI9L77IGd9RswlEjZed18u7fqetS7dyDthhVG2pvya4zZI/cxIq6oJkNr2RIt3NgYChOh0I/17DuSJJ1jAmPB0Evj8QtCCo49ENnyO5cGWn12DZWybwYkg2jQC4aDFA/u5ajTo3wOKdwHj5hgMz/z05Bn2vAdhCGl6uWWNzcNnDiu3/rjqsjOkfkp0hCP7Q==,iv:FORxJ8htcoLIEJihUN7im3dN4jhnigB70InTohtpWwU=,tag:e2DHBd2dn3piCkEdkbHdoA==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- recipient: age14grjcxaq4h55yfnjxvnqhtswxhj9sfdcvyas4lwvpa8py27pjy2sv3g6v7
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhQUpWNUgxVnhuTXd2TkF0
|
||||
SVVHemFKRWlYczZ0TnBESVNRczhuRUNnUG1BCmJKQ2JZbHhFcXJidHJzci9OaFBm
|
||||
ZTd0MGhsaVBic3dMb3psUHRCRnR3ODQKLS0tIERrSG1GVTRHdkJpVWpqdTZ4Yytq
|
||||
OHhlZjV6MjRVbXFsWjlQSU03ZDNwYm8KAswHRSdV0BW/oJyZx63iZRHsF7SZ6PO+
|
||||
hajQqmEyfcVfEu39zZzxQ2mtWlOr69I++irOhE3NeiFeJ1yIRQDJEQ==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1exny8unxynaw03yu8ppahu5z28uermghr8ag34e7kdqnaduq9stsyettzz
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBUaUNnY0hpdDAzMTNIUS9D
|
||||
RmdKbmplUk9DRXlLRXEvSnVjT05sQjcvTnpJCkd6bGRINm5yYUZOUTVzWEdjRmtG
|
||||
Mmx0ci93N2wvTWV5MzlRVnlYdUxoUWsKLS0tIEVHUlNWYStWTG01RzRrVnNXc3BW
|
||||
VkRkUXROU3plNmwvTUVhYmhCS2syQkEKKgC0EmUu1u2vZ/SZTnam+h846gZSyY4V
|
||||
JyMzkws8O5TY9juWdDzXJIU67mIgc4qrWWN3uh8k28JBZGc078b5bg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoK2JUNVdYTzkvM1BBWXRm
|
||||
citCNlE4Z1NLdEZ2R0tNZTVSMlFSeGxGOURnClJnYURYa0JZaVprQWdBcmVnOWVj
|
||||
TGVCK1JWMVlueHJUaTZZYmROM0E5aDAKLS0tIEJxYkdadGtZM250d2d6Ujl2UU9C
|
||||
YUpkVll2S2RpT0I1UVZiZFRKS1prMEEKp/bGImanJ/58vTQG/gUun/Y2QdmOEi3h
|
||||
hVS0V2QcfuGgi0/YofLOM3+M6k6ViXw07XfXmR+puvLIHKr2y11x1Q==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1xcgc6u7fmc2trgxtdtf5nhrd7axzweuxlg0ya9jre3sdrg6c6easecue9w
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDSGdEZnZEaDRpWUJVcnds
|
||||
VGFSQklvczBZdEdEbXhodW8vME9wMUpVRENjClFZcnVqYkJxdlBiZFhma0tmZjgz
|
||||
YXlIdlRDTDU4MHg1dzhGVDRJb2FGYVUKLS0tIDBXSWZ2NkxzdEk0ZlFRM00ybFNy
|
||||
M0doaWl5R2cwU2RxQm5DbWxXeTZ5S2MKwrB3SysmgzCThQOhEVx18dxIfko0+oZY
|
||||
9BSZOoFbfuwiLbtpL4J8bzxDvxn6sXxB8EBJH1hbpID53AquWDsxSw==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2025-11-19T11:16:25Z"
|
||||
mac: ENC[AES256_GCM,data:x4yor9G+QirceSYSX1K9GdfyGellT4JCkE09Tl9/mOX8HMOKFAQGknuwwU6SNGg+ciBFk4TdjQnDmVai4T8JQo9W/DLiZ+GKnWO3s+ZLDX30sEF0aMjKa43R5CCPO/Fl2XH96TaPC+8itTJQ6TpBSg51QLPcpqrMljiBNWvEoTU=,iv:Zi9rglAwgsejUmIpLN/1QlL80BSp3HP32k1xkWt2b+o=,tag:2ADk8d2G4OezkQjcV3CZuA==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.11.0
|
||||
37
hosts/amzebs-01/sites/api.ebs.amz.at.nix
Normal file
37
hosts/amzebs-01/sites/api.ebs.amz.at.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.ebs.amz.at" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBTsA1z6/vOshSqmEUGO6vFbAYCrucgNORMKyoQ5/9/l"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
error_page 404 /index.php;
|
||||
'';
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
locations."/robots.txt".extraConfig = ''
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
'';
|
||||
|
||||
locations."/".extraConfig = ''
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
37
hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix
Normal file
37
hosts/amzebs-01/sites/api.ebs.cloonar.dev.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.ebs.cloonar.dev" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqpF703JmLTBpBjTSvC0bnYu+lSYdmaGPHxMnHEbMmp"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
error_page 404 /index.php;
|
||||
'';
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
locations."/robots.txt".extraConfig = ''
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
'';
|
||||
|
||||
locations."/".extraConfig = ''
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.ebs.cloonar.dev".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
37
hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix
Normal file
37
hosts/amzebs-01/sites/api.stage.ebs.amz.at.nix
Normal file
@@ -0,0 +1,37 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
{
|
||||
services.webstack.instances."api.stage.ebs.amz.at" = {
|
||||
enableDefaultLocations = false;
|
||||
enableMysql = true;
|
||||
authorizedKeys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIqpF703JmLTBpBjTSvC0bnYu+lSYdmaGPHxMnHEbMmp"
|
||||
];
|
||||
extraConfig = ''
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header X-Content-Type-Options "nosniff";
|
||||
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
error_page 404 /index.php;
|
||||
'';
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
locations."/robots.txt".extraConfig = ''
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
'';
|
||||
|
||||
locations."/".extraConfig = ''
|
||||
try_files $uri $uri/ /index.php$is_args$args;
|
||||
'';
|
||||
phpPackage = pkgs.php82.withExtensions ({ enabled, all }:
|
||||
enabled ++ [ all.imagick ]);
|
||||
};
|
||||
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
services.nginx.virtualHosts."api.stage.ebs.amz.at".acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
}
|
||||
14
hosts/amzebs-01/sites/default.nix
Normal file
14
hosts/amzebs-01/sites/default.nix
Normal file
@@ -0,0 +1,14 @@
|
||||
{ ... }: {
|
||||
imports = [
|
||||
# Enabled vhosts (cloonar.dev)
|
||||
./api.ebs.cloonar.dev.nix
|
||||
./ebs.cloonar.dev.nix
|
||||
./ebs-mobile.cloonar.dev.nix
|
||||
|
||||
# Disabled vhosts (amz.at) - uncomment to enable
|
||||
./api.ebs.amz.at.nix
|
||||
./api.stage.ebs.amz.at.nix
|
||||
./ebs.amz.at.nix
|
||||
./stage.ebs.amz.at.nix
|
||||
];
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs-mobile.cloonar.dev.nix
Normal file
49
hosts/amzebs-01/sites/ebs-mobile.cloonar.dev.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs-mobile.cloonar.dev";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs.amz.at.nix
Normal file
49
hosts/amzebs-01/sites/ebs.amz.at.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs.amz.at";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIInwmhTIPw7NnR3LDn2T5N6by0ZPXdL3r2O/8oRUc/ki"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/ebs.cloonar.dev.nix
Normal file
49
hosts/amzebs-01/sites/ebs.cloonar.dev.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "ebs.cloonar.dev";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
49
hosts/amzebs-01/sites/stage.ebs.amz.at.nix
Normal file
49
hosts/amzebs-01/sites/stage.ebs.amz.at.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ pkgs, lib, config, ... }:
|
||||
let
|
||||
domain = "stage.ebs.amz.at";
|
||||
dataDir = "/var/www/${domain}";
|
||||
in {
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
# Use HTTP-01 challenge for Let's Encrypt
|
||||
acmeRoot = lib.mkForce "/var/lib/acme/acme-challenge";
|
||||
root = "${dataDir}";
|
||||
|
||||
locations."/favicon.ico".extraConfig = ''
|
||||
log_not_found off;
|
||||
access_log off;
|
||||
'';
|
||||
|
||||
# React client-side routing support
|
||||
locations."/".extraConfig = ''
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html$is_args$args;
|
||||
'';
|
||||
|
||||
# Cache static assets
|
||||
locations."~* \\.(js|jpg|gif|png|webp|css|woff2|svg|ico)$".extraConfig = ''
|
||||
expires 365d;
|
||||
add_header Pragma "public";
|
||||
add_header Cache-Control "public";
|
||||
'';
|
||||
|
||||
# Deny PHP execution
|
||||
locations."~ [^/]\\.php(/|$)".extraConfig = ''
|
||||
deny all;
|
||||
'';
|
||||
};
|
||||
|
||||
users.users."${domain}" = {
|
||||
isNormalUser = true;
|
||||
createHome = true;
|
||||
home = dataDir;
|
||||
homeMode = "770";
|
||||
group = "nginx";
|
||||
openssh.authorizedKeys.keys = [
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIErjoADQK5SJ5si/iezzwQn5xH1RkgnTIlbeE4BRU1FN"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.${domain} = {};
|
||||
}
|
||||
1
hosts/amzebs-01/utils
Symbolic link
1
hosts/amzebs-01/utils
Symbolic link
@@ -0,0 +1 @@
|
||||
../../utils
|
||||
1
hosts/fw/channel
Normal file
1
hosts/fw/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-25.11
|
||||
192
hosts/fw/configuration.nix
Normal file
192
hosts/fw/configuration.nix
Normal file
@@ -0,0 +1,192 @@
|
||||
{ lib, pkgs, ... }: {
|
||||
imports = [
|
||||
./fleet.nix
|
||||
./utils/bento.nix
|
||||
./utils/modules/sops.nix
|
||||
./utils/modules/lego/lego.nix
|
||||
./utils/modules/nginx.nix
|
||||
|
||||
./utils/modules/autoupgrade.nix
|
||||
./utils/modules/victoriametrics
|
||||
./utils/modules/promtail
|
||||
./utils/modules/borgbackup.nix
|
||||
./utils/modules/set-nix-channel.nix
|
||||
|
||||
# fw
|
||||
./modules/network-prefix.nix
|
||||
./modules/networking.nix
|
||||
./modules/setupnetwork.nix
|
||||
./modules/firewall.nix
|
||||
# ./modules/dhcp4.nix
|
||||
# ./modules/unbound.nix
|
||||
|
||||
./modules/dnsmasq.nix
|
||||
./modules/avahi.nix
|
||||
./modules/openconnect.nix
|
||||
./modules/wireguard.nix
|
||||
./modules/podman.nix
|
||||
./modules/omada.nix
|
||||
./modules/ddclient.nix
|
||||
# ./modules/wol.nix
|
||||
|
||||
|
||||
# microvm
|
||||
./modules/microvm.nix
|
||||
./modules/gitea-vm.nix
|
||||
# ./modules/vscode-server.nix # Add VS Code Server microvm
|
||||
|
||||
./modules/ai-mailer.nix
|
||||
# ./modules/wazuh.nix
|
||||
|
||||
# web
|
||||
./modules/web
|
||||
|
||||
# git
|
||||
./modules/gitea.nix
|
||||
# ./modules/fwmetrics.nix
|
||||
|
||||
# ha customers
|
||||
./modules/ha-customers
|
||||
|
||||
./modules/firefox-sync.nix
|
||||
./modules/fivefilters.nix
|
||||
# ./modules/pyload
|
||||
|
||||
# home assistant
|
||||
./modules/home-assistant
|
||||
./modules/deconz.nix
|
||||
# ./modules/mopidy.nix
|
||||
# ./modules/mosquitto.nix
|
||||
# ./modules/snapserver.nix
|
||||
./modules/lms.nix
|
||||
|
||||
# gaming
|
||||
# ./modules/palworld.nix
|
||||
# ./modules/ark-survival-evolved.nix
|
||||
./modules/foundry-vtt.nix
|
||||
|
||||
# setup network
|
||||
./modules/setupnetwork.nix
|
||||
./modules/set-nix-channel.nix # Automatically manage nix-channel from /var/bento/channel
|
||||
./modules/grafana-monitor.nix # Grafana online status monitor
|
||||
|
||||
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
networkPrefix = "10.42";
|
||||
|
||||
nixpkgs.overlays = [
|
||||
(import ./utils/overlays/packages.nix)
|
||||
];
|
||||
|
||||
nixpkgs.config.permittedInsecurePackages = [
|
||||
"openssl-1.1.1w"
|
||||
];
|
||||
|
||||
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
|
||||
"mongodb"
|
||||
"ai-mailer"
|
||||
"filebot"
|
||||
];
|
||||
|
||||
# Intel N100 Graphics Support for hardware transcoding
|
||||
hardware.graphics = {
|
||||
enable = true;
|
||||
extraPackages = with pkgs; [
|
||||
intel-media-driver # VAAPI driver (iHD) for modern Intel GPUs
|
||||
vpl-gpu-rt # Intel VPL/QSV runtime for Gen 12+ (N100)
|
||||
intel-compute-runtime # OpenCL support for tone-mapping
|
||||
];
|
||||
};
|
||||
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
time.timeZone = "Europe/Vienna";
|
||||
|
||||
services.logind.settings.Login.RuntimeDirectorySize = "2G";
|
||||
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
bento
|
||||
conntrack-tools # view network connection states
|
||||
ethtool # manage NIC settings (offload, NIC feeatures, ...)
|
||||
git
|
||||
htop # to see the system load
|
||||
tcpdump # view network traffic
|
||||
vim # my preferred editor
|
||||
wol
|
||||
inotify-tools
|
||||
];
|
||||
|
||||
nix = {
|
||||
settings = {
|
||||
auto-optimise-store = true;
|
||||
# Build performance optimizations
|
||||
max-jobs = 4;
|
||||
cores = 4;
|
||||
# Enable eval caching for faster rebuilds
|
||||
eval-cache = true;
|
||||
# Use binary caches to avoid unnecessary rebuilds
|
||||
substituters = [
|
||||
"https://cache.nixos.org"
|
||||
];
|
||||
trusted-public-keys = [
|
||||
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
|
||||
];
|
||||
};
|
||||
gc = {
|
||||
automatic = true;
|
||||
dates = "weekly";
|
||||
options = "--delete-older-than 60d";
|
||||
};
|
||||
# Free up to 1GiB whenever there is less than 100MiB left.
|
||||
extraOptions = ''
|
||||
min-free = ${toString (100 * 1024 * 1024)}
|
||||
max-free = ${toString (1024 * 1024 * 1024)}
|
||||
'';
|
||||
};
|
||||
|
||||
services.tlp = {
|
||||
enable = true;
|
||||
settings = {
|
||||
CPU_SCALING_GOVERNOR_ON_AC = "performance"; # powersave or performance
|
||||
CPU_ENERGY_PERF_POLICY_ON_AC = "performance"; # power or performance
|
||||
# CPU_MIN_PERF_ON_AC = 0;
|
||||
# CPU_MAX_PERF_ON_AC = 100; # max 100
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
powertop = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "multi-user.target" ];
|
||||
description = "Powertop tunings";
|
||||
path = [ pkgs.kmod ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = "yes";
|
||||
ExecStart = "${pkgs.powertop}/bin/powertop --auto-tune && for dev in /sys/class/net/*; do echo on > \"$dev/device/power/control\"; done'";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
boot.tmp.cleanOnBoot = true;
|
||||
zramSwap.enable = true;
|
||||
networking.hostName = "fw";
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
# backups
|
||||
borgbackup.repo = "u149513-sub2@u149513-sub2.your-backup.de:borg";
|
||||
services.borgbackup.jobs.default.paths = lib.mkAfter [
|
||||
"/var/lib/microvms/persist/web-02/var/backup"
|
||||
];
|
||||
|
||||
system.stateVersion = "22.05";
|
||||
}
|
||||
1
hosts/fw/fleet.nix
Symbolic link
1
hosts/fw/fleet.nix
Symbolic link
@@ -0,0 +1 @@
|
||||
../../fleet.nix
|
||||
22
hosts/fw/hardware-configuration.nix
Normal file
22
hosts/fw/hardware-configuration.nix
Normal file
@@ -0,0 +1,22 @@
|
||||
{ lib, config, modulesPath, ... }:
|
||||
{
|
||||
boot.loader.systemd-boot = {
|
||||
enable = true;
|
||||
configurationLimit = 5;
|
||||
};
|
||||
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "vmw_pvscsi" "xen_blkfront" ];
|
||||
boot.initrd.kernelModules = [ "nvme" "kvm-intel" ];
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-label/boot";
|
||||
fsType = "vfat";
|
||||
};
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-partlabel/NIXOS";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||
}
|
||||
111
hosts/fw/modules/ai-mailer.nix
Normal file
111
hosts/fw/modules/ai-mailer.nix
Normal file
@@ -0,0 +1,111 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
users.users.ai-mailer = {
|
||||
isSystemUser = true;
|
||||
group = "ai-mailer";
|
||||
home = "/var/lib/ai-mailer";
|
||||
createHome = true;
|
||||
description = "AI Mailer service user";
|
||||
};
|
||||
users.groups.ai-mailer = { };
|
||||
|
||||
environment.etc."ai-mailer/config.yaml" = {
|
||||
mode = "0400";
|
||||
user = "ai-mailer";
|
||||
group = "ai-mailer";
|
||||
text = ''
|
||||
imap:
|
||||
server: "imap.gmail.com"
|
||||
port: 993
|
||||
username: "it@paraclub.at"
|
||||
password: "file://${config.sops.secrets.ai-mailer-imap-password.path}"
|
||||
mailbox_in: "INBOX"
|
||||
draft_box: "[Gmail]/Entwürfe"
|
||||
processed_box: "INBOX/Done"
|
||||
use_tls: true
|
||||
|
||||
ai:
|
||||
openrouter_api_key: "file://${config.sops.secrets.ai-mailer-openrouter-key.path}"
|
||||
model: "openai/gpt-5-mini"
|
||||
temperature: 0.3
|
||||
max_tokens: 200000
|
||||
|
||||
context:
|
||||
urls:
|
||||
- "https://paraclub.cloonar.dev/de/tandemfallschirmspringen/faq/"
|
||||
- "https://paraclub.at/de/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/alle-infos/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/kosten-tandemsprung/"
|
||||
- "https://paraclub.at/de/ueber-uns/anfahrt/"
|
||||
- "https://paraclub.at/de/tandemfallschirmspringen/faq/"
|
||||
- "https://paraclub.at/de/ausbildung/uebersicht/"
|
||||
- "https://paraclub.at/de/ausbildung/aff-ablauf/"
|
||||
- "https://paraclub.at/de/ausbildung/kurstermine/"
|
||||
- "https://paraclub.at/de/ausbildung/anmeldung/"
|
||||
- "https://paraclub.at/de/ausbildung/kosten/"
|
||||
|
||||
polling:
|
||||
interval: "300s"
|
||||
|
||||
processing:
|
||||
max_tokens: 30000
|
||||
skip_junk_emails: false
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
file_path: "/var/log/ai-mailer/ai-mailer.log"
|
||||
'';
|
||||
};
|
||||
|
||||
sops.secrets.ai-mailer-imap-password = {
|
||||
owner = "ai-mailer";
|
||||
};
|
||||
|
||||
sops.secrets.ai-mailer-openrouter-key = {
|
||||
owner = "ai-mailer";
|
||||
};
|
||||
|
||||
systemd.services.ai-mailer = {
|
||||
description = "AI Mail Assistant Service";
|
||||
after = [ "network-online.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "ai-mailer";
|
||||
Group = "ai-mailer";
|
||||
WorkingDirectory = "/var/lib/ai-mailer";
|
||||
ExecStart = "${pkgs.ai-mailer}/bin/ai-mailer -config /etc/ai-mailer/config.yaml";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
StateDirectory = "ai-mailer";
|
||||
LogsDirectory = "ai-mailer";
|
||||
RuntimeDirectory = "ai-mailer";
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
CapabilityBoundingSet = "";
|
||||
};
|
||||
|
||||
restartTriggers = [
|
||||
"/etc/ai-mailer/config.yaml"
|
||||
config.sops.secrets.ai-mailer-imap-password.path
|
||||
config.sops.secrets.ai-mailer-openrouter-key.path
|
||||
];
|
||||
};
|
||||
}
|
||||
94
hosts/fw/modules/allywatch.nix
Normal file
94
hosts/fw/modules/allywatch.nix
Normal file
@@ -0,0 +1,94 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
domain = "a11ywatch.cloonar.com";
|
||||
confDir = "/var/lib/a11ywatch";
|
||||
|
||||
json = pkgs.formats.json { };
|
||||
in {
|
||||
# 1) Enable Podman (daemonless, drop-in for docker)
|
||||
virtualisation.podman.enable = true; # :contentReference[oaicite:0]{index=0}
|
||||
virtualisation.podman.dockerCompat = true; # :contentReference[oaicite:1]{index=1}
|
||||
virtualisation.podman.defaultNetwork.settings.dns_enabled = true;# :contentReference[oaicite:2]{index=2}
|
||||
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3000/";
|
||||
};
|
||||
};
|
||||
|
||||
environment.etc."containers/networks/a11ywatch-net.json" = {
|
||||
source = json.generate "a11ywatch-net.json" ({
|
||||
name = "a11ywatch-net";
|
||||
id = "ccb4b7fb90d2df26db27ef0995765b04f52d318db752c9474b470c5ef4d7978d";
|
||||
driver = "bridge";
|
||||
network_interface = "podman1";
|
||||
subnets = [
|
||||
{
|
||||
subnet = "10.89.0.0/24";
|
||||
gateway = "10.89.0.1";
|
||||
}
|
||||
];
|
||||
ipv6_enabled = false;
|
||||
internal = false;
|
||||
dns_enabled = true;
|
||||
ipam_options = {
|
||||
driver = "host-local";
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
users.users.a11ywatch = {
|
||||
isSystemUser = true;
|
||||
group = "a11ywatch";
|
||||
home = "/var/lib/a11ywatch";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.a11ywatch = { };
|
||||
users.groups.docker.members = [ "a11ywatch" ];
|
||||
|
||||
# 2) Create the bridge network on boot via a oneshot systemd service
|
||||
systemd.services.a11ywatch-net = {
|
||||
description = "Ensure a11ywatch-net Podman network exists";
|
||||
wants = [ "podman.service" ];
|
||||
after = [ "podman.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = ''
|
||||
${pkgs.podman}/bin/podman network inspect a11ywatch-net >/dev/null 2>&1 \
|
||||
|| ${pkgs.podman}/bin/podman network create a11ywatch-net
|
||||
'';
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
wantedBy = [
|
||||
"multi-user.target"
|
||||
];
|
||||
};
|
||||
|
||||
# 3) Declare your two containers using the podman backend
|
||||
virtualisation.oci-containers = {
|
||||
backend = "podman"; # :contentReference[oaicite:3]{index=3}
|
||||
containers = {
|
||||
a11ywatch-backend = {
|
||||
image = "docker.io/a11ywatch/a11ywatch:latest";
|
||||
autoStart = true;
|
||||
ports = [ "3280:3280" ];
|
||||
volumes = [ "${confDir}:/a11ywatch/conf" ];
|
||||
environment = { SUPER_MODE = "true"; };
|
||||
extraOptions = [ "--network=a11ywatch-net" ];
|
||||
};
|
||||
a11ywatch-frontend = {
|
||||
image = "docker.io/a11ywatch/web:latest";
|
||||
autoStart = true;
|
||||
ports = [ "3000:3000" ];
|
||||
volumes = [ "${confDir}:/a11ywatch/conf" ];
|
||||
environment = { SUPER_MODE = "true"; };
|
||||
extraOptions = [
|
||||
"--network=a11ywatch-net"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
24
hosts/fw/modules/ark-survival-evolved.nix
Normal file
24
hosts/fw/modules/ark-survival-evolved.nix
Normal file
@@ -0,0 +1,24 @@
|
||||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
virtualisation.oci-containers.backend = "podman";
|
||||
virtualisation.oci-containers.containers = {
|
||||
ark = {
|
||||
image = "hermsi/ark-server:latest";
|
||||
autoStart = true;
|
||||
environmentFiles = [
|
||||
config.sops.secrets.ark.path
|
||||
];
|
||||
volumes = [
|
||||
"/var/lib/ark/app:/app/"
|
||||
"/var/lib/ark/backup:/home/steam/ARK-Backups"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=${config.networkPrefix}.97.201"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets.ark = {};
|
||||
}
|
||||
16
hosts/fw/modules/avahi.nix
Normal file
16
hosts/fw/modules/avahi.nix
Normal file
@@ -0,0 +1,16 @@
|
||||
{ pkgs, ... }: {
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
reflector = true;
|
||||
allowInterfaces = [
|
||||
"multimedia"
|
||||
"server"
|
||||
"lan"
|
||||
"smart"
|
||||
];
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
nssmdns
|
||||
];
|
||||
}
|
||||
54
hosts/fw/modules/cloonar-assistant-config-server.nix
Normal file
54
hosts/fw/modules/cloonar-assistant-config-server.nix
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
users = [
|
||||
{
|
||||
username = "ca-test";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDglSLU9AUtbU0fCN0eByi/EHyo1QiPPLiscN5RAR+wq";
|
||||
}
|
||||
];
|
||||
|
||||
userList = lib.concatStringsSep "," (map (u: u.username) users);
|
||||
in {
|
||||
environment.etc = {
|
||||
# our single user+key file
|
||||
"cloonar_assistant_ssh/sftp_users_keys" = {
|
||||
text = lib.concatStringsSep "\n"
|
||||
(map (u: "${u.username} ${u.key}") users);
|
||||
mode = "0600";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
# the little awk script to extract the key for $1
|
||||
"cloonar_assistant_ssh/sftp-fetch-key.sh" = {
|
||||
text = ''
|
||||
#!/usr/bin/env bash
|
||||
awk -v u="$1" '$1==u { $1=""; sub(/^ +/, ""); print }' /etc/cloonar_assistant_ssh/sftp_users_keys
|
||||
'';
|
||||
mode = "0700";
|
||||
user = "root";
|
||||
group = "root";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = map (u:
|
||||
# Type 'd' = create directory if missing
|
||||
# Mode 0755, owner root:root
|
||||
"d /home/cloonar-assistant-configs/${u.username} 0755 root root -"
|
||||
) users;
|
||||
|
||||
services.openssh.extraConfig = ''
|
||||
Match User ${userList}
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding no
|
||||
ChrootDirectory /home/cloonar-assistant-configs/%u
|
||||
ForceCommand internal-sftp
|
||||
|
||||
# ← only for those matched users:
|
||||
AuthorizedKeysCommand /etc/cloonar_assistant_ssh/sftp-fetch-key.sh %u
|
||||
AuthorizedKeysCommandUser root
|
||||
'';
|
||||
}
|
||||
25
hosts/fw/modules/ddclient.nix
Normal file
25
hosts/fw/modules/ddclient.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.ddclient = {
|
||||
enable = true;
|
||||
usev4 = "if, if=wan";
|
||||
protocol = "hetzner";
|
||||
# server = "https://dns.hetzner.com/api/v1/";
|
||||
username = "dominik.polakovics@cloonar.com";
|
||||
passwordFile = config.sops.secrets.ddclient.path;
|
||||
zone = "cloonar.com";
|
||||
domains = [
|
||||
"fw.cloonar.com"
|
||||
"vpn.cloonar.com"
|
||||
"git.cloonar.com"
|
||||
"palworld.cloonar.com"
|
||||
"matrix.cloonar.com"
|
||||
"element.cloonar.com"
|
||||
"tinder.cloonar.com"
|
||||
];
|
||||
};
|
||||
|
||||
sops.secrets.ddclient = {
|
||||
# owner = config.systemd.services.ddclient.serviceConfig.User;
|
||||
};
|
||||
}
|
||||
36
hosts/fw/modules/deconz.nix
Normal file
36
hosts/fw/modules/deconz.nix
Normal file
@@ -0,0 +1,36 @@
|
||||
{ config, pkgs, ... }: {
|
||||
virtualisation = {
|
||||
oci-containers.containers = {
|
||||
deconz = {
|
||||
autoStart = true;
|
||||
image = "marthoc/deconz";
|
||||
volumes = [
|
||||
"/etc/localtime:/etc/localtime:ro"
|
||||
"/var/lib/deconz:/root/.local/share/dresden-elektronik/deCONZ"
|
||||
"/dev/bus/usb:/dev/bus/usb:ro"
|
||||
"/run/udev:/run/udev:ro"
|
||||
];
|
||||
environment = {
|
||||
DECONZ_DEVICE = "/dev/ttyACM0";
|
||||
TZ = "Europe/Vienna";
|
||||
DECONZ_UID = "0";
|
||||
DECONZ_GID = "0";
|
||||
DECONZ_START_VERBOSE = "1";
|
||||
};
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=${config.networkPrefix}.97.22"
|
||||
"--device=/dev/ttyACM0"
|
||||
"--hostname=deconz"
|
||||
"--mac-address=1a:c4:04:6e:29:bd"
|
||||
"--cap-add=CAP_MKNOD"
|
||||
"--cap-add=CAP_NET_RAW"
|
||||
"--cap-add=CAP_NET_ADMIN"
|
||||
"--device-cgroup-rule=c 166:* rmw"
|
||||
"--device-cgroup-rule=c 188:* rmw"
|
||||
"--security-opt=label=disable"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
289
hosts/fw/modules/dhcp4.nix
Normal file
289
hosts/fw/modules/dhcp4.nix
Normal file
@@ -0,0 +1,289 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
services.kea.dhcp4 = {
|
||||
enable = true;
|
||||
settings = {
|
||||
interfaces-config = {
|
||||
interfaces = [
|
||||
"lan"
|
||||
"server"
|
||||
"infrastructure"
|
||||
"multimedia"
|
||||
"smart"
|
||||
"guest"
|
||||
];
|
||||
};
|
||||
lease-database = {
|
||||
name = "/var/lib/kea/dhcp4.leases";
|
||||
persist = true;
|
||||
type = "memfile";
|
||||
};
|
||||
rebind-timer = 2000;
|
||||
renew-timer = 1000;
|
||||
subnet4 = [
|
||||
{
|
||||
id = 96;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.96.100 - ${config.networkPrefix}.96.240";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.96.0/24";
|
||||
interface = "lan";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.96.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = "cloonar.com";
|
||||
}
|
||||
{
|
||||
name = "domain-search";
|
||||
data = "cloonar.com";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "${config.networkPrefix}.96.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "04:7c:16:d5:63:5e";
|
||||
ip-address = "${config.networkPrefix}.96.5";
|
||||
server-hostname = "omada.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "30:05:5c:56:62:37";
|
||||
ip-address = "${config.networkPrefix}.96.100";
|
||||
server-hostname = "brn30055c566237.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "24:df:a7:b1:1b:74";
|
||||
ip-address = "${config.networkPrefix}.96.101";
|
||||
server-hostname = "rmproplus-b1-1b-74.cloonar.com";
|
||||
}
|
||||
];
|
||||
|
||||
}
|
||||
{
|
||||
id = 97;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.97.100 - ${config.networkPrefix}.97.240";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.97.0/24";
|
||||
interface = "server";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.97.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = "cloonar.com";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "${config.networkPrefix}.97.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "1a:c4:04:6e:29:bd";
|
||||
ip-address = "${config.networkPrefix}.97.2";
|
||||
server-hostname = "omada.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "02:00:00:00:00:03";
|
||||
ip-address = "${config.networkPrefix}.97.5";
|
||||
server-hostname = "web-02.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "02:00:00:00:00:04";
|
||||
ip-address = "${config.networkPrefix}.97.6";
|
||||
server-hostname = "matrix.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "ea:db:d4:c1:18:ba";
|
||||
ip-address = "${config.networkPrefix}.97.50";
|
||||
server-hostname = "git.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "c2:4f:64:dd:13:0c";
|
||||
ip-address = "${config.networkPrefix}.97.20";
|
||||
server-hostname = "home-assistant.cloonar.com";
|
||||
}
|
||||
{
|
||||
hw-address = "1a:c4:04:6e:29:02";
|
||||
ip-address = "${config.networkPrefix}.97.25";
|
||||
server-hostname = "deconz.cloonar.com";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 101;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.101.100 - ${config.networkPrefix}.101.240";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.101.0/24";
|
||||
interface = "infrastructure";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.101.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = "cloonar.com";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "${config.networkPrefix}.101.1";
|
||||
}
|
||||
{
|
||||
name = "capwap-ac-v4";
|
||||
code = 138;
|
||||
data = "${config.networkPrefix}.97.2";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 99;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.99.100 - ${config.networkPrefix}.99.240";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.99.0/24";
|
||||
interface = "multimedia";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.99.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = "cloonar.multimedia";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "${config.networkPrefix}.99.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "c4:a7:2b:c7:ea:30";
|
||||
ip-address = "${config.networkPrefix}.99.10";
|
||||
hostname = "metz.cloonar.multimedia";
|
||||
}
|
||||
{
|
||||
hw-address = "f0:2f:9e:d4:3b:21";
|
||||
ip-address = "${config.networkPrefix}.99.11";
|
||||
hostname = "firetv-living";
|
||||
}
|
||||
{
|
||||
hw-address = "bc:33:29:ed:24:f0";
|
||||
ip-address = "${config.networkPrefix}.99.12";
|
||||
hostname = "ps5";
|
||||
}
|
||||
{
|
||||
hw-address = "e4:2a:ac:32:3f:79";
|
||||
ip-address = "${config.networkPrefix}.99.13";
|
||||
hostname = "xbox";
|
||||
}
|
||||
{
|
||||
hw-address = "98:b6:e9:b6:ef:f4";
|
||||
ip-address = "${config.networkPrefix}.99.14";
|
||||
hostname = "switch";
|
||||
}
|
||||
{
|
||||
hw-address = "f0:2f:9e:c1:74:72";
|
||||
ip-address = "${config.networkPrefix}.99.21";
|
||||
hostname = "firetv-bedroom";
|
||||
}
|
||||
{
|
||||
hw-address = "30:05:5c:56:62:37";
|
||||
ip-address = "${config.networkPrefix}.99.100";
|
||||
server-hostname = "brn30055c566237";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 254;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.254.10 - ${config.networkPrefix}.254.254";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.254.0/24";
|
||||
interface = "guest";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.254.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "9.9.9.9";
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
id = 100;
|
||||
pools = [
|
||||
{
|
||||
pool = "${config.networkPrefix}.100.100 - ${config.networkPrefix}.100.240";
|
||||
}
|
||||
];
|
||||
subnet = "${config.networkPrefix}.100.0/24";
|
||||
interface = "smart";
|
||||
option-data = [
|
||||
{
|
||||
name = "routers";
|
||||
data = "${config.networkPrefix}.100.1";
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = "cloonar.smart";
|
||||
}
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = "${config.networkPrefix}.100.1";
|
||||
}
|
||||
];
|
||||
reservations = [
|
||||
{
|
||||
hw-address = "fc:ee:28:03:63:e9";
|
||||
ip-address = "${config.networkPrefix}.100.148";
|
||||
server-hostname = "k1c";
|
||||
}
|
||||
{
|
||||
hw-address = "cc:50:e3:bc:27:64";
|
||||
ip-address = "${config.networkPrefix}.100.112";
|
||||
server-hostname = "Nuki_Bridge_1A753F72";
|
||||
}
|
||||
|
||||
{
|
||||
hw-address = "34:6f:24:f3:af:ad";
|
||||
ip-address = "${config.networkPrefix}.100.137";
|
||||
server-hostname = "daikin86604";
|
||||
}
|
||||
{
|
||||
hw-address = "34:6f:24:c1:f8:54";
|
||||
ip-address = "${config.networkPrefix}.100.139";
|
||||
server-hostname = "daikin53800";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
valid-lifetime = 4000;
|
||||
};
|
||||
};
|
||||
}
|
||||
176
hosts/fw/modules/dnsmasq.nix
Normal file
176
hosts/fw/modules/dnsmasq.nix
Normal file
@@ -0,0 +1,176 @@
|
||||
{ config, ... }: {
|
||||
services.resolved.enable = false;
|
||||
|
||||
services.dnsmasq = {
|
||||
enable = true;
|
||||
settings = {
|
||||
port = "53";
|
||||
bind-interfaces = true; # force dnsmasq to bind immediately
|
||||
expand-hosts = true;
|
||||
|
||||
log-dhcp = true;
|
||||
|
||||
server = [
|
||||
"/epicenter.works/10.50.60.1"
|
||||
"/akvorrat.at/10.50.60.1"
|
||||
"9.9.9.9"
|
||||
"149.112.112.11"
|
||||
];
|
||||
|
||||
interface = [
|
||||
"lan"
|
||||
"server"
|
||||
"infrastructure"
|
||||
"multimedia"
|
||||
"guest"
|
||||
"smart"
|
||||
];
|
||||
|
||||
domain = [
|
||||
"cloonar.com,lan"
|
||||
"cloonar.com,server"
|
||||
"cloonar.com,infrastructure"
|
||||
"cloonar.multimedia,multimedia"
|
||||
"cloonar.smart,smart"
|
||||
"cloonar.guest,guest"
|
||||
];
|
||||
|
||||
dhcp-option = [
|
||||
"lan,15,cloonar.com" # domain name
|
||||
"lan,3,${config.networkPrefix}.96.1" # Gateway
|
||||
"lan,6,${config.networkPrefix}.96.1" # DNS
|
||||
"server,15,cloonar.com"
|
||||
"server,3,${config.networkPrefix}.97.1"
|
||||
"server,6,${config.networkPrefix}.97.1"
|
||||
"infrastructure,15,cloonar.com"
|
||||
"infrastructure,3,${config.networkPrefix}.101.1"
|
||||
"infrastructure,6,${config.networkPrefix}.101.1"
|
||||
"multimedia,15,cloonar.multimedia"
|
||||
"multimedia,3,${config.networkPrefix}.99.1"
|
||||
"multimedia,6,${config.networkPrefix}.99.1"
|
||||
"smart,15,cloonar.smart"
|
||||
"smart,3,${config.networkPrefix}.100.1"
|
||||
"smart,6,${config.networkPrefix}.100.1"
|
||||
"guest,15,cloonar.guest"
|
||||
"guest,3,${config.networkPrefix}.254.1"
|
||||
"guest,6,9.9.9.9"
|
||||
];
|
||||
|
||||
dhcp-range = [
|
||||
"lan,${config.networkPrefix}.96.100,${config.networkPrefix}.96.200,24h"
|
||||
"server,${config.networkPrefix}.97.100,${config.networkPrefix}.97.200,24h"
|
||||
"infrastructure,${config.networkPrefix}.101.100,${config.networkPrefix}.101.200,24h"
|
||||
"multimedia,${config.networkPrefix}.99.100,${config.networkPrefix}.99.200,24h"
|
||||
"smart,${config.networkPrefix}.100.100,${config.networkPrefix}.100.200,24h"
|
||||
"guest,${config.networkPrefix}.254.100,${config.networkPrefix}.254.200,24h"
|
||||
];
|
||||
|
||||
dhcp-host = [
|
||||
"24:df:a7:b1:1b:74,${config.networkPrefix}.96.101,rmproplus-b1-1b-74"
|
||||
|
||||
"30:05:5c:56:62:37,${config.networkPrefix}.99.100,brn30055c566237"
|
||||
"1a:c4:04:6e:29:bd,${config.networkPrefix}.97.2,omada"
|
||||
"02:00:00:00:00:04,${config.networkPrefix}.97.6,matrix"
|
||||
"ea:db:d4:c1:18:ba,${config.networkPrefix}.97.50,git"
|
||||
"c2:4f:64:dd:13:0c,${config.networkPrefix}.97.20,home-assistant"
|
||||
"6c:1f:f7:8e:a9:86,${config.networkPrefix}.97.11,nas"
|
||||
"1a:c4:04:6e:29:02,${config.networkPrefix}.101.25,deconz"
|
||||
|
||||
"c4:a7:2b:c7:ea:30,${config.networkPrefix}.99.10,metz"
|
||||
"f0:2f:9e:d4:3b:21,${config.networkPrefix}.99.11,firetv-living"
|
||||
"e4:2a:ac:32:3f:79,${config.networkPrefix}.99.13,xbox"
|
||||
"f0:2f:9e:c1:74:72,${config.networkPrefix}.99.21,firetv-bedroom"
|
||||
|
||||
"fc:ee:28:03:63:e9,${config.networkPrefix}.100.148,k1c"
|
||||
"cc:50:e3:bc:27:64,${config.networkPrefix}.100.112,Nuki_Bridge_1A753F72"
|
||||
"34:6f:24:f3:af:ad,${config.networkPrefix}.100.137,daikin86604"
|
||||
"34:6f:24:c1:f8:54,${config.networkPrefix}.100.139,daikin53800"
|
||||
];
|
||||
|
||||
address = [
|
||||
"/fw.cloonar.com/${config.networkPrefix}.97.1"
|
||||
"/omada.cloonar.com/${config.networkPrefix}.97.2"
|
||||
"/web-02.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/pla.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/piped.cloonar.com/${config.networkPrefix}.97.5" # Replaced by Invidious
|
||||
"/pipedapi.cloonar.com/${config.networkPrefix}.97.5" # Replaced by Invidious
|
||||
"/invidious.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/fivefilters.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/n8n.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/home-assistant.cloonar.com/${config.networkPrefix}.97.20"
|
||||
"/mopidy.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/snapcast.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/lms.cloonar.com/${config.networkPrefix}.97.21"
|
||||
"/git.cloonar.com/${config.networkPrefix}.97.50"
|
||||
"/feeds.cloonar.com/188.34.191.144"
|
||||
"/nukibridge1a753f72.cloonar.smart/${config.networkPrefix}.100.112"
|
||||
"/allywatch.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/brn30055c566237.cloonar.multimedia/${config.networkPrefix}.99.100"
|
||||
|
||||
"/stage.wsw.at/10.254.235.22"
|
||||
"/prod.wsw.at/10.254.217.23"
|
||||
"/piwik.wohnservice-wien.at/10.254.240.109"
|
||||
"/wohnberatung-wien.at/10.254.240.109"
|
||||
"/wohnpartner-wien.at/10.254.240.109"
|
||||
"/wohnservice-wien.at/10.254.240.109"
|
||||
"/mieterhilfe.at/10.254.240.109"
|
||||
"/wienbautvor.at/10.254.240.109"
|
||||
"/wienwohntbesser.at/10.254.240.109"
|
||||
"/a.stage.wohnberatung-wien.at/10.254.240.110"
|
||||
"/a.stage.wohnpartner-wien.at/10.254.240.110"
|
||||
"/a.stage.wohnservice-wien.at/10.254.240.110"
|
||||
"/a.stage.mieterhilfe.at/10.254.240.110"
|
||||
"/a.stage.wienbautvor.at/10.254.240.110"
|
||||
"/a.stage.wienwohntbesser.at/10.254.240.110"
|
||||
"/b.stage.wohnberatung-wien.at/10.254.240.110"
|
||||
"/b.stage.wohnpartner-wien.at/10.254.240.110"
|
||||
"/b.stage.wohnservice-wien.at/10.254.240.110"
|
||||
"/b.stage.mieterhilfe.at/10.254.240.110"
|
||||
"/b.stage.wienbautvor.at/10.254.240.110"
|
||||
"/b.stage.wienwohntbesser.at/10.254.240.110"
|
||||
|
||||
"/web.hilgenberg-gmbh.de/91.107.197.169"
|
||||
# gaming
|
||||
"/foundry-vtt.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/sync.cloonar.com/${config.networkPrefix}.97.5"
|
||||
|
||||
# multimedia
|
||||
"/dl.cloonar.com/${config.networkPrefix}.97.5"
|
||||
"/jellyfin.cloonar.com/${config.networkPrefix}.97.5"
|
||||
|
||||
"/deconz.cloonar.multimedia/${config.networkPrefix}.97.22"
|
||||
|
||||
"/ddl-warez.to/172.67.184.30"
|
||||
"/cdnjs.cloudflare.com/104.17.24.14"
|
||||
|
||||
# esphome devices
|
||||
"/livingroom-bulb-1.cloonar.smart/${config.networkPrefix}.100.11"
|
||||
"/livingroom-bulb-2.cloonar.smart/${config.networkPrefix}.100.12"
|
||||
"/livingroom-bulb-3.cloonar.smart/${config.networkPrefix}.100.13"
|
||||
"/livingroom-bulb-4.cloonar.smart/${config.networkPrefix}.100.14"
|
||||
"/livingroom-bulb-5.cloonar.smart/${config.networkPrefix}.100.15"
|
||||
"/livingroom-bulb-6.cloonar.smart/${config.networkPrefix}.100.16"
|
||||
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.21"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.22"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.23"
|
||||
"/bedroom-bulb-0.cloonar.smart/${config.networkPrefix}.100.24"
|
||||
|
||||
"/hallway-bulb-0.cloonar.smart/${config.networkPrefix}.100.31"
|
||||
"/hallway-bulb-0.cloonar.smart/${config.networkPrefix}.100.32"
|
||||
|
||||
"/bath-bulb-0.cloonar.smart/${config.networkPrefix}.100.41"
|
||||
"/bath-bulb-0.cloonar.smart/${config.networkPrefix}.100.42"
|
||||
|
||||
"/paraclub.at/188.34.191.144"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.dnsmasq = {
|
||||
requires = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedUDPPorts = [ 53 67 ];
|
||||
}
|
||||
59
hosts/fw/modules/firefox-sync.nix
Normal file
59
hosts/fw/modules/firefox-sync.nix
Normal file
@@ -0,0 +1,59 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
domain = "sync.cloonar.com";
|
||||
networkPrefix = config.networkPrefix;
|
||||
in {
|
||||
sops.secrets.firefox-sync = {
|
||||
mode = "0777";
|
||||
};
|
||||
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
containers."firefox-sync" = {
|
||||
autoStart = true;
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${config.networkPrefix}.97.1";
|
||||
localAddress = "${config.networkPrefix}.97.6/24";
|
||||
bindMounts = {
|
||||
"/run/secrets/firefox-sync" = {
|
||||
hostPath = "/run/secrets/firefox-sync";
|
||||
isReadOnly = true;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "firefox-sync";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${networkPrefix}.97.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
services.mysql.package = pkgs.mariadb;
|
||||
services.firefox-syncserver = {
|
||||
enable = true;
|
||||
settings.host = "0.0.0.0";
|
||||
singleNode = {
|
||||
enable = true;
|
||||
hostname = "0.0.0.0";
|
||||
url = "https://${domain}";
|
||||
};
|
||||
secrets = "/run/secrets/firefox-sync";
|
||||
logLevel = "debug";
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 5000 ];
|
||||
};
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
191
hosts/fw/modules/firewall.nix
Normal file
191
hosts/fw/modules/firewall.nix
Normal file
@@ -0,0 +1,191 @@
|
||||
{ config, pkgs, ... }: {
|
||||
networking = {
|
||||
firewall.checkReversePath = false;
|
||||
nat.enable = false;
|
||||
nftables = {
|
||||
enable = true;
|
||||
tables = {
|
||||
"cloonar-fw" = {
|
||||
family = "inet";
|
||||
content = ''
|
||||
chain snap-qos-raw {
|
||||
type filter hook prerouting priority raw; policy accept;
|
||||
tcp dport 1704 counter mark set 10 comment "Mark Snapcast traffic"
|
||||
tcp dport 3483 counter mark set 10 comment "Mark Squezelite traffic"
|
||||
udp dport 3483 counter mark set 10 comment "Mark Squezelite traffic"
|
||||
}
|
||||
|
||||
chain snap-qos-mangle {
|
||||
type filter hook postrouting priority mangle + 10; policy accept;
|
||||
mark 10 counter ip dscp set cs3 comment "Tag Snapcast with CS3"
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority 100; policy accept;
|
||||
}
|
||||
|
||||
chain rpfilter {
|
||||
type filter hook prerouting priority mangle + 10; policy drop;
|
||||
meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment "DHCPv4 client/server"
|
||||
fib saddr . mark . iif oif exists accept
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
iifname "lo" accept comment "trusted interfaces"
|
||||
iifname "lan" counter accept comment "Spice"
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
|
||||
tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
|
||||
}
|
||||
|
||||
chain input-allow {
|
||||
udp dport != { 53, 5353 } ct state new limit rate over 1/second burst 10 packets drop comment "rate limit for new connections"
|
||||
iifname lo accept
|
||||
iifname "wan" udp dport 51820 counter accept comment "Wireguard traffic"
|
||||
iifname "wan" tcp dport 9273 counter accept comment "Prometheus traffic"
|
||||
iifname "lan" tcp dport 5931 counter accept comment "Spice"
|
||||
iifname { "server", "vserver", "vm-*", "lan", "wg_cloonar" } counter accept comment "allow trusted to router"
|
||||
iifname { "multimedia", "smart", "infrastructure", "podman0", "setup" } udp dport { 53, 5353 } counter accept comment "DNS"
|
||||
iifname { "multimedia", "smart", "infrastructure", "server", "lan", "guest" } udp dport { 67 } counter accept comment "DHCP"
|
||||
iifname { "wan", "multimedia" } icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
|
||||
# Accept mDNS for avahi reflection
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 tcp dport { llmnr } counter accept
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 udp dport { mdns, llmnr } counter accept
|
||||
iifname "server" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "Avahi mDNS"
|
||||
iifname "lan" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "Avahi mDNS"
|
||||
|
||||
# Allow all returning traffic
|
||||
ct state { established, related } counter accept
|
||||
|
||||
# Allow returning traffic from wrwks and drop everthing else
|
||||
iifname "wrwks" ct state { established, related } counter accept
|
||||
iifname "wrwks" drop
|
||||
|
||||
# Allow returning traffic from wg_epicenter and drop everthing else
|
||||
iifname "wg_epicenter" ct state { established, related } counter accept
|
||||
iifname "wg_epicenter" drop
|
||||
|
||||
# Allow returning traffic from wg_ghetto_at and drop everthing else
|
||||
iifname "wg_ghetto_at" ct state { established, related } counter accept
|
||||
iifname "wg_ghetto_at" drop
|
||||
|
||||
# Allow returning traffic from wan and drop everthing else
|
||||
iifname "wan" ct state { established, related } accept comment "Allow established traffic"
|
||||
iifname "wan" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
iifname "wan" counter drop comment "Drop all other unsolicited traffic from wan"
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Input - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
|
||||
iifname "wg_cloonar" counter accept comment "test wireguard"
|
||||
|
||||
iifname "wg_cloonar" oifname lo counter accept comment "wireguard to server"
|
||||
|
||||
# enable flow offloading for better throughput
|
||||
# ip protocol { tcp, udp } flow offload @f
|
||||
|
||||
# broadcast
|
||||
iifname "server" oifname { "lan", "multimedia" } udp dport { 9 } counter accept comment "wakeonlan"
|
||||
|
||||
# multimedia airplay
|
||||
iifname "multimedia" oifname { "lan" } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 1704, 1705 } counter accept
|
||||
iifname "multimedia" oifname "server" tcp dport { 3483, 9000 } counter accept
|
||||
iifname "multimedia" oifname "server" udp dport { 3483 } counter accept
|
||||
iifname "multimedia" oifname "server" icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP"
|
||||
iifname "lan" oifname "server" udp dport { 5000, 5353, 6001 - 6011 } counter accept
|
||||
# avahi
|
||||
iifname "server" ip saddr ${config.networkPrefix}.97.20/32 oifname { "lan" } counter accept
|
||||
|
||||
# Allow Chromecast
|
||||
iifname "lan" oifname "server" udp dport 5353 ip daddr 224.0.0.251 counter accept comment "mDNS query LAN→Server"
|
||||
iifname "server" oifname "lan" udp sport 5353 ip saddr 224.0.0.251 counter accept comment "mDNS response Server→LAN"
|
||||
iifname "lan" oifname "server" tcp dport 9881 counter accept comment "chromecast"
|
||||
|
||||
# SSDP / UPnP discovery if needed
|
||||
iifname { "lan", "server" } oifname { "server", "lan" } \
|
||||
udp dport 1900 ip daddr 239.255.255.250 counter accept comment "SSDP query"
|
||||
iifname { "lan", "server" } oifname { "server", "lan" } \
|
||||
udp sport 1900 ip saddr 239.255.255.250 counter accept comment "SSDP response"
|
||||
|
||||
# smart home coap
|
||||
iifname "smart" oifname "server" ip daddr ${config.networkPrefix}.97.20/32 udp dport { 5683 } counter accept
|
||||
iifname "smart" oifname "server" ip daddr ${config.networkPrefix}.97.20/32 tcp dport { 1883 } counter accept
|
||||
|
||||
# Forward to git server
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.50 tcp dport { 22 } counter accept
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.5 tcp dport { 80, 443 } counter accept
|
||||
|
||||
# lan and vpn to any
|
||||
iifname { "lan", "server", "vserver", "wg_cloonar" } oifname { "lan", "vb-*", "vm-*", "server", "vserver", "infrastructure", "multimedia", "smart", "wg_cloonar", "guest", "setup" } counter accept
|
||||
iifname { "lan", "server", "wg_cloonar" } oifname { "wrwks", "wg_epicenter", "wg_ghetto_at" } counter accept
|
||||
iifname { "infrastructure", "setup" } oifname { "server", "vserver" } counter accept
|
||||
iifname { "lan", "wan" } udp dport { 8211, 27015 } counter accept comment "palworld"
|
||||
|
||||
# accept palword server
|
||||
iifname { "wan", "lan" } oifname "podman0" udp dport { 8211, 27015 } counter accept comment "palworld"
|
||||
# forward to ark server
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.201 tcp dport { 27020 } counter accept comment "ark survival evolved"
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.201 udp dport { 7777, 7778, 27015 } counter accept comment "ark survival evolved"
|
||||
|
||||
# firefox-sync
|
||||
oifname "server" ip daddr ${config.networkPrefix}.97.51 tcp dport { 5000 } counter accept comment "firefox-sync"
|
||||
|
||||
# allow all established, related
|
||||
ct state { established, related } accept comment "Allow established traffic"
|
||||
|
||||
# Allow trusted network WAN access
|
||||
iifname {
|
||||
"lan",
|
||||
"infrastructure",
|
||||
"server",
|
||||
"vserver",
|
||||
"multimedia",
|
||||
"smart",
|
||||
"wg_cloonar",
|
||||
"podman*",
|
||||
"guest",
|
||||
"setup",
|
||||
"vb-*",
|
||||
"vm-*",
|
||||
} oifname {
|
||||
"wan",
|
||||
} counter accept comment "Allow trusted LAN to WAN"
|
||||
|
||||
limit rate 60/minute burst 100 packets log prefix "Forward - Drop: " comment "Log any unmatched traffic"
|
||||
}
|
||||
'';
|
||||
};
|
||||
"cloonar-nat" = {
|
||||
family = "ip";
|
||||
content = ''
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority filter; policy accept;
|
||||
iifname "server" ip daddr ${config.networkPrefix}.96.255 udp dport { 9 } dnat to ${config.networkPrefix}.96.255
|
||||
iifname "wan" tcp dport { 22 } dnat to ${config.networkPrefix}.97.50
|
||||
iifname "wan" tcp dport { 80, 443 } dnat to ${config.networkPrefix}.97.5
|
||||
iifname "wan" tcp dport { 5000 } dnat to ${config.networkPrefix}.97.51
|
||||
iifname { "wan", "lan" } udp dport { 7777, 7778, 27015 } dnat to ${config.networkPrefix}.97.201
|
||||
iifname { "wan", "lan" } tcp dport { 27020 } dnat to ${config.networkPrefix}.97.201
|
||||
}
|
||||
|
||||
# Setup NAT masquerading on external interfaces
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority filter; policy accept;
|
||||
oifname { "wan", "wg_cloonar", "wrwks", "wg_epicenter", "wg_ghetto_at" } masquerade
|
||||
iifname { "lan", "wg_cloonar" } ip daddr ${config.networkPrefix}.110.101 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.50 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.51 masquerade
|
||||
iifname { "wan", "wg_cloonar" } ip daddr ${config.networkPrefix}.97.201 masquerade
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
32
hosts/fw/modules/fivefilters.nix
Normal file
32
hosts/fw/modules/fivefilters.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{ config, pkgs, ... }: {
|
||||
users.users.fivefilters = {
|
||||
isSystemUser = true;
|
||||
group = "omada";
|
||||
home = "/var/lib/fivefilters";
|
||||
createHome = true;
|
||||
};
|
||||
users.groups.fivefilters = { };
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
# parent is created by createHome already, but harmless to repeat
|
||||
"d /var/lib/fivefilters 0755 fivefilters fivefilters - -"
|
||||
"d /var/lib/fivefilters/cache 0755 fivefilters fivefilters - -"
|
||||
];
|
||||
|
||||
# TODO: check if we can run docker service as other user than root
|
||||
virtualisation = {
|
||||
oci-containers.containers = {
|
||||
fivefilters = {
|
||||
autoStart = true;
|
||||
image = "heussd/fivefilters-full-text-rss:3.8.1";
|
||||
volumes = [
|
||||
"/var/lib/fivefilters/cache:/var/www/html/cache"
|
||||
];
|
||||
extraOptions = [
|
||||
"--network=server"
|
||||
"--ip=${config.networkPrefix}.97.10"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
162
hosts/fw/modules/foundry-vtt.nix
Normal file
162
hosts/fw/modules/foundry-vtt.nix
Normal file
@@ -0,0 +1,162 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
foundry-vtt = pkgs.callPackage ../pkgs/foundry-vtt {};
|
||||
cids = import ../modules/staticids.nix;
|
||||
hostConfig = config;
|
||||
url = "https://foundry-vtt.cloonar.com"; # URL to check
|
||||
targetService = "container@foundry-vtt.service"; # systemd unit to restart (e.g. "docker-container@myapp.service")
|
||||
threshold = 3; # consecutive failures before restart
|
||||
interval = "1min"; # how often to run
|
||||
timeoutSeconds = 10; # curl timeout
|
||||
|
||||
checkUrlScript = pkgs.writeShellScript "check-foundry-up" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
URL="$1"
|
||||
TARGET="$2"
|
||||
THRESHOLD="$3"
|
||||
TIMEOUT="$4"
|
||||
|
||||
STATE_DIR="/run/url-watchdog"
|
||||
mkdir -p "$STATE_DIR"
|
||||
SAFE_TARGET="$(systemd-escape --path "$TARGET")"
|
||||
STATE_FILE="$STATE_DIR/$SAFE_TARGET.count"
|
||||
|
||||
TMP="$(mktemp)"
|
||||
# Get HTTP status; "000" if curl fails.
|
||||
status="$(curl -sS -m "$TIMEOUT" -o "$TMP" -w "%{http_code}" "$URL" || echo "000")"
|
||||
|
||||
fail=0
|
||||
if [[ "$status" == "502" || "$status" == "504" || "$status" == "000" ]]; then
|
||||
fail=1
|
||||
fi
|
||||
|
||||
count=0
|
||||
if [[ -f "$STATE_FILE" ]]; then
|
||||
count="$(cat "$STATE_FILE" 2>/dev/null || echo 0)"
|
||||
fi
|
||||
|
||||
if [[ "$fail" -eq 1 ]]; then
|
||||
count=$((count+1))
|
||||
else
|
||||
count=0
|
||||
fi
|
||||
|
||||
if [[ "$count" -ge "$THRESHOLD" ]]; then
|
||||
printf '[%s] %s failing (%s) %sx -> restarting %s\n' "$(date -Is)" "$URL" "$status" "$count" "$TARGET"
|
||||
systemctl restart "$TARGET"
|
||||
count=0
|
||||
fi
|
||||
|
||||
echo "$count" > "$STATE_FILE"
|
||||
rm -f "$TMP"
|
||||
'';
|
||||
in {
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
|
||||
containers.foundry-vtt = {
|
||||
autoStart = true;
|
||||
ephemeral = true;
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${hostConfig.networkPrefix}.97.1";
|
||||
localAddress = "${hostConfig.networkPrefix}.97.21/24";
|
||||
bindMounts = {
|
||||
"/var/lib/foundry-vtt" = {
|
||||
hostPath = "/var/lib/foundry-vtt";
|
||||
isReadOnly = false;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networking = {
|
||||
hostName = "foundry-vtt";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${hostConfig.networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "${hostConfig.networkPrefix}.97.1" ];
|
||||
};
|
||||
systemd.services.foundry-vtt = {
|
||||
description = "Foundry VTT Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
NODE_ENV = "production";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.nodejs}/bin/node ${foundry-vtt}/share/foundry-vtt/main.js --dataPath=${config.users.users.foundry-vtt.home}";
|
||||
Restart = "always";
|
||||
User = "foundry-vtt";
|
||||
WorkingDirectory = "${config.users.users.foundry-vtt.home}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.foundry-vtt = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.foundry-vtt;
|
||||
home = "/var/lib/foundry-vtt";
|
||||
group = "foundry-vtt";
|
||||
};
|
||||
|
||||
users.groups.foundry-vtt = {
|
||||
gid = cids.gids.foundry-vtt;
|
||||
};
|
||||
|
||||
system.stateVersion = "24.05";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services."restart-foundry-vtt" = {
|
||||
description = "Restart foundry-vtt container";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.systemd}/bin/systemctl restart container@foundry-vtt.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers."restart-foundry-vtt" = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
# 03:00 local time (Europe/Vienna for you)
|
||||
OnCalendar = "03:00";
|
||||
# If the machine was off at 03:00, run once at next boot
|
||||
Persistent = true;
|
||||
Unit = "restart-foundry-vtt.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.foundry-vtt-watchdog = {
|
||||
description = "Foundry VTT watchdog: restart ${targetService} on Nginx gateway errors";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${checkUrlScript} ${url} ${targetService} ${toString threshold} ${toString timeoutSeconds}";
|
||||
};
|
||||
# Ensure needed tools are on PATH inside the unit
|
||||
path = [ pkgs.curl pkgs.coreutils pkgs.systemd ];
|
||||
# Wait until networking is really up
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
};
|
||||
|
||||
systemd.timers.foundry-vtt-watchdog = {
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = interval;
|
||||
OnUnitActiveSec = interval;
|
||||
AccuracySec = "10s";
|
||||
};
|
||||
};
|
||||
}
|
||||
30
hosts/fw/modules/fwmetrics.nix
Normal file
30
hosts/fw/modules/fwmetrics.nix
Normal file
@@ -0,0 +1,30 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
configure_prom = builtins.toFile "prometheus.yml" ''
|
||||
scrape_configs:
|
||||
- job_name: 'server'
|
||||
stream_parse: true
|
||||
static_configs:
|
||||
- targets:
|
||||
- ${config.networking.hostName}:9100
|
||||
'';
|
||||
in {
|
||||
sops.secrets.victoria-agent-env = {
|
||||
sopsFile = ../utils/modules/victoriametrics/secrets.yaml;
|
||||
};
|
||||
|
||||
services.prometheus.exporters.node.enable = true;
|
||||
|
||||
systemd.services.export-fw-to-prometheus = {
|
||||
path = with pkgs; [victoriametrics];
|
||||
enable = true;
|
||||
after = ["network-online.target"];
|
||||
wants = ["network-online.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
script = "vmagent -promscrape.config=${configure_prom} -envflag.enable -remoteWrite.url=https://victoria-server.cloonar.com/api/v1/write";
|
||||
|
||||
serviceConfig = {
|
||||
EnvironmentFile=config.sops.secrets.victoria-agent-env.path;
|
||||
};
|
||||
};
|
||||
}
|
||||
44
hosts/fw/modules/gitea-runner-image-README.md
Normal file
44
hosts/fw/modules/gitea-runner-image-README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Gitea Runner Docker Image
|
||||
|
||||
This directory contains the Dockerfile for the custom Gitea Actions runner image that includes additional dependencies needed for CI workflows.
|
||||
|
||||
## Included Tools
|
||||
|
||||
- **Base**: `shivammathur/node:latest` (includes Node.js and common development tools)
|
||||
- **Chrome dependencies**: Full Puppeteer/Chromium dependencies for headless browser testing
|
||||
- **webp**: WebP image format tools (`cwebp`, `dwebp`)
|
||||
- **libavif-bin**: AVIF image format tools (`avifenc`, `avifdec`)
|
||||
|
||||
## Building the Image
|
||||
|
||||
```bash
|
||||
cd hosts/fw/modules
|
||||
docker build -f gitea-runner.Dockerfile -t git.cloonar.com/infrastructure/gitea-runner:latest .
|
||||
```
|
||||
|
||||
## Pushing to Registry
|
||||
|
||||
First, authenticate with your Gitea container registry:
|
||||
|
||||
```bash
|
||||
docker login git.cloonar.com
|
||||
```
|
||||
|
||||
Then push the image:
|
||||
|
||||
```bash
|
||||
docker push git.cloonar.com/infrastructure/gitea-runner:latest
|
||||
```
|
||||
|
||||
## Using the Image
|
||||
|
||||
The image is already configured in `gitea-vm.nix` and will be used automatically by the Gitea Actions runners for jobs labeled with `ubuntu-latest`.
|
||||
|
||||
## Updating the Image
|
||||
|
||||
When you need to add new dependencies:
|
||||
|
||||
1. Edit `gitea-runner.Dockerfile`
|
||||
2. Rebuild the image with the commands above
|
||||
3. Push to the registry
|
||||
4. Restart the runner VMs: `systemctl restart microvm@git-runner-1.service microvm@git-runner-2.service`
|
||||
54
hosts/fw/modules/gitea-runner.Dockerfile
Normal file
54
hosts/fw/modules/gitea-runner.Dockerfile
Normal file
@@ -0,0 +1,54 @@
|
||||
FROM shivammathur/node:latest
|
||||
|
||||
# Install Chrome dependencies for Puppeteer
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
fonts-liberation \
|
||||
libappindicator3-1 \
|
||||
libasound2t64 \
|
||||
libatk-bridge2.0-0 \
|
||||
libatk1.0-0 \
|
||||
libc6 \
|
||||
libcairo2 \
|
||||
libcups2 \
|
||||
libdbus-1-3 \
|
||||
libexpat1 \
|
||||
libfontconfig1 \
|
||||
libgbm1 \
|
||||
libgcc-s1 \
|
||||
libglib2.0-0 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libstdc++6 \
|
||||
libx11-6 \
|
||||
libx11-xcb1 \
|
||||
libxcb1 \
|
||||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxdamage1 \
|
||||
libxext6 \
|
||||
libxfixes3 \
|
||||
libxi6 \
|
||||
libxrandr2 \
|
||||
libxrender1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
lsb-release \
|
||||
wget \
|
||||
xdg-utils \
|
||||
webp \
|
||||
libavif-bin \
|
||||
chromium \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - && \
|
||||
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y google-chrome-stable && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Verify installations
|
||||
RUN cwebp -version && avifenc --version
|
||||
251
hosts/fw/modules/gitea-vm.nix
Normal file
251
hosts/fw/modules/gitea-vm.nix
Normal file
@@ -0,0 +1,251 @@
|
||||
{ config, lib, nixpkgs, pkgs, ... }: let
|
||||
# hostname = "git-02";
|
||||
# json = pkgs.formats.json { };
|
||||
runners = ["git-runner-1" "git-runner-2"];
|
||||
indexedRunners = lib.lists.imap1 (i: v: { name=v; value=i; }) runners;
|
||||
in {
|
||||
microvm.vms = lib.mapAttrs (runner: idx: {
|
||||
config = {
|
||||
microvm = {
|
||||
mem = 4048;
|
||||
shares = [
|
||||
{
|
||||
source = "/nix/store";
|
||||
mountPoint = "/nix/.ro-store";
|
||||
tag = "ro-store";
|
||||
proto = "virtiofs";
|
||||
}
|
||||
{
|
||||
source = "/run/secrets";
|
||||
mountPoint = "/run/secrets";
|
||||
tag = "ro-token";
|
||||
proto = "virtiofs";
|
||||
}
|
||||
];
|
||||
volumes = [
|
||||
{
|
||||
image = "rootfs.img";
|
||||
mountPoint = "/";
|
||||
size = 51200;
|
||||
}
|
||||
];
|
||||
interfaces = [
|
||||
{
|
||||
type = "tap";
|
||||
id = "vm-${runner}";
|
||||
mac = "02:00:00:00:00:0${toString idx}";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.network.networks."10-lan" = {
|
||||
matchConfig.PermanentMACAddress = "02:00:00:00:00:0${toString idx}";
|
||||
address = [ "${config.networkPrefix}.97.5${toString idx}/24" ];
|
||||
gateway = [ "${config.networkPrefix}.97.1" ];
|
||||
dns = [ "${config.networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
networking.hostName = runner;
|
||||
|
||||
virtualisation.podman.enable = true;
|
||||
|
||||
services.gitea-actions-runner.instances.${runner} = {
|
||||
enable = true;
|
||||
url = "https://git.cloonar.com";
|
||||
name = runner;
|
||||
tokenFile = "/run/secrets/gitea-runner-token";
|
||||
labels = [
|
||||
# "ubuntu-latest:docker://shivammathur/node:latest"
|
||||
"ubuntu-latest:docker://git.cloonar.com/infrastructure/gitea-runner:1.0.0"
|
||||
];
|
||||
settings = {
|
||||
container = {
|
||||
network = "podman";
|
||||
};
|
||||
cache = {
|
||||
enabled = true;
|
||||
host = "${config.networkPrefix}.97.5${toString idx}"; # LAN IP of the machine running act_runner
|
||||
port = 8088; # any free TCP port
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
];
|
||||
|
||||
networking.firewall = {
|
||||
enable = true; # default, but being explicit is fine
|
||||
allowedTCPPorts = [ 8088 ];
|
||||
};
|
||||
|
||||
system.stateVersion = "22.05";
|
||||
};
|
||||
}) (lib.listToAttrs (lib.lists.imap1 (i: v: { name=v; value=i; }) runners));
|
||||
|
||||
# microvm.vms = {
|
||||
# gitea = {
|
||||
# config = {
|
||||
# microvm = {
|
||||
# hypervisor = "cloud-hypervisor";
|
||||
# shares = [
|
||||
# {
|
||||
# source = "/nix/store";
|
||||
# mountPoint = "/nix/.ro-store";
|
||||
# tag = "ro-store";
|
||||
# proto = "virtiofs";
|
||||
# }
|
||||
# {
|
||||
# source = "/var/lib/acme/git.cloonar.com";
|
||||
# mountPoint = "/var/lib/acme/${hostname}.cloonar.com";
|
||||
# tag = "ro-cert";
|
||||
# proto = "virtiofs";
|
||||
# }
|
||||
# ];
|
||||
# interfaces = [
|
||||
# {
|
||||
# type = "tap";
|
||||
# id = "vm-${hostname}";
|
||||
# mac = "02:00:00:00:00:01";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
#
|
||||
# imports = [
|
||||
# ../fleet.nix
|
||||
# ];
|
||||
#
|
||||
# environment.systemPackages = with pkgs; [
|
||||
# vim # my preferred editor
|
||||
# ];
|
||||
#
|
||||
# networking = {
|
||||
# hostName = hostname;
|
||||
# firewall = {
|
||||
# enable = true;
|
||||
# allowedTCPPorts = [ 22 80 443 ];
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# services.nginx.enable = true;
|
||||
# services.nginx.virtualHosts."${hostname}.cloonar.com" = {
|
||||
# sslCertificate = "/var/lib/acme/${hostname}.cloonar.com/fullchain.pem";
|
||||
# sslCertificateKey = "/var/lib/acme/${hostname}.cloonar.com/key.pem";
|
||||
# sslTrustedCertificate = "/var/lib/acme/${hostname}.cloonar.com/chain.pem";
|
||||
# forceSSL = true;
|
||||
# locations."/" = {
|
||||
# proxyPass = "http://localhost:3001/";
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# services.gitea = {
|
||||
# enable = true;
|
||||
# appName = "Cloonar Gitea server"; # Give the site a name
|
||||
# settings = {
|
||||
# server = {
|
||||
# ROOT_URL = "https://${hostname}.cloonar.com/";
|
||||
# HTTP_PORT = 3001;
|
||||
# DOMAIN = "${hostname}.cloonar.com";
|
||||
# };
|
||||
# openid = {
|
||||
# ENABLE_OPENID_SIGNIN = true;
|
||||
# ENABLE_OPENID_SIGNUP = true;
|
||||
# WHITELISTED_URIS = "auth.cloonar.com";
|
||||
# };
|
||||
# service = {
|
||||
# DISABLE_REGISTRATION = true;
|
||||
# ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
# SHOW_REGISTRATION_BUTTON = false;
|
||||
# };
|
||||
# actions.ENABLED=true;
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# services.openssh.enable = true;
|
||||
# users.users.root.openssh.authorizedKeys.keys = [
|
||||
# "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
# "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
# ];
|
||||
#
|
||||
# system.stateVersion = "22.05";
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# gitea-runner-1 = {
|
||||
# config = {
|
||||
# microvm = {
|
||||
# mem = 4048;
|
||||
# shares = [
|
||||
# {
|
||||
# source = "/nix/store";
|
||||
# mountPoint = "/nix/.ro-store";
|
||||
# tag = "ro-store";
|
||||
# proto = "virtiofs";
|
||||
# }
|
||||
# {
|
||||
# source = "/run/secrets";
|
||||
# mountPoint = "/run/secrets";
|
||||
# tag = "ro-token";
|
||||
# proto = "virtiofs";
|
||||
# }
|
||||
# ];
|
||||
# volumes = [
|
||||
# {
|
||||
# image = "rootfs.img";
|
||||
# mountPoint = "/";
|
||||
# size = 102400;
|
||||
# }
|
||||
# ];
|
||||
# interfaces = [
|
||||
# {
|
||||
# type = "tap";
|
||||
# id = "vm-gitea-runner-1";
|
||||
# mac = "02:00:00:00:00:02";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
#
|
||||
# environment.systemPackages = with pkgs; [
|
||||
# vim # my preferred editor
|
||||
# ];
|
||||
#
|
||||
# networking.hostName = "gitea-runner";
|
||||
#
|
||||
# virtualisation.podman.enable = true;
|
||||
#
|
||||
# services.gitea-actions-runner.instances.vm = {
|
||||
# enable = true;
|
||||
# url = "https://git.cloonar.com";
|
||||
# name = "vm";
|
||||
# tokenFile = "/run/secrets/gitea-runner-token";
|
||||
# labels = [
|
||||
# "ubuntu-latest:docker://shivammathur/node:latest"
|
||||
# ];
|
||||
# settings = {
|
||||
# container = {
|
||||
# network = "podman";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
#
|
||||
# services.openssh.enable = true;
|
||||
# users.users.root.openssh.authorizedKeys.keys = [
|
||||
# "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
# "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
# ];
|
||||
#
|
||||
# system.stateVersion = "22.05";
|
||||
# };
|
||||
# };
|
||||
# };
|
||||
|
||||
sops.secrets.gitea-runner-token = {};
|
||||
|
||||
environment = {
|
||||
systemPackages = [
|
||||
pkgs.qemu
|
||||
pkgs.quickemu
|
||||
];
|
||||
};
|
||||
}
|
||||
142
hosts/fw/modules/gitea.nix
Normal file
142
hosts/fw/modules/gitea.nix
Normal file
@@ -0,0 +1,142 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
cids = import ../modules/staticids.nix;
|
||||
domain = "git.cloonar.com";
|
||||
networkPrefix = config.networkPrefix;
|
||||
|
||||
user = {
|
||||
isSystemUser = true;
|
||||
uid = cids.uids.gitea;
|
||||
group = "gitea";
|
||||
home = "/var/lib/gitea";
|
||||
createHome = true;
|
||||
};
|
||||
group = {
|
||||
gid = cids.gids.gitea;
|
||||
};
|
||||
in
|
||||
{
|
||||
users.users.gitea = user;
|
||||
users.groups.gitea = group;
|
||||
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
containers.git = {
|
||||
autoStart = true;
|
||||
ephemeral = false; # because of ssh key
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${networkPrefix}.97.1";
|
||||
localAddress = "${networkPrefix}.97.50/24";
|
||||
bindMounts = {
|
||||
"/var/lib/gitea" = {
|
||||
hostPath = "/var/lib/gitea/";
|
||||
isReadOnly = false;
|
||||
};
|
||||
"/var/lib/acme/gitea/" = {
|
||||
hostPath = config.security.acme.certs.${domain}.directory;
|
||||
isReadOnly = true;
|
||||
};
|
||||
"/run/secrets/gitea-mailer-password" = {
|
||||
hostPath = config.sops.secrets.gitea-mailer-password.path;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
imports = [
|
||||
../fleet.nix
|
||||
../modules/cloonar-assistant-config-server.nix
|
||||
];
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
vim # my preferred editor
|
||||
];
|
||||
|
||||
networking = {
|
||||
hostName = "git";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
sslCertificate = "/var/lib/acme/gitea/fullchain.pem";
|
||||
sslCertificateKey = "/var/lib/acme/gitea/key.pem";
|
||||
sslTrustedCertificate = "/var/lib/acme/gitea/chain.pem";
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
client_max_body_size 2048M;
|
||||
'';
|
||||
locations."/" = {
|
||||
proxyPass = "http://localhost:3001/";
|
||||
};
|
||||
};
|
||||
|
||||
services.gitea = {
|
||||
enable = true;
|
||||
appName = "Cloonar Gitea server"; # Give the site a name
|
||||
mailerPasswordFile = "/run/secrets/gitea-mailer-password";
|
||||
settings = {
|
||||
server = {
|
||||
ROOT_URL = "https://${domain}/";
|
||||
HTTP_PORT = 3001;
|
||||
DOMAIN = domain;
|
||||
};
|
||||
repository = {
|
||||
DEFAULT_BRANCH = "main";
|
||||
};
|
||||
openid = {
|
||||
ENABLE_OPENID_SIGNIN = false;
|
||||
ENABLE_OPENID_SIGNUP = true;
|
||||
WHITELISTED_URIS = "auth.cloonar.com";
|
||||
};
|
||||
service = {
|
||||
DISABLE_REGISTRATION = false;
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = true;
|
||||
SHOW_REGISTRATION_BUTTON = false;
|
||||
ENABLE_NOTIFY_MAIL = true;
|
||||
REQUIRE_SIGNIN_VIEW = false;
|
||||
};
|
||||
mailer = {
|
||||
ENABLED = true;
|
||||
FROM = "Gitea Cloonar <gitea@cloonar.com>";
|
||||
PROTOCOL = "smtp+starttls";
|
||||
SMTP_ADDR = "mail.cloonar.com";
|
||||
SMTP_PORT = 587;
|
||||
USER = "gitea@cloonar.com";
|
||||
};
|
||||
actions.ENABLED=true;
|
||||
attachment = {
|
||||
MAX_SIZE = 2048; # 2GB in MB for general attachments
|
||||
};
|
||||
packages = {
|
||||
ENABLED = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
users.users.gitea = user;
|
||||
users.groups.gitea = group;
|
||||
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets.gitea-runner = {};
|
||||
sops.secrets.gitea-mailer-password = {
|
||||
owner = "gitea";
|
||||
restartUnits = [ "container@git.service" ];
|
||||
};
|
||||
}
|
||||
193
hosts/fw/modules/grafana-monitor.nix
Normal file
193
hosts/fw/modules/grafana-monitor.nix
Normal file
@@ -0,0 +1,193 @@
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
grafanaMonitorUser = "grafana-monitor";
|
||||
grafanaMonitorGroup = "grafana-monitor";
|
||||
stateDir = "/var/lib/${grafanaMonitorUser}";
|
||||
|
||||
# Monitoring script will be defined here later
|
||||
monitorScript = pkgs.writeShellScriptBin "grafana-online-check" ''
|
||||
#!${pkgs.bash}/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
GRAFANA_URL="https://grafana.cloonar.com/api/health"
|
||||
STATE_FILE="${stateDir}/status.env"
|
||||
PUSHOVER_API_TOKEN_FILE="/run/secrets/pushover-api-token"
|
||||
PUSHOVER_USER_KEY_FILE="/run/secrets/pushover-user-key"
|
||||
MAX_FAILURES=5
|
||||
|
||||
# Ensure state directory exists (NixOS creates $HOME for the user, which is stateDir)
|
||||
# The script runs as grafanaMonitorUser, so $HOME will be /var/lib/grafana-monitor
|
||||
mkdir -p "''${HOME}"
|
||||
|
||||
# Load current state or initialize
|
||||
CONSECUTIVE_FAILURES=0
|
||||
ALERT_SENT="false"
|
||||
LAST_KNOWN_STATUS="UP" # Assume UP initially if no state file
|
||||
|
||||
# Note: STATE_FILE uses $stateDir which is /var/lib/grafana-monitor.
|
||||
# The script will run with HOME=/var/lib/grafana-monitor.
|
||||
# So, using ''${HOME}/status.env or ''${STATE_FILE} should resolve to the same path.
|
||||
# Let's stick to ''${STATE_FILE} for consistency with its definition.
|
||||
if [[ -f "''${STATE_FILE}" ]]; then
|
||||
source "''${STATE_FILE}"
|
||||
fi
|
||||
|
||||
# Check secrets
|
||||
if [[ ! -f "''${PUSHOVER_API_TOKEN_FILE}" ]] || [[ ! -r "''${PUSHOVER_API_TOKEN_FILE}" ]]; then
|
||||
echo "Error: Pushover API token file (''${PUSHOVER_API_TOKEN_FILE}) not found or not readable." >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSHOVER_API_TOKEN=$(cat "''${PUSHOVER_API_TOKEN_FILE}")
|
||||
|
||||
if [[ ! -f "''${PUSHOVER_USER_KEY_FILE}" ]] || [[ ! -r "''${PUSHOVER_USER_KEY_FILE}" ]]; then
|
||||
echo "Error: Pushover user key file (''${PUSHOVER_USER_KEY_FILE}) not found or not readable." >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSHOVER_USER_KEY=$(cat "''${PUSHOVER_USER_KEY_FILE}")
|
||||
|
||||
# Internet connectivity check
|
||||
INTERNET_CHECK_URL="https://1.1.1.1" # Using a reliable IP to bypass potential DNS issues for the check itself
|
||||
echo "Performing internet connectivity check to ''${INTERNET_CHECK_URL}..."
|
||||
if ! ${pkgs.curl}/bin/curl --head --silent --fail --connect-timeout 3 --max-time 5 "''${INTERNET_CHECK_URL}" > /dev/null 2>&1; then
|
||||
echo "Internet connectivity check failed. Cannot reach ''${INTERNET_CHECK_URL}. Skipping Grafana check and exiting successfully."
|
||||
exit 0
|
||||
else
|
||||
echo "Internet connectivity check successful. Proceeding with Grafana check."
|
||||
fi
|
||||
echo "" # Add a blank line for readability before Grafana check logs
|
||||
echo "Checking Grafana at ''${GRAFANA_URL}..."
|
||||
ACTUAL_HTTP_CODE="000" # Default if curl doesn't provide one
|
||||
CURL_ERROR_MESSAGE=""
|
||||
CURL_STDERR_OUTPUT=$(mktemp)
|
||||
# Ensure temp file is cleaned up on exit, error, or interrupt
|
||||
trap 'rm -f "''${CURL_STDERR_OUTPUT}"' EXIT TERM INT HUP
|
||||
|
||||
# -L: follow redirects
|
||||
# -sS: silent mode, but show errors
|
||||
# --fail: curl exits with 22 on server errors (4xx, 5xx)
|
||||
# --connect-timeout 5: max time to connect
|
||||
# --max-time 10: max total time for operation
|
||||
# --stderr: redirect stderr to a file to capture detailed errors
|
||||
# -o /dev/null: discard response body
|
||||
# --write-out "%{http_code}": output the HTTP status code
|
||||
if ACTUAL_HTTP_CODE=$(${pkgs.curl}/bin/curl -L -sS --fail --connect-timeout 5 --max-time 10 \
|
||||
--stderr "''${CURL_STDERR_OUTPUT}" \
|
||||
-o /dev/null --write-out "%{http_code}" "''${GRAFANA_URL}"); then
|
||||
# Curl exited with 0. With --fail, this means HTTP status was 2xx.
|
||||
echo "Grafana is UP (HTTP ''${ACTUAL_HTTP_CODE})."
|
||||
CURRENT_STATUS="UP"
|
||||
if [[ "''${LAST_KNOWN_STATUS}" == "DOWN" && "''${ALERT_SENT}" == "true" ]]; then
|
||||
echo "Grafana recovered. Sending recovery notification."
|
||||
${pkgs.curl}/bin/curl -sS -X POST \
|
||||
-F "token=''${PUSHOVER_API_TOKEN}" \
|
||||
-F "user=''${PUSHOVER_USER_KEY}" \
|
||||
-F "message=Grafana at ''${GRAFANA_URL} is back online (HTTP ''${ACTUAL_HTTP_CODE})." \
|
||||
-F "title=Grafana Recovered (fw)" \
|
||||
-F "priority=0" \
|
||||
https://api.pushover.net/1/messages.json
|
||||
ALERT_SENT="false"
|
||||
fi
|
||||
CONSECUTIVE_FAILURES=0
|
||||
else
|
||||
# Curl exited with a non-zero status.
|
||||
CURL_EXIT_CODE=$?
|
||||
CURL_ERROR_MESSAGE=$(cat "''${CURL_STDERR_OUTPUT}" | tr -d '\n' | sed 's/"/\\"/g') # Read, remove newlines, escape quotes for JSON
|
||||
|
||||
echo "Grafana check failed. Curl Exit Code: ''${CURL_EXIT_CODE}. HTTP Code reported: ''${ACTUAL_HTTP_CODE}."
|
||||
echo "Curl Stderr: ''${CURL_ERROR_MESSAGE}"
|
||||
CURRENT_STATUS="DOWN"
|
||||
CONSECUTIVE_FAILURES=$(( ''${CONSECUTIVE_FAILURES} + 1 ))
|
||||
echo "Consecutive failures: ''${CONSECUTIVE_FAILURES}"
|
||||
|
||||
if [[ ''${CONSECUTIVE_FAILURES} -ge ''${MAX_FAILURES} && "''${ALERT_SENT}" == "false" ]]; then
|
||||
echo "Grafana has been offline for ''${CONSECUTIVE_FAILURES} checks (>= ''${MAX_FAILURES}). Sending alert."
|
||||
PUSHOVER_TITLE="Grafana OFFLINE (fw)"
|
||||
PUSHOVER_MSG="Grafana ''${GRAFANA_URL} offline for ''${MAX_FAILURES}+ min. HTTP:''${ACTUAL_HTTP_CODE}, CurlExit:''${CURL_EXIT_CODE}."
|
||||
if [[ -n "''${CURL_ERROR_MESSAGE}" ]]; then
|
||||
PUSHOVER_MSG+=" Err: ''${CURL_ERROR_MESSAGE}"
|
||||
fi
|
||||
# Truncate message if too long for Pushover (1024 chars)
|
||||
PUSHOVER_MSG=$(echo "''${PUSHOVER_MSG}" | cut -c 1-1024)
|
||||
|
||||
${pkgs.curl}/bin/curl -sS -X POST \
|
||||
-F "token=''${PUSHOVER_API_TOKEN}" \
|
||||
-F "user=''${PUSHOVER_USER_KEY}" \
|
||||
-F "message=''${PUSHOVER_MSG}" \
|
||||
-F "title=''${PUSHOVER_TITLE}" \
|
||||
-F "priority=1" \
|
||||
https://api.pushover.net/1/messages.json
|
||||
ALERT_SENT="true"
|
||||
fi
|
||||
fi
|
||||
# Temp file is removed by trap
|
||||
|
||||
# Save current state
|
||||
echo "Saving state: CONSECUTIVE_FAILURES=''${CONSECUTIVE_FAILURES}, ALERT_SENT=''${ALERT_SENT}, LAST_KNOWN_STATUS=''${CURRENT_STATUS}"
|
||||
(
|
||||
echo "CONSECUTIVE_FAILURES=''${CONSECUTIVE_FAILURES}"
|
||||
echo "ALERT_SENT=''${ALERT_SENT}"
|
||||
echo "LAST_KNOWN_STATUS=''${CURRENT_STATUS}"
|
||||
) > "''${STATE_FILE}" # Using STATE_FILE which is ${stateDir}/status.env
|
||||
chmod 600 "''${STATE_FILE}"
|
||||
|
||||
echo "Grafana check finished."
|
||||
'';
|
||||
in
|
||||
{
|
||||
# Module is now implicitly enabled when imported
|
||||
config = {
|
||||
users.users.${grafanaMonitorUser} = {
|
||||
isSystemUser = true;
|
||||
group = grafanaMonitorGroup;
|
||||
home = stateDir; # Home directory for state
|
||||
createHome = true; # NixOS will create this directory
|
||||
description = "User for Grafana online monitoring service";
|
||||
};
|
||||
users.groups.${grafanaMonitorGroup} = {};
|
||||
|
||||
# Sops secrets for Pushover
|
||||
sops.secrets."pushover-api-token" = {
|
||||
owner = grafanaMonitorUser;
|
||||
group = grafanaMonitorGroup;
|
||||
mode = "0400"; # Read-only for the user
|
||||
};
|
||||
sops.secrets."pushover-user-key" = {
|
||||
owner = grafanaMonitorUser;
|
||||
group = grafanaMonitorGroup;
|
||||
mode = "0400"; # Read-only for the user
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.curl
|
||||
pkgs.coreutils # for mkdir, cat, echo, rm used in script (though bash builtins are often used)
|
||||
];
|
||||
|
||||
systemd.services.grafana-online-check = {
|
||||
description = "Grafana Online Check Service";
|
||||
wantedBy = [ "multi-user.target" ]; # Or timers.target if only started by timer
|
||||
after = [ "network-online.target" ]; # Ensure network is up and secrets are available
|
||||
requires = [ "network-online.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = grafanaMonitorUser;
|
||||
Group = grafanaMonitorGroup;
|
||||
ExecStart = "${monitorScript}/bin/grafana-online-check";
|
||||
# Permissions to write to its own home directory (stateDir) are implicit
|
||||
# If using StateDirectory= in systemd, it would be different.
|
||||
# For home directory usage, ensure the user has rights. `createHome = true` helps.
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.grafana-online-check = {
|
||||
description = "Timer to periodically check Grafana's online status";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "2min"; # Wait a bit after boot
|
||||
OnUnitActiveSec = "1min"; # Run every 1 minute after the last run
|
||||
Unit = "grafana-online-check.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
6
hosts/fw/modules/ha-customers/default.nix
Normal file
6
hosts/fw/modules/ha-customers/default.nix
Normal file
@@ -0,0 +1,6 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./ghetto.nix
|
||||
];
|
||||
}
|
||||
29
hosts/fw/modules/ha-customers/ghetto.nix
Normal file
29
hosts/fw/modules/ha-customers/ghetto.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
create_users = host: {
|
||||
users.users."${host.username}.ghetto.at" = {
|
||||
createHome = true;
|
||||
home = "/home/customers/ghetto/" + host.username;
|
||||
isNormalUser = false;
|
||||
isSystemUser = true;
|
||||
group = "sftp_users";
|
||||
openssh.authorizedKeys.keys = [
|
||||
host.key
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
];
|
||||
shell = null;
|
||||
};
|
||||
};
|
||||
|
||||
users = [
|
||||
{
|
||||
username = "fw";
|
||||
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGzJRWe8hsqAVnGSjPrcheloteWMzORoQ5Gj4IfhCROF";
|
||||
}
|
||||
];
|
||||
in {
|
||||
imports = builtins.map create_users users;
|
||||
}
|
||||
60
hosts/fw/modules/home-assistant/3dprinter.nix
Normal file
60
hosts/fw/modules/home-assistant/3dprinter.nix
Normal file
@@ -0,0 +1,60 @@
|
||||
{ config, ... }: {
|
||||
services.home-assistant.config = {
|
||||
sensor = [
|
||||
{
|
||||
platform = "rest";
|
||||
name = "creality extruder";
|
||||
resource = "http://k1c-63e9.cloonar.smart:7125/printer/objects/query?extruder";
|
||||
value_template = "OK";
|
||||
json_attributes_path = "$.result.status.extruder";
|
||||
json_attributes = [
|
||||
"pressure_advance"
|
||||
"power"
|
||||
"target"
|
||||
"temperature"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "rest";
|
||||
name = "creality print stats";
|
||||
resource = "http://k1c-63e9.cloonar.smart:7125/printer/objects/query?print_stats";
|
||||
value_template = "OK";
|
||||
json_attributes_path = "$.result.status.print_stats";
|
||||
json_attributes = [
|
||||
"filename"
|
||||
"total_duration"
|
||||
"print_duration"
|
||||
"filament_used"
|
||||
"state"
|
||||
"message"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "template";
|
||||
sensors = {
|
||||
crality_hotend_actual = {
|
||||
friendly_name = "Hot End Actual";
|
||||
value_template = "{{ state_attr('sensor.creality_extruder', 'temperature') | float | round(1) }}";
|
||||
device_class = "temperature";
|
||||
unit_of_measurement = "°C";
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
"automation 3d printer state" = {
|
||||
alias = "3d printer state change";
|
||||
trigger = [
|
||||
{
|
||||
platform = "template";
|
||||
value_template = "{{ state_attr('sensor.creality_print_stats','state') == 'standby' }}";
|
||||
}
|
||||
];
|
||||
action = {
|
||||
service = "notify.mobile_app_dominiks_iphone";
|
||||
data = {
|
||||
message = "Printer status changed to {{ state_attr('sensor.creality_print_stats','state') }}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,29 +1,49 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.home-assistant.extraComponents = [
|
||||
"daikin"
|
||||
"enocean"
|
||||
];
|
||||
# services.home-assistant.customComponents = [
|
||||
# (pkgs.callPackage ./custom-components/scheduler.nix { })
|
||||
# ];
|
||||
services.home-assistant.customLovelaceModules = [
|
||||
(pkgs.callPackage ./custom-components/lovelace-scheduler.nix { })
|
||||
];
|
||||
|
||||
services.home-assistant.config = {
|
||||
sensor = [
|
||||
{
|
||||
name = "Living Room Window Handle";
|
||||
name = "Living Room Window Handle 2";
|
||||
platform = "enocean";
|
||||
id = [ 129 0 227 53 ];
|
||||
device_class = "windowhandle";
|
||||
}
|
||||
{
|
||||
name = "Living Room Window Handle 1";
|
||||
platform = "enocean";
|
||||
id = [ 129 0 229 8 ];
|
||||
device_class = "windowhandle";
|
||||
}
|
||||
];
|
||||
"automation ac_livingroom" = {
|
||||
alias = "ac_livingroom";
|
||||
hide_entity = true;
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "sensor.windowhandle_living_room_window_handle";
|
||||
to = [ "open" "tilt" ];
|
||||
};
|
||||
trigger = [
|
||||
{
|
||||
platform = "state";
|
||||
entity_id = "sensor.windowhandle_living_room_window_handle_1";
|
||||
to = [ "open" "tilt" ];
|
||||
}
|
||||
{
|
||||
platform = "state";
|
||||
entity_id = "sensor.windowhandle_living_room_window_handle_2";
|
||||
to = [ "open" "tilt" ];
|
||||
}
|
||||
];
|
||||
action = {
|
||||
service = "climate.set_hvac_mode";
|
||||
target = {
|
||||
entity_id = "climate.livingroom_ac";
|
||||
entity_id = "climate.living_room";
|
||||
};
|
||||
data = {
|
||||
hvac_mode = "off";
|
||||
@@ -32,12 +52,11 @@
|
||||
};
|
||||
"automation ac_eco" = {
|
||||
alias = "ac_eco";
|
||||
hide_entity = true;
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"climate.livingroom_ac"
|
||||
"climate.bedroom_ac"
|
||||
"climate.living_room"
|
||||
"climate.bedroom"
|
||||
];
|
||||
to = [
|
||||
"heat"
|
||||
@@ -56,7 +75,6 @@
|
||||
};
|
||||
"automation bedroom_ac_on" = {
|
||||
alias = "bedroom ac on";
|
||||
hide_entity = true;
|
||||
trigger = {
|
||||
platform = "time";
|
||||
at = "00:30:00";
|
||||
@@ -69,7 +87,7 @@
|
||||
{
|
||||
service = "climate.set_hvac_mode";
|
||||
target = {
|
||||
entity_id = "climate.bedroom_ac";
|
||||
entity_id = "climate.bedroom";
|
||||
};
|
||||
data = {
|
||||
hvac_mode = "cold";
|
||||
@@ -82,7 +100,6 @@
|
||||
};
|
||||
"automation bedroom_ac_off" = {
|
||||
alias = "bedroom ac on";
|
||||
hide_entity = true;
|
||||
trigger = {
|
||||
platform = "template";
|
||||
value_template = ''
|
||||
@@ -92,7 +109,7 @@
|
||||
action = {
|
||||
service = "climate.set_hvac_mode";
|
||||
target = {
|
||||
entity_id = "climate.bedroom_ac";
|
||||
entity_id = "climate.bedroom";
|
||||
};
|
||||
data = {
|
||||
hvac_mode = "off";
|
||||
@@ -50,28 +50,28 @@
|
||||
sensor_low_battery = {
|
||||
name = "Sensor has low battery!";
|
||||
message = ''
|
||||
{%- set domains = ['sensor', 'battery'] -%}
|
||||
{%- set threshold = 30 -%}
|
||||
{%- set exclude_entities = ['sensor.sensors_lowest_battery_level','sensor.dominiks_iphone_battery_level'] -%}
|
||||
{% set domains = ['sensor', 'battery'] %}
|
||||
{% set threshold = 30 %}
|
||||
{%- set exclude_entities = ['sensor.sensors_lowest_battery_level','sensor.dominiks_iphone_battery_level','sensor.roborock_s8_pro_ultra_battery'] -%}
|
||||
Sensors are below 50% battery:
|
||||
{%- for domain in domains -%}
|
||||
{%- for item in states[domain] -%}
|
||||
{%- if item.entity_id not in exclude_entities -%}
|
||||
{%- if item.attributes.battery_level is defined -%}
|
||||
{%- set level = item.attributes.battery_level|int -%}
|
||||
{% for domain in domains %}
|
||||
{% for item in states[domain] %}
|
||||
{% if item.entity_id not in exclude_entities %}
|
||||
{% if item.attributes.battery_level is defined %}
|
||||
{% set level = item.attributes.battery_level|int %}
|
||||
{% if level > 0 and level < threshold %}
|
||||
- {{ item.attributes.friendly_name }} ({{ item.attributes['battery_level']|int}}%)
|
||||
{%- endif -%}
|
||||
{%- endif -%}
|
||||
{%- if item.attributes.device_class is defined and item.attributes.device_class == 'battery' -%}
|
||||
{%- set level = item.state|int -%}
|
||||
{% endif %}
|
||||
{% if item.attributes.device_class is defined and item.attributes.device_class == 'battery' %}
|
||||
{% set level = item.state|int %}
|
||||
{% if level > 0 and level <= threshold %}
|
||||
- {{ item.attributes.friendly_name }} ({{ item.state|int }}%)
|
||||
{%- endif -%}
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- endfor -%}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
'';
|
||||
entity_id = "binary_sensor.sensor_low_battery";
|
||||
state = "on";
|
||||
@@ -0,0 +1,18 @@
|
||||
{ stdenv, fetchFromGitHub }:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "ha-bermuda";
|
||||
version = "0.7.2"; # Replace with the latest version
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "agittins";
|
||||
repo = "bermuda";
|
||||
rev = "v${version}";
|
||||
sha256 = "sha256-FBmZc2I9JoLAQ55yasa0i+SM0dMg2IbR3AaKgEybRu8="; # Replace with the correct SHA256 hash
|
||||
};
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r custom_components/bermuda $out/
|
||||
'';
|
||||
}
|
||||
27
hosts/fw/modules/home-assistant/custom-components/hacs.nix
Normal file
27
hosts/fw/modules/home-assistant/custom-components/hacs.nix
Normal file
@@ -0,0 +1,27 @@
|
||||
{ lib
|
||||
, buildHomeAssistantComponent
|
||||
, fetchFromGitHub
|
||||
}:
|
||||
|
||||
buildHomeAssistantComponent rec {
|
||||
owner = "hacs";
|
||||
domain = "hacs";
|
||||
version = "2.0.1"; # Replace with the latest version
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "hacs";
|
||||
repo = "integration";
|
||||
rev = version;
|
||||
sha256 = ""; # You'll need to fill this in
|
||||
};
|
||||
|
||||
propagatedBuildInputs = [
|
||||
# Add any required dependencies here
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://github.com/hacs/integration";
|
||||
license = licenses.mit;
|
||||
description = "HACS (Home Assistant Community Store)";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{ lib
|
||||
, buildNpmPackage
|
||||
, fetchFromGitHub
|
||||
}:
|
||||
|
||||
buildNpmPackage rec {
|
||||
pname = "lovelace-scheduler";
|
||||
version = "3.2.13";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "nielsfaber";
|
||||
repo = "scheduler-card";
|
||||
rev = "v${version}";
|
||||
hash = "sha256-LFKOTu0SBeHpf8Hjvsgc/xOUux9d4lBCshdD9u7eO5o=";
|
||||
};
|
||||
|
||||
npmDepsHash = "sha256-JJexFmVbDHi2JCiCpcDupzVf0xfwy+vqWILq/dLVcBo=";
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir $out
|
||||
cp card-mod.js $out
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
passthru.entrypoint = "card-mod.js";
|
||||
|
||||
meta = with lib; {
|
||||
description = "This is a Lovelace card for Home Assistant that can be used to create a time schedule for your smart devices. You can create new rules, modify existing rules and temporarily disable rules.";
|
||||
homepage = "https://github.com/nielsfaber/scheduler-card";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.all;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
buildHomeAssistantComponent,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
gitUpdater,
|
||||
}:
|
||||
|
||||
buildHomeAssistantComponent rec {
|
||||
owner = "nielsfaber";
|
||||
domain = "scheduler";
|
||||
version = "3.3.7";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "nielsfaber";
|
||||
repo = "scheduler-component";
|
||||
rev = "refs/tags/${version}";
|
||||
hash = "sha256-zXO2UDLhSTOemzsO9G5ZUzr50Zg8kDW/aObn6Y3j70k=";
|
||||
};
|
||||
|
||||
passthru.updateScript = gitUpdater {
|
||||
ignoredVersions = "(Alpha|Beta|alpha|beta).*";
|
||||
};
|
||||
|
||||
meta = {
|
||||
changelog = "https://github.com/nielsfaber/scheduler-component/releases/tag/${version}";
|
||||
description = "This is a custom component for Home Assistant, that is used for controlling your existing devices based on time. It works nicely together with the Lovelace scheduler card.";
|
||||
homepage = "https://github.com/nielsfaber/scheduler-component";
|
||||
license = lib.licenses.agpl3Only;
|
||||
};
|
||||
}
|
||||
292
hosts/fw/modules/home-assistant/default.nix
Normal file
292
hosts/fw/modules/home-assistant/default.nix
Normal file
@@ -0,0 +1,292 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
domain = "home-assistant.cloonar.com";
|
||||
pkgs-with-home-assistant = import (builtins.fetchGit {
|
||||
name = "new-home-assistant";
|
||||
url = "https://github.com/nixos/nixpkgs/";
|
||||
rev = "18dd725c29603f582cf1900e0d25f9f1063dbf11";
|
||||
}) {};
|
||||
networkPrefix = config.networkPrefix;
|
||||
in
|
||||
{
|
||||
users.users.hass = {
|
||||
home = "/var/lib/hass";
|
||||
createHome = true;
|
||||
group = "hass";
|
||||
uid = config.ids.uids.hass;
|
||||
extraGroups = [ "dialout" ];
|
||||
};
|
||||
users.groups.hass.gid = config.ids.gids.hass;
|
||||
|
||||
security.acme.certs."${domain}" = {
|
||||
group = "nginx";
|
||||
};
|
||||
|
||||
sops.secrets."home-assistant-secrets.yaml" = {
|
||||
owner = "hass";
|
||||
restartUnits = [ "container@hass.service" ];
|
||||
};
|
||||
|
||||
sops.secrets."home-assistant-ldap" = {
|
||||
owner = "hass";
|
||||
};
|
||||
|
||||
containers.hass = {
|
||||
autoStart = true;
|
||||
ephemeral = false;
|
||||
privateNetwork = true;
|
||||
hostBridge = "server";
|
||||
hostAddress = "${networkPrefix}.97.1";
|
||||
localAddress = "${networkPrefix}.97.20/24";
|
||||
extraFlags = [
|
||||
"--capability=CAP_NET_ADMIN"
|
||||
"--capability=CAP_MKNOD"
|
||||
];
|
||||
allowedDevices = [
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "char-usb_device";
|
||||
}
|
||||
{
|
||||
modifier = "rwm";
|
||||
node = "char-ttyUSB";
|
||||
}
|
||||
];
|
||||
bindMounts = {
|
||||
"/dev/ttyUSB0" = {
|
||||
hostPath = "/dev/ttyUSB0";
|
||||
isReadOnly = false;
|
||||
};
|
||||
"/etc/localtime" = {
|
||||
hostPath = "/etc/localtime";
|
||||
};
|
||||
"/var/lib/hass" = {
|
||||
hostPath = "/var/lib/hass/";
|
||||
isReadOnly = false;
|
||||
};
|
||||
"/var/lib/acme/hass/" = {
|
||||
hostPath = "${config.security.acme.certs.${domain}.directory}";
|
||||
};
|
||||
"/run/secrets/home-assistant-ldap" = {
|
||||
hostPath = config.sops.secrets."home-assistant-ldap".path;
|
||||
};
|
||||
"/var/lib/hass/secrets.yaml" = {
|
||||
hostPath = config.sops.secrets."home-assistant-secrets.yaml".path;
|
||||
};
|
||||
};
|
||||
config = { lib, config, pkgs, ... }: {
|
||||
networkPrefix = networkPrefix;
|
||||
imports = [
|
||||
../network-prefix.nix
|
||||
./3dprinter.nix
|
||||
./ac.nix
|
||||
# ./aeg.nix
|
||||
./battery.nix
|
||||
./electricity.nix
|
||||
./enocean.nix
|
||||
./ldap.nix
|
||||
./light.nix
|
||||
./locks.nix
|
||||
./multimedia.nix
|
||||
./music.nix
|
||||
./notify.nix
|
||||
./pc.nix
|
||||
./power-saving.nix
|
||||
./pushover.nix
|
||||
./presense.nix
|
||||
./remote.nix
|
||||
./roborock.nix
|
||||
./scenes
|
||||
./scene-switch.nix
|
||||
./shelly.nix
|
||||
./sleep.nix
|
||||
./snapcast.nix
|
||||
];
|
||||
|
||||
networking = {
|
||||
hostName = "home-assistant";
|
||||
useHostResolvConf = false;
|
||||
defaultGateway = {
|
||||
address = "${networkPrefix}.96.1";
|
||||
interface = "eth0";
|
||||
};
|
||||
firewall.enable = false;
|
||||
nameservers = [ "${networkPrefix}.97.1" ];
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.wol
|
||||
pkgs.mariadb
|
||||
];
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."${domain}" = {
|
||||
sslCertificate = "/var/lib/acme/hass/fullchain.pem";
|
||||
sslCertificateKey = "/var/lib/acme/hass/key.pem";
|
||||
sslTrustedCertificate = "/var/lib/acme/hass/chain.pem";
|
||||
forceSSL = true;
|
||||
extraConfig = ''
|
||||
proxy_buffering off;
|
||||
'';
|
||||
locations."/".extraConfig = ''
|
||||
proxy_pass http://127.0.0.1:8123;
|
||||
proxy_set_header Host $host;
|
||||
proxy_redirect http:// https://;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
'';
|
||||
};
|
||||
|
||||
services.home-assistant = {
|
||||
package = pkgs-with-home-assistant.home-assistant;
|
||||
enable = true;
|
||||
};
|
||||
|
||||
services.home-assistant.extraComponents = [
|
||||
"mobile_app"
|
||||
"backup"
|
||||
"denonavr"
|
||||
"androidtv"
|
||||
"rainbird"
|
||||
"zha"
|
||||
"tplink_omada"
|
||||
];
|
||||
|
||||
systemd.services.install-hacs = {
|
||||
description = "Install HACS";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
};
|
||||
script = ''
|
||||
set -e
|
||||
HACS_VERSION="2.0.5" # Replace with the latest version
|
||||
HACS_DIR="/var/lib/hass/custom_components/hacs"
|
||||
|
||||
mkdir -p "$HACS_DIR"
|
||||
${pkgs.curl}/bin/curl -L "https://github.com/hacs/integration/releases/download/$HACS_VERSION/hacs.zip" -o /tmp/hacs.zip
|
||||
${pkgs.unzip}/bin/unzip -o /tmp/hacs.zip -d "$HACS_DIR"
|
||||
rm /tmp/hacs.zip
|
||||
chown -R hass:hass "$HACS_DIR"
|
||||
'';
|
||||
};
|
||||
|
||||
services.home-assistant.extraPackages = ps: with ps; [
|
||||
mysqlclient
|
||||
];
|
||||
|
||||
services.mysql = {
|
||||
enable = true;
|
||||
package = pkgs.mariadb;
|
||||
ensureDatabases = [ "hass" ];
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "hass";
|
||||
ensurePermissions = {
|
||||
"hass.*" = "ALL PRIVILEGES";
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
services.mysqlBackup = {
|
||||
enable = true;
|
||||
databases = [ "hass" ];
|
||||
};
|
||||
|
||||
services.home-assistant.config =
|
||||
let
|
||||
hiddenEntities = [
|
||||
"sensor.last_boot"
|
||||
"sensor.date"
|
||||
];
|
||||
in
|
||||
{
|
||||
recorder = {
|
||||
db_url = "mysql://hass@localhost/hass?unix_socket=/var/run/mysqld/mysqld.sock";
|
||||
};
|
||||
homeassistant = {
|
||||
name = "Home";
|
||||
latitude = "!secret home_latitude";
|
||||
longitude = "!secret home_longitude";
|
||||
elevation = "!secret home_elevation";
|
||||
unit_system = "metric";
|
||||
currency = "EUR";
|
||||
country = "AT";
|
||||
time_zone = "Europe/Vienna";
|
||||
external_url = "https://${domain}";
|
||||
};
|
||||
zone = {
|
||||
name = "Home";
|
||||
latitude = "!secret home_latitude";
|
||||
longitude = "!secret home_longitude";
|
||||
radius = 35;
|
||||
icon = "mdi:account-multiple";
|
||||
|
||||
};
|
||||
automation = "!include automations.yaml";
|
||||
frontend = { };
|
||||
http = {
|
||||
use_x_forwarded_for = true;
|
||||
trusted_proxies = [
|
||||
"127.0.0.1"
|
||||
"::1"
|
||||
];
|
||||
};
|
||||
api = { };
|
||||
history.exclude = {
|
||||
entities = hiddenEntities;
|
||||
domains = [
|
||||
"automation"
|
||||
"updater"
|
||||
];
|
||||
};
|
||||
"map" = { };
|
||||
enocean = {
|
||||
device = "/dev/ttyUSB0";
|
||||
};
|
||||
# logbook.exclude.entities = "hiddenEntities";
|
||||
logger = {
|
||||
default = "info";
|
||||
};
|
||||
|
||||
#icloud = {
|
||||
# username = "!secret icloud_email";
|
||||
# password = "!secret icloud_password";
|
||||
# with_family = true;
|
||||
#};
|
||||
network = { };
|
||||
zeroconf = { };
|
||||
system_health = { };
|
||||
default_config = { };
|
||||
system_log = { };
|
||||
sensor = [
|
||||
{
|
||||
platform = "template";
|
||||
sensors.bedtime_alarm = {
|
||||
friendly_name = "Bedtime Alarm";
|
||||
value_template = "09:00";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
services.mosquitto = {
|
||||
enable = true;
|
||||
listeners = [
|
||||
{
|
||||
acl = [ "pattern readwrite #" ];
|
||||
omitPasswordAuth = true;
|
||||
settings.allow_anonymous = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
users.users.hass.extraGroups = [ "dialout" ];
|
||||
system.stateVersion = "23.05";
|
||||
};
|
||||
};
|
||||
}
|
||||
28
hosts/fw/modules/home-assistant/electricity.nix
Normal file
28
hosts/fw/modules/home-assistant/electricity.nix
Normal file
@@ -0,0 +1,28 @@
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
in {
|
||||
services.home-assistant.customComponents = with pkgs.home-assistant-custom-components; [
|
||||
epex_spot
|
||||
];
|
||||
|
||||
services.home-assistant.config = {
|
||||
sensor = [
|
||||
{
|
||||
platform = "template";
|
||||
sensors = {
|
||||
electricity_price = {
|
||||
friendly_name = "Current Price of electricity";
|
||||
unit_of_measurement = "EUR/kWh";
|
||||
value_template = ''
|
||||
{{ ((states('sensor.epex_spot_data_price') | float ) + (0.0149 + 0.074 + 0.007 + 0.0074 + 0.0006)) | float }}
|
||||
'';
|
||||
entity_id = [
|
||||
"sensor.epex_spot_data_price"
|
||||
"sensor.time"
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
26
hosts/fw/modules/home-assistant/enocean.nix
Normal file
26
hosts/fw/modules/home-assistant/enocean.nix
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
services.home-assistant.config = {
|
||||
"binary_sensor pc_0" = [
|
||||
{
|
||||
platform = "enocean";
|
||||
id = [ 254 235 105 198 ];
|
||||
name = "enocean_switch_pc";
|
||||
}
|
||||
];
|
||||
"binary_sensor bed_1" = [
|
||||
{
|
||||
platform = "enocean";
|
||||
id = [ 254 207 162 105 ];
|
||||
name = "enocean_switch_bed_1";
|
||||
}
|
||||
];
|
||||
sensor = [
|
||||
{
|
||||
name = "Bathroom HT";
|
||||
platform = "enocean";
|
||||
id = [ 5 41 146 251 ];
|
||||
device_class = "temperature";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -20,7 +20,7 @@ let
|
||||
CLIENT="ldapsearch"
|
||||
SERVER="ldaps://ldap.cloonar.com:636"
|
||||
USERDN="cn=home-assistant,ou=system,ou=users,dc=cloonar,dc=com"
|
||||
PW="$(<${config.sops.secrets.home-assistant-ldap.path})"
|
||||
PW="$(</run/secrets/home-assistant-ldap)"
|
||||
BASEDN="ou=users,dc=cloonar,dc=com"
|
||||
SCOPE="one"
|
||||
FILTER="(&(objectClass=cloonarUser)(memberOf=cn=HomeAssistant,ou=groups,dc=cloonar,dc=com)(mail=$(ldap_dn_escape "$username")))"
|
||||
@@ -52,11 +52,5 @@ in
|
||||
meta = true;
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
sops.secrets.home-assistant-ldap = {
|
||||
sopsFile = ./secrets.yaml;
|
||||
owner = "hass";
|
||||
};
|
||||
}
|
||||
|
||||
522
hosts/fw/modules/home-assistant/light.nix
Normal file
522
hosts/fw/modules/home-assistant/light.nix
Normal file
@@ -0,0 +1,522 @@
|
||||
{
|
||||
services.home-assistant.extraComponents = [
|
||||
"deconz"
|
||||
"shelly"
|
||||
"sun"
|
||||
"nanoleaf"
|
||||
];
|
||||
|
||||
services.home-assistant.config = {
|
||||
homeassistant = {
|
||||
customize_domain = {
|
||||
light = {
|
||||
assumed_state = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
customize_domain = {
|
||||
light = {
|
||||
assumed_state = false;
|
||||
};
|
||||
};
|
||||
"automation light_sunrise" = {
|
||||
alias = "light_sunrise";
|
||||
trigger = {
|
||||
platform = "sun";
|
||||
event = "sunrise";
|
||||
};
|
||||
action = {
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ states.light | selectattr(\"state\",\"eq\",\"on\") | map(attribute=\"entity_id\") | list }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 254;
|
||||
color_temp = 250;
|
||||
};
|
||||
};
|
||||
};
|
||||
"automation light_sunset" = {
|
||||
alias = "light_sunset";
|
||||
trigger = {
|
||||
platform = "sun";
|
||||
event = "sunset";
|
||||
};
|
||||
action = {
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ states.light | selectattr(\"state\",\"eq\",\"on\") | map(attribute=\"entity_id\") | list }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 30;
|
||||
color_temp = 450;
|
||||
};
|
||||
};
|
||||
};
|
||||
"automation light_on" = {
|
||||
alias = "light_on";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"light.bedroom_lights"
|
||||
"light.kitchen_lights"
|
||||
"light.livingroom_lights"
|
||||
"light.hallway_lights"
|
||||
"light.bathroom_lights"
|
||||
"light.toilet_lights"
|
||||
"light.storage_lights"
|
||||
];
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [ "{{ is_state('automation.light_sunset', 'off') }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 100;
|
||||
color_temp = 250;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.toilet_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 20;
|
||||
color_temp = 450;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.hallway_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 30;
|
||||
rgbw_color = [ 255 126 0 255 ];
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.bathroom_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 30;
|
||||
rgbw_color = [ 255 126 0 255 ];
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.livingroom_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 30;
|
||||
color_temp = 450;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.bedroom_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "light.bedroom_lights";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 20;
|
||||
rgbw_color = [ 255 126 0 255 ];
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') < 5 and trigger.entity_id == 'light.kitchen_lights' }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "light.kitchen_lights";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 30;
|
||||
color_temp = 450;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state_attr('sun.sun', 'elevation') > 4 }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.turn_on";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
data = {
|
||||
brightness_pct = 100;
|
||||
color_temp = 250;
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bathroom light small" = {
|
||||
alias = "bathroom light small";
|
||||
mode = "restart";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"light.bathroom_switch_channel_1"
|
||||
];
|
||||
from = "on";
|
||||
to = "off";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "switch.turn_off";
|
||||
target = {
|
||||
entity_id = "switch.bathroom_small";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bathroom light" = {
|
||||
alias = "bathroom light";
|
||||
mode = "restart";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"light.bathroom_switch_channel_1"
|
||||
];
|
||||
from = "off";
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
delay = 3600;
|
||||
}
|
||||
{
|
||||
service = "light.turn_off";
|
||||
target = {
|
||||
entity_id = "light.bathroom_switch_channel_1";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bed_led" = {
|
||||
alias = "bed_led";
|
||||
mode = "restart";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"light.bedroom_led"
|
||||
];
|
||||
from = "off";
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
delay = 10800;
|
||||
}
|
||||
{
|
||||
service = "light.turn_off";
|
||||
target = {
|
||||
entity_id = "{{ trigger.entity_id }}";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation hallway_motion" = {
|
||||
alias = "Hallway Motion";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.hallway_motion_motion";
|
||||
};
|
||||
action = {
|
||||
service_template = "light.turn_{{ trigger.to_state.state }}";
|
||||
target = {
|
||||
entity_id = "light.hallway_lights";
|
||||
};
|
||||
};
|
||||
};
|
||||
"automation bedroom light" = {
|
||||
alias = "bedroom light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 1;
|
||||
onoff = 1;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA136D";
|
||||
click_type = "double";
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bedroom_lights";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bed light" = {
|
||||
alias = "bed light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 0;
|
||||
onoff = 1;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA136D";
|
||||
click_type = "triple";
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bedroom_bed";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation reading 1 light" = {
|
||||
alias = "reading 1 light";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 207 162 105 ];
|
||||
which = 0;
|
||||
onoff = 0;
|
||||
pushed = 1;
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
target = {
|
||||
entity_id = "light.bed_reading_1";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bed_button_2" = {
|
||||
alias = "bed_button_2";
|
||||
trigger = {
|
||||
platform = "event";
|
||||
event_type = "shelly.click";
|
||||
event_data = {
|
||||
device = "shellybutton1-E8DB84AA136D";
|
||||
};
|
||||
};
|
||||
action = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [ "{{ trigger.event.data.click_type == \"single\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "light.toggle";
|
||||
entity_id = "light.bed_reading_2";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
light = [
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Livingroom Switch";
|
||||
entity_id = "switch.livingroom_switch";
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Livingroom Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.livingroom_switch"
|
||||
"light.living_bulb_1"
|
||||
"light.living_bulb_2"
|
||||
"light.living_bulb_3"
|
||||
"light.living_bulb_4"
|
||||
"light.living_bulb_5"
|
||||
"light.living_bulb_6"
|
||||
# "light.living_room"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Kitchen Switch";
|
||||
entity_id = "switch.kitchen_switch";
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Kitchen Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.kitchen_switch"
|
||||
"light.kitchen_bulb_1"
|
||||
"light.kitchen"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Bedroom Switch";
|
||||
entity_id = "switch.bedroom_switch";
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Bedroom Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.bedroom_switch"
|
||||
"light.bedroom_bulb_1"
|
||||
"light.bedroom_bulb_2"
|
||||
"light.bedroom_bulb_3"
|
||||
"light.bedroom_bulb_4"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Bathroom Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.bathroom_switch"
|
||||
"light.bathroom_bulb_1"
|
||||
"light.bathroom_bulb_2"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Hallway Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.hallway_light_switch_mini_switch"
|
||||
"light.hallway_bulb_1"
|
||||
"light.hallway_bulb_2"
|
||||
];
|
||||
}
|
||||
{
|
||||
platform = "template";
|
||||
lights = {
|
||||
hallway_group_proxy = {
|
||||
friendly_name = "Hallway Lights (Proxy)";
|
||||
# follow the real group’s on/off state
|
||||
value_template = "{{ is_state('light.hallway_lights','on') }}";
|
||||
turn_on = {
|
||||
service = "light.turn_on";
|
||||
data = { entity_id = "light.hallway_lights"; };
|
||||
};
|
||||
turn_off = {
|
||||
service = "light.turn_off";
|
||||
data = { entity_id = "light.hallway_lights"; };
|
||||
};
|
||||
# brightness support
|
||||
set_level = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
brightness = "{{ brightness }}";
|
||||
};
|
||||
};
|
||||
# color temperature support (if you have CT-capable bulbs)
|
||||
set_temperature = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
color_temp = "{{ color_temp }}";
|
||||
};
|
||||
};
|
||||
# RGB color support
|
||||
set_color = {
|
||||
service = "light.turn_on";
|
||||
data_template = {
|
||||
entity_id = "light.hallway_lights";
|
||||
rgb_color = [ "{{ red }}" "{{ green }}" "{{ blue }}" ];
|
||||
};
|
||||
};
|
||||
# always report as “available”
|
||||
availability_template = "true";
|
||||
# declare which color modes you need
|
||||
supported_color_modes = [ "brightness" "color_temp" "rgb" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "switch";
|
||||
name = "Toilet Switch";
|
||||
entity_id = "switch.toilet";
|
||||
}
|
||||
{
|
||||
platform = "group";
|
||||
name = "Toilet Lights";
|
||||
all = true;
|
||||
entities = [
|
||||
"light.toilet_switch"
|
||||
"light.toilet_bulb"
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
{
|
||||
let
|
||||
devices = [
|
||||
"device_tracker.dominiks_iphone"
|
||||
"device_tracker.dominiks_mp01"
|
||||
];
|
||||
in {
|
||||
services.home-assistant.extraComponents = [
|
||||
"nuki"
|
||||
];
|
||||
@@ -7,16 +12,19 @@
|
||||
"automation house_door" = {
|
||||
alias = "house_door";
|
||||
mode = "restart";
|
||||
hide_entity = true;
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = [
|
||||
"person.dominik"
|
||||
];
|
||||
entity_id = devices;
|
||||
from = "not_home";
|
||||
to = "home";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "script.turn_on";
|
||||
target = {
|
||||
entity_id = "script.turn_on_circuits";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "lock.unlock";
|
||||
target = {
|
||||
@@ -57,6 +65,60 @@
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [ "{{ state.house_door != \"unlocked\" }}" ];
|
||||
sequence = [
|
||||
{
|
||||
service = "notify.mobile_app_dominiks_iphone";
|
||||
data = {
|
||||
message = "Someone is at the door!";
|
||||
actions = [
|
||||
{
|
||||
action = "action_open";
|
||||
title = "Open house door";
|
||||
}
|
||||
{
|
||||
action = "action_ignore";
|
||||
title = "Ignore";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
{
|
||||
wait_for_trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "mobile_app_notification_action";
|
||||
event_data = {
|
||||
action = "{{ action_open }}";
|
||||
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "mobile_app_notification_action";
|
||||
event_data = {
|
||||
action = "{{ action_ignore }}";
|
||||
};
|
||||
}
|
||||
|
||||
];
|
||||
}
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = "{{ wait.trigger.event.data.action == action_open }}";
|
||||
sequence = [{
|
||||
service = "lock.open";
|
||||
target = {
|
||||
entity_id = "lock.house_door";
|
||||
};
|
||||
}];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
512
hosts/fw/modules/home-assistant/multimedia.nix
Normal file
512
hosts/fw/modules/home-assistant/multimedia.nix
Normal file
@@ -0,0 +1,512 @@
|
||||
{
|
||||
services.home-assistant.extraComponents = [
|
||||
"ping"
|
||||
"broadlink"
|
||||
"androidtv"
|
||||
"samsungtv"
|
||||
"apple_tv"
|
||||
];
|
||||
services.home-assistant.config = {
|
||||
ios = {
|
||||
actions = [
|
||||
{
|
||||
name = "Home Cinema";
|
||||
label.text = "Home Cinema";
|
||||
icon = {
|
||||
icon = "theater";
|
||||
color = "#ffffff";
|
||||
};
|
||||
show_in_watch = true;
|
||||
}
|
||||
];
|
||||
};
|
||||
binary_sensor = [
|
||||
{
|
||||
name = "xbox";
|
||||
platform = "ping";
|
||||
host = "xbox.cloonar.multimedia";
|
||||
count = 2;
|
||||
scan_interval = 5;
|
||||
}
|
||||
{
|
||||
name = "ps5";
|
||||
platform = "ping";
|
||||
host = "ps5.cloonar.multimedia";
|
||||
count = 2;
|
||||
scan_interval = 5;
|
||||
}
|
||||
{
|
||||
name = "steamdeck";
|
||||
platform = "ping";
|
||||
host = "steamdeck.cloonar.com";
|
||||
count = 2;
|
||||
scan_interval = 5;
|
||||
}
|
||||
{
|
||||
platform = "template";
|
||||
sensors = {
|
||||
multimedia_device_on = {
|
||||
friendly_name = "Any multimedia device on";
|
||||
device_class = "connectivity";
|
||||
value_template = ''
|
||||
{% if ((states('media_player.living_room') != 'off') and (states('media_player.living_room') != 'standby')) or is_state('binary_sensor.ps5', 'on') or is_state('binary_sensor.xbox', 'on') or (is_state('binary_sensor.steamdeck', 'on') and (states('sensor.steamdeck_power') | float(default=0) > 5)) %}
|
||||
on
|
||||
{% else %}
|
||||
off
|
||||
{% endif %}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
script = {
|
||||
turn_on_tv = {
|
||||
sequence = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "switch.tv_switch";
|
||||
state = "off";
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
{
|
||||
service = "automation.turn_off";
|
||||
target = {
|
||||
entity_id = "automation.all_multimedia_off";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "switch.turn_on";
|
||||
target = {
|
||||
entity_id = "switch.tv_switch";
|
||||
};
|
||||
}
|
||||
{
|
||||
delay = 10;
|
||||
}
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 1;
|
||||
delay_secs = 0.4;
|
||||
hold_secs = 0;
|
||||
command = "b64:JgBOAJaSFREVNRU2FTUVERURFRAVERURFTUVNhU1FREVERUQFREVERUQFTYVNRURFREVEBURFTYVNRURFRAVNhU1FTYVNRUABfmWkhURFQANBQAAAAAAAAAAAAA=";
|
||||
};
|
||||
}
|
||||
{
|
||||
delay = 10;
|
||||
}
|
||||
{
|
||||
service = "remote.turn_on";
|
||||
target = {
|
||||
entity_id = "remote.living_room";
|
||||
};
|
||||
}
|
||||
{
|
||||
delay = 120;
|
||||
}
|
||||
{
|
||||
service = "automation.turn_on";
|
||||
target = {
|
||||
entity_id = "automation.all_multimedia_off";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "unavailable";
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 1;
|
||||
delay_secs = 0.4;
|
||||
hold_secs = 0;
|
||||
command = "b64:JgBOAJaSFREVNRU2FTUVERURFRAVERURFTUVNhU1FREVERUQFREVERUQFTYVNRURFREVEBURFTYVNRURFRAVNhU1FTYVNRUABfmWkhURFQANBQAAAAAAAAAAAAA=";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "off";
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
{
|
||||
service = "media_player.turn_on";
|
||||
target = {
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
"automation steamdeck on" = {
|
||||
alias = "steamdeck on";
|
||||
trigger = {
|
||||
platform = "template";
|
||||
value_template = "{% if is_state('binary_sensor.steamdeck', 'on') and (states('sensor.steamdeck_power') | float > 5) %}true{% endif %}";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "denonavr.get_command";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
data = {
|
||||
command = "/goform/formiPhoneAppDirect.xml?SIDVD";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation xbox on" = {
|
||||
alias = "xbox on";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.xbox";
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "denonavr.get_command";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
data = {
|
||||
command = "/goform/formiPhoneAppDirect.xml?SIGAME";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation firetv on" = {
|
||||
alias = "firetv on";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "media_player.fire_tv_firetv_living_cloonar_multimedia";
|
||||
from = "off";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "denonavr.get_command";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
data = {
|
||||
command = "/goform/formiPhoneAppDirect.xml?SIMPLAY";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation ps5 on" = {
|
||||
alias = "ps5 on";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.ps5";
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "denonavr.get_command";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
data = {
|
||||
command = "/goform/formiPhoneAppDirect.xml?SIBD";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation all multimedia off" = {
|
||||
alias = "all multimedia off";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.multimedia_device_on";
|
||||
to = "off";
|
||||
for = "00:00:30";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "media_player.turn_off";
|
||||
target = {
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "denonavr.get_command";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
data = {
|
||||
command = "/goform/formiPhoneAppDirect.xml?PWSTANDBY";
|
||||
};
|
||||
}
|
||||
# silverscreen up
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 2;
|
||||
delay_secs = 1;
|
||||
hold_secs = 0;
|
||||
command = "b64:sgBqAgkaBBoJCRsJHBoKGgoJGgQaCQkaBAgbGwoIHAgcGwkJGwgAARkbCRsJGwkJGgQaCgkaBAgbCRsbCQkbGwkJGgQIGxwJGwkJGxsJCRwIHBoKCBsECBsbCAQIGwkAARgbChoKGgoJGxsJCRoECBsJHBsJCRoEGgkJGwkcGgobCQkbGwkJGwkbGwoIHAkbGwkJGwkAARgbCRsJGwoIGxwJCRsJGwkbGwoIGxwIChoKGhwJGwkJHBsJCRsJGxsJCRsJHBsJCRsJAAEYGwkbCRsKCBscCQkbCRsJGxsJCRwbCQkbCRsbCRsJCRscCQgcCRocCQkbCRsbCQobCQABGBsJGwkbCQkbHAkJGwkbCRsbCQkbGwoJGwkbGwkbCQkbGwoIHAkbGwkJGgobGwkKGwkAARccCRsJGwkJHBsJCRsJGwkbGwkJGxsKCRsIHBsJGwkJGxsKCRoJGxwJCRsJGxsJChsIAAEZGwgcCRsJCRscCQkbCRsJGhwJCRscCQkaChsbCRsJCRscCQgcCRocCQkbCRsbCggcCQABGBsJGwkbCggcGwkJGwkbCRsbCggcGgoJGwkbGwkbCggcGwkJGwkbGwkJHAgcGwkJGwkAARgbChoKGgoJGhwJCRsJGwkcGgoJGxsJCRsJGxsJHAkJGxsJCRsJGhwJCRwJGhwJCRsJAAEYGwoaChsJCRsbCQkaChsJGxwJCRsbCQkbCRsbChsJCRsbCQkbCRsbCgkbCRsbCQkcCAABFwQaChsJGwkJGxsKCBwIHAgcGwkJGxsKCBwIGwQaCRsJCRwaCggcCBwbCQkbCRwaCggcCAAF3AAAAAAAAAAAAAAAAAAA";
|
||||
};
|
||||
}
|
||||
# turn off beamer
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 2;
|
||||
delay_secs = 1;
|
||||
hold_secs = 0;
|
||||
command = "b64:JgDaAAABKZMUERMSExITEhMSExETEhMSExITEhMSExETNxQ2ExITEhMSEzcTNxM3ExITEhM3ExITNxMSEhITEhM3EzcTEhM3EwAFyAABKJQUERMSEhITEhMSExITEhMSEhITEhMSExITNxM3ExITEhMREzcTNxQ3EhITEhM3ExITNxMSExITEhM3EzcTEhM3EwAFyAABKJQUERMSExETEhMSExITEhMSExETEhMSExITNxM3ExITEhMREzcTOBI4ExETEhM3ExITNxMSExITEhM3EzcTEhM3E5IGAA0FAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation all_multimedia_on" = {
|
||||
alias = "all multimedia on";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "binary_sensor.multimedia_device_on";
|
||||
to = "on";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "script.turn_on";
|
||||
target = {
|
||||
entity_id = "script.turn_on_tv";
|
||||
};
|
||||
}
|
||||
{
|
||||
delay = 5;
|
||||
}
|
||||
{
|
||||
service = "androidtv.adb_command";
|
||||
target = {
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
};
|
||||
data = {
|
||||
command = "adb shell am start -a android.intent.action.VIEW -d content://android.media.tv/passthrough/com.mediatek.tvinput%2F.hdmi.HDMIInputService%2FHDMI100004";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bedroom tv off" = {
|
||||
alias = "bedroom tv off";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "media_player.fire_tv_firetv_bedroom_cloonar_multimedia";
|
||||
to = "off";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "media_player.turn_off";
|
||||
target = {
|
||||
entity_id = "media_player.samsung_7_series_55";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation multimedia scene switch" = {
|
||||
alias = "multimedia scene switch";
|
||||
trigger = [
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "button_pressed";
|
||||
event_data = {
|
||||
id = [ 254 235 105 198 ];
|
||||
onoff = 1;
|
||||
};
|
||||
}
|
||||
{
|
||||
platform = "event";
|
||||
event_type = "ios.action_fired";
|
||||
event_data = {
|
||||
actionID = "Home Cinema";
|
||||
};
|
||||
}
|
||||
];
|
||||
action = [
|
||||
{
|
||||
choose = [
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "switch.tv_switch";
|
||||
state = "off";
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
{
|
||||
service = "script.turn_on";
|
||||
target = {
|
||||
entity_id = "script.turn_on_tv";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "or";
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "on";
|
||||
}
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "idle";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
# silver screen down
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 2;
|
||||
delay_secs = 1;
|
||||
hold_secs = 0;
|
||||
command = "b64:sQs0AB0JCxsLGx0IHQgLGh0ICxoLGx0JCxodCQobCxoLAAEXHQgdCR0JCxodCQsbCxsLGx0JCxoAAAAA";
|
||||
};
|
||||
}
|
||||
# turn on beamer
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 1;
|
||||
delay_secs = 0.4;
|
||||
hold_secs = 0;
|
||||
command = "b64:JgAgAQABKZMUERMSExETEhMSExITEhMSExETEhMSExITNxM3ExITERM3EzgSOBM3ExETEhM3ExITEhMSExITERM3EzcTEhM3EwAFyAABKZMTEhMRExITEhMSExITEhMRExITEhMSExITNxM3ExITERM3EzcTNxM3ExITEhM3ExITEhMSExETEhM3EzcTEhM3EwAFyAABKZMUERMRExITEhMSExITERMSExITEhMSExITNxM3ExISEhM3EzcTNxM3ExITEhM3ExITEhMSExETEhM3EzcTEhM3EwAFxwABKZQUERMRFBETEhMSExITEhISExITEhMSExITNxM3ExITERM3EzcTNxM3FBETEhM3ExITEhMSExITERM3EzcTEhM3EwANBQAAAAAAAAAA";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "media_player.turn_off";
|
||||
target = {
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "media_player.turn_on";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
conditions = [
|
||||
{
|
||||
condition = "or";
|
||||
conditions = [
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "off";
|
||||
}
|
||||
{
|
||||
condition = "state";
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
state = "unavailable";
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
sequence = [
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 2;
|
||||
delay_secs = 1;
|
||||
hold_secs = 0;
|
||||
command = "b64:sgBqAgkaBBoJCRsJHBoKGgoJGgQaCQkaBAgbGwoIHAgcGwkJGwgAARkbCRsJGwkJGgQaCgkaBAgbCRsbCQkbGwkJGgQIGxwJGwkJGxsJCRwIHBoKCBsECBsbCAQIGwkAARgbChoKGgoJGxsJCRoECBsJHBsJCRoEGgkJGwkcGgobCQkbGwkJGwkbGwoIHAkbGwkJGwkAARgbCRsJGwoIGxwJCRsJGwkbGwoIGxwIChoKGhwJGwkJHBsJCRsJGxsJCRsJHBsJCRsJAAEYGwkbCRsKCBscCQkbCRsJGxsJCRwbCQkbCRsbCRsJCRscCQgcCRocCQkbCRsbCQobCQABGBsJGwkbCQkbHAkJGwkbCRsbCQkbGwoJGwkbGwkbCQkbGwoIHAkbGwkJGgobGwkKGwkAARccCRsJGwkJHBsJCRsJGwkbGwkJGxsKCRsIHBsJGwkJGxsKCRoJGxwJCRsJGxsJChsIAAEZGwgcCRsJCRscCQkbCRsJGhwJCRscCQkaChsbCRsJCRscCQgcCRocCQkbCRsbCggcCQABGBsJGwkbCggcGwkJGwkbCRsbCggcGgoJGwkbGwkbCggcGwkJGwkbGwkJHAgcGwkJGwkAARgbChoKGgoJGhwJCRsJGwkcGgoJGxsJCRsJGxsJHAkJGxsJCRsJGhwJCRwJGhwJCRsJAAEYGwoaChsJCRsbCQkaChsJGxwJCRsbCQkbCRsbChsJCRsbCQkbCRsbCgkbCRsbCQkcCAABFwQaChsJGwkJGxsKCBwIHAgcGwkJGxsKCBwIGwQaCRsJCRwaCggcCBwbCQkbCRwaCggcCAAF3AAAAAAAAAAAAAAAAAAA";
|
||||
};
|
||||
}
|
||||
# turn off beamer
|
||||
{
|
||||
service = "remote.send_command";
|
||||
target = {
|
||||
entity_id = "remote.rmproplus";
|
||||
};
|
||||
data = {
|
||||
num_repeats = 2;
|
||||
delay_secs = 1;
|
||||
hold_secs = 0;
|
||||
command = "b64:JgDaAAABKZMUERMSExITEhMSExETEhMSExITEhMSExETNxQ2ExITEhMSEzcTNxM3ExITEhM3ExITNxMSEhITEhM3EzcTEhM3EwAFyAABKJQUERMSEhITEhMSExITEhMSEhITEhMSExITNxM3ExITEhMREzcTNxQ3EhITEhM3ExITNxMSExITEhM3EzcTEhM3EwAFyAABKJQUERMSExETEhMSExITEhMSExETEhMSExITNxM3ExITEhMREzcTOBI4ExETEhM3ExITNxMSExITEhM3EzcTEhM3E5IGAA0FAAAAAAAAAAAAAAAAAAA=";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "script.turn_on";
|
||||
target = {
|
||||
entity_id = "script.turn_on_tv";
|
||||
};
|
||||
}
|
||||
{
|
||||
service = "media_player.turn_off";
|
||||
target = {
|
||||
entity_id = "media_player.marantz_sr6015";
|
||||
};
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
{
|
||||
delay = 5;
|
||||
}
|
||||
{
|
||||
service = "androidtv.adb_command";
|
||||
target = {
|
||||
entity_id = "media_player.android_tv_metz_cloonar_multimedia";
|
||||
};
|
||||
data = {
|
||||
command = "adb shell am start -a android.intent.action.VIEW -d content://android.media.tv/passthrough/com.mediatek.tvinput%2F.hdmi.HDMIInputService%2FHDMI100004";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
68
hosts/fw/modules/home-assistant/music.nix
Normal file
68
hosts/fw/modules/home-assistant/music.nix
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
services.home-assistant.extraComponents = [
|
||||
"squeezebox"
|
||||
"slimproto"
|
||||
];
|
||||
services.home-assistant.config = {
|
||||
"automation toilet music" = {
|
||||
alias = "toilet music";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "light.toilet_switch";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "media_player.volume_mute";
|
||||
target = {
|
||||
entity_id = "media_player.music_toilet";
|
||||
};
|
||||
data = {
|
||||
is_volume_muted = "{{ trigger.to_state.state != 'on' }}";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation bathroom music" = {
|
||||
alias = "bathroom music";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "light.bathroom_switch";
|
||||
};
|
||||
action = [
|
||||
{
|
||||
service = "media_player.volume_mute";
|
||||
target = {
|
||||
entity_id = "media_player.music_bathroom";
|
||||
};
|
||||
data = {
|
||||
is_volume_muted = "{{ trigger.to_state.state != 'on' }}";
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
"automation piano" = {
|
||||
alias = "piano";
|
||||
trigger = {
|
||||
platform = "state";
|
||||
entity_id = "media_player.music_piano";
|
||||
attribute = "is_volume_muted";
|
||||
};
|
||||
condition = [
|
||||
{
|
||||
condition = "template";
|
||||
value_template = "{{ trigger.from_state.state != 'unavailable' }}";
|
||||
}
|
||||
{
|
||||
condition = "template";
|
||||
value_template = "{{ state_attr('media_player.music_piano_snapcast_client', 'is_volume_muted') == true or state_attr('media_player.music_piano_snapcast_client', 'is_volume_muted') == false }}";
|
||||
}
|
||||
];
|
||||
action = {
|
||||
service = "switch.turn_on";
|
||||
target = {
|
||||
entity_id = "switch.piano_switch_power";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user