diff options
Diffstat (limited to 'machines/xnet')
| -rw-r--r-- | machines/xnet/README | 18 | ||||
| -rw-r--r-- | machines/xnet/default.nix | 38 | ||||
| -rw-r--r-- | machines/xnet/desktop/default.nix | 124 | ||||
| -rw-r--r-- | machines/xnet/disk.nix | 139 | ||||
| -rw-r--r-- | machines/xnet/gitserver/default.nix | 54 | ||||
| -rw-r--r-- | machines/xnet/gitserver/gitweb.nix | 75 | ||||
| -rw-r--r-- | machines/xnet/monitoring/default.nix | 38 | ||||
| -rw-r--r-- | machines/xnet/monitoring/grafana.nix | 51 | ||||
| -rw-r--r-- | machines/xnet/monitoring/prometheus.nix | 15 | ||||
| -rw-r--r-- | machines/xnet/net/default.nix | 44 | ||||
| -rw-r--r-- | machines/xnet/net/sshd.nix | 46 | ||||
| -rw-r--r-- | machines/xnet/nginx.nix | 28 | ||||
| -rw-r--r-- | machines/xnet/users.nix | 74 |
13 files changed, 744 insertions, 0 deletions
diff --git a/machines/xnet/README b/machines/xnet/README new file mode 100644 index 0000000..ab1c28c --- /dev/null +++ b/machines/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/machines/xnet/default.nix b/machines/xnet/default.nix new file mode 100644 index 0000000..5a6c587 --- /dev/null +++ b/machines/xnet/default.nix @@ -0,0 +1,38 @@ +{ lib, pkgs, ... }: +let + inherit (lib) mkDefault; +in +{ + imports = [ + ./disk.nix + ./users.nix + ./nginx.nix + ./net + ./desktop + ./gitserver + # ./monitoring + ]; + + i18n.defaultLocale = mkDefault "en_US.UTF-8"; + time.timeZone = mkDefault "America/Toronto"; + + 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; + }; +} diff --git a/machines/xnet/desktop/default.nix b/machines/xnet/desktop/default.nix new file mode 100644 index 0000000..1e37497 --- /dev/null +++ b/machines/xnet/desktop/default.nix @@ -0,0 +1,124 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.xnet.desktop; + inherit (lib) mkDefault mkOption mkIf types; +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; + }; + }; + + programs.sway = { + enable = true; + wrapperFeatures = { + gtk = true; + base = true; + }; + extraPackages = with pkgs; [ + foot + fuzzel + imv + mako + mpv + playerctl + pop-icon-theme + pwvucontrol + sway-contrib.grimshot + swayidle + swaylock + tigervnc + waybar + wl-clipboard + xwayland + 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 + ''; + }; + + programs.light.enable = mkDefault true; + + qt = { + enable = true; + style = "adwaita-dark"; + platformTheme = "gnome"; + }; + + fonts.packages = with pkgs; [ + departure-mono + noto-fonts + noto-fonts-cjk-sans + noto-fonts-emoji + (nerdfonts.override { fonts = [ "NerdFontsSymbolsOnly" ]; }) + ]; + + xdg.portal = { + enable = true; + extraPortals = with pkgs; [ + xdg-desktop-portal-wlr + xdg-desktop-portal-gtk + ]; + }; + + programs.firefox = { + enable = true; + policies = { + DisableTelemetry = true; + DisableFirefoxStudies = true; + EnableTrackingProtection = { + Value = true; + Locked = true; + Cryptomining = true; + Fingerprinting = true; + }; + DisablePocket = true; + DisableFirefoxAccounts = true; + DisableAccounts = true; + DisableFirefoxScreenshots = true; + OverrideFirstRunPage = ""; + OverridePostUpdatePage = ""; + DontCheckDefaultBrowser = true; + DisplayBookmarksToolbar = "never"; + DisplayMenuBar = "default-off"; + SearchBar = "unified"; + ExtensionSettings = { + "*".installation_mode = "blocked"; # blocks all addons except the ones specified below + # uBlock Origin: + "uBlock0@raymondhill.net" = { + install_url = "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"; + installation_mode = "force_installed"; + }; + # Bitwarden: + "{446900e4-71c2-419f-a6a7-df9c091e268b}" = { + install_url = "https://addons.mozilla.org/firefox/downloads/latest/bitwarden-password-manager/latest.xpi"; + installation_mode = "force_installed"; + }; + # Dark Reader: + "addon@darkreader.org" = { + install_url = "https://addons.mozilla.org/firefox/downloads/latest/darkreader/latest.xpi"; + installation_mode = "force_installed"; + }; + }; + }; + }; + }; +} diff --git a/machines/xnet/disk.nix b/machines/xnet/disk.nix new file mode 100644 index 0000000..caef61d --- /dev/null +++ b/machines/xnet/disk.nix @@ -0,0 +1,139 @@ +{ config, lib, modulesPath, ... }: +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") + ]; + + 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 = "/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"; + }; + }; + }; + }; + }; + }; +} diff --git a/machines/xnet/gitserver/default.nix b/machines/xnet/gitserver/default.nix new file mode 100644 index 0000000..da83f73 --- /dev/null +++ b/machines/xnet/gitserver/default.nix @@ -0,0 +1,54 @@ +{ 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."; + }; + + keys = mkOption { + type = types.listOf types.str; + description = "SSH public keys used for git operations."; + }; + }; + + 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 = cfg.keys; + }; + + users.groups.git = { }; + + programs.git = { + enable = true; + config = { + init = { + defaultBranch = "master"; + }; + safe = { + directory = "*"; + }; + }; + }; + }; +} diff --git a/machines/xnet/gitserver/gitweb.nix b/machines/xnet/gitserver/gitweb.nix new file mode 100644 index 0000000..bfd593d --- /dev/null +++ b/machines/xnet/gitserver/gitweb.nix @@ -0,0 +1,75 @@ +{ 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"; + 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"; + }; + }; + + # required for rendering markdown readme + environment.systemPackages = with pkgs; [ + python312 + python312Packages.markdown + ]; + + # services.nginx.virtualHosts."${cfg.gitweb.hostName}" = { + # useACMEHost = "4kb.net"; + # addSSL = true; + # }; + }; +} diff --git a/machines/xnet/monitoring/default.nix b/machines/xnet/monitoring/default.nix new file mode 100644 index 0000000..87ae724 --- /dev/null +++ b/machines/xnet/monitoring/default.nix @@ -0,0 +1,38 @@ +{ config, ... }: +let + inherit (config.networking) hostName; + inherit (config.services.prometheus) exporters; +in +{ + imports = [ ./grafana.nix ./prometheus.nix ]; + + exporters = { + node = { + enable = true; + enabledCollectors = [ "processes" "systemd" ]; + }; + + systemd = { + enable = true; + extraFlags = [ + "--systemd.collector.enable-ip-accounting" + "--systemd.collector.enable-restart-count" + ]; + }; + }; + + services.prometheus.scrapeConfigs = [ + { + job_name = "node"; + static_configs = [{ + targets = [ "${hostName}:${toString exporters.node.port}" ]; + }]; + } + { + job_name = "systemd"; + static_configs = [{ + targets = [ "${hostName}:${toString exporters.systemd.port}" ]; + }]; + } + ]; +} diff --git a/machines/xnet/monitoring/grafana.nix b/machines/xnet/monitoring/grafana.nix new file mode 100644 index 0000000..6a37ce1 --- /dev/null +++ b/machines/xnet/monitoring/grafana.nix @@ -0,0 +1,51 @@ +{ config, pkgs, ... }: { + 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="; + }; + }; + }; + + services.grafana = { + enable = true; + settings.server = { + domain = "grafana.web.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/machines/xnet/monitoring/prometheus.nix b/machines/xnet/monitoring/prometheus.nix new file mode 100644 index 0000000..e32e078 --- /dev/null +++ b/machines/xnet/monitoring/prometheus.nix @@ -0,0 +1,15 @@ +{ config, ... }: { + services.prometheus = { + enable = true; + globalConfig.scrape_interval = "1m"; + # scrapeConfigs = [{ + # job_name = "node"; + # relabel_configs = [{ + # source_labels = [ "__address__" ]; + # regex = "(.*):[0-9]+"; + # target_label = "instance"; + # replacement = "$1"; + # }]; + # }]; + }; +} diff --git a/machines/xnet/net/default.nix b/machines/xnet/net/default.nix new file mode 100644 index 0000000..2255f53 --- /dev/null +++ b/machines/xnet/net/default.nix @@ -0,0 +1,44 @@ +{ config, lib, ... }: +let + cfg = config.xnet.net; + inherit (lib) mkOption mkIf types; + prefix = "10.26.4"; +in +{ + imports = [ + ./sshd.nix + ]; + + options.xnet.net = { + interface = mkOption { + type = types.str; + default = ""; + description = "Network interface connecting to xnet."; + }; + + addr = mkOption { + type = types.ints.between 0 255; + description = "Final octet for xnet address."; + example = 4; + }; + }; + + # TODO: + # - Add assertion that each address is only used once across config + # - Add each host to each other hosts dns configuration + config = mkIf (builtins.stringLength cfg.interface > 0) { + networking.vlans = { + "${cfg.interface}.4" = { + inherit (cfg) interface; + id = 4; + }; + }; + + networking.interfaces = { + "${cfg.interface}.4".ipv4.addresses = [{ + address = "${prefix}.${toString cfg.addr}"; + prefixLength = 24; + }]; + }; + }; +} diff --git a/machines/xnet/net/sshd.nix b/machines/xnet/net/sshd.nix new file mode 100644 index 0000000..ef225db --- /dev/null +++ b/machines/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 = true; + X11Forwarding = false; + PermitRootLogin = "no"; + 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/machines/xnet/nginx.nix b/machines/xnet/nginx.nix new file mode 100644 index 0000000..64c1d65 --- /dev/null +++ b/machines/xnet/nginx.nix @@ -0,0 +1,28 @@ +{ 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/machines/xnet/users.nix b/machines/xnet/users.nix new file mode 100644 index 0000000..53aa15b --- /dev/null +++ b/machines/xnet/users.nix @@ -0,0 +1,74 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.xnet.users; + inherit (lib) mkOption mkIf types; +in +{ + + options.xnet.users = { + enable = mkOption { + type = types.listOf (types.enum [ "kle" ]); + default = [ ]; + description = "Users to enable."; + }; + }; + + config = { + users = { + mutableUsers = false; + users.kle = mkIf (builtins.elem "kle" cfg.enable) { + hashedPassword = "$6$R4dDhaftX.vapGMd$.An36hlp3DXfkIC7bPZ0MDPo6Zvpk8JRrhy2LES.lZZj6JDa74oJkcMW3DCsIySvLJxOPXSShos0TpgJ/w0fH/"; + isNormalUser = true; + createHome = true; + extraGroups = [ "wheel" "users" "networkmanager" "video" ]; + packages = with pkgs; [ + btop + curl + fzf + jq + lynx + neovim + ranger + rsync + sshfs + tree + zip + ]; + openssh.authorizedKeys.keys = [ + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP7T2uWJFUu8aFZZgQusGKyEMocb2pKbHLDad2eIJus9" + ]; + }; + }; + + security.sudo = { + execWheelOnly = true; + extraConfig = "Defaults lecture = never"; + }; + + programs.ssh = { + knownHosts = { + "github.com".publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"; + }; + extraConfig = '' + Host github + HostName github.com + User git + PreferredAuthentications publickey + ''; + }; + + programs.git = { + config = { + init.defaultBranch = "master"; + fetch.prune = true; + core.excludesFile = pkgs.writeText "gitignore" '' + # dev shell caching + .direnv/ + .envrc + ''; + push.default = "upstream"; + push.autoSetupRemote = true; + }; + }; + }; +} |
