]> NullRing Git Server - monorepo.git/commitdiff
fix vps, maddy, everything works main
authorPreston Pan <ret2pop@gmail.com>
Fri, 13 Feb 2026 01:37:56 +0000 (17:37 -0800)
committerPreston Pan <ret2pop@gmail.com>
Fri, 13 Feb 2026 01:37:56 +0000 (17:37 -0800)
15 files changed:
about.org
config/nix.org
nix/fake-update-dns.sh [new file with mode: 0644]
nix/modules/configuration.nix
nix/modules/fail2ban.nix [new file with mode: 0644]
nix/modules/gitweb.nix
nix/modules/home/user.nix
nix/modules/maddy.nix
nix/modules/nginx.nix
nix/modules/ntfy-sh.nix
nix/modules/secrets.nix
nix/secrets/vps_secrets.yaml
nix/systems/affinity/default.nix
nix/systems/spontaneity/default.nix
nix/update-dns.sh [new file with mode: 0644]

index 833abfd2ca1d503b97fad1ac0ba269d59761ebe6..acf4cc6e38e9a87d72f3688c1deac1167f8e889c 100644 (file)
--- a/about.org
+++ b/about.org
@@ -33,13 +33,11 @@ Here are all the methods you should use to contact me:
 Here you can find [[file:resume.pdf][my resume]] in both pdf and [[file:resume.org][html]] which contains it all. I am always open for new job opportunities!
 *** IRC
 - ret2pop on nullring.xyz, 6697. Go to #nullring; this channel is the most active, probably.
 Here you can find [[file:resume.pdf][my resume]] in both pdf and [[file:resume.org][html]] which contains it all. I am always open for new job opportunities!
 *** IRC
 - ret2pop on nullring.xyz, 6697. Go to #nullring; this channel is the most active, probably.
-- ret2pop on libera.chat
-  - Note: I will not always be online and I don't use a bouncer on this server. Email me to coordinate a time if you really need to reach me this way.
 *** Matrix
 *** Matrix
-contact me on matrix at ~ret2pop:social.nullring.xyz~.
+contact me on matrix at ~@ret2pop:matrix.nullring.xyz~.
 *** Email
 - ret2pop@gmail.com
 *** Email
 - ret2pop@gmail.com
-- preston@nullring.xyz
+- ret2pop@nullring.xyz
 *** Amateur Radio (In case the world ends)
 My callsign is ~VE7PPN~.
 *** Linkedin
 *** Amateur Radio (In case the world ends)
 My callsign is ~VE7PPN~.
 *** Linkedin
index d0643cfec776914020a971970afeb1aee13b60d9..151b39b8eb6efb73aba223c061d1231fbf90ea9b 100644 (file)
@@ -552,6 +552,10 @@ the yaml file specified. Yes, this is safe to include in the repo.
         livekit = {
           format = "yaml";
         };
         livekit = {
           format = "yaml";
         };
+        mail_password = {
+          format = "yaml";
+          owner = "maddy";
+        };
         conduit_secrets = {
           format = "yaml";
         };
         conduit_secrets = {
           format = "yaml";
         };
@@ -564,6 +568,10 @@ the yaml file specified. Yes, this is safe to include in the repo.
         discord_token = {
           format = "yaml";
         };
         discord_token = {
           format = "yaml";
         };
+        ntfy = {
+          format = "yaml";
+          owner = "ntfy-sh";
+        };
       };
     };
   }
       };
     };
   }
@@ -871,7 +879,7 @@ is almost no point to cracking it with hashcat.
 ** Conduit
 This is a modern matrix server that is meant to be lightweight while
 still federating and hosting the same protocol. There is also a configuration
 ** Conduit
 This is a modern matrix server that is meant to be lightweight while
 still federating and hosting the same protocol. There is also a configuration
-for lk-jwt which is important for configuring p2p calls in matrix.
+for lk-jwt and livekit which is important for configuring p2p calls in matrix.
 #+begin_src nix :tangle ../nix/modules/conduit.nix
   { config, lib, ... }:
   {
 #+begin_src nix :tangle ../nix/modules/conduit.nix
   { config, lib, ... }:
   {
@@ -1147,18 +1155,54 @@ I run my own git server in order to have a mirror in case github goes down.
     };
   }
 #+end_src
     };
   }
 #+end_src
-** TODO Ntfy
+** Ntfy
 #+begin_src nix :tangle ../nix/modules/ntfy-sh.nix
 #+begin_src nix :tangle ../nix/modules/ntfy-sh.nix
-  { lib, config, ... }:
+  { pkgs, lib, config, ... }:
   {
     services.ntfy-sh = {
   {
     services.ntfy-sh = {
-  #    enable = lib.mkDefault config.monorepo.profiles.server.enable;
-      enable = false;
+      enable = lib.mkDefault config.monorepo.profiles.server.enable;
       settings = {
         base-url = "https://ntfy.${config.monorepo.vars.remoteHost}";
         listen-http = "127.0.0.1:2586";
         envrionmentFile = "/run/secrets/ntfy";
       settings = {
         base-url = "https://ntfy.${config.monorepo.vars.remoteHost}";
         listen-http = "127.0.0.1:2586";
         envrionmentFile = "/run/secrets/ntfy";
+        auth-file = "/var/lib/ntfy-sh/user.db";
+        auth-default-access = "deny-all";
+        enable-login = true;
+      };
+    };
+    systemd.services.ntfy-sh = {
+      serviceConfig = {
+        EnvironmentFile = "/run/secrets/ntfy";
       };
       };
+      postStart = lib.mkForce ''
+        # 1. Wait for the server to initialize the database
+        echo "Waiting for ntfy auth database to appear..."
+        TIMEOUT=30
+        while [ ! -f /var/lib/ntfy-sh/user.db ]; do
+          sleep 1
+          TIMEOUT=$((TIMEOUT-1))
+          if [ $TIMEOUT -le 0 ]; then
+            echo "Timed out waiting for database creation!"
+            exit 1
+          fi
+        done
+        
+        echo "Database found. Configuring admin user..."
+
+        # 2. Define the username
+        ADMIN_USER="ret2pop"
+
+        # 3. Check if user exists, create if missing
+        # We pipe the password twice because 'ntfy user add' asks for confirmation
+        if ! ${pkgs.ntfy-sh}/bin/ntfy user list | grep -q "$ADMIN_USER"; then
+          echo "Creating admin user $ADMIN_USER..."
+          printf "$ADMIN_PASSWORD\n$ADMIN_PASSWORD" | \
+            ${pkgs.ntfy-sh}/bin/ntfy user add --role=admin "$ADMIN_USER"
+          echo "User created."
+        else
+          echo "Admin user already exists."
+        fi
+      '';
     };
   }
 #+end_src
     };
   }
 #+end_src
