nixos-dotfiles
nixos-dotfiles
https://git.tonybtw.com/nixos-dotfiles.git
git://git.tonybtw.com/nixos-dotfiles.git
initial commit
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
+