diff --git a/common/icons/guacamole.png b/common/icons/guacamole.png
new file mode 100644
index 0000000..2fb3f9d
Binary files /dev/null and b/common/icons/guacamole.png differ
diff --git a/common/icons/jupyter.png b/common/icons/jupyter.png
new file mode 100644
index 0000000..45902ed
Binary files /dev/null and b/common/icons/jupyter.png differ
diff --git a/docs/ports.md b/docs/ports.md
deleted file mode 100644
index a412faa..0000000
--- a/docs/ports.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Ports
-
-The idea is to always use consecutive ports, but never go back and try to recycle older no longer used ports (for the sake of keeping things clean).
-
-| Port | Description                                                                 |
-| ---- | --------------------------------------------------------------------------- |
-| 8401 | [whoogle](../hosts/nixos/lapetus/services/whoogle.nix)                      |
-| 8402 | [intray api](../hosts/nixos/lapetus/services/intray.nix)                    |
-| 8403 | [intray](../hosts/nixos/lapetus/services/intray.nix)                        |
-| 8404 | [smos](../hosts/nixos/lapetus/services/smos.nix)                            |
-| 8405 | [smos docs](../hosts/nixos/lapetus/services/smos.nix)                       |
-| 8406 | [smos api](../hosts/nixos/lapetus/services/smos.nix)                        |
-| 8407 | [whoogle](../hosts/nixos/lapetus/services/whoogle.nix)                      |
-| 8408 | [vaultwarden](../hosts/nixos/lapetus/services/vaultwarden.nix)              |
-| 8409 | [grafana](../hosts/nixos/lapetus/services/grafana.nix)                      |
-| 8410 | [prometheus](../hosts/nixos/lapetus/services/prometheus.nix)                |
-| 8411 | [prometheus node exporter](../hosts/nixos/lapetus/services/prometheus.nix)  |
-| 8412 | [prometheus nginx exporter](../hosts/nixos/lapetus/services/prometheus.nix) |
-| 8413 | [commafeed](../hosts/nixos/lapetus/services/commafeed.nix)                  |
-| 8414 | [invidious](../hosts/nixos/lapetus/services/invidious.nix)                  |
-| 8415 | [radicale](../hosts/nixos/lapetus/services/radicale.nix)                    |
-| 8416 | [redlib](../hosts/nixos/lapetus/services/redlib.nix)                        |
-| 8417 | [qbittorrent](../hosts/nixos/lapetus/services/qbittorrent.nix)              |
-| 8418 | [microbin](../hosts/nixos/lapetus/services/microbin.nix)                    |
-| 8419 | [forgejo](../hosts/nixos/lapetus/services/forgejo.nix)                      |
-| 8420 | [jupyterjub](../hosts/nixos/lapetus/services/jupyter.nix)                   |
diff --git a/hosts/nixos/common/global/default.nix b/hosts/nixos/common/global/default.nix
index f484391..0f5a2c5 100644
--- a/hosts/nixos/common/global/default.nix
+++ b/hosts/nixos/common/global/default.nix
@@ -16,6 +16,7 @@ let
     ./nix.nix
     ./locale.nix
     ./persistence.nix
+    ./ports.nix
     ./wireless
 
     ../../../../common