@@ -1312,6 +1356,16 @@ to the outside world under a domain.
           };
         };
 
           };
         };
 
+        "ntfy.${config.monorepo.vars.remoteHost}" = {
+          serverName = "ntfy.${config.monorepo.vars.remoteHost}";
+          enableACME = true;
+          forceSSL = true;
+          locations."/" = {
+            proxyPass = "http://localhost:2586";
+            proxyWebsockets = true;
+          };
+        };
+
            "${config.monorepo.vars.remoteHost}" = {
           serverName = "${config.monorepo.vars.remoteHost}";
           serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ];
            "${config.monorepo.vars.remoteHost}" = {
           serverName = "${config.monorepo.vars.remoteHost}";
           serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ];
@@ -1348,6 +1402,8 @@ world. This was the easiest frontend to set up on NixOS.
       projectroot = "/srv/git/";
       extraConfig = ''
   our $export_ok = "git-daemon-export-ok";
       projectroot = "/srv/git/";
       extraConfig = ''
   our $export_ok = "git-daemon-export-ok";
+  our $site_name = "NullRing Git Server";
+  our $site_header = "NullRing Projects";
   '';
     };
   }
   '';
     };
   }
@@ -1391,16 +1447,22 @@ I need CUDA on some computers because I run local LLMs.
     ] else []);
   }
 #+end_src
     ] else []);
   }
 #+end_src
