Compare commits
383 Commits
master
...
e38c0c499f
| Author | SHA1 | Date | |
|---|---|---|---|
| e38c0c499f | |||
| 7d254cd8c4 | |||
| 2e66319cfc | |||
| e41f0aff7e | |||
| ab4fa3140e | |||
| 3130ae3dd7 | |||
| fe8ff3d895 | |||
| f4ec839241 | |||
| 5dc83564af | |||
| a68f3a329a | |||
| 74ed204e6b | |||
| 8625000952 | |||
| a8dd82d05f | |||
| 92f55320b5 | |||
| 4ce1945abe | |||
| e29f09a694 | |||
| ecabc0b6c2 | |||
| bcf5ae5da7 | |||
| 0eecea6324 | |||
| 4c0a178646 | |||
| 9c6de9d067 | |||
| 934c02609a | |||
| fb6ee3cb54 | |||
| bce720fcff | |||
| 7f39fc2dc3 | |||
| 819f1691b4 | |||
| 0a6e97f456 | |||
| d220a4588f | |||
| 7dc66abc21 | |||
| e38bf7582d | |||
| afd4970cf4 | |||
| ef59f14372 | |||
| d86eeae4a5 | |||
| 4d7908de4b | |||
| 20b12d42a8 | |||
| 51d40841ce | |||
| 09ed045da9 | |||
| 56881b766a | |||
| 19b95e707d | |||
| e71886c34e | |||
| da999fc37b | |||
| f9c292643d | |||
| bcdc8f0836 | |||
| 66722bd8a4 | |||
| 1aaf21e9c0 | |||
| 4204d68eed | |||
| 4378b13877 | |||
| c041f2e88d | |||
| 61861ff170 | |||
| d64314d68b | |||
| 35e35980b4 | |||
| 0a54a41670 | |||
| d5d4d5a5c3 | |||
| ff0766519c | |||
| 87b57d5011 | |||
| e46a4bf8a1 | |||
| 8b7a9b5141 | |||
| d3cdbdf3e2 | |||
| 1a209f6960 | |||
| 3d5e3fdb36 | |||
| bf7ee34716 | |||
| 70c44f1fa7 | |||
| 2eaa568f5b | |||
| 31d2994997 | |||
| 58e9aa8fad | |||
| 7d5300853b | |||
| e732ecb878 | |||
| 705f5bbd01 | |||
| 06a5d16ee3 | |||
| 5713eaad14 | |||
| b5dcbf08a1 | |||
| 395bddee94 | |||
| d8312b69b8 | |||
| 62757df384 | |||
| f2ea372742 | |||
| f158a80492 | |||
| 0c81c0454d | |||
| 7268ec3704 | |||
| 0dfbee6472 | |||
| 50d682d6f7 | |||
| 3ca4d4fb86 | |||
| 4fae68764c | |||
| 6035397e9b | |||
| 249d027f6b | |||
| 3343a8be6e | |||
| b8fc0971c6 | |||
| 406acf662e | |||
| 970c1214af | |||
| 0316d0b535 | |||
| 305efdbbae | |||
| 6e72841337 | |||
| 29acdc9ad6 | |||
| 04bc83e004 | |||
| 2513b76658 | |||
| 62b7742ef0 | |||
| f23720e151 | |||
| b9730784ba | |||
| 577c167d5a | |||
| 525c6a220b | |||
| bf41ee6e0a | |||
| cfcd6647ae | |||
| 4e2264dc03 | |||
| 3414edf2a8 | |||
| 0d7b84e3bc | |||
| bea9ca602e | |||
| d8a51cd389 | |||
| f93e12accd | |||
| 63a7fd716f | |||
| 799ad9a15d | |||
| 9010b2678e | |||
| 26bdecb8e0 | |||
| ff87e8c411 | |||
| d7ada535d4 | |||
| 7490ef2fe9 | |||
| a7cb54d04c | |||
| 0f4f6a6339 | |||
| 043b333654 | |||
| 683370a71c | |||
| 2724295bcf | |||
| 78a3329271 | |||
| a61f2cfe5e | |||
| ed5c1cc1af | |||
| 8ffe9ad244 | |||
| bf72ac7e97 | |||
| 4a9bb42015 | |||
| 96782b7b51 | |||
| 3d84864382 | |||
| cb96de6125 | |||
| 686a794a69 | |||
| 2ffe187b12 | |||
| 89ab7d56af | |||
| 01f13723a8 | |||
| 9c25e82c79 | |||
| 956239ab79 | |||
| bb2ae34758 | |||
| 13fdfac2ef | |||
| 93a7636c61 | |||
| 6fb3ee1af8 | |||
| 73dc9aee9e | |||
| 71082b6414 | |||
| 65a96c860b | |||
| 9b9254bb93 | |||
| 1beb980b95 | |||
| 082b80f252 | |||
| f4a65afb01 | |||
| 373a3fe3f0 | |||
| 8bbc0eda8f | |||
| 3b506a7888 | |||
| 9a5bfa3f80 | |||
| 5177f9a1f6 | |||
| 49bf4c6d91 | |||
| 9d914ed863 | |||
| 3e6bf6ce7f | |||
| b786098867 | |||
| dfa76b2a19 | |||
| 6dad0d95fb | |||
| 78f4777141 | |||
| 48077aaccc | |||
| 4b4ea7a1d2 | |||
| d1353d3a36 | |||
| 8048c4adff | |||
| 345f94742e | |||
| 777d48bf3d | |||
| 8a08272f68 | |||
| 4729770b59 | |||
| f5d412997e | |||
| 783cbe20e8 | |||
| 9a7e7d677a | |||
| 5b1f05589d | |||
| 00f05cc3db | |||
| bb99ef9a8a | |||
| 33cdebb64a | |||
| 88573ea4aa | |||
| 4ae460f6e3 | |||
| 5a8fd0a275 | |||
| 6204ba5abb | |||
| 00bbd42cab | |||
| 111db5a1ca | |||
| 9d97fd8d87 | |||
| 94fbbdf0d9 | |||
| 43dc59a04f | |||
| e3167ce6e5 | |||
| 532f4b0901 | |||
| ad00891e6b | |||
| 60bd185fa4 | |||
| f08bd1e702 | |||
| d01674e6a4 | |||
| 39abe553f5 | |||
| af5d46b3f0 | |||
| 450ba9229b | |||
| 9903e83f68 | |||
| 3ee41979bd | |||
| b400acd18e | |||
| dd0d823e36 | |||
| fafc1c4664 | |||
| 3bfc5799c7 | |||
| e7423780e8 | |||
| b3041edbdc | |||
| dbe4dfc2bc | |||
| abdc808693 | |||
| 3139fd1d9f | |||
| dc6ca7a0a9 | |||
| 6dd7d112c5 | |||
| 65fa944df8 | |||
| 7d55064a44 | |||
| a1b31b1684 | |||
| d446b24c47 | |||
| 76e09ce5c3 | |||
| 7fd3ba46c9 | |||
| 0eb95197bc | |||
| 38bf9151eb | |||
| 5dd03a947f | |||
| adbd946f50 | |||
| 78dcb215cc | |||
| 744a8c9cf4 | |||
| 00e02f328a | |||
| 7dadd2290f | |||
| 25575a69f3 | |||
| bb16555d4f | |||
| 492597b7ab | |||
| 52cb6b2410 | |||
| 6e77853453 | |||
| 8f4ff74546 | |||
| e74ef30dc6 | |||
| 3516d24868 | |||
| 92def64fea | |||
| be3aab9b48 | |||
| 5cac466f90 | |||
| 8cbfdcc0ea | |||
| 3d0fd7a030 | |||
| 33422320a5 | |||
| 23d9db27e6 | |||
| 20dcbdfdc4 | |||
| 8fb996501c | |||
| b433fd7ea0 | |||
| f0596784a1 | |||
| 5670f11510 | |||
| 0db6ce3dd4 | |||
| 2c16cebf9a | |||
| 497213a2ea | |||
| c73c887a9c | |||
| f7fda629f5 | |||
| 5e112f5df8 | |||
| 510c350b7a | |||
| 8a13b8c8fd | |||
| 0ab2061776 | |||
| 8631eac0d4 | |||
| 9c8a3790c5 | |||
| 70253c9a3a | |||
| 3b7c4d71e7 | |||
| d1b0d7154d | |||
| 7ce48d75f8 | |||
| 6c953563d0 | |||
| ef314495ba | |||
| 1b1deaf721 | |||
| 746779e952 | |||
| 5cabf6c04d | |||
| 3493c14686 | |||
| 19ea8992a0 | |||
| a17a20b7d6 | |||
| eabfbbdc95 | |||
| 86d30ea494 | |||
| bcc755d325 | |||
| 6a2f7cd1fa | |||
| 39495115a2 | |||
| f0d7ca60fe | |||
| 725f6eea30 | |||
| 421cc6e870 | |||
| 653330dd48 | |||
| 54684c0f78 | |||
| c28e7fcfd5 | |||
| a770215f0c | |||
| b3172edbd7 | |||
| 83029b52ea | |||
| 56fa799d53 | |||
| e9ba38f4eb | |||
| bcb8afbbf4 | |||
| 598eae7e71 | |||
| e260e8f7cc | |||
| 7f58985c41 | |||
| 88327be038 | |||
| 609bae352f | |||
| 5f52fc42af | |||
| 4db5cc171b | |||
| 9b045b71f9 | |||
| e800f11103 | |||
| e55437b856 | |||
| cda46a7e13 | |||
| cc7de6c0dd | |||
| e25190422b | |||
| 016db3d06a | |||
| 286555e026 | |||
| dc93bada89 | |||
| 8857835409 | |||
| 30778b1955 | |||
| 791ff77e14 | |||
| 17acafd50a | |||
| 7ddceab11c | |||
| 0abd4fc70e | |||
| f97e451af1 | |||
| a9d64fe059 | |||
| 62abe7b846 | |||
| 0c58befc43 | |||
| 2229cf072b | |||
| 10a0a08dcd | |||
| 60d977110c | |||
| aec4876832 | |||
| 90f588843a | |||
| fb8fa34ff2 | |||
| 29ff6989e5 | |||
| 3dfe3d61f6 | |||
| e607bf09fa | |||
| 1612799fb7 | |||
| 5055983881 | |||
| 1da02a7ad6 | |||
| 6b8e825a4b | |||
| ba6ec7a113 | |||
| 8697b00cfc | |||
| d45307336a | |||
| 1dcb165abf | |||
| abf1e706b7 | |||
| 807ea83892 | |||
| 267d0ec464 | |||
| 2f6806185d | |||
| 8981af315c | |||
| 1f25c4fcad | |||
| db5b88388e | |||
| 2cb814f054 | |||
| d7bdc0722f | |||
| 80eac39b90 | |||
| de08900297 | |||
| b3d040d6fd | |||
| 7c82a5a866 | |||
| cda65a56eb | |||
| d04a78de23 | |||
| 98a26f1f61 | |||
| 11150151f9 | |||
| bfb69b06ea | |||
| f151438969 | |||
| 14c246de3e | |||
| 42f8e839d1 | |||
| 686344e356 | |||
| 34c798b222 | |||
| d5ef2bf551 | |||
| 7258adbcc2 | |||
| c5e9d9c8cb | |||
| d800266fe0 | |||
| 5ab0833d78 | |||
| d680e080ad | |||
| 35db5e4bf6 | |||
| 3bb2b23be5 | |||
| 4280f0df95 | |||
| d39ed7f9fe | |||
| 972e2d2968 | |||
| f64636fe9e | |||
| 546a35f1d2 | |||
| aeea41b0e1 | |||
| 27e42e9010 | |||
| bcaca5038d | |||
| aff923d470 | |||
| 06e5fdf1ab | |||
| c4d14e5aa2 | |||
| 9438280750 | |||
| 4f2391e9b5 | |||
| 647155f859 | |||
| c91b52b448 | |||
| 18b36e43b7 | |||
| c22a769e50 | |||
| a7e754f48d | |||
| 1f1f3c7211 | |||
| 3f9d8902d5 | |||
| 58fc204f46 | |||
| cbba9016cd | |||
| 0bd5bb65f6 | |||
| cc7bd6b1a2 | |||
| e8e2890171 | |||
| 4d1c210986 | |||
| 3e790cd18a | |||
| d02f559635 | |||
| f3bbbeb12d | |||
| c3cdf9858a | |||
| 7cc4c6292b | |||
| e1c484b1c0 |
@@ -1,4 +1,6 @@
|
|||||||
.env
|
.env
|
||||||
result
|
result
|
||||||
|
result-*
|
||||||
rust-toolchain.toml
|
rust-toolchain.toml
|
||||||
target/
|
target/
|
||||||
|
docs/plans
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
keys:
|
||||||
|
- &snuff age1w4hw2ntxrtfqhht63s9lf7nhjxjmdcc927hndn5ygcqqj532qssq4m2m6p
|
||||||
|
- &yukkop age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h
|
||||||
|
- &yukkop-alt age1vv46vn4hsn2lg6jy834cpu40c3mvqklldcm3hjtynrhwtpmlpc8szruz4v
|
||||||
|
- &nrv age1x04u7ftjgx8de2gq596e7frauze764cmn7jjwqnx8szthvfft5qq0tezx6
|
||||||
|
- &bfs-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
|
- &bfs-pol-server age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu
|
||||||
|
- &bfs-new-server age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7
|
||||||
|
- &neuro-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
|
- &games-server age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj
|
||||||
|
- &hectic-lab-server age13h8twnwvgxn04l5ywtru89a6psw5d0uckr2eghxsjp88a5augvsstq5ard
|
||||||
|
- &umbriel-bfs age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt
|
||||||
|
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: sus/home.xray.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *yukkop
|
||||||
|
|
||||||
|
- path_regex: sus/bfs.xray.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *snuff
|
||||||
|
- *yukkop
|
||||||
|
- *bfs-server
|
||||||
|
- *bfs-pol-server
|
||||||
|
- *bfs-new-server
|
||||||
|
|
||||||
|
- path_regex: sus/neuro.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *yukkop
|
||||||
|
- *neuro-server
|
||||||
|
|
||||||
|
- path_regex: sus/games.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *yukkop
|
||||||
|
- *games-server
|
||||||
|
|
||||||
|
- path_regex: sus/hectic-lab.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *nrv
|
||||||
|
- *yukkop
|
||||||
|
- *yukkop-alt
|
||||||
|
- *hectic-lab-server
|
||||||
|
- *umbriel-bfs
|
||||||
|
|
||||||
|
- path_regex: sus/matrix-cluster.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *nrv
|
||||||
|
- *yukkop
|
||||||
|
- *snuff
|
||||||
|
- *yukkop-alt
|
||||||
|
- *hectic-lab-server
|
||||||
|
- *bfs-pol-server
|
||||||
|
- *umbriel-bfs
|
||||||
|
|
||||||
|
- path_regex: sus/sentinella-default.yaml$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *yukkop
|
||||||
|
- *yukkop-alt
|
||||||
|
- *nrv
|
||||||
|
- *bfs-server
|
||||||
|
- *bfs-pol-server
|
||||||
|
- *bfs-new-server
|
||||||
|
- *neuro-server
|
||||||
|
- *games-server
|
||||||
|
- *hectic-lab-server
|
||||||
|
- *umbriel-bfs
|
||||||
|
|
||||||
|
- path_regex: docs/.*\.md$
|
||||||
|
key_groups:
|
||||||
|
- age:
|
||||||
|
- *yukkop
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self
|
||||||
|
}: pkgs.mkShell {
|
||||||
|
buildInputs = (with pkgs; [
|
||||||
|
inotify-tools
|
||||||
|
gdb
|
||||||
|
gcc
|
||||||
|
]) ++ (with self.packages.${system}; [
|
||||||
|
c-hectic
|
||||||
|
nvim-pager
|
||||||
|
watch
|
||||||
|
]);
|
||||||
|
|
||||||
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
{ system, pkgs, self, ... }:
|
||||||
|
(import ./dev { inherit self system pkgs; })
|
||||||
|
// {
|
||||||
|
c = import ./c.nix { inherit self system pkgs; };
|
||||||
|
postgres-c = import ./postgres-c.nix { inherit self system pkgs; };
|
||||||
|
pure-c = import ./pure-c.nix { inherit self system pkgs; };
|
||||||
|
rust = import ./rust.nix { inherit self system pkgs; };
|
||||||
|
haskell = import ./haskell.nix { inherit self system pkgs; };
|
||||||
|
neuro = import ./neuro.nix { inherit self system pkgs; };
|
||||||
|
xmpp = import ./xmpp.nix { inherit self system pkgs; };
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs =
|
||||||
|
(with self.packages.${system}; [
|
||||||
|
nvim-alias
|
||||||
|
nvim-pager
|
||||||
|
])
|
||||||
|
++ (with pkgs; [
|
||||||
|
git
|
||||||
|
jq
|
||||||
|
yq-go
|
||||||
|
curl
|
||||||
|
#(writeScriptBin "hemar-check" ''
|
||||||
|
# ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null vm-postgres 'zsh -c check'
|
||||||
|
#'')
|
||||||
|
]);
|
||||||
|
|
||||||
|
# environment
|
||||||
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
self,
|
||||||
|
pkgs
|
||||||
|
}: {
|
||||||
|
hemar = import ./hemar { inherit self system pkgs; };
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{ pkgs, ... }: pkgs.mkShell {
|
||||||
|
buildInputs = (with pkgs; [
|
||||||
|
dash
|
||||||
|
(pkgs.writeShellScriptBin "letest" ''
|
||||||
|
${pkgs.dash}/bin/dash ${./test.sh} "$@"
|
||||||
|
'')
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/dash
|
||||||
|
|
||||||
|
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
|
||||||
|
if [ "${1:?}" = 'test' ]; then
|
||||||
|
dash "${ROOT_DIR}/package/hemar/test.sh"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self
|
||||||
|
}: self.devShells.${system}.default
|
||||||
|
// (pkgs.mkShell {
|
||||||
|
buildInputs = [pkgs.stack];
|
||||||
|
})
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self,
|
||||||
|
}: pkgs.mkShell {
|
||||||
|
buildInputs = [];
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self
|
||||||
|
}: pkgs.mkShell {
|
||||||
|
buildInputs = (with pkgs; [
|
||||||
|
inotify-tools
|
||||||
|
postgresql_15
|
||||||
|
]) ++ (with self.packages.${system}; [
|
||||||
|
nvim-pager
|
||||||
|
]) ++ (with pkgs; [
|
||||||
|
gdb
|
||||||
|
gcc
|
||||||
|
]);
|
||||||
|
|
||||||
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=${pkgs.gcc}/bin:$PATH
|
||||||
|
export PAGER="${self.packages.${system}.nvim-pager}/bin/pager"
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self
|
||||||
|
}: pkgs.mkShell {
|
||||||
|
buildInputs = (with pkgs; [ inotify-tools ]) ++ (with self.packages.${system}; [ nvim-pager ]) ++ (with pkgs; [ gdb gcc binutils ]);
|
||||||
|
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
export PATH=${pkgs.gcc}/bin:$PATH
|
||||||
|
|
||||||
|
export PAGER="${self.packages.${system}.nvim-pager}/bin/pager"
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
self,
|
||||||
|
pkgs,
|
||||||
|
system
|
||||||
|
}: let
|
||||||
|
rustToolchain =
|
||||||
|
if builtins.pathExists ./rust-toolchain.toml
|
||||||
|
then pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml
|
||||||
|
else pkgs.pkgsBuildHost.rust-bin.stable."1.81.0".default;
|
||||||
|
in
|
||||||
|
self.devShells.${system}.default
|
||||||
|
// (pkgs.mkShell {
|
||||||
|
nativeBuildInputs = [
|
||||||
|
rustToolchain
|
||||||
|
pkgs.pkg-config
|
||||||
|
];
|
||||||
|
})
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
system,
|
||||||
|
pkgs,
|
||||||
|
self,
|
||||||
|
}: let
|
||||||
|
proxychainsConf = pkgs.writeText "proxychains.conf" ''
|
||||||
|
strict_chain
|
||||||
|
proxy_dns
|
||||||
|
tcp_read_time_out 15000
|
||||||
|
tcp_connect_time_out 8000
|
||||||
|
[ProxyList]
|
||||||
|
socks5 127.0.0.1 1080
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Wrapper script for profanity with proxy
|
||||||
|
profanity-proxy = pkgs.writeShellScriptBin "profanity-proxy" ''
|
||||||
|
exec ${pkgs.proxychains-ng}/bin/proxychains4 -f ${proxychainsConf} ${pkgs.profanity}/bin/profanity "$@"
|
||||||
|
'';
|
||||||
|
in pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.profanity
|
||||||
|
pkgs.proxychains-ng
|
||||||
|
profanity-proxy
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo ""
|
||||||
|
echo "=== XMPP DevShell ==="
|
||||||
|
echo ""
|
||||||
|
echo "1. Start SSH SOCKS proxy (in another terminal):"
|
||||||
|
echo " ssh -D 1080 -N neuro"
|
||||||
|
echo ""
|
||||||
|
echo "2. Run profanity with proxy:"
|
||||||
|
echo " profanity-proxy"
|
||||||
|
echo ""
|
||||||
|
echo "3. In profanity:"
|
||||||
|
echo " /connect yukkop@accord.tube"
|
||||||
|
echo ""
|
||||||
|
'';
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,101 @@
|
|||||||
|
# Spec: sentinella-p2p-design
|
||||||
|
|
||||||
|
Scope: feature
|
||||||
|
|
||||||
|
# sentinèlla P2P Design Spec
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
Replace the hub-and-spoke sentinel topology with a fully peer-to-peer model where every node is equal.
|
||||||
|
|
||||||
|
## Topology
|
||||||
|
- Every node runs both `probe` and `watcher`
|
||||||
|
- No privileged coordinator; any node can go down without breaking monitoring of the others
|
||||||
|
- Duplicate Telegram alerts from multiple nodes detecting the same failure are **accepted** (reliability over deduplication)
|
||||||
|
|
||||||
|
## Peer Discovery — DNS multi-A record
|
||||||
|
- One DNS name (e.g. `peers.sentinella.com`) has multiple A records, one per node IP
|
||||||
|
- Configured externally via any DNS registrar (Cloudflare, Namecheap, etc.)
|
||||||
|
- Recommended TTL: **60 seconds** so new nodes propagate quickly
|
||||||
|
- Each watcher resolves the name via `getent hosts $PEERS_DNS` on every poll cycle
|
||||||
|
- Own IP (`$SELF`) is stripped from the result so a node never polls itself
|
||||||
|
- No per-node DNS names needed; IP addresses are used directly in peer URLs
|
||||||
|
|
||||||
|
```
|
||||||
|
peers.sentinella.com A 1.2.3.4 TTL 60
|
||||||
|
peers.sentinella.com A 5.6.7.8 TTL 60
|
||||||
|
peers.sentinella.com A 9.10.11.12 TTL 60
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### watcher (new, replaces sentinel)
|
||||||
|
| Variable | Default | Required | Description |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `PEERS_DNS` | — | yes | DNS name resolving to all peer IPs |
|
||||||
|
| `SELF` | — | yes | This node's own IP; excluded from peer list |
|
||||||
|
| `PEERS_PORT` | `5988` | no | Port all peers listen on |
|
||||||
|
| `PEERS_SCHEME` | `http` | no | URL scheme for peer connections |
|
||||||
|
| `PEERS_TOKEN` | — | no | Single Basic Auth token sent to all peers (replaces per-server TOKENS) |
|
||||||
|
| `TG_TOKEN` | — | yes | Telegram bot token |
|
||||||
|
| `TG_CHAT_ID` | — | yes | Telegram chat ID |
|
||||||
|
| `TIMEOUT` | `5` | no | curl timeout seconds |
|
||||||
|
| `POLLING_INTERVAL_SEC` | `3` | no | Seconds between poll rounds |
|
||||||
|
| `STATE_DIR` | `/var/lib/sentinel` | no | Directory for state files |
|
||||||
|
| `SPAM` | `0` | no | If 1, notify on every poll |
|
||||||
|
|
||||||
|
### probe / router (unchanged)
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `PORT` | `5988` | TCP port to listen on |
|
||||||
|
| `URLS` | — | Space-separated URLs to health-check |
|
||||||
|
| `VOLUMES` | all from df -P | Mount points to report |
|
||||||
|
| `TIMEOUT` | `5` | curl timeout |
|
||||||
|
| `AUTH_FILE` | — | Path to user:pass auth file |
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
### resolve_peers() in watcher.sh
|
||||||
|
```sh
|
||||||
|
resolve_peers() {
|
||||||
|
getent hosts "$PEERS_DNS" \
|
||||||
|
| awk '{print $1}' \
|
||||||
|
| grep -v "^${SELF}$" \
|
||||||
|
| awk -v s="$PEERS_SCHEME" -v p="$PEERS_PORT" '{print s"://"$1":"p}'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Called at the top of every outer poll loop iteration — no restart needed when DNS changes.
|
||||||
|
|
||||||
|
### Auth simplification
|
||||||
|
- Old: per-server CSV `TOKENS` aligned with `SERVERS`
|
||||||
|
- New: single optional `PEERS_TOKEN`; either all peers require auth or none do
|
||||||
|
|
||||||
|
### State files
|
||||||
|
- Unchanged: `$STATE_DIR/$(cksum url).state` contains last known state string
|
||||||
|
- Format: `up:N/M:200` or `down:0/0:000`
|
||||||
|
|
||||||
|
## Binaries
|
||||||
|
| Old name | New name | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `sentinel` | `watcher` | Polls peers, sends alerts |
|
||||||
|
| `probe` | `probe` | socat TCP listener (unchanged) |
|
||||||
|
| `router` | `router` | HTTP handler (unchanged + auth bug fixed) |
|
||||||
|
| `base64` | `base64` | awk base64 util (unchanged) |
|
||||||
|
|
||||||
|
## NixOS Module Options
|
||||||
|
```
|
||||||
|
hectic.sentinella.enable bool
|
||||||
|
hectic.sentinella.peersDns string # e.g. "peers.sentinella.com"
|
||||||
|
hectic.sentinella.self string # this node's own IP
|
||||||
|
hectic.sentinella.port int # default 5988
|
||||||
|
hectic.sentinella.urls [string] # URLs for probe to health-check
|
||||||
|
hectic.sentinella.volumes [string] # mount points for probe
|
||||||
|
hectic.sentinella.tgToken string
|
||||||
|
hectic.sentinella.tgChatId string
|
||||||
|
hectic.sentinella.pollingIntervalSec int # default 3
|
||||||
|
```
|
||||||
|
Generates two systemd services: `sentinella-probe` and `sentinella-watcher`.
|
||||||
|
|
||||||
|
## Known Bug to Fix (router.sh)
|
||||||
|
The Basic Auth check references `$USER` and `$PASS` which are never populated.
|
||||||
|
Fix: move `auth_ok=false` before the header loop and compare `$tok` against
|
||||||
|
each entry in `$AUTH_TOKENS` (which is correctly populated from `AUTH_FILE`).
|
||||||
Generated
+1156
-8
File diff suppressed because it is too large
Load Diff
@@ -1,256 +1,116 @@
|
|||||||
{
|
{
|
||||||
description = "yukkop's nix utilities";
|
description = "yukkop's nix utilities";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||||
|
nixpkgs-fixed.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||||
rust-overlay = {
|
rust-overlay = {
|
||||||
url = "github:oxalica/rust-overlay";
|
url = "github:oxalica/rust-overlay";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deploy-rs = {
|
||||||
|
url = "github:serokell/deploy-rs";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
hyprland = {
|
||||||
|
url = "github:hyprwm/Hyprland";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixvim = {
|
||||||
|
url = "github:nix-community/nixvim/nixos-25.11";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-fixed";
|
||||||
|
};
|
||||||
|
disko = {
|
||||||
|
url = "github:nix-community/disko";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
impermanence = {
|
||||||
|
url = "github:nix-community/impermanence";
|
||||||
|
};
|
||||||
|
home-manager = {
|
||||||
|
url = "github:nix-community/home-manager/release-25.11";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-fixed";
|
||||||
|
};
|
||||||
|
nixos-wsl = {
|
||||||
|
url = "github:nix-community/NixOS-WSL";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixos-hardware = {
|
||||||
|
url = "github:NixOS/nixos-hardware";
|
||||||
|
};
|
||||||
|
nixos-anywhere = {
|
||||||
|
url = "github:nix-community/nixos-anywhere";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
sops-nix = {
|
||||||
|
url = "github:Mic92/sops-nix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixos-mailserver = {
|
||||||
|
url = "gitlab:simple-nixos-mailserver/nixos-mailserver/snm-25.11";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nix-minecraft.url = "github:Infinidoge/nix-minecraft";
|
||||||
|
hectic-landing = {
|
||||||
|
# NOTE(yukkop): private repo - SSH access required.
|
||||||
|
# Only evaluated when nixosConfigurations."hectic-lab|x86_64-linux" is built.
|
||||||
|
url = "git+ssh://git@github.com/liquizz/hectic-landing.git";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-fixed";
|
||||||
|
};
|
||||||
|
mechabellum-replay-analysis = {
|
||||||
|
# NOTE(yukkop): private repo - SSH access required.
|
||||||
|
# Only evaluated when nixosConfigurations."hectic-lab|x86_64-linux" is built.
|
||||||
|
url = "git+ssh://git@github.com/LysmiMx/mechabellum-replay-analysis.git";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs-fixed";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, rust-overlay }:
|
outputs = {
|
||||||
let
|
self,
|
||||||
lib = nixpkgs.lib;
|
nixpkgs,
|
||||||
recursiveUpdate = lib.recursiveUpdate;
|
rust-overlay,
|
||||||
|
...
|
||||||
|
}@inputs: let
|
||||||
|
flake = ./.;
|
||||||
|
self-lib = import ./lib { inherit flake self inputs; };
|
||||||
|
|
||||||
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ];
|
# Create overlay that includes legacy packages
|
||||||
|
overlayWithLegacy = system: final: prev:
|
||||||
forSpecSystemsWithPkgs = supportedSystems: pkgOverlays: f:
|
|
||||||
builtins.foldl' (acc: system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs {
|
|
||||||
inherit system;
|
|
||||||
overlays = pkgOverlays;
|
|
||||||
};
|
|
||||||
systemOutputs = f { system = system; pkgs = pkgs; };
|
|
||||||
in
|
|
||||||
recursiveUpdate acc systemOutputs
|
|
||||||
) {} supportedSystems;
|
|
||||||
|
|
||||||
forAllSystemsWithPkgs = pkgOverlays: f: forSpecSystemsWithPkgs supportedSystems pkgOverlays f;
|
|
||||||
|
|
||||||
envErrorMessage = varName: "Error: The ${varName} environment variable is not set.";
|
|
||||||
|
|
||||||
parseEnv = import ./parse-env.nix;
|
|
||||||
|
|
||||||
dotEnv = builtins.getEnv "DOTENV";
|
|
||||||
minorEnvironment =
|
|
||||||
if dotEnv != "" then
|
|
||||||
if builtins.pathExists dotEnv then
|
|
||||||
parseEnv dotEnv
|
|
||||||
else
|
|
||||||
throw "${dotEnv} file not exist"
|
|
||||||
else
|
|
||||||
if builtins.pathExists ./.env then
|
|
||||||
parseEnv ./.env
|
|
||||||
else
|
|
||||||
{};
|
|
||||||
|
|
||||||
in
|
|
||||||
forAllSystemsWithPkgs [ (import rust-overlay) ] ({ system, pkgs }:
|
|
||||||
{
|
|
||||||
packages.${system} =
|
|
||||||
let
|
|
||||||
rust = {
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.pkgsBuildHost.rust-bin.stable."1.81.0".default
|
|
||||||
pkgs.pkg-config
|
|
||||||
];
|
|
||||||
commonArgs = {
|
|
||||||
inherit (self.lib) cargoToml;
|
|
||||||
inherit (rust) nativeBuildInputs;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
nvim-alias = pkgs.callPackage ./package/nvim-alias.nix {};
|
|
||||||
bolt-unpack = pkgs.callPackage ./package/bolt-unpack.nix {};
|
|
||||||
nvim-pager = pkgs.callPackage ./package/nvim-pager.nix {};
|
|
||||||
printobstacle = pkgs.callPackage ./package/printobstacle.nix {};
|
|
||||||
printprogress = pkgs.callPackage ./package/printprogress.nix {};
|
|
||||||
colorize = pkgs.callPackage ./package/colorize.nix {};
|
|
||||||
github.gh-tl = pkgs.callPackage ./package/github/gh-tl.nix {};
|
|
||||||
supabase-with-env-collection = pkgs.callPackage ./package/supabase-with-env-collection.nix {};
|
|
||||||
migration-name = pkgs.callPackage ./package/migration-name.nix {};
|
|
||||||
prettify-log = pkgs.callPackage ./package/prettify-log/default.nix rust.commonArgs;
|
|
||||||
pg = {
|
|
||||||
pg-from = pkgs.callPackage ./package/postgres/pg-from/default.nix rust.commonArgs;
|
|
||||||
pg-migration = pkgs.callPackage ./package/postgres/pg-migration/default.nix rust.commonArgs;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
devShells.${system} =
|
|
||||||
let
|
|
||||||
shells = self.devShells.${system};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = pkgs.mkShell {
|
|
||||||
buildInputs = (with self.packages.${system}; [
|
|
||||||
nvim-alias
|
|
||||||
#prettify-log
|
|
||||||
nvim-pager
|
|
||||||
]) ++ (with pkgs; [
|
|
||||||
git
|
|
||||||
jq
|
|
||||||
yq-go
|
|
||||||
curl
|
|
||||||
]);
|
|
||||||
|
|
||||||
# environment
|
|
||||||
PAGER="${self.packages.${system}.nvim-pager}/bin/pager";
|
|
||||||
};
|
|
||||||
rust =
|
|
||||||
let
|
let
|
||||||
rustToolchain = if builtins.pathExists ./rust-toolchain.toml then
|
baseOverlay = (import ./overlay { inherit flake self inputs nixpkgs; }) final prev;
|
||||||
pkgs.pkgsBuildHost.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml
|
legacyPackages = import ./legacy { inherit system pkgs self; };
|
||||||
else
|
pkgs = import nixpkgs {
|
||||||
pkgs.pkgsBuildHost.rust-bin.stable."1.81.0".default;
|
inherit system;
|
||||||
in
|
overlays = [ (import rust-overlay) ];
|
||||||
shells.default //
|
|
||||||
(pkgs.mkShell {
|
|
||||||
nativeBuildInputs = [
|
|
||||||
rustToolchain
|
|
||||||
pkgs.pkg-config
|
|
||||||
];
|
|
||||||
});
|
|
||||||
haskell = shells.default // (pkgs.mkShell {
|
|
||||||
buildInputs = [ pkgs.stack ];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
nixosModules.${system} = {
|
|
||||||
"preset.default" = { pkgs, modulesPath, ... }: {
|
|
||||||
imports = [
|
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
services.getty.autologinUser = "root";
|
|
||||||
|
|
||||||
programs.zsh.enable = true;
|
|
||||||
users.defaultUserShell = pkgs.zsh;
|
|
||||||
|
|
||||||
# Enable flakes and new 'nix' command
|
|
||||||
nix.settings.experimental-features = "nix-command flakes";
|
|
||||||
|
|
||||||
virtualisation.vmVariant.virtualisation = {
|
|
||||||
qemu.options = [
|
|
||||||
"-nographic"
|
|
||||||
"-display curses"
|
|
||||||
"-append console=ttyS0"
|
|
||||||
"-serial mon:stdio"
|
|
||||||
"-vga qxl"
|
|
||||||
];
|
|
||||||
forwardPorts = [
|
|
||||||
{ from = "host"; host.port = 40500; guest.port = 22; }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
PasswordAuthentication = false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.firewall = {
|
|
||||||
enable = true;
|
|
||||||
allowedTCPPorts = [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
defaultPackages = [];
|
|
||||||
systemPackages = (with pkgs; [
|
|
||||||
curl
|
|
||||||
neovim
|
|
||||||
yq-go
|
|
||||||
jq
|
|
||||||
htop-vim
|
|
||||||
]) ++ (with self.packages.${system}; [
|
|
||||||
prettify-log
|
|
||||||
nvim-pager
|
|
||||||
]);
|
|
||||||
variables = {
|
|
||||||
PAGER=with self.packages.${system}; "${nvim-pager}/bin/pager";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
system.stateVersion = "24.11";
|
|
||||||
};
|
|
||||||
"hardware.hetzner" = { ... }: {
|
|
||||||
boot.loader.grub.device = "/dev/sda";
|
|
||||||
boot.initrd.availableKernelModules = [
|
|
||||||
"ata_piix"
|
|
||||||
"uhci_hcd"
|
|
||||||
"xen_blkfront"
|
|
||||||
"vmw_pvscsi"
|
|
||||||
];
|
|
||||||
boot.initrd.kernelModules = [ "nvme" ];
|
|
||||||
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}) // {
|
|
||||||
overlays.default =
|
|
||||||
final: prev: (
|
|
||||||
let
|
|
||||||
version = "1.6.1";
|
|
||||||
buildHttpExt = versionSuffix: let
|
|
||||||
buildPostgresqlExtension =
|
|
||||||
prev.callPackage (import (builtins.path {
|
|
||||||
name = "extension-builder";
|
|
||||||
path = ./buildPostgresqlExtension.nix;
|
|
||||||
})) {
|
|
||||||
postgresql = prev."postgresql_${versionSuffix}";
|
|
||||||
};
|
|
||||||
in buildPostgresqlExtension {
|
|
||||||
pname = "http";
|
|
||||||
inherit version;
|
|
||||||
src = prev.fetchFromGitHub {
|
|
||||||
owner = "pramsey";
|
|
||||||
repo = "pgsql-http";
|
|
||||||
rev = "v${version}";
|
|
||||||
hash = "sha256-C8eqi0q1dnshUAZjIsZFwa5FTYc7vmATF3vv2CReWPM=";
|
|
||||||
};
|
|
||||||
nativeBuildInputs = with prev; [ pkg-config curl ];
|
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
|
||||||
hectic = self.packages.${prev.system};
|
|
||||||
postgresql_17 = prev.postgresql_17 // { pkgs = prev.postgresql_17.pkgs // { http = buildHttpExt "17"; }; };
|
|
||||||
postgresql_16 = prev.postgresql_16 // { pkgs = prev.postgresql_16.pkgs // { http = buildHttpExt "16"; }; };
|
|
||||||
postgresql_15 = prev.postgresql_15 // { pkgs = prev.postgresql_15.pkgs // { http = buildHttpExt "15"; }; };
|
|
||||||
postgresql_14 = prev.postgresql_14 // { pkgs = prev.postgresql_14.pkgs // { http = buildHttpExt "14"; }; };
|
|
||||||
});
|
|
||||||
lib = {
|
|
||||||
# -- For all systems --
|
|
||||||
inherit dotEnv minorEnvironment parseEnv forAllSystemsWithPkgs forSpecSystemsWithPkgs;
|
|
||||||
|
|
||||||
makeEnvironment = envVars:
|
|
||||||
builtins.listToAttrs
|
|
||||||
(map (name: { inherit name; value = self.lib.getEnv name; }) envVars);
|
|
||||||
|
|
||||||
# -- Env processing --
|
|
||||||
getEnv = varName: let
|
|
||||||
var = builtins.getEnv varName;
|
|
||||||
in
|
in
|
||||||
if var != "" then
|
baseOverlay // legacyPackages;
|
||||||
var
|
|
||||||
else if minorEnvironment ? varName then
|
|
||||||
minorEnvironment."${varName}"
|
|
||||||
else
|
|
||||||
throw (envErrorMessage varName);
|
|
||||||
|
|
||||||
# -- Cargo.toml --
|
overlays = [ self.overlays.default ];
|
||||||
cargoToml = src: (builtins.fromTOML (builtins.readFile "${src}/Cargo.toml"));
|
in self-lib.forAllSystemsWithPkgs ([(import rust-overlay)] ++ overlays) ({
|
||||||
|
system,
|
||||||
ssh.keys = {
|
pkgs,
|
||||||
hetzner-test = {
|
}: {
|
||||||
yukkop = ''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8scy1tv6zfXX6xyaukhO/fsZwif5rC89DvXNc6XxOf'';
|
packages.${system} = import ./package { inherit flake self inputs pkgs system; };
|
||||||
};
|
devShells.${system} = import ./devshell { inherit flake self inputs pkgs system; };
|
||||||
};
|
legacyPackages.${system} = import ./legacy { inherit flake self inputs pkgs system; };
|
||||||
|
checks.${system} = import ./test { inherit flake self inputs pkgs system; };
|
||||||
|
}) // {
|
||||||
|
lib = self-lib;
|
||||||
|
overlays.default = import ./overlay { inherit flake self inputs; };
|
||||||
|
nixosModules = import ./nixos/module { inherit flake self inputs; };
|
||||||
|
templates = import ./template { inherit flake self inputs; };
|
||||||
|
nixosConfigurations = {
|
||||||
|
# NOTE(yukkop): in bfs one of dependencies is shadow-4.17.4 that
|
||||||
|
# unsupported on aarch64-darwin
|
||||||
|
"bfs.netherland.xray|x86_64-linux" = import ./nixos/system/bfs.netherland.xray { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
"bfs.poland.xray|x86_64-linux" = import ./nixos/system/bfs.poland.xray { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
# FIXME(yukkop): some why I cannot merge nixosConfigurations from `forAllSystemsWithPkgs` with this
|
||||||
|
"neuro|x86_64-linux" = import ./nixos/system/neuro { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
"games|x86_64-linux" = import ./nixos/system/games { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
"wsl|x86_64-linux" = import ./nixos/system/wsl { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
|
"hectic-lab|x86_64-linux" = import ./nixos/system/hectic-lab { inherit flake self inputs; system = "x86_64-linux"; };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
writers = pkgs.callPackage ./writer { };
|
||||||
|
in {
|
||||||
|
helpers = pkgs.callPackage ./helper { };
|
||||||
|
# NOTE(yukkop): duplicate writers in root of legacyPackages and writers due nixpkgs legacyPackages consistency
|
||||||
|
writers = writers;
|
||||||
|
} // writers
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{ callPackage }: {
|
||||||
|
posix-shell = callPackage ./posix-shell {};
|
||||||
|
steam = callPackage ./steam {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
: "${OLD_NAMESPACE:=}"
|
||||||
|
|
||||||
|
nl=$(printf '\nx')
|
||||||
|
nl=${nl%x}
|
||||||
|
|
||||||
|
___pop_namespace() {
|
||||||
|
v=${OLD_NAMESPACE%%"$nl"*}
|
||||||
|
|
||||||
|
case $OLD_NAMESPACE in
|
||||||
|
*"$nl"*)
|
||||||
|
OLD_NAMESPACE=${OLD_NAMESPACE#*"$nl"}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
OLD_NAMESPACE=
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf '%s\n' "$v"
|
||||||
|
}
|
||||||
|
|
||||||
|
___peek_namespace() {
|
||||||
|
printf '%s\n' "${OLD_NAMESPACE%%"$nl"*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
___push_namespace() {
|
||||||
|
if [ -n "$OLD_NAMESPACE" ]; then
|
||||||
|
OLD_NAMESPACE=$1"$nl$OLD_NAMESPACE"
|
||||||
|
else
|
||||||
|
OLD_NAMESPACE=$1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
change_namespace() {
|
||||||
|
___push_namespace "$HECTIC_NAMESPACE"
|
||||||
|
export HECTIC_NAMESPACE="$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_namespace() {
|
||||||
|
HECTIC_NAMESPACE=$(___pop_namespace)
|
||||||
|
export HECTIC_NAMESPACE
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Regular text colors
|
||||||
|
BLACK='\033[30m'
|
||||||
|
RED='\033[31m'
|
||||||
|
GREEN='\033[32m'
|
||||||
|
YELLOW='\033[33m'
|
||||||
|
BLUE='\033[34m'
|
||||||
|
MAGENTA='\033[35m'
|
||||||
|
CYAN='\033[36m'
|
||||||
|
WHITE='\033[37m'
|
||||||
|
|
||||||
|
# Bright text colors
|
||||||
|
BBLACK='\033[90m'
|
||||||
|
BRED='\033[91m'
|
||||||
|
BGREEN='\033[92m'
|
||||||
|
BYELLOW='\033[93m'
|
||||||
|
BBLUE='\033[94m'
|
||||||
|
BMAGENTA='\033[95m'
|
||||||
|
BCYAN='\033[96m'
|
||||||
|
BWHITE='\033[97m'
|
||||||
|
|
||||||
|
# Background colors
|
||||||
|
BG_BLACK='\033[40m'
|
||||||
|
BG_RED='\033[41m'
|
||||||
|
BG_GREEN='\033[42m'
|
||||||
|
BG_YELLOW='\033[43m'
|
||||||
|
BG_BLUE='\033[44m'
|
||||||
|
BG_MAGENTA='\033[45m'
|
||||||
|
BG_CYAN='\033[46m'
|
||||||
|
BG_WHITE='\033[47m'
|
||||||
|
|
||||||
|
# Bright background colors
|
||||||
|
BG_BBLACK='\033[100m'
|
||||||
|
BG_BRED='\033[101m'
|
||||||
|
BG_BGREEN='\033[102m'
|
||||||
|
BG_BYELLOW='\033[103m'
|
||||||
|
BG_BBLUE='\033[104m'
|
||||||
|
BG_BMAGENTA='\033[105m'
|
||||||
|
BG_BCYAN='\033[106m'
|
||||||
|
BG_BWHITE='\033[107m'
|
||||||
|
|
||||||
|
# Text effects
|
||||||
|
RESET='\033[0m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
DIM='\033[2m'
|
||||||
|
ITALIC='\033[3m'
|
||||||
|
UNDERLINE='\033[4m'
|
||||||
|
BLINK='\033[5m'
|
||||||
|
INVERSE='\033[7m'
|
||||||
|
HIDDEN='\033[8m'
|
||||||
|
STRIKE='\033[9m'
|
||||||
|
|
||||||
|
: "$NC" "$BLACK" "$RED" "$GREEN" "$YELLOW" "$BLUE" "$MAGENTA" "$CYAN" "$WHITE" "$BBLACK" "$BRED" "$BGREEN" "$BYELLOW" "$BBLUE" "$BMAGENTA" "$BCYAN" "$BWHITE" "$BG_BLACK" "$BG_RED" "$BG_GREEN" "$BG_YELLOW" "$BG_BLUE" "$BG_MAGENTA" "$BG_CYAN" "$BG_WHITE" "$BG_BBLACK" "$BG_BRED" "$BG_BGREEN" "$BG_BYELLOW" "$BG_BBLUE" "$BG_BMAGENTA" "$BG_BCYAN" "$BG_BWHITE" "$RESET" "$BOLD" "$DIM" "$ITALIC" "$UNDERLINE" "$BLINK" "$INVERSE" "$HIDDEN" "$STRIKE"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{ dash, hectic }: let
|
||||||
|
shell = "${dash}/bin/dash";
|
||||||
|
bashOptions = [
|
||||||
|
"errexit"
|
||||||
|
"nounset"
|
||||||
|
];
|
||||||
|
in {
|
||||||
|
log = hectic.writeDash "log.sh" ''
|
||||||
|
${builtins.readFile ./colors.sh}
|
||||||
|
${builtins.readFile ./log.sh}
|
||||||
|
'';
|
||||||
|
colors = hectic.writeDash "colors.sh" ''
|
||||||
|
${builtins.readFile ./colors.sh}
|
||||||
|
'';
|
||||||
|
change_namespace = hectic.writeDash "change_namespace.sh" ''
|
||||||
|
${builtins.readFile ./change_namespace.sh}
|
||||||
|
'';
|
||||||
|
quote = hectic.writeDash "quote.sh" ''
|
||||||
|
${builtins.readFile ./quote.sh}
|
||||||
|
'';
|
||||||
|
pager_or_cat = hectic.writeDash "pager_or_cat.sh" ''
|
||||||
|
${builtins.readFile ./pager_or_cat.sh}
|
||||||
|
'';
|
||||||
|
with_closed_fds = hectic.writeDash "with_closed_fds.sh" ''
|
||||||
|
${builtins.readFile ./with_closed_fds.sh}
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
#!/bin/dash
|
||||||
|
|
||||||
|
# Hectic shell logger
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# # Including
|
||||||
|
# . <this file>
|
||||||
|
#
|
||||||
|
# # Required
|
||||||
|
# colors.sh
|
||||||
|
#
|
||||||
|
# # In your script (recommended: do NOT export HECTIC_NAMESPACE)
|
||||||
|
# HECTIC_NAMESPACE="my-script" # optional, defaults to basename "$0"
|
||||||
|
# # # Then use:
|
||||||
|
# log info 'starting up'
|
||||||
|
# log debug "value=${val}"
|
||||||
|
# log error "failed: ${WHITE}${reason}${NC} red text again"
|
||||||
|
#
|
||||||
|
# # Note:
|
||||||
|
# When you use NC to reset terminal colors inside log output,
|
||||||
|
# it resets back to the log level’s color instead of the terminal default.
|
||||||
|
|
||||||
|
: "${HECTIC_NAMESPACE="$(basename "$0")"}"
|
||||||
|
: "${HECTIC_LOG:=trace}" # e.g. "info;ns1=debug;ns2=trace"
|
||||||
|
|
||||||
|
validate_log_level_spec() {
|
||||||
|
spec=$HECTIC_LOG
|
||||||
|
|
||||||
|
levels="trace debug info notice warn error"
|
||||||
|
|
||||||
|
ok_level() {
|
||||||
|
for l in $levels; do
|
||||||
|
[ "$l" = "$1" ] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
oldIFS=$IFS
|
||||||
|
IFS=';'
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
set -- $spec
|
||||||
|
IFS=$oldIFS
|
||||||
|
|
||||||
|
for tok; do
|
||||||
|
case $tok in
|
||||||
|
*=*)
|
||||||
|
ns=${tok%%=*}
|
||||||
|
lvl=${tok#*=}
|
||||||
|
[ -n "$ns" ] || return 1
|
||||||
|
ok_level "$lvl" || return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
ok_level "$tok" || return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
exec 3>&2
|
||||||
|
trap 'exec 3>&-' EXIT INT HUP
|
||||||
|
|
||||||
|
validate_log_level_spec || { printf "%b%b\n" "${BBLACK}${HECTIC_NAMESPACE}> " "${color}invalid HECTIC_LOG syntax${NC}" "$@" >&3; exit 1; }
|
||||||
|
|
||||||
|
log_level_num() {
|
||||||
|
case $1 in
|
||||||
|
trace) printf %s 0 ;;
|
||||||
|
debug) printf %s 1 ;;
|
||||||
|
info) printf %s 2 ;;
|
||||||
|
notice) printf %s 3 ;;
|
||||||
|
warn) printf %s 4 ;;
|
||||||
|
error|panic) printf %s 5 ;;
|
||||||
|
*) printf %s 2 ;; # default info
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
log_effective_level() {
|
||||||
|
spec=$HECTIC_LOG
|
||||||
|
ns=$HECTIC_NAMESPACE
|
||||||
|
|
||||||
|
default_level=
|
||||||
|
ns_level=
|
||||||
|
|
||||||
|
oldIFS=$IFS
|
||||||
|
IFS=';'
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
set -- $spec
|
||||||
|
IFS=$oldIFS
|
||||||
|
|
||||||
|
for tok; do
|
||||||
|
case $tok in
|
||||||
|
*=*)
|
||||||
|
name=${tok%%=*}
|
||||||
|
lvl=${tok#*=}
|
||||||
|
[ "$name" = "$ns" ] && ns_level=$lvl
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
[ -z "$default_level" ] && default_level=$tok
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${ns_level:-${default_level:-info}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_allowed() {
|
||||||
|
msg_level="${1:?}"
|
||||||
|
eff_level="$(log_effective_level)"
|
||||||
|
|
||||||
|
msg_n="$(log_level_num "$msg_level")"
|
||||||
|
eff_n="$(log_level_num "$eff_level")"
|
||||||
|
|
||||||
|
[ "$msg_n" -ge "$eff_n" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# log(level, text...)
|
||||||
|
log() {
|
||||||
|
delimetr=${DELIMETR:-' '};
|
||||||
|
level="${1:?}"
|
||||||
|
log_allowed "$level" || return 0
|
||||||
|
|
||||||
|
case "$level" in
|
||||||
|
trace) color="$MAGENTA" ;;
|
||||||
|
debug) color="$BLUE" ;;
|
||||||
|
info) color="$GREEN" ;;
|
||||||
|
notice) color="$CYAN" ;;
|
||||||
|
warn) color="$YELLOW" ;;
|
||||||
|
error) color="$RED" ;;
|
||||||
|
panic) color="$BRED" ;;
|
||||||
|
*)
|
||||||
|
color="$WHITE"
|
||||||
|
NO_SHIFT=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ ${NO_SHIFT+x} ] || shift
|
||||||
|
|
||||||
|
# shellcheck disable=SC2059
|
||||||
|
# shellcheck disable=SC2046
|
||||||
|
[ "$level" = panic ] && printf "${BBLACK}${HECTIC_NAMESPACE}> $BRED%b$NC\n" \
|
||||||
|
'' \
|
||||||
|
'' \
|
||||||
|
'this panic is unexpected behavior of program and/or bug' \
|
||||||
|
'please contact the developer' \
|
||||||
|
'' \
|
||||||
|
''
|
||||||
|
|
||||||
|
# shellcheck disable=SC1003
|
||||||
|
fmt="$(printf "%s$delimetr" "$@" | sed 's/\\033\[0m/''\'"$color"'/g')"
|
||||||
|
shift
|
||||||
|
# shellcheck disable=SC1003
|
||||||
|
printf "${BBLACK}${HECTIC_NAMESPACE}> %b\n" "$color$fmt$NC" >&3
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
pager_or_cat_init() {
|
||||||
|
# Pipe to pager only if stdout is a terminal, otherwise output directly
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
PAGER_OR_CAT="${PAGER:-less}"
|
||||||
|
else
|
||||||
|
PAGER_OR_CAT=cat
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
quote() { printf "'%s'" "$(printf %s "$1" | sed "s/'/'\\\\''/g")"; }
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/dash
|
||||||
|
|
||||||
|
# with_closed_fds -- run command with leaked file descriptors closed
|
||||||
|
#
|
||||||
|
# Shell libraries (e.g. hectic logger) may open extra file descriptors
|
||||||
|
# (like fd 3 as a dup of stderr). Child processes inherit these fds.
|
||||||
|
# Long-running daemons (postgres, postgrest) that keep fd 3 open can
|
||||||
|
# prevent the terminal from returning to the prompt even after the
|
||||||
|
# spawning script exits.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# with_closed_fds pg_ctl -D "$data" -w start
|
||||||
|
# with_closed_fds postgrest "$config" > "$log" 2>&1 &
|
||||||
|
#
|
||||||
|
# Runs the command in a subshell where fds 3-9 are redirected to
|
||||||
|
# /dev/null. The parent shell's fd table is untouched.
|
||||||
|
with_closed_fds() {
|
||||||
|
(
|
||||||
|
exec 3>/dev/null 4>/dev/null 5>/dev/null 6>/dev/null 7>/dev/null 8>/dev/null 9>/dev/null
|
||||||
|
"$@"
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{ stdenv, steamcmd }: {
|
||||||
|
buildSteamServer = steamId: stdenv.mkDerivation {
|
||||||
|
pname = "astroneer-dedicated-server";
|
||||||
|
version = "latest";
|
||||||
|
|
||||||
|
src = null;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
steamcmd
|
||||||
|
];
|
||||||
|
|
||||||
|
buildPhase = ''
|
||||||
|
export HOME=$TMPDIR
|
||||||
|
mkdir -p $out
|
||||||
|
steamcmd \
|
||||||
|
+force_install_dir $out \
|
||||||
|
+login anonymous \
|
||||||
|
+app_update ${steamId} validate \
|
||||||
|
+quit
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = "true";
|
||||||
|
|
||||||
|
dontFixup = true;
|
||||||
|
dontStrip = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{ callPackage }: rec {
|
||||||
|
writeShellApplication = callPackage ./writeShellApplication.nix {};
|
||||||
|
writeDash = callPackage ./writeDash.nix {};
|
||||||
|
writeC = callPackage ./writeC.nix {};
|
||||||
|
writeCBin = name: writeC "/bin/${name}";
|
||||||
|
writeMinCBin = name: includes: body: writeMinC "/bin/${name}" includes body;
|
||||||
|
writeMinC = name: includes: body:
|
||||||
|
writeC name ''
|
||||||
|
${builtins.concatStringsSep "\n" (map (h: "#include " + h) includes)}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
${body}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
{ lib, writers, gcc }:
|
||||||
|
name: argsOrScript:
|
||||||
|
if
|
||||||
|
lib.isAttrs argsOrScript
|
||||||
|
&& !lib.isDerivation argsOrScript
|
||||||
|
then
|
||||||
|
writers.makeBinWriter (
|
||||||
|
argsOrScript
|
||||||
|
// {
|
||||||
|
compileScript = ''
|
||||||
|
# Force gcc to treat the input file as C code
|
||||||
|
${gcc}/bin/gcc -fsyntax-only -xc $contentPath
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Syntax check failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
${gcc}/bin/gcc -xc -o $out $contentPath
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
)
|
||||||
|
name
|
||||||
|
else
|
||||||
|
writers.makeBinWriter {
|
||||||
|
compileScript = ''
|
||||||
|
# Force gcc to treat the input file as C code
|
||||||
|
${gcc}/bin/gcc -fsyntax-only -xc $contentPath
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Syntax check failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
${gcc}/bin/gcc -xc -o $out $contentPath
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
name
|
||||||
|
argsOrScript
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{ dash, lib, writers }: name: argsOrScript:
|
||||||
|
if lib.isAttrs argsOrScript && !lib.isDerivation argsOrScript then
|
||||||
|
writers.makeScriptWriter (argsOrScript // { interpreter = "${lib.getExe dash}"; }) name
|
||||||
|
else
|
||||||
|
writers.makeScriptWriter { interpreter = "${lib.getExe dash}"; } name argsOrScript
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
{
|
||||||
|
writeTextFile,
|
||||||
|
lib,
|
||||||
|
shellcheck-minimal,
|
||||||
|
stdenv,
|
||||||
|
runtimeShell,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
The name of the script to write.
|
||||||
|
|
||||||
|
Type: String
|
||||||
|
*/
|
||||||
|
name,
|
||||||
|
/*
|
||||||
|
The shell script's text, not including a shebang.
|
||||||
|
|
||||||
|
Type: String
|
||||||
|
*/
|
||||||
|
text,
|
||||||
|
/*
|
||||||
|
Inputs to add to the shell script's `$PATH` at runtime.
|
||||||
|
|
||||||
|
Type: [String|Derivation]
|
||||||
|
*/
|
||||||
|
runtimeInputs ? [ ],
|
||||||
|
/*
|
||||||
|
Extra environment variables to set at runtime.
|
||||||
|
|
||||||
|
Type: AttrSet
|
||||||
|
*/
|
||||||
|
runtimeEnv ? null,
|
||||||
|
/*
|
||||||
|
`stdenv.mkDerivation`'s `meta` argument.
|
||||||
|
|
||||||
|
Type: AttrSet
|
||||||
|
*/
|
||||||
|
meta ? { },
|
||||||
|
/*
|
||||||
|
`stdenv.mkDerivation`'s `passthru` argument.
|
||||||
|
|
||||||
|
Type: AttrSet
|
||||||
|
*/
|
||||||
|
passthru ? { },
|
||||||
|
/*
|
||||||
|
The `checkPhase` to run. Defaults to `shellcheck` on supported
|
||||||
|
platforms and `bash -n`.
|
||||||
|
|
||||||
|
The script path will be given as `$target` in the `checkPhase`.
|
||||||
|
|
||||||
|
Type: String
|
||||||
|
*/
|
||||||
|
checkPhase ? null,
|
||||||
|
/*
|
||||||
|
Checks to exclude when running `shellcheck`, e.g. `[ "SC2016" ]`.
|
||||||
|
|
||||||
|
See <https://www.shellcheck.net/wiki/> for a list of checks.
|
||||||
|
|
||||||
|
Type: [String]
|
||||||
|
*/
|
||||||
|
excludeShellChecks ? [ ],
|
||||||
|
/*
|
||||||
|
Extra command-line flags to pass to ShellCheck.
|
||||||
|
|
||||||
|
Type: [String]
|
||||||
|
*/
|
||||||
|
extraShellCheckFlags ? [ ],
|
||||||
|
/*
|
||||||
|
Bash options to activate with `set -o` at the start of the script.
|
||||||
|
|
||||||
|
Defaults to `[ "errexit" "nounset" "pipefail" ]`.
|
||||||
|
|
||||||
|
Type: [String]
|
||||||
|
*/
|
||||||
|
bashOptions ? [
|
||||||
|
"errexit"
|
||||||
|
"nounset"
|
||||||
|
"pipefail"
|
||||||
|
],
|
||||||
|
/*
|
||||||
|
Extra arguments to pass to `stdenv.mkDerivation`.
|
||||||
|
|
||||||
|
:::{.caution}
|
||||||
|
Certain derivation attributes are used internally,
|
||||||
|
overriding those could cause problems.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Type: AttrSet
|
||||||
|
*/
|
||||||
|
derivationArgs ? { },
|
||||||
|
/*
|
||||||
|
Whether to inherit the current `$PATH` in the script.
|
||||||
|
|
||||||
|
Type: Bool
|
||||||
|
*/
|
||||||
|
inheritPath ? true,
|
||||||
|
|
||||||
|
shell ? runtimeShell,
|
||||||
|
}:
|
||||||
|
writeTextFile {
|
||||||
|
inherit
|
||||||
|
name
|
||||||
|
meta
|
||||||
|
passthru
|
||||||
|
derivationArgs
|
||||||
|
;
|
||||||
|
executable = true;
|
||||||
|
destination = "/bin/${name}";
|
||||||
|
allowSubstitutes = true;
|
||||||
|
preferLocalBuild = false;
|
||||||
|
text = ''
|
||||||
|
#!${shell}
|
||||||
|
${lib.concatMapStringsSep "\n" (option: "set -o ${option}") bashOptions}
|
||||||
|
''
|
||||||
|
+ lib.optionalString (runtimeEnv != null) (
|
||||||
|
lib.concatStrings (
|
||||||
|
lib.mapAttrsToList (name: value: ''
|
||||||
|
${lib.toShellVar name value}
|
||||||
|
export ${name}
|
||||||
|
'') runtimeEnv
|
||||||
|
)
|
||||||
|
)
|
||||||
|
+ lib.optionalString (runtimeInputs != [ ]) ''
|
||||||
|
|
||||||
|
export PATH="${lib.makeBinPath runtimeInputs}${lib.optionalString inheritPath ":$PATH"}"
|
||||||
|
''
|
||||||
|
+ ''
|
||||||
|
|
||||||
|
${text}
|
||||||
|
'';
|
||||||
|
|
||||||
|
checkPhase =
|
||||||
|
let
|
||||||
|
excludeFlags = lib.optionals (excludeShellChecks != [ ]) [
|
||||||
|
"--exclude"
|
||||||
|
(lib.concatStringsSep "," excludeShellChecks)
|
||||||
|
];
|
||||||
|
# GHC (=> shellcheck) isn't supported on some platforms (such as risc-v)
|
||||||
|
# but we still want to use writeShellApplication on those platforms
|
||||||
|
shellcheckCommand = lib.optionalString shellcheck-minimal.compiler.bootstrapAvailable ''
|
||||||
|
# use shellcheck which does not include docs
|
||||||
|
# pandoc takes long to build and documentation isn't needed for just running the cli
|
||||||
|
${lib.getExe shellcheck-minimal} ${
|
||||||
|
lib.escapeShellArgs (excludeFlags ++ extraShellCheckFlags)
|
||||||
|
} "$target"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
if checkPhase == null then
|
||||||
|
''
|
||||||
|
runHook preCheck
|
||||||
|
${stdenv.shellDryRun} "$target"
|
||||||
|
${shellcheckCommand}
|
||||||
|
runHook postCheck
|
||||||
|
''
|
||||||
|
else
|
||||||
|
checkPhase;
|
||||||
|
}
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
{ flake, inputs, self }: let
|
||||||
|
nixpkgs = inputs.nixpkgs;
|
||||||
|
lib = nixpkgs.lib;
|
||||||
|
recursiveUpdate = nixpkgs.lib.recursiveUpdate;
|
||||||
|
|
||||||
|
envErrorMessage = varName: "Error: The ${varName} environment variable is not set.";
|
||||||
|
|
||||||
|
AllSystems = [
|
||||||
|
"aarch64-darwin"
|
||||||
|
"aarch64-linux"
|
||||||
|
"armv5tel-linux"
|
||||||
|
"armv6l-linux"
|
||||||
|
"armv7l-linux"
|
||||||
|
"i686-linux"
|
||||||
|
"mipsel-linux"
|
||||||
|
"powerpc64le-linux"
|
||||||
|
"riscv64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"x86_64-linux"
|
||||||
|
];
|
||||||
|
|
||||||
|
commonSystems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
|
||||||
|
forSystemsWithPkgs = supportedSystems: pkgOverlays: f:
|
||||||
|
builtins.foldl' (
|
||||||
|
acc: system: let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = pkgOverlays;
|
||||||
|
};
|
||||||
|
systemOutputs = f {
|
||||||
|
system = system;
|
||||||
|
pkgs = pkgs;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
recursiveUpdate acc systemOutputs
|
||||||
|
) {}
|
||||||
|
supportedSystems;
|
||||||
|
|
||||||
|
forAllSystemsWithPkgs = pkgOverlays: f: forSystemsWithPkgs AllSystems pkgOverlays f;
|
||||||
|
|
||||||
|
parseEnv = import ./parse-env.nix;
|
||||||
|
|
||||||
|
dotEnv = builtins.getEnv "DOTENV";
|
||||||
|
minorEnvironment =
|
||||||
|
if dotEnv != ""
|
||||||
|
then
|
||||||
|
if builtins.pathExists dotEnv
|
||||||
|
then parseEnv dotEnv
|
||||||
|
else throw "${dotEnv} file not exist"
|
||||||
|
else if builtins.pathExists ./.env
|
||||||
|
then parseEnv ./.env
|
||||||
|
else {};
|
||||||
|
in {
|
||||||
|
# -- For all systems --
|
||||||
|
inherit dotEnv minorEnvironment parseEnv forAllSystemsWithPkgs forSystemsWithPkgs commonSystems AllSystems;
|
||||||
|
|
||||||
|
forSystems = systems: nixpkgs.lib.genAttrs systems;
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs AllSystems;
|
||||||
|
|
||||||
|
shellModules = {
|
||||||
|
logs = builtins.readFile ./shell/logs.sh;
|
||||||
|
check-tool = builtins.readFile ./shell/check-tool.sh;
|
||||||
|
local-dir = builtins.readFile ./shell/local-dir.sh;
|
||||||
|
};
|
||||||
|
|
||||||
|
sharedShellAliases = {
|
||||||
|
jc = ''journalctl'';
|
||||||
|
sc = ''journalctl'';
|
||||||
|
nv = ''nvim'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sharedShellAliasesForDevVm = self.lib.sharedShellAliases // {
|
||||||
|
sd = "shutdown now";
|
||||||
|
};
|
||||||
|
|
||||||
|
readEnvironment = { envVarsToRead, prefix ? "" }:
|
||||||
|
builtins.listToAttrs
|
||||||
|
(map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = self.lib.getEnv "${prefix}${name}";
|
||||||
|
})
|
||||||
|
envVarsToRead);
|
||||||
|
|
||||||
|
# -- Env processing --
|
||||||
|
getEnv = varName: let
|
||||||
|
var = builtins.getEnv varName;
|
||||||
|
in
|
||||||
|
if var != ""
|
||||||
|
then var
|
||||||
|
else if minorEnvironment ? varName
|
||||||
|
then minorEnvironment."${varName}"
|
||||||
|
else throw (envErrorMessage varName);
|
||||||
|
|
||||||
|
# -- Cargo.toml --
|
||||||
|
cargoToml = src: (builtins.fromTOML (builtins.readFile "${src}/Cargo.toml"));
|
||||||
|
|
||||||
|
# Consolidated SQL bundles for the `hectic` schema. Single source of truth
|
||||||
|
# for everything that creates objects in the `hectic` namespace, used by
|
||||||
|
# migrator (init-time) and db-tool (postgres-init + hydrate). Consumers apply
|
||||||
|
# the full bundle via lib/hook/apply-hectic-bundle.sh.
|
||||||
|
#
|
||||||
|
# The whole hectic system shares one `versionString`; `hectic-version.sql`
|
||||||
|
# registers (`'hectic'`, versionString) into `hectic.version` and raises an
|
||||||
|
# exception on mismatch. Per-hook version rows are intentionally absent.
|
||||||
|
#
|
||||||
|
# Each entry exposes:
|
||||||
|
# * .sql — file contents as a string, with @HECTIC_VERSION@ substituted
|
||||||
|
# * .path — Nix store path (only on entries that need no substitution)
|
||||||
|
hectic = let
|
||||||
|
versionString = lib.fileContents ./hook/sql/HECTIC_VERSION;
|
||||||
|
static = path: { inherit path; sql = builtins.readFile path; };
|
||||||
|
templated = path: {
|
||||||
|
sql = builtins.replaceStrings
|
||||||
|
[ "@HECTIC_VERSION@" ]
|
||||||
|
[ versionString ]
|
||||||
|
(builtins.readFile path);
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
inherit versionString;
|
||||||
|
version = templated ./hook/sql/hectic-version.sql;
|
||||||
|
secret = static ./hook/sql/hectic-secret.sql;
|
||||||
|
migration = static ./hook/sql/hectic-migration.sql;
|
||||||
|
inheritance = static ./hook/sql/hectic-inheritance.sql;
|
||||||
|
applyBundleScript = ./hook/apply-hectic-bundle.sh;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Back-compat alias. Prefer `self.lib.hectic.inheritance`.
|
||||||
|
hecticInheritance = let
|
||||||
|
path = ./hook/sql/hectic-inheritance.sql;
|
||||||
|
in {
|
||||||
|
inherit path;
|
||||||
|
sql = builtins.readFile path;
|
||||||
|
};
|
||||||
|
|
||||||
|
ssh.keys = {
|
||||||
|
hetzner-test = {
|
||||||
|
yukkop = ''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ8scy1tv6zfXX6xyaukhO/fsZwif5rC89DvXNc6XxOf'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
readPackages = callPackage: path: extraArgs:
|
||||||
|
with lib;
|
||||||
|
with builtins;
|
||||||
|
pipe path [
|
||||||
|
readDir
|
||||||
|
(filterAttrs (_: type: type == "directory"))
|
||||||
|
(filterAttrs (name: _: pathExists "${path}/${name}/default.nix"))
|
||||||
|
(mapAttrs (name: _: callPackage "${path}/${name}" extraArgs))
|
||||||
|
];
|
||||||
|
|
||||||
|
# Like readModulesRecursive, but reads module structure as a one-level keys,
|
||||||
|
# so that it is suited for `nix flake show`
|
||||||
|
# ```nix
|
||||||
|
# {
|
||||||
|
# "foo.bar" = import ./module/foo/bar.nix
|
||||||
|
# }
|
||||||
|
# ```
|
||||||
|
readModulesRecursive' = path: extraArgs:
|
||||||
|
with lib;
|
||||||
|
with builtins; let
|
||||||
|
paths = pipe "${path}" [
|
||||||
|
(filesystem.listFilesRecursive)
|
||||||
|
(filter (hasSuffix ".nix"))
|
||||||
|
];
|
||||||
|
pathToName = flip pipe [
|
||||||
|
(removePrefix "${path}/")
|
||||||
|
(replaceStrings ["/" ".nix"] ["." ""])
|
||||||
|
(removeSuffix ".nix")
|
||||||
|
];
|
||||||
|
attrList =
|
||||||
|
map (path': {
|
||||||
|
name = pathToName (unsafeDiscardStringContext path');
|
||||||
|
value = import path' extraArgs;
|
||||||
|
})
|
||||||
|
paths;
|
||||||
|
in
|
||||||
|
listToAttrs attrList;
|
||||||
|
|
||||||
|
nixpkgs-lib = nixpkgs.lib;
|
||||||
|
} // rec {
|
||||||
|
/* Supplied a directory, reads it's recursive structure into NixOS modules, so
|
||||||
|
that provided a `./module` dir with `module/foo/bar.nix` in it it outputs
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
foo.bar = import ./module/foo/bar.nix
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
readModulesRecursive = path:
|
||||||
|
lib.mapAttrs' (
|
||||||
|
name: value: let
|
||||||
|
name' = builtins.replaceStrings [".nix"] [""] name;
|
||||||
|
in
|
||||||
|
if value == "regular"
|
||||||
|
then {
|
||||||
|
name = name';
|
||||||
|
value = import "${path}/${name}";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inherit name;
|
||||||
|
value = readModulesRecursive "${path}/${name}";
|
||||||
|
}
|
||||||
|
) (builtins.readDir path);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/dash
|
||||||
|
# Applies the full hectic SQL bundle to a PostgreSQL database, in order:
|
||||||
|
# 1. version (hard-fails on version mismatch)
|
||||||
|
# 2. secret (hectic.secret table + load_secrets_from_env + get_secret)
|
||||||
|
# 3. migration (hectic.migration table + domains + sha256_lower trigger)
|
||||||
|
# 4. inheritance (created_at/updated_at/immutable enforcement triggers)
|
||||||
|
#
|
||||||
|
# Idempotent: each SQL file uses IF NOT EXISTS / CREATE OR REPLACE.
|
||||||
|
#
|
||||||
|
# Required env (caller injects from Nix):
|
||||||
|
# HECTIC_VERSION_SQL - path to hectic-version.sql (substituted)
|
||||||
|
# HECTIC_SECRET_SQL - path to hectic-secret.sql
|
||||||
|
# HECTIC_MIGRATION_SQL - path to hectic-migration.sql
|
||||||
|
# HECTIC_INHERITANCE_SQL - path to hectic-inheritance.sql
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# apply_hectic_bundle <PGURL> [<DOTENV_CONTENT>]
|
||||||
|
#
|
||||||
|
# If DOTENV_CONTENT is non-empty, it is loaded into hectic.secret via
|
||||||
|
# hectic.load_secrets_from_env() after the bundle is applied.
|
||||||
|
|
||||||
|
apply_hectic_bundle() {
|
||||||
|
pgurl="${1:-}"
|
||||||
|
env_content="${2:-}"
|
||||||
|
|
||||||
|
if [ -z "$pgurl" ]; then
|
||||||
|
printf '%s\n' 'apply-hectic-bundle: PGURL is required (arg 1)' >&2
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
for var in HECTIC_VERSION_SQL HECTIC_SECRET_SQL HECTIC_MIGRATION_SQL HECTIC_INHERITANCE_SQL; do
|
||||||
|
eval "val=\${$var:-}"
|
||||||
|
if [ -z "$val" ]; then
|
||||||
|
printf '%s\n' "apply-hectic-bundle: $var not set" >&2
|
||||||
|
return 3
|
||||||
|
fi
|
||||||
|
if [ ! -r "$val" ]; then
|
||||||
|
printf '%s\n' "apply-hectic-bundle: $var not readable: $val" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_VERSION_SQL" || return 1
|
||||||
|
psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_SECRET_SQL" || return 1
|
||||||
|
psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_MIGRATION_SQL" || return 1
|
||||||
|
psql "$pgurl" -v ON_ERROR_STOP=1 -f "$HECTIC_INHERITANCE_SQL" || return 1
|
||||||
|
|
||||||
|
if [ -n "$env_content" ]; then
|
||||||
|
# Dollar-quote with $ps_env$ tag to preserve all content verbatim.
|
||||||
|
psql "$pgurl" -v ON_ERROR_STOP=1 <<SQL || return 1
|
||||||
|
SELECT hectic.load_secrets_from_env(\$ps_env\$
|
||||||
|
$env_content
|
||||||
|
\$ps_env\$);
|
||||||
|
SQL
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
0.1.0
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# hectic SQL bundle
|
||||||
|
|
||||||
|
Single source of truth for every object created in the `hectic` PostgreSQL
|
||||||
|
schema. Consumed by:
|
||||||
|
|
||||||
|
- `package/migrator` — applies the bundle on `migrator init` (mandatory).
|
||||||
|
- `package/db-tool` — applies the bundle in `database hydrate` (default; opt
|
||||||
|
out with `--no-hook`).
|
||||||
|
- External consumers (e.g. `proxydoe`) — invoke `psql -f` directly against the
|
||||||
|
paths exposed via `self.lib.hectic.*.path`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `HECTIC_VERSION` | Single version string for the whole bundle (e.g. `0.1.0`). Read via `lib.fileContents`. |
|
||||||
|
| `hectic-version.sql` | Templated. Creates `hectic.version`, inserts the current `versionString`, raises on mismatch. |
|
||||||
|
| `hectic-secret.sql` | Creates `hectic.secret`, `hectic.load_secrets_from_env(text)`, `hectic.get_secret(text)`. |
|
||||||
|
| `hectic-migration.sql` | Creates the `hectic.migration` table and supporting domains/triggers used by `migrator`. |
|
||||||
|
| `hectic-inheritance.sql` | Creates `hectic.created_at`, `hectic.updated_at`, `hectic.immutable` parent tables and the DDL event triggers that enforce inheritance, attach `BEFORE UPDATE` triggers, and block DML on immutable tables outside `migration_mode`. |
|
||||||
|
|
||||||
|
`hectic-version.sql` is templated at Nix evaluation time: `@HECTIC_VERSION@`
|
||||||
|
is substituted with the contents of `HECTIC_VERSION`. All other files are
|
||||||
|
applied verbatim.
|
||||||
|
|
||||||
|
## Apply order
|
||||||
|
|
||||||
|
The bundle MUST be applied in this order (enforced by
|
||||||
|
`apply-hectic-bundle.sh`):
|
||||||
|
|
||||||
|
1. `hectic-version.sql` — version check first; aborts the rest on mismatch.
|
||||||
|
2. `hectic-secret.sql`
|
||||||
|
3. `hectic-migration.sql`
|
||||||
|
4. `hectic-inheritance.sql`
|
||||||
|
|
||||||
|
Re-applying the bundle is idempotent — every CREATE uses
|
||||||
|
`IF NOT EXISTS` / `CREATE OR REPLACE`, and the version check accepts a row
|
||||||
|
that already matches.
|
||||||
|
|
||||||
|
## Nix API (`self.lib.hectic`)
|
||||||
|
|
||||||
|
```nix
|
||||||
|
self.lib.hectic = {
|
||||||
|
versionString; # e.g. "0.1.0"
|
||||||
|
version = { sql; }; # templated
|
||||||
|
secret = { sql; path; };
|
||||||
|
migration = { sql; path; };
|
||||||
|
inheritance = { sql; path; };
|
||||||
|
applyBundleScript; # ./hook/apply-hectic-bundle.sh
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`.sql` is the file contents as a string. `.path` is the Nix store path of the
|
||||||
|
verbatim source (only available on non-templated entries; consumers needing a
|
||||||
|
materialized version of `version.sql` must do
|
||||||
|
`pkgs.runCommand "hectic-version.sql" { text = self.lib.hectic.version.sql; passAsFile = ["text"]; } ''cp "$textPath" "$out"''`).
|
||||||
|
|
||||||
|
## Shell helper (`apply-hectic-bundle.sh`)
|
||||||
|
|
||||||
|
`lib/hook/apply-hectic-bundle.sh` is a dash-compatible helper sourced by both
|
||||||
|
`migrator` and `db-tool`. Public entry point:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
apply_hectic_bundle <PGURL> [<DOTENV_CONTENT>]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `<PGURL>` — full PostgreSQL connection string.
|
||||||
|
- `<DOTENV_CONTENT>` — optional. When present, after applying the bundle the
|
||||||
|
helper invokes `hectic.load_secrets_from_env(<dotenv>)` inside a
|
||||||
|
dollar-quoted (`$ps_env$`) string so secret values cannot terminate the
|
||||||
|
literal.
|
||||||
|
|
||||||
|
Required environment (paths to the SQL files):
|
||||||
|
|
||||||
|
- `HECTIC_VERSION_SQL`
|
||||||
|
- `HECTIC_SECRET_SQL`
|
||||||
|
- `HECTIC_MIGRATION_SQL`
|
||||||
|
- `HECTIC_INHERITANCE_SQL`
|
||||||
|
|
||||||
|
`migrator` and `db-tool` set these via Nix at build time. External consumers
|
||||||
|
typically invoke `psql -f` against the paths directly instead of sourcing the
|
||||||
|
helper.
|
||||||
|
|
||||||
|
## Adding a new SQL file
|
||||||
|
|
||||||
|
1. Add `lib/hook/sql/hectic-<name>.sql`.
|
||||||
|
2. Wire it into `lib/default.nix` under `lib.hectic.<name>`.
|
||||||
|
3. Inject `HECTIC_<NAME>_SQL` in both `package/migrator/default.nix` and
|
||||||
|
`package/db-tool/default.nix`.
|
||||||
|
4. Append a `psql -f "$HECTIC_<NAME>_SQL"` step to
|
||||||
|
`lib/hook/apply-hectic-bundle.sh` in the correct order.
|
||||||
|
5. Bump `HECTIC_VERSION` if the new content changes existing semantics.
|
||||||
|
6. Update tests in `test/package/migrator/test/postgresql/init-hectic-bundle/`
|
||||||
|
and `test/package/db-tool/test/postgresql/hydrate-hook/`.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
`HECTIC_VERSION` is a single global version for the bundle, not per-file.
|
||||||
|
Bump it on any breaking change to the schema. `hectic-version.sql` raises an
|
||||||
|
exception when the database row diverges from the bundle version, forcing a
|
||||||
|
deliberate migration before the rest of the bundle runs.
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
-- hectic.created_at / hectic.updated_at / hectic.immutable inheritance machinery.
|
||||||
|
--
|
||||||
|
-- Provides:
|
||||||
|
-- * schema hectic
|
||||||
|
-- * tables hectic.created_at(created_at TIMESTAMPTZ NOT NULL DEFAULT NOW())
|
||||||
|
-- hectic.updated_at(updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW())
|
||||||
|
-- hectic.immutable() -- pure marker
|
||||||
|
-- * function hectic.set_updated_at() -- BEFORE UPDATE row trigger function
|
||||||
|
-- * function hectic.block_immutable_dml()
|
||||||
|
-- BEFORE INSERT/UPDATE/DELETE/TRUNCATE row+statement trigger function;
|
||||||
|
-- allows DML iff current_setting('hectic.migration_mode', true) = 'on'.
|
||||||
|
-- * GUC hectic.inheritance_extra_excluded_schemas
|
||||||
|
-- (text, comma-separated list of schemas the enforcement trigger skips)
|
||||||
|
-- * GUC hectic.migration_mode
|
||||||
|
-- (text, 'on' enables DML on tables inheriting hectic.immutable.
|
||||||
|
-- Intended use: SET LOCAL inside a migration transaction.)
|
||||||
|
-- * event trigger hectic_enforce_created_at_inheritance
|
||||||
|
-- RAISE EXCEPTION on CREATE TABLE that does not inherit hectic.created_at
|
||||||
|
-- * event trigger hectic_attach_updated_at_trigger
|
||||||
|
-- auto-attaches BEFORE UPDATE row trigger calling hectic.set_updated_at()
|
||||||
|
-- on any new table that inherits hectic.updated_at and lacks one.
|
||||||
|
-- * event trigger hectic_attach_immutable_triggers
|
||||||
|
-- auto-attaches BEFORE INSERT/UPDATE/DELETE FOR EACH ROW and BEFORE
|
||||||
|
-- TRUNCATE FOR EACH STATEMENT triggers calling hectic.block_immutable_dml()
|
||||||
|
-- on any new table that inherits hectic.immutable and lacks them.
|
||||||
|
--
|
||||||
|
-- Idempotent: safe to run on an already-bootstrapped database.
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS "hectic";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."created_at" (
|
||||||
|
"created_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."updated_at" (
|
||||||
|
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."immutable" ();
|
||||||
|
|
||||||
|
DO $bootstrap$
|
||||||
|
BEGIN
|
||||||
|
PERFORM set_config('hectic.inheritance_extra_excluded_schemas',
|
||||||
|
current_setting('hectic.inheritance_extra_excluded_schemas', true),
|
||||||
|
false);
|
||||||
|
EXCEPTION WHEN undefined_object THEN
|
||||||
|
PERFORM set_config('hectic.inheritance_extra_excluded_schemas', '', false);
|
||||||
|
END
|
||||||
|
$bootstrap$;
|
||||||
|
|
||||||
|
DO $bootstrap_mm$
|
||||||
|
BEGIN
|
||||||
|
PERFORM set_config('hectic.migration_mode',
|
||||||
|
current_setting('hectic.migration_mode', true),
|
||||||
|
false);
|
||||||
|
EXCEPTION WHEN undefined_object THEN
|
||||||
|
PERFORM set_config('hectic.migration_mode', '', false);
|
||||||
|
END
|
||||||
|
$bootstrap_mm$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."set_updated_at"() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
NEW."updated_at" := NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."block_immutable_dml"() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
mm text;
|
||||||
|
BEGIN
|
||||||
|
BEGIN
|
||||||
|
mm := current_setting('hectic.migration_mode', true);
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
mm := '';
|
||||||
|
END;
|
||||||
|
IF mm = 'on' THEN
|
||||||
|
IF TG_LEVEL = 'STATEMENT' THEN RETURN NULL; END IF;
|
||||||
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END IF;
|
||||||
|
RAISE EXCEPTION
|
||||||
|
'hectic: table %.% inherits hectic.immutable; % blocked outside migration_mode',
|
||||||
|
quote_ident(TG_TABLE_SCHEMA), quote_ident(TG_TABLE_NAME), TG_OP
|
||||||
|
USING HINT = 'wrap the statement in a migration transaction with '
|
||||||
|
|| 'SET LOCAL hectic.migration_mode = ''on''';
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."_is_excluded_schema"(p_schema text) RETURNS boolean
|
||||||
|
LANGUAGE plpgsql STABLE AS $fn$
|
||||||
|
DECLARE
|
||||||
|
extra text;
|
||||||
|
s text;
|
||||||
|
BEGIN
|
||||||
|
IF p_schema = 'hectic'
|
||||||
|
OR p_schema = 'information_schema'
|
||||||
|
OR p_schema LIKE 'pg\_%' ESCAPE '\'
|
||||||
|
THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
BEGIN
|
||||||
|
extra := current_setting('hectic.inheritance_extra_excluded_schemas', true);
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
extra := '';
|
||||||
|
END;
|
||||||
|
IF extra IS NULL OR extra = '' THEN
|
||||||
|
RETURN false;
|
||||||
|
END IF;
|
||||||
|
FOREACH s IN ARRAY string_to_array(extra, ',') LOOP
|
||||||
|
IF btrim(s) = p_schema THEN
|
||||||
|
RETURN true;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
RETURN false;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."_table_inherits"(p_oid oid, p_parent regclass) RETURNS boolean
|
||||||
|
LANGUAGE sql STABLE AS $fn$
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_inherits
|
||||||
|
WHERE inhrelid = p_oid AND inhparent = p_parent
|
||||||
|
);
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."enforce_created_at_inheritance"() RETURNS event_trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
obj record;
|
||||||
|
rel pg_class;
|
||||||
|
schema_name text;
|
||||||
|
parent_oid oid;
|
||||||
|
BEGIN
|
||||||
|
parent_oid := 'hectic.created_at'::regclass;
|
||||||
|
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
|
||||||
|
LOOP
|
||||||
|
SELECT * INTO rel FROM pg_class WHERE oid = obj.objid;
|
||||||
|
IF NOT FOUND THEN CONTINUE; END IF;
|
||||||
|
IF rel.relpersistence = 't' THEN CONTINUE; END IF;
|
||||||
|
IF rel.relispartition THEN CONTINUE; END IF;
|
||||||
|
SELECT nspname INTO schema_name FROM pg_namespace WHERE oid = rel.relnamespace;
|
||||||
|
IF "hectic"."_is_excluded_schema"(schema_name) THEN CONTINUE; END IF;
|
||||||
|
IF NOT "hectic"."_table_inherits"(rel.oid, parent_oid) THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
'hectic: table %.% must INHERITS (hectic.created_at)',
|
||||||
|
quote_ident(schema_name), quote_ident(rel.relname)
|
||||||
|
USING HINT = 'add INHERITS ("hectic"."created_at") to the CREATE TABLE statement, '
|
||||||
|
|| 'or add the schema to hectic.inheritance_extra_excluded_schemas';
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."attach_updated_at_trigger"() RETURNS event_trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
obj record;
|
||||||
|
rel pg_class;
|
||||||
|
schema_name text;
|
||||||
|
parent_oid oid;
|
||||||
|
trigger_name text;
|
||||||
|
has_trigger boolean;
|
||||||
|
BEGIN
|
||||||
|
parent_oid := 'hectic.updated_at'::regclass;
|
||||||
|
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
|
||||||
|
LOOP
|
||||||
|
SELECT * INTO rel FROM pg_class WHERE oid = obj.objid;
|
||||||
|
IF NOT FOUND THEN CONTINUE; END IF;
|
||||||
|
IF rel.relpersistence = 't' THEN CONTINUE; END IF;
|
||||||
|
IF rel.relispartition THEN CONTINUE; END IF;
|
||||||
|
SELECT nspname INTO schema_name FROM pg_namespace WHERE oid = rel.relnamespace;
|
||||||
|
IF schema_name = 'hectic' THEN CONTINUE; END IF;
|
||||||
|
IF NOT "hectic"."_table_inherits"(rel.oid, parent_oid) THEN CONTINUE; END IF;
|
||||||
|
trigger_name := 'hectic_set_updated_at';
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgrelid = rel.oid AND tgname = trigger_name AND NOT tgisinternal
|
||||||
|
) INTO has_trigger;
|
||||||
|
IF has_trigger THEN CONTINUE; END IF;
|
||||||
|
EXECUTE format(
|
||||||
|
'CREATE TRIGGER %I BEFORE UPDATE ON %I.%I FOR EACH ROW EXECUTE FUNCTION "hectic"."set_updated_at"()',
|
||||||
|
trigger_name, schema_name, rel.relname
|
||||||
|
);
|
||||||
|
END LOOP;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
DROP EVENT TRIGGER IF EXISTS "hectic_enforce_created_at_inheritance";
|
||||||
|
CREATE EVENT TRIGGER "hectic_enforce_created_at_inheritance"
|
||||||
|
ON ddl_command_end
|
||||||
|
WHEN TAG IN ('CREATE TABLE')
|
||||||
|
EXECUTE FUNCTION "hectic"."enforce_created_at_inheritance"();
|
||||||
|
|
||||||
|
DROP EVENT TRIGGER IF EXISTS "hectic_attach_updated_at_trigger";
|
||||||
|
CREATE EVENT TRIGGER "hectic_attach_updated_at_trigger"
|
||||||
|
ON ddl_command_end
|
||||||
|
WHEN TAG IN ('CREATE TABLE')
|
||||||
|
EXECUTE FUNCTION "hectic"."attach_updated_at_trigger"();
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."attach_immutable_triggers"() RETURNS event_trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
obj record;
|
||||||
|
rel pg_class;
|
||||||
|
schema_name text;
|
||||||
|
parent_oid oid;
|
||||||
|
has_row boolean;
|
||||||
|
has_trunc boolean;
|
||||||
|
BEGIN
|
||||||
|
parent_oid := 'hectic.immutable'::regclass;
|
||||||
|
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands() WHERE command_tag = 'CREATE TABLE'
|
||||||
|
LOOP
|
||||||
|
SELECT * INTO rel FROM pg_class WHERE oid = obj.objid;
|
||||||
|
IF NOT FOUND THEN CONTINUE; END IF;
|
||||||
|
IF rel.relpersistence = 't' THEN CONTINUE; END IF;
|
||||||
|
IF rel.relispartition THEN CONTINUE; END IF;
|
||||||
|
SELECT nspname INTO schema_name FROM pg_namespace WHERE oid = rel.relnamespace;
|
||||||
|
IF schema_name = 'hectic' THEN CONTINUE; END IF;
|
||||||
|
IF NOT "hectic"."_table_inherits"(rel.oid, parent_oid) THEN CONTINUE; END IF;
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgrelid = rel.oid
|
||||||
|
AND tgname = 'hectic_block_immutable_dml'
|
||||||
|
AND NOT tgisinternal
|
||||||
|
) INTO has_row;
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgrelid = rel.oid
|
||||||
|
AND tgname = 'hectic_block_immutable_truncate'
|
||||||
|
AND NOT tgisinternal
|
||||||
|
) INTO has_trunc;
|
||||||
|
IF NOT has_row THEN
|
||||||
|
EXECUTE format(
|
||||||
|
'CREATE TRIGGER %I BEFORE INSERT OR UPDATE OR DELETE ON %I.%I '
|
||||||
|
|| 'FOR EACH ROW EXECUTE FUNCTION "hectic"."block_immutable_dml"()',
|
||||||
|
'hectic_block_immutable_dml', schema_name, rel.relname
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
IF NOT has_trunc THEN
|
||||||
|
EXECUTE format(
|
||||||
|
'CREATE TRIGGER %I BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON %I.%I '
|
||||||
|
|| 'FOR EACH STATEMENT EXECUTE FUNCTION "hectic"."block_immutable_dml"()',
|
||||||
|
'hectic_block_immutable_truncate', schema_name, rel.relname
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
DROP EVENT TRIGGER IF EXISTS "hectic_attach_immutable_triggers";
|
||||||
|
CREATE EVENT TRIGGER "hectic_attach_immutable_triggers"
|
||||||
|
ON ddl_command_end
|
||||||
|
WHEN TAG IN ('CREATE TABLE')
|
||||||
|
EXECUTE FUNCTION "hectic"."attach_immutable_triggers"();
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
DO $bootstrap$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
|
WHERE n.nspname = 'hectic' AND t.typname = 'migration_name'
|
||||||
|
) THEN
|
||||||
|
CREATE DOMAIN "hectic"."migration_name" AS TEXT
|
||||||
|
CHECK (VALUE ~ '^[0-9]{14}-.*');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_type t JOIN pg_namespace n ON n.oid = t.typnamespace
|
||||||
|
WHERE n.nspname = 'hectic' AND t.typname = 'sha256'
|
||||||
|
) THEN
|
||||||
|
CREATE DOMAIN "hectic"."sha256" AS CHAR(64)
|
||||||
|
CHECK (VALUE ~ '^[0-9a-f]{64}$');
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$bootstrap$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."sha256_lower"() RETURNS trigger
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
NEW."hash" := lower(NEW."hash");
|
||||||
|
RETURN NEW;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."migration" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"name" "hectic"."migration_name" UNIQUE NOT NULL,
|
||||||
|
"hash" "hectic"."sha256" UNIQUE NOT NULL,
|
||||||
|
"applied_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $trg$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_trigger
|
||||||
|
WHERE tgname = 'hectic_t_sha256_lower'
|
||||||
|
AND tgrelid = '"hectic"."migration"'::regclass
|
||||||
|
AND NOT tgisinternal
|
||||||
|
) THEN
|
||||||
|
CREATE TRIGGER "hectic_t_sha256_lower"
|
||||||
|
BEFORE INSERT OR UPDATE ON "hectic"."migration"
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION "hectic"."sha256_lower"();
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$trg$;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."secret" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"key" TEXT UNIQUE NOT NULL,
|
||||||
|
"value" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."load_secrets_from_env"(env_content TEXT)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
DECLARE
|
||||||
|
line TEXT;
|
||||||
|
k TEXT;
|
||||||
|
v TEXT;
|
||||||
|
BEGIN
|
||||||
|
TRUNCATE TABLE "hectic"."secret";
|
||||||
|
|
||||||
|
FOR line IN
|
||||||
|
SELECT regexp_split_to_table(env_content, E'\n')
|
||||||
|
LOOP
|
||||||
|
line := btrim(line);
|
||||||
|
|
||||||
|
IF line = '' OR line LIKE '#%' THEN
|
||||||
|
CONTINUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
k := split_part(line, '=', 1);
|
||||||
|
v := substring(line FROM position('=' IN line) + 1);
|
||||||
|
|
||||||
|
k := btrim(k);
|
||||||
|
v := btrim(v);
|
||||||
|
|
||||||
|
IF v ~ '^".*"$' OR v ~ '^''.*''$' THEN
|
||||||
|
v := substring(v FROM 2 FOR char_length(v) - 2);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
INSERT INTO "hectic"."secret" ("key", "value") VALUES (k, v);
|
||||||
|
END LOOP;
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION "hectic"."get_secret"(k TEXT)
|
||||||
|
RETURNS TEXT
|
||||||
|
LANGUAGE plpgsql AS $fn$
|
||||||
|
BEGIN
|
||||||
|
RETURN (
|
||||||
|
SELECT "value"
|
||||||
|
FROM "hectic"."secret"
|
||||||
|
WHERE "key" = k
|
||||||
|
);
|
||||||
|
END
|
||||||
|
$fn$;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
CREATE SCHEMA IF NOT EXISTS "hectic";
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "hectic"."version" (
|
||||||
|
"name" TEXT PRIMARY KEY,
|
||||||
|
"version" TEXT NOT NULL,
|
||||||
|
"installed_at" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
DO $check$
|
||||||
|
DECLARE
|
||||||
|
existing TEXT;
|
||||||
|
BEGIN
|
||||||
|
SELECT "version" INTO existing
|
||||||
|
FROM "hectic"."version"
|
||||||
|
WHERE "name" = 'hectic';
|
||||||
|
|
||||||
|
IF existing IS NULL THEN
|
||||||
|
INSERT INTO "hectic"."version" ("name", "version")
|
||||||
|
VALUES ('hectic', '@HECTIC_VERSION@');
|
||||||
|
ELSIF existing <> '@HECTIC_VERSION@' THEN
|
||||||
|
RAISE EXCEPTION
|
||||||
|
'hectic schema version mismatch: database has %, code expects %',
|
||||||
|
existing, '@HECTIC_VERSION@';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$check$;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
file: let
|
||||||
|
envText = builtins.readFile file;
|
||||||
|
envLines = builtins.split "\n" envText;
|
||||||
|
lines = builtins.filter (line: (builtins.match "^.*=.*" line) != null) envLines;
|
||||||
|
#attributes = builtins.listToAttrs (builtins.map (line: let
|
||||||
|
# parts = builtins.split "=" line;
|
||||||
|
# key = builtins.substring 0 (builtins.stringLength parts[0] - 3) parts[0]; # Remove "var" prefix
|
||||||
|
# value = parts[1];
|
||||||
|
#in {
|
||||||
|
# name = key;
|
||||||
|
# value = value;
|
||||||
|
#}) lines);
|
||||||
|
in { inherit envLines lines; }
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
check_tool() {
|
||||||
|
if ! command -v "$1" >/dev/null; then
|
||||||
|
echo "Required tool \`$2\` are not installed or binary \`$1\` not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
printf '\033[0;34mDetecting local directories...\033[0m\n'
|
||||||
|
if git_root=$($BIN_GIT rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
LOCAL_DIR="$git_root"
|
||||||
|
printf '\033[0;32mFound git root: \033[1;37m%s\033[0m\n' "$LOCAL_DIR"
|
||||||
|
else
|
||||||
|
LOCAL_DIR="$(pwd)"
|
||||||
|
printf '\033[1;33mNot in git repo, using current dir: \033[1;37m%s\033[0m\n' "$LOCAL_DIR"
|
||||||
|
printf 'Are you realy want continue? (y/n):\n'
|
||||||
|
read -r CONTINUE
|
||||||
|
if [ "$CONTINUE" != "y" ]; then
|
||||||
|
printf '\033[0;31mAborting...\033[0m\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
PURPLE='\033[0;35m'
|
||||||
|
MAGENTA="$PURPLE"
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
WHITE='\033[1;37m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
LOG_PATH="/var/log/hectic/activation.log"
|
||||||
|
|
||||||
|
if ! mkdir -p "$(dirname "$LOG_PATH")" 2>/dev/null; then
|
||||||
|
LOG_PATH="/dev/null"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info() { text=$1; shift; printf "%b ${text}%b\n" "$BLUE" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
|
log_success() { text=$1; shift; printf "%b ${text}%b\n" "$GREEN" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
|
log_warning() { text=$1; shift; printf "%b ${text}%b\n" "$YELLOW" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
|
log_error() { text=$1; shift; printf "%b ${text}%b\n" "$RED" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
|
log_step() { text=$1; shift; printf "%b ${text}%b\n" "$PURPLE" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
|
|
||||||
|
log_header() { printf "\n%b=== %s ===%b\n" "$WHITE" "$@" "$NC" | tee -a "$LOG_PATH" >&2; }
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
}:
|
||||||
|
with builtins;
|
||||||
|
with inputs.nixpkgs.lib;
|
||||||
|
with self.lib;
|
||||||
|
let
|
||||||
|
# Combine hectic modules into one
|
||||||
|
hectic.imports = attrValues (
|
||||||
|
readModulesRecursive' ./hectic { inherit flake self inputs; }
|
||||||
|
);
|
||||||
|
# Read generic modules separately
|
||||||
|
generic = readModulesRecursive'
|
||||||
|
./generic
|
||||||
|
{ inherit flake self inputs; };
|
||||||
|
in generic // {
|
||||||
|
inherit hectic;
|
||||||
|
default = hectic;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
userNames = [
|
||||||
|
"yukkop"
|
||||||
|
"liquiz"
|
||||||
|
"vismajor"
|
||||||
|
"lvgkcfjl"
|
||||||
|
"MrAlex0O"
|
||||||
|
"Антоша"
|
||||||
|
"snuff"
|
||||||
|
];
|
||||||
|
|
||||||
|
adminNames = [ "yukkop" ];
|
||||||
|
in {
|
||||||
|
hectic.generic.matrix-cluster.users = builtins.listToAttrs (
|
||||||
|
map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = {
|
||||||
|
passwordFile = config.sops.secrets."matrix/users/${name}/password".path;
|
||||||
|
} // lib.optionalAttrs (builtins.elem name adminNames) {
|
||||||
|
admin = true;
|
||||||
|
};
|
||||||
|
}) userNames
|
||||||
|
);
|
||||||
|
|
||||||
|
sops.secrets = builtins.listToAttrs (
|
||||||
|
map (name: {
|
||||||
|
name = "matrix/users/${name}/password";
|
||||||
|
value = {
|
||||||
|
key = "matrix/users/${name}/password";
|
||||||
|
owner = "matrix-synapse";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
}) userNames
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,592 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.generic.matrix-cluster;
|
||||||
|
s3Cfg = cfg.objectStorage.s3;
|
||||||
|
|
||||||
|
s3Plugin = pkgs.matrix-synapse-plugins.matrix-synapse-s3-storage-provider;
|
||||||
|
s3ConfigDir = "/run/matrix-synapse";
|
||||||
|
s3ConfigFile = "${s3ConfigDir}/s3-media-storage.yaml";
|
||||||
|
|
||||||
|
pgDataDir = "/var/lib/postgresql/17";
|
||||||
|
|
||||||
|
matrixUsers = builtins.attrNames cfg.users;
|
||||||
|
|
||||||
|
mkUserRegistration = name: let
|
||||||
|
user = cfg.users.${name};
|
||||||
|
adminFlag = if user.admin then "--admin" else "--no-admin";
|
||||||
|
in ''
|
||||||
|
if [ ! -r "${user.passwordFile}" ]; then
|
||||||
|
printf 'Missing Matrix password file for %s: %s\n' '${name}' '${user.passwordFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.matrix-synapse}/bin/register_new_matrix_user \
|
||||||
|
-u '${name}' \
|
||||||
|
-p "$(tr -d '\n' < "${user.passwordFile}")" \
|
||||||
|
-k "$REGISTRATION_SHARED_SECRET" \
|
||||||
|
${adminFlag} \
|
||||||
|
http://127.0.0.1:8008 || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
synapseEnabled =
|
||||||
|
if cfg.overrideEnableSynapse != null
|
||||||
|
then cfg.overrideEnableSynapse
|
||||||
|
else cfg.role == "primary";
|
||||||
|
|
||||||
|
mkS3Config = ''
|
||||||
|
if [ ! -r "${s3Cfg.credentialsFile}" ]; then
|
||||||
|
printf 'Missing Matrix object storage credentials file: %s\n' '${s3Cfg.credentialsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
. "${s3Cfg.credentialsFile}"
|
||||||
|
|
||||||
|
if [ -z "$ACCESS_KEY_ID" ] || [ -z "$SECRET_ACCESS_KEY" ]; then
|
||||||
|
printf 'ACCESS_KEY_ID or SECRET_ACCESS_KEY missing in %s\n' '${s3Cfg.credentialsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${s3ConfigDir}"
|
||||||
|
|
||||||
|
cat > "${s3ConfigFile}" <<EOF
|
||||||
|
media_storage_providers:
|
||||||
|
- module: s3_storage_provider.S3StorageProviderBackend
|
||||||
|
store_local: ${lib.boolToString s3Cfg.storeLocal}
|
||||||
|
store_remote: ${lib.boolToString s3Cfg.storeRemote}
|
||||||
|
store_synchronous: ${lib.boolToString s3Cfg.storeSynchronous}
|
||||||
|
config:
|
||||||
|
bucket: ${s3Cfg.bucket}
|
||||||
|
endpoint_url: ${s3Cfg.endpointUrl}
|
||||||
|
region_name: ${s3Cfg.regionName}
|
||||||
|
prefix: "${s3Cfg.prefix}"
|
||||||
|
storage_class: "${s3Cfg.storageClass}"
|
||||||
|
threadpool_size: ${toString s3Cfg.threadpoolSize}
|
||||||
|
access_key_id: $ACCESS_KEY_ID
|
||||||
|
secret_access_key: $SECRET_ACCESS_KEY
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chown matrix-synapse:matrix-synapse "${s3ConfigFile}"
|
||||||
|
chmod 0400 "${s3ConfigFile}"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
options.hectic.generic.matrix-cluster = {
|
||||||
|
enable = lib.mkEnableOption "Matrix Synapse active/passive cluster node";
|
||||||
|
|
||||||
|
role = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "primary" "standby" ];
|
||||||
|
description = ''
|
||||||
|
Cluster role of this node. The primary runs Synapse and accepts WAL
|
||||||
|
streaming connections; the standby runs a hot-standby Postgres replica
|
||||||
|
only and keeps Synapse disabled until failover.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
matrixDomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Matrix server_name (also nginx vhost / ACME cert name).";
|
||||||
|
};
|
||||||
|
|
||||||
|
signingKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
Path to the Synapse homeserver signing key. Mounted into place at
|
||||||
|
/var/lib/matrix-synapse/homeserver.signing.key on activation.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
secretsFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Extra Synapse YAML config (registration_shared_secret, macaroon_secret_key,
|
||||||
|
form_secret). Loaded via matrix-synapse extraConfigFiles. Required when
|
||||||
|
Synapse is enabled on this node (primary, or standby after failover).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
turnSecretFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Shared secret file used by coturn for Matrix voice/video calls.
|
||||||
|
When set together with `publicIp`, the active Synapse node also enables
|
||||||
|
coturn and publishes TURN URIs to clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
publicIp = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Public IP address advertised to coturn for listening and relaying.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
maxUploadSize = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "2G";
|
||||||
|
};
|
||||||
|
|
||||||
|
enableRegistration = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
users = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
passwordFile = lib.mkOption { type = lib.types.str; };
|
||||||
|
admin = lib.mkOption { type = lib.types.bool; default = false; };
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Declarative Matrix users provisioned via register_new_matrix_user.";
|
||||||
|
};
|
||||||
|
|
||||||
|
overrideEnableSynapse = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.bool;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
When non-null, forces Synapse on/off regardless of role. Used during
|
||||||
|
failover: set to true on the standby once it has been promoted, or
|
||||||
|
false on the primary to drain it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
objectStorage.s3 = {
|
||||||
|
bucket = lib.mkOption { type = lib.types.str; };
|
||||||
|
regionName = lib.mkOption { type = lib.types.str; };
|
||||||
|
endpointUrl = lib.mkOption { type = lib.types.str; };
|
||||||
|
credentialsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
env-style file with ACCESS_KEY_ID= and SECRET_ACCESS_KEY=. MUST be
|
||||||
|
the SAME credentials/bucket on both primary and standby.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
mediaStorePath = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/matrix-synapse/media_store";
|
||||||
|
};
|
||||||
|
prefix = lib.mkOption { type = lib.types.str; default = ""; };
|
||||||
|
storageClass = lib.mkOption { type = lib.types.str; default = "STANDARD"; };
|
||||||
|
threadpoolSize = lib.mkOption { type = lib.types.int; default = 40; };
|
||||||
|
storeLocal = lib.mkOption { type = lib.types.bool; default = true; };
|
||||||
|
storeRemote = lib.mkOption { type = lib.types.bool; default = true; };
|
||||||
|
storeSynchronous = lib.mkOption { type = lib.types.bool; default = true; };
|
||||||
|
};
|
||||||
|
|
||||||
|
replication = {
|
||||||
|
peerHost = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Public IP/hostname of the other cluster node.";
|
||||||
|
};
|
||||||
|
peerPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 5432;
|
||||||
|
};
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
File containing either a raw replication password or a libpq passfile
|
||||||
|
line. Used as `passfile=` in primary_conninfo on the standby and to
|
||||||
|
set the password of the `replication` Postgres role on the primary.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
allowedSourceIPs = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [];
|
||||||
|
description = ''
|
||||||
|
CIDRs allowed to connect to Postgres for replication. Used on the
|
||||||
|
primary in pg_hba.conf hostssl entries and to gate the firewall.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
sslMode = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "require";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
acme = {
|
||||||
|
enable = lib.mkEnableOption "Porkbun DNS-01 ACME for matrixDomain";
|
||||||
|
email = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "hectic.yukkop.it@gmail.com";
|
||||||
|
description = "ACME registration email (passed to security.acme.defaults.email).";
|
||||||
|
};
|
||||||
|
porkbunApiKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "File containing PORKBUN_API_KEY value.";
|
||||||
|
};
|
||||||
|
porkbunSecretApiKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "File containing PORKBUN_SECRET_API_KEY value.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
jitsi.preferredDomain = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Optional self-hosted Jitsi Meet domain to advertise to Matrix/Element
|
||||||
|
clients alongside the cluster-managed homeserver.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge [
|
||||||
|
|
||||||
|
{
|
||||||
|
# signing key mount: copy into matrix-synapse data dir with correct perms
|
||||||
|
# regardless of whether Synapse is currently enabled on this node, so a
|
||||||
|
# failover flip does not need a separate provisioning step.
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/lib/matrix-synapse 0750 matrix-synapse matrix-synapse -"
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services.matrix-cluster-signing-key = {
|
||||||
|
description = "Install Matrix Synapse signing key from secrets";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = lib.optional synapseEnabled "matrix-synapse.service";
|
||||||
|
requiredBy = lib.optional synapseEnabled "matrix-synapse.service";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
install -d -o matrix-synapse -g matrix-synapse -m 0750 /var/lib/matrix-synapse
|
||||||
|
install -o matrix-synapse -g matrix-synapse -m 0400 \
|
||||||
|
"${cfg.signingKeyFile}" \
|
||||||
|
/var/lib/matrix-synapse/homeserver.signing.key
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.matrix-synapse = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "matrix-synapse";
|
||||||
|
};
|
||||||
|
users.groups.matrix-synapse = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
(lib.mkIf synapseEnabled {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = cfg.secretsFile != null;
|
||||||
|
message = "hectic.generic.matrix-cluster.secretsFile must be set when Synapse runs on this node.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = (cfg.turnSecretFile == null) == (cfg.publicIp == null);
|
||||||
|
message = "hectic.generic.matrix-cluster.turnSecretFile and publicIp must be set together.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
services.coturn = lib.mkIf (cfg.turnSecretFile != null) rec {
|
||||||
|
enable = true;
|
||||||
|
realm = cfg.matrixDomain;
|
||||||
|
use-auth-secret = true;
|
||||||
|
static-auth-secret-file = cfg.turnSecretFile;
|
||||||
|
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
|
||||||
|
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
|
||||||
|
listening-ips = [ cfg.publicIp ];
|
||||||
|
relay-ips = [ cfg.publicIp ];
|
||||||
|
listening-port = 3478;
|
||||||
|
tls-listening-port = 5349;
|
||||||
|
no-cli = true;
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
verbose
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.matrix-synapse = {
|
||||||
|
enable = true;
|
||||||
|
plugins = [ s3Plugin ];
|
||||||
|
extraConfigFiles = [ cfg.secretsFile s3ConfigFile ];
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
server_name = cfg.matrixDomain;
|
||||||
|
public_baseurl = "https://${cfg.matrixDomain}";
|
||||||
|
max_upload_size = cfg.maxUploadSize;
|
||||||
|
media_store_path = s3Cfg.mediaStorePath;
|
||||||
|
signing_key_path = "/var/lib/matrix-synapse/homeserver.signing.key";
|
||||||
|
|
||||||
|
experimental_features = {
|
||||||
|
msc3266_enabled = true;
|
||||||
|
msc4140_enabled = true;
|
||||||
|
msc4143_enabled = true;
|
||||||
|
msc4222_enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
matrix_rtc = {
|
||||||
|
transports = [
|
||||||
|
{
|
||||||
|
type = "livekit";
|
||||||
|
livekit_service_url = "https://${cfg.matrixDomain}/livekit/jwt";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
listeners = [
|
||||||
|
{
|
||||||
|
port = 8008;
|
||||||
|
bind_addresses = [ "0.0.0.0" ];
|
||||||
|
type = "http";
|
||||||
|
tls = false;
|
||||||
|
resources = [
|
||||||
|
{
|
||||||
|
names = [ "client" "federation" "openid" ];
|
||||||
|
compress = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
enable_registration = cfg.enableRegistration;
|
||||||
|
enable_registration_without_verification = cfg.enableRegistration;
|
||||||
|
} // lib.optionalAttrs (cfg.turnSecretFile != null) {
|
||||||
|
turn_uris = [
|
||||||
|
"turn:${cfg.matrixDomain}:3478?transport=udp"
|
||||||
|
"turn:${cfg.matrixDomain}:3478?transport=tcp"
|
||||||
|
"turns:${cfg.matrixDomain}:5349?transport=udp"
|
||||||
|
"turns:${cfg.matrixDomain}:5349?transport=tcp"
|
||||||
|
];
|
||||||
|
turn_user_lifetime = 86400000;
|
||||||
|
turn_allow_guests = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkgs.matrix-synapse ];
|
||||||
|
|
||||||
|
systemd.services.matrix-synapse-s3-config = {
|
||||||
|
description = "Generate Synapse S3 media storage config";
|
||||||
|
before = [ "matrix-synapse.service" ];
|
||||||
|
requiredBy = [ "matrix-synapse.service" ];
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = mkS3Config;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts.${cfg.matrixDomain} = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
|
extraConfig = ''
|
||||||
|
client_max_body_size ${cfg.maxUploadSize};
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
locations."=/.well-known/matrix/server" = {
|
||||||
|
extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization";
|
||||||
|
'';
|
||||||
|
return = "200 '{\"m.server\": \"${cfg.matrixDomain}:443\"}'";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = lib.mkIf (cfg.turnSecretFile != null) {
|
||||||
|
allowedUDPPorts = [ 3478 5349 ];
|
||||||
|
allowedTCPPorts = [ 3478 5349 ];
|
||||||
|
allowedTCPPortRanges = [
|
||||||
|
{
|
||||||
|
from = 49152;
|
||||||
|
to = 65535;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
allowedUDPPortRanges = [
|
||||||
|
{
|
||||||
|
from = 49152;
|
||||||
|
to = 65535;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.matrix-synapse-users = lib.mkIf (matrixUsers != []) {
|
||||||
|
description = "Provision Matrix Synapse users";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "matrix-synapse.service" ];
|
||||||
|
requires = [ "matrix-synapse.service" ];
|
||||||
|
path = with pkgs; [ curl coreutils gawk ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "matrix-synapse";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
until curl -sf http://127.0.0.1:8008/_matrix/client/versions >/dev/null; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
REGISTRATION_SHARED_SECRET="$(awk -F': *' '$1 == "registration_shared_secret" { print $2; exit }' "${cfg.secretsFile}")"
|
||||||
|
|
||||||
|
if [ -z "$REGISTRATION_SHARED_SECRET" ]; then
|
||||||
|
printf 'registration_shared_secret not found in %s\n' '${cfg.secretsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${lib.concatStringsSep "\n" (map mkUserRegistration matrixUsers)}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
{
|
||||||
|
services.postgresql = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.postgresql_17;
|
||||||
|
enableTCPIP = true;
|
||||||
|
|
||||||
|
initdbArgs = [ "--locale=C" "--encoding=UTF8" ];
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
wal_level = "replica";
|
||||||
|
max_wal_senders = 4;
|
||||||
|
hot_standby = "on";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(lib.mkIf (cfg.role == "primary") {
|
||||||
|
services.postgresql = {
|
||||||
|
authentication = lib.concatStringsSep "\n" ([
|
||||||
|
"local all all trust"
|
||||||
|
"host sameuser all 127.0.0.1/32 scram-sha-256"
|
||||||
|
"host sameuser all ::1/128 scram-sha-256"
|
||||||
|
"host all all ::1/128 scram-sha-256"
|
||||||
|
"host all all 0.0.0.0/0 scram-sha-256"
|
||||||
|
"host replication postgres 127.0.0.1/32 scram-sha-256"
|
||||||
|
"host replication postgres ::1/128 scram-sha-256"
|
||||||
|
] ++ map (cidr:
|
||||||
|
"hostssl replication replication ${cidr} scram-sha-256"
|
||||||
|
) cfg.replication.allowedSourceIPs);
|
||||||
|
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "replication";
|
||||||
|
ensureClauses = {
|
||||||
|
login = true;
|
||||||
|
replication = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# Apply replication password from SOPS-mounted file after postgres start.
|
||||||
|
systemd.services.matrix-cluster-replication-password = {
|
||||||
|
description = "Set Postgres replication role password from SOPS";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "postgresql.service" ];
|
||||||
|
requires = [ "postgresql.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "postgres";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
PW="$(tr -d '\n' < "${cfg.replication.passwordFile}")"
|
||||||
|
${config.services.postgresql.package}/bin/psql -v ON_ERROR_STOP=1 -c \
|
||||||
|
"ALTER ROLE replication WITH LOGIN REPLICATION PASSWORD '$PW';"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf (cfg.role == "standby") {
|
||||||
|
systemd.targets.postgresql.requires = lib.mkForce [
|
||||||
|
"postgresql.service"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Hot-standby bootstrap: standby.signal + primary_conninfo with passfile.
|
||||||
|
# pg_basebackup must be run manually (see runbook) before this activates
|
||||||
|
# for the first time.
|
||||||
|
systemd.services.matrix-cluster-standby-bootstrap = {
|
||||||
|
description = "Configure Matrix Postgres hot standby";
|
||||||
|
wantedBy = [ "postgresql.service" ];
|
||||||
|
before = [ "postgresql.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
if [ ! -d "${pgDataDir}" ]; then
|
||||||
|
echo "Postgres data dir ${pgDataDir} missing; run pg_basebackup first (see MATRIX-FAILOVER-RUNBOOK.md)" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Materialize a libpq passfile from the raw password secret.
|
||||||
|
PASSFILE=/var/lib/postgresql/.matrix-cluster-replication.passfile
|
||||||
|
PW="$(tr -d '\n' < "${cfg.replication.passwordFile}")"
|
||||||
|
umask 077
|
||||||
|
printf '%s:%d:replication:replication:%s\n' \
|
||||||
|
'${cfg.replication.peerHost}' \
|
||||||
|
${toString cfg.replication.peerPort} \
|
||||||
|
"$PW" > "$PASSFILE"
|
||||||
|
chown postgres:postgres "$PASSFILE"
|
||||||
|
chmod 0600 "$PASSFILE"
|
||||||
|
|
||||||
|
touch "${pgDataDir}/standby.signal"
|
||||||
|
chown postgres:postgres "${pgDataDir}/standby.signal"
|
||||||
|
|
||||||
|
CONF="${pgDataDir}/postgresql.auto.conf"
|
||||||
|
touch "$CONF"
|
||||||
|
chown postgres:postgres "$CONF"
|
||||||
|
# Strip any prior primary_conninfo line, then append fresh one.
|
||||||
|
${pkgs.gnused}/bin/sed -i '/^primary_conninfo/d' "$CONF"
|
||||||
|
printf "primary_conninfo = 'host=%s port=%d user=replication passfile=%s sslmode=%s'\n" \
|
||||||
|
'${cfg.replication.peerHost}' \
|
||||||
|
${toString cfg.replication.peerPort} \
|
||||||
|
"$PASSFILE" \
|
||||||
|
'${cfg.replication.sslMode}' >> "$CONF"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.acme.enable {
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = lib.mkDefault cfg.acme.email;
|
||||||
|
certs.${cfg.matrixDomain} = {
|
||||||
|
dnsProvider = "porkbun";
|
||||||
|
webroot = lib.mkForce null;
|
||||||
|
environmentFile = "/run/matrix-cluster/porkbun.env";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.matrix-cluster-acme-env = {
|
||||||
|
description = "Assemble Porkbun ACME environment file";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
before = [ "acme-${cfg.matrixDomain}.service" ];
|
||||||
|
requiredBy = [ "acme-${cfg.matrixDomain}.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
set -eu
|
||||||
|
install -d -m 0755 /run/matrix-cluster
|
||||||
|
API="$(tr -d '\n' < "${cfg.acme.porkbunApiKeyFile}")"
|
||||||
|
SEC="$(tr -d '\n' < "${cfg.acme.porkbunSecretApiKeyFile}")"
|
||||||
|
OUT=/run/matrix-cluster/porkbun.env
|
||||||
|
umask 077
|
||||||
|
{
|
||||||
|
printf 'PORKBUN_API_KEY=%s\n' "$API"
|
||||||
|
printf 'PORKBUN_SECRET_API_KEY=%s\n' "$SEC"
|
||||||
|
} > "$OUT"
|
||||||
|
chmod 0400 "$OUT"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
self,
|
||||||
|
flake
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
cfg = config.services.postgresql;
|
||||||
|
extensionFlags = {
|
||||||
|
pg_cron = false;
|
||||||
|
pgjwt = false;
|
||||||
|
pg_net = false;
|
||||||
|
pg_smtp_client = false;
|
||||||
|
http = false;
|
||||||
|
plsh = false;
|
||||||
|
hemar = false;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
services.postgresql = {
|
||||||
|
lazzyExtensions = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.bool;
|
||||||
|
default = extensionFlags;
|
||||||
|
};
|
||||||
|
environment = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
script = lib.mkOption {
|
||||||
|
type = with lib; types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
pkgs.writeText "init-sql-script" '''
|
||||||
|
alter user postgres with password 'myPassword';
|
||||||
|
''';'';
|
||||||
|
|
||||||
|
description = ''
|
||||||
|
A file containing SQL statements to execute on stratup or any time you change it.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
systemd.services.postgresql-script= lib.mkIf (cfg.script != null) {
|
||||||
|
description = "Some postgresql settings";
|
||||||
|
after = [ "postgresql.service" ];
|
||||||
|
wants = [ "postgresql.service" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.dash}/bin/dash ${pkgs.writeText "sql-script" ''
|
||||||
|
#!/${pkgs.dash}/bin/dash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
alias psql='${cfg.package}/bin/psql -v ON_ERROR_STOP=1 -p "${builtins.toString cfg.port}" -U postgres -d postgres'
|
||||||
|
|
||||||
|
${builtins.readFile cfg.script}
|
||||||
|
''}";
|
||||||
|
};
|
||||||
|
path = [ cfg.package ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
systemd.services.postgresql.environment = cfg.environment;
|
||||||
|
#services.postgresql = {
|
||||||
|
# settings.shared_preload_libraries =
|
||||||
|
# lib.concatStringsSep ", "
|
||||||
|
# (lib.attrNames (
|
||||||
|
# lib.filterAttrs (n: v: v &&
|
||||||
|
# n != "http"
|
||||||
|
# && n != "plsh"
|
||||||
|
# && n != "pgjwt"
|
||||||
|
# && n != "pg_smtp_client"
|
||||||
|
# ) cfg.lazzyExtensions));
|
||||||
|
|
||||||
|
# extensions = let
|
||||||
|
# packages = {
|
||||||
|
# inherit (cfg.package.pkgs) pg_net pgjwt pg_cron http pg_smtp_client plsh;
|
||||||
|
# };
|
||||||
|
# in
|
||||||
|
# lib.attrValues (
|
||||||
|
# lib.filterAttrs (n: v: v != null)
|
||||||
|
# (lib.mapAttrs' (
|
||||||
|
# name: enabled:
|
||||||
|
# if enabled
|
||||||
|
# then lib.nameValuePair name (packages.${name} or (throw "Package ${name} not found in pkgs"))
|
||||||
|
# else null
|
||||||
|
# )
|
||||||
|
# cfg.lazzyExtensions)
|
||||||
|
# );
|
||||||
|
#};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
# INFO(nrv): This is standalone shadowsocks module. Instance-specific is at ./shadowsocks.nix
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.services.shadowsocks-rust;
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
server = cfg.localAddress;
|
||||||
|
server_port = cfg.port;
|
||||||
|
method = cfg.encryptionMethod;
|
||||||
|
mode = cfg.mode;
|
||||||
|
user = "nobody";
|
||||||
|
fast_open = cfg.fastOpen;
|
||||||
|
} // optionalAttrs (cfg.plugin != null) {
|
||||||
|
plugin = cfg.plugin;
|
||||||
|
plugin_opts = cfg.pluginOpts;
|
||||||
|
} // optionalAttrs (cfg.password != null) {
|
||||||
|
password = cfg.password;
|
||||||
|
} // cfg.extraConfig;
|
||||||
|
|
||||||
|
configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
###### interface
|
||||||
|
|
||||||
|
options = {
|
||||||
|
|
||||||
|
services.shadowsocks-rust = {
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Whether to run shadowsocks-rust shadowsocks server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
localAddress = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0.0.0.0";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Local addresses to which the server binds.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 8388;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Port which the server uses.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
password = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Password for connecting clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passwordFile = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Password file with a password for connecting clients.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
|
||||||
|
default = "tcp_and_udp";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Relay protocols.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
fastOpen = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
use TCP fast-open
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
encryptionMethod = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "chacha20-ietf-poly1305";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Encryption method. See <https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
plugin = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = literalExpression ''"''${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin"'';
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
SIP003 plugin for shadowsocks
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
pluginOpts = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
example = "server;host=example.com";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Options to pass to the plugin if one was specified
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfig = mkOption {
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
example = {
|
||||||
|
nameserver = "8.8.8.8";
|
||||||
|
};
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Additional configuration for shadowsocks that is not covered by the
|
||||||
|
provided options. The provided attrset will be serialized to JSON and
|
||||||
|
has to contain valid shadowsocks options. Unfortunately most
|
||||||
|
additional options are undocumented but it's easy to find out what is
|
||||||
|
available by looking into the source code of
|
||||||
|
<https://github.com/shadowsocks/shadowsocks-rust/blob/master/src/jconf.c>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
###### implementation
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
assertions = singleton
|
||||||
|
{ assertion = cfg.password == null || cfg.passwordFile == null;
|
||||||
|
message = "Cannot use both password and passwordFile for shadowsocks-rust";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.shadowsocks-rust = {
|
||||||
|
description = "shadowsocks-rust Daemon";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = [ pkgs.shadowsocks-rust ]
|
||||||
|
++ optional (cfg.plugin != null) cfg.plugin
|
||||||
|
++ optional (cfg.passwordFile != null) pkgs.jq;
|
||||||
|
serviceConfig.PrivateTmp = true;
|
||||||
|
script = ''
|
||||||
|
${optionalString (cfg.passwordFile != null) ''
|
||||||
|
cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /run/shadowsocks.json
|
||||||
|
''}
|
||||||
|
exec ssserver --config ${if cfg.passwordFile != null then "/run/shadowsocks.json" else configFile}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
sops.secrets."ss-bfs/password" = {};
|
||||||
|
services.shadowsocks-rust = {
|
||||||
|
enable = true;
|
||||||
|
plugin = "${pkgs.shadowsocks-v2ray-plugin}/bin/v2ray-plugin";
|
||||||
|
# TODO: setup dnscrypt or a private DNS server for this
|
||||||
|
# extraConfig = {
|
||||||
|
# nameserver = "185.12.64.1"; # FIXME: this can vary across instances.
|
||||||
|
# };
|
||||||
|
port = 55228;
|
||||||
|
pluginOpts = "server";
|
||||||
|
# TODO: setup a TLS certs for this (look: (README.md) https://github.com/shadowsocks/v2ray-plugin/)
|
||||||
|
#pluginOpts = "server;tls;host=ss.bfs.band";
|
||||||
|
passwordFile = config.sops.secrets."ss-bfs/password".path;
|
||||||
|
mode = "tcp_and_udp"; # default
|
||||||
|
localAddress = "0.0.0.0";
|
||||||
|
fastOpen = true; # default
|
||||||
|
encryptionMethod = "chacha20-ietf-poly1305"; # default
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.generic.xray-system;
|
||||||
|
xrayPort = 10086;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.hectic
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.generic.xray-system = {
|
||||||
|
enable = lib.mkEnableOption "generic xray VPN server system configuration";
|
||||||
|
|
||||||
|
defaultSopsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
SOPS-encrypted secrets file used as `sops.defaultSopsFile`.
|
||||||
|
Must define the `config` and `init-postgresql` secrets.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression "../../../sus/bfs.xray.yaml";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.xray = {
|
||||||
|
enable = true;
|
||||||
|
settingsFile = config.sops.secrets."config".path;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOn1KflaIX1RU9YS/qLb0GInmndYxx2vTLZC9OA+eXZl''
|
||||||
|
''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBKPbIJATVyAw7F7vBZbHkCODXFo5gvDyqhuU0gnNUNH''
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"ata_piix"
|
||||||
|
"uhci_hcd"
|
||||||
|
"xen_blkfront"
|
||||||
|
] ++ (if pkgs.stdenv.hostPlatform.system != "aarch64-linux" then [ "vmw_pvscsi" ] else []);
|
||||||
|
boot.initrd.kernelModules = ["nvme"];
|
||||||
|
|
||||||
|
disko.devices = {
|
||||||
|
disk.vda = {
|
||||||
|
device = lib.mkDefault "/dev/vda";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
priority = 1;
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.base.enable = true;
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
gnupg.sshKeyPaths = [ ];
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
defaultSopsFile = cfg.defaultSopsFile;
|
||||||
|
|
||||||
|
secrets."config" = {};
|
||||||
|
secrets."init-postgresql" = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
xrayPort 8443
|
||||||
|
80 443 # for acme
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
xray
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.archetype.base;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.disko.nixosModules.default
|
||||||
|
inputs.nixos-mailserver.nixosModules.mailserver
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.archetype.base.enable = lib.mkEnableOption "Enable archetupe.dev";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
hectic = {
|
||||||
|
program.zsh.enable = lib.mkDefault true;
|
||||||
|
program.tmux.enable = lib.mkDefault true;
|
||||||
|
program.nixvim.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.defaultUserShell = pkgs.zsh;
|
||||||
|
|
||||||
|
# Enable flakes and new 'nix' command
|
||||||
|
nix.settings.experimental-features = "nix-command flakes";
|
||||||
|
|
||||||
|
networking.firewall.enable = true;
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
defaultPackages = [];
|
||||||
|
systemPackages = (with self.packages.${pkgs.stdenv.hostPlatform.system}; [
|
||||||
|
nvim-pager
|
||||||
|
]);
|
||||||
|
variables = {
|
||||||
|
PAGER = with self.packages.${pkgs.stdenv.hostPlatform.system}; "${nvim-pager}/bin/pager";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
system.stateVersion = "25.05";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{ ... }: {}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.archetype.dev;
|
||||||
|
in {
|
||||||
|
# necessary imports:
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.archetype.dev.enable = lib.mkEnableOption "Enable archetupe.dev";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
hectic.archetype.base.enable = true;
|
||||||
|
|
||||||
|
services.getty.autologinUser = "root";
|
||||||
|
|
||||||
|
virtualisation.vmVariant.virtualisation = {
|
||||||
|
qemu.options = [
|
||||||
|
"-nographic"
|
||||||
|
"-display curses"
|
||||||
|
"-append console=ttyS0"
|
||||||
|
"-serial mon:stdio"
|
||||||
|
"-vga qxl"
|
||||||
|
];
|
||||||
|
forwardPorts = [
|
||||||
|
{
|
||||||
|
from = "host";
|
||||||
|
host.port = 40500;
|
||||||
|
guest.port = 22;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
PasswordAuthentication = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment = {
|
||||||
|
systemPackages =
|
||||||
|
(with pkgs; [
|
||||||
|
curl
|
||||||
|
neovim
|
||||||
|
yq-go
|
||||||
|
jq
|
||||||
|
htop-vim
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Adjust zsh prompt for dev archetype: show '#' instead of '%'
|
||||||
|
home-manager.sharedModules = lib.mkAfter [
|
||||||
|
{
|
||||||
|
programs.zsh.initContent = lib.mkAfter ''
|
||||||
|
PROMPT="# %~ "
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{ ... }: { lib, ... }: {
|
||||||
|
options.hectic.archetype.explosive.enable = lib.mkEnableOption "Enable impermanence usage";
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.hardware.cloudzy;
|
||||||
|
in {
|
||||||
|
options.hectic.hardware.cloudzy = {
|
||||||
|
enable = lib.mkEnableOption "Enable hetzner-cloud hardware configurations";
|
||||||
|
ipGateway = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||||
|
example = "188.243.124.1";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
ipv4 = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||||
|
example = "188.243.124.246";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
prefixLength = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
example = 24;
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/dev/sda";
|
||||||
|
example = "/dev/disk/by-uuid/f184a16b-6eca-41cb-b48a-ff37cdce1d79";
|
||||||
|
description = ''
|
||||||
|
boot device uuid
|
||||||
|
if it is null then will use "/dev/sda"
|
||||||
|
/dev/sda - default hetzner cloud device
|
||||||
|
!! But can changes on reboot if server have volumes
|
||||||
|
!! So use IDs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
networkMatchConfigName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
example = "enp1s0";
|
||||||
|
description = ''
|
||||||
|
type of network conection,
|
||||||
|
on older hetzner servers may be `ens3`
|
||||||
|
on newer probably `enp1s0`
|
||||||
|
|
||||||
|
you can use `networkctl list` on server to know it
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
boot.loader.systemd-boot.enable = false;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
|
||||||
|
boot.loader.grub = {
|
||||||
|
enable = true;
|
||||||
|
device = cfg.device;
|
||||||
|
efiSupport = false;
|
||||||
|
forceInstall = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
disko.devices.disk.main = {
|
||||||
|
device = cfg.device;
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [ "umask=0077" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.interfaces."30-wan" = {
|
||||||
|
matchConfig.Name = cfg.networkMatchConfigName;
|
||||||
|
ipv4.addresses = [
|
||||||
|
{ address = cfg.ipv4; prefixLength = cfg.prefixLength; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
networking.defaultGateway = cfg.ipGateway;
|
||||||
|
networking.nameservers = [ "1.1.1.1" "8.8.8.8" ];
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"ata_piix"
|
||||||
|
"uhci_hcd"
|
||||||
|
"xen_blkfront"
|
||||||
|
] ++ (if pkgs.stdenv.hostPlatform.system != "aarch64-linux" then [ "vmw_pvscsi" ] else []);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.hardware.geo-hosting;
|
||||||
|
in {
|
||||||
|
options.hectic.hardware.geo-hosting = {
|
||||||
|
enable = lib.mkEnableOption "Enable geo-hosting hardware configurations";
|
||||||
|
ipv4Gateway = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||||
|
example = "188.243.124.1";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
ipv4 = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||||
|
example = "188.243.124.246";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/dev/vda";
|
||||||
|
example = "/dev/disk/by-uuid/f184a16b-6eca-41cb-b48a-ff37cdce1d79";
|
||||||
|
description = ''
|
||||||
|
boot device uuid
|
||||||
|
if it is null then will use "/dev/vda"
|
||||||
|
/dev/sva - default geo hosting device
|
||||||
|
!! But can changes on reboot if server have volumes
|
||||||
|
!! So use IDs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
networkMatchConfigName = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^(enp1s0|ens3)$";
|
||||||
|
example = "ens3";
|
||||||
|
description = ''
|
||||||
|
type of network conection
|
||||||
|
|
||||||
|
you can use `networkctl list` on server to know it
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
boot.loader.systemd-boot.enable = false;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = false;
|
||||||
|
|
||||||
|
boot.loader.grub = {
|
||||||
|
enable = true;
|
||||||
|
device = cfg.device;
|
||||||
|
efiSupport = false;
|
||||||
|
forceInstall = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
disko.devices.disk.vda = {
|
||||||
|
device = cfg.device;
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [ "umask=0077" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.interfaces.${cfg.networkMatchConfigName} = {
|
||||||
|
ipv4.addresses = [
|
||||||
|
{ address = cfg.ipv4; prefixLength = 24; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
networking.defaultGateway = cfg.ipv4Gateway;
|
||||||
|
networking.nameservers = [ "1.1.1.1" "8.8.8.8" ];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.hardware.hetzner-cloud;
|
||||||
|
in {
|
||||||
|
options.hectic.hardware.hetzner-cloud = {
|
||||||
|
enable = lib.mkEnableOption "Enable hetzner-cloud hardware configurations";
|
||||||
|
#bootParUuid = lib.mkOption {
|
||||||
|
# type = with lib.types; nullOr oneOf [
|
||||||
|
# (lib.types.strMatching "^[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}$")
|
||||||
|
# (lib.types.strMatching "^[0-9a-fA-F-]{36}$")
|
||||||
|
# ];
|
||||||
|
# default = null;
|
||||||
|
# example = "5628-19B6";
|
||||||
|
# description = ''
|
||||||
|
# boot partition uuid if it is null
|
||||||
|
# then will use "/dev/sda15" (default hetzner cloud boot device)
|
||||||
|
# '';
|
||||||
|
#};
|
||||||
|
ipv4 = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9]{1,3}\\.){3}[0-9]{1,3}$";
|
||||||
|
example = "188.243.124.246";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
ipv6 = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^([0-9a-fA-F]{1,4}:){3}[0-9a-fA-F]{1,4}$";
|
||||||
|
example = "2a01:4f8:1c1a:d883";
|
||||||
|
description = ''
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/dev/sda";
|
||||||
|
example = "/dev/disk/by-uuid/f184a16b-6eca-41cb-b48a-ff37cdce1d79";
|
||||||
|
description = ''
|
||||||
|
boot device uuid
|
||||||
|
if it is null then will use "/dev/sda"
|
||||||
|
/dev/sda - default hetzner cloud device
|
||||||
|
!! But can changes on reboot if server have volumes
|
||||||
|
!! So use IDs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
networkMatchConfigName = lib.mkOption {
|
||||||
|
type = lib.types.strMatching "^(enp1s0|ens3)$";
|
||||||
|
example = "enp1s0";
|
||||||
|
description = ''
|
||||||
|
type of network conection,
|
||||||
|
on older hetzner servers may be `ens3`
|
||||||
|
on newer probably `enp1s0`
|
||||||
|
|
||||||
|
you can use `networkctl list` on server to know it
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge
|
||||||
|
[
|
||||||
|
{
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"ata_piix"
|
||||||
|
"uhci_hcd"
|
||||||
|
"xen_blkfront"
|
||||||
|
] ++ (if pkgs.stdenv.hostPlatform.system != "aarch64-linux" then [ "vmw_pvscsi" ] else []);
|
||||||
|
|
||||||
|
networking.useDHCP = false;
|
||||||
|
networking.useNetworkd = true;
|
||||||
|
systemd.network.enable = true;
|
||||||
|
systemd.network.networks."30-wan" = {
|
||||||
|
matchConfig.Name = cfg.networkMatchConfigName;
|
||||||
|
networkConfig.DHCP = "no";
|
||||||
|
address = [
|
||||||
|
"${cfg.ipv4}/32"
|
||||||
|
"${cfg.ipv6}::/64"
|
||||||
|
];
|
||||||
|
routes = [
|
||||||
|
{ Gateway = "172.31.1.1"; GatewayOnLink = true; }
|
||||||
|
{ Gateway = "fe80::1"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
disko.devices = {
|
||||||
|
disk = {
|
||||||
|
main = {
|
||||||
|
type = "disk";
|
||||||
|
device = cfg.device;
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
priority = 1;
|
||||||
|
};
|
||||||
|
ESP = {
|
||||||
|
size = "512M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(lib.mkIf (pkgs.stdenv.hostPlatform.system == "aarch64-linux") {
|
||||||
|
boot.initrd.kernelModules = [ "virtio_gpu" ];
|
||||||
|
boot.kernelParams = [ "console=tty" ];
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
modulesPath,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.hardware.lenovo-ideapad-15arh7;
|
||||||
|
hasDisko = false;
|
||||||
|
in {
|
||||||
|
# FIXME: FUCK
|
||||||
|
#imports = [
|
||||||
|
# "${inputs.nixos-hardware}/common/cpu/amd"
|
||||||
|
# "${inputs.nixos-hardware}/common/cpu/amd/pstate.nix"
|
||||||
|
# "${inputs.nixos-hardware}/common/gpu/amd"
|
||||||
|
# "${inputs.nixos-hardware}/common/gpu/nvidia/prime-sync.nix"
|
||||||
|
# "${inputs.nixos-hardware}/common/pc/laptop"
|
||||||
|
# "${inputs.nixos-hardware}/common/pc/laptop/ssd"
|
||||||
|
#];
|
||||||
|
|
||||||
|
options.hectic.hardware.lenovo-ideapad-15arh7 = {
|
||||||
|
enable = lib.mkEnableOption "Enable lenovo-legion hardware configurations";
|
||||||
|
swapSize = lib.mkOption {
|
||||||
|
type = lib.types.either (lib.types.enum [ "100%" ]) (lib.types.strMatching "[0-9]+[KMGTP]?");
|
||||||
|
default = "0";
|
||||||
|
description = ''
|
||||||
|
Size of the partition, in sgdisk format.
|
||||||
|
sets end automatically with the + prefix
|
||||||
|
can be 100% for the whole remaining disk, will be done last in that case.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "0";
|
||||||
|
description = ''
|
||||||
|
Size of the partition, in sgdisk format.
|
||||||
|
sets end automatically with the + prefix
|
||||||
|
can be 100% for the whole remaining disk, will be done last in that case.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
/* common */
|
||||||
|
hardware.nvidia = {
|
||||||
|
modesetting.enable = true;
|
||||||
|
prime = {
|
||||||
|
amdgpuBusId = "PCI:5:0:0";
|
||||||
|
nvidiaBusId = "PCI:1:0:0";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vulkan-tools
|
||||||
|
];
|
||||||
|
/* */
|
||||||
|
|
||||||
|
/* boot */
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"nvme"
|
||||||
|
"xhci_pci"
|
||||||
|
"usb_storage"
|
||||||
|
"usbhid"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ "dm-snapshot" "amdgpu" ];
|
||||||
|
boot.kernelModules = [ "kvm-amd" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
/* */
|
||||||
|
|
||||||
|
networking.useDHCP = lib.mkDefault true;
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
|
||||||
|
/* cpu */
|
||||||
|
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
/* gpu */
|
||||||
|
services.xserver.videoDrivers = [
|
||||||
|
"nvidia"
|
||||||
|
#"amdgpu" # NOTE: probably useles with nvidia optimus prime
|
||||||
|
#"nouveau" # NOTE: open source nvidia
|
||||||
|
];
|
||||||
|
|
||||||
|
hardware.opengl = {
|
||||||
|
enable = true;
|
||||||
|
driSupport32Bit = true;
|
||||||
|
extraPackages = with pkgs; [
|
||||||
|
vulkan-loader
|
||||||
|
vulkan-validation-layers
|
||||||
|
vulkan-extension-layer
|
||||||
|
amdvlk
|
||||||
|
|
||||||
|
];
|
||||||
|
extraPackages32 = with pkgs; [
|
||||||
|
pkgsi686Linux.vulkan-loader
|
||||||
|
pkgsi686Linux.vulkan-validation-layers
|
||||||
|
pkgsi686Linux.vulkan-extension-layer
|
||||||
|
driversi686Linux.amdvlk
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
#environment.variables.VK_DRIVER_FILES=/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json;
|
||||||
|
#environment.sessionVariables.VK_DRIVER_FILES = "/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json";
|
||||||
|
|
||||||
|
#environment.sessionVariables = rec {
|
||||||
|
# VK_ICD_FILENAMES =
|
||||||
|
# "${config.hardware.nvidia.package}/share/vulkan/icd.d/nvidia_icd.x86_64.json";
|
||||||
|
|
||||||
|
# #:${config.environment.variables.VK_ICD_FILENAMES or ""}";
|
||||||
|
#};
|
||||||
|
|
||||||
|
|
||||||
|
hardware.nvidia = {
|
||||||
|
# Nvidia power management. Experimental, and can cause sleep/suspend to fail.
|
||||||
|
# Enable this if you have graphical corruption issues or application crashes after waking
|
||||||
|
# up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead
|
||||||
|
# of just the bare essentials.
|
||||||
|
powerManagement.enable = false;
|
||||||
|
|
||||||
|
# Fine-grained power management. Turns off GPU when not in use.
|
||||||
|
# Experimental and only works on modern Nvidia GPUs (Turing or newer).
|
||||||
|
powerManagement.finegrained = false;
|
||||||
|
|
||||||
|
# Use the NVidia open source kernel module (not to be confused with the
|
||||||
|
# independent third-party "nouveau" open source driver).
|
||||||
|
# Support is limited to the Turing and later architectures. Full list of
|
||||||
|
# supported GPUs is at:
|
||||||
|
# https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus
|
||||||
|
# Only available from driver 515.43.04+
|
||||||
|
# Currently alpha-quality/buggy, so false is currently the recommended setting.
|
||||||
|
open = false;
|
||||||
|
|
||||||
|
# Enable the Nvidia settings menu,
|
||||||
|
# accessible via `nvidia-settings`.
|
||||||
|
nvidiaSettings = true;
|
||||||
|
|
||||||
|
# nvidia package overwrive
|
||||||
|
package = config.boot.kernelPackages.nvidiaPackages.stable;
|
||||||
|
|
||||||
|
};
|
||||||
|
/* */
|
||||||
|
|
||||||
|
/* sound */
|
||||||
|
hardware.pulseaudio.enable = true;
|
||||||
|
hardware.pulseaudio.support32Bit = true;
|
||||||
|
/* */
|
||||||
|
|
||||||
|
/* disk */
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
inherit (cfg) device;
|
||||||
|
type = "disk";
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
name = "boot";
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
};
|
||||||
|
esp = {
|
||||||
|
name = "ESP";
|
||||||
|
size = "500M";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
swap = {
|
||||||
|
size = cfg.swapSize;
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
resumeDevice = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
name = "root";
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "lvm_pv";
|
||||||
|
vg = "root_vg";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
lvm_vg = {
|
||||||
|
root_vg = {
|
||||||
|
type = "lvm_vg";
|
||||||
|
lvs = {
|
||||||
|
root = {
|
||||||
|
size = "100%FREE";
|
||||||
|
content = {
|
||||||
|
type = "btrfs";
|
||||||
|
extraArgs = ["-f"];
|
||||||
|
|
||||||
|
subvolumes = lib.mkMerge [
|
||||||
|
{
|
||||||
|
"/root" = {
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
"/nix" = {
|
||||||
|
mountOptions = ["subvol=nix" "noatime"];
|
||||||
|
mountpoint = "/nix";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(if config.hectic.archetype.explosive.enable then {
|
||||||
|
"/persist" = {
|
||||||
|
mountOptions = ["subvol=persist" "noatime"];
|
||||||
|
mountpoint = "/persist";
|
||||||
|
};
|
||||||
|
} else {})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.hardware.zombro;
|
||||||
|
in {
|
||||||
|
options.hectic.hardware.zombro = {
|
||||||
|
enable = lib.mkEnableOption "Enable zombro hardware configurations";
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/dev/vda";
|
||||||
|
example = "/dev/disk/by-uuid/f184a16b-6eca-41cb-b48a-ff37cdce1d79";
|
||||||
|
description = ''
|
||||||
|
boot device uuid
|
||||||
|
if it is null then will use "/dev/vda"
|
||||||
|
/dev/vda - default zombro device
|
||||||
|
!! But can changes on reboot if server have volumes
|
||||||
|
!! So use IDs
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable (lib.mkMerge
|
||||||
|
[
|
||||||
|
{
|
||||||
|
boot.loader.grub.device = cfg.device;
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"ata_piix"
|
||||||
|
"uhci_hcd"
|
||||||
|
"xen_blkfront"
|
||||||
|
] ++ (if pkgs.stdenv.hostPlatform.system != "aarch64-linux" then [ "vmw_pvscsi" ] else []);
|
||||||
|
boot.initrd.kernelModules = ["nvme"];
|
||||||
|
|
||||||
|
disko.devices = {
|
||||||
|
disk.master = {
|
||||||
|
device = cfg.device;
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
boot = {
|
||||||
|
size = "1M";
|
||||||
|
type = "EF02";
|
||||||
|
priority = 1;
|
||||||
|
};
|
||||||
|
root = {
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(lib.mkIf (pkgs.stdenv.hostPlatform.system == "aarch64-linux") {
|
||||||
|
boot.initrd.kernelModules = [ "virtio_gpu" ];
|
||||||
|
boot.kernelParams = [ "console=tty" ];
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.program.nixvim;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.nixvim.nixosModules.nixvim
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.program.nixvim.enable = lib.mkEnableOption "Enable hectic nixvim config";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
programs.nixvim = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
extraPackages = with pkgs; [ gcc ];
|
||||||
|
|
||||||
|
colorschemes.kanagawa = {
|
||||||
|
enable = true;
|
||||||
|
settings.colors.theme.all = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
opts = {
|
||||||
|
spell = true;
|
||||||
|
spelllang = [ "en" "ru" "it" ];
|
||||||
|
tabstop = 2;
|
||||||
|
shiftwidth = 2;
|
||||||
|
softtabstop = 2;
|
||||||
|
expandtab = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
extraFiles = {
|
||||||
|
"spell/ru.utf-8.spl".source = pkgs.fetchurl {
|
||||||
|
url = "https://ftp.nluug.nl/vim/runtime/spell/ru.utf-8.spl";
|
||||||
|
sha256 = "sha256-6y0714ogILMLzAp8/r2s6/t6QnWBEU9muIpXeubaxU0=";
|
||||||
|
};
|
||||||
|
"spell/ru.utf-8.sug".source = pkgs.fetchurl {
|
||||||
|
url = "https://ftp.nluug.nl/vim/runtime/spell/ru.utf-8.sug";
|
||||||
|
sha256 = "sha256-6r2GForYXVv7gGiAjPeYK6sDdK/CmctJ7MidcWFvOTs=";
|
||||||
|
};
|
||||||
|
"spell/it.utf-8.spl".source = pkgs.fetchurl {
|
||||||
|
url = "https://ftp.nluug.nl/vim/runtime/spell/it.utf-8.spl";
|
||||||
|
hash = "sha256-2AczkD6DbVN5DAq4wcLyn2Y8oqd67ns4Guprh2KudBM=";
|
||||||
|
};
|
||||||
|
"spell/it.utf-8.sug".source = pkgs.fetchurl {
|
||||||
|
url = "https://ftp.nluug.nl/vim/runtime/spell/it.utf-8.sug";
|
||||||
|
hash = "sha256-4LsXYaeScJJrdaj69PTQ2EDVWis0UY/Y5RKSfCckzko=";
|
||||||
|
};
|
||||||
|
"ftdetect/hemar.vim".text = ''
|
||||||
|
au BufRead,BufNewFile *.hemar setfiletype hemar
|
||||||
|
'';
|
||||||
|
"queries/hemar/highlights.scm".text = ''
|
||||||
|
(interpolation) @keyword
|
||||||
|
|
||||||
|
(for "for" @keyword)
|
||||||
|
(for "in" @keyword)
|
||||||
|
(done "done" @keyword)
|
||||||
|
|
||||||
|
(path) @field
|
||||||
|
(string) @string
|
||||||
|
(text) @text
|
||||||
|
|
||||||
|
(for
|
||||||
|
"{[" @punctuation.bracket
|
||||||
|
"]}" @punctuation.bracket)
|
||||||
|
|
||||||
|
(done
|
||||||
|
"{[" @punctuation.bracket
|
||||||
|
"]}" @punctuation.bracket)
|
||||||
|
|
||||||
|
(interpolation
|
||||||
|
"{[" @punctuation.bracket
|
||||||
|
"]}" @punctuation.bracket)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfigLuaPre = /* lua */ ''
|
||||||
|
-- map leader
|
||||||
|
vim.api.nvim_set_keymap("", "<Space>", "<Nop>", { noremap = true, silent = true })
|
||||||
|
vim.g.mapleader = ' '
|
||||||
|
|
||||||
|
-- render markdown
|
||||||
|
require('render-markdown').setup({
|
||||||
|
link = {
|
||||||
|
enabled = true,
|
||||||
|
render_modes = false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
-- nowrap for *.nowrap.* markdown files
|
||||||
|
vim.api.nvim_create_autocmd("FileType", {
|
||||||
|
pattern = "markdown",
|
||||||
|
callback = function()
|
||||||
|
if vim.fn.expand("%:t"):find("%.nowrap%.") then vim.opt_local.wrap = false end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- toggle conceallevel
|
||||||
|
vim.keymap.set("n", "<leader>tc", ":setlocal <C-R>=&conceallevel ? 'conceallevel=0' : 'conceallevel=2'<CR><CR>", { desc = "[T]oggle [C]onceallevel" })
|
||||||
|
|
||||||
|
-- tree-sitter: register hemar parser
|
||||||
|
local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
|
||||||
|
parser_config.hemar = {
|
||||||
|
install_info = {
|
||||||
|
url = "https://github.com/hectic-lab/util.nix",
|
||||||
|
files = { "package/hemar/grammar/tree-sitter/src/parser.c" },
|
||||||
|
generate_requires_npm = false,
|
||||||
|
requires_generate_from_grammar = false,
|
||||||
|
},
|
||||||
|
filetype = "hemar",
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
extraConfigLuaPost = /* lua */ ''
|
||||||
|
vim.cmd [[
|
||||||
|
hi Normal guibg=none ctermbg=none
|
||||||
|
hi NonText guibg=none ctermbg=none
|
||||||
|
]]
|
||||||
|
'';
|
||||||
|
|
||||||
|
keymaps = [
|
||||||
|
{ mode = "n"; key = "<leader>o"; options.silent = true; action = "<cmd>Oil<CR>"; }
|
||||||
|
{ mode = "n"; key = "<leader>dd"; action = "<cmd>lua vim.diagnostic.open_float()<CR>"; }
|
||||||
|
{ mode = "n"; key = "<leader>dn"; action = "<cmd>lua vim.diagnostic.goto_next()<CR>"; }
|
||||||
|
{ mode = "n"; key = "<leader>dp"; action = "<cmd>lua vim.diagnostic.goto_prev()<CR>"; }
|
||||||
|
];
|
||||||
|
|
||||||
|
extraPlugins = with pkgs.vimPlugins; [
|
||||||
|
nvim-treesitter-parsers.templ
|
||||||
|
vim-shellcheck
|
||||||
|
vim-grammarous
|
||||||
|
];
|
||||||
|
|
||||||
|
plugins = {
|
||||||
|
render-markdown.enable = true;
|
||||||
|
fidget.enable = true;
|
||||||
|
oil.enable = true;
|
||||||
|
|
||||||
|
treesitter = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
ensure_installed = [ "hemar" ];
|
||||||
|
highlight.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
lsp = {
|
||||||
|
enable = true;
|
||||||
|
keymaps.lspBuf = {
|
||||||
|
"<leader>lh" = "hover";
|
||||||
|
"<leader>ld" = "definition";
|
||||||
|
"<leader>lD" = "references";
|
||||||
|
"<leader>lr" = "rename";
|
||||||
|
"<leader>li" = "implementation";
|
||||||
|
"<leader>lt" = "type_definition";
|
||||||
|
"<leader>lf" = "format";
|
||||||
|
"<leader>la" = "code_action";
|
||||||
|
};
|
||||||
|
servers = {
|
||||||
|
rust_analyzer = {
|
||||||
|
enable = true;
|
||||||
|
installRustc = false;
|
||||||
|
installCargo = false;
|
||||||
|
};
|
||||||
|
nixd = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
nil_ls = {
|
||||||
|
enable = true;
|
||||||
|
extraOptions.formatting.command = [ "nixpkgs-fmt" ];
|
||||||
|
};
|
||||||
|
clangd.enable = true;
|
||||||
|
ts_ls.enable = true;
|
||||||
|
gopls.enable = true;
|
||||||
|
templ.enable = true;
|
||||||
|
bashls.enable = true;
|
||||||
|
kotlin_language_server.enable = true;
|
||||||
|
metals = {
|
||||||
|
enable = true;
|
||||||
|
cmd = [ "metals" ];
|
||||||
|
};
|
||||||
|
sqls.enable = true;
|
||||||
|
java_language_server.enable = true;
|
||||||
|
pyright.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.program.tmux;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.program.tmux.enable = lib.mkEnableOption "Enable hectic tmux config";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
programs.tmux.enable = true;
|
||||||
|
programs.tmux.terminal = lib.mkOverride 50 "tmux-256color";
|
||||||
|
|
||||||
|
# alias depends on newSession = true (auto-creates session on attach)
|
||||||
|
programs.zsh.shellAliases.tmux = "tmux a";
|
||||||
|
programs.bash.shellAliases.tmux = "tmux a";
|
||||||
|
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
{
|
||||||
|
programs.tmux = {
|
||||||
|
enable = true;
|
||||||
|
plugins = with pkgs.tmuxPlugins; [ resurrect continuum ];
|
||||||
|
keyMode = "vi";
|
||||||
|
escapeTime = 500;
|
||||||
|
historyLimit = 50000;
|
||||||
|
newSession = true;
|
||||||
|
extraConfig = ''
|
||||||
|
# resurrect
|
||||||
|
set -g @resurrect-strategy-vim 'session'
|
||||||
|
set -g @resurrect-strategy-nvim 'session'
|
||||||
|
set -g @resurrect-capture-pane-contents 'on'
|
||||||
|
|
||||||
|
resurrect_dir="$HOME/.tmux/resurrect"
|
||||||
|
set -g @resurrect-dir $resurrect_dir
|
||||||
|
set -g @resurrect-hook-post-save-all 'target=$(readlink -f $resurrect_dir/last); sed "s| --cmd .*-vim-pack-dir||g; s|/etc/profiles/per-user/$USER/bin/||g; s|/home/$USER/.nix-profile/bin/||g" $target | sponge $target'
|
||||||
|
|
||||||
|
# continuum
|
||||||
|
set -g @continuum-restore 'on'
|
||||||
|
set -g @continuum-boot 'on'
|
||||||
|
set -g @continuum-save-interval '10'
|
||||||
|
|
||||||
|
bind-key -T copy-mode-vi v send-keys -X begin-selection
|
||||||
|
bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle
|
||||||
|
|
||||||
|
bind-key O select-pane -t :.-
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
home-manager.users.root.home.stateVersion = lib.mkDefault "25.05";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.program.zsh;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.program.zsh.enable = lib.mkEnableOption "Enable hectic zsh config";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
# system-level zsh must be on for home-manager zsh to work
|
||||||
|
programs.zsh.enable = true;
|
||||||
|
users.defaultUserShell = pkgs.zsh;
|
||||||
|
|
||||||
|
# Share the same zsh config with all home-manager users
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
{
|
||||||
|
programs.zsh = {
|
||||||
|
enable = true;
|
||||||
|
enableCompletion = true;
|
||||||
|
autosuggestion.enable = true;
|
||||||
|
syntaxHighlighting.enable = true;
|
||||||
|
|
||||||
|
history = {
|
||||||
|
size = 10000;
|
||||||
|
path = "$HOME/.zsh/.zsh_history";
|
||||||
|
};
|
||||||
|
|
||||||
|
oh-my-zsh = {
|
||||||
|
enable = true;
|
||||||
|
theme = "terminalparty";
|
||||||
|
};
|
||||||
|
|
||||||
|
shellAliases = self.lib.sharedShellAliases;
|
||||||
|
|
||||||
|
initContent = ''
|
||||||
|
set -ovi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Still define root for stateVersion; config comes from sharedModules
|
||||||
|
home-manager.users.root.home.stateVersion = lib.mkDefault "25.05";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let in { }
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
legacyCfg = config.hectic.services.matrix;
|
||||||
|
clusterCfg = config.hectic.generic.matrix-cluster;
|
||||||
|
clusterSynapseEnabled =
|
||||||
|
clusterCfg.enable
|
||||||
|
&& (if clusterCfg.overrideEnableSynapse != null then clusterCfg.overrideEnableSynapse else clusterCfg.role == "primary");
|
||||||
|
enabled = legacyCfg.enable || clusterSynapseEnabled;
|
||||||
|
matrixDomain = if legacyCfg.enable then legacyCfg.matrixDomain else clusterCfg.matrixDomain;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf enabled (let
|
||||||
|
keyFile = "/run/livekit.key";
|
||||||
|
in {
|
||||||
|
services.livekit = {
|
||||||
|
enable = true;
|
||||||
|
openFirewall = true;
|
||||||
|
settings.room.auto_create = false;
|
||||||
|
inherit keyFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.lk-jwt-service = {
|
||||||
|
enable = true;
|
||||||
|
livekitUrl = "wss://${matrixDomain}/livekit/sfu";
|
||||||
|
inherit keyFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.livekit-key = {
|
||||||
|
before = [ "lk-jwt-service.service" "livekit.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = with pkgs; [ livekit coreutils gawk ];
|
||||||
|
script = ''
|
||||||
|
echo "Key missing, generating key"
|
||||||
|
echo "lk-jwt-service: $(livekit-server generate-keys | tail -1 | awk '{print $3}')" > "${keyFile}"
|
||||||
|
'';
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
unitConfig.ConditionPathExists = "!${keyFile}";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.lk-jwt-service.environment.LIVEKIT_FULL_ACCESS_HOMESERVERS =
|
||||||
|
matrixDomain;
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts.${matrixDomain} = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
|
||||||
|
locations."=/.well-known/matrix/client" = {
|
||||||
|
extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization";
|
||||||
|
'';
|
||||||
|
return = ''200 '{
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://${matrixDomain}"
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
"base_url": "https://vector.im"
|
||||||
|
},
|
||||||
|
"org.matrix.msc3575.proxy": {
|
||||||
|
"url": "https://${matrixDomain}"
|
||||||
|
},
|
||||||
|
"org.matrix.msc4143.rtc_foci": [
|
||||||
|
{
|
||||||
|
"type": "livekit",
|
||||||
|
"livekit_service_url": "https://${matrixDomain}/livekit/jwt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}' '';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."= /livekit/jwt" = {
|
||||||
|
priority = 500;
|
||||||
|
proxyPass = "http://[::1]:${toString config.services.lk-jwt-service.port}/";
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /livekit/jwt/" = {
|
||||||
|
priority = 400;
|
||||||
|
proxyPass = "http://[::1]:${toString config.services.lk-jwt-service.port}/";
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."= /livekit/sfu" = {
|
||||||
|
priority = 500;
|
||||||
|
proxyPass = "http://[::1]:${toString config.services.livekit.settings.port}/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 120;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header Accept-Encoding gzip;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /livekit/sfu/" = {
|
||||||
|
priority = 400;
|
||||||
|
proxyPass = "http://[::1]:${toString config.services.livekit.settings.port}/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 120;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header Accept-Encoding gzip;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
8080
|
||||||
|
7880
|
||||||
|
7881
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
legacyCfg = config.hectic.services.matrix;
|
||||||
|
clusterCfg = config.hectic.generic.matrix-cluster;
|
||||||
|
clusterSynapseEnabled =
|
||||||
|
clusterCfg.enable
|
||||||
|
&& (if clusterCfg.overrideEnableSynapse != null then clusterCfg.overrideEnableSynapse else clusterCfg.role == "primary");
|
||||||
|
enabled = legacyCfg.enable || clusterSynapseEnabled;
|
||||||
|
matrixDomain = if legacyCfg.enable then legacyCfg.matrixDomain else clusterCfg.matrixDomain;
|
||||||
|
jitsiPreferredDomain =
|
||||||
|
if legacyCfg.enable && config.hectic.services.jitsi.enable
|
||||||
|
then config.hectic.services.jitsi.hostName
|
||||||
|
else clusterCfg.jitsi.preferredDomain;
|
||||||
|
in {
|
||||||
|
config = lib.mkIf enabled {
|
||||||
|
services.nginx.virtualHosts."element.${matrixDomain}" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
locations."= /config.element.${matrixDomain}.json".return = "302 /config.json";
|
||||||
|
|
||||||
|
root = pkgs.element-web.override {
|
||||||
|
conf = {
|
||||||
|
default_server_config = {
|
||||||
|
"m.homeserver".base_url = "https://${matrixDomain}";
|
||||||
|
"m.homeserver".server_name = matrixDomain;
|
||||||
|
"m.identity_server".base_url = "https://vector.im";
|
||||||
|
};
|
||||||
|
|
||||||
|
room_directory.servers = [
|
||||||
|
matrixDomain
|
||||||
|
];
|
||||||
|
|
||||||
|
jitsi = lib.optionalAttrs (jitsiPreferredDomain != null) {
|
||||||
|
preferred_domain = jitsiPreferredDomain;
|
||||||
|
};
|
||||||
|
|
||||||
|
default_theme = "dark";
|
||||||
|
show_labs_settings = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.jitsi;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services.jitsi = {
|
||||||
|
enable = lib.mkEnableOption "Jitsi Meet video conferencing with Prosody XMPP backend";
|
||||||
|
hostName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
FQDN for the Jitsi Meet instance (e.g. "meet.example.org").
|
||||||
|
Prosody VirtualHosts, nginx, and ACME certs are derived from this.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
secureDomain = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Require authentication to create rooms. Guests can still join
|
||||||
|
existing rooms anonymously.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
lockdown = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Restrict Prosody to localhost only (no S2S federation, c2s
|
||||||
|
only on 127.0.0.1). Set to false when running alongside a
|
||||||
|
general-purpose XMPP server (hectic.services.xmpp).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
videobridgePasswordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Path to a file containing the Jitsi Videobridge XMPP password.
|
||||||
|
If null, a random password is auto-generated.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.jitsi-meet = {
|
||||||
|
enable = true;
|
||||||
|
hostName = cfg.hostName;
|
||||||
|
|
||||||
|
prosody = {
|
||||||
|
enable = true;
|
||||||
|
lockdown = cfg.lockdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
nginx.enable = true;
|
||||||
|
videobridge = {
|
||||||
|
enable = true;
|
||||||
|
} // lib.optionalAttrs (cfg.videobridgePasswordFile != null) {
|
||||||
|
passwordFile = cfg.videobridgePasswordFile;
|
||||||
|
};
|
||||||
|
jicofo.enable = true;
|
||||||
|
|
||||||
|
secureDomain = lib.mkIf cfg.secureDomain {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.jitsi-videobridge.openFirewall = true;
|
||||||
|
|
||||||
|
services.nginx.virtualHosts.${cfg.hostName} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults = {
|
||||||
|
email = lib.mkDefault "hectic.yukkop.it@gmail.com";
|
||||||
|
enableDebugLogs = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
80 443 # HTTP/HTTPS (nginx + ACME)
|
||||||
|
5222 # XMPP c2s (if not locked down)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.services.mailserver;
|
||||||
|
transformLoginAccounts = domain: input:
|
||||||
|
builtins.listToAttrs (map (key: {
|
||||||
|
name = key + "@" + domain;
|
||||||
|
value = input.${key};
|
||||||
|
}) (builtins.attrNames input));
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
services.mailserver.enable = lib.mkEnableOption "Mail server";
|
||||||
|
|
||||||
|
services.mailserver.domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "The domain name of the mail server";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.mailserver.loginAccounts = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
hashedPassword = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
hashedPasswordFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Full path to a file containing the hashed password suitable
|
||||||
|
for use with `chpasswd -e`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = "Login accounts for the mail server";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
mailserver = {
|
||||||
|
enable = true;
|
||||||
|
fqdn = "mail." + cfg.domain;
|
||||||
|
domains = [ cfg.domain ];
|
||||||
|
|
||||||
|
loginAccounts = transformLoginAccounts cfg.domain cfg.loginAccounts;
|
||||||
|
|
||||||
|
certificateScheme = "acme-nginx";
|
||||||
|
};
|
||||||
|
|
||||||
|
# NOTE(yukkop): avoid Gmail rejection due to missing IPv6 PTR records
|
||||||
|
services.postfix.settings.main.inet_protocols = lib.mkDefault "ipv4";
|
||||||
|
|
||||||
|
security.acme.acceptTerms = true;
|
||||||
|
security.acme.defaults.email = "security@" + cfg.domain;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,508 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.matrix;
|
||||||
|
s3Cfg = cfg.objectStorage.s3;
|
||||||
|
|
||||||
|
matrixUsers = builtins.attrNames cfg.users;
|
||||||
|
|
||||||
|
s3Plugin = pkgs.matrix-synapse-plugins.matrix-synapse-s3-storage-provider;
|
||||||
|
s3ConfigDir = "/run/matrix-synapse";
|
||||||
|
s3ConfigFile = "${s3ConfigDir}/s3-media-storage.yaml";
|
||||||
|
|
||||||
|
mkUserRegistration = name: let
|
||||||
|
user = cfg.users.${name};
|
||||||
|
adminFlag = if user.admin then "--admin" else "--no-admin";
|
||||||
|
in ''
|
||||||
|
if [ ! -r "${user.passwordFile}" ]; then
|
||||||
|
printf 'Missing Matrix password file for %s: %s\n' '${name}' '${user.passwordFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${pkgs.matrix-synapse}/bin/register_new_matrix_user \
|
||||||
|
-u '${name}' \
|
||||||
|
-p "$(tr -d '\n' < "${user.passwordFile}")" \
|
||||||
|
-k "$REGISTRATION_SHARED_SECRET" \
|
||||||
|
${adminFlag} \
|
||||||
|
http://127.0.0.1:8008 || true
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkS3Config = ''
|
||||||
|
if [ ! -r "${s3Cfg.credentialsFile}" ]; then
|
||||||
|
printf 'Missing Matrix object storage credentials file: %s\n' '${s3Cfg.credentialsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
. "${s3Cfg.credentialsFile}"
|
||||||
|
|
||||||
|
if [ -z "$ACCESS_KEY_ID" ] || [ -z "$SECRET_ACCESS_KEY" ]; then
|
||||||
|
printf 'ACCESS_KEY_ID or SECRET_ACCESS_KEY missing in %s\n' '${s3Cfg.credentialsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${s3ConfigDir}"
|
||||||
|
|
||||||
|
cat > "${s3ConfigFile}" <<EOF
|
||||||
|
media_storage_providers:
|
||||||
|
- module: s3_storage_provider.S3StorageProviderBackend
|
||||||
|
store_local: ${lib.boolToString s3Cfg.storeLocal}
|
||||||
|
store_remote: ${lib.boolToString s3Cfg.storeRemote}
|
||||||
|
store_synchronous: ${lib.boolToString s3Cfg.storeSynchronous}
|
||||||
|
config:
|
||||||
|
bucket: ${s3Cfg.bucket}
|
||||||
|
endpoint_url: ${s3Cfg.endpointUrl}
|
||||||
|
region_name: ${s3Cfg.regionName}
|
||||||
|
prefix: "${s3Cfg.prefix}"
|
||||||
|
storage_class: "${s3Cfg.storageClass}"
|
||||||
|
threadpool_size: ${toString s3Cfg.threadpoolSize}
|
||||||
|
access_key_id: $ACCESS_KEY_ID
|
||||||
|
secret_access_key: $SECRET_ACCESS_KEY
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chown matrix-synapse:matrix-synapse "${s3ConfigFile}"
|
||||||
|
chmod 0400 "${s3ConfigFile}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
mkS3SyncScript = ''
|
||||||
|
${s3Plugin}/bin/s3_media_upload write
|
||||||
|
${s3Plugin}/bin/s3_media_upload upload "${s3Cfg.mediaStorePath}" "${s3Cfg.bucket}" \
|
||||||
|
--endpoint-url "${s3Cfg.endpointUrl}" \
|
||||||
|
--storage-class "${s3Cfg.storageClass}" \
|
||||||
|
--prefix "${s3Cfg.prefix}" \
|
||||||
|
${lib.optionalString s3Cfg.sync.deleteLocalAfterUpload "--delete"}
|
||||||
|
cat > /tmp/synapse-merge-config.py << 'PYEOF'
|
||||||
|
import yaml
|
||||||
|
with open("${config.services.matrix-synapse.configFile}") as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
with open("${cfg.secretsFile}") as f:
|
||||||
|
secrets = yaml.safe_load(f)
|
||||||
|
config.update(secrets)
|
||||||
|
config.setdefault("database", {}).setdefault("args", {})
|
||||||
|
config["database"]["args"].setdefault("password", "")
|
||||||
|
config["database"]["args"].setdefault("host", "/run/postgresql")
|
||||||
|
config["database"]["args"].setdefault("port", 5432)
|
||||||
|
with open("/tmp/synapse-combined-config.yaml", "w") as f:
|
||||||
|
yaml.dump(config, f, default_flow_style=False)
|
||||||
|
PYEOF
|
||||||
|
${pkgs.python3.withPackages (ps: [ps.pyyaml])}/bin/python3 /tmp/synapse-merge-config.py
|
||||||
|
${s3Plugin}/bin/s3_media_upload update-db --homeserver-config-path /tmp/synapse-combined-config.yaml 0s
|
||||||
|
rm -f /tmp/synapse-combined-config.yaml
|
||||||
|
${s3Plugin}/bin/s3_media_upload check-deleted "${s3Cfg.mediaStorePath}"
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services.matrix = {
|
||||||
|
enable = lib.mkEnableOption "Matrix Synapse homeserver with PostgreSQL and nginx";
|
||||||
|
|
||||||
|
secretsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
path to env file with matrix secrets
|
||||||
|
|
||||||
|
content:
|
||||||
|
registration_shared_secret:
|
||||||
|
macroon_secret_key
|
||||||
|
form_secret
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
postgresql = {
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 5432;
|
||||||
|
description = ''
|
||||||
|
postgres port
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
initialEnvFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
path to env file with postgresql initial secrets
|
||||||
|
|
||||||
|
content:
|
||||||
|
POSTGRESQL_PASSWORD=
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
matrixDomain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
domain to matrix
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
maxUploadSize = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "2G";
|
||||||
|
description = ''
|
||||||
|
Maximum file upload size accepted by Synapse and nginx.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
enableRegistration = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Allow open user registration on the homeserver.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
objectStorage.s3 = {
|
||||||
|
enable = lib.mkEnableOption "S3-compatible object storage for Matrix media";
|
||||||
|
|
||||||
|
bucket = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Bucket name used for Matrix media objects.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
regionName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Region name passed to the Synapse S3 storage provider.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
endpointUrl = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
S3-compatible endpoint URL.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
credentialsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
Path to an env-style file containing:
|
||||||
|
ACCESS_KEY_ID=
|
||||||
|
SECRET_ACCESS_KEY=
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaStorePath = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/matrix-synapse/media_store";
|
||||||
|
description = ''
|
||||||
|
Local Synapse media store path used before upload to object storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
prefix = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = ''
|
||||||
|
Optional object key prefix inside the bucket.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
storageClass = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "STANDARD";
|
||||||
|
description = ''
|
||||||
|
Storage class passed to the upload tool.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
threadpoolSize = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 40;
|
||||||
|
description = ''
|
||||||
|
Worker pool size for the Synapse S3 storage provider.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
storeLocal = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Mirror local uploads to object storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
storeRemote = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Mirror remotely-fetched media to object storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
storeSynchronous = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Wait for object storage upload before completing the client request.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
sync = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Periodically migrate older local media to object storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
olderThan = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "1d";
|
||||||
|
description = ''
|
||||||
|
Age threshold passed to `s3_media_upload update`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
onCalendar = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "hourly";
|
||||||
|
description = ''
|
||||||
|
systemd timer schedule for media sync.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteLocalAfterUpload = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Remove local media after successful object storage upload.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
users = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
passwordFile = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Full path to a file containing the Matrix user's password.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
admin = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to create the Matrix user as an admin.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Declarative Matrix users to provision after Synapse starts.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
(lib.mkIf cfg.enable {
|
||||||
|
services.matrix-synapse = {
|
||||||
|
enable = true;
|
||||||
|
plugins = lib.optional s3Cfg.enable s3Plugin;
|
||||||
|
extraConfigFiles = [
|
||||||
|
cfg.secretsFile
|
||||||
|
] ++ lib.optional s3Cfg.enable s3ConfigFile;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
server_name = cfg.matrixDomain;
|
||||||
|
public_baseurl = "https://${cfg.matrixDomain}";
|
||||||
|
max_upload_size = cfg.maxUploadSize;
|
||||||
|
media_store_path = lib.mkIf s3Cfg.enable s3Cfg.mediaStorePath;
|
||||||
|
|
||||||
|
experimental_features = {
|
||||||
|
msc3266_enabled = true;
|
||||||
|
msc4140_enabled = true;
|
||||||
|
msc4143_enabled = true;
|
||||||
|
msc4222_enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
matrix_rtc = {
|
||||||
|
transports = [
|
||||||
|
{
|
||||||
|
type = "livekit";
|
||||||
|
livekit_service_url = "https://${cfg.matrixDomain}/livekit/jwt";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
listeners = [
|
||||||
|
{
|
||||||
|
port = 8008;
|
||||||
|
bind_addresses = [ "0.0.0.0" ];
|
||||||
|
type = "http";
|
||||||
|
tls = false;
|
||||||
|
resources = [
|
||||||
|
{
|
||||||
|
names = [
|
||||||
|
"client"
|
||||||
|
# Ability speak between different matrix servers and get
|
||||||
|
# global id, requires .well-known
|
||||||
|
"federation"
|
||||||
|
"openid"
|
||||||
|
];
|
||||||
|
compress = false;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
enable_registration = cfg.enableRegistration;
|
||||||
|
enable_registration_without_verification = cfg.enableRegistration;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgs.matrix-synapse
|
||||||
|
];
|
||||||
|
|
||||||
|
services.postgresql = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.postgresql_17;
|
||||||
|
|
||||||
|
initdbArgs = [
|
||||||
|
"--locale=C"
|
||||||
|
"--encoding=UTF8"
|
||||||
|
];
|
||||||
|
|
||||||
|
enableTCPIP = true;
|
||||||
|
settings.port = cfg.postgresql.port;
|
||||||
|
authentication = builtins.concatStringsSep "\n" [
|
||||||
|
"local all all trust"
|
||||||
|
"host sameuser all 127.0.0.1/32 scram-sha-256"
|
||||||
|
"host sameuser all ::1/128 scram-sha-256"
|
||||||
|
"host all all ::1/128 scram-sha-256"
|
||||||
|
"host all all 0.0.0.0/0 scram-sha-256"
|
||||||
|
|
||||||
|
"host replication postgres 127.0.0.1/32 scram-sha-256"
|
||||||
|
"host replication postgres ::1/128 scram-sha-256"
|
||||||
|
];
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
wal_level = "replica";
|
||||||
|
max_wal_senders = 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureUsers = [
|
||||||
|
{
|
||||||
|
name = "matrix-synapse";
|
||||||
|
ensureClauses.login = true;
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
ensureDatabases = [
|
||||||
|
"matrix-synapse"
|
||||||
|
];
|
||||||
|
|
||||||
|
initialScript = pkgs.writeText "init-sql-script" ''
|
||||||
|
-- setup password from env/sops
|
||||||
|
DO $$#!${pkgs.dash}/bin/dash
|
||||||
|
set -e
|
||||||
|
. ${cfg.postgresql.initialEnvFile}
|
||||||
|
psql -Atc "ALTER USER postgres WITH PASSWORD '$POSTGRESQL_PASSWORD'";
|
||||||
|
$$ LANGUAGE plsh;
|
||||||
|
|
||||||
|
CREATE ROLE myuser LOGIN PASSWORD 'matrix-synapse';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts.${cfg.matrixDomain} = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8008";
|
||||||
|
extraConfig = ''
|
||||||
|
client_max_body_size ${cfg.maxUploadSize};
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
locations."=/.well-known/matrix/server" = {
|
||||||
|
extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization";
|
||||||
|
'';
|
||||||
|
return = "200 '{\"m.server\": \"${cfg.matrixDomain}:443\"}'";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults = {
|
||||||
|
email = "hectic.yukkop.it@gmail.com";
|
||||||
|
enableDebugLogs = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.matrix-synapse-users = lib.mkIf (matrixUsers != []) {
|
||||||
|
description = "Provision Matrix Synapse users";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
requires = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
path = with pkgs; [ curl coreutils gawk ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "matrix-synapse";
|
||||||
|
};
|
||||||
|
script = ''
|
||||||
|
until curl -sf http://127.0.0.1:8008/_matrix/client/versions >/dev/null; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
REGISTRATION_SHARED_SECRET="$(awk -F': *' '$1 == "registration_shared_secret" { print $2; exit }' "${cfg.secretsFile}")"
|
||||||
|
|
||||||
|
if [ -z "$REGISTRATION_SHARED_SECRET" ]; then
|
||||||
|
printf 'registration_shared_secret not found in %s\n' '${cfg.secretsFile}' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
${builtins.concatStringsSep "\n" (map mkUserRegistration matrixUsers)}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf (cfg.enable && s3Cfg.enable) {
|
||||||
|
systemd.services.matrix-synapse-s3-config = {
|
||||||
|
description = "Generate Synapse S3 media storage config";
|
||||||
|
before = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
requiredBy = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = mkS3Config;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.matrix-synapse-s3-media-sync = lib.mkIf s3Cfg.sync.enable {
|
||||||
|
description = "Sync Matrix media to S3-compatible object storage";
|
||||||
|
after = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
wants = [ config.services.matrix-synapse.serviceUnit ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
User = "matrix-synapse";
|
||||||
|
WorkingDirectory = "/var/lib/matrix-synapse";
|
||||||
|
};
|
||||||
|
script = mkS3SyncScript;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.timers.matrix-synapse-s3-media-sync = lib.mkIf s3Cfg.sync.enable {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig.OnCalendar = s3Cfg.sync.onCalendar;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.media-browser;
|
||||||
|
|
||||||
|
mediaBrowserApp = pkgs.hectic.media-browser;
|
||||||
|
in {
|
||||||
|
options.hectic.services.media-browser = {
|
||||||
|
enable = lib.mkEnableOption "Matrix media browser web app";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 3000;
|
||||||
|
description = "Port to bind the media browser web server.";
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaStorePath = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "/var/lib/matrix-synapse/media_store";
|
||||||
|
description = "Path to Synapse media store.";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3CredentialsFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = "Path to S3 credentials file (ACCESS_KEY_ID=..., SECRET_ACCESS_KEY=...).";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3Bucket = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "S3 bucket name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3Endpoint = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "S3 endpoint URL.";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3Region = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "hel1";
|
||||||
|
description = "S3 region name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
s3Prefix = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "";
|
||||||
|
description = "S3 object key prefix.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dbName = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "matrix-synapse";
|
||||||
|
description = "PostgreSQL database name.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dbUser = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "matrix-synapse";
|
||||||
|
description = "PostgreSQL database user.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
systemd.services.media-browser = {
|
||||||
|
description = "Matrix Media Browser";
|
||||||
|
after = [ "network.target" "postgresql.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
User = "matrix-synapse";
|
||||||
|
Group = "matrix-synapse";
|
||||||
|
ExecStart = "${mediaBrowserApp}/bin/media-browser-wrapped";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
};
|
||||||
|
environment = {
|
||||||
|
FLASK_ENV = "production";
|
||||||
|
PORT = toString cfg.port;
|
||||||
|
MEDIA_STORE_PATH = cfg.mediaStorePath;
|
||||||
|
S3_BUCKET = cfg.s3Bucket;
|
||||||
|
S3_ENDPOINT = cfg.s3Endpoint;
|
||||||
|
S3_REGION = cfg.s3Region;
|
||||||
|
S3_PREFIX = cfg.s3Prefix;
|
||||||
|
DB_NAME = cfg.dbName;
|
||||||
|
DB_USER = cfg.dbUser;
|
||||||
|
DB_HOST = "/run/postgresql";
|
||||||
|
DB_PORT = "5432";
|
||||||
|
};
|
||||||
|
serviceConfig.EnvironmentFile = cfg.s3CredentialsFile;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
cfg = config.hectic.services."sentinèlla";
|
||||||
|
|
||||||
|
probePort = 5988;
|
||||||
|
peersSrv = "_sentinella._tcp.hectic-lab.com";
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services."sentinèlla" = {
|
||||||
|
probe = {
|
||||||
|
enable = lib.mkEnableOption "sentinèlla probe — HTTP server exposing this node's health";
|
||||||
|
urls = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [];
|
||||||
|
description = "URLs the probe health-checks on GET /status.";
|
||||||
|
};
|
||||||
|
volumes = lib.mkOption {
|
||||||
|
type = with lib.types; listOf str;
|
||||||
|
default = [];
|
||||||
|
description = "Mount points reported on GET /disk. Empty means all volumes.";
|
||||||
|
};
|
||||||
|
authFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
example = "config.sops.secrets.\"sentinella-probe-auth\".path";
|
||||||
|
description = "Path to a file with lines of the form user:pass for Basic Auth.";
|
||||||
|
};
|
||||||
|
environmentFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Optional environment file for secrets. Supported variables:
|
||||||
|
PORT=
|
||||||
|
URLS=
|
||||||
|
VOLUMES=
|
||||||
|
AUTH_FILE=
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
watcher = {
|
||||||
|
enable = lib.mkEnableOption "sentinèlla watcher — polls peers discovered via DNS and sends Telegram alerts";
|
||||||
|
self = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
example = "1.2.3.4";
|
||||||
|
description = ''
|
||||||
|
Override the auto-detected local IP. When null (default) the watcher
|
||||||
|
uses hostname -I to find all local IPs and excludes them from the
|
||||||
|
peer list automatically. Set this only if the node is behind NAT or
|
||||||
|
has a floating IP that hostname -I does not report correctly.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
peersScheme = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "http";
|
||||||
|
description = "URL scheme used when connecting to peers (http or https).";
|
||||||
|
};
|
||||||
|
pollingIntervalSec = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 3;
|
||||||
|
description = "Seconds between polling rounds.";
|
||||||
|
};
|
||||||
|
tgToken = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Telegram bot token. Prefer environmentFile for secrets.";
|
||||||
|
};
|
||||||
|
tgChatId = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr str;
|
||||||
|
default = null;
|
||||||
|
description = "Telegram chat ID. Prefer environmentFile for secrets.";
|
||||||
|
};
|
||||||
|
environmentFile = lib.mkOption {
|
||||||
|
type = with lib.types; nullOr path;
|
||||||
|
default = config.sops.secrets."sentinèlla/watcher/environment".path;
|
||||||
|
defaultText = lib.literalExpression
|
||||||
|
"config.sops.secrets.\"sentinèlla/watcher/environment\".path";
|
||||||
|
example = "config.sops.secrets.\"sentinella-watcher-env\".path";
|
||||||
|
description = ''
|
||||||
|
Environment file for secrets. Defaults to the auto-declared SOPS
|
||||||
|
secret sentinèlla/watcher/environment (resolved from
|
||||||
|
sus/sentinella-default.yaml in the flake). Override the sopsFile
|
||||||
|
via sops.secrets."sentinèlla/watcher/environment".sopsFile if you
|
||||||
|
need a host-specific file instead.
|
||||||
|
|
||||||
|
Supported variables:
|
||||||
|
TG_TOKEN=
|
||||||
|
TG_CHAT_ID=
|
||||||
|
PEERS_TOKEN= # Basic Auth token sent to all peers
|
||||||
|
SELF=
|
||||||
|
PEERS_SRV=
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
(lib.mkIf cfg.probe.enable {
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
probePort
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."sentinella-probe" = {
|
||||||
|
description = "sentinèlla probe — node health HTTP server";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = lib.mkMerge [
|
||||||
|
{
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${self.packages.${system}."sentinèlla"}/bin/probe";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
KillMode = "mixed";
|
||||||
|
RemainAfterExit = false;
|
||||||
|
StandardOutput = "journal";
|
||||||
|
StandardError = "journal";
|
||||||
|
Environment = lib.filter (s: s != "") [
|
||||||
|
"PORT=${builtins.toString probePort}"
|
||||||
|
(lib.optionalString (cfg.probe.urls != []) "URLS=${lib.concatStringsSep " " cfg.probe.urls}")
|
||||||
|
(lib.optionalString (cfg.probe.volumes != []) "VOLUMES=${lib.concatStringsSep " " cfg.probe.volumes}")
|
||||||
|
(lib.optionalString (cfg.probe.authFile != null) "AUTH_FILE=${cfg.probe.authFile}")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
(lib.mkIf (cfg.probe.environmentFile != null) {
|
||||||
|
EnvironmentFile = cfg.probe.environmentFile;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(lib.mkIf cfg.watcher.enable {
|
||||||
|
sops.secrets."sentinèlla/watcher/environment" = lib.mkDefault {
|
||||||
|
sopsFile = "${flake}/sus/sentinella-default.yaml";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services."sentinella-watcher" = {
|
||||||
|
description = "sentinèlla watcher — p2p peer monitor";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = lib.mkMerge [
|
||||||
|
{
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${self.packages.${system}."sentinèlla"}/bin/watcher";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
KillMode = "mixed";
|
||||||
|
RemainAfterExit = false;
|
||||||
|
StandardOutput = "journal";
|
||||||
|
StandardError = "journal";
|
||||||
|
StateDirectory = "sentinella";
|
||||||
|
Environment = lib.filter (s: s != "") [
|
||||||
|
"PEERS_SRV=${peersSrv}"
|
||||||
|
(lib.optionalString (cfg.watcher.self != null) "SELF=${cfg.watcher.self}")
|
||||||
|
"PEERS_SCHEME=${cfg.watcher.peersScheme}"
|
||||||
|
"POLLING_INTERVAL_SEC=${builtins.toString cfg.watcher.pollingIntervalSec}"
|
||||||
|
"STATE_DIR=/var/lib/sentinella"
|
||||||
|
(lib.optionalString (cfg.watcher.tgToken != null) "TG_TOKEN=${cfg.watcher.tgToken}")
|
||||||
|
(lib.optionalString (cfg.watcher.tgChatId != null) "TG_CHAT_ID=${cfg.watcher.tgChatId}")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
(lib.mkIf (cfg.watcher.environmentFile != null) {
|
||||||
|
EnvironmentFile = cfg.watcher.environmentFile;
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
{ inputs, flake, self }:
|
||||||
|
{ config, pkgs, lib, ... }: let
|
||||||
|
cfg = config.hectic.services.support-bot;
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
|
||||||
|
packagesAttr = lib.mapAttrs (packageName: packageConfig:
|
||||||
|
packageConfig // {
|
||||||
|
name = packageName;
|
||||||
|
}) cfg;
|
||||||
|
packagesArr = builtins.attrValues packagesAttr;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services.support-bot = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
redisHost = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "localhost";
|
||||||
|
example = "localhost";
|
||||||
|
description = ''
|
||||||
|
redis db host
|
||||||
|
if localhost - module spawns redis
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
redisPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
example = "42069";
|
||||||
|
description = ''redis db port'';
|
||||||
|
};
|
||||||
|
redisDb = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
apply = x: if x >= 0 && x <= 15 then x else throw "must be 0..15";
|
||||||
|
default = 0;
|
||||||
|
example = "0";
|
||||||
|
description = ''redis db number (0-15)'';
|
||||||
|
};
|
||||||
|
environmentPath = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
example = ''
|
||||||
|
config.sops.secrets."name-of-service/environment".path
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
BOT_TOKEN=
|
||||||
|
BOT_DEV_ID=
|
||||||
|
BOT_GROUP_ID=
|
||||||
|
BOT_EMOJI_ID=
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression /* nix */ ''
|
||||||
|
{
|
||||||
|
"name-of-service" = {
|
||||||
|
environmentPath = config.sops.secrets."name-of-service/environment".path;
|
||||||
|
redisDb = 3;
|
||||||
|
redisPort = 42069;
|
||||||
|
redisHost = localhost;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'';
|
||||||
|
description = "Declarative support bot config";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
services.redis.servers = lib.mkMerge (map (supportConfig: {
|
||||||
|
"support-bot-${supportConfig.name}" = lib.mkIf (supportConfig.redisHost == "localhost") {
|
||||||
|
enable = true;
|
||||||
|
port = supportConfig.redisPort;
|
||||||
|
};
|
||||||
|
}) packagesArr);
|
||||||
|
systemd.services = lib.mkMerge (map (supportConfig: {
|
||||||
|
"support-bot-${supportConfig.name}" = {
|
||||||
|
description = "Support Bot Service";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
ExecStart = "${self.packages.${system}.support-bot}/bin/support-bot";
|
||||||
|
Restart = "always";
|
||||||
|
RestartSec = "5s";
|
||||||
|
EnvironmentFile = supportConfig.environmentPath;
|
||||||
|
Environment = [
|
||||||
|
"REDIS_HOST=${supportConfig.redisHost}"
|
||||||
|
"REDIS_PORT=${builtins.toString supportConfig.redisPort}"
|
||||||
|
"REDIS_DB=${builtins.toString supportConfig.redisDb}"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Shutdown configuration
|
||||||
|
TimeoutStopSec = "30s";
|
||||||
|
KillSignal = "SIGTERM";
|
||||||
|
KillMode = "mixed";
|
||||||
|
|
||||||
|
# Security and process management
|
||||||
|
RemainAfterExit = false;
|
||||||
|
StandardOutput = "journal";
|
||||||
|
StandardError = "journal";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}) packagesArr);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.matrix;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services.matrix = {
|
||||||
|
turnSecretFile = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
path to env file with matrix secrets
|
||||||
|
|
||||||
|
just raw secret
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
publicIp = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
public IP address of the server, used by coturn for
|
||||||
|
listening and relay
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.coturn = rec {
|
||||||
|
enable = true;
|
||||||
|
realm = cfg.matrixDomain;
|
||||||
|
use-auth-secret = true;
|
||||||
|
static-auth-secret-file = cfg.turnSecretFile;
|
||||||
|
cert = "${config.security.acme.certs.${realm}.directory}/full.pem";
|
||||||
|
pkey = "${config.security.acme.certs.${realm}.directory}/key.pem";
|
||||||
|
listening-ips = [cfg.publicIp];
|
||||||
|
no-tcp-relay = true;
|
||||||
|
relay-ips = [cfg.publicIp];
|
||||||
|
listening-port = 3478;
|
||||||
|
tls-listening-port = 5349;
|
||||||
|
no-cli = true;
|
||||||
|
|
||||||
|
extraConfig = ''
|
||||||
|
verbose
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedUDPPorts = [ 3478 5349 ];
|
||||||
|
allowedTCPPorts = [ 3478 5349 ];
|
||||||
|
allowedUDPPortRanges = [
|
||||||
|
{ from = 49152; to = 65535; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.matrix-synapse.settings = {
|
||||||
|
turn_uris = [
|
||||||
|
"turn:${cfg.matrixDomain}:3478?transport=udp"
|
||||||
|
"turn:${cfg.matrixDomain}:3478?transport=tcp"
|
||||||
|
"turns:${cfg.matrixDomain}:5349?transport=udp"
|
||||||
|
"turns:${cfg.matrixDomain}:5349?transport=tcp"
|
||||||
|
];
|
||||||
|
turn_user_lifetime = 86400000;
|
||||||
|
turn_allow_guests = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
cfg = config.hectic.services.xmpp;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
hectic.services.xmpp = {
|
||||||
|
enable = lib.mkEnableOption "General-purpose XMPP server (Prosody) for messaging clients like Monocles Chat";
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Primary XMPP domain. Users will have JIDs like user@domain.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
admins = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [];
|
||||||
|
example = [ "admin@example.org" ];
|
||||||
|
description = ''
|
||||||
|
List of admin JIDs.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
allowRegistration = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Allow in-band account registration from clients.
|
||||||
|
If false, create accounts with: prosodyctl register <user> <domain> <password>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
uploadFileSizeLimit = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 10485760;
|
||||||
|
description = ''
|
||||||
|
Maximum file upload size in bytes (default 10 MiB).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
services.prosody = {
|
||||||
|
enable = true;
|
||||||
|
admins = cfg.admins;
|
||||||
|
allowRegistration = cfg.allowRegistration;
|
||||||
|
|
||||||
|
ssl = {
|
||||||
|
cert = "/var/lib/acme/${cfg.domain}/fullchain.pem";
|
||||||
|
key = "/var/lib/acme/${cfg.domain}/key.pem";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
enabled = true;
|
||||||
|
domain = cfg.domain;
|
||||||
|
ssl = {
|
||||||
|
cert = "/var/lib/acme/${cfg.domain}/fullchain.pem";
|
||||||
|
key = "/var/lib/acme/${cfg.domain}/key.pem";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
muc = [
|
||||||
|
{ domain = "conference.${cfg.domain}"; }
|
||||||
|
];
|
||||||
|
|
||||||
|
httpFileShare = {
|
||||||
|
domain = "upload.${cfg.domain}";
|
||||||
|
size_limit = cfg.uploadFileSizeLimit;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Grant prosody read access to ACME certs (group is "nginx" since
|
||||||
|
# the nginx vhost requests the cert via enableACME)
|
||||||
|
users.users.prosody.extraGroups = [ "nginx" ];
|
||||||
|
|
||||||
|
# nginx vhost handles ACME HTTP-01 challenge for the XMPP domain.
|
||||||
|
# The cert also covers conference.* and upload.* subdomains.
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts.${cfg.domain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/".return = "301 https://meet.${cfg.domain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults = {
|
||||||
|
email = "hectic.yukkop.it@gmail.com";
|
||||||
|
enableDebugLogs = true;
|
||||||
|
};
|
||||||
|
# Add MUC + upload subdomains to the nginx-managed cert
|
||||||
|
certs.${cfg.domain} = {
|
||||||
|
extraDomainNames = [
|
||||||
|
"conference.${cfg.domain}"
|
||||||
|
"upload.${cfg.domain}"
|
||||||
|
];
|
||||||
|
reloadServices = [ "prosody" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
5222 # c2s (client-to-server)
|
||||||
|
5269 # s2s (server-to-server federation)
|
||||||
|
80 # ACME HTTP-01 challenge
|
||||||
|
443 # HTTPS
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
name = "yukkop";
|
||||||
|
#name = builtins.baseNameOf ./.;
|
||||||
|
home = "/home/${name}";
|
||||||
|
cfg = config.hectic.user.yukkop;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
];
|
||||||
|
|
||||||
|
options.hectic.user.yukkop.enable = lib.mkEnableOption "Enable user.yukkop";
|
||||||
|
|
||||||
|
config = lib.mkIf cfg.enable {
|
||||||
|
users.users.${name} = {
|
||||||
|
isNormalUser = true;
|
||||||
|
initialPassword = "kk";
|
||||||
|
extraGroups = [ "wheel" "docker" "owner" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
home-manager.users.${name} = {
|
||||||
|
home.stateVersion = "24.05";
|
||||||
|
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
pinentry-tty
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.password-store = {
|
||||||
|
enable = true;
|
||||||
|
package = (pkgs.pass.override {
|
||||||
|
x11Support = false;
|
||||||
|
waylandSupport = false;
|
||||||
|
dmenuSupport = false;
|
||||||
|
}).withExtensions (exts: with exts; [
|
||||||
|
pass-otp
|
||||||
|
]);
|
||||||
|
settings.PASSWORD_STORE_DIR = "${home}/.pass";
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.gpg = {
|
||||||
|
enable = true;
|
||||||
|
homedir = "${home}/.gnupg";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.gpg-agent = {
|
||||||
|
enable = true;
|
||||||
|
pinentry.package = pkgs.pinentry-tty;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
defaultCacheTtl = 60 * 60;
|
||||||
|
maxCacheTtl = 60 * 60 * 24;
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.bash.shellAliases = {
|
||||||
|
dev = "nix develop -c zsh";
|
||||||
|
# system-specific rebuild aliases can be added per-system
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.git = {
|
||||||
|
enable = true;
|
||||||
|
lfs.enable = true;
|
||||||
|
settings = {
|
||||||
|
user.name = "yukkop";
|
||||||
|
user.email = "hectic.yukkop@gmail.com";
|
||||||
|
push.autoSetupRemote = true;
|
||||||
|
init.defaultBranch = "master";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
# TODO:
|
||||||
|
# white list
|
||||||
|
# torent
|
||||||
|
# rate limit
|
||||||
|
# ping - game and speak
|
||||||
|
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.xray-system
|
||||||
|
];
|
||||||
|
|
||||||
|
hectic.generic.xray-system = {
|
||||||
|
enable = true;
|
||||||
|
defaultSopsFile = ../../../sus/bfs.xray.yaml;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system; sanitize for hostName (no dots)
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
hostName = builtins.replaceStrings ["."] ["-"] name;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = hostName; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
# Matrix Cluster Failover Runbook (`accord.tube`)
|
||||||
|
|
||||||
|
Primary: `hectic-lab` (NL, `128.140.75.58`)
|
||||||
|
Standby: `bfs.poland.xray` (PL, `91.198.166.181`)
|
||||||
|
|
||||||
|
Module: `hectic.generic.matrix-cluster` (`nixos/module/generic/matrix-cluster.nix`).
|
||||||
|
Shared secrets: `sus/matrix-cluster.yaml`.
|
||||||
|
|
||||||
|
All `psql` and `pg_ctl` invocations use PostgreSQL **17** at data dir
|
||||||
|
`/var/lib/postgresql/17`.
|
||||||
|
|
||||||
|
## Initial setup
|
||||||
|
|
||||||
|
### 1. Provision shared SOPS file (`sus/matrix-cluster.yaml`)
|
||||||
|
|
||||||
|
On a workstation with both yukkop and yukkop-alt age keys available:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo cat /var/lib/matrix-synapse/homeserver.signing.key # on NL (hectic-lab)
|
||||||
|
# Copy the single line value into the buffer for the next step.
|
||||||
|
|
||||||
|
sops sus/matrix-cluster.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Populate the editor with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
matrix:
|
||||||
|
signing-key: <paste verbatim signing-key line from NL>
|
||||||
|
postgres-replication-password: <openssl rand -base64 32>
|
||||||
|
object-storage:
|
||||||
|
credentials: |
|
||||||
|
ACCESS_KEY_ID=<verbatim copy from sus/hectic-lab.yaml>
|
||||||
|
SECRET_ACCESS_KEY=<verbatim copy from sus/hectic-lab.yaml>
|
||||||
|
porkbun-api-key: <PORKBUN_API_KEY>
|
||||||
|
porkbun-secret-api-key: <PORKBUN_SECRET_API_KEY>
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify recipients:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sops updatekeys sus/matrix-cluster.yaml
|
||||||
|
sops -d sus/matrix-cluster.yaml | grep -E 'signing-key|porkbun-api-key|object-storage'
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all five keys present, exit 0.
|
||||||
|
|
||||||
|
### 2. Deploy NL primary first
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nixos-rebuild switch --flake .#'hectic-lab|x86_64-linux' --target-host root@128.140.75.58
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify on NL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo systemctl status matrix-synapse postgresql matrix-cluster-replication-password
|
||||||
|
sudo -u postgres psql -c "select rolname, rolreplication from pg_roles where rolname='replication';"
|
||||||
|
# Expected: replication | t
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Seed PL replica with `pg_basebackup`
|
||||||
|
|
||||||
|
On PL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo systemctl stop postgresql
|
||||||
|
sudo rm -rf /var/lib/postgresql/17
|
||||||
|
sudo -u postgres install -d -m 0700 /var/lib/postgresql/17
|
||||||
|
sudo -u postgres PGPASSWORD="$(sudo cat /run/secrets/matrix/postgres-replication-password)" \
|
||||||
|
pg_basebackup \
|
||||||
|
-h 128.140.75.58 \
|
||||||
|
-p 5432 \
|
||||||
|
-U replication \
|
||||||
|
-D /var/lib/postgresql/17 \
|
||||||
|
-Fp -Xs -P -R \
|
||||||
|
--no-password
|
||||||
|
```
|
||||||
|
|
||||||
|
`-R` writes `standby.signal` and an initial `primary_conninfo`. The
|
||||||
|
matrix-cluster module's `matrix-cluster-standby-bootstrap` service will
|
||||||
|
overwrite `primary_conninfo` to use a libpq passfile on next boot.
|
||||||
|
|
||||||
|
### 4. Deploy PL standby
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nixos-rebuild switch --flake .#'bfs.poland.xray|x86_64-linux' --target-host root@91.198.166.181
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify streaming on NL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo -u postgres psql -c 'select client_addr, state, sync_state from pg_stat_replication;'
|
||||||
|
# Expected: 91.198.166.181 | streaming | async
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify standby on PL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo -u postgres psql -c 'select pg_is_in_recovery();'
|
||||||
|
# Expected: t
|
||||||
|
sudo systemctl is-active matrix-synapse
|
||||||
|
# Expected: inactive (standby keeps Synapse off)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Remove duplicate S3 credentials from `sus/hectic-lab.yaml`
|
||||||
|
|
||||||
|
Only AFTER NL is confirmed healthy reading from the new shared file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sops sus/hectic-lab.yaml
|
||||||
|
# Delete the matrix/object-storage/credentials block.
|
||||||
|
sudo nixos-rebuild switch --flake .#'hectic-lab|x86_64-linux'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Normal operations
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# NL: replication health
|
||||||
|
sudo -u postgres psql -c 'select * from pg_stat_replication;'
|
||||||
|
# Expected: 1 row, state=streaming, sync_state=async
|
||||||
|
|
||||||
|
# PL: replay status
|
||||||
|
sudo -u postgres psql -c 'select now() - pg_last_xact_replay_timestamp() as lag;'
|
||||||
|
|
||||||
|
# Both: cert renewal
|
||||||
|
sudo systemctl status acme-accord.tube.timer
|
||||||
|
sudo journalctl -u acme-accord.tube.service --since '24 hours ago'
|
||||||
|
|
||||||
|
# Synapse health (NL primary)
|
||||||
|
curl -sf https://accord.tube/_matrix/client/versions | head
|
||||||
|
```
|
||||||
|
|
||||||
|
## Planned failover (NL -> PL)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Drain NL: stop accepting writes.
|
||||||
|
sudo systemctl stop matrix-synapse
|
||||||
|
sudo systemctl stop postgresql # ensure no new WAL after this point
|
||||||
|
|
||||||
|
# 2. Promote PL replica.
|
||||||
|
sudo -u postgres pg_ctl -D /var/lib/postgresql/17 promote
|
||||||
|
# Wait until pg_is_in_recovery() returns f:
|
||||||
|
sudo -u postgres psql -c 'select pg_is_in_recovery();'
|
||||||
|
|
||||||
|
# 3. Make the role switch declarative before rebuilding.
|
||||||
|
# Edit the flake so rebuilds match the promoted database state:
|
||||||
|
# - nixos/system/bfs.poland.xray/bfs.poland.xray.nix:
|
||||||
|
# hectic.generic.matrix-cluster.role = "primary";
|
||||||
|
# hectic.generic.matrix-cluster.overrideEnableSynapse = true;
|
||||||
|
# hectic.generic.matrix-cluster.secretsFile = config.sops.secrets."matrix/secrets".path;
|
||||||
|
# - nixos/system/hectic-lab/hectic-lab.nix:
|
||||||
|
# hectic.generic.matrix-cluster.role = "standby";
|
||||||
|
# hectic.generic.matrix-cluster.overrideEnableSynapse = false;
|
||||||
|
# hectic.generic.matrix-cluster.replication.peerHost = "91.198.166.181";
|
||||||
|
# hectic.generic.matrix-cluster.replication.allowedSourceIPs = [ "128.140.75.58/32" ];
|
||||||
|
# (You will also need a matrix/secrets entry on PL - copy from NL via SOPS.)
|
||||||
|
sudo nixos-rebuild switch --flake .#'bfs.poland.xray|x86_64-linux'
|
||||||
|
sudo nixos-rebuild switch --flake .#'hectic-lab|x86_64-linux'
|
||||||
|
sudo systemctl status matrix-synapse
|
||||||
|
|
||||||
|
# 4. Swap DNS A record at Porkbun:
|
||||||
|
# accord.tube A 91.198.166.181 (was 128.140.75.58)
|
||||||
|
# TTL: set to 300 in advance of any planned failover.
|
||||||
|
# Porkbun UI: https://porkbun.com/account/domainsSpeedy -> accord.tube -> DNS -> edit A record.
|
||||||
|
# Or via API:
|
||||||
|
sudo curl -sX POST https://api.porkbun.com/api/json/v3/dns/editByNameType/accord.tube/A \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d "$(jq -n --arg k "$PORKBUN_API_KEY" --arg s "$PORKBUN_SECRET_API_KEY" \
|
||||||
|
'{secretapikey:$s,apikey:$k,content:"91.198.166.181",ttl:"300"}')"
|
||||||
|
|
||||||
|
# 5. Federation smoke test.
|
||||||
|
curl -s 'https://federationtester.matrix.org/api/report?server_name=accord.tube' | jq .FederationOK
|
||||||
|
# Expected: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected after the rebuilds:
|
||||||
|
|
||||||
|
- `bfs.poland.xray` evaluates and runs as `role = "primary"`.
|
||||||
|
- `hectic-lab` evaluates as `role = "standby"` with Synapse forced off.
|
||||||
|
- Future `nixos-rebuild` runs preserve the promoted topology instead of reapplying standby settings to PL.
|
||||||
|
|
||||||
|
## Failback (PL -> NL)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Stop NL postgres if still up; clear its data dir.
|
||||||
|
sudo systemctl stop postgresql matrix-synapse
|
||||||
|
sudo rm -rf /var/lib/postgresql/17
|
||||||
|
|
||||||
|
# 2. Re-seed NL from PL (now the live primary).
|
||||||
|
sudo -u postgres install -d -m 0700 /var/lib/postgresql/17
|
||||||
|
sudo -u postgres PGPASSWORD="$(sudo cat /run/secrets/matrix/postgres-replication-password)" \
|
||||||
|
pg_basebackup -h 91.198.166.181 -p 5432 -U replication \
|
||||||
|
-D /var/lib/postgresql/17 -Fp -Xs -P -R --no-password
|
||||||
|
|
||||||
|
# 3. Temporarily flip roles in the flake:
|
||||||
|
# - hectic-lab.nix: role = "standby"; peerHost = "91.198.166.181";
|
||||||
|
# - bfs.poland.xray.nix: role = "primary"; peerHost = "128.140.75.58";
|
||||||
|
# Rebuild both.
|
||||||
|
|
||||||
|
# 4. Once NL is streaming green, do the reverse failover dance:
|
||||||
|
sudo systemctl stop matrix-synapse # on PL
|
||||||
|
sudo -u postgres pg_ctl -D /var/lib/postgresql/17 promote # on NL
|
||||||
|
# Then revert the flake role assignments back to NL=primary / PL=standby and
|
||||||
|
# rebuild both hosts.
|
||||||
|
|
||||||
|
# 5. Swap DNS back at Porkbun (A -> 128.140.75.58).
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disaster recovery (NL permanently lost)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Promote PL as the new permanent primary.
|
||||||
|
sudo -u postgres pg_ctl -D /var/lib/postgresql/17 promote
|
||||||
|
|
||||||
|
# 2. Edit nixos/system/bfs.poland.xray/bfs.poland.xray.nix:
|
||||||
|
# hectic.generic.matrix-cluster.role = "primary";
|
||||||
|
# hectic.generic.matrix-cluster.overrideEnableSynapse = lib.mkForce null;
|
||||||
|
# hectic.generic.matrix-cluster.replication.peerHost = "<new-standby-ip>";
|
||||||
|
# hectic.generic.matrix-cluster.replication.allowedSourceIPs = [ "<new-standby-ip>/32" ];
|
||||||
|
|
||||||
|
# 3. Provision a new host (any region with Porkbun-managed DNS) and import
|
||||||
|
# self.nixosModules.matrix-cluster with role = "standby" pointed at PL's IP.
|
||||||
|
|
||||||
|
# 4. Bootstrap the new standby via pg_basebackup from PL exactly as in
|
||||||
|
# "Initial setup" step 3, replacing 128.140.75.58 with PL's IP.
|
||||||
|
|
||||||
|
# 5. Update Porkbun A record to PL's IP permanently.
|
||||||
|
```
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
matrixBackend = "https://128.140.75.58";
|
||||||
|
matrixHost = "accord.tube";
|
||||||
|
jitsiHost = "meet.accord.tube";
|
||||||
|
elementEntryDomain = "element.bfs.band";
|
||||||
|
polandEntryDomain = "bfs.band";
|
||||||
|
backendProxyConfig = ''
|
||||||
|
proxy_ssl_server_name on;
|
||||||
|
proxy_ssl_name ${matrixHost};
|
||||||
|
proxy_set_header Host ${matrixHost};
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.xray-system
|
||||||
|
self.nixosModules.matrix-cluster
|
||||||
|
self.nixosModules.matrix-cluster-users
|
||||||
|
];
|
||||||
|
|
||||||
|
hectic.generic.xray-system = {
|
||||||
|
enable = true;
|
||||||
|
defaultSopsFile = ../../../sus/bfs.xray.yaml;
|
||||||
|
};
|
||||||
|
|
||||||
|
hectic.generic.matrix-cluster = {
|
||||||
|
enable = true;
|
||||||
|
role = "primary";
|
||||||
|
matrixDomain = "accord.tube";
|
||||||
|
signingKeyFile = config.sops.secrets."matrix/signing-key".path;
|
||||||
|
secretsFile = config.sops.secrets."matrix/secrets".path;
|
||||||
|
turnSecretFile = config.sops.secrets."matrix/turn-secret".path;
|
||||||
|
publicIp = "91.198.166.181";
|
||||||
|
objectStorage.s3 = {
|
||||||
|
bucket = "matrix-hectic-lab";
|
||||||
|
regionName = "hel1";
|
||||||
|
endpointUrl = "https://hel1.your-objectstorage.com";
|
||||||
|
credentialsFile = config.sops.secrets."matrix/object-storage/credentials".path;
|
||||||
|
};
|
||||||
|
replication = {
|
||||||
|
peerHost = "128.140.75.58";
|
||||||
|
passwordFile = config.sops.secrets."matrix/postgres-replication-password".path;
|
||||||
|
};
|
||||||
|
acme = {
|
||||||
|
enable = false;
|
||||||
|
porkbunApiKeyFile = config.sops.secrets."matrix/porkbun-api-key".path;
|
||||||
|
porkbunSecretApiKeyFile = config.sops.secrets."matrix/porkbun-secret-api-key".path;
|
||||||
|
};
|
||||||
|
jitsi.preferredDomain = jitsiHost;
|
||||||
|
};
|
||||||
|
|
||||||
|
hectic.services.media-browser = {
|
||||||
|
enable = true;
|
||||||
|
port = 3001;
|
||||||
|
s3Bucket = "matrix-hectic-lab";
|
||||||
|
s3Endpoint = "https://hel1.your-objectstorage.com";
|
||||||
|
s3Region = "hel1";
|
||||||
|
s3CredentialsFile = config.sops.secrets."matrix/object-storage/credentials".path;
|
||||||
|
};
|
||||||
|
|
||||||
|
hectic.services.jitsi = {
|
||||||
|
enable = true;
|
||||||
|
hostName = jitsiHost;
|
||||||
|
};
|
||||||
|
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = "security@bfs.band";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
virtualHosts.${polandEntryDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
locations."/".return = "302 https://${elementEntryDomain}";
|
||||||
|
|
||||||
|
locations."=/.well-known/matrix/client" = {
|
||||||
|
extraConfig = ''
|
||||||
|
default_type application/json;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
add_header Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization";
|
||||||
|
'';
|
||||||
|
return = ''200 '{
|
||||||
|
"m.homeserver": {
|
||||||
|
"base_url": "https://${polandEntryDomain}"
|
||||||
|
},
|
||||||
|
"m.identity_server": {
|
||||||
|
"base_url": "https://vector.im"
|
||||||
|
},
|
||||||
|
"org.matrix.msc3575.proxy": {
|
||||||
|
"url": "https://${polandEntryDomain}"
|
||||||
|
},
|
||||||
|
"org.matrix.msc4143.rtc_foci": [
|
||||||
|
{
|
||||||
|
"type": "livekit",
|
||||||
|
"livekit_service_url": "https://${polandEntryDomain}/livekit/jwt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}' '';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."= /livekit/jwt" = {
|
||||||
|
proxyPass = "${matrixBackend}/livekit/jwt";
|
||||||
|
extraConfig = backendProxyConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /livekit/jwt/" = {
|
||||||
|
proxyPass = "${matrixBackend}/livekit/jwt/";
|
||||||
|
extraConfig = backendProxyConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."= /livekit/sfu" = {
|
||||||
|
proxyPass = "${matrixBackend}/livekit/sfu";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = backendProxyConfig + ''
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 120;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header Accept-Encoding gzip;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /livekit/sfu/" = {
|
||||||
|
proxyPass = "${matrixBackend}/livekit/sfu/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = backendProxyConfig + ''
|
||||||
|
proxy_send_timeout 120;
|
||||||
|
proxy_read_timeout 120;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_set_header Accept-Encoding gzip;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /_matrix/" = {
|
||||||
|
proxyPass = "${matrixBackend}/_matrix/";
|
||||||
|
extraConfig = backendProxyConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."^~ /_synapse/client/" = {
|
||||||
|
proxyPass = "${matrixBackend}/_synapse/client/";
|
||||||
|
extraConfig = backendProxyConfig;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualHosts.${elementEntryDomain} = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
|
||||||
|
locations."= /config.${elementEntryDomain}.json".return = "302 /config.json";
|
||||||
|
|
||||||
|
root = pkgs.element-web.override {
|
||||||
|
conf = {
|
||||||
|
default_server_config = {
|
||||||
|
"m.homeserver".base_url = "https://${polandEntryDomain}";
|
||||||
|
"m.homeserver".server_name = matrixHost;
|
||||||
|
"m.identity_server".base_url = "https://vector.im";
|
||||||
|
};
|
||||||
|
|
||||||
|
jitsi = {
|
||||||
|
preferred_domain = jitsiHost;
|
||||||
|
};
|
||||||
|
|
||||||
|
room_directory.servers = [ matrixHost ];
|
||||||
|
|
||||||
|
default_theme = "dark";
|
||||||
|
show_labs_settings = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."matrix/signing-key" = {
|
||||||
|
key = "matrix/signing-key";
|
||||||
|
owner = "matrix-synapse";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/postgres-replication-password" = {
|
||||||
|
key = "matrix/postgres-replication-password";
|
||||||
|
owner = "postgres";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/object-storage/credentials" = {
|
||||||
|
key = "matrix/object-storage/credentials";
|
||||||
|
owner = "matrix-synapse";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/secrets" = {
|
||||||
|
key = "matrix/secrets";
|
||||||
|
owner = "matrix-synapse";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/turn-secret" = {
|
||||||
|
key = "matrix/turn-secret";
|
||||||
|
owner = "turnserver";
|
||||||
|
group = "turnserver";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/porkbun-api-key" = {
|
||||||
|
key = "matrix/porkbun-api-key";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
sops.secrets."matrix/porkbun-secret-api-key" = {
|
||||||
|
key = "matrix/porkbun-secret-api-key";
|
||||||
|
mode = "0400";
|
||||||
|
sopsFile = "${flake}/sus/matrix-cluster.yaml";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system; sanitize for hostName (no dots)
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
hostName = builtins.replaceStrings ["."] ["-"] name;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
||||||
|
config.permittedInsecurePackages = [
|
||||||
|
"jitsi-meet-1.0.8792"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = hostName; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = name; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self
|
||||||
|
}: {
|
||||||
|
modulesPath,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.hectic
|
||||||
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
hardware.hetzner-cloud.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrbBG+U07f7OKvOxYIGYCaNvyozzxQF+I9Fb5TYZErK yukkop vm-postgres''
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
services.postgresql =
|
||||||
|
let
|
||||||
|
package = pkgs.postgresql_15;
|
||||||
|
in {
|
||||||
|
enable = true;
|
||||||
|
package = package;
|
||||||
|
settings =
|
||||||
|
{
|
||||||
|
port = 64317;
|
||||||
|
listen_addresses = lib.mkForce "*";
|
||||||
|
shared_preload_libraries = "";
|
||||||
|
};
|
||||||
|
extensions = [ package.pkgs.hemar ];
|
||||||
|
authentication = builtins.concatStringsSep "\n" [
|
||||||
|
"local all all trust"
|
||||||
|
"host sameuser all 127.0.0.1/32 scram-sha-256"
|
||||||
|
"host sameuser all ::1/128 scram-sha-256"
|
||||||
|
];
|
||||||
|
initialScript = pkgs.writeText "init-sql-script" ''
|
||||||
|
SET log_min_messages TO DEBUG1;
|
||||||
|
SET client_min_messages TO DEBUG1;
|
||||||
|
ALTER DATABASE postgres SET log_min_messages TO DEBUG1;
|
||||||
|
ALTER DATABASE postgres SET client_min_messages TO DEBUG1;
|
||||||
|
CREATE EXTENSION "hemar";
|
||||||
|
|
||||||
|
\i ${flake}/package/c/hemar/test/mod.sql
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
gdb
|
||||||
|
hectic.nvim-pager
|
||||||
|
#(writeScriptBin "check" ''
|
||||||
|
# journalctl -u postgresql.service | grep postgresql-post-start | sed 's|psql:/nix/store/[^:]*:[0-9]*: ||' | sed 's|^[^:]*:[^:]*:[^:]*: ||' | grep -v '^\[.*\]' | ${hectic.prettify-log}/bin/prettify-log --color-output
|
||||||
|
#'')
|
||||||
|
];
|
||||||
|
programs.zsh.shellAliases = self.lib.sharedShellAliasesForDevVm // {
|
||||||
|
conn = "sudo su postgres -c 'psql -p 64317'";
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualisation = {
|
||||||
|
vmVariant = {
|
||||||
|
systemd.services.fix-root-perms = {
|
||||||
|
description = "Fix root directory permissions";
|
||||||
|
after = [ "local-fs.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.coreutils}/bin/chmod 755 /";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualisation = {
|
||||||
|
diskSize = 1024*6;
|
||||||
|
diskImage = null;
|
||||||
|
forwardPorts = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = name; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.hectic
|
||||||
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
hardware.hetzner-cloud.enable = true;
|
||||||
|
hardware.lenovo-ideapad-15arh7.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs.hectic.writers; [
|
||||||
|
(writeMinCBin "minc-hello-world" ["<stdio.h>"] /*c*/ ''
|
||||||
|
printf("hello world\n");
|
||||||
|
'')
|
||||||
|
(writeMinCBin "minc-env" ["<stdio.h>" "<stdlib.h>"] /*c*/ ''
|
||||||
|
char *env_name;
|
||||||
|
if (argc > 1) {
|
||||||
|
env_name = argv[1];
|
||||||
|
} else {
|
||||||
|
env_name = "HOME";
|
||||||
|
}
|
||||||
|
char *value = getenv(env_name);
|
||||||
|
if (value) {
|
||||||
|
printf("%s: %s\n", env_name, value);
|
||||||
|
} else {
|
||||||
|
printf("Environment variable %s not found.\n", env_name);
|
||||||
|
}
|
||||||
|
'')
|
||||||
|
(writeMinCBin "minc-env-check" ["<stdio.h>" "<stdlib.h>"] /*c*/ ''
|
||||||
|
char *env_name;
|
||||||
|
if (argc > 1) {
|
||||||
|
env_name = argv[1];
|
||||||
|
} else {
|
||||||
|
env_name = "HOME";
|
||||||
|
}
|
||||||
|
|
||||||
|
char *value = getenv(env_name);
|
||||||
|
if (value) {
|
||||||
|
char buffer[128];
|
||||||
|
sprintf(buffer, "echo $%s\n", env_name);
|
||||||
|
system(buffer);
|
||||||
|
} else {
|
||||||
|
printf("Environment variable %s not found.\n", env_name);
|
||||||
|
}
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrbBG+U07f7OKvOxYIGYCaNvyozzxQF+I9Fb5TYZErK yukkop vm-postgres''
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.zsh.shellAliases = self.lib.sharedShellAliasesForDevVm;
|
||||||
|
|
||||||
|
virtualisation = {
|
||||||
|
vmVariant = {
|
||||||
|
systemd.services.fix-root-perms = {
|
||||||
|
description = "Fix root directory permissions";
|
||||||
|
after = [ "local-fs.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${pkgs.coreutils}/bin/chmod 755 /";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualisation = {
|
||||||
|
diskSize = 1024*6;
|
||||||
|
diskImage = null;
|
||||||
|
forwardPorts = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{ pkgs, ... }: let
|
||||||
|
astroneerServer = pkgs.hectic.helpers.steam.buildSteamServer 728470;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
systemd.services."hectic-astroneer-server" = {
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
path = with pkgs; [ steamcmd ];
|
||||||
|
script = ''
|
||||||
|
echo zalupa
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
config.allowUnfreePredicate = pkg:
|
||||||
|
builtins.elem (inputs.nixpkgs.lib.getName pkg) [ "steamcmd" "steam-unwrapped" ];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = name; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
inputs ? null,
|
||||||
|
flake ? null,
|
||||||
|
self ? null,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config ? null,
|
||||||
|
pkgs ? null,
|
||||||
|
lib ? null,
|
||||||
|
modulesPath ? null,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/profiles/qemu-guest.nix")
|
||||||
|
self.nixosModules.hectic
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
./astroneer.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
hardware.hetzner-cloud = {
|
||||||
|
enable = true;
|
||||||
|
networkMatchConfigName = "enp1s0";
|
||||||
|
ipv4 = "91.98.127.6";
|
||||||
|
ipv6 = "2a01:4f8:1c1b:6f10";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
gnupg.sshKeyPaths = [ ];
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
defaultSopsFile = ../../../sus/games.yaml;
|
||||||
|
|
||||||
|
secrets."env" = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.systemPackages = (with pkgs; [ rsync git steamcmd hectic.AstroTuxLauncher ]);
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
''ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKAaObjLBslsdTlqEcYaS1TqX4x9aVJu75y27/8MFevO''
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
inputs ? null,
|
||||||
|
flake ? null,
|
||||||
|
self ? null,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config ? null,
|
||||||
|
pkgs ? null,
|
||||||
|
lib ? null,
|
||||||
|
modulesPath ? null,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with builtins;
|
||||||
|
with lib;
|
||||||
|
# with inputs.dream.lib;
|
||||||
|
let
|
||||||
|
in {
|
||||||
|
|
||||||
|
# networking.nat = {
|
||||||
|
# enable = true;
|
||||||
|
# internalInterfaces = [ "ve-+" ];
|
||||||
|
# externalInterface = "lo";
|
||||||
|
# # Lazy IPv6 connectivity for the container
|
||||||
|
# enableIPv6 = true;
|
||||||
|
# };
|
||||||
|
|
||||||
|
# containers.webserver = {
|
||||||
|
# autoStart = true;
|
||||||
|
# privateNetwork = true;
|
||||||
|
# hostAddress = "192.168.115.10";
|
||||||
|
# localAddress = "192.168.115.11";
|
||||||
|
# hostAddress6 = "fc00::1";
|
||||||
|
# localAddress6 = "fc00::2";
|
||||||
|
# config = import "${inputs.quteproxy}/nixos/system/quteproxy-staging/quteproxy-staging.nix" {
|
||||||
|
# self = inputs.quteproxy;
|
||||||
|
# inputs = inputs.quteproxy.inputs;
|
||||||
|
# flake = inputs.quteproxy;
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
|
||||||
|
# environment.etc.nixos.source = self;
|
||||||
|
# boot.kernelModules = [ "kvm" ];
|
||||||
|
|
||||||
|
# microvm.autostart = [
|
||||||
|
# "myvm1"
|
||||||
|
# ];
|
||||||
|
# microvm.vms = {
|
||||||
|
# myvm1 = {
|
||||||
|
# flake = self;
|
||||||
|
# updateFlake = "git+file:///etc/nixos";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# microvm = {
|
||||||
|
# mem = 1024*3;
|
||||||
|
# vcpu = 4;
|
||||||
|
# storeOnDisk = false;
|
||||||
|
# shares = [
|
||||||
|
# {
|
||||||
|
# proto = "9p";
|
||||||
|
# # securityModel = "mapped";
|
||||||
|
# tag = "ro-store";
|
||||||
|
# source = "/nix/store";
|
||||||
|
# mountPoint = "/nix/.ro-store";
|
||||||
|
# }
|
||||||
|
# {
|
||||||
|
# proto = "9p";
|
||||||
|
# securityModel = "mapped";
|
||||||
|
# tag = "fsRoot";
|
||||||
|
# source = "/media/pool/mythos/vm/work/vproxy/pr";
|
||||||
|
# mountPoint = "/home/devbox-user/pr";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# interfaces = [
|
||||||
|
# {
|
||||||
|
# type = "user";
|
||||||
|
#
|
||||||
|
# # interface name on the host
|
||||||
|
# id = "vm-seht";
|
||||||
|
#
|
||||||
|
# # Ethernet address of the MicroVM's interface, not the host's
|
||||||
|
# # Locally administered have one of 2/6/A/E in the second nibble.
|
||||||
|
# mac = "02:00:00:00:00:01";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# forwardPorts = [
|
||||||
|
# { from = "host"; host.port = 40500; guest.port = 22; }
|
||||||
|
# ];
|
||||||
|
#
|
||||||
|
# writableStoreOverlay = "/nix/.rw-store";
|
||||||
|
# volumes = [
|
||||||
|
# {
|
||||||
|
# autoCreate = true;
|
||||||
|
# size = 1024*32;
|
||||||
|
#
|
||||||
|
# image = "/media/pool/mythos/vm/work/vproxy/nix-store-overlay.img";
|
||||||
|
# mountPoint = config.microvm.writableStoreOverlay;
|
||||||
|
# }
|
||||||
|
# {
|
||||||
|
# autoCreate = true;
|
||||||
|
# size = 1024*32;
|
||||||
|
#
|
||||||
|
# image = "/media/pool/mythos/vm/work/vproxy/root.img";
|
||||||
|
# mountPoint = "/";
|
||||||
|
# }
|
||||||
|
# ];
|
||||||
|
# };
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system ? "x86_64-linux",
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [ self.overlays.default ];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = name; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with builtins;
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
domain = "hectic-lab.com";
|
||||||
|
matrixDomain = "accord.tube";
|
||||||
|
sslOpts = {
|
||||||
|
sslCertificate = config.sops.secrets."ssl/porkbun/${domain}/domain.cert.pem".path;
|
||||||
|
sslCertificateKey = config.sops.secrets."ssl/porkbun/${domain}/private.key.pem".path;
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.hectic
|
||||||
|
self.nixosModules.matrix-cluster
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
|
||||||
|
self.nixosModules."shadowsocks-rust" # NOTE(nrv): impl
|
||||||
|
self.nixosModules."shadowsocks" # NOTE(nrv): usage/instance
|
||||||
|
|
||||||
|
inputs.hectic-landing.nixosModules.hectic-landing
|
||||||
|
|
||||||
|
(import ./containers.nix { inherit flake self inputs; })
|
||||||
|
(import ./mechabellum.nix { inherit flake self inputs domain sslOpts; })
|
||||||
|
(import (./. + "/sentinèlla.nix") { inherit flake self inputs domain sslOpts; })
|
||||||
|
];
|
||||||
|
|
||||||
|
services.hectic-landing = {
|
||||||
|
enable = true;
|
||||||
|
package = inputs.hectic-landing.packages.${pkgs.stdenv.hostPlatform.system}.hectic-landing;
|
||||||
|
domain = domain;
|
||||||
|
port = 3000;
|
||||||
|
host = "127.0.0.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
# NOTE(yukkop): both nixos-mailserver and hectic-landing module set
|
||||||
|
# security.acme.defaults.email. Force the mailserver-aligned address.
|
||||||
|
security.acme.defaults.email = lib.mkForce "security@${domain}";
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
hardware.hetzner-cloud = {
|
||||||
|
enable = true;
|
||||||
|
networkMatchConfigName = "enp1s0";
|
||||||
|
ipv4 = "128.140.75.58";
|
||||||
|
ipv6 = "2a01:4f8:c2c:d54a";
|
||||||
|
};
|
||||||
|
services.matrix = {
|
||||||
|
enable = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# NOTE(yukkop): disk was provisioned by Hetzner rescue image, disko was never
|
||||||
|
# run, so partition labels don't exist. Override fileSystems with actual UUIDs.
|
||||||
|
fileSystems."/" = lib.mkForce {
|
||||||
|
device = "/dev/disk/by-uuid/48ba7286-d019-4cdc-9784-459767979b07";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = lib.mkForce {
|
||||||
|
device = "/dev/disk/by-uuid/71F2-4E98";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [ "umask=0077" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.zsh.enable = true;
|
||||||
|
programs.zsh.interactiveShellInit = ''
|
||||||
|
setopt vi
|
||||||
|
'';
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
git
|
||||||
|
rsync
|
||||||
|
python311
|
||||||
|
kitty
|
||||||
|
];
|
||||||
|
|
||||||
|
# Secrets config
|
||||||
|
sops = {
|
||||||
|
gnupg.sshKeyPaths = [ ];
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
defaultSopsFile = "${flake}/sus/hectic-lab.yaml";
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
# yukkop
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMuP5NSfEQmO6m77xBWZvZ3hk7cw1q2k2vbsFd37rybU u0_a327@localhost"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBLxMo5icX2Xyng7mcWGnIi+c4ZbVygjPhuU8noCkfZ"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxgLlX/15Fk7PgIc9FSrA7oRtA8qK4GXfOhj7ZlNUaJ nix-on-droid@localhost"
|
||||||
|
# snuff
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFouceNUxI3bGC24/hfA8J3VuBpvTcZh3KhixgrMiLte"
|
||||||
|
# nrv
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/EhBI6sJb2yHbTkqhZiCzUrsLE6t+CZe7RhS22z7w5 nrv@adamantia"
|
||||||
|
# github workflow
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKPEUArBxu7NUULT7Pi8ArtVxY1uVbIBSaeRKtqz1sz1"
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.ds4d = { # NOTE(nrv): artishoque
|
||||||
|
isNormalUser = true;
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINcjBc57N6MxtMYAHEB/nwZ+OGsG3P1KWO1ZXvzQyhKn ds4d@ds4d"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
users.users.sshuttle = {
|
||||||
|
isNormalUser = true;
|
||||||
|
openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKd4iU2E5fiwPwBbeo1ZPo0YBFEj9qBPew/KitaO+OHU"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
sops.secrets."mailserver/security/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/yukkop/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/daniil-perlyk/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/snuff/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/antoshka/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/founders/hashedPassword" = {};
|
||||||
|
sops.secrets."mailserver/lvgkcfjl/hashedPassword" = {};
|
||||||
|
sops.secrets."init-postgresql" = {
|
||||||
|
key = "init-postgresql";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.mailserver = {
|
||||||
|
enable = true;
|
||||||
|
domain = domain;
|
||||||
|
loginAccounts = {
|
||||||
|
"security" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/security/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"founders" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/founders/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"lvgkcfjl" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/lvgkcfjl/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"yukkop" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/yukkop/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"daniil-perlyk" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/daniil-perlyk/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"snuff" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/snuff/hashedPassword".path;
|
||||||
|
};
|
||||||
|
"antoshka" = {
|
||||||
|
hashedPasswordFile = config.sops.secrets."mailserver/antoshka/hashedPassword".path;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mailserver.stateVersion = 3;
|
||||||
|
|
||||||
|
services.redis.servers."vproxy-bot-test-state" = {
|
||||||
|
enable = true;
|
||||||
|
port = 6379;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.mysql = {
|
||||||
|
enable = true;
|
||||||
|
package = pkgs.mariadb;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking.firewall = {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
3306 # mysql
|
||||||
|
11012 # gitea ssh
|
||||||
|
25565
|
||||||
|
55228 # ss-bfs
|
||||||
|
];
|
||||||
|
allowedUDPPorts = [
|
||||||
|
51820 # wg-bfs
|
||||||
|
55228 # ss-bfs
|
||||||
|
];
|
||||||
|
# Postgres replication: only the PL standby peer may reach 5432.
|
||||||
|
extraInputRules = ''
|
||||||
|
ip saddr 91.198.166.181/32 tcp dport 5432 accept
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
virtualisation.docker.enable = true;
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /var/www/store 0755 nginx nginx -"
|
||||||
|
];
|
||||||
|
|
||||||
|
sops.secrets."ssl/porkbun/${domain}/domain.cert.pem" = { group = "nginx"; mode = "0440"; };
|
||||||
|
sops.secrets."ssl/porkbun/${domain}/private.key.pem" = { group = "nginx"; mode = "0440"; };
|
||||||
|
sops.secrets."ssl/porkbun/${domain}/public.key.pem" = { group = "nginx"; mode = "0440"; };
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
# NOTE(yukkop): virtualHosts.${domain} is owned by the hectic-landing module
|
||||||
|
# (ACME-managed). See services.hectic-landing above.
|
||||||
|
virtualHosts."store.${domain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
root = "/var/www/store";
|
||||||
|
locations."/" = {
|
||||||
|
extraConfig = ''
|
||||||
|
autoindex on;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualHosts."snuff.${domain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_pass http://188.32.215.29:3993/;
|
||||||
|
proxy_redirect off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualHosts."nrv.${domain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_pass http://127.0.0.1:22842/;
|
||||||
|
proxy_redirect off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualHosts."yukkop.${domain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_pass http://127.0.0.1:9855/;
|
||||||
|
proxy_redirect off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
virtualHosts."gitea.${domain}" = {
|
||||||
|
enableACME = true;
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_pass http://127.0.0.1:11011/;
|
||||||
|
proxy_redirect off;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
gitea = {
|
||||||
|
enable = true;
|
||||||
|
settings.service.DISABLE_REGISTRATION = false;
|
||||||
|
settings.server = {
|
||||||
|
HTTP_PORT = 11011;
|
||||||
|
SSH_PORT = 11012;
|
||||||
|
};
|
||||||
|
database = {
|
||||||
|
createDatabase = true;
|
||||||
|
type = "postgres";
|
||||||
|
socket = "/run/postgresql";
|
||||||
|
user = "gitea";
|
||||||
|
name = "gitea";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# === WireGuard (disabled) ===
|
||||||
|
|
||||||
|
sops.secrets."wg-bfs/private-key" = {};
|
||||||
|
|
||||||
|
# networking.wireguard.interfaces = let
|
||||||
|
# subnet = "10.13.37";
|
||||||
|
# externalInterface = "eth0";
|
||||||
|
# in {
|
||||||
|
# wg-bfs = {
|
||||||
|
# ips = [ "${subnet}.1/24" ];
|
||||||
|
# listenPort = 51820;
|
||||||
|
# postSetup = ''
|
||||||
|
# ${pkgs.iptables}/bin/iptables -t 'nat' -A 'POSTROUTING' -s '${subnet}.0/24' -o '${externalInterface}' -j 'MASQUERADE'
|
||||||
|
# '';
|
||||||
|
# postShutdown = ''
|
||||||
|
# ${pkgs.iptables}/bin/iptables -t 'nat' -D 'POSTROUTING' -s '${subnet}.0/24' -o '${externalInterface}' -j 'MASQUERADE'
|
||||||
|
# '';
|
||||||
|
# privateKeyFile = config.sops.secrets."wg-bfs/private-key".path;
|
||||||
|
# generatePrivateKeyFile = false;
|
||||||
|
# peers = with lib; with builtins; let
|
||||||
|
# pubkeys = [
|
||||||
|
# "3dVzf1jxnVVTkLAyxedW+kRQBexZDzYDwpaLIcTrLjc=" # nrv (host: 2)
|
||||||
|
# "Kk2d0ncj24rO0qbuKh4V4t1OLnmVYbeaYvuEnL2OPFM=" # lysmi (host: 3)
|
||||||
|
# "BkM/NEDbR/XQ6WYQ0Yt+nJrc2HFCVsoW4QxBmkqxHn8=" # yukkop (host: 4)
|
||||||
|
# ];
|
||||||
|
# hosts = lists.range 2 254;
|
||||||
|
# zipped = zipLists pubkeys hosts;
|
||||||
|
# in flip map zipped ({ fst, snd }: {
|
||||||
|
# publicKey = "${fst}";
|
||||||
|
# allowedIPs = [ "${subnet}.${toString snd}/32" ];
|
||||||
|
# });
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
domain,
|
||||||
|
sslOpts,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
mechDomain = "mechabellum.${domain}";
|
||||||
|
apiHost = "127.0.0.1";
|
||||||
|
apiPort = 8010;
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
inputs.mechabellum-replay-analysis.nixosModules.default
|
||||||
|
];
|
||||||
|
|
||||||
|
mechabellum.api = {
|
||||||
|
enable = true;
|
||||||
|
host = apiHost;
|
||||||
|
port = apiPort;
|
||||||
|
extraEnvironment = {
|
||||||
|
CORS_ALLOWED_ORIGINS = "https://${mechDomain}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mechabellum.worker = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx.virtualHosts."${mechDomain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
root = inputs.mechabellum-replay-analysis.packages.${system}.frontend;
|
||||||
|
|
||||||
|
locations."/api" = {
|
||||||
|
proxyPass = "http://${apiHost}:${builtins.toString apiPort}";
|
||||||
|
extraConfig = ''
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
locations."/" = {
|
||||||
|
tryFiles = "$uri $uri/ /index.html";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
domain,
|
||||||
|
sslOpts,
|
||||||
|
...
|
||||||
|
}: { ... }: {
|
||||||
|
hectic.services."sentinèlla" = {
|
||||||
|
probe.enable = true;
|
||||||
|
watcher = {
|
||||||
|
enable = true;
|
||||||
|
pollingIntervalSec = 60;
|
||||||
|
# TG_TOKEN= and TG_CHAT_ID= are read from sus/sentinella-default.yaml
|
||||||
|
# (auto-declared by the module as sops.secrets."sentinèlla/watcher/environment")
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
virtualHosts."probe.${domain}" = sslOpts // {
|
||||||
|
forceSSL = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:5988";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Counter App</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.tab-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.tab-button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
background: #e5e7eb;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.tab-button.active {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.counter-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.counter-button {
|
||||||
|
padding: 15px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.counter-button:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.control-button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.control-button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="controls">
|
||||||
|
<button class="control-button" onclick="resetCounters()">Reset</button>
|
||||||
|
<button class="control-button" onclick="saveCounters()">Save</button>
|
||||||
|
<input type="file" id="loadFile" style="display: none" onchange="loadCounters(event)">
|
||||||
|
<button class="control-button" onclick="document.getElementById('loadFile').click()">Load</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-buttons">
|
||||||
|
<button class="tab-button active" onclick="showTab('counters')">Counters</button>
|
||||||
|
<button class="tab-button" onclick="showTab('chart')">Chart</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="counters" class="tab-content active">
|
||||||
|
<div class="counter-grid" id="counterGrid"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="chart" class="tab-content">
|
||||||
|
<canvas id="counterChart"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let counters = Array(20).fill(0);
|
||||||
|
let chart = null;
|
||||||
|
|
||||||
|
function initializeCounters() {
|
||||||
|
const grid = document.getElementById('counterGrid');
|
||||||
|
grid.innerHTML = '';
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'counter-button';
|
||||||
|
button.innerHTML = `Button ${i + 1}<br>Count: ${counters[i]}`;
|
||||||
|
button.onclick = () => incrementCounter(i);
|
||||||
|
grid.appendChild(button);
|
||||||
|
}
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementCounter(index) {
|
||||||
|
counters[index]++;
|
||||||
|
updateCounterDisplay();
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCounterDisplay() {
|
||||||
|
const buttons = document.querySelectorAll('.counter-button');
|
||||||
|
buttons.forEach((button, i) => {
|
||||||
|
button.innerHTML = `Button ${i + 1}<br>Count: ${counters[i]}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChart() {
|
||||||
|
if (chart) {
|
||||||
|
chart.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = document.getElementById('counterChart').getContext('2d');
|
||||||
|
chart = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: Array.from({length: 20}, (_, i) => `Button ${i + 1}`),
|
||||||
|
datasets: [{
|
||||||
|
label: 'Click Count',
|
||||||
|
data: counters,
|
||||||
|
backgroundColor: '#3b82f6'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
stepSize: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(tabId) {
|
||||||
|
document.querySelectorAll('.tab-content').forEach(tab => {
|
||||||
|
tab.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.tab-button').forEach(button => {
|
||||||
|
button.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.getElementById(tabId).classList.add('active');
|
||||||
|
document.querySelector(`[onclick="showTab('${tabId}')"]`).classList.add('active');
|
||||||
|
if (tabId === 'chart') {
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCounters() {
|
||||||
|
counters = Array(20).fill(0);
|
||||||
|
updateCounterDisplay();
|
||||||
|
updateChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCounters() {
|
||||||
|
const data = JSON.stringify(counters);
|
||||||
|
const blob = new Blob([data], {type: 'application/json'});
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'counters.json';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCounters(event) {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = function(e) {
|
||||||
|
try {
|
||||||
|
counters = JSON.parse(e.target.result);
|
||||||
|
updateCounterDisplay();
|
||||||
|
updateChart();
|
||||||
|
} catch (error) {
|
||||||
|
alert('Invalid file format');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeCounters();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>tg test</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="style.css">
|
||||||
|
<script src="https://telegram.org/js/telegram-web-app.js?56"></script>
|
||||||
|
<script src="test.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<p>TEST (again)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
function webappInit() {
|
||||||
|
console.log("Init start");
|
||||||
|
window.Telegram.WebApp.BackButton.isVisible = true;
|
||||||
|
window.Telegram.WebApp.backgroundColor = "#E60C0C";
|
||||||
|
let initData = window.Telegram.WebApp.initData;
|
||||||
|
if (initData) {
|
||||||
|
console.log("InitData", initData);
|
||||||
|
validate(initData);
|
||||||
|
}
|
||||||
|
console.log("Init end");
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(initData) {
|
||||||
|
const urlencodedData = initData;
|
||||||
|
|
||||||
|
const decodedData = decodeURIComponent(urlencodedData);
|
||||||
|
|
||||||
|
fetch(
|
||||||
|
"http://localhost:52022/rpc/webapp_auth",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Content-Profile": "qutegate",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ raw_init_data: btoa(decodedData) }),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForWebApp() {
|
||||||
|
if (window.Telegram && window.Telegram.WebApp) {
|
||||||
|
console.log("Telegram WebApp is available");
|
||||||
|
webappInit();
|
||||||
|
} else {
|
||||||
|
console.log("Telegram WebApp is not available yet");
|
||||||
|
setTimeout(waitForWebApp, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForWebApp();
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Router Access (TP-Link)
|
||||||
|
|
||||||
|
The server `neuro` is behind a NAT router at `192.168.0.1`.
|
||||||
|
|
||||||
|
## Network Info
|
||||||
|
|
||||||
|
| Device | IP |
|
||||||
|
|--------|-----|
|
||||||
|
| Router (TP-Link) | 192.168.0.1 |
|
||||||
|
| neuro (internal) | 192.168.0.10 |
|
||||||
|
| neuro (external) | 95.31.254.84 |
|
||||||
|
|
||||||
|
## Access Router Admin Panel
|
||||||
|
|
||||||
|
The router blocks requests with non-local `Host` headers, so SSH port forwarding
|
||||||
|
with `-L` returns 403. Use a SOCKS proxy instead:
|
||||||
|
|
||||||
|
1. Start SSH SOCKS proxy:
|
||||||
|
```sh
|
||||||
|
ssh -D 1080 neuro
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Launch browser with SOCKS proxy:
|
||||||
|
```sh
|
||||||
|
nix run nixpkgs#chromium -- --proxy-server="socks5://localhost:1080"
|
||||||
|
# or
|
||||||
|
nix run nixpkgs#firefox -- # then configure manually (see below)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Navigate to **http://192.168.0.1**
|
||||||
|
|
||||||
|
### Firefox Manual Configuration
|
||||||
|
|
||||||
|
1. Settings -> Network Settings -> Settings...
|
||||||
|
2. Select "Manual proxy configuration"
|
||||||
|
3. SOCKS Host: `localhost`, Port: `1080`, SOCKS v5
|
||||||
|
4. Click OK
|
||||||
|
|
||||||
|
## Port Forwarding
|
||||||
|
|
||||||
|
Ports that need to be forwarded from router to `192.168.0.10`:
|
||||||
|
|
||||||
|
| External Port | Internal Port | Protocol | Service |
|
||||||
|
|---------------|---------------|----------|---------|
|
||||||
|
| 22 | 22 | TCP | SSH |
|
||||||
|
| 80 | 80 | TCP | HTTP |
|
||||||
|
| 443 | 443 | TCP | HTTPS |
|
||||||
|
| 4443 | 4443 | TCP | Jitsi |
|
||||||
|
| 5222 | 5222 | TCP | XMPP (c2s) |
|
||||||
|
| 5269 | 5269 | TCP | XMPP (s2s) |
|
||||||
|
| 10000 | 10000 | UDP | Jitsi Videobridge |
|
||||||
|
| 25565 | 25565 | TCP | Minecraft |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
Check if ports are open externally:
|
||||||
|
```sh
|
||||||
|
nix run nixpkgs#nmap -- -Pn -p 80,443 95.31.254.84
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `open` (not `filtered`)
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
# Use folder name as name of this system
|
||||||
|
name = builtins.baseNameOf ./.;
|
||||||
|
|
||||||
|
in self.lib.nixpkgs-lib.nixosSystem {
|
||||||
|
pkgs = import inputs.nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
self.overlays.default
|
||||||
|
inputs.nix-minecraft.overlay
|
||||||
|
];
|
||||||
|
config.allowUnfreePredicate = pkg: builtins.elem (self.lib.nixpkgs-lib.getName pkg) [
|
||||||
|
"minecraft-server"
|
||||||
|
"neoforge"
|
||||||
|
|
||||||
|
"nvidia-x11"
|
||||||
|
|
||||||
|
"cuda_nvcc"
|
||||||
|
"cuda_cudart"
|
||||||
|
"cuda_cuobjdump"
|
||||||
|
"cuda_cupti"
|
||||||
|
"cuda_nvdisasm"
|
||||||
|
"cuda_cccl"
|
||||||
|
"cuda_nvml_dev"
|
||||||
|
"cuda_nvrtc"
|
||||||
|
"cuda_nvtx"
|
||||||
|
"cuda_profiler_api"
|
||||||
|
|
||||||
|
"libcusparse_lt"
|
||||||
|
"libcublas"
|
||||||
|
"libcufft"
|
||||||
|
"libcufile"
|
||||||
|
"libcurand"
|
||||||
|
"libcusolver"
|
||||||
|
"libnvjitlink"
|
||||||
|
"libcusparse"
|
||||||
|
"cudnn"
|
||||||
|
];
|
||||||
|
# jitsi-meet depends on libolm which is marked insecure (CVE-2024-4519x)
|
||||||
|
config.permittedInsecurePackages = [
|
||||||
|
"jitsi-meet-1.0.8792"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
modules = [
|
||||||
|
{ networking.hostName = name; }
|
||||||
|
(import ./${name}.nix { inherit flake self inputs; })
|
||||||
|
inputs.nix-minecraft.nixosModules.minecraft-servers
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
services.xserver.videoDrivers = [ "nvidia" ];
|
||||||
|
hardware.graphics.enable = true;
|
||||||
|
hardware.nvidia = {
|
||||||
|
open = false;
|
||||||
|
nvidiaSettings = false;
|
||||||
|
modesetting.enable = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
services.minecraft-servers = {
|
||||||
|
enable = true;
|
||||||
|
eula = true;
|
||||||
|
openFirewall = true;
|
||||||
|
|
||||||
|
servers = {
|
||||||
|
vanilla = {
|
||||||
|
enable = true;
|
||||||
|
jvmOpts = "-Xmx6G -Xms2G";
|
||||||
|
package = pkgs.minecraftServers.vanilla-1_21_11;
|
||||||
|
|
||||||
|
serverProperties = {
|
||||||
|
server-port = 25565;
|
||||||
|
difficulty = "hard";
|
||||||
|
online-mode = true;
|
||||||
|
view-distance = 32;
|
||||||
|
level-seed = "8306359138650378643";
|
||||||
|
pause-when-empty-seconds = 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
createAeronautics = {
|
||||||
|
enable = true;
|
||||||
|
jvmOpts = "-Xmx8G -Xms2G";
|
||||||
|
package = pkgs.minecraftServers.neoforge-1_21_1;
|
||||||
|
|
||||||
|
symlinks = {
|
||||||
|
mods = pkgs.linkFarmFromDrvs "mods" (
|
||||||
|
builtins.attrValues {
|
||||||
|
Sable = pkgs.fetchurl {
|
||||||
|
url = "https://cdn.modrinth.com/data/T9PomCSv/versions/g8CObHcP/sable-neoforge-1.21.1-1.1.3.jar";
|
||||||
|
sha512 = "8180e214681c171c9e3b7fa307f7a92bd7de0b8125d671291425f04a4ba26b408758d8ea80a6386d8e73bb1e6b02caf3f20afb9b91ecedd48c37ed44363ac961";
|
||||||
|
};
|
||||||
|
Create = pkgs.fetchurl {
|
||||||
|
url = "https://cdn.modrinth.com/data/LNytGWDc/versions/UjX6dr61/create-1.21.1-6.0.10.jar";
|
||||||
|
sha512 = "11cc8fc049d2f67f6548c7abfada6b82a3adb5c7ca410a742de04bbca76e03862c518721b88d806f6e6d768a4d68531fdb903a85859b25d1484d550cc7bafd4b";
|
||||||
|
};
|
||||||
|
CreateAeronautics = pkgs.fetchurl {
|
||||||
|
url = "https://cdn.modrinth.com/data/oWaK0Q19/versions/1sv6OtSz/create-aeronautics-bundled-1.21.1-1.1.3.jar";
|
||||||
|
sha512 = "94831bc4702b3864524258fa0a73a50ab3cd37e9c157b5c6688a6845b866ec5838452804050b55e490549d91dad909fc37f0d619f354c5676e2e2651b9c15ec6";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
serverProperties = {
|
||||||
|
server-port = 25566;
|
||||||
|
difficulty = "hard";
|
||||||
|
online-mode = true;
|
||||||
|
view-distance = 20;
|
||||||
|
pause-when-empty-seconds = 0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,272 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
flake,
|
||||||
|
self,
|
||||||
|
}: {
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
|
ollamaLibraryPath = lib.makeLibraryPath [
|
||||||
|
pkgs.stdenv.cc.cc.lib
|
||||||
|
pkgs.zlib
|
||||||
|
];
|
||||||
|
|
||||||
|
ollamaWrapperBundledLibraryPath = "$out/lib/ollama:$out/lib/ollama/cuda_v12:$out/lib/ollama/cuda_v13";
|
||||||
|
|
||||||
|
ollamaServiceBundledLibraryPath = "${ollamaPrebuilt}/lib/ollama:${ollamaPrebuilt}/lib/ollama/cuda_v12:${ollamaPrebuilt}/lib/ollama/cuda_v13";
|
||||||
|
|
||||||
|
ollamaPrebuilt = pkgs.stdenvNoCC.mkDerivation {
|
||||||
|
pname = "ollama";
|
||||||
|
version = "0.22.1";
|
||||||
|
|
||||||
|
src = pkgs.fetchurl {
|
||||||
|
url = "https://github.com/ollama/ollama/releases/download/v0.22.1/ollama-linux-amd64.tar.zst";
|
||||||
|
hash = "sha256-4nwP6PYKgkFi+Bzge0v9p2fc5PNX12LhSbPQ3gq62fs=";
|
||||||
|
};
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.autoPatchelfHook
|
||||||
|
pkgs.gnutar
|
||||||
|
pkgs.makeWrapper
|
||||||
|
pkgs.zstd
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.stdenv.cc.cc.lib
|
||||||
|
pkgs.zlib
|
||||||
|
];
|
||||||
|
|
||||||
|
autoPatchelfIgnoreMissingDeps = [
|
||||||
|
"libcuda.so.1"
|
||||||
|
];
|
||||||
|
|
||||||
|
unpackPhase = ''
|
||||||
|
runHook preUnpack
|
||||||
|
tar --zstd -xf $src
|
||||||
|
runHook postUnpack
|
||||||
|
'';
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out
|
||||||
|
cp -R . $out/
|
||||||
|
test -x $out/bin/ollama
|
||||||
|
mv $out/bin/ollama $out/bin/.ollama-unwrapped
|
||||||
|
makeWrapper $out/bin/.ollama-unwrapped $out/bin/ollama \
|
||||||
|
--set-default LD_LIBRARY_PATH "${ollamaLibraryPath}:/run/opengl-driver/lib:${ollamaWrapperBundledLibraryPath}"
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta.mainProgram = "ollama";
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = [
|
||||||
|
self.nixosModules.hectic
|
||||||
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
./minecraft.nix
|
||||||
|
./hardware.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETMumAHP+htbRvbrmzVoeesbT0+WcH1Wz8htk+7Ik+6"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEJZFglwpPMFLnQDOqi84nlMFktZSSu1GzUIafvClUaD"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGj7u/JuY9RwjoxnmO2b+pwC8XbMn+QOy44UpuN0Y1do riquizu"
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBbR42mLupcsF64ydGSx7HdB+qMVJq41a43UZMI7VvOj"
|
||||||
|
];
|
||||||
|
|
||||||
|
# disko.devices = {
|
||||||
|
# disk.master = {
|
||||||
|
# device = lib.mkDefault "/dev/disk/by-id/nvme-eui.00000000000000000026b7686dfafe35";
|
||||||
|
# type = "disk";
|
||||||
|
# content = {
|
||||||
|
# type = "gpt";
|
||||||
|
# partitions = {
|
||||||
|
# ESP = {
|
||||||
|
# size = "1G";
|
||||||
|
# type = "EF00";
|
||||||
|
# content = {
|
||||||
|
# type = "filesystem";
|
||||||
|
# format = "vfat";
|
||||||
|
# mountpoint = "/boot";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# root = {
|
||||||
|
# size = "100%";
|
||||||
|
# content = {
|
||||||
|
# type = "filesystem";
|
||||||
|
# format = "ext4";
|
||||||
|
# mountpoint = "/";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
|
||||||
|
#hectic.services.matrix = {
|
||||||
|
# enable = true;
|
||||||
|
# secretsFile = config.sops.secrets."matrix/secrets".path;
|
||||||
|
# turnSecretFile = config.sops.secrets."matrix/turn-secret".path;
|
||||||
|
# postgresql = {
|
||||||
|
# port = 5432;
|
||||||
|
# initialEnvFile = config.sops.secrets."init-postgresql".path;
|
||||||
|
# };
|
||||||
|
# matrixDomain = "accord.tube";
|
||||||
|
#};
|
||||||
|
|
||||||
|
#hectic.services.jitsi = {
|
||||||
|
# enable = true;
|
||||||
|
# hostName = "meet.accord.tube";
|
||||||
|
#};
|
||||||
|
|
||||||
|
#hectic.services.xmpp = {
|
||||||
|
# enable = true;
|
||||||
|
# domain = "accord.tube";
|
||||||
|
# admins = [ "yukkop@accord.tube" ];
|
||||||
|
#};
|
||||||
|
|
||||||
|
# Static landing page for bfs.band on :80 and :443 (no HTTP->HTTPS redirect).
|
||||||
|
security.acme = {
|
||||||
|
acceptTerms = true;
|
||||||
|
defaults.email = "security@bfs.band";
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts."bfs.band" = let
|
||||||
|
site = pkgs.runCommand "bfs-band-site" {} ''
|
||||||
|
mkdir -p $out
|
||||||
|
cat > $out/index.html <<'EOF'
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>bfs.band</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>bfs.band</h1>
|
||||||
|
<p>Coming soon.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOF
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
serverAliases = [ "www.bfs.band" ];
|
||||||
|
enableACME = true;
|
||||||
|
addSSL = true; # serve on both :80 and :443, no forced redirect
|
||||||
|
forceSSL = false;
|
||||||
|
root = "${site}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.ollama = {
|
||||||
|
enable = true;
|
||||||
|
host = "127.0.0.1";
|
||||||
|
port = 11434;
|
||||||
|
package = ollamaPrebuilt;
|
||||||
|
home = "/var/lib/ollama";
|
||||||
|
models = "/var/lib/ollama/models";
|
||||||
|
environmentVariables = {
|
||||||
|
LD_LIBRARY_PATH = "${ollamaLibraryPath}:/run/opengl-driver/lib:${ollamaServiceBundledLibraryPath}";
|
||||||
|
OLLAMA_NEW_ENGINE = "true";
|
||||||
|
};
|
||||||
|
loadModels = [ "qwen3.6:27b" "qwen3.5:9b" "gemma3:4b" "gpt-oss:20b" ];
|
||||||
|
openFirewall = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
networkmanager.enable = true;
|
||||||
|
useDHCP = lib.mkDefault true;
|
||||||
|
interfaces.enp6s0 = {
|
||||||
|
useDHCP = lib.mkDefault true;
|
||||||
|
wakeOnLan.enable = true;
|
||||||
|
};
|
||||||
|
firewall = {
|
||||||
|
enable = true;
|
||||||
|
allowedTCPPorts = [
|
||||||
|
80 443 # HTTP, HTTPS
|
||||||
|
];
|
||||||
|
allowedUDPPorts = [ 9 ]; # Wake on LAN
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
hardware.enableRedistributableFirmware = true;
|
||||||
|
|
||||||
|
hectic = {
|
||||||
|
archetype.base.enable = true;
|
||||||
|
archetype.dev.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
gnupg.sshKeyPaths = [ ];
|
||||||
|
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||||
|
defaultSopsFile = ../../../sus/neuro.yaml;
|
||||||
|
|
||||||
|
#secrets."init-postgresql" = {};
|
||||||
|
#secrets."matrix/secrets" = {};
|
||||||
|
#secrets."matrix/turn-secret" = {
|
||||||
|
# owner = "turnserver";
|
||||||
|
# group = "turnserver";
|
||||||
|
# mode = "0400";
|
||||||
|
#};
|
||||||
|
};
|
||||||
|
|
||||||
|
boot.loader.systemd-boot.enable = true;
|
||||||
|
boot.loader.efi.canTouchEfiVariables = true;
|
||||||
|
boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sr_mod" "ext4" ];
|
||||||
|
boot.initrd.kernelModules = [ "nvme" ];
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
|
fileSystems."/" =
|
||||||
|
{ device = "/dev/disk/by-label/NIXROOT";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" =
|
||||||
|
{ device = "/dev/disk/by-label/NIXBOOT";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [ "fmask=0022" "dmask=0022" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware = {
|
||||||
|
cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
};
|
||||||
|
|
||||||
|
swapDevices = [ ];
|
||||||
|
|
||||||
|
programs.tmux.enable = true;
|
||||||
|
|
||||||
|
zramSwap.enable = true;
|
||||||
|
zramSwap.priority = 100;
|
||||||
|
zramSwap.memoryMax = null;
|
||||||
|
zramSwap.algorithm = lib.mkDefault "zstd";
|
||||||
|
zramSwap.swapDevices = 1;
|
||||||
|
zramSwap.memoryPercent = lib.mkDefault 100;
|
||||||
|
|
||||||
|
environment.systemPackages = with pkgs; let
|
||||||
|
python-ai = python3.withPackages (ps: let
|
||||||
|
torchCuda = ps.torchWithCuda;
|
||||||
|
torchvision = ps.torchvision.override { torch = torchCuda; };
|
||||||
|
pytorch3dCuda = ps.pytorch3d.override { torch = torchCuda; };
|
||||||
|
in [
|
||||||
|
torchCuda
|
||||||
|
torchvision
|
||||||
|
pytorch3dCuda
|
||||||
|
ps.fvcore
|
||||||
|
ps.iopath
|
||||||
|
ps.tqdm
|
||||||
|
hectic.py3-openai-shap-e # Uncomment when needed; depends on torch
|
||||||
|
]);
|
||||||
|
in [
|
||||||
|
#python-ai
|
||||||
|
git
|
||||||
|
neovim
|
||||||
|
wget
|
||||||
|
ethtool
|
||||||
|
rsync
|
||||||
|
# docker
|
||||||
|
];
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user