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));
+