-** TODO Maddy
+** Maddy
+There is a non declarative part of setting dkims and spf.
 #+begin_src nix :tangle ../nix/modules/maddy.nix
   { lib, config, options, ... }:
   {
     services.maddy = {
       enable = lib.mkDefault config.monorepo.profiles.server.enable;
       openFirewall = true;
 #+begin_src nix :tangle ../nix/modules/maddy.nix
   { lib, config, options, ... }:
   {
     services.maddy = {
       enable = lib.mkDefault config.monorepo.profiles.server.enable;
       openFirewall = true;
-      hostName = "${config.monorepo.vars.remoteHost}";
+      hostname = "${config.monorepo.vars.orgHost}";
       primaryDomain = "mail.${config.monorepo.vars.orgHost}";
       primaryDomain = "mail.${config.monorepo.vars.orgHost}";
+      localDomains = [
+        "$(primary_domain)"
+        "${config.monorepo.vars.orgHost}"
+      ];
       tls = {
       tls = {
+        loader = "file";
         certificates = [
           {
             keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem";
         certificates = [
           {
             keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem";
@@ -1411,20 +1473,33 @@ I need CUDA on some computers because I run local LLMs.
       config = builtins.replaceStrings [
         "imap tcp://0.0.0.0:143"
         "submission tcp://0.0.0.0:587"
       config = builtins.replaceStrings [
         "imap tcp://0.0.0.0:143"
         "submission tcp://0.0.0.0:587"
-        "smtp tcp://0.0.0.0:25"
       ] [
         "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
         "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
       ] [
         "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
         "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
-        "smtps tls://0.0.0.0:465 smtp tcp://0.0.0.0:25"
       ] options.services.maddy.config.default;
       ensureCredentials = {
       ] options.services.maddy.config.default;
       ensureCredentials = {
-        "${config.monorepo.vars.userName}@localhost" = {
+        "${config.monorepo.vars.internetName}@${config.monorepo.vars.orgHost}" = {
           passwordFile = "/run/secrets/mail_password";
         };
       };
     };
   }
 #+end_src
           passwordFile = "/run/secrets/mail_password";
         };
       };
     };
   }
 #+end_src
+** Fail2Ban
+This is a service that bans bots that try to sign in on my server.
+#+begin_src nix :tangle ../nix/modules/fail2ban.nix
+  { lib, config, ... }:
+  {
+    services.fail2ban = {
+      enable = lib.mkDefault config.monorepo.profiles.server.enable;
+      # Ban IP after 5 failures for 1 hour
+      maxretry = 5;
+      bantime = "1h";
+      banaction = "iptables-allports";
+      banaction-allports = "iptables-allports";
+    };
+  }
+#+end_src
 ** Impermanence
 This is my impermanence profile, which removes all files on reboot except for the ones listed below.
 #+begin_src nix :tangle ../nix/modules/impermanence.nix
 ** Impermanence
 This is my impermanence profile, which removes all files on reboot except for the ones listed below.
 #+begin_src nix :tangle ../nix/modules/impermanence.nix
@@ -1559,6 +1634,9 @@ because they enhance security.
       ./docker.nix
       ./impermanence.nix
       ./coturn.nix
       ./docker.nix
       ./impermanence.nix
       ./coturn.nix
+      ./maddy.nix
+      ./ntfy-sh.nix
+      ./fail2ban.nix
     ];
 
     environment.etc."wpa_supplicant.conf".text = ''
     ];
 
     environment.etc."wpa_supplicant.conf".text = ''
@@ -1741,38 +1819,38 @@ because they enhance security.
           powersave = false;
         };
         ensureProfiles = {
           powersave = false;
         };
         ensureProfiles = {
-          profiles = {
-            home-wifi = {
-              connection = {
-                id = "TELUS6572";
-                permissions = "";
-                type = "wifi";
-              };
-              ipv4 = {
-                dns-search = "";
-                method = "auto";
-              };
-              ipv6 = {
-                addr-gen-mode = "stable-privacy";
-                dns-search = "";
-                method = "auto";
-              };
-              wifi = {
-                mac-address-blacklist = "";
-                mode = "infrastructure";
-                ssid = "TELUS6572";
-              };
-              wifi-security = {
-                auth-alg = "open";
-                key-mgmt = "wpa-psk";
-                # when someone actually steals my internet then I will be concerned.
-                # This password only matters if you actually show up to my house in real life.
-                # That would perhaps allow for some nasty networking related shenanigans.
-                # I guess we'll cross that bridge when I get there.
-                psk = "b4xnrv6cG6GX";
-              };
-            };
-          };
+          profiles = {
+            home-wifi = {
+              connection = {
+                id = "TELUS6572";
+                permissions = "";
+                type = "wifi";
+              };
+              ipv4 = {
+                dns-search = "";
+                method = "auto";
+              };
+              ipv6 = {
+                addr-gen-mode = "stable-privacy";
+                dns-search = "";
+                method = "auto";
+              };
+              wifi = {
+                mac-address-blacklist = "";
+                mode = "infrastructure";
+                ssid = "TELUS6572";
+              };
+              wifi-security = {
+                auth-alg = "open";
+                key-mgmt = "wpa-psk";
+                # when someone actually steals my internet then I will be concerned.
+                # This password only matters if you actually show up to my house in real life.
+                # That would perhaps allow for some nasty networking related shenanigans.
+                # I guess we'll cross that bridge when I get there.
+                psk = "b4xnrv6cG6GX";
+              };
+            };
+          };
         };
       };
       firewall = {
         };
       };
       firewall = {
@@ -1905,6 +1983,7 @@ because they enhance security.
       vim
       curl
       nmap
       vim
       curl
       nmap
+      exiftool
       (writeShellScriptBin "new-repo"
         ''
     #!/bin/bash
       (writeShellScriptBin "new-repo"
         ''
     #!/bin/bash
@@ -1923,12 +2002,31 @@ because they enhance security.
     users.groups.conduit = lib.mkDefault {};
     users.groups.livekit = lib.mkDefault {};
     users.groups.matterbridge = lib.mkDefault {};
     users.groups.conduit = lib.mkDefault {};
     users.groups.livekit = lib.mkDefault {};
     users.groups.matterbridge = lib.mkDefault {};
+    users.groups.maddy = lib.mkDefault {};
+    users.groups.ntfy-sh = lib.mkDefault {};
 
     users.users = {
 
     users.users = {
+      conduit = {
+        isSystemUser = lib.mkDefault true;
+        group = "conduit";
+      };
       matterbridge = {
         isSystemUser = lib.mkDefault true;
         group = "matterbridge";
       };
       matterbridge = {
         isSystemUser = lib.mkDefault true;
         group = "matterbridge";
       };
+
+      maddy = {
+        isSystemUser = lib.mkDefault true;
+        group = "maddy";
+        extraGroups = [ "acme" "nginx" ];
+      };
+
+      ntfy-sh = {
+        isSystemUser = lib.mkDefault true;
+        group = "ntfy-sh";
+        extraGroups = [ "acme" "nginx" ];
+      };
+
       ngircd = {
         isSystemUser = lib.mkDefault true;
         group = "ngircd";
       ngircd = {
         isSystemUser = lib.mkDefault true;
         group = "ngircd";
@@ -3868,28 +3966,6 @@ standard.
     };
   }
 #+end_src
     };
   }
 #+end_src
-*** Pantalaimon
-This is used with ement as a proxy in order to connect to a remote
-matrix server while having encryption.
-#+begin_src nix :tangle ../nix/modules/home/pantalaimon.nix
-  { lib, config, ... }:
-  {
-    services.pantalaimon = {
-      enable = lib.mkDefault config.monorepo.profiles.graphics.enable;
-      settings = {
-        Default = {
-          LogLevel = "Debug";
-          SSL = true;
-        };
-        local-matrix = {
-          Homeserver = "https://matrix.${config.monorepo.vars.orgHost}";
-          ListenAddress = "127.0.0.1";
-          ListenPort = "8008";
-        };
-      };
-    };
-  }
-#+end_src
 *** User
 This configuration is the backbone configuration for the default user. It specifies some
 generally useful packages and something every home should have, as well as some dependencies
 *** User
 This configuration is the backbone configuration for the default user. It specifies some
 generally useful packages and something every home should have, as well as some dependencies
@@ -3942,7 +4018,7 @@ for these configurations.
 
         # Apps
         # octaveFull
 
         # Apps
         # octaveFull
-        vesktop grim swww vim kotatogram-desktop tg qwen-code element-desktop jami
+        vesktop grim swww vim kotatogram-desktop tg qwen-code element-desktop thunderbird jami
 
         # Sound/media
         pavucontrol alsa-utils imagemagick ffmpeg helvum
 
         # Sound/media
         pavucontrol alsa-utils imagemagick ffmpeg helvum
@@ -4140,7 +4216,7 @@ as several other useful services.
       zramSwap = {
         enable = true;
         algorithm = "zstd";
       zramSwap = {
         enable = true;
         algorithm = "zstd";
-        memoryPercent = 50; # Creates ~16GB of compressed swap space
+        memoryPercent = 50;
       };
       monorepo = {
         vars.device = "/dev/nvme0n1";
       };
       monorepo = {
         vars.device = "/dev/nvme0n1";
@@ -4167,7 +4243,8 @@ I want cuda in home manager too.
   }
 #+end_src
 ** Spontaneity
   }
 #+end_src
 ** Spontaneity
-Spontaneity is my VPS instance.
+Spontaneity is my VPS instance. Note that much of this is not fully reproducible; you must change the IPs yourself and you must change
+some DNS records to match what you have on your system after deployment.
 #+begin_src nix :tangle ../nix/systems/spontaneity/default.nix
   { config, lib, ... }:
     let
 #+begin_src nix :tangle ../nix/systems/spontaneity/default.nix
   { config, lib, ... }:
     let
@@ -4196,7 +4273,28 @@ Spontaneity is my VPS instance.
         };
 
         boot.loader.grub.device = "nodev";
         };
 
         boot.loader.grub.device = "nodev";
+        boot.kernel.sysctl = {
+          "net.ipv6.conf.ens3.autoconf" = 0;
+          # Keep accept_ra = 1 so you still get the default gateway/route!
+          "net.ipv6.conf.ens3.accept_ra" = 1; 
+        };
+
+        systemd.network.enable = true;
+        systemd.network.networks."40-ens3" = {
+          matchConfig.Name = "ens3";
+          networkConfig = {
+            # This is the magic combo for Vultr:
+            IPv6AcceptRA = true;         # Accept routes (so we know where the internet is)
+            IPv6PrivacyExtensions = false; # No random privacy IPs
+          };
+          ipv6AcceptRAConfig = {
+            UseAutonomousPrefix = false; # Do NOT generate an IP address from the RA
+          };
+        };
         networking = {
         networking = {
+          useDHCP = lib.mkForce false;
+          networkmanager.enable = lib.mkForce false;
+          tempAddresses = "disabled";
           extraHosts = ''
       127.0.0.1 livekit.${config.monorepo.vars.orgHost}
       127.0.0.1 matrix.${config.monorepo.vars.orgHost}
           extraHosts = ''
       127.0.0.1 livekit.${config.monorepo.vars.orgHost}
       127.0.0.1 matrix.${config.monorepo.vars.orgHost}
@@ -4207,6 +4305,7 @@ Spontaneity is my VPS instance.
               prefixLength = 24;
             }
           ];
               prefixLength = 24;
             }
           ];
