nixos-dotfiles

nixos-dotfiles

https://git.tonybtw.com/nixos-dotfiles.git git://git.tonybtw.com/nixos-dotfiles.git

initial commit

Commit
748a4d2f9bad3b6d4af19a4137d1d64605d8bf06
Author
tonybtw <tonybtw@tonybtw.com>
Date
2025-11-23 05:29:09

Diff

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..56e7cd8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+hardware_configuration.nix
+.bashrc.local
diff --git a/config/alacritty b/config/alacritty
new file mode 160000
index 0000000..ad01fc8
--- /dev/null
+++ b/config/alacritty
@@ -0,0 +1 @@
+Subproject commit ad01fc88cbeeebb04e6544ea1cc65ba11d223378
diff --git a/config/bashrc b/config/bashrc
new file mode 160000
index 0000000..f2dd505
--- /dev/null
+++ b/config/bashrc
@@ -0,0 +1 @@
+Subproject commit f2dd505edcbdea2fec62f4602ac671d4085f381f
diff --git a/config/dmenu b/config/dmenu
new file mode 160000
index 0000000..9ae1c5e
--- /dev/null
+++ b/config/dmenu
@@ -0,0 +1 @@
+Subproject commit 9ae1c5e1e900dd94ab8086997946a1bc10b3adbb
diff --git a/config/doom b/config/doom
new file mode 160000
index 0000000..535925a
--- /dev/null
+++ b/config/doom
@@ -0,0 +1 @@
+Subproject commit 535925a95139ac5ed7e28010ea7a5efadb86f4af
diff --git a/config/dwl b/config/dwl
new file mode 160000
index 0000000..ab4cb6e
--- /dev/null
+++ b/config/dwl
@@ -0,0 +1 @@
+Subproject commit ab4cb6e28365cf8754d6d3bdd293c05abfc27e26
diff --git a/config/dwm b/config/dwm
new file mode 160000
index 0000000..a7d8a17
--- /dev/null
+++ b/config/dwm
@@ -0,0 +1 @@
+Subproject commit a7d8a175eefb5ff4565225d6e8cffb0e568a8c86
diff --git a/config/dwmblocks b/config/dwmblocks
new file mode 160000
index 0000000..5dae67a
--- /dev/null
+++ b/config/dwmblocks
@@ -0,0 +1 @@
+Subproject commit 5dae67a286025f6d5d4332b38aafbdba8d3cd965
diff --git a/config/foot b/config/foot
new file mode 160000
index 0000000..3c58f92
--- /dev/null
+++ b/config/foot
@@ -0,0 +1 @@
+Subproject commit 3c58f929de5421989d4088423769c96edabd21f4
diff --git a/config/hypr b/config/hypr
new file mode 160000
index 0000000..bd4ad38
--- /dev/null
+++ b/config/hypr
@@ -0,0 +1 @@
+Subproject commit bd4ad3899e4cb491373158e082f0439d02f07da7
diff --git a/config/mango b/config/mango
new file mode 160000
index 0000000..a899349
--- /dev/null
+++ b/config/mango
@@ -0,0 +1 @@
+Subproject commit a89934940fc4716218a940742005b581fe83a695
diff --git a/config/nvim b/config/nvim
new file mode 160000
index 0000000..cd7ef09
--- /dev/null
+++ b/config/nvim
@@ -0,0 +1 @@
+Subproject commit cd7ef093151fe926c5f8fd158596b916b3dbfd02
diff --git a/config/oxwm b/config/oxwm
new file mode 160000
index 0000000..2ebf5c1
--- /dev/null
+++ b/config/oxwm
@@ -0,0 +1 @@
+Subproject commit 2ebf5c1838be0d41ec99e7726d09ea7a22c6229b
diff --git a/config/picom b/config/picom
new file mode 160000
index 0000000..dc0d908
--- /dev/null
+++ b/config/picom
@@ -0,0 +1 @@
+Subproject commit dc0d9084527d5cfdf8dbb1ef2ae9ad0f050f5b3b
diff --git a/config/qtile b/config/qtile
new file mode 160000
index 0000000..375c650
--- /dev/null
+++ b/config/qtile
@@ -0,0 +1 @@
+Subproject commit 375c65096aea4dc739fd6bcd162c5dcbea35ec54
diff --git a/config/quickshell b/config/quickshell
new file mode 160000
index 0000000..5d14201
--- /dev/null
+++ b/config/quickshell
@@ -0,0 +1 @@
+Subproject commit 5d14201591f75fc1fc5c8a8ec3aea7ef58b42d98
diff --git a/config/rofi b/config/rofi
new file mode 160000
index 0000000..255b59d
--- /dev/null
+++ b/config/rofi
@@ -0,0 +1 @@
+Subproject commit 255b59d670c282efa04b7c87995feb66d1695db6
diff --git a/config/st b/config/st
new file mode 160000
index 0000000..f9166a5
--- /dev/null
+++ b/config/st
@@ -0,0 +1 @@
+Subproject commit f9166a5fba404e90e731c8277c48acabdf316b8b
diff --git a/config/sway b/config/sway
new file mode 160000
index 0000000..b5d7344
--- /dev/null
+++ b/config/sway
@@ -0,0 +1 @@
+Subproject commit b5d7344640b4bac410f7112919bb278cfab01a68
diff --git a/config/tmux b/config/tmux
new file mode 160000
index 0000000..4df1f05
--- /dev/null
+++ b/config/tmux
@@ -0,0 +1 @@
+Subproject commit 4df1f0517171520faf1b91eb949769dff1dbd2b2
diff --git a/config/waybar b/config/waybar
new file mode 160000
index 0000000..bb0b975
--- /dev/null
+++ b/config/waybar
@@ -0,0 +1 @@
+Subproject commit bb0b975b264f28dc5d7f0e22682f4ff2cafc8a80
diff --git a/configuration.nix b/configuration.nix
new file mode 100644
index 0000000..aefeffe
--- /dev/null
+++ b/configuration.nix
@@ -0,0 +1,177 @@
+{
+  config,
+  lib,
+  pkgs,
+  unstable_pkgs,
+  nwm,
+  oxwm,
+  ...
+}: let
+  ns = pkgs.writeShellScriptBin "ns" (builtins.readFile ./scripts/nixpkgs.sh);
+  # dwlsesh = pkgs.writeShellScriptBin "dwlsesh" (builtins.readFile ./scripts/dwlsesh.sh);
+  cinit = pkgs.writeShellScriptBin "cinit" (builtins.readFile ./scripts/cinit.sh);
+  # myDwl = pkgs.dwl.overrideAttrs (old: {
+  #   src = ./config/dwl;
+  #   nativeBuildInputs =
+  #     (old.nativeBuildInputs or [])
+  #     ++ [
+  #       pkgs.pkg-config
+  #       pkgs.wayland
+  #       pkgs.wayland-protocols
+  #       pkgs.scdoc
+  #     ];
+  #
+  #   buildInputs =
+  #     (old.buildInputs or [])
+  #     ++ [
+  #       unstable_pkgs.wlroots
+  #       unstable_pkgs.libdrm
+  #       pkgs.wayland
+  #       unstable_pkgs.fcft
+  #       pkgs.pixman
+  #       pkgs.fontconfig
+  #       pkgs.freetype
+  #       pkgs.harfbuzz
+  #       pkgs.libxkbcommon
+  #     ];
+  #   patches = [];
+  # });
+in {
+  imports = [
+    ./hardware-configuration.nix
+  ];
+
+  boot.loader.systemd-boot.enable = true;
+  boot.loader.efi.canTouchEfiVariables = true;
+
+  networking.hostName = "nixos-btw";
+  networking.networkmanager.enable = true;
+
+  programs.nix-ld.enable = true;
+
+  time.timeZone = "America/Los_Angeles";
+  services = {
+    picom.enable = true;
+    displayManager = {
+      ly.enable = true;
+    };
+    xserver = {
+      enable = true;
+      autoRepeatDelay = 200;
+      autoRepeatInterval = 35;
+      windowManager = {
+        xmonad = {
+          enable = true;
+          enableContribAndExtras = true;
+          extraPackages = hpkgs: [
+            hpkgs.xmonad
+            hpkgs.xmonad-extras
+            hpkgs.xmonad-contrib
+          ];
+        };
+        qtile.enable = true;
+        oxwm.enable = true;
+        dwm = {
+          enable = true;
+          package = unstable_pkgs.dwm.overrideAttrs {
+            src = ./config/dwm;
+          };
+        };
+      };
+      modules = [pkgs.xorg.xf86inputlibinput];
+      displayManager.sessionCommands = ''
+        xwallpaper --zoom ~/walls/waifus/waifu3.jpg
+      '';
+    };
+  };
+
+  users.users.tony = {
+    isNormalUser = true;
+    extraGroups = ["wheel"];
+    packages = with pkgs; [
+      tree
+    ];
+  };
+
+  # programs.hyprland = {
+  #   enable = true;
+  #   xwayland.enable = true;
+  #   package = unstable_pkgs.hyprland;
+  # };
+
+  programs.mango = {
+    enable = true;
+  };
+
+  # programs.sway = {
+  #   enable = true;
+  #   wrapperFeatures.gtk = true;
+  #   xwayland.enable = true;
+  # };
+
+  services.desktopManager.cosmic.enable = true;
+
+  programs.firefox.enable = true;
+  environment.systemPackages = [
+    pkgs.vim
+    pkgs.wget
+    pkgs.alacritty
+    pkgs.git
+    pkgs.pavucontrol
+    pkgs.xorg.xinit
+    pkgs.xorg.xf86inputlibinput
+    pkgs.ncurses.dev
+    pkgs.pkg-config
+    unstable_pkgs.haskellPackages.xmonad-contrib
+    unstable_pkgs.hyprland
+    unstable_pkgs.uwsm
+    (unstable_pkgs.writeShellScriptBin
+      "reload_waybar"
+      ''
+        pkill waybar
+        sleep 0.2
+        waybar -c ~/.config/mango/config.jsonc &
+      '')
+    (unstable_pkgs.writeShellScriptBin
+      "snip"
+      ''
+        ${unstable_pkgs.grim}/bin/grim -l 0 -g "$(${unstable_pkgs.slurp}/bin/slurp)" - | wl-copy
+      '')
+    ns
+    # dwlsesh
+    # myDwl
+    cinit
+    unstable_pkgs.xmobar
+  ];
+
+  # Hack for now.. will fix later
+  # services.displayManager.sessionPackages = [
+  #   (pkgs.writeTextFile {
+  #     name = "dwl-session";
+  #     destination = "/share/wayland-sessions/dwl.desktop";
+  #     text = ''
+  #       [Desktop Entry]
+  #       Name=dwl
+  #       Comment=dwl window manager
+  #       Exec=dwlsesh
+  #       TryExec=dwl
+  #       Type=Application
+  #     '';
+  #     passthru.providedSessions = ["dwl"];
+  #   })
+  # ];
+
+  fonts.packages = with pkgs; [
+    nerd-fonts.jetbrains-mono
+  ];
+
+  nixpkgs.config.allowUnfree = true;
+  # programs.niri.enable = true;
+
+  nix.settings = {
+    experimental-features = ["nix-command" "flakes"];
+    # Increase download buffer size limit
+    download-buffer-size = 268435456; # 256 MB
+  };
+  system.stateVersion = "25.05";
+}
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..ad3d7ed
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,200 @@
+{
+  "nodes": {
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": "nixpkgs-lib"
+      },
+      "locked": {
+        "lastModified": 1749398372,
+        "narHash": "sha256-tYBdgS56eXYaWVW3fsnPQ/nFlgWi/Z2Ymhyu21zVM98=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "9305fe4e5c2a6fcf5ba6a3ff155720fbe4076569",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "flake-utils": {
+      "inputs": {
+        "systems": [
+          "mango",
+          "scenefx",
+          "systems"
+        ]
+      },
+      "locked": {
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "home-manager": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1758463745,
+        "narHash": "sha256-uhzsV0Q0I9j2y/rfweWeGif5AWe0MGrgZ/3TjpDYdGA=",
+        "owner": "nix-community",
+        "repo": "home-manager",
+        "rev": "3b955f5f0a942f9f60cdc9cacb7844335d0f21c3",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "ref": "release-25.05",
+        "repo": "home-manager",
+        "type": "github"
+      }
+    },
+    "mango": {
+      "inputs": {
+        "flake-parts": "flake-parts",
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "scenefx": "scenefx"
+      },
+      "locked": {
+        "lastModified": 1762893548,
+        "narHash": "sha256-0Mo2S4cB8TjhgzRmf2eumaxufSNYeKsXOmpQaAuwfwQ=",
+        "owner": "tonybanters",
+        "repo": "mangowc",
+        "rev": "859e9a91ded0b9700c14bdbdc8ccfafa4493fca1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "tonybanters",
+        "repo": "mangowc",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1762977756,
+        "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-lib": {
+      "locked": {
+        "lastModified": 1748740939,
+        "narHash": "sha256-rQaysilft1aVMwF14xIdGS3sj1yHlI6oKQNBRTF40cc=",
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "rev": "656a64127e9d791a334452c6b6606d17539476e2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "nixpkgs.lib",
+        "type": "github"
+      }
+    },
+    "nixpkgs-unstable": {
+      "locked": {
+        "lastModified": 1762977756,
+        "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "oxwm": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1763100308,
+        "narHash": "sha256-StnQuPGSQgH5zKh4xIs1wF/2G0TidUMpPGIaj2waxec=",
+        "path": "/home/tony/repos/oxwm",
+        "type": "path"
+      },
+      "original": {
+        "path": "/home/tony/repos/oxwm",
+        "type": "path"
+      }
+    },
+    "root": {
+      "inputs": {
+        "home-manager": "home-manager",
+        "mango": "mango",
+        "nixpkgs": "nixpkgs",
+        "nixpkgs-unstable": "nixpkgs-unstable",
+        "oxwm": "oxwm"
+      }
+    },
+    "scenefx": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": [
+          "mango",
+          "nixpkgs"
+        ],
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1762447505,
+        "narHash": "sha256-VEBQ8KXkSS4c+kdAhmvq06lEd9WNeCXdRK1U+qSilFw=",
+        "owner": "wlrfx",
+        "repo": "scenefx",
+        "rev": "7f9e7409f6169fa637f1265895c121a8f8b70272",
+        "type": "github"
+      },
+      "original": {
+        "owner": "wlrfx",
+        "repo": "scenefx",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1689347949,
+        "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
+        "owner": "nix-systems",
+        "repo": "default-linux",
+        "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default-linux",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..d798f76
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,93 @@
+{
+  description = "NixOS from Scratch";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+    home-manager = {
+      url = "github:nix-community/home-manager/release-25.05";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    oxwm = {
+      url = "path:/home/tony/repos/oxwm";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    # nwm = {
+    #   url = "github:tonybanters/nwm-flake";
+    #   inputs.nixpkgs.follows = "nixpkgs";
+    # };
+    mango = {
+      url = "github:tonybanters/mangowc";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    # quickshell = {
+    #   url = "github:outfoxxed/quickshell";
+    #   inputs.nixpkgs.follows = "nixpkgs";
+    # };
+    # noctalia = {
+    #   url = "github:noctalia-dev/noctalia-shell";
+    #   inputs.nixpkgs.follows = "nixpkgs";
+    #   inputs.quickshell.follows = "quickshell";
+    # };
+  };
+
+  outputs = {
+    self,
+    nixpkgs,
+    nixpkgs-unstable,
+    home-manager,
+    oxwm,
+    mango,
+    # nwm,
+    # noctalia,
+    ...
+  }: let
+    system = "x86_64-linux";
+    pkgs = import nixpkgs {
+      system = system;
+    };
+    unstable_pkgs = import nixpkgs-unstable {
+      system = system;
+      config.allowUnfree = true; # Sorry Uncle Richard
+    };
+  in {
+    devShells.${system}.st = pkgs.mkShell {
+      # toolchain + headers/libs
+      packages = with pkgs; [
+        pkg-config
+        xorg.libX11
+        xorg.libXft
+        xorg.libXinerama
+        fontconfig
+        freetype
+        harfbuzz
+      ];
+      shellHook = ''
+        export PS1="(suckless-dev) $PS1"
+      '';
+    };
+    nixosConfigurations.nixos-btw = nixpkgs.lib.nixosSystem {
+      system = system;
+      specialArgs = {inherit unstable_pkgs oxwm;};
+      modules = [
+        ./configuration.nix
+        ./irc.nix
+        # ./modules/noctalia.nix
+        oxwm.nixosModules.default
+        # nwm.nixosModules.default
+        mango.nixosModules.mango
+        home-manager.nixosModules.home-manager
+        {
+          home-manager = {
+            useGlobalPkgs = true;
+            useUserPackages = true;
+            users.tony = import ./home.nix;
+            backupFileExtension = "backup";
+            extraSpecialArgs = {inherit unstable_pkgs;};
+          };
+        }
+      ];
+    };
+  };
+}
diff --git a/future-modules/IRC-SERVER-SETUP.md b/future-modules/IRC-SERVER-SETUP.md
new file mode 100644
index 0000000..88f2807
--- /dev/null
+++ b/future-modules/IRC-SERVER-SETUP.md
@@ -0,0 +1,350 @@
+# IRC Server Setup & Connection Guide
+
+## Server Setup
+
+### 1. Configure Your Server
+
+Edit `irc-public.nix` and update these important fields:
+
+```nix
+Name = irc.yourdomain.com          # Your actual domain
+AdminInfo1 = Your Name             # Your name
+AdminInfo2 = Your Community Server # Server description
+AdminEMail = admin@yourdomain.com  # Your email
+CloakHostSalt = your-random-salt-here  # Generate random string
+```
+
+Generate a random salt for host cloaking:
+```bash
+openssl rand -hex 32
+```
+
+### 2. Set Up Operator Password
+
+Generate a secure password hash:
+```bash
+# Generate password hash
+htpasswd -nBC 10 "" | cut -d: -f2
+```
+
+Update the `[Operator]` section with the hash:
+```nix
+[Operator]
+Name = admin
+Password = $2y$10$your-generated-hash-here
+Mask = *!*@yourdomain.com  # Restrict to your domain
+```
+
+### 3. Enable in Your NixOS Configuration
+
+Add to your `flake.nix` modules:
+```nix
+modules = [
+  ./configuration.nix
+  ./irc-public.nix  # Add this line
+  # ... other modules
+];
+```
+
+### 4. Set Up DNS
+
+Point your domain to your server's IP:
+```
+A     irc.yourdomain.com    -> YOUR_SERVER_IP
+AAAA  irc.yourdomain.com    -> YOUR_SERVER_IPv6 (optional)
+```
+
+### 5. (Optional) Enable SSL/TLS
+
+For encrypted connections, uncomment the SSL section in `irc-public.nix` and generate certificates:
+
+**Option A: Let's Encrypt (Recommended)**
+```nix
+# Uncomment in irc-public.nix:
+security.acme = {
+  acceptTerms = true;
+  defaults.email = "admin@yourdomain.com";
+  certs."irc.yourdomain.com" = {
+    group = "ngircd";
+    postRun = "systemctl reload ngircd.service";
+  };
+};
+```
+
+**Option B: Self-Signed Certificates**
+```bash
+sudo mkdir -p /etc/ngircd/ssl
+cd /etc/ngircd/ssl
+
+# Generate DH parameters
+openssl dhparam -out dhparams.pem 2048
+
+# Generate self-signed certificate
+openssl req -x509 -newkey rsa:4096 -keyout server-key.pem \
+  -out server-cert.pem -days 365 -nodes \
+  -subj "/CN=irc.yourdomain.com"
+
+sudo chown ngircd:ngircd /etc/ngircd/ssl/*
+sudo chmod 600 /etc/ngircd/ssl/server-key.pem
+```
+
+### 6. Deploy
+
+```bash
+sudo nixos-rebuild switch --flake /home/tony/nixos-dotfiles#nixos-btw
+```
+
+### 7. Verify Server is Running
+
+```bash
+systemctl status ngircd.service
+ss -tlnp | grep 6667
+```
+
+---
+
+## How to Connect to the Server
+
+### For Users
+
+#### Desktop IRC Clients
+
+**HexChat (Linux/Windows/Mac)**
+1. Install HexChat from your package manager or [hexchat.github.io](https://hexchat.github.io/)
+2. Open HexChat and go to Network List
+3. Click "Add" and name it "Community Server"
+4. Click "Edit" and add:
+   - Server: `irc.yourdomain.com/6667` (or `/6697` for SSL)
+   - Check "Use SSL" if using port 6697
+5. Set your nickname
+6. Click "Connect"
+7. Join channels: `/join #general`
+
+**WeeChat (Linux/Mac Terminal)**
+```bash
+# Install
+sudo apt install weechat  # Debian/Ubuntu
+# or: brew install weechat  # macOS
+
+# Run and configure
+weechat
+/server add community irc.yourdomain.com/6667
+/set irc.server.community.nicks "yournick"
+/connect community
+/join #general
+```
+
+**Irssi (Linux/Mac Terminal)**
+```bash
+# Install
+sudo apt install irssi  # Debian/Ubuntu
+
+# Run and connect
+irssi
+/network add -nick yournick Community
+/server add -auto -network Community irc.yourdomain.com 6667
+/connect Community
+/join #general
+```
+
+**mIRC (Windows)**
+1. Download from [mirc.com](https://www.mirc.com/)
+2. Tools → Options → Connect
+3. Add server:
+   - Description: Community Server
+   - IRC Server: irc.yourdomain.com
+   - Ports: 6667 (or 6697 for SSL)
+4. Connect and join #general
+
+**Textual (macOS)**
+1. Download from Mac App Store
+2. Server → New Server
+3. Enter server details:
+   - Address: irc.yourdomain.com
+   - Port: 6667 (or 6697 for SSL)
+4. Connect
+
+#### Mobile IRC Clients
+
+**Android - Revolution IRC**
+1. Install from Google Play Store
+2. Add Server:
+   - Name: Community Server
+   - Address: irc.yourdomain.com
+   - Port: 6667 (or 6697 for SSL)
+3. Set nickname and connect
+4. Join #general
+
+**iOS - Palaver**
+1. Install from App Store
+2. Add Network:
+   - Server: irc.yourdomain.com
+   - Port: 6667
+3. Set nickname and connect
+
+#### Web Browser (if TheLounge enabled)
+
+Visit: `http://your-server-ip:9000`
+- Register an account
+- Auto-connects to the server
+- Join channels from the interface
+
+---
+
+## Basic IRC Commands
+
+### Essential Commands
+```
+/nick NewNickname          - Change your nickname
+/join #channel             - Join a channel
+/part #channel             - Leave a channel
+/msg nickname message      - Send private message
+/query nickname            - Open private chat window
+/quit                      - Disconnect from server
+```
+
+### Channel Commands
+```
+/topic New topic text      - Set channel topic (if op)
+/me does something         - Send action message
+/names                     - List users in channel
+/whois nickname           - Get info about user
+```
+
+### Channel Operator Commands
+```
+/mode #channel +o nick     - Give operator status
+/mode #channel +v nick     - Give voice (can talk in moderated)
+/mode #channel +m          - Moderated channel
+/mode #channel +t          - Only ops can set topic
+/kick #channel nick        - Kick user from channel
+/mode #channel +b nick!*@* - Ban user
+```
+
+---
+
+## Default Channels
+
+- **#general** - Main discussion channel
+- **#announcements** - Server announcements (moderated, read-only for regular users)
+- **#support** - Help and support
+- **#offtopic** - Off-topic discussions
+
+---
+
+## Server Administration
+
+### Becoming an Operator
+```
+/oper admin your-password
+```
+
+### Managing Users
+```
+/kill nickname reason       - Disconnect user
+/gline *!*@hostname 3600 reason  - Global ban for 1 hour
+```
+
+### Server Commands
+```
+/rehash                    - Reload configuration
+/restart                   - Restart server
+```
+
+---
+
+## Troubleshooting
+
+### Can't Connect
+1. Check firewall: `sudo ufw status` or `sudo firewall-cmd --list-ports`
+2. Verify service: `systemctl status ngircd.service`
+3. Check logs: `journalctl -u ngircd.service -f`
+4. Test locally: `telnet localhost 6667`
+
+### SSL Connection Issues
+1. Verify certificates exist in `/etc/ngircd/ssl/`
+2. Check certificate permissions: `ls -la /etc/ngircd/ssl/`
+3. Test SSL: `openssl s_client -connect irc.yourdomain.com:6697`
+
+### Port Already in Use
+```bash
+# Find what's using the port
+sudo ss -tlnp | grep 6667
+# Kill the process if needed
+sudo systemctl stop ngircd.service
+```
+
+---
+
+## Security Best Practices
+
+1. **Use SSL/TLS** - Enable port 6697 with Let's Encrypt
+2. **Strong Operator Password** - Use bcrypt hash (htpasswd)
+3. **Firewall Rules** - Only allow IRC ports
+4. **Regular Updates** - Keep NixOS and ngircd updated
+5. **Monitor Logs** - Watch for abuse: `journalctl -u ngircd.service -f`
+6. **Backup Config** - Keep your configuration in git
+
+---
+
+## Additional Features
+
+### Bridge to Discord (Optional)
+
+Use [matterbridge](https://github.com/42wim/matterbridge) to bridge IRC and Discord:
+
+```nix
+services.matterbridge = {
+  enable = true;
+  configPath = "/etc/matterbridge/config.toml";
+};
+```
+
+### IRC Bouncer (Stay Connected)
+
+Use [ZNC](https://wiki.znc.in/) to stay connected even when offline:
+
+```nix
+services.znc = {
+  enable = true;
+  config = {
+    LoadModule = [ "webadmin" ];
+    Listener.l = {
+      Port = 5000;
+      SSL = false;
+    };
+  };
+};
+```
+
+---
+
+## Support
+
+- IRC Support Channel: #support on irc.yourdomain.com
+- Server Admin: admin@yourdomain.com
+- Documentation: https://ngircd.barton.de/documentation
+
+---
+
+## Quick Start Summary
+
+**Server Admin:**
+```bash
+# 1. Edit irc-public.nix with your details
+# 2. Add to flake.nix modules
+# 3. Deploy
+sudo nixos-rebuild switch --flake .#nixos-btw
+# 4. Verify
+systemctl status ngircd.service
+```
+
+**Users:**
+```bash
+# Install IRC client (e.g., HexChat)
+# Connect to: irc.yourdomain.com:6667
+# Join: #general
+# Chat!
+```
+
+Enjoy your self-hosted IRC community server!
diff --git a/future-modules/cloudflare-setup.md b/future-modules/cloudflare-setup.md
new file mode 100644
index 0000000..2072c4a
--- /dev/null
+++ b/future-modules/cloudflare-setup.md
@@ -0,0 +1,259 @@
+# Cloudflare Setup Guide for Home Server
+
+This guide walks you through setting up Cloudflare to:
+1. Point `chat.tonybtw.com` to your home IP (dynamic DNS)
+2. Get SSL certificates via DNS challenge (works behind NAT)
+
+---
+
+## Step 1: Point Domain to Cloudflare
+
+1. **Log in to Cloudflare:** https://dash.cloudflare.com
+2. **Add a site:** Click "Add a site" → Enter `tonybtw.com`
+3. **Choose Free plan**
+4. **Cloudflare will scan your DNS records** (if you have existing ones)
+5. **Click Continue**
+
+---
+
+## Step 2: Update Nameservers at Your Registrar
+
+Cloudflare will show you two nameservers like:
+```
+chloe.ns.cloudflare.com
+walt.ns.cloudflare.com
+```
+
+1. Log in to where you bought `tonybtw.com` (Namecheap, Porkbun, etc.)
+2. Find **"Nameservers"** or **"DNS Settings"**
+3. Change from default nameservers to Cloudflare's nameservers
+4. Save changes
+
+**Wait 5-60 minutes** for nameserver propagation. Cloudflare will email you when it's active.
+
+---
+
+## Step 3: Add DNS Records in Cloudflare
+
+Once Cloudflare is active:
+
+1. Go to **DNS → Records**
+2. Add these records:
+
+### **A Records:**
+
+| Type | Name  | Content | Proxy Status | TTL |
+|------|-------|---------|--------------|-----|
+| A | chat | YOUR_HOME_IP | DNS only (gray cloud) | Auto |
+| A | conference.chat | YOUR_HOME_IP | DNS only | Auto |
+| A | upload.chat | YOUR_HOME_IP | DNS only | Auto |
+
+**IMPORTANT:** Click the **gray cloud** icon (not orange). Orange = Cloudflare proxy (breaks XMPP).
+
+**To find your home IP:**
+```bash
+curl ifconfig.me
+```
+
+### **SRV Records (Optional but Recommended):**
+
+These help XMPP clients auto-discover your server.
+
+| Type | Name | Service | Protocol | Priority | Weight | Port | Target |
+|------|------|---------|----------|----------|--------|------|--------|
+| SRV | @ | _xmpp-client | _tcp | 0 | 5 | 5222 | chat.tonybtw.com |
+| SRV | @ | _xmpp-server | _tcp | 0 | 5 | 5269 | chat.tonybtw.com |
+
+**Note:** Some SRV record UIs differ. Key values:
+- Service: `_xmpp-client` or `_xmpp-server`
+- Protocol: `_tcp`
+- Port: `5222` (client) or `5269` (server)
+- Target: `chat.tonybtw.com`
+
+---
+
+## Step 4: Create API Token for Dynamic DNS
+
+Your home IP changes occasionally. We need an API token so NixOS can update Cloudflare automatically.
+
+1. Go to **My Profile → API Tokens**
+2. Click **"Create Token"**
+3. Click **"Create Custom Token"**
+4. Set:
+   - **Token Name:** `NixOS DDNS`
+   - **Permissions:**
+     - `Zone → DNS → Edit`
+   - **Zone Resources:**
+     - `Include → Specific zone → tonybtw.com`
+5. Click **"Continue to summary"**
+6. Click **"Create Token"**
+7. **Copy the token** (you'll only see it once!)
+
+Example token format: `abcdef1234567890_ABCDEFGHIJKLMNOP`
+
+Save this for the next step.
+
+---
+
+## Step 5: Create Cloudflare Token File on Your Server
+
+On your home server (the laptop):
+
+```bash
+# Create secrets directory
+sudo mkdir -p /root/secrets
+sudo chmod 700 /root/secrets
+
+# Create Cloudflare DDNS token file
+sudo nano /root/secrets/cloudflare-token
+```
+
+Paste your API token (just the token, nothing else):
+```
+abcdef1234567890_ABCDEFGHIJKLMNOP
+```
+
+Save and exit (Ctrl+O, Enter, Ctrl+X).
+
+```bash
+# Set permissions
+sudo chmod 600 /root/secrets/cloudflare-token
+```
+
+---
+
+## Step 6: Create Cloudflare ACME Credentials
+
+For Let's Encrypt SSL certificates using DNS challenge:
+
+```bash
+sudo nano /root/secrets/cloudflare-acme-credentials
+```
+
+Paste this (replace with YOUR token):
+```
+CF_DNS_API_TOKEN=abcdef1234567890_ABCDEFGHIJKLMNOP
+```
+
+Save and exit.
+
+```bash
+# Set permissions
+sudo chmod 600 /root/secrets/cloudflare-acme-credentials
+```
+
+---
+
+## Step 7: Verify DNS Configuration
+
+Wait 5 minutes for DNS propagation, then test:
+
+```bash
+# Check A record
+dig chat.tonybtw.com
+
+# Should show:
+# chat.tonybtw.com. 300 IN A YOUR_HOME_IP
+
+# Check SRV records
+dig SRV _xmpp-client._tcp.tonybtw.com
+
+# Should show:
+# _xmpp-client._tcp.tonybtw.com. 300 IN SRV 0 5 5222 chat.tonybtw.com.
+```
+
+If these work, you're ready to deploy!
+
+---
+
+## Step 8: Deploy NixOS Configuration
+
+Now that Cloudflare is set up, go back to the home deployment guide and continue with:
+1. Importing modules in `configuration.nix`
+2. Running `nixos-rebuild switch`
+3. Creating XMPP accounts
+
+---
+
+## Troubleshooting
+
+### **DNS not resolving?**
+- Check nameservers are pointing to Cloudflare: `dig NS tonybtw.com`
+- Wait longer (DNS propagation can take up to 48 hours, usually 10 minutes)
+- Make sure proxy is **disabled** (gray cloud, not orange)
+
+### **API token not working?**
+- Verify token has `Zone → DNS → Edit` permission
+- Check token is scoped to `tonybtw.com` zone
+- Regenerate token if unsure
+
+### **ACME certificate failing?**
+- Check `/root/secrets/cloudflare-acme-credentials` format
+- Verify token has DNS edit permission
+- Check logs: `sudo journalctl -u acme-chat.tonybtw.com`
+
+---
+
+## Cloudflare Settings (Recommended)
+
+### **SSL/TLS:**
+1. Go to **SSL/TLS → Overview**
+2. Set encryption mode to **"Full"** (not "Full (strict)")
+3. This allows self-signed certs on your server
+
+### **Security:**
+- **Firewall Rules:** You can add rules to block countries/IPs if needed
+- **Rate Limiting:** Can limit connections to prevent abuse
+- **DDoS Protection:** Automatic (part of Cloudflare free tier)
+
+### **Speed:**
+- **Auto Minify:** Leave OFF (can break XMPP)
+- **Brotli:** Can enable (safe)
+
+---
+
+## What Cloudflare Can and Can't See
+
+### **Cloudflare CAN see (when proxy is DISABLED - gray cloud):**
+- DNS queries for `chat.tonybtw.com`
+- Nothing else (they're just a DNS provider)
+
+### **Cloudflare CAN'T see:**
+- Your XMPP traffic (encrypted, goes directly to your home IP)
+- Who you're talking to (OMEMO encryption)
+- Message contents (E2E encrypted)
+
+**By using "DNS only" mode (gray cloud), you bypass Cloudflare's proxy entirely.** They just update DNS records. Your XMPP traffic goes directly from clients → your home server.
+
+---
+
+## Maintenance
+
+### **Updating Your Home IP Manually (if DDNS fails):**
+1. Go to Cloudflare Dashboard
+2. **DNS → Records**
+3. Edit `chat` A record
+4. Change IP to your current home IP
+5. Save
+
+But NixOS should handle this automatically via `cloudflare-dyndns` service.
+
+### **Check DDNS Status:**
+```bash
+# Check if DDNS service is running
+sudo systemctl status cloudflare-dyndns
+
+# Check logs
+sudo journalctl -u cloudflare-dyndns
+```
+
+---
+
+## Next Steps
+
+Once Cloudflare is configured:
+1. Continue with router port forwarding guide
+2. Deploy NixOS configuration
+3. Test XMPP connection
+
+You're almost there!
diff --git a/future-modules/home-deployment-guide.md b/future-modules/home-deployment-guide.md
new file mode 100644
index 0000000..5da1d60
--- /dev/null
+++ b/future-modules/home-deployment-guide.md
@@ -0,0 +1,673 @@
+# Home Server XMPP Deployment Guide
+
+Complete guide to deploying XMPP on your home server (the laptop downstairs) with Cloudflare DDNS and `chat.tonybtw.com`.
+
+---
+
+## Overview
+
+**What you're building:**
+- XMPP server (Prosody) on your home laptop
+- Domain: `chat.tonybtw.com`
+- Cloudflare DDNS (auto-updates your home IP)
+- SSL via Let's Encrypt (DNS challenge)
+- Port forwarding on router
+- Foundation for future homelab (Jellyfin, Nextcloud, etc.)
+
+**Time estimate:** 1-2 hours
+
+---
+
+## Prerequisites
+
+- [ ] You own `tonybtw.com`
+- [ ] Your laptop is running NixOS
+- [ ] Your laptop is always on (or nearly always)
+- [ ] You have access to your router admin panel
+- [ ] Your ISP doesn't block ports 80, 443, 5222, 5269, 5281
+
+---
+
+## Step 1: Cloudflare Setup
+
+Follow the **`cloudflare-setup.md`** guide completely.
+
+**Summary:**
+1. Point `tonybtw.com` nameservers to Cloudflare
+2. Add DNS A records for `chat.tonybtw.com`
+3. Create API token for DDNS
+4. Create `/root/secrets/cloudflare-token` file
+5. Create `/root/secrets/cloudflare-acme-credentials` file
+
+**Stop here and do that first.** Come back when Cloudflare is configured.
+
+---
+
+## Step 2: Router Port Forwarding
+
+Follow the **`router-portforward-guide.md`** guide completely.
+
+**Summary:**
+1. Find your server's local IP (e.g., `192.168.1.100`)
+2. Set static IP (DHCP reservation)
+3. Forward ports: 22, 80, 443, 5222, 5269, 5281
+4. Test ports are open from outside network
+
+**Stop here and do that.** Come back when port forwarding works.
+
+---
+
+## Step 3: Prepare NixOS Configuration
+
+On your home server (laptop):
+
+```bash
+cd /etc/nixos
+```
+
+### **A. Copy Config Files**
+
+If you have them in `~/nixos-dotfiles`:
+
+```bash
+sudo cp ~/nixos-dotfiles/home-server-base.nix /etc/nixos/
+sudo cp ~/nixos-dotfiles/xmpp-home.nix /etc/nixos/
+```
+
+Or clone from your git repo if you pushed them.
+
+### **B. Edit Configuration Files**
+
+#### **Edit `home-server-base.nix`:**
+
+```bash
+sudo vim /etc/nixos/home-server-base.nix
+```
+
+**Change line 20:**
+```nix
+domains = [
+  "chat.tonybtw.com"  # Verify this is YOUR domain
+];
+```
+
+**Change line 44:**
+```nix
+email = "tony@tonybtw.com";  # YOUR email for Let's Encrypt
+```
+
+#### **Edit `xmpp-home.nix`:**
+
+```bash
+sudo vim /etc/nixos/xmpp-home.nix
+```
+
+**Change line 23:**
+```nix
+admins = [ "tony@chat.tonybtw.com" ];  # YOUR username
+```
+
+---
+
+## Step 4: Update Main Configuration
+
+Edit your main configuration:
+
+```bash
+sudo vim /etc/nixos/configuration.nix
+```
+
+**Add these imports** at the top (in the `imports` section):
+
+```nix
+{ config, pkgs, ... }:
+
+{
+  imports = [
+    ./hardware-configuration.nix
+    ./home-server-base.nix
+    ./xmpp-home.nix
+    # ... any other imports you have
+  ];
+
+  # Rest of your config...
+}
+```
+
+Save and exit.
+
+---
+
+## Step 5: Verify Secrets Files Exist
+
+```bash
+# Check Cloudflare token
+sudo cat /root/secrets/cloudflare-token
+# Should show your API token
+
+# Check ACME credentials
+sudo cat /root/secrets/cloudflare-acme-credentials
+# Should show: CF_DNS_API_TOKEN=your_token
+
+# If missing, go back to cloudflare-setup.md Step 5-6
+```
+
+---
+
+## Step 6: Deploy Configuration
+
+```bash
+# Build and activate new configuration
+sudo nixos-rebuild switch
+```
+
+**This will:**
+1. Install Prosody
+2. Configure firewall
+3. Set up Cloudflare DDNS service
+4. Request Let's Encrypt SSL certificates (takes ~60 seconds)
+5. Start XMPP server
+
+**Watch for errors.** Common issues:
+- Cloudflare API token wrong → Check `/root/secrets/cloudflare-token`
+- ACME certificate fails → Check DNS is pointing to your home IP
+- Prosody fails to start → Check logs: `sudo journalctl -u prosody`
+
+**If successful, you'll see:**
+```
+building the system configuration...
+activating the configuration...
+setting up /etc...
+reloading the following units: dbus.service
+starting the following units: prosody.service, cloudflare-dyndns.service
+```
+
+---
+
+## Step 7: Verify Services Are Running
+
+```bash
+# Check Prosody
+sudo systemctl status prosody
+# Should show: active (running)
+
+# Check Cloudflare DDNS
+sudo systemctl status cloudflare-dyndns
+# Should show: active (running)
+
+# Check ACME certificate
+sudo systemctl status acme-chat.tonybtw.com
+# Should show: finished successfully
+
+# View Prosody logs
+sudo journalctl -u prosody -n 50
+# Should show: "Prosody is using the epoll backend for connection handling"
+```
+
+**If any service failed:**
+```bash
+# See detailed logs
+sudo journalctl -u prosody -n 100
+sudo journalctl -u acme-chat.tonybtw.com -n 100
+```
+
+---
+
+## Step 8: Create XMPP Admin Account
+
+```bash
+# Create your admin account
+sudo prosodyctl adduser tony@chat.tonybtw.com
+
+# Enter a strong password when prompted
+```
+
+**Test it worked:**
+```bash
+sudo prosodyctl list:users
+# Should show: tony@chat.tonybtw.com
+```
+
+---
+
+## Step 9: Test XMPP Connection (Local)
+
+Install an XMPP client on your laptop:
+
+```bash
+nix-shell -p dino
+```
+
+Open Dino:
+1. Click **"Add Account"**
+2. **Jabber ID:** `tony@chat.tonybtw.com`
+3. **Password:** (the one you just set)
+4. Click **"Connect"**
+
+**Expected result:** Green indicator, "Connected"
+
+**If connection fails:**
+- Check Prosody is running: `sudo systemctl status prosody`
+- Check firewall allows 5222: `sudo iptables -L -v | grep 5222`
+- Check logs: `sudo journalctl -u prosody -f`
+
+---
+
+## Step 10: Test XMPP Connection (External)
+
+**From another network** (phone on mobile data, or friend's computer):
+
+1. Install XMPP client:
+   - Android: Conversations
+   - iOS: Monal
+   - Desktop: Gajim or Dino
+
+2. Add account:
+   - **Jabber ID:** `tony@chat.tonybtw.com`
+   - **Password:** (your password)
+
+3. Connect
+
+**Expected result:** Connection successful
+
+**If connection fails:**
+- Check ports are forwarded: Use https://www.yougetsignal.com/tools/open-ports/ to test port 5222
+- Check DNS resolves: `dig chat.tonybtw.com` should show your home IP
+- Check Cloudflare proxy is DISABLED (gray cloud, not orange)
+
+---
+
+## Step 11: Test SSL Certificate
+
+```bash
+# Test SSL certificate
+openssl s_client -connect chat.tonybtw.com:5222 -starttls xmpp
+
+# Should show certificate details and:
+# Verify return code: 0 (ok)
+```
+
+**If certificate is invalid:**
+```bash
+# Check ACME logs
+sudo journalctl -u acme-chat.tonybtw.com
+
+# Manually renew if needed
+sudo systemctl start acme-chat.tonybtw.com
+```
+
+---
+
+## Step 12: Create User Accounts for Friends
+
+```bash
+# Create accounts
+sudo prosodyctl adduser alice@chat.tonybtw.com
+sudo prosodyctl adduser bob@chat.tonybtw.com
+sudo prosodyctl adduser charlie@chat.tonybtw.com
+
+# List all users
+sudo prosodyctl list:users
+```
+
+---
+
+## Step 13: Create Group Rooms
+
+In Dino (or any XMPP client), logged in as your admin account:
+
+1. **Join/Create Room:**
+   - Address: `#general@conference.chat.tonybtw.com`
+   - Click "Join"
+   - Room will be auto-created
+
+2. **Make Room Persistent:**
+   - In Dino: Right-click room → "Configure Room"
+   - Enable "Persistent" and "Public"
+   - Save
+
+3. **Create more rooms:**
+   - `#random@conference.chat.tonybtw.com`
+   - `#tech@conference.chat.tonybtw.com`
+   - `#gaming@conference.chat.tonybtw.com`
+
+---
+
+## Step 14: Invite Friends
+
+Send each friend:
+
+1. **Their credentials:**
+   ```
+   Username: yourname@chat.tonybtw.com
+   Password: [their_password]
+   ```
+
+2. **The setup guide:**
+   Share `xmpp-setup.md` (edit it first to replace `yourdomain.com` with `chat.tonybtw.com`)
+
+3. **Room to join:**
+   ```
+   #general@conference.chat.tonybtw.com
+   ```
+
+---
+
+## Maintenance
+
+### **View Logs**
+
+```bash
+# Prosody logs
+sudo journalctl -u prosody -f
+
+# DDNS logs
+sudo journalctl -u cloudflare-dyndns -f
+
+# System logs
+sudo journalctl -f
+```
+
+### **Restart Services**
+
+```bash
+sudo systemctl restart prosody
+sudo systemctl restart cloudflare-dyndns
+```
+
+### **Manage Users**
+
+```bash
+# Add user
+sudo prosodyctl adduser newuser@chat.tonybtw.com
+
+# Change password
+sudo prosodyctl passwd username@chat.tonybtw.com
+
+# Delete user
+sudo prosodyctl deluser olduser@chat.tonybtw.com
+
+# List users
+sudo prosodyctl list:users
+```
+
+### **Check Server Status**
+
+```bash
+# Prosody status and stats
+sudo prosodyctl about
+
+# Connected users
+sudo prosodyctl mod_admin_telnet
+# Then type: c2s:show()
+# Exit with: quit
+```
+
+### **Renew SSL Certificates**
+
+Automatic renewal happens via systemd timer. Check status:
+
+```bash
+# Check ACME timer
+sudo systemctl list-timers | grep acme
+
+# Manually renew
+sudo systemctl start acme-chat.tonybtw.com
+```
+
+---
+
+## Troubleshooting
+
+### **Prosody Won't Start**
+
+```bash
+# Check config syntax
+sudo prosodyctl check config
+
+# Check certificate permissions
+sudo ls -la /var/lib/acme/chat.tonybtw.com/
+
+# Should be readable by prosody user
+sudo chown -R prosody:prosody /var/lib/acme/chat.tonybtw.com/
+```
+
+### **Can't Connect from External Network**
+
+```bash
+# Check if ports are actually open (from another network):
+nc -zv YOUR_HOME_IP 5222
+
+# Check DNS resolution:
+dig chat.tonybtw.com
+# Should show your home IP
+
+# Check Cloudflare DDNS is updating:
+sudo journalctl -u cloudflare-dyndns | tail -20
+
+# Check your current home IP:
+curl ifconfig.me
+```
+
+### **SSL Certificate Failed**
+
+```bash
+# View ACME logs
+sudo journalctl -u acme-chat.tonybtw.com -n 100
+
+# Common issues:
+# 1. DNS not pointing to your IP yet (wait 10 minutes)
+# 2. Cloudflare API token wrong (check /root/secrets/cloudflare-acme-credentials)
+# 3. Rate limit (Let's Encrypt allows 5 certs/week)
+
+# Retry manually:
+sudo systemctl start acme-chat.tonybtw.com
+```
+
+### **Home IP Changed But DNS Not Updating**
+
+```bash
+# Check DDNS service
+sudo systemctl status cloudflare-dyndns
+
+# Manually trigger update
+sudo systemctl restart cloudflare-dyndns
+
+# Check Cloudflare dashboard
+# Go to Cloudflare → DNS → Records
+# Verify "chat" A record shows current IP
+```
+
+---
+
+## Backup Strategy
+
+**What to backup:**
+- User accounts and messages: `/var/lib/prosody/`
+- SSL certificates: `/var/lib/acme/`
+- Configuration: `/etc/nixos/`
+
+### **Simple Backup Script:**
+
+```bash
+#!/usr/bin/env bash
+# /root/backup-homelab.sh
+
+BACKUP_DIR="/home/tony/backups"
+DATE=$(date +%Y%m%d-%H%M)
+
+mkdir -p $BACKUP_DIR
+
+# Backup Prosody data
+sudo tar czf $BACKUP_DIR/prosody-$DATE.tar.gz /var/lib/prosody/
+
+# Backup configs
+sudo tar czf $BACKUP_DIR/nixos-$DATE.tar.gz /etc/nixos/
+
+# Keep last 30 days
+find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
+
+echo "Backup complete: $BACKUP_DIR"
+```
+
+Make it executable:
+```bash
+sudo chmod +x /root/backup-homelab.sh
+```
+
+**Automate with systemd timer:**
+
+```nix
+# Add to configuration.nix
+systemd.timers.homelab-backup = {
+  wantedBy = [ "timers.target" ];
+  timerConfig = {
+    OnCalendar = "daily";
+    Persistent = true;
+  };
+};
+
+systemd.services.homelab-backup = {
+  serviceConfig.ExecStart = "/root/backup-homelab.sh";
+};
+```
+
+Then: `sudo nixos-rebuild switch`
+
+---
+
+## Next Steps (Homelab Expansion)
+
+Once XMPP is running, you can add more services:
+
+### **Jellyfin (Media Server):**
+
+```nix
+services.jellyfin = {
+  enable = true;
+  openFirewall = true;
+};
+
+# Access at: http://your-server-ip:8096
+# Later: Set up jellyfin.tonybtw.com with Nginx reverse proxy
+```
+
+### **Nextcloud (File Sync/Share):**
+
+```nix
+services.nextcloud = {
+  enable = true;
+  hostName = "cloud.tonybtw.com";
+  config.adminpassFile = "/root/secrets/nextcloud-admin-pass";
+};
+```
+
+### **Vaultwarden (Password Manager):**
+
+```nix
+services.vaultwarden = {
+  enable = true;
+  config = {
+    domain = "https://vault.tonybtw.com";
+    signupsAllowed = false;
+  };
+};
+```
+
+**All of these will use:**
+- Same Cloudflare DDNS setup
+- Same router port forwarding (or Nginx reverse proxy)
+- Same Let's Encrypt SSL certificates
+
+Your XMPP setup is the foundation. Everything else builds on top.
+
+---
+
+## Monitoring
+
+### **Check Disk Space:**
+
+```bash
+df -h
+ncdu /  # Interactive disk usage
+```
+
+### **Check Memory:**
+
+```bash
+free -h
+htop
+```
+
+### **Check Uptime:**
+
+```bash
+uptime
+```
+
+### **Check Network:**
+
+```bash
+# Current connections
+sudo ss -tunap | grep prosody
+
+# Bandwidth usage (install iftop)
+sudo nix-shell -p iftop
+sudo iftop
+```
+
+---
+
+## Security Checklist
+
+- [ ] Fail2ban is running: `sudo systemctl status fail2ban`
+- [ ] Firewall is enabled: `sudo iptables -L -v`
+- [ ] SSH has strong password or key-only auth
+- [ ] Root login is disabled
+- [ ] Prosody admin account has strong password
+- [ ] SSL certificates are valid
+- [ ] Router admin password is changed from default
+- [ ] Only necessary ports are forwarded
+
+---
+
+## You're Done!
+
+Your home XMPP server is now running at `chat.tonybtw.com`.
+
+**Share with friends:** Give them `xmpp-setup.md` (updated with your domain)
+
+**Future additions:**
+- Jellyfin for media streaming
+- Nextcloud for file sync
+- Nginx reverse proxy for web services
+- Monitoring (Grafana + Prometheus)
+
+Welcome to self-hosting. You now control your communication infrastructure. 🎉
+
+---
+
+## Quick Reference Commands
+
+```bash
+# Restart XMPP
+sudo systemctl restart prosody
+
+# View XMPP logs
+sudo journalctl -u prosody -f
+
+# Add user
+sudo prosodyctl adduser user@chat.tonybtw.com
+
+# List users
+sudo prosodyctl list:users
+
+# Check server status
+sudo prosodyctl about
+
+# Update system
+sudo nixos-rebuild switch --upgrade
+
+# Check open ports
+sudo ss -tulpn
+```
diff --git a/future-modules/home-server-base.nix b/future-modules/home-server-base.nix
new file mode 100644
index 0000000..f10876b
--- /dev/null
+++ b/future-modules/home-server-base.nix
@@ -0,0 +1,171 @@
+# home-server-base.nix - Base configuration for home server
+#
+# This provides the foundation for your homelab:
+# - Cloudflare DDNS (updates chat.tonybtw.com to your home IP)
+# - Nginx reverse proxy (handles SSL for all services)
+# - Fail2ban (basic protection)
+# - SSH hardening (less strict than VPS since it's on your LAN)
+#
+# Import this in your configuration.nix along with service-specific configs
+#
+{ config, pkgs, lib, ... }:
+
+{
+  # ============================================================================
+  # Cloudflare Dynamic DNS - Keeps your domain pointing to home IP
+  # ============================================================================
+  services.cloudflare-dyndns = {
+    enable = true;
+    apiTokenFile = "/root/secrets/cloudflare-token";
+    domains = [
+      "chat.tonybtw.com"
+      # Add more as you expand:
+      # "jellyfin.tonybtw.com"
+      # "cloud.tonybtw.com"
+    ];
+    ipv4 = true;
+    ipv6 = false;  # Enable if you have IPv6
+  };
+
+  # ============================================================================
+  # Nginx Reverse Proxy - Handle SSL and route to services
+  # ============================================================================
+  services.nginx = {
+    enable = true;
+    recommendedProxySettings = true;
+    recommendedTlsSettings = true;
+    recommendedOptimisation = true;
+    recommendedGzipSettings = true;
+
+    # Shared SSL configuration (Let's Encrypt)
+    # Individual vhosts will reference this
+  };
+
+  # ============================================================================
+  # Let's Encrypt SSL Certificates
+  # ============================================================================
+  security.acme = {
+    acceptTerms = true;
+    defaults = {
+      email = "tony@tonybtw.com";  # CHANGE THIS to your email
+      dnsProvider = "cloudflare";  # Use Cloudflare DNS challenge (works behind NAT)
+      credentialsFile = "/root/secrets/cloudflare-acme-credentials";
+    };
+
+    certs."chat.tonybtw.com" = {
+      domain = "chat.tonybtw.com";
+      extraDomainNames = [
+        "conference.chat.tonybtw.com"
+        "upload.chat.tonybtw.com"
+      ];
+      group = "prosody";
+      postRun = "systemctl reload prosody.service";
+    };
+
+    # Add more certs as you expand:
+    # certs."jellyfin.tonybtw.com" = {
+    #   domain = "jellyfin.tonybtw.com";
+    #   group = "jellyfin";
+    # };
+  };
+
+  # ============================================================================
+  # Firewall - Allow necessary ports
+  # ============================================================================
+  networking.firewall = {
+    enable = true;
+
+    # Open ports for services
+    allowedTCPPorts = [
+      22    # SSH (consider changing to non-standard port)
+      80    # HTTP (ACME challenges + redirect to HTTPS)
+      443   # HTTPS (Nginx reverse proxy)
+      5222  # XMPP client-to-server (C2S)
+      5269  # XMPP server-to-server (S2S) - optional, for federation
+      5281  # XMPP HTTPS (file uploads)
+      # Add more as needed:
+      # 8096  # Jellyfin (or proxy via Nginx)
+    ];
+
+    # Basic rate limiting
+    extraCommands = ''
+      # Rate-limit SSH (4 attempts per minute)
+      iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
+      iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
+
+      # Rate-limit XMPP connections
+      iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set --name XMPP
+      iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name XMPP -j DROP
+    '';
+
+    extraStopCommands = ''
+      iptables -D INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set --name XMPP 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name XMPP -j DROP 2>/dev/null || true
+    '';
+  };
+
+  # ============================================================================
+  # Fail2ban - Basic Protection
+  # ============================================================================
+  services.fail2ban = {
+    enable = true;
+    maxretry = 5;  # Less strict than VPS (you might fat-finger your password)
+    bantime = "10m";
+    findtime = "10m";
+
+    jails = {
+      sshd = ''
+        enabled = true
+        filter = sshd
+        action = iptables[name=SSH, port=22, protocol=tcp]
+        maxretry = 5
+      '';
+    };
+  };
+
+  # ============================================================================
+  # SSH Configuration - Moderate Security (it's on your LAN)
+  # ============================================================================
+  services.openssh = {
+    enable = true;
+    settings = {
+      PasswordAuthentication = true;  # Allow passwords on LAN (change to false if paranoid)
+      PermitRootLogin = "no";
+      PubkeyAuthentication = true;
+    };
+  };
+
+  # ============================================================================
+  # Automatic Garbage Collection
+  # ============================================================================
+  nix.gc = {
+    automatic = true;
+    dates = "weekly";
+    options = "--delete-older-than 30d";
+  };
+
+  # ============================================================================
+  # System Packages
+  # ============================================================================
+  environment.systemPackages = with pkgs; [
+    vim
+    wget
+    curl
+    htop
+    tmux
+    git
+    btop
+    ncdu  # Disk usage analyzer
+  ];
+
+  # ============================================================================
+  # Secrets Management
+  # ============================================================================
+  # You'll need to create these files manually (see deployment guide)
+  system.activationScripts.secrets = ''
+    mkdir -p /root/secrets
+    chmod 700 /root/secrets
+  '';
+}
diff --git a/future-modules/irc-public.nix b/future-modules/irc-public.nix
new file mode 100644
index 0000000..4818be3
--- /dev/null
+++ b/future-modules/irc-public.nix
@@ -0,0 +1,161 @@
+# irc-public.nix - Public-facing IRC server configuration
+{
+  config,
+  pkgs,
+  ...
+}: {
+  services.ngircd = {
+    enable = true;
+    package = pkgs.ngircd.overrideAttrs (oldAttrs: {
+      configureFlags = builtins.filter (f: f != "--with-pam") oldAttrs.configureFlags;
+      buildInputs = builtins.filter (i: i != pkgs.pam) oldAttrs.buildInputs;
+    });
+
+    config = ''
+      [Global]
+      Name = irc.yourdomain.com
+      Info = Community IRC Server
+      AdminInfo1 = Your Name
+      AdminInfo2 = Your Community Server
+      AdminEMail = admin@yourdomain.com
+
+      # Network and connection settings
+      Listen = 0.0.0.0
+      Ports = 6667
+      MotdFile = /etc/ngircd/motd.txt
+
+      # User limits
+      MaxConnections = 500
+      MaxConnectionsIP = 5
+      MaxJoins = 10
+      MaxNickLength = 16
+      PingTimeout = 120
+      PongTimeout = 20
+
+      # DNS and ident
+      DNS = yes
+
+      [Limits]
+      ConnectRetry = 60
+      MaxPenaltyTime = -1
+
+      [Options]
+      AllowRemoteOper = no
+      ChrootDir =
+      CloakHost = %x
+      CloakHostModeX = %x
+      CloakHostSalt = your-random-salt-here
+      CloakUserToNick = yes
+      ConnectIPv4 = yes
+      ConnectIPv6 = yes
+      DefaultUserModes = i
+      MorePrivacy = no
+      NoticeBeforeRegistration = no
+      OperCanUseMode = yes
+      OperChanPAutoOp = yes
+      OperServerMode = yes
+      PredefChannelsOnly = no
+      RequireAuthPing = no
+      ScrubCTCP = no
+      SyslogFacility = local1
+      WebircPassword =
+
+      [SSL]
+      CAFile =
+      CertFile = /etc/ngircd/ssl/server-cert.pem
+      CipherList = HIGH:!aNULL:@STRENGTH
+      DHFile = /etc/ngircd/ssl/dhparams.pem
+      KeyFile = /etc/ngircd/ssl/server-key.pem
+      Ports = 6697
+
+      # Operator accounts (CHANGE THESE!)
+      [Operator]
+      Name = admin
+      Password = change-this-password-hash
+      Mask = *!*@*
+
+      # Default channels
+      [Channel]
+      Name = #general
+      Topic = General Discussion
+      Modes = nt
+
+      [Channel]
+      Name = #announcements
+      Topic = Server Announcements
+      Modes = ntm
+
+      [Channel]
+      Name = #support
+      Topic = Help and Support
+      Modes = nt
+
+      [Channel]
+      Name = #offtopic
+      Topic = Off Topic Chat
+      Modes = nt
+    '';
+  };
+
+  # Create MOTD file
+  environment.etc."ngircd/motd.txt".text = ''
+    ╔═══════════════════════════════════════════════════╗
+    ║       Welcome to the Community IRC Server!        ║
+    ╚═══════════════════════════════════════════════════╝
+
+    Server Rules:
+    1. Be respectful to all users
+    2. No spam or flooding
+    3. No harassment or hate speech
+    4. Keep discussion appropriate for all ages
+    5. Follow channel-specific rules
+
+    Available channels:
+    - #general      : General discussion
+    - #announcements: Server announcements (moderated)
+    - #support      : Help and support
+    - #offtopic     : Off topic chat
+
+    For help, type: /join #support
+
+    Enjoy your stay!
+  '';
+
+  # Open firewall ports
+  networking.firewall.allowedTCPPorts = [
+    6667  # Plain IRC
+    6697  # SSL IRC
+  ];
+
+  # Optional: Setup Let's Encrypt for SSL
+  # Uncomment and configure if you want SSL support
+  # security.acme = {
+  #   acceptTerms = true;
+  #   defaults.email = "admin@yourdomain.com";
+  #   certs."irc.yourdomain.com" = {
+  #     group = "ngircd";
+  #     postRun = "systemctl reload ngircd.service";
+  #   };
+  # };
+
+  # Optional: TheLounge web IRC client
+  # Uncomment to enable web-based IRC access
+  # services.thelounge = {
+  #   enable = true;
+  #   port = 9000;
+  #   public = true;  # Allow registration
+  #
+  #   extraConfig = {
+  #     reverseProxy = true;  # If behind nginx
+  #
+  #     defaults = {
+  #       name = "Community IRC";
+  #       host = "127.0.0.1";
+  #       port = 6667;
+  #       tls = false;
+  #       rejectUnauthorized = false;
+  #       join = "#general";
+  #     };
+  #   };
+  # };
+}
diff --git a/future-modules/irc.nix b/future-modules/irc.nix
new file mode 100644
index 0000000..c8b4783
--- /dev/null
+++ b/future-modules/irc.nix
@@ -0,0 +1,52 @@
+# irc.nix
+{
+  config,
+  pkgs,
+  ...
+}: {
+  services.ngircd = {
+    enable = true;
+    package = pkgs.ngircd.overrideAttrs (oldAttrs: {
+      configureFlags = builtins.filter (f: f != "--with-pam") oldAttrs.configureFlags;
+      buildInputs = builtins.filter (i: i != pkgs.pam) oldAttrs.buildInputs;
+    });
+
+    # This becomes ngircd.conf verbatim.
+    config = ''
+      [Global]
+      Name = irc.local
+      AdminInfo1 = Test IRC
+      AdminInfo2 = Local instance
+      MaxNickLength = 16
+      Listen = 127.0.0.1
+      Ports = 6667
+
+      [Channel]
+      Name = #linux
+      Topic = Local test
+    '';
+  };
+
+  services.thelounge = {
+    enable = true;
+    port = 9000;
+
+    extraConfig = {
+      public = false;
+      reverseProxy = false; # no nginx for local testing
+
+      defaults = {
+        name = "localnet";
+        host = "127.0.0.1";
+        port = 6667;
+        tls = false;
+        nick = "test%%%"; # <= 9 chars incl. suffix
+        join = "#linux";
+      };
+
+      lockNetwork = true;
+    };
+  };
+
+  networking.firewall.allowedTCPPorts = [9000 6667];
+}
diff --git a/future-modules/router-portforward-guide.md b/future-modules/router-portforward-guide.md
new file mode 100644
index 0000000..39a4dc1
--- /dev/null
+++ b/future-modules/router-portforward-guide.md
@@ -0,0 +1,378 @@
+# Router Port Forwarding Guide
+
+To make your home XMPP server accessible from the internet, you need to forward ports on your router. This guide covers common routers.
+
+---
+
+## What is Port Forwarding?
+
+**Normal setup:** Router blocks incoming connections (firewall)
+**Port forwarding:** Router allows specific ports through to your server
+
+You're telling your router: "When someone connects to port 5222, send that traffic to my server at 192.168.1.100:5222"
+
+---
+
+## Step 1: Find Your Server's Local IP
+
+On your home server (the laptop running NixOS):
+
+```bash
+ip addr show | grep "inet "
+```
+
+Look for your local IP, probably something like:
+- `192.168.1.100` (common)
+- `192.168.0.100`
+- `10.0.0.100`
+
+**Write this down.** You'll need it for port forwarding.
+
+---
+
+## Step 2: Set Static IP (Important!)
+
+If your server gets a different IP after reboot, port forwarding breaks.
+
+### **Option A: Static IP via Router (Recommended)**
+
+1. Log in to your router (see Step 3)
+2. Find **DHCP Reservations** or **Static IP** or **Address Reservation**
+3. Add a reservation:
+   - **Device:** Your server (find by MAC address or hostname)
+   - **IP Address:** `192.168.1.100` (or whatever you chose)
+4. Save
+
+### **Option B: Static IP via NixOS**
+
+Add to your NixOS configuration:
+
+```nix
+# In configuration.nix
+networking = {
+  interfaces.eth0 = {  # or wlp3s0 for WiFi - check with `ip addr`
+    useDHCP = false;
+    ipv4.addresses = [{
+      address = "192.168.1.100";
+      prefixLength = 24;
+    }];
+  };
+  defaultGateway = "192.168.1.1";  # Your router's IP
+  nameservers = [ "1.1.1.1" "8.8.8.8" ];
+};
+```
+
+Then:
+```bash
+sudo nixos-rebuild switch
+```
+
+**Recommendation:** Use Option A (router-side) - easier to manage.
+
+---
+
+## Step 3: Access Your Router
+
+### **Find Router IP:**
+
+```bash
+ip route | grep default
+```
+
+Usually shows: `192.168.1.1` or `192.168.0.1` or `10.0.0.1`
+
+### **Log In:**
+
+Open browser, go to: `http://192.168.1.1` (or whatever IP you found)
+
+**Common default logins:**
+
+| Router Brand | Default Username | Default Password | URL |
+|--------------|------------------|------------------|-----|
+| Netgear | admin | password | http://192.168.1.1 |
+| Linksys | admin | admin | http://192.168.1.1 |
+| TP-Link | admin | admin | http://192.168.0.1 |
+| ASUS | admin | admin | http://192.168.1.1 |
+| D-Link | admin | (blank) | http://192.168.0.1 |
+| Belkin | (blank) | (blank) | http://192.168.2.1 |
+| Xfinity/Comcast | admin | password | http://10.0.0.1 |
+| AT&T | (varies) | (on router sticker) | http://192.168.1.254 |
+| Verizon FIOS | admin | (on router sticker) | http://192.168.1.1 |
+
+**If default doesn't work:**
+- Check sticker on router
+- Google "[your router model] default password"
+- You may have changed it before
+
+---
+
+## Step 4: Find Port Forwarding Settings
+
+Every router brand has a different UI. Look for:
+
+- **Port Forwarding**
+- **Virtual Server**
+- **Port Mapping**
+- **NAT Forwarding**
+- **Applications & Gaming** (Linksys)
+- **Advanced → Port Forwarding**
+
+**Common locations:**
+- Netgear: **Advanced → Port Forwarding/Port Triggering**
+- Linksys: **Security → Apps & Gaming → Single Port Forwarding**
+- TP-Link: **Forwarding → Virtual Servers**
+- ASUS: **WAN → Virtual Server / Port Forwarding**
+- D-Link: **Advanced → Port Forwarding**
+
+---
+
+## Step 5: Add Port Forwarding Rules
+
+Add these rules (one rule per port):
+
+### **Rule 1: SSH**
+- **Service Name:** SSH
+- **External Port:** 22
+- **Internal Port:** 22
+- **Internal IP:** `192.168.1.100` (your server)
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+### **Rule 2: HTTP (ACME challenges)**
+- **Service Name:** HTTP
+- **External Port:** 80
+- **Internal Port:** 80
+- **Internal IP:** `192.168.1.100`
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+### **Rule 3: HTTPS**
+- **Service Name:** HTTPS
+- **External Port:** 443
+- **Internal Port:** 443
+- **Internal IP:** `192.168.1.100`
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+### **Rule 4: XMPP Client (C2S)**
+- **Service Name:** XMPP-C2S
+- **External Port:** 5222
+- **Internal Port:** 5222
+- **Internal IP:** `192.168.1.100`
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+### **Rule 5: XMPP Server (S2S) - Optional**
+Only needed if you want to federate with other XMPP servers.
+
+- **Service Name:** XMPP-S2S
+- **External Port:** 5269
+- **Internal Port:** 5269
+- **Internal IP:** `192.168.1.100`
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+### **Rule 6: XMPP HTTPS (File Uploads)**
+- **Service Name:** XMPP-HTTPS
+- **External Port:** 5281
+- **Internal Port:** 5281
+- **Internal IP:** `192.168.1.100`
+- **Protocol:** TCP
+- **Enabled:** Yes
+
+---
+
+## Step 6: Save and Apply
+
+Click **Save** or **Apply** in your router interface.
+
+**Some routers require a reboot** - check if there's a "Reboot" button or just wait a minute.
+
+---
+
+## Step 7: Test Port Forwarding
+
+### **From Outside Your Network:**
+
+Use your phone on mobile data (NOT WiFi), or ask a friend:
+
+```bash
+# Test if ports are open
+nc -zv YOUR_HOME_IP 22
+nc -zv YOUR_HOME_IP 5222
+
+# Or use online tool:
+# Visit: https://www.yougetsignal.com/tools/open-ports/
+# Enter your home IP and port 5222
+```
+
+**Expected result:** "Connection successful" or "Port is open"
+
+### **Find Your Home IP:**
+
+```bash
+curl ifconfig.me
+```
+
+Or visit: https://whatismyipaddress.com
+
+---
+
+## Common Issues
+
+### **Issue 1: Ports Still Closed**
+
+**Causes:**
+1. Router hasn't applied changes (reboot router)
+2. ISP blocks ports (see below)
+3. Firewall on server blocks traffic (check NixOS firewall config)
+4. Double NAT (you have two routers)
+
+**Solutions:**
+- Reboot router
+- Check ISP doesn't block ports (call them)
+- Verify NixOS firewall allows ports:
+  ```bash
+  sudo iptables -L -n -v | grep 5222
+  ```
+
+### **Issue 2: ISP Blocks Ports**
+
+Some ISPs block common server ports (especially residential plans).
+
+**Commonly blocked ports:**
+- Port 25 (SMTP email) - almost always blocked
+- Port 80 (HTTP) - sometimes blocked
+- Port 443 (HTTPS) - rarely blocked
+- Port 5222 (XMPP) - rarely blocked
+
+**Workarounds:**
+1. **Use non-standard ports:**
+   - XMPP on 5222 → change to 52222
+   - HTTP on 80 → change to 8080
+   - Update NixOS config and DNS SRV records
+2. **Call ISP and ask for "business class" or "static IP"** (often removes blocks)
+3. **Use VPN tunnel** (Tailscale, WireGuard)
+
+### **Issue 3: Double NAT**
+
+If you have:
+- ISP modem/router → Your router → Your server
+
+You need to port forward on BOTH routers, or put your router in "bridge mode."
+
+**Check for double NAT:**
+```bash
+# On your server:
+ip route | grep default
+
+# Note the router IP (e.g., 192.168.1.1)
+# Then check what your router's "WAN IP" is in its admin panel
+# If WAN IP is also 192.168.x.x or 10.x.x.x, you have double NAT
+```
+
+**Fix:** Put ISP modem in bridge mode, or port forward on both.
+
+---
+
+## Security Considerations
+
+### **Exposing SSH to Internet (Port 22)**
+
+**Risk:** Bots will try to brute-force your SSH.
+
+**Mitigations:**
+1. **Fail2ban is enabled** (in your NixOS config) - auto-bans attackers
+2. **Change SSH port to non-standard:**
+   ```nix
+   services.openssh.ports = [ 2222 ];  # Instead of 22
+   ```
+   Then forward external port 2222 → internal port 2222
+3. **SSH key-only** (disable password auth):
+   ```nix
+   services.openssh.settings.PasswordAuthentication = false;
+   ```
+
+### **Exposing Your Home IP**
+
+**Reality check:**
+- Your IP is already visible when you browse the web
+- DNS records will show your IP publicly
+- Anyone pinging `chat.tonybtw.com` will see your home IP
+
+**If this bothers you:**
+- Use Vultr instead (VPS hides home IP)
+- Use Tailscale (VPN mesh, no public exposure)
+
+---
+
+## Router-Specific Guides
+
+### **Netgear:**
+1. Go to **Advanced → Advanced Setup → Port Forwarding**
+2. Click **Add Custom Service**
+3. Fill in service name, ports, IP
+4. Click **Apply**
+
+### **Linksys:**
+1. Go to **Security → Apps & Gaming**
+2. Click **Single Port Forwarding** tab
+3. Fill in application name, external/internal ports, IP
+4. Check **Enabled** box
+5. Click **Save Settings**
+
+### **TP-Link:**
+1. Go to **Forwarding → Virtual Servers**
+2. Click **Add New**
+3. Fill in service port, internal port, IP address
+4. Protocol: TCP
+5. Status: Enabled
+6. Click **Save**
+
+### **ASUS:**
+1. Go to **WAN → Virtual Server / Port Forwarding**
+2. Enable **Port Forwarding**
+3. Fill in service name, port range, local IP
+4. Protocol: TCP
+5. Click **Add** then **Apply**
+
+### **Google WiFi / Nest WiFi:**
+1. Open Google Home app
+2. Tap your Wi-Fi network
+3. Settings → Advanced Networking → Port Management
+4. Tap "+" to add port forwarding
+5. Select your server device, enter ports
+
+---
+
+## Alternative: UPnP (Not Recommended)
+
+Some routers support UPnP (Universal Plug and Play) which auto-forwards ports.
+
+**Don't use this:**
+- Security risk (any device on your network can open ports)
+- Less reliable
+- Manual port forwarding is safer
+
+---
+
+## Next Steps
+
+Once port forwarding is working:
+1. Continue to home deployment guide
+2. Deploy NixOS configuration
+3. Test XMPP from outside your network
+
+---
+
+## Troubleshooting Checklist
+
+- [ ] Server has static IP (via DHCP reservation)
+- [ ] Port forwarding rules are saved and applied
+- [ ] Router has been rebooted
+- [ ] Ports are open (tested with nc or online tool)
+- [ ] NixOS firewall allows ports (check `iptables -L`)
+- [ ] No double NAT (or both routers configured)
+- [ ] ISP doesn't block ports (test with online tools)
+
+If all checked and still not working, your ISP might be the problem. Consider Tailscale or Vultr as alternatives.
diff --git a/future-modules/vultr-deploy-guide.md b/future-modules/vultr-deploy-guide.md
new file mode 100644
index 0000000..d3895f2
--- /dev/null
+++ b/future-modules/vultr-deploy-guide.md
@@ -0,0 +1,603 @@
+# Vultr Deployment Guide - Secure XMPP Server
+
+Complete guide to deploying a hardened XMPP server on Vultr.
+
+---
+
+## Step 1: Create Vultr Instance
+
+1. Log in to [Vultr](https://vultr.com)
+2. Click **"Deploy New Server"**
+3. Choose:
+   - **Server Type:** Cloud Compute
+   - **Location:** Closest to you/friends (e.g., New York, Los Angeles)
+   - **Image:** NixOS 24.05 (if available) OR Ubuntu 22.04 (we'll install NixOS)
+   - **Plan:** $6/month (1 CPU, 1GB RAM) - sufficient for <50 users
+   - **Additional Features:** Enable IPv6 (optional)
+4. **SSH Keys:** Upload your SSH public key (see Step 2)
+5. **Server Hostname:** `xmpp.yourdomain.com`
+6. Click **"Deploy Now"**
+
+Wait ~5 minutes for deployment.
+
+---
+
+## Step 2: Generate SSH Key (Local Machine)
+
+If you don't have an SSH key:
+
+```bash
+# Generate ED25519 key (most secure)
+ssh-keygen -t ed25519 -C "your_email@example.com"
+
+# Press Enter for default location (~/.ssh/id_ed25519)
+# Set a strong passphrase (optional but recommended)
+
+# Display public key (copy this)
+cat ~/.ssh/id_ed25519.pub
+```
+
+**Copy the output** (starts with `ssh-ed25519 AAAA...`) - you'll need it.
+
+---
+
+## Step 3: Initial SSH Connection
+
+```bash
+# Get IP from Vultr dashboard
+ssh root@YOUR_SERVER_IP
+
+# If using custom key location:
+ssh -i ~/.ssh/id_ed25519 root@YOUR_SERVER_IP
+```
+
+---
+
+## Step 4: Install NixOS (If Not Pre-installed)
+
+**If Vultr provided NixOS, skip to Step 5.**
+
+**If you started with Ubuntu:**
+
+```bash
+# On the Vultr server (as root):
+curl -L https://nixos.org/nix/install | sh
+source ~/.nix-profile/etc/profile.d/nix.sh
+
+# Install NixOS
+nix-env -iA nixos.nixos-install-tools
+
+# Follow NixOS installation guide:
+# https://nixos.org/manual/nixos/stable/#sec-installing-from-other-distro
+```
+
+Or easier: Use Vultr's Custom ISO feature to upload NixOS ISO and reinstall from scratch.
+
+---
+
+## Step 5: Clone Your Config
+
+On your **local machine**:
+
+```bash
+cd ~/nixos-dotfiles
+
+# Add Vultr-specific config
+cp xmpp.nix vultr-xmpp.nix
+cp vultr-security.nix .
+
+# Edit configurations (see Step 6)
+```
+
+---
+
+## Step 6: Configure Files
+
+### **A. Edit `vultr-security.nix`**
+
+```bash
+vim vultr-security.nix
+```
+
+**Change line 66:**
+```nix
+users.users.tony = {  # CHANGE THIS to your username
+```
+To your desired username (e.g., `users.users.alice`).
+
+**Change line 77-83:** Add your SSH public key:
+```nix
+openssh.authorizedKeys.keys = [
+  "ssh-ed25519 AAAAC3Nza... your@email.com"  # Paste your key here
+];
+```
+
+### **B. Edit `xmpp.nix`**
+
+```bash
+vim xmpp.nix
+```
+
+**Change line 12:**
+```nix
+domain = "yourdomain.com";  # CHANGE THIS
+```
+To your actual domain (e.g., `chat.example.com`).
+
+**Change line 126:**
+```nix
+defaults.email = "admin@yourdomain.com";  # CHANGE THIS
+```
+
+---
+
+## Step 7: Create `configuration.nix` for Vultr
+
+Create `/home/tony/nixos-dotfiles/vultr-configuration.nix`:
+
+```nix
+{ config, pkgs, ... }:
+
+{
+  imports = [
+    ./hardware-configuration.nix  # Will be generated on server
+    ./xmpp.nix
+    ./vultr-security.nix
+  ];
+
+  # Boot loader
+  boot.loader.grub.enable = true;
+  boot.loader.grub.device = "/dev/vda";  # Vultr uses virtio
+
+  # Hostname
+  networking.hostName = "xmpp";
+
+  # Time zone
+  time.timeZone = "America/New_York";  # Change to your timezone
+
+  # Networking (Vultr provides DHCP)
+  networking.useDHCP = true;
+
+  # System packages
+  environment.systemPackages = with pkgs; [
+    vim
+    git
+    htop
+    tmux
+  ];
+
+  # NixOS version
+  system.stateVersion = "24.05";  # Match your NixOS version
+}
+```
+
+---
+
+## Step 8: Deploy to Vultr
+
+### **Option A: Copy Files Manually**
+
+On your **local machine**:
+
+```bash
+# Copy config files to server
+scp -r ~/nixos-dotfiles root@YOUR_SERVER_IP:/etc/nixos/
+
+# SSH into server
+ssh root@YOUR_SERVER_IP
+
+# On server: Generate hardware config
+nixos-generate-config --show-hardware-config > /etc/nixos/hardware-configuration.nix
+
+# Rebuild
+nixos-rebuild switch
+```
+
+### **Option B: Git Deploy (Recommended)**
+
+On your **local machine**:
+
+```bash
+cd ~/nixos-dotfiles
+git init
+git add xmpp.nix vultr-security.nix vultr-configuration.nix
+git commit -m "Initial Vultr config"
+
+# Push to private repo (GitHub, GitLab, etc.)
+git remote add origin git@github.com:yourusername/nixos-server-config.git
+git push -u origin main
+```
+
+On **Vultr server**:
+
+```bash
+# Clone your config
+cd /etc/nixos
+git clone git@github.com:yourusername/nixos-server-config.git .
+
+# Generate hardware config
+nixos-generate-config --show-hardware-config > hardware-configuration.nix
+
+# Build and switch
+nixos-rebuild switch
+```
+
+---
+
+## Step 9: DNS Configuration
+
+In your domain registrar (e.g., Namecheap, Cloudflare):
+
+### **Required DNS Records:**
+
+```
+Type  Name                           Value              TTL
+----  ----                           -----              ---
+A     chat.yourdomain.com            YOUR_SERVER_IP     300
+A     conference.yourdomain.com      YOUR_SERVER_IP     300
+A     upload.yourdomain.com          YOUR_SERVER_IP     300
+
+SRV   _xmpp-client._tcp              5222 0 5 chat.yourdomain.com.   300
+SRV   _xmpp-server._tcp              5269 0 5 chat.yourdomain.com.   300
+```
+
+**SRV Record Format (if your DNS provider asks):**
+- **Service:** `_xmpp-client`
+- **Protocol:** `_tcp`
+- **Priority:** `0`
+- **Weight:** `5`
+- **Port:** `5222`
+- **Target:** `chat.yourdomain.com.` (note the trailing dot)
+
+Wait 5-10 minutes for DNS propagation.
+
+**Test DNS:**
+```bash
+dig chat.yourdomain.com
+dig SRV _xmpp-client._tcp.yourdomain.com
+```
+
+---
+
+## Step 10: First Deploy and Test
+
+On the **Vultr server** (as root):
+
+```bash
+# Switch to new config
+nixos-rebuild switch
+
+# This will:
+# - Install Prosody
+# - Configure firewall
+# - Get Let's Encrypt SSL certificates
+# - Set up fail2ban
+# - Harden SSH
+
+# IMPORTANT: Before logging out, test SSH with your new user!
+```
+
+---
+
+## Step 11: Test New User SSH (CRITICAL!)
+
+**Open a NEW terminal window** (don't close root session yet):
+
+```bash
+# Test SSH with your new user
+ssh youruser@YOUR_SERVER_IP
+
+# If this works, great! If not, fix it before logging out as root.
+```
+
+**If SSH works, continue. If not:**
+- Check your SSH key is correct in `vultr-security.nix`
+- Verify user is in `wheel` group
+- Check `systemctl status sshd`
+
+---
+
+## Step 12: Create XMPP Admin Account
+
+On the **Vultr server** (as your user, using sudo):
+
+```bash
+# Create admin account
+sudo prosodyctl adduser admin@yourdomain.com
+
+# Enter a strong password when prompted
+```
+
+---
+
+## Step 13: Create User Accounts
+
+```bash
+# Create accounts for friends
+sudo prosodyctl adduser alice@yourdomain.com
+sudo prosodyctl adduser bob@yourdomain.com
+sudo prosodyctl adduser charlie@yourdomain.com
+
+# List all users
+sudo prosodyctl list:users
+```
+
+---
+
+## Step 14: Test XMPP Connection
+
+### **From your local machine:**
+
+1. Install an XMPP client:
+   - **Linux:** `nix-shell -p dino` or `sudo apt install dino-im`
+   - **Mac:** Download Monal from App Store
+   - **Windows:** Download Gajim
+
+2. Add account:
+   - **Jabber ID:** `admin@yourdomain.com`
+   - **Password:** (the one you set)
+
+3. Connect. If it works, you're done!
+
+### **Test from command line:**
+
+```bash
+# Test SSL certificate
+openssl s_client -connect chat.yourdomain.com:5222 -starttls xmpp
+
+# Should show certificate details and "Verify return code: 0 (ok)"
+```
+
+---
+
+## Step 15: Create Group Rooms
+
+In your XMPP client (logged in as admin):
+
+1. **Join/Create Room:**
+   - Address: `#general@conference.yourdomain.com`
+   - It will auto-create
+
+2. **Make Room Persistent:**
+   - Open room settings/config
+   - Enable "Persistent" and "Public"
+
+3. **Repeat for other rooms:**
+   - `#random@conference.yourdomain.com`
+   - `#tech@conference.yourdomain.com`
+   - `#gaming@conference.yourdomain.com`
+
+---
+
+## Step 16: Lock Down Root (Final Hardening)
+
+**Only do this after confirming your user account SSH works!**
+
+On the **Vultr server**:
+
+```bash
+# Lock root account (already done in config, but verify)
+sudo passwd -l root
+
+# Verify SSH config
+sudo cat /etc/ssh/sshd_config | grep PermitRootLogin
+# Should show: PermitRootLogin no
+
+# Restart SSH
+sudo systemctl restart sshd
+```
+
+**Test that root SSH is blocked:**
+```bash
+# From your local machine (should fail):
+ssh root@YOUR_SERVER_IP
+# Expected: Permission denied
+```
+
+---
+
+## Step 17: Give Friends Access
+
+Send each friend:
+
+1. **Their credentials:**
+   - Username: `theirname@yourdomain.com`
+   - Password: (the one you created)
+
+2. **The setup guide:**
+   - Share `xmpp-setup.md` with them
+
+3. **Room invitations:**
+   - Tell them to join `#general@conference.yourdomain.com`
+
+---
+
+## Maintenance Commands
+
+### **Checking Status**
+
+```bash
+# Prosody status
+sudo systemctl status prosody
+
+# View logs
+sudo journalctl -u prosody -f
+
+# Check connected users
+sudo prosodyctl about
+```
+
+### **Managing Users**
+
+```bash
+# Add user
+sudo prosodyctl adduser newuser@yourdomain.com
+
+# Change password
+sudo prosodyctl passwd username@yourdomain.com
+
+# Delete user
+sudo prosodyctl deluser baduser@yourdomain.com
+
+# List users
+sudo prosodyctl list:users
+```
+
+### **Fail2ban Status**
+
+```bash
+# Check banned IPs
+sudo fail2ban-client status sshd
+
+# Unban an IP
+sudo fail2ban-client set sshd unbanip 1.2.3.4
+```
+
+### **Updating System**
+
+```bash
+# Update NixOS
+sudo nixos-rebuild switch --upgrade
+
+# Or manually pull latest config from git
+cd /etc/nixos
+git pull
+sudo nixos-rebuild switch
+```
+
+---
+
+## Troubleshooting
+
+### **Can't connect to XMPP server?**
+
+```bash
+# Check Prosody is running
+sudo systemctl status prosody
+
+# Check firewall
+sudo iptables -L -n -v | grep 5222
+
+# Check SSL certificate
+sudo prosodyctl cert check yourdomain.com
+```
+
+### **Let's Encrypt certificate failed?**
+
+```bash
+# Check ACME status
+sudo systemctl status acme-yourdomain.com
+
+# View logs
+sudo journalctl -u acme-yourdomain.com
+
+# Common issues:
+# - DNS not pointing to server yet (wait 10 minutes)
+# - Port 80/443 blocked (check firewall)
+# - Rate limit hit (Let's Encrypt limits 5 certs/week)
+```
+
+### **SSH locked out?**
+
+Use Vultr's web console:
+1. Go to Vultr dashboard
+2. Click your server
+3. Click "View Console"
+4. Log in as root (if you haven't locked it yet)
+
+---
+
+## Security Checklist
+
+- [ ] Root login disabled
+- [ ] SSH key-only authentication
+- [ ] Fail2ban enabled
+- [ ] Firewall rules active
+- [ ] SSL certificates valid
+- [ ] Sudo requires password
+- [ ] Non-standard SSH port (optional)
+- [ ] Automatic updates enabled
+- [ ] Logs being written
+
+**Verify:**
+```bash
+sudo systemctl status sshd
+sudo systemctl status fail2ban
+sudo systemctl status prosody
+sudo iptables -L -v
+```
+
+---
+
+## Cost Estimate
+
+- **Vultr VPS:** $6/month (1GB RAM)
+- **Domain name:** ~$12/year
+- **Total:** ~$84/year ($7/month)
+
+Compare to Discord Nitro: $10/month with worse privacy.
+
+---
+
+## Next Steps
+
+1. Invite friends (share `xmpp-setup.md`)
+2. Create more rooms as needed
+3. Set up backups (see below)
+4. Optional: Add voice/video (Jitsi Meet)
+
+---
+
+## Backup Strategy (Recommended)
+
+**What to backup:**
+- User accounts: `/var/lib/prosody/`
+- SSL certs: `/var/lib/acme/`
+- Config: `/etc/nixos/`
+
+**Simple backup script:**
+
+```bash
+#!/usr/bin/env bash
+# backup-xmpp.sh
+
+BACKUP_DIR="/root/backups"
+DATE=$(date +%Y%m%d)
+
+mkdir -p $BACKUP_DIR
+
+# Backup Prosody data
+tar czf $BACKUP_DIR/prosody-$DATE.tar.gz /var/lib/prosody/
+
+# Backup configs
+tar czf $BACKUP_DIR/nixos-$DATE.tar.gz /etc/nixos/
+
+# Keep last 7 days
+find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
+```
+
+**Automate:**
+```nix
+# Add to configuration.nix
+systemd.timers.xmpp-backup = {
+  wantedBy = [ "timers.target" ];
+  timerConfig = {
+    OnCalendar = "daily";
+    Persistent = true;
+  };
+};
+
+systemd.services.xmpp-backup = {
+  serviceConfig.ExecStart = "/root/backup-xmpp.sh";
+};
+```
+
+---
+
+## You're Done!
+
+Your XMPP server is now running, hardened, and ready for your friends.
+
+**Share this guide with friends:** `xmpp-setup.md`
+
+Welcome to decentralized, private communication. 🎉
diff --git a/future-modules/vultr-security.nix b/future-modules/vultr-security.nix
new file mode 100644
index 0000000..a698fd8
--- /dev/null
+++ b/future-modules/vultr-security.nix
@@ -0,0 +1,322 @@
+# vultr-security.nix - Hardened security configuration for VPS hosting
+#
+# This config implements defense-in-depth for a public XMPP server:
+# - SSH key-only authentication (no passwords)
+# - Fail2ban for brute-force prevention
+# - Firewall with strict rules (iptables via NixOS)
+# - Disabled root login
+# - Automatic security updates
+# - Minimal attack surface
+#
+# Usage:
+#   1. Generate SSH key locally: ssh-keygen -t ed25519 -C "your@email.com"
+#   2. Add your public key to 'authorizedKeys' below
+#   3. Import this module in configuration.nix
+#   4. nixos-rebuild switch
+#   5. IMPORTANT: Test SSH works BEFORE logging out! (ssh -i ~/.ssh/id_ed25519 youruser@server)
+#
+{ config, pkgs, lib, ... }:
+
+{
+  # ============================================================================
+  # SSH Configuration - Key-only, no root, no passwords
+  # ============================================================================
+  services.openssh = {
+    enable = true;
+    ports = [ 22 ];  # Change to non-standard port if you want (e.g., 2222)
+
+    settings = {
+      # Disable password authentication entirely
+      PasswordAuthentication = false;
+      KbdInteractiveAuthentication = false;  # No keyboard-interactive auth
+      ChallengeResponseAuthentication = false;
+
+      # Disable root login (force use of sudo from normal user)
+      PermitRootLogin = "no";
+
+      # Only allow public key authentication
+      PubkeyAuthentication = true;
+
+      # Additional hardening
+      PermitEmptyPasswords = false;
+      X11Forwarding = false;  # Disable X11 forwarding (not needed)
+      PrintMotd = false;
+
+      # Prevent SSH from being used as a tunnel
+      AllowTcpForwarding = false;
+      AllowStreamLocalForwarding = false;
+      GatewayPorts = "no";
+
+      # Limit login attempts
+      MaxAuthTries = 3;
+      MaxSessions = 2;
+
+      # Use strong ciphers only
+      Ciphers = [
+        "chacha20-poly1305@openssh.com"
+        "aes256-gcm@openssh.com"
+        "aes128-gcm@openssh.com"
+        "aes256-ctr"
+        "aes192-ctr"
+        "aes128-ctr"
+      ];
+
+      KexAlgorithms = [
+        "curve25519-sha256"
+        "curve25519-sha256@libssh.org"
+        "diffie-hellman-group16-sha512"
+        "diffie-hellman-group18-sha512"
+        "diffie-hellman-group-exchange-sha256"
+      ];
+
+      Macs = [
+        "hmac-sha2-512-etm@openssh.com"
+        "hmac-sha2-256-etm@openssh.com"
+        "umac-128-etm@openssh.com"
+      ];
+    };
+  };
+
+  # ============================================================================
+  # User Configuration
+  # ============================================================================
+  users.users.tony = {  # CHANGE THIS to your desired username
+    isNormalUser = true;
+    description = "Tony (Admin)";
+
+    # Add user to wheel group for sudo access
+    extraGroups = [ "wheel" "networkmanager" ];
+
+    # SSH public keys (ADD YOUR KEY HERE!)
+    openssh.authorizedKeys.keys = [
+      # Add your public key from ~/.ssh/id_ed25519.pub
+      # Example format:
+      # "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx your@email.com"
+
+      # IMPORTANT: Replace this with your actual public key!
+      "ssh-ed25519 REPLACE_THIS_WITH_YOUR_PUBLIC_KEY your@email.com"
+    ];
+
+    # Set a strong password as backup (in case SSH key is lost)
+    # Generate hash with: mkpasswd -m sha-512
+    # hashedPassword = "$6$rounds=500000$...";  # Uncomment and add hashed password
+  };
+
+  # Disable root password (force sudo from normal user)
+  users.users.root.hashedPassword = "!";  # "!" means locked account
+
+  # ============================================================================
+  # Sudo Configuration - Secure but usable
+  # ============================================================================
+  # NOTE: We keep sudo enabled because you need it to manage the server
+  # Disabling sudo would lock you out from system administration
+  security.sudo = {
+    enable = true;
+
+    # Require password for sudo (even with key-based SSH)
+    execWheelOnly = true;  # Only wheel group can sudo
+    wheelNeedsPassword = true;  # Require password for sudo commands
+
+    extraConfig = ''
+      # Require password every time (no timeout)
+      Defaults timestamp_timeout=0
+
+      # Log all sudo commands
+      Defaults logfile=/var/log/sudo.log
+      Defaults log_input, log_output
+
+      # Require password for ALL commands (no exceptions)
+      Defaults passwd_tries=3
+      Defaults badpass_message="Authentication failed. This incident will be logged."
+
+      # Prevent sudo -i or sudo su (force explicit commands)
+      # Uncomment these if you want to be extra strict:
+      # Defaults !rootpw
+      # Defaults !runaspw
+    '';
+  };
+
+  # Disable 'su' command (force sudo for all privilege escalation)
+  security.sudo.extraConfig = ''
+    # Block 'su' by preventing root password authentication
+    # Users must use 'sudo' for specific commands instead of 'su'
+  '';
+
+  # Remove 'su' from system entirely (optional - uncomment if desired)
+  # environment.systemPackages = lib.mkForce (builtins.filter (pkg: pkg.pname or "" != "shadow") config.environment.systemPackages);
+
+  # ============================================================================
+  # Firewall - Only allow necessary ports
+  # ============================================================================
+  networking.firewall = {
+    enable = true;
+
+    # Allowed ports (ONLY what's needed)
+    allowedTCPPorts = [
+      22    # SSH (change if you modified SSH port above)
+      80    # HTTP (for Let's Encrypt ACME challenge)
+      443   # HTTPS (for Let's Encrypt ACME challenge)
+      5222  # XMPP client-to-server (C2S)
+      5269  # XMPP server-to-server (S2S) - remove if no federation
+      5281  # XMPP HTTPS (file uploads)
+    ];
+
+    # Block ping (optional - makes server invisible to ping sweeps)
+    allowPing = false;
+
+    # Advanced firewall rules using iptables
+    extraCommands = ''
+      # Rate-limit SSH connections (prevent brute-force)
+      iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH
+      iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP
+
+      # Rate-limit XMPP connections (prevent abuse)
+      iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set --name XMPP
+      iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name XMPP -j DROP
+
+      # Drop invalid packets
+      iptables -A INPUT -m state --state INVALID -j DROP
+
+      # Allow established connections
+      iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
+
+      # Log dropped packets (for debugging - comment out in production)
+      # iptables -A INPUT -j LOG --log-prefix "iptables-dropped: " --log-level 4
+    '';
+
+    extraStopCommands = ''
+      iptables -D INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 --name SSH -j DROP 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set --name XMPP 2>/dev/null || true
+      iptables -D INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name XMPP -j DROP 2>/dev/null || true
+    '';
+  };
+
+  # ============================================================================
+  # Fail2Ban - Automatic IP banning for repeated failed attempts
+  # ============================================================================
+  services.fail2ban = {
+    enable = true;
+
+    # Ban for 1 hour after 3 failed attempts within 10 minutes
+    maxretry = 3;
+    bantime = "1h";
+    findtime = "10m";
+
+    jails = {
+      # SSH brute-force protection
+      sshd = ''
+        enabled = true
+        filter = sshd
+        action = iptables[name=SSH, port=22, protocol=tcp]
+        maxretry = 3
+        findtime = 600
+        bantime = 3600
+      '';
+
+      # XMPP brute-force protection
+      # (Custom filter - see below)
+      prosody = ''
+        enabled = true
+        filter = prosody-auth
+        action = iptables[name=XMPP, port=5222, protocol=tcp]
+        logpath = /var/log/prosody/prosody.log
+        maxretry = 5
+        findtime = 600
+        bantime = 3600
+      '';
+    };
+  };
+
+  # Custom fail2ban filter for Prosody auth failures
+  environment.etc."fail2ban/filter.d/prosody-auth.conf".text = ''
+    [Definition]
+    failregex = ^.* \[error\] .* (authentication failure|certificate expired) for .* from <HOST>
+    ignoreregex =
+  '';
+
+  # ============================================================================
+  # Automatic Security Updates (Optional - Recommended)
+  # ============================================================================
+  system.autoUpgrade = {
+    enable = true;
+    dates = "weekly";  # Update once per week
+    allowReboot = false;  # Don't auto-reboot (manual intervention)
+    channel = "https://nixos.org/channels/nixos-24.05";  # Stable channel
+  };
+
+  # ============================================================================
+  # Additional Hardening
+  # ============================================================================
+
+  # Disable unnecessary services
+  services.avahi.enable = false;  # No mDNS/Bonjour
+  services.printing.enable = false;  # No CUPS printing
+
+  # Kernel hardening
+  boot.kernel.sysctl = {
+    # Prevent IP spoofing
+    "net.ipv4.conf.all.rp_filter" = 1;
+    "net.ipv4.conf.default.rp_filter" = 1;
+
+    # Ignore ICMP redirects
+    "net.ipv4.conf.all.accept_redirects" = 0;
+    "net.ipv4.conf.default.accept_redirects" = 0;
+    "net.ipv6.conf.all.accept_redirects" = 0;
+    "net.ipv6.conf.default.accept_redirects" = 0;
+
+    # Ignore ICMP echo requests (disable ping response)
+    "net.ipv4.icmp_echo_ignore_all" = 1;
+
+    # Disable IPv6 if not needed (optional)
+    # "net.ipv6.conf.all.disable_ipv6" = 1;
+
+    # Enable TCP SYN cookies (prevent SYN flood attacks)
+    "net.ipv4.tcp_syncookies" = 1;
+
+    # Disable source routing
+    "net.ipv4.conf.all.accept_source_route" = 0;
+    "net.ipv6.conf.all.accept_source_route" = 0;
+
+    # Log suspicious packets
+    "net.ipv4.conf.all.log_martians" = 1;
+  };
+
+  # ============================================================================
+  # Logging and Monitoring
+  # ============================================================================
+  services.journald.extraConfig = ''
+    MaxRetentionSec=1month
+    SystemMaxUse=1G
+  '';
+
+  # Log failed login attempts
+  security.pam.loginLimits = [
+    { domain = "*"; type = "hard"; item = "maxlogins"; value = "3"; }
+  ];
+
+  # ============================================================================
+  # Minimal Package Set (Reduce attack surface)
+  # ============================================================================
+  environment.systemPackages = with pkgs; [
+    # Essential tools only
+    vim
+    wget
+    curl
+    htop
+    tmux
+    git
+
+    # Security tools
+    fail2ban
+    iptables
+
+    # Prosody admin tools
+    # (prosodyctl is included with services.prosody)
+  ];
+
+  # Disable documentation to save space and reduce attack surface
+  documentation.enable = false;
+  documentation.man.enable = false;
+  documentation.nixos.enable = false;
+}
diff --git a/future-modules/xmpp-home.nix b/future-modules/xmpp-home.nix
new file mode 100644
index 0000000..4f74421
--- /dev/null
+++ b/future-modules/xmpp-home.nix
@@ -0,0 +1,146 @@
+# xmpp-home.nix - XMPP server for home hosting
+#
+# Differences from VPS version:
+# - Uses chat.tonybtw.com (your actual domain)
+# - Cloudflare DNS challenge for SSL (works behind NAT)
+# - Less strict resource limits
+# - Assumes you're on a residential connection
+#
+{ config, pkgs, lib, ... }:
+
+let
+  domain = "chat.tonybtw.com";
+  mucDomain = "conference.chat.tonybtw.com";
+  uploadDomain = "upload.chat.tonybtw.com";
+in
+{
+  services.prosody = {
+    enable = true;
+
+    # Package with community modules
+    package = pkgs.prosody.override {
+      withCommunityModules = [
+        "http_upload"
+        "cloud_notify"
+        "bookmarks2"
+        "smacks"
+      ];
+    };
+
+    # Admin accounts (create these after deployment)
+    admins = [ "tony@${domain}" ];  # CHANGE THIS to your username
+
+    # SSL certificates (managed by ACME in home-server-base.nix)
+    ssl = {
+      cert = "/var/lib/acme/${domain}/fullchain.pem";
+      key = "/var/lib/acme/${domain}/key.pem";
+    };
+
+    # File uploads (100MB limit)
+    uploadHttp = {
+      domain = uploadDomain;
+      uploadFileSizeLimit = "100 * 1024 * 1024";
+      uploadExpireAfter = "60 * 60 * 24 * 30";  # 30 days
+    };
+
+    # Multi-User Chat (group chat rooms)
+    muc = [
+      {
+        domain = mucDomain;
+        name = "Tony's Chat Rooms";
+        restrictRoomCreation = false;  # Anyone can create rooms
+        maxHistoryMessages = 50;
+      }
+    ];
+
+    # Main XMPP domain
+    virtualHosts.${domain} = {
+      enabled = true;
+      domain = domain;
+      ssl = {
+        cert = "/var/lib/acme/${domain}/fullchain.pem";
+        key = "/var/lib/acme/${domain}/key.pem";
+      };
+    };
+
+    # Modern XMPP features
+    modules = {
+      # Core
+      roster = true;
+      saslauth = true;
+      tls = true;
+      dialback = true;
+      disco = true;
+
+      # Modern messaging
+      carbons = true;
+      mam = true;
+      smacks = true;
+      csi = true;
+      cloud_notify = true;
+
+      # Privacy
+      blocklist = true;
+      bookmarks = true;
+      bookmarks2 = true;
+
+      # Convenience
+      ping = true;
+      register = false;  # Manual account creation only
+      admin_adhoc = true;
+      admin_telnet = true;
+
+      # HTTP
+      http_files = true;
+      http_upload = true;
+
+      # MUC
+      muc_mam = true;
+    };
+
+    extraConfig = ''
+      -- Logging
+      log = {
+        info = "/var/log/prosody/prosody.log";
+        error = "/var/log/prosody/prosody.err";
+        "*syslog";
+      }
+
+      -- Message history (keep for 1 year)
+      archive_expires_after = "1y"
+      mam_default_config = { always = true }
+
+      -- Connection settings
+      c2s_stanza_size_limit = 256 * 1024  -- 256KB
+
+      -- TLS settings
+      ssl = {
+        protocol = "tlsv1_2+";
+        ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
+        options = { "no_sslv2", "no_sslv3", "no_ticket", "no_compression", "cipher_server_preference", "single_dh_use", "single_ecdh_use" };
+      }
+
+      -- OMEMO encryption support
+      modules_enabled = { "omemo_all_access" }
+
+      -- Disable federation if you only want private server (optional)
+      -- Uncomment to prevent federation with other XMPP servers:
+      -- modules_disabled = { "s2s" }
+    '';
+  };
+
+  # Create log directory
+  systemd.tmpfiles.rules = [
+    "d /var/log/prosody 0750 prosody prosody -"
+  ];
+
+  # Optional: Nginx reverse proxy for XMPP web client (if you want one later)
+  # services.nginx.virtualHosts."${domain}" = {
+  #   enableACME = true;
+  #   forceSSL = true;
+  #   locations."/" = {
+  #     # Could serve a web client like Converse.js here
+  #     root = "/var/www/xmpp";
+  #   };
+  # };
+}
diff --git a/future-modules/xmpp-setup.md b/future-modules/xmpp-setup.md
new file mode 100644
index 0000000..5c43d68
--- /dev/null
+++ b/future-modules/xmpp-setup.md
@@ -0,0 +1,356 @@
+# How to Join the XMPP Server
+
+Welcome! We're moving from Discord to XMPP for better privacy and control. This guide will help you get set up in 5 minutes.
+
+## What is XMPP?
+
+XMPP is like Discord, but:
+- **Privacy-focused** - No tracking, no ads, no data mining
+- **Self-hosted** - We control the server, not a corporation
+- **Open source** - Free software you can trust
+- **End-to-end encrypted** - Your messages are secure (OMEMO encryption)
+
+You get all the important features: group chats, DMs, file sharing, voice/video calls, message history, and mobile apps.
+
+---
+
+## Quick Setup Guide
+
+### Step 1: Get Your Account Credentials
+
+You'll receive:
+- **Username:** `yourname@yourdomain.com`
+- **Password:** (a temporary password - change it after first login)
+- **Server:** `yourdomain.com`
+
+### Step 2: Install a Client
+
+Choose based on your device:
+
+#### **Android** (Recommended: Conversations)
+1. Open Google Play Store
+2. Search for **"Conversations"** (€3.99) or **"Monocles Chat"** (free)
+3. Install it
+4. Open the app and tap **"Create Account"** or **"Add Account"**
+5. Enter your credentials:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+6. Tap **"Connect"**
+7. Done! You'll see your contact list and can join rooms
+
+**Why Conversations?** Best XMPP client, supports all modern features, active development, one-time payment.
+
+---
+
+#### **iPhone** (Recommended: Monal)
+1. Open App Store
+2. Search for **"Monal"** (free)
+3. Install it
+4. Open the app and tap **"Add Account"**
+5. Choose **"XMPP"**
+6. Enter your credentials:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+7. Tap **"Save"**
+8. Done! You'll see your contact list and can join rooms
+
+---
+
+#### **Linux** (Recommended: Dino or Gajim)
+
+**Option A: Dino (Modern, GNOME-style)**
+```bash
+# NixOS
+nix-shell -p dino
+
+# Ubuntu/Debian
+sudo apt install dino-im
+
+# Arch
+sudo pacman -S dino
+
+# Fedora
+sudo dnf install dino
+```
+
+1. Open Dino
+2. Click **"Add Account"**
+3. Enter:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+4. Click **"Connect"**
+
+**Option B: Gajim (Feature-rich)**
+```bash
+# NixOS
+nix-shell -p gajim
+
+# Ubuntu/Debian
+sudo apt install gajim
+
+# Arch
+sudo pacman -S gajim
+
+# Fedora
+sudo dnf install gajim
+```
+
+1. Open Gajim
+2. Go to **Accounts → Add Account**
+3. Choose **"I already have an account"**
+4. Enter:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+5. Click **"Connect"**
+
+---
+
+#### **Windows** (Recommended: Gajim)
+1. Download Gajim: https://gajim.org/download/
+2. Run the installer
+3. Open Gajim
+4. Go to **Accounts → Add Account**
+5. Choose **"I already have an account"**
+6. Enter your credentials:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+7. Click **"Connect"**
+
+---
+
+#### **macOS** (Recommended: Monal)
+1. Open Mac App Store
+2. Search for **"Monal"** (free)
+3. Install it
+4. Open Monal and click **"Add Account"**
+5. Choose **"XMPP"**
+6. Enter your credentials:
+   - **Jabber ID:** `yourname@yourdomain.com`
+   - **Password:** (the one provided)
+7. Click **"Save"**
+
+---
+
+### Step 3: Join Group Chats
+
+Once connected, join our community rooms:
+
+#### **In Conversations (Android):**
+1. Tap the **"+"** button (bottom right)
+2. Select **"Join group chat"**
+3. Enter: `#general@conference.yourdomain.com`
+4. Tap **"Join"**
+
+#### **In Monal (iOS/macOS):**
+1. Tap **"Chats"** tab
+2. Tap the **"+"** button (top right)
+3. Select **"Join Channel"**
+4. Enter: `#general@conference.yourdomain.com`
+5. Tap **"Join"**
+
+#### **In Dino/Gajim (Desktop):**
+1. Click **"Join Group Chat"** or press `Ctrl+J`
+2. Enter: `#general@conference.yourdomain.com`
+3. Click **"Join"**
+
+#### **Available Rooms:**
+- `#general@conference.yourdomain.com` - Main discussion
+- `#random@conference.yourdomain.com` - Off-topic chat
+- `#tech@conference.yourdomain.com` - Tech discussion
+- `#gaming@conference.yourdomain.com` - Gaming chat
+
+---
+
+## Features Guide
+
+### **Sending Messages**
+Just type and hit Enter. Markdown-like formatting works in most clients:
+- `*bold*` → **bold**
+- `_italic_` → _italic_
+- `` `code` `` → `code`
+
+### **Sending Files**
+- **Mobile:** Tap the attachment icon
+- **Desktop:** Drag and drop files, or click the attachment button
+- **Size limit:** 100MB per file
+
+### **Voice/Video Calls**
+- **Gajim/Dino:** Click the phone/video icon in a 1-on-1 chat
+- **Conversations:** Tap the phone icon in a contact's chat
+- **Monal:** Limited support (text chat is primary)
+
+### **End-to-End Encryption (OMEMO)**
+All DMs are encrypted by default. For group chats:
+1. Open the room
+2. Enable OMEMO in settings
+3. Everyone in the room needs OMEMO-capable clients
+
+### **Push Notifications**
+- **Mobile:** Works automatically (Conversations, Monal)
+- **Desktop:** Enable system notifications in client settings
+
+### **Message History**
+The server saves your messages for 1 year. When you log in on a new device, you'll see recent history automatically.
+
+### **Changing Your Password**
+Most clients have a "Change Password" option in account settings. Or ask an admin to reset it.
+
+---
+
+## Comparison: Discord vs XMPP
+
+| Feature | Discord | XMPP |
+|---------|---------|------|
+| **Group chats** | ✓ Servers/Channels | ✓ Rooms |
+| **Direct messages** | ✓ | ✓ |
+| **File sharing** | ✓ (25MB free) | ✓ (100MB) |
+| **Voice/video** | ✓ | ✓ (varies by client) |
+| **Screen sharing** | ✓ | ✗ (not yet) |
+| **Message history** | ✓ | ✓ |
+| **Mobile apps** | ✓ | ✓ |
+| **End-to-end encryption** | ✗ | ✓ (OMEMO) |
+| **Self-hosted** | ✗ | ✓ |
+| **Open source** | ✗ | ✓ |
+| **Privacy** | Poor (data mining) | Excellent (self-hosted) |
+| **Ads/tracking** | Yes | No |
+
+**Bottom line:** XMPP has 90% of Discord's features, but with 100% more privacy and control.
+
+---
+
+## Troubleshooting
+
+### **Can't connect?**
+- Check your username is `yourname@yourdomain.com` (not just `yourname`)
+- Verify your password (no typos)
+- Make sure you have internet connection
+- Try a different client (sometimes mobile works when desktop doesn't)
+
+### **No push notifications on mobile?**
+- Enable notifications in phone settings
+- Conversations: Needs Google Play Services OR UnifiedPush
+- Monal: Should work automatically
+
+### **Can't join a room?**
+- Make sure the room address is correct: `#roomname@conference.yourdomain.com`
+- Check if you're connected (green indicator)
+- Try typing the full address instead of using autocomplete
+
+### **Messages not syncing across devices?**
+- Make sure "Message Carbons" is enabled (usually automatic)
+- Gajim: Preferences → Advanced → Message Carbons
+
+### **Voice calls not working?**
+- Check microphone permissions
+- Desktop only: Gajim and Dino support calls (Conversations on Android too)
+- Make sure both parties are using compatible clients
+
+### **Someone's messages show a warning icon?**
+- OMEMO encryption issue
+- Their device hasn't verified yours yet
+- Usually safe to trust on first contact (like Signal)
+
+---
+
+## Getting Help
+
+- **Join the support room:** `#support@conference.yourdomain.com`
+- **Contact an admin:** Message `admin@yourdomain.com`
+- **XMPP documentation:** https://xmpp.org/getting-started/
+
+---
+
+## Tips for Discord Refugees
+
+### **Mental Model Shift:**
+- **Discord Server** → **XMPP Room** (group chat)
+- **Discord Channel** → **XMPP Room** (each room is independent)
+- **Discord DM** → **XMPP Chat** (works the same)
+- **@mention** → `@username` (type @ then start typing their name)
+
+### **Things That Work Differently:**
+- **No bots (yet):** XMPP has bots but not Discord-style
+- **No custom emoji reactions:** Standard emoji only
+- **No roles/permissions:** Room moderators exist but simpler
+- **No status games:** You can set a text status but no "Playing Valorant" integration
+
+### **Things That Are Better:**
+- **E2E encryption:** Your messages are actually private
+- **No spying:** Server admin (me) can't read encrypted DMs
+- **No data mining:** Your info isn't sold to advertisers
+- **Lightweight:** Apps use less battery/RAM
+- **Decentralized:** If this server dies, you can move to another XMPP server
+
+### **Learning Curve:**
+- **Day 1:** Feels weird, where are my Discord features?
+- **Day 3:** Okay, this works pretty well
+- **Week 1:** I like how lightweight this is
+- **Month 1:** Wait, Discord had ads? And no encryption?
+
+---
+
+## Creating Your Own Rooms
+
+Want to create a private chat room?
+
+#### **In Conversations (Android):**
+1. Tap **"+"** → **"Create group chat"**
+2. Choose **"Create new channel"**
+3. Enter room name (e.g., "secret-plans")
+4. Tap **"Create"**
+
+#### **In Gajim/Dino:**
+1. **"Join Group Chat"** → **"Create New Room"**
+2. Room address: `yourroom@conference.yourdomain.com`
+3. Click **"Create"**
+
+#### **Room Privacy:**
+- **Public:** Anyone can join
+- **Private:** Invite-only (set in room config)
+- **OMEMO-enabled:** End-to-end encrypted (enable after creation)
+
+---
+
+## Migrating from Discord
+
+### **Export your Discord data (optional):**
+Discord doesn't have a good export tool. If you have important messages:
+1. Use DiscordChatExporter (third-party tool)
+2. Save channels as HTML/JSON
+3. Upload to file sharing if needed
+
+### **Telling people you switched:**
+```
+Hey! I'm moving to XMPP for privacy reasons.
+
+Add me: yourname@yourdomain.com
+
+What's XMPP? It's like Discord but self-hosted and encrypted.
+Get the Conversations app (Android) or Monal (iOS).
+
+I'll still check Discord occasionally, but I'm mainly on XMPP now.
+```
+
+---
+
+## Why We Switched
+
+- **Privacy:** Discord mines your data, XMPP doesn't
+- **Control:** We own the server, not a corporation
+- **No ads:** Self-hosted means no monetization pressure
+- **Open source:** Auditable code, community-driven
+- **Decentralized:** Not dependent on one company staying solvent
+- **Better security:** E2E encryption actually works
+- **Lightweight:** Less resource-heavy than Discord
+
+Discord is convenient, but we're trading convenience for privacy and autonomy.
+
+---
+
+## Welcome to XMPP!
+
+Once you're connected, say hi in `#general`! We're a small community, but we value privacy and open-source software.
+
+If you have questions, ask in `#support` or DM an admin.
+
+Enjoy your freedom from surveillance capitalism. :)
diff --git a/future-modules/xmpp.nix b/future-modules/xmpp.nix
new file mode 100644
index 0000000..966417c
--- /dev/null
+++ b/future-modules/xmpp.nix
@@ -0,0 +1,170 @@
+# xmpp.nix - Prosody XMPP server configuration
+# A modern, privacy-focused alternative to Discord
+#
+# Usage:
+#   1. Import this module in your configuration.nix
+#   2. Update "yourdomain.com" to your actual domain
+#   3. nixos-rebuild switch
+#   4. Create user accounts: prosodyctl adduser user@yourdomain.com
+#
+{ config, pkgs, lib, ... }:
+
+let
+  domain = "yourdomain.com";  # CHANGE THIS to your domain
+  mucDomain = "conference.${domain}";
+  uploadDomain = "upload.${domain}";
+in
+{
+  services.prosody = {
+    enable = true;
+
+    # Package with community modules for modern XMPP features
+    package = pkgs.prosody.override {
+      withCommunityModules = [
+        "http_upload"
+        "cloud_notify"
+        "bookmarks2"
+        "smacks"
+      ];
+    };
+
+    # Admin users (can create/delete rooms, manage users, see server stats)
+    # Add accounts here AFTER you create them with prosodyctl
+    admins = [ "admin@${domain}" ];
+
+    # SSL/TLS certificates (Let's Encrypt)
+    ssl = {
+      cert = "/var/lib/acme/${domain}/fullchain.pem";
+      key = "/var/lib/acme/${domain}/key.pem";
+    };
+
+    # File uploads (for images, documents, etc.)
+    # Users can share files up to 100MB
+    uploadHttp = {
+      domain = uploadDomain;
+      uploadFileSizeLimit = "100 * 1024 * 1024";  # 100MB
+      uploadExpireAfter = "60 * 60 * 24 * 30";    # 30 days
+    };
+
+    # Group chat (Multi-User Chat / MUC)
+    # This is where your channels/rooms live
+    muc = [
+      {
+        domain = mucDomain;
+        name = "Chat Rooms";
+        restrictRoomCreation = false;  # Anyone can create rooms
+        maxHistoryMessages = 50;       # Room history sent to new joiners
+      }
+    ];
+
+    # Your main XMPP domain
+    virtualHosts.${domain} = {
+      enabled = true;
+      domain = domain;
+      ssl = {
+        cert = "/var/lib/acme/${domain}/fullchain.pem";
+        key = "/var/lib/acme/${domain}/key.pem";
+      };
+    };
+
+    # Enable modern XMPP features
+    modules = {
+      # Core features
+      roster = true;              # Contact lists
+      saslauth = true;            # Authentication
+      tls = true;                 # Encryption in transit
+      dialback = true;            # Server-to-server auth
+      disco = true;               # Service discovery
+
+      # Modern messaging features
+      carbons = true;             # Sync messages across devices
+      mam = true;                 # Message history (Message Archive Management)
+      smacks = true;              # Stream management (better mobile connections)
+      csi = true;                 # Client state indication (battery saving)
+      cloud_notify = true;        # Push notifications
+
+      # Privacy and security
+      blocklist = true;           # Block unwanted users
+      bookmarks = true;           # Save favorite rooms
+      bookmarks2 = true;          # Modern bookmarks (XEP-0402)
+
+      # Convenience
+      ping = true;                # Keep-alive
+      register = false;           # Disable public registration (you create accounts manually)
+      admin_adhoc = true;         # Admin commands via client
+      admin_telnet = true;        # Admin console (telnet to localhost:5582)
+
+      # HTTP
+      http_files = true;          # Serve files over HTTP
+      http_upload = true;         # File upload support
+
+      # Room features
+      muc_mam = true;             # Message history in group chats
+    };
+
+    # Extra configuration (Lua)
+    extraConfig = ''
+      -- Logging
+      log = {
+        info = "/var/log/prosody/prosody.log";
+        error = "/var/log/prosody/prosody.err";
+        "*syslog";
+      }
+
+      -- Message Archive Management (history) settings
+      archive_expires_after = "1y"  -- Keep messages for 1 year
+      mam_default_config = { always = true }  -- Enable history by default
+
+      -- Connection limits
+      c2s_stanza_size_limit = 256 * 1024  -- 256KB (for file upload stanzas)
+
+      -- TLS/SSL settings
+      ssl = {
+        protocol = "tlsv1_2+";
+        ciphers = "HIGH+kEDH:HIGH+kEECDH:HIGH:!PSK:!SRP:!3DES:!aNULL";
+        options = { "no_sslv2", "no_sslv3", "no_ticket", "no_compression", "cipher_server_preference", "single_dh_use", "single_ecdh_use" };
+      }
+
+      -- Certificates for additional domains
+      certificates = "/var/lib/acme"
+
+      -- Enable OMEMO encryption support
+      modules_enabled = { "omemo_all_access" }
+    '';
+  };
+
+  # Let's Encrypt for SSL certificates
+  security.acme = {
+    acceptTerms = true;
+    defaults.email = "admin@${domain}";  # CHANGE THIS to your email
+
+    certs.${domain} = {
+      group = "prosody";
+
+      # Reload Prosody after certificate renewal
+      postRun = "systemctl reload prosody.service";
+
+      # Extra domain names (for MUC and uploads)
+      extraDomainNames = [ mucDomain uploadDomain ];
+    };
+  };
+
+  # Open firewall ports
+  networking.firewall.allowedTCPPorts = [
+    5222  # Client-to-server (C2S) - your friends connect here
+    5269  # Server-to-server (S2S) - federation with other XMPP servers (optional)
+    5281  # HTTPS for file uploads and admin interface
+  ];
+
+  # Create log directory
+  systemd.tmpfiles.rules = [
+    "d /var/log/prosody 0750 prosody prosody -"
+  ];
+
+  # Optional: Firewall rules to rate-limit connection attempts
+  # Uncomment to prevent brute-force attacks
+  # networking.firewall.extraCommands = ''
+  #   iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --set
+  #   iptables -A INPUT -p tcp --dport 5222 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 -j DROP
+  # '';
+}
diff --git a/hardware-configuration.nix b/hardware-configuration.nix
new file mode 100644
index 0000000..13e6361
--- /dev/null
+++ b/hardware-configuration.nix
@@ -0,0 +1,40 @@
+# 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" "thunderbolt" "nvme" "usb_storage" "sd_mod" ];
+  boot.initrd.kernelModules = [ ];
+  boot.kernelModules = [ "kvm-intel" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    { device = "/dev/disk/by-uuid/10f1514b-e441-4ec9-8af7-a2c2aef1ea5a";
+      fsType = "ext4";
+    };
+
+  fileSystems."/boot" =
+    { device = "/dev/disk/by-uuid/AC57-0142";
+      fsType = "vfat";
+      options = [ "fmask=0022" "dmask=0022" ];
+    };
+
+  swapDevices =
+    [ { device = "/dev/disk/by-uuid/adf96073-a6d5-47b9-be6c-0449c5ac207a"; }
+    ];
+
+  # 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.wlp0s20f3.useDHCP = lib.mkDefault true;
+
+  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+  hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
+}
diff --git a/home.nix b/home.nix
new file mode 100644
index 0000000..5bc39a8
--- /dev/null
+++ b/home.nix
@@ -0,0 +1,85 @@
+{
+  config,
+  pkgs,
+  ...
+}: let
+  dotfiles = "${config.home.homeDirectory}/nixos-dotfiles/config";
+  create_symlink = path: config.lib.file.mkOutOfStoreSymlink path;
+  configs = {
+    qtile = "qtile";
+    nvim = "nvim";
+    alacritty = "alacritty";
+    rofi = "rofi";
+    picom = "picom";
+    doom = "doom";
+    tmux = "tmux";
+    foot = "foot";
+    sway = "sway";
+    quickshell = "quickshell";
+    hypr = "hypr";
+    waybar = "waybar";
+    mango = "mango";
+    oxwm = "oxwm";
+  };
+in {
+  imports = [
+    ./modules/neovim.nix
+    ./modules/other.nix
+    ./modules/doom-emacs.nix
+    ./modules/work.nix
+    ./modules/themes.nix
+    ./modules/suckless.nix
+    ./modules/wayland.nix
+    ./modules/unstable.nix
+  ];
+
+  home.username = "tony";
+  home.homeDirectory = "/home/tony";
+  home.stateVersion = "25.05";
+
+  programs.git = {
+    enable = true;
+    extraConfig = {
+      credential.helper = "store";
+      user.name = "tonybtw";
+      user.email = "tonybtw@tonybtw.com";
+      clone.defaultRemoteName = "tony";
+    };
+  };
+
+  xdg.configFile =
+    builtins.mapAttrs
+    (name: subpath: {
+      source = create_symlink "${dotfiles}/${subpath}";
+      recursive = true;
+    })
+    configs;
+
+  home.file.".bashrc".source = ./config/bashrc/.bashrc;
+
+  home.packages = with pkgs; [
+    rustc
+    cargo
+    distrobox
+    podman
+    rofi
+    fastfetch
+    xwallpaper
+    pcmanfm
+    tldr
+    xclip
+    maim
+    pfetch-rs
+    lxappearance
+    jq
+    eza
+    acpi
+    tmux
+    gh
+    gimp
+    libreoffice
+    xorg.xclock
+    direnv
+    qbittorrent
+  ];
+}
diff --git a/modules/doom-emacs.nix b/modules/doom-emacs.nix
new file mode 100644
index 0000000..7dc856a
--- /dev/null
+++ b/modules/doom-emacs.nix
@@ -0,0 +1,16 @@
+{ pkgs, ... }:
+{
+  programs.emacs = {
+    enable = true;
+    package = pkgs.emacs;
+  };
+
+  home.packages = with pkgs; [
+    git
+    ripgrep
+    fd
+    gcc
+    sqlite
+  ];
+}
+
diff --git a/modules/neovim.nix b/modules/neovim.nix
new file mode 100644
index 0000000..a5c192a
--- /dev/null
+++ b/modules/neovim.nix
@@ -0,0 +1,47 @@
+{
+  pkgs,
+  unstable_pkgs,
+  ...
+}: {
+  # neovim-nightly
+  # nixpkgs.overlays = [ neovim-nightly.overlays.default ];
+  # Install Neovim, and Dependencies
+  home.packages = with pkgs; [
+    neovim
+    # tools required for plugins
+    ripgrep
+    fzf
+    fd
+
+    # language servers
+    nil
+    nixpkgs-fmt
+    lua-language-server
+    pyright
+    unstable_pkgs.ncurses.dev
+    unstable_pkgs.pkg-config
+    unstable_pkgs.rust-analyzer
+    copilot-language-server
+    clang-tools
+    rustfmt
+    alejandra
+    jq
+    unstable_pkgs.vscode-json-languageserver
+    unstable_pkgs.prettier
+
+    unstable_pkgs.zig
+    unstable_pkgs.zls
+    # haskell / xmonad
+    (unstable_pkgs.haskellPackages.ghcWithPackages (hpkgs:
+      with hpkgs; [
+        xmonad
+        xmonad-contrib
+      ]))
+    unstable_pkgs.haskell-language-server
+
+    # needed for lazy.nvim
+    nodejs
+    gcc
+    gnumake
+  ];
+}
diff --git a/modules/noctalia.nix b/modules/noctalia.nix
new file mode 100644
index 0000000..5ff63a5
--- /dev/null
+++ b/modules/noctalia.nix
@@ -0,0 +1,9 @@
+{
+  pkgs,
+  noctalia,
+  ...
+}: {
+  environment.systemPackages = with pkgs; [
+    noctalia.packages.${system}.default
+  ];
+}
diff --git a/modules/other.nix b/modules/other.nix
new file mode 100644
index 0000000..059e432
--- /dev/null
+++ b/modules/other.nix
@@ -0,0 +1,11 @@
+{pkgs, ...}: {
+  # Install other packages (organize later)
+  home.packages = with pkgs; [
+    nitch
+    bat
+    nix-search-tv
+    fzf
+    delta
+    btop
+  ];
+}
diff --git a/modules/oxwm.nix b/modules/oxwm.nix
new file mode 100644
index 0000000..cb44683
--- /dev/null
+++ b/modules/oxwm.nix
@@ -0,0 +1,13 @@
+{ pkgs, ... }:
+
+{
+  home.packages = [
+    pkgs.rustc
+    pkgs.cargo
+    pkgs.xorg.xorgserver
+    pkgs.xorg.xclock
+    pkgs.xterm
+    pkgs.just
+  ];
+}
+
diff --git a/modules/rust.nix b/modules/rust.nix
new file mode 100644
index 0000000..8783feb
--- /dev/null
+++ b/modules/rust.nix
@@ -0,0 +1,9 @@
+{ pkgs, ... }:
+
+{
+  # Install rust packages
+  home.packages = with pkgs; [
+    rustc
+    cargo
+  ];
+}
diff --git a/modules/suckless.nix b/modules/suckless.nix
new file mode 100644
index 0000000..620380f
--- /dev/null
+++ b/modules/suckless.nix
@@ -0,0 +1,21 @@
+{pkgs, ...}: {
+  home.packages = with pkgs; [
+    (pkgs.st.overrideAttrs (_: {
+      src = ../config/st;
+      patches = [];
+    }))
+    (pkgs.dmenu.overrideAttrs (_: {
+      src = ../config/dmenu;
+      patches = [];
+    }))
+    (pkgs.dwmblocks.overrideAttrs (_: {
+      src = ../config/dwmblocks;
+      patches = [];
+    }))
+    (pkgs.slstatus.overrideAttrs (_: {
+      src = ../config/slstatus;
+      patches = [];
+    }))
+    slock
+  ];
+}
diff --git a/modules/themes.nix b/modules/themes.nix
new file mode 100644
index 0000000..fc0bd11
--- /dev/null
+++ b/modules/themes.nix
@@ -0,0 +1,14 @@
+{ config, pkgs, ... }:
+{
+  home.packages = with pkgs; [
+    arc-theme 
+    materia-theme 
+    qogir-theme
+    orchis-theme
+    numix-gtk-theme
+    gtk-engine-murrine 
+    catppuccin-gtk
+    candy-icons
+  ];
+}
+
diff --git a/modules/unstable.nix b/modules/unstable.nix
new file mode 100644
index 0000000..531f18c
--- /dev/null
+++ b/modules/unstable.nix
@@ -0,0 +1,10 @@
+{unstable_pkgs, ...}: {
+  home.packages = with unstable_pkgs; [
+    discord
+    quickshell
+    claude-code
+    obs-studio
+    evil-helix
+    zed-editor
+  ];
+}
diff --git a/modules/wayland.nix b/modules/wayland.nix
new file mode 100644
index 0000000..7d3511d
--- /dev/null
+++ b/modules/wayland.nix
@@ -0,0 +1,24 @@
+{pkgs, ...}: {
+  home.packages = with pkgs; [
+    foot
+    wayland
+    xwayland
+    libdrm
+    fcft
+    wmenu
+    wofi
+    bemenu
+    grim
+    slurp
+    wl-clipboard
+    swaybg
+    swaylock
+    swayidle
+    jq
+    procps
+    waybar
+    hyprpaper
+    fuzzel
+    xwayland-satellite
+  ];
+}
diff --git a/modules/work.nix b/modules/work.nix
new file mode 100644
index 0000000..3bb1312
--- /dev/null
+++ b/modules/work.nix
@@ -0,0 +1,11 @@
+{ pkgs, ... }:
+
+{
+  home.packages = with pkgs; [
+    slack
+    zoom
+    jetbrains.datagrip
+    brave
+  ];
+}
+
diff --git a/scripts/cinit.sh b/scripts/cinit.sh
new file mode 100755
index 0000000..4ad842f
--- /dev/null
+++ b/scripts/cinit.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+if [ -z "$1" ]; then
+    echo "Usage: $0 <project_name>"
+    exit 1
+fi
+
+PROJECT_NAME="$1"
+
+mkdir -p "$PROJECT_NAME" || exit 1
+cd "$PROJECT_NAME" || exit 1
+
+cat > main.c << 'EOF'
+#include <stdio.h>
+
+int main(void) {
+    printf("Hello, World!\n");
+    return 0;
+}
+EOF
+
+cat > Makefile << 'EOF'
+CC = gcc
+CFLAGS = -std=c23 -Wall -Wextra -fsanitize=address -g
+TARGET = main
+
+all: $(TARGET)
+
+$(TARGET): main.c
+	$(CC) $(CFLAGS) -o $(TARGET) main.c
+
+clean:
+	rm -f $(TARGET)
+
+.PHONY: all clean
+EOF
+
+echo "Project '$PROJECT_NAME' created successfully!"
diff --git a/scripts/dwlsesh.sh b/scripts/dwlsesh.sh
new file mode 100644
index 0000000..0bc9490
--- /dev/null
+++ b/scripts/dwlsesh.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+slstatus -s | dwl -s "sh -c 'swaybg -i /home/tony/walls/waifus/waifu3.jpg & '"
+
+
diff --git a/scripts/nixpkgs.sh b/scripts/nixpkgs.sh
new file mode 100644
index 0000000..7accb4c
--- /dev/null
+++ b/scripts/nixpkgs.sh
@@ -0,0 +1,127 @@
+#!/usr/bin/env bash
+
+# In case the system uses a non-POSIX shell, like fish or nushell,
+# we want to ensure run also our forked processes in a bash environment.
+SHELL="bash"
+
+# === Change keybinds or add more here ===
+
+declare -a INDEXES=(
+    "nixpkgs ctrl-n"
+    "home-manager ctrl-h"
+
+    # you can add any indexes combination here,
+    # like `nixpkgs,nixos`
+
+    "all ctrl-a"
+)
+
+SEARCH_SNIPPET_KEY="ctrl-w"
+OPEN_SOURCE_KEY="ctrl-s"
+OPEN_HOMEPAGE_KEY="ctrl-o"
+NIX_SHELL_KEY="ctrl-i"
+PRINT_PREVIEW_KEY="ctrl-p"
+
+OPENER="xdg-open"
+
+if [[ "$(uname)" == 'Darwin' ]]; then
+    SEARCH_SNIPPET_KEY="alt-w"
+    OPEN_SOURCE_KEY="alt-s"
+    OPEN_HOMEPAGE_KEY="alt-o"
+    NIX_SHELL_KEY="alt-i"
+    PRINT_PREVIEW_KEY="alt-p"
+
+    OPENER="open"
+fi
+
+# ========================================
+
+# for debug / development
+CMD="${NIX_SEARCH_TV:-nix-search-tv}"
+
+# bind_index binds the given $key to the given $index
+bind_index() {
+    local key="$1"
+    local index="$2"
+
+    local prompt=""
+    local indexes_flag=""
+    if [[ -n "$index" && "$index" != "all" ]]; then
+        indexes_flag="--indexes $index"
+        prompt=$index
+    fi
+
+    local preview="$CMD preview $indexes_flag"
+    local print="$CMD print $indexes_flag"
+
+    echo "$key:change-prompt($prompt> )+change-preview($preview {})+reload($print)"
+}
+
+STATE_FILE="/tmp/nix-search-tv-fzf"
+
+# save_state saves the currently displayed index
+# to the $STATE_FILE. This file serves as an external script state
+# for communication between "print" and "preview" commands
+save_state() {
+    local index="$1"
+
+    local indexes_flag=""
+    if [[ -n "$index" && "$index" != "all" ]]; then
+        indexes_flag="--indexes $index"
+    fi
+
+    echo "execute(echo $indexes_flag > $STATE_FILE)"
+}
+
+HEADER="$OPEN_HOMEPAGE_KEY - open homepage
+$OPEN_SOURCE_KEY - open source
+$SEARCH_SNIPPET_KEY - search github for snippets
+$NIX_SHELL_KEY - nix-shell
+$PRINT_PREVIEW_KEY - print preview
+"
+
+FZF_BINDS=""
+for e in "${INDEXES[@]}"; do
+    index=$(echo "$e" | awk '{ print $1 }')
+    keybind=$(echo "$e" | awk '{ print $2 }')
+
+    fzf_bind=$(bind_index "$keybind" "$index")
+    fzf_save_state=$(save_state "$index")
+    FZF_BINDS="$FZF_BINDS --bind '$fzf_bind+$fzf_save_state'"
+
+    newline=$'\n'
+    HEADER="$HEADER$keybind - $index$newline"
+done
+
+# reset the state
+echo "" >/tmp/nix-search-tv-fzf
+
+SEARCH_SNIPPET_CMD=$'echo "{}"'
+# fzf surrounds the matched package with ', trim them
+SEARCH_SNIPPET_CMD="$SEARCH_SNIPPET_CMD | tr -d \"\'\" "
+# if it's multi-index search, then we need to remote the prefix
+SEARCH_SNIPPET_CMD="$SEARCH_SNIPPET_CMD | awk \'{ if (\$2) { print \$2 } else print \$1 }\' "
+SEARCH_SNIPPET_CMD="$SEARCH_SNIPPET_CMD | xargs printf \"https://github.com/search?type=code&q=lang:nix+%s\" \$1 "
+
+NIX_SHELL_CMD='nix-shell --run $SHELL -p $(echo "{}" | sed "s:nixpkgs/::g"'
+NIX_SHELL_CMD="$NIX_SHELL_CMD | tr -d \"\'\")"
+
+PREVIEW_WINDOW="wrap"
+[ "$(tput cols)" -lt 90 ] && PREVIEW_WINDOW="$PREVIEW_WINDOW,up"
+
+eval "$CMD print | fzf \
+    --preview '$CMD preview \$(cat $STATE_FILE) {}' \
+    --bind '$OPEN_SOURCE_KEY:execute($CMD source \$(cat $STATE_FILE) {} | xargs $OPENER)' \
+    --bind '$OPEN_HOMEPAGE_KEY:execute($CMD homepage \$(cat $STATE_FILE) {} | xargs $OPENER)' \
+    --bind $'$SEARCH_SNIPPET_KEY:execute($SEARCH_SNIPPET_CMD | xargs $OPENER)' \
+    --bind $'$NIX_SHELL_KEY:become($NIX_SHELL_CMD)' \
+    --bind $'$PRINT_PREVIEW_KEY:become($CMD preview \$(cat $STATE_FILE) {})' \
+    --layout reverse \
+    --scheme history \
+    --preview-window='$PREVIEW_WINDOW' \
+    --header '$HEADER' \
+    --header-first \
+    --header-border \
+    --header-label \"Help\" \
+    $FZF_BINDS
+"
diff --git a/scripts/snip.sh b/scripts/snip.sh
new file mode 100755
index 0000000..2b6cf06
--- /dev/null
+++ b/scripts/snip.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+grim -l 0 -g "$(slurp)" - | wl-copy
+