summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.sops.yaml41
-rw-r--r--archetypes/collections/bluetooth.nix12
-rw-r--r--archetypes/collections/desktop/cad.nix21
-rw-r--r--archetypes/collections/desktop/chromium.nix18
-rw-r--r--archetypes/collections/desktop/crypto.nix13
-rw-r--r--archetypes/collections/desktop/default.nix17
-rw-r--r--archetypes/collections/desktop/extra.nix16
-rw-r--r--archetypes/collections/desktop/firefox.nix12
-rw-r--r--archetypes/collections/desktop/graphics.nix16
-rw-r--r--archetypes/collections/desktop/office.nix18
-rw-r--r--archetypes/collections/desktop/pipewire.nix14
-rw-r--r--archetypes/collections/desktop/printing.nix17
-rw-r--r--archetypes/collections/desktop/thumbnailers.nix23
-rw-r--r--archetypes/collections/desktop/utilities.nix32
-rw-r--r--archetypes/collections/desktop/video.nix17
-rw-r--r--archetypes/collections/desktop/xserver.nix34
-rw-r--r--archetypes/collections/development/default.nix45
-rw-r--r--archetypes/collections/development/docker.nix15
-rw-r--r--archetypes/collections/filesystems.nix (renamed from modules/root/software/ddcutil.nix)5
-rw-r--r--archetypes/collections/fonts.nix16
-rw-r--r--archetypes/collections/neovim.nix18
-rw-r--r--archetypes/collections/nvidia.nix14
-rw-r--r--archetypes/collections/utilities.nix (renamed from modules/root/software/utilities.nix)15
-rw-r--r--archetypes/collections/virtualization.nix21
-rw-r--r--archetypes/default.nix15
-rw-r--r--archetypes/profiles/btrfs/default.nix17
-rw-r--r--archetypes/profiles/cuda/default.nix26
-rw-r--r--archetypes/profiles/default.nix11
-rw-r--r--archetypes/profiles/desktop/default.nix144
-rw-r--r--archetypes/profiles/desktop/resources/fontconfig/90-commit-mono-options.conf17
-rw-r--r--archetypes/profiles/desktop/resources/fontconfig/90-tamzen-disable-anti-aliasing.conf14
-rw-r--r--archetypes/profiles/gnome/default.nix13
-rw-r--r--archetypes/profiles/headless/default.nix37
-rw-r--r--archetypes/profiles/pi/default.nix30
-rw-r--r--archetypes/profiles/zfs/default.nix33
-rw-r--r--derivations/lowbat/default.nix30
-rw-r--r--derivations/pavolctld/default.nix29
-rw-r--r--derivations/st/default.nix50
-rw-r--r--flake.lock178
-rw-r--r--flake.nix139
-rw-r--r--home-manager/default.nix19
-rw-r--r--home-manager/firefox.nix113
-rw-r--r--home-manager/fontconfig.nix11
-rw-r--r--home-manager/gammastep.nix18
-rw-r--r--home-manager/home-cleanup.nix21
-rw-r--r--home-manager/htop.nix46
-rw-r--r--home-manager/mint-theme.nix43
-rw-r--r--home-manager/neovim.nix108
-rw-r--r--home-manager/pcmanfm.nix14
-rw-r--r--home-manager/qt-gtk-theme.nix15
-rw-r--r--home-manager/resources/firefox/userChrome.css175
-rw-r--r--home-manager/resources/pcmanfm/root.desktop (renamed from modules/home/resources/pcmanfm/root.desktop)2
-rw-r--r--home-manager/resources/xdg-mime/audio65
-rw-r--r--home-manager/resources/xdg-mime/directory2
-rw-r--r--home-manager/resources/xdg-mime/email6
-rw-r--r--home-manager/resources/xdg-mime/image21
-rw-r--r--home-manager/resources/xdg-mime/pdf4
-rw-r--r--home-manager/resources/xdg-mime/text15
-rw-r--r--home-manager/resources/xdg-mime/video55
-rw-r--r--home-manager/theme-st.nix46
-rw-r--r--home-manager/wallpapers.nix17
-rw-r--r--home-manager/xdg-mime.nix42
-rw-r--r--hosts/T495/configuration.nix88
-rw-r--r--hosts/T495/hardware-configuration.nix (renamed from modules/hosts/T495/hardware-configuration.nix)0
-rw-r--r--hosts/T495/resources/secrets/wg.yaml17
-rw-r--r--hosts/T495/wg.nix9
-rw-r--r--hosts/X230/configuration.nix65
-rw-r--r--hosts/X230/hardware-configuration.nix (renamed from modules/hosts/T430/hardware-configuration.nix)21
-rw-r--r--hosts/X230/resources/secrets/wg0.yaml16
-rw-r--r--hosts/X230/wg.nix (renamed from modules/hosts/T495/wg.nix)0
-rw-r--r--hosts/flex-wg-router/configuration.nix83
-rw-r--r--hosts/flex-wg-router/hardware-configuration.nix (renamed from modules/hosts/optiplex/hardware-configuration.nix)20
-rw-r--r--hosts/flex-wg-router/resources/secrets/wg.yaml17
-rw-r--r--hosts/flex-wg-router/wg.nix9
-rw-r--r--hosts/gnuslashprinter/configuration.nix29
-rw-r--r--hosts/gnuslashprinter/flask-relay-ctl.nix37
-rw-r--r--hosts/gnuslashprinter/hardware-configuration.nix44
-rw-r--r--hosts/gnuslashprinter/klipper.nix100
-rw-r--r--hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml10
-rw-r--r--hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py54
-rw-r--r--hosts/gnuslashprinter/resources/klipper/mcu/config109
-rw-r--r--hosts/gnuslashprinter/resources/klipper/printer.cfg180
-rw-r--r--hosts/hp-envy-office/configuration.nix69
-rw-r--r--hosts/hp-envy-office/hardware-configuration.nix (renamed from modules/hosts/hp-envy-office/hardware-configuration.nix)0
-rw-r--r--hosts/hp-envy-office/resources/secrets/wg.yaml16
-rw-r--r--hosts/hp-envy-office/wg.nix7
-rw-r--r--hosts/libreX60/bios-flashing.nix8
-rw-r--r--hosts/libreX60/configuration.nix47
-rw-r--r--hosts/libreX60/hardware-configuration.nix46
-rw-r--r--hosts/libreX60/home-timmy/configuration.nix8
-rw-r--r--hosts/libreX60/home.nix22
-rw-r--r--hosts/libreX60/powertop-auto-tune.nix17
-rw-r--r--hosts/optiplex/configuration.nix109
-rw-r--r--hosts/optiplex/g610.nix9
-rw-r--r--hosts/optiplex/hardware-configuration.nix44
-rw-r--r--hosts/piframe/configuration.nix77
-rw-r--r--hosts/piframe/hardware-configuration.nix24
-rw-r--r--hosts/poweredge/bitcoind.nix41
-rw-r--r--hosts/poweredge/configuration.nix45
-rw-r--r--hosts/poweredge/ddns-updater.nix22
-rw-r--r--hosts/poweredge/filebrowser.nix48
-rw-r--r--hosts/poweredge/fileshares.nix47
-rw-r--r--hosts/poweredge/gitea.nix41
-rw-r--r--hosts/poweredge/hardware-configuration.nix50
-rw-r--r--hosts/poweredge/immich.nix78
-rw-r--r--hosts/poweredge/jellyfin.nix91
-rw-r--r--hosts/poweredge/lgtm.nix201
-rw-r--r--hosts/poweredge/networking.nix157
-rw-r--r--hosts/poweredge/notification-mailer.nix27
-rw-r--r--hosts/poweredge/nvidia.nix22
-rw-r--r--hosts/poweredge/resources/secrets/ddns-updater-config.yaml16
-rw-r--r--hosts/poweredge/resources/secrets/grafana.yaml16
-rw-r--r--hosts/poweredge/resources/secrets/mailer-pass.yaml16
-rw-r--r--hosts/poweredge/resources/secrets/router.yaml17
-rw-r--r--hosts/poweredge/resources/secrets/transmission.yaml17
-rw-r--r--hosts/poweredge/router-hosts.nix84
-rw-r--r--hosts/poweredge/transmission.nix118
-rw-r--r--hosts/sweetiepc/configuration.nix51
-rw-r--r--hosts/sweetiepc/hardware-configuration.nix38
-rw-r--r--hosts/sweetiepc/resources/secrets/wg.yaml16
-rw-r--r--hosts/sweetiepc/wg.nix7
-rw-r--r--lib/default.nix3
-rw-r--r--lib/home-manager/default.nix3
-rw-r--r--lib/home-manager/mkprofile.nix9
-rw-r--r--lib/mkprofile.nix22
-rw-r--r--modules/home/alacritty.nix67
-rw-r--r--modules/home/chrome.nix28
-rw-r--r--modules/home/default.nix24
-rw-r--r--modules/home/firefox.nix80
-rw-r--r--modules/home/git.nix7
-rw-r--r--modules/home/gtk-bookmarks.nix17
-rw-r--r--modules/home/htop.nix38
-rw-r--r--modules/home/initial-home-setup.nix22
-rw-r--r--modules/home/pcmanfm.nix6
-rw-r--r--modules/home/resources/activation-scripts/clone-repos.sh24
-rw-r--r--modules/home/resources/firefox/uiCustomization.json1
-rw-r--r--modules/home/resources/firefox/userChrome.css193
-rw-r--r--modules/home/theme.nix74
-rw-r--r--modules/home/wallpapers.nix14
-rw-r--r--modules/hosts/T430/configuration.nix18
-rw-r--r--modules/hosts/T430/home.nix6
-rw-r--r--modules/hosts/T495/configuration.nix41
-rw-r--r--modules/hosts/T495/games.nix7
-rw-r--r--modules/hosts/T495/home.nix13
-rw-r--r--modules/hosts/T495/input-leap.nix11
-rw-r--r--modules/hosts/T495/resources/input-leap/input-leap.conf12
-rw-r--r--modules/hosts/T495/resources/secrets/wg0.yaml21
-rw-r--r--modules/hosts/hp-envy-office/configuration.nix39
-rw-r--r--modules/hosts/hp-envy-office/home.nix12
-rw-r--r--modules/hosts/libreX60/configuration.nix14
-rw-r--r--modules/hosts/libreX60/home.nix8
-rw-r--r--modules/hosts/optiplex/configuration.nix50
-rw-r--r--modules/hosts/optiplex/home.nix7
-rw-r--r--modules/root/autologin.nix13
-rw-r--r--modules/root/bluetooth.nix10
-rw-r--r--modules/root/bootloader.nix43
-rw-r--r--modules/root/default.nix50
-rw-r--r--modules/root/doas.nix19
-rw-r--r--modules/root/fonts.nix16
-rw-r--r--modules/root/home-manager.nix11
-rw-r--r--modules/root/hosts.nix15
-rw-r--r--modules/root/localization.nix5
-rw-r--r--modules/root/nas.nix36
-rw-r--r--modules/root/normaluser.nix12
-rw-r--r--modules/root/pipewire.nix12
-rw-r--r--modules/root/powerkeys.nix7
-rw-r--r--modules/root/resources/secrets/secrets.yaml22
-rw-r--r--modules/root/resources/x11/xinit-startx-xdg.patch14
-rw-r--r--modules/root/software/awesome.nix20
-rw-r--r--modules/root/software/default.nix34
l---------modules/root/software/derivations1
-rw-r--r--modules/root/software/desktop.nix79
-rw-r--r--modules/root/software/development.nix18
-rw-r--r--modules/root/software/docker.nix21
-rw-r--r--modules/root/software/system.nix13
-rw-r--r--modules/root/software/virtualisation.nix10
-rw-r--r--modules/root/ssh.nix8
-rw-r--r--modules/root/tlp.nix9
-rw-r--r--modules/root/wifi.nix18
-rw-r--r--modules/root/x11.nix30
-rw-r--r--modules/root/zsh.nix10
-rw-r--r--nixos/bootloader.nix60
-rw-r--r--nixos/decklink.nix21
-rw-r--r--nixos/default.nix28
-rw-r--r--nixos/geoclue.nix14
-rw-r--r--nixos/hosts.nix4
-rw-r--r--nixos/net-iface-labels.nix28
-rw-r--r--nixos/nix.nix (renamed from modules/root/nix.nix)0
-rw-r--r--nixos/polkit.nix48
-rw-r--r--nixos/powerkeys.nix14
-rw-r--r--nixos/programs/awesome.nix23
-rw-r--r--nixos/programs/ddcutil.nix12
-rw-r--r--nixos/resources/polkit-1/rules.d/0-wheel-no-pass.rules5
-rw-r--r--nixos/resources/polkit-1/rules.d/10-reboot-poweroff-users.rules17
-rw-r--r--nixos/secrets.nix (renamed from modules/root/secrets.nix)18
-rw-r--r--nixos/services/cgit.nix142
-rw-r--r--nixos/services/fileshares.nix150
-rw-r--r--nixos/services/mailer.nix67
-rw-r--r--nixos/services/router/default.nix8
-rw-r--r--nixos/services/router/dns-dhcp.nix229
-rw-r--r--nixos/services/router/hairpinning.nix47
-rw-r--r--nixos/services/router/routing.nix39
-rw-r--r--nixos/services/router/unbound-blocklist.nix82
-rw-r--r--nixos/services/searxng.nix120
-rw-r--r--nixos/services/zfs/zed-mailer.nix20
-rw-r--r--nixos/suspend.nix21
-rw-r--r--nixos/wireless.nix22
-rw-r--r--nixos/zshenv.nix24
-rw-r--r--pkgs/crazydiskinfo/default.nix41
-rw-r--r--pkgs/default.nix40
-rw-r--r--pkgs/immich-frame/default.nix46
-rw-r--r--pkgs/lowbat/default.nix28
-rw-r--r--pkgs/st/overrides.nix19
-rw-r--r--pkgs/unclutter-desktop-entry/default.nix14
-rw-r--r--pkgs/vimPlugins/overrides.nix46
-rw-r--r--pkgs/xerox-workcentre-7800-series-driver/default.nix86
-rw-r--r--pkgs/xorg/overrides.nix18
-rw-r--r--poweredge-pro outline58
-rwxr-xr-xrebuild10
-rw-r--r--todo27
-rw-r--r--users/default-home.nix5
-rw-r--r--users/default.nix5
-rw-r--r--users/timmy/default.nix92
-rw-r--r--users/timmy/home.nix106
-rw-r--r--users/timmy/hosts.nix7
-rw-r--r--users/timmy/localization.nix14
-rw-r--r--users/timmy/nas.nix35
-rw-r--r--users/timmy/printing.nix (renamed from modules/root/printing.nix)32
-rw-r--r--users/timmy/repos.nix48
-rw-r--r--users/timmy/resources/secrets/hashed-root-password.yaml34
-rw-r--r--users/timmy/resources/secrets/wpa_supplicant-conf.yaml16
-rw-r--r--users/timmy/user.nix22
-rw-r--r--users/timmy/wifi.nix17
233 files changed, 6614 insertions, 1750 deletions
diff --git a/.sops.yaml b/.sops.yaml
index a49783e..b77c3fd 100644
--- a/.sops.yaml
+++ b/.sops.yaml
@@ -1,7 +1,42 @@
keys:
- - &SSHDerived age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ - &general age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ - &flex-wg-router age1f0tmpy2nam58skmznjyqd3zf54rxtfrk6fda0vlpq9y3yg6wac7sjf0vja
+ - &poweredge age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ - &sweetiepc age1fl4rk54n2xv4vssjkw4nywy93s4yqv69uzq2f64zuwd8fjq5u4cq9wmmsw
creation_rules:
- - path_regex: secrets/.*.yaml$
+ - path_regex: timmy/resources/secrets/.*
key_groups:
- age:
- - *SSHDerived
+ - *general
+ - *flex-wg-router
+ - *poweredge
+
+ - path_regex: T495/resources/secrets/.*
+ key_groups:
+ - age:
+ - *general
+
+ - path_regex: X230/resources/secrets/.*
+ key_groups:
+ - age:
+ - *general
+
+ - path_regex: hp-envy-office/resources/secrets/.*
+ key_groups:
+ - age:
+ - *general
+
+ - path_regex: flex-wg-router/resources/secrets/.*
+ key_groups:
+ - age:
+ - *flex-wg-router
+
+ - path_regex: poweredge/resources/secrets/.*
+ key_groups:
+ - age:
+ - *poweredge
+
+ - path_regex: sweetiepc/resources/secrets/.*
+ key_groups:
+ - age:
+ - *sweetiepc
diff --git a/archetypes/collections/bluetooth.nix b/archetypes/collections/bluetooth.nix
new file mode 100644
index 0000000..749a9f1
--- /dev/null
+++ b/archetypes/collections/bluetooth.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.bluetooth;
+in {
+ options._archetypes.collections.bluetooth = {
+ enable = lib.mkEnableOption "enables bluetooth and blueman";
+ };
+
+ config = lib.mkIf cfg.enable {
+ hardware.bluetooth.enable = true;
+ services.blueman.enable = config._archetypes.collections.desktop.utilities.enable; # FIXME
+ };
+}
diff --git a/archetypes/collections/desktop/cad.nix b/archetypes/collections/desktop/cad.nix
new file mode 100644
index 0000000..6b2c26b
--- /dev/null
+++ b/archetypes/collections/desktop/cad.nix
@@ -0,0 +1,21 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.cad;
+in {
+ options._archetypes.collections.desktop.cad = {
+ enable = lib.mkEnableOption "install cad and 3d printing software";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ blender
+ freecad
+ prusa-slicer
+ ];
+ # TODO remove once #467783 is resolved
+ environment.sessionVariables = {
+ # Make GTK3 file-chooser settings discoverable
+ # per https://github.com/NixOS/nixpkgs/issues/467783#issuecomment-3648708206
+ GSETTINGS_SCHEMA_DIR ="${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}/glib-2.0/schemas";
+ };
+ };
+}
diff --git a/archetypes/collections/desktop/chromium.nix b/archetypes/collections/desktop/chromium.nix
new file mode 100644
index 0000000..b4638f0
--- /dev/null
+++ b/archetypes/collections/desktop/chromium.nix
@@ -0,0 +1,18 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.chromium;
+in {
+ options._archetypes.collections.desktop.chromium = {
+ enable = lib.mkEnableOption "install chromium browser";
+ package = lib.mkOption {
+ type = lib.types.package;
+ default = pkgs.ungoogled-chromium;
+ description = "chromium package to install";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = [
+ cfg.package
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/crypto.nix b/archetypes/collections/desktop/crypto.nix
new file mode 100644
index 0000000..bfb9186
--- /dev/null
+++ b/archetypes/collections/desktop/crypto.nix
@@ -0,0 +1,13 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.crypto;
+in {
+ options._archetypes.collections.desktop.crypto = {
+ enable = lib.mkEnableOption "install crypto wallets";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ unstable.sparrow
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/default.nix b/archetypes/collections/desktop/default.nix
new file mode 100644
index 0000000..6ac79dd
--- /dev/null
+++ b/archetypes/collections/desktop/default.nix
@@ -0,0 +1,17 @@
+{
+ imports = [
+ ./cad.nix
+ ./chromium.nix
+ ./crypto.nix
+ ./extra.nix
+ ./firefox.nix
+ ./graphics.nix
+ ./office.nix
+ ./pipewire.nix
+ ./printing.nix
+ ./thumbnailers.nix
+ ./utilities.nix
+ ./video.nix
+ ./xserver.nix
+ ];
+}
diff --git a/archetypes/collections/desktop/extra.nix b/archetypes/collections/desktop/extra.nix
new file mode 100644
index 0000000..5cf4221
--- /dev/null
+++ b/archetypes/collections/desktop/extra.nix
@@ -0,0 +1,16 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.extraUtilities;
+in {
+ options._archetypes.collections.desktop.extraUtilities = {
+ enable = lib.mkEnableOption "install extra desktop utilities";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ jellyfin-mpv-shim
+ qbittorrent
+ remmina
+ sent
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/firefox.nix b/archetypes/collections/desktop/firefox.nix
new file mode 100644
index 0000000..02656f6
--- /dev/null
+++ b/archetypes/collections/desktop/firefox.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.firefox;
+in {
+ options._archetypes.collections.desktop.firefox = {
+ enable = lib.mkEnableOption "install firefox";
+ };
+
+ config = lib.mkIf cfg.enable {
+ programs.firefox.enable = true;
+ };
+}
+
diff --git a/archetypes/collections/desktop/graphics.nix b/archetypes/collections/desktop/graphics.nix
new file mode 100644
index 0000000..f4242d1
--- /dev/null
+++ b/archetypes/collections/desktop/graphics.nix
@@ -0,0 +1,16 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.graphics;
+in {
+ options._archetypes.collections.desktop.graphics = {
+ enable = lib.mkEnableOption "install graphic design software";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ blender
+ geeqie
+ gimp3
+ inkscape
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/office.nix b/archetypes/collections/desktop/office.nix
new file mode 100644
index 0000000..23e6862
--- /dev/null
+++ b/archetypes/collections/desktop/office.nix
@@ -0,0 +1,18 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.office;
+in {
+ options._archetypes.collections.desktop.office = {
+ enable = lib.mkEnableOption "install office software";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ hunspell # Spell checking in libreoffice
+ hunspellDicts.en_US
+ kdePackages.okular
+ libreoffice
+ pdfchain
+ thunderbird
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/pipewire.nix b/archetypes/collections/desktop/pipewire.nix
new file mode 100644
index 0000000..d80ac90
--- /dev/null
+++ b/archetypes/collections/desktop/pipewire.nix
@@ -0,0 +1,14 @@
+{ lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.pipewire;
+in {
+ options._archetypes.collections.desktop.pipewire = {
+ enable = lib.mkEnableOption "enables pipewire";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.pipewire = {
+ enable = true;
+ pulse.enable = true;
+ };
+ };
+}
diff --git a/archetypes/collections/desktop/printing.nix b/archetypes/collections/desktop/printing.nix
new file mode 100644
index 0000000..ed31048
--- /dev/null
+++ b/archetypes/collections/desktop/printing.nix
@@ -0,0 +1,17 @@
+{ lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.printing;
+in {
+ options._archetypes.collections.desktop.printing = {
+ enable = lib.mkEnableOption "enables printing and avahi service";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.printing.enable = true;
+
+ services.avahi = {
+ enable = true;
+ nssmdns4 = true;
+ openFirewall = true;
+ };
+ };
+}
diff --git a/archetypes/collections/desktop/thumbnailers.nix b/archetypes/collections/desktop/thumbnailers.nix
new file mode 100644
index 0000000..50e4aa7
--- /dev/null
+++ b/archetypes/collections/desktop/thumbnailers.nix
@@ -0,0 +1,23 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.thumbnailers;
+in {
+ options._archetypes.collections.desktop.thumbnailers = {
+ enable = lib.mkEnableOption "install thumbnailers for graphical file managers";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # https://wiki.nixos.org/wiki/Thumbnails
+ # Thumbnailers created in /run/current-system/sw/share/thumbnailers
+ environment.systemPackages = with pkgs; [
+ # Video
+ ffmpegthumbnailer
+ # Images
+ gdk-pixbuf
+ # HEIF images
+ libheif
+ libheif.out
+ # RAW images
+ nufraw-thumbnailer
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/utilities.nix b/archetypes/collections/desktop/utilities.nix
new file mode 100644
index 0000000..479145b
--- /dev/null
+++ b/archetypes/collections/desktop/utilities.nix
@@ -0,0 +1,32 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.utilities;
+in {
+ options._archetypes.collections.desktop.utilities = {
+ enable = lib.mkEnableOption "install basic desktop utilities";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ arandr
+ dex # Execute .desktop files
+ dmenu
+ gammastep
+ libnotify
+ lowbat
+ mpv
+ nsxiv
+ pavolctld
+ pavucontrol
+ pcmanfm
+ qdirstat
+ qdiskinfo
+ scrot
+ st
+ wpa_supplicant_gui
+ zathura
+ ];
+
+ programs.localsend.enable = true; # Installs & opens firewall
+ services.gvfs.enable = true; # GVfs allows for mounting drives in a graphical file manager
+ };
+}
diff --git a/archetypes/collections/desktop/video.nix b/archetypes/collections/desktop/video.nix
new file mode 100644
index 0000000..cce6516
--- /dev/null
+++ b/archetypes/collections/desktop/video.nix
@@ -0,0 +1,17 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.video;
+in {
+ options._archetypes.collections.desktop.video = {
+ enable = lib.mkEnableOption "install video capture and editing software";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ dvgrab
+ ffmpeg
+ mpv
+ obs-studio
+ vlc
+ ];
+ };
+}
diff --git a/archetypes/collections/desktop/xserver.nix b/archetypes/collections/desktop/xserver.nix
new file mode 100644
index 0000000..9d62048
--- /dev/null
+++ b/archetypes/collections/desktop/xserver.nix
@@ -0,0 +1,34 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.desktop.xserver;
+in {
+ options._archetypes.collections.desktop.xserver = {
+ enable = lib.mkEnableOption "installs xserver";
+ utilities.enable = lib.mkEnableOption "installs basic xserver utilities";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services = {
+ xserver = {
+ enable = true;
+ displayManager.startx.enable = true;
+ enableTearFree = true;
+ };
+ libinput.enable = true; # Enable touchpad support
+ };
+
+ # Install basic X utilities
+ environment.systemPackages = with pkgs; lib.optionals cfg.utilities.enable [
+ setxkbmap
+ unclutter-desktop-entry
+ unclutter-xfixes
+ xcape
+ xclip
+ xdotool
+ xinput
+ xkill
+ xrandr
+ xset
+ xwallpaper
+ ];
+ };
+}
diff --git a/archetypes/collections/development/default.nix b/archetypes/collections/development/default.nix
new file mode 100644
index 0000000..87fe2d2
--- /dev/null
+++ b/archetypes/collections/development/default.nix
@@ -0,0 +1,45 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.development;
+ hugoFirewallPort = 1313;
+in {
+ imports = [
+ ./docker.nix
+ ];
+
+ options._archetypes.collections.development = {
+ utilities.enable = lib.mkEnableOption "install basic dev utilities";
+ android.enable = lib.mkEnableOption "install android dev tools";
+ c.enable = lib.mkEnableOption "install c dev tools";
+ lua.enable = lib.mkEnableOption "install lua dev tools";
+ web = {
+ hugo = {
+ enable = lib.mkEnableOption "install hugo";
+ openFirewall = lib.mkEnableOption "open the port ${hugoFirewallPort} for viewing content from hugo serve on other devices";
+ };
+ node.enable = lib.mkEnableOption "install node";
+ };
+ };
+
+ config = {
+ environment.systemPackages = with pkgs; [
+ git
+ python3
+ ] ++ lib.optionals cfg.android.enable [
+ #adb-sync
+ android-tools
+ ] ++ lib.optionals cfg.c.enable [
+ gcc
+ git
+ gnumake
+ pkg-config
+ ] ++ lib.optionals cfg.lua.enable [
+ lua
+ ] ++ lib.optionals cfg.web.hugo.enable [
+ hugo
+ ] ++ lib.optionals cfg.web.node.enable [
+ nodejs
+ ];
+
+ networking.firewall.allowedTCPPorts = lib.mkIf cfg.web.hugo.openFirewall [ hugoFirewallPort ];
+ };
+}
diff --git a/archetypes/collections/development/docker.nix b/archetypes/collections/development/docker.nix
new file mode 100644
index 0000000..0c9fce4
--- /dev/null
+++ b/archetypes/collections/development/docker.nix
@@ -0,0 +1,15 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.development.docker;
+in {
+ options._archetypes.collections.development.docker = {
+ enable = lib.mkEnableOption "enables docker";
+ };
+
+ config = lib.mkIf cfg.enable {
+ virtualisation.docker.enable = true;
+
+ environment.systemPackages = with pkgs; [
+ docker-compose
+ ];
+ };
+}
diff --git a/modules/root/software/ddcutil.nix b/archetypes/collections/filesystems.nix
index 93e0af5..00ab409 100644
--- a/modules/root/software/ddcutil.nix
+++ b/archetypes/collections/filesystems.nix
@@ -1,6 +1,7 @@
{ pkgs, ... }: {
- hardware.i2c.enable = true;
environment.systemPackages = with pkgs; [
- ddcutil
+ cryptsetup
+ exfat
+ ntfs3g
];
}
diff --git a/archetypes/collections/fonts.nix b/archetypes/collections/fonts.nix
new file mode 100644
index 0000000..136e31b
--- /dev/null
+++ b/archetypes/collections/fonts.nix
@@ -0,0 +1,16 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.fonts;
+in {
+ options._archetypes.collections.fonts = {
+ enable = lib.mkEnableOption "enables fonts";
+ };
+
+ config = lib.mkIf cfg.enable {
+ fonts.packages = with pkgs; [
+ commit-mono
+ inter
+ nerd-fonts.jetbrains-mono
+ tamzen
+ ];
+ };
+}
diff --git a/archetypes/collections/neovim.nix b/archetypes/collections/neovim.nix
new file mode 100644
index 0000000..2040d77
--- /dev/null
+++ b/archetypes/collections/neovim.nix
@@ -0,0 +1,18 @@
+{ lib, pkgs, ... }: let
+ cfg.neovimPackage = pkgs.unstable.neovim;
+
+ # Neovim package that includes aliases vi(m) -> nvim
+ neovim-aliases = pkgs.symlinkJoin {
+ name = "neovim-aliases";
+ paths = [ cfg.neovimPackage ];
+ postBuild = ''
+ ln -sf $out/bin/nvim $out/bin/vi
+ ln -sf $out/bin/nvim $out/bin/vim
+ '';
+ };
+in {
+ # NOTE: Don't use programs.neovim since that will build neovim from source
+ environment.systemPackages = with pkgs; [
+ neovim-aliases
+ ];
+}
diff --git a/archetypes/collections/nvidia.nix b/archetypes/collections/nvidia.nix
new file mode 100644
index 0000000..ce22e8b
--- /dev/null
+++ b/archetypes/collections/nvidia.nix
@@ -0,0 +1,14 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.nvidia;
+in {
+ options._archetypes.collections.nvidia = {
+ enable = lib.mkEnableOption "enables nvidia debugging packages, etc.";
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.systemPackages = with pkgs; [
+ config.hardware.nvidia.package # nvidia-smi
+ nvtopPackages.nvidia
+ ];
+ };
+}
diff --git a/modules/root/software/utilities.nix b/archetypes/collections/utilities.nix
index dabf163..1597d75 100644
--- a/modules/root/software/utilities.nix
+++ b/archetypes/collections/utilities.nix
@@ -1,17 +1,20 @@
{ pkgs, ... }: {
environment.systemPackages = with pkgs; [
+ R
+ dash # TODO should be default /bin/sh
entr
fastfetch
ffmpeg
htop
jq
- light
+ killall
+ #light # TODO find replacement
lm_sensors
mediainfo
- neovim
nmap
openssl
p7zip
+ pciutils
powertop
pv
rsync
@@ -22,11 +25,13 @@
testdisk
tmux
uhubctl
+ usbutils
+ vimv-rs
wget
wireguard-tools
- xxHash
+ xxhash
yt-dlp
- (callPackage ./derivations/lowbat {})
- (callPackage ./derivations/pavolctld {})
];
+
+ services.gpm.enable = true;
}
diff --git a/archetypes/collections/virtualization.nix b/archetypes/collections/virtualization.nix
new file mode 100644
index 0000000..b91f6e1
--- /dev/null
+++ b/archetypes/collections/virtualization.nix
@@ -0,0 +1,21 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config._archetypes.collections.virtualization;
+in {
+ options._archetypes.collections.virtualization = {
+ enable = lib.mkEnableOption "enables virtualization and virt-manager";
+ };
+
+ config = lib.mkIf cfg.enable {
+ virtualisation = {
+ spiceUSBRedirection.enable = true;
+ libvirtd.enable = true;
+ # Enable efi support with ovmf firmware
+ libvirtd.qemu = {
+ package = pkgs.qemu_kvm;
+ runAsRoot = true;
+ swtpm.enable = true;
+ };
+ };
+ programs.virt-manager.enable = config._archetypes.collections.desktop.utilities.enable; # FIXME
+ };
+}
diff --git a/archetypes/default.nix b/archetypes/default.nix
new file mode 100644
index 0000000..f306073
--- /dev/null
+++ b/archetypes/default.nix
@@ -0,0 +1,15 @@
+{
+ imports = [
+ ./collections/bluetooth.nix
+ ./collections/desktop
+ ./collections/development
+ ./collections/filesystems.nix
+ ./collections/fonts.nix
+ ./collections/neovim.nix
+ ./collections/nvidia.nix
+ ./collections/utilities.nix
+ ./collections/virtualization.nix
+
+ ./profiles
+ ];
+}
diff --git a/archetypes/profiles/btrfs/default.nix b/archetypes/profiles/btrfs/default.nix
new file mode 100644
index 0000000..88fad38
--- /dev/null
+++ b/archetypes/profiles/btrfs/default.nix
@@ -0,0 +1,17 @@
+{ lib, pkgs, ... }: let
+ mkBtrfs = lib.mkOverride 820;
+
+ # https://wiki.nixos.org/wiki/Btrfs
+ nixosConfig = {
+ services.btrfs = {
+ autoScrub.enable = mkBtrfs true;
+ };
+
+ # Make docker work with btrfs
+ virtualisation.docker.storageDriver = mkBtrfs "btrfs";
+ };
+
+ homeConfig = {};
+in {
+ imports = [ (lib._mkProfileArchetype "btrfs" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/cuda/default.nix b/archetypes/profiles/cuda/default.nix
new file mode 100644
index 0000000..b57b854
--- /dev/null
+++ b/archetypes/profiles/cuda/default.nix
@@ -0,0 +1,26 @@
+{ lib, pkgs, ... }: let
+ mkCuda = lib.mkOverride 800;
+
+ nixosConfig = {
+ # Binary cache packages built with cudaSupport
+ nix.settings = {
+ substituters = mkCuda [ "https://cache.nixos-cuda.org" ];
+ trusted-public-keys = mkCuda [ "cache.nixos-cuda.org:74DUi4Ye579gUqzH4ziL9IyiJBlDpMRn9MBN8oNan9M=" ];
+ };
+
+ # Enable cudaSupport
+ nixpkgs.config.cudaSupport = mkCuda true;
+
+ _archetypes.collections = {
+ nvidia.enable = mkCuda true;
+ };
+
+ # Enable nvidia graphics
+ services.xserver.videoDrivers = mkCuda [ "nvidia" ]; # xserver.videoDrivers does not imply X
+ hardware.graphics.enable = mkCuda true;
+ };
+
+ homeConfig = {};
+in {
+ imports = [ (lib._mkProfileArchetype "cuda" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/default.nix b/archetypes/profiles/default.nix
new file mode 100644
index 0000000..1664484
--- /dev/null
+++ b/archetypes/profiles/default.nix
@@ -0,0 +1,11 @@
+{
+ imports = [
+ ./gnome # 700
+ ./cuda # 800
+ ./zfs # 810
+ ./btrfs # 820
+ ./pi # 900
+ ./headless # 910
+ ./desktop # 920
+ ];
+}
diff --git a/archetypes/profiles/desktop/default.nix b/archetypes/profiles/desktop/default.nix
new file mode 100644
index 0000000..2f71791
--- /dev/null
+++ b/archetypes/profiles/desktop/default.nix
@@ -0,0 +1,144 @@
+{ lib, pkgs, ... }: let
+ mkDesktop = lib.mkOverride 920;
+
+ nixosConfig = {
+ _archetypes.collections = {
+ desktop = {
+ firefox.enable = mkDesktop true;
+ pipewire.enable = mkDesktop true;
+ printing.enable = mkDesktop true;
+ thumbnailers.enable = mkDesktop true;
+ utilities.enable = mkDesktop true;
+ xserver = {
+ enable = mkDesktop true;
+ utilities.enable = mkDesktop true;
+ };
+ };
+ development = {
+ utilities.enable = mkDesktop true;
+ };
+ fonts.enable = mkDesktop true;
+ };
+
+ security = {
+ polkit = {
+ enable = mkDesktop true;
+ _gnome.enable = mkDesktop true;
+ _allowUserPowerControls = mkDesktop true;
+ };
+ };
+
+ programs = {
+ _ddcutil.enable = mkDesktop true;
+ };
+
+ services = {
+ _geoclue2.enable = mkDesktop true;
+ xserver = {
+ windowManager._awesome.enable = mkDesktop true;
+ enableTearFree = mkDesktop true;
+ };
+ logind._safePowerKey.enable = mkDesktop true;
+ openssh = {
+ enable = mkDesktop true;
+ settings.X11Forwarding = mkDesktop true;
+ };
+ tlp.enable = mkDesktop true;
+
+ # Ensure video group can change backlight
+ udev.extraRules = mkDesktop ''
+ SUBSYSTEM=="backlight", ACTION=="add", RUN+="${pkgs.coreutils}/bin/chgrp video /sys/class/backlight/%k/brightness", RUN+="${pkgs.coreutils}/bin/chmod g+w /sys/class/backlight/%k/brightness"
+ '';
+ };
+
+ # Disable ipv6 by default
+ networking.enableIPv6 = mkDesktop false;
+
+ # Install dconf. Home-manager will break without it if gtk theme is changed
+ environment.systemPackages = [ pkgs.dconf ];
+ };
+
+ homeConfig = {
+ gtk = {
+ enable = mkDesktop true;
+ _mintTheme.enable = mkDesktop true;
+ font.name = mkDesktop "sans-serif";
+ cursorTheme.name = mkDesktop "Adwaita";
+ };
+
+ qt._gtkPlatformTheme.enable = mkDesktop true;
+
+ fonts.fontconfig = {
+ enable = mkDesktop true;
+ defaultFonts = {
+ # These fonts are added above in fonts collection
+ sansSerif = mkDesktop [ "Inter" ];
+ monospace = mkDesktop [ "CommitMono" ];
+ };
+ configFile = {
+ tamzen-disable-anti-aliasing = {
+ enable = true;
+ source = ./resources/fontconfig/90-tamzen-disable-anti-aliasing.conf;
+ };
+ commit-mono-options = {
+ enable = true;
+ source = ./resources/fontconfig/90-commit-mono-options.conf;
+ };
+ };
+ };
+
+ programs = {
+ _pcmanfm.openAsRootOption.enable = mkDesktop true;
+ _st = {
+ enable = mkDesktop true;
+ font = {
+ name = mkDesktop "JetBrainsMonoNL Nerd Font Mono";
+ # Default attrs
+ attrs = {
+ size = 12;
+ antialias = true;
+ autohint = true;
+ };
+ };
+ };
+ firefox._configure = mkDesktop true;
+ htop._configure = mkDesktop true;
+ zathura = {
+ enable = mkDesktop true;
+ options = {
+ selection-clipboard = mkDesktop "clipboard"; # Copy highlighted text
+ };
+ };
+ };
+
+ services = {
+ _gammastep.enable = true;
+ #polkit-gnome.enable = mkDesktop true; # Doesn't work on X
+ };
+
+ xdg = {
+ autostart = {
+ enable = mkDesktop true;
+ entries = mkDesktop [
+ "${pkgs.gammastep}/share/applications/gammastep-indicator.desktop"
+ "${pkgs.unclutter-desktop-entry}/share/applications/unclutter.desktop"
+ ];
+ };
+ };
+
+ xdg.mimeApps._defaultCategoryApplications = {
+ enable = true;
+ categoryApplications = {
+ audio = [ "mpv" ];
+ directory = [ "pcmanfm" ];
+ email = [ "thunderbird" ];
+ image = [ "nsxiv" ];
+ pdf = [ "org.pwmt.zathura-pdf-mupdf" ];
+ text = [ "nvim" ];
+ video = [ "mpv" ];
+ };
+ };
+ };
+in {
+ imports = [ (lib._mkProfileArchetype "desktop" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/desktop/resources/fontconfig/90-commit-mono-options.conf b/archetypes/profiles/desktop/resources/fontconfig/90-commit-mono-options.conf
new file mode 100644
index 0000000..9c7373a
--- /dev/null
+++ b/archetypes/profiles/desktop/resources/fontconfig/90-commit-mono-options.conf
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+
+<fontconfig>
+ <description>Enable opentype features for CommitMono to make it match better with Inter as its monospace counterpart</description>
+ <match target="font">
+ <test name="family" compare="eq" ignore-blanks="true">
+ <string>CommitMono</string>
+ </test>
+ <edit name="fontfeatures" mode="append">
+ <string>ss03 on</string> <!-- smart case -->
+ <string>ss04 on</string> <!-- symbol spacing -->
+ <string>cv02 on</string> <!-- alt g -->
+ <string>cv06 on</string> <!-- alt 6 & 9 -->
+ </edit>
+ </match>
+</fontconfig>
diff --git a/archetypes/profiles/desktop/resources/fontconfig/90-tamzen-disable-anti-aliasing.conf b/archetypes/profiles/desktop/resources/fontconfig/90-tamzen-disable-anti-aliasing.conf
new file mode 100644
index 0000000..5bf94d7
--- /dev/null
+++ b/archetypes/profiles/desktop/resources/fontconfig/90-tamzen-disable-anti-aliasing.conf
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+
+<fontconfig>
+ <description>Disable anti-aliasing for Tamzen since it is a bitmap font</description>
+ <match target="pattern">
+ <test name="family" compare="eq" qual="any">
+ <string>Tamzen</string>
+ </test>
+ <edit name="antialias" mode="assign">
+ <bool>false</bool>
+ </edit>
+ </match>
+</fontconfig>
diff --git a/archetypes/profiles/gnome/default.nix b/archetypes/profiles/gnome/default.nix
new file mode 100644
index 0000000..6b7184b
--- /dev/null
+++ b/archetypes/profiles/gnome/default.nix
@@ -0,0 +1,13 @@
+{ lib, pkgs, ... }: let
+ mkGnome = lib.mkOverride 700;
+
+ nixosConfig = {
+ services.displayManager.gdm.enable = mkGnome true;
+ services.desktopManager.gnome.enable = mkGnome true;
+ services.tlp.enable = mkGnome false; # Conflicts with power-profiles-daemon
+ };
+
+ homeConfig = {};
+in {
+ imports = [ (lib._mkProfileArchetype "gnome" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/headless/default.nix b/archetypes/profiles/headless/default.nix
new file mode 100644
index 0000000..ac1eb80
--- /dev/null
+++ b/archetypes/profiles/headless/default.nix
@@ -0,0 +1,37 @@
+{ lib, ... }: let
+ mkHeadless = lib.mkOverride 910;
+
+ nixosConfig = {
+ _archetypes.collections = {
+ development = {
+ utilities.enable = mkHeadless true;
+ };
+ };
+
+ programs = {
+ _ddcutil.enable = mkHeadless true;
+ };
+
+ services = {
+ openssh.enable = mkHeadless true;
+ smartd.enable = mkHeadless true;
+ };
+
+ # Disable ipv6 by default
+ networking.enableIPv6 = mkHeadless false;
+ };
+
+ homeConfig = {
+ # Disable fontconfig features explicitly to avoid warnings
+ fonts.fontconfig = {
+ subpixelRendering = mkHeadless "none";
+ hinting = mkHeadless "none";
+ };
+
+ programs = {
+ htop._configure = mkHeadless true;
+ };
+ };
+in {
+ imports = [ (lib._mkProfileArchetype "headless" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/pi/default.nix b/archetypes/profiles/pi/default.nix
new file mode 100644
index 0000000..639111a
--- /dev/null
+++ b/archetypes/profiles/pi/default.nix
@@ -0,0 +1,30 @@
+{ lib, pkgs, ... }: let
+ mkPi = lib.mkOverride 900;
+
+ nixosConfig = {
+ boot.loader = {
+ # Use the extlinux boot loader. (NixOS wants to enable GRUB by default)
+ grub.enable = false;
+ # Enables the generation of /boot/extlinux/extlinux.conf
+ generic-extlinux-compatible.enable = true;
+ };
+ };
+
+ homeConfig = {
+ # Use raspian logo in fastfetch
+ programs.fastfetch.settings = {
+ logo.source = mkPi "raspbian";
+ # Default options for the rest of this
+ # TODO figure out how to just override logo and keep defaults w/config file
+ modules = [
+ "title" "separator" "os" "host" "kernel" "uptime" "packages"
+ "shell" "display" "de" "wm" "wmtheme" "theme" "icons" "font"
+ "cursor" "terminal" "terminalfont" "cpu" "gpu" "memory" "swap"
+ "disk" "localip" "battery" "poweradapter" "locale" "break"
+ "colors"
+ ];
+ };
+ };
+in {
+ imports = [ (lib._mkProfileArchetype "pi" nixosConfig homeConfig) ];
+}
diff --git a/archetypes/profiles/zfs/default.nix b/archetypes/profiles/zfs/default.nix
new file mode 100644
index 0000000..00f3c0b
--- /dev/null
+++ b/archetypes/profiles/zfs/default.nix
@@ -0,0 +1,33 @@
+{ lib, pkgs, ... }: let
+ mkZfs = lib.mkOverride 810;
+
+ # https://wiki.nixos.org/wiki/ZFS
+ nixosConfig = {
+ boot._loader = {
+ grub.zfsSupport = mkZfs true;
+ mode = mkZfs "efi";
+ };
+
+ boot.supportedFilesystems = [ "zfs" ];
+
+ services.zfs = {
+ trim.enable = mkZfs true;
+ autoSnapshot.enable = mkZfs true;
+ autoScrub.enable = mkZfs true;
+ };
+
+ # Make docker work with zfs
+ virtualisation.docker.storageDriver = mkZfs "zfs";
+
+ # Ensure root pools are safely imported (default after 26.11)
+ # NOTE: If NixOS fails to boot because it cannot import the root pool,
+ # you should boot with the zfs_force=1 option as a kernel parameter
+ # (e.g. by manually editing the kernel params via your bootloader). You
+ # should only need to do this after unclean shutdowns.
+ boot.zfs.forceImportRoot = mkZfs false;
+ };
+
+ homeConfig = {};
+in {
+ imports = [ (lib._mkProfileArchetype "zfs" nixosConfig homeConfig) ];
+}
diff --git a/derivations/lowbat/default.nix b/derivations/lowbat/default.nix
deleted file mode 100644
index 02ff111..0000000
--- a/derivations/lowbat/default.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{ pkgs ? import <nixpkgs> {} }:
-
-pkgs.stdenv.mkDerivation rec {
- pname = "lowbat";
- version = "1.2.1";
-
- src = pkgs.fetchzip {
- url = "https://git.tjkeller.xyz/${pname}/snapshot/${pname}-${version}.tar.xz";
- sha256 = "sha256-xsUHdmicHX/fQQgojVjWfzI82e4NezkmlACOpdPxLE0=";
- };
-
- nativeBuildInputs = [ pkgs.pkg-config ];
- buildInputs = [ pkgs.libnotify pkgs.glib ];
-
- buildPhase = ''
- make
- '';
-
- installPhase = ''
- mkdir -p $out/bin $out/share/man/man1
- cp lowbat $out/bin/
- cp lowbat.1 $out/share/man/man1/
- '';
-
- meta = with pkgs.lib; {
- description = "A minimal battery level monitor daemon, written in C";
- license = licenses.mit;
- maintainers = [ maintainers.tjkeller ];
- };
-}
diff --git a/derivations/pavolctld/default.nix b/derivations/pavolctld/default.nix
deleted file mode 100644
index c78e3fe..0000000
--- a/derivations/pavolctld/default.nix
+++ /dev/null
@@ -1,29 +0,0 @@
-{ pkgs ? import <nixpkgs> {} }:
-
-pkgs.stdenv.mkDerivation rec {
- pname = "pavolctld";
- version = "1.0.0";
-
- src = pkgs.fetchzip {
- url = "https://git.tjkeller.xyz/${pname}/snapshot/${pname}-${version}.tar.xz";
- sha256 = "sha256-eJ5njP2wu2kxsNgDdULOaATJJarLh+LkM5M9+kujRt4=";
- };
-
- buildInputs = [ pkgs.libpulseaudio ];
-
- buildPhase = ''
- make
- '';
-
- installPhase = ''
- mkdir -p $out/bin $out/share/man/man1
- cp pavolctld $out/bin/
- cp pavolctld.1 $out/share/man/man1/
- '';
-
- meta = with pkgs.lib; {
- description = "A minimal PulseAudio volume control/monitoring daemon";
- license = licenses.mit;
- maintainers = [ maintainers.tjkeller ];
- };
-}
diff --git a/derivations/st/default.nix b/derivations/st/default.nix
deleted file mode 100644
index eb7c6bc..0000000
--- a/derivations/st/default.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- lib,
- stdenv,
- fetchzip,
- fontconfig,
- libX11,
- libXcursor,
- libXext,
- libXft,
- ncurses,
- pkg-config,
-}:
-
-stdenv.mkDerivation rec {
- pname = "st";
- version = "tj-0.9.2.2";
-
- src = fetchzip {
- url = "https://git.tjkeller.xyz/${pname}/snapshot/${pname}-${version}.tar.xz";
- sha256 = "sha256-p1u7TEeLeC5Kjvi8na4YhyQQ0qdPaUWZL6hq9O4HaKA=";
- };
-
- nativeBuildInputs = [
- pkg-config
- ];
- buildInputs = [
- fontconfig
- libX11
- libXcursor
- libXext
- libXft
- ncurses
- ];
-
- installPhase = ''
- runHook preInstall
-
- TERMINFO=$out/share/terminfo make install PREFIX=$out
-
- runHook postInstall
- '';
-
- meta = with lib; {
- homepage = "https://git.tjkeller.xyz/st";
- description = "My st with various patches";
- mainProgram = "st";
- license = licenses.mit;
- maintainers = [ maintainers.tjkeller ];
- };
-}
diff --git a/flake.lock b/flake.lock
index 696cf81..ffa52a1 100644
--- a/flake.lock
+++ b/flake.lock
@@ -3,18 +3,17 @@
"arkenfox": {
"inputs": {
"flake-compat": "flake-compat",
- "flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"pre-commit": "pre-commit"
},
"locked": {
- "lastModified": 1725263787,
- "narHash": "sha256-OSNjus8VSkLCSikN6Qeq+II1bwqTRJEwl6NJvFoQHoE=",
+ "lastModified": 1770200864,
+ "narHash": "sha256-COdM5Lwfw07Q+YefYmcop1VPEX7e2goM9IiwfIrJt0g=",
"owner": "dwarfmaster",
"repo": "arkenfox-nixos",
- "rev": "72addd96455cce49c0c8524c53aecd02cf20adec",
+ "rev": "94638bbfacfa250e5c88aaa7a8f2145926fb8e08",
"type": "github"
},
"original": {
@@ -26,11 +25,11 @@
"flake-compat": {
"flake": false,
"locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "lastModified": 1767039857,
+ "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
@@ -39,40 +38,6 @@
"type": "github"
}
},
- "flake-compat_2": {
- "flake": false,
- "locked": {
- "lastModified": 1696426674,
- "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-utils": {
- "inputs": {
- "systems": "systems"
- },
- "locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
"gitignore": {
"inputs": {
"nixpkgs": [
@@ -102,110 +67,105 @@
]
},
"locked": {
- "lastModified": 1733951536,
- "narHash": "sha256-Zb5ZCa7Xj+0gy5XVXINTSr71fCfAv+IKtmIXNrykT54=",
+ "lastModified": 1780361225,
+ "narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
"owner": "nix-community",
"repo": "home-manager",
- "rev": "1318c3f3b068cdcea922fa7c1a0a1f0c96c22f5f",
+ "rev": "e28654b71096e08c019d4861ca26acb646f583d8",
"type": "github"
},
"original": {
"owner": "nix-community",
- "ref": "release-24.11",
+ "ref": "release-26.05",
"repo": "home-manager",
"type": "github"
}
},
"nixpkgs": {
"locked": {
- "lastModified": 1719082008,
- "narHash": "sha256-jHJSUH619zBQ6WdC21fFAlDxHErKVDJ5fpN0Hgx4sjs=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "9693852a2070b398ee123a329e68f0dab5526681",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs-stable": {
- "locked": {
- "lastModified": 1720386169,
- "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "194846768975b7ad2c4988bdb82572c00222c0d7",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-24.05",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs-stable_2": {
- "locked": {
- "lastModified": 1729357638,
- "narHash": "sha256-66RHecx+zohbZwJVEPF7uuwHeqf8rykZTMCTqIrOew4=",
- "owner": "NixOS",
+ "lastModified": 1780203844,
+ "narHash": "sha256-K5sT4jTpGs15ADhviMKNBH38REpPf5Q6mM1+N6cArVE=",
+ "owner": "nixos",
"repo": "nixpkgs",
- "rev": "bb8c2cf7ea0dd2e18a52746b2c3a5b0c73b93c22",
+ "rev": "b51242d7d43689db2f3be91bd05d5b24fbb469c4",
"type": "github"
},
"original": {
- "owner": "NixOS",
- "ref": "release-24.05",
+ "owner": "nixos",
+ "ref": "nixos-26.05",
"repo": "nixpkgs",
"type": "github"
}
},
- "nixpkgs_2": {
+ "nixpkgs-unstable": {
"locked": {
- "lastModified": 1734083684,
- "narHash": "sha256-5fNndbndxSx5d+C/D0p/VF32xDiJCJzyOqorOYW4JEo=",
+ "lastModified": 1780243769,
+ "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "314e12ba369ccdb9b352a4db26ff419f7c49fa84",
+ "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c",
"type": "github"
},
"original": {
"owner": "nixos",
- "ref": "nixos-24.11",
+ "ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit": {
"inputs": {
- "flake-compat": "flake-compat_2",
+ "flake-compat": [
+ "arkenfox",
+ "flake-compat"
+ ],
"gitignore": "gitignore",
- "nixpkgs": "nixpkgs",
- "nixpkgs-stable": "nixpkgs-stable"
+ "nixpkgs": [
+ "arkenfox",
+ "nixpkgs"
+ ]
},
"locked": {
- "lastModified": 1724857454,
- "narHash": "sha256-Qyl9Q4QMTLZnnBb/8OuQ9LSkzWjBU1T5l5zIzTxkkhk=",
+ "lastModified": 1769939035,
+ "narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
"owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "4509ca64f1084e73bc7a721b20c669a8d4c5ebe6",
+ "repo": "git-hooks.nix",
+ "rev": "a8ca480175326551d6c4121498316261cbb5b260",
"type": "github"
},
"original": {
"owner": "cachix",
- "repo": "pre-commit-hooks.nix",
+ "repo": "git-hooks.nix",
"type": "github"
}
},
+ "reposync": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1767033825,
+ "narHash": "sha256-z+V1Tc7+d73+vMh/M+kt2x3ctRzEqZvHxyIB1z/+2rs=",
+ "ref": "refs/heads/master",
+ "rev": "6fcb76423a96bca42520cf80b945a193d2a4c377",
+ "revCount": 6,
+ "type": "git",
+ "url": "git://git.tjkeller.xyz/hm-reposync"
+ },
+ "original": {
+ "type": "git",
+ "url": "git://git.tjkeller.xyz/hm-reposync"
+ }
+ },
"root": {
"inputs": {
"arkenfox": "arkenfox",
"home-manager": "home-manager",
- "nixpkgs": "nixpkgs_2",
+ "nixpkgs": "nixpkgs",
+ "nixpkgs-unstable": "nixpkgs-unstable",
+ "reposync": "reposync",
"sops-nix": "sops-nix"
}
},
@@ -213,15 +173,14 @@
"inputs": {
"nixpkgs": [
"nixpkgs"
- ],
- "nixpkgs-stable": "nixpkgs-stable_2"
+ ]
},
"locked": {
- "lastModified": 1729394972,
- "narHash": "sha256-fADlzOzcSaGsrO+THUZ8SgckMMc7bMQftztKFCLVcFI=",
+ "lastModified": 1777944972,
+ "narHash": "sha256-VfGRo1qTBKOe3s2gOv8LSoA6Fk19PvBlwQ1ECN0Evn8=",
"owner": "Mic92",
"repo": "sops-nix",
- "rev": "c504fd7ac946d7a1b17944d73b261ca0a0b226a5",
+ "rev": "c591bf665727040c6cc5cb409079acb22dcce33c",
"type": "github"
},
"original": {
@@ -229,21 +188,6 @@
"repo": "sops-nix",
"type": "github"
}
- },
- "systems": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
}
},
"root": "root",
diff --git a/flake.nix b/flake.nix
index 547d9db..e73ab18 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,69 +2,112 @@ rec {
description = "TimmyOS System Config";
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
+ nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
-
home-manager = {
- url = "github:nix-community/home-manager/release-24.11";
+ url = "github:nix-community/home-manager/release-26.05";
inputs.nixpkgs.follows = "nixpkgs";
};
-
arkenfox = {
url = "github:dwarfmaster/arkenfox-nixos";
inputs.nixpkgs.follows = "nixpkgs";
};
+ reposync = {
+ url = "git://git.tjkeller.xyz/hm-reposync";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
};
- outputs = { nixpkgs, home-manager, arkenfox, ... }@inputs :
- let
- system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages.${system};
- userDetails = {
- username = "timmy";
- home.root = "/home/timmy";
- home.downloads = "/home/timmy/dls";
- home.documents = "/home/timmy/docs";
- home.pictures = "/home/timmy/pics";
- email = "tjkeller.xyz";
- fullname = "Tim Keller";
- };
- mkNixosConfiguration = hostname: nixpkgs.lib.nixosSystem {
- inherit system;
- specialArgs = {
- inherit inputs;
- inherit userDetails;
- };
- modules = [
- ./modules/hosts/${hostname}/configuration.nix
- ./modules/root
- home-manager.nixosModules.home-manager {
- home-manager = {
- backupFileExtension = "backup";
- useGlobalPkgs = true;
- useUserPackages = true;
- users.${userDetails.username} = import ./modules/home;
- extraSpecialArgs = {
- inherit userDetails;
- };
- sharedModules = [
- inputs.arkenfox.hmModules.arkenfox
- ./modules/hosts/${hostname}/home.nix
- ];
- };
- }
- ];
+ outputs = { nixpkgs, home-manager, ... }@inputs: let
+ extLib = nixpkgs.lib.extend (final: prev: import ./lib);
+ extLibHomeManagerStandalone = nixpkgs.lib.extend (final: prev: import ./lib/home-manager);
+ homeManagerModules = [
+ inputs.arkenfox.hmModules.arkenfox
+ inputs.reposync.hmModules.reposync
+ ./home-manager
+ ];
+ mkNixosConfiguration = system: hostname: nixpkgs.lib.nixosSystem {
+ inherit system;
+ specialArgs = {
+ inherit inputs;
+ inherit hostname;
+ lib = extLib;
};
- in {
- nixosConfigurations = {
- T430 = mkNixosConfiguration "T430";
- T495 = mkNixosConfiguration "T495";
- hp-envy-office = mkNixosConfiguration "hp-envy-office";
- optiplex = mkNixosConfiguration "optiplex";
+ modules = [
+ ./hosts/${hostname}/configuration.nix
+ ./hosts/${hostname}/hardware-configuration.nix
+ ./archetypes
+ ./nixos
+ ./pkgs
+ ./users
+ inputs.sops-nix.nixosModules.sops
+ home-manager.nixosModules.home-manager {
+ home-manager = {
+ backupFileExtension = "backup"; # In case file is overwritten
+ useGlobalPkgs = true;
+ useUserPackages = true;
+ sharedModules = homeManagerModules;
+ };
+ }
+ ];
+ };
+ mkNixosConfigurations = hostsBySystem: builtins.listToAttrs (
+ builtins.concatMap (system:
+ map (host: {
+ name = host;
+ value = mkNixosConfiguration system host;
+ }) hostsBySystem.${system}
+ ) (builtins.attrNames hostsBySystem)
+ );
+ mkHomeConfiguration = user: system: hostname: home-manager.lib.homeManagerConfiguration {
+ pkgs = nixpkgs.legacyPackages.${system};
+ extraSpecialArgs = {
+ inherit inputs;
+ inherit hostname;
+ lib = extLibHomeManagerStandalone;
};
+ modules = homeManagerModules ++ [
+ ./hosts/${hostname}/home-${user}/configuration.nix
+ ./archetypes/profiles
+ ./pkgs
+ ./users/default-home.nix
+ ];
+ };
+ mkHomeConfigurations = hostsBySystem: builtins.listToAttrs (
+ builtins.concatMap (system:
+ map (host: {
+ name = host;
+ value = mkHomeConfiguration "timmy" system host; # TODO
+ }) hostsBySystem.${system}
+ ) (builtins.attrNames hostsBySystem)
+ );
+ in {
+ # Configured system hostnames go here under their respective system architecture
+ nixosConfigurations = mkNixosConfigurations {
+ x86_64-linux = [
+ "T495"
+ "X230"
+ "flex-wg-router"
+ "gnuslashprinter"
+ "hp-envy-office"
+ "libreX60"
+ "optiplex"
+ "poweredge"
+ "sweetiepc"
+ ];
+ aarch64-linux = [
+ "piframe"
+ ];
};
+ homeConfigurations = mkHomeConfigurations {
+ x86_64-linux = [
+ "libreX60"
+ ];
+ };
+ };
}
diff --git a/home-manager/default.nix b/home-manager/default.nix
new file mode 100644
index 0000000..8d9c722
--- /dev/null
+++ b/home-manager/default.nix
@@ -0,0 +1,19 @@
+{
+ imports = [
+ ./firefox.nix
+ ./fontconfig.nix
+ ./gammastep.nix
+ ./home-cleanup.nix
+ ./htop.nix
+ ./mint-theme.nix
+ ./neovim.nix
+ ./pcmanfm.nix
+ ./qt-gtk-theme.nix
+ ./theme-st.nix
+ ./wallpapers.nix
+ ./xdg-mime.nix
+ ];
+
+ # Let Home Manager install and manage itself.
+ programs.home-manager.enable = true;
+}
diff --git a/home-manager/firefox.nix b/home-manager/firefox.nix
new file mode 100644
index 0000000..7c95e56
--- /dev/null
+++ b/home-manager/firefox.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }: let
+ cfg = config.programs.firefox;
+ search = {
+ engines = {
+ "Timmy Search" = {
+ urls = [{
+ template = "https://search.tjkeller.xyz/search";
+ params = [ { name = "q"; value = "{searchTerms}"; } ];
+ }];
+ iconURI = "https://search.tjkeller.xyz/static/themes/simple/img/favicon.svg"; # TODO doesn't seem to work
+ };
+ "Nix Packages" = {
+ urls = [{
+ template = "https://search.nixos.org/packages";
+ params = [ { name = "query"; value = "{searchTerms}"; } ];
+ }];
+ icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
+ definedAliases = [ "@np" ];
+ };
+ "NixOS Options" = {
+ urls = [{
+ template = "https://search.nixos.org/options";
+ params = [ { name = "query"; value = "{searchTerms}"; } ];
+ }];
+ icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
+ definedAliases = [ "@no" ];
+ };
+ };
+ default = "Timmy Search";
+ privateDefault = "Timmy Search";
+ force = true; # Overwrite old
+ };
+ userChrome = builtins.readFile ./resources/firefox/userChrome.css;
+ uiCustomization = {
+ placements = {
+ nav-bar = [
+ "back-button"
+ "forward-button"
+ "stop-reload-button"
+ "vertical-spacer"
+ "home-button"
+ "urlbar-container"
+ "downloads-button"
+ "_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action" # Bitwarden
+ "unified-extensions-button"
+ ];
+ TabsToolbar = [ "tabbrowser-tabs" "new-tab-button" ];
+ # Ensure these extensions go to overflow menu instead of nav-bar
+ unified-extensions-area = [
+ "ublock0_raymondhill_net-browser-action"
+ "sponsorblocker_ajay_app-browser-action"
+ ];
+ };
+ currentVersion = 23; # Required
+ };
+ arkenfox = {
+ enable = true;
+ enableAllSections = true;
+ "0100"."0102"."browser.startup.page".value = 3; # 0=blank, 1=home, 2=last visited page, 3=resume previous session
+ "0100"."0103"."browser.startup.homepage".enable = false;
+ "0100"."0104"."browser.newtabpage.enabled".enable = false;
+ #"2800"."2811"."privacy.clearOnShutdown.history".enable = false;
+ };
+ settings = {
+ "browser.compactmode.show" = true;
+ "browser.uiCustomization.state" = builtins.toJSON uiCustomization; # Toolbar etc.
+ "browser.uidensity" = 1; # Compact
+ "toolkit.legacyUserProfileCustomizations.stylesheets" = true; # userchrome
+
+ "app.normandy.first_run" = false;
+ "browser.aboutConfig.showWarning" = false; # arkenfox does
+ #"browser.download.dir" = userDetails.userDirs.downloads;
+ "browser.newtabpage.activity-stream.feeds.section.topstories" = false;
+ "browser.newtabpage.activity-stream.feeds.topsites" = false;
+ "browser.urlbar.suggest.topsites" = false;
+ "devtools.toolbox.host" = "window";
+ "dom.push.enabled" = false; #
+ "extensions.pocket.enabled" = false;
+ "general.smoothScroll" = false;
+ #"geo.provider.use_geoclue" = true;
+ };
+ workSettings = settings // {
+ "extensions.activeThemeID" = "firefox-compact-dark@mozilla.org"; # Use builtin dark theme instead of system theme
+ };
+in {
+ options.programs.firefox = {
+ _configure = lib.mkEnableOption "configure firefox profiles";
+ };
+
+ config = lib.mkIf cfg._configure {
+ programs.firefox = {
+ enable = lib.mkDefault true;
+ arkenfox.enable = lib.mkDefault true;
+ profiles = {
+ Personal = {
+ id = 0;
+ isDefault = true;
+ inherit search;
+ inherit userChrome;
+ inherit arkenfox;
+ inherit settings;
+ };
+ Work = {
+ id = 1;
+ inherit search;
+ inherit userChrome;
+ inherit arkenfox;
+ settings = workSettings;
+ };
+ };
+ };
+ };
+}
diff --git a/home-manager/fontconfig.nix b/home-manager/fontconfig.nix
new file mode 100644
index 0000000..7c12b7e
--- /dev/null
+++ b/home-manager/fontconfig.nix
@@ -0,0 +1,11 @@
+{ lib, config, ... }: {
+ config = {
+ # Warn whenever subpixelRendering is not enabled
+ warnings = [
+ (lib.mkIf (config.fonts.fontconfig.subpixelRendering == null)
+ ''fonts.fontconfig.subpixelRendering is unset; this may result in suboptimal font rendering. Set this value to "none" to disable the feature explicitly.'')
+ (lib.mkIf (config.fonts.fontconfig.hinting == null)
+ ''fonts.fontconfig.hinting is unset; this may result in suboptimal font clarity. Set this value to "none" to disable the feature explicitly.'')
+ ];
+ };
+}
diff --git a/home-manager/gammastep.nix b/home-manager/gammastep.nix
new file mode 100644
index 0000000..f7ea3b3
--- /dev/null
+++ b/home-manager/gammastep.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }: let
+ cfg = config.services._gammastep;
+in {
+ options.services._gammastep = {
+ enable = lib.mkEnableOption "configure gammastep";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.gammastep = {
+ enable = true;
+ provider = lib.mkDefault "geoclue2";
+ temperature = {
+ day = lib.mkDefault 6500; # gammastep default, the hm module defaults to 5500
+ night = lib.mkDefault 3600;
+ };
+ };
+ };
+}
diff --git a/home-manager/home-cleanup.nix b/home-manager/home-cleanup.nix
new file mode 100644
index 0000000..1f7c426
--- /dev/null
+++ b/home-manager/home-cleanup.nix
@@ -0,0 +1,21 @@
+{ config, lib, ... }: let
+ cacheHome = config.xdg.cacheHome;
+ configHome = config.xdg.configHome;
+ dataHome = config.xdg.dataHome;
+in {
+ # Gtk 2
+ # This file may be generated by home-manager, so this will need to be set
+ # so that it ends up in the correct location
+ gtk.gtk2.configLocation = "${configHome}/gtk-2.0/gtkrc";
+
+ # Npm
+ xdg.configFile.npmrc = {
+ enable = lib.mkDefault true;
+ text = builtins.replaceStrings ["\t"] [""] ''
+ prefix=${dataHome}/npm
+ cache=${cacheHome}/npm
+ init-module=${dataHome}/npm/npm-init.js
+ '';
+ target = "npm/npmrc";
+ };
+}
diff --git a/home-manager/htop.nix b/home-manager/htop.nix
new file mode 100644
index 0000000..36ccbac
--- /dev/null
+++ b/home-manager/htop.nix
@@ -0,0 +1,46 @@
+{ config, lib, ... }: let
+ cfg = config.programs.htop;
+in {
+ options.programs.htop = {
+ _configure = lib.mkEnableOption "apply sensible defaults to htop";
+ };
+
+ config = lib.mkIf cfg._configure {
+ programs.htop.enable = lib.mkDefault true;
+ programs.htop.settings = {
+ fields = with config.lib.htop.fields; [
+ PID
+ USER
+ 124 # EXE
+ PERCENT_CPU
+ PERCENT_MEM
+ STARTTIME
+ ELAPSED # time elapsed since process start
+ IO_READ_RATE
+ RBYTES # num read bytes
+ IO_WRITE_RATE
+ WBYTES # num written bytes
+ TIME
+ STIME # sys time
+ CSTIME # child sys time
+ NICE
+ STATE
+ PRIORITY
+ 125 # CWD
+ ];
+ } // (with config.lib.htop; leftMeters [
+ (bar "AllCPUs2")
+ (bar "CPU")
+ (bar "Memory")
+ (bar "DiskIO")
+ (bar "NetworkIO")
+ ]) // (with config.lib.htop; rightMeters [
+ (text "Hostname")
+ (text "Clock")
+ (text "Uptime")
+ (text "Battery")
+ (text "LoadAverage")
+ (text "Tasks")
+ ]);
+ };
+}
diff --git a/home-manager/mint-theme.nix b/home-manager/mint-theme.nix
new file mode 100644
index 0000000..a77be6a
--- /dev/null
+++ b/home-manager/mint-theme.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.gtk._mintTheme; # gtk.theme type is null or submodule under the hood so gtk.theme._mint, etc. can't be used
+ transformColorValue = color: dark:
+ "Mint-Y" + lib.optionalString dark "-Dark"
+ + lib.optionalString (color != "Green") ("-" + color)
+ ;
+in {
+ options.gtk._mintTheme = {
+ enable = lib.mkEnableOption "enables mint theme";
+ color = lib.mkOption {
+ type = lib.types.enum [
+ "Aqua" "Blue" "Green" "Grey" "Orange"
+ "Pink" "Purple" "Red" "Sand" "Teal"
+ ];
+ default = "Green";
+ description = "mint-y theme color";
+ };
+ dark = lib.mkEnableOption "whether to use dark variant of mint theme";
+ icons.color = lib.mkOption {
+ type = lib.types.enum [
+ "Aqua" "Blue" "Green" "Grey" "Orange" "Pink" "Purple" "Red"
+ "Sand" "Teal"
+ ];
+ default = "Aqua";
+ description = "mint-y icons color eg. 'Aqua' or 'Red'";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ gtk = {
+ enable = true;
+ theme = {
+ package = pkgs.mint-themes;
+ name = transformColorValue cfg.color cfg.dark;
+ };
+ iconTheme = {
+ package = pkgs.mint-y-icons;
+ name = transformColorValue cfg.icons.color false;
+ };
+ };
+ };
+}
diff --git a/home-manager/neovim.nix b/home-manager/neovim.nix
new file mode 100644
index 0000000..2b19257
--- /dev/null
+++ b/home-manager/neovim.nix
@@ -0,0 +1,108 @@
+{ lib, config, pkgs, ... }: let
+ cfg = config.programs._neovim;
+
+ pluginDir = let
+ extraPlugins = cfg.plugins.packages // {
+ start = (cfg.plugins.packages.start or [])
+ ++ lib.optionals (cfg.plugins.lsp.enable && cfg.plugins.lsp.lspconfig.enable) [ cfg.plugins.lsp.lspconfig.pluginPackage ]
+ ++ lib.optionals cfg.plugins.treesitter.enable [ cfg.plugins.treesitter.pluginPackage ];
+ };
+ in pkgs.linkFarm "nvim-site"
+ (lib.concatLists (lib.mapAttrsToList (stage: plugins:
+ map (plugin: {
+ name = "share/nvim/site/pack/plugins/${stage}/${plugin.name}";
+ path = plugin;
+ }) plugins
+ ) extraPlugins));
+
+ extraPackages = pkgs.symlinkJoin {
+ name = "nvim-extra-packages";
+ paths = cfg.extraPackages
+ ++ lib.optionals cfg.plugins.lsp.enable cfg.plugins.lsp.languageServers.packages;
+ };
+
+ parserDir = let
+ allTSParserPackages = builtins.filter lib.isDerivation
+ (builtins.attrValues cfg.plugins.treesitter.parsers.installAllFromPackageSet);
+ in pkgs.symlinkJoin {
+ name = "nvim-treesitter-parsers";
+ paths = if cfg.plugins.treesitter.parsers.installAll then allTSParserPackages else cfg.plugins.treesitter.parsers.packages;
+ };
+
+ finalPackage = pkgs.symlinkJoin {
+ name = "neovim-not-wrapped";
+ paths = [ cfg.package ];
+ nativeBuildInputs = [ pkgs.makeWrapper ];
+ postBuild = ''
+ wrapProgram $out/bin/nvim \
+ --prefix PATH : ${extraPackages} \
+ --prefix XDG_DATA_DIRS : ${pluginDir}/share \
+ '' + lib.optionalString cfg.plugins.treesitter.enable ''
+ --add-flags '--cmd "set rtp^=${parserDir}"' \
+ --add-flags '--cmd "set rtp^=${cfg.plugins.treesitter.pluginPackage}/runtime"' \
+ '' + ''
+ # blank line here to terminate multiline command string
+ '' + lib.optionalString cfg.viAlias ''
+ ln -s $out/bin/nvim $out/bin/vi
+ '' + lib.optionalString cfg.vimAlias ''
+ ln -s $out/bin/nvim $out/bin/vim
+ '';
+ };
+in {
+ options.programs._neovim = {
+ enable = lib.mkEnableOption "install and configure neovim";
+ package = lib.mkPackageOption pkgs "neovim" {};
+ viAlias = lib.mkEnableOption "alias vi => nvim";
+ vimAlias = lib.mkEnableOption "alias vim => nvim";
+ plugins = {
+ lsp = {
+ enable = lib.mkEnableOption "install defined language servers into nvim path";
+ lspconfig = {
+ enable = lib.mkEnableOption "install plugin `lspconfig.pluginPackage`";
+ pluginPackage = lib.mkPackageOption pkgs.vimPlugins "nvim-lspconfig" {};
+ };
+ languageServers.packages = lib.mkOption {
+ type = lib.types.listOf lib.types.package;
+ default = [ ];
+ description = "language server packages to add to nvim path when lsp is enabled";
+ };
+ };
+ treesitter = {
+ enable = lib.mkEnableOption "install treesitter and parsers as nvim plugins";
+ pluginPackage = lib.mkPackageOption pkgs.vimPlugins "nvim-treesitter" {};
+ parsers.installAll = lib.mkEnableOption "install all available parsers. overrides `parsers.packages`";
+ parsers.installAllFromPackageSet = lib.mkOption {
+ type = lib.types.attrsOf lib.types.anything;
+ default = pkgs.vimPlugins.nvim-treesitter-parsers;
+ defaultText = lib.literalExpression "pkgs.vimPlugins.nvim-treesitter-parsers";
+ description = "parent package from which to install all treesitter parsers";
+ };
+ parsers.packages = lib.mkOption {
+ type = lib.types.listOf lib.types.package;
+ default = [ ];
+ description = "treesitter parsers to install as plugins";
+ };
+ };
+ packages = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.listOf lib.types.package);
+ default = [ ];
+ example = {
+ start = with pkgs.vimPlugins; [
+ nvim-colorizer-lua
+ ];
+ };
+ description = "extra plugins to be made available to nvim via packpath, organized by stage";
+ };
+ };
+ extraPackages = lib.mkOption {
+ type = lib.types.listOf lib.types.package;
+ default = [ ];
+ description = "extra packages to add to nvim path";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # NOTE: Don't use programs.neovim since that will build neovim from source
+ home.packages = [ finalPackage ];
+ };
+}
diff --git a/home-manager/pcmanfm.nix b/home-manager/pcmanfm.nix
new file mode 100644
index 0000000..fc2737a
--- /dev/null
+++ b/home-manager/pcmanfm.nix
@@ -0,0 +1,14 @@
+{ config, lib, ... }: let
+ cfg = config.programs._pcmanfm.openAsRootOption;
+in {
+ options.programs._pcmanfm.openAsRootOption = {
+ enable = lib.mkEnableOption "enable 'open as root' option in pcmanfm right-click menu";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.file.pcmanfm-root = {
+ target = ".local/share/file-manager/actions/root.desktop";
+ source = ./resources/pcmanfm/root.desktop;
+ };
+ };
+}
diff --git a/home-manager/qt-gtk-theme.nix b/home-manager/qt-gtk-theme.nix
new file mode 100644
index 0000000..2e53ccc
--- /dev/null
+++ b/home-manager/qt-gtk-theme.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+let
+ cfg = config.qt._gtkPlatformTheme;
+in {
+ options.qt._gtkPlatformTheme = {
+ enable = lib.mkEnableOption "use gtk theme in qt";
+ };
+
+ config = lib.mkIf cfg.enable {
+ qt = {
+ enable = lib.mkDefault true;
+ platformTheme.name = "gtk3";
+ };
+ };
+}
diff --git a/home-manager/resources/firefox/userChrome.css b/home-manager/resources/firefox/userChrome.css
new file mode 100644
index 0000000..7ec4588
--- /dev/null
+++ b/home-manager/resources/firefox/userChrome.css
@@ -0,0 +1,175 @@
+/* TODO create separate repo for this */
+/* TOP BAR */
+#navigator-toolbox {
+ --uc-navigationbar-width: 40vw;
+
+ #TabsToolbar {
+ /* Move new tab to far right */
+ #tabbrowser-arrowscrollbox-periphery { margin-left: auto }
+ /* remove alltabs button */
+ #alltabs-button { display: none }
+ }
+
+ #nav-bar {
+ /* Remove overflow button */
+ #nav-bar-overflow-button { display: none }
+ }
+
+ #PersonalToolbar {
+ /* Change background color of bookmarks toolbar */
+ background: var(--toolbox-bgcolor) !important;
+ /* Adjust spacing of toolbar items */
+ #PlacesToolbarItems { gap: .35rem; margin-block: .2rem }
+ }
+}
+
+/* Media queries for width of nav bar */
+@media screen and (max-width: 1000px) {
+ #navigator-toolbox { --uc-navigationbar-width: 50vw }
+}
+@media screen and (max-width: 800px) {
+ #navigator-toolbox { --uc-navigationbar-width: 60vw }
+}
+
+/* Change look of tabs in smaller view */
+@media screen and (max-width: 800px) {
+ #TabsToolbar-customization-target {
+ /* TODO fix w/o. Don't allow tabbar to move up when moving tabs */
+ height: 37px;
+ /* Make tabs fill more vertical space */
+ .tab-background { margin-block: 1px !important }
+ }
+}
+
+
+/* COMBINE TOP BAR */
+/* Combine top bar into single line if width >= 800px */
+@media screen and (min-width: 800px) {
+ /* Half height bar */
+ :root { --uc-toolbar-height: 36px }
+
+ /* Modify these to change relative widths or default height */
+ #navigator-toolbox {
+ margin-bottom: 0px;
+ padding-top: 1px; /* Top bar is cut off otherwise */
+
+ #TabsToolbar {
+ margin-left: calc(var(--uc-navigationbar-width) + 2px); /* Resize tab bar */
+ margin-top: -4px;
+ margin-bottom: 2px;
+
+ #TabsToolbar-customization-target {
+ height: var(--uc-toolbar-height);
+
+ /* Tabs extend to bottom of bar */
+ .tab-background {
+ margin-bottom: 0 !important;
+ margin-top: 3px !important; /* See above navigator-toolbox rule. Avoid cutting off top */
+ }
+
+ /* 1px margin on touch density causes tabs to be too high */
+ .tab-close-button { margin-top: 0 !important }
+ }
+
+ /* Center tab buttons */
+ #tabs-newtab-button, #alltabs-button { margin-top: 4px !important }
+ }
+
+ /* Integrate url / nav bar */
+ #nav-bar {
+ margin-right:calc(100vw - var(--uc-navigationbar-width));
+ margin-top: calc(0px - var(--uc-toolbar-height));
+ border-radius: 0 var(--tab-border-radius) var(--tab-border-radius) 0;
+ border-right: 1px solid ThreeDShadow !important;
+ box-shadow: 0 0 4px rgba(0,0,0,.4) !important;
+ /* Fix spacing around tabs by removing nav bar top border */
+ border: none !important;
+
+ #urlbar-container {
+ /* Remove min and max width of urlbar */
+ width: 0 !important;
+ /* Make opened urlbar overlay the toolbar */
+ #urlbar[open]:focus-within { min-width: 50vw !important }
+ }
+ }
+
+ /* Bottom of tags is cut off by 1px without this */
+ #PersonalToolbar { margin-top: 1px }
+ }
+
+ /* Fix customization view */
+ #customization-panelWrapper .panel-arrowbox .panel-arrow {
+ margin-inline-end: initial !important;
+ }
+
+ /* Shorten findbar */
+ findbar {
+ width: 600px !important;
+ border-radius: 0 0 0 5px;
+ border-bottom: none !important;
+ box-shadow: 0 2px 4px rgba(0,0,0,.4); /* Move down 2px so it doesn't go over the tab bar */
+ }
+}
+
+
+/* FINDBAR */
+findbar {
+ width: 100vw;
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 0 !important;
+ padding-top: 1px !important;
+ background: -moz-headerbar Field !important;
+ border-top-width: 0px !important;
+ border-bottom: 1px solid ThreeDShadow;
+
+ .findbar-container {
+ padding-bottom: 5px !important; /* Move search bar closer to left edge */
+ padding-top: 2px !important; /* Move search bar closer to left edge */
+ height: max-content !important;
+ gap: 2px;
+ justify-content: space-between;
+ flex-wrap: wrap;
+
+ /* Force textbox to fill up first line */
+ .findbar-textbox {
+ width: 100% !important;
+ background: Field !important; /* Set the background color to be consistent with found-matches label when unfocused */
+ }
+
+ description {
+ /* Hide description showing wrap conditions etc. */
+ display: none;
+ /* Show description when it says "Phrase Not Found" */
+ &[status=notfound] { display: inline-block }
+ }
+
+ /* Move found matches label (roughly) into the textbox */
+ label.found-matches,
+ description {
+ position: absolute;
+ top: 6.5px;
+ right: 110px;
+ color: color-mix(in srgb, -moz-headerbartext, transparent 46%) !important;
+ /* So this text overrides the text below, TODO find a more elegant solution */
+ padding-inline: 1ex;
+ background: Field;
+ }
+
+ /* Force checkboxes onto second line */
+ hbox { width: 100% }
+ }
+}
+
+
+/* BOOKMARK BAR */
+@-moz-document url(chrome://browser/content/browser.xhtml) {
+ #PersonalToolbar {
+ background: -moz-headerbar Field !important;
+ border-top: 1px solid ThreeDShadow !important;
+ z-index: 1000; /* To not be affected by shadow from navbar */
+ /* Space out bookmark items */
+ .bookmark-item .toolbarbutton-text { padding: 2.5px 4px }
+ }
+}
diff --git a/modules/home/resources/pcmanfm/root.desktop b/home-manager/resources/pcmanfm/root.desktop
index 4b4d263..f5863e9 100644
--- a/modules/home/resources/pcmanfm/root.desktop
+++ b/home-manager/resources/pcmanfm/root.desktop
@@ -7,5 +7,5 @@ Icon=gtk-dialog-authentication
[X-Action-Profile profile-zero]
MimeTypes=inode/directory;
-Exec=/home/timmy/.local/bin/x11/xsudo /usr/bin/pcmanfm %u
+Exec=pkexec env DISPLAY="$DISPLAY" XAUTHORITY="$XAUTHORITY" XDG_DATA_DIRS="$XDG_DATA_DIRS" XDG_CONFIG_HOME="$XDG_CONFIG_HOME" pcmanfm %u
Name=Default profile
diff --git a/home-manager/resources/xdg-mime/audio b/home-manager/resources/xdg-mime/audio
new file mode 100644
index 0000000..fd00b26
--- /dev/null
+++ b/home-manager/resources/xdg-mime/audio
@@ -0,0 +1,65 @@
+application/x-cue
+application/x-extension-m4a
+application/x-ogm-audio
+application/x-shorten
+audio/3gpp
+audio/3gpp2
+audio/AMR
+audio/aac
+audio/ac3
+audio/aiff
+audio/amr-wb
+audio/dv
+audio/eac3
+audio/flac
+audio/m3u
+audio/m4a
+audio/mp1
+audio/mp2
+audio/mp3
+audio/mp4
+audio/mpeg
+audio/mpeg2
+audio/mpeg3
+audio/mpegurl
+audio/mpg
+audio/musepack
+audio/ogg
+audio/opus
+audio/rn-mpeg
+audio/scpls
+audio/vnd.dolby.heaac.1
+audio/vnd.dolby.heaac.2
+audio/vnd.dts
+audio/vnd.dts.hd
+audio/vnd.rn-realaudio
+audio/vorbis
+audio/wav
+audio/webm
+audio/x-aac
+audio/x-adpcm
+audio/x-aiff
+audio/x-ape
+audio/x-m4a
+audio/x-matroska
+audio/x-mp1
+audio/x-mp2
+audio/x-mp3
+audio/x-mpegurl
+audio/x-mpg
+audio/x-ms-asf
+audio/x-ms-wma
+audio/x-musepack
+audio/x-pls
+audio/x-pn-au
+audio/x-pn-realaudio
+audio/x-pn-wav
+audio/x-pn-windows-pcm
+audio/x-realaudio
+audio/x-scpls
+audio/x-shorten
+audio/x-tta
+audio/x-vorbis
+audio/x-vorbis+ogg
+audio/x-wav
+audio/x-wavpack
diff --git a/home-manager/resources/xdg-mime/directory b/home-manager/resources/xdg-mime/directory
new file mode 100644
index 0000000..4284766
--- /dev/null
+++ b/home-manager/resources/xdg-mime/directory
@@ -0,0 +1,2 @@
+inode/directory
+inode/mount-point
diff --git a/home-manager/resources/xdg-mime/email b/home-manager/resources/xdg-mime/email
new file mode 100644
index 0000000..db65256
--- /dev/null
+++ b/home-manager/resources/xdg-mime/email
@@ -0,0 +1,6 @@
+application/mbox
+application/vnd.ms-outlook
+message/rfc822
+text/calendar
+x-scheme-handler/mailto
+x-scheme-handler/mid
diff --git a/home-manager/resources/xdg-mime/image b/home-manager/resources/xdg-mime/image
new file mode 100644
index 0000000..1e2623b
--- /dev/null
+++ b/home-manager/resources/xdg-mime/image
@@ -0,0 +1,21 @@
+application/postscript
+image/avif
+image/bmp
+image/gif
+image/heic
+image/heif
+image/jp2
+image/jpeg
+image/jpg
+image/jxl
+image/png
+image/svg+xml
+image/svg+xml-compressed
+image/tiff
+image/webp
+image/x-bmp
+image/x-portable-anymap
+image/x-portable-bitmap
+image/x-portable-graymap
+image/x-tga
+image/x-xpixmap
diff --git a/home-manager/resources/xdg-mime/pdf b/home-manager/resources/xdg-mime/pdf
new file mode 100644
index 0000000..73bd5d0
--- /dev/null
+++ b/home-manager/resources/xdg-mime/pdf
@@ -0,0 +1,4 @@
+application/epub+zip
+application/oxps
+application/pdf
+application/x-fictionbook
diff --git a/home-manager/resources/xdg-mime/text b/home-manager/resources/xdg-mime/text
new file mode 100644
index 0000000..709cb57
--- /dev/null
+++ b/home-manager/resources/xdg-mime/text
@@ -0,0 +1,15 @@
+application/x-shellscript
+text/english
+text/plain
+text/x-c
+text/x-c++
+text/x-c++hdr
+text/x-c++src
+text/x-chdr
+text/x-csrc
+text/x-java
+text/x-makefile
+text/x-moc
+text/x-pascal
+text/x-tcl
+text/x-tex
diff --git a/home-manager/resources/xdg-mime/video b/home-manager/resources/xdg-mime/video
new file mode 100644
index 0000000..6a0839e
--- /dev/null
+++ b/home-manager/resources/xdg-mime/video
@@ -0,0 +1,55 @@
+application/mxf
+application/ogg
+application/sdp
+application/smil
+application/streamingmedia
+application/vnd.apple.mpegurl
+application/vnd.ms-asf
+application/vnd.rn-realmedia
+application/vnd.rn-realmedia-vbr
+application/x-extension-mp4
+application/x-matroska
+application/x-mpegurl
+application/x-ogg
+application/x-ogm
+application/x-ogm-video
+application/x-smil
+application/x-streamingmedia
+video/3gp
+video/3gpp
+video/3gpp2
+video/avi
+video/divx
+video/dv
+video/fli
+video/flv
+video/mkv
+video/mp2t
+video/mp4
+video/mp4v-es
+video/mpeg
+video/msvideo
+video/ogg
+video/quicktime
+video/vnd.divx
+video/vnd.mpegurl
+video/vnd.rn-realvideo
+video/webm
+video/x-avi
+video/x-flc
+video/x-flic
+video/x-flv
+video/x-m4v
+video/x-matroska
+video/x-mpeg2
+video/x-mpeg3
+video/x-ms-afs
+video/x-ms-asf
+video/x-ms-wmv
+video/x-ms-wmx
+video/x-ms-wvxvideo
+video/x-msvideo
+video/x-ogm
+video/x-ogm+ogg
+video/x-theora
+video/x-theora+ogg
diff --git a/home-manager/theme-st.nix b/home-manager/theme-st.nix
new file mode 100644
index 0000000..0a2cf34
--- /dev/null
+++ b/home-manager/theme-st.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }: let
+ cfg = config.programs._st;
+ toString = x: if lib.isBool x then (if x then "true" else "false") else builtins.toString x;
+ # http://freedesktop.org/software/fontconfig/fontconfig-user.html
+ generateFontConfigString = name: attrs:
+ name + lib.optionalString (attrs != {}) (
+ ":" + lib.concatStringsSep ":" (
+ lib.mapAttrsToList (key: value: "${key}=${toString value}") attrs
+ )
+ )
+ ;
+ themed-st = pkgs.stdenv.mkDerivation {
+ name = "themed-st";
+ dontUnpack = true;
+ buildInputs = [ pkgs.makeWrapper ];
+ installPhase = ''
+ mkdir -p $out/bin
+ makeWrapper ${pkgs.st}/bin/st $out/bin/st \
+ --add-flags '-f"${generateFontConfigString cfg.font.name cfg.font.attrs}"'
+ '';
+ };
+in {
+ options.programs._st = {
+ enable = lib.mkEnableOption "enables theming st with home manager";
+ font = {
+ name = lib.mkOption {
+ type = lib.types.str;
+ example = "JetBrainsMonoNL Nerd Font Mono";
+ default = "monospace";
+ };
+ attrs = lib.mkOption {
+ type = lib.types.attrs;
+ default = {};
+ example = {
+ size = 12;
+ antialias = true;
+ autohint = true;
+ };
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ home.packages = [ themed-st ];
+ };
+}
diff --git a/home-manager/wallpapers.nix b/home-manager/wallpapers.nix
new file mode 100644
index 0000000..de64d76
--- /dev/null
+++ b/home-manager/wallpapers.nix
@@ -0,0 +1,17 @@
+{ lib, config, pkgs, ... }: let
+ cfg = config.programs._seasonalwallpaper.wallpapers;
+in {
+ options.programs._seasonalwallpaper.wallpapers = {
+ download = lib.mkEnableOption "enables all wallpapers";
+ };
+
+ config = lib.mkIf cfg.download {
+ home.file.wallpapers = {
+ target = ".local/share/wallpaper";
+ source = pkgs.fetchzip {
+ url = "https://tjkeller.xyz/permalinks/wallpapers.tar";
+ hash = "sha256-lhj9wUMZfd0XOrUtWY9HB64oXrS/EjsvsaJHzu4ucFg=";
+ };
+ };
+ };
+}
diff --git a/home-manager/xdg-mime.nix b/home-manager/xdg-mime.nix
new file mode 100644
index 0000000..e74b4b3
--- /dev/null
+++ b/home-manager/xdg-mime.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }: let
+ cfg = config.xdg.mimeApps._defaultCategoryApplications;
+
+ # Mime-type category files are stored here
+ mimeResources = ./resources/xdg-mime;
+
+ # Files present in mimeResources with newline separated mime-types
+ # Would be more fun to load these dynamically with builtins.readDir, but I will be disciplined
+ categories = [ "audio" "directory" "email" "image" "pdf" "text" "video" ];
+
+ # Dynamically generate categories
+ categoryApplications = (lib.genAttrs categories (category: lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [ ];
+ description = "set the default application used for ${category} files. omit '.desktop'";
+ }));
+
+ # Create list of mime-types from the category file
+ getCategoryMimeTypes = category: lib.filter (s: s != "") (lib.splitString "\n" (builtins.readFile "${mimeResources}/${category}"));
+
+ # Structure default applications for a specified category how home-manager expects
+ assignMimeTypes = applications: category: lib.map (mimetype:
+ { "${mimetype}" = lib.mkIf (applications != []) (lib.map (app: "${app}.desktop") applications); }
+ ) (getCategoryMimeTypes category);
+
+ # Combine all category applications
+ defaultApplications = lib.mkMerge (lib.flatten (
+ lib.map (category: assignMimeTypes cfg.categoryApplications.${category} category) categories
+ ));
+in {
+ options.xdg.mimeApps._defaultCategoryApplications = {
+ enable = lib.mkEnableOption "set default applications as defined in the resources dir";
+ inherit categoryApplications;
+ };
+
+ config = lib.mkIf cfg.enable {
+ xdg.mimeApps = {
+ enable = lib.mkDefault true;
+ inherit defaultApplications;
+ };
+ };
+}
diff --git a/hosts/T495/configuration.nix b/hosts/T495/configuration.nix
new file mode 100644
index 0000000..b80f27e
--- /dev/null
+++ b/hosts/T495/configuration.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }: {
+ imports = [ ./wg.nix ];
+
+ # Setup bootloader
+ boot._loader.enable = true;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ collections = {
+ desktop = {
+ extraUtilities.enable = true;
+ cad.enable = true;
+ chromium.enable = true;
+ crypto.enable = true;
+ graphics.enable = true;
+ office.enable = true;
+ };
+ development = {
+ android.enable = true;
+ c.enable = true;
+ docker.enable = true;
+ lua.enable = true;
+ web = {
+ hugo = {
+ enable = true;
+ openFirewall = true;
+ };
+ node.enable = true;
+ };
+ };
+ bluetooth.enable = true;
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ autologin.enable = true;
+ nas = {
+ enable = true;
+ office.enable = true;
+ };
+ wifi.enable = true;
+ };
+
+ # Install spotify
+ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+ "spotify"
+ ];
+ environment.systemPackages = with pkgs; [
+ spotify
+ ];
+
+ # Use amdgpu driver for x11
+ services.xserver.videoDrivers = [ "amdgpu" ];
+
+ # Configure home
+ home-manager.users.timmy = {
+ gtk._mintTheme = {
+ dark = true;
+ color = "Purple";
+ icons.color = "Purple";
+ };
+ programs._st = {
+ enable = true;
+ font = {
+ name = "TamzenForPowerline";
+ attrs = {
+ pixelsize = 20;
+ };
+ };
+ };
+ programs._seasonalwallpaper.wallpapers.download = true;
+ fonts.fontconfig = {
+ subpixelRendering = "rgb";
+ hinting = "slight";
+ };
+ };
+
+ system.stateVersion = "24.05";
+}
diff --git a/modules/hosts/T495/hardware-configuration.nix b/hosts/T495/hardware-configuration.nix
index 6258a83..6258a83 100644
--- a/modules/hosts/T495/hardware-configuration.nix
+++ b/hosts/T495/hardware-configuration.nix
diff --git a/hosts/T495/resources/secrets/wg.yaml b/hosts/T495/resources/secrets/wg.yaml
new file mode 100644
index 0000000..7581228
--- /dev/null
+++ b/hosts/T495/resources/secrets/wg.yaml
@@ -0,0 +1,17 @@
+wg0: ENC[AES256_GCM,data:ZWvG10UxVMb7e8wxTgQqybp2NsziJdTRDqF/ZJ1N3bf220P3l59jQcHyHi7l9UFbUhErM/zxXc0/B7AE1TPzcFGZRxqQC3TenCCW8RuKkm9bXcctseXtLqRr/EYRH07952dTzvdN/eBxLXLPqgi/K1qnuWBLS2zf/WKAJyCFsKPlzhH6lA7+VAM16Q9m+0nktruQ34DMok1LBlN7DN/gZkJWx6Ipzst4f9lrDiSm1QNmxskGUqDdPWednIJ8d8iW2j5xFBgzE0iUyZu0VwuOqP2ioUTYLiLGbx2/XrRXyz66u5gx1agBtpeAirWmNcmJlDl8olik64tXQoemHUHXQokpsuR1ENnm2DFqB1lVbXA3Q00cyzYX08cii7olGHKbVaty2n4RMAFwTBh2fRYs4LsH+cyn4umhLkOhUEbFInujrA5vVltQv9h/r4Y22osBgVNuSQ4I5hl4d348yc45yCENnW6iSs8Diu141l+IbxvQPIpbR462ctYtM+TRJW4AFbgPUdHrRD7DrURnyP9ZePSIU/1pPFZNeNJjYwDETXaz,iv:WLgAIxHhKLgw8TGfuXTlh7rkcjIawYjb3J6mo3sRSmE=,tag:b7tZzypGa8EmUinCJcDkoQ==,type:str]
+wg1: ENC[AES256_GCM,data:MCojgM00HJuTgY+K6KBUnDqH4Y8Jerc37vnYO4SC/srlNXxp5QKAuyTDwb7bjmGboyOHq//GLbRMDN5vZEeUQNGyjsycElmSl76bcLS2XPdGrbxGZD7HQY9PY+Z0V1avwVovRrITfG2irffnVxqfis8R5GBOovy/MRhyjG8B6vhastYoGp1ncAmAV+QsUGn/MTeKr+MpZ1Q+SGyeu8gAvPknOEzrR8WqK0ySbWKCQjeEe38kLNwhSNI1cp4Md02dskNUWnVKRyvfXOuYoPsGAmhVikZ+C4yFlndEJoQ5IJPbTO0mv2394bKtflCyB2hnerrTd+NaZcRgPP72Lvw3gDmx7olweL7f2uzr5Vs4NO1AIWcw/MkJxW2UU4GzHbJZM7H3b/IUY1+y8UDEk4rtBT9t+M5LapuVbpagwulIP7+O7Bjsgb4ZJxaGDjtUe3JcUgMDRAexDa7lioDZMpWT5UPRvUczfncqXs+b40vo02eZqiAxtWVK/8587uo=,iv:lsCA/USQc6ADWdRVYIQHXQtdtukEf7cXNTXIKzlynDU=,tag:xlW4MCIwMC8A8f2maHhPQg==,type:str]
+sops:
+ age:
+ - enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4NEN4NGxDR1oreGVoSGhE
+ TzMxSEY0QVBhS2Z6MW15ci9aVlJ0a3IyVlZBCldPRVNvcUhJSHhWSEk3akd4RjN0
+ ajhUV2d1ZWRsRFU4cTE2dGl6RmM4MGsKLS0tIFhnUjl5aDJqWVB1NE15SlNzR2Iv
+ YTNydURsOUMrSXZGdk9UOUdUQlA0SFUKxEDJRR6tpYva9qpWo9NxwCxk/xpRVoTl
+ YJkmDZzMcXikXXiro96AprP9dXJXvMPKYPGl2Zsal8PlGFPBoHW2GA==
+ -----END AGE ENCRYPTED FILE-----
+ recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ lastmodified: "2026-06-19T19:30:00Z"
+ mac: ENC[AES256_GCM,data:tqZqi1l/15EAq/aqiXHmmi1aPUrdKJiK7pQZa5bUOdThfB3CVIQ4s0lfvybhMJfU6gbuiGih/Umr0bJ9joaFozFn/mPPUj7sjuqzHk1bxMG3qp1kHmmegAVBtjv0Fht62N1MFtH8woG3k+iRmd0ndTdFCMa0OVDvsCzw3t11wbw=,iv:/mQWM769XROsm6MEHpEsX8TiBEQd6PWODkb9CkH0eZY=,tag:oMObl25mPwPcqluWwUefcQ==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.13.1
diff --git a/hosts/T495/wg.nix b/hosts/T495/wg.nix
new file mode 100644
index 0000000..7b98ad8
--- /dev/null
+++ b/hosts/T495/wg.nix
@@ -0,0 +1,9 @@
+{ config, pkgs, inputs, ... }: {
+ sops.secrets.wg0 = { sopsFile = ./resources/secrets/wg.yaml; key = "wg0"; }; # Office
+ sops.secrets.wg1 = { sopsFile = ./resources/secrets/wg.yaml; key = "wg1"; }; # Home
+
+ networking.wg-quick.interfaces = {
+ wg0.configFile = config.sops.secrets.wg0.path;
+ wg1.configFile = config.sops.secrets.wg1.path;
+ };
+}
diff --git a/hosts/X230/configuration.nix b/hosts/X230/configuration.nix
new file mode 100644
index 0000000..22856f2
--- /dev/null
+++ b/hosts/X230/configuration.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }: {
+ imports = [ ./wg.nix ];
+
+ # Setup bootloader
+ boot._loader.enable = true;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ collections = {
+ desktop = {
+ crypto.enable = true;
+ graphics.enable = true;
+ office.enable = true;
+ };
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ autologin.enable = true;
+ wifi.enable = true;
+ };
+
+ # Use intel driver for better performance and vsync
+ services.xserver.videoDrivers = [ "intel" ];
+
+ # Configure home
+ home-manager.users.timmy = {
+ gtk._mintTheme = {
+ dark = true;
+ color = "Teal";
+ icons.color = "Teal";
+ };
+ fonts.fontconfig.defaultFonts.monospace = [ "TamzenForPowerline" ];
+ gtk = {
+ font.name = "monospace";
+ font.size = 8;
+ cursorTheme.size = 24;
+ };
+ programs._st = {
+ enable = true;
+ font = {
+ name = "TamzenForPowerline";
+ attrs = {
+ pixelsize = 14;
+ };
+ };
+ };
+ programs._seasonalwallpaper.wallpapers.download = true;
+ fonts.fontconfig = {
+ subpixelRendering = "rgb";
+ hinting = "full";
+ };
+ };
+
+ system.stateVersion = "24.05";
+}
diff --git a/modules/hosts/T430/hardware-configuration.nix b/hosts/X230/hardware-configuration.nix
index 206a525..0791585 100644
--- a/modules/hosts/T430/hardware-configuration.nix
+++ b/hosts/X230/hardware-configuration.nix
@@ -8,14 +8,29 @@
[ (modulesPath + "/installer/scan/not-detected.nix")
];
- boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sr_mod" "sdhci_pci" ];
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usb_storage" "sd_mod" "sdhci_pci" ];
boot.initrd.kernelModules = [ ];
- boot.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
- { device = "/dev/disk/by-uuid/b991914b-3a4c-4248-9472-b5403729601a";
+ { device = "/dev/disk/by-uuid/41036740-73bc-4004-a302-01233b4d83b8";
fsType = "btrfs";
+ options = [ "subvol=@" ];
+ };
+
+ boot.initrd.luks.devices."enc".device = "/dev/disk/by-uuid/6019772f-4a1c-4abd-9c70-b1d71cc2de65";
+
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/41036740-73bc-4004-a302-01233b4d83b8";
+ fsType = "btrfs";
+ options = [ "subvol=@home" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/72D4-F66A";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
};
swapDevices = [ ];
diff --git a/hosts/X230/resources/secrets/wg0.yaml b/hosts/X230/resources/secrets/wg0.yaml
new file mode 100644
index 0000000..5941b70
--- /dev/null
+++ b/hosts/X230/resources/secrets/wg0.yaml
@@ -0,0 +1,16 @@
+wg0: ENC[AES256_GCM,data:wcgowNptTdrJNjzH6n/ulbec5+GPkuRAUNidWFy4dhClioTg8vdrXhhwP+sykwEZYOjsLurkU0Rw1w9ds+AGe3J+FnW1qKdskcY+8t/CyNY51pUbzMCKxexnNj52+0+VlH6FAUyplo6ESg/vlWCFyuyACWjQfdqDW/1PxJzrYqZ7MIwbCdntjE/84F52BqxePt3LolzvzTGUOx5Lr6Jbv9i3tv1R9NmZxt5t2gwaGbIIPWMVZh972w5HJYa8bfx67vuyj6HE46tMiu8WdQbHfjRvVIA+0OtHihpDGHfi5Q6iXpO/rk4YJZjsiEgTMTqD08HD5Gm+wBFwHSJjCOBpBsq3GsspDLNI+EXel7Gmtk+BhL1tFQdpYPwz7bHd03Znawr4Br7R0gHJg4FXnhKlG+SyDqXKirnCyCTfUotIBmU0dX4tzmwiOMyvImgXZA==,iv:GAk27qkZDopzdWnBeL7yTmyn9dM2wSzKd41NRhsyNNY=,tag:Ba7jZhqEa8dUedIvVFBbHA==,type:str]
+sops:
+ age:
+ - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4NEN4NGxDR1oreGVoSGhE
+ TzMxSEY0QVBhS2Z6MW15ci9aVlJ0a3IyVlZBCldPRVNvcUhJSHhWSEk3akd4RjN0
+ ajhUV2d1ZWRsRFU4cTE2dGl6RmM4MGsKLS0tIFhnUjl5aDJqWVB1NE15SlNzR2Iv
+ YTNydURsOUMrSXZGdk9UOUdUQlA0SFUKxEDJRR6tpYva9qpWo9NxwCxk/xpRVoTl
+ YJkmDZzMcXikXXiro96AprP9dXJXvMPKYPGl2Zsal8PlGFPBoHW2GA==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-09T12:36:15Z"
+ mac: ENC[AES256_GCM,data:T8hQbFrPOGYQl8tbqUMLdQN3qjhcAXmKfwqEsLQkDjr2nxUXDz8d58TUsaRvkHC4jPo9lXyPL5SSpua2kzJIcDDLPkiPB/3qV8ksJQ0dgcfLkUnrI1mtoo9AOPnHrTjstSAR2cmiFbCTqRpkLnGwI+NoCLjNjd/GNSAlMl00QMg=,iv:boSHzNAuKGkAhtkApOOJEtW4gt13mMmCin24gf3dXIs=,tag:1+4+BE8ZPgvgf4RlH6Wmhw==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/modules/hosts/T495/wg.nix b/hosts/X230/wg.nix
index 3881b63..3881b63 100644
--- a/modules/hosts/T495/wg.nix
+++ b/hosts/X230/wg.nix
diff --git a/hosts/flex-wg-router/configuration.nix b/hosts/flex-wg-router/configuration.nix
new file mode 100644
index 0000000..a0777f0
--- /dev/null
+++ b/hosts/flex-wg-router/configuration.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }: let
+ ipAddress = "10.1.1.1";
+in {
+ imports = [ ./wg.nix ];
+
+ # Setup bootloader
+ boot._loader.enable = true;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ headless = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ };
+
+ # Network config
+ networking = {
+ # Label lan and wan interfaces
+ _interfaceLabels = {
+ enable = true;
+ interfaces = {
+ lan0 = "98:b7:85:22:9b:43"; # External
+ wan0 = "54:ee:75:8c:4b:2d"; # Internal
+ };
+ };
+ # Set ip addresses
+ interfaces = {
+ lan0.ipv4.addresses = [{
+ address = ipAddress;
+ prefixLength = 24;
+ }];
+ wan0.ipv4.addresses = [{
+ address = "46.110.173.163"; # Reserved static ip for wg-router
+ prefixLength = 31;
+ }];
+ };
+ defaultGateway = {
+ address = "46.110.173.161";
+ interface = "wan0";
+ };
+ # Firewall rules
+ firewall = {
+ interfaces.wan0 = {
+ allowedUDPPorts = [ 51820 ];
+ };
+ };
+ };
+
+ # Router config
+ services._router = {
+ routing = {
+ enable = true;
+ interfaces = {
+ lan = [ "lan0" ];
+ wan = "wan0";
+ };
+ };
+ dnsDhcpConfig = {
+ localDomain = "wg-router.pls.lan";
+ dhcp = {
+ defaultGateway = ipAddress;
+ localhostIp = ipAddress;
+ rangeStart = "10.1.1.100";
+ rangeEnd = "10.1.1.250";
+ staticLeases = {
+ idrac-8HT2W52 = {
+ macAddress = "18:fb:7b:9d:16:b3";
+ staticIp = "10.1.1.10";
+ };
+ };
+ };
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy.enable = true;
+
+ system.stateVersion = "25.05";
+}
diff --git a/modules/hosts/optiplex/hardware-configuration.nix b/hosts/flex-wg-router/hardware-configuration.nix
index 3d9651e..01cff6d 100644
--- a/modules/hosts/optiplex/hardware-configuration.nix
+++ b/hosts/flex-wg-router/hardware-configuration.nix
@@ -8,29 +8,29 @@
[ (modulesPath + "/installer/scan/not-detected.nix")
];
- boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "firewire_ohci" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
- { device = "/dev/disk/by-uuid/a9d54117-31f6-4cad-b1b2-250f6bfd7808";
+ { device = "/dev/disk/by-uuid/01eae5fd-a46e-4a36-8a9d-247a0b16bcef";
fsType = "btrfs";
options = [ "subvol=@" ];
};
- fileSystems."/home" =
- { device = "/dev/disk/by-uuid/a9d54117-31f6-4cad-b1b2-250f6bfd7808";
- fsType = "btrfs";
- options = [ "subvol=@home" ];
- };
-
fileSystems."/boot" =
- { device = "/dev/disk/by-uuid/6987-81B6";
+ { device = "/dev/disk/by-uuid/345A-436A";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/01eae5fd-a46e-4a36-8a9d-247a0b16bcef";
+ fsType = "btrfs";
+ options = [ "subvol=@home" ];
+ };
+
swapDevices = [ ];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
@@ -38,7 +38,7 @@
# still possible to use this option, but it's recommended to use it in conjunction
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
networking.useDHCP = lib.mkDefault true;
- # networking.interfaces.eno1.useDHCP = lib.mkDefault true;
+ # networking.interfaces.enp3s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
diff --git a/hosts/flex-wg-router/resources/secrets/wg.yaml b/hosts/flex-wg-router/resources/secrets/wg.yaml
new file mode 100644
index 0000000..1f6867b
--- /dev/null
+++ b/hosts/flex-wg-router/resources/secrets/wg.yaml
@@ -0,0 +1,17 @@
+wg1: ENC[AES256_GCM,data:r7jNBzEcItmlEtjhKCbyOBaNYfutKxC2UdUYSLHfYyLnwwdIwM1kfvd5K1/UZNAKoG7sHpBha59M1tvZAOIGAFnzG14YsVrMD8w6Qy4pc0FmdyNHDEM4EwaqHFRjbb5oBAFv6lI2VZ3AgXf6StXoVUYtbEA1QBVqVq4Syk6/CalnhkE2LuZpuVA5GZUZ8aTmFRp9zOnhcNoVJMrokTUswV4Mgn3zt2Tb+3bfoZJ9jbb6H8P/F0NGU+phy0EENZMIqOGBP5aPPIZfVQYphQcG6BYiddti3Copq57vqh/qOB70LPle6b/IsaT/K8Xqjp8PjNI/e5gkZdVwIGx/w3Gk0+CkD0tDEUMBdsFfvm7Dbz3xQxN66/0ZMGQgic0xtytr/DfKCIMIwsr33GKavP7OXEJ6lUF615Y4PQhNNx4ePlgcttt2b7TG5bM8nxKsaQ==,iv:mLYNgKXCp8w2JO90Rsn7gtifEn4Yc6JKnjws7uo1w10=,tag:c51B1fZe1HnJhFDc86HnOg==,type:str]
+wg0: ENC[AES256_GCM,data:SJQ21aLwoQ0nEHfoHRd+ksL8pX7HoCRVjGIS/BZxq9JQhHJg9ZHHbwwUkz/3vrq1S+PD7e1bL0FHpgHPuZVHawpaFIeWd6TEPH+6oUxlRbDaEbcWR5POlNyMVV3z9TnOElgmqT0VUqfY80NEqFPbCLdjcWHjnwO4nzrEhPMA9WG2PFCAnZNUtVXh2mnblA61/xmxkSVysahBP+bTHA8a+v/AXy7WrHbnHizTeevdCMqWyDhzHvO8hfH4tU/xJ7GQrG/bxk4JZ6XT8a2CAqmNEKyWicB/zSc5NdILNQL7Kx2mzg/fDp4nltf7iBRZfLuN+r7whrKJ2lJQPATeyjMlIgHUcnohjihiOsGYiBcB3/Y4hIHVt7rRBMoFBB2OgNKC3gx6saZreRxLHZcRZFcVm39G9vaw6EI=,iv:qO8vMlstL/kOxFSlUd/dCtAK9ZzZt+LH/9vfulqHiMc=,tag:yuiwA8Hp8qDrF3UPlCMSUg==,type:str]
+sops:
+ age:
+ - recipient: age1f0tmpy2nam58skmznjyqd3zf54rxtfrk6fda0vlpq9y3yg6wac7sjf0vja
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoNVZ5dmNSd1NRQUhURkl2
+ dnk3MkNjMFUyNnU5L1FFNTV0a1NUbUZ6ZUEwCmNNUldIdnoycVpwUHJrcXZvZXp5
+ NGVHcUlHUm1uK0QxV1JmdDVyQVoxZDgKLS0tIDJhSHhkYjNML045SHNobytucnVZ
+ L25wUWRJbzZMZDFseXdvOFJXQVRxN28KJjC3ola24tTEV8tFYpnsId4d0S+jHkS9
+ ME6i4jorWRlQKdYn/gTUoqgMAvJEc73hjTfgX6bFshhuhflfGxXQQw==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-31T19:40:45Z"
+ mac: ENC[AES256_GCM,data:XON+JNOWr6WRYYI0+vCC4qiDST8iY/XQZlkB16l5vMsirS3j5iAIi60hn5viFqMn+IIV7GretbVnBVP32R4C59II8bIySzrsdJj5AuvTvdBvglhkelhiDnchqE98KCG9zr41bJsSaQ/8ubRy7b5jiu9aqzQFg9UQZousecIu/MU=,iv:IJNCc05iu0sZxa3RFh5l1TMcwl3YKRtVXn4wfdOy6M8=,tag:OO5uC8nAjqsWoxC1N801GA==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/hosts/flex-wg-router/wg.nix b/hosts/flex-wg-router/wg.nix
new file mode 100644
index 0000000..b454b81
--- /dev/null
+++ b/hosts/flex-wg-router/wg.nix
@@ -0,0 +1,9 @@
+{ config, pkgs, inputs, ... }: {
+ sops.secrets.wg0.sopsFile = ./resources/secrets/wg.yaml;
+ sops.secrets.wg1.sopsFile = ./resources/secrets/wg.yaml;
+
+ networking.wg-quick.interfaces = {
+ wg0.configFile = config.sops.secrets.wg0.path;
+ wg1.configFile = config.sops.secrets.wg1.path;
+ };
+}
diff --git a/hosts/gnuslashprinter/configuration.nix b/hosts/gnuslashprinter/configuration.nix
new file mode 100644
index 0000000..9e1f734
--- /dev/null
+++ b/hosts/gnuslashprinter/configuration.nix
@@ -0,0 +1,29 @@
+{
+ imports = [
+ ./flask-relay-ctl.nix
+ ./klipper.nix
+ ];
+
+ boot._loader.enable = true;
+
+ _archetypes = {
+ profiles = {
+ headless = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy.enable = true;
+
+ # Name devices
+ services.udev.extraRules = ''
+ SUBSYSTEM=="tty", KERNELS=="3-9", SYMLINK+="gsp-power"
+ SUBSYSTEM=="tty", KERNELS=="3-10", SYMLINK+="gsp-control"
+ '';
+
+ system.stateVersion = "25.11";
+}
diff --git a/hosts/gnuslashprinter/flask-relay-ctl.nix b/hosts/gnuslashprinter/flask-relay-ctl.nix
new file mode 100644
index 0000000..27507b6
--- /dev/null
+++ b/hosts/gnuslashprinter/flask-relay-ctl.nix
@@ -0,0 +1,37 @@
+{ pkgs, ... }: let
+ flaskrelayctl = pkgs.python3Packages.buildPythonApplication {
+ pname = "flaskrelayctl";
+ version = "1.0";
+ src = ./resources/flaskrelayctl;
+ pyproject = true;
+ build-system = with pkgs.python3Packages; [ setuptools ];
+ dependencies = with pkgs.python3Packages; [
+ flask
+ pyserial
+ ];
+ };
+in {
+ users.users.relay-api = {
+ isSystemUser = true;
+ group = "relay-api";
+ extraGroups = [ "dialout" ];
+ };
+ users.groups.relay-api = {};
+
+ systemd.services.relay-api = let
+ RELAYCTL_DEV = "/dev/gsp-power";
+ in {
+ description = "USB Relay Flask API";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ User = "relay-api";
+ Group = "relay-api";
+ ExecStart = "${flaskrelayctl}/bin/flaskrelayctl";
+ Restart = "always";
+ DeviceAllow = [ "${RELAYCTL_DEV} rw" ];
+ };
+ };
+
+ environment.systemPackages = [ flaskrelayctl ];
+}
diff --git a/hosts/gnuslashprinter/hardware-configuration.nix b/hosts/gnuslashprinter/hardware-configuration.nix
new file mode 100644
index 0000000..278d6e6
--- /dev/null
+++ b/hosts/gnuslashprinter/hardware-configuration.nix
@@ -0,0 +1,44 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/416a481f-d656-4bd1-acdb-bd0a6f2d2c3a";
+ fsType = "btrfs";
+ options = [ "subvol=@" ];
+ };
+
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/416a481f-d656-4bd1-acdb-bd0a6f2d2c3a";
+ fsType = "btrfs";
+ options = [ "subvol=@home" ];
+ };
+
+ fileSystems."/nix" =
+ { device = "/dev/disk/by-uuid/416a481f-d656-4bd1-acdb-bd0a6f2d2c3a";
+ fsType = "btrfs";
+ options = [ "subvol=@nix" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/18C4-6D53";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/gnuslashprinter/klipper.nix b/hosts/gnuslashprinter/klipper.nix
new file mode 100644
index 0000000..331a0c7
--- /dev/null
+++ b/hosts/gnuslashprinter/klipper.nix
@@ -0,0 +1,100 @@
+{
+ # Klipper firmware
+ services.klipper = {
+ enable = true;
+ firmwares = {
+ mcu = {
+ enable = true;
+ # Serial port connected to the microcontroller
+ serial = "/dev/gsp-control";
+ # Klipper flash must be enabled in order to build mcu firmware
+ # The resulting `klipper-flash-mcu` command will show the location of the firmware bin in the nix store
+ enableKlipperFlash = true;
+ # Run klipper-genconf to generate this
+ configFile = ./resources/klipper/mcu/config;
+ };
+ };
+ configFile = ./resources/klipper/printer.cfg;
+ logFile = "/var/lib/klipper/klipper.log";
+ };
+ # Mutable config
+ services.klipper.mutableConfig = true;
+ #configDir = "/var/lib/moonraker/config"; # Accessible by moonraker # TODO
+
+ # Moonraker web-api
+ security.polkit.enable = true; # required for services.moonraker.allowSystemControl
+ services.moonraker = {
+ user = "root";
+ enable = true;
+ address = "0.0.0.0";
+ allowSystemControl = true;
+ settings = {
+ authorization = {
+ force_logins = true;
+ cors_domains = [
+ "*.local"
+ "*.lan"
+ "*://app.fluidd.xyz"
+ "*://my.mainsail.xyz"
+ ];
+ trusted_clients = [
+ "10.0.0.0/8"
+ "127.0.0.0/8"
+ "169.254.0.0/16"
+ "172.16.0.0/12"
+ "192.168.0.0/16"
+ "FE80::/10"
+ "::1/128"
+ ];
+ };
+ #file_manager.check_klipper_config_path = false; # Disable warning when klipper config is not accessible by moonraker
+ # mainsail.cfg
+ #"update_manager mainsail-config" = {
+ # type = "git_repo";
+ # primary_branch = "master";
+ # path = "~/mainsail-config";
+ # origin = "https://github.com/mainsail-crew/mainsail-config.git";
+ # managed_services = "klipper";
+ #};
+
+ "power printer" = let
+ relayApiHost = "http://localhost:5050";
+ in {
+ type = "http";
+ on_url = "${relayApiHost}/on";
+ off_url = "${relayApiHost}/off";
+ status_url = "${relayApiHost}/status";
+ response_template = ''
+
+ # The module will perform the "GET" request using the appropriate url.
+ # We use the `last_response` method to fetch the result and decode the
+ # json response.
+ {% set resp = http_request.last_response().json() %}
+ # The expression below will render "on" or "off".
+ {resp["state"].lower()}
+ '';
+ off_when_shutdown = true;
+ on_when_job_queued = true;
+ locked_while_printing = true;
+ restart_klipper_when_powered = true;
+ restart_delay = "1";
+ bound_services = "klipper";
+ initial_state = "off";
+ };
+ };
+ };
+
+ # Mainsail web-interface
+ services.mainsail = {
+ enable = true;
+ hostName = "0.0.0.0";
+ nginx.listenAddresses = [ "0.0.0.0" ];
+ };
+ services.nginx.clientMaxBodySize = "1000m"; # Allow large gcodes, etc.
+ networking.firewall.allowedTCPPorts = [ 80 ]; # Port for mainsail via nginx
+
+ # Webcam support in mainsail
+ # TODO hook to restart ustreamer when webcam is connected
+ services.ustreamer.enable = true;
+ services.mainsail.nginx.locations."/webcam/".proxyPass = "http://localhost:8080/stream"; # Default location for ustreamer stream
+}
diff --git a/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml b/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml
new file mode 100644
index 0000000..b151b74
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/flaskrelayctl/pyproject.toml
@@ -0,0 +1,10 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "flaskrelayctl"
+version = "1.0"
+
+[project.scripts]
+flaskrelayctl = "server:main"
diff --git a/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py b/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py
new file mode 100644
index 0000000..f2c0bc3
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/flaskrelayctl/server/__init__.py
@@ -0,0 +1,54 @@
+from flask import Flask, jsonify
+import serial
+import os
+
+app = Flask(__name__)
+
+RELAY_DEV = os.environ.get("RELAYCTL_DEV", "/dev/ttyUSB0")
+RELAY_HOST = os.environ.get("RELAYCTL_HOST", "127.0.0.1")
+RELAY_PORT = os.environ.get("RELAYCTL_PORT", "5050")
+RELAY_BAUD = 9600
+RELAY_ID = 1 # NOTE: multi relay boards are not supported (only relay id 1)
+
+def get_cmd(state: bool):
+ """
+ LCUS relay 4-byte control command syntax is as follows:
+ start: always 0xA0
+ relay: id of relay (counts from 1)
+ state: coil energized 0 or 1
+ checksum: sum of previous 3 bytes
+ """
+ start = 0xA0
+ relay = RELAY_ID
+ state = int(state)
+ checksum = start + relay + state
+ return bytes((start, relay, state, checksum))
+
+def get_serial():
+ return serial.Serial(RELAY_DEV, RELAY_BAUD, timeout=1)
+
+@app.route("/on", methods=["GET", "POST"])
+def on():
+ with get_serial() as ser:
+ ser.write(get_cmd(True))
+ return jsonify({"state": "ON"})
+
+@app.route("/off", methods=["GET", "POST"])
+def off():
+ with get_serial() as ser:
+ ser.write(get_cmd(False))
+ return jsonify({"state": "OFF"})
+
+@app.route("/status", methods=["GET"])
+def status():
+ with get_serial() as ser:
+ ser.write(bytes((0xFF,))) # write 0xFF to get status
+ r = ser.readline()
+ state = r.decode("utf-8").removeprefix("CH1: ").strip()
+ return jsonify({"state": state})
+
+def main():
+ app.run(host=RELAY_HOST, port=int(RELAY_PORT))
+
+if __name__ == "__main__":
+ main()
diff --git a/hosts/gnuslashprinter/resources/klipper/mcu/config b/hosts/gnuslashprinter/resources/klipper/mcu/config
new file mode 100644
index 0000000..3e192a3
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/klipper/mcu/config
@@ -0,0 +1,109 @@
+# CONFIG_LOW_LEVEL_OPTIONS is not set
+# CONFIG_MACH_AVR is not set
+# CONFIG_MACH_ATSAM is not set
+# CONFIG_MACH_ATSAMD is not set
+# CONFIG_MACH_LPC176X is not set
+CONFIG_MACH_STM32=y
+# CONFIG_MACH_HC32F460 is not set
+# CONFIG_MACH_RPXXXX is not set
+# CONFIG_MACH_PRU is not set
+# CONFIG_MACH_AR100 is not set
+# CONFIG_MACH_LINUX is not set
+# CONFIG_MACH_SIMU is not set
+CONFIG_BOARD_DIRECTORY="stm32"
+CONFIG_MCU="stm32f103xe"
+CONFIG_CLOCK_FREQ=72000000
+CONFIG_SERIAL=y
+CONFIG_FLASH_SIZE=0x10000
+CONFIG_FLASH_BOOT_ADDRESS=0x8000000
+CONFIG_RAM_START=0x20000000
+CONFIG_RAM_SIZE=0x5000
+CONFIG_STACK_SIZE=512
+CONFIG_FLASH_APPLICATION_ADDRESS=0x8007000
+CONFIG_STM32_SELECT=y
+CONFIG_MACH_STM32F103=y
+# CONFIG_MACH_STM32F207 is not set
+# CONFIG_MACH_STM32F401 is not set
+# CONFIG_MACH_STM32F405 is not set
+# CONFIG_MACH_STM32F407 is not set
+# CONFIG_MACH_STM32F429 is not set
+# CONFIG_MACH_STM32F446 is not set
+# CONFIG_MACH_STM32F765 is not set
+# CONFIG_MACH_STM32F031 is not set
+# CONFIG_MACH_STM32F042 is not set
+# CONFIG_MACH_STM32F070 is not set
+# CONFIG_MACH_STM32F072 is not set
+# CONFIG_MACH_STM32G070 is not set
+# CONFIG_MACH_STM32G071 is not set
+# CONFIG_MACH_STM32G0B0 is not set
+# CONFIG_MACH_STM32G0B1 is not set
+# CONFIG_MACH_STM32G431 is not set
+# CONFIG_MACH_STM32G474 is not set
+# CONFIG_MACH_STM32H723 is not set
+# CONFIG_MACH_STM32H743 is not set
+# CONFIG_MACH_STM32H750 is not set
+# CONFIG_MACH_STM32L412 is not set
+# CONFIG_MACH_N32G452 is not set
+# CONFIG_MACH_N32G455 is not set
+CONFIG_MACH_STM32F1=y
+CONFIG_HAVE_STM32_USBFS=y
+CONFIG_STM32_USB_DOUBLE_BUFFER_TX=y
+CONFIG_HAVE_STM32_CANBUS=y
+CONFIG_STM32_DFU_ROM_ADDRESS=0
+# CONFIG_STM32_FLASH_START_2000 is not set
+# CONFIG_STM32_FLASH_START_5000 is not set
+CONFIG_STM32_FLASH_START_7000=y
+# CONFIG_STM32_FLASH_START_8000 is not set
+# CONFIG_STM32_FLASH_START_8800 is not set
+# CONFIG_STM32_FLASH_START_9000 is not set
+# CONFIG_STM32_FLASH_START_10000 is not set
+# CONFIG_STM32_FLASH_START_800 is not set
+# CONFIG_STM32_FLASH_START_1000 is not set
+# CONFIG_STM32_FLASH_START_4000 is not set
+# CONFIG_STM32_FLASH_START_0000 is not set
+CONFIG_CLOCK_REF_FREQ=8000000
+CONFIG_STM32F0_TRIM=16
+# CONFIG_STM32_USB_PA11_PA12 is not set
+CONFIG_STM32_SERIAL_USART1=y
+# CONFIG_STM32_CANBUS_PA11_PA12 is not set
+# CONFIG_STM32_CANBUS_PA11_PB9 is not set
+CONFIG_SERIAL_BAUD=250000
+CONFIG_USB_VENDOR_ID=0x1d50
+CONFIG_USB_DEVICE_ID=0x614e
+CONFIG_USB_SERIAL_NUMBER="12345"
+CONFIG_WANT_ADC=y
+CONFIG_WANT_SPI=y
+CONFIG_WANT_SOFTWARE_SPI=y
+CONFIG_WANT_I2C=y
+CONFIG_WANT_SOFTWARE_I2C=y
+CONFIG_WANT_HARD_PWM=y
+CONFIG_WANT_BUTTONS=y
+CONFIG_WANT_TMCUART=y
+CONFIG_WANT_NEOPIXEL=y
+CONFIG_WANT_PULSE_COUNTER=y
+CONFIG_WANT_ST7920=y
+CONFIG_WANT_HD44780=y
+CONFIG_WANT_ADXL345=y
+CONFIG_WANT_LIS2DW=y
+CONFIG_WANT_MPU9250=y
+CONFIG_WANT_ICM20948=y
+CONFIG_WANT_THERMOCOUPLE=y
+CONFIG_WANT_HX71X=y
+CONFIG_WANT_ADS1220=y
+CONFIG_WANT_LDC1612=y
+CONFIG_WANT_SENSOR_ANGLE=y
+CONFIG_NEED_SENSOR_BULK=y
+CONFIG_WANT_LOAD_CELL_PROBE=y
+CONFIG_NEED_SOS_FILTER=y
+CONFIG_CANBUS_FREQUENCY=1000000
+CONFIG_INLINE_STEPPER_HACK=y
+CONFIG_HAVE_STEPPER_OPTIMIZED_BOTH_EDGE=y
+CONFIG_WANT_STEPPER_OPTIMIZED_BOTH_EDGE=y
+CONFIG_HAVE_GPIO=y
+CONFIG_HAVE_GPIO_ADC=y
+CONFIG_HAVE_GPIO_SPI=y
+CONFIG_HAVE_GPIO_I2C=y
+CONFIG_HAVE_GPIO_HARD_PWM=y
+CONFIG_HAVE_STRICT_TIMING=y
+CONFIG_HAVE_CHIPID=y
+CONFIG_HAVE_BOOTLOADER_REQUEST=y
diff --git a/hosts/gnuslashprinter/resources/klipper/printer.cfg b/hosts/gnuslashprinter/resources/klipper/printer.cfg
new file mode 100644
index 0000000..4baba56
--- /dev/null
+++ b/hosts/gnuslashprinter/resources/klipper/printer.cfg
@@ -0,0 +1,180 @@
+# This file contains pin mappings for the stock 2020 Creality Ender 3
+# V2. To use this config, during "make menuconfig" select the
+# STM32F103 with a "28KiB bootloader" and serial (on USART1 PA10/PA9)
+# communication.
+
+# If you prefer a direct serial connection, in "make menuconfig"
+# select "Enable extra low-level configuration options" and select
+# serial (on USART3 PB11/PB10), which is broken out on the 10 pin IDC
+# cable used for the LCD module as follows:
+# 3: Tx, 4: Rx, 9: GND, 10: VCC
+
+# Flash this firmware by copying "out/klipper.bin" to a SD card and
+# turning on the printer with the card inserted. The firmware
+# filename must end in ".bin" and must not match the last filename
+# that was flashed.
+
+# See docs/Config_Reference.md for a description of parameters.
+
+[include mainsail.cfg]
+
+# Default V2 config
+[stepper_x]
+step_pin: PC2
+dir_pin: PB9
+enable_pin: !PC3
+microsteps: 16
+rotation_distance: 40
+endstop_pin: ^PA5
+position_endstop: -20
+position_min: -20
+position_max: 235
+homing_speed: 50
+
+[stepper_y]
+step_pin: PB8
+dir_pin: PB7
+enable_pin: !PC3
+microsteps: 16
+rotation_distance: 40
+endstop_pin: ^PA6
+position_endstop: -8
+position_min: -8
+position_max: 235
+homing_speed: 50
+
+[stepper_z]
+step_pin: PB6
+dir_pin: !PB5
+enable_pin: !PC3
+microsteps: 16
+rotation_distance: 8
+endstop_pin: probe:z_virtual_endstop
+#position_endstop: 0.0
+position_min: -5
+position_max: 250
+
+[extruder]
+max_extrude_only_distance: 100.0
+step_pin: PB4
+dir_pin: PB3
+enable_pin: !PC3
+microsteps: 16
+rotation_distance: 34.406
+nozzle_diameter: 0.400
+filament_diameter: 1.750
+heater_pin: PA1
+sensor_type: EPCOS 100K B57560G104F
+sensor_pin: PC5
+control: pid
+# tuned for stock hardware with 200 degree Celsius target
+pid_Kp: 21.527
+pid_Ki: 1.063
+pid_Kd: 108.982
+min_temp: 0
+max_temp: 250
+
+[heater_bed]
+heater_pin: PA2
+sensor_type: EPCOS 100K B57560G104F
+sensor_pin: PC4
+control: pid
+# tuned for stock hardware with 50 degree Celsius target
+pid_Kp: 54.027
+pid_Ki: 0.770
+pid_Kd: 948.182
+min_temp: 0
+max_temp: 130
+
+[fan]
+pin: PA0
+
+[mcu]
+serial: /dev/gsp-control
+restart_method: command
+
+[printer]
+kinematics: cartesian
+max_velocity: 300
+max_accel: 3000
+max_z_velocity: 5
+max_z_accel: 100
+
+######################################################################
+# 128x64 Full Graphic Creality CR10 / ENDER 3 stockdisplay
+######################################################################
+
+# This section is used for a Creality "12864" display with a single
+# ribbon cable between the display's EXP3 plug and the
+# micro-controller board's EXP1 connector.
+
+[display]
+lcd_type: st7920
+cs_pin: EXP1_7
+sclk_pin: EXP1_6
+sid_pin: EXP1_8
+encoder_pins: ^EXP1_5, ^EXP1_3
+click_pin: ^!EXP1_2
+
+[output_pin beeper]
+pin: EXP1_1
+
+[board_pins]
+aliases:
+ EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9=<GND>,
+ EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V>,
+ PROBE_IN=PB1,PROBE_OUT=PB0,FIL_RUNOUT=PA6
+
+[display_status]
+
+######
+# Bed leveling
+
+[bltouch]
+sensor_pin: ^PROBE_IN
+control_pin: PROBE_OUT
+x_offset: -40
+y_offset: -12
+#z_offset: 1.1
+probe_with_touch_mode: true
+stow_on_each_sample: false
+
+[bed_mesh]
+speed: 120
+mesh_min: 10, 10
+mesh_max: 195, 220
+probe_count: 5,5
+algorithm: bicubic
+
+[safe_z_home]
+home_xy_position: 157.5, 129.5
+speed: 75
+z_hop: 10
+z_hop_speed: 5
+move_to_previous: true
+
+
+#*# <---------------------- SAVE_CONFIG ---------------------->
+#*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated.
+#*#
+#*# [bed_mesh default]
+#*# version = 1
+#*# points =
+#*# -0.017500, 0.025000, 0.050000, 0.122500, 0.155000
+#*# -0.045000, -0.040000, 0.037500, 0.102500, 0.145000
+#*# -0.007500, -0.032500, 0.005000, 0.092500, 0.132500
+#*# -0.060000, -0.045000, -0.007500, 0.087500, 0.127500
+#*# 0.075000, 0.042500, 0.015000, 0.105000, 0.175000
+#*# x_count = 5
+#*# y_count = 5
+#*# mesh_x_pps = 2
+#*# mesh_y_pps = 2
+#*# algo = bicubic
+#*# tension = 0.2
+#*# min_x = 10.0
+#*# max_x = 195.0
+#*# min_y = 10.0
+#*# max_y = 220.0
+#*#
+#*# [bltouch]
+#*# z_offset = 1.342
diff --git a/hosts/hp-envy-office/configuration.nix b/hosts/hp-envy-office/configuration.nix
new file mode 100644
index 0000000..5e3a463
--- /dev/null
+++ b/hosts/hp-envy-office/configuration.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }: {
+ imports = [ ./wg.nix ];
+
+ # Setup bootloader
+ boot._loader.enable = true;
+ boot.loader.timeout = 15; # Show for longer since it's usually skipped
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ collections = {
+ desktop = {
+ extraUtilities.enable = true;
+ chromium.enable = true;
+ graphics.enable = true;
+ office.enable = true;
+ };
+ development = {
+ docker.enable = true;
+ web = {
+ node.enable = true;
+ };
+ };
+ virtualization.enable = true;
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ nas = {
+ enable = true;
+ office.enable = true;
+ office.automount = true;
+ };
+ };
+
+ # Disable suspend
+ systemd._suspend.disable = true;
+
+ # Use amdgpu driver for x11
+ services.xserver.videoDrivers = [ "amdgpu" ];
+
+ # Configure home
+ home-manager.users.timmy = {
+ gtk._mintTheme = {
+ dark = true;
+ color = "Blue";
+ icons.color = "Blue";
+ };
+ programs._seasonalwallpaper.wallpapers.download = true;
+ fonts.fontconfig = {
+ subpixelRendering = "rgb";
+ hinting = "none";
+ };
+ gtk.gtk3.bookmarks = [
+ "file:///home/timmy/docs/src/sites/admin Admin"
+ "file:///media/chexx/chexx cHEXx"
+ ];
+ };
+
+ system.stateVersion = "24.11";
+}
diff --git a/modules/hosts/hp-envy-office/hardware-configuration.nix b/hosts/hp-envy-office/hardware-configuration.nix
index 22a8c24..22a8c24 100644
--- a/modules/hosts/hp-envy-office/hardware-configuration.nix
+++ b/hosts/hp-envy-office/hardware-configuration.nix
diff --git a/hosts/hp-envy-office/resources/secrets/wg.yaml b/hosts/hp-envy-office/resources/secrets/wg.yaml
new file mode 100644
index 0000000..f3b53aa
--- /dev/null
+++ b/hosts/hp-envy-office/resources/secrets/wg.yaml
@@ -0,0 +1,16 @@
+wg1: ENC[AES256_GCM,data:XWdnE2QvfvFlMKUW6BoUSsEXDmYj4aNfbxvA6pFeIZM7NEtIwC4/NsplPwFIZwF372/bwDGXGocuh5gd1p/eAlsyz2DrAS+8g1+4T40EPPmXPgh++vUTvcpPlt74Qxp2yAeEU4CU7UPLvlxSvNjh5PGS68Cw7KxSB7kiWFxRWtm5oVfb+U6cBaQE6Biie7wPmXNWOobGHTfFYDeNmH6w33nH4lCV2MC0eYty9ytwHeVS7gUNrk4oxIfd+1FmNzwNHtVZvRg4wRzcc2M9fD0LuyuY6QVS/qaJG4hNNEHZ6qa0VMTnOzQ4jFHtd5jnz2vb7ckE7UWcFPjXYObcykk0End7sHVN/bD+fUv56JKZOHvVYFgs6OwCzUPAufnv10+h,iv:LMEpZW3mwGuIpJoacBYL8M0ROVNeVMzeb7ncZtfxIDA=,tag:aNCziN9CVgm0IB8VvVorEA==,type:str]
+sops:
+ age:
+ - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhdUJRS21FeFlseWJXU1dt
+ MnhQRnFvZWM0M1o4bUhBWW9KRDdnZ1pGZkYwCnhPYmFHZmdnRS9lb2xsTXZBcmIx
+ dHF5dmlrbjJyUk84QVBLTEFwMWdESGMKLS0tIHFyZGpSeTFoNEQyZThFc2RyQkhY
+ Q1ZvODVWSXE1STlkZ09tVXdVeU1WaVkKhKMfJclNgHXN7pww2w3AaKwcWiBo676g
+ RWSkV6C+5purA0CzTu1uC3CKz8UK8mVgPfamSZdZQU8+6bGMmseWoQ==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-31T19:32:51Z"
+ mac: ENC[AES256_GCM,data:zpEYjHqta6HuRHIKijbLmAND5mCaR14ZUdEeXq/zJ8g4DgWrAkaukhYdXhLH+SEUZt8d3tmj5Eq+6oz9qEjdWhBuPykxVBmBiqIhQBgACCMhSL5v3wY1rxL2ZiQ7szEuwh0GjXpkzPno0Z2+xZ6FzVsJdGnZwykru+JWQcUIfvk=,iv:yUiP/clvI/NnDrji9eMYiTqtO1xsTc7u86V/nlQSMIA=,tag:UyMz/BdYoGxXCJIb8tITcQ==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/hosts/hp-envy-office/wg.nix b/hosts/hp-envy-office/wg.nix
new file mode 100644
index 0000000..763496e
--- /dev/null
+++ b/hosts/hp-envy-office/wg.nix
@@ -0,0 +1,7 @@
+{ config, pkgs, inputs, ... }: {
+ sops.secrets.wg1.sopsFile = ./resources/secrets/wg.yaml;
+
+ networking.wg-quick.interfaces = {
+ wg1.configFile = config.sops.secrets.wg1.path;
+ };
+}
diff --git a/hosts/libreX60/bios-flashing.nix b/hosts/libreX60/bios-flashing.nix
new file mode 100644
index 0000000..6117813
--- /dev/null
+++ b/hosts/libreX60/bios-flashing.nix
@@ -0,0 +1,8 @@
+# https://libreboot.org/docs/install/#thinkpad-t60x60x60tabletx60s
+
+{ pkgs, ... }: {
+ environment.systemPackages = [ pkgs.flashprog ];
+
+ boot.kernelParams = [ "iomem=relaxed" ];
+}
+
diff --git a/hosts/libreX60/configuration.nix b/hosts/libreX60/configuration.nix
new file mode 100644
index 0000000..bf4f388
--- /dev/null
+++ b/hosts/libreX60/configuration.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }: {
+ imports = [
+ ./powertop-auto-tune.nix
+ # Uncomment this module and reboot to enable bios flashing
+ #./bios-flashing.nix
+ ];
+
+ # Use grub
+ boot._loader = {
+ enable = true;
+ loader = "grub";
+ mode = "bios";
+ grub.biosDevice = "/dev/sda";
+ };
+
+ # NOTE libre kernel removed, check back later
+ # Use libre kernel
+ #boot.kernelPackages = pkgs.linuxPackages-libre;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ autologin.enable = true;
+ wifi.enable = true;
+ };
+
+ # i915 Gpu requires intel driver
+ services.xserver.videoDrivers = [ "intel" ];
+
+ # Configure home
+ home-manager.users.timmy = {
+ imports = [ ./home.nix ];
+ };
+
+ system.stateVersion = "24.11";
+}
diff --git a/hosts/libreX60/hardware-configuration.nix b/hosts/libreX60/hardware-configuration.nix
new file mode 100644
index 0000000..b0a7868
--- /dev/null
+++ b/hosts/libreX60/hardware-configuration.nix
@@ -0,0 +1,46 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "firewire_ohci" "usb_storage" "sd_mod" "sdhci_pci" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/91572803-436d-4f43-b41f-dfba2103752e";
+ fsType = "btrfs";
+ options = [ "subvol=@" ];
+ };
+
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/91572803-436d-4f43-b41f-dfba2103752e";
+ fsType = "btrfs";
+ options = [ "subvol=@home" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/F618-D6C2";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
+ # (the default) this is the recommended approach. When using systemd-networkd it's
+ # still possible to use this option, but it's recommended to use it in conjunction
+ # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
+ networking.useDHCP = lib.mkDefault true;
+ networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
+ networking.interfaces.wlp2s0.useDHCP = lib.mkDefault true;
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/libreX60/home-timmy/configuration.nix b/hosts/libreX60/home-timmy/configuration.nix
new file mode 100644
index 0000000..a5f064c
--- /dev/null
+++ b/hosts/libreX60/home-timmy/configuration.nix
@@ -0,0 +1,8 @@
+{
+ imports = [ ../home.nix ];
+
+ targets.genericLinux.enable = true;
+
+ _archetypes.profiles.desktop.enable = true;
+ _users.timmy.home.enable = true;
+}
diff --git a/hosts/libreX60/home.nix b/hosts/libreX60/home.nix
new file mode 100644
index 0000000..b537fc2
--- /dev/null
+++ b/hosts/libreX60/home.nix
@@ -0,0 +1,22 @@
+{
+ gtk._mintTheme = {
+ dark = true;
+ color = "Red";
+ icons.color = "Red";
+ };
+ fonts.fontconfig.defaultFonts.monospace = [ "TamzenForPowerline" ];
+ gtk = {
+ font.name = "monospace";
+ font.size = 8;
+ cursorTheme.size = 24;
+ };
+ programs._st = {
+ enable = true;
+ font = {
+ name = "TamzenForPowerline";
+ attrs = {
+ pixelsize = 14;
+ };
+ };
+ };
+}
diff --git a/hosts/libreX60/powertop-auto-tune.nix b/hosts/libreX60/powertop-auto-tune.nix
new file mode 100644
index 0000000..0eb9578
--- /dev/null
+++ b/hosts/libreX60/powertop-auto-tune.nix
@@ -0,0 +1,17 @@
+# https://en.wikibooks.org/wiki/Libreboot/ThinkPad_X60#Remove_High_Pitched_Whining_Noise
+# TLDR; running `powertop --auto-tune` is supposed to kill the high pitched noises produced by the X60
+
+{ pkgs, ... }: {
+ environment.systemPackages = [ pkgs.powertop ];
+
+ # Create systemd service
+ systemd.services.powertop-autotune = {
+ description = "Powertop Auto-Tune";
+ wantedBy = [ "multi-user.target" ];
+ after = [ "network.target" ];
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.powertop}/bin/powertop --auto-tune";
+ };
+ };
+}
diff --git a/hosts/optiplex/configuration.nix b/hosts/optiplex/configuration.nix
new file mode 100644
index 0000000..5108e72
--- /dev/null
+++ b/hosts/optiplex/configuration.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, home-manager, ... }: {
+ # Setup bootloader
+ boot._loader.enable = true;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ btrfs.enable = true;
+ };
+ collections = {
+ desktop = {
+ extraUtilities.enable = true;
+ cad.enable = true;
+ chromium.enable = true;
+ crypto.enable = true;
+ graphics.enable = true;
+ office.enable = true;
+ video.enable = true;
+ };
+ development = {
+ android.enable = true;
+ c.enable = true;
+ docker.enable = true;
+ lua.enable = true;
+ web = {
+ hugo = {
+ enable = true;
+ openFirewall = true;
+ };
+ node.enable = true;
+ };
+ };
+ virtualization.enable = true;
+ };
+ };
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ autologin.enable = true;
+ nas = {
+ enable = true;
+ home.enable = true;
+ home.automount = true;
+ };
+ };
+
+ # Disable suspend
+ systemd._suspend.disable = true;
+
+ # Allow unfree for nvidia + others
+ nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
+ "blackmagic-desktop-video"
+ "decklink"
+ "nvidia-kernel-modules"
+ "nvidia-settings"
+ "nvidia-x11"
+ "spotify"
+ "steam"
+ "steam-original"
+ "steam-run"
+ "steam-unwrapped"
+ ];
+
+ # Install more software
+ environment.systemPackages = with pkgs; [
+ prismlauncher
+ spotify
+ #vintagestory
+ ];
+
+ programs.steam.enable = true;
+
+ # Use nvidia driver
+ services.xserver.videoDrivers = [ "nvidia" ];
+ hardware.nvidia = {
+ modesetting.enable = true; # Required
+ powerManagement.enable = false; # Can cause bugs
+ powerManagement.finegrained = false; # Only works on turing and newer
+ nvidiaSettings = true;
+ open = false; # Not compatible w/ GTX-1050
+ package = config.boot.kernelPackages.nvidiaPackages.legacy_580;
+ forceFullCompositionPipeline = true; # Enables vsync
+ };
+
+ # Enable decklink support for intensity pro
+ services._decklink.enable = true;
+ services._decklink.decklinkSupport.enable = true;
+
+ # Configure home
+ home-manager.users.timmy = {
+ gtk._mintTheme = {
+ dark = true;
+ color = "Green";
+ icons.color = "Green";
+ };
+ programs._seasonalwallpaper.wallpapers.download = true;
+ fonts.fontconfig = {
+ subpixelRendering = "rgb";
+ hinting = "none";
+ };
+ };
+
+ system.stateVersion = "25.11";
+}
diff --git a/hosts/optiplex/g610.nix b/hosts/optiplex/g610.nix
new file mode 100644
index 0000000..8cceafc
--- /dev/null
+++ b/hosts/optiplex/g610.nix
@@ -0,0 +1,9 @@
+{ pkgs, ... }: {
+ # Turn off backlight on my G610 keyboard
+ systemd.services.G610DisableBacklight = {
+ description = "G610 disable backlight";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig.ExecStart = "${pkgs.g810-led}/bin/g610-led -a 00";
+ };
+}
diff --git a/hosts/optiplex/hardware-configuration.nix b/hosts/optiplex/hardware-configuration.nix
new file mode 100644
index 0000000..9485a66
--- /dev/null
+++ b/hosts/optiplex/hardware-configuration.nix
@@ -0,0 +1,44 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "nvme" "firewire_ohci" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/e4094dd8-d1fd-4aa1-8f95-82a9144a32be";
+ fsType = "btrfs";
+ options = [ "subvol=root" ];
+ };
+
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/e4094dd8-d1fd-4aa1-8f95-82a9144a32be";
+ fsType = "btrfs";
+ options = [ "subvol=home" ];
+ };
+
+ fileSystems."/nix" =
+ { device = "/dev/disk/by-uuid/e4094dd8-d1fd-4aa1-8f95-82a9144a32be";
+ fsType = "btrfs";
+ options = [ "subvol=nix" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/9350-1411";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/piframe/configuration.nix b/hosts/piframe/configuration.nix
new file mode 100644
index 0000000..b45a293
--- /dev/null
+++ b/hosts/piframe/configuration.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }: {
+ _archetypes = {
+ # Use headless and pi profiles
+ profiles.headless = {
+ enable = true;
+ home.users.timmy.enable = true;
+ home.users.piframe.enable = true;
+ };
+ profiles.pi = {
+ enable = true;
+ home.users.timmy.enable = true;
+ home.users.piframe.enable = true;
+ };
+ collections = {
+ desktop.xserver.enable = true;
+ };
+ };
+
+ # Install twm as basic window manager to boot into and immediately go fullscreen on immich-frame
+ services.xserver.windowManager.twm.enable = true;
+
+ # Install immich-frame
+ environment.systemPackages = with pkgs; [
+ immich-frame
+ ];
+
+ # Open 8080 for immich-frame
+ networking.firewall.allowedTCPPorts = [ 8080 ];
+
+ # Enable user timmy for ssh maintenance and wifi secrets
+ _users.timmy = {
+ enable = true;
+ wifi.enable = true;
+ };
+
+ # Enable piframe user
+ users.users.piframe = {
+ isNormalUser = true;
+ };
+
+ # Configure automatic login with getty
+ services.getty.autologinUser = "piframe";
+
+ # Configure piframe home
+ home-manager.users.piframe = {
+ services = {
+ _gammastep.enable = true;
+ # Manual location to avoid having to mess with geoclue
+ gammastep = {
+ provider = "manual";
+ latitude = 41.881832;
+ longitude = -87.623177;
+ };
+ };
+ programs.bash = {
+ enable = true;
+ profileExtra = ''
+ # startx on tty1 immediately
+ [ -z $DISPLAY ] && [ `tty` = /dev/tty1 ] && startx
+ '';
+ };
+ home.file.".xinitrc".text = ''
+ #!/bin/sh
+ gammastep &
+ unclutter --start-hidden &
+ immich-frame --fullscreen &
+ exec twm
+ '';
+ home.file.".twmrc".text = ''
+ *NoTitle # Hide title bar on all windows
+ *NoBorder # Hide borders on all windows
+ '';
+ home.stateVersion = "25.11";
+ };
+
+ system.stateVersion = "25.11";
+}
diff --git a/hosts/piframe/hardware-configuration.nix b/hosts/piframe/hardware-configuration.nix
new file mode 100644
index 0000000..ceb755a
--- /dev/null
+++ b/hosts/piframe/hardware-configuration.nix
@@ -0,0 +1,24 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "usbhid" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/44444444-4444-4444-8888-888888888888";
+ fsType = "ext4";
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
+}
diff --git a/hosts/poweredge/bitcoind.nix b/hosts/poweredge/bitcoind.nix
new file mode 100644
index 0000000..e282234
--- /dev/null
+++ b/hosts/poweredge/bitcoind.nix
@@ -0,0 +1,41 @@
+{
+ containers.bitcoind = {
+ autoStart = true;
+ ephemeral = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:04";
+
+ # Host path
+ bindMounts = {
+ "/var/lib/bitcoind-main" = {
+ hostPath = "/media/ingens/bitcoin";
+ isReadOnly = false;
+ };
+ };
+
+ config = { lib, pkgs, config, ... }: let
+ p2pPort = 8333;
+ rpcPort = 8332;
+ in {
+ # Network
+ networking.enableIPv6 = false;
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ p2pPort rpcPort ];
+
+ # Bitcoin
+ services.bitcoind."main" = {
+ enable = true;
+ port = p2pPort;
+ rpc.port = rpcPort;
+ rpc.users.timmy.passwordHMAC = "7557196eec6e3e062a57b0720abff7cf$617211e94b964f548cfbb5c23590f690c2940e424fffe72485276493f1cb0b29";
+ extraConfig = ''
+ rpcallowip=192.168.1.0/24
+ rpcbind=0.0.0.0
+ '';
+ };
+
+ system.stateVersion = "25.11";
+ };
+ };
+}
diff --git a/hosts/poweredge/configuration.nix b/hosts/poweredge/configuration.nix
new file mode 100644
index 0000000..c15f222
--- /dev/null
+++ b/hosts/poweredge/configuration.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }: {
+ imports = [
+ ./bitcoind.nix
+ ./ddns-updater.nix
+ ./filebrowser.nix
+ ./fileshares.nix
+ ./gitea.nix
+ ./immich.nix
+ ./jellyfin.nix
+ ./lgtm.nix
+ ./networking.nix
+ ./nvidia.nix
+ ./transmission.nix
+ #./notification-mailer.nix # TODO move some of this stuff to archetype
+ ];
+
+ # Allow unfree packages for nvidia/cuda
+ nixpkgs.config.allowUnfree = true;
+
+ # Setup bootloader
+ boot._loader.enable = true;
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ headless = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ zfs.enable = true;
+ cuda.enable = true;
+ };
+ };
+
+ # Import zfs pools
+ boot.zfs.extraPools = [ "ingens" "memoria" ];
+
+ # Enable user timmy
+ _users.timmy.enable = true;
+
+ # Without this, "ZFS requires networking.hostId to be set" will be raised
+ networking.hostId = "4d9e002f";
+
+ system.stateVersion = "25.11";
+}
diff --git a/hosts/poweredge/ddns-updater.nix b/hosts/poweredge/ddns-updater.nix
new file mode 100644
index 0000000..319e3c4
--- /dev/null
+++ b/hosts/poweredge/ddns-updater.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }: let
+ credential = "config";
+in {
+ # Config for ddns-updater, owned by the ddns-updater systemd service user
+ sops.secrets.ddns-updater-config.sopsFile = ./resources/secrets/ddns-updater-config.yaml;
+
+ # Load secret as a credential in systemd service
+ systemd.services.ddns-updater.serviceConfig = {
+ LoadCredential = [
+ "${credential}:${config.sops.secrets.ddns-updater-config.path}"
+ ];
+ };
+
+ # Enable ddns updater
+ services.ddns-updater = {
+ enable = true;
+ environment = {
+ SERVER_ENABLED="no";
+ CONFIG_FILEPATH = "%d/${credential}";
+ };
+ };
+}
diff --git a/hosts/poweredge/filebrowser.nix b/hosts/poweredge/filebrowser.nix
new file mode 100644
index 0000000..818b3df
--- /dev/null
+++ b/hosts/poweredge/filebrowser.nix
@@ -0,0 +1,48 @@
+{
+ containers.filebrowser = {
+ autoStart = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:05";
+
+ # Host path
+ bindMounts = {
+ "/var/lib/filebrowser/data" = {
+ hostPath = "/media";
+ isReadOnly = true;
+ };
+ };
+
+ config = { lib, pkg, config, ... }: let
+ publicPort = 9000;
+ in {
+ # Network
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ 80 publicPort ]; # Caddy (private + public)
+
+ # Filebrowser
+ services.filebrowser.enable = true;
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ # Private
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:8080
+ '';
+ # Public (not sure why toString is needed but ok)
+ virtualHosts.":${toString publicPort}".extraConfig = ''
+ @shared {
+ path /share/*
+ path /static/*
+ path /api/public/*
+ }
+
+ reverse_proxy @shared localhost:8080
+ '';
+ };
+
+ system.stateVersion = "26.05";
+ };
+ };
+}
diff --git a/hosts/poweredge/fileshares.nix b/hosts/poweredge/fileshares.nix
new file mode 100644
index 0000000..c5fca71
--- /dev/null
+++ b/hosts/poweredge/fileshares.nix
@@ -0,0 +1,47 @@
+{
+ # Configure services
+ services._fileShares = {
+ enable = true;
+ smb.openFirewall = true;
+ nfs.openFirewall = true;
+ };
+ # Shares
+ services._fileShares.shares = {
+ PS2 = {
+ path = "/media/ingens/games/ps2";
+ smb = {
+ enable = true;
+ allowUser = "ps2";
+ extraOptions = {
+ "min protocol" = "NT1";
+ "max protocol" = "NT1";
+ };
+ };
+ };
+ WinBackups = {
+ path = "/media/ingens/backups/windows";
+ smb.enable = true;
+ };
+ pictures = {
+ path = "/media/ingens/pictures";
+ nfs.enable = true;
+ };
+ tapes = {
+ path = "/media/ingens/tapes";
+ nfs.enable = true;
+ smb.enable = true;
+ };
+ backups = {
+ path = "/media/ingens/backups";
+ nfs.enable = true;
+ };
+ };
+
+ # ps2 user
+ users.users.ps2 = {
+ isSystemUser = true;
+ password = "ps2";
+ group = "ps2";
+ };
+ users.groups.ps2 = {};
+}
diff --git a/hosts/poweredge/gitea.nix b/hosts/poweredge/gitea.nix
new file mode 100644
index 0000000..4c800df
--- /dev/null
+++ b/hosts/poweredge/gitea.nix
@@ -0,0 +1,41 @@
+{
+ containers.gitea = {
+ autoStart = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:03";
+
+ # TODO make a mountpoint for git repositories
+
+ config = { lib, pkgs, config, ... }: {
+ # Network
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ 80 22 ]; # Caddy + ssh
+
+ # Gitea
+ services.gitea = {
+ enable = true;
+ #user = "git"; # So ssh cloning uses git@gitea
+ settings = {
+ repository = {
+ ENABLE_PUSH_CREATE_USER = true;
+ ENABLE_PUSH_CREATE_ORG = true;
+ };
+ };
+ };
+
+ # SSH for repositories
+ services.openssh.enable = true;
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:3000
+ '';
+ };
+
+ system.stateVersion = "25.11";
+ };
+ };
+}
diff --git a/hosts/poweredge/hardware-configuration.nix b/hosts/poweredge/hardware-configuration.nix
new file mode 100644
index 0000000..0fcc098
--- /dev/null
+++ b/hosts/poweredge/hardware-configuration.nix
@@ -0,0 +1,50 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "megaraid_sas" "usbhid" "usb_storage" "sd_mod" "sr_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "zpool/root";
+ fsType = "zfs";
+ options = [ "zfsutil" ];
+ };
+
+ fileSystems."/nix" =
+ { device = "zpool/nix";
+ fsType = "zfs";
+ options = [ "zfsutil" ];
+ };
+
+ fileSystems."/var" =
+ { device = "zpool/var";
+ fsType = "zfs";
+ options = [ "zfsutil" ];
+ };
+
+ fileSystems."/home" =
+ { device = "zpool/home";
+ fsType = "zfs";
+ options = [ "zfsutil" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/D083-98C0";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/poweredge/immich.nix b/hosts/poweredge/immich.nix
new file mode 100644
index 0000000..444a0f2
--- /dev/null
+++ b/hosts/poweredge/immich.nix
@@ -0,0 +1,78 @@
+{ pkgs, ... }: let
+ onnxruntimeCuda = pkgs.onnxruntime.override { cudaSupport = true; };
+in {
+ containers.immich = {
+ autoStart = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:01";
+
+ # Host path
+ bindMounts = {
+ "/var/lib/immich" = {
+ hostPath = "/media/ingens/immich";
+ isReadOnly = false;
+ };
+ };
+
+ # GPU
+ allowedDevices = [
+ { node = "/dev/nvidia0"; modifier = "rw"; }
+ { node = "/dev/nvidiactl"; modifier = "rw"; }
+ { node = "/dev/nvidia-uvm"; modifier = "rw"; }
+ { node = "/dev/nvidia-uvm-tools"; modifier = "rw"; }
+ { node = "/dev/nvidia-modeset"; modifier = "rw"; }
+ ];
+
+ bindMounts = {
+ # NVENC/NVDEC - video transcoding
+ "/dev/nvidia0" = { hostPath = "/dev/nvidia0"; isReadOnly = false; };
+ "/dev/nvidiactl" = { hostPath = "/dev/nvidiactl"; isReadOnly = false; };
+ # CUDA - required for ML inference
+ "/dev/nvidia-uvm" = { hostPath = "/dev/nvidia-uvm"; isReadOnly = false; };
+ "/dev/nvidia-uvm-tools" = { hostPath = "/dev/nvidia-uvm-tools"; isReadOnly = false; };
+ # Modeset - needed by some driver paths
+ "/dev/nvidia-modeset" = { hostPath = "/dev/nvidia-modeset"; isReadOnly = false; };
+ };
+
+ config = { lib, pkgs, config, ... }: {
+ imports = [ ./nvidia.nix ];
+
+ # Network
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ 80 ]; # Caddy
+
+ # Immich
+ services.immich = {
+ enable = true;
+ accelerationDevices = [
+ "/dev/nvidia0"
+ "/dev/nvidiactl"
+ "/dev/nvidia-uvm"
+ "/dev/nvidia-uvm-tools"
+ "/dev/nvidia-modeset"
+ ];
+
+ # Tell immich-server to use NVENC for transcoding
+ environment = {
+ NVIDIA_VISIBLE_DEVICES = "all";
+ NVIDIA_DRIVER_CAPABILITIES = "compute,video,utility";
+ };
+
+ # Enable the ML microservice with CUDA
+ machine-learning.enable = true;
+ };
+ environment.systemPackages = with pkgs; [ immich immich-cli ];
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:2283
+ '';
+ };
+
+ system.stateVersion = "25.11";
+ };
+ };
+}
diff --git a/hosts/poweredge/jellyfin.nix b/hosts/poweredge/jellyfin.nix
new file mode 100644
index 0000000..761c3ae
--- /dev/null
+++ b/hosts/poweredge/jellyfin.nix
@@ -0,0 +1,91 @@
+{
+ containers.jellyfin = {
+ autoStart = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:02";
+ bindMounts = {
+ "/media" = {
+ hostPath = "/media/ingens/media";
+ isReadOnly = true;
+ };
+ };
+
+ # GPU
+ allowedDevices = [
+ { node = "/dev/nvidia0"; modifier = "rw"; }
+ { node = "/dev/nvidiactl"; modifier = "rw"; }
+ { node = "/dev/nvidia-uvm"; modifier = "rw"; }
+ { node = "/dev/nvidia-uvm-tools"; modifier = "rw"; }
+ { node = "/dev/nvidia-modeset"; modifier = "rw"; }
+ ];
+
+ bindMounts = {
+ # NVENC/NVDEC - video transcoding
+ "/dev/nvidia0" = { hostPath = "/dev/nvidia0"; isReadOnly = false; };
+ "/dev/nvidiactl" = { hostPath = "/dev/nvidiactl"; isReadOnly = false; };
+ # CUDA - required for ML inference
+ "/dev/nvidia-uvm" = { hostPath = "/dev/nvidia-uvm"; isReadOnly = false; };
+ "/dev/nvidia-uvm-tools" = { hostPath = "/dev/nvidia-uvm-tools"; isReadOnly = false; };
+ # Modeset - needed by some driver paths
+ "/dev/nvidia-modeset" = { hostPath = "/dev/nvidia-modeset"; isReadOnly = false; };
+ };
+
+ config = { lib, config, pkgs, ... }: {
+ imports = [ ./nvidia.nix ];
+
+ # Network
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ 80 ]; # Caddy
+
+ # Jellyfin
+ services.jellyfin = {
+ enable = true;
+ openFirewall = true;
+
+ hardwareAcceleration = {
+ enable = true;
+ type = "nvenc";
+ device = "/dev/nvidia0"; # Required by the module's assertion, unused in encoding.xml for nvenc
+ };
+
+ forceEncodingConfig = true; # Override encoding.xml
+
+ transcoding = {
+ enableHardwareEncoding = true;
+
+ hardwareEncodingCodecs = {
+ hevc = true; # Pascal 4th-gen NVENC supports HEVC 8/10-bit encode
+ av1 = false; # NOT supported on Pascal — do not enable
+ };
+
+ hardwareDecodingCodecs = {
+ h264 = true;
+ hevc = true;
+ hevc10bit = true;
+ vp8 = true;
+ vp9 = true;
+ mpeg2 = true;
+ vc1 = true;
+ av1 = false; # NOT supported on Pascal NVDEC
+ hevcRExt10bit = false; # Range Extensions decode is a newer-gen feature, skip on Pascal
+ hevcRExt12bit = false;
+ };
+
+ encodingPreset = "medium";
+ enableToneMapping = true; # works via CUDA-based tonemap_cuda filter on Pascal+
+ };
+ };
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:8096
+ '';
+ };
+
+ system.stateVersion = "26.05";
+ };
+ };
+}
diff --git a/hosts/poweredge/lgtm.nix b/hosts/poweredge/lgtm.nix
new file mode 100644
index 0000000..6f43c02
--- /dev/null
+++ b/hosts/poweredge/lgtm.nix
@@ -0,0 +1,201 @@
+{ pkgs, config, ... }: {
+ # Log shipping via Alloy
+ services.alloy.enable = true;
+
+ environment.etc."alloy/config.alloy".text = ''
+ loki.source.journal "journal" {
+ forward_to = [loki.write.default.receiver]
+ relabel_rules = loki.relabel.journal.rules
+ max_age = "12h"
+ }
+
+ loki.relabel "journal" {
+ forward_to = []
+ rule {
+ source_labels = ["__journal__systemd_unit"]
+ target_label = "unit"
+ }
+ }
+
+ loki.write "default" {
+ endpoint {
+ url = "http://lgtm:3100/loki/api/v1/push"
+ }
+ }
+ '';
+
+ # Export prometheus data from host for container to access
+ services.prometheus.exporters.smartctl = {
+ enable = true;
+ port = 9633;
+ listenAddress = "0.0.0.0";
+ };
+
+ services.prometheus.exporters.node = {
+ enable = true;
+ port = 9100;
+ listenAddress = "0.0.0.0";
+ enabledCollectors = [ "systemd" "diskstats" "filesystem" "meminfo" "cpu" ];
+ # zfs + hwmon already on by default, no need to list them
+ };
+
+ # # Host config — zpool_influxdb needs real pool access
+ # services.telegraf = {
+ # enable = true;
+ # extraConfig = {
+ # agent.interval = "30s";
+ #
+ # inputs.execd = [{
+ # command = [ "${pkgs.zpool_influxdb}/bin/zpool_influxdb" "--execd" ];
+ # signal = "none";
+ # data_format = "influx";
+ # }];
+ #
+ # outputs.prometheus_client = [{
+ # listen = ":9273";
+ # metric_version = 2;
+ # }];
+ # };
+ # };
+
+ # Allow ports for prometheus
+ networking.firewall.allowedTCPPorts = [ 9100 9273 9633 ]; # at least reachable from container subnet
+
+ # Secret key for grafana
+ sops.secrets.grafana-secret-key = { sopsFile = ./resources/secrets/grafana.yaml; key = "secret_key"; }; # Office
+
+ # Container
+ containers.grafana = {
+ autoStart = true;
+ privateNetwork = true;
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:06";
+
+ # Bind wg0-router secret to container
+ bindMounts."/run/secrets/grafana-secret_key" = {
+ hostPath = config.sops.secrets.grafana-secret-key.path;
+ isReadOnly = true;
+ };
+
+ config = { lib, pkgs, config, ... }: {
+ # Network
+ networking.enableIPv6 = false;
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.allowedTCPPorts = [ 80 3100 9090 ]; # Caddy (grafana) + loki + prometheus
+
+ # Loki
+ services.loki = {
+ enable = true;
+ configuration = {
+ server = {
+ http_listen_port = 3100;
+ http_listen_address = "0.0.0.0"; # explicit, since the host's Alloy needs to reach it to push logs
+ };
+ auth_enabled = false;
+
+ common = {
+ path_prefix = "/var/lib/loki";
+ storage.filesystem = {
+ chunks_directory = "/var/lib/loki/chunks";
+ rules_directory = "/var/lib/loki/rules";
+ };
+ ring = {
+ instance_addr = "127.0.0.1";
+ kvstore.store = "inmemory";
+ };
+ replication_factor = 1;
+ };
+
+ schema_config.configs = [{
+ from = "2024-01-01";
+ store = "tsdb";
+ object_store = "filesystem";
+ schema = "v13";
+ index = { prefix = "index_"; period = "24h"; };
+ }];
+ };
+ };
+
+ # Prometheus
+ services.prometheus = {
+ enable = true;
+ scrapeConfigs = [
+ {
+ job_name = "smartctl";
+ static_configs = [{ targets = [ "poweredge:9633" ]; }];
+ }
+ {
+ job_name = "node";
+ static_configs = [{ targets = [ "poweredge:9100" ]; }];
+ }
+ {
+ job_name = "zpool";
+ static_configs = [{ targets = [ "poweredge:9273" ]; }];
+ }
+ ];
+ };
+
+ # Grafana
+ # https://wiki.nixos.org/wiki/Grafana
+ services.grafana = {
+ enable = true;
+ settings.server = { http_addr = "0.0.0.0"; http_port = 3000; };
+ provision = {
+ enable = true;
+
+ # Creates a *mutable* dashboard provider, pulling from /etc/grafana-dashboards.
+ # With this, you can manually provision dashboards from JSON with `environment.etc` like below.
+ dashboards.settings.providers = [{
+ name = "my dashboards";
+ disableDeletion = true;
+ options = {
+ path = "/etc/grafana-dashboards";
+ foldersFromFilesStructure = true;
+ };
+ }];
+
+ datasources.settings.datasources = [
+ { name = "Prometheus"; type = "prometheus"; url = "http://127.0.0.1:9090"; isDefault = true; }
+ { name = "Loki"; type = "loki"; url = "http://127.0.0.1:3100"; }
+ ];
+ };
+ };
+
+ # Grafana secret key
+ systemd.services.grafana.serviceConfig = {
+ LoadCredential = [
+ "secret_key:/run/secrets/grafana-secret_key"
+ ];
+ };
+ services.grafana.settings.security.secret_key = "$__file{/run/credentials/grafana.service/secret_key}";
+
+ # Grafana dashboards
+ environment.etc = let
+ mkDashDl = id: rev: "https://grafana.com/api/dashboards/${toString id}/revisions/${toString rev}/download";
+ in {
+ "grafana-dashboards/node-exporter-full.json".source = pkgs.fetchurl {
+ url = mkDashDl 1860 45;
+ sha256 = "sha256-GExrdAnzBtp1Ul13cvcZRbEM6iOtFrXXjEaY6g6lGYY=";
+ };
+ "grafana-dashboards/smartctl-exporter.json".source = pkgs.fetchurl {
+ url = mkDashDl 22604 2;
+ sha256 = "sha256-ci8WE23fZ+ltEKFoUdNNVXsUIV0jqtas79ia2lYIo88=";
+ };
+ "grafana-dashboards/zfs-pool-metrics.json".source = pkgs.fetchurl {
+ url = mkDashDl 15362 1;
+ sha256 = "sha256-latX1gwRjdpIalT5kpm9MfDaaXFLZCJ0ZP8CZJo8guI=";
+ };
+ };
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:3000
+ '';
+ };
+
+ system.stateVersion = "26.05";
+ };
+ };
+}
diff --git a/hosts/poweredge/networking.nix b/hosts/poweredge/networking.nix
new file mode 100644
index 0000000..bf0aa1c
--- /dev/null
+++ b/hosts/poweredge/networking.nix
@@ -0,0 +1,157 @@
+let
+ hostIp = "192.168.1.10";
+in { config, ... }: {
+ networking = {
+ # Label lan and wan interfaces
+ _interfaceLabels = {
+ enable = true;
+ interfaces.lan0 = "50:9a:4c:5d:c3:7a";
+ interfaces.wan0 = "50:9a:4c:5d:c3:7b";
+ };
+ # Create bridged lan interface for all containers
+ bridges.br-lan0.interfaces = [ "lan0" ];
+ # Disable dhcp on router interfaces
+ interfaces = {
+ veth-router-lan.useDHCP = false;
+ vb-router-lan0.useDHCP = false;
+ };
+ # Configure network
+ defaultGateway = "10.255.255.1"; # Read explaination for veth-router-lan below
+ nameservers = [ "192.168.1.1" ]; # DNS will only be available from this ip address THROUGH the default gateway
+ # br-lan0 will be the interface used for networking on poweredge host
+ interfaces.br-lan0.ipv4.addresses = [{
+ address = hostIp;
+ prefixLength = 24;
+ }];
+ };
+
+ # Secrets
+ sops.secrets.router-wg0 = { sopsFile = ./resources/secrets/router.yaml; key = "wg0"; }; # Office
+ sops.secrets.router-wg1 = { sopsFile = ./resources/secrets/router.yaml; key = "wg1"; }; # Remote access
+
+ # Router container
+ containers.router = {
+ autoStart = true;
+ ephemeral = true;
+ privateNetwork = true;
+ # Pass wan0 directly into container since it isn't needed elsewhere
+ interfaces = [ "wan0" ];
+ # Setup router lan0
+ # NOTE: Host/container communication is not possible through a hostBridge interface
+ extraVeths.vb-router-lan0.hostBridge = "br-lan0";
+ # Setup virtual host-router bridge interface.
+ # This is the default gateway for host/container communication since
+ # communication isn't possible through hostBridge interfaces.
+ # This is essentially equivalent to connecting the host to the
+ # container with a virtual ethernet cable on a separate interface.
+ extraVeths.veth-router-lan = {
+ hostAddress = "10.255.255.2";
+ localAddress = "10.255.255.1";
+ };
+ # Bind secrets to container
+ bindMounts."/run/secrets/wg0" = {
+ hostPath = config.sops.secrets.router-wg0.path;
+ isReadOnly = true;
+ };
+ bindMounts."/run/secrets/wg1" = {
+ hostPath = config.sops.secrets.router-wg1.path;
+ isReadOnly = true;
+ };
+
+ config = { lib, config, ... }: {
+ imports = [
+ ../../nixos/services/router
+ ./router-hosts.nix # Contains dhcp config + static leases + overrides
+ ];
+
+ networking = {
+ # Set ip addresses
+ enableIPv6 = false;
+ interfaces = {
+ vb-router-lan0.ipv4.addresses = [{
+ address = "192.168.1.1";
+ prefixLength = 24;
+ }];
+ wan0.useDHCP = true;
+ };
+ # Setup wireguard
+ wg-quick.interfaces = {
+ wg0.configFile = "/run/secrets/wg0";
+ wg1.configFile = "/run/secrets/wg1";
+ };
+ # NAT (port-forwarding) rules
+ nat.forwardPorts =[
+ { # Bitcoin p2p
+ sourcePort = 8333;
+ proto = "tcp";
+ destination = "192.168.1.44:8333";
+ }
+ { # Filebrowser
+ sourcePort = 19045;
+ proto = "tcp";
+ destination = "192.168.1.45:9000";
+ }
+ ];
+ firewall.allowedUDPPorts = [ 51820 ]; # Allow wg1 running on router host through w/o NAT
+ # Additional advanced rules
+ # TODO add multi NAT feature to router service (this is just a normal nat rule)
+ nftables = {
+ enable = true;
+ tables = {
+ # NAT/masquerade wg0 allowing vb-router-lan0 clients to access wg0
+ wg-nat = {
+ family = "ip";
+ content = ''
+ chain post {
+ type nat hook postrouting priority srcnat; policy accept;
+ iifname "vb-router-lan0" oifname "wg0" masquerade comment "vb-router-lan0 => wg0"
+ iifname "veth-router-lan" oifname "wg0" masquerade comment "veth-router-lan => wg0"
+ }
+ '';
+ };
+ };
+ };
+ };
+
+ # Setup router
+ services._router = {
+ dnsDhcpConfig.enable = true;
+ routing = {
+ enable = true;
+ interfaces = {
+ lan = [ "vb-router-lan0" "veth-router-lan" ];
+ wan = "wan0";
+ };
+ };
+ };
+
+ services.unbound._blocklists = {
+ enable = true;
+ hageziBlocklists = [ "pro" "nsfw" ];
+ };
+
+ system.stateVersion = "25.11";
+ };
+ };
+
+ # FIXME the following snippet will cause the router container to fail to start:
+ # networking.defaultGateway = {
+ # address = "10.255.255.1";
+ # interface = "veth-router-lan";
+ # };
+ # Journalctl will report:
+ # poweredge container router: Bring veth-router-lan up
+ # poweredge container router: RTNETLINK answers: File exists
+ # poweredge systemd: container@router.service: Control process exited, code=exited, status=2/INVALIDARGUMENT
+ # So the issue nixos is creating an interface with that same name.
+ # As a temporary workaround, the following service is deployed (after the container starts):
+ systemd.services.router-default-route = {
+ after = [ "container@router.service" ];
+ wants = [ "container@router.service" ];
+ serviceConfig.Type = "oneshot";
+ script = ''
+ /run/current-system/sw/bin/ip route replace default via 10.255.255.1 dev veth-router-lan
+ '';
+ wantedBy = [ "multi-user.target" ];
+ };
+}
diff --git a/hosts/poweredge/notification-mailer.nix b/hosts/poweredge/notification-mailer.nix
new file mode 100644
index 0000000..d8fddc7
--- /dev/null
+++ b/hosts/poweredge/notification-mailer.nix
@@ -0,0 +1,27 @@
+{ config, ... }: let
+ serverEmail = "server-notifications@tjkeller.xyz";
+in {
+ # Mailer password secret for mail application password
+ sops.secrets.mailerPassword.sopsFile = ./resources/secrets/mailer-pass.yaml;
+
+ # Enable mta for system event notifications
+ services.mail._mailer = {
+ sender = {
+ host = "mail.tjkeller.xyz";
+ user = serverEmail;
+ from = serverEmail;
+ passwordFile = config.sops.secrets.mailerPassword.path;
+ };
+ recipient = serverEmail;
+ };
+
+ # Enable zed mailer module
+ services.zfs._zedMailer.enable = true;
+
+ # Enable smartd notifications
+ services.smartd.notifications.mail = {
+ enable = true;
+ sender = serverEmail;
+ recipient = serverEmail;
+ };
+}
diff --git a/hosts/poweredge/nvidia.nix b/hosts/poweredge/nvidia.nix
new file mode 100644
index 0000000..257a46c
--- /dev/null
+++ b/hosts/poweredge/nvidia.nix
@@ -0,0 +1,22 @@
+{ config, pkgs, ... }: {
+ # Configure nixpkgs for nvidia/cuda
+ nixpkgs.config.allowUnfree = true;
+ nixpkgs.config.cudaSupport = true;
+
+ # NVIDIA
+ services.xserver.videoDrivers = [ "nvidia" ]; # xserver.videoDrivers does not imply X
+ hardware.graphics.enable = true;
+ hardware.nvidia = {
+ modesetting.enable = true; # Required
+ nvidiaSettings = false; # Don't need gui
+ open = false;
+ package = config.boot.kernelPackages.nvidiaPackages.legacy_580; # Support for P600
+ powerManagement.enable = false; # Can cause bugs
+ powerManagement.finegrained = false; # Only works on turing and newer
+ };
+
+ # Packages
+ environment.systemPackages = with pkgs; [
+ config.hardware.nvidia.package # nvidia-smi
+ ];
+}
diff --git a/hosts/poweredge/resources/secrets/ddns-updater-config.yaml b/hosts/poweredge/resources/secrets/ddns-updater-config.yaml
new file mode 100644
index 0000000..3be017b
--- /dev/null
+++ b/hosts/poweredge/resources/secrets/ddns-updater-config.yaml
@@ -0,0 +1,16 @@
+ddns-updater-config: ENC[AES256_GCM,data:vJ3z4R6P1gHKfkm6L2mQl68MKDJwpMNmrAOQo+4GkO2NC6EjKTLoSKhFiaGWVjMm7nrVfYRV+U/6b4VJXV4qURWhsm41t3x8zXAtt0viLC6pv+uMtuxadhU2Zxij4U2bSiMn6sSbfHd3uGIym7FnfOIL3LPEanVMuRUk20a0ZgHBdq1BPk6r5V8AoGfsu1XWHTvnO4ggg9oQPtGhurKTXixTD0Rb1Iv43JXLXqK/O3JGD5h4XbDmXB9eTqiBHUgZ0E4F5SE23L5mO0kI0TNNph2lTHXdfB+5,iv:xFry3gzdvvYh127yhYySvp5UHDa8Y+t/bg2+mwJ/HXo=,tag:pH2CE2l2UpNJiLJ+tjVvqQ==,type:str]
+sops:
+ age:
+ - recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtWitQcVlaTmFVaHIraHlT
+ VFBDVEtlQUlqckN4eFF3YU95N3ZNU3JQcFNzCnkrR2xmTEtyUHRWQlRnTWZSaGVT
+ U0wvcGt6R0w4L3dSakVDVWVpTUhWbWMKLS0tIGVKSXVTL1B2L2FlSkQwSDVYd3Fk
+ WE8rLy81UEU5ZG9SaHRLOHNqOWUzWnMKBFtzJ9frroYk6hoW+1ww/3LpxCEa1Vtr
+ KNNnHKry8lQQDmalN5ZVYMTVAlTnQQ6QE7DxBukUwWYmizQ+BY8HDg==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-31T01:47:37Z"
+ mac: ENC[AES256_GCM,data:8ozC5JWR/s3nNK+njc7zO32/7ptd//wuWGWZPHXrPV1iVyYndczGgu0ekEyKeRCn/WwGE5pyt32gy0l2Y+k7j7mV6GJguy6qhltani6Mz2Gfy5sRohn5s2rBDTiSYEVAgGTRt56DLxGD36P6xFPm+wHGspjCzNALrPretuN5xFg=,iv:+/mlXEMEO80pDVpFwZmnyywvHR/V9zHkbloF/e/dJ6Q=,tag:O+Ox0xUzERjeB+VftiUNEg==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/hosts/poweredge/resources/secrets/grafana.yaml b/hosts/poweredge/resources/secrets/grafana.yaml
new file mode 100644
index 0000000..91b9188
--- /dev/null
+++ b/hosts/poweredge/resources/secrets/grafana.yaml
@@ -0,0 +1,16 @@
+secret_key: ENC[AES256_GCM,data:9GHuENHGk+VKukRuUJi+fxOX+ZFa5q5t5UiGtxSQOEctu9LOJqvHJerFv3U=,iv:6OJTeWbotDVj/ogg9WC170Q4Kdfd1PXf0hHwXd/YaUQ=,tag:qlnEl2uzDuhwOKp17R76Tg==,type:str]
+sops:
+ age:
+ - enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrc0tWWHhtM2Q5UC9weSsr
+ WDdaOXJjZGVoekJKaXUyUXhsRXMxMjQrYUFnCnBqTGJEMFF6clQ1RXd5NE9CenQ5
+ U0swNHdNUUxndnlycGVGM0lpY0dFaVEKLS0tIHVlK2MySUJzaGVXd21iWFFzcEJM
+ Nmh3V2hIbUhWNUxlOE1idTdWNWZ6alEKWae75mhvfpTNOcvm7x1zf19QkmZqrAf7
+ jpkyKDE//d4Rn3h4Ogpl5bdXb9RyOoSEJUG+EDPFyynqdodCJ2ucpw==
+ -----END AGE ENCRYPTED FILE-----
+ recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ lastmodified: "2026-06-18T22:40:22Z"
+ mac: ENC[AES256_GCM,data:5HoPrQU8Q+SGEH4jQHy1otFXGILqttDpcbv+edNoKI/QFoUE74M4PrGzltlManZstqK3nfWV3HNIcY6GRe8TMP5ANC/8UIXfEDerP1Q0kj6xeyLumMCJ6iCjygF2SzL5YMWqBSsCJvH5O10sWncc2iPnGrQPJ8PksIxkXCe4YYk=,iv:f0WpJwENciWkyDKpGM/PaE2lmXWvNiMAxNpqlCEFhM8=,tag:nQAPTJPYsgRIUkDxktIvmA==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.13.1
diff --git a/hosts/poweredge/resources/secrets/mailer-pass.yaml b/hosts/poweredge/resources/secrets/mailer-pass.yaml
new file mode 100644
index 0000000..331bd66
--- /dev/null
+++ b/hosts/poweredge/resources/secrets/mailer-pass.yaml
@@ -0,0 +1,16 @@
+pass: ENC[AES256_GCM,data:RHOvLwbDIb8FZ+dG66e5U43qR0aXlLLZGAnlbRjSl8hxCMEtJ4940nggiaIV75jCaiWyLutay7MrKPKZBHDZwBIqcJYQRWm1zWGkoZi0/bX38vUFWOpI4qku9fIB2qll,iv:bqEnTagxlRqlAmMgFCtXXCSSlODE598yoV4fU0jSYL8=,tag:c/ZiGCDSb8quDoYiIKbMeQ==,type:str]
+sops:
+ age:
+ - recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEUXlZaUhSUkNGK2xpVzRp
+ OEhYMTV6bnpPZC9tdHZWbnZxcUp6WWVLMnhFCmZmckVBckdRS1g0MjJQdE80S2Js
+ aGlNek1nSmU2aGI4cWVXR0NmbjJwa00KLS0tIDJ3N3BoenQ5ZW02K3BLNWxkWU5y
+ Ym56YzI5Zk9KeFhzZXJXR3NoOUl0ckEKOLweZrk/Pe6BG48+RrwOxyOy0Zb768aZ
+ YIxTBv/qSzZei6VqZHiIwTUEMyE7z3CS0dBFws6q4fB4LfIpv6fiYg==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-31T01:37:29Z"
+ mac: ENC[AES256_GCM,data:WIGXvuwB4bcBDfMRrrMQ7faUkxFdreyYiuy6bNPI2pzvvUFTSo/lJTv/DjisSARdYmFHFvdResIXUjg75Sc2I5IrvRxZxnYqx/3z5k/WOFWb8HSKH2H+OUHtLkqWJSCQ9YBuX2tys93mEXgwchPpn4nzVaYBgxZl54F3icX7tsE=,iv:BS9KPGkVaH0G0bAZz6+LR0NDcmqw6khOkih5DyvGyug=,tag:dA9YVL1xEqUqe6hDzOH7XQ==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/hosts/poweredge/resources/secrets/router.yaml b/hosts/poweredge/resources/secrets/router.yaml
new file mode 100644
index 0000000..edf7b41
--- /dev/null
+++ b/hosts/poweredge/resources/secrets/router.yaml
@@ -0,0 +1,17 @@
+wg0: ENC[AES256_GCM,data:hxZrOTMx2lS7dkQPV48jUbHxoNr32KSM/wG0OPY8mcgHX4FsHStdH/CN4qlkH9co90yFKjwqcJ3fHXaSbIhp3oIYTo1R2/8KggHV+az4skb7v4kzZgU5u5V2B57gIRFrPHBBq1UtDkBBpANr7zypeLPuEzvCQaZiexrsCcET7xwhsEYtVc5WlISMHhi6sFzo1AeEVfwFvIUN9geXJwP3JycYRpaNXOVYAbPwer8fYa6mnFZIk+sruCO85QNoWQ2T1wPiC9U7Ki9q+FOaOzJWGdpiG3nQdeYlDgAwoPRNi3c0J6t3CS9xC+qC0y4esOL7ay4S2dDoL9ftChQjhWaGdq5+LeyXAPlATGs5aFtBMwEecQ5OPXTri2D5m/gVLW/TGmUT5ZVPymm/wAX+m4IWK6X3FylWnlEoUh+4cBO+2zjTbXEccRuJTOEZAGIsEg==,iv:ycsGniDPFOvFFGqCgiyCWkCS8AxFV7JbXgUxDPXAFmk=,tag:uXkB8b7S3sW9noTIK4J/Zw==,type:str]
+wg1: ENC[AES256_GCM,data:4qwFVFszUQgBquAcgA4dB0TdBmTxprZLaL7+FQjrqG8avDFNxZyUFc9fHXiNRsvbgr+UKGVhdOast0y6kOaeQqqPiHLqQD/MN5wu8+SLFTaNppOKiwsjrK9x43GCxRT/ytmQWgb3pBVf7ruwbmykjDhZ577f3yhnNYhJuHoGLvTdJ18pxSE6DaukrJTnwa6BhxKv62IGlGHLYi5u9keR3LFCBwYyAIEjU9E+Na7mZ4cM2Y61/e0KTekMcQHqlBqvWmIDbdnLsoC/JVl/A8gIlU1TiOqo7geiUte/wwzc3dtkK+WQWuvaDrdtWdsUaKJEFPZhmx6KWZk4gTMdIlbP+KMm0X39rXIkMMyfWqs2rklnwm+BIg1vayQYsuAH3DXofxNlWcOEsjvnw19kPy8CrYOFAx2tPeyUWs1mFEQYeB0gafvRrFJfMUkh4uQuJnznjJH+8fmVM66EA6GLSRFUsjK/v4yEKftQQkYKuWRO9MpO5kTYo0YiSO9xOPbxNe+VVC1oJnQxaTeXNV2JZLjce/3+aZ3oFu5KG11yx+OqfRDBdJVcg6jEgVBlNY88ZrNTUUb9tEqlSXwuradURX1GekYwM0fBRTW+f/oiJFsbbBA8SODWl/50HnxcTqkeiYmnB2PA167nFLFOL/MYRh+txXbpcH4lwoPshPjGnM3XGxUxNIU+xqFFLHn/h6WhiyUZ6SkRgNGaGQ4w6RuRvZm3aHb0wAW0pwOnPoMbh2XDVTkdjFLxmJpAUHfy2W+FIYAGIU0DcT3/r2dW91hDSzjnysRxrPRiOc37nvsrqxjZd0+WI4KtHhpgxGkPyoV6CcFcKr7GrImlh2cVDr3oxrUHI9JrmD7VWKJV/ws6tSZsfHc=,iv:zXw3y9MIyaw/zXXl4CgavbP7QbAritZdhAWBL40vhtw=,tag:SXsG2HtActAlJrt7foqldA==,type:str]
+sops:
+ age:
+ - enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAxOFFVRFl2MDhiUVRPdnZJ
+ b3BSZ0pSaG8vR3o3SGlmVENvN3ZObHU3eUc4CmJrdk8rbXpBMWhTZ2hvdlk0VWo1
+ ejdnTDR4bFlUUXc4Smd0NEp6b3crZFUKLS0tIFBsQUdRWjZLSDY1ZlFaRDdLOVhs
+ MFFGY0NSU3NaK2U0U1NndUIyYm54ZWsKZ6q9j1fNaNSzBA0rbZYyUWt3U2V/7/9/
+ FZTKd8mH/uCoxvK8unlVZ9uYNADRh2smp7LwK1AWie/6khAIFqVBeg==
+ -----END AGE ENCRYPTED FILE-----
+ recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ lastmodified: "2026-06-22T19:36:47Z"
+ mac: ENC[AES256_GCM,data:Dvl3nQrqpPiYUfRHJuLQCbt0QF1duV0/E+S8A2kQ3zjP5NxmO4lKFRXgcWwsDzm+IQl4pcIMQ+FqU/C7ujTqxQ3/PSRx4o51POWDwJA5DmNpVNzH87BuXDxXPiItlPKzAfpbs8TwGCIOvmOcEP7Pd55eWJucRAq29N6nnixFHHQ=,iv:G91DcLnshSlBrtTTyJdJyuOa4wgjNhWsoVRDUk+WJOU=,tag:9Fcj8Oxu5q/u9pAiU18xwg==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.13.1
diff --git a/hosts/poweredge/resources/secrets/transmission.yaml b/hosts/poweredge/resources/secrets/transmission.yaml
new file mode 100644
index 0000000..5945804
--- /dev/null
+++ b/hosts/poweredge/resources/secrets/transmission.yaml
@@ -0,0 +1,17 @@
+ovpn-auth: ENC[AES256_GCM,data:3F3/4v0xOsdWguywaKxE6IB6pzHONWk6F6Es,iv:7ZJUyeb0EZTVkTIk5W2rhBjXmUHm2TLe5/YKgOJiqPA=,tag:idiz76VtruJ2NOhUhtbXjw==,type:str]
+ovpn-config: ENC[AES256_GCM,data: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,iv:Uy0Z4W4PUcwfkGfR6Mtf6AWShsU3dmLz+H9JOJd463c=,tag:B1KAAANSnRvb0ECwYP0QxQ==,type:str]
+sops:
+ age:
+ - enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVNkthU0srVEFZMk43ZFFu
+ dEJ3QkJyMEJ2OUhCR1Nwc3pQSHEzaXhjZFRzCm9idXlpUVM2WTRXQWY2SzFJei93
+ bzdEdEdiS1NHYy9mYWY3UU5xcmJMN2MKLS0tIGtSblFnZDdHK1ltUTNyWUFVdFd5
+ czZxZzUvSjlzcmtZRURleURwWGZVSjQK+DsUTq8yTkKZGK2rj1c9tFPm9nRoAjkQ
+ D5HFkN8uRVTB91JEx077I8VZLXJgJ6HFGOpW2EGV4nDilL9YP288Eg==
+ -----END AGE ENCRYPTED FILE-----
+ recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ lastmodified: "2026-06-20T18:05:47Z"
+ mac: ENC[AES256_GCM,data:+w9BPkacf5X9hJhh5Bmm8nf5eANZs07K/BS3c1q6jVdUoHPkVbOz0EbAEQkheW8xQAH5lIQl0D5GXSbGyhLnV3mKwL3RgV532aAY02IT68ygDvFPx6ziBEmqBlWaTZ3DopKOqPzje0olMuefYF1Li1LihmLEX3kMxwBFOr8oSn0=,iv:Lkq8NdRjbMI21bfVVoq08kgZQmgdFrh+xKgd8j/VAkc=,tag:bMwLv5ABrMSzwq5t6/s65g==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.13.1
diff --git a/hosts/poweredge/router-hosts.nix b/hosts/poweredge/router-hosts.nix
new file mode 100644
index 0000000..2a08558
--- /dev/null
+++ b/hosts/poweredge/router-hosts.nix
@@ -0,0 +1,84 @@
+let
+ localDomain = "home.lan"; # TODO RFC8375 .home.arpa
+ dhcp = {
+ inherit staticLeases;
+ defaultGateway = "192.168.1.1";
+ localhostIp = "192.168.1.1";
+ rangeStart = "192.168.1.50";
+ rangeEnd = "192.168.1.250";
+ };
+ staticLeases = {
+ # Network
+ idrac-7N94GK2 = {
+ macAddress = "50:9a:4c:5d:c3:7c";
+ staticIp = "192.168.1.2";
+ };
+ OpenWrt-Attic = {
+ macAddress = "34:98:b5:60:5e:be";
+ staticIp = "192.168.1.3";
+ };
+ OpenWrt-Basement = {
+ macAddress = "8c:3b:ad:35:c7:8c";
+ staticIp = "192.168.1.4";
+ };
+ ArcherC54 = {
+ macAddress = "12:eb:b6:13:f9:e2";
+ staticIp = "192.168.1.5";
+ };
+ # Desktops
+ T495 = {
+ macAddress = "04:33:c2:9d:34:74";
+ staticIp = "192.168.1.11";
+ };
+ optiplex = {
+ macAddress = "e4:54:e8:bc:ba:05";
+ staticIp = "192.168.1.12";
+ };
+ X230 = {
+ macAddress = "84:3a:4b:60:34:c4";
+ staticIp = "192.168.1.13";
+ };
+ # Services
+ gnuslashprinter = {
+ macAddress = "00:23:24:5b:f0:6d";
+ staticIp = "192.168.1.40";
+ };
+ immich = {
+ macAddress = "02:00:00:00:00:01";
+ staticIp = "192.168.1.41";
+ };
+ jellyfin = {
+ macAddress = "02:00:00:00:00:02";
+ staticIp = "192.168.1.42";
+ };
+ gitea = {
+ macAddress = "02:00:00:00:00:03";
+ staticIp = "192.168.1.43";
+ };
+ bitcoind = {
+ macAddress = "02:00:00:00:00:04";
+ staticIp = "192.168.1.44";
+ };
+ filebrowser = {
+ macAddress = "02:00:00:00:00:05";
+ staticIp = "192.168.1.45";
+ };
+ grafana = {
+ macAddress = "02:00:00:00:00:06";
+ staticIp = "192.168.1.46";
+ };
+ transmission = {
+ macAddress = "02:00:00:00:00:07";
+ staticIp = "192.168.1.47";
+ };
+ };
+ dns.hostOverrides = {
+ "router.${localDomain}" = "192.168.1.1";
+ "poweredge.${localDomain}" = "192.168.1.10";
+ };
+in {
+ services._router.dnsDhcpConfig = {
+ inherit localDomain dhcp dns;
+ };
+ networking.hosts."192.168.1.1" = [ "router.${localDomain}" "router" ];
+}
diff --git a/hosts/poweredge/transmission.nix b/hosts/poweredge/transmission.nix
new file mode 100644
index 0000000..cded95d
--- /dev/null
+++ b/hosts/poweredge/transmission.nix
@@ -0,0 +1,118 @@
+{ config, pkgs, ... }: {
+ # Secrets
+ sops.secrets.transmission-ovpn-config = { sopsFile = ./resources/secrets/transmission.yaml; key = "ovpn-config"; };
+ sops.secrets.transmission-ovpn-auth = { sopsFile = ./resources/secrets/transmission.yaml; key = "ovpn-auth"; };
+
+ # Container
+ containers.transmission = let
+ home = "/var/lib/transmission";
+ download-dir = "${home}/complete";
+ incomplete-dir = "${home}/incomplete";
+ in {
+ autoStart = true;
+ privateNetwork = true;
+ enableTun = true; # OpenVPN requires
+ hostBridge = "br-lan0";
+ localMacAddress = "02:00:00:00:00:07";
+
+ # Download dirs
+ bindMounts = {
+ "${download-dir}" = {
+ hostPath = "/media/ingens/media/.incomplete";
+ isReadOnly = false;
+ };
+ "${incomplete-dir}" = {
+ hostPath = "/media/ingens/media/.complete";
+ isReadOnly = false;
+ };
+ };
+
+ # Bind secrets
+ bindMounts."/run/secrets/ovpn-config.ovpn" = {
+ hostPath = config.sops.secrets.transmission-ovpn-config.path;
+ isReadOnly = true;
+ };
+ bindMounts."/run/secrets/ovpn-auth" = {
+ hostPath = config.sops.secrets.transmission-ovpn-auth.path;
+ isReadOnly = true;
+ };
+
+ config = { lib, config, ... }: {
+ # Network
+ networking.enableIPv6 = false; # Prevent ip leaks
+ networking.interfaces.eth0.useDHCP = true;
+ networking.firewall.interfaces = {
+ eth0.allowedTCPPorts = [ 80 ]; # RPC interface
+ # Torrent ports
+ tun0 = {
+ allowedTCPPorts = [ 51413 ];
+ allowedUDPPorts = [ 51413 ];
+ };
+ };
+
+ # Transmission
+ services.transmission = {
+ inherit home;
+ enable = true;
+
+ settings = {
+ inherit download-dir incomplete-dir;
+ };
+ };
+
+ # TODO remove (#258793)
+ systemd.services.transmission.serviceConfig = {
+ RootDirectoryStartOnly = lib.mkForce null;
+ RootDirectory = lib.mkForce null;
+ };
+
+ # Reverse proxy
+ services.caddy = {
+ enable = true;
+ virtualHosts.":80".extraConfig = ''
+ reverse_proxy localhost:9091
+ '';
+ };
+
+ # OpenVPN
+ services.openvpn.servers.main = {
+ config = ''
+ config /run/secrets/ovpn-config.ovpn
+ auth-user-pass /run/secrets/ovpn-auth
+ '';
+ autoStart = true;
+ updateResolvConf = true;
+ };
+
+ # VPN killswitch
+ networking.firewall.extraCommands = ''
+ # Get domain name host and port from ovpn config
+ SERVER_HOST=$(${pkgs.gawk}/bin/awk '/^remote /{print $2;exit}' /run/secrets/ovpn-config.ovpn)
+ SERVER_PORT=$(${pkgs.gawk}/bin/awk '/^remote /{print $3;exit}' /run/secrets/ovpn-config.ovpn)
+
+ # Resolve server ip from host
+ while [ -z "$SERVER_IP" ]; do
+ sleep 3
+ SERVER_IP=$(${pkgs.getent}/bin/getent hosts "$SERVER_HOST" 2>/dev/null | ${pkgs.gawk}/bin/awk '{print $1}')
+ echo "SERVER_IP: $SERVER_IP"
+ done
+
+ # Only allow out traffic from tun0
+ ${pkgs.iptables}/bin/iptables -P OUTPUT DROP
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -o lo -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -o tun0 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # Exception: allow established connections
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -p udp -d "$SERVER_IP" --dport "$SERVER_PORT" -j ACCEPT
+
+ # Allow DNS
+ DNS_IP=$(${pkgs.gawk}/bin/awk '/^nameserver /{print $2; exit}' /etc/resolv.conf)
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -o eth0 -p udp -d "$DNS_IP" --dport 53 -j ACCEPT
+ ${pkgs.iptables}/bin/iptables -A OUTPUT -o eth0 -p tcp -d "$DNS_IP" --dport 53 -j ACCEPT
+
+ # Allow transmission RPC
+ '';
+
+ system.stateVersion = "26.05";
+ };
+ };
+}
diff --git a/hosts/sweetiepc/configuration.nix b/hosts/sweetiepc/configuration.nix
new file mode 100644
index 0000000..7bf5fcb
--- /dev/null
+++ b/hosts/sweetiepc/configuration.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, home-manager, ... }: {
+ #imports = [ ./wg.nix ];
+
+ # Setup bootloader
+ boot._loader = {
+ enable = true;
+ loader = "grub";
+ };
+
+ # Enable common options
+ _archetypes = {
+ profiles = {
+ desktop = {
+ enable = true;
+ home.users.timmy.enable = true;
+ };
+ zfs.enable = true;
+ btrfs.enable = true;
+ gnome.enable = true;
+ };
+ collections = {
+ desktop = {
+ office.enable = true;
+ };
+ };
+ };
+
+ # Import zfs pools
+ boot.zfs.extraPools = [ "geminus" "quattuor" ];
+
+ # Enable user timmy
+ _users.timmy = {
+ enable = true;
+ };
+
+ # Disable suspend
+ systemd._suspend.disable = true;
+
+ # Configure home
+ home-manager.users.timmy = {
+ fonts.fontconfig = {
+ subpixelRendering = "rgb";
+ hinting = "none";
+ };
+ };
+
+ # Without this, "ZFS requires networking.hostId to be set" will be raised
+ networking.hostId = "fdde503a";
+
+ system.stateVersion = "26.05";
+}
diff --git a/hosts/sweetiepc/hardware-configuration.nix b/hosts/sweetiepc/hardware-configuration.nix
new file mode 100644
index 0000000..5ed162c
--- /dev/null
+++ b/hosts/sweetiepc/hardware-configuration.nix
@@ -0,0 +1,38 @@
+# Do not modify this file! It was generated by ‘nixos-generate-config’
+# and may be overwritten by future invocations. Please make changes
+# to /etc/nixos/configuration.nix instead.
+{ config, lib, pkgs, modulesPath, ... }:
+
+{
+ imports =
+ [ (modulesPath + "/installer/scan/not-detected.nix")
+ ];
+
+ boot.initrd.availableKernelModules = [ "ehci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
+ boot.initrd.kernelModules = [ ];
+ boot.kernelModules = [ "kvm-intel" ];
+ boot.extraModulePackages = [ ];
+
+ fileSystems."/" =
+ { device = "/dev/disk/by-uuid/91572803-436d-4f43-b41f-dfba2103752e";
+ fsType = "btrfs";
+ options = [ "subvol=@" ];
+ };
+
+ fileSystems."/home" =
+ { device = "/dev/disk/by-uuid/91572803-436d-4f43-b41f-dfba2103752e";
+ fsType = "btrfs";
+ options = [ "subvol=@home" ];
+ };
+
+ fileSystems."/boot" =
+ { device = "/dev/disk/by-uuid/F618-D6C2";
+ fsType = "vfat";
+ options = [ "fmask=0022" "dmask=0022" ];
+ };
+
+ swapDevices = [ ];
+
+ nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+ hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/hosts/sweetiepc/resources/secrets/wg.yaml b/hosts/sweetiepc/resources/secrets/wg.yaml
new file mode 100644
index 0000000..9edaea5
--- /dev/null
+++ b/hosts/sweetiepc/resources/secrets/wg.yaml
@@ -0,0 +1,16 @@
+wg0: ENC[AES256_GCM,data:xow2iL5l7MqlUeCZ2e9R4ygKlK3+ZP2yIR73aHWiIaO24aF02I88cWajOvs44/CP2tTHJLM6OltN/rKr3+4DLCbhOrngl/uFyWv/Xva4n9ZCWBv0DgtQ0Qbk/Bi6Q+h3Uf7XXlOtsUNvzSXvW4L+EcwsTMQRU5CIpSHcIsyG/IsQ1ypHpzDPM8j3HHtoPp6zaYPpQiELoMIIuIjvnvjTAAEWYMPuiV4OgJy2kiAUFkEwDud3MV/e/D/q861lKSz5qiKWnxE/So9kmA4HaSw9EOi46EcFojat7r3LiludcMify4V+IliVs7LPYf8kaHSOPWdmd8cgDeYP3Np0ydhB2+2xSbRLCgZvJWuxkStbeNjA5sC3tAmA86I+axhjVGRD9NdbpksdDORBMJhx2iWVhBBvM5p/Ya0f5nXHlsLw4MLSCKOZlTf9OM5YlYGw/GA30nJbcOVx1TzK+BJ0cYsz7U4YJWEMHTEPIQ==,iv:TM0Csdn65smwseVzq36IJAcapEZx+yjhRyG3Atz2Jk0=,tag:A6wWrO8PJAPFaI2IFq5Ucw==,type:str]
+sops:
+ age:
+ - enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlS0pyVGtTekV0MXQ5WHYy
+ MlRZYnVVaXdKVDlGNlZOekxUWng4NUlWTXlBCnBzbzNwUWtTdExBUXpCNy83YStm
+ Y0xvTlo5bkczS2xSdUtjenNjQlhDNlkKLS0tIGd6RjlZWGtZNVpXeVNpd0EyTk5h
+ cWN0b0dXQjNwNFZjdXdiUDZCUDZETkkKJlDzHxpRHbh4+LPEaWjrmiFFGMOL4Hdi
+ lMhSrn/EmMmf6p82FVpWMfr7fEFc9lCRw1VytKNUc1Le3nIpkM8iJQ==
+ -----END AGE ENCRYPTED FILE-----
+ recipient: age1fl4rk54n2xv4vssjkw4nywy93s4yqv69uzq2f64zuwd8fjq5u4cq9wmmsw
+ lastmodified: "2026-06-22T19:17:43Z"
+ mac: ENC[AES256_GCM,data:o+p9xd02QZ9VcUwiAMAGExf5FBrDaKCkOen58d1zTzZuiH2Svcmg5xrA+N23XuEi9kVTcqby9fpymgTOlwNZn8JbmMYjLLiykBIWQvOPKNjAGzp2A9KFA1DpIzTegM+xXbnKv+FEZdRagAojkAUftUUzQYAUsmQVDnflzwNsSUI=,iv:s0oM+TgNNUgNZQ99Pb33UCUbhdZJ93Dv1X+GK6oFUwo=,tag:306d418MjlEZnPi6L2qd/g==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.13.1
diff --git a/hosts/sweetiepc/wg.nix b/hosts/sweetiepc/wg.nix
new file mode 100644
index 0000000..bf459d9
--- /dev/null
+++ b/hosts/sweetiepc/wg.nix
@@ -0,0 +1,7 @@
+{ config, pkgs, inputs, ... }: {
+ sops.secrets.wg0 = { sopsFile = ./resources/secrets/wg.yaml; key = "wg0"; };
+
+ networking.wg-quick.interfaces = {
+ wg0.configFile = config.sops.secrets.wg0.path;
+ };
+}
diff --git a/lib/default.nix b/lib/default.nix
new file mode 100644
index 0000000..01801a2
--- /dev/null
+++ b/lib/default.nix
@@ -0,0 +1,3 @@
+{
+ _mkProfileArchetype = import ./mkprofile.nix;
+}
diff --git a/lib/home-manager/default.nix b/lib/home-manager/default.nix
new file mode 100644
index 0000000..01801a2
--- /dev/null
+++ b/lib/home-manager/default.nix
@@ -0,0 +1,3 @@
+{
+ _mkProfileArchetype = import ./mkprofile.nix;
+}
diff --git a/lib/home-manager/mkprofile.nix b/lib/home-manager/mkprofile.nix
new file mode 100644
index 0000000..dae4d60
--- /dev/null
+++ b/lib/home-manager/mkprofile.nix
@@ -0,0 +1,9 @@
+profileName: nixosConfig: homeConfig: { config, lib, ... }: let
+ cfg = config._archetypes.profiles.${profileName};
+in {
+ options._archetypes.profiles.${profileName} = {
+ enable = lib.mkEnableOption "enable (standalone) home-manager profile ${profileName} for the configured user";
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [ homeConfig ]);
+}
diff --git a/lib/mkprofile.nix b/lib/mkprofile.nix
new file mode 100644
index 0000000..c8cfe12
--- /dev/null
+++ b/lib/mkprofile.nix
@@ -0,0 +1,22 @@
+profileName: nixosConfig: homeConfig: { config, lib, ... }: let
+ cfg = config._archetypes.profiles.${profileName};
+ enabledUsers = lib.filterAttrs (user: userCfg: userCfg.enable) cfg.home.users;
+ homeConfigs = {
+ home-manager.users = lib.mapAttrs (user: userCfg: homeConfig) enabledUsers;
+ };
+in {
+ options._archetypes.profiles.${profileName} = {
+ enable = lib.mkEnableOption "enable nixos profile ${profileName}";
+ home.users = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.submodule {
+ options = {
+ enable = lib.mkEnableOption "home-manager profile ${profileName} for user";
+ };
+ });
+ default = {};
+ description = "enable home-manager profile ${profileName} for user the specified user";
+ };
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [ nixosConfig homeConfigs ]);
+}
diff --git a/modules/home/alacritty.nix b/modules/home/alacritty.nix
deleted file mode 100644
index f60fe01..0000000
--- a/modules/home/alacritty.nix
+++ /dev/null
@@ -1,67 +0,0 @@
-{ config, lib, ... }: {
- options = {
- alacritty = {
- enable = lib.mkEnableOption "enables creation of alacritty config file";
- font.size = lib.mkOption {
- type = lib.types.int;
- default = 12;
- };
- font.family = lib.mkOption {
- type = lib.types.str;
- default = "JetBrainsMonoNL Nerd Font Mono";
- };
- };
- };
-
- config = {
- programs.alacritty.enable = config.alacritty.enable;
- programs.alacritty.settings = lib.mkIf config.alacritty.enable {
- colors = {
- bright = {
- black = "#7f7f7f";
- blue = "#1578c1";
- cyan = "#00c4a3";
- green = "#2bb500";
- magenta = "#b14ff7";
- red = "#ed1207";
- white = "#ffffff";
- yellow = "#fc9700";
- };
- normal = {
- black = "#101010";
- blue = "#1578c1";
- cyan = "#00c4a3";
- green = "#2bb500";
- magenta = "#b14ff7";
- red = "#ed1207";
- white = "#ffffff";
- yellow = "#fc9700";
- };
- primary = {
- background = "#101010";
- foreground = "#ffffff";
- };
- };
- cursor.style.blinking = "Never";
- env.TERM = "xterm-256color";
- font = {
- size = config.alacritty.font.size;
- normal.family = config.alacritty.font.family;
- };
- mouse.bindings = [{
- action = "PasteSelection";
- mouse = "Right";
- }];
- window = {
- dynamic_padding = true;
- dynamic_title = true;
- title = "Terminal";
- # Pretend to be st
- class = {
- general = "st";
- instance = "st";
- };
- };
- };
- };
-}
diff --git a/modules/home/chrome.nix b/modules/home/chrome.nix
deleted file mode 100644
index 976a74b..0000000
--- a/modules/home/chrome.nix
+++ /dev/null
@@ -1,28 +0,0 @@
-{ pkgs, lib, ... }: {
- programs.chromium = {
- enable = true;
- package = pkgs.ungoogled-chromium;
- };
- programs.chromium.extensions =
- let
- createChromiumExtensionFor = browserVersion: { id, sha256, version }:
- {
- inherit id;
- crxPath = builtins.fetchurl {
- url = "https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&prodversion=${browserVersion}&x=id%3D${id}%26installsource%3Dondemand%26uc";
- name = "${id}.crx";
- inherit sha256;
- };
- inherit version;
- };
- createChromiumExtension = createChromiumExtensionFor (lib.versions.major pkgs.ungoogled-chromium.version);
- in
- [
- (createChromiumExtension {
- # ublock origin
- id = "cjpalhdlnbpafiamejdnhcphjbkeiagm";
- sha256 = "sha256:1i0668xhq5iflb4fn0ghwp79iz6mwspgxdqwp6incbvsyzr596kg";
- version = "1.61.0";
- })
- ];
-}
diff --git a/modules/home/default.nix b/modules/home/default.nix
deleted file mode 100644
index b44f360..0000000
--- a/modules/home/default.nix
+++ /dev/null
@@ -1,24 +0,0 @@
-{ lib, userDetails, ... }: {
- home = {
- username = userDetails.username;
- homeDirectory = userDetails.home.root;
- stateVersion = "24.05";
- };
-
- imports = [
- ./alacritty.nix
- #./chrome.nix
- ./firefox.nix
- ./git.nix
- ./gtk-bookmarks.nix
- ./htop.nix
- ./initial-home-setup.nix
- ./pcmanfm.nix # TODO mk name changeable & doesn't seem to work right now
- ./theme.nix
- ./wallpapers.nix
- ];
-
- alacritty.enable = lib.mkDefault true;
- theme.mint.enable = lib.mkDefault true;
- wallpapers.enable = lib.mkDefault false;
-}
diff --git a/modules/home/firefox.nix b/modules/home/firefox.nix
deleted file mode 100644
index 5e015ff..0000000
--- a/modules/home/firefox.nix
+++ /dev/null
@@ -1,80 +0,0 @@
-{ pkgs, userDetails, ... }: {
- programs.firefox = {
- # TODO see if there is way to login to moz account in profile
- enable = true;
- arkenfox = {
- enable = true;
- };
- profiles = let
- search = {
- engines = {
- "Timmy Search" = {
- urls = [{ template = "https://search.tjkeller.xyz/search?q={searchTerms}"; }]; # Don't know how to do w/ POST but I prefer GET anyways
- iconURI = "https://search.tjkeller.xyz/static/themes/simple/img/favicon.svg"; # TODO doesn't seem to work
- };
- "Nix Packages" = {
- urls = [{
- template = "https://search.nixos.org/packages";
- params = [
- { name = "type"; value = "packages"; }
- { name = "query"; value = "{searchTerms}"; }
- ];
- }];
-
- icon = "${pkgs.nixos-icons}/share/icons/hicolor/scalable/apps/nix-snowflake.svg";
- definedAliases = [ "@np" ];
- };
- };
- default = "Timmy Search";
- privateDefault = "Timmy Search";
- force = true; # Overwrite old
- };
- userChrome = builtins.readFile ./resources/firefox/userChrome.css;
- arkenfox = {
- enable = true;
- enableAllSections = true;
- "0100"."0102"."browser.startup.page".value = 3; # 0=blank, 1=home, 2=last visited page, 3=resume previous session
- "0100"."0103"."browser.startup.homepage".enable = false;
- "0100"."0104"."browser.newtabpage.enabled".enable = false;
- "2800"."2811"."privacy.clearOnShutdown.history".enable = false;
- };
- settings = {
- "browser.compactmode.show" = true;
- "browser.uiCustomization.state" = builtins.readFile ./resources/firefox/uiCustomization.json; # Toolbar etc.
- "browser.uidensity" = 1; # Compact
- "toolkit.legacyUserProfileCustomizations.stylesheets" = true; # userchrome
-
- "app.normandy.first_run" = false;
- "browser.aboutConfig.showWarning" = false; # arkenfox does
- "browser.download.dir" = userDetails.home.downloads; # FF will create this dir if it doesn't exist
- "browser.newtabpage.activity-stream.feeds.section.topstories" = false;
- "browser.newtabpage.activity-stream.feeds.topsites" = false;
- "browser.urlbar.suggest.topsites" = false;
- "devtools.toolbox.host" = "window";
- "dom.push.enabled" = false; #
- "extensions.pocket.enabled" = false;
- "general.smoothScroll" = false;
- #identity.fxaccounts.account.device.name = "timmy’s Firefox on nixos"; # HOSTNAME
- };
- workSettings = settings // {
- "extensions.activeThemeID" = "firefox-compact-dark@mozilla.org"; # Use builtin dark theme instead of system theme
- };
- in {
- Personal = {
- id = 0;
- isDefault = true;
- inherit search;
- inherit userChrome;
- inherit arkenfox;
- inherit settings;
- };
- Work = {
- id = 1;
- inherit search;
- inherit userChrome;
- inherit arkenfox;
- settings = workSettings;
- };
- };
- };
-}
diff --git a/modules/home/git.nix b/modules/home/git.nix
deleted file mode 100644
index 9d15a90..0000000
--- a/modules/home/git.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{ userDetails, ... }: {
- programs.git = {
- enable = true;
- userName = userDetails.fullname;
- userEmail = userDetails.email;
- };
-}
diff --git a/modules/home/gtk-bookmarks.nix b/modules/home/gtk-bookmarks.nix
deleted file mode 100644
index 067543d..0000000
--- a/modules/home/gtk-bookmarks.nix
+++ /dev/null
@@ -1,17 +0,0 @@
-{ config, lib, userDetails, ... }: {
- options = {
- additional-gtk-bookmarks = lib.mkOption {
- type = with lib.types; listOf str;
- default = [];
- description = "gtk bookmarks for file managers, etc.";
- };
- };
-
- config = {
- gtk.gtk3.bookmarks = with userDetails; [
- "file://${home.downloads} Downloads"
- "file://${home.documents} Documents"
- "file://${home.pictures} Pictures"
- ] ++ config.additional-gtk-bookmarks;
- };
-}
diff --git a/modules/home/htop.nix b/modules/home/htop.nix
deleted file mode 100644
index 523fee8..0000000
--- a/modules/home/htop.nix
+++ /dev/null
@@ -1,38 +0,0 @@
-{ config, ... }: {
- programs.htop.enable = true;
- programs.htop.settings = {
- fields = with config.lib.htop.fields; [
- PID
- USER
- 124 # EXE
- PERCENT_CPU
- PERCENT_MEM
- STARTTIME
- ELAPSED # time elapsed since process start
- IO_READ_RATE
- RBYTES # num read bytes
- IO_WRITE_RATE
- WBYTES # num written bytes
- TIME
- STIME # sys time
- CSTIME # child sys time
- NICE
- STATE
- PRIORITY
- 125 # CWD
- ];
- } // (with config.lib.htop; leftMeters [
- (bar "AllCPUs2")
- (bar "CPU")
- (bar "Memory")
- (bar "DiskIO")
- (bar "NetworkIO")
- ]) // (with config.lib.htop; rightMeters [
- (text "Hostname")
- (text "Clock")
- (text "Uptime")
- (text "Battery")
- (text "LoadAverage")
- (text "Tasks")
- ]);
-}
diff --git a/modules/home/initial-home-setup.nix b/modules/home/initial-home-setup.nix
deleted file mode 100644
index 046f5a5..0000000
--- a/modules/home/initial-home-setup.nix
+++ /dev/null
@@ -1,22 +0,0 @@
-{ config, lib, pkgs, userDetails, ... }: {
- home.activation = {
- cloneRepos = lib.hm.dag.entryAfter ["writeBoundary"] ''
- export PATH="${config.home.path}/bin:$PATH"
- ${builtins.readFile ./resources/activation-scripts/clone-repos.sh}
- '';
- linkZshProfile = lib.hm.dag.entryAfter ["writeBoundary"] ''
- run ln -sf $VERBOSE_ARG $HOME/.config/zsh/zprofile $HOME/.zprofile
- '';
- cleanupHome = lib.hm.dag.entryAfter ["writeBoundary"] ''
- run rm -f $VERBOSE_ARG $HOME/{.zcompdump,.zshrc,.zsh_history,.bash_history}
- '';
- mimewiz = lib.hm.dag.entryAfter ["writeBoundary"] ''
- export PATH="${pkgs.xdg-utils}/bin:$PATH"
- export PATH="$HOME/.local/bin/misc:$PATH"
- run mimewiz -i # already verbose
- '';
- createDirs = lib.hm.dag.entryAfter ["writeBoundary"] ''
- run mkdir -p $VERBOSE_ARG "${userDetails.home.downloads}"
- '';
- };
-}
diff --git a/modules/home/pcmanfm.nix b/modules/home/pcmanfm.nix
deleted file mode 100644
index 3c70521..0000000
--- a/modules/home/pcmanfm.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- home.file.pcmanfm-root = {
- target = ".local/share/file-manager/actions/root.desktop";
- source = ./resources/pcmanfm/root.desktop;
- };
-}
diff --git a/modules/home/resources/activation-scripts/clone-repos.sh b/modules/home/resources/activation-scripts/clone-repos.sh
deleted file mode 100644
index 887968b..0000000
--- a/modules/home/resources/activation-scripts/clone-repos.sh
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-server="https://git.tjkeller.xyz/"
-
-clonemissing() {
- # clone to $2
- [ -d "$2"/.git ] && return
- run mkdir -p $VERBOSE_ARG "$2"
- run git clone $VERBOSE_ARG "$server$1" "$2"
-
- # link to $3
- [ -z "$3" ] && return
- run mkdir -p $VERBOSE_ARG "$3"
- run ln -sf $VERBOSE_ARG "$2"/* "$3"
-}
-
-# # repo # clone to # link to
-clonemissing scripts $HOME/docs/src/scripts $HOME/.local/bin
-clonemissing dotconfig $HOME/docs/src/config $HOME/.config
-clonemissing userscripts $HOME/docs/src/userscripts ''
-clonemissing nixos $HOME/docs/src/nixos ''
-clonemissing awesome $HOME/.config/awesome ''
-clonemissing nvim $HOME/.config/nvim ''
-clonemissing zsh $HOME/.config/zsh ''
diff --git a/modules/home/resources/firefox/uiCustomization.json b/modules/home/resources/firefox/uiCustomization.json
deleted file mode 100644
index 339b716..0000000
--- a/modules/home/resources/firefox/uiCustomization.json
+++ /dev/null
@@ -1 +0,0 @@
-{"placements":{"widget-overflow-fixed-list":[],"unified-extensions-area":["ublock0_raymondhill_net-browser-action","sponsorblocker_ajay_app-browser-action","_e6e36c9a-8323-446c-b720-a176017e38ff_-browser-action","dearrow_ajay_app-browser-action","_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action","idcac-pub_guus_ninja-browser-action","addon_darkreader_org-browser-action","_74145f27-f039-47ce-a470-a662b129930a_-browser-action","_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action","jid1-bofifl9vbdl2zq_jetpack-browser-action","addon_fastforward_team-browser-action","jid1-tsgsxbhncspbwq_jetpack-browser-action","_aecec67f-0d10-4fa7-b7c7-609a2db280cf_-browser-action"],"nav-bar":["back-button","forward-button","stop-reload-button","home-button","urlbar-container","downloads-button","unified-extensions-button"],"toolbar-menubar":["menubar-items"],"TabsToolbar":["tabbrowser-tabs","new-tab-button","alltabs-button"],"PersonalToolbar":["personal-bookmarks"]},"seen":["save-to-pocket-button","developer-button","dearrow_ajay_app-browser-action","_446900e4-71c2-419f-a6a7-df9c091e268b_-browser-action","_e6e36c9a-8323-446c-b720-a176017e38ff_-browser-action","sponsorblocker_ajay_app-browser-action","ublock0_raymondhill_net-browser-action","idcac-pub_guus_ninja-browser-action","addon_darkreader_org-browser-action","_74145f27-f039-47ce-a470-a662b129930a_-browser-action","_762f9885-5a13-4abd-9c77-433dcd38b8fd_-browser-action","jid1-bofifl9vbdl2zq_jetpack-browser-action","addon_fastforward_team-browser-action","jid1-tsgsxbhncspbwq_jetpack-browser-action","_aecec67f-0d10-4fa7-b7c7-609a2db280cf_-browser-action"],"dirtyAreaCache":["nav-bar","PersonalToolbar","unified-extensions-area","toolbar-menubar","TabsToolbar"],"currentVersion":20,"newElementCount":5}
diff --git a/modules/home/resources/firefox/userChrome.css b/modules/home/resources/firefox/userChrome.css
deleted file mode 100644
index de0e112..0000000
--- a/modules/home/resources/firefox/userChrome.css
+++ /dev/null
@@ -1,193 +0,0 @@
-/* TOP BAR */
-#navigator-toolbox {
- --uc-navigationbar-width: 40vw;
-}
-
-/* Remove overflow button */
-#nav-bar-overflow-button { display: none; }
-
-/* remove alltabs button */
-#alltabs-button { display: none; }
-
-/* Change background color of toolbar */
-#navigator-toolbox-background {
- background: var(--toolbar-field-border-color) !important;
-}
-
-/* Fix vertical spacing of tabs */
-#TabsToolbar {
- margin-top: -1px;
-}
-
-/* Move new tab to far right */
-#tabbrowser-arrowscrollbox-periphery {
- margin-left: auto;
-}
-
-/* Fix spacing around tabs by removing nav bar top border */
-#nav-bar {
- border: none !important;
-}
-
-/* Media queries for width of nav bar */
-@media screen and (max-width: 1000px) {
- #navigator-toolbox {
- --uc-navigationbar-width: 50vw;
- }
-}
-@media screen and (max-width: 800px) {
- #navigator-toolbox {
- --uc-navigationbar-width: 60vw;
- }
-}
-
-/* Change look of tabs in smaller view */
-@media screen and (max-width: 800px) {
- .tab-background {
- margin-block: .2em !important;
- }
- #TabsToolbar {
- margin-left: 5px;
- }
-}
-
-
-/* COMBINE TOP BAR */
-/* Combine top bar into single line if width >= 800px */
-@media screen and (min-width: 800px) {
- :root {
- --uc-toolbar-height: 36px; /* Half height bar */
- }
-
- /* Modify these to change relative widths or default height */
- #navigator-toolbox {
- margin-bottom: 0px;
- padding-top: 1px; /* Top bar is cut off otherwise */
- }
-
- #TabsToolbar {
- margin-left: calc(var(--uc-navigationbar-width) + 2px); /* Resize tab bar */
- margin-top: -4px;
- margin-bottom: 2px;
- }
-
- /* Tabs extend to bottom of bar */
- .tab-background {
- margin-bottom: 0 !important;
- margin-top: 3px !important; /* See above navigator-toolbox rule. Avoid cutting off top */
- }
-
- /* Center tab buttons */
- #tabs-newtab-button,
- #alltabs-button
- {
- margin-top: 4px !important;
- }
-
- /* Integrate url / nav bar */
- #nav-bar {
- margin-right:calc(100vw - var(--uc-navigationbar-width));
- margin-top: calc(0px - var(--uc-toolbar-height));
- border-radius: 0 var(--tab-border-radius) var(--tab-border-radius) 0;
- border-right: 1px solid ThreeDShadow !important;
- box-shadow: 0 0 4px rgba(0,0,0,.4) !important;
- }
-
- /* 1px margin on touch density causes tabs to be too high */
- .tab-close-button {
- margin-top: 0 !important
- }
-
- /* Make opened urlbar overlay the toolbar */
- #urlbar[open]:focus-within {
- min-width: 50vw !important;
- }
-
- /* Remove min and max width of urlbar */
- #urlbar-container {
- width: 0 !important;
- }
-
- /* Fix customization view */
- #customization-panelWrapper .panel-arrowbox .panel-arrow {
- margin-inline-end: initial !important;
- }
-
- /* Shorten findbar */
- findbar {
- width: 600px !important;
- border-radius: 0 0 0 5px;
- border-bottom: none !important;
- box-shadow: 0 2px 4px rgba(0,0,0,.4); /* Move down 2px so it doesn't go over the tab bar */
- }
-}
-
-
-/* FINDBAR */
-findbar {
- width: 100vw;
- position: absolute;
- top: 0;
- right: 0;
- padding: 0 !important;
- padding-top: 1px !important;
- background: -moz-headerbar Field !important;
- border-top-width: 0px !important;
- border-bottom: 1px solid ThreeDShadow;
-}
-
-findbar .findbar-container {
- padding-bottom: 5px !important; /* Move search bar closer to left edge */
- padding-top: 2px !important; /* Move search bar closer to left edge */
- height: max-content !important;
- gap: 2px;
- justify-content: space-between;
- flex-wrap: wrap;
-}
-
-/* Force textbox to fill up first line */
-findbar .findbar-textbox {
- width: 100% !important;
- background: Field !important; /* Set the background color to be consistent with found-matches label when unfocused */
-}
-
-/* Hide description showing wrap conditions etc. */
-findbar description {
- display: none;
-}
-
-/* Move found matches label (roughly) into the textbox */
-findbar label.found-matches,
-findbar description {
- position: absolute;
- top: 6.5px;
- right: 110px;
- color: color-mix(in srgb, -moz-headerbartext, transparent 46%) !important;
- /* So this text overrides the text below, TODO find a more elegant solution */
- padding-inline: 1ex;
- background: Field;
-}
-
-/* Show description when it says "Phrase Not Found" */
-findbar description[status=notfound] {
- display: inline-block;
-}
-
-/* Force checkboxes onto second line */
-findbar .findbar-container hbox {
- width: 100%;
-}
-
-
-/* BOOKMARK BAR */
-@-moz-document url(chrome://browser/content/browser.xhtml) {
- #PersonalToolbar {
- background: -moz-headerbar Field !important;
- border-top: 1px solid ThreeDShadow !important;
- z-index: 1000; /* To not be affected by shadow from navbar */
- }
- /* Space out bookmark items */
- .bookmark-item .toolbarbutton-text {
- padding: 2.5px 4px;
- }
-}
diff --git a/modules/home/theme.nix b/modules/home/theme.nix
deleted file mode 100644
index a1c4f9b..0000000
--- a/modules/home/theme.nix
+++ /dev/null
@@ -1,74 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
- transformColorValue = color :
- if color == "Green" then "Mint-Y"
- else if color == "Dark-Green" then "Mint-Y-Dark"
- else "Mint-Y-${color}"
- ;
-in {
- options = {
- theme.mint = {
- enable = lib.mkEnableOption "enables mint theme";
- # TODO add theme.dark option instead of specifying Dark-Color
- theme.color = lib.mkOption {
- type = lib.types.enum [
- "Aqua" "Blue" "Green" "Grey" "Orange" "Pink" "Purple" "Red"
- "Sand" "Teal" "Dark-Aqua" "Dark-Blue" "Dark-Green"
- "Dark-Grey" "Dark-Orange" "Dark-Pink" "Dark-Purple"
- "Dark-Red" "Dark-Sand" "Dark-Teal"
- ];
- default = "Dark-Aqua";
- description = "mint-y theme color eg. 'Dark-Aqua' or 'Red'";
- };
- icons.color = lib.mkOption {
- type = lib.types.enum [
- "Aqua" "Blue" "Green" "Grey" "Orange" "Pink" "Purple" "Red"
- "Sand" "Teal"
- ];
- default = "Aqua";
- description = "mint-y icons color eg. 'Aqua' or 'Red'";
- };
- };
- theme.font = {
- sansSerif = lib.mkOption {
- type = lib.types.str;
- default = "Inter";
- description = "default sans serif font";
- };
- monospace = lib.mkOption {
- type = lib.types.str;
- default = "CommitMono";
- description = "default monospace font";
- };
- };
- };
-
- config = {
- gtk = {
- enable = true;
- theme = lib.mkIf config.theme.mint.enable {
- package = pkgs.mint-themes;
- name = transformColorValue config.theme.mint.theme.color;
- };
- iconTheme = lib.mkIf config.theme.mint.enable {
- package = pkgs.mint-y-icons;
- name = transformColorValue config.theme.mint.icons.color;
- };
- font = {
- name = config.theme.font.sansSerif;
- };
- cursorTheme = {
- name = "Adwaita";
- };
- gtk2.configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
- };
- qt = {
- enable = true;
- platformTheme.name = "gtk3";
- };
- fonts.fontconfig.defaultFonts = {
- sansSerif = [ config.theme.font.sansSerif ];
- monospace = [ config.theme.font.monospace ];
- };
- };
-}
diff --git a/modules/home/wallpapers.nix b/modules/home/wallpapers.nix
deleted file mode 100644
index d7e067a..0000000
--- a/modules/home/wallpapers.nix
+++ /dev/null
@@ -1,14 +0,0 @@
-{ lib, config, pkgs, ... }: {
- options = {
- wallpapers.enable = lib.mkEnableOption "enables all wallpapers";
- };
-
- config = lib.mkIf config.wallpapers.enable {
- home.file.".local/share/wallpaper" = {
- source = pkgs.fetchzip {
- url = "https://tjkeller.xyz/permalinks/wallpapers.tar";
- hash = "sha256-lhj9wUMZfd0XOrUtWY9HB64oXrS/EjsvsaJHzu4ucFg=";
- };
- };
- };
-}
diff --git a/modules/hosts/T430/configuration.nix b/modules/hosts/T430/configuration.nix
deleted file mode 100644
index 282515d..0000000
--- a/modules/hosts/T430/configuration.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-{ config, lib, pkgs, ... }: {
- imports = [ ./hardware-configuration.nix ];
- networking.hostName = "T430";
-
- # TODO this does not function
- #boot.initrd.systemd.extraBin = {
- # sh = "${pkgs.dash}/bin/dash";
- # vim = "${pkgs.neovim}/bin/nvim";
- #};
-
- # Open ports in the firewall.
- # networking.firewall.allowedTCPPorts = [ ... ];
- # networking.firewall.allowedUDPPorts = [ ... ];
- # Or disable the firewall altogether.
- # networking.firewall.enable = false;
-
- system.stateVersion = "24.05";
-}
diff --git a/modules/hosts/T430/home.nix b/modules/hosts/T430/home.nix
deleted file mode 100644
index 9885a06..0000000
--- a/modules/hosts/T430/home.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{ ... }: {
- imports = [ ../../home ];
-
- alacritty.font.size = 9;
- wallpapers.enable = true;
-}
diff --git a/modules/hosts/T495/configuration.nix b/modules/hosts/T495/configuration.nix
deleted file mode 100644
index 526b7bb..0000000
--- a/modules/hosts/T495/configuration.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-{ config, lib, pkgs, ... }: {
- imports = [
- ./hardware-configuration.nix
- ./games.nix
- ./input-leap.nix
- ./wg.nix
- ];
- networking.hostName = "T495";
-
- bootloader.mode = "efi";
-
- # Enable extra software pkgs
- software.desktop = {
- chromium.enable = true;
- cad.enable = true;
- crypto.enable = true;
- graphics.enable = true;
- office.enable = true;
- utilities.enable = true;
- };
- software.development = {
- docker.enable = true;
- };
-
- # Enable network drives
- nas.enable = true;
- nas.office.enable = true;
- nas.office.automount = false;
-
- networking.hosts = {
- "192.168.77.3" = [ "devel" ];
- };
-
- # Use amdgpu driver for x11
- services.xserver.videoDrivers = [ "amdgpu" ];
-
- # Enable bluetooth
- bluetooth.enable = true;
-
- system.stateVersion = "24.05";
-}
diff --git a/modules/hosts/T495/games.nix b/modules/hosts/T495/games.nix
deleted file mode 100644
index 419d854..0000000
--- a/modules/hosts/T495/games.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{ pkgs, ... }: {
- nixpkgs.config.allowUnfree = true;
- environment.systemPackages = with pkgs; [
- #vintagestory
- prismlauncher
- ];
-}
diff --git a/modules/hosts/T495/home.nix b/modules/hosts/T495/home.nix
deleted file mode 100644
index 817b0c0..0000000
--- a/modules/hosts/T495/home.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-{ ... }: {
- imports = [ ../../home ];
-
- theme.mint.theme.color = "Dark-Purple";
- theme.mint.icons.color = "Purple";
- wallpapers.enable = true;
-
- home.file.input-leap-config = {
- source = ./resources/input-leap;
- recursive = true;
- target = "./.config/input-leap";
- };
-}
diff --git a/modules/hosts/T495/input-leap.nix b/modules/hosts/T495/input-leap.nix
deleted file mode 100644
index cf6d018..0000000
--- a/modules/hosts/T495/input-leap.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-{ pkgs, ... }: {
- environment.systemPackages = with pkgs; [
- input-leap
- ];
-
- networking.firewall = {
- allowedTCPPorts = [
- 24800 # input-leaps
- ];
- };
-}
diff --git a/modules/hosts/T495/resources/input-leap/input-leap.conf b/modules/hosts/T495/resources/input-leap/input-leap.conf
deleted file mode 100644
index c2d5176..0000000
--- a/modules/hosts/T495/resources/input-leap/input-leap.conf
+++ /dev/null
@@ -1,12 +0,0 @@
-section: screens
- T495:
- optiplex:
-end
-
-section: links
- T495:
- up = optiplex
-
- optiplex:
- down = T495
-end
diff --git a/modules/hosts/T495/resources/secrets/wg0.yaml b/modules/hosts/T495/resources/secrets/wg0.yaml
deleted file mode 100644
index 6df5ff6..0000000
--- a/modules/hosts/T495/resources/secrets/wg0.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
-wg0: ENC[AES256_GCM,data:Zxw98YrH2QYyU8u4sk0geOUQXfOZK9scGPECbKruehcDffJlRspmsS2k3sSahdAIcJjIjauGmZ7usXus2wKAiRnYsl2e4P/0IUVp8L8UJp52iPZVEvT9JRELPYQ9gBBNmp/HDYglxP7uHx2bzcCTvCr4fFdQZgsUSl/pd6pBtSMwupsK5U9Xfz9Sh7JNTlpgpU4jlkl676XnrCo0HyL7rXMp4S6Ruhfve8gwJYP8QLn4DiwnP69Flc54MDQRKSvlvYca8dZIrS1N3h9k6aO1eB/z4I3qPN0/p7yT9pdfemaDpEqf1+byRKGYuED1Ge8P3D0+qRWJBd5GmoSHC8NV9KVV3DrAJ/QLOLCqm8oN0UGxQW5lbWi6lIQ708kodEDOsBREexRPOzjYAjajBr6O6wCtRThgce4yOg9ZwqxVDERqIz1fR034b56LrZtKjOSlJtmkmMCuCGrVkKj4L7JvBfKtCjBN+TOyONsClY/Vhd+XzNWWk8d8FVraOE2rJN4+ss7UL+jBth3DfSbr739cEQDhHRKoMPvXy0FhPVw=,iv:CN82pOBNCtICzM0Ac9Gh+x8pUSK1Fc2v2KmM+de3A78=,tag:KuGiC+Ak550d6njSdX4muA==,type:str]
-sops:
- kms: []
- gcp_kms: []
- azure_kv: []
- hc_vault: []
- age:
- - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
- enc: |
- -----BEGIN AGE ENCRYPTED FILE-----
- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB4NEN4NGxDR1oreGVoSGhE
- TzMxSEY0QVBhS2Z6MW15ci9aVlJ0a3IyVlZBCldPRVNvcUhJSHhWSEk3akd4RjN0
- ajhUV2d1ZWRsRFU4cTE2dGl6RmM4MGsKLS0tIFhnUjl5aDJqWVB1NE15SlNzR2Iv
- YTNydURsOUMrSXZGdk9UOUdUQlA0SFUKxEDJRR6tpYva9qpWo9NxwCxk/xpRVoTl
- YJkmDZzMcXikXXiro96AprP9dXJXvMPKYPGl2Zsal8PlGFPBoHW2GA==
- -----END AGE ENCRYPTED FILE-----
- lastmodified: "2025-01-05T18:21:12Z"
- mac: ENC[AES256_GCM,data:VEtpQLHMoQK7Qg4PT2DkNNMurjRE0ZadyiQ0uYsPJ0K2lS0gD8pPwb8btiq7KXXOGWWZOMYRDaRKOENy44f/k+16GOpO6jaKAfN6eEcidaWlP7zvpeNzt7LImFqn/Sjv4rq1+DmvGxyyNjuK8BwvvAfnZwG6KXbn7Bh6xgfTnLs=,iv:Jf4f5j6BTkDBmah3izIESsAn7wDMUFU6NbsqpFIdui4=,tag:yNg374unwHaDEK43+Y0eWg==,type:str]
- pgp: []
- unencrypted_suffix: _unencrypted
- version: 3.9.2
diff --git a/modules/hosts/hp-envy-office/configuration.nix b/modules/hosts/hp-envy-office/configuration.nix
deleted file mode 100644
index 9a7a43a..0000000
--- a/modules/hosts/hp-envy-office/configuration.nix
+++ /dev/null
@@ -1,39 +0,0 @@
-{ config, lib, pkgs, ... }: {
- imports = [
- ./hardware-configuration.nix
- ];
- networking.hostName = "hp-envy-office";
-
- # Enable extra software pkgs
- software.desktop = {
- chromium.enable = true;
- cad.enable = false;
- crypto.enable = false;
- graphics.enable = true;
- office.enable = true;
- utilities.enable = true;
- };
- software.development = {
- docker.enable = true;
- };
-
- # Enable network drives
- nas.enable = true;
- nas.office.enable = true;
- nas.home.enable = false;
-
- networking.hosts = {
- "192.168.77.3" = [ "devel" ];
- };
-
- # Use amdgpu driver for x11
- services.xserver.videoDrivers = [ "amdgpu" ];
-
- # Enable bluetooth
- bluetooth.enable = true;
-
- # Disable autologin
- autologin.enable = false;
-
- system.stateVersion = "24.11";
-}
diff --git a/modules/hosts/hp-envy-office/home.nix b/modules/hosts/hp-envy-office/home.nix
deleted file mode 100644
index ed02130..0000000
--- a/modules/hosts/hp-envy-office/home.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ userDetails, ... }: {
- imports = [ ../../home ];
-
- theme.mint.theme.color = "Dark-Green";
- theme.mint.icons.color = "Green";
- wallpapers.enable = true;
-
- additional-gtk-bookmarks = with userDetails; [
- "file://${home.documents}/src/sites/admin Admin"
- "file:///media/chexx/chexx cHEXx"
- ];
-}
diff --git a/modules/hosts/libreX60/configuration.nix b/modules/hosts/libreX60/configuration.nix
deleted file mode 100644
index 942793d..0000000
--- a/modules/hosts/libreX60/configuration.nix
+++ /dev/null
@@ -1,14 +0,0 @@
-{ config, lib, pkgs, ... }: {
- imports = [ ./hardware-configuration.nix ];
- networking.hostName = "libreX60";
-
- # Enable extra software pkgs
- software.desktop = {
- office.enable = true;
- };
-
- # Enable network drives
- nas.enable = true;
-
- system.stateVersion = "24.11";
-}
diff --git a/modules/hosts/libreX60/home.nix b/modules/hosts/libreX60/home.nix
deleted file mode 100644
index 453bfd6..0000000
--- a/modules/hosts/libreX60/home.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{ ... }: {
- imports = [ ../../home ];
-
- theme.mint.theme.color = "Dark-Red";
- theme.mint.icons.color = "Red";
- theme.font.sansSerif = "Tamzen";
- theme.font.monospace = "Tamzen";
-}
diff --git a/modules/hosts/optiplex/configuration.nix b/modules/hosts/optiplex/configuration.nix
deleted file mode 100644
index cbde612..0000000
--- a/modules/hosts/optiplex/configuration.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-{ config, lib, pkgs, ... }: {
- imports = [
- ./hardware-configuration.nix
- ];
- networking.hostName = "optiplex";
-
- bootloader.mode = "efi";
-
- # Allow unfree for nvidia + others
- nixpkgs.config.allowUnfree = true;
-
- # Enable extra software pkgs
- software.desktop = {
- chromium.enable = true;
- cad.enable = true;
- crypto.enable = true;
- graphics.enable = true;
- office.enable = true;
- utilities.enable = true;
- };
-
- # Install more programs
- environment.systemPackages = with pkgs; [
- prismlauncher
- spotify
- #vintagestory
- ];
-
- # Enable network drives
- nas.enable = true;
-
- # Disable wifi
- wifi.enable = false;
-
- networking.hosts = {
- "192.168.77.3" = [ "devel" ];
- };
-
- # Use nvidia driver
- services.xserver.videoDrivers = [ "nvidia" ];
- hardware.nvidia = {
- modesetting.enable = true; # Required
- powerManagement.enable = false; # Can cause bugs
- nvidiaSettings = true;
- open = false; # Not compatible w/ GTX-1050
- package = config.boot.kernelPackages.nvidiaPackages.stable; # Still good for 1050
- };
-
- system.stateVersion = "24.11";
-}
diff --git a/modules/hosts/optiplex/home.nix b/modules/hosts/optiplex/home.nix
deleted file mode 100644
index ee56dc6..0000000
--- a/modules/hosts/optiplex/home.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{ ... }: {
- imports = [ ../../home ];
-
- theme.mint.theme.color = "Dark-Green";
- theme.mint.icons.color = "Green";
- wallpapers.enable = true;
-}
diff --git a/modules/root/autologin.nix b/modules/root/autologin.nix
deleted file mode 100644
index 6e66160..0000000
--- a/modules/root/autologin.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-{ lib, config, userDetails, ... }: {
- options = {
- autologin.enable = lib.mkEnableOption "enables getty automatic login";
- #autologin.user = lib.mkEnableOption "populate username";
- #autologin.password = lib.mkEnableOption "populate password";
- };
-
- config = lib.mkIf config.autologin.enable {
- services.getty = {
- autologinUser = userDetails.username;
- };
- };
-}
diff --git a/modules/root/bluetooth.nix b/modules/root/bluetooth.nix
deleted file mode 100644
index d55eade..0000000
--- a/modules/root/bluetooth.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ lib, config, ... }: {
- options = {
- bluetooth.enable = lib.mkEnableOption "enables bluetooth support";
- };
-
- config = {
- hardware.bluetooth.enable = true;
- services.blueman.enable = true;
- };
-}
diff --git a/modules/root/bootloader.nix b/modules/root/bootloader.nix
deleted file mode 100644
index 0a45264..0000000
--- a/modules/root/bootloader.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-{ lib, config, ... }: {
- options = {
- bootloader.loader = lib.mkOption {
- type = lib.types.enum [ "grub" "systemd-boot" ];
- default = "systemd-boot";
- description = "whether to install grub or systemd-boot as the bootloader";
- };
- bootloader.mode = lib.mkOption {
- type = lib.types.enum [ "efi" "bios" ];
- default = "efi";
- description = "whether to install the bootloader in efi or bios mode";
- };
- bootloader.grub = {
- biosDevice = lib.mkOption {
- type = lib.types.str;
- description = "device to install grub on";
- };
- };
- bootloader.memtest86.enable = lib.mkEnableOption "make Memtest86+ available from the bootloader";
- };
-
- config = {
- boot.loader = {
- grub = {
- enable = config.bootloader.loader == "grub";
- efiSupport = config.bootloader.mode == "efi";
- efiInstallAsRemovable = config.bootloader.mode == "efi";
- device = if config.bootloader.mode == "bios" then config.bootloader.grub.biosDevice else "nodev";
- enableCryptodisk = true;
- memtest86.enable = config.bootloader.memtest86.enable;
- };
- systemd-boot = {
- enable = config.bootloader.loader == "systemd-boot";
- editor = false;
- memtest86.enable = config.bootloader.memtest86.enable;
- };
- efi = lib.mkIf (config.bootloader.mode == "efi") {
- efiSysMountPoint = lib.mkIf (config.bootloader.loader == "grub") "/boot/efi";
- canTouchEfiVariables = true;
- };
- };
- };
-}
diff --git a/modules/root/default.nix b/modules/root/default.nix
deleted file mode 100644
index e108318..0000000
--- a/modules/root/default.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-{ lib, ... }: {
- imports = [
- ./software
- ./autologin.nix
- ./bluetooth.nix
- ./bootloader.nix
- ./doas.nix
- ./fonts.nix
- ./home-manager.nix
- ./hosts.nix
- ./localization.nix
- ./nas.nix
- ./nix.nix
- ./normaluser.nix
- ./pipewire.nix
- ./powerkeys.nix
- ./printing.nix
- ./secrets.nix
- ./ssh.nix
- ./tlp.nix
- ./wifi.nix
- ./x11.nix
- ./zsh.nix
- ];
-
- autologin.enable = lib.mkDefault true;
- avahi.enable = lib.mkDefault true;
- bluetooth.enable = lib.mkDefault false;
- doas.enable = lib.mkDefault true;
- fonts.enable = lib.mkDefault true;
- nas = {
- enable = lib.mkDefault false;
- home.enable = lib.mkDefault true;
- home.automount = lib.mkDefault false;
- office.enable = lib.mkDefault false;
- office.automount = lib.mkDefault false;
- };
- bootloader = {
- mode = lib.mkDefault "bios";
- memtest86.enable = lib.mkDefault true;
- };
- home-manager.enable = lib.mkDefault true;
- pipewire.enable = lib.mkDefault true;
- printing.enable = lib.mkDefault true;
- tlp.enable = lib.mkDefault true;
- scanning.enable = lib.mkDefault true;
- wifi.enable = lib.mkDefault true;
- xserver.enable = lib.mkDefault true;
- zsh.enable = lib.mkDefault true;
-}
diff --git a/modules/root/doas.nix b/modules/root/doas.nix
deleted file mode 100644
index c6707ce..0000000
--- a/modules/root/doas.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-{ lib, config, ... }: {
- options = {
- doas.enable = lib.mkEnableOption "enables doas";
- sudo.enable = lib.mkEnableOption "enables sudo";
- };
-
- config = {
- security = {
- #sudo.enable = config.sudo.enable;
- sudo.enable = true; # TODO remove once can be built from flake w git
- sudo.wheelNeedsPassword = false;
- doas.enable = config.doas.enable;
- doas.extraRules = lib.mkIf config.doas.enable [{
- keepEnv = true;
- }];
- doas.wheelNeedsPassword = false;
- };
- };
-}
diff --git a/modules/root/fonts.nix b/modules/root/fonts.nix
deleted file mode 100644
index 3ae8eef..0000000
--- a/modules/root/fonts.nix
+++ /dev/null
@@ -1,16 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- fonts.enable = lib.mkEnableOption "enables fonts";
- };
-
- config = lib.mkIf config.fonts.enable {
- fonts.packages = with pkgs; [
- commit-mono
- inter
- tamzen
- (nerdfonts.override {
- fonts = [ "JetBrainsMono" ];
- })
- ];
- };
-}
diff --git a/modules/root/home-manager.nix b/modules/root/home-manager.nix
deleted file mode 100644
index d271523..0000000
--- a/modules/root/home-manager.nix
+++ /dev/null
@@ -1,11 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- home-manager.enable = lib.mkEnableOption "enables home-manager";
- };
-
- config = lib.mkIf config.home-manager.enable {
- environment.systemPackages = with pkgs; [
- home-manager
- ];
- };
-}
diff --git a/modules/root/hosts.nix b/modules/root/hosts.nix
deleted file mode 100644
index 14daaf1..0000000
--- a/modules/root/hosts.nix
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- networking.hosts = {
- "192.168.1.9" = [ "optiplex" ];
- "192.168.1.30" = [ "localgit" ];
- "192.168.1.11" = [ "truenas-home" ];
- "192.168.77.11" = [ "truenas-office" ];
- "192.168.77.8" = [ "publicgit" "tjkeller" ];
- "173.9.253.3" = [
- "git.tjkeller.xyz"
- "piped.tjkeller.xyz"
- "search.tjkeller.xyz"
- "tjkeller.xyz"
- ];
- };
-}
diff --git a/modules/root/localization.nix b/modules/root/localization.nix
deleted file mode 100644
index 8313f07..0000000
--- a/modules/root/localization.nix
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- time.timeZone = "America/Chicago";
- i18n.defaultLocale = "en_US.UTF-8";
- services.xserver.xkb.layout = "us";
-}
diff --git a/modules/root/nas.nix b/modules/root/nas.nix
deleted file mode 100644
index 0116ba3..0000000
--- a/modules/root/nas.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-{ lib, config, ... } :
-let
- mkNetworkFileSystem = device: automount: {
- device = "${device}";
- fsType = "nfs";
- options = [ "defaults" ] ++ lib.optionals (!automount) [ "noauto" ];
- };
-in {
- options = {
- nas = {
- enable = lib.mkEnableOption "enable network shares";
- home = {
- enable = lib.mkEnableOption "enable home network shares";
- automount = lib.mkEnableOption "automount home network shares";
- };
- office = {
- enable = lib.mkEnableOption "enable office network shares";
- automount = lib.mkEnableOption "automount home network shares";
- };
- };
- };
-
- config = lib.mkIf config.nas.enable {
- fileSystems = lib.optionalAttrs config.nas.home.enable {
- "/media/Storage/Media" = mkNetworkFileSystem "truenas-home:/mnt/Storage/Media" config.nas.home.automount;
- "/media/Storage/Backups" = mkNetworkFileSystem "truenas-home:/mnt/Storage/Backups" config.nas.home.automount;
- "/media/Storage/Tapes" = mkNetworkFileSystem "truenas-home:/mnt/Storage/Backups/Tapes" config.nas.home.automount;
- "/media/Family Photos" = mkNetworkFileSystem "truenas-home:/mnt/Media/Photos" config.nas.home.automount;
- } // lib.optionalAttrs config.nas.office.enable {
- "/media/chexx/chexx" = mkNetworkFileSystem "truenas-office:/mnt/Storage/chexx" config.nas.office.automount;
- "/media/chexx/tkdocs" = mkNetworkFileSystem "truenas-office:/mnt/Storage/Users/Tim-Keller" config.nas.office.automount;
- "/media/chexx/scans" = mkNetworkFileSystem "truenas-office:/mnt/Storage/Scans" config.nas.office.automount;
- };
- # TODO auto mkdirz
- };
-}
diff --git a/modules/root/normaluser.nix b/modules/root/normaluser.nix
deleted file mode 100644
index fc243ea..0000000
--- a/modules/root/normaluser.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ config, userDetails, ... }: {
- users.users.root = {
- hashedPasswordFile = config.sops.secrets.hashed-root-password.path;
- };
- users.users.${userDetails.username} = {
- description = userDetails.fullname;
- #home = userDetails.home.root;
- isNormalUser = true;
- hashedPasswordFile = config.sops.secrets.hashed-root-password.path;
- extraGroups = [ "wheel" "nixbld" ];
- };
-}
diff --git a/modules/root/pipewire.nix b/modules/root/pipewire.nix
deleted file mode 100644
index fd97d42..0000000
--- a/modules/root/pipewire.nix
+++ /dev/null
@@ -1,12 +0,0 @@
-{ lib, config, ... }: {
- options = {
- pipewire.enable = lib.mkEnableOption "enables pipewire";
- };
-
- config = lib.mkIf config.pipewire.enable {
- services.pipewire = {
- enable = true;
- pulse.enable = true;
- };
- };
-}
diff --git a/modules/root/powerkeys.nix b/modules/root/powerkeys.nix
deleted file mode 100644
index fd4aa00..0000000
--- a/modules/root/powerkeys.nix
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- services.logind = {
- hibernateKey = "suspend";
- powerKey = "ignore";
- powerKeyLongPress = "poweroff";
- };
-}
diff --git a/modules/root/resources/secrets/secrets.yaml b/modules/root/resources/secrets/secrets.yaml
deleted file mode 100644
index 03f9517..0000000
--- a/modules/root/resources/secrets/secrets.yaml
+++ /dev/null
@@ -1,22 +0,0 @@
-wpa_supplicant-conf: ENC[AES256_GCM,data:0FI1Re1PbiJmtsqb5Ddj1g/e22FkSOxHtbhchybFJAn1Q6PBYpMM/myMUQqZDqCNDhR8f+b2LYcrFx+c9g+yDsR9VcgVe/NK1U5jvep5go3JIibR0NmtfPVZNMvThmVnzO+6aGtggjN8PTx4nm+GKzf7YZPV/buYRdWExJRf0loXgNM8iLtjnu1QGZjWNBtFGbTRHeiax1QhvPrawp76PNrdpzD3EkY26HZ2TRfXFP8ta93T43sac9iVj+U3ggn9MTNvLDGiOF1lAN/W69EeCnyzw3sbxCsuSYFQ4GKkggaehje58VpsG7rZMIHYhI6PQcctO6WUupBi80KcBCnQQKy0Pir7GUPwhw1NPKuOgTX9Otz1dXxJgNk+gA4NTmyCWh2LO0utW0bjAAoqhn5sti5TssxqT2q+bw8KiAqRMZKyj8JDKg4cpMCVUbSH4fcIK1ADXa0OzItgVZnhEKJeD0SYqScXoRlExiDxHG/yrYuJYKtUQSrYJXjJJcTreTyMXWUh1E/nvS7egnXFMYYxDdTvJ5bQ/Zp94FI+twhNfMxKF5qo2gcGUHjQu/M=,iv:LKr6fcQ2emSjQmEt1HgyLpFLg4ZxDOVgJEfkm4nQzbY=,tag:M+oo8dpWclIRaPyW17Ldwg==,type:str]
-hashed-root-password: ENC[AES256_GCM,data:KUoB8Z0ifh7lE9ir9AqkiMRHfw6rusXw3KC1dLIRd4YpbTiNI+cAdC474LR721+LNWoj5ZytSdDsVyS+t3o076rV4sgWgL17jPPf+H2KE5FOmQKYTUiHfSBsLKyyhpie4tpFJWv/3cCW8Q==,iv:0sZPz3V7IqTGbF3Fnm+FbgBS3GTnHsRx0OzIoAE1H64=,tag:H6CQlANfiD6ZuQhONKyMAQ==,type:str]
-sops:
- kms: []
- gcp_kms: []
- azure_kv: []
- hc_vault: []
- age:
- - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
- enc: |
- -----BEGIN AGE ENCRYPTED FILE-----
- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAvTUlBZXQ5Z080UUxoUGdr
- dm9hRE5uTzFwWXhOWkJnbXNvazd1UnplcUdZCnRKQ3RVT1RGZURLYUxINStBSU4x
- bUZudFp2SC9DSkVhNTRHV0MrRFFMckEKLS0tIGNBb3FLQVJsTGVsY3hMdy94WWZx
- UkJ1SGJrWXNtbmlmc2c4M1IxdUpVOWMKjaakq+n8ZijGjaNVM8/dQApaVFp9+q3K
- nhvon4p5KUFE+myABnEknaSZ5UcvW6ZLff9AB7l35NZhGXAhv+y6HA==
- -----END AGE ENCRYPTED FILE-----
- lastmodified: "2025-01-09T16:42:38Z"
- mac: ENC[AES256_GCM,data:LUBRGB/NdT2Lvrecb4w3Xbq4ulMyhHwNjuGyH/fjFJOcNfOCNmwaxIRN59CBi65UxGe93mgYYKJtbCKUZA9JhEfC81e+wkD0ZpEaNBu2YAYetf6hE9LqlYO05QIf/qwXySkCXRKdDl5afcmBVXTj+6qDEljkGtWX7CPLlodvuSQ=,iv:EfYL215e52Ir3SSTba7WGFSTQHgtqzyfWUWTBS+lwrU=,tag:VjE1o7WCT/PWIxk2/b/eow==,type:str]
- pgp: []
- unencrypted_suffix: _unencrypted
- version: 3.9.2
diff --git a/modules/root/resources/x11/xinit-startx-xdg.patch b/modules/root/resources/x11/xinit-startx-xdg.patch
deleted file mode 100644
index c1bca97..0000000
--- a/modules/root/resources/x11/xinit-startx-xdg.patch
+++ /dev/null
@@ -1,14 +0,0 @@
-diff --git a/startx.cpp b/startx.cpp
-index dfbebe1..472a1b0 100644
---- a/startx.cpp
-+++ b/startx.cpp
-@@ -272,7 +272,7 @@ if [ x"$enable_xauth" = x1 ] ; then
- dummy=0
-
- XCOMM create a file with auth information for the server. ':0' is a dummy.
-- xserverauthfile=$HOME/.serverauth.$$
-+ xserverauthfile="${XAUTHORITY:-$HOME/.Xauthority}"
- trap "rm -f '$xserverauthfile'" HUP INT QUIT ILL TRAP BUS TERM
- xauth -q -f "$xserverauthfile" << EOF
- add :$dummy . $mcookie
-
diff --git a/modules/root/software/awesome.nix b/modules/root/software/awesome.nix
deleted file mode 100644
index fdc86e8..0000000
--- a/modules/root/software/awesome.nix
+++ /dev/null
@@ -1,20 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- xserver.awesome.enable = lib.mkEnableOption "enables awesomewm";
- };
-
- config = lib.mkIf (config.xserver.enable && config.xserver.awesome.enable ) {
- services.xserver.windowManager.awesome = {
- enable = true;
- noArgb = true; # disables transparency. why not?
- luaModules = with pkgs.luajitPackages; [
- lgi
- ];
- package = with pkgs; awesome.override {
- gtk3Support = true;
- gtk3 = gtk3;
- lua = luajit;
- };
- };
- };
-}
diff --git a/modules/root/software/default.nix b/modules/root/software/default.nix
deleted file mode 100644
index 8d1e987..0000000
--- a/modules/root/software/default.nix
+++ /dev/null
@@ -1,34 +0,0 @@
-{ lib, config, ... }: {
- imports = [
- ./awesome.nix
- ./ddcutil.nix
- ./desktop.nix
- ./development.nix
- ./docker.nix
- ./system.nix
- ./utilities.nix
- ./virtualisation.nix
- ];
-
- software.desktop = {
- enable = lib.mkDefault config.xserver.enable;
- chromium.enable = lib.mkDefault false;
- cad.enable = lib.mkDefault false;
- crypto.enable = lib.mkDefault false;
- firefox.enable = lib.mkDefault true;
- graphics.enable = lib.mkDefault false;
- office.enable = lib.mkDefault false;
- utilities.enable = lib.mkDefault false;
- };
-
- software.development = {
- enable = lib.mkDefault true;
- docker = {
- enable = lib.mkDefault false;
- btrfsSupport = lib.mkDefault true;
- };
- };
-
- virtualisation.enable = lib.mkDefault false;
- xserver.awesome.enable = lib.mkDefault true;
-}
diff --git a/modules/root/software/derivations b/modules/root/software/derivations
deleted file mode 120000
index a075779..0000000
--- a/modules/root/software/derivations
+++ /dev/null
@@ -1 +0,0 @@
-../../../derivations \ No newline at end of file
diff --git a/modules/root/software/desktop.nix b/modules/root/software/desktop.nix
deleted file mode 100644
index 88101d8..0000000
--- a/modules/root/software/desktop.nix
+++ /dev/null
@@ -1,79 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- software.desktop = {
- enable = lib.mkEnableOption "enables desktop apps";
- chromium = {
- enable = lib.mkEnableOption "enables selected chromium browser package";
- package = lib.mkOption {
- type = lib.types.package;
- default = pkgs.ungoogled-chromium;
- description = "chromium package to install";
- };
- };
- cad.enable = lib.mkEnableOption "enables cad and 3d printing apps";
- crypto.enable = lib.mkEnableOption "enables crypto wallet apps";
- firefox.enable = lib.mkEnableOption "enables firefox";
- graphics.enable = lib.mkEnableOption "enables graphic design apps";
- office.enable = lib.mkEnableOption "enables office apps";
- utilities.enable = lib.mkEnableOption "enables miscellaneous utility apps";
- };
- };
-
- config = lib.mkIf config.software.desktop.enable {
- environment.systemPackages = with pkgs; [
- # Default
- arandr
- dmenu
- libnotify
- mpv
- pavucontrol
- pcmanfm
- redshift
- scrot
- sxiv
- wpa_supplicant_gui
- zathura
- (callPackage ./derivations/st {})
- ] ++ pkgs.lib.optionals config.software.desktop.chromium.enable [
- # Chrome
- config.software.desktop.chromium.package
- ] ++ pkgs.lib.optionals config.software.desktop.cad.enable [
- # CAD
- blender
- freecad
- prusa-slicer
- ] ++ pkgs.lib.optionals config.software.desktop.crypto.enable [
- # Crypto Wallets
- bisq2
- electrum
- monero-gui
- ] ++ pkgs.lib.optionals config.software.desktop.graphics.enable [
- # Graphics
- blender
- geeqie
- gimp
- inkscape
- ] ++ pkgs.lib.optionals config.software.desktop.office.enable [
- # Office
- libreoffice
- pdfchain
- thunderbird
- ] ++ pkgs.lib.optionals config.software.desktop.utilities.enable [
- # Misc Utilities
- jellyfin-mpv-shim
- qbittorrent
- qdirstat
- remmina
- ];
-
- programs.localsend.enable = config.software.desktop.utilities.enable; # Installs & opens firewall
- programs.firefox.enable = config.software.desktop.firefox.enable;
-
- # GVfs allows for mounting drives in a graphical file manager
- services.gvfs.enable = true;
-
- # For home-manager to configure gtk
- # TODO this should be there instead
- programs.dconf.enable = config.software.desktop.enable;
- };
-}
diff --git a/modules/root/software/development.nix b/modules/root/software/development.nix
deleted file mode 100644
index 2a4dfba..0000000
--- a/modules/root/software/development.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- software.development.enable = lib.mkEnableOption "enables development tools";
- };
-
- config = lib.mkIf config.software.development.enable {
- environment.systemPackages = with pkgs; [
- adb-sync
- android-tools
- gcc
- git
- gnumake
- hugo
- lua
- pkg-config
- ];
- };
-}
diff --git a/modules/root/software/docker.nix b/modules/root/software/docker.nix
deleted file mode 100644
index bf1898c..0000000
--- a/modules/root/software/docker.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-{ pkgs, lib, config, userDetails, ... }: {
- options = {
- software.development.docker = {
- enable = lib.mkEnableOption "enables docker";
- btrfsSupport = lib.mkEnableOption "changes docker storageDriver to btrfs";
- };
- };
-
- config = lib.mkIf config.software.development.docker.enable {
- virtualisation.docker = {
- enable = true;
- storageDriver = lib.mkIf config.software.development.docker.btrfsSupport "btrfs";
- };
-
- environment.systemPackages = with pkgs; [
- docker-compose
- ];
-
- users.groups.docker.members = [ userDetails.username ];
- };
-}
diff --git a/modules/root/software/system.nix b/modules/root/software/system.nix
deleted file mode 100644
index f0a31f2..0000000
--- a/modules/root/software/system.nix
+++ /dev/null
@@ -1,13 +0,0 @@
-{ pkgs, ... }: {
- environment.systemPackages = with pkgs; [
- age # Secrets
- cryptsetup
- dash # TODO should be default /bin/sh
- exfat
- git # Needed for home-manager
- python3
- sops # Secrets
- ];
-
- services.gpm.enable = true;
-}
diff --git a/modules/root/software/virtualisation.nix b/modules/root/software/virtualisation.nix
deleted file mode 100644
index 4ae15f5..0000000
--- a/modules/root/software/virtualisation.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ lib, config, ... }: {
- options = {
- virtualisation.enable = lib.mkEnableOption "enables virtualisation and virt-manager";
- };
-
- config = lib.mkIf config.virtualisation.enable {
- virtualisation.libvirtd.enable = true;
- programs.virt-manager.enable = config.software.desktop.enable;
- };
-}
diff --git a/modules/root/ssh.nix b/modules/root/ssh.nix
deleted file mode 100644
index 9f6d54d..0000000
--- a/modules/root/ssh.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- services.openssh = {
- enable = true;
- settings = {
- X11Forwarding = true;
- };
- };
-}
diff --git a/modules/root/tlp.nix b/modules/root/tlp.nix
deleted file mode 100644
index 3414c03..0000000
--- a/modules/root/tlp.nix
+++ /dev/null
@@ -1,9 +0,0 @@
-{ lib, config, ... }: {
- options = {
- tlp.enable = lib.mkEnableOption "enables tlp";
- };
-
- config = lib.mkIf config.tlp.enable {
- services.tlp.enable = true;
- };
-}
diff --git a/modules/root/wifi.nix b/modules/root/wifi.nix
deleted file mode 100644
index 542cfd7..0000000
--- a/modules/root/wifi.nix
+++ /dev/null
@@ -1,18 +0,0 @@
-{ lib, config, ... }: {
- options = {
- wifi.enable = lib.mkEnableOption "enables wifi";
- };
-
- config = lib.mkIf config.wifi.enable {
- networking.wireless = {
- enable = true; # Enables wireless support via wpa_supplicant.
- userControlled.enable = true;
- allowAuxiliaryImperativeNetworks = true; # Networks defined in aux imperitive networks (/etc/wpa_supplicant.conf)
- };
-
- # Link /etc/wpa_supplicant.conf -> secret config
- environment.etc."wpa_supplicant.conf" = {
- source = config.sops.secrets.wpa_supplicant-conf.path;
- };
- };
-}
diff --git a/modules/root/x11.nix b/modules/root/x11.nix
deleted file mode 100644
index fd15c52..0000000
--- a/modules/root/x11.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-{ pkgs, lib, config, ... }: {
- options = {
- xserver.enable = lib.mkEnableOption "enables xserver";
- };
-
- config = lib.mkIf config.xserver.enable {
- services.xserver.enable = true;
- services.xserver.displayManager.startx.enable = true;
- services.libinput.enable = true; # Enable touchpad support
-
- environment.systemPackages = with pkgs; [
- unclutter
- xcape
- xclip
- xdotool
- xorg.setxkbmap
- xorg.xinput
- xorg.xkill
- xorg.xrandr
- xorg.xset
- xwallpaper
- # Patch startx to be compliant with xdg base dir spec
- (xorg.xinit.overrideAttrs (old: rec {
- patches = [
- ./resources/x11/xinit-startx-xdg.patch
- ];
- }))
- ];
- };
-}
diff --git a/modules/root/zsh.nix b/modules/root/zsh.nix
deleted file mode 100644
index 697cb4c..0000000
--- a/modules/root/zsh.nix
+++ /dev/null
@@ -1,10 +0,0 @@
-{ lib, config, pkgs, ... }: {
- options = {
- zsh.enable = lib.mkEnableOption "use zsh as default shell";
- };
-
- config = lib.mkIf config.zsh.enable {
- programs.zsh.enable = true;
- users.defaultUserShell = pkgs.zsh;
- };
-}
diff --git a/nixos/bootloader.nix b/nixos/bootloader.nix
new file mode 100644
index 0000000..333f584
--- /dev/null
+++ b/nixos/bootloader.nix
@@ -0,0 +1,60 @@
+{ lib, config, ... }: let
+ cfg = config.boot._loader;
+in {
+ options.boot._loader = {
+ enable = lib.mkEnableOption "enable unified bootloader config";
+ loader = lib.mkOption {
+ type = lib.types.enum [ "grub" "systemd-boot" ];
+ default = "systemd-boot";
+ description = "whether to install grub or systemd-boot as the bootloader";
+ };
+ mode = lib.mkOption {
+ type = lib.types.enum [ "efi" "bios" ];
+ default = "efi";
+ description = "whether to install the bootloader in efi or bios mode";
+ };
+ grub = {
+ biosDevice = lib.mkOption {
+ type = lib.types.str;
+ description = "device to install grub on";
+ };
+ zfsSupport = lib.mkEnableOption "zfs boot device support";
+ };
+ memtest86.enable = lib.mkEnableOption "make Memtest86+ available from the bootloader";
+ };
+
+ config = let
+ usingEfi = cfg.mode == "efi";
+ usingBios = cfg.mode == "bios";
+ usingGrub = cfg.loader == "grub";
+ usingSystemdBoot = cfg.loader == "systemd-boot";
+ in lib.mkIf cfg.enable {
+ boot.loader = {
+ grub = lib.mkIf usingGrub {
+ enable = true;
+ # bios
+ device = lib.mkIf usingBios cfg.grub.biosDevice;
+ # efi
+ efiSupport = usingEfi;
+ efiInstallAsRemovable = usingEfi;
+ # zfs
+ zfsSupport = cfg.grub.zfsSupport;
+ mirroredBoots = lib.mkIf cfg.grub.zfsSupport [
+ { devices = [ "nodev" ]; path = "/boot"; }
+ ];
+ # misc
+ enableCryptodisk = true;
+ memtest86.enable = cfg.memtest86.enable;
+ };
+ systemd-boot = lib.mkIf usingSystemdBoot {
+ enable = true;
+ editor = false;
+ memtest86.enable = cfg.memtest86.enable;
+ };
+ efi = lib.mkIf usingEfi {
+ efiSysMountPoint = lib.mkIf usingGrub "/boot/efi";
+ canTouchEfiVariables = lib.mkIf usingSystemdBoot true;
+ };
+ };
+ };
+}
diff --git a/nixos/decklink.nix b/nixos/decklink.nix
new file mode 100644
index 0000000..d86fde3
--- /dev/null
+++ b/nixos/decklink.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }: let
+ cfg = config.services._decklink;
+in {
+ options.services._decklink = {
+ enable = lib.mkEnableOption "enables decklink support";
+ decklinkSupport.enable = lib.mkEnableOption "enable decklinkSupport flag for all packages that support it by default";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Kernel modules
+ hardware.decklink.enable = true;
+
+ # DesktopVideoHelper
+ environment.systemPackages = [ pkgs.blackmagic-desktop-video ];
+
+ # Enable decklinkSupport flag globally for all packages that support it (e.g. obs)
+ nixpkgs.overlays = [
+ (final: prev: { decklinkSupport = cfg.decklinkSupport.enable; })
+ ];
+ };
+}
diff --git a/nixos/default.nix b/nixos/default.nix
new file mode 100644
index 0000000..ea8a317
--- /dev/null
+++ b/nixos/default.nix
@@ -0,0 +1,28 @@
+{
+ imports = [
+ ./programs/awesome.nix
+ ./programs/ddcutil.nix
+
+ ./services/cgit.nix
+ ./services/fileshares.nix
+ ./services/mailer.nix
+ ./services/searxng.nix
+ ./services/zfs/zed-mailer.nix
+ ./services/router
+
+ #./services/mainsail.nix
+
+ ./bootloader.nix
+ ./decklink.nix
+ ./geoclue.nix
+ ./hosts.nix
+ ./net-iface-labels.nix
+ ./nix.nix
+ ./polkit.nix
+ ./powerkeys.nix
+ ./secrets.nix
+ ./suspend.nix
+ ./wireless.nix
+ ./zshenv.nix
+ ];
+}
diff --git a/nixos/geoclue.nix b/nixos/geoclue.nix
new file mode 100644
index 0000000..344ef24
--- /dev/null
+++ b/nixos/geoclue.nix
@@ -0,0 +1,14 @@
+{ config, lib, ... }: let
+ cfg = config.services._geoclue2;
+in {
+ options.services._geoclue2 = {
+ enable = lib.mkEnableOption "enable geoclue2 service with minimal sources and set timezone automatically";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.geoclue2 = {
+ enable = true;
+ };
+ services.automatic-timezoned.enable = lib.mkDefault true;
+ };
+}
diff --git a/nixos/hosts.nix b/nixos/hosts.nix
new file mode 100644
index 0000000..a87f3b4
--- /dev/null
+++ b/nixos/hosts.nix
@@ -0,0 +1,4 @@
+{ hostname, ... }: {
+ networking.hostName = hostname; # From flake.nix
+ environment.etc.hosts.mode = "0644"; # Allow temporary imperative modifications
+}
diff --git a/nixos/net-iface-labels.nix b/nixos/net-iface-labels.nix
new file mode 100644
index 0000000..b7ac655
--- /dev/null
+++ b/nixos/net-iface-labels.nix
@@ -0,0 +1,28 @@
+{ config, lib, ... }: let
+ cfg = config.networking._interfaceLabels;
+ validMac = str: builtins.match ''^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$'' str != null;
+ macAddressType = lib.types.str // {
+ check = validMac;
+ description = "a mac address (xx:xx:xx:xx:xx:xx)";
+ };
+in {
+ options.networking._interfaceLabels = {
+ enable = lib.mkEnableOption "manually label network interfaces";
+ interfaces = lib.mkOption {
+ type = lib.types.attrsOf macAddressType;
+ default = {};
+ example = {
+ "lan0" = "00:11:22:33:44:55";
+ "lan1" = "66:77:88:99:AA:BB";
+ };
+ description = "label network interfaces by their mac address";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ networking.usePredictableInterfaceNames = false;
+ services.udev.extraRules = lib.concatStringsSep "\n" (
+ lib.mapAttrsToList (name: mac: ''ACTION=="add", SUBSYSTEM=="net", ATTR{address}=="${mac}", NAME="${name}"'') cfg.interfaces
+ );
+ };
+}
diff --git a/modules/root/nix.nix b/nixos/nix.nix
index ff8dd28..ff8dd28 100644
--- a/modules/root/nix.nix
+++ b/nixos/nix.nix
diff --git a/nixos/polkit.nix b/nixos/polkit.nix
new file mode 100644
index 0000000..d78130a
--- /dev/null
+++ b/nixos/polkit.nix
@@ -0,0 +1,48 @@
+{ lib, config, pkgs, ... }: let
+ cfg = config.security.polkit;
+
+ # This authentication agent will only autostart in a select few environments (e.g. GNOME, XFCE) by default.
+ # This derivation will allow the polkit_gnome agent to start in any environment so long as it is enabled.
+ polkit_gnome-autostart = pkgs.stdenv.mkDerivation {
+ name = "polkit_gnome-autostart";
+ priority = 5;
+
+ # Copy the autostart desktop entry and replace OnlyShowIn with NotShowIn to invert the selection.
+ # The default one will still select the same environments, but this new one will select the inverse,
+ # e.g. any environment that is not listed in the stock desktop entry.
+ buildCommand = ''
+ mkdir -p $out/etc/xdg/autostart
+ cp ${cfg._gnome.package}/etc/xdg/autostart/polkit-gnome-authentication-agent-1.desktop $out/etc/xdg/autostart/polkit-gnome-authentication-agent-1-de-agnostic.desktop
+ substituteInPlace $out/etc/xdg/autostart/polkit-gnome-authentication-agent-1-de-agnostic.desktop \
+ --replace-fail 'OnlyShowIn=' 'NotShowIn='
+ '';
+ };
+in {
+ options.security.polkit = {
+ _gnome = {
+ enable = lib.mkEnableOption "enables polkit_gnome authentication agent";
+ package = lib.mkPackageOption pkgs "polkit_gnome" { };
+ };
+ _allowUserPowerControls = lib.mkEnableOption "allow users to control the power state of the machine using poweroff/reboot commands";
+ _wheelNoPass = lib.mkEnableOption "allow users in wheel group to execute all commands without password";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Gnome
+ environment.systemPackages = lib.mkIf cfg._gnome.enable [ cfg._gnome.package polkit_gnome-autostart ];
+
+ # Optional rules
+ environment.etc = let
+ pkRulesD = "polkit-1/rules.d";
+ in {
+ "${pkRulesD}/10-reboot-poweroff-users.rules" = {
+ enable = cfg._allowUserPowerControls;
+ source = ./resources/polkit-1/rules.d/10-reboot-poweroff-users.rules;
+ };
+ "${pkRulesD}/0-wheel-no-pass.rules" = {
+ enable = cfg._wheelNoPass;
+ source = ./resources/polkit-1/rules.d/0-wheel-no-pass.rules;
+ };
+ };
+ };
+}
diff --git a/nixos/powerkeys.nix b/nixos/powerkeys.nix
new file mode 100644
index 0000000..040cfa8
--- /dev/null
+++ b/nixos/powerkeys.nix
@@ -0,0 +1,14 @@
+{ lib, config, ... }: let
+ cfg = config.services.logind._safePowerKey;
+in {
+ options.services.logind._safePowerKey = {
+ enable = lib.mkEnableOption "do not immediately shutdown when powerkey is pressed";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.logind.settings.Login = {
+ HandlePowerKey = "ignore"; # Allow for powerkey to activate power script
+ HandlePowerKeyLongPress = "poweroff";
+ };
+ };
+}
diff --git a/nixos/programs/awesome.nix b/nixos/programs/awesome.nix
new file mode 100644
index 0000000..580aec1
--- /dev/null
+++ b/nixos/programs/awesome.nix
@@ -0,0 +1,23 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config.services.xserver.windowManager._awesome;
+in {
+ options.services.xserver.windowManager._awesome = {
+ enable = lib.mkEnableOption "installs awesome window manager";
+ };
+
+ config = lib.mkIf cfg.enable {
+ services.xserver.windowManager.awesome = {
+ # TODO use git version of awesome, maybe version on archlinux, notify when new version available
+ enable = true;
+ noArgb = true; # disables transparency. why not?
+ luaModules = with pkgs.luajitPackages; [
+ lgi
+ ];
+ package = with pkgs; awesome.override {
+ gtk3Support = true;
+ gtk3 = gtk3;
+ lua = luajit;
+ };
+ };
+ };
+}
diff --git a/nixos/programs/ddcutil.nix b/nixos/programs/ddcutil.nix
new file mode 100644
index 0000000..ef1801b
--- /dev/null
+++ b/nixos/programs/ddcutil.nix
@@ -0,0 +1,12 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config.programs._ddcutil;
+in {
+ options.programs._ddcutil = {
+ enable = lib.mkEnableOption "enables ddcutil and i2c control";
+ };
+
+ config = lib.mkIf cfg.enable {
+ hardware.i2c.enable = true;
+ environment.systemPackages = [ pkgs.ddcutil ];
+ };
+}
diff --git a/nixos/resources/polkit-1/rules.d/0-wheel-no-pass.rules b/nixos/resources/polkit-1/rules.d/0-wheel-no-pass.rules
new file mode 100644
index 0000000..93abf0d
--- /dev/null
+++ b/nixos/resources/polkit-1/rules.d/0-wheel-no-pass.rules
@@ -0,0 +1,5 @@
+polkit.addRule(function(action, subject) {
+ if (subject.isInGroup("wheel")) {
+ return polkit.Result.YES;
+ }
+});
diff --git a/nixos/resources/polkit-1/rules.d/10-reboot-poweroff-users.rules b/nixos/resources/polkit-1/rules.d/10-reboot-poweroff-users.rules
new file mode 100644
index 0000000..ed9dbf7
--- /dev/null
+++ b/nixos/resources/polkit-1/rules.d/10-reboot-poweroff-users.rules
@@ -0,0 +1,17 @@
+polkit.addRule(function (action, subject) {
+ if (
+ subject.isInGroup("users") &&
+ [
+ "org.freedesktop.login1.reboot",
+ "org.freedesktop.login1.reboot-multiple-sessions",
+ "org.freedesktop.login1.power-off",
+ "org.freedesktop.login1.power-off-multiple-sessions",
+ "org.freedesktop.login1.suspend",
+ "org.freedesktop.login1.suspend-multiple-sessions",
+ "org.freedesktop.login1.hibernate",
+ "org.freedesktop.login1.hibernate-multiple-sessions",
+ ].indexOf(action.id) !== -1
+ ) {
+ return polkit.Result.YES;
+ }
+});
diff --git a/modules/root/secrets.nix b/nixos/secrets.nix
index 47262fd..3d21e62 100644
--- a/modules/root/secrets.nix
+++ b/nixos/secrets.nix
@@ -1,25 +1,17 @@
-{ pkgs, inputs, config, userDetails, ... }: {
- imports = [ inputs.sops-nix.nixosModules.sops ];
+{ inputs, config, pkgs, ... }: {
+ environment.systemPackages = with pkgs; [ age sops ];
sops = {
- defaultSopsFile = ./resources/secrets/secrets.yaml;
defaultSopsFormat = "yaml";
- age.keyFile = "${userDetails.home.root}/.config/sops/age/keys.txt";
-
- secrets = {
- wpa_supplicant-conf = { };
- hashed-root-password = { };
- };
+ age.sshKeyPaths = [ "/root/.ssh/id_ed25519" ];
};
+
# This service is a workaround to ensure that secrets are available on
# reboot when the secret keys are on a separate subvolume
systemd.services.npcnix-force-rebuild-sops-hack = {
wantedBy = [ "multi-user.target" ];
- before = [
- # List all services that require secrets
- "wpa_supplicant.service"
- ];
+ before = [ "wpa_supplicant.service" ];
serviceConfig = {
ExecStart = "/run/current-system/activate";
Type = "oneshot";
diff --git a/nixos/services/cgit.nix b/nixos/services/cgit.nix
new file mode 100644
index 0000000..66de1d8
--- /dev/null
+++ b/nixos/services/cgit.nix
@@ -0,0 +1,142 @@
+{ lib, pkgs, config, ... }:
+let
+ cfg = config.services._cgit;
+in {
+ options.services._cgit = {
+ enable = lib.mkEnableOption "enables cgit service";
+ hostAddress = lib.mkOption {
+ type = lib.types.str;
+ description = "hostAddress for the container";
+ default = "10.0.1.1";
+ };
+ localAddress = lib.mkOption {
+ type = lib.types.str;
+ description = "localAddress for the container";
+ default = "10.0.1.2";
+ };
+ rootTitle = lib.mkOption {
+ type = lib.types.str;
+ description = "cgit site title";
+ default = "";
+ };
+ rootDesc = lib.mkOption {
+ type = lib.types.str;
+ description = "cgit site description";
+ default = "";
+ };
+ extraConfig = lib.mkOption {
+ type = lib.types.str;
+ description = "cgitrc lines inserted verbatim at the end";
+ default = "";
+ };
+ ssh.authorizedKeys = {
+ keys = lib.mkOption {
+ type = lib.types.listOf lib.types.singleLineStr;
+ default = [ ];
+ description = ''
+ see `options.openssh.authorizedKeys.keys`.
+ ssh authorized keys for git user in container.
+ '';
+ };
+ keyFiles = lib.mkOption {
+ type = lib.types.listOf lib.types.path;
+ default = [ ];
+ description = ''
+ see `options.openssh.authorizedKeys.keyFiles`.
+ ssh authorized keys for git user in container.
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Configure cgit container
+ containers.cgit = {
+ autoStart = true;
+ privateNetwork = true;
+ hostAddress = cfg.hostAddress;
+ localAddress = cfg.localAddress;
+
+ specialArgs = {
+ authorizedKeys = cfg.ssh.authorizedKeys;
+ cgitrc = with cfg; {
+ inherit rootTitle;
+ inherit rootDesc;
+ inherit extraConfig;
+ };
+ };
+
+ config = { lib, config, authorizedKeys, cgitrc, ... }: {
+ # Create git user for ssh access
+ users.users.git = {
+ isNormalUser = true;
+ home = "/srv/git"; # Serve from git user's home to allow cloning git@cgit:repo
+ group = "git";
+ createHome = true;
+ homeMode = "750"; # Allow read permissions for group members
+ shell = pkgs.bash;
+ openssh = { inherit authorizedKeys; };
+ };
+ users.groups.git.members = [ "lighttpd" ]; # Create the git group and add lighttpd user as a member so /srv/git can be served by cgit
+
+ # Enable git
+ programs.git.enable = true;
+
+ # Enable ssh service
+ services.openssh.enable = true;
+
+ # Enable cgit service
+ services.lighttpd.enable = true;
+ services.lighttpd.cgit = {
+ enable = true;
+ #subdir = ""; # FIXME this does not work for some reason
+ configText = ''
+ # Based on joseluisq/alpine-cgit
+ root-title=${cgitrc.rootTitle}
+ root-desc=${cgitrc.rootDesc}
+
+ source-filter=${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+ about-filter=${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+
+ readme=:README.md
+ readme=:README.html
+ readme=:README.txt
+ readme=:README
+ readme=:INSTALL.md
+ readme=:INSTALL.html
+ readme=:INSTALL.txt
+ readme=:INSTALL
+
+ # Cache
+ #cache-root=/var/cache/cgit
+ #cache-size=2000
+
+ enable-index-links=1
+ enable-index-owner=0
+ enable-remote-branches=1
+ enable-log-filecount=1
+ enable-log-linecount=1
+ enable-git-config=1
+ snapshots=tar.xz zip
+
+ robots=noindex, nofollow
+
+ virtual-root=/cgit
+ section-from-path=0
+ max-repo-count=100
+ scan-path=/srv/git
+
+ # extra config
+ ${cgitrc.extraConfig}
+ '';
+ };
+
+ # Networking, etc.
+ networking.firewall.allowedTCPPorts = [ 80 22 ];
+ networking.hostName = "cgit";
+
+ system.stateVersion = "25.05";
+ };
+ };
+ };
+}
diff --git a/nixos/services/fileshares.nix b/nixos/services/fileshares.nix
new file mode 100644
index 0000000..e61797f
--- /dev/null
+++ b/nixos/services/fileshares.nix
@@ -0,0 +1,150 @@
+{ lib, config, ... }: let
+ cfg = config.services._fileShares;
+
+ fileShareType = lib.types.submodule (
+ { name, ... }: {
+ options = {
+ enable = lib.mkEnableOption "enable this file share"; # FIXME mk default
+ name = lib.mkOption {
+ type = lib.types.str;
+ default = name;
+ description = "name of share";
+ };
+ path = lib.mkOption {
+ type = lib.types.str;
+ default = name;
+ description = "path to share";
+ };
+ readOnly = lib.mkEnableOption "make share read only";
+ allowHosts = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = [];
+ example = [ "192.168.1.100" "192.168.0.0/24" ];
+ description = "ip hosts to allow";
+ };
+ # TODO denyHosts etc
+ nfs = {
+ enable = lib.mkEnableOption "share file with nfs protocol";
+ };
+ smb = {
+ enable = lib.mkEnableOption "share file with smb protocol";
+ allowGuests = lib.mkEnableOption "allow unauthenticated users to mount";
+ allowUser = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "allow user";
+ };
+ allowGroup = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ description = "allow group";
+ };
+ extraOptions = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ default = {};
+ description = "extra smb options for this share";
+ };
+ };
+ };
+ }
+ );
+
+ # Shares
+ sambaShares = lib.filterAttrs (_: s: s.smb.enable) cfg.shares;
+ nfsShares = lib.filterAttrs (_: s: s.nfs.enable) cfg.shares;
+
+ # SMB
+ sambaGlobalSettings.global = let
+ hostname = config.networking.hostName;
+ in {
+ "workgroup" = lib.mkDefault "WORKGROUP";
+ "server string" = lib.mkDefault hostname;
+ "netbios name" = lib.mkDefault hostname;
+ "security" = lib.mkDefault "user";
+ #"use sendfile" = "yes";
+ #"max protocol" = "smb2";
+ "hosts allow" = lib.mkDefault "192.168.1. 127.0.0.1 localhost"; # TODO does this work?
+ "hosts deny" = lib.mkDefault "0.0.0.0/0";
+ "guest account" = lib.mkDefault "nobody";
+ "map to guest" = lib.mkDefault "bad user";
+ };
+ mkSambaShare = s: {
+ "path" = s.path;
+ "browsable" = lib.boolToYesNo true;
+ "read only" = lib.boolToYesNo s.readOnly;
+ "guest ok" = lib.boolToYesNo s.smb.allowGuests;
+ "create mask" = "0644"; # TODO configure
+ "directory mask" = "0755"; # TODO configure
+ # allow user/group
+ "force user" = lib.mkIf (s.smb.allowUser != null) s.smb.allowUser;
+ "force group" = lib.mkIf (s.smb.allowGroup != null) s.smb.allowGroup;
+ # allow hosts
+ "hosts deny" = lib.mkIf (s.allowHosts != []) "ALL";
+ "hosts allow" = lib.mkIf (s.allowHosts != []) (lib.concatStringsSep " " s.allowHosts);
+ } // s.smb.extraOptions;
+ sambaSettings = sambaGlobalSettings // lib.mapAttrs (_: v: mkSambaShare v) sambaShares;
+
+ # NFS
+ mkNFSShareHost = s: host: ''${host}(${if s.readOnly then "ro" else "rw"},sync,no_subtree_check)''; # TODO configure
+ mkNFSShare = s: ''
+ ${s.path} ${lib.concatMapStringsSep " " (mkNFSShareHost s) (if s.allowHosts == [] then ["*"] else s.allowHosts) }
+ '';
+ nfsExports = lib.concatMapAttrsStringSep "\n" (_: v: mkNFSShare v) nfsShares;
+in {
+ options.services._fileShares = {
+ enable = lib.mkEnableOption "protocol agnostic fileshares module";
+ shares = lib.mkOption {
+ type = lib.types.attrsOf fileShareType;
+ default = {};
+ description = "nfs/smb fileshares";
+ };
+ smb = {
+ openFirewall = lib.mkEnableOption "open firewall for smb";
+ };
+ nfs = {
+ enableNFSv3 = lib.mkEnableOption "enable support for nfsv3";
+ openFirewall = lib.mkEnableOption "open firewall for nfs(v4/v3)";
+ };
+ };
+
+ config = lib.mkIf cfg.enable (lib.mkMerge [
+ # SMB config
+ (lib.mkIf (sambaShares != []) {
+ services.samba = {
+ enable = true;
+ openFirewall = lib.mkDefault cfg.smb.openFirewall;
+ settings = sambaSettings;
+ };
+ })
+
+ # NFS(v4/v3) config
+ (lib.mkIf (nfsShares != []) {
+ services.nfs.server = lib.mkMerge [
+ # Base config
+ {
+ enable = true;
+ exports = nfsExports;
+ }
+
+ # NFSv3 additional config
+ (lib.mkIf cfg.nfs.enableNFSv3 {
+ # fixed rpc.statd port; for firewall
+ lockdPort = 4001;
+ mountdPort = 4002;
+ statdPort = 4000;
+ extraNfsdConfig = '''';
+ })
+ ];
+
+ # NFS firewall
+ networking.firewall = lib.mkIf cfg.nfs.openFirewall (
+ if cfg.nfs.enableNFSv3 then {
+ allowedTCPPorts = [ 111 2049 4000 4001 4002 20048 ];
+ allowedUDPPorts = [ 111 2049 4000 4001 4002 20048 ];
+ } else {
+ allowedTCPPorts = [ 2049 ];
+ }
+ );
+ })
+ ]);
+}
diff --git a/nixos/services/mailer.nix b/nixos/services/mailer.nix
new file mode 100644
index 0000000..fadaaf1
--- /dev/null
+++ b/nixos/services/mailer.nix
@@ -0,0 +1,67 @@
+{ lib, pkgs, config, ... }: let
+ cfg = config.services.mail._mailer;
+in {
+ options.services.mail._mailer = {
+ enable = lib.mkEnableOption "enable msmtp mailer service";
+ sender = {
+ host = lib.mkOption {
+ type = lib.types.str;
+ description = "";
+ example = "smtp.mail.example.com";
+ };
+ user = lib.mkOption {
+ type = lib.types.str;
+ description = "";
+ example = "myname@example.com";
+ };
+ from = lib.mkOption {
+ type = lib.types.str;
+ description = "";
+ example = "myname@example.com";
+ };
+ passwordFile = lib.mkOption {
+ type = lib.types.path;
+ description = "";
+ };
+ };
+ recipient = lib.mkOption {
+ type = lib.types.str;
+ description = "";
+ example = "admin@example.com";
+ };
+ };
+
+ # https://wiki.nixos.org/wiki/ZFS#Mail_notifications_(ZFS_Event_Daemon)
+ config = lib.mkIf cfg.enable {
+ # MTA
+ programs.msmtp = {
+ enable = true;
+ setSendmail = true;
+ defaults = {
+ aliases = "/etc/aliases";
+ port = 587;
+ auth = "plain";
+ tls = "on";
+ tls_starttls = "on";
+ };
+ accounts = {
+ default = with cfg.sender; {
+ inherit host user from;
+ passwordeval = "cat ${passwordFile}";
+ };
+ };
+ };
+
+ # Configure an alias for root account.
+ # With this alias configured, all mails sent to root, such as cron job
+ # results and failed sudo login events, will be redirected to the
+ # configured email account.
+ environment.etc.aliases.text = ''
+ root: ${cfg.recipient}
+ '';
+
+ # For zed enableMail, enable sendmailSetuidWrapper
+ services.mail.sendmailSetuidWrapper.enable = true;
+ };
+}
+
diff --git a/nixos/services/router/default.nix b/nixos/services/router/default.nix
new file mode 100644
index 0000000..4e59752
--- /dev/null
+++ b/nixos/services/router/default.nix
@@ -0,0 +1,8 @@
+{
+ imports = [
+ ./dns-dhcp.nix
+ ./hairpinning.nix
+ ./routing.nix
+ ./unbound-blocklist.nix
+ ];
+}
diff --git a/nixos/services/router/dns-dhcp.nix b/nixos/services/router/dns-dhcp.nix
new file mode 100644
index 0000000..1839b0f
--- /dev/null
+++ b/nixos/services/router/dns-dhcp.nix
@@ -0,0 +1,229 @@
+{ config, lib, ... }: let
+ cfg = config.services._router.dnsDhcpConfig;
+ staticLeaseType = lib.types.submodule (
+ { name, ... }: {
+ options = {
+ enable = lib.mkEnableOption "enable this static lease";
+ macAddress = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ example = "00:11:22:33:44:55";
+ };
+ hostname = lib.mkOption {
+ type = lib.types.str;
+ default = name;
+ };
+ staticIp = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ example = "192.168.1.2";
+ };
+ };
+
+ config.enable = lib.mkDefault true;
+ }
+ );
+in {
+ options.services._router.dnsDhcpConfig = {
+ enable = lib.mkEnableOption "enable pre-configured unbound(outbound) + dnsmasq(local) dns(+dhcp) server";
+ #enableUnbound = lib.mkEnableOption "enable unbound as outbound dns server";
+ dns = {
+ hostOverrides = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ default = {};
+ example = {
+ "poweredge.home.lan" = "192.168.1.10";
+ "router.home.lan" = "192.168.1.1";
+ "example.com" = "192.168.1.100";
+ };
+ description = "dns overrides for specific hosts";
+ };
+ };
+ dhcp = {
+ defaultGateway = lib.mkOption {
+ type = lib.types.str;
+ default = "192.168.1.1";
+ };
+ localhostIp = lib.mkOption {
+ type = lib.types.str;
+ default = "192.168.1.1";
+ description = "ip of the dns+dhcp host";
+ };
+ rangeStart = lib.mkOption {
+ type = lib.types.str;
+ default = "192.168.1.100";
+ description = "dhcp range start";
+ };
+ rangeEnd = lib.mkOption {
+ type = lib.types.str;
+ default = "192.168.1.250";
+ description = "dhcp range end";
+ };
+ rangeSubnetMask = lib.mkOption {
+ type = lib.types.str;
+ default = "255.255.255.0";
+ description = "dhcp range subnet";
+ };
+ leaseTime = lib.mkOption {
+ type = lib.types.str;
+ default = "12h";
+ description = "how long a dhcp lease will last. suffix is one of [h,m,s]";
+ };
+ staticLeases = lib.mkOption {
+ type = lib.types.attrsOf staticLeaseType;
+ default = {};
+ example = {
+ hostname0 = {
+ macAddress = "00:11:22:33:44:55";
+ staticIp = "192.168.1.6";
+ };
+ hostname1 = {
+ macAddress = "66:77:88:99:AA:BB";
+ staticIp = "192.168.1.7";
+ };
+ };
+ description = "dhcp static leases";
+ };
+ };
+ localDomain = lib.mkOption {
+ type = lib.types.str;
+ default = "home.lan";
+ description = "";
+ };
+ #ipv6 = lib.mkEnableOption "enable ipv6"; # TODO
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Unbound as a recursive DNS resolver.
+ # Preferred for its security/privacy features + performance
+ services.unbound = {
+ enable = true;
+ settings = {
+ server = {
+ # Listen on all interfaces (or specify specific IPs)
+ interface = [ "0.0.0.0" ]; # ++ lib.optionals cfg.ipv6 [ "::0" ];
+
+ # Allow queries from local networks
+ access-control = [
+ "127.0.0.0/8 allow"
+ "192.168.0.0/16 allow"
+ "10.0.0.0/8 allow"
+ "172.16.0.0/12 allow"
+ ];
+
+ # Cache settings
+ cache-min-ttl = 300; # 5 min
+ cache-max-ttl = 60 * 60 * 24; # 1 day
+
+ # Prefetch DNS records for better performance
+ prefetch-key = true;
+ prefetch = true;
+
+ # Enable DNSSEC validation (signed DNS, prevents man-in-the-middle attacks)
+ # `auto-trust-anchor-file` is set by default to "/var/unbound/root.key"
+ trust-anchor-file = ''""'';
+ domain-insecure = cfg.localDomain; # Local domain is not DNSSEC-signed
+ harden-below-nxdomain = true; # Protect against non-existent domain response attacks
+ harden-glue = true; # Protect against incorrect DNS glue record attacks
+ harden-dnssec-stripped = true; # Ensures that DNSSEC signatures are not stripped from DNS responses
+
+ # Allow queries to localhost for dnsmasq local domain resolution
+ do-not-query-localhost = false;
+
+ # Privacy / Security
+ harden-referral-path = true; # Prevents receiving data from servers that are outside the zone in the DNS referral chain
+ qname-minimisation = true; # Minimizes the amount of information exposed in DNS queries by only sending the essential parts of the query
+ hide-identity = true; # Hide software identity in response
+ hide-version = true; # Hide unbound version in response
+ use-caps-for-id = false; # Disables using uppercase characters in the DNS transaction ID, for compatibility
+ edns-buffer-size = 1232; # Sets the EDNS (Extension mechanisms for DNS) buffer size to 1232 bytes, which is the default for many DNS resolvers
+
+ # Logging
+ #verbosity = 3;
+ #log-queries = true;
+ #log-replies = true;
+ #log-local-actions = true;
+ };
+ # Forward unknown to public resolver via DoT
+ forward-zone = [
+ # Local DNS: forward to dnsmasq
+ {
+ name = ''"${cfg.localDomain}."''; # TODO mk config
+ forward-addr = "127.0.0.1@5353"; # TODO mk config
+ }
+ # Local reverse DNS: forward reverse lookups (PTR records)
+ {
+ name = ''"1.168.192.in-addr.arpa"''; # NOTE: 192.168.1.0 -> 1.168.192
+ forward-addr = "127.0.0.1@5353";
+ }
+ # Upstream DNS
+ {
+ name = ''"."'';
+ forward-addr = [
+ "9.9.9.9#dns.quad9.net"
+ "149.112.112.112#dns.quad9.net"
+ ];
+ forward-tls-upstream = true; # Encrypted DNS
+ }
+ ];
+ remote-control.control-enable = true;
+ };
+ };
+
+ # Configure dnsmasq for dhcp and local hostname resolution
+ services.dnsmasq = {
+ enable = true;
+ settings = let
+ mkDNSHostOverride = host: ip: "${host},${ip}";
+ dnsHostOverrides = lib.mapAttrsToList (host: ip: mkDNSHostOverride host ip) cfg.dns.hostOverrides;
+ mkDHCPRange = ipRangeStart: ipRangeEnd: subnetMask: leaseTime: "${ipRangeStart},${ipRangeEnd},${subnetMask},${leaseTime}";
+ mkDHCPOption = option: value: "option:${option},${value}";
+ mkDHCPStaticLease = macAddress: hostname: staticIp: "${macAddress},${hostname},${staticIp},infinite";
+ dhcpStaticLeases = lib.mapAttrsToList (_: l: mkDHCPStaticLease l.macAddress l.hostname l.staticIp) cfg.dhcp.staticLeases;
+ in {
+ # General
+ no-resolv = true; # Do not read /etc/resolv.conf, resolve only the LAN
+ no-poll = true; # Do not poll /etc/resolv.conf for changes
+ # TODO config local domain
+ local = "/${cfg.localDomain}/"; # Use local-only for the defined domain (prevents upstream leaks)
+ domain = cfg.localDomain; # Define the local domain name
+ expand-hosts = true; # Create both fully-qualified and short-name entries from DHCP hostnames
+
+ # DNS Server
+ port = 5353; # Use port 5353 for DNS server since unbound is the main DNS resolver
+
+ host-record = dnsHostOverrides; # Setup host overrides for dns
+
+ # DHCP Server
+ # TODO config
+ #dhcp-range = mkDHCPRange "192.168.1.50" "192.168.1.150" "255.255.255.0" "12h"; # Enable DHCP on the LAN interface
+ dhcp-range = with cfg.dhcp; mkDHCPRange rangeStart rangeEnd rangeSubnetMask leaseTime; # Enable DHCP on the LAN interface
+
+ dhcp-host = dhcpStaticLeases; # Setup static leases
+
+ dhcp-option = with cfg.dhcp; [
+ (mkDHCPOption "router" defaultGateway) # Set default gateway for clients
+ #(mkDHCPOption "ntp-server" defaultGateway) # Set ntp server for clients
+ (mkDHCPOption "dns-server" localhostIp) # Set dns server for clients
+ (mkDHCPOption "domain-search" cfg.localDomain) # Add search rule to clients so they can resolve hostnames w/o the local domain suffix
+ ];
+
+ #dhcp-no-override = true; # don't use kernel DHCP helpers that bypass macvlan
+
+ # Logging
+ #log-dhcp = true; # Log DHCP events
+ #log-queries = true; # Log DNS queries
+
+ # Cache
+ cache-size = 1000; # Small cache limit is fine since Unbound does the heavy caching
+ };
+ };
+
+ # Search localDomain so host can resolve short names
+ # This is eq. to dnsmasq's dhcp-option "domain-search" for clients, it just adds a search rule to resolv.conf
+ networking.search = [ cfg.localDomain ];
+
+ # Add localhost as default nameserver
+ networking.nameservers = lib.mkDefault [ cfg.dhcp.localhostIp ];
+ };
+}
diff --git a/nixos/services/router/hairpinning.nix b/nixos/services/router/hairpinning.nix
new file mode 100644
index 0000000..e449525
--- /dev/null
+++ b/nixos/services/router/hairpinning.nix
@@ -0,0 +1,47 @@
+{ config, lib, ... }: let
+ cfg = config.networking.nat._hairpinning;
+
+ forwardPorts = config.networking.nat.forwardPorts;
+ internalInterfaces = config.networking.nat.internalInterfaces;
+
+ # DNAT
+ mkIfacesMap = ifs: lib.concatMapStringsSep ", " (i: ''"${i}"'') ifs;
+ mkDnatRule = ifs: proto: sourcePort: destination: ''
+ iifname { ${mkIfacesMap ifs} } ${proto} dport ${toString sourcePort} dnat to ${destination}
+ '';
+ dnatRules = lib.concatMapStrings (p: mkDnatRule internalInterfaces p.proto p.sourcePort p.destination) forwardPorts;
+
+ # SNAT
+ mkSnatTargets = proto: destIp: destPort: ''
+ ip daddr ${destIp} ${proto} dport ${destPort} masquerade
+ '';
+ snatTargets = lib.concatMapStrings(p: let
+ d = lib.splitString ":" p.destination;
+ destIp = builtins.elemAt d 0;
+ destPort = builtins.elemAt d 1;
+ in mkSnatTargets p.proto destIp destPort) forwardPorts;
+in {
+ options.networking.nat._hairpinning = {
+ enable = lib.mkEnableOption "enable nat hairpinning (loopback) to allow internal interfaces to reach forwarded services via the external ip";
+ };
+ config = lib.mkIf (config.networking.nat.enable && cfg.enable) {
+ warnings = [
+ (lib.mkIf (forwardPorts == [])
+ "nat hairpinning enabled but no forwardPorts have been configured. skipping configuration.")
+ ];
+
+ networking.nftables.tables."hairpin" = lib.mkIf (forwardPorts != []) {
+ family = "ip";
+ content = ''
+ chain pre {
+ type nat hook prerouting priority dstnat; policy accept;
+ ${dnatRules}
+ }
+ chain post {
+ type nat hook postrouting priority srcnat; policy accept;
+ ${snatTargets}
+ }
+ '';
+ };
+ };
+}
diff --git a/nixos/services/router/routing.nix b/nixos/services/router/routing.nix
new file mode 100644
index 0000000..c86301f
--- /dev/null
+++ b/nixos/services/router/routing.nix
@@ -0,0 +1,39 @@
+{ config, lib, ... }: let
+ cfg = config.services._router.routing;
+in {
+ options.services._router.routing = {
+ enable = lib.mkEnableOption "enable nftables routing";
+ interfaces = {
+ wan = lib.mkOption {
+ type = lib.types.str;
+ default = "";
+ description = "wan interface";
+ };
+ lan = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ default = "";
+ description = "lan interfaces";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # https://hackers-arise.com/linux-basics-for-hackers-building-a-router-with-nftables/
+ networking = {
+ nftables.enable = true;
+ firewall = {
+ enable = true;
+ backend = "nftables"; # Set explicitly
+
+ # Allow lan interfaces to access the router
+ trustedInterfaces = cfg.interfaces.lan;
+ };
+ nat = {
+ enable = lib.mkDefault true;
+ externalInterface = lib.mkDefault cfg.interfaces.wan;
+ internalInterfaces = lib.mkDefault cfg.interfaces.lan;
+ _hairpinning.enable = lib.mkDefault (config.networking.nat.forwardPorts != []);
+ };
+ };
+ };
+}
diff --git a/nixos/services/router/unbound-blocklist.nix b/nixos/services/router/unbound-blocklist.nix
new file mode 100644
index 0000000..27f2a04
--- /dev/null
+++ b/nixos/services/router/unbound-blocklist.nix
@@ -0,0 +1,82 @@
+{ lib, config, pkgs, ... }: let
+ cfg = config.services.unbound._blocklists;
+ hageziList = list: [
+ "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/${list}.txt"
+ "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/${list}.txt"
+ "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/${list}.txt"
+ ];
+ hageziBlocklists = lib.listToAttrs (
+ map (n: { name = "hagezi_${n}"; value = n; }) cfg.hageziBlocklists
+ );
+ blocklists = hageziBlocklists // cfg.extraBlocklists;
+in {
+ options.services.unbound._blocklists = {
+ enable = lib.mkEnableOption "enable rpz blocklist generation in unbound";
+ hageziBlocklists = lib.mkOption {
+ type = lib.types.listOf lib.types.str;
+ description = "hagezi blocklists to enable";
+ example = [ "pro" "nsfw" ];
+ };
+ extraBlocklists = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.listOf lib.types.str);
+ example = {
+ hageziNSFW = [
+ "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/nsfw.txt"
+ "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/nsfw.txt"
+ "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/nsfw.txt"
+ ];
+ hageziPro = [
+ "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/pro.txt"
+ "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/pro.txt"
+ "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/pro.txt"
+ ];
+ };
+ default = {};
+ description = "blocklist urls in response policy zone (rpz) format";
+ };
+ # TODO
+ #extraBlacklistedDomains = lib.mkOption {
+ # type = lib.types.listOf lib.types.str;
+ # example = [
+ # "example.com"
+ # "*.example.com"
+ # "elpmaxe.com"
+ # "*.elpmaxe.com"
+ # ];
+ # default = [];
+ # description = "additional domains to block";
+ #};
+ #extraWhitelistedDomains = lib.mkOption {
+ # type = lib.types.listOf lib.types.str;
+ # example = [
+ # "example.com"
+ # "*.example.com"
+ # "elpmaxe.com"
+ # "*.elpmaxe.com"
+ # ];
+ # default = [];
+ # description = "whitelist domains that would otherwise be blocked";
+ #};
+ };
+
+ config = lib.mkIf (cfg.enable && config.services.unbound.enable) {
+ # Configure rpz + blocklists in unbound
+ services.unbound.settings = let
+ # https://unbound.docs.nlnetlabs.nl/en/latest/topics/filtering/rpz.html
+ rpzEntry = name: url: { inherit name url; rpz-action-override = "nxdomain"; }; # TODO extra attrs option instead of adding rpz-action-override by default
+ ## Generate extraBlockedDomains
+ #extraBlockedDomainsRPZ = lib.strings.concatStringsSep "\n" (builtins.map (domain: "${domain} CNAME ."));
+ #extraBlockedDomainsRPZFile = pkgs.writeText "extraBlockedDomains" ''
+ # $TTL 300
+ # @ SOA localhost. root.localhost. 1 43200 3600 86400 300
+ # NS localhost.
+ # ${extraBlockedDomainsRPZ}
+ #'';
+ #extraBlockedDomainsRPZEntries = rpzEntry "extraBlockedDomains" extraBlockedDomainsRPZFile;
+ rpz = lib.mapAttrsToList rpzEntry blocklists;
+ in {
+ server.module-config = ''"respip validator iterator"''; # Adds respip before validator and iterator. Needed for rpz config
+ inherit rpz;
+ };
+ };
+}
diff --git a/nixos/services/searxng.nix b/nixos/services/searxng.nix
new file mode 100644
index 0000000..f90ad4e
--- /dev/null
+++ b/nixos/services/searxng.nix
@@ -0,0 +1,120 @@
+{ pkgs, lib, config, ... }: let
+ cfg = config.services._searxng;
+ environmentFile = "/run/searx/searxng.env";
+ generateEnvironmentFile = ''
+ umask 077
+ echo "SEARXNG_SECRET=$(head -c 56 /dev/urandom | base64)" > ${environmentFile}
+ ls /run/searx
+ '';
+in {
+ options.services._searxng = {
+ enable = lib.mkEnableOption "enables searxng service";
+ uwsgi.enable = lib.mkEnableOption "enables searxng uwsgi";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Generate secret key
+ systemd.services.searx-environment-file = {
+ description = "Generate environment file with secret key for searx";
+ wantedBy = [ "searx-init.service" ];
+ partOf = [ "searx-init.service" ];
+ before = [ "searx-init.service" ];
+ serviceConfig = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ User = "searx";
+ RuntimeDirectory = "searx";
+ RuntimeDirectoryMode = "750";
+ ConditionPathExists = "!${environmentFile}";
+ };
+ script = generateEnvironmentFile;
+ };
+
+ # Configure searxng
+ services.searx = {
+ enable = true;
+ redisCreateLocally = true;
+ package = pkgs.searxng;
+ inherit environmentFile; # Provides secret key
+
+ # UWSGI configuration
+ runInUwsgi = cfg.uwsgi.enable;
+
+ uwsgiConfig = {
+ socket = "/run/searx/searx.sock";
+ http = ":8888";
+ chmod-socket = "660";
+ };
+
+ settings = {
+ general = {
+ instance_name = "TJK Search";
+ donation_url = "https://tjkeller.xyz";
+ enable_metrics = false;
+ };
+
+ # Search engine settings
+ search = {
+ safe_search = 2; # Strict
+ autocomplete = "";
+ default_lang = "en-US";
+ };
+
+ preferences.lock = [ "safesearch" ]; # Lock safe_search at strict
+
+ # https://docs.searxng.org/admin/plugins.html
+ enabled_plugins = [
+ "Tor check plugin"
+ "Tracker URL remover"
+ "Basic Calculator"
+ "Unit converter plugin"
+ "Hash plugin"
+ "Self Information"
+ "Open Access DOI rewrite"
+ "Hostnames plugin"
+ ];
+
+ hostnames.replace = {
+ "(.*\.)?youtube\.com$" = "piped.tjkeller.xyz";
+ "(.*\.)?youtu\.be$" = "piped.tjkeller.xyz";
+ "(.*\.)?reddit\.com$" = "old.reddit.com";
+ };
+
+ # Enable / disabled search engines from default list
+ engines = lib.mapAttrsToList (name: value: { inherit name; disabled = !value; }) {
+ # Images
+ "artic" = false;
+ "deviantart" = false;
+ "flickr" = false;
+ "library of congress" = false;
+ "openverse" = false;
+ "pinterest" = false;
+ "public domain image archive" = false;
+ "unsplash" = false;
+ "wallhaven" = false;
+ "wikicommons.images" = false;
+
+ # Videos
+ "bitchute" = true;
+ "dailymotion" = false;
+ "piped" = false;
+ "rumble" = true;
+ "sepiasearch" = false;
+ "vimeo" = false;
+ "wikicommons.videos" = false;
+
+ # Music
+ "piped.music" = false;
+
+ # Files
+ "1337x" = true;
+ "annas archive" = true;
+ "library genesis" = true;
+
+ # Apps
+ "fdroid" = true;
+ };
+ };
+ };
+ };
+}
diff --git a/nixos/services/zfs/zed-mailer.nix b/nixos/services/zfs/zed-mailer.nix
new file mode 100644
index 0000000..06acd1f
--- /dev/null
+++ b/nixos/services/zfs/zed-mailer.nix
@@ -0,0 +1,20 @@
+{ lib, pkgs, config, ... }: let
+ cfg = config.services.zfs._zedMailer;
+in {
+ options.services.zfs._zedMailer = {
+ enable = lib.mkEnableOption "enable zed mailer service";
+ };
+
+ # https://wiki.nixos.org/wiki/ZFS#Mail_notifications_(ZFS_Event_Daemon)
+ config = lib.mkIf cfg.enable {
+ services.zfs.zed = {
+ enableMail = true;
+ settings = {
+ ZED_EMAIL_ADDR = [ "root" ];
+ # send notification if scrub succeeds
+ ZED_NOTIFY_VERBOSE = true;
+ };
+ };
+ };
+}
+
diff --git a/nixos/suspend.nix b/nixos/suspend.nix
new file mode 100644
index 0000000..0e719ab
--- /dev/null
+++ b/nixos/suspend.nix
@@ -0,0 +1,21 @@
+{ lib, config, ... }: let
+ cfg = config.systemd._suspend;
+in {
+ options.systemd._suspend = {
+ disable = lib.mkEnableOption "disables suspend";
+ };
+
+ config = lib.mkIf cfg.disable {
+ # Disable suspend targets
+ systemd.targets = builtins.listToAttrs (map (name: {
+ inherit name;
+ value = {
+ enable = false;
+ unitConfig.DefaultDependencies = "no";
+ };
+ }) ["sleep" "suspend" "hibernate" "hybrid-sleep"]);
+
+ # Hibernate key => suspend
+ services.logind.settings.Login.HandleHibernateKey = "suspend";
+ };
+}
diff --git a/nixos/wireless.nix b/nixos/wireless.nix
new file mode 100644
index 0000000..22c759f
--- /dev/null
+++ b/nixos/wireless.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }: let
+ cfg = config.networking.wireless;
+ mkCredential = cred: file: "${cred}:${file}";
+ mkCredentialPath = cred: _: "/run/credentials/wpa_supplicant.service/${cred}";
+in {
+ options.networking.wireless = {
+ _extraSecretConfigs = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ description = "extra config files loaded into systemd credentials";
+ default = {};
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.wpa_supplicant.serviceConfig = {
+ LoadCredential = lib.mapAttrsToList mkCredential cfg._extraSecretConfigs;
+ };
+ networking.wireless = {
+ extraConfigFiles = lib.mapAttrsToList mkCredentialPath cfg._extraSecretConfigs;
+ };
+ };
+}
diff --git a/nixos/zshenv.nix b/nixos/zshenv.nix
new file mode 100644
index 0000000..4c2fc7c
--- /dev/null
+++ b/nixos/zshenv.nix
@@ -0,0 +1,24 @@
+{ lib, config, ... }: let
+ cfg = config.programs.zsh._zshenv;
+ generatedExports = lib.concatLines (
+ lib.mapAttrsToList (var: value: ''export ${var}="${value}"'') cfg.exports
+ );
+in {
+ options.programs.zsh._zshenv = {
+ enable = lib.mkEnableOption "generate /etc/zshenv.local";
+ exports = lib.mkOption {
+ type = lib.types.attrsOf lib.types.str;
+ default = {};
+ example = {
+ ZDOTDIR = "$HOME/.config/zsh";
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ environment.etc."zshenv.local".text = ''
+ # Exports
+ ${generatedExports}
+ '';
+ };
+}
diff --git a/pkgs/crazydiskinfo/default.nix b/pkgs/crazydiskinfo/default.nix
new file mode 100644
index 0000000..61de338
--- /dev/null
+++ b/pkgs/crazydiskinfo/default.nix
@@ -0,0 +1,41 @@
+{ lib
+, cmake
+, fetchFromGitHub
+, fetchpatch
+, libatasmart
+, ncurses5
+, stdenv
+}:
+
+stdenv.mkDerivation rec {
+ pname = "crazydiskinfo";
+ version = "1.1.0";
+
+ src = fetchFromGitHub {
+ owner = "otakuto";
+ repo = pname;
+ rev = "refs/tags/${version}";
+ sha256 = "sha256-+6ShoaggQ256sLSJDj4d16OipUYX/4JjEwLL5hswjiQ=";
+ };
+
+ patches = [
+ # Patch to fix build warnings
+ (fetchpatch {
+ url = "https://github.com/otakuto/crazydiskinfo/compare/1.1.0...8563aa8.diff";
+ sha256 = "sha256-z9aOQFsivu6sYgsZBOlnTvqnoR2ro/jyuO/WOimLSXk=";
+ })
+ ];
+
+ nativeBuildInputs = [ cmake ];
+ buildInputs = [ libatasmart ncurses5 ];
+
+ cmakeFlags = [ "-DCMAKE_INSTALL_PREFIX=$(out)" ];
+
+ meta = with lib; {
+ description = "CrazyDiskInfo is an interactive TUI S.M.A.R.T viewer for Unix systems";
+ homepage = "https://github.com/otakuto/crazydiskinfo";
+ mainProgram = "crazy";
+ license = licenses.mit;
+ maintainers = [ maintainers.tjkeller ];
+ };
+}
diff --git a/pkgs/default.nix b/pkgs/default.nix
new file mode 100644
index 0000000..1de13ff
--- /dev/null
+++ b/pkgs/default.nix
@@ -0,0 +1,40 @@
+{ pkgs, inputs, ... }: {
+ nixpkgs.overlays = with pkgs; [
+ (final: prev: {
+ immich-frame = (callPackage ./immich-frame {});
+ lowbat = (callPackage ./lowbat {});
+ workcentre-7800-series = (callPackage ./xerox-workcentre-7800-series-driver {});
+ unclutter-desktop-entry = (callPackage ./unclutter-desktop-entry {});
+
+ # Use my vimv-rs until pr gets merged
+ vimv-rs = prev.vimv-rs.overrideAttrs (oldAttrs: {
+ src = fetchFromGitHub {
+ owner = "tjkeller-xyz";
+ repo = "vimv-rs";
+ rev = "5deb76fb81dd4acf3c4809087ff3a1d846ab4769";
+ sha256 = "sha256-XMn+5mIxSEHaR31ixMi6o7PSkN1iYjDT4aOiQkfEwpA=";
+ };
+ });
+
+ # Use my tamzen until pr gets merged
+ tamzen = prev.tamzen.overrideAttrs (oldAttrs: {
+ src = fetchFromGitHub {
+ owner = "tjkeller-xyz";
+ repo = "tamzen-font";
+ rev = "middledot";
+ sha256 = "sha256-mVZ8SdYKTdMG1qBKKYdNjuoYvWkEq2ph2O1ztsNJEhs=";
+ };
+ });
+ })
+ # Import overlays
+ (import ./st/overrides.nix)
+ (import ./vimPlugins/overrides.nix)
+ (import ./xorg/overrides.nix)
+ # Allow installing unstable packages via pkgs.unstable.<name>
+ (final: prev: {
+ unstable = import inputs.nixpkgs-unstable {
+ inherit (final) system;
+ };
+ })
+ ];
+}
diff --git a/pkgs/immich-frame/default.nix b/pkgs/immich-frame/default.nix
new file mode 100644
index 0000000..9c2c0f0
--- /dev/null
+++ b/pkgs/immich-frame/default.nix
@@ -0,0 +1,46 @@
+{
+ fetchgit,
+ pkgs,
+ python3Packages,
+ buildNpmPackage,
+}:
+let
+ version = "0.4.1";
+ src = fetchgit {
+ url = "https://git.tjkeller.xyz/immich-frame";
+ tag = "v${version}";
+ hash = "sha256-1E//uhk/egkt5y1XMn52EHumdgswxq45bwntNIoCeRM=";
+ };
+
+ frontend = buildNpmPackage {
+ inherit version src;
+ pname = "immich-frame-frontend";
+
+ npmWorkspace = "src/client";
+ npmDepsHash = "sha256-/8dHzpqvnDZb+SV9O/GvTyspKLaPDs4vt3QrzoLsyEI=";
+
+ installPhase = ''
+ mkdir -p $out
+ cp -R dist $out/
+ '';
+ };
+in python3Packages.buildPythonApplication {
+ inherit version src;
+ pname = "immich-frame";
+
+ makeWrapperArgs = [ "--set" "IMMICH_FRAME_STATIC_WEB_ASSETS" "${frontend}/dist" ];
+
+ pyproject = true;
+ build-system = with python3Packages; [ setuptools ];
+ propagatedBuildInputs = with python3Packages; [
+ flask
+ flask-cors # DEBUG
+ flask-socketio
+ numpy
+ pillow
+ platformdirs
+ pygame
+ pyopengl
+ requests
+ ];
+}
diff --git a/pkgs/lowbat/default.nix b/pkgs/lowbat/default.nix
new file mode 100644
index 0000000..64ec205
--- /dev/null
+++ b/pkgs/lowbat/default.nix
@@ -0,0 +1,28 @@
+{ lib
+, fetchzip
+, glib
+, libnotify
+, pkg-config
+, stdenv
+}:
+
+stdenv.mkDerivation rec {
+ pname = "lowbat";
+ version = "1.2.2";
+
+ src = fetchzip {
+ url = "https://git.tjkeller.xyz/${pname}/snapshot/${pname}-${version}.tar.xz";
+ sha256 = "sha256-utdcY9qoDcrBWnGC8m/LUsnoxQ0ilssPDF44A6D/C+8=";
+ };
+
+ nativeBuildInputs = [ pkg-config ];
+ buildInputs = [ libnotify glib ];
+
+ installFlags = [ "PREFIX=$(out)" ];
+
+ meta = with lib; {
+ description = "A minimal battery level monitor daemon, written in C";
+ license = licenses.mit;
+ maintainers = [ maintainers.tjkeller ];
+ };
+}
diff --git a/pkgs/st/overrides.nix b/pkgs/st/overrides.nix
new file mode 100644
index 0000000..744cbf8
--- /dev/null
+++ b/pkgs/st/overrides.nix
@@ -0,0 +1,19 @@
+final: prev: {
+ st = prev.st.overrideAttrs (finalAttrs: previousAttrs: {
+ version = "tj-0.9.3.1";
+
+ src = prev.fetchgit {
+ url = "https://git.tjkeller.xyz/${previousAttrs.pname}";
+ tag = finalAttrs.version;
+ sha256 = "sha256-MYVGoGQsUMiIzvCXTjpYLv9sRpcoaO9wZ65fTGaY0Vw=";
+ };
+
+ makeFlags = previousAttrs.makeFlags ++ [ "CFLAGS=-Ofast" ];
+ buildInputs = with prev.pkgs; previousAttrs.buildInputs ++ [ libxcursor libxext ];
+
+ meta = with prev.lib; previousAttrs.meta // {
+ homepage = "https://git.tjkeller.xyz/st";
+ maintainers = previousAttrs.meta.maintainers ++ [ maintainers.tjkeller ];
+ };
+ });
+}
diff --git a/pkgs/unclutter-desktop-entry/default.nix b/pkgs/unclutter-desktop-entry/default.nix
new file mode 100644
index 0000000..c98cbda
--- /dev/null
+++ b/pkgs/unclutter-desktop-entry/default.nix
@@ -0,0 +1,14 @@
+{ pkgs }:
+
+pkgs.makeDesktopItem {
+ name = "unclutter";
+ desktopName = "Unclutter";
+ comment = "Hides mouse pointer while not in use";
+ exec = "${pkgs.unclutter-xfixes}/bin/unclutter";
+ icon = "input-mouse";
+ categories = [ "Utility" ];
+ keywords = [ "mouse" "cursor" "hide" "idle" "pointer" ];
+ #extraConfig = ''
+ # NoDisplay=true
+ #'';
+}
diff --git a/pkgs/vimPlugins/overrides.nix b/pkgs/vimPlugins/overrides.nix
new file mode 100644
index 0000000..a56511b
--- /dev/null
+++ b/pkgs/vimPlugins/overrides.nix
@@ -0,0 +1,46 @@
+final: prev: {
+ vimPlugins = prev.vimPlugins // {
+ minitab-nvim = prev.vimUtils.buildVimPlugin rec {
+ pname = "minitab.nvim";
+ version = "2024-11-28";
+ rev = "f6183e8cb6f408e54dd5d1d13c0075376655a3ec";
+ src = prev.fetchzip {
+ url = "https://git.tjkeller.xyz/${pname}/snapshot/${pname}-${rev}.tar.xz";
+ sha256 = "sha256-INJC+6GNTF5MpvtOh6/eYD5CMiXxlP6aHT5TX2lJqiM=";
+ };
+ meta.homepage = "https://git.tjkeller.xyz/minitab.nvim";
+ };
+
+ # TODO
+ #nvim-treesitter-parsers = prev.vimPlugins.nvim-treesitter-parsers // {
+ # #jinja2 = prev.tree-sitter.buildGrammar rec {
+ # # language = "jinja2";
+ # # version = "2025-10-24";
+ # # src = prev.fetchFromGitHub {
+ # # owner = "geigerzaehler";
+ # # repo = "tree-sitter-jinja2";
+ # # rev = "ecab6215c53da5c1126d81d1bf664b3b5cf87d15";
+ # # sha256 = "sha256-gYU9gQZMlLKYHVAISa5AF/KXzr0PA0M8gK0/B7gXcYs=";
+ # # };
+ # # meta.homepage = "https://github.com/geigerzaehler/tree-sitter-jinja2";
+ # #};
+ # #jinja2 = prev.vimUtils.buildVimPlugin rec {
+ # # pname = "jinja2";
+ # # version = "2025-10-24";
+ # # src = prev.fetchFromGitHub {
+ # # owner = "geigerzaehler";
+ # # repo = "tree-sitter-jinja2";
+ # # rev = "ecab6215c53da5c1126d81d1bf664b3b5cf87d15";
+ # # sha256 = "sha256-gYU9gQZMlLKYHVAISa5AF/KXzr0PA0M8gK0/B7gXcYs=";
+ # # };
+ # # patches = [
+ # # (prev.fetchpatch {
+ # # url = "https://github.com/geigerzaehler/tree-sitter-jinja2/pull/6.diff";
+ # # sha256 = "sha256-CBUAwb9hz1Cpa4RaY+6aTMJNU+hX9D3BGpNxL+MoAq0=";
+ # # })
+ # # ];
+ # # meta.homepage = "https://github.com/geigerzaehler/tree-sitter-jinja2";
+ # #};
+ #};
+ };
+}
diff --git a/pkgs/xerox-workcentre-7800-series-driver/default.nix b/pkgs/xerox-workcentre-7800-series-driver/default.nix
new file mode 100644
index 0000000..cfdaaae
--- /dev/null
+++ b/pkgs/xerox-workcentre-7800-series-driver/default.nix
@@ -0,0 +1,86 @@
+{
+ stdenv,
+ lib,
+ fetchurl,
+ dpkg,
+ autoPatchelfHook,
+ cups,
+ xorg,
+}:
+let
+ debPlatform =
+ if stdenv.hostPlatform.system == "x86_64-linux" then
+ "x86_64"
+ else if stdenv.hostPlatform.system == "i686-linux" then
+ "i686"
+ else
+ throw "Unsupported system: ${stdenv.hostPlatform.system}";
+in
+stdenv.mkDerivation rec {
+ pname = "xerox-workcentre-7800-series-driver";
+ version = "5.20.661.4684";
+ debpkg = "XeroxOfficev5Pkg-Linux${debPlatform}-${version}.deb";
+
+ src = fetchurl {
+ url = "https://download.support.xerox.com/pub/drivers/CQ8580/drivers/linux/pt_BR/${debpkg}";
+ sha256 = "014k0r9ij3401mnab1qzv96bjl9x7rf11aw1ibf0q370pk9jqqjb"; # TODO correct hash for i686
+ };
+
+ nativeBuildInputs = [ dpkg autoPatchelfHook ];
+ # TODO add support for disable xorg
+ buildInputs = [
+ cups
+ stdenv.cc.cc.lib
+ xorg.libX11
+ xorg.libXrender
+ xorg.libXfixes
+ xorg.libXdamage
+ xorg.libXcomposite
+ xorg.libXcursor
+ xorg.libXrandr
+ xorg.libXext
+ xorg.libXinerama
+ ];
+
+ sourceRoot = ".";
+ unpackCmd = "dpkg-deb -x $curSrc .";
+
+ dontConfigure = true;
+ dontBuild = true;
+
+ installPhase = ''
+ runHook preInstall
+
+ mkdir -p $out
+
+ # Copy and patch the binaries and libraries
+ cp -r opt $out/
+ cp -r usr $out/
+
+ # Move the PPD to CUPS model dir
+ mkdir -p $out/share/cups/model
+ cp opt/XeroxOffice/prtsys/ppd/*.ppd $out/share/cups/model/
+
+ # Install the CUPS filters
+ mkdir -p $out/lib/cups/filter
+ cp usr/lib/cups/filter/* $out/lib/cups/filter/
+
+ # Install man pages
+ mkdir -p $out/share/man
+ cp -r usr/share/man/* $out/share/man/
+
+ runHook postInstall
+ '';
+
+ meta = with lib; {
+ description = "Xerox WorkCentre 7800 Series Linux Printer Driver";
+ longDescription = ''
+ WorkCentre 7830/7835/7845/7855
+ '';
+ homepage = "https://www.support.xerox.com/en-us/product/workcentre-7800-series/downloads?platform=linux";
+ sourceProvenance = with sourceTypes; [ binaryNativeCode ];
+ #license = licenses.unfree;
+ maintainers = [];
+ platforms = platforms.linux;
+ };
+}
diff --git a/pkgs/xorg/overrides.nix b/pkgs/xorg/overrides.nix
new file mode 100644
index 0000000..0cd9490
--- /dev/null
+++ b/pkgs/xorg/overrides.nix
@@ -0,0 +1,18 @@
+final: prev: {
+ # Apply startx patch to create serverauth file in /tmp instead of home directory
+ xinit = prev.xinit.overrideAttrs (finalAttrs: previousAttrs: {
+ version = "1.4.4";
+ patchtag = "${finalAttrs.version}-1"; # Archlinux xinit package tagged release to fetch patch from
+ # Override src since is hardcoded to 1.4.2
+ src = prev.fetchurl {
+ url = "mirror://xorg/individual/app/xinit-${finalAttrs.version}.tar.xz";
+ sha256 = "sha256-QKR8ehZMf5gc43h7Szf35BH7QyMdzeVD1wCUB12s/vk=";
+ };
+ patches = [
+ (prev.fetchpatch {
+ url = "https://gitlab.archlinux.org/archlinux/packaging/packages/xorg-xinit/-/raw/${finalAttrs.patchtag}/06_move_serverauthfile_into_tmp.diff";
+ sha256 = "1whzs5bw7ph12r3abs1g9fydibkr291jh56a0zp17d4x070jnkda";
+ })
+ ];
+ });
+}
diff --git a/poweredge-pro outline b/poweredge-pro outline
new file mode 100644
index 0000000..bdb7fb8
--- /dev/null
+++ b/poweredge-pro outline
@@ -0,0 +1,58 @@
+poweredge-pro
+
+https://wiki.nixos.org/wiki/ZFS
+
+SERVICES:
+- nfs/smb file shares
+- cronjobs / systemd units
+ - zfs scrubbing
+ - zfs snapshotting
+- services.smartd
+- ups safe power down
+- allow backup to truenas
+- virtual machines
+ - virtual router opnsense
+- unbound + dnsmasq
+ - local hostname resolving e.g. ping poweredge-pro
+ - overrides and blocklists (manual black/whitelist)
+- router profile
+- web services
+ - admin streamline (docker for now)
+ - chexx-internal-webservices (docker)
+ - chexx-webservices (docker)
+ - filebrowser (potential container to avoid break out)
+ - gitea (container with ssh & git)
+ - mailcow (docker)
+- tjkeller web services
+ - cgit (public git) (container with ssh & git)
+ - searxng
+ - minitube
+ - smtm
+
+SECURITY
+- boot security
+ - boots without password
+ - certain services start without password
+ - chexx-webservices
+ - all tjkeller services
+ - others require password to be entered for encryption (all not mentioned above)
+- file security
+ - encrypted zfs pools
+
+DISKS
+- boot + services disk
+ - zfs
+ - encryption
+ - datasets
+ - /var/lib/machines
+ - /var/lib/docker
+
+
+IPS
+- streamline: 10.0.0.2 : 5021
+- chexx-internal-webservices: 10.0.0.2 : 8080
+- chexx-webservices: 10.0.0.2 : 8081
+- filebrowser: 10.0.1.2
+- gitea: 10.0.1.3
+- mailcow: 10.0.0.2 : 21 445 etc
+- tj cgit: 10.0.1.4
diff --git a/rebuild b/rebuild
new file mode 100755
index 0000000..a9b63e3
--- /dev/null
+++ b/rebuild
@@ -0,0 +1,10 @@
+#!/bin/sh
+if [ ! -f ~/.config/sops/age/keys.txt ]; then
+ mkdir -p ~/.config/sops/age
+ echo "---------------------------------------------------------------------------------------------------"
+ echo "| WARNING: Sops key not found. Please generate one from your ssh key using the following command: |"
+ echo "| nix run nixpkgs#ssh-to-age -- -private-key -i ~/.ssh/private > ~/.config/sops/age/keys.txt |"
+ echo "| cat ~/.config/sops/age/keys.txt | age-keygen -y # Public key |"
+ echo "---------------------------------------------------------------------------------------------------"
+fi
+nixos-rebuild switch --sudo --flake "$(dirname "$0")/#$(hostname)" $@
diff --git a/todo b/todo
index b3fc58b..2378d09 100644
--- a/todo
+++ b/todo
@@ -1,18 +1,29 @@
# Misc / low priority
fix screen turning off when playing video for extended period
-sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
- ^- systemctl restart systemd-logind.service
-
-ff violentmonkey userscripts
+ff violentmonkey userscripts import
pcmanfm config FIX
-fonts.fontconfig.antialias = false && gtk font override on librex60
libreoffice calibre dark+svg icon theme and dark mode
-# Secrets
+thunderbird config
+
fix firefox no show http/s and no hist
-clone repos activation script fix when cloning from ssh
-thunderbird (https://home-manager-options.extranix.com/?query=thunderbird&release=release-24.05)
# Not needed
install zsh as root
+
+#
+git-sync
+geoclue cache location
+geoclue continuously try to get location if not available
+get rid of launch script, move x config to new repo, retire dotconfig repo
+font improvements hinting etc home manager
+
+#
+zsh support
+zsh plugins installed via nix pkg manager instead of zplug
+headless virtual machines / view display with vnc in browser
+support office xerox printer properly
+
+#
+create mountpoints for nas
diff --git a/users/default-home.nix b/users/default-home.nix
new file mode 100644
index 0000000..d0cc18b
--- /dev/null
+++ b/users/default-home.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ./timmy/home.nix
+ ]
+}
diff --git a/users/default.nix b/users/default.nix
new file mode 100644
index 0000000..8eb5373
--- /dev/null
+++ b/users/default.nix
@@ -0,0 +1,5 @@
+{
+ imports = [
+ ./timmy
+ ];
+}
diff --git a/users/timmy/default.nix b/users/timmy/default.nix
new file mode 100644
index 0000000..c38cbb7
--- /dev/null
+++ b/users/timmy/default.nix
@@ -0,0 +1,92 @@
+{ lib, config, pkgs, home-manager, ... }: let
+ cfg = config._users.timmy;
+ username = "timmy";
+ fullname = "Tim Keller";
+ home = "/home/${username}";
+ sshKeyPaths = [ "${home}/.ssh/id_ed25519" ];
+ #sshPublicKeyPaths = lib.map (keyPath: keyPath + ".pub") sshKeyPaths;
+ sshPublicKeys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDnsnAWcz46OVi1MWSxpOIUtUvwalijDwvW+oEvNjzep" ];
+in {
+ options._users.${username} = {
+ enable = lib.mkEnableOption "create user ${username}";
+ autologin.enable = lib.mkEnableOption "enables getty automatic login";
+ };
+
+ # FIXME
+ imports = [
+ ./hosts.nix
+ ./localization.nix
+ ./nas.nix
+ ./printing.nix
+ ./repos.nix
+ ./user.nix
+ ./wifi.nix
+ ];
+
+ config = lib.mkIf cfg.enable {
+ # Setup zsh
+ programs.zsh = {
+ enable = true;
+ _zshenv = {
+ enable = true;
+ exports = {
+ ZDOTDIR = "$HOME/.config/zsh";
+ };
+ };
+ };
+ #environment.systemPackages = [ pkgs.zsh-fast-syntax-highlighting ]; # TODO
+
+ # Setup normal user
+ users.users.${username} = {
+ inherit home;
+ description = fullname;
+ isNormalUser = true;
+ shell = pkgs.zsh;
+ extraGroups = [
+ "nixbld"
+ "video"
+ "wheel"
+ ] ++ lib.optionals config.hardware.i2c.enable [
+ "i2c"
+ ] ++ lib.optionals config.virtualisation.libvirtd.enable [
+ "libvirtd"
+ ] ++ lib.optionals config.virtualisation.docker.enable [
+ "docker"
+ ];
+ };
+
+ # Enable sudo, doas, polkit nopass
+ security.sudo = {
+ enable = true;
+ wheelNeedsPassword = false;
+ };
+ security.doas = {
+ enable = true;
+ wheelNeedsPassword = false;
+ extraRules = [{ keepEnv = true; }];
+ };
+ security.polkit = {
+ _wheelNoPass = true;
+ };
+
+ # Configure automatic login with getty
+ services.getty = lib.mkIf cfg.autologin.enable {
+ autologinUser = username;
+ };
+
+ # Configure sops age key paths since age keys are generated via ssh private key
+ sops.age = { inherit sshKeyPaths; };
+
+ # Add authorizedKeys to cgit service
+ services._cgit.ssh.authorizedKeys = {
+ keys = sshPublicKeys;
+ #keyFiles = sshPublicKeyPaths;
+ };
+
+ # Configure user home
+ home-manager.users.${username} = {
+ imports = [ ./home.nix ];
+ _users.${username}.home.enable = lib.mkDefault true;
+ };
+ };
+}
diff --git a/users/timmy/home.nix b/users/timmy/home.nix
new file mode 100644
index 0000000..4d61600
--- /dev/null
+++ b/users/timmy/home.nix
@@ -0,0 +1,106 @@
+{ lib, pkgs, config, ... }: let
+ cfg = config._users.${username}.home;
+ username = "timmy";
+ email = "tjk@tjkeller.xyz";
+ fullname = "Tim Keller";
+ home = "/home/${username}";
+ userDirs = {
+ desktop = "${home}";
+ download = "${home}/dls";
+ documents = "${home}/docs";
+ pictures = "${home}/pics";
+ projects = "${home}/docs/src";
+ # Set these as null so they're not created
+ music = null;
+ publicShare = null;
+ templates = null;
+ videos = null;
+ };
+in {
+ options._users.${username}.home = {
+ enable = lib.mkEnableOption "configure home for user ${username}";
+ #neovim = {
+ # enableLSP = lib.mkEnableOption "whether to enable lsp";
+ #};
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Setup home-manager
+ home = {
+ username = username;
+ homeDirectory = home;
+ stateVersion = "26.05";
+ };
+
+ # Setup userdirs
+ xdg.userDirs = {
+ enable = true;
+ createDirectories = true;
+ } // userDirs;
+
+ # Setup git
+ programs.git = {
+ enable = true;
+ settings = {
+ user = {
+ name = fullname;
+ email = email;
+ };
+ merge.tool = "nvimdiff";
+ mergetool.nvimdiff = {
+ cmd = ''nvim -d "$LOCAL" "$MERGED" "$REMOTE"'';
+ prompt = false;
+ };
+ # Unfortunately, this doesn't work for some reason
+ #url = {
+ # "ssh://git@publicgit/".insteadOf = "https://git.tjkeller.xyz/";
+ #};
+ };
+ };
+
+ # Setup gtk bookmarks
+ gtk.gtk3.bookmarks = let
+ mkBookmark = name: dir: ''file://${dir} ${lib.toUpper (lib.substring 0 1 name)}${lib.substring 1 (-1) name}''; # Make first letter upper case
+ attrsToBookmarks = attrs: lib.mapAttrsToList (name: dir: lib.mkIf (dir != null) (mkBookmark name dir)) attrs;
+ bookmarksUserDirs = attrsToBookmarks userDirs;
+ in bookmarksUserDirs;
+
+ # Setup neovim
+ programs._neovim = {
+ enable = true;
+ viAlias = true;
+ vimAlias = true;
+ plugins = {
+ lsp = {
+ enable = lib.mkDefault true;
+ lspconfig.enable = true;
+ languageServers.packages = with pkgs; [
+ python313Packages.python-lsp-server
+ svelte-language-server
+ tailwindcss-language-server
+ typescript-language-server
+ vscode-langservers-extracted
+ ];
+ };
+ treesitter = {
+ enable = lib.mkDefault true;
+ parsers.installAll = lib.mkDefault true;
+ };
+ packages.start = with pkgs.vimPlugins; [
+ autoclose-nvim
+ blink-cmp
+ gruvbox-nvim
+ minitab-nvim
+ nvim-colorizer-lua
+ nvim-ts-autotag # depends on treesitter
+ rainbow-delimiters-nvim # depends on treesitter
+ snacks-nvim
+ ];
+ };
+ extraPackages = with pkgs; [
+ gcc # Treesitter requires a C compiler
+ unzip # For zip, xlsx, etc. files
+ ];
+ };
+ };
+}
diff --git a/users/timmy/hosts.nix b/users/timmy/hosts.nix
new file mode 100644
index 0000000..4f0c0ac
--- /dev/null
+++ b/users/timmy/hosts.nix
@@ -0,0 +1,7 @@
+{
+ networking.hosts = {
+ "192.168.77.11" = [ "truenas-office" ];
+ "192.168.77.8" = [ "publicgit" "tjkeller" ];
+ "192.168.77.3" = [ "devel" ];
+ };
+}
diff --git a/users/timmy/localization.nix b/users/timmy/localization.nix
new file mode 100644
index 0000000..bcbb422
--- /dev/null
+++ b/users/timmy/localization.nix
@@ -0,0 +1,14 @@
+{ lib, config, ... }: {
+ time.timeZone = lib.mkDefault "America/Chicago"; # Overwritten by automatic-timezoned
+ i18n.defaultLocale = "en_US.UTF-8";
+ services.xserver.xkb.layout = "us";
+
+ # Setup static geoclue2 if wifi is disabled
+ services.geoclue2 = {
+ enableStatic = lib.mkDefault (!config._users.timmy.wifi.enable);
+ staticLatitude = 41.881832;
+ staticLongitude = -87.623177;
+ staticAltitude = 600;
+ staticAccuracy = 500;
+ };
+}
diff --git a/users/timmy/nas.nix b/users/timmy/nas.nix
new file mode 100644
index 0000000..d878ac9
--- /dev/null
+++ b/users/timmy/nas.nix
@@ -0,0 +1,35 @@
+{ lib, config, ... } :
+let
+ cfg = config._users.timmy.nas;
+ mkNetworkFileSystem = device: automount: {
+ device = "${device}";
+ fsType = "nfs";
+ options = [ "defaults" ] ++ lib.optionals (!automount) [ "noauto" ];
+ };
+in {
+ options._users.timmy.nas = {
+ enable = lib.mkEnableOption "enable network shares";
+ home = {
+ enable = lib.mkEnableOption "enable home network shares";
+ automount = lib.mkEnableOption "automount home network shares";
+ };
+ office = {
+ enable = lib.mkEnableOption "enable office network shares";
+ automount = lib.mkEnableOption "automount home network shares";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # TODO clean this up
+ fileSystems = lib.optionalAttrs cfg.home.enable {
+ "/media/ingens/backups" = mkNetworkFileSystem "poweredge:/media/ingens/backups" cfg.home.automount;
+ "/media/ingens/tapes" = mkNetworkFileSystem "poweredge:/media/ingens/tapes" cfg.home.automount;
+ "/media/ingens/pictures" = mkNetworkFileSystem "poweredge:/media/ingens/pictures" cfg.home.automount;
+ } // lib.optionalAttrs cfg.office.enable {
+ "/media/chexx/chexx" = mkNetworkFileSystem "truenas-office:/mnt/Storage/chexx" cfg.office.automount;
+ "/media/chexx/tkdocs" = mkNetworkFileSystem "truenas-office:/mnt/Storage/Users/Tim-Keller" cfg.office.automount;
+ "/media/chexx/scans" = mkNetworkFileSystem "truenas-office:/mnt/Storage/Scans" cfg.office.automount;
+ };
+ # TODO auto mkdirz
+ };
+}
diff --git a/modules/root/printing.nix b/users/timmy/printing.nix
index 69ff573..9dbba29 100644
--- a/modules/root/printing.nix
+++ b/users/timmy/printing.nix
@@ -1,33 +1,23 @@
{ lib, config, pkgs, ... }: {
- options = {
- printing.enable = lib.mkEnableOption "enables printing";
- avahi.enable = lib.mkEnableOption "enables avahi";
- scanning.enable = lib.mkEnableOption "enables scanning";
- };
-
- config = lib.mkIf config.printing.enable {
- services.printing = {
- enable = true;
- drivers = [
- pkgs.epson-escpr2
- ];
- };
-
- services.avahi = {
- enable = true;
- nssmdns4 = true;
- openFirewall = true;
- };
+ config = lib.mkIf config.services.printing.enable {
+ # Printer drivers
+ services.printing.drivers = [
+ pkgs.epson-escpr2
+ #pkgs.workcentre-7800-series
+ ];
- environment.systemPackages = with pkgs; pkgs.lib.optionals config.scanning.enable [
+ # Scanning programs
+ environment.systemPackages = with pkgs; [
epsonscan2
];
+ # Printers
networking.hosts = {
"192.168.1.35" = [ "Epson_ET-8500" ];
- "192.168.77.40" = [ "Xerox_WorkCentre_7855" ];
+ #"192.168.77.40" = [ "Xerox_WorkCentre_7855" ];
};
+ # Add printers to cups
hardware.printers.ensurePrinters = [
{
name = "Epson_ET-8500";
diff --git a/users/timmy/repos.nix b/users/timmy/repos.nix
new file mode 100644
index 0000000..eed0219
--- /dev/null
+++ b/users/timmy/repos.nix
@@ -0,0 +1,48 @@
+{ config, ... }: let
+ server = "https://git.tjkeller.xyz/";
+ # TODO find how to get home manager config instead of nixos config
+ #srcHome = "${config.xdg.userDirs.documents}/src";
+ #configHome = config.xdg.configHome;
+ home = "/home/timmy";
+ srcHome = "${home}/docs/src";
+ configHome = "${home}/.config";
+in {
+ config = {
+ home-manager.users.timmy = {
+ reposync.enable = true;
+ reposync.outOfStoreGitRepository = {
+ config = {
+ inherit server;
+ repository = "dotconfig";
+ targetPrefix = srcHome;
+ stow.".".targetPrefix = configHome;
+ };
+ nixos = {
+ inherit server;
+ targetPrefix = srcHome;
+ };
+ scripts = {
+ inherit server;
+ targetPrefix = srcHome;
+ stow."*".target = ".local/bin";
+ };
+ userscripts = {
+ inherit server;
+ targetPrefix = srcHome;
+ };
+ awesome = {
+ inherit server;
+ targetPrefix = configHome;
+ };
+ nvim = {
+ inherit server;
+ targetPrefix = configHome;
+ };
+ zsh = {
+ inherit server;
+ targetPrefix = configHome;
+ };
+ };
+ };
+ };
+}
diff --git a/users/timmy/resources/secrets/hashed-root-password.yaml b/users/timmy/resources/secrets/hashed-root-password.yaml
new file mode 100644
index 0000000..aead57a
--- /dev/null
+++ b/users/timmy/resources/secrets/hashed-root-password.yaml
@@ -0,0 +1,34 @@
+hashed-root-password: ENC[AES256_GCM,data:7Qgoeb/6JPNupkHCBEzCs0FMP2cDEw972bjCRWeMrBrAMZzLsZc3Mbv03s1zLztUp6Ie93R5lVsamxKPUnaPt+Tnr/l+0E9aTmt7j7L6UzmWr12nj3FHxxTSU9ief6+ioIk+S4eICJspIQ==,iv:VoWP4qBCGzuYRpQw4nilUXByJ+ZwyZR/BdKowi+53DM=,tag:x6A00VCm8BEOhtv/WySXrQ==,type:str]
+sops:
+ age:
+ - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBraEhrNTZaMjVQQUdYOUU5
+ MURZVTcxRWxBRC9HMEg3amtsNE1qWEdsSkc4CmRYaVZxT0I3eHphbHAydzUwZEVH
+ UU1FL0J2bW5yMWYxeDAydlhOZ3dvbmcKLS0tIGwzcWtZbmI5aG9tSXF1d3hlelg3
+ dEczZGlSWmg0OEhoeERSSUMxVHR4a00Kwe7zenWUpfI+NxCM6m208smw6vGuPb7x
+ UF3d0LvQ7YJqlUsuuUjaBQcx0EHgjYH6NiT3ZimeBJJ4WfYEi87hkw==
+ -----END AGE ENCRYPTED FILE-----
+ - recipient: age1f0tmpy2nam58skmznjyqd3zf54rxtfrk6fda0vlpq9y3yg6wac7sjf0vja
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBISklHVjNLWkN6WmlRWGhv
+ LzczV3g5N1NKYWFGd09laWtmYU5RUUJDWkdRCkxBb2FRNnh1d3lIYVVNd0h3NG9M
+ VHkrZmpYbVdtc1hjR29vaTduOUtBQzQKLS0tIFF5Z1l3N25kaHdVNGI2aTJaQU90
+ SXdjTmdnUU5zdXlEVzM1cnl6R096QjgK1ZscVvYvEpiDgCXOaduqZ+aT1lCD2HBS
+ eOpseCvD78JQym55CWvZEGHjmZOH3+Ay2Wd0+W2Z9E43yKxIkT/Nng==
+ -----END AGE ENCRYPTED FILE-----
+ - recipient: age1zfvmt2avdlfz0fvchczplc84u7m8vqausm7zytl9s4x9m9yax4cqy30zpz
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIZHVaTTNsWlQ2Ynk0WVNY
+ QkNEOUwvNXI0ZUdNSFZUdVJsQnh1NUZlbm13Ckg0NG1HU1pMOS9UZWJGaFBCQWth
+ VHZkdFB3RHEwMEhqZ3RtNmpzYUZPczQKLS0tIEwrVGViSTZGTGIrUnBBeGl0Mk1R
+ ZmZvWk9paVc2YmN5R0xZUmU2cmc0VDQKQVbliGNMYdEKW+z5f/yEnVvxIJFeA5h8
+ l6d9kxegWkQtQCBqEAC7+0ftDC/BnzdZD9aQAA/VeNNwtkrXib7YZQ==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2025-07-17T22:34:07Z"
+ mac: ENC[AES256_GCM,data:B95HuJC2o8B+P1f9kAtJTcSty7YSAByuqe/Xs6ce6780p05FuzWM5X9bwvwsYXngGNKqCHksWf50UXzJ3eyc6y4ISxdxljAv2FmJFKw4NkfGaOMiRLlGPMn1uFpOtkRT+qL0+mupWG/Ap3zcpbxjsDx46PUur+e6yRxlAHw8mGw=,iv:DYobhWK+4+7vOog7BrBASiHrEzzz0P6zqgWxexfcLG8=,tag:skGwUpDEB8e3TCjrxs5peA==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.10.2
diff --git a/users/timmy/resources/secrets/wpa_supplicant-conf.yaml b/users/timmy/resources/secrets/wpa_supplicant-conf.yaml
new file mode 100644
index 0000000..5f82025
--- /dev/null
+++ b/users/timmy/resources/secrets/wpa_supplicant-conf.yaml
@@ -0,0 +1,16 @@
+wpa_supplicant-conf: ENC[AES256_GCM,data:Hntld2CdmE9ef+2PANrTT4+IHGxYEkKaMFg9xUmPz6RXvrtS2oAIVYV8lFFQ8jltbtzW8eCgFOqz2NfwyDaOhqRv/xLK/4MVpVRG0njmYV2e0YtfSe3XBOwoQaVxHKl7hk92Yat5vFBJY79+w3DiM36/WBEY0RlxsKIViEU76JYMpNhqipaqlZG2IjfNTIv4y6DlHaKiolzNQagtvqhid3GmOkMoHaQp5nIp9taW81hWFZZbIZMnEFIO60z3p3LXvhDPCXfkoZRv7obbl/rXzLzp2lSOV7sBLRqy3it2SplP1tGOkePsDiBppHIrr9PXoyebY2+e8ACGPKv0tNmreEYthlHOxsmOuAGdWD3oZrGz/HA7h2YhHCnT4fQTpUj3vJ03LpnZ8RMzyxexAiAFVFh1JJ34etX+rHTzVgF7dN0nbZk3hb0YXQAj1lynDFYXIXkVfGq1hDRM/z9Q0JfB9Ef7iQVz4k0nO5QlyElWl1yBOjOWNoIibaysodDWRTxw21AWQ5GEEzMgG0AenHfzsrjUfSWUP5EKakiBGN0LduRyY5RmHQYbVHpkpP1PqCBmWBwm1xQ6JsOaajvNnc/x3fNZjIzCbGWprB/KbLnAbsF279cTIxzfMZEO3rHKxxsp,iv:h59506YhHe5Sq61StpMzGLRtx1Vr+pUoWquJeNjALh4=,tag:dS+7M5p8Xdpk6jfgzTk30w==,type:str]
+sops:
+ age:
+ - recipient: age1w80rc0dnuu8nw99gw64c596qqetm78jdnsqajr0u7ephykekr39qfz8vnv
+ enc: |
+ -----BEGIN AGE ENCRYPTED FILE-----
+ YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxdXBUWXEvRU85Tk1lNWgz
+ ZHpENjdudmRuaEJIeVRHOHlDbWNzK0tQMGs4CmV6MVBpdE5PMTBWMm1PcDVFQ2VM
+ b0IwWDFxLy8xYUcxRVZFSEsyYlBFS1UKLS0tIHpCbDQ0a29TZlVFTGp4aXJCSmJ4
+ ZGxqMFQ1NDk1OHJIOUd0cVV0dzNNQlkKzYX36u0rEq6dMTCJf6OON6LzcEEnAB5A
+ +M9t3OKUUNtwgksjBUEwqBLJ1sU9amijpK63GUxwp74YDtsb0YXHiw==
+ -----END AGE ENCRYPTED FILE-----
+ lastmodified: "2026-03-07T16:00:28Z"
+ mac: ENC[AES256_GCM,data:pHvHTZVa9xxiblcWhemSaG6f9tu1KIk8eS8bj3F9guitTwXKdtSpAD6ZXn1IOWRFsAui8yrRjt9COdMjThOaOP2QjnOthJ3TDnfRaZnGoPBRzscsFLf2DApm6QBLokWEFP/1w6WxZQM/ajFo4qsxq0llym1146CEJ/hCqMMmWD8=,iv:GLLi3lIo2wusrZCBXfNMwj5fSoy/6tiP84yyRBXZhsc=,tag:8U+2YBYzx0Pw6OtXB5MsYQ==,type:str]
+ unencrypted_suffix: _unencrypted
+ version: 3.12.1
diff --git a/users/timmy/user.nix b/users/timmy/user.nix
new file mode 100644
index 0000000..2695549
--- /dev/null
+++ b/users/timmy/user.nix
@@ -0,0 +1,22 @@
+{ lib, config, pkgs, userDetails, ... }: let
+ cfg = config._archetypes.tjkeller.setPasswords;
+ hashedPasswordFile = config.sops.secrets.hashed-root-password.path;
+in {
+ options._archetypes.tjkeller.setPasswords = {
+ enable = lib.mkEnableOption "set users password. requires hashed root password from sops";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Load hashed root password secret
+ sops.secrets.hashed-root-password = {
+ sopsFile = ./resources/secrets/hashed-root-password.yaml;
+ neededForUsers = true;
+ };
+
+ # Apply password file
+ users.users = {
+ root = { inherit hashedPasswordFile; };
+ ${userDetails.username} = lib.mkIf config._archetypes.users.primary.enable { inherit hashedPasswordFile; };
+ };
+ };
+}
diff --git a/users/timmy/wifi.nix b/users/timmy/wifi.nix
new file mode 100644
index 0000000..ab5332f
--- /dev/null
+++ b/users/timmy/wifi.nix
@@ -0,0 +1,17 @@
+{ lib, config, ... }: let
+ cfg = config._users.timmy.wifi;
+in {
+ options._users.timmy.wifi = {
+ enable = lib.mkEnableOption "enables wifi";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Load wpa_supplicant.conf secret config
+ sops.secrets.wpa_supplicant-conf.sopsFile = ./resources/secrets/wpa_supplicant-conf.yaml;
+
+ networking.wireless = {
+ enable = true; # Enables wireless support via wpa_supplicant.
+ _extraSecretConfigs.networks = config.sops.secrets.wpa_supplicant-conf.path; # Load secret network config via systemd credential
+ };
+ };
+}