diff --git a/hosts/nixos/common/global/ports.nix b/hosts/nixos/common/global/ports.nix
new file mode 100644
index 0000000..6af19e6
--- /dev/null
+++ b/hosts/nixos/common/global/ports.nix
@@ -0,0 +1,26 @@
+# The idea is to always use consecutive ports, but never go back and try to
+# recycle older no longer used ports (for the sake of keeping things clean).
+{
+  satellite.ports = {
+    whoogle = 8401;
+    intray-api = 8402;
+    intray-client = 8403;
+    smos-docs = 8404;
+    smos-api = 8405;
+    smos-client = 8406;
+    vaultwarden = 8407;
+    actual = 8408;
+    grafana = 8409;
+    prometheus = 8410;
+    prometheus-node-exporter = 8411;
+    prometheus-nginx-exporter = 8412;
+    commafeed = 8413;
+    invidious = 8414;
+    radicale = 8415;
+    redlib = 8416;
+    qbittorrent = 8417;
+    microbin = 8418;
+    forgejo = 8419;
+    jupyterhub = 8420;
+  };
+}
diff --git a/hosts/nixos/common/optional/services/nginx.nix b/hosts/nixos/common/optional/services/nginx.nix
index 3e6b801..723a441 100644
--- a/hosts/nixos/common/optional/services/nginx.nix
+++ b/hosts/nixos/common/optional/services/nginx.nix
@@ -1,5 +1,6 @@
 {
   imports = [ ./acme.nix ];
+  satellite.nginx.domain = "moonythm.dev"; # Root domain used throughout my config
   services.nginx = {
     enable = true;
     recommendedGzipSettings = true;
diff --git a/hosts/nixos/lapetus/default.nix b/hosts/nixos/lapetus/default.nix
index 55658cc..0d6eefd 100644
--- a/hosts/nixos/lapetus/default.nix
+++ b/hosts/nixos/lapetus/default.nix
@@ -2,31 +2,39 @@
   imports = [
     ../common/global
     ../common/users/pilot.nix
+    ../common/optional/oci.nix
+    ../common/optional/services/acme.nix
     ../common/optional/services/kanata.nix
+    ../common/optional/services/nginx.nix
+    ../common/optional/services/postgres.nix
     ../common/optional/services/restic
 
-    ./services/syncthing.nix
-    ./services/whoogle.nix
-    ./services/pounce.nix
-    ./services/intray.nix
-    ./services/smos.nix
-    ./services/vaultwarden.nix
+    # ./services/commafeed.nix
+    # ./services/ddclient.nix
     ./services/actual.nix
-    ./services/homer.nix
-    ./services/zfs.nix
-    ./services/prometheus.nix
-    ./services/grafana.nix
-    ./services/commafeed.nix
-    ./services/invidious.nix
+    ./services/cloudflared.nix
     ./services/diptime.nix
+    ./services/forgejo.nix
+    ./services/grafana.nix
+    ./services/guacamole
+    ./services/homer.nix
+    ./services/intray.nix
+    ./services/invidious.nix
+    ./services/jellyfin.nix
+    ./services/jupyter.nix
+    ./services/microbin.nix
+    ./services/pounce.nix
+    ./services/prometheus.nix
+    ./services/prometheus.nix
+    ./services/qbittorrent.nix # turned on/off depending on whether my vpn is paid for
     ./services/radicale.nix
     ./services/redlib.nix
-    ./services/jellyfin.nix
-    ./services/qbittorrent.nix # turned on/off depending on whether my vpn is paid for
-    ./services/microbin.nix
-    ./services/forgejo.nix
-    ./services/jupyter.nix
-    # ./services/ddclient.nix
+    ./services/smos.nix
+    ./services/syncthing.nix
+    ./services/vaultwarden.nix
+    ./services/whoogle.nix
+    ./services/zfs.nix
+
     ./filesystems
     ./hardware
   ];
diff --git a/hosts/nixos/lapetus/secrets.yaml b/hosts/nixos/lapetus/secrets.yaml
index 0eae1bb..c45c1d4 100644
--- a/hosts/nixos/lapetus/secrets.yaml
+++ b/hosts/nixos/lapetus/secrets.yaml
@@ -8,6 +8,7 @@ microbin_env: ENC[AES256_GCM,data:nxiE9GIvEb0xgqomDdMyy2UtG25pt7h+6JUZkAgIejZbJf
 forgejo_mail_password: ENC[AES256_GCM,data:linrpmA8b+8e1+tWNl0=,iv:Mk7suPq0Jt960Zl9s2jj3SSAKt4t8Lv4eKdIo0o8JbE=,tag:TZ0qGJIVSFSUt/0cqamvdw==,type:str]
 javi_password: ENC[AES256_GCM,data:5Ifh/DclUz0/AL69Th/GckolrjerLOnDW77SOf+/L3v39T+EOYgK2GDNKtWGGWYX5sdxZ9JwLS3ZVsIOnN4zjFhgV+GChJWkkzjdpJEtpHlmmBKlyS31Fw7SixVkL3y3VJhw72aVv3bMKQ==,iv:FzAmvIlrhna5InsQCRrWVdrKZGmHMb0njWdvgBurdYs=,tag:/Iguu2FbdV/4RSGTnFdyYA==,type:str]
 vpn_env: ENC[AES256_GCM,data:+61Ft1xj1WnaGH6SdUj3sQunDeTWTQ/G2GVQr1KxXVmLehAdO3W2qwqPRsq0qaad3E6eXd7kMU78w1/9fXM34mJXArmXNPW1X+0549+NX4t3QVP83cIRw6B5vwlWMIA8ixEk46a+t7/C6A10hqpyhqHmeyQEOwJvG+Pou61lBmhSkMQy5gjH4ZNsHHZV0/6ZxSk0yAPQq76cPz4dFvyDzdonLnb+2s1KhHC3D7P6SfuWnfJ1EglrDT8R+A==,iv:mw26zTyFnq9CjN06eRmBTWNjh6SRDY7WOCyhBCmyglg=,tag:cPJvzgtruQNLSg7B+br6xQ==,type:str]
+guacamole_users: ENC[AES256_GCM,data:owqflMMpGLUWPcab8JDlQ12zOuCrfkohxims8we9gKuFX28hrjN1wCmgFDGgX5VqWp07SOKdQVI05xMMO2e9pYgUpeKx5FIa/S/4Q/RNelwWPLmhxqGyEt59L5IBgTfgW5qQMvsl1Q7Xgf8X5bFFUSRmzAx0RFamMPqr71vwPphT7XNnPT8QWQo/2bEUqH0Tb5ITkb1FRSMQzIempuKe4K+awISIDAMwLf87esnoHx+dzQQQObSkKGNDpZ39+IUmeyvTIgN7uWQ349jIzkjNTVMzMUWdpDM6Mn9NwQA8m9BXns9vAe03Ama5cQ0ei64eZSb5uYki0BIV2pYccmIBn4Y/QgfMi5F/L9khr94iiVfH6EWtrDIpVwFt9TNZA/gHbOkQVPyHUMGOpCn2tK5ifFDK0URH5E7WM66BiL3yKd7B9SvQuEN8bnFn64QgiYxSp5NxxGx1ZLE7pzSCEtXkWkCYDHe0xDpOUd+zEfOIeQo7Cdn7y5QaXFyrcwryprYb5Hx/5TtTkCm/+r9nl7BbMKnPLblJvsA77/7pws8xcLgNSAR0ROWqB88vauI1uvwdWN5U5VxsEJmoNQXQKOLOtiQ2mFfIY3DYdHtw1L26ISIYOU6XR7jI/MwOBPO94izEW/j1SwGNgMN5Aydu0RRUhyK9RojjR5CEYjyuqclcD0asJ4faYQn8GvnXJPKF/+U7C6O1g6/ejl3fjwyWRb4kreIATdRfZH+m3nVHgSMvPNk8aPlIs51+SIYMtzf32lKp/AVn2hFKF5g+r2rhWH13Lt9poUg2l//l3jPDY1hpc6dm/5H9ZIGKIqcBlYxqnQngUu51lGO0r6z7u/EsIbInnd5TLK5JrTfnuLuwbyo1yetT2J0MNumy9FcrTsDK4+7DEdAJUxPBRrzllgi+hrnDlVLgLH+7y/gc8lB0cE53GBBXp1N7SfJ1,iv:RFuPux63nSefW3+F08jb94q/NwIKE9g/DGjN++oMdXc=,tag:tCCUIttbK5wfbNpjzY0Bgw==,type:str]
 sops:
     kms: []
     gcp_kms: []
@@ -32,8 +33,8 @@ sops:
             RHZ6alYrUU5BZ2xlMkdGR1dWRG5aeGMKJdsdtVZ6Mk9Vo3a+tS+rzAgaF2wpH+8U
             lWhA+c0Kbe8EJT8hm7Vr8PqBmElz4V9AnXSCTp7D+Cu4pfWsHopLUQ==
             -----END AGE ENCRYPTED FILE-----
-    lastmodified: "2024-05-31T23:18:45Z"
-    mac: ENC[AES256_GCM,data:IUiFsu7+ANxUSr5hR6L3lwK+hP2LpGQFPliGOC3XyhxjLsEkbdCi/CqkaJH7tWZTSUoxMPtdn8JC8mGsnW0puhB6YWA26dbXjlvKGWO+02wcMONy3+prW8v7KLXWe513wsLx1fHOjHMchZj/p8gagOGw19aoGdsTNXnQczwPumo=,iv:hH5R9KFTWvps0JC8iKOkDJMeOfdatFHkz6LedeyY9WE=,tag:2pLvEplrGHpBHbtVfFxCfQ==,type:str]
+    lastmodified: "2024-06-13T13:36:09Z"
+    mac: ENC[AES256_GCM,data:3YUMJJaAeU6S7BwB5FzUuke3SKMZ0naRtRQoAnSRMMj39dQmg20rQy8F5cWsPvQAbDhOnY/1t3IxGbc8LGQkapcJJhbLiWuQmnPylZuMIgXhsnEzSyZ195FJcTGP5JTfmUb0GZ29MSBAlqRcZb0IDZjbOpVigp5BbD+s64HpdFE=,iv:p1pg4A1JEX3YlvoG6ncaavbJvURPlkAQM/jKbE+9sgE=,tag:WvULhegnyz/HXRfCEP6DiQ==,type:str]
     pgp: []
     unencrypted_suffix: _unencrypted
     version: 3.8.1
diff --git a/hosts/nixos/lapetus/services/actual.nix b/hosts/nixos/lapetus/services/actual.nix
index 269cf06..3a05634 100644
--- a/hosts/nixos/lapetus/services/actual.nix
+++ b/hosts/nixos/lapetus/services/actual.nix
@@ -1,23 +1,15 @@
 { config, ... }:
-let
-  port = 8408;
-  host = "actual.moonythm.dev";
-  dataDir = "/persist/state/var/lib/actual";
+let dataDir = "/persist/state/var/lib/actual";
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ../../common/optional/oci.nix
-  ];
-
-  services.nginx.virtualHosts.${host} = config.satellite.proxy port { };
+  satellite.nginx.at.actual.port = config.satellite.ports.actual;
   systemd.tmpfiles.rules = [ "d ${dataDir}" ];
 
   virtualisation.oci-containers.containers.actual = {
     image = "actualbudget/actual-server:latest";
     autoStart = true;
 
-    ports = [ "${toString port}:5006" ]; # server:docker
+    ports = [ "${toString config.satellite.nginx.at.actual.port}:5006" ]; # server:docker
     volumes = [ "${dataDir}:/data" ]; # server:docker
   };
 }
diff --git a/hosts/nixos/lapetus/services/commafeed.nix b/hosts/nixos/lapetus/services/commafeed.nix
index e792a87..156cfcd 100644
--- a/hosts/nixos/lapetus/services/commafeed.nix
+++ b/hosts/nixos/lapetus/services/commafeed.nix
@@ -1,18 +1,11 @@
 { config, ... }:
 let
-  port = 8413;
-  host = "rss.moonythm.dev";
+  port = config.satellite.ports.commafeed;
   dataDir = "/persist/state/var/lib/commafeed";
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ../../common/optional/oci.nix
-  ];
-
   systemd.tmpfiles.rules = [ "d ${dataDir}" ];
-  services.nginx.virtualHosts.${host} = config.satellite.proxy port
-    { proxyWebsockets = true; };
+  satellite.nginx.at.rss.port = port;
 
   virtualisation.oci-containers.containers.commafeed = {
     image = "athou/commafeed:latest";
@@ -27,7 +20,7 @@ in
 
     # https://github.com/Athou/commafeed/blob/master/commafeed-server/config.yml.example
     environment = {
-      CF_APP_PUBLICURL = "https://${host}";
+      CF_APP_PUBLICURL = "https://${config.satellite.nginx.at.rss.host}";
       CF_APP_ALLOWREGISTRATIONS = "false"; # I already made an account
       CF_APP_MAXENTRIESAGEDAYS = "0"; # Fetch old entries
 
diff --git a/hosts/nixos/lapetus/services/ddclient.nix b/hosts/nixos/lapetus/services/ddclient.nix
index 3c3f16d..aee905f 100644
--- a/hosts/nixos/lapetus/services/ddclient.nix
+++ b/hosts/nixos/lapetus/services/ddclient.nix
@@ -1,8 +1,6 @@
 # DDClient is a dynamic dns service
 { config, pkgs, ... }:
 {
-  imports = [ ../../common/optional/services/acme.nix ];
-
   services.ddclient = {
     enable = true;
     interval = "1m";
diff --git a/hosts/nixos/lapetus/services/diptime.nix b/hosts/nixos/lapetus/services/diptime.nix
index 9a345d5..38d998f 100644
--- a/hosts/nixos/lapetus/services/diptime.nix
+++ b/hosts/nixos/lapetus/services/diptime.nix
@@ -1,12 +1,9 @@
 # I couldn't find a hosted version of this
 { pkgs, config, ... }: {
-  imports = [ ../../common/optional/services/nginx.nix ];
-
-  services.nginx.virtualHosts."diptime.moonythm.dev" =
-    config.satellite.static (pkgs.fetchFromGitHub {
-      owner = "bhickey";
-      repo = "diplomatic-timekeeper";
-      rev = "d6ea7b9d9e94ee6d2db8e4e7cff5f8f1c3f04464";
-      sha256 = "09s6awz5m6hzpc6jp96c118i372430c7b41acm5m62bllcvrj9vk";
-    });
+  satellite.nginx.at.diptime.files = pkgs.fetchFromGitHub {
+    owner = "bhickey";
+    repo = "diplomatic-timekeeper";
+    rev = "d6ea7b9d9e94ee6d2db8e4e7cff5f8f1c3f04464";
+    sha256 = "09s6awz5m6hzpc6jp96c118i372430c7b41acm5m62bllcvrj9vk";
+  };
 }
diff --git a/hosts/nixos/lapetus/services/forgejo.nix b/hosts/nixos/lapetus/services/forgejo.nix
index 64c0927..7f002c7 100644
--- a/hosts/nixos/lapetus/services/forgejo.nix
+++ b/hosts/nixos/lapetus/services/forgejo.nix
@@ -1,6 +1,6 @@
 { lib, config, ... }:
 let
-  port = 8419;
+  port = config.satellite.ports.forgejo;
   host = "git.moonythm.dev";
   cfg = config.services.forgejo;
 in
diff --git a/hosts/nixos/lapetus/services/grafana.nix b/hosts/nixos/lapetus/services/grafana.nix
index f6f4abf..2466323 100644
--- a/hosts/nixos/lapetus/services/grafana.nix
+++ b/hosts/nixos/lapetus/services/grafana.nix
@@ -1,5 +1,6 @@
 { config, pkgs, ... }:
 let
+  port = config.satellite.ports.grafana;
   secret = name: "$__file{${config.sops.secrets.${name}.path}}";
   sopsSettings = {
     sopsFile = ../secrets.yaml;
@@ -7,11 +8,6 @@ let
   };
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ./prometheus.nix
-  ];
-
   sops.secrets.grafana_smtp_pass = sopsSettings;
   sops.secrets.grafana_discord_webhook = sopsSettings;
 
@@ -21,9 +17,9 @@ in
 
     settings = {
       server = rec {
-        domain = "grafana.moonythm.dev";
+        domain = config.satellite.nginx.at.grafana.host;
         root_url = "https://${domain}";
-        http_port = 8409;
+        http_port = port;
       };
 
       # {{{ Smtp
@@ -90,8 +86,7 @@ in
   };
   # }}}
   # {{{ Networking & storage
-  services.nginx.virtualHosts.${config.services.grafana.settings.server.domain} =
-    config.satellite.proxy config.services.grafana.settings.server.http_port { };
+  satellite.nginx.at.grafana.port = port;
 
   environment.persistence."/persist/state".directories = [{
     directory = config.services.grafana.dataDir;
diff --git a/hosts/nixos/lapetus/services/guacamole/default.nix b/hosts/nixos/lapetus/services/guacamole/default.nix
new file mode 100644
index 0000000..7e8e5ca
--- /dev/null
+++ b/hosts/nixos/lapetus/services/guacamole/default.nix
@@ -0,0 +1,14 @@
+{ config, ... }:
+{
+  sops.secrets.guacamoleUsers.sopsFile = ../../secrets.yaml;
+  satellite.nginx.at.guacamole.port = 8443; # default tomcat port
+
+  services.guacamole-server = {
+    enable = true;
+    services.guacamole-server.userMappingXml = config.sops.secrets.guacamoleUsers.path;
+  };
+
+  services.guacamole-client = {
+    enable = true;
+  };
+}
diff --git a/hosts/nixos/lapetus/services/guacamole/ed25519.pub b/hosts/nixos/lapetus/services/guacamole/ed25519.pub
new file mode 100644
index 0000000..0f74877
--- /dev/null
+++ b/hosts/nixos/lapetus/services/guacamole/ed25519.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL4xXKQ07lTMuCIg9Grejp2+o50Fo1ptxyK1oGnWt8jA adrielus@tethys
diff --git a/hosts/nixos/lapetus/services/homer.nix b/hosts/nixos/lapetus/services/homer.nix
index 4d5f93b..523bb91 100644
--- a/hosts/nixos/lapetus/services/homer.nix
+++ b/hosts/nixos/lapetus/services/homer.nix
@@ -20,184 +20,193 @@ let
   icon = file: "assets/${iconPath}/${file}";
 in
 {
-  imports = [ ../../common/optional/services/nginx.nix ];
+  satellite.nginx.at.lab.files = pkgs.homer.withAssets {
+    extraAssets = [ iconPath ];
+    config = {
+      title = "✨ The celestial citadel ✨";
 
-  services.nginx.virtualHosts."lab.moonythm.dev" =
-    config.satellite.static (pkgs.homer.withAssets {
-      extraAssets = [ iconPath ];
-      config = {
-        title = "✨ The celestial citadel ✨";
+      header = false;
+      footer = false;
+      connectivityCheck = true;
 
-        header = false;
-        footer = false;
-        connectivityCheck = true;
+      colors.light = colors;
+      colors.dark = colors;
 
-        colors.light = colors;
-        colors.dark = colors;
-
-        services = [
-          # {{{ Infrastructure
-          {
-            name = "Infrastructure";
-            icon = fa "code";
-            items = [
-              {
-                name = "Prometheus";
-                type = "Prometheus";
-                subtitle = "Monitoring system";
-                logo = icon "prometheus.png";
-                url = "https://prometheus.moonythm.dev";
-              }
-              {
-                name = "Grafana";
-                subtitle = "Pretty dashboards :3";
-                logo = icon "grafana.png";
-                url = "https://grafana.moonythm.dev";
-              }
-              {
-                name = "Syncthing";
-                subtitle = "File synchronization";
-                logo = icon "syncthing.png";
-                url = "https://lapetus.syncthing.moonythm.dev";
-              }
-            ];
-          }
-          # }}}
-          # {{{ External
-          {
-            name = "External";
-            icon = fa "arrow-up-right-from-square";
-            items = [
-              {
-                name = "Tailscale";
-                subtitle = "Access this homelab from anywhere";
-                logo = icon "tailscale.png";
-                url = "https://tailscale.com/";
-              }
-              {
-                name = "Dotfiles";
-                subtitle = "Configuration for all my machines";
-                logo = icon "github.png";
-                url = "https://github.com/prescientmoon/everything-nix";
-              }
-              {
-                name = "Cloudflare";
-                subtitle = "Domain management";
-                logo = icon "cloudflare.png";
-                url = "https://dash.cloudflare.com/761d3e81b3e42551e33c4b73274ecc82/moonythm.dev/";
-              }
-            ];
-          }
-          # }}}
-          # {{{ Productivity
-          {
-            name = "Productivity";
-            icon = fa "rocket";
-            items = [
-              {
-                name = "Intray";
-                subtitle = "GTD capture tool";
-                icon = fa "inbox";
-                url = "https://intray.moonythm.dev";
-              }
-              {
-                name = "Smos";
-                subtitle = "A comprehensive self-management system.";
-                icon = fa "cubes-stacked";
-                url = "https://smos.moonythm.dev";
-              }
-              {
-                name = "Actual";
-                subtitle = "Budgeting tool";
-                logo = icon "actual.png";
-                url = "https://actual.moonythm.dev";
-              }
-            ];
-          }
-          # }}}
-          # {{{ Pillars
-          {
-            name = "Tooling";
-            icon = fa "toolbox";
-            items = [
-              {
-                name = "Vaultwarden";
-                subtitle = "Password manager";
-                logo = icon "bitwarden.png";
-                url = "https://warden.moonythm.dev";
-              }
-              {
-                name = "Whoogle";
-                subtitle = "Search engine";
-                logo = icon "whoogle.webp";
-                url = "https://search.moonythm.dev";
-              }
-              {
-                name = "Radicale";
-                subtitle = "Calendar server";
-                logo = icon "radicale.svg";
-                url = "https://cal.moonythm.dev";
-              }
-              {
-                name = "Microbin";
-                subtitle = "Code & file sharing";
-                logo = icon "microbin.png";
-                url = "https://cal.moonythm.dev";
-              }
-              {
-                name = "Forgejo";
-                subtitle = "Git forge";
-                logo = icon "forgejo.svg";
-                url = "https://git.moonythm.dev";
-              }
-            ];
-          }
-          # }}}
-          # {{{ Entertainment
-          {
-            name = "Entertainment";
-            icon = fa "gamepad";
-            items = [
-              {
-                name = "Invidious";
-                subtitle = "Youtube client";
-                logo = icon "invidious.png";
-                url = "https://yt.moonythm.dev";
-              }
-              {
-                name = "Redlib";
-                subtitle = "Reddit client";
-                logo = icon "libreddit.png";
-                url = "https://redlib.moonythm.dev";
-              }
-              {
-                name = "Diptime";
-                subtitle = "Diplomacy timer";
-                icon = fa "globe";
-                url = "https://diptime.moonythm.dev";
-              }
-              {
-                name = "Commafeed";
-                subtitle = "RSS reader";
-                logo = icon "commafeed.png";
-                url = "https://rss.moonythm.dev";
-              }
-              {
-                name = "Qbittorrent";
-                subtitle = "Torrent client";
-                logo = icon "qbittorrent.png";
-                url = "https://qbit.moonythm.dev";
-              }
-              {
-                name = "Jellyfin";
-                subtitle = "Media server";
-                logo = icon "jellyfin.png";
-                url = "https://media.moonythm.dev";
-              }
-            ];
-          }
-          # }}}
-        ];
-      };
-    });
+      services = [
+        # {{{ Infrastructure
+        {
+          name = "Infrastructure";
+          icon = fa "code";
+          items = [
+            {
+              name = "Prometheus";
+              type = "Prometheus";
+              subtitle = "Monitoring system";
+              logo = icon "prometheus.png";
+              url = "https://prometheus.moonythm.dev";
+            }
+            {
+              name = "Grafana";
+              subtitle = "Pretty dashboards :3";
+              logo = icon "grafana.png";
+              url = "https://grafana.moonythm.dev";
+            }
+            {
+              name = "Syncthing";
+              subtitle = "File synchronization";
+              logo = icon "syncthing.png";
+              url = "https://lapetus.syncthing.moonythm.dev";
+            }
+            {
+              name = "Guacamole";
+              subtitle = "Server remote access";
+              logo = icon "guacamole.png";
+              url = "https://guacamole.moonythm.dev";
+            }
+          ];
+        }
+        # }}}
+        # {{{ External
+        {
+          name = "External";
+          icon = fa "arrow-up-right-from-square";
+          items = [
+            {
+              name = "Tailscale";
+              subtitle = "Access this homelab from anywhere";
+              logo = icon "tailscale.png";
+              url = "https://tailscale.com/";
+            }
+            {
+              name = "Dotfiles";
+              subtitle = "Configuration for all my machines";
+              logo = icon "github.png";
+              url = "https://github.com/prescientmoon/everything-nix";
+            }
+            {
+              name = "Cloudflare";
+              subtitle = "Domain management";
+              logo = icon "cloudflare.png";
+              url = "https://dash.cloudflare.com/761d3e81b3e42551e33c4b73274ecc82/moonythm.dev/";
+            }
+          ];
+        }
+        # }}}
+        # {{{ Productivity
+        {
+          name = "Productivity";
+          icon = fa "rocket";
+          items = [
+            {
+              name = "Intray";
+              subtitle = "GTD capture tool";
+              icon = fa "inbox";
+              url = "https://intray.moonythm.dev";
+            }
+            {
+              name = "Smos";
+              subtitle = "A comprehensive self-management system.";
+              icon = fa "cubes-stacked";
+              url = "https://smos.moonythm.dev";
+            }
+            {
+              name = "Actual";
+              subtitle = "Budgeting tool";
+              logo = icon "actual.png";
+              url = "https://actual.moonythm.dev";
+            }
+          ];
+        }
+        # }}}
+        # {{{ Tooling
+        {
+          name = "Tooling";
+          icon = fa "toolbox";
+          items = [
+            {
+              name = "Vaultwarden";
+              subtitle = "Password manager";
+              logo = icon "bitwarden.png";
+              url = "https://warden.moonythm.dev";
+            }
+            {
+              name = "Whoogle";
+              subtitle = "Search engine";
+              logo = icon "whoogle.webp";
+              url = "https://search.moonythm.dev";
+            }
+            {
+              name = "Radicale";
+              subtitle = "Calendar server";
+              logo = icon "radicale.svg";
+              url = "https://cal.moonythm.dev";
+            }
+            {
+              name = "Microbin";
+              subtitle = "Code & file sharing";
+              logo = icon "microbin.png";
+              url = "https://bin.moonythm.dev";
+            }
+            {
+              name = "Forgejo";
+              subtitle = "Git forge";
+              logo = icon "forgejo.svg";
+              url = "https://git.moonythm.dev";
+            }
+            {
+              name = "Jupyterhub";
+              subtitle = "Notebook collaboration suite";
+              logo = icon "jupyter.png";
+              url = "https://jupyter.moonythm.dev";
+            }
+          ];
+        }
+        # }}}
+        # {{{ Entertainment
+        {
+          name = "Entertainment";
+          icon = fa "gamepad";
+          items = [
+            {
+              name = "Invidious";
+              subtitle = "Youtube client";
+              logo = icon "invidious.png";
+              url = "https://yt.moonythm.dev";
+            }
+            {
+              name = "Redlib";
+              subtitle = "Reddit client";
+              logo = icon "libreddit.png";
+              url = "https://redlib.moonythm.dev";
+            }
+            {
+              name = "Diptime";
+              subtitle = "Diplomacy timer";
+              icon = fa "globe";
+              url = "https://diptime.moonythm.dev";
+            }
+            {
+              name = "Commafeed";
+              subtitle = "RSS reader";
+              logo = icon "commafeed.png";
+              url = "https://rss.moonythm.dev";
+            }
+            {
+              name = "Qbittorrent";
+              subtitle = "Torrent client";
+              logo = icon "qbittorrent.png";
+              url = "https://qbit.moonythm.dev";
+            }
+            {
+              name = "Jellyfin";
+              subtitle = "Media server";
+              logo = icon "jellyfin.png";
+              url = "https://media.moonythm.dev";
+            }
+          ];
+        }
+        # }}}
+      ];
+    };
+  };
 }
diff --git a/hosts/nixos/lapetus/services/intray.nix b/hosts/nixos/lapetus/services/intray.nix
index e177150..29163f9 100644
--- a/hosts/nixos/lapetus/services/intray.nix
+++ b/hosts/nixos/lapetus/services/intray.nix
@@ -1,15 +1,11 @@
 { inputs, config, ... }:
 let
   username = "prescientmoon";
-  apiHost = "api.intray.moonythm.dev";
-  apiPort = 8402;
-  webPort = 8403;
+  apiPort = config.satellite.ports.intray-api;
+  webPort = config.satellite.ports.intray-client;
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    inputs.intray.nixosModules.x86_64-linux.default
-  ];
+  imports = [ inputs.intray.nixosModules.x86_64-linux.default ];
 
   # {{{ Configure intray 
   services.intray.production = {
@@ -22,13 +18,13 @@ in
     web-server = {
       enable = true;
       port = webPort;
-      api-url = "https://${apiHost}";
+      api-url = config.satellite.nginx.at."api.intray".url;
     };
   };
   # }}}
   # {{{ Networking & storage
-  services.nginx.virtualHosts.${apiHost} = config.satellite.proxy apiPort { };
-  services.nginx.virtualHosts."intray.moonythm.dev" = config.satellite.proxy webPort { };
+  satellite.nginx.at."intray".port = webPort;
+  satellite.nginx.at."api.intray".port = apiPort;
 
   environment.persistence."/persist/state".directories = [
     "/www/intray/production/data"
diff --git a/hosts/nixos/lapetus/services/invidious.nix b/hosts/nixos/lapetus/services/invidious.nix
index 37ba2a1..1542ca8 100644
--- a/hosts/nixos/lapetus/services/invidious.nix
+++ b/hosts/nixos/lapetus/services/invidious.nix
@@ -1,22 +1,16 @@
 { config, pkgs, ... }: {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ../../common/optional/services/postgres.nix
-  ];
-
   sops.secrets.invidious_hmac_key.sopsFile = ../secrets.yaml;
   sops.templates."invidious_hmac_key.json" = {
     content = ''{ "hmac_key": "${config.sops.placeholder.invidious_hmac_key}" }'';
     mode = "0444"; # I don't care about this key that much, as I'm the only user of this instance
   };
 
-  services.nginx.virtualHosts.${config.services.invidious.domain} =
-    config.satellite.proxy config.services.invidious.port { };
+  satellite.nginx.at.yt.port = config.satellite.ports.invidious;
 
   services.invidious = {
     enable = true;
-    domain = "yt.moonythm.dev";
-    port = 8414;
+    domain = config.satellite.nginx.at.yt.host;
+    port = config.satellite.nginx.at.yt.port;
     hmacKeyFile = config.sops.templates."invidious_hmac_key.json".path;
 
     settings = {
diff --git a/hosts/nixos/lapetus/services/jellyfin.nix b/hosts/nixos/lapetus/services/jellyfin.nix
index 60bd844..b0c4809 100644
--- a/hosts/nixos/lapetus/services/jellyfin.nix
+++ b/hosts/nixos/lapetus/services/jellyfin.nix
@@ -1,9 +1,6 @@
 { config, pkgs, ... }: {
-  imports = [ ../../common/optional/services/nginx.nix ];
-
-  services.nginx.virtualHosts."media.moonythm.dev" =
-    config.satellite.proxy 8096 { }; # This is the default port, and can only be changed via the GUI
-
+  # This is the default port, and can only be changed via the GUI
+  satellite.nginx.at.media.port = 8096;
   services.jellyfin.enable = true;
 
   # {{{ Storage 
diff --git a/hosts/nixos/lapetus/services/jupyter.nix b/hosts/nixos/lapetus/services/jupyter.nix
index 38bc882..addfa69 100644
--- a/hosts/nixos/lapetus/services/jupyter.nix
+++ b/hosts/nixos/lapetus/services/jupyter.nix
@@ -18,7 +18,7 @@ in
 
   services.jupyterhub = {
     enable = true;
-    port = 8420;
+    port = config.satellite.ports.jupyterhub;
 
     jupyterhubEnv = appEnv;
     jupyterlabEnv = appEnv;
diff --git a/hosts/nixos/lapetus/services/microbin.nix b/hosts/nixos/lapetus/services/microbin.nix
index 0b857eb..52da379 100644
--- a/hosts/nixos/lapetus/services/microbin.nix
+++ b/hosts/nixos/lapetus/services/microbin.nix
@@ -1,11 +1,9 @@
 { config, lib, ... }:
 let
-  port = 8418;
+  port = config.satellite.ports.microbin;
   host = "bin.moonythm.dev";
 in
 {
-  imports = [ ./cloudflared.nix ];
-
   sops.secrets.microbin_env.sopsFile = ../secrets.yaml;
   satellite.cloudflared.targets.${host}.port = port;
 
diff --git a/hosts/nixos/lapetus/services/prometheus.nix b/hosts/nixos/lapetus/services/prometheus.nix
index 593d044..8b8f3d1 100644
--- a/hosts/nixos/lapetus/services/prometheus.nix
+++ b/hosts/nixos/lapetus/services/prometheus.nix
@@ -1,14 +1,10 @@
 { config, pkgs, ... }:
-let host = "prometheus.moonythm.dev";
-in
 {
-  imports = [ ../../common/optional/services/nginx.nix ];
-
   # {{{ Main config
   services.prometheus = {
     enable = true;
-    port = 8410;
-    webExternalUrl = "https://${host}";
+    port = config.satellite.ports.prometheus;
+    webExternalUrl = config.satellite.nginx.at.prometheus.url;
 
     # {{{ Base exporters
     exporters = {
@@ -16,12 +12,12 @@ in
       node = {
         enable = true;
         enabledCollectors = [ "systemd" ];
-        port = 8411;
+        port = config.satellite.ports.prometheus-node-exporter;
       };
 
       nginx = {
         enable = true;
-        port = 8412;
+        port = config.satellite.ports.prometheus-nginx-exporter;
       };
     };
 
@@ -38,9 +34,7 @@ in
   };
   # }}}
   # {{{ Networking & storage
-  services.nginx.virtualHosts.${host} =
-    config.satellite.proxy config.services.prometheus.port
-      { proxyWebsockets = true; };
+  satellite.nginx.at.prometheus.port = config.services.prometheus.port;
 
   environment.persistence."/persist/state".directories = [{
     directory = "/var/lib/prometheus2";
diff --git a/hosts/nixos/lapetus/services/qbittorrent.nix b/hosts/nixos/lapetus/services/qbittorrent.nix
index b4b606a..c0a6170 100644
--- a/hosts/nixos/lapetus/services/qbittorrent.nix
+++ b/hosts/nixos/lapetus/services/qbittorrent.nix
@@ -3,27 +3,20 @@
 # https://www.reddit.com/r/HomeServer/comments/xapl93/a_minimal_configuration_stepbystep_guide_to_media/
 { config, pkgs, ... }:
 let
-  port = 8417;
+  port = config.satellite.ports.qbittorrent;
   dataDir = "/persist/data/media";
   configDir = "/persist/state/var/lib/qbittorrent";
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ../../common/optional/oci.nix
-  ];
-
+  # {{{ Networking & storage
+  satellite.nginx.at.qbit.port = port;
   sops.secrets.vpn_env.sopsFile = ../secrets.yaml;
-
-  services.nginx.virtualHosts."qbit.moonythm.dev" =
-    config.satellite.proxy port { proxyWebsockets = true; };
-
   systemd.tmpfiles.rules = [
     "d ${dataDir} 777 ${config.users.users.pilot.name} users"
     "d ${configDir}"
   ];
-
-  # {{{ qbit
+  # }}}
+  # {{{ Qbit
   virtualisation.oci-containers.containers.qbittorrent = {
     image = "linuxserver/qbittorrent:latest";
     extraOptions = [ "--network=container:gluetun" ];
@@ -37,7 +30,7 @@ in
     };
   };
   # }}}
-  # {{{ vpn
+  # {{{ Vpn
   virtualisation.oci-containers.containers.gluetun = {
     image = "qmcgaw/gluetun";
     extraOptions = [
diff --git a/hosts/nixos/lapetus/services/radicale.nix b/hosts/nixos/lapetus/services/radicale.nix
index 726cdbb..c7eafad 100644
--- a/hosts/nixos/lapetus/services/radicale.nix
+++ b/hosts/nixos/lapetus/services/radicale.nix
@@ -1,6 +1,6 @@
 { config, ... }:
 let
-  port = 8415;
+  port = config.satellite.ports.radicale;
   dataDir = "/persist/data/radicale";
 in
 {
@@ -14,7 +14,5 @@ in
   };
 
   systemd.tmpfiles.rules = [ "d ${dataDir} 0700 radicale radicale" ];
-
-  services.nginx.virtualHosts."cal.moonythm.dev" =
-    config.satellite.proxy port { };
+  satellite.nginx.at.cal.port = port;
 }
diff --git a/hosts/nixos/lapetus/services/redlib.nix b/hosts/nixos/lapetus/services/redlib.nix
index 739b5cd..c588024 100644
--- a/hosts/nixos/lapetus/services/redlib.nix
+++ b/hosts/nixos/lapetus/services/redlib.nix
@@ -1,13 +1,9 @@
 { config, lib, upkgs, ... }:
-let port = 8416;
+let port = config.satellite.ports.redlib;
 in
 {
-  imports = [ ../../common/optional/services/nginx.nix ];
-
-  services.nginx.virtualHosts."redlib.moonythm.dev" =
-    config.satellite.proxy port { };
-
   services.libreddit.enable = true;
+  satellite.nginx.at.redlib.port = port;
   systemd.services.libreddit.serviceConfig.ExecStart =
     lib.mkForce "${upkgs.redlib}/bin/redlib --port ${toString port}";
 }
diff --git a/hosts/nixos/lapetus/services/smos.nix b/hosts/nixos/lapetus/services/smos.nix
index 70122df..d149846 100644
--- a/hosts/nixos/lapetus/services/smos.nix
+++ b/hosts/nixos/lapetus/services/smos.nix
@@ -1,20 +1,8 @@
 { inputs, config, ... }:
-let
-  username = "prescientmoon";
-  docsHost = "docs.smos.moonythm.dev";
-  apiHost = "api.smos.moonythm.dev";
-  webHost = "smos.moonythm.dev";
-  docsPort = 8404;
-  apiPort = 8405;
-  webPort = 8406;
-
-  https = host: "https://${host}";
+let username = "prescientmoon";
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    inputs.smos.nixosModules.x86_64-linux.default
-  ];
+  imports = [ inputs.smos.nixosModules.x86_64-linux.default ];
 
   # {{{ Configure smos 
   services.smos.production = {
@@ -24,16 +12,16 @@ in
     docs-site = {
       enable = true;
       openFirewall = false;
-      port = docsPort;
-      api-url = https apiHost;
-      web-url = https webHost;
+      port = config.satellite.nginx.at."docs.smos".port;
+      api-url = config.satellite.nginx.at."api.smos".url;
+      web-url = config.satellite.nginx.at."smos".url;
     };
     # }}}
     # {{{ Api server
     api-server = {
       enable = true;
       openFirewall = false;
-      port = apiPort;
+      port = config.satellite.nginx.at."api.smos".port;
       admin = username;
 
       max-backups-per-user = 5;
@@ -45,25 +33,18 @@ in
     web-server = {
       enable = true;
       openFirewall = false;
-      port = webPort;
-      docs-url = https docsHost;
-      api-url = https apiHost;
-      web-url = https webHost;
+      port = config.satellite.nginx.at."smos".port;
+      docs-url = config.satellite.nginx.at."docs.smos".url;
+      api-url = config.satellite.nginx.at."api.smos".url;
+      web-url = config.satellite.nginx.at."smos".url;
     };
     # }}}
   };
   # }}}
   # {{{ Networking & storage
-  services.nginx.virtualHosts.${docsHost} = config.satellite.proxy docsPort { };
-  services.nginx.virtualHosts.${apiHost} = config.satellite.proxy apiPort { };
-  services.nginx.virtualHosts.${webHost} = config.satellite.proxy webPort {
-    proxyWebsockets = true;
-
-    # Just to make sure we don't run into 413 errors on big syncs
-    extraConfig = ''
-      client_max_body_size 0;
-    '';
-  };
+  satellite.nginx.at."docs.smos".port = config.satellite.ports.smos-docs;
+  satellite.nginx.at."api.smos".port = config.satellite.ports.smos-api;
+  satellite.nginx.at."smos".port = config.satellite.ports.smos-client;
 
   environment.persistence."/persist/state".directories = [
     "/www/smos/production"
diff --git a/hosts/nixos/lapetus/services/syncthing.nix b/hosts/nixos/lapetus/services/syncthing.nix
index 734fc40..6b50103 100644
--- a/hosts/nixos/lapetus/services/syncthing.nix
+++ b/hosts/nixos/lapetus/services/syncthing.nix
@@ -2,16 +2,11 @@
 let port = 8384;
 in
 {
-  imports = [
-    ../../common/optional/services/syncthing.nix
-    ../../common/optional/services/nginx.nix
-  ];
-
   services.syncthing = {
     settings.folders = { };
     guiAddress = "127.0.0.1:${toString port}";
     settings.gui.insecureSkipHostcheck = true;
   };
 
-  services.nginx.virtualHosts."lapetus.syncthing.moonythm.dev" = config.satellite.proxy port { };
+  satellite.nginx.at."lapetus.syncthing".port = port;
 }
diff --git a/hosts/nixos/lapetus/services/vaultwarden.nix b/hosts/nixos/lapetus/services/vaultwarden.nix
index d726e31..41932b9 100644
--- a/hosts/nixos/lapetus/services/vaultwarden.nix
+++ b/hosts/nixos/lapetus/services/vaultwarden.nix
@@ -1,13 +1,6 @@
 { config, ... }:
-let
-  port = 8407;
-  host = "warden.moonythm.dev";
-in
 {
-  imports = [ ../../common/optional/services/nginx.nix ];
-
-  services.nginx.virtualHosts.${host} =
-    config.satellite.proxy port { proxyWebsockets = true; };
+  satellite.nginx.at.warden.port = config.satellite.ports.vaultwarden;
 
   # {{{ Secrets 
   sops.secrets.vaultwarden_env = {
@@ -21,11 +14,11 @@ in
     enable = true;
     environmentFile = config.sops.secrets.vaultwarden_env.path;
     config = {
-      DOMAIN = "https://${host}";
+      DOMAIN = "https://${config.satellite.nginx.at.warden.host}";
+      ROCKET_PORT = config.satellite.nginx.at.warden.port;
       ROCKET_ADDRESS = "127.0.0.1";
-      ROCKET_PORT = port;
 
-      SIGNUPS_ALLOWED = true;
+      SIGNUPS_ALLOWED = false;
       SHOW_PASSWORD_HINT = false;
 
       SMTP_SECURITY = "force_tls";
diff --git a/hosts/nixos/lapetus/services/whoogle.nix b/hosts/nixos/lapetus/services/whoogle.nix
index 4346294..f23d6de 100644
--- a/hosts/nixos/lapetus/services/whoogle.nix
+++ b/hosts/nixos/lapetus/services/whoogle.nix
@@ -1,39 +1,32 @@
 { lib, config, ... }:
-let
-  port = 8401;
-  websiteBlocklist = [
-    "www.saashub.com"
-    "slant.co"
-    "nix-united.com"
-    "libhunt.com"
-    "www.devopsschool.com"
-    "medevel.com"
-    "alternativeto.net"
-    "linuxiac.com"
-    "www.linuxlinks.com"
-    "sourceforge.net"
-  ];
+let websiteBlocklist = [
+  "www.saashub.com"
+  "slant.co"
+  "nix-united.com"
+  "libhunt.com"
+  "www.devopsschool.com"
+  "medevel.com"
+  "alternativeto.net"
+  "linuxiac.com"
+  "www.linuxlinks.com"
+  "sourceforge.net"
+];
 in
 {
-  imports = [
-    ../../common/optional/services/nginx.nix
-    ../../common/optional/oci.nix
-  ];
-
   virtualisation.oci-containers.containers.whoogle-search = {
     image = "benbusby/whoogle-search";
     autoStart = true;
-    ports = [ "${toString port}:5000" ]; # server:docker
+    ports = [ "${toString config.satellite.nginx.at.search.port}:5000" ]; # server:docker
     environment = {
       WHOOGLE_UPDATE_CHECK = "0";
       WHOOGLE_CONFIG_DISABLE = "0";
       WHOOGLE_CONFIG_BLOCK = lib.concatStringsSep "," websiteBlocklist;
       WHOOGLE_CONFIG_THEME = "system";
       WHOOGLE_ALT_WIKI = ""; # disable redirecting wikipedia links
-      WHOOGLE_ALT_RD = "redlib.moonythm.dev";
-      WHOOGLE_ALT_YT = "yt.moonythm.dev";
+      WHOOGLE_ALT_RD = config.satellite.nginx.at.redlib.host;
+      WHOOGLE_ALT_YT = config.satellite.nginx.at.yt.host;
     };
   };
 
-  services.nginx.virtualHosts."search.moonythm.dev" = config.satellite.proxy port { };
+  satellite.nginx.at.search.port = config.satellite.ports.whoogle;
 }
diff --git a/modules/README.md b/modules/README.md
index b388f86..18e7d21 100644
--- a/modules/README.md
+++ b/modules/README.md
@@ -26,7 +26,8 @@ This directory contains custom module definitions used throughout my config.
 | Name                                   | Attribute               | Description                                 |
 | -------------------------------------- | ----------------------- | ------------------------------------------- |
 | [pounce](./nixos/pounce.nix)           | `services.pounce`       | Module for pounce & calico configuration    |
-| [nginx](./nixos/nginx.nix)             | `satellite.proxy`       | Helpers for nginx configuration             |
+| [nginx](./nixos/nginx.nix)             | `satellite.nginx`       | Helpers for nginx configuration             |
+| [ports](./nixos/ports.nix)             | `satellite.ports`       | Global port specification                   |
 | [cloudflared](./nixos/cloudflared.nix) | `satellite.cloudflared` | Helpers for cloudflare tunnel configuration |
 | [pilot](./nixos/pilot.nix)             | `satellite.pilot`       | Defined the concept of a "main user"        |
 
diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix
index 950c9f5..4f811fa 100644
--- a/modules/nixos/default.nix
+++ b/modules/nixos/default.nix
@@ -3,6 +3,7 @@
 {
   # example = import ./example.nix;
   cloudflared = import ./cloudflared.nix;
+  ports = import ./ports.nix;
   nginx = import ./nginx.nix;
   pilot = import ./pilot.nix;
   pounce = import ./pounce.nix;
diff --git a/modules/nixos/nginx.nix b/modules/nixos/nginx.nix
index b6c37a0..a507aad 100644
--- a/modules/nixos/nginx.nix
+++ b/modules/nixos/nginx.nix
@@ -1,25 +1,85 @@
-{ lib, ... }: {
-  options.satellite.proxy = lib.mkOption {
-    type = lib.types.functionTo (lib.types.functionTo lib.types.anything);
-    description = "Helper function for generating a quick proxy config";
+{ config, lib, ... }:
+let cfg = config.satellite.nginx;
+in
+{
+  options.satellite.nginx = {
+    domain = lib.mkOption {
+      description = "Root domain to use as a default for configurations.";
+      type = lib.types.str;
+    };
+
+    at = lib.mkOption {
+      description = "Per-subdomain nginx configuration";
+      type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: {
+        options.name = lib.mkOption {
+          description = "Attribute name leading to this submodule";
+          type = lib.types.str;
+        };
+
+        config.name = name;
+
+        options.host = lib.mkOption {
+          description = "Host to route requests from";
+          type = lib.types.str;
+          default = "${name}.${cfg.domain}";
+        };
+
+        options.url = lib.mkOption {
+          description = "External https url used to access this host";
+          type = lib.types.str;
+        };
+
+        config.url = "https://${config.host}";
+
+        options.port = lib.mkOption {
+          description = "Port to proxy requests to";
+          type = lib.types.nullOr lib.types.port;
+          default = null;
+        };
+
+        options.files = lib.mkOption {
+          description = "Path to serve files from";
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+        };
+      }));
+      default = { };
+    };
   };
 
-  options.satellite.static = lib.mkOption {
-    type = lib.types.functionTo lib.types.anything;
-    description = "Helper function for generating a quick file serving config";
-  };
+  config = {
+    assertions =
+      let assertSingleTarget = config:
+        {
+          assertion = (config.port == null) == (config.files != null);
+          message = ''
+            Precisely one of the options 'satellite.nginx.at.${config.name}.port'
+            and 'satellite.nginx.at.${config.name}.files' must be specified.
+          '';
+        };
+      in lib.mapAttrsToList (_: assertSingleTarget) cfg.at;
 
-  config.satellite.proxy = port: extra: {
-    enableACME = true;
-    acmeRoot = null;
-    forceSSL = true;
-    locations."/" = { proxyPass = "http://localhost:${toString port}"; } // extra;
-  };
-
-  config.satellite.static = root: {
-    inherit root;
-    enableACME = true;
-    acmeRoot = null;
-    forceSSL = true;
+    services.nginx.virtualHosts =
+      let mkNginxConfig = { host, port, files }: {
+        name = host;
+        value =
+          let extra =
+            if port != null then {
+              locations."/" = {
+                proxyPass = "http://localhost:${toString port}";
+                proxyWebsockets = true;
+              };
+            }
+            else {
+              root = files;
+            };
+          in
+          {
+            enableACME = true;
+            acmeRoot = null;
+            forceSSL = true;
+          } // extra;
+      };
+      in lib.attrsets.mapAttrs' (_: mkNginxConfig) cfg.at;
   };
 }
diff --git a/modules/nixos/ports.nix b/modules/nixos/ports.nix
new file mode 100644
index 0000000..72c8bb4
--- /dev/null
+++ b/modules/nixos/ports.nix
@@ -0,0 +1,9 @@
+# Generic interface for working specifying a single-source of truth for ports!
+{ lib, ... }:
+{
+  options.satellite.ports = lib.mkOption {
+    description = "Record of custom app-port mappings to use throughput the config";
+    type = lib.types.lazyAttrsOf lib.types.port;
+    default = { };
+  };
+}
diff --git a/scripts/dns/dns.txt b/scripts/dns/dns.txt
index 16399c8..670816a 100644
--- a/scripts/dns/dns.txt
+++ b/scripts/dns/dns.txt
@@ -16,6 +16,7 @@ cal                IN CNAME  lapetus
 diptime            IN CNAME  lapetus
 docs.smos          IN CNAME  lapetus
 grafana            IN CNAME  lapetus
+guacamole          IN CNAME  lapetus
 intray             IN CNAME  lapetus
 irc                IN CNAME  lapetus
 jupyter            IN CNAME  lapetus
@@ -37,8 +38,8 @@ tunnel.lapetus     IN CNAME  347d9ead-a523-4f8b-bca7-3066e31e2952.cfargotunnel.c
 
 ; lapetus services using cloudflare tunnels
 bin                IN CNAME  tunnel.lapetus
-jupyter            IN CNAME  tunnel.lapetus
 git                IN CNAME  tunnel.lapetus
+jupyter            IN CNAME  tunnel.lapetus
 
 ; github pages
 doffycup           IN CNAME  prescientmoon.github.io.
diff --git a/scripts/rebuild.sh b/scripts/rebuild.sh
index 3c9c12d..8b106b2 100755
--- a/scripts/rebuild.sh
+++ b/scripts/rebuild.sh
@@ -1,2 +1,2 @@
 #!/usr/bin/env bash
-sudo nixos-rebuild switch --flake .#$hostname --show-trace --fast
+sudo nixos-rebuild switch --flake .#$HOSTNAME --show-trace --fast
diff --git a/scripts/repl.sh b/scripts/repl.sh
new file mode 100755
index 0000000..12ec9e4
--- /dev/null
+++ b/scripts/repl.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+nix repl ".#nixosConfigurations.$1.config"