summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/dns.nix38
-rw-r--r--modules/users.nix48
-rw-r--r--modules/xnet/README18
-rw-r--r--modules/xnet/default.nix114
-rw-r--r--modules/xnet/desktop/default.nix208
-rw-r--r--modules/xnet/disk.nix155
-rw-r--r--modules/xnet/gitserver/default.nix49
-rw-r--r--modules/xnet/gitserver/gitweb.nix70
-rw-r--r--modules/xnet/monitoring/22604_rev2.json1565
-rw-r--r--modules/xnet/monitoring/default.nix58
-rw-r--r--modules/xnet/monitoring/grafana.nix78
-rw-r--r--modules/xnet/net/default.nix128
-rw-r--r--modules/xnet/net/dns.nix38
-rw-r--r--modules/xnet/net/sshd.nix46
-rw-r--r--modules/xnet/nginx.nix27
-rw-r--r--modules/xnet/persist.nix61
16 files changed, 2701 insertions, 0 deletions
diff --git a/modules/dns.nix b/modules/dns.nix
new file mode 100644
index 0000000..0632fa9
--- /dev/null
+++ b/modules/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/users.nix b/modules/users.nix
new file mode 100644
index 0000000..f9f7df3
--- /dev/null
+++ b/modules/users.nix
@@ -0,0 +1,48 @@
+{ pkgs, perSystem, ... }:
+let
+ inherit (builtins)
+ attrValues;
+
+ inherit (perSystem.self)
+ perf-flamegraph
+ ;
+
+ keys = {
+ t480 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEOw8YEbHsKy38JHp9W1wcxxZgWCDgnabOXccZUN5ddd";
+ t1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7T2uWJFUu8aFZZgQusGKyEMocb2pKbHLDad2eIJus9";
+ };
+in
+{
+ kle = {
+ initialHashedPassword = "$6$R4dDhaftX.vapGMd$.An36hlp3DXfkIC7bPZ0MDPo6Zvpk8JRrhy2LES.lZZj6JDa74oJkcMW3DCsIySvLJxOPXSShos0TpgJ/w0fH/";
+ isNormalUser = true;
+ shell = pkgs.fish;
+ home = "/persist/usr/kle";
+ createHome = true;
+ extraGroups = [ "wheel" "users" "networkmanager" "video" "corectrl" ];
+ packages = with pkgs; [
+ btop
+ curl
+ emacs
+ fzf
+ guvcview
+ jq
+ jujutsu
+ lynx
+ neovim
+ perf-flamegraph
+ ranger
+ ripgrep
+ rsync
+ sshfs
+ tree
+ zip
+
+ # nix debugging
+ nixd
+ nixpkgs-fmt
+ ];
+
+ openssh.authorizedKeys.keys = attrValues keys;
+ };
+}
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;
+}