diff --git a/hmModules/helix/default.nix b/hmModules/helix/default.nix index 89f0e01..a593e7d 100644 --- a/hmModules/helix/default.nix +++ b/hmModules/helix/default.nix @@ -13,5 +13,19 @@ true-color = true; # to make colors coherent when in ssh }; }; + languages = { + language = [ + { + name = "nix"; + language-servers = ["nixd"]; + } + ]; + language-servers = [ + { + name = "nixd"; + command = "nixd"; + } + ]; + }; }; } diff --git a/hosts/default.nix b/hosts/default.nix index 27ff71f..16a5f4f 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -182,6 +182,7 @@ # "matrix-registration-shared-secret".owner = "matrix-synapse"; # "matrix-sliding-sync-secret".owner = "matrix-synapse"; "autistici-password".owner = "forgejo"; + "garmin-collector-environment".owner = "garmin-collector"; }; }; }; diff --git a/hosts/picard/default.nix b/hosts/picard/default.nix index 97be64f..5337448 100644 --- a/hosts/picard/default.nix +++ b/hosts/picard/default.nix @@ -42,6 +42,7 @@ "adb" "guix" "prometheus-exporters" + "promtail" ] ++ [ ./disko.nix diff --git a/hosts/sisko/default.nix b/hosts/sisko/default.nix index 8aba34a..f86b986 100644 --- a/hosts/sisko/default.nix +++ b/hosts/sisko/default.nix @@ -34,6 +34,9 @@ "prometheus-exporters" "loki" "promtail" + "garmin-collector" + "restic" + # "immich" ] ++ [ ./disko.nix diff --git a/modules/garmin-collector/default.nix b/modules/garmin-collector/default.nix new file mode 100644 index 0000000..b4ac0e1 --- /dev/null +++ b/modules/garmin-collector/default.nix @@ -0,0 +1,46 @@ +{ + pkgs, + lib, + fleetFlake, + config, + ... +}: { + users.users.garmin-collector = { + isSystemUser = true; + group = "garmin-collector"; + extraGroups = ["garmin-collector"]; + home = "/var/lib/garmin-collector"; + }; + + users.groups.garmin-collector = {}; + + systemd.services.garmin-collector = { + description = "Garmin collector pushing to Prometheus Pushgateway"; + wantedBy = ["multi-user.target"]; + environment = { + PUSHGATEWAY_ADDRESS = config.services.prometheus.pushgateway.web.listen-address; + }; + serviceConfig = { + Group = "garmin-collector"; + User = "garmin-collector"; + WorkingDirectory = "/var/lib/garmin-collector"; + ExecStart = '' + ${lib.getExe fleetFlake.packages.${pkgs.system}.garmin-collector} + ''; + EnvironmentFile = config.age.secrets.garmin-collector-environment.path; + }; + }; + + systemd.timers."garmin-collector" = { + wantedBy = ["timers.target"]; + timerConfig = { + OnBootSec = "5m"; + OnUnitActiveSec = "4h"; + Unit = "garmin-collector.service"; + }; + }; + + environment.persistence."/persist".directories = [ + "/var/lib/garmin-collector" + ]; +} diff --git a/modules/prometheus-exporters/default.nix b/modules/prometheus-exporters/default.nix index ceaab99..a56d1cb 100644 --- a/modules/prometheus-exporters/default.nix +++ b/modules/prometheus-exporters/default.nix @@ -1,32 +1,56 @@ { config, pkgs, + lib, ... -}: { - services.prometheus.exporters.node = { - enable = true; - enabledCollectors = [ - "cpu" - "conntrack" - "diskstats" - "entropy" - "filefd" - "filesystem" - "loadavg" - "mdadm" - "meminfo" - "netdev" - "netstat" - "stat" - "time" - "vmstat" - "systemd" - "logind" - "interrupts" - "ksmd" - "textfile" - "pressure" - ]; - extraFlags = ["--collector.ethtool" "--collector.softirqs" "--collector.tcpstat" "--collector.wifi"]; +}: let + hostname = config.networking.hostName; + mkFor = hosts: lib.mkIf (builtins.elem hostname hosts); +in { + services.prometheus.exporters = { + node = mkFor ["sisko" "picard"] { + enable = true; + enabledCollectors = [ + "cpu" + "conntrack" + "diskstats" + "entropy" + "filefd" + "filesystem" + "loadavg" + "mdadm" + "meminfo" + "netdev" + "netstat" + "stat" + "time" + "vmstat" + "systemd" + "logind" + "interrupts" + "ksmd" + "textfile" + "pressure" + ]; + extraFlags = ["--collector.ethtool" "--collector.softirqs" "--collector.tcpstat" "--collector.wifi"]; + }; + wireguard = mkFor ["sisko" "picard"] { + enable = true; + }; + zfs = mkFor ["picard"] { + enable = true; + }; + # restic = mkFor ["sisko"] { + # enable = true; + # }; + postgres = mkFor ["sisko"] { + enable = true; + }; + nginx = mkFor ["sisko"] { + enable = true; + }; + smartctl = mkFor ["sisko"] { + enable = true; + }; }; } diff --git a/modules/prometheus/default.nix b/modules/prometheus/default.nix index e77b7d6..c35b04e 100644 --- a/modules/prometheus/default.nix +++ b/modules/prometheus/default.nix @@ -3,6 +3,12 @@ in { services.prometheus = { enable = true; + pushgateway = { + enable = true; + web = { + listen-address = "sisko.fleet:9094"; + }; + }; checkConfig = false; # Otherwise it will fail because it cannot access bearer_token_file webExternalUrl = "https://status.aciceri.dev"; globalConfig.scrape_interval = "10s"; @@ -17,6 +23,14 @@ in { } ]; } + { + job_name = "pushgateway"; + static_configs = [ + { + targets = [cfg.pushgateway.web.listen-address]; + } + ]; + } { job_name = "node"; static_configs = [ @@ -25,6 +39,54 @@ in { } ]; } + { + job_name = "wireguard"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9586") ["picard"]; + } + ]; + } + { + job_name = "zfs"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9134") ["picard"]; + } + ]; + } + { + job_name = "restic"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9753") ["sisko"]; + } + ]; + } + { + job_name = "postgres"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9187") ["sisko"]; + } + ]; + } + { + job_name = "nginx"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9117") ["sisko"]; + } + ]; + } + { + job_name = "smartctl"; + static_configs = [ + { + targets = builtins.map (host: "${host}.fleet:9633") ["sisko"]; + } + ]; + } ]; }; environment.persistence."/persist".directories = [ diff --git a/modules/promtail/default.nix b/modules/promtail/default.nix index 74f2eec..f6d7621 100644 --- a/modules/promtail/default.nix +++ b/modules/promtail/default.nix @@ -11,7 +11,7 @@ }; clients = [ { - url = "http://sisko.fleet:${builtins.toString config.services.loki.configuration.server.http_listen_port}/loki/api/v1/push"; + url = "http://sisko.fleet:${builtins.toString config.services.loki.configuration.server.http_listen_port or 3100}/loki/api/v1/push"; } ]; positions = { diff --git a/modules/promtail/protmail.yaml b/modules/promtail/protmail.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/modules/restic/default.nix b/modules/restic/default.nix index 4496e61..52da5cf 100644 --- a/modules/restic/default.nix +++ b/modules/restic/default.nix @@ -3,43 +3,33 @@ pkgs, lib, ... -}: { - options.backup = { - paths = lib.mkOption { - type = lib.types.listOf lib.types.path; - default = []; +}: let + user = "u382036-sub1"; + host = "u382036.your-storagebox.de"; + port = "23"; +in { + age.secrets = { + HETZNER_STORAGE_BOX_SISKO_SSH_PASSWORD = { + file = ../../secrets/hetzner-storage-box-sisko-ssh-password.age; + owner = "root"; }; - }; - config.services.restic = { - backups = { - hetzner = { - paths = config.backup.paths; - passwordFile = config.age.secrets.restic-hetzner-password.path; - extraOptions = [ - # Use the host ssh key, for authorizing new hosts: - # cat /etc/ssh/ssh_host_ed25519_key.pub | ssh -p23 u382036-sub1@u382036-sub1.your-storagebox.de install-ssh-key - "sftp.command='ssh -p23 u382036-sub1@u382036-sub1.your-storagebox.de -i /etc/ssh/ssh_host_ed25519_key -s sftp'" - ]; - repository = "sftp://u382036-sub1@u382036-sub1.your-storagebox.de:23/"; - initialize = true; - timerConfig.OnCalendar = "daily"; - timerConfig.RandomizedDelaySec = "1h"; - }; + SISKO_RESTIC_PASSWORD = { + file = ../../secrets/sisko-restic-password.age; + owner = "root"; }; }; - config.environment.systemPackages = builtins.map (path: - pkgs.writeShellApplication { - name = "restic-restore-${builtins.replaceStrings ["/"] ["-"] path}"; - runtimeInputs = with pkgs; [restic]; - text = '' - restic -r ${config.services.restic.backups.hetzner.repository} \ - ${lib.concatMapStringsSep ''\'' (option: "-o ${option}") config.services.restic.backups.hetzner.extraOptions} \ - --password-file ${config.services.restic.backups.hetzner.passwordFile} \ - restore latest \ - --path "${path}"\ - --target "$1" - ''; - }) - config.services.restic.backups.hetzner.paths; + services.openssh.knownHosts."${host}".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs"; + + services.restic.backups.sisko = { + paths = ["/persist"]; + passwordFile = config.age.secrets.SISKO_RESTIC_PASSWORD.path; + extraOptions = [ + "sftp.command='${lib.getExe pkgs.sshpass} -f ${config.age.secrets.HETZNER_STORAGE_BOX_SISKO_SSH_PASSWORD.path} ssh -p${port} ${user}@${host} -s sftp'" + ]; + repository = "sftp://${user}@${host}:${port}/"; + initialize = true; + timerConfig.OnCalendar = "daily"; + timerConfig.RandomizedDelaySec = "1h"; + }; } diff --git a/modules/rock5b-proxy/default.nix b/modules/rock5b-proxy/default.nix index 1906a71..6e51923 100644 --- a/modules/rock5b-proxy/default.nix +++ b/modules/rock5b-proxy/default.nix @@ -35,17 +35,17 @@ proxyPass = "http://localhost:${builtins.toString config.services.invidious.port}"; }; }; - "photos.aciceri.dev" = { - extraConfig = '' - client_max_body_size 50000M; - ''; - forceSSL = true; - enableACME = true; - locations."/" = { - proxyPass = "http://localhost:2283"; - proxyWebsockets = true; - }; - }; + # "photos.aciceri.dev" = { + # extraConfig = '' + # client_max_body_size 50000M; + # ''; + # forceSSL = true; + # enableACME = true; + # locations."/" = { + # proxyPass = "http://localhost:2283"; + # proxyWebsockets = true; + # }; + # }; # "jellyfin.aciceri.dev" = { # forceSSL = true; diff --git a/packages/garmin-collector/default.nix b/packages/garmin-collector/default.nix new file mode 100644 index 0000000..2e19330 --- /dev/null +++ b/packages/garmin-collector/default.nix @@ -0,0 +1,12 @@ +{ + writers, + python3Packages, + ... +}: +writers.writePython3Bin "garmin-collector" { + libraries = with python3Packages; [ + prometheus-client + garminconnect + ]; + flakeIgnore = ["E501"]; +} (builtins.readFile ./garmin-collector.py) diff --git a/packages/garmin-collector/garmin-collector.py b/packages/garmin-collector/garmin-collector.py new file mode 100644 index 0000000..f296638 --- /dev/null +++ b/packages/garmin-collector/garmin-collector.py @@ -0,0 +1,82 @@ +# !/usr/bin/env python3 + +import datetime +import os + +from garth.exc import GarthHTTPError + +from garminconnect import ( + Garmin, + GarminConnectAuthenticationError, +) + + +from prometheus_client import CollectorRegistry, push_to_gateway +from prometheus_client.core import GaugeMetricFamily + +email = os.getenv("GARMIN_EMAIL") +password = os.getenv("GARMIN_PASSWORD") +tokenstore = os.getenv("GARMINTOKENS") or "~/.garminconnect" +tokenstore_base64 = os.getenv("GARMINTOKENS_BASE64") or "~/.garminconnect_base64" +gateway_address = os.getenv("PUSHGATEWAY_ADDRESS") + +today = datetime.date.today() + + +def init_api(email=email, password=password): + """Initialize Garmin API with your credentials.""" + + try: + print( + f"Trying to login to Garmin Connect using token data from directory '{tokenstore}'...\n" + ) + + garmin = Garmin() + garmin.login(tokenstore) + except (FileNotFoundError, GarthHTTPError, GarminConnectAuthenticationError): + # Session is expired. You'll need to log in again + print( + "Login tokens not present, login with your Garmin Connect credentials to generate them.\n" + f"They will be stored in '{tokenstore}' for future use.\n" + ) + garmin = Garmin(email=email, password=password, is_cn=False) + garmin.login() + # Save Oauth1 and Oauth2 token files to directory for next login + garmin.garth.dump(tokenstore) + print( + f"Oauth tokens stored in '{tokenstore}' directory for future use. (first method)\n" + ) + # Encode Oauth1 and Oauth2 tokens to base64 string and safe to file for next login (alternative way) + token_base64 = garmin.garth.dumps() + dir_path = os.path.expanduser(tokenstore_base64) + with open(dir_path, "w") as token_file: + token_file.write(token_base64) + print( + f"Oauth tokens encoded as base64 string and saved to '{dir_path}' file for future use. (second method)\n" + ) + + return garmin + + +class GarminCollector: + def __init__(self): + super().__init__() + self.api = init_api() + + def collect(self): + try: + body = self.api.get_daily_weigh_ins(today.isoformat())["totalAverage"] + metric_gauge = GaugeMetricFamily("body_composition", "Body composition and weight", labels=["metric"]) + for k in ["weight", "bmi", "bodyFat", "bodyWater", "boneMass", "muscleMass", "physiqueRating", "visceralFat"]: + metric_gauge.add_metric([k], body[k]) + except Exception as e: + print(f"Something went wrong while fetching body composition data\n{e}") + + yield metric_gauge + + +if __name__ == "__main__": + registry = CollectorRegistry() + registry.register(GarminCollector()) + + push_to_gateway(gateway_address, job='garmin', registry=registry) diff --git a/secrets/garmin-collector-environment.age b/secrets/garmin-collector-environment.age new file mode 100644 index 0000000..efc5779 Binary files /dev/null and b/secrets/garmin-collector-environment.age differ diff --git a/secrets/hetzner-storage-box-sisko-ssh-password.age b/secrets/hetzner-storage-box-sisko-ssh-password.age new file mode 100644 index 0000000..d2e9b5a Binary files /dev/null and b/secrets/hetzner-storage-box-sisko-ssh-password.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 8f3c4ef..b41e70e 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -27,6 +27,9 @@ in "matrix-sliding-sync-secret.age".publicKeys = [ccr-ssh ccr-gpg sisko]; "forgejo-runners-token.age".publicKeys = [ccr-ssh ccr-gpg picard]; "forgejo-nix-access-tokens.age".publicKeys = [ccr-ssh ccr-gpg picard]; + "garmin-collector-environment.age".publicKeys = [ccr-ssh ccr-gpg sisko]; + "hetzner-storage-box-sisko-ssh-password.age".publicKeys = [ccr-ssh ccr-gpg sisko]; + "sisko-restic-password.age".publicKeys = [ccr-ssh ccr-gpg sisko]; # WireGuard "picard-wireguard-private-key.age".publicKeys = [ccr-ssh ccr-gpg picard]; diff --git a/secrets/sisko-restic-password.age b/secrets/sisko-restic-password.age new file mode 100644 index 0000000..c754950 --- /dev/null +++ b/secrets/sisko-restic-password.age @@ -0,0 +1,30 @@ +age-encryption.org/v1 +-> ssh-rsa /AagBw +TKW/pV8ANvSWay5wTsFhV0CDSqn/wZAzNRP0WgRzBJbsrFP2/YYkhRHFtwkMjeXm +qEJPeXYdpgT6+FXq3nfhTaK/AbeebBRWO7dgGfKBosJ6Mc+PMhephrQ+oH6/zbG5 +l5QclAZ4NOfkD3f/nnqog13nKTijHjHcTnEWYZZz8RowaUEkEjo4Xbgw1MUbC8yJ +khyqZOTVFnfKgcSW5rlnsbrZKkmwYYY8mej27I9AFeSLgE0DOF3OWxrNxuPdxICp +h/kfQ2lPw75TWX5vj8WKOOxjAvheIiJDAAdfOoroK1BqKAUmpC6HjpC3cJZhrMmE +Xtob+esC39M8QBO1vUB639/I0AKAMbn3rE617StUr2QyyyNahnOOOPaZplCk/uM8 +Sde8d+VwTuvJXosuxi7Z+lQbeyCg7WmRigRoSiL6+9HcdMtDMDRjtloVq1o+iHXc +5A99Eeq0D/rBVSDmXKkVpcwLfruWL1v061+K7PPnjKa2CjnoEjAZDfqeQI+OBLZP +zqJ1CcQUnujYEpyhy4YV1ZpLZYOt48osEhUvG/eFnfymeDeAVAts725uzboN3uX8 +ETM5k0cW1ElSTL0BltRn8hRs8BSVXtKIucRXERomIwK+45ux8DHFS2NQlEHs2x1g +d4coPbCgMt7nBPYGnAUOYaWyw6dcaCAPNoVVIyUP1ps +-> ssh-rsa QHr3/A +GM2npxcLnNk81fSJUW9tcDnaKcx42cuxaObl8oCB43GIFm7K5L89FHj4Ww9RUJy0 +V41RQ802OBgudJqOI63DcW7mZ905fqLTnKZ75EJJSGgqjY0EcCOc2Oy8kV/BidWP +scmDbd+mQ1INuZBr9GBkD1brESh4vHtByPD6wkFKXlVkVTL49EQt8uBw8/0+uF0B +5a1aRQ09IkVPjluDMy2fc4VpgvkdnuXsMRD8vPk6gGzVlii72htGwYYWtIP9CgpY +trp85RxVGuqUTULFBOGXcc7YjfE1DWkPoeokCL8m7aVzdasZl+cl/Ick6rJueuQI +5ESvYKqRTfZ+oA8MapNtAZ7Nl8CT8VJoRyI6IQvPynRXCBK9D6gEAWc5l6Kv15Fl +73c8Q5I2oIaLOfeMYcZ1bL5Zvspa6Rsb5BtvOuOkacxx7GjMar1G2tUY4W3vFqn9 +yf8/Uc61LU6BYVvFh6DI6TwHp6xp/DrWZYhXCvNfirMn1NSw+8q0EEcIr2sUdkbx +gf2onMjtRP/Mki0oqkMTXnIsCzL/Y7D13GdouVqz0Ttbg/BEa8RnSaJxDIwQ1Wlz +VCC+oK/jTr+0pfP+3iR75WuGC0ce+muEN/L29H6wFk4N2oar/r0BYZZ6BtV9I9kS +8xnIxKvrcJ4O5dYy4f/lMeTRlPp6pz1jjtb6AVcNzHE +-> ssh-ed25519 +vdRnA qQe9nesjyr3dCtSa7xfgsw1RjKx5UGTzg+/XrcDzl0A +912JZmwcsvsg2D8G9LakTfOa70hCkk4DALZP1fKcw2A +--- GzPDMAdvn0Gvp+gqVd/1EKvMPtqPhIjpVYRDAcvhwaU +Ș x +ydOSa)avGჳMX %O=`~$  \ No newline at end of file