From fed32ecfca37be983e879a2aa80eb8ca1b6b300c Mon Sep 17 00:00:00 2001 From: Magic_RB Date: Mon, 12 Jun 2023 23:25:40 +0200 Subject: [PATCH] Possibly functional blowhole configuration Signed-off-by: Magic_RB --- flake.lock | 223 +++++++- flake.nix | 10 + nixos/modules/grafana.nix | 181 ++++++ nixos/modules/hashicorp-envoy.nix | 173 ++++++ nixos/modules/telegraf.nix | 72 +++ nixos/systems/blowhole/bind.nix | 81 +++ nixos/systems/blowhole/consul.nix | 80 +++ nixos/systems/blowhole/default.nix | 73 +++ nixos/systems/blowhole/filesystems.nix | 77 +++ nixos/systems/blowhole/firewall.nix | 303 ++++++++++ nixos/systems/blowhole/grub.nix | 10 + nixos/systems/blowhole/hardware.nix | 18 + nixos/systems/blowhole/hostapd.nix | 539 ++++++++++++++++++ nixos/systems/blowhole/ical2org.nix | 32 ++ nixos/systems/blowhole/klipper.nix | 534 +++++++++++++++++ nixos/systems/blowhole/monitoring.nix | 533 +++++++++++++++++ nixos/systems/blowhole/nas.nix | 108 ++++ nixos/systems/blowhole/networking.nix | 14 + nixos/systems/blowhole/nfs.nix | 62 ++ nixos/systems/blowhole/nixpkgs.nix | 18 + nixos/systems/blowhole/nomad.nix | 161 ++++++ nixos/systems/blowhole/users.nix | 19 + nixos/systems/blowhole/uterranix.nix | 66 +++ nixos/systems/blowhole/vault-agent.nix | 76 +++ nixos/systems/blowhole/vault.nix | 80 +++ nixos/systems/blowhole/watchdog.nix | 7 + nixos/systems/blowhole/zones/blocked.zone | 15 + .../blowhole/zones/hosts.in.redalder.org.zone | 16 + .../blowhole/zones/in.redalder.org.zone | 26 + nixos/systems/omen/hardware.nix | 2 +- overlays/ical2org.nix | 71 +++ overlays/zfs-relmount/default.nix | 11 + overlays/zfs-relmount/zfs-relmount.sh | 82 +++ patches/0001-intel_lar-and-noscan.patch | 159 ++++++ patches/999-hostapd-2.10-lar.patch | 106 ++++ patches/hostapd-2.10-lar.patch | 139 +++++ 36 files changed, 4164 insertions(+), 13 deletions(-) create mode 100644 nixos/modules/grafana.nix create mode 100644 nixos/modules/hashicorp-envoy.nix create mode 100644 nixos/modules/telegraf.nix create mode 100644 nixos/systems/blowhole/bind.nix create mode 100644 nixos/systems/blowhole/consul.nix create mode 100644 nixos/systems/blowhole/default.nix create mode 100644 nixos/systems/blowhole/filesystems.nix create mode 100644 nixos/systems/blowhole/firewall.nix create mode 100644 nixos/systems/blowhole/grub.nix create mode 100644 nixos/systems/blowhole/hardware.nix create mode 100644 nixos/systems/blowhole/hostapd.nix create mode 100644 nixos/systems/blowhole/ical2org.nix create mode 100644 nixos/systems/blowhole/klipper.nix create mode 100644 nixos/systems/blowhole/monitoring.nix create mode 100644 nixos/systems/blowhole/nas.nix create mode 100644 nixos/systems/blowhole/networking.nix create mode 100644 nixos/systems/blowhole/nfs.nix create mode 100644 nixos/systems/blowhole/nixpkgs.nix create mode 100644 nixos/systems/blowhole/nomad.nix create mode 100644 nixos/systems/blowhole/users.nix create mode 100644 nixos/systems/blowhole/uterranix.nix create mode 100644 nixos/systems/blowhole/vault-agent.nix create mode 100644 nixos/systems/blowhole/vault.nix create mode 100644 nixos/systems/blowhole/watchdog.nix create mode 100644 nixos/systems/blowhole/zones/blocked.zone create mode 100644 nixos/systems/blowhole/zones/hosts.in.redalder.org.zone create mode 100644 nixos/systems/blowhole/zones/in.redalder.org.zone create mode 100644 overlays/ical2org.nix create mode 100644 overlays/zfs-relmount/default.nix create mode 100644 overlays/zfs-relmount/zfs-relmount.sh create mode 100644 patches/0001-intel_lar-and-noscan.patch create mode 100644 patches/999-hostapd-2.10-lar.patch create mode 100644 patches/hostapd-2.10-lar.patch diff --git a/flake.lock b/flake.lock index 118156b..2b33359 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,24 @@ { "nodes": { + "deploy-rs": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": "nixpkgs_7", + "utils": "utils" + }, + "locked": { + "lastModified": 1648475189, + "narHash": "sha256-gAGAS6IagwoUr1B0ohE3iR6sZ8hP4LSqzYLC8Mq3WGU=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "83e0c78291cd08cb827ba0d553ad9158ae5a95c3", + "type": "github" + }, + "original": { + "id": "deploy-rs", + "type": "indirect" + } + }, "dwarffs": { "inputs": { "nix": "nix", @@ -40,6 +59,37 @@ } }, "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1648199409, + "narHash": "sha256-JwPKdC2PoVBkG6E+eWw3j6BMR6sL3COpYWfif7RVb8Y=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "64a525ee38886ab9028e6f61790de0832aa3ef03", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1627913399, + "narHash": "sha256-hY8g6H2KFL8ownSiFeMOjwPC8P0ueXpCVEbxgda3pko=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "12c64ca55c1014cdc1b16ed5a804aa8576601ff2", + "type": "github" + }, + "original": { + "id": "flake-compat", + "type": "indirect" + } + }, + "flake-compat_3": { "flake": false, "locked": { "lastModified": 1673956053, @@ -55,7 +105,7 @@ "type": "github" } }, - "flake-compat_2": { + "flake-compat_4": { "flake": false, "locked": { "lastModified": 1673956053, @@ -126,6 +176,20 @@ } }, "flake-utils_2": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_3": { "inputs": { "systems": "systems_2" }, @@ -143,7 +207,7 @@ "type": "github" } }, - "flake-utils_3": { + "flake-utils_4": { "locked": { "lastModified": 1667395993, "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", @@ -180,6 +244,22 @@ "type": "github" } }, + "gitignore-nix": { + "flake": false, + "locked": { + "lastModified": 1611672876, + "narHash": "sha256-qHu3uZ/o9jBHiA3MEKHJ06k7w4heOhA+4HCSIvflRxo=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "211907489e9f198594c0eb0ca9256a1949c9d412", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": "nixpkgs_2" @@ -231,6 +311,22 @@ "type": "github" } }, + "lowdown-src_2": { + "flake": false, + "locked": { + "lastModified": 1632468475, + "narHash": "sha256-NNOm9CbdA8cuwbvaBHslGbPTiU6bh1Ao+MpEPx4rSGo=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "6bd668af3fd098bdd07a1bedd399564141e275da", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, "nil": { "inputs": { "flake-utils": "flake-utils", @@ -270,6 +366,24 @@ "type": "indirect" } }, + "nix_2": { + "inputs": { + "lowdown-src": "lowdown-src_2", + "nixpkgs": "nixpkgs_8" + }, + "locked": { + "lastModified": 1633098935, + "narHash": "sha256-UtuBczommNLwUNEnfRI7822z4vPA7OoRKsgAZ8zsHQI=", + "owner": "nixos", + "repo": "nix", + "rev": "4f496150eb4e0012914c11f0a3ff4df2412b1d09", + "type": "github" + }, + "original": { + "id": "nix", + "type": "indirect" + } + }, "nixinate": { "inputs": { "nixpkgs": "nixpkgs_4" @@ -406,6 +520,19 @@ "type": "github" } }, + "nixpkgs_10": { + "locked": { + "lastModified": 1676569297, + "narHash": "sha256-2n4C4H3/U+3YbDrQB6xIw7AaLdFISCCFwOkcETAigqU=", + "path": "/nix/store/qhj65h4klgmnkblfly0apznnl3qdir6x-source", + "rev": "ac1f5b72a9e95873d1de0233fddcb56f99884b37", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1684570954, @@ -488,11 +615,43 @@ }, "nixpkgs_7": { "locked": { - "lastModified": 1676569297, - "narHash": "sha256-2n4C4H3/U+3YbDrQB6xIw7AaLdFISCCFwOkcETAigqU=", - "path": "/nix/store/qhj65h4klgmnkblfly0apznnl3qdir6x-source", - "rev": "ac1f5b72a9e95873d1de0233fddcb56f99884b37", - "type": "path" + "lastModified": 1648219316, + "narHash": "sha256-Ctij+dOi0ZZIfX5eMhgwugfvB+WZSrvVNAyAuANOsnQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "30d3d79b7d3607d56546dd2a6b49e156ba0ec634", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_8": { + "locked": { + "lastModified": 1632864508, + "narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "82891b5e2c2359d7e58d08849e4c89511ab94234", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-21.05-small", + "type": "indirect" + } + }, + "nixpkgs_9": { + "locked": { + "lastModified": 1632495107, + "narHash": "sha256-4NGE56r+FJGBaCYu3CTH4O83Ys4TrtnEPXrvdwg1TDs=", + "owner": "serokell", + "repo": "nixpkgs", + "rev": "be220b2dc47092c1e739bf6aaf630f29e71fe1c4", + "type": "github" }, "original": { "id": "nixpkgs", @@ -501,8 +660,8 @@ }, "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat_2", - "flake-utils": "flake-utils_3", + "flake-compat": "flake-compat_4", + "flake-utils": "flake-utils_4", "gitignore": "gitignore", "nixpkgs": [ "tuxedo-rs", @@ -537,6 +696,7 @@ "nixpkgs": "nixpkgs_6", "nixpkgs-hashicorp": "nixpkgs-hashicorp", "secret": "secret", + "serokell-nix": "serokell-nix", "tuxedo-nixos": "tuxedo-nixos", "tuxedo-rs": "tuxedo-rs", "udp-over-tcp": "udp-over-tcp", @@ -582,6 +742,30 @@ "type": "path" } }, + "serokell-nix": { + "inputs": { + "deploy-rs": "deploy-rs", + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "gitignore-nix": "gitignore-nix", + "nix": "nix_2", + "nixpkgs": "nixpkgs_9" + }, + "locked": { + "lastModified": 1665438610, + "narHash": "sha256-s8/jYo5qseJ4ilyAM2sz1mD5DBybSTrkfd4b9pkgdcU=", + "owner": "serokell", + "repo": "serokell.nix", + "rev": "a4def0b297a0ec69066747df909251a6a7555b1d", + "type": "github" + }, + "original": { + "owner": "serokell", + "ref": "magicrb-allow-wildcards-with-no-main", + "repo": "serokell.nix", + "type": "github" + } + }, "systems": { "locked": { "lastModified": 1681028828, @@ -630,7 +814,7 @@ }, "tuxedo-nixos": { "inputs": { - "flake-compat": "flake-compat", + "flake-compat": "flake-compat_3", "nixpkgs": [ "nixpkgs" ] @@ -651,7 +835,7 @@ }, "tuxedo-rs": { "inputs": { - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ], @@ -690,7 +874,7 @@ "uterranix": { "inputs": { "flake-parts": "flake-parts_2", - "nixpkgs": "nixpkgs_7", + "nixpkgs": "nixpkgs_10", "terranix": "terranix" }, "locked": { @@ -704,6 +888,21 @@ "type": "path" } }, + "utils": { + "locked": { + "lastModified": 1648297722, + "narHash": "sha256-W+qlPsiZd8F3XkzXOzAoR+mpFqzm3ekQkJNa+PIh1BQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "0f8662f1319ad6abf89b3380dd2722369fc51ade", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "vtermModule": { "flake": false, "locked": { diff --git a/flake.nix b/flake.nix index db11488..03efc46 100644 --- a/flake.nix +++ b/flake.nix @@ -12,6 +12,7 @@ nil.url = "github:oxalica/nil"; uterranix.url = "path:///home/main/uterranix"; dwarffs.url = "github:edolstra/dwarffs"; + serokell-nix.url = "github:serokell/serokell.nix?ref=magicrb-allow-wildcards-with-no-main"; tuxedo-rs.url = "github:AaronErhardt/tuxedo-rs"; tuxedo-rs.inputs.nixpkgs.follows = "nixpkgs"; @@ -42,6 +43,7 @@ nixos/systems/heater nixos/systems/toothpick nixos/systems/liveusb + nixos/systems/blowhole overlays/udp-over-tcp.nix overlays/emacsclient-remote @@ -49,10 +51,15 @@ overlays/emacs-rofi overlays/tree-sitter-grammars.nix overlays/emacs-master-nativecomp + overlays/zfs-relmount + overlays/ical2org.nix ]; flake.nixosModules = { hashicorp = nixos/modules/hashicorp.nix; + hashicorp-envoy = nixos/modules/hashicorp-envoy.nix; + telegraf = nixos/modules/telegraf.nix; + grafana = nixos/modules/grafana.nix; }; flake.apps = inputs.nixpkgs.lib.genAttrs config.systems (system: { @@ -62,6 +69,9 @@ flake.patches = { hashicorp-nomad.revert-change-consul-si-tokens-to-be-local = patches/0001-Revert-Change-consul-SI-tokens-to-be-local.patch; hashicorp-nomad.add-nix-integration = patches/0001-Add-Nix-integration.patch; + hostapd.intel_lar-and-noscan = patches/0001-intel_lar-and-noscan.patch; + hostapd.hostapd-2_10-lar = patches/999-hostapd-2.10-lar.patch; + hostapd.hostapd-2_10-lar-2 = patches/hostapd-2.10-lar.patch; }; systems = [ diff --git a/nixos/modules/grafana.nix b/nixos/modules/grafana.nix new file mode 100644 index 0000000..726e54b --- /dev/null +++ b/nixos/modules/grafana.nix @@ -0,0 +1,181 @@ +{ options, config, lib, pkgs, ... }: +let + inherit (lib) + mkEnableOption + mkOption + literalExpression + types + mkDefault + mkIf + recursiveUpdate + ; + + cfg = config.services.grafana-magic; + settingsFile = settingsFormatIni.generate "config.ini" (recursiveUpdate cfg.settings { + paths.provisioning = "/etc/grafana.d/provisioning"; + }); + + provisioningSettingsFormat = pkgs.formats.yaml {}; + settingsFormatIni = pkgs.formats.ini {}; +in { + options.services.grafana-magic = { + enable = mkEnableOption (lib.mdDoc "grafana"); + + package = mkOption { + description = lib.mdDoc "Package to use."; + default = pkgs.grafana; + defaultText = literalExpression "pkgs.grafana"; + type = types.package; + }; + + dataDir = mkOption { + description = lib.mdDoc "Data directory."; + default = "/var/lib/grafana"; + type = types.path; + }; + + settings = mkOption { + description = lib.mdDoc '' + Grafana settings. See + for available options. INI format is used. + ''; + + type = types.submodule { + freeformType = settingsFormatIni.type; + + options = { + paths.provisioning = mkOption { + type = types.submodule { + options = + let + provisioningOption = name: cname: + mkOption { + type = types.submodule { + options = { + apiVersion = mkOption { + type = types.int; + default = 1; + }; + + "delete${cname}" = mkOption { + type = provisioningSettingsFormat.type; + default = []; + }; + + "${name}" = mkOption { + type = provisioningSettingsFormat.type; + default = []; + }; + }; + }; + default = {}; + }; + in + { + datasources = provisioningOption "datasources" "Datasources"; + plugins = provisioningOption "plugins" "Plugins"; + dashboards = provisioningOption "dashboards" "Dashboards"; + notifiers = provisioningOption "notifiers" "Notifiers"; + alerting = provisioningOption "alerting" "Alerting"; + }; + }; + default = {}; + apply = x: + let + ln = name: + '' + mkdir -p $out/${name} + ln -s ${provisioningSettingsFormat.generate "config.yaml" x.${name}} $out/${name}/config.yaml + ''; + in + pkgs.runCommand "grafana-provisioning" {} '' + ${ln "datasources"} + ${ln "notifiers"} + ${ln "alerting"} + ${ln "plugins"} + ${ln "dashboards"} + ''; + }; + }; + }; + + default = {}; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + services.grafana-magic.settings = { + server = { + static_root_path = "${cfg.package}/share/grafana/public"; + http_port = mkDefault 3000; + protocol = mkDefault "http"; + }; + }; + + environment.etc."grafana.d/main.ini" = { + source = settingsFile; + }; + + environment.etc."grafana.d/provisioning" = { + source = cfg.settings.paths.provisioning; + }; + + systemd.services.grafana = { + description = "Grafana Service Daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "networking.target" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${settingsFile}"; + WorkingDirectory = cfg.dataDir; + User = "grafana"; + RuntimeDirectory = "grafana"; + RuntimeDirectoryMode = "0755"; + # Hardening + AmbientCapabilities = lib.mkIf (cfg.settings.server.http_port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = if (cfg.settings.server.http_port < 1024) then [ "CAP_NET_BIND_SERVICE" ] else [ "" ]; + DeviceAllow = [ "" ]; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + # Upstream grafana is not setting SystemCallFilter for compatibility + # reasons, see https://github.com/grafana/grafana/pull/40176 + SystemCallFilter = [ + "@system-service" + "~@privileged" + ] ++ lib.optional (cfg.settings.server.protocol == "socket") [ "@chown" ]; + UMask = "0027"; + }; + preStart = '' + ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir} + ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir} + ''; + }; + + users.users.grafana = { + uid = config.ids.uids.grafana; + description = "Grafana user"; + home = cfg.dataDir; + createHome = true; + group = "grafana"; + }; + users.groups.grafana = {}; + }; +} diff --git a/nixos/modules/hashicorp-envoy.nix b/nixos/modules/hashicorp-envoy.nix new file mode 100644 index 0000000..978244e --- /dev/null +++ b/nixos/modules/hashicorp-envoy.nix @@ -0,0 +1,173 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.hashicorp-envoy; + + serviceFormat = pkgs.formats.json {}; + serviceFile = name: value: + if value.type == "normal" then + serviceFormat.generate "${name}-service.json" { service = value.service; } + else + serviceFormat.generate "${name}-service.json" value.service; +in +{ + options.services.hashicorp-envoy = mkOption { + description = mdDoc '' + ''; + type = types.attrsOf (types.submodule { + options = { + service = mkOption { + description = mdDoc '' + ''; + type = with types; oneOf [ serviceFormat.type (listOf serviceFormat.type) ]; + }; + + type = mkOption { + description = mdDoc '' + ''; + type = with types; enum [ "ingress" "terminating" "normal" ]; + default = "normal"; + }; + + environment = mkOption { + description = mdDoc '' + ''; + type = with types; attrsOf str; + default = {}; + }; + + adminBind = mkOption { + description = mdDoc '' + ''; + type = types.str; + }; + + address = mkOption { + description = mdDoc '' + ''; + type = types.str; + default = "0.0.0.0:19000"; + }; + + + drainTime = mkOption { + description = mdDoc '' + ''; + type = types.int; + default = 15; + }; + + parentShutdownTime = mkOption { + description = mdDoc '' + ''; + type = types.int; + default = 20; + }; + + hotRestart = mkOption { + description = mdDoc '' + ''; + type = types.bool; + default = false; + }; + + consulPackage = mkOption { + description = mdDoc '' + ''; + type = types.package; + default = pkgs.consul; + }; + + envoyPackage = mkOption { + description = mdDoc '' + ''; + type = types.package; + default = pkgs.envoy; + }; + }; + }); + default = {}; + }; + + config = { + systemd.services = flip mapAttrs' cfg (name: value: + nameValuePair + "hashicorp-envoy-${name}" + { + description = name; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + + path = [ value.envoyPackage ]; + + restartIfChanged = true; + + preStart = + if value.type == "normal" then + '' + ${value.consulPackage}/bin/consul services register ${serviceFile name value} + '' + else + '' + ${value.consulPackage}/bin/consul config write ${serviceFile name value} + ''; + postStop = + if value.type == "normal" then + '' + ${value.consulPackage}/bin/consul services deregister -id=${value.service.id} + '' + else + '' + ${value.consulPackage}/bin/consul config delete -filename ${serviceFile name value} + ''; + script = + let + startEnvoy = pkgs.writeShellScript "start_envoy_${name}.sh" + '' + exec ${value.consulPackage}/bin/consul connect envoy \ + ${optionalString (value.type == "normal") '' + -sidecar-for ${value.service.id} \ + ''} \ + ${optionalString (value.type == "ingress") '' + -gateway=ingress \ + -register \ + -service ${value.service.name} \ + ''} \ + -admin-bind ${value.adminBind} \ + -address ${value.address} \ + ${optionalString value.hotRestart '' + -- \ + $([[ $RESTART_EPOCH == 0 ]] && printf -- "--use-dynamic-base-id --base-id-path $RUNTIME_DIRECTORY/id") \ + $([[ $RESTART_EPOCH == 0 ]] || printf -- "--base-id $(cat $RUNTIME_DIRECTORY/id)") \ + --restart-epoch $RESTART_EPOCH \ + --drain-time-s ${toString value.drainTime} \ + --parent-shutdown-time-s ${toString value.parentShutdownTime} + ''} + ''; + in + if value.hotRestart then + "exec ${pkgs.python3}/bin/python ${value.envoyPackage.src}/restarter/hot-restarter.py ${startEnvoy}" + else + "exec ${startEnvoy}"; + + environment = value.environment; + + serviceConfig = { + ExecReload = if value.hotRestart then "${pkgs.coreutils}/bin/kill -HUP $MAINPID" else null; + KillMode = "control-group"; + KillSignal = "SIGINT"; + LimitNOFILE = 65536; + LimitNPROC = "infinity"; + OOMScoreAdjust = -1000; + Restart = "always"; + RestartSec = 2; + TasksMax = "infinity"; + + RuntimeDirectory = name; + }; + } + ); + }; +} diff --git a/nixos/modules/telegraf.nix b/nixos/modules/telegraf.nix new file mode 100644 index 0000000..dd80b62 --- /dev/null +++ b/nixos/modules/telegraf.nix @@ -0,0 +1,72 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.telegraf-magic; + + settingsFormat = pkgs.formats.toml {}; + configFile = settingsFormat.generate "config.toml" cfg.settings; +in { + options = { + services.telegraf-magic = { + enable = mkEnableOption (lib.mdDoc "telegraf server"); + + package = mkOption { + default = pkgs.telegraf; + defaultText = literalExpression "pkgs.telegraf"; + description = lib.mdDoc "Which telegraf derivation to use"; + type = types.package; + }; + + settings = mkOption { + default = {}; + description = lib.mdDoc "Extra configuration options for telegraf"; + type = settingsFormat.type; + example = { + outputs.influxdb = { + urls = ["http://localhost:8086"]; + database = "telegraf"; + }; + inputs.statsd = { + service_address = ":8125"; + delete_timings = true; + }; + }; + }; + + systemd = mkOption { + default = {}; + description = lib.mdDoc "Applied to `systemd.services.telegraf`."; + type = types.unspecified; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.telegraf = mkMerge [ + (cfg.systemd) + { + description = "Telegraf Agent"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + ExecStart="${cfg.package}/bin/telegraf -config ${configFile}"; + ExecReload="${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + RuntimeDirectory = "telegraf"; + User = "telegraf"; + Group = "telegraf"; + Restart = "on-failure"; + # for ping probes + AmbientCapabilities = [ "CAP_NET_RAW" ]; + }; + } + ]; + + users.users.telegraf = { + uid = config.ids.uids.telegraf; + group = "telegraf"; + description = "telegraf daemon user"; + }; + + users.groups.telegraf = {}; + }; +} diff --git a/nixos/systems/blowhole/bind.nix b/nixos/systems/blowhole/bind.nix new file mode 100644 index 0000000..1256be3 --- /dev/null +++ b/nixos/systems/blowhole/bind.nix @@ -0,0 +1,81 @@ +{ lib, pkgs, secret, ... }: +let + inherit (lib) + concatMapStringsSep; + + loggingConfig = '' + logging { + ${concatMapStringsSep "\n" (x: + '' + channel ${x}_file { + file "/var/log/named/${x}.log" versions 3 size 5m; + severity dynamic; + print-time yes; + }; + category ${x} { ${x}_file; }; + '') [ + "default" + "database" + "security" + "config" + "resolver" + "xfer-in" + "xfer-out" + "notify" + "client" + "unmatched" + "queries" + "network" + "update" + "network" + "dispatch" + "dnssec" + "lame-servers" + ]} + }; + ''; +in +{ + systemd.tmpfiles.rules = [ + "d /var/log/named 0750 named named - -" + ]; + + services.bind = { + enable = true; + forward = "only"; + forwarders = [ + "127.0.0.1 port 5353" + ]; + + directory = "/var/lib/bind"; + zones = { + "in.redalder.org" = { + file = ./zones/in.redalder.org.zone; + master = true; + }; + "hosts.in.redalder.org" = { + file = ./zones/hosts.in.redalder.org.zone; + master = true; + }; + }; + + cacheNetworks = [ + "127.0.0.0/8" + (secret.network.networks.home.wireless or "") + (secret.network.networks.home.mine or "") + "10.64.99.0/24" + (secret.network.networks.home.amsterdam or "") + (secret.network.networks.vpn or "") + "172.26.64.0/20" + ]; + extraConfig = loggingConfig; + extraOptions = '' + # recursion yes; + dnssec-validation auto; + ''; + }; + + systemd.services.bind = { + before = [ "network-online.target" ]; + }; +} diff --git a/nixos/systems/blowhole/consul.nix b/nixos/systems/blowhole/consul.nix new file mode 100644 index 0000000..538b86d --- /dev/null +++ b/nixos/systems/blowhole/consul.nix @@ -0,0 +1,80 @@ +{inputs', lib, config, pkgs, secret, ...}: +let + inherit (lib) + singleton + mkForce; +in +{ + services.hashicorp.vault-agent = { + settings.template = singleton { + source = pkgs.writeText "consul.json.vtmpl" + '' + { + "encrypt": "{{ with secret "kv/data/homelab-1/blowhole/consul/encryption_key" }}{{ or .Data.data.key "" }}{{ end }}", + "acl": { + "tokens": { + "agent": "{{ with secret "kv/data/homelab-1/blowhole/consul/agent_token" }}{{ or .Data.data.secret "" }}{{ end }}", + "default": "{{ with secret "kv/data/homelab-1/blowhole/consul/anonymous_token" }}{{ or .Data.data.secret "" }}{{ end }}" + } + } + } + ''; + destination = "/run/secrets/consul.json"; + command = pkgs.writeShellScript "consul-command" '' + sudo systemctl try-reload-or-restart hashicorp-consul.service + ''; + }; + }; + + systemd.services.hashicorp-consul.unitConfig = { + ConditionPathExists = "/run/secrets/consul.json"; + }; + + services.hashicorp.consul = { + enable = true; + + extraSettingsPaths = singleton "/run/secrets/consul.json"; + package = inputs'.nixpkgs-hashicorp.legacyPackages.${pkgs.stdenv.system}.consul; + + settings = { + datacenter = "homelab-1"; + data_dir = "/var/lib/consul"; + log_level = "INFO"; + + server = true; + + bind_addr = secret.network.ips.blowhole.ip or ""; + client_addr = secret.network.ips.blowhole.ip or ""; + + primary_datacenter = "homelab-1"; + + acl = { + enabled = true; + default_policy = "deny"; + enable_token_persistence = true; + }; + + ports = { + http = 8500; + grpc = 8502; + }; + + connect.enabled = true; + + ca_file = "/var/secrets/consul-ca.crt"; + # cert_file = "" + # key_file = "" + verify_incoming = false; + verify_outgoing = false; + verify_server_hostname = false; + + ui_config.enabled = true; + domain = "consul.in.redalder.org"; + }; + }; + + systemd.services.hashicorp-consul.serviceConfig = { + LimitNOFILE = mkForce "infinity"; + LimitNPROC = mkForce "infinity"; + }; +} diff --git a/nixos/systems/blowhole/default.nix b/nixos/systems/blowhole/default.nix new file mode 100644 index 0000000..5f12477 --- /dev/null +++ b/nixos/systems/blowhole/default.nix @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: 2022 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ inputs, lib, config, ... }: +let + inherit (lib) + flip + mapAttrs + singleton; + + config' = config; +in +{ + flake.nixosConfigurations.blowhole = inputs.nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + + specialArgs = { + config' = config'; + inputs' = inputs; + secret = + if builtins.pathExists "${inputs.secret}/default.nix" then + import inputs.secret { inherit lib; } + else + {}; + }; + + modules = singleton + ({ pkgs, config, ... }: + { + imports = [ + ./bind.nix + ./consul.nix + ./filesystems.nix + ./firewall.nix + ./grub.nix + ./hardware.nix + ./hostapd.nix + ./ical2org.nix + ./klipper.nix + ./monitoring.nix + ./nas.nix + ./networking.nix + ./nfs.nix + ./nomad.nix + ./uterranix.nix + ./vault-agent.nix + ./vault.nix + ./watchdog.nix + ./nixpkgs.nix + ./users.nix + ../../common/remote_access.nix + inputs.serokell-nix.nixosModules.acme-sh + config'.flake.nixosModules.hashicorp + config'.flake.nixosModules.hashicorp-envoy + config'.flake.nixosModules.telegraf + config'.flake.nixosModules.grafana + ]; + + _module.args.nixinate = { + host = "blowhole.hosts.in.redalder.org"; + sshUser = "main"; + buildOn = "local"; + substituteOnTarget = true; + hermetic = false; + nixOptions = [ + "--override-input secret path://$HOME/dotfiles/secret" + ]; + }; + + system.stateVersion = "21.05"; + }); + }; +} diff --git a/nixos/systems/blowhole/filesystems.nix b/nixos/systems/blowhole/filesystems.nix new file mode 100644 index 0000000..880293d --- /dev/null +++ b/nixos/systems/blowhole/filesystems.nix @@ -0,0 +1,77 @@ +{ pkgs, lib, secret, ... }: +let + inherit (lib) + singleton; +in +{ + environment.systemPackages = with pkgs; [ + sshfs + ]; + + fileSystems = + { + "/boot" = { + device = "/dev/disk/by-uuid/738acc32-3e2e-4986-987c-40264153d5bf"; + fsType = "ext4"; + }; + "/" = { + device = "blowhole-zpool/local/root"; + fsType = "zfs"; + }; + "/nix" = { + device = "blowhole-zpool/local/nix"; + fsType = "zfs"; + }; + + "/var/nfs" = { + device = "/dev/disk/by-uuid/e06f6d2c-e434-4eec-b00d-b13c1ecc96f0"; + fsType = "btrfs"; + options = [ + "subvol=/nfs" + "noatime" + ]; + }; + + "/mnt/cctv" = { + device = "camera@${secret.network.ips.woodchip or ""}:/"; + fsType = "fuse.sshfs"; + noCheck = true; + options = [ + "_netdev" + "noauto" + "x-systemd.automount" + "IdentityFile=/run/secrets/id_ed_camera" + "StrictHostKeyChecking=no" + "allow_other" + "reconnect" + "Port=2522" + ]; + }; + + "/old-root" = { + device = "/dev/disk/by-uuid/e06f6d2c-e434-4eec-b00d-b13c1ecc96f0"; + fsType = "btrfs"; + options = [ + "subvol=/arch" + "noatime" + ]; + }; + "/var/lib/nomad" = { + device = "blowhole-zpool/persist/nomad"; + fsType = "zfs"; + }; + "/var/secrets" = { + device = "blowhole-zpool/persist/secrets"; + fsType = "zfs"; + }; + "/var/lib/consul" = { + device = "/old-root/var/lib/consul"; + options = singleton "bind"; + }; + "/var/lib/vault" = { + device = "/old-root/var/lib/vault"; + options = singleton "bind"; + }; + } + // secret.mounts.blowhole or {}; +} diff --git a/nixos/systems/blowhole/firewall.nix b/nixos/systems/blowhole/firewall.nix new file mode 100644 index 0000000..a121cd4 --- /dev/null +++ b/nixos/systems/blowhole/firewall.nix @@ -0,0 +1,303 @@ +{ pkgs, secret, config, lib, ... }: +let + inherit (lib) + mapAttrs + const + mkForce + singleton; + + wlan = "wlp10s0"; + lan = "enp8s0f1"; + wan = "enp3s0"; + doVPN = "do_vpn0"; + + nomad = mapAttrs (const toString) { + inherit (config.services.hashicorp.nomad.settings.client) + min_dynamic_port + max_dynamic_port; + }; +in +{ + boot.kernel.sysctl = { + # Enable forwarding on IPv4 but disable on IPv6 + "net.ipv4.conf.all.forwarding" = true; + "net.ipv6.conf.all.forwarding" = false; + + # source: https://github.com/mdlayher/homelab/blob/master/nixos/routnerr-2/configuration.nix#L52 + # By default, not automatically configure any IPv6 addresses. + "net.ipv6.conf.all.accept_ra" = 0; + "net.ipv6.conf.all.autoconf" = 0; + "net.ipv6.conf.all.use_tempaddr" = 0; + + # On WAN, allow IPv6 autoconfiguration and tempory address use. + # "net.ipv6.conf.${name}.accept_ra" = 2; + # "net.ipv6.conf.${name}.autoconf" = 1; + }; + + services.dnscrypt-proxy2 = { + enable = true; + upstreamDefaults = true; + settings = { + listen_addresses = singleton "127.0.0.1:5353"; + + dnscrypt_servers = false; + doh_servers = true; + odoh_servers = false; + + block_ipv6 = true; + + static."mullvad".stamp = "sdns://AgcAAAAAAAAAAAAPZG9oLm11bGx2YWQubmV0Ci9kbnMtcXVlcnk"; + static."meganerd".stamp = "sdns://AQcAAAAAAAAADjEzNi4yNDQuOTcuMTE0ICif6V9M6EF_9Xo_MHwkDN4ZJjERopSJN8hBuUWg9YeMJTIuZG5zY3J5cHQtY2VydC5jaGV3YmFjY2EubWVnYW5lcmQubmw"; + sources = {}; + }; + }; + + systemd.services.dnscrypt-proxy2 = { + before = [ "network-online.target" ]; + }; + + services.dhcpd4 = { + enable = true; + interfaces = [ "${lan}" "${wlan}" ]; + extraConfig = '' + option domain-name-servers ${secret.network.ips.blowhole.ip or ""}; + option subnet-mask 255.255.255.0; + + ${secret.dhcp.blowhole.zones or (const "") { inherit wlan lan; }} + ''; + }; + + networking = { + useDHCP = false; + hostName = "blowhole"; + + resolvconf.useLocalResolver = false; + nameservers = singleton (secret.network.ips.blowhole.ip or ""); + + # Disable the in-built iptable based firewall + firewall.enable = mkForce false; + + localCommands = '' + ip link add enp4s0 type dummy || true + ip link set enp4s0 up || true + ip addr add ${secret.network.ips.blowhole.ip}/24 dev enp4s0 || true + ''; + + interfaces = { + # Don't do DHCP on the LAN interface + "${lan}" = { + useDHCP = false; + ipv4.addresses = [{ + address = secret.network.ips.blowhole.ip or ""; + prefixLength = 24; + }]; + }; + "${wlan}" = { + useDHCP = false; + ipv4.addresses = [{ + address = secret.network.ips.blowhole.wlan or ""; + prefixLength = 24; + }]; + }; + # But do DHCP on the WAN interface + "${wan}".useDHCP = true; + }; + + wireguard = { + enable = true; + interfaces."${doVPN}" = + secret.wireguard."${config.networking.hostName}" or {} // { + listenPort = 6666; + privateKeyFile = "/var/secrets/${doVPN}.key"; + }; + }; + + nftables = { + enable = true; + ruleset = '' + table ip nf_filter { + chain input_out { + ct state { established, related } accept comment "Allow established traffic" + icmp type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP" + } + + chain input_doVPN { + tcp dport { 4646, 4647, 4648 } accept comment "Nomad traffic" + tcp dport { 8600, 8500, 8502, 8300, 8301, 8302 } accept comment "Consul traffic" + tcp dport { 8200 } accept comment "Vault traffic" + tcp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept comment "NFS traffic" + tcp dport ${nomad.min_dynamic_port}-${nomad.max_dynamic_port} accept comment "Consul Connect sidecar traffic" + tcp dport { 53 } accept comment "DNS traffic" + tcp dport { 80 } accept comment "HTTP traffic" + + udp dport { 8600, 8301, 8302 } comment "Consul traffic" + udp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept comment "NFS traffic" + udp dport ${nomad.min_dynamic_port}-${nomad.max_dynamic_port} accept comment "Consul Connect sidecar traffic" + udp dport { 53 } accept comment "DNS traffic" + } + + chain input { + type filter hook input priority 0; policy drop; + + tcp dport 22 accept comment "Accept SSH traffic always" + iifname != "lo" tcp dport 5353 drop comment "Drop traffic to dnscrypt-proxy always except for localhost to localhost traffic" + + iifname { "nomad", "ve-monitor", "ve-klipper" } oifname { "nomad", "ve-monitor", "ve-klipper" } accept comment "Allow Nomad to do whatever it wants in its interface" + iifname { "${wlan}", "${lan}", "lo" } accept comment "Allow local network to access the router" + iifname { "${wan}", "${doVPN}", "nomad", "docker0", "ve-monitor", "ve-klipper" } jump input_out + iifname { "${doVPN}" } jump input_doVPN + + # Allow containers to reach the DNS server + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } tcp dport 53 accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } udp dport 53 accept + + # Allow Nomad Containers to reach Nomad + iifname { "nomad" } tcp dport 4646 accept + + # Allow proxies to reach consul + iifname { "nomad", "ve-monitor", "ve-klipper" } tcp dport 8500 accept + iifname { "ve-monitor", "ve-klipper" } tcp dport 8502 accept + + # Allow containers to reach the NFS server + iifname { "docker0" } tcp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept comment "NFS traffic" + iifname { "docker0" } udp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept comment "NFS traffic" + + meta nftrace set 1 + } + + chain output { + type filter hook output priority 0; policy accept; + + # Drop all DNS traffic if leaving through "wan" + # oifname { "${wan}" } tcp dport 53 drop + # oifname { "${wan}" } udp dport 53 drop + # Allow DoT traffic to leave through "wan" if it comes from "lo" + # iifname != { "lo" } oifname { "${wan}" } tcp dport 853 drop + } + + chain forward { + type filter hook forward priority 10; policy drop; + + # Enable flow offloading for better throughput + # ip protocol { tcp, udp } flow offload @f + + # Drop all DNS or DoT traffic if forwarded through "wan" + oifname { "${wan}" } tcp dport 853 drop + oifname { "${wan}" } tcp dport 53 drop + oifname { "${wan}" } udp dport 53 drop + + # Allow trusted LAN to WAN" + iifname { "${lan}", "${wlan}" } oifname { "${wan}" } accept + iifname { "${wan}" } oifname { "${lan}", "${wlan}" } ct state established, related accept + + + iifname { "nomad" } oifname { "${doVPN}", "${lan}", "${wlan}" } accept + iifname { "${doVPN}", "${lan}", "${wlan}" } oifname { "nomad" } accept + iifname { "${doVPN}" } oifname { "${lan}", "${wlan}" } accept + iifname { "${lan}", "${wlan}" } oifname { "${doVPN}" } accept + + # Allow containers to reach WAN + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${wan}" } accept + iifname { "${wan}" } oifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } ct state established, related accept + + # Allow containers to reach the DNS and NFS server + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip daddr 10.64.2.1 tcp dport { 53 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip saddr 10.64.2.1 tcp sport { 53 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip daddr 10.64.2.1 tcp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip saddr 10.64.2.1 tcp sport { 111, 2049, 4000, 4001, 4002, 20048 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip daddr 10.64.2.1 udp dport { 53 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip saddr 10.64.2.1 udp sport { 53 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip daddr 10.64.2.1 udp dport { 111, 2049, 4000, 4001, 4002, 20048 } accept + iifname { "nomad", "docker0", "ve-monitor", "ve-klipper" } oifname { "${lan}" } ip saddr 10.64.2.1 udp sport { 111, 2049, 4000, 4001, 4002, 20048 } accept + + + # Rules to make CNI happy + meta mark and 0x01 == 0x01 accept + + meta nftrace set 1 + } + } + + table ip nf_nat { + chain postrouting { + type nat hook postrouting priority 100; policy accept; + oifname "${wan}" masquerade + } + + chain prerouting { + type nat hook prerouting priority 100; policy accept; + } + } + + table ip6 nf_filter { + chain output { + type filter hook output priority 0; policy drop; + + meta nftrace set 1 + + oifname "lo" icmpv6 type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP" + oifname "lo" ip6 saddr "::1" ip6 daddr "::1" reject + } + chain input { + type filter hook input priority 0; policy drop; + + meta nftrace set 1 + + iifname "lo" icmpv6 type { echo-request, destination-unreachable, time-exceeded } counter accept comment "Allow select ICMP" + } + chain forward { + type filter hook forward priority 0; policy drop; + } + } + ''; + }; + }; + + systemd.services.nftables = { + serviceConfig = + let + rulesScript = pkgs.writeShellScript "nftables-rules" '' + set -ex + export PATH=${pkgs.nftables}/bin:${pkgs.iptables}/bin:${pkgs.bash}/bin:$PATH + + tmpfile="$(mktemp)" + iptables-save -t filter >> $tmpfile + iptables-save -t nat >> $tmpfile + + nft flush ruleset + + cat $tmpfile | iptables-restore + nft -f "${pkgs.writeText "nftables-rules" config.networking.nftables.ruleset}" + rm $tmpfile + + iptables -D FORWARD -j MARK --set-mark 0x01 || true + iptables -D FORWARD -j MARK --set-mark 0x00 || true + + iptables -I FORWARD -j MARK --set-mark 0x01 + iptables -A FORWARD -j MARK --set-mark 0x00 + ''; + in { + ExecStart = mkForce rulesScript; + ExecReload = mkForce rulesScript; + ExecStop = mkForce (pkgs.writeShellScript "nftables-flush" '' + set -ex + export PATH=${pkgs.nftables}/bin:${pkgs.iptables}/bin:${pkgs.bash}/bin:$PATH + + tmpfile="$(mktemp)" + iptables-save -t filter >> $tmpfile + iptables-save -t nat >> $tmpfile + + nft flush ruleset + + cat $tmpfile | iptables-restore + rm $tmpfile + + iptables -D FORWARD -j MARK --set-mark 0x01 || true + iptables -D FORWARD -j MARK --set-mark 0x00 || true + + iptables -I FORWARD -j MARK --set-mark 0x01 + iptables -A FORWARD -j MARK --set-mark 0x00 + ''); + }; + }; +} diff --git a/nixos/systems/blowhole/grub.nix b/nixos/systems/blowhole/grub.nix new file mode 100644 index 0000000..c4f4218 --- /dev/null +++ b/nixos/systems/blowhole/grub.nix @@ -0,0 +1,10 @@ +{ ... }: +{ + boot.loader = { + systemd-boot.enable = false; + grub = { + enable = true; + devices = [ "/dev/disk/by-id/usb-Verbatim_STORE_N_GO_072124E3712B7287-0:0" ]; + }; + }; +} diff --git a/nixos/systems/blowhole/hardware.nix b/nixos/systems/blowhole/hardware.nix new file mode 100644 index 0000000..74175bf --- /dev/null +++ b/nixos/systems/blowhole/hardware.nix @@ -0,0 +1,18 @@ +{ config, ... }: +{ + boot = { + supportedFilesystems = ["zfs"]; + initrd.availableKernelModules = [ + "xhci_pci" + "ahci" + "usbhid" + "usb_storage" + "sd_mod" + "nvme" + ]; + zfs.enableUnstable = true; + kernelPackages = config.boot.zfs.package.latestCompatibleLinuxPackages; + }; + + hardware.enableRedistributableFirmware = true; +} diff --git a/nixos/systems/blowhole/hostapd.nix b/nixos/systems/blowhole/hostapd.nix new file mode 100644 index 0000000..9b23fd2 --- /dev/null +++ b/nixos/systems/blowhole/hostapd.nix @@ -0,0 +1,539 @@ +{ pkgs, config, lib, config', ... }: +let + inherit (lib) + singleton; + + openwrtRepo = pkgs.fetchFromGitHub { + owner = "openwrt"; + repo = "openwrt"; + rev = "67e8cc07f9bb95984624198ccf02123f348246df"; + sha256 = "sha256-rBQDTUG9fqwSLrj+LZ6L1x55Y3gkfUubY5zwX9XK3+s="; + }; +in +{ + # giturl="https://raw.githubusercontent.com/openwrt/openwrt/75b83e94a395fedeb4d308f42013a72c6fee2df4/package/network/services/hostapd/patches/" + # for patch in *.patch + # do + # nix-prefetch-url "$giturl$patch" 2>/dev/null | \ + # sed -e 's~^~{ url = "'"$giturl$patch"'"; sha256 = "~' | sed -e 's~$~"; \}~' + # done + + services.hashicorp.vault-agent.settings.template = singleton { + source = pkgs.writeText "hostapd_wpa_psk.vtmpl" '' + {{ with secret "kv/data/homelab-1/blowhole/hostapd/wpa_psk" -}} + {{ range $key, $value := .Data.data -}} + {{ with $data := $value -}} + {{ $data.mac_address }} {{ $data.psk }} + {{ end -}} + {{ end -}} + {{ end -}} + ''; + destination = "/run/secrets/hostapd_wpa_psk"; + }; + + systemd.services.hostapd.unitConfig = { + ConditionPathExists = "/run/secrets/hostapd_wpa_psk"; + }; + + services.hostapd = { + interface = "wlp10s0"; + driver = "nl80211"; + ssid = "nothing"; + wpa = false; + hwMode = "g"; + channel = 14; + countryCode = "NL"; + enable = true; + extraConfig = '' + wpa_psk_file=/run/secrets/hostapd_wpa_psk + + #ieee80211d=1 + #ieee80211h=1 + #ieee80211n=1 + # ieee80211ac=1 + + # intel_lar=1 + noscan=0 + beacon_int=100 + channel=14 + # channel=149 + # chanlist=149 + + #ht_capab=[HT40+][LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935][DSSS_CCK-40] + #ht_capab=[VHT_IBSS][RRM][MU_MIMO_AIR_SNIFFER][SCAN_START_TIME][BSS_PARENT_TSF][FILS_STA][FILS_MAX_CHANNEL_TIME][ACCEPT_BCAST_PROBE_RESP][OCE_PROBE_REQ_HIGH_TX_RATE][CONTROL_PORT_OVER_NL80211][TXQS][ENABLE_FTM_RESPONDER][CONTROL_PORT_NO_PREAUTH][PROTECTED_TWT][DEL_IBSS_STA][BEACON_PROTECTION_CLIENT][SCAN_FREQ_KHZ][CONTROL_PORT_OVER_NL80211_TX_STATUS] + + ap_isolate=1 + preamble=1 + wmm_enabled=1 + utf8_ssid=1 + + auth_algs=1 + wpa=2 + wpa_pairwise=CCMP + wpa_disable_eapol_key_retries=0 + wpa_key_mgmt=WPA-PSK + okc=0 + disable_pmksa_caching=1 + + bssid=e0:d0:45:81:50:00 + + # bss=wlp10s1 + # ssid=nothing2 + # bssid=e0:d0:45:81:50:01 + + # hw_mode=g + # channel=9 + # vht_capab= + # ht_capab= + + # auth_algs=1 + # wpa=2 + # wpa_pairwise=CCMP + # wpa_disable_eapol_key_retries=0 + # wpa_key_mgmt=WPA-PSK + # okc=0 + # disable_pmksa_caching=1 + # wpa_passphrase=${config.services.hostapd.wpaPassphrase} + ''; + }; + + nixpkgs.overlays = singleton + (final: prev: + { + hostapd = prev.hostapd.overrideAttrs (old: { + buildInputs = old.buildInputs ++ (with pkgs; [ + libubox + ubus + ]); + + src = pkgs.fetchgit { + url = "http://w1.fi/hostap.git"; + rev = "bb945b98fefc64887dffb40773a19d77585cee42"; + sha256 = "sha256-bDxMWjvgyNdBUH8pXJ+yMl3vBBoz57LOJEZHAGSDyS0="; + }; + + extraConfig = '' + # Example hostapd build time configuration + # + # This file lists the configuration options that are used when building the + # hostapd binary. All lines starting with # are ignored. Configuration option + # lines must be commented out complete, if they are not to be included, i.e., + # just setting VARIABLE=n is not disabling that variable. + # + # This file is included in Makefile, so variables like CFLAGS and LIBS can also + # be modified from here. In most cass, these lines should use += in order not + # to override previous values of the variables. + + # Driver interface for Host AP driver + #CONFIG_DRIVER_HOSTAP=y + + # Driver interface for wired authenticator + CONFIG_DRIVER_WIRED=y + + # Driver interface for drivers using the nl80211 kernel interface + CONFIG_DRIVER_NL80211=y + + # QCA vendor extensions to nl80211 + #CONFIG_DRIVER_NL80211_QCA=y + + # driver_nl80211.c requires libnl. If you are compiling it yourself + # you may need to point hostapd to your version of libnl. + # + #CFLAGS += -I$ + #LIBS += -L$ + + # Use libnl v2.0 (or 3.0) libraries. + #CONFIG_LIBNL20=y + + # Use libnl 3.2 libraries (if this is selected, CONFIG_LIBNL20 is ignored) + #CONFIG_LIBNL32=y + + + # Driver interface for FreeBSD net80211 layer (e.g., Atheros driver) + #CONFIG_DRIVER_BSD=y + #CFLAGS += -I/usr/local/include + #LIBS += -L/usr/local/lib + #LIBS_p += -L/usr/local/lib + #LIBS_c += -L/usr/local/lib + + # Driver interface for no driver (e.g., RADIUS server only) + #CONFIG_DRIVER_NONE=y + + # IEEE 802.11F/IAPP + CONFIG_IAPP=y + + # WPA2/IEEE 802.11i RSN pre-authentication + CONFIG_RSN_PREAUTH=y + + # IEEE 802.11w (management frame protection) + #CONFIG_IEEE80211W=y + + # Support Operating Channel Validation + #CONFIG_OCV=y + + # Integrated EAP server + CONFIG_EAP=y + + # EAP Re-authentication Protocol (ERP) in integrated EAP server + CONFIG_ERP=y + + # EAP-MD5 for the integrated EAP server + CONFIG_EAP_MD5=y + + # EAP-TLS for the integrated EAP server + CONFIG_EAP_TLS=y + + # EAP-MSCHAPv2 for the integrated EAP server + CONFIG_EAP_MSCHAPV2=y + + # EAP-PEAP for the integrated EAP server + CONFIG_EAP_PEAP=y + + # EAP-GTC for the integrated EAP server + CONFIG_EAP_GTC=y + + # EAP-TTLS for the integrated EAP server + CONFIG_EAP_TTLS=y + + # EAP-SIM for the integrated EAP server + #CONFIG_EAP_SIM=y + + # EAP-AKA for the integrated EAP server + #CONFIG_EAP_AKA=y + + # EAP-AKA' for the integrated EAP server + # This requires CONFIG_EAP_AKA to be enabled, too. + #CONFIG_EAP_AKA_PRIME=y + + # EAP-PAX for the integrated EAP server + #CONFIG_EAP_PAX=y + + # EAP-PSK for the integrated EAP server (this is _not_ needed for WPA-PSK) + #CONFIG_EAP_PSK=y + + # EAP-pwd for the integrated EAP server (secure authentication with a password) + #CONFIG_EAP_PWD=y + + # EAP-SAKE for the integrated EAP server + #CONFIG_EAP_SAKE=y + + # EAP-GPSK for the integrated EAP server + #CONFIG_EAP_GPSK=y + # Include support for optional SHA256 cipher suite in EAP-GPSK + #CONFIG_EAP_GPSK_SHA256=y + + # EAP-FAST for the integrated EAP server + CONFIG_EAP_FAST=y + + # EAP-TEAP for the integrated EAP server + # Note: The current EAP-TEAP implementation is experimental and should not be + # enabled for production use. The IETF RFC 7170 that defines EAP-TEAP has number + # of conflicting statements and missing details and the implementation has + # vendor specific workarounds for those and as such, may not interoperate with + # any other implementation. This should not be used for anything else than + # experimentation and interoperability testing until those issues has been + # resolved. + #CONFIG_EAP_TEAP=y + + # Wi-Fi Protected Setup (WPS) + CONFIG_WPS=y + # Enable UPnP support for external WPS Registrars + #CONFIG_WPS_UPNP=y + # Enable WPS support with NFC config method + #CONFIG_WPS_NFC=y + + # EAP-IKEv2 + #CONFIG_EAP_IKEV2=y + + # Trusted Network Connect (EAP-TNC) + #CONFIG_EAP_TNC=y + + # EAP-EKE for the integrated EAP server + #CONFIG_EAP_EKE=y + + # PKCS#12 (PFX) support (used to read private key and certificate file from + # a file that usually has extension .p12 or .pfx) + CONFIG_PKCS12=y + + # RADIUS authentication server. This provides access to the integrated EAP + # server from external hosts using RADIUS. + #CONFIG_RADIUS_SERVER=y + + # Build IPv6 support for RADIUS operations + CONFIG_IPV6=y + + # IEEE Std 802.11r-2008 (Fast BSS Transition) + CONFIG_IEEE80211R=y + + # Use the hostapd's IEEE 802.11 authentication (ACL), but without + # the IEEE 802.11 Management capability (e.g., FreeBSD/net80211) + #CONFIG_DRIVER_RADIUS_ACL=y + + # IEEE 802.11n (High Throughput) support + CONFIG_IEEE80211N=y + + # Wireless Network Management (IEEE Std 802.11v-2011) + # Note: This is experimental and not complete implementation. + CONFIG_WNM=y + + # IEEE 802.11ac (Very High Throughput) support + CONFIG_IEEE80211AC=y + + # IEEE 802.11ax HE support + # Note: This is experimental and work in progress. The definitions are still + # subject to change and this should not be expected to interoperate with the + # final IEEE 802.11ax version. + CONFIG_IEEE80211AX=y + + # Remove debugging code that is printing out debug messages to stdout. + # This can be used to reduce the size of the hostapd considerably if debugging + # code is not needed. + #CONFIG_NO_STDOUT_DEBUG=y + + # Add support for writing debug log to a file: -f /tmp/hostapd.log + # Disabled by default. + #CONFIG_DEBUG_FILE=y + + # Send debug messages to syslog instead of stdout + CONFIG_DEBUG_SYSLOG=y + + # Add support for sending all debug messages (regardless of debug verbosity) + # to the Linux kernel tracing facility. This helps debug the entire stack by + # making it easy to record everything happening from the driver up into the + # same file, e.g., using trace-cmd. + #CONFIG_DEBUG_LINUX_TRACING=y + + # Remove support for RADIUS accounting + #CONFIG_NO_ACCOUNTING=y + + # Remove support for RADIUS + #CONFIG_NO_RADIUS=y + + # Remove support for VLANs + #CONFIG_NO_VLAN=y + + # Enable support for fully dynamic VLANs. This enables hostapd to + # automatically create bridge and VLAN interfaces if necessary. + CONFIG_FULL_DYNAMIC_VLAN=y + + # Use netlink-based kernel API for VLAN operations instead of ioctl() + # Note: This requires libnl 3.1 or newer. + #CONFIG_VLAN_NETLINK=y + + # Remove support for dumping internal state through control interface commands + # This can be used to reduce binary size at the cost of disabling a debugging + # option. + CONFIG_NO_DUMP_STATE=y + + # Enable tracing code for developer debugging + # This tracks use of memory allocations and other registrations and reports + # incorrect use with a backtrace of call (or allocation) location. + #CONFIG_WPA_TRACE=y + # For BSD, comment out these. + #LIBS += -lexecinfo + #LIBS_p += -lexecinfo + #LIBS_c += -lexecinfo + + # Use libbfd to get more details for developer debugging + # This enables use of libbfd to get more detailed symbols for the backtraces + # generated by CONFIG_WPA_TRACE=y. + #CONFIG_WPA_TRACE_BFD=y + # For BSD, comment out these. + #LIBS += -lbfd -liberty -lz + #LIBS_p += -lbfd -liberty -lz + #LIBS_c += -lbfd -liberty -lz + + # hostapd depends on strong random number generation being available from the + # operating system. os_get_random() function is used to fetch random data when + # needed, e.g., for key generation. On Linux and BSD systems, this works by + # reading /dev/urandom. It should be noted that the OS entropy pool needs to be + # properly initialized before hostapd is started. This is important especially + # on embedded devices that do not have a hardware random number generator and + # may by default start up with minimal entropy available for random number + # generation. + # + # As a safety net, hostapd is by default trying to internally collect + # additional entropy for generating random data to mix in with the data + # fetched from the OS. This by itself is not considered to be very strong, but + # it may help in cases where the system pool is not initialized properly. + # However, it is very strongly recommended that the system pool is initialized + # with enough entropy either by using hardware assisted random number + # generator or by storing state over device reboots. + # + # hostapd can be configured to maintain its own entropy store over restarts to + # enhance random number generation. This is not perfect, but it is much more + # secure than using the same sequence of random numbers after every reboot. + # This can be enabled with -e command line option. The specified + # file needs to be readable and writable by hostapd. + # + # If the os_get_random() is known to provide strong random data (e.g., on + # Linux/BSD, the board in question is known to have reliable source of random + # data from /dev/urandom), the internal hostapd random pool can be disabled. + # This will save some in binary size and CPU use. However, this should only be + # considered for builds that are known to be used on devices that meet the + # requirements described above. + CONFIG_NO_RANDOM_POOL=y + + # Should we attempt to use the getrandom(2) call that provides more reliable + # yet secure randomness source than /dev/random on Linux 3.17 and newer. + # Requires glibc 2.25 to build, falls back to /dev/random if unavailable. + CONFIG_GETRANDOM=y + + # Should we use poll instead of select? Select is used by default. + #CONFIG_ELOOP_POLL=y + + # Should we use epoll instead of select? Select is used by default. + CONFIG_ELOOP_EPOLL=y + + # Should we use kqueue instead of select? Select is used by default. + #CONFIG_ELOOP_KQUEUE=y + + # Select TLS implementation + # openssl = OpenSSL (default) + # gnutls = GnuTLS + # internal = Internal TLSv1 implementation (experimental) + # linux = Linux kernel AF_ALG and internal TLSv1 implementation (experimental) + # none = Empty template + CONFIG_TLS=openssl + + # TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.1) + # can be enabled to get a stronger construction of messages when block ciphers + # are used. + #CONFIG_TLSV11=y + + # TLS-based EAP methods require at least TLS v1.0. Newer version of TLS (v1.2) + # can be enabled to enable use of stronger crypto algorithms. + #CONFIG_TLSV12=y + + # Select which ciphers to use by default with OpenSSL if the user does not + # specify them. + #CONFIG_TLS_DEFAULT_CIPHERS="DEFAULT:!EXP:!LOW" + + # If CONFIG_TLS=internal is used, additional library and include paths are + # needed for LibTomMath. Alternatively, an integrated, minimal version of + # LibTomMath can be used. See beginning of libtommath.c for details on benefits + # and drawbacks of this option. + CONFIG_INTERNAL_LIBTOMMATH=y + #ifndef CONFIG_INTERNAL_LIBTOMMATH + #LTM_PATH=/usr/src/libtommath-0.39 + #CFLAGS += -I$(LTM_PATH) + #LIBS += -L$(LTM_PATH) + #LIBS_p += -L$(LTM_PATH) + #endif + # At the cost of about 4 kB of additional binary size, the internal LibTomMath + # can be configured to include faster routines for exptmod, sqr, and div to + # speed up DH and RSA calculation considerably + #CONFIG_INTERNAL_LIBTOMMATH_FAST=y + + # Interworking (IEEE 802.11u) + # This can be used to enable functionality to improve interworking with + # external networks. + CONFIG_INTERWORKING=y + + # Hotspot 2.0 + #CONFIG_HS20=y + + # Enable SQLite database support in hlr_auc_gw, EAP-SIM DB, and eap_user_file + #CONFIG_SQLITE=y + + # Enable Fast Session Transfer (FST) + #CONFIG_FST=y + + # Enable CLI commands for FST testing + #CONFIG_FST_TEST=y + + # Testing options + # This can be used to enable some testing options (see also the example + # configuration file) that are really useful only for testing clients that + # connect to this hostapd. These options allow, for example, to drop a + # certain percentage of probe requests or auth/(re)assoc frames. + # + #CONFIG_TESTING_OPTIONS=y + + # Automatic Channel Selection + # This will allow hostapd to pick the channel automatically when channel is set + # to "acs_survey" or "0". Eventually, other ACS algorithms can be added in + # similar way. + # + # Automatic selection is currently only done through initialization, later on + # we hope to do background checks to keep us moving to more ideal channels as + # time goes by. ACS is currently only supported through the nl80211 driver and + # your driver must have survey dump capability that is filled by the driver + # during scanning. + # + # You can customize the ACS survey algorithm with the hostapd.conf variable + # acs_num_scans. + # + # Supported ACS drivers: + # * ath9k + # * ath5k + # * ath10k + # + # For more details refer to: + # http://wireless.kernel.org/en/users/Documentation/acs + # + #CONFIG_ACS=y + + # Multiband Operation support + # These extentions facilitate efficient use of multiple frequency bands + # available to the AP and the devices that may associate with it. + #CONFIG_MBO=y + + # Client Taxonomy + # Has the AP retain the Probe Request and (Re)Association Request frames from + # a client, from which a signature can be produced which can identify the model + # of client device like "Nexus 6P" or "iPhone 5s". + CONFIG_TAXONOMY=y + + # Fast Initial Link Setup (FILS) (IEEE 802.11ai) + #CONFIG_FILS=y + # FILS shared key authentication with PFS + #CONFIG_FILS_SK_PFS=y + + # Include internal line edit mode in hostapd_cli. This can be used to provide + # limited command line editing and history support. + #CONFIG_WPA_CLI_EDIT=y + + # Opportunistic Wireless Encryption (OWE) + # Experimental implementation of draft-harkins-owe-07.txt + #CONFIG_OWE=y + + # Airtime policy support + CONFIG_AIRTIME_POLICY=y + + # Proxy ARP support + CONFIG_PROXYARP=y + + # Override default value for the wpa_disable_eapol_key_retries configuration + # parameter. See that parameter in hostapd.conf for more details. + #CFLAGS += -DDEFAULT_WPA_DISABLE_EAPOL_KEY_RETRIES=1 + + # uBus IPC/RPC System + # Services can connect to the bus and provide methods + # that can be called by other services or clients. + CONFIG_UBUS=y + + # OpenWrt patch 380-disable-ctrl-iface-mib.patch + # leads to the MIB only being compiled in if + # CONFIG_CTRL_IFACE_MIB is enabled. + CONFIG_CTRL_IFACE_MIB=y + ''; + + patches = singleton config'.flake.patches.hostapd.intel_lar-and-noscan; + + prePatch = '' + patchFolder="${openwrtRepo}/package/network/services/hostapd/patches" + for file in $(ls $patchFolder) ; do + if [ $file != "300-noscan.patch" ] ; then + patches+=" $patchFolder/$file" + fi + done + ''; + + postPatch = '' + cp -RT ${openwrtRepo}/package/network/services/hostapd/src . + ''; + }); + } + ); +} diff --git a/nixos/systems/blowhole/ical2org.nix b/nixos/systems/blowhole/ical2org.nix new file mode 100644 index 0000000..d555409 --- /dev/null +++ b/nixos/systems/blowhole/ical2org.nix @@ -0,0 +1,32 @@ +{ pkgs, secret, ... }: +{ + systemd.services.ical-vu-sync = { + serviceConfig.Type = "oneshot"; + path = with pkgs; [ + bash + ical2orgpy + curl + ]; + script = '' + rm "${secret.ical2org.orgPath or ""}" + cat < "${secret.ical2org.orgPath or ""}" + :PROPERTIES: + :ID: 56ed0bf0-c6d0-4a86-980a-905ccab89345 + :END: + #+title: VU Calendar + #+filetags: :project-forced: + EOF + curl '${secret.ical2org.icalUrlRooster or ""}' -o - | ical2orgpy - - >> "${secret.ical2org.orgPath or ""}" + curl '${secret.ical2org.icalUrlCanvas or ""}' -o - | CANVAS_TODO=1 ical2orgpy - - >> "${secret.ical2org.orgPath or ""}" + chown 404:404 "${secret.ical2org.orgPath or ""}" + ''; + }; + # systemd.timers.ical-vu-sync = { + # wantedBy = [ "timers.target" ]; + # partOf = [ "ical-vu-sync.service" ]; + # timerConfig = { + # OnCalendar = "*-*-* 3:00:00"; + # Unit = "ical-vu-sync.service"; + # }; + # }; +} diff --git a/nixos/systems/blowhole/klipper.nix b/nixos/systems/blowhole/klipper.nix new file mode 100644 index 0000000..ba4324f --- /dev/null +++ b/nixos/systems/blowhole/klipper.nix @@ -0,0 +1,534 @@ +{ inputs, lib, pkgs, secret, config, config', ...}: +let + inherit (lib) + concatStringsSep + singleton + concatMapStringsSep + splitString; +in +{ + uterranix.config = { tflib, ... }: + let + inherit (tflib) + tf; + in + { + output."envoy_klipper".value = tf "vault_consul_secret_backend_role.envoy-klipper"; + }; + + services.hashicorp.vault-agent = + { + settings.template = [ + { + source = pkgs.writeText "envoy-klipper.token.vtmpl" '' + {{ with secret "consul/creds/envoy-klipper" }}{{ .Data.token }}{{ end }} + ''; + destination = "/run/secrets/klipper/envoy-klipper.token"; + command = + let + serviceList = + [ "hashicorp-envoy-mainsail" ]; + in + pkgs.writeShellScript "envoy-mainsail-reload.sh" + '' + sudo systemd-run -P --machine klipper /run/current-system/sw/bin/bash -l -c \ + 'systemctl try-reload-or-restart ${concatStringsSep " " serviceList}' + ''; + } + ]; + }; + + fileSystems."/var/lib/klipper" = { + device = "blowhole-zpool/persist/klipper"; + fsType = "zfs"; + }; + + systemd.services."container@klipper" = { + restartIfChanged = lib.mkForce false; + }; + + containers.klipper = { + ephemeral = true; + autoStart = true; + privateNetwork = true; + + localAddress = "10.64.99.6"; + hostAddress = "10.64.99.5"; + + bindMounts = { + "/run/secrets" = { + hostPath = "/run/secrets/klipper"; + isReadOnly = true; + }; + "/var/lib/klipper" = { + hostPath = "/var/lib/klipper"; + isReadOnly = false; + }; + "/var/lib/moonraker/gcodes" = { + hostPath = "/var/lib/klipper/gcodes"; + isReadOnly = false; + }; + "/dev/serial/by-id/" = { + hostPath = "/dev/serial/by-id/"; + isReadOnly = false; + }; + }; + + allowedDevices = singleton { + node = "/dev/serial/by-id/usb-Klipper_lpc1768_13E0FF0C469027AEBAA84A52871E00F5-if00"; + modifier = "rwm"; + }; + + config = { + nixpkgs.overlays = config.nixpkgs.overlays; + + imports = with config'.flake.nixosModules; [ + hashicorp + hashicorp-envoy + ]; + + services.hashicorp-envoy.mainsail = { + service = { + name = "mainsail"; + id = "mainsail"; + address = "10.64.99.6"; + port = 80; + + connect.sidecar_service = {}; + }; + + environment = { + "CONSUL_HTTP_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8500"; + "CONSUL_GRPC_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8502"; + "CONSUL_HTTP_TOKEN_FILE" = "/run/secrets/envoy-klipper.token"; + }; + + address = "10.64.99.6:19000"; + adminBind = "127.0.0.1:19100"; + hotRestart = false; + }; + + users.users.klipper = { + home = "/var/lib/klipper"; + isSystemUser = true; + group = "klipper"; + uid = 321; + }; + + users.groups.klipper = { + gid = 321; + }; + + services.klipper = { + enable = true; + + user = "klipper"; + group = "klipper"; + + package = pkgs.klipper; + + settings = + let + indentGcode = gcode: + "\n" + (concatMapStringsSep "\n" (x: " " + x) (splitString "\n" gcode)); + in + { + stepper_x = { + step_pin = "P2.2"; + dir_pin = "!P2.6"; + enable_pin = "!P2.1"; + rotation_distance = "40"; + microsteps = "16"; + endstop_pin = "P1.29"; # P1.28 for X-max + position_endstop = "0"; + position_max = "235"; + homing_speed = "50"; + }; + + stepper_y = { + step_pin = "P0.19"; + dir_pin = "!P0.20"; + enable_pin = "!P2.8"; + rotation_distance = "40"; + microsteps = "16"; + endstop_pin = "P1.27"; # P1.26 for Y-max + position_endstop = "0"; + position_max = "235"; + homing_speed = "50"; + }; + + stepper_z = { + step_pin = "P0.22"; + dir_pin = "P2.11"; + enable_pin = "!P0.21"; + rotation_distance = "8"; + microsteps = "16"; + endstop_pin = "P1.25"; # P1.24 for Z-max" + position_min = "-4.5"; + position_endstop = "1.290"; + position_max = "250"; + }; + + extruder = { + step_pin = "P2.13"; + dir_pin = "!P0.11"; + enable_pin = "!P2.12"; + rotation_distance = "23.291"; + gear_ratio = "3:1"; + microsteps = "16"; + nozzle_diameter = "0.400"; + filament_diameter = "1.750"; + heater_pin = "P2.7"; + sensor_type = "PT1000"; + sensor_pin = "P0.24"; + control = "pid"; + pid_Kp = "22.2"; + pid_Ki = "1.08"; + pid_Kd = "114"; + min_temp = "0"; + max_temp = "260"; + pressure_advance = "0.92"; + }; + + bed_screws = { + screw1 = "30,35"; + screw2 = "200,35"; + screw3 = "200,205"; + screw4 = "30,205"; + }; + + "heater_fan my_nozzle_fan" = { + pin = "P2.4"; + heater = "extruder"; + heater_temp = "50.0"; + fan_speed = "1.0"; + }; + + heater_bed = { + heater_pin = "P2.5"; + sensor_type = "ATC Semitec 104GT-2"; + sensor_pin = "P0.23"; + control = "watermark"; + min_temp = "0"; + max_temp = "80"; + }; + + fan = { + pin = "P2.3"; + }; + + mcu = { + serial = "/dev/serial/by-id/usb-Klipper_lpc1768_13E0FF0C469027AEBAA84A52871E00F5-if00"; + }; + + printer = { + kinematics = "cartesian"; + max_velocity = "200"; + max_accel = "2000"; + max_z_velocity = "25"; + max_z_accel = "100"; + }; + + virtual_sdcard = { + path = "/var/lib/moonraker/gcodes"; + }; + + ### Mainsail + pause_resume = {}; + display_status = {}; + endstop_phase = {}; + + "tmc2208 stepper_x" = { + uart_pin = "P1.17"; + run_current = "0.475"; + hold_current = "0.275"; + stealthchop_threshold = "250"; + }; + + "tmc2208 stepper_y" = { + uart_pin = "P1.15"; + run_current = "0.475"; + hold_current = "0.275"; + stealthchop_threshold = "250"; + }; + + "tmc2208 stepper_z" = { + uart_pin = "P1.10"; + run_current = "0.475"; + hold_current = "0.275"; + stealthchop_threshold = "30"; + }; + + "tmc2208 extruder" = { + uart_pin = "P1.8"; + run_current = "0.560"; + hold_current = "0.360"; + stealthchop_threshold = "5"; + }; + + board_pins = { + aliases = + indentGcode + '' + # EXP1 header + EXP1_1=P1.30, EXP1_3=P1.18, EXP1_5=P1.20, EXP1_7=P1.22, EXP1_9=, + EXP1_2=P0.28, EXP1_4=P1.19, EXP1_6=P1.21, EXP1_8=P1.23, EXP1_10=<5V>, + # EXP2 header + EXP2_1=P0.17, EXP2_3=P3.26, EXP2_5=P3.25, EXP2_7=P1.31, EXP2_9=, + EXP2_2=P0.15, EXP2_4=P0.16, EXP2_6=P0.18, EXP2_8=, EXP2_10= + # Pins EXP2_1, EXP2_6, EXP2_2 are also MISO, MOSI, SCK of bus "ssp0" + ''; + }; + + display = { + lcd_type = "st7920"; + cs_pin = "EXP1_7"; + sclk_pin = "EXP1_6"; + sid_pin = "EXP1_8"; + encoder_pins = "^EXP1_5, ^EXP1_3"; + click_pin = "^!EXP1_2"; + }; + + # "endstop_phase stepper_z" = + # { endstop_phase = "29"; + # }; + + # "endstop_phase stepper_y" = + # { endstop_phase = "57"; + # }; + + # "endstop_phase stepper_x" = + # { endstop_phase = "3"; + # }; + + "gcode_macro M600" = { + gcode = + indentGcode + '' + {% set x = params.X|default(50)|float %} + {% set y = params.Y|default(0)|float %} + {% set z = params.Z|default(10)|float %} + SAVE_GCODE_STATE NAME=M600_state + PAUSE + G91 + G1 E-.8 F2700 + G1 Z{z} + G90 + G1 X{x} Y{y} F3000 + G91 + G1 E-50 F1000 + G1 X0.1 F3000 + G1 E-50 F1000 + G1 X-0.1 F3000 + G1 E-50 F1000 + G1 X0.1 F3000 + G1 E-50 F1000 + G1 X-0.1 F3000 + G1 E-50 F1000 + G1 X0.1 F3000 + G1 E-50 F1000 + G1 X-0.1 F3000 + RESTORE_GCODE_STATE NAME=M600_state + ''; + }; + + "gcode_macro CANCEL_PRINT" = { + rename_existing = "BASE_CANCEL_PRINT"; + gcode = + indentGcode + '' + TURN_OFF_HEATERS + CLEAR_PAUSE + SDCARD_RESET_FILE + BASE_CANCEL_PRINT + ''; + }; + + "gcode_macro PARK_WAIT" = { + gcode = + indentGcode + '' + {% set x = params.X|default(0)|float %} + {% set y = params.Y|default(230)|float %} + {% set z = params.Z|default(10)|float %} + {% set e = params.Z|default(20)|float %} + {% set millis = params.MILLIS|default(5)|float %} + + SAVE_GCODE_STATE NAME=PAUSE_state + G91 + G1 E-{e} F2100 + G1 Z{z} + G90 + G1 X{x} Y{y} F6000 + + G4 P{millis} + + G91 + G1 E{e} F2100 + G90 + RESTORE_GCODE_STATE NAME=PAUSE_state MOVE=1 + ''; + }; + + "gcode_macro PAUSE" = { + rename_existing = "BASE_PAUSE"; + gcode = + indentGcode + '' + {% set x = params.X|default(0)|float %} + {% set y = params.Y|default(230)|float %} + {% set z = params.Z|default(10)|float %} + {% set e = params.E|default(20)|float %} + + SAVE_GCODE_STATE NAME=PAUSE_state + BASE_PAUSE + G91 + G1 E-{e} F2100 + G1 Z{z} + G90 + G1 X{x} Y{y} F6000 + ''; + }; + + "gcode_macro RESUME" = { + rename_existing = "BASE_RESUME"; + gcode = + indentGcode + '' + {% set e = params.Z|default(20)|float %} + + G91 + G1 E{e} F2100 + G90 + RESTORE_GCODE_STATE NAME=PAUSE_state MOVE=1 + BASE_RESUME + ''; + }; + + "gcode_macro PRIME_LINE" = { + gcode = + indentGcode + '' + G92 E0 # Reset Extruder + G1 Z2.0 F3000 # Move Z Axis up little to prevent scratching of Heat Bed + G1 X0.1 Y20 Z0.3 F5000.0 # Move to start position + G1 X0.1 Y200.0 Z0.3 F1500.0 E15 # Draw the first line + G1 X0.4 Y200.0 Z0.3 F5000.0 # Move to side a little + G1 X0.4 Y20 Z0.3 F1500.0 E30 # Draw the second line + G92 E0 # Reset Extruder + G1 Z2.0 F3000 # Move Z Axis up little to prevent scratching of Heat Bed + G1 X5 Y20 Z0.3 F5000.0 # Move over to prevent blob squish + ''; + }; + + "gcode_macro START_PRINT" = { + gcode = + indentGcode + '' + {% set z = params.Z|default(0)|float %} + + # Use absolute coordinates + G90 + # Reset the G-Code Z offset (adjust Z offset if needed) + SET_GCODE_OFFSET Z={z} + # Home the printer + G28 + # Prime line + G0 Z0 + PRIME_LINE + ''; + }; + + "gcode_macro END_PRINT" = { + gcode = + indentGcode + '' + G91 # Relative positioning + G1 E-2 F2700 # Retract a bit + G1 E-2 Z0.2 F2400 # Retract and raise Z + G1 X5 Y5 F3000 # Wipe out + G1 Z10 #Raise Z more + G90 # Absolute positionning + + G1 X0 Y200 # Present print + M106 S0 # Turn-off fan + M104 S0 # Turn-off hotend + M140 S0 # Turn-off bed + + M84 X Y E # Disable all steppers but Z + ''; + }; + }; + }; + + services.moonraker = { + enable = true; + + group = "klipper"; + + settings = { + authorization.trusted_clients = [ + "127.0.0.1" + (secret.network.ips.heater or "") + (secret.network.ips.edge.vpn or "") + (secret.network.ips.omen.vpn or "") + ]; + + octoprint_compat = {}; + history = {}; + }; + }; + + services.nginx = { + enable = true; + + recommendedGzipSettings = true; + recommendedProxySettings = true; + recommendedOptimisation = true; + + upstreams."apiserver" = { + servers."127.0.0.1:7125" = {}; + extraConfig = '' + ip_hash; + ''; + }; + + virtualHosts.${secret.network.ips.blowhole.dns or ""} = { + root = pkgs.mainsail; + + locations."/".extraConfig = '' + try_files $uri $uri/ /index.html; + ''; + + locations."/index.html".extraConfig = '' + add_header Cache-Control "no-store, no-cache, must-revalidate"; + ''; + + locations."/websocket".extraConfig = '' + proxy_pass http://apiserver/websocket; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 86400; + ''; + + + locations."~ ^/(printer|api|access|machine|server)/".extraConfig = '' + proxy_pass http://apiserver$request_uri; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Scheme $scheme; + ''; + + extraConfig = '' + client_max_body_size 512M; + ''; + }; + }; + }; + }; +} diff --git a/nixos/systems/blowhole/monitoring.nix b/nixos/systems/blowhole/monitoring.nix new file mode 100644 index 0000000..5c7b57f --- /dev/null +++ b/nixos/systems/blowhole/monitoring.nix @@ -0,0 +1,533 @@ +# SPDX-FileCopyrightText: 2023 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ pkgs, roots, lib, inputs', config, secret, config', ... }: +let + inherit (lib) + singleton + nixosTests + concatStringsSep; +in +{ + uterranix.config = { tflib, ... }: + let + inherit (tflib) + tf; + in + { + output."envoy_grafana".value = tf "vault_consul_secret_backend_role.envoy-grafana"; + output."envoy_blowhole".value = tf "vault_consul_secret_backend_role.envoy-blowhole"; + + data."influxdb-v2_organization"."redalder" = { + name = "redalder"; + }; + + resource."influxdb-v2_bucket"."metrics_bucket" = { + name = "metrics"; + description = "Metrics bucket"; + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + retention_rules = { + every_seconds = 30 * 24 * 60 * 60; # days * h/d * m/h * s/m + }; + }; + + resource."influxdb-v2_bucket"."logs_bucket" = { + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + name = "logs"; + description = "Logs bucket"; + retention_rules = { + every_seconds = 30 * 24 * 60 * 60; # days * h/d * m/h * s/m + }; + }; + + resource."influxdb-v2_authorization"."telegraf_authorization" = { + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + description = "Token for telegraf ingestion"; + status = "active"; + permissions = [ + { + action = "write"; + resource = { + id = "\${influxdb-v2_bucket.logs_bucket.id}"; + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + type = "buckets"; + }; + } + { + action = "write"; + resource = { + id = "\${influxdb-v2_bucket.metrics_bucket.id}"; + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + type = "buckets"; + }; + } + ]; + }; + + resource."influxdb-v2_authorization"."grafana_authorization" = { + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + description = "Token for Grefana"; + status = "active"; + permissions = [ + { + action = "read"; + resource = { + id = "\${influxdb-v2_bucket.logs_bucket.id}"; + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + type = "buckets"; + }; + } + { + action = "read"; + resource = { + id = "\${influxdb-v2_bucket.metrics_bucket.id}"; + org_id = "\${data.influxdb-v2_organization.redalder.id}"; + type = "buckets"; + }; + } + ]; + }; + + resource."vault_mount"."kv" = { + path = "kv"; + type = "kv"; + options = { version = 2; }; + description = "KV Version 2 secret engine mount"; + }; + + resource."vault_kv_secret_v2"."telegraf_secret" = { + mount = "\${vault_mount.kv.path}"; + name = "homelab-1/blowhole/monitor/telegraf"; + options = { version = 2; }; + data_json = builtins.toJSON { + influxdb_token = "\${influxdb-v2_authorization.telegraf_authorization.token}"; + }; + }; + + resource."vault_kv_secret_v2"."grafana_secret" = { + mount = "\${vault_mount.kv.path}"; + name = "homelab-1/blowhole/monitor/grafana"; + options = { version = 2; }; + data_json = builtins.toJSON { + influxdb_token = "\${influxdb-v2_authorization.grafana_authorization.token}"; + }; + }; + }; + + nixpkgs.overlays = singleton (_: _: + { + telegraf = + pkgs.buildGoModule rec { + pname = "telegraf"; + version = "1.25.3"; + + excludedPackages = "test"; + doCheck = false; + + subPackages = singleton "cmd/telegraf"; + + src = pkgs.fetchFromGitHub { + owner = "influxdata"; + repo = "telegraf"; + rev = "v${version}"; + sha256 = "sha256-FUZDS4As9qP2Dn0NSBM/e8udDLMk5OZol4CQSI39T4s="; + }; + + vendorHash = "sha256-uWoWvS9ZZzhpE+PiJv0fqblMLOAGIrhCdi0ugvF/lQI="; + proxyVendor = true; + + ldflags = [ + "-w" "-s" "-X main.version=${version}" + ]; + + passthru.tests = { inherit (nixosTests) telegraf; }; + + meta = with lib; { + description = "The plugin-driven server agent for collecting & reporting metrics"; + license = licenses.mit; + homepage = "https://www.influxdata.com/time-series-platform/telegraf/"; + maintainers = with maintainers; [ mic92 roblabla timstott ]; + }; + }; + }); + + services.hashicorp.vault-agent = + { + settings.template = [ + { + source = pkgs.writeText "envoy-grafana.token.vtmpl" '' + {{ with secret "consul/creds/envoy-grafana" }}{{ .Data.token }}{{ end }} + ''; + destination = "/run/secrets/monitor/envoy-grafana.token"; + command = + let + serviceList = + [ "hashicorp-envoy-grafana" "hashicorp-envoy-influx" "hashicorp-envoy-telegraf" ]; + in + pkgs.writeShellScript "envoy-grafana-reload.sh" '' + sudo systemd-run -P --machine monitor /run/current-system/sw/bin/bash -l -c \ + 'systemctl try-reload-or-restart ${concatStringsSep " " serviceList}' + ''; + } + { + source = pkgs.writeText "envoy-blowhole.token.vtmpl" '' + {{ with secret "consul/creds/envoy-blowhole" }}{{ .Data.token }}{{ end }} + ''; + destination = "/run/secrets/envoy-blowhole.token"; + command = pkgs.writeShellScript "envoy-blowhole-reload.sh" '' + sudo systemctl try-reload-or-restart hashicorp-envoy-telegraf + ''; + } + { + source = pkgs.writeText "telegraf.env.vtmpl" '' + INFLUXDB_TOKEN={{ with secret "kv/data/homelab-1/blowhole/monitor/telegraf" }}{{ .Data.data.influxdb_token }}{{ end }} + ''; + destination = "/run/secrets/monitor/telegraf.env"; + command = pkgs.writeShellScript "monitor-telegraf-reload.sh" '' + sudo systemd-run -P --machine monitor /run/current-system/sw/bin/bash -l -c \ + 'systemctl try-reload-or-restart telegraf' + ''; + } + { + source = pkgs.writeText "grafana-influx.token.vtmpl" '' + {{ with secret "kv/data/homelab-1/blowhole/monitor/grafana" }} + {{ .Data.data.influxdb_token }} + {{ end }} + ''; + destination = "/run/secrets/monitor/grafana-influx.token"; + perms = "0644"; + command = pkgs.writeShellScript "monitor-telegraf-reload.sh" '' + sudo systemd-run -P --machine monitor /run/current-system/sw/bin/bash -l -c \ + 'systemctl try-reload-or-restart grafana' + ''; + } + ]; + }; + + + ## There is no way to say, hey, listen on localhost. The listeners option is missing the `address` field + ## and the `name` field so it's impossible to configure.... + services.hashicorp-envoy.telegraf = { + type = "ingress"; + address = "${secret.network.ips.blowhole.ip or ""}:19000"; + service = { + kind = "ingress-gateway"; + name = "telegraf-blowhole"; + + listeners = singleton { + port = 8086; + protocol = "tcp"; + services = singleton { + name = "telegraf"; + }; + }; + }; + + environment = { + "CONSUL_HTTP_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8500"; + "CONSUL_GRPC_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8502"; + "CONSUL_HTTP_TOKEN_FILE" = "/run/secrets/envoy-blowhole.token"; + }; + + adminBind = "127.0.0.1:19100"; + hotRestart = false; + }; + + services.telegraf-magic = { + enable = true; + settings = { + inputs.cpu = { + percpu = true; + totalcpu = true; + tags.host = "blowhole"; + tags.bucket = "metrics"; + }; + + inputs.mem = { + tags.host = "blowhole"; + tags.bucket = "metrics"; + }; + + inputs.nomad = { + url = "http://${secret.network.ips.blowhole.ip or ""}:4646"; + tags.host = "blowhole"; + tags.bucket = "metrics"; + }; + + # aggregators.minmax = { + # period = "30s"; + # drop_original = true; + # namepass = [ "nomad" ]; + # }; + + inputs.zfs = { + tags.host = "blowhole"; + tags.bucket = "metrics"; + }; + + # inputs.tail = [ + # { + # files = ["/var/lib/nomad/alloc/*/alloc/logs/*.stdout.*"]; + # data_format = "value"; + # data_type = "string"; + + # name_override = "nomad_alloc_log"; + # tags.bucket = "logs"; + # } + # { + # files = ["/var/lib/nomad/alloc/*/alloc/logs/*.stderr.*"]; + # data_format = "value"; + # data_type = "string"; + + # name_override = "nomad_alloc_log"; + # tags.bucket = "logs"; + # } + # ]; + + inputs.docker_log = { + tags.bucket = "logs"; + }; + + outputs.influxdb_v2 = [ + { + urls = singleton "http://${secret.network.ips.blowhole.ip or ""}:8086"; + bucket = "metrics"; + tagpass.bucket = singleton "metrics"; + } + { + urls = singleton "http://${secret.network.ips.blowhole.ip or ""}:8086"; + bucket = "logs"; + tagpass.bucket = singleton "logs"; + } + ]; + }; + + systemd = { + serviceConfig.SupplementaryGroups = [ "docker" ]; + }; + }; + + fileSystems."/var/lib/grafana" = { + device = "blowhole-zpool/persist/grafana"; + fsType = "zfs"; + }; + + fileSystems."/var/lib/grafana-postgres" = { + device = "blowhole-zpool/persist/grafana-postgres"; + fsType = "zfs"; + }; + + fileSystems."/var/lib/grafana-influxdb2" = { + device = "blowhole-zpool/persist/grafana-influxdb2"; + fsType = "zfs"; + }; + + systemd.services."container@monitor".serviceConfig.LimitNOFILE = "infinity"; + + # TODO: split interface name and container name, i.e. rewrite the container module....... again + containers.monitor = { + ephemeral = true; + autoStart = true; + privateNetwork = true; + + localAddress = "10.64.99.2"; + hostAddress = "10.64.99.1"; + + extraFlags = [ + "--capability=CAP_IPC_LOCK" + ]; + + bindMounts = { + "/run/secrets" = { + hostPath = "/run/secrets/monitor"; + isReadOnly = true; + }; + "/var/lib/grafana" = { + hostPath = "/var/lib/grafana"; + isReadOnly = false; + }; + "/var/lib/postgresql" = { + hostPath = "/var/lib/grafana-postgres"; + isReadOnly = false; + }; + "/var/lib/influxdb2" = { + hostPath = "/var/lib/grafana-influxdb2"; + isReadOnly = false; + }; + }; + config = { + nixpkgs.overlays = config.nixpkgs.overlays; + + imports = with config'.flake.nixosModules; [ + hashicorp + hashicorp-envoy + telegraf + grafana + ]; + + services.hashicorp-envoy.grafana = { + service = { + name = "grafana"; + id = "grafana"; + address = "10.64.99.2"; + port = 3000; + + connect.sidecar_service = {}; + }; + + environment = { + "CONSUL_HTTP_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8500"; + "CONSUL_GRPC_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8502"; + "CONSUL_HTTP_TOKEN_FILE" = "/run/secrets/envoy-grafana.token"; + }; + + address = "10.64.99.2:19000"; + adminBind = "127.0.0.1:19100"; + hotRestart = false; + }; + + services.postgresql = { + enable = true; + ensureDatabases = singleton "grafana"; + ensureUsers = singleton { + name = "grafana"; + ensurePermissions."DATABASE grafana" = "ALL PRIVILEGES"; + }; + }; + + systemd.services.grafana = { + serviceConfig = { + Restart = "always"; + RestartSec = "10s"; + }; + }; + + services.grafana-magic = { + enable = true; + settings = { + security = { + content_security_policy = true; + disable_gravatar = true; + data_source_proxy_whitelist = concatStringsSep " " [ + "127.0.0.1:8086" + ]; + }; + server = { + domain = "grafana.in.redalder.org"; + }; + system = { + http_addr = "127.0.0.1"; + }; + database = { + type = "postgres"; + host = "/var/run/postgresql"; + name = "grafana"; + user = "grafana"; + }; + + paths.provisioning = { + datasources.datasources = [ + { + name = "InfluxDB"; + type = "influxdb"; + access = "proxy"; + orgId = 1; + uid = "influxdb"; + url = "http://127.0.0.1:8086"; + jsonData = { + version = "Flux"; + organization = "redalder"; + defaultBucket = "bucket"; + }; + secureJsonData = { + token = "$__file{/run/secrets/grafana-influx.token}"; + }; + } + ]; + }; + }; + }; + + services.hashicorp-envoy.influx = { + service = { + name = "influx"; + id = "influx"; + address = "10.64.99.2"; + port = 8086; + + connect.sidecar_service = {}; + }; + + environment = { + "CONSUL_HTTP_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8500"; + "CONSUL_GRPC_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8502"; + "CONSUL_HTTP_TOKEN_FILE" = "/run/secrets/envoy-grafana.token"; + }; + + address = "10.64.99.2:19001"; + adminBind = "127.0.0.1:19101"; + hotRestart = false; + }; + + services.influxdb2 = { + enable = true; + settings = { + http-bind-address = "127.0.0.1:8086"; + hardening-enabled = true; + reporting-disabled = true; + }; + }; + + services.hashicorp-envoy.telegraf = { + service = { + name = "telegraf"; + id = "telegraf"; + address = "10.64.99.2"; + port = 8087; + + connect.sidecar_service = {}; + }; + + environment = { + "CONSUL_HTTP_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8500"; + "CONSUL_GRPC_ADDR" = "http://${secret.network.ips.blowhole.ip or ""}:8502"; + "CONSUL_HTTP_TOKEN_FILE" = "/run/secrets/envoy-grafana.token"; + }; + + address = "10.64.99.2:19002"; + adminBind = "127.0.0.1:19102"; + hotRestart = false; + }; + + services.telegraf-magic = { + enable = true; + settings = { + inputs.influxdb_v2_listener = { + service_address = "127.0.0.1:8087"; + bucket_tag = "bucket"; + parser_type = "upstream"; + }; + + inputs.systemd_units = { + unittype = "service"; + tags = { + host = "blowhole#monitoring"; + bucket = "metrics"; + }; + }; + + outputs.influxdb_v2 = singleton { + urls = [ "http://127.0.0.1:8086" ]; + token = "\${INFLUXDB_TOKEN}"; + organization = "redalder"; + bucket_tag = "bucket"; + }; + }; + + systemd.serviceConfig = { + EnvironmentFile = "/run/secrets/telegraf.env"; + }; + }; + }; + }; +} diff --git a/nixos/systems/blowhole/nas.nix b/nixos/systems/blowhole/nas.nix new file mode 100644 index 0000000..64e6b5a --- /dev/null +++ b/nixos/systems/blowhole/nas.nix @@ -0,0 +1,108 @@ +{ pkgs, ... }: +{ + fileSystems."/mnt/cartman" = { + device = "storfa/ds1/cartman"; + fsType = "zfs"; + }; + + systemd.services.mnt-kyle-zfs-relmount = { + requires = ["mnt-kyle.mount"]; + after = ["mnt-kyle.mount"]; + requiredBy = ["local-fs.target"]; + + path = with pkgs; [zfs util-linux]; + + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + ExecStart = "${pkgs.zfs-relmount}/bin/zfs-relmount mount storfa/ds1/kyle /mnt/kyle"; + }; + }; + + fileSystems."/mnt/kyle" = { + device = "storfa/ds1/kyle"; + fsType = "zfs"; + }; + + systemd.services.mnt-cartman-zfs-relmount = { + requires = ["mnt-cartman.mount"]; + after = ["mnt-cartman.mount"]; + requiredBy = ["local-fs.target"]; + + path = with pkgs; [zfs util-linux]; + + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + ExecStart = "${pkgs.zfs-relmount}/bin/zfs-relmount mount storfa/ds1/cartman /mnt/cartman"; + }; + }; + + fileSystems."/mnt/stan" = { + device = "storfa/ds1/stan"; + fsType = "zfs"; + }; + + systemd.services.mnt-stan-zfs-relmount = { + requires = ["mnt-stan.mount"]; + after = ["mnt-stan.mount"]; + requiredBy = ["local-fs.target"]; + + path = with pkgs; [zfs util-linux]; + + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + ExecStart = "${pkgs.zfs-relmount}/bin/zfs-relmount mount storfa/ds1/stan /mnt/stan"; + }; + }; + + fileSystems."/run/restic" = { + fsType = "tmpfs"; + options = [ "size=64M" ]; + }; + + services.restic.backups.cartman = { + initialize = true; + timerConfig = { + OnCalendar = "03:00"; + }; + + paths = [ "/run/restic/cartman" ]; + backupPrepareCommand = '' + snapshot="$(date +restic%+4Y_%U_%u)" + ${pkgs.zfs-relmount}/bin/zfs-relmount snapshot storfa/ds1/cartman "''${snapshot}" + + mkdir /run/restic/cartman + ${pkgs.zfs-relmount}/bin/zfs-relmount mount-snapshot storfa/ds1/cartman /run/restic/cartman "''${snapshot}" + + export RESTIC_PROGRESS_FPS=1 + ''; + backupCleanupCommand = '' + ${pkgs.zfs-relmount}/bin/zfs-relmount umount storfa/ds1/cartman /run/restic/cartman + rm -r /run/restic/cartman + ''; + + passwordFile = ""; + repository = ""; + }; + + systemd.timers."restic-backups-cartman" = { + timerConfig = { + Persistent = true; + WakeSystem = true; + }; + }; + + systemd.services."restic-backups-cartman" = { + path = with pkgs; [ + util-linux + zfs + ]; + serviceConfig = { + Nice = 19; + IOSchedulingClass = "idle"; + EnvironmentFile = "/var/secrets/restic-b2"; + }; + }; +} diff --git a/nixos/systems/blowhole/networking.nix b/nixos/systems/blowhole/networking.nix new file mode 100644 index 0000000..a97d665 --- /dev/null +++ b/nixos/systems/blowhole/networking.nix @@ -0,0 +1,14 @@ +# SPDX-FileCopyrightText: 2023 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ ... }: +{ + networking = { + hostName = "blowhole"; + useDHCP = false; + interfaces.enp7s0f1.useDHCP = true; + + firewall.enable = false; + hostId = "2cb135ac"; + }; +} diff --git a/nixos/systems/blowhole/nfs.nix b/nixos/systems/blowhole/nfs.nix new file mode 100644 index 0000000..0e33f8e --- /dev/null +++ b/nixos/systems/blowhole/nfs.nix @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: 2023 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ lib, ... }: +let + inherit (lib) + ; +in +{ + systemd.services.nfs-mountd.serviceConfig = { + LimitNOFILE = 8192; + }; + + services.nfs.server = { + enable = true; + lockdPort = 4001; + mountdPort = 4002; + statdPort = 4000; + exports = '' + /var/nfs/jellyfin/cache 10.64.2.1/32(rw,subtree_check,async,no_root_squash,crossmnt) + /var/nfs/jellyfin/config 10.64.2.1/32(rw,subtree_check,async,no_root_squash,crossmnt) + /var/nfs/jellyfin/media 10.64.2.1/32(rw,subtree_check,async,no_root_squash,crossmnt) + + /var/nfs/gitea-data 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/gitea-db 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/hydra-data 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/hydra-nix 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/hydra-db 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/minecraft/atm6 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/ingress-letsencrypt 10.64.0.1(rw,subtree_check,async,no_root_squash) + + /var/nfs/Magic_RB 10.64.2.129(rw,subtree_check,async) + /mnt/cartman 10.64.0.8/32(rw,subtree_check,async,no_root_squash,crossmnt) 10.64.2.129(rw,subtree_check,async,crossmnt) + /mnt/kyle 10.64.0.8/32(rw,subtree_check,async,no_root_squash,crossmnt) 10.64.2.129(rw,subtree_check,async,crossmnt) + /mnt/stan 10.64.0.8/32(rw,subtree_check,async,no_root_squash,crossmnt) 10.64.2.129(rw,subtree_check,async,crossmnt) + + /var/nfs/home-assistant_hass 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/home-assistant_db 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/home-assistant_mosquitto 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/home-assistant_zigbee2mqtt 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/syncthing/data 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/syncthing/config 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/syncthing/storage 10.64.2.1/32(rw,subtree_check,async,crossmnt) + + /var/nfs/dovecot/maildir 10.64.0.8/32(rw,subtree_check,async,no_root_squash) 10.64.2.1/32(rw,subtree_check,async,no_root_squash) 10.64.3.20/32(rw,subtree_check,async,no_root_squash) + /var/nfs/getmail/getmail.d 10.64.0.8/32(rw,subtree_check,async,no_root_squash) 10.64.2.1/32(rw,subtree_check,async,no_root_squash) 10.64.3.20/32(rw,subtree_check,async,no_root_squash) + /var/nfs/mail-configuration 10.64.0.8/32(rw,subtree_check,async,no_root_squash) 10.64.2.1/32(rw,subtree_check,async,no_root_squash) 10.64.3.20/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/baikal/specific 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/baikal/config 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + + /var/nfs/matrix/synapse 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/matrix/postgresql 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/matrix/mautrix-facebook 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + /var/nfs/matrix/registrations 10.64.2.1/32(rw,subtree_check,async,no_root_squash) + ''; + }; +} diff --git a/nixos/systems/blowhole/nixpkgs.nix b/nixos/systems/blowhole/nixpkgs.nix new file mode 100644 index 0000000..3e4b33e --- /dev/null +++ b/nixos/systems/blowhole/nixpkgs.nix @@ -0,0 +1,18 @@ +{ inputs', config', config, ... }: +{ + imports = [ + ../../common/nixpkgs.nix + ]; + + nixpkgs.overlays = + (with config'.flake.overlays; [ + udp-over-tcp + emacsclient-remote + zfs-relmount + ical2org + ]) + ++ + (with inputs'.nixng.overlays; [ + default + ]); +} diff --git a/nixos/systems/blowhole/nomad.nix b/nixos/systems/blowhole/nomad.nix new file mode 100644 index 0000000..ae17b19 --- /dev/null +++ b/nixos/systems/blowhole/nomad.nix @@ -0,0 +1,161 @@ +{inputs', lib, config, pkgs, secret, config', ...}: +let + inherit (lib) + singleton; +in +{ + services.hashicorp.vault-agent = { + settings.template = singleton { + source = pkgs.writeText "nomad.json.vtmpl" '' + { + "server": { + "encrypt": "{{ with secret "kv/data/homelab-1/blowhole/nomad/encryption_key" }}{{ or .Data.data.key "" }}{{ end }}" + }, + "vault": { + "token": "{{ with secret "kv/data/homelab-1/blowhole/nomad/vault_token" }}{{ or .Data.data.secret "" }}{{ end }}" + }, + "consul": { + "token": "{{ with secret "kv/data/homelab-1/blowhole/nomad/consul_token" }}{{ or .Data.data.secret "" }}{{ end }}" + } + } + ''; + destination = "/run/secrets/nomad.json"; + command = pkgs.writeShellScript "nomad-command" '' + sudo systemctl try-reload-or-restart hashicorp-nomad.service + ''; + }; + }; + + systemd.services.hashicorp-nomad.unitConfig = { + ConditionPathExists = "/run/secrets/nomad.json"; + }; + + services.hashicorp.nomad = { + enable = true; + + extraPackages = with pkgs; [ + coreutils + iproute2 + iptables + consul + glibc + config.nix.package + git + ]; + extraSettingsPaths = [ + "/run/secrets/nomad.json" + ]; + package = inputs'.nixpkgs-hashicorp.legacyPackages.${pkgs.stdenv.system}.nomad_1_5.overrideAttrs (old: + { + patches = with config'.flake.patches; [ + hashicorp-nomad.revert-change-consul-si-tokens-to-be-local + hashicorp-nomad.add-nix-integration + ]; + }); + + settings = { + bind_addr = secret.network.ips.blowhole.ip or ""; + server.enabled = true; + + tls = { + # http = false # true + # rpc = true + + # ca_file = "nomad-ca.pem" + # cert_file = "client.pem" + # key_file = "client-key.pem" + + # verify_server_hostname = true + # verify_https_client = true + }; + + vault = { + enabled = true; + address = "https://${secret.network.ips.vault.dns or ""}:8200"; + allow_unauthenticated = true; + create_from_role = "nomad-cluster"; + }; + + consul = { + address = "${secret.network.ips.blowhole.ip or ""}:8500"; + auto_advertise = true; + server_auto_join = true; + client_auto_join = true; + }; + + acl.enabled = true; + + telemetry = { + publish_allocation_metrics = true; + publish_node_metrics = true; + }; + + client = { + cni_path = "${pkgs.cni-plugins}/bin"; + + min_dynamic_port = 20000; + max_dynamic_port = 32000; + + options = { + "docker.privileged.enabled" = "true"; + }; + + host_network."default".cidr = secret.network.networks.home.mine or ""; + host_network."mesh".cidr = secret.network.networks.home.mine or ""; + + network_interface = "enp4s0"; + + host_volume."jellyfin-mount".path = "/mnt/jellyfin-mount"; + host_volume."cctv" = { + path = "/mnt/cctv"; + read_only = false; + }; + + enabled = true; + }; + + plugin."docker" = { + config = { + allow_caps = [ + "CHOWN" + "DAC_OVERRIDE" + "FSETID" + "FOWNER" + "MKNOD" + "NET_RAW" + "SETGID" + "SETUID" + "SETFCAP" + "SETPCAP" + "NET_BIND_SERVICE" + "SYS_CHROOT" + "KILL" + "AUDIT_WRITE" + "SYS_ADMIN" + ]; + allow_privileged = true; + extra_labels = [ + "job_name" + "job_id" + "task_group_name" + "task_name" + "namespace" + "node_name" + "node_id" + ]; + }; + }; + + disable_update_check = true; + data_dir = "/var/lib/nomad"; + + datacenter = "homelab-1"; + region = "homelab-1"; + }; + }; + + virtualisation.docker.enable = true; + virtualisation.docker.daemon.settings.dns = [ + (secret.network.ips.blowhole or "") + ]; +} diff --git a/nixos/systems/blowhole/users.nix b/nixos/systems/blowhole/users.nix new file mode 100644 index 0000000..be52f31 --- /dev/null +++ b/nixos/systems/blowhole/users.nix @@ -0,0 +1,19 @@ +{ inputs', config', secret, ... }: +{ + imports = [ + inputs'.home-manager.nixosModules.default + ../../common/users.nix + ]; + + home-manager.useGlobalPkgs = true; + home-manager.extraSpecialArgs = { + config' = config'; + inputs' = inputs'; + secret = secret; + }; + home-manager.users.main = { + imports = [ (inputs'.self + "/home-manager/modules/profiles/server.nix") ]; + + home.stateVersion = "21.05"; + }; +} diff --git a/nixos/systems/blowhole/uterranix.nix b/nixos/systems/blowhole/uterranix.nix new file mode 100644 index 0000000..d3d89ec --- /dev/null +++ b/nixos/systems/blowhole/uterranix.nix @@ -0,0 +1,66 @@ +{ config, inputs', lib, config', pkgs, ... }: +let + inherit (lib) + singleton; +in +{ + imports = [ inputs'.uterranix.nixosModules.default ]; + + uterranix.config = { config, tflib, ... }: + let + inherit (tflib) + tf; + in + { + terraform.required_providers = + config'.flake.uterranix.config.${pkgs.stdenv.system}.terraform.required_providers; + + imports = config'.uterranix.modules; + + resource."vault_consul_secret_backend_role"."envoy-grafana" = { + name = "envoy-grafana"; + + backend = "consul"; + + service_identities = [ + "grafana" + "influx" + "telegraf" + ]; + + node_identities = singleton "blowhole:homelab-1"; + }; + + resource."consul_acl_policy"."envoy-blowhole" = { + name = "envoy-blowhole"; + datacenters = singleton "homelab-1"; + + rules = '' + mesh = "write" + ''; + }; + + resource."vault_consul_secret_backend_role"."envoy-blowhole" = { + name = "envoy-blowhole"; + backend = "consul"; + + consul_policies = singleton (tf "consul_acl_policy.envoy-blowhole.name"); + + service_identities = singleton "telegraf-blowhole"; + + node_identities = [ + "blowhole:homelab-1" + ]; + }; + + resource."vault_consul_secret_backend_role"."envoy-klipper" = { + name = "envoy-klipper"; + + backend = "consul"; + + service_identities = singleton "mainsail"; + + node_identities = singleton "blowhole:homelab-1"; + }; + }; +} diff --git a/nixos/systems/blowhole/vault-agent.nix b/nixos/systems/blowhole/vault-agent.nix new file mode 100644 index 0000000..e45f4d7 --- /dev/null +++ b/nixos/systems/blowhole/vault-agent.nix @@ -0,0 +1,76 @@ +{ pkgs, lib, config, tf, inputs', ... }: +let + inherit (lib) + singleton; +in +{ + systemd.services.hashicorp-vault-agent = + let + config = pkgs.writeText "hashicorp-vault-agent-tmpfiles.d" '' + d /run/secrets 0750 root root 0 + x /run/secrets/monitor 0755 root root - + d /run/secrets/monitor 0755 root root 0 + x /run/secrets/klipper 0755 root root - + d /run/secrets/klipper 0755 root root 0 + ''; + in + { + preStart = "systemd-tmpfiles --create " + config; + postStop = "systemd-tmpfiles --clean " + config; + }; + + services.hashicorp.vault-agent = { + enable = true; + package = inputs'.nixpkgs-hashicorp.legacyPackages.${pkgs.stdenv.system}.vault; + + command = "agent"; + + extraPackages = with pkgs; [ + sudo + getent + ]; + + settings = { + vault = { + address = "https://vault.in.redalder.org:8200"; + retry = { + num_retries = 5; + }; + }; + + auto_auth = { + method = singleton { + "approle" = { + mount_path = "auth/approle"; + config = { + role_id_file_path = "/var/secrets/approle.roleid"; + secret_id_file_path = "/var/secrets/approle.secretid"; + remove_secret_id_file_after_reading = false; + }; + }; + }; + + sink = singleton { + type = "file"; + config = { + path = "/run/secrets/vault-token"; + }; + }; + }; + + template = [ + { + source = pkgs.writeText "id_ed_camera" '' + {{ with secret "kv/data/homelab-1/blowhole/id_ed_camera" }}{{ .Data.data.private }}{{ end }} + ''; + destination = "/run/secrets/id_ed_camera"; + command = pkgs.writeShellScript "id_ed_camera-command" '' + export PATH=${pkgs.util-linux}/bin:$PATH + chown root:root /run/secrets/id_ed_camera + chmod 600 /run/secrets/id_ed_camera + ''; + } + ]; + }; + }; +} diff --git a/nixos/systems/blowhole/vault.nix b/nixos/systems/blowhole/vault.nix new file mode 100644 index 0000000..646f545 --- /dev/null +++ b/nixos/systems/blowhole/vault.nix @@ -0,0 +1,80 @@ +{lib, config, pkgs, secret, inputs', ...}: +let + inherit (lib) + mkForce; + certs = config.services.acme-sh.certs; +in +{ + services.hashicorp.vault = { + enable = true; + + package = inputs'.nixpkgs-hashicorp.legacyPackages.${pkgs.stdenv.system}.vault-bin; + + settings = { + backend."file".path = "/var/lib/vault"; + + ui = true; + + listener = [ + { + "tcp" = { + address = "localhost:8200"; + tls_cert_file = "${certs.vault.certPath}"; + tls_key_file = "${certs.vault.keyPath}"; + }; + } + { + "tcp" = { + address = "${secret.network.ips.blowhole.ip or ""}:8200"; + tls_cert_file = "${certs.vault.certPath}"; + tls_key_file = "${certs.vault.keyPath}"; + }; + } + ]; + + storage."raft" = { + path = "/var/lib/vault"; + node_id = "blowhole"; + }; + cluster_addr = "https://${secret.network.ips.blowhole.ip or ""}:8201"; + api_addr = "http://${secret.network.ips.blowhole.ip or ""}:8200"; + }; + }; + + services.acme-sh.certs.vault = { + production = true; + user = "root"; + domains."vault.in.redalder.org" = "dns_hetzner"; + mainDomain = "vault.in.redalder.org"; + postRun = "systemctl try-reload-or-restart --no-block hashicorp-vault.service"; + }; + + systemd.services."acme-sh-vault" = { + serviceConfig.EnvironmentFile = mkForce "/var/secrets/hetzner.env"; + }; + + services.acme-sh.certs.vault-wildcard = { + production = true; + user = "root"; + domains."*.in.redalder.org" = "dns_hetzner"; + mainDomain = "*.in.redalder.org"; + # Trigger vault to reread certificate files. + postRun = '' + PEM_BUNDLE=$(cat < +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ inputs, ... }: +{ + flake.overlays.ical2org = + final: prev: { + x-wr-timezone = with prev; + python3.pkgs.buildPythonPackage rec { + pname = "x_wr_timezone"; + version = "0.0.5"; + + src = python3.pkgs.fetchPypi { + inherit pname version; + sha256 = "sha256-wFyzS5tYpGB6eI2whtyuV2ZyjkuU4GcocNxVk6bhP+Y="; + }; + + propagatedBuildInputs = with python3.pkgs; [ + pytz + icalendar + pygments + restructuredtext_lint + pytest + ]; + + meta = with lib; {}; + }; + + recurring-ical-events = with prev; + python3.pkgs.buildPythonPackage rec { + pname = "recurring_ical_events"; + version = "1.0.2b0"; + + src = python3.pkgs.fetchPypi { + inherit pname version; + sha256 = "sha256-aoQU7rxRJvqe3PLHPto5T2rCvFSkmqfiClwCL6SRjk0="; + }; + + propagatedBuildInputs = with python3.pkgs; [ + pytz + python-dateutil + final.x-wr-timezone + tzdata + pytest-cov + pbr + ]; + + meta = with lib; {}; + }; + + ical2orgpy = with prev; + python3.pkgs.buildPythonApplication rec { + pname = "ical2orgpy"; + version = "0.4.0"; + + src = inputs.ical2org; + + PBR_VERSION="1.2.3"; + propagatedBuildInputs = with python3.pkgs; [ + click + future + icalendar + pytz + tzlocal + final.recurring-ical-events + ]; + + meta = with prev.lib; {}; + }; + }; +} diff --git a/overlays/zfs-relmount/default.nix b/overlays/zfs-relmount/default.nix new file mode 100644 index 0000000..86237e4 --- /dev/null +++ b/overlays/zfs-relmount/default.nix @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2022 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later +{ + flake.overlays.zfs-relmount = + final: prev: { + zfs-relmount = + prev.writeShellScriptBin "zfs-relmount" + (builtins.readFile ./zfs-relmount.sh); + }; +} diff --git a/overlays/zfs-relmount/zfs-relmount.sh b/overlays/zfs-relmount/zfs-relmount.sh new file mode 100644 index 0000000..b5048c7 --- /dev/null +++ b/overlays/zfs-relmount/zfs-relmount.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2022 Richard Brežák +# +# SPDX-License-Identifier: LGPL-3.0-or-later + +function recurse_children() +{ + local volume="${1}" + local dir="${2}" + local relmount="${3}" + local children="${4}" + local action="${5}" + + for child in $children + do + if [ "${child}" == "${volume}" ] + then + continue + fi + + recursive_perform "${child}" "${dir}/$(basename "${child}")" "${action}" + done +} + +function recursive_perform() +{ + local volume="${1}" + local dir="${2}" + local action="${3}" + + local relmount="$(zfs get -Ho value :relmount "${volume}")" + local children="$(zfs list -Hrd 1 "${volume}" -o name | tr '\n' ' ')" + + if ! [ -z "${relmount}" ] && [ "${relmount}" != "-" ] + then + case "${relmount}" in + "yes") + eval "${action}" + recurse_children "${volume}" "${dir}" "${relmount}" "${children}" "${action}" + ;; + "pass") + recurse_children "${volume}" "${dir}" "${relmount}" "${children}" "${action}" + ;; + "*") + ;; + esac + fi +} + +action="${1}" +shift 1 + +case $action in + "mount") + zfs_src="${1}" + dst_dir="${2}" + + recursive_perform "${zfs_src}" "${dst_dir}" 'mount -o X-mount.mkdir -t zfs "${volume}" "${dir}"' + ;; + "mount-snapshot") + zfs_src="${1}" + dst_dir="${2}" + snapshot="${3}" + + recursive_perform "${zfs_src}" "${dst_dir}" 'mount -o X-mount.mkdir -t zfs "${volume}"@'"${snapshot}"' "${dir}"' + ;; + "umount") + zfs_src="${1}" + dst_dir="${2}" + + recursive_perform "${zfs_src}" "${dst_dir}" 'umount -t zfs "${dir}"' + ;; + "snapshot") + root="${1}" + snapshot="${2}" + + recursive_perform "${root}" "${root}" 'zfs snapshot "${volume}"@'"${snapshot}" + ;; + "*") + ;; +esac diff --git a/patches/0001-intel_lar-and-noscan.patch b/patches/0001-intel_lar-and-noscan.patch new file mode 100644 index 0000000..48ec604 --- /dev/null +++ b/patches/0001-intel_lar-and-noscan.patch @@ -0,0 +1,159 @@ +From 3590d38c853ce2fd7c2397f6421171234fb9d0a6 Mon Sep 17 00:00:00 2001 +From: Magic_RB +Date: Sat, 27 May 2023 18:11:58 +0200 +Subject: [PATCH] intel_lar and noscan + +Signed-off-by: Magic_RB +--- + hostapd/config_file.c | 4 ++++ + src/ap/ap_config.h | 1 + + src/ap/hw_features.c | 45 +++++++++++++++++++++++++++++++++++++++--- + src/ap/ieee802_11_ht.c | 6 ++++++ + 4 files changed, 53 insertions(+), 3 deletions(-) + +diff --git a/hostapd/config_file.c b/hostapd/config_file.c +index 8e179d151..6fa0849a0 100644 +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -2874,6 +2874,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, + line, bss->wpa_deny_ptk0_rekey); + return 1; + } ++ } else if (os_strcmp(buf, "noscan") == 0) { ++ conf->noscan = atoi(pos); + } else if (os_strcmp(buf, "wpa_group_update_count") == 0) { + char *endp; + unsigned long val = strtoul(pos, &endp, 0); +@@ -3446,6 +3448,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, + if (bss->ocv && !bss->ieee80211w) + bss->ieee80211w = 1; + #endif /* CONFIG_OCV */ ++ } else if (os_strcmp(buf, "noscan") == 0) { ++ conf->noscan = atoi(pos); + } else if (os_strcmp(buf, "ieee80211n") == 0) { + conf->ieee80211n = atoi(pos); + } else if (os_strcmp(buf, "ht_capab") == 0) { +diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h +index 8598602b1..a25cf2cbe 100644 +--- a/src/ap/ap_config.h ++++ b/src/ap/ap_config.h +@@ -1061,6 +1061,7 @@ struct hostapd_config { + + int ht_op_mode_fixed; + u16 ht_capab; ++ int noscan; + int ieee80211n; + int secondary_channel; + int no_pri_sec_switch; +diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c +index 842d9f5ba..30ff7b496 100644 +--- a/src/ap/hw_features.c ++++ b/src/ap/hw_features.c +@@ -24,6 +24,17 @@ + #include "beacon.h" + #include "hw_features.h" + ++static void ieee80211n_do_nothing(struct hostapd_iface *iface) ++{ ++ wpa_printf(MSG_DEBUG, ++ "Scan finished!"); ++} ++ ++static void ieee80211n_scan_channels_2g4(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++static void ieee80211n_scan_channels_5g(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++ + + void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features) +@@ -82,6 +93,33 @@ int hostapd_get_hw_features(struct hostapd_iface *iface) + + if (hostapd_drv_none(hapd)) + return -1; ++ ++ ++ ++ // scan ++ struct wpa_driver_scan_params params; ++ int ret1; ++ ++ os_memset(¶ms, 0, sizeof(params)); ++ ieee80211n_scan_channels_5g(iface, ¶ms); ++ ++ ret1 = hostapd_driver_scan(iface->bss[0], ¶ms); ++ ++ if (ret1 == -EBUSY) { ++ wpa_printf(MSG_ERROR, ++ "Failed to request a scan of neighboring BSSes ret=%d (%s)!", ++ ret1, strerror(-ret1)); ++ } ++ ++ if (ret1 == 0) { ++ iface->scan_cb = ieee80211n_do_nothing; ++ wpa_printf(MSG_DEBUG, ++ "Sleeping..."); ++ for (int i=0; i<110; i++) { ++ usleep(100000); ++ } ++ } ++ + modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags, + &dfs_domain); + if (modes == NULL) { +@@ -308,7 +346,6 @@ static int ieee80211n_check_40mhz_2g4(struct hostapd_iface *iface, + sec_chan); + } + +- + static void ieee80211n_check_scan(struct hostapd_iface *iface) + { + struct wpa_scan_results *scan_res; +@@ -517,8 +554,10 @@ static int ieee80211n_check_40mhz(struct hostapd_iface *iface) + int ret; + + /* Check that HT40 is used and PRI / SEC switch is allowed */ +- if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch) ++ if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch || iface->conf->noscan) { ++ wpa_printf(MSG_DEBUG, "Not scanning due to noscan?"); + return 0; ++ } + + hostapd_set_state(iface, HAPD_IFACE_HT_SCAN); + wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling " +@@ -967,7 +1006,7 @@ static int hostapd_is_usable_chans(struct hostapd_iface *iface) + if (!hostapd_is_usable_punct_bitmap(iface)) + return 0; + +- if (!iface->conf->secondary_channel) ++ if (!iface->conf->secondary_channel || iface->conf->noscan) + return 1; + + if (hostapd_is_usable_chan(iface, iface->freq + +diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c +index 59ecbdce7..99d6e82bc 100644 +--- a/src/ap/ieee802_11_ht.c ++++ b/src/ap/ieee802_11_ht.c +@@ -230,6 +230,9 @@ void hostapd_2040_coex_action(struct hostapd_data *hapd, + return; + } + ++ if (iface->conf->noscan) ++ return; ++ + if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie)) { + wpa_printf(MSG_DEBUG, + "Ignore too short 20/40 BSS Coexistence Management frame"); +@@ -390,6 +393,9 @@ void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta) + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return; + ++ if (iface->conf->noscan) ++ return; ++ + wpa_printf(MSG_INFO, "HT: Forty MHz Intolerant is set by STA " MACSTR + " in Association Request", MAC2STR(sta->addr)); + +-- +2.39.1 + diff --git a/patches/999-hostapd-2.10-lar.patch b/patches/999-hostapd-2.10-lar.patch new file mode 100644 index 0000000..d5159e8 --- /dev/null +++ b/patches/999-hostapd-2.10-lar.patch @@ -0,0 +1,106 @@ +--- a/hostapd/config_file.c ++++ b/hostapd/config_file.c +@@ -3459,6 +3459,8 @@ + conf->noscan = atoi(pos); + } else if (os_strcmp(buf, "ht_coex") == 0) { + conf->no_ht_coex = !atoi(pos); ++ } else if (os_strcmp(buf, "intel_lar") == 0) { ++ conf->intel_lar = atoi(pos); + } else if (os_strcmp(buf, "ieee80211n") == 0) { + conf->ieee80211n = atoi(pos); + } else if (os_strcmp(buf, "ht_capab") == 0) { +--- a/src/ap/ap_config.h ++++ b/src/ap/ap_config.h +@@ -1050,6 +1050,7 @@ + u16 ht_capab; + int noscan; + int no_ht_coex; ++ int intel_lar; + int ieee80211n; + int secondary_channel; + int no_pri_sec_switch; +--- a/src/ap/hw_features.c ++++ b/src/ap/hw_features.c +@@ -26,6 +26,17 @@ + #include "beacon.h" + #include "hw_features.h" + ++static void ieee80211n_do_nothing(struct hostapd_iface *iface) ++{ ++ wpa_printf(MSG_DEBUG, ++ "Scan finished!"); ++} ++ ++static void ieee80211n_scan_channels_2g4(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++static void ieee80211n_scan_channels_5g(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++ + + void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features) +@@ -82,6 +93,33 @@ + + if (hostapd_drv_none(hapd)) + return -1; ++ ++ //if (!iface->conf->noscan) { ++ if (iface->conf->intel_lar && !iface->conf->noscan) { ++ // scan ++ struct wpa_driver_scan_params params; ++ int ret1; ++ ++ os_memset(¶ms, 0, sizeof(params)); ++ ieee80211n_scan_channels_5g(iface, ¶ms); ++ ++ ret1 = hostapd_driver_scan(iface->bss[0], ¶ms); ++ ++ if (ret1 == -EBUSY) { ++ wpa_printf(MSG_ERROR, ++ "Failed to request a scan of neighboring BSSes ret=%d (%s)!", ++ ret1, strerror(-ret1)); ++ } ++ ++ if (ret1 == 0) { ++ iface->scan_cb = ieee80211n_do_nothing; ++ wpa_printf(MSG_DEBUG, ++ "Sleeping..."); ++ for (int i=0; i<110; i++) { ++ usleep(100000); ++ } ++ } ++ } + modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags, + &dfs_domain); + if (modes == NULL) { + +@@ -308,7 +346,6 @@ + sec_chan); + } + +- + static void ieee80211n_check_scan(struct hostapd_iface *iface) + { + struct wpa_scan_results *scan_res; +@@ -517,8 +554,10 @@ + int ret; + + /* Check that HT40 is used and PRI / SEC switch is allowed */ +- if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch) ++ if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch || iface->conf->noscan) { ++ wpa_printf(MSG_DEBUG, "Not scanning due to noscan?"); + return 0; ++ } + + hostapd_set_state(iface, HAPD_IFACE_HT_SCAN); + wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling " +@@ -916,7 +954,7 @@ + if (!hostapd_is_usable_edmg(iface)) + return 0; + +- if (!iface->conf->secondary_channel) ++ if (!iface->conf->secondary_channel || iface->conf->noscan) + return 1; + + if (hostapd_is_usable_chan(iface, iface->freq + + diff --git a/patches/hostapd-2.10-lar.patch b/patches/hostapd-2.10-lar.patch new file mode 100644 index 0000000..23fde21 --- /dev/null +++ b/patches/hostapd-2.10-lar.patch @@ -0,0 +1,139 @@ +diff -ru a/hostapd/config_file.c b/hostapd/config_file.c +--- a/hostapd/config_file.c 2022-01-16 15:51:29.000000000 -0500 ++++ b/hostapd/config_file.c 2022-07-06 15:58:31.206322430 -0500 +@@ -2906,6 +2906,8 @@ + line, bss->wpa_deny_ptk0_rekey); + return 1; + } ++ } else if (os_strcmp(buf, "noscan") == 0) { ++ conf->noscan = atoi(pos); + } else if (os_strcmp(buf, "wpa_group_update_count") == 0) { + char *endp; + unsigned long val = strtoul(pos, &endp, 0); +@@ -3474,6 +3476,8 @@ + if (bss->ocv && !bss->ieee80211w) + bss->ieee80211w = 1; + #endif /* CONFIG_OCV */ ++ } else if (os_strcmp(buf, "noscan") == 0) { ++ conf->noscan = atoi(pos); + } else if (os_strcmp(buf, "ieee80211n") == 0) { + conf->ieee80211n = atoi(pos); + } else if (os_strcmp(buf, "ht_capab") == 0) { +diff -ru a/src/ap/ap_config.h b/src/ap/ap_config.h +--- a/src/ap/ap_config.h 2022-01-16 15:51:29.000000000 -0500 ++++ b/src/ap/ap_config.h 2022-07-06 15:58:31.206322430 -0500 +@@ -1014,6 +1014,7 @@ + + int ht_op_mode_fixed; + u16 ht_capab; ++ int noscan; + int ieee80211n; + int secondary_channel; + int no_pri_sec_switch; +diff -ru a/src/ap/hw_features.c b/src/ap/hw_features.c +--- a/src/ap/hw_features.c 2022-01-16 15:51:29.000000000 -0500 ++++ b/src/ap/hw_features.c 2022-07-06 22:57:53.007315518 -0500 +@@ -24,6 +24,17 @@ + #include "beacon.h" + #include "hw_features.h" + ++static void ieee80211n_do_nothing(struct hostapd_iface *iface) ++{ ++ wpa_printf(MSG_DEBUG, ++ "Scan finished!"); ++} ++ ++static void ieee80211n_scan_channels_2g4(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++static void ieee80211n_scan_channels_5g(struct hostapd_iface *iface, ++ struct wpa_driver_scan_params *params); ++ + + void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features, + size_t num_hw_features) +@@ -82,6 +93,33 @@ + + if (hostapd_drv_none(hapd)) + return -1; ++ ++ ++ ++ // scan ++ struct wpa_driver_scan_params params; ++ int ret1; ++ ++ os_memset(¶ms, 0, sizeof(params)); ++ ieee80211n_scan_channels_5g(iface, ¶ms); ++ ++ ret1 = hostapd_driver_scan(iface->bss[0], ¶ms); ++ ++ if (ret1 == -EBUSY) { ++ wpa_printf(MSG_ERROR, ++ "Failed to request a scan of neighboring BSSes ret=%d (%s)!", ++ ret1, strerror(-ret1)); ++ } ++ ++ if (ret1 == 0) { ++ iface->scan_cb = ieee80211n_do_nothing; ++ wpa_printf(MSG_DEBUG, ++ "Sleeping..."); ++ for (int i=0; i<110; i++) { ++ usleep(100000); ++ } ++ } ++ + modes = hostapd_get_hw_feature_data(hapd, &num_modes, &flags, + &dfs_domain); + if (modes == NULL) { +@@ -308,7 +346,6 @@ + sec_chan); + } + +- + static void ieee80211n_check_scan(struct hostapd_iface *iface) + { + struct wpa_scan_results *scan_res; +@@ -517,8 +554,10 @@ + int ret; + + /* Check that HT40 is used and PRI / SEC switch is allowed */ +- if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch) ++ if (!iface->conf->secondary_channel || iface->conf->no_pri_sec_switch || iface->conf->noscan) { ++ wpa_printf(MSG_DEBUG, "Not scanning due to noscan?"); + return 0; ++ } + + hostapd_set_state(iface, HAPD_IFACE_HT_SCAN); + wpa_printf(MSG_DEBUG, "Scan for neighboring BSSes prior to enabling " +@@ -915,7 +954,7 @@ + if (!hostapd_is_usable_edmg(iface)) + return 0; + +- if (!iface->conf->secondary_channel) ++ if (!iface->conf->secondary_channel || iface->conf->noscan) + return 1; + + if (hostapd_is_usable_chan(iface, iface->freq + +diff -ru a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c +--- a/src/ap/ieee802_11_ht.c 2022-01-16 15:51:29.000000000 -0500 ++++ b/src/ap/ieee802_11_ht.c 2022-07-06 15:58:31.206322430 -0500 +@@ -230,6 +230,9 @@ + return; + } + ++ if (iface->conf->noscan) ++ return; ++ + if (len < IEEE80211_HDRLEN + 2 + sizeof(*bc_ie)) { + wpa_printf(MSG_DEBUG, + "Ignore too short 20/40 BSS Coexistence Management frame"); +@@ -390,6 +393,9 @@ + if (iface->current_mode->mode != HOSTAPD_MODE_IEEE80211G) + return; + ++ if (iface->conf->noscan) ++ return; ++ + wpa_printf(MSG_INFO, "HT: Forty MHz Intolerant is set by STA " MACSTR + " in Association Request", MAC2STR(sta->addr)); +