+          interfaces.ens3.useDHCP = lib.mkForce false;
           interfaces.ens3.ipv6.addresses = [
             {
               address = ipv6addr;
           interfaces.ens3.ipv6.addresses = [
             {
               address = ipv6addr;
@@ -4247,6 +4346,16 @@ Spontaneity is my VPS instance.
               "${config.monorepo.vars.orgHost}" = {
                 a.data = ipv4addr;
                 aaaa.data = ipv6addr;
               "${config.monorepo.vars.orgHost}" = {
                 a.data = ipv4addr;
                 aaaa.data = ipv6addr;
+
+                mx.data = [
+                  {
+                    preference = 10;
+                    exchange = "mail.${config.monorepo.vars.orgHost}";
+                  }
+                ];
+                txt = {
+                  data = "v=spf1 ip4:${ipv4addr} ip6:${ipv6addr} -all";
+                };
               };
             };
             subDomains = {
               };
             };
             subDomains = {
@@ -4254,9 +4363,24 @@ Spontaneity is my VPS instance.
               "notes.${config.monorepo.vars.remoteHost}" = {
                 a.data = "45.76.87.125";
               };
               "notes.${config.monorepo.vars.remoteHost}" = {
                 a.data = "45.76.87.125";
               };
+
+              "_dmarc.${config.monorepo.vars.orgHost}" = {
+                txt = {
+                  data = "v=DMARC1; p=none";
+                };
+              };
+
+              "default._domainkey.${config.monorepo.vars.orgHost}" = {
+                txt = {
+                  data = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsC9GpfjvQlldPrHAC7Yt+ZF0aduUIVV4j2+KUkF0j6NsrpOgvU6COWKQSod/B/qyPBLWf+w5P5YiJ9XnOgw6Db/I9C67eusEHnV/cbvokXLQjSBvXee1OEdrT9i+6iUgDeGWP4CrD1DcwvXzAcCI9exy3yALHVlbkyYvi0KAYofs8dVQ3JCwSCMlol71lA6ULJ2zbCIWeSOv9/C6QZ5HOIeeoFLesX6O/YvF4FYxWbSHy244TXYuczQKuayjKgD6e8gIT5WJRQj8IAWOQ2podWw6hSuB3Ig+ekoOfnl5ivJGOMbAzFTj8FtbS4ncyidLU1kIOeuLfiILeDDLlIeYTwIDAQAB";
+                };
+              };
+
+              "ntfy.${config.monorepo.vars.remoteHost}" = {};
               "matrix.${config.monorepo.vars.remoteHost}" = {};
               "www.${config.monorepo.vars.remoteHost}" = {};
               "matrix.${config.monorepo.vars.remoteHost}" = {};
               "www.${config.monorepo.vars.remoteHost}" = {};
-              "mail.${config.monorepo.vars.remoteHost}" = {};
+              "mail.${config.monorepo.vars.remoteHost}" = {
+              };
 
               "livekit.${config.monorepo.vars.orgHost}" = {};
               "${config.monorepo.vars.orgHost}" = {};
 
               "livekit.${config.monorepo.vars.orgHost}" = {};
               "${config.monorepo.vars.orgHost}" = {};
diff --git a/nix/fake-update-dns.sh b/nix/fake-update-dns.sh
new file mode 100644 (file)
index 0000000..a236cb2
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+export CLOUDFLARE_TOKEN="$(cat /run/user/1000/secrets/cloudflare-dns | tr -d '\n')"
+poetry run octodns-sync --config-file result
index 35944e0756d9eb6375221767911f960ba69a3ce3..0845ceabfa2b59a00ca3eb19c9724c0cf8e5c7c3 100644 (file)
@@ -24,6 +24,9 @@
     ./docker.nix
     ./impermanence.nix
     ./coturn.nix
     ./docker.nix
     ./impermanence.nix
     ./coturn.nix
+    ./maddy.nix
+    ./ntfy-sh.nix
+    ./fail2ban.nix
   ];
 
   environment.etc."wpa_supplicant.conf".text = ''
   ];
 
   environment.etc."wpa_supplicant.conf".text = ''
@@ -206,38 +209,38 @@ country=CA
         powersave = false;
       };
       ensureProfiles = {
         powersave = false;
       };
       ensureProfiles = {
-        profiles = {
-          home-wifi = {
-            connection = {
-              id = "TELUS6572";
-              permissions = "";
-              type = "wifi";
-            };
-            ipv4 = {
-              dns-search = "";
-              method = "auto";
-            };
-            ipv6 = {
-              addr-gen-mode = "stable-privacy";
-              dns-search = "";
-              method = "auto";
-            };
-            wifi = {
-              mac-address-blacklist = "";
-              mode = "infrastructure";
-              ssid = "TELUS6572";
-            };
-            wifi-security = {
-              auth-alg = "open";
-              key-mgmt = "wpa-psk";
-              # when someone actually steals my internet then I will be concerned.
-              # This password only matters if you actually show up to my house in real life.
-              # That would perhaps allow for some nasty networking related shenanigans.
-              # I guess we'll cross that bridge when I get there.
-              psk = "b4xnrv6cG6GX";
-            };
-          };
-        };
+        profiles = {
+          home-wifi = {
+            connection = {
+              id = "TELUS6572";
+              permissions = "";
+              type = "wifi";
+            };
+            ipv4 = {
+              dns-search = "";
+              method = "auto";
+            };
+            ipv6 = {
+              addr-gen-mode = "stable-privacy";
+              dns-search = "";
+              method = "auto";
+            };
+            wifi = {
+              mac-address-blacklist = "";
+              mode = "infrastructure";
+              ssid = "TELUS6572";
+            };
+            wifi-security = {
+              auth-alg = "open";
+              key-mgmt = "wpa-psk";
+              # when someone actually steals my internet then I will be concerned.
+              # This password only matters if you actually show up to my house in real life.
+              # That would perhaps allow for some nasty networking related shenanigans.
+              # I guess we'll cross that bridge when I get there.
+              psk = "b4xnrv6cG6GX";
+            };
+          };
+        };
       };
     };
     firewall = {
       };
     };
     firewall = {
@@ -370,6 +373,7 @@ country=CA
     vim
     curl
     nmap
     vim
     curl
     nmap
+    exiftool
     (writeShellScriptBin "new-repo"
       ''
   #!/bin/bash
     (writeShellScriptBin "new-repo"
       ''
   #!/bin/bash
@@ -388,12 +392,31 @@ country=CA
   users.groups.conduit = lib.mkDefault {};
   users.groups.livekit = lib.mkDefault {};
   users.groups.matterbridge = lib.mkDefault {};
   users.groups.conduit = lib.mkDefault {};
   users.groups.livekit = lib.mkDefault {};
   users.groups.matterbridge = lib.mkDefault {};
+  users.groups.maddy = lib.mkDefault {};
+  users.groups.ntfy-sh = lib.mkDefault {};
 
   users.users = {
 
   users.users = {
+    conduit = {
+      isSystemUser = lib.mkDefault true;
+      group = "conduit";
+    };
     matterbridge = {
       isSystemUser = lib.mkDefault true;
       group = "matterbridge";
     };
     matterbridge = {
       isSystemUser = lib.mkDefault true;
       group = "matterbridge";
     };
+
+    maddy = {
+      isSystemUser = lib.mkDefault true;
+      group = "maddy";
+      extraGroups = [ "acme" "nginx" ];
+    };
+
+    ntfy-sh = {
+      isSystemUser = lib.mkDefault true;
+      group = "ntfy-sh";
+      extraGroups = [ "acme" "nginx" ];
+    };
+
     ngircd = {
       isSystemUser = lib.mkDefault true;
       group = "ngircd";
     ngircd = {
       isSystemUser = lib.mkDefault true;
       group = "ngircd";
diff --git a/nix/modules/fail2ban.nix b/nix/modules/fail2ban.nix
new file mode 100644 (file)
index 0000000..5d289a0
--- /dev/null
@@ -0,0 +1,11 @@
+{ lib, config, ... }:
+{
+  services.fail2ban = {
+    enable = lib.mkDefault config.monorepo.profiles.server.enable;
+    # Ban IP after 5 failures for 1 hour
+    maxretry = 5;
+    bantime = "1h";
+    banaction = "iptables-allports";
+    banaction-allports = "iptables-allports";
+  };
+}
index c98e4c6edd0547691159d42d98f976c052d0fe52..69cb951e854225a515ed4f28202c05af974c63c3 100644 (file)
@@ -5,6 +5,8 @@
     projectroot = "/srv/git/";
     extraConfig = ''
 our $export_ok = "git-daemon-export-ok";
     projectroot = "/srv/git/";
     extraConfig = ''
 our $export_ok = "git-daemon-export-ok";
+our $site_name = "NullRing Git Server";
+our $site_header = "NullRing Projects";
 '';
   };
 }
 '';
   };
 }
index 356a0fe376799e4d745f1bc990bc32b755af3177..55e16fc2db98fd7f2bd3d4548768e0eaba61bc73 100644 (file)
@@ -45,7 +45,7 @@
 
       # Apps
       # octaveFull
 
       # Apps
       # octaveFull
-      vesktop grim swww vim kotatogram-desktop tg qwen-code element-desktop jami
+      vesktop grim swww vim kotatogram-desktop tg qwen-code element-desktop thunderbird jami
 
       # Sound/media
       pavucontrol alsa-utils imagemagick ffmpeg helvum
 
       # Sound/media
       pavucontrol alsa-utils imagemagick ffmpeg helvum
index ab98f87eddb68112a342ce4374e7f78a6cf781fb..42f24f95563a13d3a8f9f660bd4d282a8b44671e 100644 (file)
@@ -3,9 +3,14 @@
   services.maddy = {
     enable = lib.mkDefault config.monorepo.profiles.server.enable;
     openFirewall = true;
   services.maddy = {
     enable = lib.mkDefault config.monorepo.profiles.server.enable;
     openFirewall = true;
-    hostName = "${config.monorepo.vars.remoteHost}";
+    hostname = "${config.monorepo.vars.orgHost}";
     primaryDomain = "mail.${config.monorepo.vars.orgHost}";
     primaryDomain = "mail.${config.monorepo.vars.orgHost}";
+    localDomains = [
+      "$(primary_domain)"
+      "${config.monorepo.vars.orgHost}"
+    ];
     tls = {
     tls = {
+      loader = "file";
       certificates = [
         {
           keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem";
       certificates = [
         {
           keyPath = "/var/lib/acme/mail.${config.monorepo.vars.orgHost}/key.pem";
     config = builtins.replaceStrings [
       "imap tcp://0.0.0.0:143"
       "submission tcp://0.0.0.0:587"
     config = builtins.replaceStrings [
       "imap tcp://0.0.0.0:143"
       "submission tcp://0.0.0.0:587"
-      "smtp tcp://0.0.0.0:25"
     ] [
       "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
       "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
     ] [
       "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
       "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
-      "smtps tls://0.0.0.0:465 smtp tcp://0.0.0.0:25"
     ] options.services.maddy.config.default;
     ensureCredentials = {
     ] options.services.maddy.config.default;
     ensureCredentials = {
-      "${config.monorepo.vars.userName}@localhost" = {
+      "${config.monorepo.vars.internetName}@${config.monorepo.vars.orgHost}" = {
         passwordFile = "/run/secrets/mail_password";
       };
     };
         passwordFile = "/run/secrets/mail_password";
       };
     };
index a3c079bc053ef1089be915fd67357b132a69934a..87f11c1a097b8af35ca52d48f0a3834d27680954 100644 (file)
         };
       };
 
         };
       };
 
+      "ntfy.${config.monorepo.vars.remoteHost}" = {
+        serverName = "ntfy.${config.monorepo.vars.remoteHost}";
+        enableACME = true;
+        forceSSL = true;
+        locations."/" = {
+          proxyPass = "http://localhost:2586";
+          proxyWebsockets = true;
+        };
+      };
+
            "${config.monorepo.vars.remoteHost}" = {
         serverName = "${config.monorepo.vars.remoteHost}";
         serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ];
            "${config.monorepo.vars.remoteHost}" = {
         serverName = "${config.monorepo.vars.remoteHost}";
         serverAliases = [ "${config.monorepo.vars.internetName}.${config.monorepo.vars.orgHost}" ];
index 9311af2b678e072e2400597db838785a55ab92d8..0eeac784b8bf24833d9a9b13eeb562ea12b5251a 100644 (file)
@@ -1,12 +1,48 @@
-{ lib, config, ... }:
+{ pkgs, lib, config, ... }:
 {
   services.ntfy-sh = {
 {
   services.ntfy-sh = {
-#    enable = lib.mkDefault config.monorepo.profiles.server.enable;
-    enable = false;
+    enable = lib.mkDefault config.monorepo.profiles.server.enable;
     settings = {
       base-url = "https://ntfy.${config.monorepo.vars.remoteHost}";
       listen-http = "127.0.0.1:2586";
       envrionmentFile = "/run/secrets/ntfy";
     settings = {
       base-url = "https://ntfy.${config.monorepo.vars.remoteHost}";
       listen-http = "127.0.0.1:2586";
       envrionmentFile = "/run/secrets/ntfy";
+      auth-file = "/var/lib/ntfy-sh/user.db";
+      auth-default-access = "deny-all";
+      enable-login = true;
     };
   };
     };
   };
+  systemd.services.ntfy-sh = {
+    serviceConfig = {
+      EnvironmentFile = "/run/secrets/ntfy";
+    };
+    postStart = lib.mkForce ''
+      # 1. Wait for the server to initialize the database
+      echo "Waiting for ntfy auth database to appear..."
+      TIMEOUT=30
+      while [ ! -f /var/lib/ntfy-sh/user.db ]; do
+        sleep 1
+        TIMEOUT=$((TIMEOUT-1))
+        if [ $TIMEOUT -le 0 ]; then
+          echo "Timed out waiting for database creation!"
+          exit 1
+        fi
+      done
+      
+      echo "Database found. Configuring admin user..."
+
+      # 2. Define the username
+      ADMIN_USER="ret2pop"
+
+      # 3. Check if user exists, create if missing
+      # We pipe the password twice because 'ntfy user add' asks for confirmation
+      if ! ${pkgs.ntfy-sh}/bin/ntfy user list | grep -q "$ADMIN_USER"; then
+        echo "Creating admin user $ADMIN_USER..."
+        printf "$ADMIN_PASSWORD\n$ADMIN_PASSWORD" | \
+          ${pkgs.ntfy-sh}/bin/ntfy user add --role=admin "$ADMIN_USER"
+        echo "User created."
+      else
+        echo "Admin user already exists."
+      fi
+    '';
+  };
 }
 }
index d1c711c0074001eacf32614815c4937ec92009d1..f7deb5d8941c09596383d56c1f91aba0604abbc0 100644 (file)
@@ -87,6 +87,10 @@ channel="-5290629325"
       livekit = {
         format = "yaml";
       };
       livekit = {
         format = "yaml";
       };
+      mail_password = {
+        format = "yaml";
+        owner = "maddy";
+      };
       conduit_secrets = {
         format = "yaml";
       };
       conduit_secrets = {
         format = "yaml";
       };
@@ -99,6 +103,10 @@ channel="-5290629325"
       discord_token = {
         format = "yaml";
       };
       discord_token = {
         format = "yaml";
       };
+      ntfy = {
+        format = "yaml";
+        owner = "ntfy-sh";
+      };
     };
   };
 }
     };
   };
 }
index d60ced34f39fe0ec80601da8856f109b7334c252..87c513b3870a620a22e0191062c89aa3913bed85 100644 (file)
@@ -9,6 +9,7 @@ znc_password_salt: ENC[AES256_GCM,data:e7YZkNB32RiqgCPGoehwsfZzOHM=,iv:GrhwBRBZ1
 telegram_token: ENC[AES256_GCM,data:hfstqM3NphVnK86LYp8EYe09kflMzQ1/SO5rm5UIkWN7wdl7mbq+sw3svc4YhQ==,iv:o6TbrGBCly0s3US9041cKmpLpThB/umhBEdZE9E3v54=,tag:WJ/KS4Uc9wtIcjpyfmzLfA==,type:str]
 discord_token: ENC[AES256_GCM,data:1mJ0lKTz2SmaP3PIn3ThWX6Mjbv3tywtLtF65SVkkCEtI79wcPeqK83l6jb3yG+ugntNR7lfQxLgbbURnTil3jc7yVOsYreL,iv:ExZ8xFkH6RR7rHATh8oBEEZWfV5Rt1YVEx8gUicQrV0=,tag:wKJ3P8ie/ppHU9VStQlk0Q==,type:str]
 mail_password: ENC[AES256_GCM,data:W24/1l9YrV+M1enkAgRv2uZuhUIYAjpcRkX7tbc=,iv:F8oLCpthhecllJvGSmHUaFgmBKDg/g3o85CPJ/nCcxU=,tag:bPxcZNXdQ/jkK+saaIKbSw==,type:str]
 telegram_token: ENC[AES256_GCM,data:hfstqM3NphVnK86LYp8EYe09kflMzQ1/SO5rm5UIkWN7wdl7mbq+sw3svc4YhQ==,iv:o6TbrGBCly0s3US9041cKmpLpThB/umhBEdZE9E3v54=,tag:WJ/KS4Uc9wtIcjpyfmzLfA==,type:str]
 discord_token: ENC[AES256_GCM,data:1mJ0lKTz2SmaP3PIn3ThWX6Mjbv3tywtLtF65SVkkCEtI79wcPeqK83l6jb3yG+ugntNR7lfQxLgbbURnTil3jc7yVOsYreL,iv:ExZ8xFkH6RR7rHATh8oBEEZWfV5Rt1YVEx8gUicQrV0=,tag:wKJ3P8ie/ppHU9VStQlk0Q==,type:str]
 mail_password: ENC[AES256_GCM,data:W24/1l9YrV+M1enkAgRv2uZuhUIYAjpcRkX7tbc=,iv:F8oLCpthhecllJvGSmHUaFgmBKDg/g3o85CPJ/nCcxU=,tag:bPxcZNXdQ/jkK+saaIKbSw==,type:str]
+ntfy: ENC[AES256_GCM,data:wKCZ/7GXRWPoVRoXDBD0E0sR6ZRQjSE8USwqHQFOT/QiqIx+aI0awcRuORyGbCE=,iv:aBO9I/528sX6ncnBHMBDVB6mLbc45A7xXiu9p7Kh0Ao=,tag:Xgp3UURxPYcO5DlN53sBVw==,type:str]
 sops:
     age:
         - recipient: age1acpuyy2qnduyxzwvusd8urr6a78e3f37ylhvh2pngyqytf5r8ans5vkest
 sops:
     age:
         - recipient: age1acpuyy2qnduyxzwvusd8urr6a78e3f37ylhvh2pngyqytf5r8ans5vkest
@@ -20,7 +21,7 @@ sops:
             dDZONnI0bG5heTYzaDkxeGo3VlFmdm8K377mvFFxtFSURAWeFvLDJTkm8wppKr/B
             Y4qrdU3xBaTwqlsC/7lElQClaUbM+YMF/padENsD6IfyoGN8lGUQQw==
             -----END AGE ENCRYPTED FILE-----
             dDZONnI0bG5heTYzaDkxeGo3VlFmdm8K377mvFFxtFSURAWeFvLDJTkm8wppKr/B
             Y4qrdU3xBaTwqlsC/7lElQClaUbM+YMF/padENsD6IfyoGN8lGUQQw==
             -----END AGE ENCRYPTED FILE-----
-    lastmodified: "2026-02-11T01:17:40Z"
-    mac: ENC[AES256_GCM,data:9z5nlQA2Wjw7kgk+i2BMFIePGRdNbagYZ6fQpdyQQTEERl/TK7E8hozIIo48lmhdqXkjK8Vsgon/lnl2QbLfh8sTlpYGfewUaAzERrxW0JPEeY+JqcTaWO/16SNDd5dcd1aYWZILPcjPnz2/wwI3TMWpQG85lEDSXyLMommNdDc=,iv:uzLQtiZ7AJM/eS8/pLvty9YvErCMpx8xhk/d6jxKouQ=,tag:6ebxZG7BlF4ZxnJpp4QBVg==,type:str]
+    lastmodified: "2026-02-12T08:21:04Z"
+    mac: ENC[AES256_GCM,data:537FYu1bJ+0KgbdtKt7kw3Wx4JT2Nb71L36cMZLEFUpwE+pvpe3+iYV/J28TzYVHwByoQ1Q4LYRc/EQqSlT0oaV7yOpfLNS+cb7devATEHhGVrDUThmnF8YvyQFiQsyq/PFNqQ3RLEmSeAsJBWrFB5r8uqRJZKAW/URfFjYqdj0=,iv:tXRflj6rvxewU0QhbsOUSmAHkfWExDZMA6Z8Z65/y0w=,tag:h+y17+ilWPQ4+I/WZIbWAQ==,type:str]
     unencrypted_suffix: _unencrypted
     version: 3.11.0
     unencrypted_suffix: _unencrypted
     version: 3.11.0
index 9918089ea1eaab0cb6a911559e578448033ef10f..755d6d8ed6b33223393d511ededb2a22b21caac2 100644 (file)
@@ -8,7 +8,7 @@
     zramSwap = {
       enable = true;
       algorithm = "zstd";
     zramSwap = {
       enable = true;
       algorithm = "zstd";
-      memoryPercent = 50; # Creates ~16GB of compressed swap space
+      memoryPercent = 50;
     };
     monorepo = {
       vars.device = "/dev/nvme0n1";
     };
     monorepo = {
       vars.device = "/dev/nvme0n1";
index 7d637bf83a741897df5e1f01b288877d89e0a9d3..7bdcf74453bcdaac74c0df517056b1e1ad038146 100644 (file)
       };
 
       boot.loader.grub.device = "nodev";
       };
 
       boot.loader.grub.device = "nodev";
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.ens3.autoconf" = 0;
+        # Keep accept_ra = 1 so you still get the default gateway/route!
+        "net.ipv6.conf.ens3.accept_ra" = 1; 
+      };
+
+      systemd.network.enable = true;
+      systemd.network.networks."40-ens3" = {
+        matchConfig.Name = "ens3";
+        networkConfig = {
+          # This is the magic combo for Vultr:
+          IPv6AcceptRA = true;         # Accept routes (so we know where the internet is)
+          IPv6PrivacyExtensions = false; # No random privacy IPs
+        };
+        ipv6AcceptRAConfig = {
+          UseAutonomousPrefix = false; # Do NOT generate an IP address from the RA
+        };
+      };
       networking = {
       networking = {
+        useDHCP = lib.mkForce false;
+        networkmanager.enable = lib.mkForce false;
+        tempAddresses = "disabled";
         extraHosts = ''
     127.0.0.1 livekit.${config.monorepo.vars.orgHost}
     127.0.0.1 matrix.${config.monorepo.vars.orgHost}
         extraHosts = ''
     127.0.0.1 livekit.${config.monorepo.vars.orgHost}
     127.0.0.1 matrix.${config.monorepo.vars.orgHost}
@@ -36,6 +57,7 @@
             prefixLength = 24;
           }
         ];
             prefixLength = 24;
           }
         ];
