diff options
Diffstat (limited to 'modules/xnet')
| -rw-r--r-- | modules/xnet/README | 18 | ||||
| -rw-r--r-- | modules/xnet/default.nix | 114 | ||||
| -rw-r--r-- | modules/xnet/desktop/default.nix | 208 | ||||
| -rw-r--r-- | modules/xnet/disk.nix | 155 | ||||
| -rw-r--r-- | modules/xnet/gitserver/default.nix | 49 | ||||
| -rw-r--r-- | modules/xnet/gitserver/gitweb.nix | 70 | ||||
| -rw-r--r-- | modules/xnet/monitoring/22604_rev2.json | 1565 | ||||
| -rw-r--r-- | modules/xnet/monitoring/default.nix | 58 | ||||
| -rw-r--r-- | modules/xnet/monitoring/grafana.nix | 78 | ||||
| -rw-r--r-- | modules/xnet/net/default.nix | 128 | ||||
| -rw-r--r-- | modules/xnet/net/dns.nix | 38 | ||||
| -rw-r--r-- | modules/xnet/net/sshd.nix | 46 | ||||
| -rw-r--r-- | modules/xnet/nginx.nix | 27 | ||||
| -rw-r--r-- | modules/xnet/persist.nix | 61 |
14 files changed, 2615 insertions, 0 deletions
diff --git a/modules/xnet/README b/modules/xnet/README new file mode 100644 index 0000000..ab1c28c --- /dev/null +++ b/modules/xnet/README @@ -0,0 +1,18 @@ +xnet +==== + +Base configuration for any machines running a standard xnet +configuration. + +Notable features: + + - Root filesystem running in RAM + - Automated ZFS partitioning of disk + - Hardened SSH and web services + +Usage +===== + +The entire module is exposed from the top-level flake as a nixosModule, +but it changes too often to be considered stable. Options are made +available when imported under the 'xnet' attribute set. diff --git a/modules/xnet/default.nix b/modules/xnet/default.nix new file mode 100644 index 0000000..9d224cf --- /dev/null +++ b/modules/xnet/default.nix @@ -0,0 +1,114 @@ +{ pkgs, lib, config, ... }: +let + inherit (builtins) + fetchurl + filter + readFile + ; + + inherit (lib) + mkDefault + mkOption + types + splitString + ; + + gitKeys = fetchurl { + url = "https://github.com/kbujari.keys"; + sha256 = "1kskbiyqvjz1wsmcrgh9v0iryf33y70zk503z0m96wmzdjllmc94"; + }; + +in +{ + imports = [ + ./disk.nix + ./nginx.nix + ./persist.nix + ./net + ./desktop + ./gitserver + ./monitoring + ]; + + options.xnet = { + pubKeys = mkOption { + type = types.listOf types.str; + default = filter (s: s != "") + (splitString "\n" (readFile gitKeys)); + }; + }; + + config = { + i18n.defaultLocale = mkDefault "en_US.UTF-8"; + time.timeZone = mkDefault "America/Toronto"; + + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; + + nix = { + settings = { + auto-optimise-store = true; + experimental-features = [ "nix-command" "flakes" ]; + warn-dirty = false; + + # timeout fast from binary cache + connect-timeout = 5; + }; + gc = { + automatic = true; + options = mkDefault "--delete-older-than 30d"; + }; + }; + + documentation = { + doc.enable = mkDefault false; + info.enable = mkDefault false; + }; + + users.mutableUsers = false; + users.users.root = { + openssh.authorizedKeys.keys = config.xnet.pubKeys; + initialPassword = "hello"; + }; + + security.sudo = { + execWheelOnly = true; + extraConfig = "Defaults lecture = never"; + }; + + nixpkgs.config.allowUnfree = true; + + programs.ssh = { + knownHosts = { + "github.com".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"; + "gitlab.com".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf"; + "git.sr.ht".publicKey = " ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMZvRd4EtM7R+IHVMWmDkVU3VLQTSwQDSAvW0t2Tkj60"; + "pascal.ee.ryerson.ca".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFmWInQNT6EoU1NtUYzTs5jtpfbO/m6yvCckOiEGjvDc"; + }; + extraConfig = '' + Host github + HostName github.com + User git + PreferredAuthentications publickey + + Host ee + HostName pascal.ee.ryerson.ca + User kbujari + ''; + }; + + programs.git = { + enable = true; + config = { + init.defaultBranch = "master"; + fetch.prune = true; + core.excludesFile = pkgs.writeText "gitignore" '' + # dev shell caching + .direnv/ + .envrc + ''; + push.default = "upstream"; + push.autoSetupRemote = true; + }; + }; + }; +} diff --git a/modules/xnet/desktop/default.nix b/modules/xnet/desktop/default.nix new file mode 100644 index 0000000..995381f --- /dev/null +++ b/modules/xnet/desktop/default.nix @@ -0,0 +1,208 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.xnet.desktop; + inherit (lib) mkDefault mkOption mkForce mkIf types; + inherit (builtins) listToAttrs; +in +{ + options.xnet.desktop = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable graphical desktop."; + }; + }; + + config = mkIf cfg.enable { + security.rtkit.enable = true; + services.pipewire = { + enable = true; + pulse.enable = true; + alsa.enable = true; + }; + + systemd.user.services.waybar = { + enable = true; + after = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + wantedBy = [ "graphical-session.target" ]; + serviceConfig = { + ExecStart = "${pkgs.waybar}/bin/waybar"; + Type = "simple"; + }; + }; + + programs.sway = { + enable = true; + wrapperFeatures = { + gtk = true; + base = true; + }; + xwayland.enable = true; + extraPackages = with pkgs; [ + foot + fuzzel + imv + mako + mpv + niri + playerctl + pop-icon-theme + pwvucontrol + sway-contrib.grimshot + swayidle + swaylock + tigervnc + udiskie + waybar + wl-clipboard + zathura + ]; + extraSessionCommands = '' + export MOZ_ENABLE_WAYLAND=1 + export MOZ_USE_XINPUT2=1 + export MOZ_WEBRENDER=1 + export XDG_CURRENT_DESKTOP=sway + export XDG_SESSION_TYPE=wayland + ''; + }; + + qt = { + enable = true; + style = "adwaita-dark"; + platformTheme = "gnome"; + }; + + fonts.packages = with pkgs; [ + departure-mono + iosevka + noto-fonts + noto-fonts-cjk-sans + noto-fonts-emoji + nerd-fonts.symbols-only + terminus_font + ]; + + xdg.portal = { + enable = true; + extraPortals = with pkgs; [ + xdg-desktop-portal-wlr + xdg-desktop-portal-gtk + ]; + }; + + environment.etc."xdg/user-dirs.defaults".text = '' + DOWNLOAD=/tmp/downloads + ''; + + environment.systemPackages = [ pkgs.man-pages pkgs.man-pages-posix ]; + + programs = { + fish = { + enable = true; + interactiveShellInit = '' + # disable greeting + set fish_greeting + ''; + }; + light.enable = mkDefault true; + direnv = { + enable = true; + nix-direnv.enable = true; + }; + + # GTK settings + dconf = { + enable = true; + profiles.user.databases = [{ + lockAll = true; + settings = { + "org/gnome/desktop/interface" = { + color-scheme = "prefer-dark"; + gtk-font-name = "System-ui 10"; + icon-theme = "Pop"; + theme-name = "Adwaita-dark"; + }; + }; + }]; + }; + }; + + # Helper for managing dotfiles + environment.shellAliases.dots = + "git --git-dir=$HOME/.local/cfg/ --work-tree=$HOME"; + + # Enable yubikey for SSH and more + services = { + yubikey-agent.enable = true; + pcscd.enable = true; + udev.packages = with pkgs; [ yubikey-personalization ]; + udisks2.enable = true; + }; + + programs.firefox = { + enable = true; + preferences = { + # Enable hardware transcoding + "media.ffmpeg.vaapi.enabled" = true; + + # Disable controlling media with keyboard + "media.hardwaremediakeys.enabled" = false; + # Enable legacy compact mode + "browser.compactmode.show" = true; + "browser.uidensity" = 1; + + # Disable ctrl+q closing browser + "browser.quitShortcut.disabled" = true; + + # Hardcode theme to dark mode + "ui.systemUsesDarkTheme" = 1; + }; + policies = { + DefaultDownloadDirectory = "/tmp/firefox"; + DisableTelemetry = true; + DisableFirefoxStudies = true; + EnableTrackingProtection = { + Value = true; + Locked = true; + Cryptomining = true; + Fingerprinting = true; + }; + DisableAccounts = true; + DisableFirefoxAccounts = true; + DisableFirefoxScreenshots = true; + DisablePocket = true; + DisplayBookmarksToolbar = "never"; + DisplayMenuBar = "default-off"; + DontCheckDefaultBrowser = true; + Homepage = { + URL = "about:blank"; + StartPage = "homepage"; + }; + HttpsOnlyMode = "enabled"; + DNSOverHTTPS = false; + NewTabPage = false; + OfferToSaveLogins = false; + PasswordManagerEnabled = false; + SearchBar = "unified"; + SearchEngines.Default = "DuckDuckGo"; + SearchSuggestEnabled = false; + ExtensionSettings = + let + extension = shortId: uuid: { + name = uuid; + value = { + install_url = "https://addons.mozilla.org/en-US/firefox/downloads/latest/${shortId}/latest.xpi"; + installation_mode = "force_installed"; + }; + }; + in + listToAttrs [ + (extension "ublock-origin" "uBlock0@raymondhill.net") + (extension "bitwarden-password-manager" "{446900e4-71c2-419f-a6a7-df9c091e268b}") + (extension "darkreader" "addon@darkreader.org") + ]; + }; + }; + }; +} diff --git a/modules/xnet/disk.nix b/modules/xnet/disk.nix new file mode 100644 index 0000000..82e9c87 --- /dev/null +++ b/modules/xnet/disk.nix @@ -0,0 +1,155 @@ +{ config, lib, modulesPath, inputs, ... }: +let + cfg = config.xnet.disk; + commonOpts = { + acltype = "posixacl"; + atime = "off"; + compression = "on"; + normalization = "formD"; + relatime = "off"; + xattr = "sa"; + "com.sun:auto-snapshot" = "false"; + }; + + inherit (lib) mkOption mkDefault mkIf types; +in +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + inputs.disko.nixosModules.default + ]; + + options.xnet.disk = { + enable = mkOption { + type = types.bool; + default = false; + description = "Apply xnet-standard ZFS disk layout."; + }; + + device = mkOption { + type = types.str; + description = "Device used for zroot ZFS pool."; + }; + }; + + config = mkIf cfg.enable { + networking.hostId = builtins.substring 0 8 + (builtins.hashString "md5" config.networking.hostName); + + services.zfs = { + autoScrub.enable = true; + trim.enable = true; + }; + + # With root running in memory, swap should be required unless + # otherwise specified + zramSwap.enable = mkDefault true; + + boot = { + kernelParams = [ "nohibernate" "elevator=none" ]; + supportedFilesystems = [ "vfat" "zfs" ]; + zfs.devNodes = mkDefault "/dev/disk/by-partuuid"; + loader = { + systemd-boot.enable = true; + efi.canTouchEfiVariables = true; + }; + initrd = { + systemd.enable = true; + availableKernelModules = [ + "xhci_pci" + "ahci" + "nvme" + "usb_storage" + "sd_mod" + "sdhci_pci" + ]; + }; + tmp.cleanOnBoot = mkDefault true; + }; + + disko.devices.disk.main = { + type = "disk"; + device = cfg.device; + content = { + type = "gpt"; + partitions.ESP = { + size = "1G"; + type = "EF00"; + content = { + type = "filesystem"; + format = "vfat"; + mountpoint = "/boot"; + }; + }; + partitions.ZFS = { + size = "100%"; + content = { + type = "zfs"; + pool = "zroot"; + }; + }; + }; + }; + + disko.devices = { + nodev."/" = { + fsType = "tmpfs"; + mountOptions = [ "defaults" "size=2G" "mode=755" ]; + }; + + zpool.zroot = { + type = "zpool"; + options = { + ashift = "12"; + autotrim = "on"; + }; + + datasets = { + "local" = { + type = "zfs_fs"; + options = commonOpts // { + mountpoint = "none"; + }; + }; + + "local/nix" = { + type = "zfs_fs"; + mountpoint = "/nix"; + options.mountpoint = "legacy"; + }; + + "local/reserved" = { + type = "zfs_fs"; + options = { + refreservation = "10G"; + mountpoint = "none"; + }; + }; + + "persist" = { + type = "zfs_fs"; + mountpoint = "/persist"; + options = commonOpts // { + mountpoint = "legacy"; + }; + }; + }; + }; + }; + + services.sanoid = { + enable = true; + templates.default = { + autosnap = true; + autoprune = true; + hourly = 24; + daily = 14; + monthly = 1; + }; + datasets."zroot/persist" = { + useTemplate = [ "default" ]; + recursive = true; + }; + }; + }; +} diff --git a/modules/xnet/gitserver/default.nix b/modules/xnet/gitserver/default.nix new file mode 100644 index 0000000..2152ebd --- /dev/null +++ b/modules/xnet/gitserver/default.nix @@ -0,0 +1,49 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.xnet.gitServer; + inherit (lib) mkOption mkIf types; +in +{ + imports = [ ./gitweb.nix ]; + + options.xnet.gitServer = { + enable = mkOption { + type = types.bool; + default = false; + description = "Serve git repos over SSH."; + }; + + path = mkOption { + type = types.path; + default = "/persist/repo/git"; + description = "Directory where repos will be stored."; + }; + }; + + config = mkIf cfg.enable { + users.users.git = { + group = "git"; + initialPassword = ""; + isSystemUser = true; + home = cfg.path; + homeMode = "755"; + createHome = true; + shell = "${pkgs.git}/bin/git-shell"; + openssh.authorizedKeys.keys = config.xnet.pubKeys; + }; + + users.groups.git = { }; + + programs.git = { + enable = true; + config = { + init = { + defaultBranch = "master"; + }; + safe = { + directory = "*"; + }; + }; + }; + }; +} diff --git a/modules/xnet/gitserver/gitweb.nix b/modules/xnet/gitserver/gitweb.nix new file mode 100644 index 0000000..da9a5ee --- /dev/null +++ b/modules/xnet/gitserver/gitweb.nix @@ -0,0 +1,70 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.xnet.gitServer.gitweb; + inherit (lib) mkOption mkIf types; +in +{ + options.xnet.gitServer.gitweb = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable web interface to git repos."; + }; + + hostName = mkOption { + type = types.str; + default = "src.web.4kb.net"; + description = "Hostname the webUI is served from."; + }; + }; + + config = mkIf cfg.enable { + xnet.nginx.enable = true; + + users.users.nginx.extraGroups = [ "git" ]; + services.cgit.main = { + enable = true; + scanPath = config.xnet.gitServer.path; + package = pkgs.cgit-pink; + nginx = { + virtualHost = cfg.hostName; + location = "/"; + }; + extraConfig = '' + mimetype.gif=image/gif + mimetype.html=text/html + mimetype.jpeg=image/jpeg + mimetype.jpg=image/jpeg + mimetype.pdf=application/pdf + mimetype.png=image/png + mimetype.svg=image/svg+xml + readme=:readme + readme=:readme.md + readme=:readme.txt + readme=:README + readme=:README.md + readme=:README.txt + ''; + settings = { + about-filter = "${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh"; + source-filter = "${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py"; + clone-url = "https://${cfg.hostName}/$CGIT_REPO_URL git@${cfg.hostName}:$CGIT_REPO_URL"; + enable-commit-graph = true; + enable-http-clone = false; + enable-index-links = true; + enable-remote-branches = true; + remove-suffix = true; + robots = "noindex, nofollow"; + root-desc = "What I cannot create, I do not understand"; + root-title = cfg.hostName; + section-from-path = true; + snapshots = "tar.gz tar.bz2 zip"; + }; + }; + + # services.nginx.virtualHosts."${cfg.gitweb.hostName}" = { + # useACMEHost = "4kb.net"; + # addSSL = true; + # }; + }; +} diff --git a/modules/xnet/monitoring/22604_rev2.json b/modules/xnet/monitoring/22604_rev2.json new file mode 100644 index 0000000..0adeb22 --- /dev/null +++ b/modules/xnet/monitoring/22604_rev2.json @@ -0,0 +1,1565 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "11.4.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "Extended smartctl-exporter Dashboard", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "links": [], + "mappings": [], + "min": 20, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 13, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": ["mean", "lastNotNull", "max"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "avg(smartctl_device_temperature{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"}) by (instance,device,model_name)", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "1m", + "intervalFactor": 1, + "legendFormat": "{{instance}} {{device}} {{model_name}}", + "refId": "B" + } + ], + "title": "Disk Temperature", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 13, + "y": 0 + }, + "id": 13, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(smartctl_device{instance=~\"$node\", device=~\"$disk\", interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"})", + "instant": false, + "range": true, + "refId": "A" + } + ], + "title": "Devices", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "fieldMinMax": false, + "mappings": [], + "min": 0, + "noValue": "0", + "unit": "none" + }, + "overrides": [ + { + "__systemRef": "hideSeriesFrom", + "matcher": { + "id": "byNames", + "options": { + "mode": "exclude", + "names": ["Value"], + "prefix": "All except:", + "readOnly": true + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": false, + "tooltip": false, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 2, + "x": 15, + "y": 0 + }, + "id": 16, + "options": { + "displayLabels": ["percent"], + "legend": { + "displayMode": "table", + "placement": "bottom", + "showLegend": true, + "values": ["value"] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "/^Value$/", + "values": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(smartctl_device) by (interface)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 7, + "x": 17, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "smartctl_device_media_errors{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"} > 0", + "format": "time_series", + "hide": false, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{instance}} {{device}} {{model_name}}", + "range": true, + "refId": "A" + } + ], + "title": "Media Errors", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "fieldMinMax": false, + "mappings": [], + "min": 0, + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 13, + "y": 4 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "count((smartctl_device_smart_status{instance=~\"$node\",device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device) == 0)", + "format": "table", + "instant": true, + "intervalFactor": 1, + "legendFormat": "__auto", + "refId": "A" + } + ], + "title": "Unhealthy", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "bars", + "fillOpacity": 100, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "stepAfter", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "links": [], + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byValue", + "options": { + "op": "gte", + "reducer": "allIsZero", + "value": 0 + } + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": false + } + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 7, + "x": 17, + "y": 4 + }, + "id": 5, + "options": { + "legend": { + "calcs": ["lastNotNull"], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "smartctl_device_critical_warning{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"} > 0", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "Critical Warnings {{device}}", + "range": true, + "refId": "A" + } + ], + "title": "Critical Warnings", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0 + }, + { + "color": "green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 15, + "y": 6 + }, + "id": 15, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "count(count(smartctl_device) by (instance))", + "instant": true, + "legendFormat": "nodes", + "range": false, + "refId": "A" + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false, + "minWidth": 150 + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeFromNow" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + }, + { + "id": "displayName", + "value": "Last Checked" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #A" + }, + "properties": [ + { + "id": "displayName", + "value": "Power On Time" + }, + { + "id": "unit", + "value": "s" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "model_name" + }, + "properties": [ + { + "id": "displayName", + "value": "Device Model" + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "device" + }, + "properties": [ + { + "id": "displayName", + "value": "Device" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value #B" + }, + "properties": [ + { + "id": "displayName", + "value": "Power Cycles" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "instance" + }, + "properties": [ + { + "id": "displayName", + "value": "Node" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Node" + }, + "properties": [ + { + "id": "custom.width", + "value": 80 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device" + }, + "properties": [ + { + "id": "custom.width", + "value": 107 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Last Checked" + }, + "properties": [ + { + "id": "custom.width", + "value": 142 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device Model" + }, + "properties": [ + { + "id": "custom.width", + "value": 291 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Power On Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 162 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Power Cycles" + }, + "properties": [ + { + "id": "custom.width", + "value": 117 + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 13, + "x": 0, + "y": 8 + }, + "id": 10, + "interval": "1h", + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Power On Time" + } + ] + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "max(smartctl_device_power_on_seconds{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"}) by (instance, device, model_name)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "__auto", + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "max(smartctl_device_power_cycle_count{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"}) by (instance, device, model_name)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Power Cycles", + "refId": "B" + } + ], + "title": "Disk Lifetime", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Total Written" + }, + "properties": [ + { + "id": "displayName", + "value": "Total Written" + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "decimals", + "value": 2 + }, + { + "id": "custom.align" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeFromNow" + }, + { + "id": "custom.align" + }, + { + "id": "displayName", + "value": "Last Checked" + }, + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "model_name" + }, + "properties": [ + { + "id": "displayName", + "value": "Device Model" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "device" + }, + "properties": [ + { + "id": "displayName", + "value": "Device" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "instance" + }, + "properties": [ + { + "id": "displayName", + "value": "Node" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "serial_number" + }, + "properties": [ + { + "id": "displayName", + "value": "Serial Number" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Device" + }, + "properties": [ + { + "id": "custom.width", + "value": 115 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "instance" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Serial Number" + }, + "properties": [ + { + "id": "custom.width", + "value": 177 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "model_name" + }, + "properties": [ + { + "id": "custom.width", + "value": 277 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Written" + }, + "properties": [ + { + "id": "custom.width", + "value": 123 + } + ] + } + ] + }, + "gridPos": { + "h": 10, + "w": 11, + "x": 13, + "y": 8 + }, + "id": 3, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": true, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Total Written" + } + ] + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "max(smartctl_device_bytes_written{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"}) by (device, instance, model_name, serial_number)", + "format": "table", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "__auto", + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "max((smartctl_device_attribute{instance=~\"$node\", device=~\"$disk\", attribute_name=\"Total_LBAs_Written\", attribute_value_type=\"raw\"} * on (instance,device) smartctl_device_block_size{instance=~\"$node\", device=~\"$disk\",blocks_type=\"physical\"}) * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"$type\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"}) by (device, instance, model_name, serial_number)\n", + "format": "table", + "hide": false, + "instant": true, + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "Total Data Written on SSDs", + "transformations": [ + { + "id": "merge", + "options": { + "reducers": [] + } + }, + { + "id": "calculateField", + "options": { + "alias": "Total Written", + "binary": { + "left": { + "fixed": "" + }, + "right": { + "fixed": "" + } + }, + "mode": "reduceRow", + "reduce": { + "include": ["Value #B", "Value #A"], + "reducer": "sum" + }, + "replaceFields": false, + "window": { + "reducer": "mean", + "windowAlignment": "trailing", + "windowSize": 0.1, + "windowSizeMode": "percentage" + } + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "Time", + "device", + "instance", + "model_name", + "serial_number", + "Total Written" + ] + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "", + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 75 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 13, + "x": 0, + "y": 18 + }, + "id": 8, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sort_desc(100 - smartctl_device_attribute{instance=~\"$node\", device=~\"$disk\", attribute_name=\"Wear_Leveling_Count\",attribute_value_type=\"value\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"sat\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"})", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{instance}}:{{device}}", + "refId": "A" + } + ], + "title": "Wear Level on SATA Devices", + "type": "gauge" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "displayName": "", + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 75 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 11, + "w": 11, + "x": 13, + "y": 18 + }, + "id": 14, + "options": { + "minVizHeight": 75, + "minVizWidth": 75, + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "sizing": "auto" + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "code", + "expr": "sort_desc(smartctl_device_percentage_used{instance=~\"$node\", device=~\"$disk\"} * on(instance, device) group_left(interface,serial_number,model_family,model_name) smartctl_device{interface=~\"nvme\",serial_number=~\"$serial_number\",model_name=~\"$device_model\"})\n", + "format": "time_series", + "instant": true, + "interval": "", + "intervalFactor": 1, + "legendFormat": "{{instance}}:{{device}}", + "refId": "A" + } + ], + "title": "Wear Level on NVMe Devices", + "type": "gauge" + } + ], + "refresh": "1m", + "schemaVersion": 40, + "tags": ["prometheus", "node_exporter", "smartmon"], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "definition": "label_values(smartctl_version,instance)", + "includeAll": true, + "multi": true, + "name": "node", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(smartctl_version,instance)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "definition": "label_values(smartctl_device,device)", + "includeAll": true, + "multi": true, + "name": "disk", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(smartctl_device,device)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "definition": "label_values(smartctl_device,interface)", + "includeAll": true, + "multi": true, + "name": "type", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(smartctl_device,interface)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "definition": "label_values(smartctl_device,model_name)", + "includeAll": true, + "multi": true, + "name": "device_model", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(smartctl_device,model_name)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "definition": "label_values(smartctl_device,serial_number)", + "includeAll": true, + "multi": true, + "name": "serial_number", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(smartctl_device,serial_number)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "sort": 1, + "type": "query" + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["1m", "5m", "15m", "30m", "1h", "2h", "1d"] + }, + "timezone": "browser", + "title": "SMARTctl Exporter Dashboard", + "uid": "ce8j0dmrrej9cc", + "version": 47, + "weekStart": "", + "gnetId": 22604 +} diff --git a/modules/xnet/monitoring/default.nix b/modules/xnet/monitoring/default.nix new file mode 100644 index 0000000..eed4963 --- /dev/null +++ b/modules/xnet/monitoring/default.nix @@ -0,0 +1,58 @@ +{ config, ... }: +let + inherit (builtins) + map + ; + + inherit (config.services.prometheus) exporters; +in +{ + imports = [ ./grafana.nix ]; + + + config = { + services.prometheus = { + enable = config.xnet.monitoring.hostGrafana; + globalConfig.scrape_interval = "1m"; + + exporters = { + node = { + enable = true; + openFirewall = true; + enabledCollectors = [ "processes" "systemd" ]; + }; + + # systemd = { + # enable = true; + # openFirewall = true; + # extraFlags = [ + # "--systemd.collector.enable-ip-accounting" + # "--systemd.collector.enable-restart-count" + # ]; + # }; + + smartctl = { + enable = true; + openFirewall = true; + }; + }; + + scrapeConfigs = + let + hosts = [ "iridium" "radon" ]; + in + [{ + job_name = "node"; + static_configs = [{ + targets = map (h: "${h}:${toString exporters.node.port}") hosts; + }]; + } + { + job_name = "smartctl"; + static_configs = [{ + targets = map (h: "${h}:${toString exporters.smartctl.port}") hosts; + }]; + }]; + }; + }; +} diff --git a/modules/xnet/monitoring/grafana.nix b/modules/xnet/monitoring/grafana.nix new file mode 100644 index 0000000..e95318d --- /dev/null +++ b/modules/xnet/monitoring/grafana.nix @@ -0,0 +1,78 @@ +{ config, pkgs, lib, ... }: +let + inherit (lib) mkOption mkIf types; + + inherit (config.xnet.monitoring) + hostGrafana + ; +in +{ + options.xnet.monitoring = { + hostGrafana = mkOption { + type = types.bool; + default = false; + description = "Scrape hosts and host the data."; + }; + }; + + config = mkIf hostGrafana { + services.grafana.provision = { + enable = true; + datasources.settings.datasources = [{ + name = "Prometheus"; + type = "prometheus"; + url = "http://localhost:9090"; + access = "proxy"; + editable = false; + }]; + + dashboards.settings.providers = [{ + name = "Fetched Dashboards"; + options.path = "/etc/grafana/dashboards"; + }]; + }; + + environment.etc = { + "grafana/dashboards/node-exporter.json" = { + user = "grafana"; + group = "grafana"; + source = pkgs.fetchurl { + url = "https://grafana.com/api/dashboards/1860/revisions/37/download"; + hash = "sha256-1DE1aaanRHHeCOMWDGdOS1wBXxOF84UXAjJzT5Ek6mM="; + }; + }; + "grafana/dashboards/smartctl-exporter.json" = { + user = "grafana"; + group = "grafana"; + source = ./grafana.nix; + # source = pkgs.fetchurl { + # url = "https://grafana.com/api/dashboards/22604/revisions/2/download"; + # hash = "sha256-ci8WE23fZ+ltEKFoUdNNVXsUIV0jqtas79ia2lYIo88="; + # }; + }; + }; + + services.grafana = { + enable = true; + settings.server = { + domain = "grafana.plaza.4kb.net"; + protocol = "socket"; + }; + settings."auth.anonymous" = { + enabled = true; + org_role = "Admin"; + }; + }; + + users.groups.grafana.members = [ "nginx" ]; + systemd.services.nginx.serviceConfig.ProtectHome = false; + + services.nginx.virtualHosts."${config.services.grafana.settings.server.domain}" = { + # useACMEHost = "4kb.net"; + # addSSL = true; + locations."/" = { + proxyPass = "http://unix:/${toString config.services.grafana.settings.server.socket}"; + }; + }; + }; +} diff --git a/modules/xnet/net/default.nix b/modules/xnet/net/default.nix new file mode 100644 index 0000000..7242855 --- /dev/null +++ b/modules/xnet/net/default.nix @@ -0,0 +1,128 @@ +{ config, lib, ... }: +let + cfg = config.xnet.net; + + inherit (builtins) + attrNames + attrValues + map + length + ; + + inherit (lib) + concatLines + genAttrs + mapAttrsToList + mergeAttrsList + mkOption + mkIf + types + ; + + # Networks are logical groupings of hosts wired together. This is + # accomplished using VLANS on the same layer 2 network. + # + # Hosts can hard code their spot on a network here. This module aims + # to generate network information for a host using this set of facts. + # The module should gracefully fall back to defaults if a given fact + # is not specified for the host consuming the module. + # + # A host will have it's /etc/hosts populated with all other hosts in + # a network to avoid a dynamic DNS server. + networks = { + + # Open network for xnet hosts + plaza = { + mask = 24; + vlan = 88; + hosts = { + t1 = "10.88.88.1"; + radon = "10.88.88.2"; + iridium = "10.88.88.3"; + }; + }; + + # Reserved network for kubernetes related nodes. A distinct network + # is created here for hosts that provide resources to workers. + # Generally, nodes on this network are spawned dynamically and are + # not a part of this NixOS config directly, although they may boot + # from assets served from a node here. + kubenet = { + mask = 24; + vlan = 42; + hosts = { + iridium = "10.88.42.254"; + }; + }; + }; +in +{ + imports = [ + ./sshd.nix + ./dns.nix + ]; + + options.xnet.net = { + join = mkOption { + type = types.listOf (types.enum (attrNames networks)); + default = [ ]; + description = "Available networks."; + }; + + interface = mkOption { + type = types.str; + default = ""; + description = "Network interface connecting to xnet."; + }; + }; + + config = mkIf ((length cfg.join) > 0) { + # networkd will handle this + networking.useDHCP = false; + + systemd.network = + let + name = config.networking.hostName; + + genConfig = net: { + netdevs."${toString networks.${net}.vlan}-${net}" = { + netdevConfig.Kind = "vlan"; + netdevConfig.Name = net; + vlanConfig.Id = networks.${net}.vlan; + }; + + networks."10-${cfg.interface}" = { + matchConfig.Name = cfg.interface; + vlan = cfg.join; + + # Accept DHCP on physical interface + DHCP = "ipv4"; + + # Network is up when xnet physical interface gets carrier + linkConfig.RequiredForOnline = "carrier"; + + # Allow resolved to resolve mDNS + networkConfig.MulticastDNS = true; + }; + + networks."${toString networks.${net}.vlan}-${net}" = { + matchConfig.Name = net; + + # Set up address for known host + address = [ "${networks.${net}.hosts.${name}}/${toString networks.${net}.mask}" ]; + }; + }; + + values = attrValues (genAttrs cfg.join genConfig); + in + { enable = true; } // mergeAttrsList values; + + networking.extraHosts = + let + genHosts = net: concatLines + (mapAttrsToList (name: value: "${value} ${name} ${name}.${net}.4kb.net") + networks.${net}.hosts); + in + concatLines (map (net: genHosts net) cfg.join); + }; +} diff --git a/modules/xnet/net/dns.nix b/modules/xnet/net/dns.nix new file mode 100644 index 0000000..0632fa9 --- /dev/null +++ b/modules/xnet/net/dns.nix @@ -0,0 +1,38 @@ +{ lib, ... }: +let + inherit (lib) + mkDefault + ; + + quad9 = [ + "9.9.9.9#dns.quad9.net" + "149.112.112.112#dns.quad9.net" + "2620:fe::fe#dns.quad9.net" + "2620:fe::9#dns.quad9.net" + ]; + + cloudflare = [ + "1.1.1.1#one.one.one.one" + "1.0.0.1#one.one.one.one" + "2606:4700:4700::1111#one.one.one.one" + "2606:4700:4700::1001#one.one.one.one" + ]; +in +{ + + networking.nameservers = mkDefault quad9; + + services.resolved = { + enable = true; + llmnr = "false"; + dnssec = "false"; + dnsovertls = mkDefault "opportunistic"; + fallbackDns = quad9 ++ cloudflare; + extraConfig = '' + MulticastDNS=yes + ''; + }; + + # Allow mDNS resolution + networking.firewall.allowedTCPPorts = [ 5353 ]; +} diff --git a/modules/xnet/net/sshd.nix b/modules/xnet/net/sshd.nix new file mode 100644 index 0000000..9d7976e --- /dev/null +++ b/modules/xnet/net/sshd.nix @@ -0,0 +1,46 @@ +{ config, lib, ... }: +let + cfg = config.xnet.net.sshd; + inherit (lib) mkOption mkIf types; +in +{ + options.xnet.net.sshd = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable hardened SSH service."; + }; + }; + + config = mkIf cfg.enable { + services.openssh = { + enable = true; + startWhenNeeded = true; + openFirewall = true; + hostKeys = [{ + path = "/persist/certs/ssh/ssh_host_ed25519_key"; + type = "ed25519"; + }]; + settings = { + UsePAM = false; + X11Forwarding = false; + PermitRootLogin = "prohibit-password"; + PasswordAuthentication = false; + Ciphers = [ "chacha20-poly1305@openssh.com" ]; + Macs = [ "hmac-sha2-512-etm@openssh.com" ]; + KexAlgorithms = [ "curve25519-sha256@libssh.org" ]; + }; + sftpServerExecutable = "internal-sftp"; + sftpFlags = [ "-f AUTHPRIV" "-l INFO" ]; + extraConfig = + let + pubkeyTypes = lib.strings.concatStringsSep "," [ + "sk-ssh-ed25519-cert-v01@openssh.com" + "ssh-ed25519-cert-v01@openssh.com" + "ssh-ed25519" + ]; + in + "PubkeyAcceptedKeyTypes ${pubkeyTypes}"; + }; + }; +} diff --git a/modules/xnet/nginx.nix b/modules/xnet/nginx.nix new file mode 100644 index 0000000..eadd794 --- /dev/null +++ b/modules/xnet/nginx.nix @@ -0,0 +1,27 @@ +{ config, lib, ... }: +let + cfg = config.xnet.nginx; + + inherit (lib) mkOption mkIf types; +in +{ + options.xnet.nginx = { + enable = mkOption { + type = types.bool; + default = false; + description = "Enable optimized nginx."; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 80 443 ]; + + services.nginx = { + enable = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + }; + }; +} diff --git a/modules/xnet/persist.nix b/modules/xnet/persist.nix new file mode 100644 index 0000000..10eb883 --- /dev/null +++ b/modules/xnet/persist.nix @@ -0,0 +1,61 @@ +{ config, lib, ... }: +let + inherit (lib) + concatLists + isString + mkOption + types + ; + +in +{ + options.xnet.persist = mkOption { + type = types.listOf (types.either + types.str + (types.submodule { + options = { + path = mkOption { + type = types.str; + description = "Path to persist"; + }; + user = mkOption { + type = types.nullOr types.str; + default = null; + description = "User ID to set on the persisted directory"; + }; + group = mkOption { + type = types.nullOr types.str; + default = null; + description = "Group ID to set on the persisted directory"; + }; + + mode = mkOption { + type = types.nullOr types.str; + default = null; + description = "Permissions to set"; + }; + }; + })); + default = [ ]; + description = "Automatically persisted state."; + }; + + config.systemd.tmpfiles.rules = + let + mkEntry = entry: + let + path = if isString entry then entry else entry.path; + user = if isString entry then "-" else entry.user; + group = if isString entry then "-" else entry.group; + mode = if isString entry then "-" else entry.mode; + + dent = "d /persist${path} ${mode} ${user} ${group} - -"; + link = "L+ ${path} - - - - /persist${path}"; + perm = "Z ${mode} ${user} ${group} - -"; + in + [ dent link perm ]; + + entries = map mkEntry config.xnet.persist; + in + concatLists entries; +} |
