diff --git a/nixos/module/hectic/service/sentinèlla.nix b/nixos/module/hectic/service/sentinèlla.nix index bdc28a0..6473d58 100644 --- a/nixos/module/hectic/service/sentinèlla.nix +++ b/nixos/module/hectic/service/sentinèlla.nix @@ -10,17 +10,15 @@ ... }: let system = pkgs.stdenv.hostPlatform.system; - cfg = config.hectic.services."sentinèlla"; + cfg = config.hectic.services."sentinèlla"; + + probePort = 5988; + peersDns = "peers.sentinella.hectic-lab.com"; in { options = { hectic.services."sentinèlla" = { probe = { enable = lib.mkEnableOption "sentinèlla probe — HTTP server exposing this node's health"; - port = lib.mkOption { - type = lib.types.port; - default = 5988; - description = "TCP port the probe listens on."; - }; urls = lib.mkOption { type = with lib.types; listOf str; default = []; @@ -52,16 +50,6 @@ in { watcher = { enable = lib.mkEnableOption "sentinèlla watcher — polls peers discovered via DNS and sends Telegram alerts"; - peersDns = lib.mkOption { - type = lib.types.str; - example = "peers.sentinella.com"; - description = '' - DNS name with multiple A records, one per peer node. - Configure externally (e.g. Cloudflare) with TTL 60: - peers.sentinella.com A 1.2.3.4 - peers.sentinella.com A 5.6.7.8 - ''; - }; self = lib.mkOption { type = with lib.types; nullOr str; default = null; @@ -73,11 +61,6 @@ in { has a floating IP that hostname -I does not report correctly. ''; }; - peersPort = lib.mkOption { - type = lib.types.port; - default = 5988; - description = "Port all peer probes listen on."; - }; peersScheme = lib.mkOption { type = lib.types.str; default = "http"; @@ -125,6 +108,13 @@ in { 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" ]; @@ -142,7 +132,7 @@ in { StandardOutput = "journal"; StandardError = "journal"; Environment = lib.filter (s: s != "") [ - "PORT=${builtins.toString cfg.probe.port}" + "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}") @@ -178,9 +168,9 @@ in { StandardError = "journal"; StateDirectory = "sentinella"; Environment = lib.filter (s: s != "") [ - "PEERS_DNS=${cfg.watcher.peersDns}" - (lib.optionalString (cfg.watcher.self != null) "SELF=${cfg.watcher.self}") - "PEERS_PORT=${builtins.toString cfg.watcher.peersPort}" + "PEERS_DNS=${peersDns}" + (lib.optionalString (cfg.watcher.self != null) "SELF=${cfg.watcher.self}") + "PEERS_PORT=${builtins.toString probePort}" "PEERS_SCHEME=${cfg.watcher.peersScheme}" "POLLING_INTERVAL_SEC=${builtins.toString cfg.watcher.pollingIntervalSec}" "STATE_DIR=/var/lib/sentinella" diff --git a/nixos/system/hectic-lab/hectic-lab.nix b/nixos/system/hectic-lab/hectic-lab.nix index 112a217..e94e0b5 100644 --- a/nixos/system/hectic-lab/hectic-lab.nix +++ b/nixos/system/hectic-lab/hectic-lab.nix @@ -100,12 +100,12 @@ in { ]; }; - sops.secrets."mailserver/security/hashedPassword" = {}; - sops.secrets."mailserver/yukkop/hashedPassword" = {}; + 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/snuff/hashedPassword" = {}; + sops.secrets."mailserver/antoshka/hashedPassword" = {}; + sops.secrets."mailserver/founders/hashedPassword" = {}; services.mailserver = { enable = true; diff --git a/nixos/system/hectic-lab/sentinèlla.nix b/nixos/system/hectic-lab/sentinèlla.nix index 7054564..a599a13 100644 --- a/nixos/system/hectic-lab/sentinèlla.nix +++ b/nixos/system/hectic-lab/sentinèlla.nix @@ -5,19 +5,12 @@ domain, sslOpts, ... -}: { ... }: let - port = 5869; -in { +}: { ... }: { hectic.services."sentinèlla" = { - probe = { - enable = true; - inherit port; - }; + probe.enable = true; watcher = { - enable = true; - peersDns = "peers.${domain}"; - peersPort = port; - pollingIntervalSec = 60; + 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") }; @@ -27,7 +20,7 @@ in { virtualHosts."probe.${domain}" = sslOpts // { forceSSL = true; locations."/" = { - proxyPass = "http://127.0.0.1:${builtins.toString port}"; + proxyPass = "http://127.0.0.1:5988"; }; }; }; diff --git a/package/sentinèlla/default.nix b/package/sentinèlla/default.nix index 0f6ee9a..8f23bd0 100644 --- a/package/sentinèlla/default.nix +++ b/package/sentinèlla/default.nix @@ -1,4 +1,4 @@ -{ symlinkJoin, writeTextFile, socat, dash, hectic, curl, gawk, jq, inetutils }: +{ symlinkJoin, writeTextFile, socat, dash, hectic, curl, gawk, jq, inetutils, getent }: let shell = "${dash}/bin/dash"; bashOptions = [ @@ -34,7 +34,7 @@ let watcher = hectic.writeShellApplication { inherit shell bashOptions; name = "watcher"; - runtimeInputs = [ curl jq inetutils ]; + runtimeInputs = [ curl jq gawk inetutils getent ]; text = '' ${builtins.readFile ./log.sh} ${builtins.readFile ./colors.sh} diff --git a/package/sentinèlla/watcher.sh b/package/sentinèlla/watcher.sh index 903281a..106d8b2 100644 --- a/package/sentinèlla/watcher.sh +++ b/package/sentinèlla/watcher.sh @@ -136,13 +136,11 @@ while :; do printf '%s\n' "$peers" | while IFS= read -r url; do [ -n "$url" ] || continue - auth_h="" - [ -n "$PEERS_TOKEN" ] && auth_h="-H 'Authorization: Basic $PEERS_TOKEN'" - tmpb=$(mktemp) || exit 1 - # shellcheck disable=SC2086 - code=$(sh -c "curl -sS -m \"$TIMEOUT\" -w '%{http_code}' -o \"$tmpb\" $auth_h \"$url\"") \ - || code="000" + set -- curl -sS -m "$TIMEOUT" -w '%{http_code}' -o "$tmpb" + [ -n "$PEERS_TOKEN" ] && set -- "$@" -H "Authorization: Basic $PEERS_TOKEN" + set -- "$@" "$url" + code=$("$@" 2>/dev/null) || code="000" body=$(cat "$tmpb"); rm -f "$tmpb" ok="down"; total=0; good=0 @@ -166,6 +164,8 @@ while :; do if [ "$cur" != "$last" ] || [ "$SPAM" = "1" ]; then notify "$msg" printf '%s' "$cur" >"$sfile" + else + log info "no change: ${WHITE}${msg}${NC}" fi done diff --git a/sus/sentinella-default.yaml b/sus/sentinella-default.yaml index bb9e309..0829342 100644 --- a/sus/sentinella-default.yaml +++ b/sus/sentinella-default.yaml @@ -1,5 +1,81 @@ sentinèlla: watcher: - environment: | - TG_TOKEN= - TG_CHAT_ID= + environment: ENC[AES256_GCM,data:Nm0t4/mXm7TJfdj42C6r5a8IgU+qNRdmjcF0/WhrYUoHkCYbBg6wqIIBg+v6FPrx4oRFukBNBkDpT5cwTGFmzKEQGpOAiSgZYrweme0+GXv/zA==,iv:fjzzOyfNCJYwT92IpJPgf31hNGczb1L0pSw60+4TTbU=,tag:NrbhaztHa5/a0O1Rol22ew==,type:str] +sops: + age: + - recipient: age1r25zdeqq8nac6dgca9en28r57ffyz9u9d8z5yc25gc8xqz747vaqmdtk0h + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMOUpaK2VFZXZZMUFjUm5v + d01xbUI5bUJ2WUtick1qdjZpZktsQXF1SEdNCmllTTMrRFI1aVMxZkkxVW40SExo + eUh3Z3RWdVpSeG9Ucmx6QWRSQlZMUlEKLS0tIE9rbEUraWRzbTRDcXEvaE9JVFJH + NFVUcDVYQmhqTlhERGZnaHNTLyt0cWMKUwh34l0uWkyqALqNmt4tTxjCC2NwDg5A + k5p+T1qDSm9T1HnMFz8/kJvxEG6xm2R+tDQlYDFkmUn/tSHU97JMdQ== + -----END AGE ENCRYPTED FILE----- + - recipient: age1vv46vn4hsn2lg6jy834cpu40c3mvqklldcm3hjtynrhwtpmlpc8szruz4v + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhY3ZEQmowd3RBOXhkVmdI + djF5N1QxRENib0hCTUh0RkF5aG9adHVRcHpjCmk5cVR5RlRSUWoxd1NqdXozZVpM + S0JUZWI5eEFaR0JzZ2N3RnVycWNsam8KLS0tIFdndEN0V2IrV0xUQjRGZ09lbWMy + RlhveTAyN3FrSTYwY3kwdlIrcG9wbzgKVnWZ/TO72XLslo+mAQDOefhfv5FyEk6K + 0kyernRAnFKkOu/KrLL2DCRScmz0WUdazhTu0tNmP6kk5FFWIWbkzw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1x04u7ftjgx8de2gq596e7frauze764cmn7jjwqnx8szthvfft5qq0tezx6 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjMG1OWGFWUVZsaTN2SWR5 + U2ZMdmIvY0E3Rk1nc3NqUXFUT2VoazVTN1hNCmJGeG13ZlBRd2Z4YkpWYjdJblVu + L2NXZk9vL3B0Rmc5V2o5V2pNOGtNRDgKLS0tIFNkZHRUd3ZZSlROSDk2dlhsU3ZJ + c3VYc2RFbmtlbzJnalFOR0lyNTFFcjgKd3iD9NOIqY/LU+nrHZnmJ6Ain91M3731 + 4QqelqyYPj8EcdnhxMCh9rhS4zadXz219bGM3D0T3rjSNpF7B3Xh7w== + -----END AGE ENCRYPTED FILE----- + - recipient: age15yzgmsvl3ku2w863h6gw2vpmw37m9aruv6xrj4fue6n2jpm7pyuqk9xjmj + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5cnJrUTVIWHpITFZ4ZDJi + M0piM3FuUkU0eWhOVEZIQ2lSUFdmbFBMbEdFCmZSRDQ2MlY2SUFza0kvWlFBNnZn + Tjk4QVRXRjR2NDdkakhqM0FLU25CdjAKLS0tIGR1Y3dBWlY5a1ZoMTQzTy9iRE1E + aHNxTmhuWVQ2SmR5bkNySVVSZ2tFQXMKpiY4+Q0etpunQ563bCwOOZ5aF9Uax4AO + fAAu5YDtth/ZlDb0V4sAcrJ1OHWu03HfzabETPBQgWr9oo9zk1P04A== + -----END AGE ENCRYPTED FILE----- + - recipient: age1fpytf05sg9n6ywpwkmn09lhpfvgtud9h75h76jhxha475zpnasqq952rpu + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGSFRzcnJvVnlYaDNsSjZP + TDRhdmNVRmtSZUNFd1JKL3ZjVW44c0VqRm1NCnJkTlpyQno4ODhWYlhsVkpGbERr + R0VheXhOTmdkMWhKWWp1bVpxZUlucHcKLS0tIHBSRVVkaXZMQXpkemRNNzIxQld4 + RTlsQ3RLOWtJbVNNZ3hZaU1naitwYU0KKi0IL1lUayBsqr/yW74UmMbfsWKDbHMr + lY7K++/IhnE0AvZTIwrjeN2+mwb/0UtqcUGv5chCzOHPBPclkE/KVA== + -----END AGE ENCRYPTED FILE----- + - recipient: age17yx98qk9gzgcf2q6zhhp05p6mmtrkgz66dvyk9gqclypvlr8rersxjy5v7 + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYKytQZGVCbTBpVTFkakN4 + ekFUWFk1UzlXUEhNaFcrcWJQbjJHaGVpR0hvCllmSWw0dmJmNnNHK3BKL2wrMThF + bWNRTGEwanVpVVZiakVYUm4wKzV3WjQKLS0tIGpBMU5haDl4RW5xSHkrU3FWU294 + OEtnQ1haNytQQjZycFQrNWQvQ3NaK2sK61NpwiCcbfe5i+V25jErSRUiUDcBx5SJ + /AAVOHc1OZzTPZTC1Qh3LEmqhXM2Mwb8arfA3PJERmWEnw2TFoeIDA== + -----END AGE ENCRYPTED FILE----- + - recipient: age13h8twnwvgxn04l5ywtru89a6psw5d0uckr2eghxsjp88a5augvsstq5ard + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVTGVvWkIwZWdFME5CeU1F + QnBkdlBRMGoxQlpEVmg2OWk3eFJQU0o4UHlzCnFYRGFqZ2JHVWUzZk5wc1VnbXM0 + YnZuOUQrekNIa3BNMjZoNE5JYVI5aWsKLS0tIHZpRTg3Z29wQnlyb3MxQm9mdkE2 + c3crSjlZQ05wbEVlREM2WVRaZkJoU3cK97wpdGSdVhJPTa7qZJPCD/bbufQZllLt + hPiA4WTP0/MGWlRhIuiwozc2sGndZPWullGANFOK/9QFkb0Zi+PsOw== + -----END AGE ENCRYPTED FILE----- + - recipient: age1jxntjca8q2vxvf2jaal4xyvm2ae6sh62fhv897694kuzawfrk5asj00zdt + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqMkVZSFk3dTVxWlJpUjRO + eVF1UFdVQUxIUVIvTHVsdHdVM1V2ZDVLOEhVCkx3SmV3aHZtVFFUQVJOT2tHQjBF + U3ZVNmtTeHdjemNQVFpXa3lMbzVJTG8KLS0tIDFkZ1oreFdsekRpTVVMdTZyMWoy + cnVyVHB5TmkxdkNleGVFSkJwemR5bGMKoEtPziwcX7DV+RJezLBAeDTbJkYj0isc + FBzlI3QoqwQJM5jn1aIjyl8jeE/gEeuyfHJ7Y5pSrAW1poz5fz5mwQ== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2026-04-26T23:54:52Z" + mac: ENC[AES256_GCM,data:oqOH7exYj8XNAprU1VhMxVTcGqPsHmiEcHFseeNZ6Q6xd+6c+gOEw7iHfdXwON6Dv7ZZbX5eThhrXcg/3Wo+YlGwH0E9ju4PDo9V6L1ajys4HvIDaAL9XVjAuKvGALXIFeboUnAYhlcMQu6+wdDwn+wPlA0J3LXk9XcqciyMx3M=,iv:RXnTGgx3kG22YwmQha1+AeLvg5l97ye3NPr4EGsTzHs=,tag:Du8w4psXyjqMHNCbmrrLCw==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2