+        interfaces.ens3.useDHCP = lib.mkForce false;
         interfaces.ens3.ipv6.addresses = [
           {
             address = ipv6addr;
         interfaces.ens3.ipv6.addresses = [
           {
             address = ipv6addr;
             "${config.monorepo.vars.orgHost}" = {
               a.data = ipv4addr;
               aaaa.data = ipv6addr;
             "${config.monorepo.vars.orgHost}" = {
               a.data = ipv4addr;
               aaaa.data = ipv6addr;
+
+              mx.data = [
+                {
+                  preference = 10;
+                  exchange = "mail.${config.monorepo.vars.orgHost}";
+                }
+              ];
+              txt = {
+                data = "v=spf1 ip4:${ipv4addr} ip6:${ipv6addr} -all";
+              };
             };
           };
           subDomains = {
             };
           };
           subDomains = {
             "notes.${config.monorepo.vars.remoteHost}" = {
               a.data = "45.76.87.125";
             };
             "notes.${config.monorepo.vars.remoteHost}" = {
               a.data = "45.76.87.125";
             };
+
+            "_dmarc.${config.monorepo.vars.orgHost}" = {
+              txt = {
+                data = "v=DMARC1; p=none";
+              };
+            };
+
+            "default._domainkey.${config.monorepo.vars.orgHost}" = {
+              txt = {
+                data = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsC9GpfjvQlldPrHAC7Yt+ZF0aduUIVV4j2+KUkF0j6NsrpOgvU6COWKQSod/B/qyPBLWf+w5P5YiJ9XnOgw6Db/I9C67eusEHnV/cbvokXLQjSBvXee1OEdrT9i+6iUgDeGWP4CrD1DcwvXzAcCI9exy3yALHVlbkyYvi0KAYofs8dVQ3JCwSCMlol71lA6ULJ2zbCIWeSOv9/C6QZ5HOIeeoFLesX6O/YvF4FYxWbSHy244TXYuczQKuayjKgD6e8gIT5WJRQj8IAWOQ2podWw6hSuB3Ig+ekoOfnl5ivJGOMbAzFTj8FtbS4ncyidLU1kIOeuLfiILeDDLlIeYTwIDAQAB";
+              };
+            };
+
+            "ntfy.${config.monorepo.vars.remoteHost}" = {};
             "matrix.${config.monorepo.vars.remoteHost}" = {};
             "www.${config.monorepo.vars.remoteHost}" = {};
             "matrix.${config.monorepo.vars.remoteHost}" = {};
             "www.${config.monorepo.vars.remoteHost}" = {};
-            "mail.${config.monorepo.vars.remoteHost}" = {};
+            "mail.${config.monorepo.vars.remoteHost}" = {
+            };
 
             "livekit.${config.monorepo.vars.orgHost}" = {};
             "${config.monorepo.vars.orgHost}" = {};
 
             "livekit.${config.monorepo.vars.orgHost}" = {};
             "${config.monorepo.vars.orgHost}" = {};
diff --git a/nix/update-dns.sh b/nix/update-dns.sh
new file mode 100644 (file)
index 0000000..d3b0819
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+export CLOUDFLARE_TOKEN="$(cat /run/user/1000/secrets/cloudflare-dns | tr -d '\n')"
+poetry run octodns-sync --config-file result --doit