ctucx.git: nixfiles

ctucx' nixfiles

commit 80800c10ba8796bde888922206f7c62b0990f6b8
parent 1ff6e0f8f2caf6eac76822359efdc3740c287e15
Author: Katja (ctucx) <git@ctu.cx>
Date: Mon, 3 Mar 2025 18:09:06 +0100

move webservices to `configurations/nixos/websites`
17 files changed, 625 insertions(+), 646 deletions(-)
A
configurations/nixos/websites/dav.ctu.cx.nix
|
48
++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/hector/fedi/gotosocial.nix -> configurations/nixos/websites/fedi.ctu.cx.nix
|
0
A
configurations/nixos/websites/fedi.home.ctu.cx.nix
|
114
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/websites/git.ctu.cx.nix
|
251
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R
machines/hector/grocy.nix -> configurations/nixos/websites/grocy.ctu.cx.nix
|
0
R
machines/hector/prometheus.nix -> configurations/nixos/websites/prometheus.ctu.cx.nix
|
0
A
configurations/nixos/websites/vault.ctu.cx.nix
|
63
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
configurations/nixos/websites/zuggeschmack.de.nix
|
139
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
machines/briefkasten/default.nix
|
4
+---
D
machines/briefkasten/gotosocial.nix
|
118
-------------------------------------------------------------------------------
M
machines/hector/default.nix
|
16
++++++++--------
D
machines/hector/fedi/default.nix
|
10
----------
D
machines/hector/git.nix
|
251
-------------------------------------------------------------------------------
D
machines/hector/radicale.nix
|
48
------------------------------------------------
D
machines/hector/vaultwarden.nix
|
63
---------------------------------------------------------------
M
machines/trabbi/default.nix
|
2
+-
D
machines/trabbi/gotosocial.nix
|
144
-------------------------------------------------------------------------------
diff --git a/configurations/nixos/websites/dav.ctu.cx.nix b/configurations/nixos/websites/dav.ctu.cx.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  dns.zones."ctu.cx".subdomains.dav.CNAME = [ "${config.networking.fqdn}." ];
+
+  age.secrets = {
+    restic-radicale.file = ./. + "/../../../secrets/${config.networking.hostName}/restic/radicale.age";
+    radicale-users = {
+      file  = ./. + "/../../../secrets/${config.networking.hostName}/radicale-users.age";
+      owner = "radicale";
+    };
+  };
+
+  restic-backups.radicale = {
+    user         = "radicale";
+    passwordFile = config.age.secrets.restic-radicale.path;
+    paths        = [ "/var/lib/radicale" ];
+  };
+
+  systemd.services.radicale.onFailure = [ "email-notify@%i.service" ];
+
+  services = {
+    radicale = {
+      enable = true;
+      settings = {
+        server.hosts                        = [ "[::1]:5232" ];
+        web.type                            = "internal";
+        storage.filesystem_folder           = "/var/lib/radicale/collections";
+        headers.Access-Control-Allow-Origin = "*";
+        auth.type                           = "htpasswd";
+        auth.htpasswd_filename              = config.age.secrets.radicale-users.path;
+        auth.htpasswd_encryption            = "plain";
+      };
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts."dav.ctu.cx" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        kTLS        = true;
+        locations."/".proxyPass = "http://[::1]:5232/";
+      };
+    };
+  };
+
+}
diff --git a/machines/hector/fedi/gotosocial.nix b/configurations/nixos/websites/fedi.ctu.cx.nix
diff --git a/configurations/nixos/websites/fedi.home.ctu.cx.nix b/configurations/nixos/websites/fedi.home.ctu.cx.nix
@@ -0,0 +1,114 @@
+{ pkgs, lib, config, ... }:
+
+{
+
+  dns.zones."ctu.cx".subdomains."fedi.home".AAAA = [ config.networking.primaryIP ];
+
+  age.secrets.restic-gotosocial.file = ./. + "/../../../secrets/${config.networking.hostName}/restic/gotosocial.age";
+
+  systemd.services.restic-backup-gotosocial.serviceConfig.ReadWritePaths = [ "/var/lib/gotosocial" ];
+
+  restic-backups.gotosocial = {
+    user            = "gotosocial";
+    passwordFile    = config.age.secrets.restic-gotosocial.path;
+    sqliteDatabases = [ "/var/lib/gotosocial/db.sqlite" ];
+    paths           = [ "/var/lib/gotosocial/storage" "/var/lib/gotosocial/backup.json" ];
+    runBeforeBackup = ''
+      ${pkgs.gotosocial}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path /var/lib/gotosocial/backup.json
+    '';
+  };
+
+  systemd.services.gotosocial.serviceConfig = {
+    Group = lib.mkForce config.services.nginx.group;
+  };
+
+  services.gotosocial = {
+    enable   = true;
+    group    = "nginx";
+    settings = {
+      application-name = "ctucx.gts";
+
+      host             = "fedi.home.ctu.cx";
+      account-domain   = "fedi.home.ctu.cx";
+      protocol         = "https";
+
+      bind-address     = "[::1]";
+      port             = 8085;
+
+      trusted-proxies  = [ "::1/128" "172.17.0.0/24" ];
+
+      db-type          = "sqlite";
+      db-address       = "/var/lib/gotosocial/db.sqlite";
+
+      accounts-allow-custom-css  = true;
+      accounts-registration-open = false;
+
+      instance-expose-peers         = true;
+      instance-expose-suspended     = true;
+      instance-expose-suspended-web = true;
+
+      instance-languages            = [ "de" "en-us" ];
+
+      storage-backend            = "local";
+      storage-local-base-path    = "/var/lib/gotosocial/storage";
+
+      media-remote-max-size      = 0;
+      media-remote-cache-days    = 3;
+      media-cleanup-from         = "02:00";
+    };
+  };
+
+  services.nginx.appendHttpConfig = ''
+    proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w;
+  '';
+
+  services.nginx.virtualHosts."fedi.home.ctu.cx" = {
+    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+    forceSSL    = true;
+    kTLS        = true;
+    locations   = {
+      "= /".return = "307 /@leah";
+
+      "/" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        proxyWebsockets = true;
+      };
+
+      "~ /.well-known/(webfinger|host-meta)$" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        extraConfig = ''
+          proxy_cache gotosocial_ap_public_responses;
+          proxy_cache_background_update on;
+          proxy_cache_key $scheme://$host$uri$is_args$query_string;
+          proxy_cache_valid 200 10m;
+          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
+          proxy_cache_lock on;
+          add_header X-Cache-Status $upstream_cache_status;
+        '';
+      };
+
+      "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        extraConfig = ''
+          proxy_cache gotosocial_ap_public_responses;
+          proxy_cache_background_update on;
+          proxy_cache_key $scheme://$host$uri;
+          proxy_cache_valid 200 604800s;
+          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
+          proxy_cache_lock on;
+
+          add_header X-Cache-Status $upstream_cache_status;
+        '';
+      };
+
+      "/assets/".extraConfig = ''
+        alias ${config.services.gotosocial.package}/share/web/assets/;
+        autoindex off;
+        expires max;
+        add_header Cache-Control "public, immutable";
+      '';
+
+    };
+  };
+
+}
diff --git a/configurations/nixos/websites/git.ctu.cx.nix b/configurations/nixos/websites/git.ctu.cx.nix
@@ -0,0 +1,251 @@
+{ config, lib, pkgs, ... }:
+
+let
+  stagitFunctions = pkgs.writeShellScript "stagitFunctions" ''
+    export LC_CTYPE="en_US.UTF-8"
+
+    is_public_and_listed() {
+      if [ ! -f "$1/git-daemon-export-ok" ]; then
+        return 1
+      fi
+      return 0
+    }
+
+    is_forced_update() {
+      test "$oldrev" = "0000000000000000000000000000000000000000" && return 1
+      test "$newrev" = "0000000000000000000000000000000000000000" && return 1
+
+      hasrevs="$(${pkgs.git}/bin/git rev-list "$oldrev" "^$newrev" | ${pkgs.gnused}/bin/sed 1q)"
+      if test -n "$hasrevs"; then
+        return 0
+      fi
+      return 1
+    }
+
+    build_stagit_repo() {
+      reponame="$(basename "$1" ".git")"
+      printf "[%s] Generate stagit HTML pages... " "$reponame"
+
+      mkdir -p "/var/lib/gitolite/stagit-cache"
+      mkdir -p "/var/lib/stagit/$reponame"
+
+      cd "/var/lib/stagit/$reponame" || return 1
+
+      # build repo pages
+      ${pkgs.stagit}/bin/stagit -c "/var/lib/gitolite/stagit-cache/$reponame" -n 'ctucx.git' -h 'https://git.ctu.cx/' -s 'git@${config.networking.hostName}.ctu.cx:' "$1"
+
+      # set correct permissions
+      chown git:git -R /var/lib/stagit/$reponame;
+      chmod 755 -R /var/lib/stagit/$reponame;
+
+      echo "done"
+    }
+
+    build_stagit_index() {
+      printf "Generating stagit index... "
+
+      # set assets if not already there
+      ln -sf "${pkgs.stagit}/share/doc/stagit/style.css" "/var/lib/stagit/style.css" 2> /dev/null
+
+      # generate index arguments
+      args="-n 'ctucx.git' -e 'git@ctu.cx'"
+
+      for category in "nix" "etc" "nimlang" "nimlang libraries" "archive"; do
+        args="$args -c '$category'"
+        for repo in "$HOME/repositories/"*.git/; do
+          repo="''${repo%/}"
+          is_public_and_listed "$repo" || continue
+
+          [ "$(${pkgs.gawk}/bin/awk -F '=' '/category/ {print $2}' $repo/config | ${pkgs.gnused}/bin/sed -e 's/^[[:space:]]*//')" = "$category" ] && args="$args $repo"
+        done
+      done
+
+      # build index
+      echo "$args" | xargs ${pkgs.stagit}/bin/stagit-index > /var/lib/stagit/index.html
+
+      # set correct permissions
+      chown git:git /var/lib/stagit/index.html;
+      chmod 755 /var/lib/stagit/index.html;
+
+      echo "done"
+    }
+
+
+    update_stagit_repo() {
+      repo="$(pwd)"
+      reponame="$(basename "$repo" ".git")"
+
+      cd "$repo" || return 1
+      is_public_and_listed "$repo" || return 0
+
+      # if forced update, remove directory and cache file
+      is_forced_update && printf "[%s] Forced update, trigger complete regeneration of stagit-pages... \n" "$reponame" && rm -rf "/var/lib/stagit/$reponame" "/var/lib/gitolite/stagit-cache/$reponame"
+
+      build_stagit_repo "$repo"
+      build_stagit_index
+    }
+
+  '';
+
+  rebuildWebdir = ''
+    source ${stagitFunctions}
+
+    # clear webdir
+    rm -rf /var/lib/stagit/*
+
+    # clear cache
+    rm -rf /var/lib/gitolite/stagit-cache/*
+
+    # generate pages per repo
+    for repo in "$HOME/repositories/"*.git/; do
+      repo="''${repo%/}"
+      is_public_and_listed "$repo" || continue
+
+      build_stagit_repo "$repo"
+    done
+
+    # generate index page
+    build_stagit_index
+  '';
+
+in {
+
+  services.openssh.settings.Macs = [
+    "hmac-sha2-512"
+    "hmac-sha2-512-etm@openssh.com"
+    "hmac-sha2-256-etm@openssh.com"
+    "umac-128-etm@openssh.com"
+  ];
+
+  dns.zones."ctu.cx".subdomains = {
+    cgit.CNAME = [ "${config.networking.fqdn}." ];
+    git.CNAME  = [ "${config.networking.fqdn}." ];
+  };
+
+  age.secrets.restic-gitolite.file = ./. + "/../../../secrets/${config.networking.hostName}/restic/gitolite.age";
+
+  restic-backups.gitolite = {
+    user         = "git";
+    passwordFile = config.age.secrets.restic-gitolite.path;
+    paths        = [ "/var/lib/gitolite" ];
+  };
+
+  services = {
+    gitolite = {
+      enable      = true;
+      user        = "git";
+      group       = "git";
+      adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDb2eZ2ymt+Zsf0eTlmjW2jPdS013lbde1+EGkgu6bz9lVTR8aawshF2HcoaWp5a5dJr3SKyihDM8hbWSYB3qyTHihNGyCArqSvAtZRw301ailRVHGqiwUITTfcg1533TtmWvlJZgOIFM1VvSAfdueDRRRzbygmn749fS9nhUTDzLtjqX5LvhpqhzsD+eOqPrV6Ne8E1e42JxQb5AJPY1gj9mk6eAarvtEHQYEe+/hp9ERjtCdN5DfuOJnqfaKS0ytPj/NbQskbX/TMgeUVio11iC2NbXsnAtzMmtbLX4mxlDQrR6aZmU/rHQ4aeJqI/Tj2rrF46icri7s0tnnit1OjT5PSxXgifcOtn06qoxYZMT1x+Dyrt40vNkGmxmxCnirm8B+6MKXgd/Ys+7tnOm1ht8TmLm96x6KdOiF3Zq/tMxhPAzp8JriTKSo7k7U9XxStFghTbhhBNc7OX89ZbpalLEnvbQiz87gZxhcx8cLvzIjslOHmZOSWC5Pgr4wwuj3Akq63i4ya6/BzM6v4UoBuDAB6fz3NHKL4R5X20la7Pvt7OBysQkGClWfj6ipMR1bFE2mfYtlMioXNgTjC+NCpEl1+81MH7dv2565Hk8CLV8FMxv6GujbAZGjjcM47lpWM1cBQvpBMUA/lLkyiCPK0YxNWAB7Co+jYDl6CR0Ubew== cardno:6445161";
+
+      extraGitoliteRc = ''
+        $RC{GIT_CONFIG_KEYS} = ".*";
+        $RC{UMASK}           = 0027;
+
+        push(@{$RC{ENABLE}}, 'cgit');
+        push(@{$RC{ENABLE}}, 'symbolic-ref');
+        push(@{$RC{ENABLE}}, 'rebuild-webdir');
+        push(@{$RC{ENABLE}}, 'rebuild-webdir');
+
+        $RC{NON_CORE} = "rebuild-webdir-trigger POST_COMPILE rebuild-stagit";
+      '';
+
+      triggers.rebuild-webdir = rebuildWebdir;
+      commands.rebuild-webdir = rebuildWebdir;
+      commonHooks.post-receive = ''
+        # update stagit pages
+        source ${stagitFunctions}
+        update_stagit_repo "$1"
+      '';
+    };
+
+    fcgiwrap = {
+      instances.git = {
+        process.user   = "git";
+        process.group  = "git";
+        socket.user    = "nginx";
+        socket.group   = "nginx";
+      };
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts = {
+        "cgit.ctu.cx" = {
+          useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+          forceSSL    = true;
+          kTLS        = true;
+          locations = {
+            "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".return = "307 https://git.ctu.cx$request_uri";
+            "~ '^/([a-zA-Z0-9_.]+)/*$'".return                                      = "307 https://git.ctu.cx/$1";
+            "~ '^/([a-zA-Z0-9_.]+)/tree/([a-zA-Z0-9_./-]+[a-zA-Z0-9_-])/*$'".return = "307 https://git.ctu.cx/$1/tree/$2.html";
+            "~ '^/([a-zA-Z0-9_.]+)/tree/*$'".return                                 = "307 https://git.ctu.cx/$1/tree.html";
+            "~ '^/([a-zA-Z0-9_.]+)/log/*$'".return                                  = "307 https://git.ctu.cx/$1/log.html";
+            "~ '^/([a-zA-Z0-9_.]+)/commit/*$'".extraConfig = ''
+              if ($arg_id) {
+                return 307 https://git.ctu.cx/$1/commit/$arg_id.html;
+              }
+
+              return 307 https://git.ctu.cx/$1/log.html;
+            '';
+          };
+        };
+
+        "git.ctu.cx" = {
+          useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+          forceSSL    = true;
+          kTLS        = true;
+          root        = "/var/lib/stagit";
+          locations = {
+            "@redir".return = "307 ../log.html";
+            "~ '^/([a-zA-Z0-9_.]+)/commit/.*$'".extraConfig = "error_page 404 = @redir;";
+
+            "~* \.html$".extraConfig = ''
+              add_header Last-Modified $date_gmt;
+              add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
+              if_modified_since off;
+              expires off;
+              etag off;
+            '';
+
+            "~ '^/[a-zA-Z0-9._-]+/raw'".extraConfig = ''
+              types {
+                application/json                                 json;
+
+                application/wasm                                 wasm;
+                font/woff                                        woff;
+                font/woff2                                       woff2;
+
+                application/pdf                                  pdf;
+
+                image/gif                                        gif;
+                image/jpeg                                       jpeg jpg;
+                image/png                                        png;
+                image/svg+xml                                    svg svgz;
+                image/webp                                       webp;
+                image/x-icon                                     ico;
+              }
+
+              default_type   text/plain;
+              try_files $uri =404;
+            '';
+
+            "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".extraConfig = ''
+              if ($query_string = service=git-receive-pack) {
+                return 403;
+              }
+
+              include "${pkgs.nginx}/conf/fastcgi_params";
+              fastcgi_param SCRIPT_FILENAME  "${pkgs.git}/libexec/git-core/git-http-backend";
+              fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories;
+              fastcgi_param PATH_INFO        $uri;
+              fastcgi_pass  unix:${config.services.fcgiwrap.instances.git.socket.address};
+            '';
+          };
+        };
+
+      };
+    };
+
+  };
+
+}
diff --git a/machines/hector/grocy.nix b/configurations/nixos/websites/grocy.ctu.cx.nix
diff --git a/machines/hector/prometheus.nix b/configurations/nixos/websites/prometheus.ctu.cx.nix
diff --git a/configurations/nixos/websites/vault.ctu.cx.nix b/configurations/nixos/websites/vault.ctu.cx.nix
@@ -0,0 +1,62 @@
+{ pkgs, config, ... }:
+
+{
+
+  dns.zones."ctu.cx".subdomains.vault.CNAME = [ "${config.networking.fqdn}." ];
+
+  age.secrets = {
+    restic-vaultwarden.file = ./. + "/../../../secrets/${config.networking.hostName}/restic/vaultwarden.age";
+    vaultwarden-secrets = {
+      file  = ./. + "/../../../secrets/${config.networking.hostName}/vaultwarden-secrets.age";
+      owner = "vaultwarden";
+      group = "vaultwarden";
+    };
+  };
+
+  restic-backups.vaultwarden = {
+    user         = "vaultwarden";
+    passwordFile = config.age.secrets.restic-vaultwarden.path;
+    paths        = [ "/var/lib/vaultwarden" "/var/backups/vaultwarden"];
+  };
+
+  systemd.services.vaultwarden.onFailure = [ "email-notify@%i.service" ];
+
+  services = {
+    vaultwarden = {
+      enable          = true;
+      dbBackend       = "sqlite";
+      backupDir       = "/var/backups/vaultwarden";
+      environmentFile = config.age.secrets.vaultwarden-secrets.path;
+      config          = {
+        DOMAIN          = "https://vault.ctu.cx";
+        SIGNUPS_ALLOWED = false;
+
+        PUSH_ENABLED = true;
+
+        SMTP_HOST     = "hector.ctu.cx";
+        SMTP_FROM     = "vaultwarden@ctu.cx";
+        SMTP_USERNAME = "vaultwarden@ctu.cx";
+        SMTP_PORT     = 587;
+        SMTP_SECURITY = "starttls";
+
+        ROCKET_ADDRESS = "::1";
+        ROCKET_PORT = 8582;
+      };
+    };
+
+    nginx = {
+      enable = true;
+      virtualHosts."vault.ctu.cx" = {
+        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+        forceSSL    = true;
+        kTLS        = true;
+        locations."/".proxyPass = "http://[::1]:${toString config.services.vaultwarden.config.ROCKET_PORT}/";
+        locations."/notifications/hub" = {
+          proxyPass = "http://[::1]:${toString config.services.vaultwarden.config.ROCKET_PORT}/";
+          proxyWebsockets = true;
+        };
+      };
+    };
+  };
+
+}+
\ No newline at end of file
diff --git a/configurations/nixos/websites/zuggeschmack.de.nix b/configurations/nixos/websites/zuggeschmack.de.nix
@@ -0,0 +1,139 @@
+{ pkgs, lib, config, ... }:
+
+{
+
+  dns.zones."zuggeschmack.de" = (pkgs.dns.lib.combinators.host config.networking.primaryIP4 config.networking.primaryIP) // {
+    subdomains."client".CNAME = [ "${config.networking.fqdn}." ];
+  };
+
+  age.secrets = {
+    restic-gotosocial.file = ./. + "/../../../secrets/${config.networking.hostName}/restic/gotosocial.age";
+    gotosocial-env.file    = ./. + "/../../../secrets/${config.networking.hostName}/gotosocial-env.age";
+  };
+
+  systemd.services.restic-backup-gotosocial.serviceConfig.ReadWritePaths = [ "/var/lib/gotosocial" ];
+
+  restic-backups.gotosocial = {
+    user            = "gotosocial";
+    passwordFile    = config.age.secrets.restic-gotosocial.path;
+    sqliteDatabases = [ "/var/lib/gotosocial/db.sqlite" ];
+    paths           = [ "/var/lib/gotosocial/storage" "/var/lib/gotosocial/backup.json" ];
+    runBeforeBackup = ''
+      ${pkgs.gotosocial}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path /var/lib/gotosocial/backup.json
+    '';
+  };
+
+  systemd.services.gotosocial.serviceConfig.Group = lib.mkForce config.services.nginx.group;
+
+  services.gotosocial = {
+    enable          = true;
+    group           = "nginx";
+    environmentFile = config.age.secrets.gotosocial-env.path;
+    settings        = {
+      application-name = "ZugGeschmack.de";
+
+      host             = "zuggeschmack.de";
+      account-domain   = "zuggeschmack.de";
+      protocol         = "https";
+
+      bind-address     = "[::1]";
+      port             = 8085;
+
+      trusted-proxies  = [ "::1/128" "172.17.0.0/24" ];
+
+      db-type          = "sqlite";
+      db-address       = "/var/lib/gotosocial/db.sqlite";
+
+      accounts-allow-custom-css  = true;
+      accounts-registration-open = true;
+
+      instance-expose-peers         = true;
+      instance-expose-suspended     = true;
+      instance-expose-suspended-web = true;
+
+      instance-languages            = [ "de" "en-us" ];
+
+      storage-backend            = "local";
+      storage-local-base-path    = "/var/lib/gotosocial/storage";
+
+      media-local-max-size       = "50MiB";
+      media-remote-max-size      = "50MiB";
+
+      media-remote-cache-days    = 3;
+      media-cleanup-from         = "01:00";
+
+      smtp-host     = "hector.ctu.cx";
+      smtp-port     = 587;
+      smtp-username = "gts@zuggeschmack.de";
+      smtp-from     = "gts@zuggeschmack.de";
+    };
+  };
+
+  services.nginx.appendHttpConfig = ''
+    proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w;
+  '';
+
+  services.nginx.virtualHosts."zuggeschmack.de" = {
+    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+    forceSSL    = true;
+    kTLS        = true;
+    extraConfig = ''
+      client_max_body_size 50M;
+    '';
+    locations = {
+      "/" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        proxyWebsockets = true;
+        extraConfig = ''
+          client_max_body_size 50M;
+        '';
+      };
+
+      "~ /.well-known/(webfinger|host-meta)$" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        extraConfig = ''
+          proxy_cache gotosocial_ap_public_responses;
+          proxy_cache_background_update on;
+          proxy_cache_key $scheme://$host$uri$is_args$query_string;
+          proxy_cache_valid 200 10m;
+          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
+          proxy_cache_lock on;
+          add_header X-Cache-Status $upstream_cache_status;
+        '';
+      };
+
+      "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = {
+        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
+        extraConfig = ''
+          proxy_cache gotosocial_ap_public_responses;
+          proxy_cache_background_update on;
+          proxy_cache_key $scheme://$host$uri;
+          proxy_cache_valid 200 604800s;
+          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
+          proxy_cache_lock on;
+
+          add_header X-Cache-Status $upstream_cache_status;
+        '';
+      };
+
+      "/assets/".extraConfig = ''
+        alias ${config.services.gotosocial.package}/share/web/assets/;
+        autoindex off;
+        expires max;
+        add_header Cache-Control "public, immutable";
+      '';
+    };
+
+  };
+
+  services.nginx.virtualHosts."client.zuggeschmack.de" = {
+    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
+    forceSSL    = true;
+    kTLS        = true;
+    root        = pkgs.masto-fe-standalone;
+    extraConfig = ''
+      try_files $uri $uri/ /index.html;
+    '';
+  };
+
+}
diff --git a/machines/briefkasten/default.nix b/machines/briefkasten/default.nix
@@ -14,13 +14,11 @@
 
     ctucxConfig.websites."music.home.ctu.cx"
     ctucxConfig.websites."audiobooks.home.ctu.cx"
+    ctucxConfig.websites."fedi.home.ctu.cx"
 
     # syncthing (and it's backup)
     ./syncthing.nix
 
-    # fedi server
-    ./gotosocial.nix
-
     ./smarthome
 
     ./scanner-sftp.nix
diff --git a/machines/briefkasten/gotosocial.nix b/machines/briefkasten/gotosocial.nix
@@ -1,118 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-let
-  gotosocial = pkgs.callPackage ../../pkgs/gotosocial {};
-
-in {
-
-  dns.zones."ctu.cx".subdomains."fedi.home".AAAA = [ config.networking.primaryIP ];
-
-  age.secrets.restic-gotosocial.file = ./. + "/../../secrets/${config.networking.hostName}/restic/gotosocial.age";
-
-  systemd.services.restic-backup-gotosocial.serviceConfig.ReadWritePaths = [ "/var/lib/gotosocial" ];
-
-  restic-backups.gotosocial = {
-    user            = "gotosocial";
-    passwordFile    = config.age.secrets.restic-gotosocial.path;
-    sqliteDatabases = [ "/var/lib/gotosocial/db.sqlite" ];
-    paths           = [ "/var/lib/gotosocial/storage" "/var/lib/gotosocial/backup.json" ];
-    runBeforeBackup = ''
-      ${gotosocial}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path /var/lib/gotosocial/backup.json
-    '';
-  };
-
-  systemd.services.gotosocial.serviceConfig = {
-    Group = lib.mkForce config.services.nginx.group;
-  };
-
-  services.gotosocial = {
-    enable   = true;
-    package  = gotosocial;
-    group    = "nginx";
-    settings = {
-      application-name = "ctucx.gts";
-
-      host             = "fedi.home.ctu.cx";
-      account-domain   = "fedi.home.ctu.cx";
-      protocol         = "https";
-
-      bind-address     = "[::1]";
-      port             = 8085;
-
-      trusted-proxies  = [ "::1/128" "172.17.0.0/24" ];
-
-      db-type          = "sqlite";
-      db-address       = "/var/lib/gotosocial/db.sqlite";
-
-      accounts-allow-custom-css  = true;
-      accounts-registration-open = false;
-
-      instance-expose-peers         = true;
-      instance-expose-suspended     = true;
-      instance-expose-suspended-web = true;
-
-      instance-languages            = [ "de" "en-us" ];
-
-      storage-backend            = "local";
-      storage-local-base-path    = "/var/lib/gotosocial/storage";
-
-      media-remote-max-size      = 0;
-      media-remote-cache-days    = 3;
-      media-cleanup-from         = "02:00";
-    };
-  };
-
-  services.nginx.appendHttpConfig = ''
-    proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w;
-  '';
-
-  services.nginx.virtualHosts."fedi.home.ctu.cx" = {
-    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-    forceSSL    = true;
-    kTLS        = true;
-    locations   = {
-      "= /".return = "307 /@leah";
-
-      "/" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        proxyWebsockets = true;
-      };
-
-      "~ /.well-known/(webfinger|host-meta)$" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        extraConfig = ''
-          proxy_cache gotosocial_ap_public_responses;
-          proxy_cache_background_update on;
-          proxy_cache_key $scheme://$host$uri$is_args$query_string;
-          proxy_cache_valid 200 10m;
-          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
-          proxy_cache_lock on;
-          add_header X-Cache-Status $upstream_cache_status;
-        '';
-      };
-
-      "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        extraConfig = ''
-          proxy_cache gotosocial_ap_public_responses;
-          proxy_cache_background_update on;
-          proxy_cache_key $scheme://$host$uri;
-          proxy_cache_valid 200 604800s;
-          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
-          proxy_cache_lock on;
-
-          add_header X-Cache-Status $upstream_cache_status;
-        '';
-      };
-
-      "/assets/".extraConfig = ''
-        alias ${config.services.gotosocial.package}/share/web/assets/;
-        autoindex off;
-        expires max;
-        add_header Cache-Control "public, immutable";
-      '';
-
-    };
-  };
-
-}
diff --git a/machines/hector/default.nix b/machines/hector/default.nix
@@ -12,28 +12,28 @@
     ctucxConfig.websites."things.ctu.cx"
     ctucxConfig.websites."bikemap.ctu.cx"
     ctucxConfig.websites."photos.ctu.cx"
+    ctucxConfig.websites."grocy.ctu.cx"
 
     # monitoring
-    ./prometheus.nix
+    ctucxConfig.websites."prometheus.ctu.cx"
     ./grafana
 
-    # cal- and card-dav server
-    ./radicale.nix
+    # cal-/card-dav server (radicale)
+    ctucxConfig.websites."dav.ctu.cx"
 
     # vaultwarden password-store
-    ./vaultwarden.nix
+    ctucxConfig.websites."vault.ctu.cx"
 
     # git server (gitolite+stagit)
-    ./git.nix
+    ctucxConfig.websites."git.ctu.cx"
 
-    # communication
-    ./fedi
+    # fediverse server (gotosocial)
+    ctucxConfig.websites."fedi.ctu.cx"
     ./matrix
     ./mail
 
     ./syncthing.nix
 
-    ./grocy.nix
   ];
 
   dns.zones."ctu.cx".subdomains."${config.networking.hostName}" = (pkgs.dns.lib.combinators.host config.networking.primaryIP4 config.networking.primaryIP);
diff --git a/machines/hector/fedi/default.nix b/machines/hector/fedi/default.nix
@@ -1,9 +0,0 @@
-{ ... }:
-
-{
-
-  imports = [
-    ./gotosocial.nix
-  ];
-
-}-
\ No newline at end of file
diff --git a/machines/hector/git.nix b/machines/hector/git.nix
@@ -1,251 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  stagitFunctions = pkgs.writeShellScript "stagitFunctions" ''
-    export LC_CTYPE="en_US.UTF-8"
-
-    is_public_and_listed() {
-      if [ ! -f "$1/git-daemon-export-ok" ]; then
-        return 1
-      fi
-      return 0
-    }
-
-    is_forced_update() {
-      test "$oldrev" = "0000000000000000000000000000000000000000" && return 1
-      test "$newrev" = "0000000000000000000000000000000000000000" && return 1
-
-      hasrevs="$(${pkgs.git}/bin/git rev-list "$oldrev" "^$newrev" | ${pkgs.gnused}/bin/sed 1q)"
-      if test -n "$hasrevs"; then
-        return 0
-      fi
-      return 1
-    }
-
-    build_stagit_repo() {
-      reponame="$(basename "$1" ".git")"
-      printf "[%s] Generate stagit HTML pages... " "$reponame"
-
-      mkdir -p "/var/lib/gitolite/stagit-cache"
-      mkdir -p "/var/lib/stagit/$reponame"
-
-      cd "/var/lib/stagit/$reponame" || return 1
-
-      # build repo pages
-      ${pkgs.stagit}/bin/stagit -c "/var/lib/gitolite/stagit-cache/$reponame" -n 'ctucx.git' -h 'https://git.ctu.cx/' -s 'git@${config.networking.hostName}.ctu.cx:' "$1"
-
-      # set correct permissions
-      chown git:git -R /var/lib/stagit/$reponame;
-      chmod 755 -R /var/lib/stagit/$reponame;
-
-      echo "done"
-    }
-
-    build_stagit_index() {
-      printf "Generating stagit index... "
-
-      # set assets if not already there
-      ln -sf "${pkgs.stagit}/share/doc/stagit/style.css" "/var/lib/stagit/style.css" 2> /dev/null
-
-      # generate index arguments
-      args="-n 'ctucx.git' -e 'git@ctu.cx'"
-
-      for category in "nix" "etc" "nimlang" "nimlang libraries" "archive"; do
-        args="$args -c '$category'"
-        for repo in "$HOME/repositories/"*.git/; do
-          repo="''${repo%/}"
-          is_public_and_listed "$repo" || continue
-
-          [ "$(${pkgs.gawk}/bin/awk -F '=' '/category/ {print $2}' $repo/config | ${pkgs.gnused}/bin/sed -e 's/^[[:space:]]*//')" = "$category" ] && args="$args $repo"
-        done
-      done
-
-      # build index
-      echo "$args" | xargs ${pkgs.stagit}/bin/stagit-index > /var/lib/stagit/index.html
-
-      # set correct permissions
-      chown git:git /var/lib/stagit/index.html;
-      chmod 755 /var/lib/stagit/index.html;
-
-      echo "done"
-    }
-
-
-    update_stagit_repo() {
-      repo="$(pwd)"
-      reponame="$(basename "$repo" ".git")"
-
-      cd "$repo" || return 1
-      is_public_and_listed "$repo" || return 0
-
-      # if forced update, remove directory and cache file
-      is_forced_update && printf "[%s] Forced update, trigger complete regeneration of stagit-pages... \n" "$reponame" && rm -rf "/var/lib/stagit/$reponame" "/var/lib/gitolite/stagit-cache/$reponame"
-
-      build_stagit_repo "$repo"
-      build_stagit_index
-    }
-
-  '';
-
-  rebuildWebdir = ''
-    source ${stagitFunctions}
-
-    # clear webdir
-    rm -rf /var/lib/stagit/*
-
-    # clear cache
-    rm -rf /var/lib/gitolite/stagit-cache/*
-
-    # generate pages per repo
-    for repo in "$HOME/repositories/"*.git/; do
-      repo="''${repo%/}"
-      is_public_and_listed "$repo" || continue
-
-      build_stagit_repo "$repo"
-    done
-
-    # generate index page
-    build_stagit_index
-  '';
-
-in {
-
-  services.openssh.settings.Macs = [
-    "hmac-sha2-512"
-    "hmac-sha2-512-etm@openssh.com"
-    "hmac-sha2-256-etm@openssh.com"
-    "umac-128-etm@openssh.com"
-  ];
-
-  dns.zones."ctu.cx".subdomains = {
-    cgit.CNAME = [ "${config.networking.fqdn}." ];
-    git.CNAME  = [ "${config.networking.fqdn}." ];
-  };
-
-  age.secrets.restic-gitolite.file = ./. + "/../../secrets/${config.networking.hostName}/restic/gitolite.age";
-
-  restic-backups.gitolite = {
-    user         = "git";
-    passwordFile = config.age.secrets.restic-gitolite.path;
-    paths        = [ "/var/lib/gitolite" ];
-  };
-
-  services = {
-    gitolite = {
-      enable      = true;
-      user        = "git";
-      group       = "git";
-      adminPubkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDb2eZ2ymt+Zsf0eTlmjW2jPdS013lbde1+EGkgu6bz9lVTR8aawshF2HcoaWp5a5dJr3SKyihDM8hbWSYB3qyTHihNGyCArqSvAtZRw301ailRVHGqiwUITTfcg1533TtmWvlJZgOIFM1VvSAfdueDRRRzbygmn749fS9nhUTDzLtjqX5LvhpqhzsD+eOqPrV6Ne8E1e42JxQb5AJPY1gj9mk6eAarvtEHQYEe+/hp9ERjtCdN5DfuOJnqfaKS0ytPj/NbQskbX/TMgeUVio11iC2NbXsnAtzMmtbLX4mxlDQrR6aZmU/rHQ4aeJqI/Tj2rrF46icri7s0tnnit1OjT5PSxXgifcOtn06qoxYZMT1x+Dyrt40vNkGmxmxCnirm8B+6MKXgd/Ys+7tnOm1ht8TmLm96x6KdOiF3Zq/tMxhPAzp8JriTKSo7k7U9XxStFghTbhhBNc7OX89ZbpalLEnvbQiz87gZxhcx8cLvzIjslOHmZOSWC5Pgr4wwuj3Akq63i4ya6/BzM6v4UoBuDAB6fz3NHKL4R5X20la7Pvt7OBysQkGClWfj6ipMR1bFE2mfYtlMioXNgTjC+NCpEl1+81MH7dv2565Hk8CLV8FMxv6GujbAZGjjcM47lpWM1cBQvpBMUA/lLkyiCPK0YxNWAB7Co+jYDl6CR0Ubew== cardno:6445161";
-
-      extraGitoliteRc = ''
-        $RC{GIT_CONFIG_KEYS} = ".*";
-        $RC{UMASK}           = 0027;
-
-        push(@{$RC{ENABLE}}, 'cgit');
-        push(@{$RC{ENABLE}}, 'symbolic-ref');
-        push(@{$RC{ENABLE}}, 'rebuild-webdir');
-        push(@{$RC{ENABLE}}, 'rebuild-webdir');
-
-        $RC{NON_CORE} = "rebuild-webdir-trigger POST_COMPILE rebuild-stagit";
-      '';
-
-      triggers.rebuild-webdir = rebuildWebdir;
-      commands.rebuild-webdir = rebuildWebdir;
-      commonHooks.post-receive = ''
-        # update stagit pages
-        source ${stagitFunctions}
-        update_stagit_repo "$1"
-      '';
-    };
-
-    fcgiwrap = {
-      instances.git = {
-        process.user   = "git";
-        process.group  = "git";
-        socket.user    = "nginx";
-        socket.group   = "nginx";
-      };
-    };
-
-    nginx = {
-      enable = true;
-      virtualHosts = {
-        "cgit.ctu.cx" = {
-          useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-          forceSSL    = true;
-          kTLS        = true;
-          locations = {
-            "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".return = "307 https://git.ctu.cx$request_uri";
-            "~ '^/([a-zA-Z0-9_.]+)/*$'".return                                      = "307 https://git.ctu.cx/$1";
-            "~ '^/([a-zA-Z0-9_.]+)/tree/([a-zA-Z0-9_./-]+[a-zA-Z0-9_-])/*$'".return = "307 https://git.ctu.cx/$1/tree/$2.html";
-            "~ '^/([a-zA-Z0-9_.]+)/tree/*$'".return                                 = "307 https://git.ctu.cx/$1/tree.html";
-            "~ '^/([a-zA-Z0-9_.]+)/log/*$'".return                                  = "307 https://git.ctu.cx/$1/log.html";
-            "~ '^/([a-zA-Z0-9_.]+)/commit/*$'".extraConfig = ''
-              if ($arg_id) {
-                return 307 https://git.ctu.cx/$1/commit/$arg_id.html;
-              }
-
-              return 307 https://git.ctu.cx/$1/log.html;
-            '';
-          };
-        };
-
-        "git.ctu.cx" = {
-          useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-          forceSSL    = true;
-          kTLS        = true;
-          root        = "/var/lib/stagit";
-          locations = {
-            "@redir".return = "307 ../log.html";
-            "~ '^/([a-zA-Z0-9_.]+)/commit/.*$'".extraConfig = "error_page 404 = @redir;";
-
-            "~* \.html$".extraConfig = ''
-              add_header Last-Modified $date_gmt;
-              add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
-              if_modified_since off;
-              expires off;
-              etag off;
-            '';
-
-            "~ '^/[a-zA-Z0-9._-]+/raw'".extraConfig = ''
-              types {
-                application/json                                 json;
-
-                application/wasm                                 wasm;
-                font/woff                                        woff;
-                font/woff2                                       woff2;
-
-                application/pdf                                  pdf;
-
-                image/gif                                        gif;
-                image/jpeg                                       jpeg jpg;
-                image/png                                        png;
-                image/svg+xml                                    svg svgz;
-                image/webp                                       webp;
-                image/x-icon                                     ico;
-              }
-
-              default_type   text/plain;
-              try_files $uri =404;
-            '';
-
-            "~ '^/[a-zA-Z0-9._-]+/(git-(receive|upload)-pack|HEAD|info/refs|objects/(info/(http-)?alternates|packs)|[0-9a-f]{2}/[0-9a-f]{38}|pack/pack-[0-9a-f]{40}\.(pack|idx))$'".extraConfig = ''
-              if ($query_string = service=git-receive-pack) {
-                return 403;
-              }
-
-              include "${pkgs.nginx}/conf/fastcgi_params";
-              fastcgi_param SCRIPT_FILENAME  "${pkgs.git}/libexec/git-core/git-http-backend";
-              fastcgi_param GIT_PROJECT_ROOT /var/lib/gitolite/repositories;
-              fastcgi_param PATH_INFO        $uri;
-              fastcgi_pass  unix:${config.services.fcgiwrap.instances.git.socket.address};
-            '';
-          };
-        };
-
-      };
-    };
-
-  };
-
-}
diff --git a/machines/hector/radicale.nix b/machines/hector/radicale.nix
@@ -1,48 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-
-  dns.zones."ctu.cx".subdomains.dav.CNAME = [ "${config.networking.fqdn}." ];
-
-  age.secrets = {
-    restic-radicale.file = ./. + "/../../secrets/${config.networking.hostName}/restic/radicale.age";
-    radicale-users = {
-      file  = ./. + "/../../secrets/${config.networking.hostName}/radicale-users.age";
-      owner = "radicale";
-    };
-  };
-
-  restic-backups.radicale = {
-    user         = "radicale";
-    passwordFile = config.age.secrets.restic-radicale.path;
-    paths        = [ "/var/lib/radicale" ];
-  };
-
-  systemd.services.radicale.onFailure = [ "email-notify@%i.service" ];
-
-  services = {
-    radicale = {
-      enable = true;
-      settings = {
-        server.hosts                        = [ "[::1]:5232" ];
-        web.type                            = "internal";
-        storage.filesystem_folder           = "/var/lib/radicale/collections";
-        headers.Access-Control-Allow-Origin = "*";
-        auth.type                           = "htpasswd";
-        auth.htpasswd_filename              = config.age.secrets.radicale-users.path;
-        auth.htpasswd_encryption            = "plain";
-      };
-    };
-
-    nginx = {
-      enable = true;
-      virtualHosts."dav.ctu.cx" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        kTLS        = true;
-        locations."/".proxyPass = "http://[::1]:5232/";
-      };
-    };
-  };
-
-}
diff --git a/machines/hector/vaultwarden.nix b/machines/hector/vaultwarden.nix
@@ -1,62 +0,0 @@
-{ pkgs, config, ... }:
-
-{
-
-  dns.zones."ctu.cx".subdomains.vault.CNAME = [ "${config.networking.fqdn}." ];
-
-  age.secrets = {
-    restic-vaultwarden.file = ./. + "/../../secrets/${config.networking.hostName}/restic/vaultwarden.age";
-    vaultwarden-secrets = {
-      file  = ./. + "/../../secrets/${config.networking.hostName}/vaultwarden-secrets.age";
-      owner = "vaultwarden";
-      group = "vaultwarden";
-    };
-  };
-
-  restic-backups.vaultwarden = {
-    user         = "vaultwarden";
-    passwordFile = config.age.secrets.restic-vaultwarden.path;
-    paths        = [ "/var/lib/vaultwarden" "/var/backups/vaultwarden"];
-  };
-
-  systemd.services.vaultwarden.onFailure = [ "email-notify@%i.service" ];
-
-  services = {
-    vaultwarden = {
-      enable          = true;
-      dbBackend       = "sqlite";
-      backupDir       = "/var/backups/vaultwarden";
-      environmentFile = config.age.secrets.vaultwarden-secrets.path;
-      config          = {
-        DOMAIN          = "https://vault.ctu.cx";
-        SIGNUPS_ALLOWED = false;
-
-        PUSH_ENABLED = true;
-
-        SMTP_HOST     = "hector.ctu.cx";
-        SMTP_FROM     = "vaultwarden@ctu.cx";
-        SMTP_USERNAME = "vaultwarden@ctu.cx";
-        SMTP_PORT     = 587;
-        SMTP_SECURITY = "starttls";
-
-        ROCKET_ADDRESS = "::1";
-        ROCKET_PORT = 8582;
-      };
-    };
-
-    nginx = {
-      enable = true;
-      virtualHosts."vault.ctu.cx" = {
-        useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-        forceSSL    = true;
-        kTLS        = true;
-        locations."/".proxyPass = "http://[::1]:${toString config.services.vaultwarden.config.ROCKET_PORT}/";
-        locations."/notifications/hub" = {
-          proxyPass = "http://[::1]:${toString config.services.vaultwarden.config.ROCKET_PORT}/";
-          proxyWebsockets = true;
-        };
-      };
-    };
-  };
-
-}-
\ No newline at end of file
diff --git a/machines/trabbi/default.nix b/machines/trabbi/default.nix
@@ -8,7 +8,7 @@
     ctucxConfig.services.prometheus-exporters
     ctucxConfig.services.dns-server
 
-    ./gotosocial.nix
+    ctucxConfig.websites."zuggeschmack.de"
   ];
 
   dns.zones."ctu.cx".subdomains."${config.networking.hostName}" = (pkgs.dns.lib.combinators.host config.networking.primaryIP4 config.networking.primaryIP);
diff --git a/machines/trabbi/gotosocial.nix b/machines/trabbi/gotosocial.nix
@@ -1,144 +0,0 @@
-{ pkgs, lib, config, ... }:
-
-let
-  gotosocial = pkgs.callPackage ../../pkgs/gotosocial {};
-
-in {
-
-  dns.zones."zuggeschmack.de" = (pkgs.dns.lib.combinators.host config.networking.primaryIP4 config.networking.primaryIP) // {
-    subdomains."client".CNAME = [ "${config.networking.fqdn}." ];
-  };
-
-  age.secrets = {
-    restic-gotosocial.file = ./. + "/../../secrets/${config.networking.hostName}/restic/gotosocial.age";
-    gotosocial-env.file    = ./. + "/../../secrets/${config.networking.hostName}/gotosocial-env.age";
-  };
-
-  systemd.services.restic-backup-gotosocial.serviceConfig.ReadWritePaths = [ "/var/lib/gotosocial" ];
-
-  restic-backups.gotosocial = {
-    user            = "gotosocial";
-    passwordFile    = config.age.secrets.restic-gotosocial.path;
-    sqliteDatabases = [ "/var/lib/gotosocial/db.sqlite" ];
-    paths           = [ "/var/lib/gotosocial/storage" "/var/lib/gotosocial/backup.json" ];
-    runBeforeBackup = ''
-      ${gotosocial}/bin/gotosocial --config-path /etc/gotosocial.yaml admin export --path /var/lib/gotosocial/backup.json
-    '';
-  };
-
-  systemd.services.gotosocial.serviceConfig.Group = lib.mkForce config.services.nginx.group;
-
-  services.gotosocial = {
-    enable          = true;
-    package         = gotosocial;
-    group           = "nginx";
-    environmentFile = config.age.secrets.gotosocial-env.path;
-    settings        = {
-      application-name = "ZugGeschmack.de";
-
-      host             = "zuggeschmack.de";
-      account-domain   = "zuggeschmack.de";
-      protocol         = "https";
-
-      bind-address     = "[::1]";
-      port             = 8085;
-
-      trusted-proxies  = [ "::1/128" "172.17.0.0/24" ];
-
-      db-type          = "sqlite";
-      db-address       = "/var/lib/gotosocial/db.sqlite";
-
-      accounts-allow-custom-css  = true;
-      accounts-registration-open = true;
-
-      instance-expose-peers         = true;
-      instance-expose-suspended     = true;
-      instance-expose-suspended-web = true;
-
-      instance-languages            = [ "de" "en-us" ];
-
-      storage-backend            = "local";
-      storage-local-base-path    = "/var/lib/gotosocial/storage";
-
-      media-local-max-size       = "50MiB";
-      media-remote-max-size      = "50MiB";
-
-      media-remote-cache-days    = 3;
-      media-cleanup-from         = "01:00";
-
-
-      smtp-host     = "hector.ctu.cx";
-      smtp-port     = 587;
-      smtp-username = "gts@zuggeschmack.de";
-      smtp-from     = "gts@zuggeschmack.de";
-    };
-  };
-
-  services.nginx.appendHttpConfig = ''
-    proxy_cache_path /var/cache/nginx keys_zone=gotosocial_ap_public_responses:10m inactive=1w;
-  '';
-
-  services.nginx.virtualHosts."zuggeschmack.de" = {
-    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-    forceSSL    = true;
-    kTLS        = true;
-    extraConfig = ''
-      client_max_body_size 50M;
-    '';
-    locations = {
-      "/" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        proxyWebsockets = true;
-        extraConfig = ''
-          client_max_body_size 50M;
-        '';
-      };
-
-      "~ /.well-known/(webfinger|host-meta)$" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        extraConfig = ''
-          proxy_cache gotosocial_ap_public_responses;
-          proxy_cache_background_update on;
-          proxy_cache_key $scheme://$host$uri$is_args$query_string;
-          proxy_cache_valid 200 10m;
-          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
-          proxy_cache_lock on;
-          add_header X-Cache-Status $upstream_cache_status;
-        '';
-      };
-
-      "~ ^\/users\/(?:[a-z0-9_\.]+)\/main-key$" = {
-        proxyPass   = "http://${toString config.services.gotosocial.settings.bind-address}:${toString config.services.gotosocial.settings.port}";
-        extraConfig = ''
-          proxy_cache gotosocial_ap_public_responses;
-          proxy_cache_background_update on;
-          proxy_cache_key $scheme://$host$uri;
-          proxy_cache_valid 200 604800s;
-          proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_429;
-          proxy_cache_lock on;
-
-          add_header X-Cache-Status $upstream_cache_status;
-        '';
-      };
-
-      "/assets/".extraConfig = ''
-        alias ${config.services.gotosocial.package}/share/web/assets/;
-        autoindex off;
-        expires max;
-        add_header Cache-Control "public, immutable";
-      '';
-    };
-
-  };
-
-  services.nginx.virtualHosts."client.zuggeschmack.de" = {
-    useACMEHost = "${config.networking.hostName}.${config.networking.domain}";
-    forceSSL    = true;
-    kTLS        = true;
-    root        = pkgs.masto-fe-standalone;
-    extraConfig = ''
-      try_files $uri $uri/ /index.html;
-    '';
-  };
-
-}